Skip to content

Commit 2d84fe2

Browse files
committed
Merge remote-tracking branch 'origin/staging' into staging-v11
2 parents 0d68a8c + 998fd5a commit 2d84fe2

8 files changed

Lines changed: 270 additions & 46 deletions

File tree

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
name: CI Cache Cleanup
2+
3+
# test-build.yml keys the Next.js build cache sticky disk per branch, so every PR
4+
# leaves a ~5 GB volume behind. Branches are short-lived; the disks aren't. Only
5+
# the pull_request disks are reclaimed — the push disks belong to main/staging/dev
6+
# and must stay warm.
7+
8+
on:
9+
pull_request:
10+
types: [closed]
11+
12+
permissions:
13+
contents: read
14+
15+
jobs:
16+
delete-nextjs-cache:
17+
name: Delete Next.js build cache disk
18+
# Sticky disks only exist on Blacksmith; the GitHub break-glass path uses
19+
# actions/cache, which expires on its own.
20+
if: vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith'
21+
runs-on: blacksmith-2vcpu-ubuntu-2404
22+
timeout-minutes: 5
23+
24+
steps:
25+
# Must stay byte-identical to the Mount Next.js build cache key in
26+
# test-build.yml, or this deletes nothing and the disks accumulate.
27+
# Non-blocking: PRs skipped by ci.yml's paths-ignore never made a disk.
28+
- name: Delete sticky disk
29+
uses: useblacksmith/stickydisk-delete@b41313d28b8647d72114c9ba3c96bb04061562b6 # v1
30+
continue-on-error: true
31+
with:
32+
delete-key: ${{ github.repository }}-nextjs-cache-pull_request${{ github.event.pull_request.head.repo.fork && '-fork' || '' }}-${{ github.head_ref }}

.github/workflows/test-build.yml

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -265,14 +265,21 @@ jobs:
265265

266266
# Turbopack's persistent build cache (NEXT_TURBOPACK_BUILD_CACHE below)
267267
# writes ~5 GB into .next/cache — a sticky disk mounts it in ~1s where an
268-
# actions/cache round-trip would eat the warm-build win. Same event/fork
269-
# namespacing as the other mounts; the GitHub fallback inside cache-mount
270-
# still uses actions/cache with a run_id-suffixed key.
268+
# actions/cache round-trip would eat the warm-build win.
269+
#
270+
# Keyed per branch, not just per event. A sticky disk is one mutable volume
271+
# per key: mounting clones the last committed snapshot, job end commits back
272+
# last-write-wins. An event-only key had every open PR restoring a cache
273+
# built from a different branch — 14.0 min vs 9.3 min for the single-writer
274+
# push disk on the same commit. Branch scoping also keeps us off
275+
# cross-commit restore, which turbopackFileSystemCacheForBuild (beta) does
276+
# not document as supported (vercel/next.js#87283: stale HTML from a cache
277+
# built at another commit). ci-cache-cleanup.yml reclaims the disks.
271278
- name: Mount Next.js build cache
272279
uses: ./.github/actions/cache-mount
273280
with:
274281
provider: ${{ vars.CI_PROVIDER }}
275-
key: ${{ github.repository }}-nextjs-cache-${{ github.event_name }}${{ github.event.pull_request.head.repo.fork && '-fork' || '' }}
282+
key: ${{ github.repository }}-nextjs-cache-${{ github.event_name }}${{ github.event.pull_request.head.repo.fork && '-fork' || '' }}-${{ github.head_ref || github.ref_name }}
276283
path: ./apps/sim/.next/cache
277284

278285
# Running out of RAM kills the whole VM and surfaces only as "the runner
@@ -293,6 +300,13 @@ jobs:
293300
- name: Install dependencies
294301
run: bun install --frozen-lockfile --ignore-scripts
295302

