From d320fd2025009cc0ed6ca3584acf56459683388d Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 22 May 2026 08:17:56 +0000 Subject: [PATCH] feat: sync search types and fetch errors with platform API Co-authored-by: william --- src/__tests__/linkup-client.test.ts | 46 +++++++++++++++-------------- src/errors.ts | 14 ++++++++- src/types.ts | 10 ++----- src/utils/refine-error.utils.ts | 3 ++ 4 files changed, 42 insertions(+), 31 deletions(-) diff --git a/src/__tests__/linkup-client.test.ts b/src/__tests__/linkup-client.test.ts index fee6182..9dc5f0e 100644 --- a/src/__tests__/linkup-client.test.ts +++ b/src/__tests__/linkup-client.test.ts @@ -1,10 +1,10 @@ import axios, { type AxiosResponse } from 'axios'; import { z } from 'zod'; import { - FetchUrlIsFileError, LinkupAuthenticationError, LinkupFetchError, LinkupFetchResponseTooLargeError, + LinkupFetchUnsupportedContentTypeError, LinkupInsufficientCreditError, LinkupInvalidRequestError, LinkupNoResultError, @@ -13,7 +13,7 @@ import { LinkupUnknownError, } from '../errors'; import { LinkupClient } from '../linkup-client'; -import type { ImageSearchResult, SearchParams, Source, TextSearchResult } from '../types'; +import type { SearchParams, Source } from '../types'; import { refineError } from '../utils/refine-error.utils'; import type { X402Signer } from '../x402/types'; @@ -151,16 +151,16 @@ describe('LinkupClient', () => { url: 'http://foo.bar/baz', }, { - content: 'foo bar baz', - favicon: 'http://foo.bar/favicon.ico', + favicon: 'http://bar.baz/favicon.ico', name: 'bar', - type: 'text', - url: 'http://foo.bar/baz', + snippet: 'bar baz qux', + url: 'http://bar.baz/qux', }, { + favicon: 'http://foo.bar/favicon.ico', name: 'baz', - type: 'image', - url: 'http://foo.bar/baz', + snippet: '', + url: 'http://baz.qux/foo', }, ], }, @@ -177,16 +177,14 @@ describe('LinkupClient', () => { expect((result.sources.at(0) as Source)?.url).toEqual('http://foo.bar/baz'); expect((result.sources.at(0) as Source)?.snippet).toEqual('foo bar baz'); expect((result.sources.at(0) as Source)?.favicon).toEqual('http://foo.bar/favicon.ico'); - expect((result.sources.at(1) as TextSearchResult)?.type).toEqual('text'); - expect((result.sources.at(1) as TextSearchResult)?.name).toEqual('bar'); - expect((result.sources.at(1) as TextSearchResult)?.url).toEqual('http://foo.bar/baz'); - expect((result.sources.at(1) as TextSearchResult)?.content).toEqual('foo bar baz'); - expect((result.sources.at(1) as TextSearchResult)?.favicon).toEqual( - 'http://foo.bar/favicon.ico', - ); - expect((result.sources.at(2) as ImageSearchResult)?.type).toEqual('image'); - expect((result.sources.at(2) as ImageSearchResult)?.name).toEqual('baz'); - expect((result.sources.at(2) as ImageSearchResult)?.url).toEqual('http://foo.bar/baz'); + expect((result.sources.at(1) as Source)?.name).toEqual('bar'); + expect((result.sources.at(1) as Source)?.url).toEqual('http://bar.baz/qux'); + expect((result.sources.at(1) as Source)?.snippet).toEqual('bar baz qux'); + expect((result.sources.at(1) as Source)?.favicon).toEqual('http://bar.baz/favicon.ico'); + expect((result.sources.at(2) as Source)?.name).toEqual('baz'); + expect((result.sources.at(2) as Source)?.url).toEqual('http://baz.qux/foo'); + expect((result.sources.at(2) as Source)?.snippet).toEqual(''); + expect((result.sources.at(2) as Source)?.favicon).toEqual('http://foo.bar/favicon.ico'); }); it('should handle searchResults output type', async () => { @@ -695,11 +693,15 @@ describe('LinkupClient', () => { }, }, { - description: '400 FETCH_URL_IS_FILE', - ErrorClass: FetchUrlIsFileError, - expectedMessage: 'The URL points to a file', + description: '400 FETCH_UNSUPPORTED_CONTENT_TYPE', + ErrorClass: LinkupFetchUnsupportedContentTypeError, + expectedMessage: 'The URL returned an unsupported content type', input: { - error: { code: 'FETCH_URL_IS_FILE', details: [], message: 'The URL points to a file' }, + error: { + code: 'FETCH_UNSUPPORTED_CONTENT_TYPE', + details: [], + message: 'The URL returned an unsupported content type', + }, statusCode: 400, }, }, diff --git a/src/errors.ts b/src/errors.ts index 4b8ef40..b525f96 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -108,6 +108,17 @@ export class LinkupFetchResponseTooLargeError extends LinkupError { } } +export class LinkupFetchUnsupportedContentTypeError extends LinkupError { + constructor(message?: string) { + super(message); + this.name = LinkupFetchUnsupportedContentTypeError.name; + + if ('captureStackTrace' in Error) { + Error.captureStackTrace(this, LinkupFetchUnsupportedContentTypeError); + } + } +} + // Payment required error, raised when x402 payment signing fails or the 402 persists after retry. export class LinkupPaymentRequiredError extends LinkupError { constructor(message?: string) { @@ -120,7 +131,8 @@ export class LinkupPaymentRequiredError extends LinkupError { } } -export class FetchUrlIsFileError extends LinkupError { +// Backward-compatible alias for older platform deployments. +export class FetchUrlIsFileError extends LinkupFetchUnsupportedContentTypeError { constructor(message?: string) { super(message); this.name = FetchUrlIsFileError.name; diff --git a/src/types.ts b/src/types.ts index 8295934..05dd131 100644 --- a/src/types.ts +++ b/src/types.ts @@ -30,13 +30,7 @@ export type StructuredWithSources = { sources: StructuredSource[]; }; -export type StructuredSource = { - url: string; - content: string; - name: string; - type: string; - favicon: string; -}; +export type StructuredSource = TextSearchResult | ImageSearchResult; type BaseSearchRequestParams = { query: string; @@ -93,7 +87,7 @@ export type ImageSearchResult = { export type SourcedAnswer = { answer: string; - sources: (Source | TextSearchResult | ImageSearchResult)[]; + sources: Source[]; }; export type Source = { diff --git a/src/utils/refine-error.utils.ts b/src/utils/refine-error.utils.ts index fb0ee07..2beadfc 100644 --- a/src/utils/refine-error.utils.ts +++ b/src/utils/refine-error.utils.ts @@ -4,6 +4,7 @@ import { LinkupError, LinkupFetchError, LinkupFetchResponseTooLargeError, + LinkupFetchUnsupportedContentTypeError, LinkupInsufficientCreditError, LinkupInvalidRequestError, LinkupNoResultError, @@ -41,6 +42,8 @@ export const refineError = (e: LinkupApiError): LinkupError => { return new LinkupFetchError(message); case 'FETCH_RESPONSE_TOO_LARGE': return new LinkupFetchResponseTooLargeError(message); + case 'FETCH_UNSUPPORTED_CONTENT_TYPE': + return new LinkupFetchUnsupportedContentTypeError(message); case 'FETCH_URL_IS_FILE': return new FetchUrlIsFileError(message); default: