Skip to content

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

Open
cyphercodes wants to merge 37 commits intosimstudioai:stagingfrom
cyphercodes:fix/telegram-photo-url
Open

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

Conversation

@cyphercodes
Copy link

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.

waleedlatif1 and others added 30 commits February 16, 2026 00:36
…stash, algolia tools; isolated-vm robustness improvements, tables backend (simstudioai#3271)

* feat(tools): advanced fields for youtube, vercel; added cloudflare and dataverse tools (simstudioai#3257)

* refactor(vercel): mark optional fields as advanced mode

Move optional/power-user fields behind the advanced toggle:
- List Deployments: project filter, target, state
- Create Deployment: project ID override, redeploy from, target
- List Projects: search
- Create/Update Project: framework, build/output/install commands
- Env Vars: variable type
- Webhooks: project IDs filter
- Checks: path, details URL
- Team Members: role filter
- All operations: team ID scope

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style(youtube): mark optional params as advanced mode

Hide pagination, sort order, and filter fields behind the advanced
toggle for a cleaner default UX across all YouTube operations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* added advanced fields for vercel and youtube, added cloudflare and dataverse block

* addded desc for dataverse

* add more tools

* ack comment

* more

* ops

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* feat(tables): added tables (simstudioai#2867)

* updates

* required

* trashy table viewer

* updates

* updates

* filtering ui

* updates

* updates

* updates

* one input mode

* format

* fix lints

* improved errors

* updates

* updates

* chages

* doc strings

* breaking down file

* update comments with ai

* updates

* comments

* changes

* revert

* updates

* dedupe

* updates

* updates

* updates

* refactoring

* renames & refactors

* refactoring

* updates

* undo

* update db

* wand

* updates

* fix comments

* fixes

* simplify comments

* u[dates

* renames

* better comments

* validation

* updates

* updates

* updates

* fix sorting

* fix appearnce

* updating prompt to make it user sort

* rm

* updates

* rename

* comments

* clean comments

* simplicifcaiton

* updates

* updates

* refactor

* reduced type confusion

* undo

* rename

* undo changes

* undo

* simplify

* updates

* updates

* revert

* updates

* db updates

* type fix

* fix

* fix error handling

* updates

* docs

* docs

* updates

* rename

* dedupe

* revert

* uncook

* updates

* fix

* fix

* fix

* fix

* prepare merge

* readd migrations

* add back missed code

* migrate enrichment logic to general abstraction

* address bugbot concerns

* adhere to size limits for tables

* remove conflicting migration

* add back migrations

* fix tables auth

* fix permissive auth

* fix lint

* reran migrations

* migrate to use tanstack query for all server state

* update table-selector

* update names

* added tables to permission groups, updated subblock types

---------

Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai>
Co-authored-by: waleed <walif6@gmail.com>

* fix(snapshot): changed insert to upsert when concurrent identical child workflows are running (simstudioai#3259)

* fix(snapshot): changed insert to upsert when concurrent identical child workflows are running

* fixed ci tests failing

* fix(workflows): disallow duplicate workflow names at the same folder level (simstudioai#3260)

* feat(tools): added redis, upstash, algolia, and revenuecat (simstudioai#3261)

* feat(tools): added redis, upstash, algolia, and revenuecat

* ack comment

* feat(models): add gemini-3.1-pro-preview and update gemini-3-pro thinking levels (simstudioai#3263)

* fix(audit-log): lazily resolve actor name/email when missing (simstudioai#3262)

* fix(blocks): move type coercions from tools.config.tool to tools.config.params (simstudioai#3264)

* fix(blocks): move type coercions from tools.config.tool to tools.config.params

Number() coercions in tools.config.tool ran at serialization time before
variable resolution, destroying dynamic references like <block.result.count>
by converting them to NaN/null. Moved all coercions to tools.config.params
which runs at execution time after variables are resolved.

Fixed in 15 blocks: exa, arxiv, sentry, incidentio, wikipedia, ahrefs,
posthog, elasticsearch, dropbox, hunter, lemlist, spotify, youtube, grafana,
parallel. Also added mode: 'advanced' to optional exa fields.

Closes simstudioai#3258

* fix(blocks): address PR review — move remaining param mutations from tool() to params()

- Moved field mappings from tool() to params() in grafana, posthog,
  lemlist, spotify, dropbox (same dynamic reference bug)
- Fixed parallel.ts excerpts/full_content boolean logic
- Fixed parallel.ts search_queries empty case (must set undefined)
- Fixed elasticsearch.ts timeout not included when already ends with 's'
- Restored dropbox.ts tool() switch for proper default fallback

* fix(blocks): restore field renames to tool() for serialization-time validation

Field renames (e.g. personalApiKey→apiKey) must be in tool() because
validateRequiredFieldsBeforeExecution calls selectToolId()→tool() then
checks renamed field names on params. Only type coercions (Number(),
boolean) stay in params() to avoid destroying dynamic variable references.

* improvement(resolver): resovled empty sentinel to not pass through unexecuted valid refs to text inputs (simstudioai#3266)

* fix(blocks): add required constraint for serviceDeskId in JSM block (simstudioai#3268)

* fix(blocks): add required constraint for serviceDeskId in JSM block

* fix(blocks): rename custom field values to request field values in JSM create request

* fix(trigger): add isolated-vm support to trigger.dev container builds (simstudioai#3269)

Scheduled workflow executions running in trigger.dev containers were
failing to spawn isolated-vm workers because the native module wasn't
available in the container. This caused loop condition evaluation to
silently fail and exit after one iteration.

- Add isolated-vm to build.external and additionalPackages in trigger config
- Include isolated-vm-worker.cjs via additionalFiles for child process spawning
- Add fallback path resolution for worker file in trigger.dev environment

* fix(tables): hide tables from sidebar and block registry (simstudioai#3270)

* fix(tables): hide tables from sidebar and block registry

* fix(trigger): add isolated-vm support to trigger.dev container builds (simstudioai#3269)

Scheduled workflow executions running in trigger.dev containers were
failing to spawn isolated-vm workers because the native module wasn't
available in the container. This caused loop condition evaluation to
silently fail and exit after one iteration.

- Add isolated-vm to build.external and additionalPackages in trigger config
- Include isolated-vm-worker.cjs via additionalFiles for child process spawning
- Add fallback path resolution for worker file in trigger.dev environment

* lint

* fix(trigger): update node version to align with main app (simstudioai#3272)

* fix(build): fix corrupted sticky disk cache on blacksmith (simstudioai#3273)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Lakee Sivaraya <71339072+lakeesiv@users.noreply.github.com>
Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai>
Co-authored-by: Vikhyath Mondreti <vikhyathvikku@gmail.com>
… fixes, removed retired models, hex integration
…ogle tasks and bigquery integrations, workflow lock
waleedlatif1 and others added 7 commits March 21, 2026 12:43
v0.6.8: mothership tool loop
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

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

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
Contributor

greptile-apps bot commented Mar 26, 2026

Greptile Summary

This PR fixes a real user-facing bug where plain https:// URLs passed to Telegram's Send Photo/Video/Audio/Animation blocks were rejected with "Photo is required." because normalizeFileInput only accepted JSON-serialised file objects. The fix is minimal — a URL-detection prefix in the string branch — and is covered by a solid new test suite.\n\nTwo concerns worth addressing before merging:\n\n- Untyped string[] return from non-single overload: When a URL is passed with single: false, the function returns [trimmed] (a string[]), but the overload declares object[] | string | undefined. TypeScript doesn't catch this because string[] is structurally assignable to object, but callers iterating the array expecting file objects will silently get undefined for any property access. The overload should be updated to (object | string)[] | string | undefined.\n- Broader blast radius across ~15 other blocks: normalizeFileInput is shared by Box, Confluence, Google Drive, Fireflies, Jira, Linear, and others. Previously a URL string silently returned undefined (causing a validation error); now the URL string is passed through to those upload-oriented handlers, which expect { name, url, size } file objects. This could produce confusing downstream errors. Scoping the URL shortcut behind an opt-in allowUrl?: boolean option would isolate the change to Telegram without risk to other integrations.

Confidence Score: 3/5

The core Telegram fix is correct, but the shared utility change has unintended reach across ~15 other blocks and a type-safety gap worth resolving before merge.

The intended fix works and tests are thorough. However, modifying a broadly-shared utility function without scoping the change introduces a silent behavioural change for upload-oriented blocks, and the string[] vs object[] overload mismatch is a real type-safety regression.

apps/sim/blocks/utils.ts — specifically the non-single overload return type and the opt-in scoping of the URL passthrough.

Important Files Changed

Filename Overview
apps/sim/blocks/utils.ts Adds URL passthrough to normalizeFileInput — fixes Telegram photo/video/audio/animation but silently broadens behaviour for all ~15 other block callers; non-single overload return type also doesn't declare string[].
apps/sim/blocks/utils.test.ts New test file with comprehensive coverage of URL strings (single/non-single, http/https, whitespace), JSON strings, plain objects/arrays, and edge cases (null, undefined, empty string, multiple-file error).

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[normalizeFileInput called] --> B{fileParam falsy?}
    B -- yes --> C[return undefined]
    B -- no --> D{typeof fileParam === 'string'?}
    D -- yes --> E{trimmed starts with http/https?}
    E -- yes --> F{options.single?}
    F -- yes --> G[return trimmed URL string]
    F -- no --> H[return string array]
    E -- no --> I[JSON.parse attempt]
    I -- success --> J[fileParam = parsed value]
    I -- fail --> K[return undefined]
    J --> L{Array.isArray?}
    D -- no --> L
    L -- yes --> M{length > 0?}
    M -- yes --> N[files = fileParam]
    M -- no --> O[return undefined]
    L -- no --> P{is plain object?}
    P -- yes --> Q[files = wrapped in array]
    P -- no --> O
    N --> R{options.single?}
    Q --> R
    R -- yes --> S{files.length > 1?}
    S -- yes --> T[throw error]
    S -- no --> U[return files 0]
    R -- no --> V[return files array]
Loading

Reviews (1): 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
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
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

@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

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

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.

4 participants