Skip to content

Commit 37b8a66

Browse files
glasseraiclaude
andcommitted
fix(glasser): bound polling failures, split social inputs, drop section comments
Address review: a rejected or undecodable poll now counts toward the three-error budget and returns success: false instead of escaping the loop; Social Media Search gets its own query and URL controls keyed on its mode rather than the hidden web action; the news query copy says the domain stays required (the API contract requires it); test drops its any cast; non-TSDoc section comments removed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
1 parent 397c2fe commit 37b8a66

6 files changed

Lines changed: 79 additions & 44 deletions

File tree

‎apps/sim/blocks/blocks/glasser.ts‎

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ const ID_TO_PARAM: Record<string, string> = {
7373
ci_query: 'query',
7474
sr_platform: 'platform',
7575
sr_mode: 'mode',
76+
sr_query: 'query',
77+
sr_url: 'url',
7678
}
7779

7880
/** Per operation, the sub-block that carries the API's `action`. */
@@ -118,7 +120,7 @@ export const GlasserBlock: BlockConfig<GlasserResponse> = {
118120
[SOCIAL]: [
119121
{ text: 'Get', field: 'sr_mode', core: true },
120122
{ text: 'on', field: 'sr_platform', core: true },
121-
{ text: 'for', field: ['query', 'handle', 'url'] },
123+
{ text: 'for', field: ['sr_query', 'handle', 'sr_url'] },
122124
],
123125
[MARKET]: [
124126
{ text: 'Get', field: 'md_action', core: true },
@@ -144,7 +146,6 @@ export const GlasserBlock: BlockConfig<GlasserResponse> = {
144146
value: () => PEOPLE,
145147
},
146148

147-
// --- Find Prospects ---
148149
{
149150
id: 'ps_action',
150151
title: 'Action',
@@ -273,7 +274,6 @@ export const GlasserBlock: BlockConfig<GlasserResponse> = {
273274
},
274275
},
275276

276-
// --- Company Intelligence ---
277277
{
278278
id: 'ci_action',
279279
title: 'Company Report',
@@ -306,7 +306,6 @@ export const GlasserBlock: BlockConfig<GlasserResponse> = {
306306
mode: 'advanced',
307307
},
308308

309-
// --- Keywords and SEO ---
310309
{
311310
id: 'seo_action',
312311
title: 'SEO Report',
@@ -344,7 +343,6 @@ export const GlasserBlock: BlockConfig<GlasserResponse> = {
344343
},
345344
},
346345

347-
// --- Web Research ---
348346
{
349347
id: 'wr_action',
350348
title: 'Action',
@@ -371,7 +369,7 @@ export const GlasserBlock: BlockConfig<GlasserResponse> = {
371369
placeholder: 'Search phrase or question',
372370
condition: {
373371
field: 'operation',
374-
value: [WEB, SOCIAL],
372+
value: WEB,
375373
and: { field: 'wr_action', value: ['scrape', 'similar'], not: true },
376374
},
377375
required: {
@@ -387,7 +385,7 @@ export const GlasserBlock: BlockConfig<GlasserResponse> = {
387385
placeholder: 'https://example.com/page',
388386
condition: {
389387
field: 'operation',
390-
value: [WEB, SOCIAL],
388+
value: WEB,
391389
and: { field: 'wr_action', value: ['scrape', 'similar'] },
392390
},
393391
required: {
@@ -405,7 +403,6 @@ export const GlasserBlock: BlockConfig<GlasserResponse> = {
405403
mode: 'advanced',
406404
},
407405

408-
// --- Social Media Search ---
409406
{
410407
id: 'sr_platform',
411408
title: 'Platform',
@@ -436,6 +433,30 @@ export const GlasserBlock: BlockConfig<GlasserResponse> = {
436433
value: () => 'search',
437434
condition: { field: 'operation', value: SOCIAL },
438435
},
436+
{
437+
id: 'sr_query',
438+
title: 'Query',
439+
type: 'long-input',
440+
placeholder: 'Search phrase',
441+
condition: {
442+
field: 'operation',
443+
value: SOCIAL,
444+
and: { field: 'sr_mode', value: ['search', 'find'] },
445+
},
446+
required: { field: 'operation', value: SOCIAL, and: { field: 'sr_mode', value: 'search' } },
447+
},
448+
{
449+
id: 'sr_url',
450+
title: 'URL',
451+
type: 'short-input',
452+
placeholder: 'Post URL, or a LinkedIn profile or company page URL',
453+
condition: {
454+
field: 'operation',
455+
value: SOCIAL,
456+
and: { field: 'sr_mode', value: 'search', not: true },
457+
},
458+
required: { field: 'operation', value: SOCIAL, and: { field: 'sr_mode', value: 'post' } },
459+
},
439460
{
440461
id: 'handle',
441462
title: 'Handle',
@@ -448,7 +469,6 @@ export const GlasserBlock: BlockConfig<GlasserResponse> = {
448469
},
449470
},
450471

451-
// --- Market Data ---
452472
{
453473
id: 'md_action',
454474
title: 'Market Report',
@@ -551,7 +571,6 @@ export const GlasserBlock: BlockConfig<GlasserResponse> = {
551571
},
552572
},
553573

554-
// --- Shared ---
555574
{
556575
id: 'country',
557576
title: 'Country',
@@ -630,7 +649,6 @@ export const GlasserBlock: BlockConfig<GlasserResponse> = {
630649
task_id: { type: 'string', description: 'Task the runs are filed under' },
631650
limit: { type: 'number', description: 'Rows to return' },
632651
country: { type: 'string', description: 'Country as ISO code or name' },
633-
// Find Prospects
634652
ps_action: { type: 'string', description: 'search, enrich or find_email' },
635653
job_titles: { type: 'string', description: 'Job titles, comma-separated' },
636654
seniorities: { type: 'json', description: 'Seniority levels' },
@@ -640,23 +658,20 @@ export const GlasserBlock: BlockConfig<GlasserResponse> = {
640658
full_name: { type: 'string', description: "Person's full name" },
641659
email: { type: 'string', description: 'Email address' },
642660
linkedin_url: { type: 'string', description: 'LinkedIn profile URL' },
643-
// Company Intelligence
644661
ci_action: { type: 'string', description: 'Company report to fetch' },
645662
domain: { type: 'string', description: 'Website domain' },
646663
ci_query: { type: 'string', description: 'Company name for news search' },
647-
// Keywords and SEO
648664
seo_action: { type: 'string', description: 'SEO report to fetch' },
649665
seo_keywords: { type: 'string', description: 'Keywords, comma-separated' },
650-
// Web Research
651666
wr_action: { type: 'string', description: 'Web research action' },
652667
query: { type: 'string', description: 'Search phrase or question' },
653-
url: { type: 'string', description: 'Page or post URL' },
668+
url: { type: 'string', description: 'Page URL' },
654669
language: { type: 'string', description: 'Two-letter language code' },
655-
// Social Media Search
656670
sr_platform: { type: 'string', description: 'Social platform' },
657671
sr_mode: { type: 'string', description: 'What to read on the platform' },
672+
sr_query: { type: 'string', description: 'Search phrase (social)' },
673+
sr_url: { type: 'string', description: 'Post, profile or company page URL (social)' },
658674
handle: { type: 'string', description: 'Username or subreddit' },
659-
// Market Data
660675
md_action: { type: 'string', description: 'Market report to fetch' },
661676
address: { type: 'string', description: 'US street address' },
662677
city: { type: 'string', description: 'US city' },

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

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

‎apps/sim/tools/glasser/company_intelligence.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export const companyIntelligenceTool: ToolConfig<
4646
required: false,
4747
visibility: 'user-or-llm',
4848
description:
49-
'For news via Serper only: the company name to search Google News by, instead of the domain',
49+
'For news via Serper only: the company name to search Google News by. The domain is still required and identifies the company.',
5050
},
5151
limit: COMMON_PARAMS.limit,
5252
provider: {

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

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,8 @@ const respond = (body: unknown, status = 200) =>
4343

4444
describe('glasser request shape', () => {
4545
it('posts to the gtm solution with a bearer key and a fresh idempotency key per call', () => {
46-
const headers = peopleSearchTool.request.headers as (p: any) => Record<string, string>
47-
const first = headers({ apiKey: 'gl_test' })
48-
const second = headers({ apiKey: 'gl_test' })
46+
const first = peopleSearchTool.request.headers({ apiKey: 'gl_test' })
47+
const second = peopleSearchTool.request.headers({ apiKey: 'gl_test' })
4948

5049
expect(peopleSearchTool.request.url).toBe(`${GLASSER_API_BASE}/v1/solutions/gtm/people_search`)
5150
expect(first.Authorization).toBe('Bearer gl_test')
@@ -169,6 +168,23 @@ describe('glasser polling', () => {
169168
expect(result.output.status).toBe('COMPLETED')
170169
})
171170

171+
it('counts a rejected poll toward the error budget instead of throwing', async () => {
172+
vi.useFakeTimers()
173+
const fetchMock = vi.fn().mockRejectedValue(new Error('socket hang up'))
174+
vi.stubGlobal('fetch', fetchMock)
175+
const initial = await companyIntelligenceTool.transformResponse!(
176+
respond(run({ status: 'RUNNING', output: null }), 202)
177+
)
178+
179+
const pending = pollRun(initial, { apiKey: 'k' })
180+
await vi.advanceTimersByTimeAsync(6000)
181+
const result = await pending
182+
183+
expect(fetchMock).toHaveBeenCalledTimes(3)
184+
expect(result.success).toBe(false)
185+
expect(result.error).toBe('Glasser polling failed: socket hang up')
186+
})
187+
172188
it('gives up after repeated polling errors', async () => {
173189
vi.useFakeTimers()
174190
const fetchMock = vi.fn().mockImplementation(async () => respond({ error: 'down' }, 503))

‎apps/sim/tools/glasser/run.ts‎

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { getErrorMessage } from '@sim/utils/errors'
12
import { sleep } from '@sim/utils/helpers'
23
import { generateId } from '@sim/utils/id'
34
import type { GlasserResponse, GlasserRun, GlasserRunOutput } from '@/tools/glasser/types'
@@ -98,7 +99,9 @@ export const transformRun: ToolConfig<{ apiKey: string }, GlasserResponse>['tran
9899

99100
/**
100101
* A run that came back QUEUED or RUNNING is read from `GET /v1/runs/{id}` until it is
101-
* terminal or the budget is spent. Used as every Glasser tool's `postProcess`.
102+
* terminal or the budget is spent. Used as every Glasser tool's `postProcess`. It never throws:
103+
* a thrown error would make the executor fall back to the initial in-flight result, so every
104+
* failure is returned as a bounded `success: false` instead.
102105
*/
103106
export async function pollRun(
104107
result: GlasserResponse,
@@ -117,29 +120,30 @@ export async function pollRun(
117120
await sleep(POLL_INTERVAL_MS)
118121
elapsed += POLL_INTERVAL_MS
119122

120-
const response = await fetch(`${GLASSER_API_BASE}/v1/runs/${encodeURIComponent(runId)}`, {
121-
headers: {
122-
Authorization: `Bearer ${params.apiKey}`,
123-
'User-Agent': USER_AGENT,
124-
},
125-
})
126-
127-
if (!response.ok) {
128-
consecutiveErrors += 1
129-
if (consecutiveErrors >= MAX_CONSECUTIVE_POLL_ERRORS) {
130-
const errorText = await response.text().catch(() => '')
131-
return {
132-
success: false,
133-
error: `Glasser API error: ${response.status} - ${errorText}`,
134-
output: result.output,
135-
}
123+
let failure: string | null = null
124+
try {
125+
const response = await fetch(`${GLASSER_API_BASE}/v1/runs/${encodeURIComponent(runId)}`, {
126+
headers: {
127+
Authorization: `Bearer ${params.apiKey}`,
128+
'User-Agent': USER_AGENT,
129+
},
130+
})
131+
if (response.ok) {
132+
const run = (await response.json()) as GlasserRun
133+
if (TERMINAL_STATUSES.has(run.status)) return runResult(run)
134+
consecutiveErrors = 0
135+
continue
136136
}
137-
continue
137+
const errorText = await response.text().catch(() => '')
138+
failure = `Glasser API error: ${response.status} - ${errorText}`
139+
} catch (error) {
140+
failure = `Glasser polling failed: ${getErrorMessage(error)}`
138141
}
139-
consecutiveErrors = 0
140142

141-
const run = (await response.json()) as GlasserRun
142-
if (TERMINAL_STATUSES.has(run.status)) return runResult(run)
143+
consecutiveErrors += 1
144+
if (consecutiveErrors >= MAX_CONSECUTIVE_POLL_ERRORS) {
145+
return { success: false, error: failure, output: result.output }
146+
}
143147
}
144148

145149
return {

‎apps/sim/tools/glasser/web_research.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ export const webResearchTool: ToolConfig<GlasserWebResearchParams, GlasserRespon
6060
},
6161

6262
request: {
63-
// Only `answer` hands the query to a model (Exa's answer endpoint); every other action is a plain search or fetch.
63+
/** Only `answer` hands the query to a model (Exa's answer endpoint); every other action is a plain search or fetch. */
6464
modelInput: {
6565
mode: 'project',
6666
select: (params) => (params.action === 'answer' ? { query: params.query } : {}),

0 commit comments

Comments
 (0)