{"version":3,"sources":["../src/api.ts","../src/cache.ts","../src/skeleton.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Adapter over the global `Summarizer` API exposed by Chrome 138+. Feature-\n * detected; on browsers without it, every entry point returns `null` so\n * callers can stay declarative.\n *\n * Spec: https://developer.chrome.com/docs/ai/summarizer-api\n */\n\nexport interface SummarizerInstance {\n  summarize(text: string): Promise<string>;\n  summarizeStreaming?(text: string): AsyncIterable<string>;\n  destroy?(): void;\n}\n\nexport interface DownloadProgressEvent extends Event {\n  readonly loaded: number;\n}\n\nexport interface CreateMonitor {\n  addEventListener(\n    type: \"downloadprogress\",\n    listener: (event: DownloadProgressEvent) => void,\n  ): void;\n}\n\nexport interface SummarizerCreateOptions {\n  type?: \"tldr\" | \"key-points\" | \"teaser\" | \"headline\";\n  format?: \"markdown\" | \"plain-text\";\n  length?: \"short\" | \"medium\" | \"long\";\n  preference?: \"auto\" | \"speed\" | \"capability\";\n  sharedContext?: string;\n  expectedInputLanguages?: string[];\n  expectedContextLanguages?: string[];\n  outputLanguage?: string;\n  /** Observe the first-call model download (~1.7 GB on a fresh profile). */\n  monitor?: (m: CreateMonitor) => void;\n}\n\nexport type SummarizerAvailability =\n  | \"unavailable\"\n  | \"downloadable\"\n  | \"downloading\"\n  | \"available\";\n\n/**\n * Subset of `SummarizerCreateOptions` that the spec accepts when probing\n * availability. Edge enforces these strictly (e.g. fires a warning when\n * `outputLanguage` is missing); Chrome is more lenient. Pass the same\n * shape you'd pass to `create()` to get accurate, warning-free results\n * across browsers.\n */\nexport interface SummarizerAvailabilityOptions {\n  type?: SummarizerCreateOptions[\"type\"];\n  format?: SummarizerCreateOptions[\"format\"];\n  length?: SummarizerCreateOptions[\"length\"];\n  preference?: SummarizerCreateOptions[\"preference\"];\n  expectedInputLanguages?: string[];\n  expectedContextLanguages?: string[];\n  outputLanguage?: string;\n}\n\nexport interface SummarizerApi {\n  availability(\n    options?: SummarizerAvailabilityOptions,\n  ): Promise<SummarizerAvailability>;\n  create(options?: SummarizerCreateOptions): Promise<SummarizerInstance>;\n}\n\n/** Internal: read the native global. Not exported from the package. */\nexport const getSummarizerApi = (): SummarizerApi | null => {\n  if (typeof globalThis === \"undefined\") return null;\n  return (\n    (globalThis as unknown as { Summarizer?: SummarizerApi }).Summarizer ?? null\n  );\n};\n\n/** Whether the current environment exposes the Summarizer API. */\nexport const isAvailable = (): boolean => getSummarizerApi() !== null;\n\nexport const checkAvailability = async (\n  options?: SummarizerAvailabilityOptions,\n): Promise<SummarizerAvailability | null> => {\n  const api = getSummarizerApi();\n  if (!api?.availability) return null;\n  try {\n    return await api.availability(options);\n  } catch {\n    return null;\n  }\n};\n\nconst DEFAULT_MAX_CACHED_SESSIONS = 8;\n\ninterface CacheConfig {\n  max: number;\n}\n\nconst cacheConfig: CacheConfig = { max: DEFAULT_MAX_CACHED_SESSIONS };\n\n// Map iteration order is insertion order, which lets us use it as an LRU:\n// on hit we re-insert to bump recency, and on overflow we evict the\n// oldest (first) entry.\nconst sessionCache = new Map<string, Promise<SummarizerInstance>>();\n\nconst normalizeCacheMax = (max: number): number =>\n  Number.isFinite(max) ? Math.max(0, Math.floor(max)) : 0;\n\nexport interface ConfigureSummarizerCacheOptions {\n  /** Soft cap on cached summarizer sessions. Default: `8`. */\n  max?: number;\n}\n\n/**\n * Bound the internal summarizer session cache. Excess entries are evicted in\n * LRU order (their `destroy?()` is invoked when present). Lowering `max`\n * immediately evicts down to the new ceiling.\n */\nexport const configureSummarizerCache = (\n  options: ConfigureSummarizerCacheOptions = {},\n): void => {\n  if (options.max !== undefined) {\n    cacheConfig.max = normalizeCacheMax(options.max);\n  }\n  trim();\n};\n\nconst destroySession = (entry: Promise<SummarizerInstance>): void => {\n  entry\n    .then((session) => {\n      try {\n        session.destroy?.();\n      } catch {\n        // best-effort; the spec doesn't require destroy to be infallible.\n      }\n    })\n    .catch(() => {\n      // session never resolved (e.g. create() rejected); nothing to destroy.\n    });\n};\n\nconst trim = (): void => {\n  while (sessionCache.size > cacheConfig.max) {\n    const oldestKey = sessionCache.keys().next().value;\n    if (oldestKey === undefined) return;\n    const evicted = sessionCache.get(oldestKey);\n    sessionCache.delete(oldestKey);\n    if (evicted) destroySession(evicted);\n  }\n};\n\n/**\n * Drop every cached summarizer session. Sessions live for the tab lifetime by\n * default; call this to free them eagerly when navigating away from a feature\n * that won't be revisited.\n */\nexport const clearSummarizerSessions = (): void => {\n  for (const entry of sessionCache.values()) {\n    destroySession(entry);\n  }\n  sessionCache.clear();\n};\n\n/**\n * Drop the cached summarizer whose create-options match `options`.\n */\nexport const clearSummarizerSession = (\n  options: SummarizerCreateOptions,\n): void => {\n  const key = JSON.stringify(options);\n  const entry = sessionCache.get(key);\n  if (!entry) return;\n  sessionCache.delete(key);\n  destroySession(entry);\n};\n\n/**\n * Get or create a `Summarizer` session for the given options. Sessions live\n * for the tab lifetime so navigating between same-config pages skips the\n * ~1-3s cold start. On `create()` failure the cache slot is purged so the\n * next call retries instead of returning a poisoned promise.\n */\nexport const getOrCreateSummarizer = (\n  api: SummarizerApi,\n  options: SummarizerCreateOptions,\n): Promise<SummarizerInstance> => {\n  const key = JSON.stringify(options);\n  let session = sessionCache.get(key);\n  if (session) {\n    // Bump recency: delete + reinsert so this entry moves to the end of the\n    // LRU order.\n    sessionCache.delete(key);\n    sessionCache.set(key, session);\n    return session;\n  }\n  session = api.create(options).catch((err) => {\n    sessionCache.delete(key);\n    throw err;\n  });\n  sessionCache.set(key, session);\n  trim();\n  return session;\n};\n\n/** Test-only escape hatch; drop every cached session. */\nexport const __clearSessionCacheForTests = (): void => {\n  sessionCache.clear();\n  cacheConfig.max = DEFAULT_MAX_CACHED_SESSIONS;\n};\n","/**\n * `sessionStorage`-backed cache so a successful summary renders instantly on\n * revisit, skipping the model entirely. The cache is best-effort: storage\n * disabled / quota exceeded falls through silently; the summary still\n * renders.\n */\n\nexport interface SummaryCache {\n  get(key: string): string | null;\n  set(key: string, value: string): void;\n}\n\n/**\n * Public-facing `cache` option. Pass `\"session\"` / `\"local\"` for storage\n * shortcuts, or any `{ get, set }`-shaped object for a custom backend.\n */\nexport type CacheOption = \"session\" | \"local\" | SummaryCache;\n\ninterface DefaultCacheOptions {\n  /** Storage backend. Default: `globalThis.sessionStorage`. */\n  storage?: Storage;\n  /** Prefix for cache keys. Default: `\"summarizer:\"`. */\n  prefix?: string;\n}\n\nexport const createSessionStorageCache = (\n  options: DefaultCacheOptions = {},\n): SummaryCache => {\n  const prefix = options.prefix ?? \"summarizer:\";\n  const storage =\n    options.storage ??\n    (typeof globalThis !== \"undefined\"\n      ? (globalThis as { sessionStorage?: Storage }).sessionStorage\n      : undefined);\n\n  return {\n    get(key) {\n      if (!storage) return null;\n      try {\n        return storage.getItem(prefix + key);\n      } catch {\n        return null;\n      }\n    },\n    set(key, value) {\n      if (!storage) return;\n      try {\n        storage.setItem(prefix + key, value);\n      } catch {\n        // quota / disabled storage; best-effort only.\n      }\n    },\n  };\n};\n\n/**\n * Resolve the public `cache` option into a concrete `{ get, set }` backend.\n * `undefined` → no caching. `\"session\"` / `\"local\"` → wrap the matching\n * web-storage backend (no-op fallback if unavailable). Object → passthrough.\n */\nexport const resolveCache = (\n  value: CacheOption | undefined,\n): SummaryCache | undefined => {\n  if (value === undefined) return undefined;\n  if (value === \"session\") {\n    return createSessionStorageCache();\n  }\n  if (value === \"local\") {\n    const storage =\n      typeof globalThis !== \"undefined\"\n        ? (globalThis as { localStorage?: Storage }).localStorage\n        : undefined;\n    return createSessionStorageCache({ storage });\n  }\n  return value;\n};\n\n/**\n * Build a default cache key from the route, input, and summary options that\n * affect the output. A legacy string input still returns the compact\n * `pathname:lang` key. Pass a custom string to `summarize()`'s `cacheKey`\n * option for finer-grained invalidation.\n */\ninterface DefaultCacheKeyInput {\n  text: string;\n  language: string;\n  languageHints?: boolean;\n  type?: string;\n  length?: string;\n  format?: string;\n  preference?: string;\n  sharedContext?: string;\n}\n\nconst currentPathname = (): string => {\n  let pathname = \"\";\n  if (typeof globalThis === \"undefined\") {\n    pathname = \"\";\n  } else {\n    const loc = (globalThis as { location?: Location }).location;\n    pathname = loc?.pathname ?? \"\";\n  }\n  return pathname;\n};\n\nexport const defaultCacheKey = (\n  input: string | DefaultCacheKeyInput,\n): string => {\n  const pathname = currentPathname();\n  if (typeof input === \"string\")\n    return pathname ? `${pathname}:${input}` : input;\n  return JSON.stringify([\n    pathname,\n    input.text.trim(),\n    input.language,\n    input.languageHints === true,\n    input.type ?? \"\",\n    input.length ?? \"\",\n    input.format ?? \"\",\n    input.preference ?? \"\",\n    input.sharedContext ?? \"\",\n  ]);\n};\n","/** Internal output normalization for `summarize()`. */\n\n/** Strip wrapping quotes / whitespace and collapse internal whitespace. */\nexport const cleanSummary = (raw: string): string =>\n  raw\n    .replace(/^[\"“”'`\\s]+|[\"“”'`\\s]+$/g, \"\")\n    .replace(/\\s+/g, \" \")\n    .trim();\n","/**\n * @web-ai-sdk/summarizer; building block for the Web's Built-in Summarizer API.\n *\n * Vanilla TypeScript / DOM core. The React adapter at `@web-ai-sdk/summarizer/react` is a\n * thin hook around this module.\n *\n * Spec: https://developer.chrome.com/docs/ai/summarizer-api\n */\n\nimport {\n  type CreateMonitor,\n  checkAvailability,\n  getOrCreateSummarizer,\n  getSummarizerApi,\n  isAvailable,\n  type SummarizerApi,\n  type SummarizerAvailability,\n  type SummarizerAvailabilityOptions,\n  type SummarizerCreateOptions,\n  type SummarizerInstance,\n} from \"./api.js\";\nimport {\n  type CacheOption,\n  defaultCacheKey,\n  resolveCache,\n  type SummaryCache,\n} from \"./cache.js\";\nimport { cleanSummary } from \"./skeleton.js\";\n\nexport type {\n  CacheOption,\n  CreateMonitor,\n  SummarizerApi,\n  SummarizerAvailability,\n  SummarizerAvailabilityOptions,\n  SummarizerCreateOptions,\n  SummarizerInstance,\n  SummaryCache,\n};\nexport { checkAvailability, isAvailable };\n\nexport interface SummarizeOptions {\n  /** Text to summarize. Empty / whitespace input resolves to `{ output: null }`. */\n  input: string;\n  /** BCP-47 language for input + output hints. Falls back to omitting hints if unsupported. */\n  language: string;\n  /** Languages the model supports for input/output hints. Default: `[\"en\", \"es\", \"ja\"]`. */\n  supportedLanguages?: readonly string[];\n  /** Summary shape. Default: `\"tldr\"`. */\n  type?: \"tldr\" | \"key-points\" | \"teaser\" | \"headline\";\n  /** Length preset. Default: `\"medium\"`. */\n  length?: \"short\" | \"medium\" | \"long\";\n  /** Output format. Default: `\"plain-text\"`. */\n  format?: \"plain-text\" | \"markdown\";\n  /**\n   * Performance preference hint. `\"speed\"` biases toward a faster, lighter\n   * model; `\"capability\"` toward a more comprehensive one; `\"auto\"` lets the\n   * browser balance the two. The browser may override the hint when a\n   * functional requirement (e.g. the requested language) needs a more capable\n   * model. Default: `\"auto\"` (matches the platform default).\n   */\n  preference?: \"auto\" | \"speed\" | \"capability\";\n  /** Native `sharedContext` string (a hint about who/what the summary is for). */\n  sharedContext?: string;\n  /** Observe the first-call model download (~1.7 GB on a fresh profile). */\n  monitor?: (m: CreateMonitor) => void;\n  /**\n   * Result cache. Off by default; every call hits the model. Pass\n   * `\"session\"` / `\"local\"` for the matching web-storage shortcut, or any\n   * `{ get, set }`-shaped object for a custom backend.\n   */\n  cache?: CacheOption;\n  /** Cache key. Default: JSON string of route, input, and summary options. */\n  cacheKey?: string;\n  /**\n   * Streaming update callback (cleaned text, monotonically growing).\n   * Receives the **cumulative** buffer, not deltas.\n   */\n  onUpdate?: (text: string) => void;\n  /** Abort signal. */\n  signal?: AbortSignal;\n}\n\nexport interface SummarizeResult {\n  /** Final summary text (cleaned), or `null` if the input was empty. */\n  output: string | null;\n  /** Whether the result came from the cache (no model call). */\n  cached: boolean;\n}\n\nconst NORMALIZE_LANG = (lang: string): string =>\n  lang.split(\"-\")[0]?.toLowerCase() ?? lang.toLowerCase();\n\nconst DEFAULT_SUPPORTED_LANGUAGES = [\"en\", \"es\", \"ja\"] as const;\n\nexport class SummarizerUnavailableError extends Error {\n  override readonly name = \"SummarizerUnavailableError\";\n}\n\n/**\n * Generate a summary. Uses streaming when the underlying instance supports\n * it, one-shot otherwise. Returns `{ output: null }` for empty input.\n * Throws `SummarizerUnavailableError` when the API isn't present in the\n * environment.\n *\n * Output is normalized via an internal cleaner (wrapping quotes/whitespace\n * stripped, internal whitespace collapsed). Anything beyond that — e.g.\n * trimming terminal punctuation for headline-style use cases — is the\n * consumer's concern.\n */\nexport const summarize = async (\n  options: SummarizeOptions,\n): Promise<SummarizeResult> => {\n  const api = getSummarizerApi();\n  if (!api?.create) {\n    throw new SummarizerUnavailableError(\n      \"Summarizer API is not available in this environment.\",\n    );\n  }\n\n  const text = options.input.trim();\n  if (!text) return { output: null, cached: false };\n\n  const lang = NORMALIZE_LANG(options.language);\n  const supportedLanguages = (\n    options.supportedLanguages ?? DEFAULT_SUPPORTED_LANGUAGES\n  ).map(NORMALIZE_LANG);\n  const supported = new Set(supportedLanguages);\n  const languageHints = supported.has(lang);\n  const cache = resolveCache(options.cache);\n  const cacheKey =\n    options.cacheKey ??\n    defaultCacheKey({\n      text,\n      language: lang,\n      languageHints,\n      type: options.type,\n      length: options.length,\n      format: options.format,\n      preference: options.preference,\n      sharedContext: options.sharedContext,\n    });\n  if (cache) {\n    const cached = cache.get(cacheKey);\n    if (cached) return { output: cached, cached: true };\n  }\n\n  const langOptions: Pick<\n    SummarizerCreateOptions,\n    \"expectedInputLanguages\" | \"expectedContextLanguages\" | \"outputLanguage\"\n  > = languageHints\n    ? {\n        expectedInputLanguages: [lang],\n        expectedContextLanguages: [lang],\n        outputLanguage: lang,\n      }\n    : {};\n\n  const baseCreateOptions: SummarizerCreateOptions = {\n    type: options.type ?? \"tldr\",\n    format: options.format ?? \"plain-text\",\n    length: options.length ?? \"medium\",\n    preference: options.preference ?? \"auto\",\n    sharedContext: options.sharedContext ?? \"\",\n    ...langOptions,\n    ...(options.monitor ? { monitor: options.monitor } : {}),\n  };\n\n  // We pass the same options shape to availability() as we do to create().\n  // Edge requires this for accurate results and warns when fields like\n  // outputLanguage are missing; Chrome is more lenient but accepts the same\n  // input. `preference` is a create-core option, so availability() accepts it\n  // too; forwarding it keeps the probe honest about the configuration we're\n  // actually about to create. The narrower SummarizerAvailabilityOptions shape\n  // still filters out create-only fields like `sharedContext`.\n  const availability = await api\n    .availability({\n      ...(baseCreateOptions.type ? { type: baseCreateOptions.type } : {}),\n      ...(baseCreateOptions.format ? { format: baseCreateOptions.format } : {}),\n      ...(baseCreateOptions.length ? { length: baseCreateOptions.length } : {}),\n      ...(baseCreateOptions.preference\n        ? { preference: baseCreateOptions.preference }\n        : {}),\n      ...(baseCreateOptions.expectedInputLanguages\n        ? { expectedInputLanguages: baseCreateOptions.expectedInputLanguages }\n        : {}),\n      ...(baseCreateOptions.expectedContextLanguages\n        ? {\n            expectedContextLanguages:\n              baseCreateOptions.expectedContextLanguages,\n          }\n        : {}),\n      ...(baseCreateOptions.outputLanguage\n        ? { outputLanguage: baseCreateOptions.outputLanguage }\n        : {}),\n    })\n    .catch(() => \"unavailable\" as const);\n  if (availability === \"unavailable\") {\n    throw new SummarizerUnavailableError(\"Summarizer reports unavailable.\");\n  }\n  if (options.signal?.aborted) throw new AbortError();\n\n  const sessionPromise = getOrCreateSummarizer(api, baseCreateOptions);\n\n  // Wrap session-create failures with context so consumers can branch on\n  // a single typed error instead of parsing browser-specific messages.\n  let summarizer: SummarizerInstance;\n  try {\n    summarizer = await sessionPromise;\n  } catch (err) {\n    if (err instanceof AbortError) throw err;\n    const message = (err as Error)?.message ?? String(err);\n    throw new SummarizerUnavailableError(\n      `Summarizer.create() failed: ${message}`,\n    );\n  }\n  if (options.signal?.aborted) throw new AbortError();\n\n  // The W3C Web AI streaming contract is ambiguous between \"delta\" (each\n  // chunk is new content) and \"cumulative\" (each chunk is the full text\n  // so far). Chrome ships delta; some Edge backends ship cumulative.\n  // Detect per-chunk and merge accordingly.\n  const mergeChunk = (buffer: string, chunk: string): string =>\n    chunk.startsWith(buffer) ? chunk : buffer + chunk;\n\n  let finalText: string;\n  if (typeof summarizer.summarizeStreaming === \"function\" && options.onUpdate) {\n    let buffer = \"\";\n    for await (const chunk of summarizer.summarizeStreaming(text)) {\n      if (options.signal?.aborted) throw new AbortError();\n      buffer = mergeChunk(buffer, chunk);\n      options.onUpdate(cleanSummary(buffer));\n    }\n    finalText = cleanSummary(buffer);\n  } else if (typeof summarizer.summarizeStreaming === \"function\") {\n    let buffer = \"\";\n    for await (const chunk of summarizer.summarizeStreaming(text)) {\n      if (options.signal?.aborted) throw new AbortError();\n      buffer = mergeChunk(buffer, chunk);\n    }\n    finalText = cleanSummary(buffer);\n  } else {\n    const raw = await summarizer.summarize(text);\n    if (options.signal?.aborted) throw new AbortError();\n    finalText = cleanSummary(raw);\n  }\n\n  if (finalText && cache) cache.set(cacheKey, finalText);\n  return { output: finalText || null, cached: false };\n};\n\nclass AbortError extends Error {\n  override readonly name = \"AbortError\";\n  constructor() {\n    super(\"Summarization aborted\");\n  }\n}\n"]}