Skip to content

Commit 44b581b

Browse files
committed
fix(tools): cover late attachments and simplify file outputs
1 parent ce39bf1 commit 44b581b

98 files changed

Lines changed: 2685 additions & 818 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.agents/skills/add-tools/SKILL.md‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -280,12 +280,21 @@ for raw binary responses, not provider JSON containing base64 or tools that fetc
280280
Keep provider-specific limits and bounded reads; a file declaration is not permission to enlarge
281281
arbitrary JSON responses.
282282

283+
Attachment readers that download files inside `transformResponse` need their own bounded reads:
284+
the first response cap does not cover subsequent fetches. Accept `ToolResponseContext` as the third
285+
transform argument, forward its `signal`, and share one `AttachmentDownloadBudget` across sequential
286+
downloads. Prefer raw provider endpoints over base64 metadata. Return the same file object in the
287+
declared `file` / `file[]` output and nested message associations; `FileToolProcessor` stores it once
288+
and replaces every alias with the same `UserFile` in both workflow and Copilot execution.
289+
283290
Preserve stored `UserFile` fields (`id`, `key`, `url`, `context`, `type`, `name`, `size`) in transforms;
284291
rebuilding the old `{ name, mimeType, data, size }` shape discards the reference. File outputs do not
285292
need duplicate inline text/base64 aliases; the file system handles content materialization. When
286-
an existing tool exposes those aliases or nested inline file data, preserve its legacy version and
293+
an existing tool explicitly exposes content aliases in its contract, preserve its legacy version and
287294
use the existing block/tool version pattern for a file-only output. Test a file over 10 MiB through
288-
executor admission, single persistence, trusted ownership, and the unchanged JSON cap.
295+
executor admission, single persistence, trusted ownership, and the unchanged JSON cap. Avoid adding
296+
top-level filename, size, MIME type, URL, or success fields that merely repeat the canonical file or
297+
tool result; keep additional provider fields only when they convey distinct information.
289298

290299
### Output Types
291300
- `'string'`, `'number'`, `'boolean'` - Primitives

‎.agents/skills/validate-integration/SKILL.md‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,15 @@ If any tool lists, searches, exports, imports, downloads, uploads, paginates, ba
351351
- [ ] Transforms retain stored `UserFile` identity/access fields, and tests cover a >10 MiB file
352352
crossing executor admission without another upload. New file outputs contain references only,
353353
without inline content aliases; preserve legacy versions when removing existing inline fields
354+
- [ ] Scan every file-producing path, including attachment fetches inside `transformResponse`, URL
355+
descriptors, export operations, and old/new block versions; checking download-named tools alone
356+
misses late reads that occur after the first response admission
357+
- [ ] Late attachment reads share a per-call byte budget, bound actual streamed bytes independently
358+
of provider size metadata, and forward `ToolResponseContext.signal` through every fetch/read
359+
- [ ] Both workflow and Copilot tests produce compact `UserFile` outputs; nested message attachment
360+
aliases reference the same stored files, with no duplicate upload or raw bytes left behind
361+
- [ ] New output contracts omit redundant copies of file name, MIME type, size, URL, and success;
362+
retained provider metadata has a distinct purpose, and types match the stored-file runtime shape
354363
- [ ] Large result payloads are summarized, paginated, referenced, or capped rather than raw-dumped
355364
- [ ] Pagination and download tests cover caps, early stop behavior, or partial-result preservation when relevant
356365

‎apps/docs/components/ui/icon-mapping.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
478478
jotform: JotformIcon,
479479
jsm: JiraServiceManagementIcon,
480480
jupyter: JupyterIcon,
481+
jupyter_v2: JupyterIcon,
481482
kalshi: KalshiIcon,
482483
kalshi_v2: KalshiIcon,
483484
ketch: KetchIcon,

‎apps/docs/content/docs/integrations/jupyter.mdx‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ description: Manage files, notebooks, kernels, and sessions on a Jupyter server
66
import { BlockInfoCard } from "@/components/ui/block-info-card"
77

88
<BlockInfoCard
9-
type="jupyter"
9+
type="jupyter_v2"
1010
color="#FFFFFF"
1111
/>
1212

@@ -61,7 +61,7 @@ List files, notebooks, and subdirectories at a path on a Jupyter server
6161

6262
### Jupyter Get Content
6363

64-
Read a file or notebook from a Jupyter server
64+
Download a file as a stored file, or read structured notebook and directory content
6565

6666
#### Input
6767

@@ -75,11 +75,11 @@ Read a file or notebook from a Jupyter server
7575

7676
| Parameter | Type | Description |
7777
| --------- | ---- | ----------- |
78-
| `name` | string | File or notebook name |
79-
| `path` | string | Path relative to the server root |
80-
| `mimetype` | string | MIME type of the content |
81-
| `text` | string | Text content, for text files and notebooks \(JSON-stringified\) |
82-
| `file` | file | Binary content stored as a file, for base64-format content |
78+
| `file` | file | Downloaded file |
79+
| `text` | string | JSON-stringified notebook or directory content |
80+
| `name` | string | Notebook or directory name |
81+
| `path` | string | Notebook or directory path |
82+
| `mimetype` | string | Notebook or directory MIME type |
8383

8484
### Jupyter Create File
8585

