Skip to content

Commit a32895d

Browse files
committed
fix(deps): preserve runtime compatibility
1 parent 03305e4 commit a32895d

15 files changed

Lines changed: 233 additions & 209 deletions

File tree

apps/sim/blocks/pi-model-options.test.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,15 @@ const originalOpenRouterModels = useProvidersStore.getState().providers.openrout
1313
describe('Pi model options', () => {
1414
beforeAll(() => {
1515
const store = useProvidersStore.getState()
16-
store.setProviderModels('base', ['claude-sonnet-4-6', 'claude-sonnet-4-0', 'gpt-5.4'])
16+
store.setProviderModels('base', [
17+
'claude-sonnet-4-6',
18+
'claude-opus-4-1',
19+
'claude-sonnet-4-0',
20+
'gpt-5.4',
21+
'cerebras/zai-glm-4.7',
22+
'glm-5.1',
23+
'glm-4.5-air',
24+
])
1725
store.setProviderModels('openrouter', [
1826
'openrouter/openai/gpt-5',
1927
'openrouter/openrouter/fusion',
@@ -43,6 +51,15 @@ describe('Pi model options', () => {
4351
expect(modelIds).not.toContain('claude-sonnet-4-0')
4452
})
4553

54+
it('keeps persisted and selectable models available', () => {
55+
const modelIds = getPiModelOptions().map(({ id }) => id)
56+
57+
expect(modelIds).toContain('claude-opus-4-1')
58+
expect(modelIds).toContain('cerebras/zai-glm-4.7')
59+
expect(modelIds).toContain('glm-5.1')
60+
expect(modelIds).toContain('glm-4.5-air')
61+
})
62+
4663
it("does not apply OpenRouter capability filters beyond Pi's catalog", () => {
4764
const modelIds = getPiModelOptions().map(({ id }) => id)
4865

apps/sim/executor/handlers/pi/core/pi-sdk.test.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,7 @@ describe('createSealedPiResourceLoader', () => {
2121
})
2222
expect(loader.getAgentsFiles()).toEqual({ agentsFiles: [] })
2323
expect(loader.getSystemPrompt()).toBe('sealed system prompt')
24-
expect(loader.getSystemPromptSource()).toBeUndefined()
2524
expect(loader.getAppendSystemPrompt()).toEqual([])
26-
expect(loader.getAppendSystemPromptSources()).toEqual([])
2725
await expect(loader.reload()).resolves.toBeUndefined()
2826
})
2927
})

apps/sim/executor/handlers/pi/core/pi-sdk.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,7 @@ export function createSealedPiResourceLoader(sdk: PiSdk, systemPrompt: string):
9090
getThemes: () => ({ themes: [], diagnostics: [] }),
9191
getAgentsFiles: () => ({ agentsFiles: [] }),
9292
getSystemPrompt: () => systemPrompt,
93-
getSystemPromptSource: () => undefined,
9493
getAppendSystemPrompt: () => [],
95-
getAppendSystemPromptSources: () => [],
9694
extendResources: () => {},
9795
reload: async () => {},
9896
}

apps/sim/executor/handlers/pi/pi-handler.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,19 @@ describe('PiBlockHandler', () => {
267267
expect(mockResolveKey).not.toHaveBeenCalled()
268268
})
269269

270+
it('passes a persisted catalog model to the backend', async () => {
271+
mockGetProviderFromModel.mockReturnValue('cerebras')
272+
mockResolvePiModelId.mockReturnValue('zai-glm-4.7')
273+
274+
await handler.execute(ctx(), block, localInputs({ model: 'cerebras/zai-glm-4.7' }))
275+
276+
expect(mockRunLocal.mock.calls[0][0]).toMatchObject({
277+
model: 'cerebras/zai-glm-4.7',
278+
piModel: 'zai-glm-4.7',
279+
providerId: 'cerebras',
280+
})
281+
})
282+
270283
it('routes Local Dev to the local backend with SSH params', async () => {
271284
const output = await handler.execute(ctx(), block, localInputs())
272285
expect(mockRunLocal).toHaveBeenCalledTimes(1)

apps/sim/lib/internal/file/parser.test.ts

Lines changed: 45 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
*
44
* @vitest-environment node
55
*/
6+
7+
import { Readable } from 'node:stream'
68
import {
79
authMockFns,
810
createMockRequest,
@@ -24,15 +26,15 @@ const {
2426
mockGetStorageProvider,
2527
mockIsUsingCloudStorage,
2628
mockIsSupportedFileType,
27-
mockParseFile,
2829
mockParseBuffer,
2930
mockPdfParseBuffer,
31+
mockCreateReadStream,
3032
mockFsAccess,
3133
mockFsStat,
32-
mockFsReadFile,
3334
mockFsWriteFile,
3435
mockJoin,
3536
actualPath,
37+
mockUploadExecutionFile,
3638
mockUploadWorkspaceFile,
3739
mockReadWorkspaceFileNameByKey,
3840
} = vi.hoisted(() => {
@@ -44,10 +46,6 @@ const {
4446
mockGetStorageProvider: vi.fn().mockReturnValue('s3'),
4547
mockIsUsingCloudStorage: vi.fn().mockReturnValue(true),
4648
mockIsSupportedFileType: vi.fn().mockReturnValue(true),
47-
mockParseFile: vi.fn().mockResolvedValue({
48-
content: 'parsed content',
49-
metadata: { pageCount: 1 },
50-
}),
5149
mockParseBuffer: vi.fn().mockResolvedValue({
5250
content: 'parsed buffer content',
5351
metadata: { pageCount: 1 },
@@ -56,9 +54,9 @@ const {
5654
content: 'parsed PDF content',
5755
metadata: { pageCount: 1 },
5856
}),
57+
mockCreateReadStream: vi.fn(),
5958
mockFsAccess: vi.fn().mockResolvedValue(undefined),
6059
mockFsStat: vi.fn().mockImplementation(() => ({ isFile: () => true, size: 17 })),
61-
mockFsReadFile: vi.fn().mockResolvedValue(Buffer.from('test file content')),
6260
mockFsWriteFile: vi.fn().mockResolvedValue(undefined),
6361
mockJoin: vi.fn((...args: string[]): string => {
6462
if (args[0] === '/test/uploads') {
@@ -67,6 +65,7 @@ const {
6765
return actualPath.join(...args)
6866
}),
6967
actualPath,
68+
mockUploadExecutionFile: vi.fn(),
7069
mockUploadWorkspaceFile: vi
7170
.fn()
7271
.mockImplementation(
@@ -98,10 +97,13 @@ vi.mock('@/lib/uploads', () => ({
9897

9998
vi.mock('@/lib/file-parsers', () => ({
10099
isSupportedFileType: mockIsSupportedFileType,
101-
parseFile: mockParseFile,
102100
parseBuffer: mockParseBuffer,
103101
}))
104102

103+
vi.mock('node:fs', () => ({
104+
createReadStream: mockCreateReadStream,
105+
}))
106+
105107
vi.mock('@/lib/file-parsers/pdf-parser', () => ({
106108
PdfParser: class {
107109
parseBuffer(...args: Parameters<typeof mockPdfParseBuffer>) {
@@ -131,7 +133,7 @@ vi.mock('@/lib/core/utils/logging', () => ({
131133
}))
132134

133135
vi.mock('@/lib/uploads/contexts/execution', () => ({
134-
uploadExecutionFile: vi.fn(),
136+
uploadExecutionFile: mockUploadExecutionFile,
135137
}))
136138

137139
vi.mock('@/lib/uploads/contexts/workspace/workspace-file-manager', () => ({
@@ -152,12 +154,10 @@ vi.mock('fs/promises', () => ({
152154
default: {
153155
access: mockFsAccess,
154156
stat: mockFsStat,
155-
readFile: mockFsReadFile,
156157
writeFile: mockFsWriteFile,
157158
},
158159
access: mockFsAccess,
159160
stat: mockFsStat,
160-
readFile: mockFsReadFile,
161161
writeFile: mockFsWriteFile,
162162
}))
163163

@@ -241,14 +241,19 @@ describe('file parser operation', () => {
241241
storageServiceMockFns.mockHasCloudStorage.mockReturnValue(true)
242242
storageServiceMockFns.mockDownloadFile.mockResolvedValue(Buffer.from('test file content'))
243243
mockFsStat.mockResolvedValue({ isFile: () => true, size: 17 })
244-
mockFsReadFile.mockResolvedValue(Buffer.from('test file content'))
244+
mockCreateReadStream.mockImplementation(() => Readable.from([Buffer.from('test file content')]))
245245
mockIsSupportedFileType.mockReturnValue(true)
246+
mockUploadExecutionFile.mockResolvedValue({
247+
id: 'file_test',
248+
name: 'report.pdf',
249+
url: '/api/files/serve/execution/report.pdf',
250+
size: 17,
251+
type: 'application/pdf',
252+
key: 'execution/report.pdf',
253+
context: 'execution',
254+
})
246255
mockUploadWorkspaceFile.mockClear()
247256
mockReadWorkspaceFileNameByKey.mockResolvedValue({ name: null })
248-
mockParseFile.mockResolvedValue({
249-
content: 'parsed content',
250-
metadata: { pageCount: 1 },
251-
})
252257
mockParseBuffer.mockResolvedValue({
253258
content: 'parsed buffer content',
254259
metadata: { pageCount: 1 },
@@ -422,21 +427,39 @@ describe('file parser operation', () => {
422427
})
423428
})
424429

425-
it('forwards request cancellation to local file parsing and reads', async () => {
430+
it('parses and uploads one bounded local-file snapshot', async () => {
426431
setupFileApiMocks({
427432
cloudEnabled: false,
428433
storageProvider: 'local',
429434
authenticated: true,
430435
})
436+
mockFsStat.mockResolvedValue({ isFile: () => true, size: 3 })
431437
const req = createMockRequest('POST', {
432438
filePath: 'workspace/report.pdf',
433439
})
434440

435441
const response = await POST(req)
436442

443+
const data = await response.json()
444+
const parsedBuffer = mockParseBuffer.mock.calls[0][0]
445+
437446
expect(response.status).toBe(200)
438-
expect(mockParseFile).toHaveBeenCalledWith(expect.any(String), { signal: req.signal })
439-
expect(mockFsReadFile).toHaveBeenCalledWith(expect.any(String), { signal: req.signal })
447+
expect(data.output).toMatchObject({
448+
content: 'parsed buffer content',
449+
fileType: 'application/pdf',
450+
size: 17,
451+
})
452+
expect(mockCreateReadStream).toHaveBeenCalledWith('/test/uploads/workspace/report.pdf')
453+
expect(mockCreateReadStream).toHaveBeenCalledOnce()
454+
expect(mockParseBuffer).toHaveBeenCalledWith(parsedBuffer, 'pdf', { signal: req.signal })
455+
expect(mockParseBuffer).toHaveBeenCalledOnce()
456+
expect(mockUploadExecutionFile).toHaveBeenCalledWith(
457+
expect.any(Object),
458+
parsedBuffer,
459+
'report.pdf',
460+
'application/pdf',
461+
'test-user-id'
462+
)
440463
})
441464

442465
it('should reject parser complexity limits instead of returning raw text', async () => {
@@ -740,7 +763,9 @@ describe('file parser operation', () => {
740763
expect(response.status).toBe(200)
741764
expect(data.success).toBe(false)
742765
expect(data.error).toContain('too large')
743-
expect(mockFsReadFile).not.toHaveBeenCalled()
766+
expect(mockCreateReadStream).not.toHaveBeenCalled()
767+
expect(mockParseBuffer).not.toHaveBeenCalled()
768+
expect(mockUploadExecutionFile).not.toHaveBeenCalled()
744769
})
745770

746771
it('should process execution file URLs with context query param', async () => {

apps/sim/lib/internal/file/parser.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { createReadStream } from 'node:fs'
12
import { Buffer, isUtf8 } from 'buffer'
23
import { createHash } from 'crypto'
34
import fsPromises from 'fs/promises'
@@ -10,12 +11,16 @@ import binaryExtensionsList from 'binary-extensions'
1011
import type { ContractBody } from '@/lib/api/contracts'
1112
import type { fileParseContract } from '@/lib/api/contracts/storage-transfer'
1213
import { sanitizeUrlForLog } from '@/lib/core/utils/logging'
13-
import { assertKnownSizeWithinLimit, isPayloadSizeLimitError } from '@/lib/core/utils/stream-limits'
14+
import {
15+
assertKnownSizeWithinLimit,
16+
isPayloadSizeLimitError,
17+
readNodeStreamToBufferWithLimit,
18+
} from '@/lib/core/utils/stream-limits'
1419
import {
1520
assertUserFileContentAccess,
1621
type ExecutionMaterializationContext,
1722
} from '@/lib/execution/payloads/materialization.server'
18-
import { isSupportedFileType, parseFile } from '@/lib/file-parsers'
23+
import { isSupportedFileType, parseBuffer } from '@/lib/file-parsers'
1924
import { isFileParserError } from '@/lib/file-parsers/errors'
2025
import { isUsingCloudStorage, StorageService } from '@/lib/uploads'
2126
import { uploadExecutionFile } from '@/lib/uploads/contexts/execution'
@@ -881,13 +886,17 @@ async function handleLocalFile(
881886
const stats = await fsPromises.stat(fullPath)
882887
assertKnownSizeWithinLimit(stats.size, maxDownloadBytes, 'local file')
883888

884-
const result = await parseFile(fullPath, { signal })
889+
const fileBuffer = await readNodeStreamToBufferWithLimit(createReadStream(fullPath), {
890+
maxBytes: maxDownloadBytes,
891+
label: 'local file',
892+
signal,
893+
})
894+
const extension = path.extname(filename).toLowerCase().substring(1)
895+
const result = await parseBuffer(fileBuffer, extension, { signal })
885896
const content = assertParsedContentWithinLimit(result.content, maxParsedOutputBytes)
886-
const fileBuffer = await fsPromises.readFile(fullPath, { signal })
887897
signal?.throwIfAborted()
888898
const hash = createHash('md5').update(fileBuffer).digest('hex')
889899

890-
const extension = path.extname(filename).toLowerCase().substring(1)
891900
const mimeType = fileType || getMimeTypeFromExtension(extension)
892901

893902
// Store file in execution storage if executionContext is provided
@@ -916,7 +925,7 @@ async function handleLocalFile(
916925
userFile,
917926
metadata: {
918927
fileType: mimeType,
919-
size: stats.size,
928+
size: fileBuffer.length,
920929
hash,
921930
processingTime: 0,
922931
},

apps/sim/lib/internal/mysql/client.test.ts

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,26 @@
33
*/
44
import { beforeEach, describe, expect, it, vi } from 'vitest'
55

6-
const { mockCreateConnection, mockNetConnect, mockValidateDatabaseHost } = vi.hoisted(() => ({
7-
mockCreateConnection: vi.fn(),
8-
mockNetConnect: vi.fn(),
9-
mockValidateDatabaseHost: vi.fn(),
10-
}))
6+
const { mockCreateConnection, mockNetConnect, mockTypedParameterNull, mockValidateDatabaseHost } =
7+
vi.hoisted(() => {
8+
class MockTypedParameter {}
9+
return {
10+
mockCreateConnection: vi.fn(),
11+
mockNetConnect: vi.fn(),
12+
mockTypedParameterNull: vi.fn(() => new MockTypedParameter()),
13+
mockValidateDatabaseHost: vi.fn(),
14+
}
15+
})
1116

1217
vi.mock('node:net', () => ({
1318
default: { connect: mockNetConnect },
1419
}))
1520

1621
vi.mock('mysql2/promise', () => ({
17-
default: { createConnection: mockCreateConnection },
22+
default: {
23+
createConnection: mockCreateConnection,
24+
TypedParameter: { NULL: mockTypedParameterNull },
25+
},
1826
}))
1927

2028
vi.mock('@/lib/core/security/input-validation.server', () => ({
@@ -123,4 +131,44 @@ describe('MySQL client', () => {
123131
)
124132
expect(connection.execute).not.toHaveBeenCalled()
125133
})
134+
135+
it.each([
136+
new Map([['key', 'value']]),
137+
new Set(['value']),
138+
/value/,
139+
Object.assign(Object.create(null) as Record<string, unknown>, { key: 'value' }),
140+
])('rejects non-plain structured bind values: %s', async (value) => {
141+
const connection = { execute: vi.fn(), destroy: vi.fn() }
142+
143+
await expect(executeMysqlCommand(connection as never, 'SELECT ?', [value])).rejects.toThrow(
144+
'MySQL bind values must contain only supported scalar or structured values'
145+
)
146+
expect(connection.execute).not.toHaveBeenCalled()
147+
})
148+
149+
it('accepts nested plain structured bind values', async () => {
150+
const result = { affectedRows: 1 }
151+
const values = [{ nested: ['value', 1, true, null] }]
152+
const connection = {
153+
execute: vi.fn().mockResolvedValue([result]),
154+
destroy: vi.fn(),
155+
}
156+
157+
await expect(executeMysqlCommand(connection as never, 'SELECT ?', values)).resolves.toBe(result)
158+
expect(connection.execute).toHaveBeenCalledWith('SELECT ?', values)
159+
})
160+
161+
it('accepts mysql2 typed parameters', async () => {
162+
const result = { affectedRows: 1 }
163+
const typedParameter = mockTypedParameterNull()
164+
const connection = {
165+
execute: vi.fn().mockResolvedValue([result]),
166+
destroy: vi.fn(),
167+
}
168+
169+
await expect(
170+
executeMysqlCommand(connection as never, 'SELECT ?', [typedParameter])
171+
).resolves.toBe(result)
172+
expect(connection.execute).toHaveBeenCalledWith('SELECT ?', [typedParameter])
173+
})
126174
})

apps/sim/lib/internal/mysql/client.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ export interface MysqlConnectionConfig {
1111
ssl: 'disabled' | 'required' | 'preferred'
1212
}
1313

14+
const MYSQL_TYPED_PARAMETER_PROTOTYPE = Object.getPrototypeOf(mysql.TypedParameter.NULL())
15+
1416
function isMysqlExecuteValue(value: unknown): value is mysql.ExecuteValues {
1517
if (
1618
value === null ||
@@ -24,6 +26,9 @@ function isMysqlExecuteValue(value: unknown): value is mysql.ExecuteValues {
2426

2527
if (Array.isArray(value)) return value.every(isMysqlExecuteValue)
2628
if (typeof value !== 'object') return false
29+
const prototype = Object.getPrototypeOf(value)
30+
if (prototype === MYSQL_TYPED_PARAMETER_PROTOTYPE) return true
31+
if (prototype !== Object.prototype) return false
2732
return Object.values(value).every(isMysqlExecuteValue)
2833
}
2934

0 commit comments

Comments
 (0)