{"version":3,"file":"embeddings.cjs","names":["flattenHeaders","getContext","getTestId","matchFixtureDiagnostic","applyChaos","resolveResponse","isErrorResponse","serializeErrorResponse","isEmbeddingResponse","buildEmbeddingResponse","resolveStrictMode","strictNoMatchMessage","strictNoMatchLogLine","strictOverrideField","proxyAndRecord","generateDeterministicEmbedding"],"sources":["../src/embeddings.ts"],"sourcesContent":["/**\n * OpenAI Embeddings API support for aimock.\n *\n * Handles POST /v1/embeddings requests. Matches fixtures using the `inputText`\n * field, and falls back to generating a deterministic embedding from the input\n * text hash when no fixture matches.\n */\n\nimport type * as http from \"node:http\";\nimport type {\n  ChatCompletionRequest,\n  Fixture,\n  HandlerDefaults,\n  RecordProviderKey,\n} from \"./types.js\";\nimport {\n  isEmbeddingResponse,\n  isErrorResponse,\n  serializeErrorResponse,\n  generateDeterministicEmbedding,\n  buildEmbeddingResponse,\n  flattenHeaders,\n  getTestId,\n  resolveResponse,\n  resolveStrictMode,\n  strictOverrideField,\n  getContext,\n  strictNoMatchMessage,\n  strictNoMatchLogLine,\n} from \"./helpers.js\";\nimport { matchFixtureDiagnostic } from \"./router.js\";\nimport { writeErrorResponse } from \"./sse-writer.js\";\nimport type { Journal } from \"./journal.js\";\nimport { applyChaos } from \"./chaos.js\";\nimport { proxyAndRecord } from \"./recorder.js\";\n\n// ─── Embeddings API request types ──────────────────────────────────────────\n\ninterface EmbeddingRequest {\n  input: string | string[];\n  model: string;\n  encoding_format?: \"float\" | \"base64\";\n  dimensions?: number;\n  [key: string]: unknown;\n}\n\n// ─── Request handler ───────────────────────────────────────────────────────\n\nexport async function handleEmbeddings(\n  req: http.IncomingMessage,\n  res: http.ServerResponse,\n  raw: string,\n  fixtures: Fixture[],\n  journal: Journal,\n  defaults: HandlerDefaults,\n  setCorsHeaders: (res: http.ServerResponse) => void,\n  providerKey: RecordProviderKey = \"openai\",\n): Promise<void> {\n  const { logger } = defaults;\n  setCorsHeaders(res);\n\n  let embeddingReq: EmbeddingRequest;\n  try {\n    embeddingReq = JSON.parse(raw) as EmbeddingRequest;\n  } catch (parseErr) {\n    const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n    journal.add({\n      method: req.method ?? \"POST\",\n      path: req.url ?? \"/v1/embeddings\",\n      headers: flattenHeaders(req.headers),\n      body: null,\n      response: { status: 400, fixture: null },\n    });\n    writeErrorResponse(\n      res,\n      400,\n      JSON.stringify({\n        error: {\n          message: `Malformed JSON body: ${detail}`,\n          type: \"invalid_request_error\",\n          code: \"invalid_json\",\n        },\n      }),\n    );\n    return;\n  }\n\n  // Validate required input parameter\n  if (embeddingReq.input === undefined || embeddingReq.input === null) {\n    journal.add({\n      method: req.method ?? \"POST\",\n      path: req.url ?? \"/v1/embeddings\",\n      headers: flattenHeaders(req.headers),\n      body: null,\n      response: { status: 400, fixture: null },\n    });\n    writeErrorResponse(\n      res,\n      400,\n      JSON.stringify({\n        error: {\n          message: \"Missing required parameter: 'input'\",\n          type: \"invalid_request_error\",\n        },\n      }),\n    );\n    return;\n  }\n\n  // Normalize input to array of strings\n  const inputs: string[] = Array.isArray(embeddingReq.input)\n    ? embeddingReq.input\n    : [embeddingReq.input];\n\n  // Concatenate all inputs for matching purposes\n  const combinedInput = inputs.join(\" \");\n\n  // Build a synthetic ChatCompletionRequest for the fixture router.\n  // We attach `embeddingInput` so the router's inputText matching can use it.\n  const syntheticReq: ChatCompletionRequest = {\n    model: embeddingReq.model,\n    messages: [],\n    embeddingInput: combinedInput,\n    _endpointType: \"embedding\",\n    _context: getContext(req),\n  };\n\n  const testId = getTestId(req);\n  const { fixture, skippedBySequenceOrTurn } = matchFixtureDiagnostic(\n    fixtures,\n    syntheticReq,\n    journal.getFixtureMatchCountsForTest(testId),\n    defaults.requestTransform,\n  );\n\n  if (fixture) {\n    journal.incrementFixtureMatchCount(fixture, fixtures, testId);\n    logger.debug(`Fixture matched: ${JSON.stringify(fixture.match).slice(0, 120)}`);\n  } else {\n    logger.debug(`No fixture matched for request`);\n  }\n\n  if (\n    applyChaos(\n      res,\n      fixture,\n      defaults.chaos,\n      req.headers,\n      journal,\n      {\n        method: req.method ?? \"POST\",\n        path: req.url ?? \"/v1/embeddings\",\n        headers: flattenHeaders(req.headers),\n        body: syntheticReq,\n      },\n      fixture ? \"fixture\" : \"proxy\",\n      defaults.registry,\n      defaults.logger,\n    )\n  )\n    return;\n\n  if (fixture) {\n    const response = await resolveResponse(fixture, syntheticReq);\n\n    // Error response\n    if (isErrorResponse(response)) {\n      const status = response.status ?? 500;\n      journal.add({\n        method: req.method ?? \"POST\",\n        path: req.url ?? \"/v1/embeddings\",\n        headers: flattenHeaders(req.headers),\n        body: syntheticReq,\n        response: { status, fixture },\n      });\n      writeErrorResponse(res, status, serializeErrorResponse(response), {\n        retryAfter: response.retryAfter,\n      });\n      return;\n    }\n\n    // Embedding response — use the fixture's embedding for each input\n    if (isEmbeddingResponse(response)) {\n      journal.add({\n        method: req.method ?? \"POST\",\n        path: req.url ?? \"/v1/embeddings\",\n        headers: flattenHeaders(req.headers),\n        body: syntheticReq,\n        response: { status: 200, fixture },\n      });\n      const embeddings = inputs.map(() => [...response.embedding]);\n      const body = buildEmbeddingResponse(embeddings, embeddingReq.model);\n      res.writeHead(200, { \"Content-Type\": \"application/json\" });\n      res.end(JSON.stringify(body));\n      return;\n    }\n\n    // Fixture matched but response type is not compatible with embeddings\n    journal.add({\n      method: req.method ?? \"POST\",\n      path: req.url ?? \"/v1/embeddings\",\n      headers: flattenHeaders(req.headers),\n      body: syntheticReq,\n      response: { status: 500, fixture },\n    });\n    writeErrorResponse(\n      res,\n      500,\n      JSON.stringify({\n        error: {\n          message:\n            \"Fixture response did not match any known embedding type (must have embedding or error)\",\n          type: \"server_error\",\n        },\n      }),\n    );\n    return;\n  }\n\n  // No fixture match — check strict mode first, then try proxy\n  if (resolveStrictMode(defaults.strict, req.headers)) {\n    const strictMessage = strictNoMatchMessage(skippedBySequenceOrTurn);\n    logger.error(\n      strictNoMatchLogLine(\n        req.method ?? \"POST\",\n        req.url ?? \"/v1/embeddings\",\n        skippedBySequenceOrTurn,\n      ),\n    );\n    journal.add({\n      method: req.method ?? \"POST\",\n      path: req.url ?? \"/v1/embeddings\",\n      headers: flattenHeaders(req.headers),\n      body: syntheticReq,\n      response: {\n        status: 503,\n        fixture: null,\n        ...strictOverrideField(defaults.strict, req.headers),\n      },\n    });\n    writeErrorResponse(\n      res,\n      503,\n      JSON.stringify({\n        error: {\n          message: strictMessage,\n          type: \"invalid_request_error\",\n          code: \"no_fixture_match\",\n        },\n      }),\n    );\n    return;\n  }\n\n  if (defaults.record) {\n    const outcome = await proxyAndRecord(\n      req,\n      res,\n      syntheticReq,\n      providerKey,\n      req.url ?? \"/v1/embeddings\",\n      fixtures,\n      defaults,\n      raw,\n    );\n    if (outcome === \"handled_by_hook\") return;\n    if (outcome !== \"not_configured\") {\n      journal.add({\n        method: req.method ?? \"POST\",\n        path: req.url ?? \"/v1/embeddings\",\n        headers: flattenHeaders(req.headers),\n        body: syntheticReq,\n        response: { status: res.statusCode ?? 200, fixture: null, source: \"proxy\" },\n      });\n      return;\n    }\n  }\n\n  // No fixture match — generate deterministic embeddings from input text\n  logger.warn(\n    `No embedding fixture matched for \"${combinedInput.slice(0, 80)}\" — returning deterministic fallback`,\n  );\n  const dimensions = embeddingReq.dimensions ?? 1536;\n  const embeddings = inputs.map((input) => generateDeterministicEmbedding(input, dimensions));\n\n  journal.add({\n    method: req.method ?? \"POST\",\n    path: req.url ?? \"/v1/embeddings\",\n    headers: flattenHeaders(req.headers),\n    body: syntheticReq,\n    response: { status: 200, fixture: null },\n  });\n\n  const body = buildEmbeddingResponse(embeddings, embeddingReq.model);\n  res.writeHead(200, { \"Content-Type\": \"application/json\" });\n  res.end(JSON.stringify(body));\n}\n"],"mappings":"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"}