Skip to content

Commit 27ec412

Browse files
authored
fix(tools): fixed tool outputs (#2534)
* fixed typeform * fixed supabase tools * salesforce fix * removed comments * pinecone fix * fixed intercom * regex constant * removed fallback logic
1 parent 1f0e3f2 commit 27ec412

31 files changed

+1352
-220
lines changed

apps/sim/app/api/auth/oauth/token/route.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ export const dynamic = 'force-dynamic'
1010

1111
const logger = createLogger('OAuthTokenAPI')
1212

13+
const SALESFORCE_INSTANCE_URL_REGEX = /__sf_instance__:([^\s]+)/
14+
1315
const tokenRequestSchema = z.object({
1416
credentialId: z
1517
.string({ required_error: 'Credential ID is required' })
@@ -78,10 +80,20 @@ export async function POST(request: NextRequest) {
7880
try {
7981
// Refresh the token if needed
8082
const { accessToken } = await refreshTokenIfNeeded(requestId, credential, credentialId)
83+
84+
let instanceUrl: string | undefined
85+
if (credential.providerId === 'salesforce' && credential.scope) {
86+
const instanceMatch = credential.scope.match(SALESFORCE_INSTANCE_URL_REGEX)
87+
if (instanceMatch) {
88+
instanceUrl = instanceMatch[1]
89+
}
90+
}
91+
8192
return NextResponse.json(
8293
{
8394
accessToken,
8495
idToken: credential.idToken || undefined,
96+
...(instanceUrl && { instanceUrl }),
8597
},
8698
{ status: 200 }
8799
)
@@ -147,10 +159,21 @@ export async function GET(request: NextRequest) {
147159

148160
try {
149161
const { accessToken } = await refreshTokenIfNeeded(requestId, credential, credentialId)
162+
163+
// For Salesforce, extract instanceUrl from the scope field
164+
let instanceUrl: string | undefined
165+
if (credential.providerId === 'salesforce' && credential.scope) {
166+
const instanceMatch = credential.scope.match(SALESFORCE_INSTANCE_URL_REGEX)
167+
if (instanceMatch) {
168+
instanceUrl = instanceMatch[1]
169+
}
170+
}
171+
150172
return NextResponse.json(
151173
{
152174
accessToken,
153175
idToken: credential.idToken || undefined,
176+
...(instanceUrl && { instanceUrl }),
154177
},
155178
{ status: 200 }
156179
)

apps/sim/blocks/blocks/pinecone.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ export const PineconeBlock: BlockConfig<PineconeResponse> = {
264264

265265
outputs: {
266266
matches: { type: 'json', description: 'Search matches' },
267-
upsertedCount: { type: 'number', description: 'Upserted count' },
267+
statusText: { type: 'string', description: 'Status of the upsert operation' },
268268
data: { type: 'json', description: 'Response data' },
269269
model: { type: 'string', description: 'Model information' },
270270
vector_type: { type: 'string', description: 'Vector type' },

apps/sim/blocks/blocks/supabase.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -596,13 +596,20 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e
596596
},
597597
// Storage Upload fields
598598
{
599-
id: 'path',
600-
title: 'File Path',
599+
id: 'fileName',
600+
title: 'File Name',
601601
type: 'short-input',
602-
placeholder: 'folder/file.jpg',
602+
placeholder: 'myfile.pdf',
603603
condition: { field: 'operation', value: 'storage_upload' },
604604
required: true,
605605
},
606+
{
607+
id: 'path',
608+
title: 'Folder Path (optional)',
609+
type: 'short-input',
610+
placeholder: 'folder/subfolder/',
611+
condition: { field: 'operation', value: 'storage_upload' },
612+
},
606613
{
607614
id: 'fileContent',
608615
title: 'File Content',
@@ -1065,10 +1072,10 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e
10651072
countType: { type: 'string', description: 'Count type: exact, planned, or estimated' },
10661073
// Storage operation inputs
10671074
bucket: { type: 'string', description: 'Storage bucket name' },
1068-
path: { type: 'string', description: 'File path in storage' },
1075+
path: { type: 'string', description: 'File or folder path in storage' },
10691076
fileContent: { type: 'string', description: 'File content (base64 for binary)' },
10701077
contentType: { type: 'string', description: 'MIME type of the file' },
1071-
fileName: { type: 'string', description: 'Optional filename override for downloaded file' },
1078+
fileName: { type: 'string', description: 'File name for upload or download override' },
10721079
upsert: { type: 'boolean', description: 'Whether to overwrite existing file' },
10731080
download: { type: 'boolean', description: 'Whether to force download' },
10741081
paths: { type: 'array', description: 'Array of file paths' },

apps/sim/blocks/blocks/typeform.ts

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,10 +199,76 @@ export const TypeformBlock: BlockConfig<TypeformResponse> = {
199199
{
200200
id: 'operations',
201201
title: 'JSON Patch Operations',
202-
type: 'long-input',
203-
placeholder: 'JSON array of patch operations (RFC 6902)',
202+
type: 'code',
203+
language: 'json',
204+
placeholder: '[{"op": "replace", "path": "/title", "value": "New Title"}]',
204205
condition: { field: 'operation', value: 'typeform_update_form' },
205206
required: true,
207+
wandConfig: {
208+
enabled: true,
209+
maintainHistory: true,
210+
prompt: `You are an expert at creating JSON Patch operations (RFC 6902) for Typeform forms.
211+
Generate ONLY the JSON array of patch operations based on the user's request.
212+
The output MUST be a valid JSON array, starting with [ and ending with ].
213+
214+
Current operations: {context}
215+
216+
### JSON PATCH OPERATIONS
217+
Each operation is an object with:
218+
- "op": The operation type ("add", "remove", "replace", "move", "copy", "test")
219+
- "path": JSON pointer to the target location (e.g., "/title", "/fields/0", "/settings/language")
220+
- "value": The new value (required for "add", "replace", "copy", "test")
221+
- "from": Source path (required for "move" and "copy")
222+
223+
### COMMON TYPEFORM PATHS
224+
- /title - Form title
225+
- /settings/language - Form language (e.g., "en", "es", "fr")
226+
- /settings/is_public - Whether form is public (true/false)
227+
- /settings/show_progress_bar - Show progress bar (true/false)
228+
- /fields - Array of form fields
229+
- /fields/- - Add to end of fields array
230+
- /fields/0 - First field
231+
- /welcome_screens - Array of welcome screens
232+
- /thankyou_screens - Array of thank you screens
233+
- /theme/href - Theme URL reference
234+
235+
### FIELD OBJECT STRUCTURE
236+
{
237+
"type": "short_text" | "long_text" | "email" | "number" | "multiple_choice" | "yes_no" | "rating" | "date" | "dropdown" | "file_upload",
238+
"title": "Question text",
239+
"ref": "unique_reference_id",
240+
"properties": { ... },
241+
"validations": { "required": true/false }
242+
}
243+
244+
### EXAMPLES
245+
246+
**Change form title:**
247+
[{"op": "replace", "path": "/title", "value": "My Updated Form"}]
248+
249+
**Add a new text field:**
250+
[{"op": "add", "path": "/fields/-", "value": {"type": "short_text", "title": "What is your name?", "ref": "name_field", "validations": {"required": true}}}]
251+
252+
**Add multiple choice field:**
253+
[{"op": "add", "path": "/fields/-", "value": {"type": "multiple_choice", "title": "Select your favorite color", "ref": "color_field", "properties": {"choices": [{"label": "Red"}, {"label": "Blue"}, {"label": "Green"}]}}}]
254+
255+
**Remove first field:**
256+
[{"op": "remove", "path": "/fields/0"}]
257+
258+
**Update form settings:**
259+
[{"op": "replace", "path": "/settings/language", "value": "es"}, {"op": "replace", "path": "/settings/is_public", "value": false}]
260+
261+
**Multiple operations:**
262+
[
263+
{"op": "replace", "path": "/title", "value": "Customer Feedback Form"},
264+
{"op": "add", "path": "/fields/-", "value": {"type": "rating", "title": "Rate your experience", "ref": "rating_field", "properties": {"steps": 5}}},
265+
{"op": "replace", "path": "/settings/show_progress_bar", "value": true}
266+
]
267+
268+
Do not include any explanations, markdown formatting, or other text outside the JSON array.`,
269+
placeholder: 'Describe how you want to update the form...',
270+
generationType: 'json-object',
271+
},
206272
},
207273
...getTrigger('typeform_webhook').subBlocks,
208274
],
@@ -322,6 +388,9 @@ export const TypeformBlock: BlockConfig<TypeformResponse> = {
322388
fields: { type: 'json', description: 'Form fields array' },
323389
welcome_screens: { type: 'json', description: 'Welcome screens array' },
324390
thankyou_screens: { type: 'json', description: 'Thank you screens array' },
391+
created_at: { type: 'string', description: 'Form creation timestamp' },
392+
last_updated_at: { type: 'string', description: 'Form last update timestamp' },
393+
published_at: { type: 'string', description: 'Form publication timestamp' },
325394
_links: { type: 'json', description: 'Related resource links' },
326395
// Delete form outputs
327396
deleted: { type: 'boolean', description: 'Whether the form was deleted' },

apps/sim/lib/auth/auth.ts

Lines changed: 69 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,35 @@ export const auth = betterAuth({
120120
})
121121

122122
if (existing) {
123+
let scopeToStore = account.scope
124+
125+
if (account.providerId === 'salesforce' && account.accessToken) {
126+
try {
127+
const response = await fetch(
128+
'https://login.salesforce.com/services/oauth2/userinfo',
129+
{
130+
headers: {
131+
Authorization: `Bearer ${account.accessToken}`,
132+
},
133+
}
134+
)
135+
136+
if (response.ok) {
137+
const data = await response.json()
138+
139+
if (data.profile) {
140+
const match = data.profile.match(/^(https:\/\/[^/]+)/)
141+
if (match && match[1] !== 'https://login.salesforce.com') {
142+
const instanceUrl = match[1]
143+
scopeToStore = `__sf_instance__:${instanceUrl} ${account.scope}`
144+
}
145+
}
146+
}
147+
} catch (error) {
148+
logger.error('Failed to fetch Salesforce instance URL', { error })
149+
}
150+
}
151+
123152
await db
124153
.update(schema.account)
125154
.set({
@@ -129,7 +158,7 @@ export const auth = betterAuth({
129158
idToken: account.idToken,
130159
accessTokenExpiresAt: account.accessTokenExpiresAt,
131160
refreshTokenExpiresAt: account.refreshTokenExpiresAt,
132-
scope: account.scope,
161+
scope: scopeToStore,
133162
updatedAt: new Date(),
134163
})
135164
.where(eq(schema.account.id, existing.id))
@@ -140,24 +169,45 @@ export const auth = betterAuth({
140169
return { data: account }
141170
},
142171
after: async (account) => {
143-
// Salesforce doesn't return expires_in in its token response (unlike other OAuth providers).
144-
// We set a default 2-hour expiration so token refresh logic works correctly.
145-
if (account.providerId === 'salesforce' && !account.accessTokenExpiresAt) {
146-
const twoHoursFromNow = new Date(Date.now() + 2 * 60 * 60 * 1000)
147-
try {
148-
await db
149-
.update(schema.account)
150-
.set({ accessTokenExpiresAt: twoHoursFromNow })
151-
.where(eq(schema.account.id, account.id))
152-
logger.info(
153-
'[databaseHooks.account.create.after] Set default expiration for Salesforce token',
154-
{ accountId: account.id, expiresAt: twoHoursFromNow }
155-
)
156-
} catch (error) {
157-
logger.error(
158-
'[databaseHooks.account.create.after] Failed to set Salesforce token expiration',
159-
{ accountId: account.id, error }
160-
)
172+
if (account.providerId === 'salesforce') {
173+
const updates: {
174+
accessTokenExpiresAt?: Date
175+
scope?: string
176+
} = {}
177+
178+
if (!account.accessTokenExpiresAt) {
179+
updates.accessTokenExpiresAt = new Date(Date.now() + 2 * 60 * 60 * 1000)
180+
}
181+
182+
if (account.accessToken) {
183+
try {
184+
const response = await fetch(
185+
'https://login.salesforce.com/services/oauth2/userinfo',
186+
{
187+
headers: {
188+
Authorization: `Bearer ${account.accessToken}`,
189+
},
190+
}
191+
)
192+
193+
if (response.ok) {
194+
const data = await response.json()
195+
196+
if (data.profile) {
197+
const match = data.profile.match(/^(https:\/\/[^/]+)/)
198+
if (match && match[1] !== 'https://login.salesforce.com') {
199+
const instanceUrl = match[1]
200+
updates.scope = `__sf_instance__:${instanceUrl} ${account.scope}`
201+
}
202+
}
203+
}
204+
} catch (error) {
205+
logger.error('Failed to fetch Salesforce instance URL', { error })
206+
}
207+
}
208+
209+
if (Object.keys(updates).length > 0) {
210+
await db.update(schema.account).set(updates).where(eq(schema.account.id, account.id))
161211
}
162212
}
163213
},
@@ -928,8 +978,6 @@ export const auth = betterAuth({
928978
redirectURI: `${getBaseUrl()}/api/auth/oauth2/callback/salesforce`,
929979
getUserInfo: async (tokens) => {
930980
try {
931-
logger.info('Fetching Salesforce user profile')
932-
933981
const response = await fetch(
934982
'https://login.salesforce.com/services/oauth2/userinfo',
935983
{

0 commit comments

Comments
 (0)