Skip to content

Commit bef642b

Browse files
Bill LeoutsakosBill Leoutsakos
authored andcommitted
fix(oci-vision): normalize native block optional parameters
1 parent 70646c5 commit bef642b

2 files changed

Lines changed: 98 additions & 3 deletions

File tree

apps/sim/blocks/blocks/oci_vision.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -504,8 +504,14 @@ export const OciVisionBlock: BlockConfig<OciVisionResponse> = {
504504
params: (params) => {
505505
const operation = (params.operation || 'analyze_image') as OciVisionOperation
506506
if (!operationFields[operation]) throw new Error('Unsupported OCI Vision operation')
507-
const result: Record<string, unknown> = { oauthCredential: params.oauthCredential }
508-
if (params.region) result.region = params.region
507+
/** Parameter transforms are patches: explicitly clear absent or inactive semantic fields. */
508+
const result: Record<string, unknown> = Object.fromEntries(
509+
Object.values(operationFields)
510+
.flat()
511+
.map((field) => [field, undefined])
512+
)
513+
result.oauthCredential = params.oauthCredential
514+
result.region = params.region || undefined
509515
for (const field of operationFields[operation]) {
510516
const value = params[field]
511517
if (value !== undefined && value !== null && value !== '') result[field] = value
@@ -530,7 +536,7 @@ export const OciVisionBlock: BlockConfig<OciVisionResponse> = {
530536
['FACE_DETECTION', ['faceMaxResults', 'shouldReturnLandmarks']],
531537
] as const) {
532538
if (!features.includes(feature)) {
533-
for (const field of fields) delete result[field]
539+
for (const field of fields) result[field] = undefined
534540
}
535541
}
536542
}

apps/sim/lib/internal/oci-vision/execute-tool.test.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ vi.mock('@/lib/internal/oci-vision/operations', () => ({
1111
import { OciClientError } from '@/lib/internal/oci/errors'
1212
import { executeOciVisionTool } from '@/lib/internal/oci-vision/execute-tool'
1313
import type { InternalToolOperationCall } from '@/lib/internal/tool-operations/types'
14+
import { OciVisionBlock } from '@/blocks/blocks/oci_vision'
15+
import { ociVisionAnalyzeImageTool } from '@/tools/oci_vision/analyze_image'
1416

1517
function request(overrides: Partial<InternalToolOperationCall> = {}): InternalToolOperationCall {
1618
return {
@@ -29,6 +31,93 @@ describe('OCI Vision internal dispatch', () => {
2931
executeOperation.mockResolvedValue({ success: true, output: { job: { id: 'job-1' } } })
3032
})
3133

34+
it.each([null, '', undefined])(
35+
'omits blank and inactive feature controls after the native parameter merge (%s)',
36+
async (blank) => {
37+
const raw = {
38+
operation: 'analyze_image',
39+
oauthCredential: 'selected',
40+
source: 'object_storage',
41+
namespaceName: 'namespace',
42+
bucketName: 'images',
43+
imageObjectName: 'image.png',
44+
features: ['TEXT_DETECTION'],
45+
language: 'ENG',
46+
compartmentId: blank,
47+
faceMaxResults: blank,
48+
shouldReturnLandmarks: false,
49+
classificationMaxResults: blank,
50+
objectDetectionMaxResults: 5,
51+
}
52+
const params = {
53+
...raw,
54+
...OciVisionBlock.tools.config?.params?.(raw),
55+
accessToken: 'resolved',
56+
}
57+
const response = await executeOciVisionTool(
58+
request({
59+
toolId: ociVisionAnalyzeImageTool.id,
60+
input: ociVisionAnalyzeImageTool.operation.input(params),
61+
})
62+
)
63+
expect(response.status).toBe(200)
64+
expect(executeOperation).toHaveBeenCalledWith(
65+
expect.objectContaining({
66+
credentialId: 'resolved',
67+
features: ['TEXT_DETECTION'],
68+
language: 'ENG',
69+
}),
70+
expect.anything()
71+
)
72+
for (const field of [
73+
'compartmentId',
74+
'faceMaxResults',
75+
'shouldReturnLandmarks',
76+
'classificationMaxResults',
77+
'objectDetectionMaxResults',
78+
])
79+
expect(executeOperation.mock.lastCall?.[0][field]).toBeUndefined()
80+
}
81+
)
82+
83+
it('preserves active false controls and rejects invalid nonempty feature limits', async () => {
84+
const raw = {
85+
operation: 'analyze_image',
86+
source: 'object_storage',
87+
namespaceName: 'namespace',
88+
bucketName: 'images',
89+
imageObjectName: 'image.png',
90+
features: ['FACE_DETECTION'],
91+
shouldReturnLandmarks: false,
92+
faceMaxResults: '2',
93+
}
94+
const params = {
95+
...raw,
96+
...OciVisionBlock.tools.config?.params?.(raw),
97+
accessToken: 'resolved',
98+
}
99+
expect(
100+
(
101+
await executeOciVisionTool(
102+
request({
103+
toolId: ociVisionAnalyzeImageTool.id,
104+
input: ociVisionAnalyzeImageTool.operation.input(params),
105+
})
106+
)
107+
).status
108+
).toBe(200)
109+
expect(executeOperation).toHaveBeenCalledWith(
110+
expect.objectContaining({
111+
shouldReturnLandmarks: false,
112+
faceMaxResults: 2,
113+
}),
114+
expect.anything()
115+
)
116+
expect(() =>
117+
OciVisionBlock.tools.config?.params?.({ ...raw, faceMaxResults: 'invalid' })
118+
).toThrow()
119+
})
120+
32121
it('uses trusted context and ignores workspace or credential aliases in input', async () => {
33122
const response = await executeOciVisionTool(
34123
request({

0 commit comments

Comments
 (0)