303+
# The disk mounts successfully whether or not it carried anything and turbo
304+
# buffers the build log, so cache warmth is otherwise unobservable — #5859
305+
# shipped a cache that carried almost nothing and it took a PR to notice.
306+
# Reported, never gated.
307+
- name: Report Next.js cache size (pre-build)
308+
run: du -sh apps/sim/.next/cache 2>/dev/null || echo 'cold — no cache restored'
309+
296310
- name: Build application
297311
env:
298312
NODE_OPTIONS: '--no-warnings --max-old-space-size=8192'
@@ -304,7 +318,13 @@ jobs:
304318
AWS_REGION: 'us-west-2'
305319
ENCRYPTION_KEY: '7cf672e460e430c1fba707575c2b0e2ad5a99dddf9b7b7e3b5646e630861db1c' # dummy key for CI only
306320
TURBO_CACHE_DIR: .turbo
307-
# Opt into Turbopack's persistent build cache (beta) for this CI
308-
# check build only — measured 105s cold vs 22s warm locally.
321+
# Opt into Turbopack's persistent build cache (beta) for this CI check
322+
# build only. #5869's 105s-cold/22s-warm was measured locally and has
323+
# never reproduced in CI (compile has ranged 3.5-17.8 min) — local
324+
# numbers, not a CI target.
309325
NEXT_TURBOPACK_BUILD_CACHE: '1'
310326
run: bunx turbo run build --filter=sim
327+
328+
- name: Report Next.js cache size (post-build)
329+
if: always()
330+
run: du -sh apps/sim/.next/cache 2>/dev/null || echo 'no cache written'

apps/sim/blocks/blocks/pi.ts

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ import { getEnv, isTruthy } from '@/lib/core/config/env'
33
import type { BlockConfig } from '@/blocks/types'
44
import { AuthMode, IntegrationType } from '@/blocks/types'
55
import {
6+
getApiKeyCondition,
67
getPiModelOptions,
78
getProviderCredentialSubBlocks,
89
PROVIDER_CREDENTIAL_INPUTS,
910
} from '@/blocks/utils'
11+
import { isPiByokOnlyMode } from '@/providers/pi-providers'
1012
import type { ToolResponse } from '@/tools/types'
1113

1214
interface PiResponse extends ToolResponse {
@@ -110,6 +112,20 @@ function getSearchApiKeyCondition() {
110112
}
111113
}
112114