‎apps/docs/content/docs/integrations/microsoft_dataverse.mdx‎

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -152,11 +152,7 @@ Download a file from a Dataverse file or image column and return its stored file
152152
| Parameter | Type | Description |
153153
| --------- | ---- | ----------- |
154154
| `file` | file | Downloaded file stored in execution files |
155-
| `fileName` | string | Name of the downloaded file |
156-
| `fileSize` | number | File size in bytes |
157-
| `mimeType` | string | MIME type of the file |
158155
| `fileColumn` | string | File column the file was downloaded from |
159-
| `success` | boolean | Whether the file was downloaded successfully |
160156

161157
### Execute Microsoft Dataverse Action
162158

‎apps/docs/content/docs/integrations/quiver.mdx‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ Generate SVG images from text prompts using QuiverAI
5757

5858
| Parameter | Type | Description |
5959
| --------- | ---- | ----------- |
60-
| `file` | file | First generated SVG stored as a file |
6160
| `files` | file[] | All generated SVG files |
6261
| `id` | string | Request ID |
6362
| `usage` | json | Token usage statistics |
@@ -87,7 +86,6 @@ Convert raster images into vector SVG format using QuiverAI
8786

8887
| Parameter | Type | Description |
8988
| --------- | ---- | ----------- |
90-
| `file` | file | First generated SVG stored as a file |
9189
| `files` | file[] | All generated SVG files |
9290
| `id` | string | Request ID |
9391
| `usage` | json | Token usage statistics |

‎apps/docs/content/docs/integrations/sftp.mdx‎

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,7 @@ Download a file from a remote SFTP server
8282

8383
| Parameter | Type | Description |
8484
| --------- | ---- | ----------- |
85-
| `success` | boolean | Whether the download was successful |
8685
| `file` | file | Downloaded file stored in execution files |
87-
| `fileName` | string | Name of the downloaded file |
88-
| `size` | number | File size in bytes |
89-
| `message` | string | Operation status message |
9086

9187
### SFTP List Directory
9288

‎apps/docs/content/docs/integrations/ssh.mdx‎

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -161,12 +161,8 @@ Download a file from a remote SSH server
161161

162162
| Parameter | Type | Description |
163163
| --------- | ---- | ----------- |
164-
| `downloaded` | boolean | Whether the file was downloaded successfully |
165164
| `file` | file | Downloaded file stored in execution files |
166-
| `fileName` | string | Name of the downloaded file |
167165
| `remotePath` | string | Source path on the remote server |
168-
| `size` | number | File size in bytes |
169-
| `message` | string | Operation status message |
170166

171167
### SSH List Directory
172168

‎apps/sim/blocks/blocks/binary-download-versioning.test.ts‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,17 @@ describe.each(cases)(
8484
})
8585
}
8686
)
87+
88+
it('keeps Dataverse upload metadata separate from canonical download files', () => {
89+
expect(MicrosoftDataverseV2Block.outputs).not.toHaveProperty('fileSize')
90+
expect(MicrosoftDataverseV2Block.outputs).not.toHaveProperty('mimeType')
91+
expect(MicrosoftDataverseV2Block.outputs.fileName.condition).toEqual({
92+
field: 'operation',
93+
value: 'upload_file',
94+
})
95+
expect(MicrosoftDataverseV2Block.outputs.success.condition).toEqual({
96+
field: 'operation',
97+
value: 'download_file',
98+
not: true,
99+
})
100+
})
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { describe, expect, it } from 'vitest'
2+
import { JupyterBlock, JupyterV2Block } from '@/blocks/blocks/jupyter'
3+
4+
describe('Jupyter block versions', () => {
5+
it('offers v2 for new blocks and preserves the legacy block', () => {
6+
expect(JupyterBlock.hideFromToolbar).toBe(true)
7+
expect(JupyterBlock.sunset).toEqual({ status: 'legacy', replacedBy: 'jupyter_v2' })
8+
expect(JupyterV2Block.hideFromToolbar).toBe(false)
9+
expect(JupyterV2Block.sunset).toBeUndefined()
10+
expect(JupyterV2Block.canvasPresentation).toBe(JupyterBlock.canvasPresentation)
11+
expect(JupyterV2Block.subBlocks).toBe(JupyterBlock.subBlocks)
12+
})
13+
14+
it.each(JupyterBlock.tools.access)('versions only Get Content: %s', (operation) => {
15+
const id = operation === 'jupyter_get_content' ? 'jupyter_get_content_v2' : operation
16+
expect(JupyterBlock.tools.config.tool({ operation })).toBe(operation)
17+
expect(JupyterV2Block.tools.config?.tool({ operation })).toBe(id)
18+
expect(JupyterV2Block.tools.access).toContain(id)
19+
})
20+
21+
it('preserves the path and credentials for the versioned read', () => {
22+
expect(
23+
JupyterV2Block.tools.config?.params?.({
24+
operation: 'jupyter_get_content',
25+
serverUrl: 'https://jupyter.example.com',
26+
token: 'token',
27+
path: 'reports/book.xlsx',
28+
})
29+
).toEqual({
30+
serverUrl: 'https://jupyter.example.com',
31+
token: 'token',
32+
path: 'reports/book.xlsx',
33+
})
34+
})
35+
})

0 commit comments

Comments
 (0)