fix(onedrive): parse array values correctly (#1981)

* fix(onedrive): parse array values correctly

* fix onedrive

* fix

* fix onedrive input parsing by reusing code subblock

* fix type
This commit is contained in:
Vikhyath Mondreti
2025-11-13 19:24:33 -08:00
committed by GitHub
parent dab70a8f1d
commit 785f847c48
7 changed files with 117 additions and 25 deletions

View File

@@ -6,6 +6,7 @@ import { createLogger } from '@/lib/logs/console/logger'
import { processSingleFileToUserFile } from '@/lib/uploads/utils/file-utils'
import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server'
import { generateRequestId } from '@/lib/utils'
import { normalizeExcelValues } from '@/tools/onedrive/utils'
export const dynamic = 'force-dynamic'
@@ -13,6 +14,14 @@ const logger = createLogger('OneDriveUploadAPI')
const MICROSOFT_GRAPH_BASE = 'https://graph.microsoft.com/v1.0'
const ExcelCellSchema = z.union([z.string(), z.number(), z.boolean(), z.null()])
const ExcelRowSchema = z.array(ExcelCellSchema)
const ExcelValuesSchema = z.union([
z.string(),
z.array(ExcelRowSchema),
z.array(z.record(ExcelCellSchema)),
])
const OneDriveUploadSchema = z.object({
accessToken: z.string().min(1, 'Access token is required'),
fileName: z.string().min(1, 'File name is required'),
@@ -20,7 +29,7 @@ const OneDriveUploadSchema = z.object({
folderId: z.string().optional().nullable(),
mimeType: z.string().optional(),
// Optional Excel write-after-create inputs
values: z.array(z.array(z.union([z.string(), z.number(), z.boolean(), z.null()]))).optional(),
values: ExcelValuesSchema.optional(),
})
export async function POST(request: NextRequest) {
@@ -46,6 +55,7 @@ export async function POST(request: NextRequest) {
const body = await request.json()
const validatedData = OneDriveUploadSchema.parse(body)
const excelValues = normalizeExcelValues(validatedData.values)
let fileBuffer: Buffer
let mimeType: string
@@ -180,7 +190,7 @@ export async function POST(request: NextRequest) {
// If this is an Excel creation and values were provided, write them using the Excel API
let excelWriteResult: any | undefined
const shouldWriteExcelContent =
isExcelCreation && Array.isArray(validatedData.values) && validatedData.values.length > 0
isExcelCreation && Array.isArray(excelValues) && excelValues.length > 0
if (shouldWriteExcelContent) {
try {
@@ -232,7 +242,7 @@ export async function POST(request: NextRequest) {
logger.warn(`[${requestId}] Error listing worksheets, using default Sheet1`, listError)
}
let processedValues: any = validatedData.values || []
let processedValues: any = excelValues || []
if (
Array.isArray(processedValues) &&

View File

@@ -221,17 +221,26 @@ export function Code({
// Derived state
const effectiveLanguage = (languageValue as 'javascript' | 'python' | 'json') || language
const trimmedCode = code.trim()
const containsReferencePlaceholders =
trimmedCode.includes('{{') ||
trimmedCode.includes('}}') ||
trimmedCode.includes('<') ||
trimmedCode.includes('>')
const shouldValidateJson = effectiveLanguage === 'json' && !containsReferencePlaceholders
const isValidJson = useMemo(() => {
if (subBlockId !== 'responseFormat' || !code.trim()) {
if (!shouldValidateJson || !trimmedCode) {
return true
}
try {
JSON.parse(code)
JSON.parse(trimmedCode)
return true
} catch {
return false
}
}, [subBlockId, code])
}, [shouldValidateJson, trimmedCode])
const gutterWidthPx = useMemo(() => {
const lineCount = code.split('\n').length
@@ -309,14 +318,29 @@ export function Code({
: storeValue
// Effects: JSON validation
const lastValidationStatus = useRef<boolean>(true)
useEffect(() => {
if (onValidationChange && subBlockId === 'responseFormat') {
const timeoutId = setTimeout(() => {
onValidationChange(isValidJson)
}, 150)
return () => clearTimeout(timeoutId)
if (!onValidationChange) return
const nextStatus = shouldValidateJson ? isValidJson : true
if (lastValidationStatus.current === nextStatus) {
return
}
}, [isValidJson, onValidationChange, subBlockId])
lastValidationStatus.current = nextStatus
if (!shouldValidateJson) {
onValidationChange(nextStatus)
return
}
const timeoutId = setTimeout(() => {
onValidationChange(nextStatus)
}, 150)
return () => clearTimeout(timeoutId)
}, [isValidJson, onValidationChange, shouldValidateJson])
// Effects: AI stream handlers setup
useEffect(() => {

View File

@@ -190,7 +190,7 @@ const renderLabel = (
<div className='flex items-center gap-[6px] whitespace-nowrap'>
{config.title}
{required && <span className='ml-0.5'>*</span>}
{config.id === 'responseFormat' && (
{config.type === 'code' && config.language === 'json' && (
<Tooltip.Root>
<Tooltip.Trigger asChild>
<AlertTriangle

View File

@@ -3,6 +3,7 @@ import { createLogger } from '@/lib/logs/console/logger'
import type { BlockConfig } from '@/blocks/types'
import { AuthMode } from '@/blocks/types'
import type { OneDriveResponse } from '@/tools/onedrive/types'
import { normalizeExcelValuesForToolParams } from '@/tools/onedrive/utils'
const logger = createLogger('OneDriveBlock')
@@ -78,9 +79,10 @@ export const OneDriveBlock: BlockConfig<OneDriveResponse> = {
{
id: 'values',
title: 'Values',
type: 'long-input',
placeholder:
'Enter values as JSON array of arrays (e.g., [["A1","B1"],["A2","B2"]]) or an array of objects',
type: 'code',
language: 'json',
generationType: 'json-object',
placeholder: 'Enter a JSON array of rows (e.g., [["A1","B1"],["A2","B2"]])',
condition: {
field: 'operation',
value: 'create_file',
@@ -89,6 +91,13 @@ export const OneDriveBlock: BlockConfig<OneDriveResponse> = {
value: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
},
},
wandConfig: {
enabled: true,
prompt:
'Generate a JSON array of arrays that can be written directly into an Excel worksheet.',
placeholder: 'Describe the table you want to generate...',
generationType: 'json-object',
},
required: false,
},
// File upload (basic mode)
@@ -351,17 +360,15 @@ export const OneDriveBlock: BlockConfig<OneDriveResponse> = {
params: (params) => {
const { credential, folderId, fileId, mimeType, values, downloadFileName, ...rest } = params
let parsedValues
try {
parsedValues = values ? JSON.parse(values as string) : undefined
} catch (error) {
throw new Error('Invalid JSON format for values')
let normalizedValues: ReturnType<typeof normalizeExcelValuesForToolParams>
if (values !== undefined) {
normalizedValues = normalizeExcelValuesForToolParams(values)
}
return {
credential,
...rest,
values: parsedValues,
values: normalizedValues,
folderId: folderId || undefined,
fileId: fileId || undefined,
pageSize: rest.pageSize ? Number.parseInt(rest.pageSize as string, 10) : undefined,
@@ -380,7 +387,7 @@ export const OneDriveBlock: BlockConfig<OneDriveResponse> = {
fileReference: { type: 'json', description: 'File reference from previous block' },
content: { type: 'string', description: 'Text content to upload' },
mimeType: { type: 'string', description: 'MIME type of file to create' },
values: { type: 'string', description: 'Cell values for new Excel as JSON' },
values: { type: 'json', description: 'Cell values for new Excel as JSON' },
fileId: { type: 'string', description: 'File ID to download' },
downloadFileName: { type: 'string', description: 'File name override for download' },
folderId: { type: 'string', description: 'Folder ID' },

View File

@@ -210,7 +210,7 @@ export interface SubBlockConfig {
}
})
// Props specific to 'code' sub-block type
language?: 'javascript' | 'json'
language?: 'javascript' | 'json' | 'python'
generationType?: GenerationType
collapsible?: boolean // Whether the code block can be collapsed
defaultCollapsed?: boolean // Whether the code block is collapsed by default

View File

@@ -99,7 +99,9 @@ export interface OneDriveToolParams {
pageToken?: string
exportMimeType?: string
// Optional Excel write parameters (used when creating an .xlsx without file content)
values?: (string | number | boolean | null)[][]
values?:
| (string | number | boolean | null)[][]
| Array<Record<string, string | number | boolean | null>>
}
export type OneDriveResponse =

View File

@@ -0,0 +1,49 @@
import type { OneDriveToolParams } from '@/tools/onedrive/types'
export type ExcelCell = string | number | boolean | null
export type ExcelArrayValues = ExcelCell[][]
export type ExcelObjectValues = Array<Record<string, ExcelCell>>
export type NormalizedExcelValues = ExcelArrayValues | ExcelObjectValues
/**
* Ensures Excel values are always represented as arrays before hitting downstream tooling.
* Accepts JSON strings, array-of-arrays, or array-of-objects and normalizes them.
*/
export function normalizeExcelValues(values: unknown): NormalizedExcelValues | undefined {
if (values === null || values === undefined) {
return undefined
}
if (typeof values === 'string') {
const trimmed = values.trim()
if (!trimmed) {
return undefined
}
try {
const parsed = JSON.parse(trimmed)
if (!Array.isArray(parsed)) {
throw new Error('Excel values must be an array of rows or array of objects')
}
return parsed as NormalizedExcelValues
} catch (_error) {
throw new Error('Invalid JSON format for values')
}
}
if (Array.isArray(values)) {
return values as NormalizedExcelValues
}
throw new Error('Excel values must be an array of rows or array of objects')
}
/**
* Convenience helper for contexts that expect the narrower ToolParams typing.
*/
export function normalizeExcelValuesForToolParams(
values: unknown
): OneDriveToolParams['values'] | undefined {
const normalized = normalizeExcelValues(values)
return normalized as OneDriveToolParams['values'] | undefined
}