{"version":3,"sources":["../src/azure_functions.ts"],"sourcesContent":["/**\n * Copyright 2026 Xavier Portilla Edo\n * Copyright 2026 Google LLC\n * Copyright 2026 Bloom Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { app } from \"@azure/functions\";\nimport type {\n  HttpRequest,\n  HttpResponseInit,\n  InvocationContext,\n  HttpHandler,\n} from \"@azure/functions\";\nimport { type Flow, type z, type ActionContext, UserFacingError } from \"genkit\";\nimport {\n  getCallableJSON,\n  getHttpStatus,\n  type ContextProvider,\n  type RequestData,\n} from \"genkit/context\";\n\n// Re-export genkit context types for convenience\nexport type { ContextProvider, RequestData, ActionContext };\n\n/**\n * Type helpers to extract input/output types from Flow\n */\ntype FlowInput<F extends Flow> =\n  F extends Flow<infer I, z.ZodTypeAny, z.ZodTypeAny> ? z.infer<I> : never;\n\ntype FlowOutput<F extends Flow> =\n  F extends Flow<z.ZodTypeAny, infer O, z.ZodTypeAny> ? z.infer<O> : never;\n\ntype FlowStream<F extends Flow> =\n  F extends Flow<z.ZodTypeAny, z.ZodTypeAny, infer S> ? z.infer<S> : never;\n\n/**\n * CORS configuration options\n */\nexport interface CorsOptions {\n  /**\n   * Allowed origins for CORS requests.\n   * Can be a string, array of strings, or '*' for all origins.\n   * @default '*'\n   */\n  origin?: string | string[];\n\n  /**\n   * Allowed HTTP methods.\n   * @default ['POST', 'OPTIONS']\n   */\n  methods?: string[];\n\n  /**\n   * Allowed headers in requests.\n   * @default ['Content-Type', 'Authorization']\n   */\n  allowedHeaders?: string[];\n\n  /**\n   * Headers exposed to the client.\n   */\n  exposedHeaders?: string[];\n\n  /**\n   * Whether to allow credentials.\n   * @default false\n   */\n  credentials?: boolean;\n\n  /**\n   * Max age for preflight cache (in seconds).\n   * @default 86400 (24 hours)\n   */\n  maxAge?: number;\n}\n\n/**\n * Extended action context that includes Azure Functions-specific information\n */\nexport interface AzureFunctionsActionContext extends ActionContext {\n  /** Azure Functions-specific context data */\n  azureFunctions?: {\n    request: {\n      url: string;\n      headers: Record<string, string>;\n      query: Record<string, string>;\n      params: Record<string, string>;\n    };\n    context: {\n      functionName: string;\n      invocationId: string;\n    };\n  };\n}\n\n/**\n * Options for configuring the Azure Functions handler\n */\nexport interface AzureFunctionsOptions<\n  C extends ActionContext = ActionContext,\n  T = unknown,\n> {\n  /**\n   * The authorization level for the Azure Functions HTTP trigger.\n   * @default 'anonymous'\n   */\n  authLevel?: \"anonymous\" | \"function\" | \"admin\";\n\n  /**\n   * HTTP methods to register for the Azure Functions HTTP trigger.\n   * @default ['POST', 'OPTIONS']\n   */\n  httpMethods?: string[];\n\n  /**\n   * Optional custom route for the Azure Functions HTTP trigger.\n   * If not provided, the function name is used as the route.\n   */\n  route?: string;\n\n  /**\n   * CORS configuration. Set to false to disable CORS headers.\n   * @default { origin: '*', methods: ['POST', 'OPTIONS'] }\n   */\n  cors?: CorsOptions | boolean;\n\n  /**\n   * Context provider that parses request data and returns context for the flow.\n   * This follows the same pattern as express, next.js, and other Genkit integrations.\n   *\n   * The context provider receives a RequestData object containing:\n   * - method: HTTP method ('GET', 'POST', etc.)\n   * - headers: Lowercase headers from the request\n   * - input: Parsed request body\n   *\n   * Return an ActionContext object that will be available via getContext() in the flow.\n   * Throw UserFacingError for authentication/authorization failures.\n   *\n   * @example\n   * ```typescript\n   * import { UserFacingError } from 'genkit';\n   *\n   * const authProvider: ContextProvider = async (req) => {\n   *   const token = req.headers['authorization'];\n   *   if (!token) {\n   *     throw new UserFacingError('UNAUTHENTICATED', 'Missing auth token');\n   *   }\n   *   const user = await verifyToken(token);\n   *   return { auth: { user } };\n   * };\n   *\n   * export const handler = onCallGenkit(\n   *   { contextProvider: authProvider },\n   *   myFlow\n   * );\n   * ```\n   */\n  contextProvider?: ContextProvider<C, T>;\n\n  /**\n   * Custom error handler for transforming errors before response.\n   */\n  onError?: (error: Error) =>\n    | { statusCode: number; message: string }\n    | Promise<{\n        statusCode: number;\n        message: string;\n      }>;\n\n  /**\n   * Whether to log incoming requests (for debugging).\n   * @default false\n   */\n  debug?: boolean;\n\n  /**\n   * Whether to return a streaming handler.\n   * When true, the handler returns a streaming response using\n   * `ReadableStream` for incremental SSE delivery.\n   *\n   * The streaming handler is compatible with `streamFlow` from `genkit/beta/client`.\n   * For clients sending `Accept: text/event-stream`, it writes SSE chunks\n   * incrementally. Otherwise it falls back to a buffered JSON response.\n   *\n   * @default false\n   *\n   * @example\n   * ```typescript\n   * export const handler = onCallGenkit(\n   *   { streaming: true },\n   *   myStreamingFlow\n   * );\n   * ```\n   */\n  streaming?: boolean;\n}\n\n/**\n * Response wrapper for successful flow execution (callable protocol).\n * Follows the same format as express and other Genkit integrations.\n */\nexport interface FlowResponse<T> {\n  result: T;\n}\n\n/**\n * Response wrapper for failed flow execution (callable protocol).\n * Shape matches genkit's getCallableJSON output.\n */\nexport interface FlowErrorResponse {\n  error: {\n    status: string;\n    message: string;\n    details?: unknown;\n  };\n}\n\n/**\n * Union type for flow responses\n */\nexport type AzureFunctionsFlowResponse<T> = FlowResponse<T> | FlowErrorResponse;\n\n/**\n * Azure Functions handler type\n */\nexport type AzureFunctionsHandler = HttpHandler;\n\n/**\n * Run options for flow execution\n */\nexport interface FlowRunOptions {\n  context?: Record<string, unknown>;\n}\n\n/**\n * Callable function type that includes the raw handler and metadata\n */\nexport interface CallableAzureFunction<F extends Flow> {\n  /**\n   * The Azure Functions HTTP handler\n   */\n  handler: HttpHandler;\n\n  /**\n   * The underlying Genkit flow\n   */\n  flow: F;\n\n  /**\n   * Execute the flow directly (for testing)\n   */\n  run: (\n    input: FlowInput<F>,\n    options?: FlowRunOptions,\n  ) => Promise<FlowOutput<F>>;\n\n  /**\n   * Stream the flow directly (for testing)\n   */\n  stream: (\n    input: FlowInput<F>,\n    options?: FlowRunOptions,\n  ) => {\n    stream: AsyncIterable<FlowStream<F>>;\n    output: Promise<FlowOutput<F>>;\n  };\n\n  /**\n   * Flow name\n   */\n  flowName: string;\n}\n\n/**\n * Builds CORS headers based on options\n */\nfunction buildCorsHeaders(\n  corsOptions: CorsOptions | boolean | undefined,\n  requestOrigin?: string,\n): Record<string, string> {\n  if (corsOptions === false) {\n    return {};\n  }\n\n  const opts: CorsOptions =\n    corsOptions === true || corsOptions === undefined ? {} : corsOptions;\n\n  const headers: Record<string, string> = {\n    \"Content-Type\": \"application/json\",\n  };\n\n  // Handle origin\n  const origin = opts.origin ?? \"*\";\n  if (Array.isArray(origin)) {\n    // Check if request origin is in allowed list\n    if (requestOrigin && origin.includes(requestOrigin)) {\n      headers[\"Access-Control-Allow-Origin\"] = requestOrigin;\n    }\n    // If request origin is not in the allowlist, don't set the header\n  } else {\n    headers[\"Access-Control-Allow-Origin\"] = origin;\n  }\n\n  // Handle methods\n  const methods = opts.methods ?? [\"POST\", \"OPTIONS\"];\n  headers[\"Access-Control-Allow-Methods\"] = methods.join(\", \");\n\n  // Handle allowed headers\n  const allowedHeaders = opts.allowedHeaders ?? [\n    \"Content-Type\",\n    \"Authorization\",\n  ];\n  headers[\"Access-Control-Allow-Headers\"] = allowedHeaders.join(\", \");\n\n  // Handle exposed headers\n  if (opts.exposedHeaders && opts.exposedHeaders.length > 0) {\n    headers[\"Access-Control-Expose-Headers\"] = opts.exposedHeaders.join(\", \");\n  }\n\n  // Handle credentials\n  if (opts.credentials) {\n    headers[\"Access-Control-Allow-Credentials\"] = \"true\";\n  }\n\n  // Handle max age\n  const maxAge = opts.maxAge ?? 86400;\n  headers[\"Access-Control-Max-Age\"] = String(maxAge);\n\n  return headers;\n}\n\n/**\n * Parses the request body from an Azure Functions HttpRequest.\n * Supports the Genkit callable protocol format where input is wrapped in { data: ... }\n * as well as direct input format for convenience.\n */\nasync function parseRequestBody<T>(request: HttpRequest): Promise<T> {\n  let bodyText: string;\n  try {\n    bodyText = await request.text();\n  } catch {\n    return {} as T;\n  }\n\n  if (!bodyText) {\n    return {} as T;\n  }\n\n  try {\n    const parsed = JSON.parse(bodyText);\n    // Support callable protocol: { data: <input> }\n    if (parsed && typeof parsed === \"object\" && \"data\" in parsed) {\n      return parsed.data as T;\n    }\n    return parsed as T;\n  } catch {\n    throw new UserFacingError(\n      \"INVALID_ARGUMENT\",\n      \"Invalid JSON in request body\",\n    );\n  }\n}\n\n/**\n * Gets the request origin from headers\n */\nfunction getRequestOrigin(request: HttpRequest): string | undefined {\n  return request.headers.get(\"origin\") || undefined;\n}\n\n/**\n * Converts Azure Functions request headers to lowercase record (as required by RequestData)\n */\nfunction normalizeHeaders(request: HttpRequest): Record<string, string> {\n  const result: Record<string, string> = {};\n  request.headers.forEach((value, key) => {\n    result[key.toLowerCase()] = value;\n  });\n  return result;\n}\n\n/**\n * Gets query parameters as a plain record\n */\nfunction getQueryParams(request: HttpRequest): Record<string, string> {\n  const result: Record<string, string> = {};\n  const url = new URL(request.url);\n  url.searchParams.forEach((value, key) => {\n    result[key] = value;\n  });\n  return result;\n}\n\n/**\n * Converts Azure Functions request to Genkit RequestData format\n */\nfunction toRequestData<T>(request: HttpRequest, input: T): RequestData<T> {\n  return {\n    method: request.method as RequestData[\"method\"],\n    headers: normalizeHeaders(request),\n    input,\n  };\n}\n\n/**\n * Creates an Azure Functions handler for a Genkit flow.\n *\n * This function wraps a Genkit flow to create an Azure Functions HTTP handler that:\n * - Handles CORS automatically\n * - Supports ContextProvider for authentication/authorization\n * - Provides proper error handling\n * - Returns standardized response format\n * - Supports streaming responses via ReadableStream\n *\n * @example Basic usage (auto-registers Azure Functions HTTP trigger)\n * ```typescript\n * import { genkit, z } from 'genkit';\n * import { onCallGenkit, azureOpenAI, gpt4o } from 'genkitx-azure-openai';\n *\n * const ai = genkit({\n *   plugins: [azureOpenAI()],\n *   model: gpt4o,\n * });\n *\n * const myFlow = ai.defineFlow(\n *   { name: 'myFlow', inputSchema: z.string(), outputSchema: z.string() },\n *   async (input) => {\n *     const { text } = await ai.generate({ prompt: input });\n *     return text;\n *   }\n * );\n *\n * // Automatically registered as POST /api/myFlow (uses flow name)\n * export const myFlowFn = onCallGenkit(myFlow);\n * ```\n *\n * @example With ContextProvider for authentication\n * ```typescript\n * import { UserFacingError } from 'genkit';\n * import type { ContextProvider } from 'genkit/context';\n *\n * interface AuthContext {\n *   auth: { user: { id: string; name: string } };\n * }\n *\n * const authProvider: ContextProvider<AuthContext> = async (req) => {\n *   const token = req.headers['authorization'];\n *   if (!token) {\n *     throw new UserFacingError('UNAUTHENTICATED', 'Missing auth token');\n *   }\n *   const user = await verifyToken(token);\n *   return { auth: { user } };\n * };\n *\n * // Registered as POST /api/myFlow (uses flow name)\n * export const mySecureFlowFn = onCallGenkit(\n *   { contextProvider: authProvider },\n *   myFlow\n * );\n * ```\n *\n * @param flow - The Genkit flow to wrap\n * @returns A CallableAzureFunction with `handler`, `flow`, `run`, `stream`, and `flowName`\n */\nexport function onCallGenkit<F extends Flow>(flow: F): CallableAzureFunction<F>;\n\n/**\n * Creates an Azure Functions handler for a Genkit flow with options.\n *\n * @param opts - Configuration options for the Azure Functions handler\n * @param flow - The Genkit flow to wrap\n * @returns A CallableAzureFunction with `handler`, `flow`, `run`, `stream`, and `flowName`\n */\nexport function onCallGenkit<C extends ActionContext, F extends Flow>(\n  opts: AzureFunctionsOptions<C, FlowInput<F>> & { streaming: true },\n  flow: F,\n): CallableAzureFunction<F>;\n\nexport function onCallGenkit<C extends ActionContext, F extends Flow>(\n  opts: AzureFunctionsOptions<C, FlowInput<F>>,\n  flow: F,\n): CallableAzureFunction<F>;\n\n/**\n * Implementation of onCallGenkit\n */\nexport function onCallGenkit<C extends ActionContext, F extends Flow>(\n  optsOrFlow: F | AzureFunctionsOptions<C, FlowInput<F>>,\n  flowArg?: F,\n): CallableAzureFunction<F> {\n  let opts: AzureFunctionsOptions<C, FlowInput<F>>;\n  let flow: F;\n\n  if (arguments.length === 1) {\n    opts = {};\n    flow = optsOrFlow as F;\n  } else {\n    opts = optsOrFlow as AzureFunctionsOptions<C, FlowInput<F>>;\n    flow = flowArg as F;\n  }\n\n  const flowName = flow.__action?.name || \"unknown\";\n\n  /**\n   * Build Azure Functions-specific context from the request\n   */\n  function buildAzureFunctionsContext(\n    request: HttpRequest,\n    azureContext: InvocationContext,\n  ): AzureFunctionsActionContext {\n    return {\n      azureFunctions: {\n        request: {\n          url: request.url,\n          headers: normalizeHeaders(request),\n          query: getQueryParams(request),\n          params: request.params as Record<string, string>,\n        },\n        context: {\n          functionName: azureContext.functionName,\n          invocationId: azureContext.invocationId,\n        },\n      },\n    };\n  }\n\n  /**\n   * Resolve action context, merging Azure Functions context with context provider\n   */\n  async function resolveActionContext(\n    request: HttpRequest,\n    azureContext: InvocationContext,\n    input: FlowInput<F>,\n  ): Promise<ActionContext> {\n    const azureFunctionsContext = buildAzureFunctionsContext(\n      request,\n      azureContext,\n    );\n\n    if (opts.contextProvider) {\n      const requestData = toRequestData(request, input);\n      const providerContext = await opts.contextProvider(requestData);\n      return { ...azureFunctionsContext, ...providerContext };\n    }\n\n    return azureFunctionsContext;\n  }\n\n  /**\n   * Build error response\n   */\n  async function buildErrorResponse(\n    error: unknown,\n    corsHeaders: Record<string, string>,\n  ): Promise<HttpResponseInit> {\n    if (opts.onError) {\n      const customError = await opts.onError(\n        error instanceof Error ? error : new Error(String(error)),\n      );\n      return {\n        status: customError.statusCode,\n        headers: corsHeaders,\n        jsonBody: {\n          error: {\n            status: \"INTERNAL\",\n            message: customError.message,\n          },\n        } satisfies FlowErrorResponse,\n      };\n    }\n\n    return {\n      status: getHttpStatus(error),\n      headers: corsHeaders,\n      jsonBody: getCallableJSON(error),\n    };\n  }\n\n  /**\n   * Non-streaming handler\n   */\n  async function standardHandler(\n    request: HttpRequest,\n    azureContext: InvocationContext,\n  ): Promise<HttpResponseInit> {\n    const requestOrigin = getRequestOrigin(request);\n    const corsHeaders = buildCorsHeaders(opts.cors, requestOrigin);\n\n    // Handle OPTIONS preflight request\n    if (request.method === \"OPTIONS\") {\n      return {\n        status: 204,\n        headers: corsHeaders,\n      };\n    }\n\n    // Debug logging\n    if (opts.debug) {\n      azureContext.log(\n        `[${flowName}] Request: ${request.method} ${request.url}`,\n      );\n      azureContext.log(\n        `[${flowName}] Headers:`,\n        JSON.stringify(normalizeHeaders(request), null, 2),\n      );\n    }\n\n    try {\n      // Parse request body\n      const input = await parseRequestBody<FlowInput<F>>(request);\n\n      // Resolve context\n      const actionContext = await resolveActionContext(\n        request,\n        azureContext,\n        input,\n      );\n\n      if (opts.debug) {\n        azureContext.log(`[${flowName}] Running flow with input:`, input);\n      }\n\n      // Execute the flow with context\n      const runResult = await flow.run(input, { context: actionContext });\n      const result = runResult.result as FlowOutput<F>;\n\n      if (opts.debug) {\n        azureContext.log(`[${flowName}] Flow completed successfully`);\n      }\n\n      // Return success response (callable protocol)\n      return {\n        status: 200,\n        headers: corsHeaders,\n        jsonBody: {\n          result,\n        } satisfies FlowResponse<FlowOutput<F>>,\n      };\n    } catch (error) {\n      azureContext.error(`[${flowName}] Error:`, error);\n      return buildErrorResponse(error, corsHeaders);\n    }\n  }\n\n  /**\n   * Streaming handler using ReadableStream\n   */\n  async function streamingHandler(\n    request: HttpRequest,\n    azureContext: InvocationContext,\n  ): Promise<HttpResponseInit> {\n    const requestOrigin = getRequestOrigin(request);\n    const corsHeaders = buildCorsHeaders(opts.cors, requestOrigin);\n\n    // Handle OPTIONS preflight\n    if (request.method === \"OPTIONS\") {\n      return {\n        status: 204,\n        headers: corsHeaders,\n      };\n    }\n\n    if (opts.debug) {\n      azureContext.log(\n        `[${flowName}] Stream request: ${request.method} ${request.url}`,\n      );\n    }\n\n    try {\n      const input = await parseRequestBody<FlowInput<F>>(request);\n\n      // Resolve context\n      const actionContext = await resolveActionContext(\n        request,\n        azureContext,\n        input,\n      );\n\n      // Check if client wants SSE streaming\n      const acceptHeader = request.headers.get(\"accept\") || \"\";\n      const clientWantsStreaming = acceptHeader.includes(\"text/event-stream\");\n\n      if (clientWantsStreaming) {\n        // Real streaming: return SSE events via ReadableStream\n        const encoder = new TextEncoder();\n\n        const readableStream = new ReadableStream({\n          async start(controller) {\n            try {\n              const { stream, output } = flow.stream(input, {\n                context: actionContext,\n              });\n\n              for await (const chunk of stream) {\n                const sseData = `data: ${JSON.stringify({ message: chunk })}\\n\\n`;\n                controller.enqueue(encoder.encode(sseData));\n              }\n\n              const result = (await output) as FlowOutput<F>;\n              const sseFinal = `data: ${JSON.stringify({ result })}\\n\\n`;\n              controller.enqueue(encoder.encode(sseFinal));\n\n              controller.close();\n\n              if (opts.debug) {\n                azureContext.log(\n                  `[${flowName}] Streaming flow completed successfully`,\n                );\n              }\n            } catch (error) {\n              azureContext.error(`[${flowName}] Stream error:`, error);\n              const errorData = `data: ${JSON.stringify(getCallableJSON(error))}\\n\\n`;\n              controller.enqueue(encoder.encode(errorData));\n              controller.close();\n            }\n          },\n        });\n\n        return {\n          status: 200,\n          headers: {\n            ...corsHeaders,\n            \"Content-Type\": \"text/event-stream\",\n            \"Cache-Control\": \"no-cache\",\n            Connection: \"keep-alive\",\n          },\n          body: readableStream,\n        };\n      } else {\n        // Non-streaming: buffered JSON response\n        const runResult = await flow.run(input, {\n          context: actionContext,\n        });\n        const result = runResult.result as FlowOutput<F>;\n\n        return {\n          status: 200,\n          headers: corsHeaders,\n          jsonBody: { result },\n        };\n      }\n    } catch (error) {\n      azureContext.error(`[${flowName}] Stream error:`, error);\n      return buildErrorResponse(error, corsHeaders);\n    }\n  }\n\n  // Choose the handler based on streaming option\n  const handler: HttpHandler = opts.streaming\n    ? streamingHandler\n    : standardHandler;\n\n  // Build the callable function object\n  const callableFunction: CallableAzureFunction<F> = {\n    handler,\n    flow,\n    flowName,\n    run: async (\n      input: FlowInput<F>,\n      options?: FlowRunOptions,\n    ): Promise<FlowOutput<F>> => {\n      const runResult = await flow.run(input, {\n        context: options?.context,\n      });\n      return runResult.result as FlowOutput<F>;\n    },\n    stream: (\n      input: FlowInput<F>,\n      options?: FlowRunOptions,\n    ): {\n      stream: AsyncIterable<FlowStream<F>>;\n      output: Promise<FlowOutput<F>>;\n    } => {\n      return flow.stream(input, {\n        context: options?.context,\n      }) as unknown as {\n        stream: AsyncIterable<FlowStream<F>>;\n        output: Promise<FlowOutput<F>>;\n      };\n    },\n  };\n\n  // Auto-register the Azure Functions HTTP trigger using the flow name\n  const methods = (opts.httpMethods ?? [\"POST\", \"OPTIONS\"]) as (\n    | \"GET\"\n    | \"POST\"\n    | \"PUT\"\n    | \"DELETE\"\n    | \"PATCH\"\n    | \"HEAD\"\n    | \"OPTIONS\"\n  )[];\n  const authLevel = opts.authLevel ?? \"anonymous\";\n\n  app.http(flowName, {\n    methods,\n    authLevel,\n    ...(opts.route ? { route: opts.route } : {}),\n    handler,\n  });\n\n  return callableFunction;\n}\n\n// ============================================================================\n// Context Provider Helpers\n// ============================================================================\n\n/**\n * Context with API key authentication\n */\nexport interface ApiKeyContext extends ActionContext {\n  auth: {\n    apiKey: string;\n  };\n}\n\n/**\n * Context with bearer token authentication\n */\nexport interface BearerTokenContext extends ActionContext {\n  auth: {\n    token: string;\n  };\n}\n\n/**\n * Creates a context provider that requires an API key in a specific header.\n *\n * @example\n * ```typescript\n * // Require API key to match a specific value\n * const callable = onCallGenkit(\n *   { contextProvider: requireApiKey('X-API-Key', process.env.API_KEY!) },\n *   myFlow\n * );\n *\n * // Or with a custom validation function\n * const callable = onCallGenkit(\n *   {\n *     contextProvider: requireApiKey('X-API-Key', async (key) => {\n *       const valid = await validateApiKey(key);\n *       if (!valid) {\n *         throw new UserFacingError('PERMISSION_DENIED', 'Invalid API key');\n *       }\n *     })\n *   },\n *   myFlow\n * );\n * ```\n */\nexport function requireApiKey(\n  headerName: string,\n  expectedValueOrValidator: string | ((apiKey: string) => void | Promise<void>),\n): ContextProvider<ApiKeyContext> {\n  const lowerHeaderName = headerName.toLowerCase();\n\n  return async (request: RequestData): Promise<ApiKeyContext> => {\n    const apiKey = request.headers[lowerHeaderName];\n\n    if (!apiKey) {\n      throw new UserFacingError(\n        \"UNAUTHENTICATED\",\n        `Missing required header: ${headerName}`,\n      );\n    }\n\n    if (typeof expectedValueOrValidator === \"string\") {\n      if (apiKey !== expectedValueOrValidator) {\n        throw new UserFacingError(\"PERMISSION_DENIED\", \"Invalid API key\");\n      }\n    } else {\n      await expectedValueOrValidator(apiKey);\n    }\n\n    return {\n      auth: { apiKey },\n    };\n  };\n}\n\n/**\n * Creates a context provider that requires Bearer token authentication.\n *\n * @example\n * ```typescript\n * // With custom token validation\n * const callable = onCallGenkit(\n *   {\n *     contextProvider: requireBearerToken(async (token) => {\n *       const user = await verifyJWT(token);\n *       return { auth: { user } };\n *     })\n *   },\n *   myFlow\n * );\n * ```\n */\nexport function requireBearerToken<\n  C extends ActionContext = BearerTokenContext,\n>(validateToken: (token: string) => C | Promise<C>): ContextProvider<C> {\n  return async (request: RequestData): Promise<C> => {\n    const authHeader = request.headers[\"authorization\"];\n\n    if (!authHeader) {\n      throw new UserFacingError(\n        \"UNAUTHENTICATED\",\n        \"Missing Authorization header\",\n      );\n    }\n\n    const match = authHeader.match(/^Bearer\\s+(.+)$/i);\n    if (!match) {\n      throw new UserFacingError(\n        \"UNAUTHENTICATED\",\n        \"Invalid Authorization header format. Expected: Bearer <token>\",\n      );\n    }\n\n    const token = match[1];\n    return await validateToken(token);\n  };\n}\n\n/**\n * Creates a context provider that requires a specific header to be present.\n *\n * @example\n * ```typescript\n * // Require header to exist\n * const callable = onCallGenkit(\n *   { contextProvider: requireHeader('X-Request-ID') },\n *   myFlow\n * );\n *\n * // Require header to have specific value\n * const callable = onCallGenkit(\n *   { contextProvider: requireHeader('X-API-Version', '2.0') },\n *   myFlow\n * );\n * ```\n */\nexport function requireHeader(\n  headerName: string,\n  expectedValue?: string,\n): ContextProvider<ActionContext> {\n  const lowerHeaderName = headerName.toLowerCase();\n\n  return async (request: RequestData): Promise<ActionContext> => {\n    const value = request.headers[lowerHeaderName];\n\n    if (!value) {\n      throw new UserFacingError(\n        \"UNAUTHENTICATED\",\n        `Missing required header: ${headerName}`,\n      );\n    }\n\n    if (expectedValue !== undefined && value !== expectedValue) {\n      throw new UserFacingError(\n        \"PERMISSION_DENIED\",\n        `Invalid value for header: ${headerName}`,\n      );\n    }\n\n    return {};\n  };\n}\n\n/**\n * Creates a context provider that always allows requests (no authentication).\n * Useful for public endpoints.\n *\n * @example\n * ```typescript\n * const callable = onCallGenkit(\n *   { contextProvider: allowAll() },\n *   myPublicFlow\n * );\n * ```\n */\nexport function allowAll(): ContextProvider<ActionContext> {\n  return async (): Promise<ActionContext> => ({});\n}\n\n/**\n * Combines multiple context providers. All providers must succeed.\n * The returned context is a merge of all provider contexts.\n *\n * @example\n * ```typescript\n * const callable = onCallGenkit(\n *   {\n *     contextProvider: allOf(\n *       requireHeader('X-Request-ID'),\n *       requireApiKey('X-API-Key', process.env.API_KEY!)\n *     )\n *   },\n *   myFlow\n * );\n * ```\n */\nexport function allOf<C extends ActionContext = ActionContext>(\n  ...providers: ContextProvider<ActionContext>[]\n): ContextProvider<C> {\n  return async (request: RequestData): Promise<C> => {\n    let mergedContext: ActionContext = {};\n\n    for (const provider of providers) {\n      const context = await provider(request);\n      mergedContext = { ...mergedContext, ...context };\n    }\n\n    return mergedContext as C;\n  };\n}\n\n/**\n * Tries context providers in order, returning the first one that succeeds.\n * If all providers fail, throws the error from the last provider.\n *\n * @example\n * ```typescript\n * // Accept either API key or Bearer token\n * const callable = onCallGenkit(\n *   {\n *     contextProvider: anyOf(\n *       requireApiKey('X-API-Key', process.env.API_KEY!),\n *       requireBearerToken(async (token) => {\n *         const user = await verifyJWT(token);\n *         return { auth: { user } };\n *       })\n *     )\n *   },\n *   myFlow\n * );\n * ```\n */\nexport function anyOf<C extends ActionContext = ActionContext>(\n  ...providers: ContextProvider<ActionContext>[]\n): ContextProvider<C> {\n  return async (request: RequestData): Promise<C> => {\n    let lastError: Error | undefined;\n\n    for (const provider of providers) {\n      try {\n        const context = await provider(request);\n        return context as C;\n      } catch (error) {\n        lastError = error instanceof Error ? error : new Error(String(error));\n      }\n    }\n\n    throw lastError || new UserFacingError(\"UNAUTHENTICATED\", \"Unauthorized\");\n  };\n}\n\nexport default onCallGenkit;\n"],"mappings":"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","names":[]}