Skip to content

Commit 05b66a3

Browse files
committed
improvement(tools): retire direct execution
1 parent 1f41ce7 commit 05b66a3

226 files changed

Lines changed: 5666 additions & 4450 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-block/SKILL.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ When the user asks you to create a block:
1919

2020
Blocks depend on tool outputs. If the underlying tool response schema is not documented or live-verified, you MUST tell the user instead of guessing block outputs.
2121

22+
When block work changes tool execution, same-process work must use a registered
23+
`InternalToolConfig.operation`. Never add a Sim `/api/...` self-hop or the retired
24+
`directExecution` property.
25+
2226
- Do NOT invent block outputs for undocumented tool responses
2327
- Do NOT describe unknown JSON shapes as if they were confirmed
2428
- Do NOT wire fields into the block just because they seem likely to exist

.agents/skills/add-integration/SKILL.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ Choose the tool boundary before writing the declaration:
6868
- Use `ToolConfig.request` only for an absolute external HTTP(S) provider endpoint.
6969

7070
Never point a tool at `/api/...`, construct an absolute URL back to Sim, declare
71-
`request.internal`, or add an API route merely to reuse code, normalize files, or authorize
71+
`request.internal`, add the retired `directExecution` property, or add an API route merely to reuse code, normalize files, or authorize
7272
resources. A real external/browser route and an in-process tool may share the same operation, but
7373
neither calls the other. Follow the full transport and handler rules in the `add-tools` skill.
7474

@@ -171,7 +171,7 @@ Hard rules:
171171
- Never substitute secret plaintext into source or serialize plaintext provenance.
172172
- Never hand-roll private provenance headers/envelopes; the shared `executeTool` boundary owns
173173
transport and strips private metadata from functional results.
174-
- Never attach private provenance to an external URL or to `directExecution`. Project proven
174+
- Never attach private provenance to an external URL. Project proven
175175
model-visible external fields with `request.modelInput`; otherwise preserve ordinary request
176176
semantics. Use a registered in-process operation when encrypted provenance must cross the
177177
boundary.
@@ -607,7 +607,7 @@ If creating V2 versions (API-aligned outputs):
607607
- [ ] Chose exactly one boundary per tool: registered `InternalToolConfig.operation` or absolute
608608
external HTTP(S) `ToolConfig.request`
609609
- [ ] No tool points to `/api/...`, constructs a URL back to Sim, declares `request.internal`, or
610-
has an HTTP fallback for an in-process operation
610+
`directExecution`, or has an HTTP fallback for an in-process operation
611611
- [ ] All params have correct visibility
612612
- [ ] All nullable fields use `?? null`
613613
- [ ] All optional outputs have `optional: true`

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ Every tool must use exactly one of these configurations:
5454
HTTP(S) provider endpoint.
5555