115+
const hostedModelApiKeyCondition = getApiKeyCondition()
116+
117+
/**
118+
* API Key visibility for the Pi block.
119+
*
120+
* Create PR hands the model key to the sandbox as an environment variable, so
121+
* Sim never supplies a hosted key there — the field is shown for every model,
122+
* including ones that are hosted elsewhere in Sim. Review Code and Local Dev
123+
* keep the model client inside Sim, so they follow the standard hosted-model
124+
* rule and hide the field when Sim covers the key.
125+
*/
126+
const piApiKeyCondition = (values?: Record<string, unknown>) =>
127+
isPiByokOnlyMode(values?.mode) ? CLOUD : hostedModelApiKeyCondition(values)
128+
113129
export const PiBlock: BlockConfig<PiResponse> = {
114130
type: 'pi',
115131
name: 'Pi Coding Agent',
@@ -122,7 +138,7 @@ export const PiBlock: BlockConfig<PiResponse> = {
122138
- Enable Babysit Mode on Create PR when trusted review bots and required checks should be monitored and fixed in bounded rounds.
123139
- Use Review Code to analyze an existing PR and leave summary + inline review comments.
124140
- Use Local Dev to edit a repo on your own machine; expose the machine on a public hostname/tunnel so Sim can reach it over SSH.
125-
- Create PR requires your own provider API key because the model runs in the sandbox. Review Code keeps the model key in Sim and can use either BYOK or a hosted key.
141+
- Create PR requires your own provider API key for every model, including ones Sim hosts, because the model runs in the sandbox. Review Code and Local Dev keep the model key in Sim and can use either BYOK or a hosted key.
126142
- Internet Search is off by default and always needs your own key for the selected provider, entered on the block. There is no workspace BYOK fallback and no hosted key. Leave it on None unless the task genuinely needs external information.
127143
`,
128144
category: 'blocks',
@@ -178,8 +194,15 @@ export const PiBlock: BlockConfig<PiResponse> = {
178194
options: getPiModelOptions,
179195
commandSearchable: true,
180196
},
181-
182-
...getProviderCredentialSubBlocks(),
197+
...getProviderCredentialSubBlocks().map((subBlock) =>
198+
subBlock.id === 'apiKey'
199+
? {
200+
...subBlock,
201+
placeholder: 'Enter your API key for the selected model',
202+
condition: piApiKeyCondition,
203+
}
204+
: subBlock
205+
),
183206

184207
{
185208
id: 'searchProvider',
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/**
2+
* @vitest-environment node
3+
*/
4+
import { resetEnvFlagsMock, setEnvFlags } from '@sim/testing'
5+
import { afterAll, afterEach, beforeEach, describe, expect, it } from 'vitest'
6+
import { evaluateSubBlockCondition } from '@/lib/workflows/subblocks/visibility'
7+
import { PiBlock } from '@/blocks/blocks/pi'
8+
import { getHostedModels } from '@/providers/utils'
9+
10+
const apiKeySubBlock = PiBlock.subBlocks.find((subBlock) => subBlock.id === 'apiKey')
11+
const hostedModel = getHostedModels()[0]
12+
13+
function isApiKeyVisible(values: Record<string, unknown>): boolean {
14+
return evaluateSubBlockCondition(apiKeySubBlock?.condition, values)
15+
}
16+
17+
describe('Pi API Key visibility', () => {
18+
beforeEach(() => {
19+
setEnvFlags({ isHosted: true })
20+
})
21+
22+
afterEach(() => {
23+
setEnvFlags({ isHosted: false })
24+
})
25+
26+
afterAll(resetEnvFlagsMock)
27+
28+
it('exposes an apiKey subblock that is required when visible', () => {
29+
expect(apiKeySubBlock).toBeDefined()
30+
expect(apiKeySubBlock?.required).toBe(true)
31+
})
32+
33+
// The bug this guards: the field used to hide for hosted models in every mode,
34+
// and Create PR then failed at execution demanding a BYOK key the user was
35+
// never asked for.
36+
it('shows the field in Create PR even for a model Sim hosts', () => {
37+
expect(hostedModel).toBeDefined()
38+
expect(isApiKeyVisible({ mode: 'cloud', model: hostedModel })).toBe(true)
39+
})
40+
41+
it('shows the field in Create PR for a model Sim does not host', () => {
42+
expect(isApiKeyVisible({ mode: 'cloud', model: 'some-unhosted-model' })).toBe(true)
43+
})
44+
45+
it.each([['local'], ['cloud_review']])(
46+
'hides the field in %s mode for a model Sim hosts',
47+
(mode) => {
48+
expect(isApiKeyVisible({ mode, model: hostedModel })).toBe(false)
49+
}
50+
)
51+
52+
it.each([['local'], ['cloud_review']])(
53+
'shows the field in %s mode for a model Sim does not host',
54+
(mode) => {
55+
expect(isApiKeyVisible({ mode, model: 'some-unhosted-model' })).toBe(true)
56+
}
57+
)
58+
})

apps/sim/executor/handlers/pi/keys.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import type { PiSupportedProvider } from '@/providers/pi-provider-configs'
1818
import {
1919
getPiProviderApiKeyEnvVar,
2020
getPiWorkspaceBYOKProviderId,
21+
isPiByokOnlyMode,
2122
isPiSupportedProvider,
2223
} from '@/providers/pi-providers'
2324

@@ -45,7 +46,7 @@ export async function resolvePiModelKey(params: ResolvePiModelKeyParams): Promis
4546
return { apiKey: params.apiKey, isBYOK: true }
4647
}
4748

48-
if (params.mode === 'cloud') {
49+
if (isPiByokOnlyMode(params.mode)) {
4950
const workspaceBYOKProviderId = getPiWorkspaceBYOKProviderId(providerId)
5051
if (params.workspaceId && workspaceBYOKProviderId) {
5152
const byok = await getBYOKKey(params.workspaceId, workspaceBYOKProviderId)

0 commit comments

Comments
 (0)