Skip to content

Commit ada5f16

Browse files
Bill LeoutsakosBill Leoutsakos
authored andcommitted
fix(netsuite): complete generated integration docs
1 parent aa2cdfa commit ada5f16

7 files changed

Lines changed: 425 additions & 82 deletions

File tree

‎apps/docs/content/docs/en/integrations/netsuite.mdx‎

Lines changed: 323 additions & 72 deletions
Large diffs are not rendered by default.

‎apps/sim/blocks/blocks/netsuite.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -663,7 +663,7 @@ Return ONLY the comma-separated sublist IDs - no explanations, no extra text.`,
663663
data: {
664664
type: 'json',
665665
description:
666-
'NetSuite payload: account-specific record fields, collection items and paging fields, metadata, or async task data',
666+
'NetSuite payload: account-specific records, collections, field-keyed select options with paging, metadata, or async task data',
667667
},
668668
location: { type: 'string', description: 'Created resource or async job location' },
669669
jobId: { type: 'string', description: 'Async job ID parsed from the Location header' },

‎apps/sim/tools/generated/tool-outputs.ts‎

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

‎apps/sim/tools/netsuite/get_select_options.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
buildQuery,
44
buildRecordPath,
55
createNetSuiteTool,
6+
NETSUITE_SELECT_OPTIONS_OUTPUTS,
67
OPTIONAL_JSON_BODY_PARAM,
78
optionalTrim,
89
PAGINATION_PARAMS,
@@ -54,4 +55,5 @@ export const netsuiteGetSelectOptionsTool = createNetSuiteTool<NetSuiteGetSelect
5455
body: params.body ?? {},
5556
}
5657
},
58+
outputs: NETSUITE_SELECT_OPTIONS_OUTPUTS,
5759
})

‎apps/sim/tools/netsuite/netsuite.test.ts‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -650,6 +650,16 @@ describe('NetSuite operation contracts', () => {
650650
},
651651
},
652652
})
653+
expect(netsuiteGetSelectOptionsTool.outputs?.data).toMatchObject({
654+
type: 'json',
655+
description: expect.stringContaining('_selectOptions'),
656+
properties: {
657+
links: { type: 'array' },
658+
},
659+
})
660+
for (const pagingField of ['items', 'count', 'offset', 'hasMore', 'totalResults']) {
661+
expect(netsuiteGetSelectOptionsTool.outputs?.data?.description).toContain(pagingField)
662+
}
653663
})
654664

655665
it('lists async tasks and requires a task ID for the documented task-status endpoint', async () => {

‎apps/sim/tools/netsuite/utils.ts‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,19 @@ export const NETSUITE_COLLECTION_OUTPUTS = {
179179
},
180180
} as const satisfies Record<string, ToolOutputProperty>
181181

182+
export const NETSUITE_SELECT_OPTIONS_OUTPUTS = {
183+
...NETSUITE_OUTPUTS,
184+
data: {
185+
type: 'json',
186+
description:
187+
'Select-options response keyed by requested field ID; each dynamic field contains an _selectOptions object with links, items, count, offset, hasMore, and totalResults',
188+
nullable: true,
189+
properties: {
190+
links: NETSUITE_LINKS_OUTPUT,
191+
},
192+
},
193+
} as const satisfies Record<string, ToolOutputProperty>
194+
182195
export const NETSUITE_ACTION_OUTPUTS = {
183196
...NETSUITE_OUTPUTS,
184197
data: {

‎scripts/generate-docs.ts‎

Lines changed: 75 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -300,13 +300,43 @@ async function loadTriggerRegistry(): Promise<Record<string, RegistryTrigger>> {
300300
return module.TRIGGER_REGISTRY as Record<string, RegistryTrigger>
301301
}
302302

303+
interface GeneratedToolParam {
304+
type?: string
305+
required?: boolean
306+
description?: string
307+
}
308+
309+
interface GeneratedToolMetadataEntry {
310+
name?: string
311+
description?: string
312+
params?: Record<string, GeneratedToolParam>
313+
}
314+
315+
/** Serializable tool metadata generated from the executable registry and checked by CI. */
316+
let generatedToolMetadata: Record<string, GeneratedToolMetadataEntry> | null = null
317+
318+
async function loadGeneratedToolMetadata(): Promise<Record<string, GeneratedToolMetadataEntry>> {
319+
if (generatedToolMetadata) return generatedToolMetadata
320+
const module = await import(path.join(rootDir, 'apps/sim/tools/generated/tool-metadata.ts'))
321+
generatedToolMetadata = module.default as Record<string, GeneratedToolMetadataEntry>
322+
return generatedToolMetadata
323+
}
324+
325+
let generatedToolOutputs: Record<string, Record<string, any>> | null = null
326+
327+
async function loadGeneratedToolOutputs(): Promise<Record<string, Record<string, any>>> {
328+
if (generatedToolOutputs) return generatedToolOutputs
329+
const module = await import(path.join(rootDir, 'apps/sim/tools/generated/tool-outputs.ts'))
330+
generatedToolOutputs = module.default as Record<string, Record<string, any>>
331+
return generatedToolOutputs
332+
}
333+
303334
/** Human-facing tool names, keyed by tool id. Kept in sync with the registry by CI. */
304335
let toolDisplayNames: Map<string, string> | null = null
305336

306337
async function loadToolDisplayNames(): Promise<Map<string, string>> {
307338
if (toolDisplayNames) return toolDisplayNames
308-
const module = await import(path.join(rootDir, 'apps/sim/tools/generated/tool-metadata.ts'))
309-
const metadata = module.default as Record<string, { name?: string }>
339+
const metadata = await loadGeneratedToolMetadata()
310340
toolDisplayNames = new Map(
311341
Object.entries(metadata).flatMap(([id, entry]) =>
312342
entry?.name ? [[id, entry.name] as const] : []
@@ -2011,7 +2041,7 @@ function extractOutputsFromToolContent(content: string, toolPrefix: string): Rec
20112041
* Returns null when the tool declares its own config, which is the common case.
20122042
*/
20132043
function resolveFactorySource(fileContent: string, toolFilePath: string, rootDir: string): string {
2014-
const factoryCall = fileContent.match(/=\s*(create\w+)\s*\(\s*\{/)
2044+
const factoryCall = fileContent.match(/=\s*(create\w+)(?:\s*<[^>]+>)?\s*\(\s*\{/)
20152045
if (!factoryCall) return ''
20162046

20172047
const factoryName = factoryCall[1]
@@ -2919,11 +2949,48 @@ async function getToolInfo(toolName: string): Promise<{
29192949
return null
29202950
}
29212951

2922-
return extractToolInfo(
2923-
toolName,
2924-
toolFileContent,
2925-
resolveFactorySource(toolFileContent, foundFile, rootDir)
2926-
)
2952+
const factorySource = resolveFactorySource(toolFileContent, foundFile, rootDir)
2953+
const extractedInfo = extractToolInfo(toolName, toolFileContent, factorySource)
2954+
2955+
const delegatesParams = /\.\.\.options\.params\b/.test(factorySource)
2956+
const delegatesOutputs = /\boptions\.outputs\b/.test(factorySource)
2957+
if (!factorySource || (!delegatesParams && !delegatesOutputs)) return extractedInfo
2958+
2959+
// Factory-built tools often compose params and outputs through imported constants and
2960+
// spreads. The source parser intentionally stays lightweight, so fall back to the
2961+
// executable registry's generated metadata when it exposes fields the parser missed.
2962+
const [metadata, outputs] = await Promise.all([
2963+
loadGeneratedToolMetadata(),
2964+
loadGeneratedToolOutputs(),
2965+
])
2966+
const generatedEntry = metadata[toolName]
2967+
if (!generatedEntry) return extractedInfo
2968+
2969+
const generatedParams = Object.entries(generatedEntry.params ?? {})
2970+
.filter(([name]) => name !== 'accessToken' && name !== 'params' && name !== 'tools')
2971+
.map(([name, param]) => ({
2972+
name,
2973+
type: typeof param.type === 'string' ? param.type : 'string',
2974+
required: param.required === true,
2975+
description: param.description ?? 'No description',
2976+
}))
2977+
const extractedParams = extractedInfo?.params ?? []
2978+
const extractedOutputs = extractedInfo?.outputs ?? {}
2979+
const generatedOutputs = outputs[toolName] ?? {}
2980+
2981+
return {
2982+
description:
2983+
extractedInfo?.description ?? generatedEntry.description ?? 'No description available',
2984+
params:
2985+
delegatesParams && generatedParams.length > extractedParams.length
2986+
? generatedParams
2987+
: extractedParams,
2988+
outputs:
2989+
delegatesOutputs &&
2990+
Object.keys(generatedOutputs).length > Object.keys(extractedOutputs).length
2991+
? generatedOutputs
2992+
: extractedOutputs,
2993+
}
29272994
} catch (error) {
29282995
console.error(`Error getting info for tool ${toolName}:`, error)
29292996
return null

0 commit comments

Comments
 (0)