5656
Never set a tool URL to `/api/...`, construct an absolute URL back to Sim, declare
57-
`request.internal`, import a route module, or create an API route merely to normalize files,
57+
`request.internal`, add the retired `directExecution` property, import a route module, or create an API route merely to normalize files,
5858
authorize access, or reuse server code. A real browser/API route may remain as a thin adapter, but
5959
the route and the tool must call the same operation directly. A true cross-process/capability
6060
boundary uses an explicit server client and is not disguised as a tool self-hop.
@@ -524,6 +524,7 @@ All tool IDs MUST use `snake_case`: `{service}_{action}` (e.g., `x_create_tweet`
524524
HTTP(S) `ToolConfig.request`
525525
- [ ] No tool request points to `/api/...`, constructs a URL back to Sim, or declares
526526
`request.internal`
527+
- [ ] No tool declares `directExecution`; in-process work uses a registered operation
527528
- [ ] All params have explicit `required: true` or `required: false`
528529
- [ ] All params have appropriate `visibility`
529530
- [ ] All nullable response fields use `?? null`

.agents/skills/add-trigger/SKILL.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -508,7 +508,8 @@ Two rules the checks enforce:
508508
Webhook and polling routes are legitimate external ingress boundaries. They must not call this
509509
Sim app's own API routes to reuse provider or business logic. Extract the shared provider operation
510510
or authorized application use case and call it directly from the trigger handler and any other
511-
server adapter. HTTP is reserved for an actual cross-process/capability boundary.
511+
server adapter. HTTP is reserved for an actual cross-process/capability boundary. Tool work uses a
512+
registered `InternalToolConfig.operation`; the retired `directExecution` property must not return.
512513

513514
### Trigger Definition
514515
- [ ] Created `utils.ts` with options, instructions, extra fields, and output builders

.agents/skills/tool-registry-boundary/SKILL.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,12 @@ You keep the 4,300-tool executable registry out of module graphs that don't exec
1111

1212
> Client-reachable code reads tool **metadata**. Only code that actually executes a tool imports the **registry**.
1313
14-
`@/tools/registry` is a ~9,000-line barrel importing every tool. Each `ToolConfig` mixes plain data (`params`, `outputs`, `name`) with closures — `request.url`, `request.headers`, `transformResponse`, `directExecution`, `postProcess`. Those closures reach the SDK clients, API helpers and parsers each integration needs, and that is what makes the barrel expensive: reaching it costs ~4,700 additional modules.
14+
`@/tools/registry` is a ~9,000-line barrel importing every tool. External `ToolConfig` entries mix
15+
plain data (`params`, `outputs`, `name`) with request/response closures, while
16+
`InternalToolConfig` entries contain semantic input projection and load their server implementation
17+
through `lib/internal/tool-operations/registry.server.ts`. Request closures can still reach SDK
18+
clients, API helpers, and parsers, which is what makes the executable barrel expensive: reaching it
19+
costs ~4,700 additional modules.
1520

1621
`getTool()` returns the whole `ToolConfig`, so a single `getTool` import anywhere in a client-reachable file drags all of it in.
1722

@@ -95,4 +100,5 @@ The canvas route reached the registry through **four** redundant edges — `prov
95100

96101
Ask what the caller does with the config. If it reads `params`, `outputs`, `name`, `description` or just checks existence, it belongs on `@/tools/metadata` — no exceptions, even on a path you believe is server-only today, because a future client import will silently re-attach the registry to the graph.
97102

98-
If it genuinely executes — builds a request, transforms a response, runs `directExecution` — use `getTool`, and keep that file off client-reachable paths.
103+
If it genuinely executes — builds an external request, transforms a response, or dispatches a
104+
registered internal operation — use `getTool`, and keep that file off client-reachable paths.

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,8 +159,9 @@ search, extraction, or "AI-powered" marketing terminology.
159159
- [ ] Sim-owned durable writes and internal execution handoffs that can enter workflows/models use
160160
field-scoped `request.secretProvenance`; authenticated receivers validate the exact selection
161161
and scope, strip private metadata, and persist, import, or propagate it at the owning boundary
162-
- [ ] Private provenance is never attached to external URLs or `directExecution`; proven
163-
model-visible external fields use projection, while other external inputs remain unchanged
162+
- [ ] Private provenance is never attached to external URLs; registered in-process operations
163+
preserve it through `operation.modelInput` / `operation.secretProvenance`, while proven
164+
model-visible external fields use request projection and other external inputs remain unchanged
164165
- [ ] No tool performs raw secret plaintext/source substitution or serializes plaintext provenance
165166
- [ ] No `transformResponse` or tool-local helper blanket-sanitizes ordinary third-party results;
166167
only execution-scoped, activated Sim provenance is projected at shared model/log boundaries

apps/sim/app/api/tools/netsuite/objects/route.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,11 @@ vi.mock('@/lib/oauth/credential-service', () => ({
3131
resolveCredentialAccessToken: mockResolveCredentialAccessToken,
3232
resolveOAuthAccountId: mockResolveOAuthAccountId,
3333
}))
34-
vi.mock('@/tools/netsuite/get_async_status', () => ({
35-
netsuiteGetAsyncStatusTool: { directExecution: mockGetAsyncStatus },
34+
vi.mock('@/lib/internal/netsuite/operations/get-async-status', () => ({
35+
executeNetsuiteGetAsyncStatusOperation: mockGetAsyncStatus,
3636
}))
37-
vi.mock('@/tools/netsuite/list_record_types', () => ({
38-
netsuiteListRecordTypesTool: { directExecution: mockListRecordTypes },
37+
vi.mock('@/lib/internal/netsuite/operations/list-record-types', () => ({
38+
executeNetsuiteListRecordTypesOperation: mockListRecordTypes,
3939
}))
4040

4141
import { POST } from '@/app/api/tools/netsuite/objects/route'

apps/sim/app/api/tools/netsuite/objects/route.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ import { generateRequestId } from '@/lib/core/utils/request'
1212
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
1313
import { NETSUITE_SERVICE_ACCOUNT_PROVIDER_ID } from '@/lib/credentials/client-credential-accounts/descriptors'
1414
import { TokenServiceAccountValidationError } from '@/lib/credentials/token-service-accounts/errors'
15+
import { executeNetsuiteGetAsyncStatusOperation } from '@/lib/internal/netsuite/operations/get-async-status'
16+
import { executeNetsuiteListRecordTypesOperation } from '@/lib/internal/netsuite/operations/list-record-types'
1517
import { resolveCredentialAccessToken, resolveOAuthAccountId } from '@/lib/oauth/credential-service'
16-
import { netsuiteGetAsyncStatusTool } from '@/tools/netsuite/get_async_status'
17-
import { netsuiteListRecordTypesTool } from '@/tools/netsuite/list_record_types'
1818
import type { NetSuiteAuthParams } from '@/tools/netsuite/types'
1919
import { normalizeSuiteTalkUrl } from '@/tools/netsuite/utils'
2020
import type { ToolResponse } from '@/tools/types'
@@ -180,14 +180,13 @@ async function executeDiscoveryTool(
180180
throwIfAborted(signal)
181181
switch (body.kind) {
182182
case 'record_types': {
183-
const execute = netsuiteListRecordTypesTool.directExecution
184-
if (!execute) throw new Error('NetSuite record-type tool is not executable')
185-
return execute(auth, signal)
183+
return executeNetsuiteListRecordTypesOperation(auth, signal)
186184
}
187185
case 'async_tasks': {
188-
const execute = netsuiteGetAsyncStatusTool.directExecution
189-
if (!execute) throw new Error('NetSuite asynchronous-status tool is not executable')
190-
return execute({ ...auth, jobId: body.jobId, view: 'tasks' }, signal)
186+
return executeNetsuiteGetAsyncStatusOperation(
187+
{ ...auth, jobId: body.jobId, view: 'tasks' },
188+
signal
189+
)
191190
}
192191
}
193192
}

apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-workflow/preview-workflow.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -416,12 +416,12 @@ export function PreviewWorkflow({
416416

417417
// Check for direct error on the subflow block itself (e.g., loop resolution errors)
418418
// before falling back to children-derived status
419-
const directExecution = blockExecutionMap.get(blockId)
419+
const blockExecution = blockExecutionMap.get(blockId)
420420
const subflowExecutionStatus: ExecutionStatus | undefined =
421-
directExecution?.status === 'error'
421+
blockExecution?.status === 'error'
422422
? 'error'
423423
: (getSubflowExecutionStatus(blockId) ??
424-
(directExecution ? (directExecution.status as ExecutionStatus) : undefined))
424+
(blockExecution ? (blockExecution.status as ExecutionStatus) : undefined))
425425

426426
nodeArray.push({
427427
id: blockId,
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import {
2+
executeBitbucketGetFileOperation,
3+
executeBitbucketGetPipelineStepLogOperation,
4+
executeBitbucketGetPullRequestDiffOperation,
5+
executeBitbucketGetPullRequestDiffstatOperation,
6+
} from '@/lib/internal/bitbucket/operations'
7+
import { executeToolOperationImplementation } from '@/lib/internal/tool-operations/execute'
8+
import type { InternalToolOperationHandler } from '@/lib/internal/tool-operations/types'
9+
10+
export const executeBitbucketTool: InternalToolOperationHandler = async (request) => {
11+
switch (request.toolId) {
12+
case 'bitbucket_get_file':
13+
return executeToolOperationImplementation(executeBitbucketGetFileOperation, request)
14+
case 'bitbucket_get_pipeline_step_log':
15+
return executeToolOperationImplementation(
16+
executeBitbucketGetPipelineStepLogOperation,
17+
request
18+
)
19+
case 'bitbucket_get_pull_request_diff':
20+
return executeToolOperationImplementation(
21+
executeBitbucketGetPullRequestDiffOperation,
22+
request
23+
)
24+
case 'bitbucket_get_pull_request_diffstat':
25+
return executeToolOperationImplementation(
26+
executeBitbucketGetPullRequestDiffstatOperation,
27+
request
28+
)
29+
default:
30+
return Response.json(
31+
{ success: false, error: `Unsupported bitbucket tool: ${request.toolId}` },
32+
{ status: 500 }
33+
)
34+
}
35+
}

0 commit comments

Comments
 (0)