{"version":3,"file":"adapter.cjs","names":["#options","#engine","#resolveEngine","#enginePromise"],"sources":["../../../../src/batteries/embeddings/webllm/adapter.ts"],"sourcesContent":["/**\n * WebLLM (WebGPU, in-process) Embeddings adapter battery.\n *\n * @module @nhtio/adk/batteries/embeddings/webllm/adapter\n *\n * @remarks\n * Embeddings battery backed by WebLLM's in-process `engine.embeddings.create()` (the OpenAI-style\n * embeddings API exposed by `@mlc-ai/web-llm`). Runs entirely in the browser on WebGPU — no\n * network round-trip, no API key.\n *\n * This class is the **same battery** as {@link @nhtio/adk/batteries/embeddings/openai!OpenAIEmbeddingsAdapter}\n * in every user-facing respect — identical method surface (`isAvailable` / `dimensions` /\n * `preload` / `reset` / `embed` / `embedMany`), identical `number[]` return shape, identical\n * query/document prefix handling (the shared `applyEmbeddingPrefix` helper) — differing only in\n * the engine. Construction validates eagerly and throws\n * {@link @nhtio/adk/batteries/embeddings/webllm/exceptions!E_INVALID_WEBLLM_EMBEDDINGS_OPTIONS} on failure.\n *\n * `@mlc-ai/web-llm` is an optional peer dependency, imported lazily so non-WebGPU consumers pay\n * nothing for it.\n */\n\nimport { isError } from '@nhtio/adk/guards'\nimport { validateOptions } from './validation'\nimport { applyEmbeddingPrefix } from '../openai/helpers'\nimport { E_INVALID_WEBLLM_EMBEDDINGS_OPTIONS, E_WEBLLM_EMBEDDINGS_ENGINE_ERROR } from './exceptions'\nimport type { EmbedOptions } from '../openai/types'\nimport type {\n  WebLLMEmbeddingsAdapterOptions,\n  WebLLMEmbeddingsEngine,\n  CreateWebLLMEmbeddingsEngine,\n} from './types'\n\nconst defaultCreateEngine: CreateWebLLMEmbeddingsEngine = async ({\n  model,\n  engineConfig,\n  chatOptions,\n  onInitProgress,\n}) => {\n  const { CreateMLCEngine } = await import('@mlc-ai/web-llm')\n  return (await CreateMLCEngine(\n    model,\n    { ...(engineConfig ?? {}), initProgressCallback: onInitProgress },\n    chatOptions\n  )) as WebLLMEmbeddingsEngine\n}\n\n/**\n * Embeddings adapter for WebLLM's in-process embeddings API.\n *\n * @remarks\n * Reusable: construct once, call {@link WebLLMEmbeddingsAdapter.embed} / {@link embedMany} as many\n * times as needed. The engine is resolved lazily on first use (or via {@link preload}) and cached\n * with single-flight semantics so concurrent calls share one load.\n */\nexport class WebLLMEmbeddingsAdapter {\n  readonly #options: WebLLMEmbeddingsAdapterOptions\n  #engine: WebLLMEmbeddingsEngine | undefined\n  #enginePromise: Promise<WebLLMEmbeddingsEngine> | undefined\n\n  /**\n   * Whether WebGPU — and therefore this battery — is available in the current runtime.\n   */\n  public static isAvailable(): boolean {\n    return (\n      typeof globalThis.navigator !== 'undefined' &&\n      'gpu' in globalThis.navigator &&\n      typeof (globalThis.navigator as { gpu?: unknown }).gpu !== 'undefined'\n    )\n  }\n\n  /**\n   * @param options - Constructor options. Validated eagerly.\n   * @throws {@link @nhtio/adk/batteries/embeddings/webllm/exceptions!E_INVALID_WEBLLM_EMBEDDINGS_OPTIONS} when `options` does not satisfy\n   *   {@link @nhtio/adk/batteries/embeddings/webllm/validation!webLLMEmbeddingsOptionsSchema} (e.g. missing `model`).\n   */\n  constructor(options: unknown) {\n    this.#options = validateOptions(options)\n    this.#engine = this.#options.engine\n  }\n\n  /** Declared output dimensionality (from options), or `undefined` if not configured. */\n  get dimensions(): number | undefined {\n    return this.#options.dimensions\n  }\n\n  /** Whether WebGPU is available, honoring an injected `isWebGPUAvailable` probe. */\n  isAvailable(): boolean {\n    return (this.#options.isWebGPUAvailable ?? WebLLMEmbeddingsAdapter.isAvailable)()\n  }\n\n  /**\n   * Eagerly loads (and caches) the engine so the first `embed` call is fast. Idempotent.\n   *\n   * @throws {@link @nhtio/adk/batteries/embeddings/webllm/exceptions!E_INVALID_WEBLLM_EMBEDDINGS_OPTIONS} when no WebGPU is available and no engine\n   *   was injected.\n   * @throws {@link @nhtio/adk/batteries/embeddings/webllm/exceptions!E_WEBLLM_EMBEDDINGS_ENGINE_ERROR} when engine creation fails.\n   */\n  async preload(): Promise<void> {\n    await this.#resolveEngine()\n  }\n\n  /** Drops the cached engine and in-flight load so the next call reloads. */\n  reset(): void {\n    this.#engine = undefined\n    this.#enginePromise = undefined\n  }\n\n  async #resolveEngine(): Promise<WebLLMEmbeddingsEngine> {\n    if (this.#engine) return this.#engine\n    if (!this.isAvailable()) {\n      throw new E_INVALID_WEBLLM_EMBEDDINGS_OPTIONS([\n        'WebLLM requires a browser/runtime with WebGPU support',\n      ])\n    }\n    this.#enginePromise ??= (async () => {\n      const createEngine = this.#options.createEngine ?? defaultCreateEngine\n      try {\n        const engine = await createEngine({\n          model: this.#options.model,\n          engineConfig: this.#options.engineConfig,\n          chatOptions: this.#options.chatOptions,\n          onInitProgress: this.#options.onInitProgress,\n        })\n        this.#engine = engine\n        return engine\n      } catch (err) {\n        // Clear the cached promise so a later call can retry a transient load failure.\n        this.#enginePromise = undefined\n        throw new E_WEBLLM_EMBEDDINGS_ENGINE_ERROR([isError(err) ? err.message : String(err)])\n      }\n    })()\n    return this.#enginePromise\n  }\n\n  /**\n   * Embeds a single string.\n   *\n   * @param text - The input text.\n   * @param opts - Per-call options (`kind`).\n   * @returns The embedding vector as a plain `number[]`.\n   */\n  async embed(text: string, opts?: EmbedOptions): Promise<number[]> {\n    const [vec] = await this.embedMany([text], opts)\n    return vec\n  }\n\n  /**\n   * Embeds a batch of strings in a single engine call.\n   *\n   * @param texts - The input texts.\n   * @param opts - Per-call options (`kind`). Defaults to `kind: 'document'`.\n   * @returns One embedding vector per input, in input order, each a plain `number[]`.\n   * @throws {@link @nhtio/adk/batteries/embeddings/webllm/exceptions!E_WEBLLM_EMBEDDINGS_ENGINE_ERROR} when the engine call fails or returns a\n   *   malformed result.\n   */\n  async embedMany(texts: string[], opts?: EmbedOptions): Promise<number[][]> {\n    if (texts.length === 0) return []\n    const kind = opts?.kind ?? 'document'\n    const input = applyEmbeddingPrefix(texts, kind, this.#options)\n\n    const engine = await this.#resolveEngine()\n\n    let response: { data?: Array<{ embedding: number[]; index: number }> }\n    try {\n      response = await engine.embeddings.create({ model: this.#options.model, input })\n    } catch (err) {\n      throw new E_WEBLLM_EMBEDDINGS_ENGINE_ERROR([isError(err) ? err.message : String(err)])\n    }\n\n    if (!response || !Array.isArray(response.data) || response.data.length !== input.length) {\n      throw new E_WEBLLM_EMBEDDINGS_ENGINE_ERROR([\n        `expected ${input.length} vectors, got ${response?.data?.length ?? 'none'}`,\n      ])\n    }\n    return response.data\n      .slice()\n      .sort((a, b) => a.index - b.index)\n      .map((d) => d.embedding)\n  }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCA,IAAM,sBAAoD,OAAO,EAC/D,OACA,cACA,aACA,qBACI;CACJ,MAAM,EAAE,oBAAoB,MAAM,OAAO;CACzC,OAAQ,MAAM,gBACZ,OACA;EAAE,GAAI,gBAAgB,CAAC;EAAI,sBAAsB;CAAe,GAChE,WACF;AACF;;;;;;;;;AAUA,IAAa,0BAAb,MAAa,wBAAwB;CACnC;CACA;CACA;;;;CAKA,OAAc,cAAuB;EACnC,OACE,OAAO,WAAW,cAAc,eAChC,SAAS,WAAW,aACpB,OAAQ,WAAW,UAAgC,QAAQ;CAE/D;;;;;;CAOA,YAAY,SAAkB;EAC5B,KAAKA,WAAW,+CAAA,gBAAgB,OAAO;EACvC,KAAKC,UAAU,KAAKD,SAAS;CAC/B;;CAGA,IAAI,aAAiC;EACnC,OAAO,KAAKA,SAAS;CACvB;;CAGA,cAAuB;EACrB,QAAQ,KAAKA,SAAS,qBAAqB,wBAAwB,aAAa;CAClF;;;;;;;;CASA,MAAM,UAAyB;EAC7B,MAAM,KAAKE,eAAe;CAC5B;;CAGA,QAAc;EACZ,KAAKD,UAAU,KAAA;EACf,KAAKE,iBAAiB,KAAA;CACxB;CAEA,MAAMD,iBAAkD;EACtD,IAAI,KAAKD,SAAS,OAAO,KAAKA;EAC9B,IAAI,CAAC,KAAK,YAAY,GACpB,MAAM,IAAI,+CAAA,oCAAoC,CAC5C,uDACF,CAAC;EAEH,KAAKE,oBAAoB,YAAY;GACnC,MAAM,eAAe,KAAKH,SAAS,gBAAgB;GACnD,IAAI;IACF,MAAM,SAAS,MAAM,aAAa;KAChC,OAAO,KAAKA,SAAS;KACrB,cAAc,KAAKA,SAAS;KAC5B,aAAa,KAAKA,SAAS;KAC3B,gBAAgB,KAAKA,SAAS;IAChC,CAAC;IACD,KAAKC,UAAU;IACf,OAAO;GACT,SAAS,KAAK;IAEZ,KAAKE,iBAAiB,KAAA;IACtB,MAAM,IAAI,+CAAA,iCAAiC,CAAC,eAAA,QAAQ,GAAG,IAAI,IAAI,UAAU,OAAO,GAAG,CAAC,CAAC;GACvF;EACF,GAAG;EACH,OAAO,KAAKA;CACd;;;;;;;;CASA,MAAM,MAAM,MAAc,MAAwC;EAChE,MAAM,CAAC,OAAO,MAAM,KAAK,UAAU,CAAC,IAAI,GAAG,IAAI;EAC/C,OAAO;CACT;;;;;;;;;;CAWA,MAAM,UAAU,OAAiB,MAA0C;EACzE,IAAI,MAAM,WAAW,GAAG,OAAO,CAAC;EAEhC,MAAM,QAAQ,4CAAA,qBAAqB,OADtB,MAAM,QAAQ,YACqB,KAAKH,QAAQ;EAE7D,MAAM,SAAS,MAAM,KAAKE,eAAe;EAEzC,IAAI;EACJ,IAAI;GACF,WAAW,MAAM,OAAO,WAAW,OAAO;IAAE,OAAO,KAAKF,SAAS;IAAO;GAAM,CAAC;EACjF,SAAS,KAAK;GACZ,MAAM,IAAI,+CAAA,iCAAiC,CAAC,eAAA,QAAQ,GAAG,IAAI,IAAI,UAAU,OAAO,GAAG,CAAC,CAAC;EACvF;EAEA,IAAI,CAAC,YAAY,CAAC,MAAM,QAAQ,SAAS,IAAI,KAAK,SAAS,KAAK,WAAW,MAAM,QAC/E,MAAM,IAAI,+CAAA,iCAAiC,CACzC,YAAY,MAAM,OAAO,gBAAgB,UAAU,MAAM,UAAU,QACrE,CAAC;EAEH,OAAO,SAAS,KACb,MAAM,EACN,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK,EAChC,KAAK,MAAM,EAAE,SAAS;CAC3B;AACF"}