{"version":3,"file":"jina.mjs","names":[],"sources":["../../src/providers/jina.ts"],"sourcesContent":["import type {\n  SearchResult,\n  SearchRequestOptions,\n  ReadResult,\n  ReadOptions,\n  ProviderConfig,\n} from \"../core/types.ts\";\nimport { Provider, assertProviderBaseURL } from \"../core/provider.ts\";\nimport { JINA_MAX_RESULTS, JINA_SEARCH_CATEGORIES } from \"../core/providers.ts\";\nimport { AuthError, HTTPError, normalizeError } from \"../core/errors.ts\";\n\ninterface JinaResult {\n  readonly title?: string;\n  readonly description?: string;\n  readonly url: string;\n  readonly content?: string;\n  readonly text?: string;\n  readonly html?: string;\n  readonly publishedTime?: string;\n  readonly links?: readonly string[] | Readonly<Record<string, string>> | null;\n  readonly images?: readonly string[] | Readonly<Record<string, string>> | null;\n  readonly metadata?: Readonly<Record<string, unknown>> | null;\n  readonly warning?: string;\n  readonly screenshotUrl?: string;\n  readonly pageshotUrl?: string;\n}\n\ninterface JinaEnvelope {\n  readonly code?: number;\n  readonly status?: number;\n  readonly message?: string;\n  readonly error?: string;\n  readonly detail?: unknown;\n  readonly meta?: Readonly<Record<string, unknown>>;\n}\n\ninterface JinaSearchResponse extends JinaEnvelope {\n  readonly data?: readonly JinaResult[] | null;\n}\n\ninterface JinaReadResponse extends JinaEnvelope {\n  readonly data?: JinaResult | null;\n}\n\nexport class JinaProvider extends Provider {\n  static readonly providerName = \"jina\";\n  static readonly defaultBaseURL = \"https://s.jina.ai\";\n\n  private readonly searchBaseURL: string;\n  private readonly readBaseURL: string;\n  private readonly apiKey?: string;\n\n  constructor(config: Readonly<ProviderConfig>) {\n    super(config, JinaProvider);\n    this.searchBaseURL = this.baseURL;\n    this.readBaseURL = (config.readBaseURL ?? deriveReadBaseURL(this.searchBaseURL)).replace(\n      /\\/+$/,\n      \"\",\n    );\n    assertProviderBaseURL(this.readBaseURL, JinaProvider.providerName);\n    this.apiKey = config.apiKey;\n  }\n\n  async search(query: string, options?: SearchRequestOptions): Promise<SearchResult[]> {\n    const apiKey = requireApiKey(this.apiKey);\n    try {\n      const url = `${this.searchBaseURL}/search?${searchParams(query, options)}`;\n      const response = await this.client.getJSON<JinaSearchResponse>(\n        url,\n        {\n          Authorization: `Bearer ${apiKey}`,\n          Accept: \"application/json\",\n        },\n        options?.signal,\n      );\n      assertJinaSuccess(response, url);\n      return (response.data ?? []).map(mapSearchResult);\n    } catch (error) {\n      throw normalizeError(error, \"jina\");\n    }\n  }\n\n  async read(url: string, options?: Readonly<ReadOptions>): Promise<ReadResult> {\n    try {\n      const requestUrl = `${this.readBaseURL}/${encodeURIComponent(url)}`;\n      const response = await this.client.getJSON<JinaReadResponse>(\n        requestUrl,\n        readHeaders(this.apiKey, options),\n        options?.signal,\n      );\n      assertJinaSuccess(response, requestUrl);\n      return mapReadResult(response.data ?? { url, content: \"\" });\n    } catch (error) {\n      throw normalizeError(error, \"jina\");\n    }\n  }\n}\n\nfunction requireApiKey(apiKey: string | undefined): string {\n  if (apiKey) return apiKey;\n  throw new AuthError(\"Missing API key for Jina. Set JINA_API_KEY\", \"jina\");\n}\n\nfunction searchParams(query: string, options?: SearchRequestOptions): string {\n  const params = new URLSearchParams({\n    q: query,\n    count: String(clampMaxResults(options?.maxResults ?? 10)),\n  });\n  if (isJinaSearchType(options?.category)) params.set(\"type\", options.category);\n  for (const domain of options?.includeDomains ?? []) params.append(\"site\", domain);\n  return params.toString();\n}\n\nfunction deriveReadBaseURL(searchBaseURL: string): string {\n  const match = searchBaseURL.match(/^(https?:\\/\\/)(.+\\.)?s\\.jina\\.ai$/);\n  if (!match) return searchBaseURL;\n  return `${match[1]}${match[2] ?? \"\"}r.jina.ai`;\n}\n\nfunction readHeaders(\n  apiKey: string | undefined,\n  options: Readonly<ReadOptions> | undefined,\n): Record<string, string> {\n  return {\n    Accept: \"application/json\",\n    ...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),\n    ...readOptionHeaders(options),\n  };\n}\n\nfunction readOptionHeaders(options: Readonly<ReadOptions> | undefined): Record<string, string> {\n  if (!options) return {};\n  return { ...readContentHeaders(options), ...readControlHeaders(options) };\n}\n\nfunction readContentHeaders(options: Readonly<ReadOptions>): Record<string, string> {\n  return {\n    ...(options.format ? { \"X-Return-Format\": options.format } : {}),\n    ...(options.maxTokens === undefined ? {} : { \"X-Token-Budget\": String(options.maxTokens) }),\n    ...(options.targetSelector ? { \"X-Target-Selector\": options.targetSelector } : {}),\n  };\n}\n\nfunction readControlHeaders(options: Readonly<ReadOptions>): Record<string, string> {\n  return {\n    ...(options.removeSelector ? { \"X-Remove-Selector\": options.removeSelector } : {}),\n    ...(options.timeout === undefined ? {} : { \"X-Timeout\": String(options.timeout) }),\n    ...(options.noCache ? { \"X-No-Cache\": \"true\" } : {}),\n  };\n}\n\nfunction clampMaxResults(maxResults: number): number {\n  return Math.min(Math.max(maxResults, 1), JINA_MAX_RESULTS);\n}\n\nfunction isJinaSearchType(\n  category: string | undefined,\n): category is (typeof JINA_SEARCH_CATEGORIES)[number] {\n  return JINA_SEARCH_CATEGORIES.some((supported) => supported === category);\n}\n\nfunction mapSearchResult(result: Readonly<JinaResult>): SearchResult {\n  return {\n    url: result.url,\n    title: result.title ?? \"\",\n    snippet: result.description ?? snippetFrom(result.content) ?? snippetFrom(result.text) ?? \"\",\n    publishedDate: result.publishedTime,\n    text: result.content ?? result.text,\n    image: firstImage(result.images),\n    metadata: resultMetadata(result),\n  };\n}\n\nfunction mapReadResult(result: Readonly<JinaResult>): ReadResult {\n  return {\n    url: result.url,\n    title: result.title,\n    description: result.description,\n    content: result.content ?? result.text ?? result.html ?? \"\",\n    text: result.text,\n    html: result.html,\n    publishedDate: result.publishedTime,\n    image: firstImage(result.images),\n    links: stringValues(result.links),\n    images: stringValues(result.images),\n    metadata: resultMetadata(result),\n  };\n}\n\nfunction snippetFrom(text: string | undefined): string | undefined {\n  return text ? text.slice(0, 200) : undefined;\n}\n\nfunction firstImage(\n  images: readonly string[] | Readonly<Record<string, string>> | null | undefined,\n): string | undefined {\n  return stringValues(images)?.[0];\n}\n\nfunction stringValues(\n  value: readonly string[] | Readonly<Record<string, string>> | null | undefined,\n): string[] | undefined {\n  const values = Array.isArray(value) ? value : value ? Object.values(value) : [];\n  const strings = values.filter(isNonEmptyString);\n  return strings.length > 0 ? strings : undefined;\n}\n\nfunction isNonEmptyString(value: unknown): value is string {\n  return typeof value === \"string\" && value.length > 0;\n}\n\nfunction assertJinaSuccess(response: Readonly<JinaEnvelope>, url: string): void {\n  const code = response.code;\n  const status = response.status;\n  if ((code !== undefined && code >= 400) || (status !== undefined && status >= 40000)) {\n    const statusCode =\n      code !== undefined && code >= 400 ? code : Math.floor((status ?? 50000) / 100);\n    throw new HTTPError(statusCode, url, jinaErrorMessage(response));\n  }\n}\n\nfunction jinaErrorMessage(response: Readonly<JinaEnvelope>): string {\n  if (response.message) return response.message;\n  if (response.error) return response.error;\n  if (response.detail !== undefined) return JSON.stringify(response.detail);\n  return `Jina API error: code=${response.code ?? \"unknown\"} status=${response.status ?? \"unknown\"}`;\n}\n\nfunction resultMetadata(result: Readonly<JinaResult>): Record<string, unknown> | undefined {\n  const metadata: Record<string, unknown> = result.metadata ? { ...result.metadata } : {};\n\n  if (result.warning) metadata.warning = result.warning;\n  if (result.screenshotUrl) metadata.screenshotUrl = result.screenshotUrl;\n  if (result.pageshotUrl) metadata.pageshotUrl = result.pageshotUrl;\n\n  return Object.keys(metadata).length > 0 ? metadata : undefined;\n}\n"],"mappings":"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"}