Date: Tue, 1 Sep 2026 12:45:22 -0700
Subject: [PATCH 2/3] fix(demo): validate Cal embed configuration
---
.../demo-scheduler/demo-scheduler.test.tsx | 25 +++++++++-
.../demo-scheduler/demo-scheduler.tsx | 49 ++++++++++++++++---
2 files changed, 65 insertions(+), 9 deletions(-)
diff --git a/apps/sim/app/(landing)/demo/components/demo-scheduler/demo-scheduler.test.tsx b/apps/sim/app/(landing)/demo/components/demo-scheduler/demo-scheduler.test.tsx
index 4a01329ee9c..2f0711f0091 100644
--- a/apps/sim/app/(landing)/demo/components/demo-scheduler/demo-scheduler.test.tsx
+++ b/apps/sim/app/(landing)/demo/components/demo-scheduler/demo-scheduler.test.tsx
@@ -28,6 +28,7 @@ vi.mock('@/lib/consent/tracking-consent', () => ({
import {
DemoScheduler,
preloadCalEmbed,
+ resolveCalEmbedConfig,
} from '@/app/(landing)/demo/components/demo-scheduler/demo-scheduler'
const LEAD = {
@@ -70,7 +71,9 @@ describe('DemoScheduler', () => {
expect.objectContaining({
namespace: 'demo',
calLink: 'team/sim/demo',
- style: { width: '100%', height: '100%', overflow: 'auto' },
+ calOrigin: 'https://app.cal.com',
+ embedJsUrl: 'https://app.cal.com/embed/embed.js',
+ className: 'size-full overflow-auto',
config: {
name: LEAD.name,
email: LEAD.email,
@@ -146,8 +149,26 @@ describe('DemoScheduler', () => {
})
expect(mockGetCalApi).toHaveBeenCalledOnce()
- expect(mockGetCalApi).toHaveBeenCalledWith({ namespace: 'demo' })
+ expect(mockGetCalApi).toHaveBeenCalledWith({
+ namespace: 'demo',
+ embedJsUrl: 'https://app.cal.com/embed/embed.js',
+ })
expect(mockCal).toHaveBeenCalledOnce()
expect(mockCal).toHaveBeenCalledWith('preload', { calLink: 'team/sim/demo' })
})
+
+ it('falls back from malformed Cal configuration and preserves valid custom origins', () => {
+ expect(resolveCalEmbedConfig('javascript:alert(1)')).toEqual({
+ calLink: 'team/sim/demo',
+ calOrigin: 'https://app.cal.com',
+ embedJsUrl: 'https://app.cal.com/embed/embed.js',
+ })
+ expect(resolveCalEmbedConfig('https://book.example.com/team/demo?theme=light#ignored')).toEqual(
+ {
+ calLink: 'team/demo?theme=light',
+ calOrigin: 'https://book.example.com',
+ embedJsUrl: 'https://book.example.com/embed/embed.js',
+ }
+ )
+ })
})
diff --git a/apps/sim/app/(landing)/demo/components/demo-scheduler/demo-scheduler.tsx b/apps/sim/app/(landing)/demo/components/demo-scheduler/demo-scheduler.tsx
index bdfcad5ad72..682c1990987 100644
--- a/apps/sim/app/(landing)/demo/components/demo-scheduler/demo-scheduler.tsx
+++ b/apps/sim/app/(landing)/demo/components/demo-scheduler/demo-scheduler.tsx
@@ -8,8 +8,41 @@ import { useTrackingConsent } from '@/lib/consent/tracking-consent'
import type { DemoLead } from '@/app/(landing)/demo/components/demo-form'
const CAL_NAMESPACE = 'demo'
-/** The Cal.com event the demo books - set `NEXT_PUBLIC_CAL_LINK` to override. */
-const CAL_LINK = process.env.NEXT_PUBLIC_CAL_LINK ?? 'team/sim/demo'
+const DEFAULT_CAL_ORIGIN = 'https://app.cal.com'
+const DEFAULT_CAL_LINK = 'team/sim/demo'
+
+interface CalEmbedConfig {
+ calLink: string
+ calOrigin: string
+ embedJsUrl: string
+}
+
+function parseCalEmbedConfig(link: string): CalEmbedConfig {
+ const url = new URL(link.replace(/^\/+/, ''), `${DEFAULT_CAL_ORIGIN}/`)
+ if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password) {
+ throw new Error('Cal link must use HTTP(S) without embedded credentials')
+ }
+
+ const calLink = `${url.pathname.replace(/^\/+/, '')}${url.search}`
+ if (!calLink) throw new Error('Cal link must include an event path')
+
+ return {
+ calLink,
+ calOrigin: url.origin,
+ embedJsUrl: `${url.origin}/embed/embed.js`,
+ }
+}
+
+/** Resolves the configured booker, falling back safely when the environment value is invalid. */
+export function resolveCalEmbedConfig(configuredLink?: string): CalEmbedConfig {
+ try {
+ return parseCalEmbedConfig(configuredLink?.trim() || DEFAULT_CAL_LINK)
+ } catch {
+ return parseCalEmbedConfig(DEFAULT_CAL_LINK)
+ }
+}
+
+const CAL_EMBED = resolveCalEmbedConfig(process.env.NEXT_PUBLIC_CAL_LINK)
/**
* Sim's brand color, matching the `--brand-agent` token. The embed renders in a
@@ -37,9 +70,9 @@ let calEmbedPreloaded = false
export function preloadCalEmbed(): void {
if (calEmbedPreloaded) return
calEmbedPreloaded = true
- getCalApi({ namespace: CAL_NAMESPACE })
+ getCalApi({ namespace: CAL_NAMESPACE, embedJsUrl: CAL_EMBED.embedJsUrl })
.then((cal) => {
- cal('preload', { calLink: CAL_LINK })
+ cal('preload', { calLink: CAL_EMBED.calLink })
})
.catch(() => {
calEmbedPreloaded = false
@@ -71,7 +104,7 @@ export function DemoScheduler({ lead }: DemoSchedulerProps) {
}
if (marketing) window.twq?.('event', X_DEMO_BOOKED_EVENT_ID, {})
}
- const api = getCalApi({ namespace: CAL_NAMESPACE })
+ const api = getCalApi({ namespace: CAL_NAMESPACE, embedJsUrl: CAL_EMBED.embedJsUrl })
api
.then((cal) => {
if (cancelled) return
@@ -104,8 +137,10 @@ export function DemoScheduler({ lead }: DemoSchedulerProps) {
Date: Tue, 1 Sep 2026 13:25:08 -0700
Subject: [PATCH 3/3] fix(deps): preserve runtime compatibility
---
apps/sim/blocks/pi-model-options.test.ts | 19 +-
.../executor/handlers/pi/core/pi-sdk.test.ts | 2 -
apps/sim/executor/handlers/pi/core/pi-sdk.ts | 2 -
.../executor/handlers/pi/pi-handler.test.ts | 13 ++
apps/sim/lib/internal/file/parser.test.ts | 65 ++++--
apps/sim/lib/internal/file/parser.ts | 21 +-
apps/sim/lib/internal/mysql/client.test.ts | 60 +++++-
apps/sim/lib/internal/mysql/client.ts | 5 +
apps/sim/lib/media/ffmpeg.test.ts | 7 +
apps/sim/lib/media/ffmpeg.ts | 5 +-
apps/sim/package.json | 4 +-
.../providers/pi-model-catalog.generated.ts | 191 +++++-------------
apps/sim/providers/pi-providers.test.ts | 8 +
apps/sim/scripts/pi-sandbox-packages.ts | 4 +-
bun.lock | 36 ++--
15 files changed, 233 insertions(+), 209 deletions(-)
diff --git a/apps/sim/blocks/pi-model-options.test.ts b/apps/sim/blocks/pi-model-options.test.ts
index ac1fb08274f..f0e2201e554 100644
--- a/apps/sim/blocks/pi-model-options.test.ts
+++ b/apps/sim/blocks/pi-model-options.test.ts
@@ -13,7 +13,15 @@ const originalOpenRouterModels = useProvidersStore.getState().providers.openrout
describe('Pi model options', () => {
beforeAll(() => {
const store = useProvidersStore.getState()
- store.setProviderModels('base', ['claude-sonnet-4-6', 'claude-sonnet-4-0', 'gpt-5.4'])
+ store.setProviderModels('base', [
+ 'claude-sonnet-4-6',
+ 'claude-opus-4-1',
+ 'claude-sonnet-4-0',
+ 'gpt-5.4',
+ 'cerebras/zai-glm-4.7',
+ 'glm-5.1',
+ 'glm-4.5-air',
+ ])
store.setProviderModels('openrouter', [
'openrouter/openai/gpt-5',
'openrouter/openrouter/fusion',
@@ -43,6 +51,15 @@ describe('Pi model options', () => {
expect(modelIds).not.toContain('claude-sonnet-4-0')
})
+ it('keeps persisted and selectable models available', () => {
+ const modelIds = getPiModelOptions().map(({ id }) => id)
+
+ expect(modelIds).toContain('claude-opus-4-1')
+ expect(modelIds).toContain('cerebras/zai-glm-4.7')
+ expect(modelIds).toContain('glm-5.1')
+ expect(modelIds).toContain('glm-4.5-air')
+ })
+
it("does not apply OpenRouter capability filters beyond Pi's catalog", () => {
const modelIds = getPiModelOptions().map(({ id }) => id)
diff --git a/apps/sim/executor/handlers/pi/core/pi-sdk.test.ts b/apps/sim/executor/handlers/pi/core/pi-sdk.test.ts
index e5644c94271..59da66ee33c 100644
--- a/apps/sim/executor/handlers/pi/core/pi-sdk.test.ts
+++ b/apps/sim/executor/handlers/pi/core/pi-sdk.test.ts
@@ -21,9 +21,7 @@ describe('createSealedPiResourceLoader', () => {
})
expect(loader.getAgentsFiles()).toEqual({ agentsFiles: [] })
expect(loader.getSystemPrompt()).toBe('sealed system prompt')
- expect(loader.getSystemPromptSource()).toBeUndefined()
expect(loader.getAppendSystemPrompt()).toEqual([])
- expect(loader.getAppendSystemPromptSources()).toEqual([])
await expect(loader.reload()).resolves.toBeUndefined()
})
})
diff --git a/apps/sim/executor/handlers/pi/core/pi-sdk.ts b/apps/sim/executor/handlers/pi/core/pi-sdk.ts
index 317f3b618ff..30e43c61b54 100644
--- a/apps/sim/executor/handlers/pi/core/pi-sdk.ts
+++ b/apps/sim/executor/handlers/pi/core/pi-sdk.ts
@@ -90,9 +90,7 @@ export function createSealedPiResourceLoader(sdk: PiSdk, systemPrompt: string):
getThemes: () => ({ themes: [], diagnostics: [] }),
getAgentsFiles: () => ({ agentsFiles: [] }),
getSystemPrompt: () => systemPrompt,
- getSystemPromptSource: () => undefined,
getAppendSystemPrompt: () => [],
- getAppendSystemPromptSources: () => [],
extendResources: () => {},
reload: async () => {},
}
diff --git a/apps/sim/executor/handlers/pi/pi-handler.test.ts b/apps/sim/executor/handlers/pi/pi-handler.test.ts
index d6dcc62fb41..ff299764613 100644
--- a/apps/sim/executor/handlers/pi/pi-handler.test.ts
+++ b/apps/sim/executor/handlers/pi/pi-handler.test.ts
@@ -267,6 +267,19 @@ describe('PiBlockHandler', () => {
expect(mockResolveKey).not.toHaveBeenCalled()
})
+ it('passes a persisted catalog model to the backend', async () => {
+ mockGetProviderFromModel.mockReturnValue('cerebras')
+ mockResolvePiModelId.mockReturnValue('zai-glm-4.7')
+
+ await handler.execute(ctx(), block, localInputs({ model: 'cerebras/zai-glm-4.7' }))
+
+ expect(mockRunLocal.mock.calls[0][0]).toMatchObject({
+ model: 'cerebras/zai-glm-4.7',
+ piModel: 'zai-glm-4.7',
+ providerId: 'cerebras',
+ })
+ })
+
it('routes Local Dev to the local backend with SSH params', async () => {
const output = await handler.execute(ctx(), block, localInputs())
expect(mockRunLocal).toHaveBeenCalledTimes(1)
diff --git a/apps/sim/lib/internal/file/parser.test.ts b/apps/sim/lib/internal/file/parser.test.ts
index 5ebea8ee5b2..dc091099550 100644
--- a/apps/sim/lib/internal/file/parser.test.ts
+++ b/apps/sim/lib/internal/file/parser.test.ts
@@ -3,6 +3,8 @@
*
* @vitest-environment node
*/
+
+import { Readable } from 'node:stream'
import {
authMockFns,
createMockRequest,
@@ -24,15 +26,15 @@ const {
mockGetStorageProvider,
mockIsUsingCloudStorage,
mockIsSupportedFileType,
- mockParseFile,
mockParseBuffer,
mockPdfParseBuffer,
+ mockCreateReadStream,
mockFsAccess,
mockFsStat,
- mockFsReadFile,
mockFsWriteFile,
mockJoin,
actualPath,
+ mockUploadExecutionFile,
mockUploadWorkspaceFile,
mockReadWorkspaceFileNameByKey,
} = vi.hoisted(() => {
@@ -44,10 +46,6 @@ const {
mockGetStorageProvider: vi.fn().mockReturnValue('s3'),
mockIsUsingCloudStorage: vi.fn().mockReturnValue(true),
mockIsSupportedFileType: vi.fn().mockReturnValue(true),
- mockParseFile: vi.fn().mockResolvedValue({
- content: 'parsed content',
- metadata: { pageCount: 1 },
- }),
mockParseBuffer: vi.fn().mockResolvedValue({
content: 'parsed buffer content',
metadata: { pageCount: 1 },
@@ -56,9 +54,9 @@ const {
content: 'parsed PDF content',
metadata: { pageCount: 1 },
}),
+ mockCreateReadStream: vi.fn(),
mockFsAccess: vi.fn().mockResolvedValue(undefined),
mockFsStat: vi.fn().mockImplementation(() => ({ isFile: () => true, size: 17 })),
- mockFsReadFile: vi.fn().mockResolvedValue(Buffer.from('test file content')),
mockFsWriteFile: vi.fn().mockResolvedValue(undefined),
mockJoin: vi.fn((...args: string[]): string => {
if (args[0] === '/test/uploads') {
@@ -67,6 +65,7 @@ const {
return actualPath.join(...args)
}),
actualPath,
+ mockUploadExecutionFile: vi.fn(),
mockUploadWorkspaceFile: vi
.fn()
.mockImplementation(
@@ -98,10 +97,13 @@ vi.mock('@/lib/uploads', () => ({
vi.mock('@/lib/file-parsers', () => ({
isSupportedFileType: mockIsSupportedFileType,
- parseFile: mockParseFile,
parseBuffer: mockParseBuffer,
}))
+vi.mock('node:fs', () => ({
+ createReadStream: mockCreateReadStream,
+}))
+
vi.mock('@/lib/file-parsers/pdf-parser', () => ({
PdfParser: class {
parseBuffer(...args: Parameters) {
@@ -131,7 +133,7 @@ vi.mock('@/lib/core/utils/logging', () => ({
}))
vi.mock('@/lib/uploads/contexts/execution', () => ({
- uploadExecutionFile: vi.fn(),
+ uploadExecutionFile: mockUploadExecutionFile,
}))
vi.mock('@/lib/uploads/contexts/workspace/workspace-file-manager', () => ({
@@ -152,12 +154,10 @@ vi.mock('fs/promises', () => ({
default: {
access: mockFsAccess,
stat: mockFsStat,
- readFile: mockFsReadFile,
writeFile: mockFsWriteFile,
},
access: mockFsAccess,
stat: mockFsStat,
- readFile: mockFsReadFile,
writeFile: mockFsWriteFile,
}))
@@ -241,14 +241,19 @@ describe('file parser operation', () => {
storageServiceMockFns.mockHasCloudStorage.mockReturnValue(true)
storageServiceMockFns.mockDownloadFile.mockResolvedValue(Buffer.from('test file content'))
mockFsStat.mockResolvedValue({ isFile: () => true, size: 17 })
- mockFsReadFile.mockResolvedValue(Buffer.from('test file content'))
+ mockCreateReadStream.mockImplementation(() => Readable.from([Buffer.from('test file content')]))
mockIsSupportedFileType.mockReturnValue(true)
+ mockUploadExecutionFile.mockResolvedValue({
+ id: 'file_test',
+ name: 'report.pdf',
+ url: '/api/files/serve/execution/report.pdf',
+ size: 17,
+ type: 'application/pdf',
+ key: 'execution/report.pdf',
+ context: 'execution',
+ })
mockUploadWorkspaceFile.mockClear()
mockReadWorkspaceFileNameByKey.mockResolvedValue({ name: null })
- mockParseFile.mockResolvedValue({
- content: 'parsed content',
- metadata: { pageCount: 1 },
- })
mockParseBuffer.mockResolvedValue({
content: 'parsed buffer content',
metadata: { pageCount: 1 },
@@ -422,21 +427,39 @@ describe('file parser operation', () => {
})
})
- it('forwards request cancellation to local file parsing and reads', async () => {
+ it('parses and uploads one bounded local-file snapshot', async () => {
setupFileApiMocks({
cloudEnabled: false,
storageProvider: 'local',
authenticated: true,
})
+ mockFsStat.mockResolvedValue({ isFile: () => true, size: 3 })
const req = createMockRequest('POST', {
filePath: 'workspace/report.pdf',
})
const response = await POST(req)
+ const data = await response.json()
+ const parsedBuffer = mockParseBuffer.mock.calls[0][0]
+
expect(response.status).toBe(200)
- expect(mockParseFile).toHaveBeenCalledWith(expect.any(String), { signal: req.signal })
- expect(mockFsReadFile).toHaveBeenCalledWith(expect.any(String), { signal: req.signal })
+ expect(data.output).toMatchObject({
+ content: 'parsed buffer content',
+ fileType: 'application/pdf',
+ size: 17,
+ })
+ expect(mockCreateReadStream).toHaveBeenCalledWith('/test/uploads/workspace/report.pdf')
+ expect(mockCreateReadStream).toHaveBeenCalledOnce()
+ expect(mockParseBuffer).toHaveBeenCalledWith(parsedBuffer, 'pdf', { signal: req.signal })
+ expect(mockParseBuffer).toHaveBeenCalledOnce()
+ expect(mockUploadExecutionFile).toHaveBeenCalledWith(
+ expect.any(Object),
+ parsedBuffer,
+ 'report.pdf',
+ 'application/pdf',
+ 'test-user-id'
+ )
})
it('should reject parser complexity limits instead of returning raw text', async () => {
@@ -740,7 +763,9 @@ describe('file parser operation', () => {
expect(response.status).toBe(200)
expect(data.success).toBe(false)
expect(data.error).toContain('too large')
- expect(mockFsReadFile).not.toHaveBeenCalled()
+ expect(mockCreateReadStream).not.toHaveBeenCalled()
+ expect(mockParseBuffer).not.toHaveBeenCalled()
+ expect(mockUploadExecutionFile).not.toHaveBeenCalled()
})
it('should process execution file URLs with context query param', async () => {
diff --git a/apps/sim/lib/internal/file/parser.ts b/apps/sim/lib/internal/file/parser.ts
index 1f484ca4f3b..f47dc5e749f 100644
--- a/apps/sim/lib/internal/file/parser.ts
+++ b/apps/sim/lib/internal/file/parser.ts
@@ -1,3 +1,4 @@
+import { createReadStream } from 'node:fs'
import { Buffer, isUtf8 } from 'buffer'
import { createHash } from 'crypto'
import fsPromises from 'fs/promises'
@@ -10,12 +11,16 @@ import binaryExtensionsList from 'binary-extensions'
import type { ContractBody } from '@/lib/api/contracts'
import type { fileParseContract } from '@/lib/api/contracts/storage-transfer'
import { sanitizeUrlForLog } from '@/lib/core/utils/logging'
-import { assertKnownSizeWithinLimit, isPayloadSizeLimitError } from '@/lib/core/utils/stream-limits'
+import {
+ assertKnownSizeWithinLimit,
+ isPayloadSizeLimitError,
+ readNodeStreamToBufferWithLimit,
+} from '@/lib/core/utils/stream-limits'
import {
assertUserFileContentAccess,
type ExecutionMaterializationContext,
} from '@/lib/execution/payloads/materialization.server'
-import { isSupportedFileType, parseFile } from '@/lib/file-parsers'
+import { isSupportedFileType, parseBuffer } from '@/lib/file-parsers'
import { isFileParserError } from '@/lib/file-parsers/errors'
import { isUsingCloudStorage, StorageService } from '@/lib/uploads'
import { uploadExecutionFile } from '@/lib/uploads/contexts/execution'
@@ -881,13 +886,17 @@ async function handleLocalFile(
const stats = await fsPromises.stat(fullPath)
assertKnownSizeWithinLimit(stats.size, maxDownloadBytes, 'local file')
- const result = await parseFile(fullPath, { signal })
+ const fileBuffer = await readNodeStreamToBufferWithLimit(createReadStream(fullPath), {
+ maxBytes: maxDownloadBytes,
+ label: 'local file',
+ signal,
+ })
+ const extension = path.extname(filename).toLowerCase().substring(1)
+ const result = await parseBuffer(fileBuffer, extension, { signal })
const content = assertParsedContentWithinLimit(result.content, maxParsedOutputBytes)
- const fileBuffer = await fsPromises.readFile(fullPath, { signal })
signal?.throwIfAborted()
const hash = createHash('md5').update(fileBuffer).digest('hex')
- const extension = path.extname(filename).toLowerCase().substring(1)
const mimeType = fileType || getMimeTypeFromExtension(extension)
// Store file in execution storage if executionContext is provided
@@ -916,7 +925,7 @@ async function handleLocalFile(
userFile,
metadata: {
fileType: mimeType,
- size: stats.size,
+ size: fileBuffer.length,
hash,
processingTime: 0,
},
diff --git a/apps/sim/lib/internal/mysql/client.test.ts b/apps/sim/lib/internal/mysql/client.test.ts
index cbb4e9a1a71..6894a504469 100644
--- a/apps/sim/lib/internal/mysql/client.test.ts
+++ b/apps/sim/lib/internal/mysql/client.test.ts
@@ -3,18 +3,26 @@
*/
import { beforeEach, describe, expect, it, vi } from 'vitest'
-const { mockCreateConnection, mockNetConnect, mockValidateDatabaseHost } = vi.hoisted(() => ({
- mockCreateConnection: vi.fn(),
- mockNetConnect: vi.fn(),
- mockValidateDatabaseHost: vi.fn(),
-}))
+const { mockCreateConnection, mockNetConnect, mockTypedParameterNull, mockValidateDatabaseHost } =
+ vi.hoisted(() => {
+ class MockTypedParameter {}
+ return {
+ mockCreateConnection: vi.fn(),
+ mockNetConnect: vi.fn(),
+ mockTypedParameterNull: vi.fn(() => new MockTypedParameter()),
+ mockValidateDatabaseHost: vi.fn(),
+ }
+ })
vi.mock('node:net', () => ({
default: { connect: mockNetConnect },
}))
vi.mock('mysql2/promise', () => ({
- default: { createConnection: mockCreateConnection },
+ default: {
+ createConnection: mockCreateConnection,
+ TypedParameter: { NULL: mockTypedParameterNull },
+ },
}))
vi.mock('@/lib/core/security/input-validation.server', () => ({
@@ -123,4 +131,44 @@ describe('MySQL client', () => {
)
expect(connection.execute).not.toHaveBeenCalled()
})
+
+ it.each([
+ new Map([['key', 'value']]),
+ new Set(['value']),
+ /value/,
+ Object.assign(Object.create(null) as Record, { key: 'value' }),
+ ])('rejects non-plain structured bind values: %s', async (value) => {
+ const connection = { execute: vi.fn(), destroy: vi.fn() }
+
+ await expect(executeMysqlCommand(connection as never, 'SELECT ?', [value])).rejects.toThrow(
+ 'MySQL bind values must contain only supported scalar or structured values'
+ )
+ expect(connection.execute).not.toHaveBeenCalled()
+ })
+
+ it('accepts nested plain structured bind values', async () => {
+ const result = { affectedRows: 1 }
+ const values = [{ nested: ['value', 1, true, null] }]
+ const connection = {
+ execute: vi.fn().mockResolvedValue([result]),
+ destroy: vi.fn(),
+ }
+
+ await expect(executeMysqlCommand(connection as never, 'SELECT ?', values)).resolves.toBe(result)
+ expect(connection.execute).toHaveBeenCalledWith('SELECT ?', values)
+ })
+
+ it('accepts mysql2 typed parameters', async () => {
+ const result = { affectedRows: 1 }
+ const typedParameter = mockTypedParameterNull()
+ const connection = {
+ execute: vi.fn().mockResolvedValue([result]),
+ destroy: vi.fn(),
+ }
+
+ await expect(
+ executeMysqlCommand(connection as never, 'SELECT ?', [typedParameter])
+ ).resolves.toBe(result)
+ expect(connection.execute).toHaveBeenCalledWith('SELECT ?', [typedParameter])
+ })
})
diff --git a/apps/sim/lib/internal/mysql/client.ts b/apps/sim/lib/internal/mysql/client.ts
index c29c016c45c..3fd425bba5c 100644
--- a/apps/sim/lib/internal/mysql/client.ts
+++ b/apps/sim/lib/internal/mysql/client.ts
@@ -11,6 +11,8 @@ export interface MysqlConnectionConfig {
ssl: 'disabled' | 'required' | 'preferred'
}
+const MYSQL_TYPED_PARAMETER_PROTOTYPE = Object.getPrototypeOf(mysql.TypedParameter.NULL())
+
function isMysqlExecuteValue(value: unknown): value is mysql.ExecuteValues {
if (
value === null ||
@@ -24,6 +26,9 @@ function isMysqlExecuteValue(value: unknown): value is mysql.ExecuteValues {
if (Array.isArray(value)) return value.every(isMysqlExecuteValue)
if (typeof value !== 'object') return false
+ const prototype = Object.getPrototypeOf(value)
+ if (prototype === MYSQL_TYPED_PARAMETER_PROTOTYPE) return true
+ if (prototype !== Object.prototype) return false
return Object.values(value).every(isMysqlExecuteValue)
}
diff --git a/apps/sim/lib/media/ffmpeg.test.ts b/apps/sim/lib/media/ffmpeg.test.ts
index 978437bd9b8..8c0cc0dfb9a 100644
--- a/apps/sim/lib/media/ffmpeg.test.ts
+++ b/apps/sim/lib/media/ffmpeg.test.ts
@@ -92,6 +92,13 @@ describe('extFromMime', () => {
expect(extFromMime(' Video/MP4; codecs=avc1 ')).toBe('mp4')
})
+ it.each(['constructor', '__proto__', 'toString'])(
+ 'does not resolve inherited object properties as MIME types: %s',
+ (mimeType) => {
+ expect(extFromMime(mimeType)).toBe('bin')
+ }
+ )
+
it.each(['video/../../../../escaped', 'video/..\\..\\..\\..\\escaped', 'video/mp4\\..\\escaped'])(
'rejects path syntax in an unknown MIME subtype: %s',
(mimeType) => {
diff --git a/apps/sim/lib/media/ffmpeg.ts b/apps/sim/lib/media/ffmpeg.ts
index ae16df7b1fe..16c3f396250 100644
--- a/apps/sim/lib/media/ffmpeg.ts
+++ b/apps/sim/lib/media/ffmpeg.ts
@@ -182,8 +182,9 @@ const EXT_TO_MIME: Record = {
function extFromMime(mime: string): string {
const normalizedMime = mime.split(';', 1)[0].trim().toLowerCase()
- const knownExtension = MIME_TO_EXT[normalizedMime]
- if (knownExtension) return knownExtension
+ if (Object.hasOwn(MIME_TO_EXT, normalizedMime)) {
+ return MIME_TO_EXT[normalizedMime]
+ }
const subtype = normalizedMime.split('/')[1]
return subtype && /^[a-z0-9][a-z0-9.+_-]{0,63}$/.test(subtype) ? subtype : 'bin'
diff --git a/apps/sim/package.json b/apps/sim/package.json
index 26cb83df104..65c4ac25044 100644
--- a/apps/sim/package.json
+++ b/apps/sim/package.json
@@ -72,8 +72,8 @@
"@cerebras/cerebras_cloud_sdk": "^1.23.0",
"@daytona/sdk": "0.207.0",
"@e2b/code-interpreter": "2.7.1",
- "@earendil-works/pi-ai": "0.84.3",
- "@earendil-works/pi-coding-agent": "0.84.3",
+ "@earendil-works/pi-ai": "0.80.10",
+ "@earendil-works/pi-coding-agent": "0.80.10",
"@floating-ui/dom": "1.7.6",
"@google-cloud/storage": "7.21.0",
"@google/genai": "2.13.0",
diff --git a/apps/sim/providers/pi-model-catalog.generated.ts b/apps/sim/providers/pi-model-catalog.generated.ts
index ae68ab0afbf..e08acce2e65 100644
--- a/apps/sim/providers/pi-model-catalog.generated.ts
+++ b/apps/sim/providers/pi-model-catalog.generated.ts
@@ -7,12 +7,13 @@ export const PI_MODEL_IDS_BY_PROVIDER = {
'claude-fable-5',
'claude-haiku-4-5',
'claude-haiku-4-5-20251001',
+ 'claude-opus-4-1',
+ 'claude-opus-4-1-20250805',
'claude-opus-4-5',
'claude-opus-4-5-20251101',
'claude-opus-4-6',
'claude-opus-4-7',
'claude-opus-4-8',
- 'claude-opus-5',
'claude-sonnet-4-5',
'claude-sonnet-4-5-20250929',
'claude-sonnet-4-6',
@@ -31,12 +32,18 @@ export const PI_MODEL_IDS_BY_PROVIDER = {
'gpt-4o-mini',
'gpt-5',
'gpt-5-chat-latest',
+ 'gpt-5-codex',
'gpt-5-mini',
'gpt-5-nano',
'gpt-5-pro',
'gpt-5.1',
+ 'gpt-5.1-chat-latest',
+ 'gpt-5.1-codex',
+ 'gpt-5.1-codex-max',
+ 'gpt-5.1-codex-mini',
'gpt-5.2',
'gpt-5.2-chat-latest',
+ 'gpt-5.2-codex',
'gpt-5.2-pro',
'gpt-5.3-chat-latest',
'gpt-5.3-codex',
@@ -54,35 +61,31 @@ export const PI_MODEL_IDS_BY_PROVIDER = {
'o1',
'o1-pro',
'o3',
+ 'o3-deep-research',
'o3-mini',
'o3-pro',
'o4-mini',
+ 'o4-mini-deep-research',
],
google: [
- 'deep-research-max-preview-04-2026',
- 'deep-research-preview-04-2026',
- 'gemini-2.5-computer-use-preview-10-2025',
+ 'gemini-2.0-flash',
+ 'gemini-2.0-flash-lite',
'gemini-2.5-flash',
'gemini-2.5-flash-lite',
'gemini-2.5-pro',
'gemini-3-flash-preview',
+ 'gemini-3-pro-preview',
'gemini-3.1-flash-lite',
- 'gemini-3.1-flash-lite-image',
'gemini-3.1-flash-lite-preview',
- 'gemini-3.1-flash-live-preview',
'gemini-3.1-pro-preview',
'gemini-3.1-pro-preview-customtools',
'gemini-3.5-flash',
- 'gemini-3.5-flash-lite',
- 'gemini-3.6-flash',
- 'gemini-3.7-flash',
'gemini-flash-latest',
'gemini-flash-lite-latest',
- 'gemini-robotics-er-1.6-preview',
'gemma-4-26b-a4b-it',
'gemma-4-31b-it',
],
- xai: ['grok-4.3', 'grok-4.5', 'grok-4.6', 'grok-build-0.1'],
+ xai: ['grok-4.3', 'grok-4.5', 'grok-build-0.1'],
deepseek: ['deepseek-v4-flash', 'deepseek-v4-pro'],
mistral: [
'codestral-latest',
@@ -115,18 +118,19 @@ export const PI_MODEL_IDS_BY_PROVIDER = {
'open-mixtral-8x7b',
'pixtral-12b',
'pixtral-large-latest',
- 'voxtral-small-latest',
],
groq: [
'llama-3.1-8b-instant',
'llama-3.3-70b-versatile',
+ 'meta-llama/llama-4-scout-17b-16e-instruct',
'openai/gpt-oss-120b',
'openai/gpt-oss-20b',
'openai/gpt-oss-safeguard-20b',
- 'qwen/qwen3.6-27b',
+ 'qwen/qwen3-32b',
],
- cerebras: ['gemma-4-31b', 'gpt-oss-120b'],
+ cerebras: ['gemma-4-31b', 'gpt-oss-120b', 'zai-glm-4.7'],
openrouter: [
+ 'ai21/jamba-large-1.7',
'aion-labs/aion-2.0',
'aion-labs/aion-3.0',
'aion-labs/aion-3.0-mini',
@@ -137,39 +141,24 @@ export const PI_MODEL_IDS_BY_PROVIDER = {
'amazon/nova-pro-v1',
'anthropic/claude-3-haiku',
'anthropic/claude-fable-5',
- 'anthropic/claude-fable-5:batch',
'anthropic/claude-haiku-4.5',
- 'anthropic/claude-haiku-4.5:batch',
'anthropic/claude-opus-4',
'anthropic/claude-opus-4.1',
- 'anthropic/claude-opus-4.1:batch',
'anthropic/claude-opus-4.5',
- 'anthropic/claude-opus-4.5:batch',
'anthropic/claude-opus-4.6',
- 'anthropic/claude-opus-4.6:batch',
'anthropic/claude-opus-4.7',
'anthropic/claude-opus-4.7-fast',
- 'anthropic/claude-opus-4.7:batch',
'anthropic/claude-opus-4.8',
'anthropic/claude-opus-4.8-fast',
- 'anthropic/claude-opus-4.8:batch',
- 'anthropic/claude-opus-5',
- 'anthropic/claude-opus-5-fast',
- 'anthropic/claude-opus-5:batch',
'anthropic/claude-sonnet-4',
'anthropic/claude-sonnet-4.5',
- 'anthropic/claude-sonnet-4.5:batch',
'anthropic/claude-sonnet-4.6',
- 'anthropic/claude-sonnet-4.6:batch',
'anthropic/claude-sonnet-5',
- 'anthropic/claude-sonnet-5:batch',
'arcee-ai/trinity-large-thinking',
'arcee-ai/virtuoso-large',
'auto',
'bytedance-seed/seed-1.6',
'bytedance-seed/seed-1.6-flash',
- 'bytedance-seed/seed-2-1-turbo',
- 'bytedance-seed/seed-2.0-code',
'bytedance-seed/seed-2.0-lite',
'bytedance-seed/seed-2.0-mini',
'cohere/command-r-08-2024',
@@ -184,36 +173,19 @@ export const PI_MODEL_IDS_BY_PROVIDER = {
'deepseek/deepseek-v3.2',
'deepseek/deepseek-v3.2-exp',
'deepseek/deepseek-v4-flash',
- 'deepseek/deepseek-v4-flash-0731',
- 'deepseek/deepseek-v4-flash-vision-exp',
'deepseek/deepseek-v4-pro',
- 'deepseek/deepseek-v4-pro-0813',
- 'dots-studio/dots-3-note-preview:free',
'google/gemini-2.5-flash',
'google/gemini-2.5-flash-lite',
- 'google/gemini-2.5-flash-lite:batch',
- 'google/gemini-2.5-flash:batch',
'google/gemini-2.5-pro',
'google/gemini-2.5-pro-preview',
'google/gemini-2.5-pro-preview-05-06',
- 'google/gemini-2.5-pro:batch',
'google/gemini-3-flash-preview',
- 'google/gemini-3-flash-preview:batch',
'google/gemini-3-pro-image',
'google/gemini-3.1-flash-lite',
'google/gemini-3.1-flash-lite-preview',
- 'google/gemini-3.1-flash-lite:batch',
'google/gemini-3.1-pro-preview',
'google/gemini-3.1-pro-preview-customtools',
- 'google/gemini-3.1-pro-preview:batch',
'google/gemini-3.5-flash',
- 'google/gemini-3.5-flash-lite',
- 'google/gemini-3.5-flash-lite:batch',
- 'google/gemini-3.5-flash:batch',
- 'google/gemini-3.6-flash',
- 'google/gemini-3.6-flash:batch',
- 'google/gemini-3.7-flash',
- 'google/gemini-3.7-flash:batch',
'google/gemma-3-12b-it',
'google/gemma-3-27b-it',
'google/gemma-4-26b-a4b-it',
@@ -222,29 +194,27 @@ export const PI_MODEL_IDS_BY_PROVIDER = {
'google/gemma-4-31b-it:free',
'ibm-granite/granite-4.1-8b',
'inception/mercury-2',
- 'inclusionai/ling-3.0-flash',
+ 'inclusionai/ling-2.6-1t',
+ 'inclusionai/ling-2.6-flash',
+ 'inclusionai/ring-2.6-1t',
'kwaipilot/kat-coder-air-v2.5',
'kwaipilot/kat-coder-pro-v2',
'kwaipilot/kat-coder-pro-v2.5',
- 'liquid/lfm-2.5-2.6b:free',
- 'meituan/longcat-2.0',
'meta-llama/llama-3.1-70b-instruct',
'meta-llama/llama-3.1-8b-instruct',
'meta-llama/llama-3.3-70b-instruct',
+ 'meta-llama/llama-3.3-70b-instruct:free',
'meta-llama/llama-4-maverick',
'meta-llama/llama-4-scout',
- 'meta/muse-glimmer-30b',
'meta/muse-spark-1.1',
- 'meta/muse-spark-1.2',
- 'meta/muse-spark-1.2-contributor',
'minimax/minimax-m1',
'minimax/minimax-m2',
'minimax/minimax-m2.1',
'minimax/minimax-m2.5',
'minimax/minimax-m2.7',
'minimax/minimax-m3',
- 'minimax/minimax-m3:batch',
'mistralai/codestral-2508',
+ 'mistralai/devstral-2512',
'mistralai/ministral-14b-2512',
'mistralai/ministral-3b-2512',
'mistralai/ministral-8b-2512',
@@ -266,115 +236,83 @@ export const PI_MODEL_IDS_BY_PROVIDER = {
'moonshotai/kimi-k2.5',
'moonshotai/kimi-k2.6',
'moonshotai/kimi-k2.7-code',
- 'moonshotai/kimi-k2.7-code:batch',
'moonshotai/kimi-k3',
'nex-agi/nex-n2-mini',
'nex-agi/nex-n2-pro',
+ 'nvidia/llama-3.3-nemotron-super-49b-v1.5',
'nvidia/nemotron-3-nano-30b-a3b',
'nvidia/nemotron-3-nano-30b-a3b:free',
'nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free',
'nvidia/nemotron-3-super-120b-a12b',
'nvidia/nemotron-3-super-120b-a12b:free',
'nvidia/nemotron-3-ultra-550b-a55b',
- 'nvidia/nemotron-3-ultra-550b-a55b:batch',
'nvidia/nemotron-3-ultra-550b-a55b:free',
- 'nvidia/nemotron-3.5-lightning',
- 'nvidia/nemotron-3.5-lightning:free',
'nvidia/nemotron-nano-12b-v2-vl:free',
'nvidia/nemotron-nano-9b-v2:free',
'openai/gpt-3.5-turbo',
'openai/gpt-3.5-turbo-0613',
'openai/gpt-3.5-turbo-16k',
- 'openai/gpt-3.5-turbo:batch',
'openai/gpt-4',
'openai/gpt-4-turbo',
'openai/gpt-4-turbo-preview',
- 'openai/gpt-4-turbo:batch',
'openai/gpt-4.1',
'openai/gpt-4.1-mini',
- 'openai/gpt-4.1-mini:batch',
'openai/gpt-4.1-nano',
- 'openai/gpt-4.1-nano:batch',
- 'openai/gpt-4.1:batch',
'openai/gpt-4o',
'openai/gpt-4o-2024-05-13',
'openai/gpt-4o-2024-08-06',
'openai/gpt-4o-2024-11-20',
'openai/gpt-4o-mini',
'openai/gpt-4o-mini-2024-07-18',
- 'openai/gpt-4o-mini:batch',
- 'openai/gpt-4o:batch',
'openai/gpt-5',
- 'openai/gpt-5-codex:batch',
+ 'openai/gpt-5-codex',
'openai/gpt-5-mini',
- 'openai/gpt-5-mini:batch',
'openai/gpt-5-nano',
- 'openai/gpt-5-nano:batch',
'openai/gpt-5-pro',
- 'openai/gpt-5-pro:batch',
'openai/gpt-5.1',
+ 'openai/gpt-5.1-chat',
'openai/gpt-5.1-codex',
'openai/gpt-5.1-codex-max',
'openai/gpt-5.1-codex-mini',
- 'openai/gpt-5.1:batch',
'openai/gpt-5.2',
'openai/gpt-5.2-chat',
'openai/gpt-5.2-codex',
'openai/gpt-5.2-pro',
- 'openai/gpt-5.2-pro:batch',
- 'openai/gpt-5.2:batch',
+ 'openai/gpt-5.3-chat',
'openai/gpt-5.3-codex',
'openai/gpt-5.4',
'openai/gpt-5.4-mini',
- 'openai/gpt-5.4-mini:batch',
'openai/gpt-5.4-nano',
- 'openai/gpt-5.4-nano:batch',
'openai/gpt-5.4-pro',
- 'openai/gpt-5.4-pro:batch',
- 'openai/gpt-5.4:batch',
'openai/gpt-5.5',
'openai/gpt-5.5-pro',
- 'openai/gpt-5.5-pro:batch',
- 'openai/gpt-5.5:batch',
'openai/gpt-5.6-luna',
'openai/gpt-5.6-luna-pro',
- 'openai/gpt-5.6-luna-pro:batch',
- 'openai/gpt-5.6-luna:batch',
'openai/gpt-5.6-sol',
'openai/gpt-5.6-sol-pro',
- 'openai/gpt-5.6-sol-pro:batch',
- 'openai/gpt-5.6-sol:batch',
'openai/gpt-5.6-terra',
'openai/gpt-5.6-terra-pro',
- 'openai/gpt-5.6-terra-pro:batch',
- 'openai/gpt-5.6-terra:batch',
- 'openai/gpt-5:batch',
'openai/gpt-audio',
'openai/gpt-audio-mini',
'openai/gpt-chat-latest',
'openai/gpt-oss-120b',
'openai/gpt-oss-20b',
+ 'openai/gpt-oss-20b:free',
'openai/gpt-oss-safeguard-20b',
'openai/o1',
- 'openai/o1:batch',
'openai/o3',
+ 'openai/o3-deep-research',
'openai/o3-mini',
'openai/o3-mini-high',
- 'openai/o3-mini-high:batch',
- 'openai/o3-mini:batch',
'openai/o3-pro',
- 'openai/o3-pro:batch',
- 'openai/o3:batch',
'openai/o4-mini',
+ 'openai/o4-mini-deep-research',
'openai/o4-mini-high',
- 'openai/o4-mini-high:batch',
- 'openai/o4-mini:batch',
'openrouter/auto',
- 'openrouter/auto-beta',
'openrouter/free',
'openrouter/fusion',
- 'poolside/laguna-s-2.1',
- 'poolside/laguna-s-2.1:free',
+ 'poolside/laguna-m.1',
+ 'poolside/laguna-m.1:free',
'poolside/laguna-xs-2.1',
'poolside/laguna-xs-2.1:free',
'qwen/qwen-2.5-72b-instruct',
@@ -396,9 +334,11 @@ export const PI_MODEL_IDS_BY_PROVIDER = {
'qwen/qwen3-coder-flash',
'qwen/qwen3-coder-next',
'qwen/qwen3-coder-plus',
+ 'qwen/qwen3-coder:free',
'qwen/qwen3-max',
'qwen/qwen3-max-thinking',
'qwen/qwen3-next-80b-a3b-instruct',
+ 'qwen/qwen3-next-80b-a3b-instruct:free',
'qwen/qwen3-next-80b-a3b-thinking',
'qwen/qwen3-vl-235b-a22b-instruct',
'qwen/qwen3-vl-235b-a22b-thinking',
@@ -420,34 +360,22 @@ export const PI_MODEL_IDS_BY_PROVIDER = {
'qwen/qwen3.6-flash',
'qwen/qwen3.6-max-preview',
'qwen/qwen3.6-plus',
- 'qwen/qwen3.7-flash',
'qwen/qwen3.7-max',
'qwen/qwen3.7-plus',
- 'qwen/qwen3.8-2.4t-a95b',
- 'qwen/qwen3.8-27b',
- 'qwen/qwen3.8-max',
'rekaai/reka-edge',
'relace/relace-search',
'sakana/fugu-ultra',
- 'sakana/sakana-namazu',
'sao10k/l3.1-euryale-70b',
- 'stealth/ox-alpha',
'stepfun/step-3.5-flash',
'stepfun/step-3.7-flash',
'tencent/hy3',
'tencent/hy3-preview',
+ 'tencent/hy3:free',
'thedrummer/unslopnemo-12b',
- 'thinkingmachines/inkling',
- 'thinkingmachines/inkling-small',
- 'thinkingmachines/inkling-small:free',
- 'thinkingmachines/inkling:batch',
- 'thinkingmachines/inkling:free',
'upstage/solar-pro-3',
- 'upstage/solar-pro4',
'x-ai/grok-4.20',
'x-ai/grok-4.3',
'x-ai/grok-4.5',
- 'x-ai/grok-4.6',
'x-ai/grok-build-0.1',
'xiaomi/mimo-v2.5',
'xiaomi/mimo-v2.5-pro',
@@ -462,104 +390,81 @@ export const PI_MODEL_IDS_BY_PROVIDER = {
'z-ai/glm-5-turbo',
'z-ai/glm-5.1',
'z-ai/glm-5.2',
- 'z-ai/glm-5.2:batch',
- 'z-ai/glm-5.2:free',
- 'z-ai/glm-5.3',
'z-ai/glm-5v-turbo',
'~anthropic/claude-fable-latest',
'~anthropic/claude-haiku-latest',
'~anthropic/claude-opus-latest',
'~anthropic/claude-sonnet-latest',
- '~deepseek/deepseek-v4-flash-latest',
'~google/gemini-flash-latest',
'~google/gemini-pro-latest',
'~moonshotai/kimi-latest',
'~openai/gpt-latest',
'~openai/gpt-mini-latest',
'~x-ai/grok-latest',
- '~z-ai/glm-latest',
],
fireworks: [
'accounts/fireworks/models/deepseek-v4-flash',
- 'accounts/fireworks/models/deepseek-v4-flash-0731',
'accounts/fireworks/models/deepseek-v4-pro',
- 'accounts/fireworks/models/deepseek-v4-pro-0813',
+ 'accounts/fireworks/models/glm-5p1',
'accounts/fireworks/models/glm-5p2',
'accounts/fireworks/models/gpt-oss-120b',
'accounts/fireworks/models/gpt-oss-20b',
- 'accounts/fireworks/models/inkling',
'accounts/fireworks/models/kimi-k2p6',
'accounts/fireworks/models/kimi-k2p7-code',
- 'accounts/fireworks/models/kimi-k3',
'accounts/fireworks/models/minimax-m2p7',
'accounts/fireworks/models/minimax-m3',
- 'accounts/fireworks/models/muse-glimmer-30b',
- 'accounts/fireworks/models/nemotron-3-ultra-nvfp4',
- 'accounts/fireworks/models/nemotron-lightning-3p5-30b-a3b',
'accounts/fireworks/models/qwen3p7-plus',
- 'accounts/fireworks/models/qwen3p8-max',
+ 'accounts/fireworks/routers/glm-5p1-fast',
'accounts/fireworks/routers/glm-5p2-fast',
'accounts/fireworks/routers/kimi-k2p6-fast',
'accounts/fireworks/routers/kimi-k2p6-turbo',
'accounts/fireworks/routers/kimi-k2p7-code-fast',
- 'accounts/fireworks/routers/kimi-k3-fast',
],
together: [
'MiniMaxAI/MiniMax-M2.7',
'MiniMaxAI/MiniMax-M3',
'Qwen/Qwen2.5-7B-Instruct-Turbo',
+ 'Qwen/Qwen3-235B-A22B-Instruct-2507-tput',
+ 'Qwen/Qwen3.5-397B-A17B',
'Qwen/Qwen3.5-9B',
'Qwen/Qwen3.6-Plus',
'Qwen/Qwen3.7-Max',
- 'deepseek-ai/DeepSeek-V4-Flash-0731',
'deepseek-ai/DeepSeek-V4-Pro',
- 'deepseek-ai/DeepSeek-V4-Pro-0813',
+ 'essentialai/Rnj-1-Instruct',
'google/gemma-4-31B-it',
'meta-llama/Llama-3.3-70B-Instruct-Turbo',
'moonshotai/Kimi-K2.6',
'moonshotai/Kimi-K2.7-Code',
- 'moonshotai/Kimi-K3',
'nvidia/nemotron-3-ultra-550b-a55b',
'openai/gpt-oss-120b',
'openai/gpt-oss-20b',
- 'thinkingmachines/Inkling',
+ 'zai-org/GLM-5',
+ 'zai-org/GLM-5.1',
'zai-org/GLM-5.2',
],
nvidia: [
- 'deepseek-ai/deepseek-v4-flash-0731',
- 'google/gemma-3-12b-it',
- 'google/gemma-3-4b-it',
'meta/llama-3.1-70b-instruct',
'meta/llama-3.1-8b-instruct',
'meta/llama-3.2-11b-vision-instruct',
'meta/llama-3.2-90b-vision-instruct',
'meta/llama-3.3-70b-instruct',
- 'meta/muse-glimmer-30b',
'minimaxai/minimax-m3',
- 'mistralai/mistral-7b-instruct-v0.3',
+ 'mistralai/mistral-large-3-675b-instruct-2512',
+ 'mistralai/mistral-small-4-119b-2603',
'moonshotai/kimi-k2.6',
- 'moonshotai/kimi-k3',
- 'nvidia/cosmos-reason2-8b',
- 'nvidia/llama-3.1-nemotron-70b-instruct',
- 'nvidia/llama-3.1-nemotron-nano-8b-v1',
- 'nvidia/llama-3.1-nemotron-nano-vl-8b-v1',
- 'nvidia/llama-3.1-nemotron-ultra-253b-v1',
- 'nvidia/llama-3.3-nemotron-super-49b-v1',
- 'nvidia/llama-3.3-nemotron-super-49b-v1.5',
'nvidia/nemotron-3-nano-30b-a3b',
'nvidia/nemotron-3-nano-omni-30b-a3b-reasoning',
'nvidia/nemotron-3-super-120b-a12b',
'nvidia/nemotron-3-ultra-550b-a55b',
- 'nvidia/nemotron-3.5-lightning-30b-a3b',
- 'nvidia/nemotron-nano-12b-v2-vl',
'nvidia/nvidia-nemotron-nano-9b-v2',
'openai/gpt-oss-120b',
'openai/gpt-oss-20b',
- 'poolside/laguna-xs-2.1',
+ 'qwen/qwen3.5-122b-a10b',
+ 'stepfun-ai/step-3.5-flash',
'stepfun-ai/step-3.7-flash',
- 'thinkingmachines/inkling',
+ 'z-ai/glm-5.2',
],
- zai: ['glm-4.7', 'glm-5-turbo', 'glm-5.2', 'glm-5.2-highspeed', 'glm-5.3'],
+ zai: ['glm-4.5-air', 'glm-4.7', 'glm-5-turbo', 'glm-5.1', 'glm-5.2', 'glm-5v-turbo'],
kimi: [
'kimi-k2-0711-preview',
'kimi-k2-0905-preview',
diff --git a/apps/sim/providers/pi-providers.test.ts b/apps/sim/providers/pi-providers.test.ts
index 45e3ef6b8d3..c2bf73bbf80 100644
--- a/apps/sim/providers/pi-providers.test.ts
+++ b/apps/sim/providers/pi-providers.test.ts
@@ -32,8 +32,16 @@ describe('Pi provider catalog', () => {
)
})
+ it('keeps persisted and selectable Sim models in the pinned Pi catalog', () => {
+ expect(resolvePiModelId('anthropic', 'claude-opus-4-1')).toBe('claude-opus-4-1')
+ expect(resolvePiModelId('cerebras', 'cerebras/zai-glm-4.7')).toBe('zai-glm-4.7')
+ expect(resolvePiModelId('zai', 'glm-5.1')).toBe('glm-5.1')
+ expect(resolvePiModelId('zai', 'glm-4.5-air')).toBe('glm-4.5-air')
+ })
+
it('rejects provider/model pairs absent from the installed Pi catalog', () => {
expect(resolvePiModelId('anthropic', 'claude-sonnet-999')).toBeUndefined()
+ expect(resolvePiModelId('zai', 'glm-4.5-air-unknown')).toBeUndefined()
expect(resolvePiModelId('unsupported', 'model')).toBeUndefined()
})
})
diff --git a/apps/sim/scripts/pi-sandbox-packages.ts b/apps/sim/scripts/pi-sandbox-packages.ts
index ee2c4af8340..a2296e3fc20 100644
--- a/apps/sim/scripts/pi-sandbox-packages.ts
+++ b/apps/sim/scripts/pi-sandbox-packages.ts
@@ -17,7 +17,7 @@
export const PI_BUN_VERSION = '1.3.14'
/** Exact Pi version mirrored from the app dependencies and lockfile. */
-export const PI_PACKAGE_VERSION = '0.84.3'
+export const PI_PACKAGE_VERSION = '0.80.10'
/** Exact global package versions mirrored from package.json and bun.lock. */
export const PI_GLOBAL_NPM_PACKAGES = [
@@ -49,7 +49,7 @@ export const PI_APT = [
] as const
/**
- * Pi 0.84 requires Node >= 22.19 — higher than the Node 20 both the E2B base and
+ * Pi 0.80 requires Node >= 22.19 — higher than the Node 20 both the E2B base and
* the other two sandbox images carry, so this image installs its own.
*/
export const PI_NODE_MAJOR = 22
diff --git a/bun.lock b/bun.lock
index 113004d8cb0..410e0f3d581 100644
--- a/bun.lock
+++ b/bun.lock
@@ -185,8 +185,8 @@
"@cerebras/cerebras_cloud_sdk": "^1.23.0",
"@daytona/sdk": "0.207.0",
"@e2b/code-interpreter": "2.7.1",
- "@earendil-works/pi-ai": "0.84.3",
- "@earendil-works/pi-coding-agent": "0.84.3",
+ "@earendil-works/pi-ai": "0.80.10",
+ "@earendil-works/pi-coding-agent": "0.80.10",
"@floating-ui/dom": "1.7.6",
"@google-cloud/storage": "7.21.0",
"@google/genai": "2.13.0",
@@ -1169,19 +1169,13 @@
"@e2b/code-interpreter": ["@e2b/code-interpreter@2.7.1", "", { "dependencies": { "e2b": "^2.39.0" } }, "sha512-1M4YhyWOWZzZyt1XessPbMwkVOsfpsWTiOgPKbA60CtDQasUQA8bRdN7HxsajYMrNxF5tk8J0fwrY7oa2oTK4g=="],
- "@earendil-works/pi-agent-core": ["@earendil-works/pi-agent-core@0.84.3", "", { "dependencies": { "@earendil-works/pi-ai": "^0.84.3", "@earendil-works/pi-telemetry": "^0.84.3", "diff": "8.0.4", "ignore": "7.0.5", "typebox": "1.3.7", "yaml": "2.9.0" } }, "sha512-VURr+xBRl3RxYcw3kT9Pn3yfi6LbRoCJgHF7h1mAblMjtLNV/MfG/RyF0uJizBAM886AEakSiw3j9c/aSngppg=="],
+ "@earendil-works/pi-agent-core": ["@earendil-works/pi-agent-core@0.80.10", "", { "dependencies": { "@earendil-works/pi-ai": "^0.80.10", "ignore": "7.0.5", "typebox": "1.1.38", "yaml": "2.9.0" } }, "sha512-nwnOR3SuLYGRFfyQm8ri4Nj5VGVAvAM9GuqQd3u7BUQj0d6hmD2F8w7OHAAjThE3CuySIdM+v8E22QJG6/RfCg=="],
- "@earendil-works/pi-ai": ["@earendil-works/pi-ai@0.84.3", "", { "dependencies": { "@anthropic-ai/sdk": "0.91.1", "@aws-sdk/client-bedrock-runtime": "3.1048.0", "@earendil-works/pi-telemetry": "^0.84.3", "@google/genai": "1.52.0", "@smithy/node-http-handler": "4.7.3", "http-proxy-agent": "7.0.2", "https-proxy-agent": "7.0.6", "openai": "6.40.0", "partial-json": "0.1.7", "typebox": "1.3.7" }, "bin": { "pi-ai": "dist/cli.js" } }, "sha512-M0YUV8vNO3y2WwWSyY8ijKJV5W4gkSUixuvk+Z00ZBjsyMfsdXfITsHEwP1UIf09YRWXT6oGn0GlCamt+P32XQ=="],
+ "@earendil-works/pi-ai": ["@earendil-works/pi-ai@0.80.10", "", { "dependencies": { "@anthropic-ai/sdk": "0.91.1", "@aws-sdk/client-bedrock-runtime": "3.1048.0", "@google/genai": "1.52.0", "@mistralai/mistralai": "2.2.6", "@opentelemetry/api": "1.9.0", "@smithy/node-http-handler": "4.7.3", "http-proxy-agent": "7.0.2", "https-proxy-agent": "7.0.6", "openai": "6.26.0", "partial-json": "0.1.7", "typebox": "1.1.38" }, "bin": { "pi-ai": "dist/cli.js" } }, "sha512-Moe/H8c87yacDGK9dPbWphZNjVsrb3nTrIHycOQJAkFEnY9PYxOOd74+ny44kATfPU9Dm7aTHefar3pZF+UKUA=="],
- "@earendil-works/pi-client": ["@earendil-works/pi-client@0.84.3", "", { "dependencies": { "@earendil-works/pi-protocol": "^0.84.3" } }, "sha512-zfErYane+390W0xpBJ/FWCp6aktPpkpcIcXUeZiAziWLoxE80ZNQALRyOSa/gGS5V+1OkNnMYxRxbzN0zUvnOA=="],
+ "@earendil-works/pi-coding-agent": ["@earendil-works/pi-coding-agent@0.80.10", "", { "dependencies": { "@earendil-works/pi-agent-core": "^0.80.10", "@earendil-works/pi-ai": "^0.80.10", "@earendil-works/pi-tui": "^0.80.10", "@silvia-odwyer/photon-node": "0.3.4", "chalk": "5.6.2", "cross-spawn": "7.0.6", "diff": "8.0.4", "glob": "13.0.6", "highlight.js": "10.7.3", "hosted-git-info": "9.0.3", "ignore": "7.0.5", "jiti": "2.7.0", "minimatch": "10.2.5", "proper-lockfile": "4.1.2", "semver": "7.8.0", "typebox": "1.1.38", "undici": "8.5.0", "yaml": "2.9.0" }, "optionalDependencies": { "@mariozechner/clipboard": "0.3.9" }, "bin": { "pi": "dist/cli.js" } }, "sha512-aL4apbupCHiVLSXASXvRzH4Q2vmtfrDa+0s909CJuVu/GgGylbDzr7oyF1mPmip5E+VxYYxKWmph4hV04wUcQg=="],
- "@earendil-works/pi-coding-agent": ["@earendil-works/pi-coding-agent@0.84.3", "", { "dependencies": { "@earendil-works/pi-agent-core": "^0.84.3", "@earendil-works/pi-ai": "^0.84.3", "@earendil-works/pi-client": "^0.84.3", "@earendil-works/pi-protocol": "^0.84.3", "@earendil-works/pi-tui": "^0.84.3", "@silvia-odwyer/photon-node": "0.3.4", "chalk": "5.6.2", "cross-spawn": "7.0.6", "diff": "8.0.4", "grok-mermaid": "0.2.2", "highlight.js": "10.7.3", "hosted-git-info": "9.0.3", "ignore": "7.0.5", "jiti": "2.7.0", "minimatch": "10.2.5", "proper-lockfile": "4.1.2", "semver": "7.8.0", "typebox": "1.3.7", "undici": "8.9.0", "yaml": "2.9.0" }, "optionalDependencies": { "@mariozechner/clipboard": "0.3.9" }, "bin": { "pi": "dist/bundle/cli.js" } }, "sha512-Yr2p9PubrbFZmYEPYI+C8KmZP9xlFuLDnAG64RtU0ZDgrdiXYWa+y7WGyJO5OlqPliOkVCMd9IzVszO3/t0D0w=="],
-
- "@earendil-works/pi-protocol": ["@earendil-works/pi-protocol@0.84.3", "", { "dependencies": { "typebox": "1.3.7" } }, "sha512-9a4g6WhLOvRqvsIOFaWxg/2gdrbY4Thclwj5ipLUPAWChfsDJ/8XdPc2sRhSOkD6EsxpEFJz3xppcfwI6EcZDg=="],
-
- "@earendil-works/pi-telemetry": ["@earendil-works/pi-telemetry@0.84.3", "", {}, "sha512-sgEkWoKrvSGaKn+YfLLFZmn+/A7B/w62eLwTD57nI+C9to8ITlFFVbgC2OtwvPnT3NFGHdCd53qhBEMIlptD1g=="],
-
- "@earendil-works/pi-tui": ["@earendil-works/pi-tui@0.84.3", "", { "dependencies": { "get-east-asian-width": "1.6.0", "marked": "18.0.5" } }, "sha512-fS6OEQKEEALnKa6Uw8LcgZZ+9CWck7f3MQSCETQp6leUgIFwMEDtKmOUnL9nsYm+RIPmy7OmplVxYRbV6hiaFg=="],
+ "@earendil-works/pi-tui": ["@earendil-works/pi-tui@0.80.10", "", { "dependencies": { "get-east-asian-width": "1.6.0", "marked": "18.0.5" } }, "sha512-c2JO29PbhKPEQ6fgHQKAl0WhwuFqzWfzspMmP+8B5tpDuP+0mvarRbKKg8gq4b+pQx/QX+6aVS4ko7deoyjQjg=="],
"@electric-sql/client": ["@electric-sql/client@1.0.14", "", { "dependencies": { "@microsoft/fetch-event-source": "^2.0.1" }, "optionalDependencies": { "@rollup/rollup-darwin-arm64": "^4.18.1" } }, "sha512-LtPAfeMxXRiYS0hyDQ5hue2PjljUiK9stvzsVyVb4nwxWQxfOWTSF42bHTs/o5i3x1T4kAQ7mwHpxa4A+f8X7Q=="],
@@ -1481,6 +1475,8 @@
"@microsoft/fetch-event-source": ["@microsoft/fetch-event-source@2.0.1", "", {}, "sha512-W6CLUJ2eBMw3Rec70qrsEW0jOm/3twwJv21mrmj2yORiaVmVYGS4sSS5yUwvQc1ZlDLYGPnClVWmUUMagKNsfA=="],
+ "@mistralai/mistralai": ["@mistralai/mistralai@2.2.6", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.40.0", "ws": "^8.18.0", "zod": "^3.25.0 || ^4.0.0", "zod-to-json-schema": "^3.25.0" }, "peerDependencies": { "@opentelemetry/api": "^1.9.0" }, "optionalPeers": ["@opentelemetry/api"] }, "sha512-W8pX7zHxjJvMIpw8JMxeJEleapXX0Q9NPszdNzqkM3MIEoIGPObdodujj+WHteXEvGfaP/AMwlNyRfEzSY6dQQ=="],
+
"@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.29.0", "", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ=="],
"@monaco-editor/loader": ["@monaco-editor/loader@1.7.0", "", { "dependencies": { "state-local": "^1.0.6" } }, "sha512-gIwR1HrJrrx+vfyOhYmCZ0/JcWqG5kbfG7+d3f/C1LXk2EvzAbHSg3MQ5lO2sMlo9izoAZ04shohfKLVT6crVA=="],
@@ -3215,8 +3211,6 @@
"gray-matter": ["gray-matter@4.0.3", "", { "dependencies": { "js-yaml": "^3.13.1", "kind-of": "^6.0.2", "section-matter": "^1.0.0", "strip-bom-string": "^1.0.0" } }, "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q=="],
- "grok-mermaid": ["grok-mermaid@0.2.2", "", {}, "sha512-XcJEP5dDC8liHBh52mlLjU18fNvu1ckFsu0QpIG3+APZ270fsj9wxpiA6cOURmbUEuoMVgjbC2+UYgTdCqqgzA=="],
-
"groq-sdk": ["groq-sdk@0.15.0", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" } }, "sha512-aYDEdr4qczx3cLCRRe+Beb37I7g/9bD5kHF+EEDxcrREWw1vKoRcfP3vHEkJB7Ud/8oOuF0scRwDpwWostTWuQ=="],
"gtoken": ["gtoken@8.0.0", "", { "dependencies": { "gaxios": "^7.0.0", "jws": "^4.0.0" } }, "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw=="],
@@ -4849,29 +4843,25 @@
"@daytona/sdk/socket.io-client": ["socket.io-client@4.8.3", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.4.1", "engine.io-client": "~6.6.1", "socket.io-parser": "~4.2.4" } }, "sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g=="],
- "@earendil-works/pi-agent-core/typebox": ["typebox@1.3.7", "", {}, "sha512-meKuifc33Pccx0O6PdIzYMq3Og8zvP4TIi/a+Bw3AEMZMxOD0+RHGQvpglEe6Zdy3wZ8nqn/j95h8LUZLk/6Hg=="],
-
"@earendil-works/pi-ai/@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.91.1", "", { "dependencies": { "json-schema-to-ts": "^3.1.1" }, "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["zod"], "bin": { "anthropic-ai-sdk": "bin/cli" } }, "sha512-LAmu761tSN9r66ixvmciswUj/ZC+1Q4iAfpedTfSVLeswRwnY3n2Nb6Tsk+cLPP28aLOPWeMgIuTuCcMC6W/iw=="],
"@earendil-works/pi-ai/@aws-sdk/client-bedrock-runtime": ["@aws-sdk/client-bedrock-runtime@3.1048.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.11", "@aws-sdk/credential-provider-node": "^3.972.42", "@aws-sdk/eventstream-handler-node": "^3.972.16", "@aws-sdk/middleware-eventstream": "^3.972.12", "@aws-sdk/middleware-websocket": "^3.972.19", "@aws-sdk/token-providers": "3.1048.0", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/fetch-http-handler": "^5.4.2", "@smithy/node-http-handler": "^4.7.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-u+NT61JZEkRFtpL0CAw1N1dwxnaLgwVXQl/zjJxTGgLyS/jTIdg2SdoEoCTHxgDyCnqa1HEi9QOoE9/pYRNpOQ=="],
"@earendil-works/pi-ai/@google/genai": ["@google/genai@1.52.0", "", { "dependencies": { "google-auth-library": "^10.3.0", "p-retry": "^4.6.2", "protobufjs": "^7.5.4", "ws": "^8.18.0" }, "peerDependencies": { "@modelcontextprotocol/sdk": "^1.25.2" }, "optionalPeers": ["@modelcontextprotocol/sdk"] }, "sha512-gwSvbpiN/17O9TbsqSsE/OzZcpv5Fo4RQjdngGgogtuB9RsyJ8ZHhX5KjHj1bp5N9snN2eK8LDGXSaWW2hof8Q=="],
+ "@earendil-works/pi-ai/@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
+
"@earendil-works/pi-ai/@smithy/node-http-handler": ["@smithy/node-http-handler@4.7.3", "", { "dependencies": { "@smithy/core": "^3.24.3", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-/jPhevcTFPMVl6KNjbaI47iOg1zxC7IsnX4PQDGVZKMFceOXtB8IEYaB7a9VvkP/3oC60WzTeKocvSI7vLT0vA=="],
- "@earendil-works/pi-ai/openai": ["openai@6.40.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"] }, "sha512-MWtTjd/gQt4jpbji61NTgFWJLoY/PdRJ6wG9/ZDRMYNMlBKrCrSlkLI+KgHP1vR1qT6LKSAyAqIxno6lcK9JiA=="],
+ "@earendil-works/pi-ai/openai": ["openai@6.26.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-zd23dbWTjiJ6sSAX6s0HrCZi41JwTA1bQVs0wLQPZ2/5o2gxOJA5wh7yOAUgwYybfhDXyhwlpeQf7Mlgx8EOCA=="],
- "@earendil-works/pi-ai/typebox": ["typebox@1.3.7", "", {}, "sha512-meKuifc33Pccx0O6PdIzYMq3Og8zvP4TIi/a+Bw3AEMZMxOD0+RHGQvpglEe6Zdy3wZ8nqn/j95h8LUZLk/6Hg=="],
+ "@earendil-works/pi-coding-agent/glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="],
"@earendil-works/pi-coding-agent/jiti": ["jiti@2.7.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="],
"@earendil-works/pi-coding-agent/semver": ["semver@7.8.0", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA=="],
- "@earendil-works/pi-coding-agent/typebox": ["typebox@1.3.7", "", {}, "sha512-meKuifc33Pccx0O6PdIzYMq3Og8zvP4TIi/a+Bw3AEMZMxOD0+RHGQvpglEe6Zdy3wZ8nqn/j95h8LUZLk/6Hg=="],
-
- "@earendil-works/pi-coding-agent/undici": ["undici@8.9.0", "", {}, "sha512-aWZpUj7XoGonMClx4gdDRfgBjqeA+F473aDmROQQbM9n6PRfK/u1q/a0X4wMTgcHfT8H6fpbt98PFuDUwFg2YA=="],
-
- "@earendil-works/pi-protocol/typebox": ["typebox@1.3.7", "", {}, "sha512-meKuifc33Pccx0O6PdIzYMq3Og8zvP4TIi/a+Bw3AEMZMxOD0+RHGQvpglEe6Zdy3wZ8nqn/j95h8LUZLk/6Hg=="],
+ "@earendil-works/pi-coding-agent/undici": ["undici@8.5.0", "", {}, "sha512-xamtWoB1EshgjpmlXd7GGm2VfdDtw1+rD8uhry8pSNW3If6S8E0m2T2+orSKeZXEn/aPJMviCpDBA65WJt8zhg=="],
"@earendil-works/pi-tui/marked": ["marked@18.0.5", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-S6GcvALHg6K4ohtu4E7x0a1AqhAjp6cV8KhLSyN9qVapnzJkusVBxZRcIU9AeYsbe6P1hKDusSbEOzGyyuce6w=="],