Skip to content

Commit b024d63

Browse files
committed
Update oauth cred tool
1 parent aa3be4b commit b024d63

File tree

1 file changed

+111
-18
lines changed

1 file changed

+111
-18
lines changed

apps/sim/lib/copilot/orchestrator/sse/handlers/tool-execution.ts

Lines changed: 111 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,110 @@ const logger = createLogger('CopilotSseToolExecution')
2828

2929
const OUTPUT_PATH_TOOLS = new Set(['function_execute', 'user_table'])
3030

31+
/**
32+
* Try to pull a flat array of row-objects out of the various shapes that
33+
* `function_execute` and `user_table` can return.
34+
*/
35+
function extractTabularData(output: unknown): Record<string, unknown>[] | null {
36+
if (!output || typeof output !== 'object') return null
37+
38+
if (Array.isArray(output)) {
39+
if (output.length > 0 && typeof output[0] === 'object' && output[0] !== null) {
40+
return output as Record<string, unknown>[]
41+
}
42+
return null
43+
}
44+
45+
const obj = output as Record<string, unknown>
46+
47+
// function_execute shape: { result: [...], stdout: "..." }
48+
if (Array.isArray(obj.result)) {
49+
const rows = obj.result
50+
if (rows.length > 0 && typeof rows[0] === 'object' && rows[0] !== null) {
51+
return rows as Record<string, unknown>[]
52+
}
53+
}
54+
55+
// user_table query_rows shape: { data: { rows: [{ data: {...} }], totalCount } }
56+
if (obj.data && typeof obj.data === 'object' && !Array.isArray(obj.data)) {
57+
const data = obj.data as Record<string, unknown>
58+
if (Array.isArray(data.rows) && data.rows.length > 0) {
59+
const rows = data.rows as Record<string, unknown>[]
60+
// user_table rows nest actual values inside .data
61+
if (typeof rows[0].data === 'object' && rows[0].data !== null) {
62+
return rows.map((r) => r.data as Record<string, unknown>)
63+
}
64+
return rows
65+
}
66+
}
67+
68+
return null
69+
}
70+
71+
function escapeCsvValue(value: unknown): string {
72+
if (value === null || value === undefined) return ''
73+
const str = typeof value === 'object' ? JSON.stringify(value) : String(value)
74+
if (str.includes(',') || str.includes('"') || str.includes('\n') || str.includes('\r')) {
75+
return `"${str.replace(/"/g, '""')}"`
76+
}
77+
return str
78+
}
79+
80+
function convertRowsToCsv(rows: Record<string, unknown>[]): string {
81+
if (rows.length === 0) return ''
82+
83+
const headerSet = new Set<string>()
84+
for (const row of rows) {
85+
for (const key of Object.keys(row)) {
86+
headerSet.add(key)
87+
}
88+
}
89+
const headers = [...headerSet]
90+
91+
const lines = [headers.map(escapeCsvValue).join(',')]
92+
for (const row of rows) {
93+
lines.push(headers.map((h) => escapeCsvValue(row[h])).join(','))
94+
}
95+
return lines.join('\n')
96+
}
97+
98+
type OutputFormat = 'json' | 'csv' | 'txt' | 'md' | 'html'
99+
100+
const EXT_TO_FORMAT: Record<string, OutputFormat> = {
101+
'.json': 'json',
102+
'.csv': 'csv',
103+
'.txt': 'txt',
104+
'.md': 'md',
105+
'.html': 'html',
106+
}
107+
108+
const FORMAT_TO_CONTENT_TYPE: Record<OutputFormat, string> = {
109+
json: 'application/json',
110+
csv: 'text/csv',
111+
txt: 'text/plain',
112+
md: 'text/markdown',
113+
html: 'text/html',
114+
}
115+
116+
function resolveOutputFormat(fileName: string, explicit?: string): OutputFormat {
117+
if (explicit && explicit in FORMAT_TO_CONTENT_TYPE) return explicit as OutputFormat
118+
const ext = fileName.slice(fileName.lastIndexOf('.')).toLowerCase()
119+
return EXT_TO_FORMAT[ext] ?? 'json'
120+
}
121+
122+
function serializeOutputForFile(output: unknown, format: OutputFormat): string {
123+
if (typeof output === 'string') return output
124+
125+
if (format === 'csv') {
126+
const rows = extractTabularData(output)
127+
if (rows && rows.length > 0) {
128+
return convertRowsToCsv(rows)
129+
}
130+
}
131+
132+
return JSON.stringify(output, null, 2)
133+
}
134+
31135
async function maybeWriteOutputToFile(
32136
toolName: string,
33137
params: Record<string, unknown> | undefined,
@@ -38,30 +142,19 @@ async function maybeWriteOutputToFile(
38142
if (!OUTPUT_PATH_TOOLS.has(toolName)) return result
39143
if (!context.workspaceId || !context.userId) return result
40144

145+
const args = params?.args as Record<string, unknown> | undefined
41146
const outputPath =
42-
(params?.outputPath as string | undefined) ??
43-
((params?.args as Record<string, unknown> | undefined)?.outputPath as string | undefined)
147+
(params?.outputPath as string | undefined) ?? (args?.outputPath as string | undefined)
44148
if (!outputPath) return result
45149

150+
const explicitFormat =
151+
(params?.outputFormat as string | undefined) ?? (args?.outputFormat as string | undefined)
46152
const fileName = outputPath.replace(/^files\//, '')
153+
const format = resolveOutputFormat(fileName, explicitFormat)
47154

48155
try {
49-
let content: string
50-
if (typeof result.output === 'string') {
51-
content = result.output
52-
} else {
53-
content = JSON.stringify(result.output, null, 2)
54-
}
55-
56-
const contentType = fileName.endsWith('.json')
57-
? 'application/json'
58-
: fileName.endsWith('.csv')
59-
? 'text/csv'
60-
: fileName.endsWith('.md')
61-
? 'text/markdown'
62-
: fileName.endsWith('.html')
63-
? 'text/html'
64-
: 'text/plain'
156+
const content = serializeOutputForFile(result.output, format)
157+
const contentType = FORMAT_TO_CONTENT_TYPE[format]
65158

66159
const buffer = Buffer.from(content, 'utf-8')
67160
const uploaded = await uploadWorkspaceFile(

0 commit comments

Comments
 (0)