Skip to content

Commit 30467d3

Browse files
committed
Merge remote-tracking branch 'upstream/staging' into fix/5625-docker-prod-missing-secrets
# Conflicts: # README.md # docker-compose.prod.yml
2 parents a619927 + 8b199d7 commit 30467d3

1,396 files changed

Lines changed: 186946 additions & 23643 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: 83 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,25 @@ export const {ServiceName}Block: BlockConfig = {
144144

145145
**Scope descriptions:** When adding a new OAuth provider, also add human-readable descriptions for all scopes in `SCOPE_DESCRIPTIONS` within `lib/oauth/utils.ts`.
146146

147+
**Service accounts (shared, app-level credentials):** A plain `oauth-input` already lets users *select* an existing service account — those credentials fold into the picker automatically (a Google service account created for any Google service appears in every Google block's picker). You only set `credentialKind` when you want to change the *connect* action:
148+
149+
```typescript
150+
{
151+
id: 'credential',
152+
title: 'Account',
153+
type: 'oauth-input',
154+
serviceId: '{service}',
155+
requiredScopes: getScopesForService('{service}'),
156+
credentialKind: 'any', // omit | 'service-account' | 'any'
157+
}
158+
```
159+
160+
- **omit (default):** lists OAuth accounts + any existing service accounts; the only connect action is "Connect account" (OAuth). Use this for the common "let users pick a service account someone set up elsewhere, but don't offer inline setup" case — no config needed.
161+
- **`'service-account'`:** service-account credentials *only*, plus an inline setup action that opens the provider's connect modal. Use when a block accepts *only* an app credential.
162+
- **`'any'`:** merged picker — OAuth accounts *and* service accounts in one grouped dropdown, with a connect action for each. Use when a block supports both (e.g. Slack: a personal account *or* a custom bot).
163+
164+
Optional companions: `credentialLabels` (override the picker's section/connect-row copy) and `allowServiceAccounts: true` (trigger-mode only — list service accounts, which triggers otherwise exclude; set only when the trigger's polling path can resolve a service-account token). The connect modal, provider families (Google JSON key, Atlassian token, token-paste, client-credential, Slack bot), and the preview gate are all resolved from `serviceAccountProviderId` — you don't wire them per block.
165+
147166
### Selectors (with dynamic options)
148167
```typescript
149168
// Channel selector (Slack, Discord, etc.)
@@ -238,48 +257,94 @@ When your block accepts file uploads, use the basic/advanced mode pattern with `
238257
},
239258
```
240259

260+
**Keep the pair to one logical thing.** Basic is the file upload, advanced is *only* a reference to
261+
a file from a previous block. Gmail attachments are the reference implementation
262+
(`apps/sim/blocks/blocks/gmail.ts``attachmentFiles` / `attachments`).
263+
264+
Do not overload the advanced side with alternate identifiers (a remote URL, a provider asset ID, a
265+
path). A subblock whose meaning changes based on what the string looks like is impossible to reason
266+
about, forces the params function to sniff the value, and makes the field's type meaningless. Give
267+
each alternative its own subblock outside the pair:
268+
269+
```typescript
270+
// ✓ Good — the pair is "a file"; other sources are their own fields
271+
{ id: 'mediaFile', type: 'file-upload', canonicalParamId: 'media', mode: 'basic' },
272+
{ id: 'mediaFileRef', type: 'short-input', canonicalParamId: 'media', mode: 'advanced' },
273+
{ id: 'mediaId', type: 'short-input', mode: 'advanced' }, // separate concept
274+
{ id: 'mediaLink', type: 'short-input', mode: 'advanced' }, // separate concept
275+
276+
// ✗ Bad — one field meaning three things, resolved by guessing
277+
{ id: 'mediaRef', type: 'short-input', canonicalParamId: 'media', mode: 'advanced',
278+
placeholder: 'File reference, media ID, or public URL' },
279+
```
280+
281+
When several fields are mutually exclusive alternatives, mark them all `required: false` and enforce
282+
"exactly one" at execution — a conditionally-required canonical pair rejects the workflow before the
283+
other paths ever get a chance to supply the value.
284+
241285
**Critical constraints:**
242286
- `canonicalParamId` must NOT match any subblock's `id` in the same block
243-
- Values are stored under subblock `id`, not `canonicalParamId`
287+
- A canonical group is **block-wide**, not per-operation: `buildCanonicalIndex` keys groups by
288+
`canonicalParamId` across every subblock, and a group has exactly one `basicId`. Two operations
289+
that each need a file pair need two distinct `canonicalParamId` values.
290+
- All members of a group must share the same `required` status
244291

245292
### Normalizing File Input in tools.config
246293

247-
Use `normalizeFileInput` to handle all input variants:
294+
Put the normalization in `tools.config.params`, never in `tools.config.tool``tool` runs at
295+
serialization, before variable resolution, so a `<block.output>` file reference is not yet a value
296+
there.
248297

249298
```typescript
250299
import { normalizeFileInput } from '@/blocks/utils'
251300

252301
tools: {
253302
access: ['service_upload'],
254303
config: {
255-
tool: (params) => {
256-
// Check all field IDs: uploadFile (basic), fileRef (advanced), fileContent (legacy)
257-
const normalizedFile = normalizeFileInput(
258-
params.uploadFile || params.fileRef || params.fileContent,
259-
{ single: true }
260-
)
261-
if (normalizedFile) {
262-
params.file = normalizedFile
304+
tool: (params) => `service_${params.operation}`,
305+
params: (params) => {
306+
// Read the CANONICAL id, not the subblock ids
307+
const { file: fileParam, ...rest } = params
308+
const file = normalizeFileInput(fileParam, { single: true })
309+
return {
310+
...rest,
311+
...(file ? { file } : {}),
263312
}
264-
return `service_${params.operation}`
265313
},
266314
},
267315
}
268316
```
269317

270-
**Why this pattern?**
271-
- Values come through as `params.uploadFile` or `params.fileRef` (the subblock IDs)
272-
- `canonicalParamId` only controls UI/schema mapping, not runtime values
273-
- `normalizeFileInput` handles JSON strings from advanced mode template resolution
318+
**Where the value actually lives at runtime.** The subblock `id` is where the UI *stores* the value,
319+
but it is not what the params function receives. `extractBlockParams`
320+
(`apps/sim/serializer/index.ts`) collapses each canonical group at serialization time:
321+
322+
```typescript
323+
const sourceIds = [group.basicId, ...group.advancedIds].filter(Boolean)
324+
sourceIds.forEach((id) => delete params[id]) // subblock ids are deleted
325+
if (chosen !== undefined) params[group.canonicalId] = chosen
326+
```
327+
328+
So by the time `tools.config.params(inputs)` runs (`executor/handlers/generic/generic-handler.ts`),
329+
`params.uploadFile` and `params.fileRef` are **gone** and the value is under `params.file`. Reading a
330+
subblock id there yields `undefined` and silently sends no file.
331+
332+
Only the active mode's value survives — `getCanonicalValues` returns the basic value in basic mode
333+
and the first non-empty advanced value in advanced mode, so a stale value in the dormant mode can
334+
never leak. `normalizeFileInput` then handles the JSON string that advanced-mode template resolution
335+
produces.
336+
337+
Note that `generic-handler` merges rather than replaces (`{ ...inputs, ...transformedParams }`), so
338+
omitting a key from the returned object does not strip it from what the tool receives. Tools simply
339+
ignore params they do not declare.
274340

275341
### File Input Types in `inputs`
276342

277-
Use `type: 'json'` for file inputs:
343+
Declare the **canonical** id with `type: 'json'` — the subblock ids never reach `inputs`:
278344

279345
```typescript
280346
inputs: {
281-
uploadFile: { type: 'json', description: 'Uploaded file (UserFile)' },
282-
fileRef: { type: 'json', description: 'File reference from previous block' },
347+
file: { type: 'json', description: 'File to upload (UserFile or reference)' },
283348
// Legacy field for backwards compatibility
284349
fileContent: { type: 'string', description: 'Legacy: base64 encoded content' },
285350
}

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

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ export const {Service}Block: BlockConfig = {
213213
```typescript
214214
// Basic: Visual selector
215215
{
216-
id: 'channel',
216+
id: 'channelSelector',
217217
type: 'channel-selector',
218218
mode: 'basic',
219219
canonicalParamId: 'channel',
@@ -228,10 +228,19 @@ export const {Service}Block: BlockConfig = {
228228
}
229229
```
230230

231+
Note neither subblock `id` is `channel` — the canonical id is a third name that both members map
232+
onto, and it is the only one that survives serialization.
233+
231234
**Critical Canonical Param Rules:**
232235
- `canonicalParamId` must NOT match any subblock's `id` in the block
233-
- `canonicalParamId` must be unique per operation/condition context
234-
- Only use `canonicalParamId` to link basic/advanced alternatives for the same logical parameter
236+
- `canonicalParamId` must be unique **block-wide**, not per operation. `buildCanonicalIndex` keys
237+
groups by `canonicalParamId` across all subblocks and a group holds exactly one `basicId`, so two
238+
operations that each need their own pair must use two different canonical ids
239+
- Only use `canonicalParamId` to link basic/advanced alternatives for the same logical parameter.
240+
A pair carries ONE concept — for files that means upload (basic) + file reference (advanced), as
241+
in Gmail attachments (`blocks/blocks/gmail.ts`). Never overload the advanced side with alternate
242+
identifiers like a URL or a provider asset ID; give those their own subblocks, mark all the
243+
mutually exclusive sources `required: false`, and enforce "exactly one" at execution
235244
- `mode` only controls UI visibility, NOT serialization. Without `canonicalParamId`, both basic and advanced field values would be sent
236245
- Every subblock `id` must be unique within the block. Duplicate IDs cause conflicts even with different conditions
237246
- **Required consistency:** If one subblock in a canonical group has `required: true`, ALL subblocks in that group must have `required: true` (prevents bypassing validation by switching modes)

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ Use a precise WebFetch prompt: *"Extract for {model_id}: exact model id string,
5252
| `reasoningEffort` | `openai/core.ts`, `azure-openai`, `anthropic/core.ts` (mapped to thinking), `gemini/core.ts` | **Dead on xai, deepseek, mistral, groq, cerebras, openrouter, fireworks, bedrock, vertex** unless their core consumes it — re-grep before assuming |
5353
| `verbosity` | `openai/core.ts`, `azure-openai/index.ts` only | Dead elsewhere |
5454
| `thinking` | `anthropic/core.ts`, `gemini/core.ts` | Dead elsewhere |
55+
| `thinking.streamed` | Docs generator + `getThinkingStreamVisibility` (`models.ts`); `anthropic/core.ts` uses `'summary'` to request `display: 'summarized'` on agent-events runs | **Mandatory on Anthropic-family thinking models** (`agent-stream-docs:check` fails without it); other families fall back to provider defaults |
5556
| `nativeStructuredOutputs` | `anthropic/core.ts`, `fireworks/index.ts`, `openrouter/index.ts` | Dead on openai, xai, google, vertex, bedrock, azure-openai, deepseek, mistral, groq, cerebras |
5657
| `maxOutputTokens` | Read by UI + executor for token estimation | Always meaningful — set if provider documents a cap |
5758
| `computerUse` | `anthropic/core.ts` | Dead elsewhere |
@@ -146,6 +147,15 @@ If anything matches, run the affected provider tests and update assertions as ne
146147

147148
The Consumption Matrix (Step 2) tells you which capability *flags* are honored by existing provider code. But if the new model needs **net-new** request handling that the provider doesn't implement yet — a new beta header (e.g. Anthropic's `anthropic-beta` structured-outputs header in `anthropic/index.ts`), a new thinking/reasoning encoding, a Responses-API quirk — you must edit `apps/sim/providers/<provider>/core.ts` / `index.ts`. Setting a flag whose behavior isn't implemented is a silent no-op. When you do edit provider code, reuse the shared helpers rather than hand-rolling: streaming responses are assembled via `createStreamingExecution` (`@/providers/streaming-execution`) and tool schemas via `adaptOpenAIChatToolSchema` / `adaptAnthropicToolSchema` (`@/providers/tool-schema-adapter`).
148149

150+
### Thinking/reasoning models: `streamed` visibility + generated docs
151+
152+
If the entry has `capabilities.thinking` or `capabilities.reasoningEffort`, it appears in the autogenerated "Streamed thinking and tool calls" table on the Agent block docs page:
153+
154+
- **Anthropic-family (`anthropic`, `azure-anthropic`) thinking models MUST declare `capabilities.thinking.streamed`** (`'full' | 'summary' | 'none'`). Verify against Anthropic's current thinking-display and streaming docs: visible thinking returned by the API is summarized, including when Sim opts models whose default display is `omitted` into `display: 'summarized'` on agent-events runs, so current Claude thinking models use `'summary'`. Use `'full'` only if future official API docs explicitly guarantee raw thinking deltas. `bun run agent-stream-docs:check` (CI) fails if the field is missing.
155+
- Other families usually omit the field and inherit the provider default in `getThinkingStreamVisibility` (Gemini/OpenAI → summaries; Bedrock/Meta → none; OpenAI-compatible vendors with documented reasoning fields → full deltas). Set it explicitly only when the model deviates from its family.
156+
- After inserting the entry, run `bun run agent-stream-docs:generate` and commit the regenerated `apps/docs/content/docs/en/workflows/blocks/agent.mdx` — CI diffs it.
157+
- Include the `streamed` value (with its source URL) in the verification report when set.
158+
149159
### Wrong family entirely?
150160

151161
- **Embedding or rerank model** → it does NOT go in the `models[]` array. Use `EMBEDDING_MODEL_PRICING` / `RERANK_MODEL_PRICING` in `models.ts` instead.
@@ -155,6 +165,7 @@ The Consumption Matrix (Step 2) tells you which capability *flags* are honored b
155165

156166
```bash
157167
bun run lint
168+
bun run agent-stream-docs:generate # only when the entry has thinking/reasoningEffort
158169
```
159170

160171
Lint must pass before reporting done. **If lint fails:** read the error, fix the syntax/typing issue in the entry you just wrote (do not delete the entry — it's the work product), re-run lint, and note the fix in a "Lint adjustments" line in the verification report. Never report done with lint failing.
@@ -201,6 +212,7 @@ Omitting a field is **not the same as verifying it**. Any field you cannot confi
201212
- ❌ Trusting a marketing email (xAI's grok-4.3 email claimed "3 reasoning efforts" but the API rejects `reasoning_effort` — verified by official docs only)
202213
- ❌ Setting `nativeStructuredOutputs: true` on xai/openai/google (dead — only anthropic/fireworks/openrouter consume it)
203214
- ❌ Setting `thinking` on non-Anthropic/non-Gemini providers
215+
- ❌ Adding an Anthropic-family thinking model without `thinking.streamed` (CI `agent-stream-docs:check` fails), or skipping `bun run agent-stream-docs:generate` after adding any thinking/reasoning model
204216
- ❌ Setting `verbosity` on anything other than OpenAI gpt-5.x
205217
- ❌ Copying `pricing.updatedAt` from a sibling instead of using today's date
206218
- ❌ Inventing a `cachedInput` price by dividing input by 4 (varies by provider — find an explicit number)

0 commit comments

Comments
 (0)