genkitx-azure-openai
Version:
Genkit AI framework plugin for Azure OpenAI APIs.
1 lines • 36.8 kB
Source Map (JSON)
{"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":[]}