@usagetap/sdk 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -76,11 +76,12 @@ function inferUsageFromResponse(response, hints) {
76
76
  if (!candidate.usage) {
77
77
  return void 0;
78
78
  }
79
+ const cachedInputTokens = candidate.usage.prompt_tokens_details?.cached_tokens ?? candidate.usage.cached_tokens;
79
80
  return {
80
81
  modelUsed: candidate.model ?? hints?.preferredModel,
81
82
  inputTokens: candidate.usage.prompt_tokens,
82
83
  responseTokens: candidate.usage.completion_tokens,
83
- cachedTokens: candidate.usage.cached_tokens
84
+ cachedInputTokens
84
85
  };
85
86
  }
86
87
  function wrapStreamForUsageTap(source, finalize, ctx) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/adapters/openai.ts","../../src/adapters/openrouter.ts"],"names":[],"mappings":"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","file":"openrouter.mjs","sourcesContent":["import type OpenAI from \"openai\";\r\nimport { UsageTapClient } from \"../client\";\r\nimport { UsageTapError } from \"../errors\";\r\nimport type {\r\n BeginCallRequest,\r\n BeginCallResponseBody,\r\n EndCallRequest,\r\n UsageTapSuccessResponse,\r\n VendorHints,\r\n WithUsageContext,\r\n WithUsageOptions,\r\n} from \"../types\";\r\n\r\nexport interface OpenAIAdapterInit {\r\n client: OpenAI;\r\n usageTap: UsageTapClient;\r\n}\r\n\r\nexport interface OpenAIRequestContext {\r\n hints?: VendorHints;\r\n begin: UsageTapSuccessResponse<BeginCallResponseBody>;\r\n}\r\n\r\nexport interface OpenAIInvokeParams<TResponse> {\r\n begin: BeginCallRequest;\r\n call: (client: OpenAI, ctx: OpenAIRequestContext) => Promise<TResponse>;\r\n extractUsage?: (response: TResponse) => Partial<Omit<EndCallRequest, \"callId\" | \"error\">> | void;\r\n withUsageOptions?: WithUsageOptions;\r\n}\r\n\r\nexport interface OpenAIInvokeResult<TResponse> {\r\n data: TResponse;\r\n begin: UsageTapSuccessResponse<BeginCallResponseBody>;\r\n}\r\n\r\nexport interface OpenAIStreamCallResult<TStream> {\r\n stream: AsyncIterable<TStream>;\r\n onComplete?: () => Promise<Partial<Omit<EndCallRequest, \"callId\" | \"error\">> | void> | Partial<Omit<EndCallRequest, \"callId\" | \"error\">> | void;\r\n}\r\n\r\nexport interface OpenAIStreamParams<TStream> {\r\n begin: BeginCallRequest;\r\n call: (client: OpenAI, ctx: OpenAIRequestContext) => Promise<OpenAIStreamCallResult<TStream>>;\r\n withUsageOptions?: WithUsageOptions;\r\n}\r\n\r\nexport interface OpenAIStreamResult<TStream> {\r\n stream: AsyncIterable<TStream> & { __usageTapFinalize?: () => Promise<void> };\r\n begin: UsageTapSuccessResponse<BeginCallResponseBody>;\r\n finalize: () => Promise<void>;\r\n}\r\n\r\nexport interface OpenAIAdapter {\r\n invoke<TResponse>(params: OpenAIInvokeParams<TResponse>): Promise<OpenAIInvokeResult<TResponse>>;\r\n invokeStream<TStream>(params: OpenAIStreamParams<TStream>): Promise<OpenAIStreamResult<TStream>>;\r\n}\r\n\r\ntype ReplaceProperty<T, K extends keyof T, V> = Omit<T, K> & Record<K, V>;\r\n\r\nexport type WrapOpenAIContext = BeginCallRequest;\r\n\r\nexport interface WrapOpenAIOptions {\r\n defaultContext?: Partial<WrapOpenAIContext>;\r\n applyVendorHints?: boolean;\r\n}\r\n\r\ntype ChatCompletionsResource = OpenAI[\"chat\"][\"completions\"];\r\ntype ChatCompletionCreate = ChatCompletionsResource[\"create\"];\r\ntype ChatCompletionCreateParams = Parameters<ChatCompletionCreate>[0];\r\ntype ChatCompletionCreateOptions = Parameters<ChatCompletionCreate>[1];\r\ntype ChatCompletionCreateReturn = ReturnType<ChatCompletionCreate>;\r\n\r\nexport type WrapOpenAICallOptions = (ChatCompletionCreateOptions extends undefined\r\n ? { usageTap?: Partial<WrapOpenAIContext>; withUsage?: WithUsageOptions }\r\n : ChatCompletionCreateOptions & { usageTap?: Partial<WrapOpenAIContext>; withUsage?: WithUsageOptions });\r\n\r\ninterface WrappedChatCompletions extends Omit<ChatCompletionsResource, \"create\"> {\r\n create: (\r\n params: ChatCompletionCreateParams,\r\n options?: WrapOpenAICallOptions,\r\n ) => ChatCompletionCreateReturn;\r\n}\r\n\r\ntype ResponsesResource = OpenAI extends { responses: infer R } ? R : never;\r\ntype ResponsesCreate = ResponsesResource extends { create: infer T } ? T : never;\r\ntype ResponsesCreateParams = ResponsesCreate extends (...args: infer P) => unknown ? P[0] : never;\r\ntype ResponsesCreateOptions = ResponsesCreate extends (...args: infer P) => unknown ? P[1] : never;\r\ntype ResponsesCreateReturn = ResponsesCreate extends (...args: unknown[]) => infer R ? R : never;\r\n\r\nexport type WrapOpenAIResponseCallOptions = (ResponsesCreateOptions extends undefined\r\n ? { usageTap?: Partial<WrapOpenAIContext>; withUsage?: WithUsageOptions }\r\n : ResponsesCreateOptions & { usageTap?: Partial<WrapOpenAIContext>; withUsage?: WithUsageOptions });\r\n\r\ntype WrappedResponses = ResponsesResource extends undefined\r\n ? undefined\r\n : Omit<NonNullable<ResponsesResource>, \"create\"> & {\r\n create: (\r\n params: ResponsesCreateParams,\r\n options?: WrapOpenAIResponseCallOptions,\r\n ) => ResponsesCreateReturn;\r\n };\r\n\r\nexport type WrappedOpenAI = OpenAI & {\r\n chat: ReplaceProperty<OpenAI[\"chat\"], \"completions\", WrappedChatCompletions>;\r\n} & (ResponsesResource extends undefined\r\n ? { responses?: undefined }\r\n : { responses: WrappedResponses }) & {\r\n toNextResponse: typeof toNextResponse;\r\n pipeToResponse: typeof pipeToResponse;\r\n unwrap: () => OpenAI;\r\n };\r\n\r\nexport interface StreamOpenAIRouteOptions {\r\n getRequest: (req: Request) => Promise<{\r\n params: ChatCompletionCreateParams;\r\n usageTap?: Partial<WrapOpenAIContext>;\r\n withUsage?: WithUsageOptions;\r\n }>;\r\n wrapOptions?: WrapOpenAIOptions;\r\n defaultContext?: Partial<WrapOpenAIContext>;\r\n stream?: {\r\n mode?: StreamMode;\r\n headers?: Record<string, string>;\r\n responseInit?: ResponseInit;\r\n };\r\n}\r\n\r\nexport type StreamMode = \"text\" | \"sse\";\r\n\r\nexport interface StreamToResponseOptions {\r\n mode?: StreamMode;\r\n headers?: Record<string, string>;\r\n contentType?: string;\r\n sse?: {\r\n event?: string;\r\n retry?: number;\r\n };\r\n}\r\n\r\nexport function createOpenAIAdapter(init: OpenAIAdapterInit): OpenAIAdapter {\r\n const { client, usageTap } = init;\r\n\r\n return {\r\n async invoke<TResponse>(params: OpenAIInvokeParams<TResponse>): Promise<OpenAIInvokeResult<TResponse>> {\r\n const result = await usageTap.withUsage<OpenAIInvokeResult<TResponse>>(\r\n params.begin,\r\n async (ctx) => {\r\n const response = await params.call(client, {\r\n hints: ctx.begin.data.vendorHints,\r\n begin: ctx.begin,\r\n });\r\n\r\n tryInferUsage(response, ctx.begin.data.vendorHints, params.extractUsage, ctx);\r\n\r\n return {\r\n data: response,\r\n begin: ctx.begin,\r\n } satisfies OpenAIInvokeResult<TResponse>;\r\n },\r\n params.withUsageOptions,\r\n );\r\n\r\n return result;\r\n },\r\n\r\n async invokeStream<TStream>(params: OpenAIStreamParams<TStream>): Promise<OpenAIStreamResult<TStream>> {\r\n const result = await usageTap.withUsage<OpenAIStreamResult<TStream>>(\r\n params.begin,\r\n async (ctx) => {\r\n const { stream, onComplete } = await params.call(client, {\r\n hints: ctx.begin.data.vendorHints,\r\n begin: ctx.begin,\r\n });\r\n\r\n const wrapped = wrapStreamForUsageTap(stream, async () => {\r\n if (!onComplete) return;\r\n try {\r\n const maybeUsage = await onComplete();\r\n if (maybeUsage) {\r\n ctx.setUsage(maybeUsage);\r\n }\r\n } catch (error) {\r\n ctx.setError({\r\n code: \"USAGE_FINALIZE_ERROR\",\r\n message: error instanceof Error ? error.message : String(error),\r\n });\r\n throw error;\r\n }\r\n }, ctx);\r\n\r\n const finalize = async (): Promise<void> => {\r\n await wrapped.__usageTapFinalize?.();\r\n };\r\n\r\n return {\r\n stream: wrapped,\r\n begin: ctx.begin,\r\n finalize,\r\n } satisfies OpenAIStreamResult<TStream>;\r\n },\r\n params.withUsageOptions,\r\n );\r\n\r\n return result;\r\n },\r\n };\r\n}\r\n\r\nexport type UsageTapStream<T> = AsyncIterable<T> & { __usageTapFinalize?: () => Promise<void> };\r\ntype UsageTapIterableIterator<T> = AsyncIterator<T> & UsageTapStream<T> & {\r\n __usageTapFinalize: () => Promise<void>;\r\n};\r\n\r\nexport function toNextResponse<T>(\r\n stream: UsageTapStream<T>,\r\n options: StreamToResponseOptions = {},\r\n): Response {\r\n const mode = options.mode ?? \"text\";\r\n const headers = new Headers(options.headers ?? {});\r\n\r\n if (mode === \"sse\") {\r\n headers.set(\"content-type\", \"text/event-stream; charset=utf-8\");\r\n headers.set(\"cache-control\", \"no-cache, no-transform\");\r\n headers.set(\"connection\", \"keep-alive\");\r\n headers.set(\"x-accel-buffering\", \"no\");\r\n } else {\r\n headers.set(\"content-type\", options.contentType ?? \"text/plain; charset=utf-8\");\r\n }\r\n\r\n const encoder = new TextEncoder();\r\n let iterator: AsyncIterator<T> | undefined;\r\n\r\n const body = new ReadableStream<Uint8Array>({\r\n async start(controller: ReadableStreamDefaultController<Uint8Array>): Promise<void> {\r\n try {\r\n const getIterator = stream[Symbol.asyncIterator];\r\n if (typeof getIterator !== \"function\") {\r\n controller.close();\r\n return;\r\n }\r\n\r\n iterator = getIterator.call(stream);\r\n\r\n while (true) {\r\n const result = await iterator.next();\r\n if (result.done) {\r\n break;\r\n }\r\n\r\n const text = chunkToText(result.value);\r\n if (!text) {\r\n continue;\r\n }\r\n\r\n if (mode === \"sse\") {\r\n controller.enqueue(encoder.encode(formatSsePayload(text, options.sse)));\r\n } else {\r\n controller.enqueue(encoder.encode(text));\r\n }\r\n }\r\n controller.close();\r\n } catch (error) {\r\n controller.error(error);\r\n } finally {\r\n await stream.__usageTapFinalize?.();\r\n }\r\n },\r\n async cancel(): Promise<void> {\r\n if (!iterator) {\r\n const getIterator = stream[Symbol.asyncIterator];\r\n if (typeof getIterator === \"function\") {\r\n iterator = getIterator.call(stream);\r\n }\r\n }\r\n\r\n if (iterator && typeof iterator.return === \"function\") {\r\n await iterator.return();\r\n }\r\n await stream.__usageTapFinalize?.();\r\n },\r\n });\r\n\r\n return new Response(body, { headers });\r\n}\r\n\r\nexport async function pipeToResponse<T>(\r\n stream: UsageTapStream<T>,\r\n res: NodeResponseLike,\r\n options: StreamToResponseOptions = {},\r\n): Promise<void> {\r\n const mode = options.mode ?? \"text\";\r\n\r\n if (mode === \"sse\") {\r\n setHeaderIfPossible(res, \"Content-Type\", \"text/event-stream; charset=utf-8\");\r\n setHeaderIfPossible(res, \"Cache-Control\", \"no-cache, no-transform\");\r\n setHeaderIfPossible(res, \"Connection\", \"keep-alive\");\r\n setHeaderIfPossible(res, \"X-Accel-Buffering\", \"no\");\r\n } else {\r\n setHeaderIfPossible(res, \"Content-Type\", options.contentType ?? \"text/plain; charset=utf-8\");\r\n }\r\n\r\n const encoder = new TextEncoder();\r\n const iterator = stream[Symbol.asyncIterator]();\r\n\r\n try {\r\n while (true) {\r\n const result = await iterator.next();\r\n if (result.done) {\r\n break;\r\n }\r\n const text = chunkToText(result.value);\r\n if (!text) {\r\n continue;\r\n }\r\n\r\n const payload = mode === \"sse\" ? formatSsePayload(text, options.sse) : text;\r\n res.write(Buffer.from(encoder.encode(payload)));\r\n res.flush?.();\r\n }\r\n } finally {\r\n res.end();\r\n await stream.__usageTapFinalize?.();\r\n }\r\n}\r\n\r\nconst USAGETAP_CORRELATION_HEADER = \"x-usage-correlation-id\";\r\n\r\nexport function wrapOpenAI(\r\n client: OpenAI,\r\n usageTap: UsageTapClient,\r\n options: WrapOpenAIOptions = {},\r\n): WrappedOpenAI {\r\n if (!client) {\r\n throw new UsageTapError(\"USAGETAP_BAD_REQUEST\", \"wrapOpenAI requires an OpenAI client instance\");\r\n }\r\n\r\n const defaultContext = options.defaultContext;\r\n const applyVendorHints = options.applyVendorHints !== false;\r\n\r\n const proxiedChat = client.chat\r\n ? createChatProxy(client.chat, usageTap, defaultContext, applyVendorHints)\r\n : undefined;\r\n\r\n const proxiedResponses = typeof client.responses !== \"undefined\"\r\n ? createResponsesProxy(client.responses, usageTap, defaultContext, applyVendorHints)\r\n : undefined;\r\n\r\n const handler: ProxyHandler<OpenAI> = {\r\n get(target, prop, receiver) {\r\n if (prop === \"chat\" && proxiedChat) {\r\n return proxiedChat;\r\n }\r\n\r\n if (prop === \"responses\" && typeof target.responses !== \"undefined\") {\r\n return proxiedResponses ?? (Reflect.get(target as object, prop, receiver) as unknown);\r\n }\r\n\r\n if (prop === \"toNextResponse\") {\r\n return toNextResponse;\r\n }\r\n\r\n if (prop === \"pipeToResponse\") {\r\n return pipeToResponse;\r\n }\r\n\r\n if (prop === \"unwrap\") {\r\n return () => target;\r\n }\r\n\r\n return Reflect.get(target as object, prop, receiver) as unknown;\r\n },\r\n };\r\n\r\n return new Proxy(client, handler) as WrappedOpenAI;\r\n}\r\n\r\nexport function streamOpenAIRoute(\r\n usageTap: UsageTapClient,\r\n openai: OpenAI,\r\n options: StreamOpenAIRouteOptions,\r\n): (req: Request) => Promise<Response> {\r\n if (!options?.getRequest) {\r\n throw new UsageTapError(\"USAGETAP_BAD_REQUEST\", \"streamOpenAIRoute requires a getRequest function\");\r\n }\r\n\r\n const wrapConfig: WrapOpenAIOptions | undefined = options.wrapOptions || options.defaultContext\r\n ? {\r\n ...(options.wrapOptions ?? {}),\r\n defaultContext: options.defaultContext ?? options.wrapOptions?.defaultContext,\r\n }\r\n : undefined;\r\n\r\n const wrappedClient = wrapConfig\r\n ? wrapOpenAI(openai, usageTap, wrapConfig)\r\n : wrapOpenAI(openai, usageTap);\r\n\r\n return async (req: Request): Promise<Response> => {\r\n const requestConfig = await options.getRequest(req);\r\n const mergedParams: ChatCompletionCreateParams = {\r\n ...requestConfig.params,\r\n stream: true,\r\n };\r\n\r\n const callOptions: Partial<WrapOpenAICallOptions> = {};\r\n if (requestConfig.usageTap) {\r\n callOptions.usageTap = requestConfig.usageTap;\r\n }\r\n if (requestConfig.withUsage) {\r\n callOptions.withUsage = requestConfig.withUsage;\r\n }\r\n\r\n const stream = await wrappedClient.chat.completions.create(\r\n mergedParams,\r\n Object.keys(callOptions).length ? (callOptions as WrapOpenAICallOptions) : undefined,\r\n );\r\n\r\n const baseResponse = toNextResponse(stream as UsageTapStream<unknown>, {\r\n mode: options.stream?.mode ?? \"sse\",\r\n headers: options.stream?.headers,\r\n });\r\n\r\n const init = options.stream?.responseInit;\r\n if (!init) {\r\n return baseResponse;\r\n }\r\n\r\n const mergedHeaders = new Headers(baseResponse.headers);\r\n if (init.headers) {\r\n const extra = normalizeHeaders(init.headers);\r\n for (const [key, value] of Object.entries(extra)) {\r\n mergedHeaders.set(key, value);\r\n }\r\n }\r\n\r\n return new Response(baseResponse.body, {\r\n status: init.status ?? baseResponse.status,\r\n statusText: init.statusText ?? baseResponse.statusText,\r\n headers: mergedHeaders,\r\n });\r\n };\r\n}\r\n\r\nexport interface NodeResponseLike {\r\n write(chunk: string | Uint8Array | Buffer): unknown;\r\n end(chunk?: string | Uint8Array | Buffer): unknown;\r\n setHeader?(name: string, value: string): void;\r\n headersSent?: boolean;\r\n statusCode?: number;\r\n status?(code: number): void;\r\n flush?(): void;\r\n}\r\n\r\nfunction createChatProxy(\r\n resource: OpenAI[\"chat\"],\r\n usageTap: UsageTapClient,\r\n defaultContext: Partial<WrapOpenAIContext> | undefined,\r\n applyVendorHints: boolean,\r\n): ReplaceProperty<OpenAI[\"chat\"], \"completions\", WrappedChatCompletions> {\r\n const completions = createChatCompletionsProxy(\r\n resource.completions,\r\n usageTap,\r\n defaultContext,\r\n applyVendorHints,\r\n );\r\n\r\n const handler: ProxyHandler<OpenAI[\"chat\"]> = {\r\n get(target, prop, receiver) {\r\n if (prop === \"completions\") {\r\n return completions;\r\n }\r\n return Reflect.get(target as object, prop, receiver) as unknown;\r\n },\r\n };\r\n\r\n return new Proxy(resource, handler) as ReplaceProperty<OpenAI[\"chat\"], \"completions\", WrappedChatCompletions>;\r\n}\r\n\r\nfunction createResponsesProxy(\r\n resource: ResponsesResource,\r\n usageTap: UsageTapClient,\r\n defaultContext: Partial<WrapOpenAIContext> | undefined,\r\n applyVendorHints: boolean,\r\n): WrappedResponses | undefined {\r\n if (!resource || typeof resource !== \"object\") {\r\n return undefined;\r\n }\r\n\r\n if (!(\"create\" in resource) || typeof (resource as { create?: unknown }).create !== \"function\") {\r\n return resource as unknown as WrappedResponses;\r\n }\r\n\r\n const originalCreate = (resource as { create: (...args: unknown[]) => unknown }).create.bind(resource);\r\n\r\n const wrappedCreate = (\r\n params: ResponsesCreateParams,\r\n options?: WrapOpenAIResponseCallOptions,\r\n ): ResponsesCreateReturn => {\r\n const { requestOptions, usageContext, withUsage } = splitUsageOptions(options);\r\n const beginRequest = resolveBeginRequest(defaultContext, usageContext);\r\n const wantsStream = isStreamingRequest(params);\r\n\r\n return usageTap.withUsage(beginRequest, (ctx) => {\r\n const finalParams = applyVendorHints\r\n ? applyResponsesVendorHints(params, ctx.begin.data.vendorHints)\r\n : params;\r\n const request = attachCorrelationHeader(requestOptions, ctx.begin.correlationId) as ResponsesCreateOptions;\r\n\r\n if (wantsStream) {\r\n const apiPromise = originalCreate(finalParams, request);\r\n const wrappedPromise = transformApiPromise(apiPromise, (rawStream) => {\r\n ensureAsyncIterable(rawStream, \"responses.create\");\r\n const wrappedStream = wrapStreamForUsageTap(rawStream, async () => {\r\n const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints);\r\n if (usage) {\r\n ctx.setUsage(usage);\r\n }\r\n }, ctx);\r\n return wrappedStream;\r\n });\r\n\r\n return wrappedPromise as unknown as ResponsesCreateReturn;\r\n }\r\n\r\n const apiPromise = originalCreate(finalParams, request);\r\n const wrappedPromise = transformApiPromise(apiPromise, (response) => {\r\n tryInferUsage(response, ctx.begin.data.vendorHints, undefined, ctx);\r\n return response;\r\n });\r\n return wrappedPromise as unknown as ResponsesCreateReturn;\r\n }, withUsage) as ResponsesCreateReturn;\r\n };\r\n\r\n const handler: ProxyHandler<object> = {\r\n get(target, prop, receiver) {\r\n if (prop === \"create\") {\r\n return wrappedCreate;\r\n }\r\n return Reflect.get(target, prop, receiver) as unknown;\r\n },\r\n };\r\n\r\n return new Proxy(resource as object, handler) as WrappedResponses;\r\n}\r\n\r\nfunction createChatCompletionsProxy(\r\n resource: ChatCompletionsResource,\r\n usageTap: UsageTapClient,\r\n defaultContext: Partial<WrapOpenAIContext> | undefined,\r\n applyVendorHints: boolean,\r\n): WrappedChatCompletions {\r\n const originalCreate = resource.create.bind(resource);\r\n const streamCandidate = (resource as { stream?: unknown }).stream;\r\n const originalStream = typeof streamCandidate === \"function\"\r\n ? (streamCandidate as (...args: unknown[]) => unknown).bind(resource)\r\n : undefined;\r\n\r\n const wrappedCreate = (\r\n params: ChatCompletionCreateParams,\r\n options?: WrapOpenAICallOptions,\r\n ): ChatCompletionCreateReturn => {\r\n const { requestOptions, usageContext, withUsage } = splitUsageOptions(options);\r\n const beginRequest = resolveBeginRequest(defaultContext, usageContext);\r\n const wantsStream = isStreamingRequest(params);\r\n\r\n return usageTap.withUsage(beginRequest, (ctx) => {\r\n const finalParams = applyVendorHints\r\n ? applyChatVendorHints(params, ctx.begin.data.vendorHints)\r\n : params;\r\n const request = attachCorrelationHeader(requestOptions, ctx.begin.correlationId) as ChatCompletionCreateOptions;\r\n\r\n if (wantsStream) {\r\n const apiPromise = originalCreate(finalParams, request);\r\n const wrappedPromise = transformApiPromise(apiPromise, (rawStream) => {\r\n ensureAsyncIterable(rawStream, \"chat.completions.create\");\r\n const wrappedStream = wrapStreamForUsageTap(rawStream, async () => {\r\n const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints);\r\n if (usage) {\r\n ctx.setUsage(usage);\r\n }\r\n }, ctx);\r\n return wrappedStream;\r\n });\r\n\r\n return wrappedPromise as unknown as ChatCompletionCreateReturn;\r\n }\r\n\r\n const apiPromise = originalCreate(finalParams, request);\r\n const wrappedPromise = transformApiPromise(apiPromise, (response) => {\r\n tryInferUsage(response, ctx.begin.data.vendorHints, undefined, ctx);\r\n return response;\r\n });\r\n return wrappedPromise as unknown as ChatCompletionCreateReturn;\r\n }, withUsage) as ChatCompletionCreateReturn;\r\n };\r\n\r\n const wrappedStream = originalStream\r\n ? (\r\n params: ChatCompletionCreateParams,\r\n options?: WrapOpenAICallOptions,\r\n ): ChatCompletionCreateReturn => {\r\n const { requestOptions, usageContext, withUsage } = splitUsageOptions(options);\r\n const beginRequest = resolveBeginRequest(defaultContext, usageContext);\r\n\r\n return usageTap.withUsage(beginRequest, (ctx) => {\r\n const finalParams = applyVendorHints\r\n ? applyChatVendorHints(params, ctx.begin.data.vendorHints)\r\n : params;\r\n const request = attachCorrelationHeader(requestOptions, ctx.begin.correlationId) as ChatCompletionCreateOptions;\r\n\r\n const apiPromise = originalStream(finalParams, request);\r\n const wrappedPromise = transformApiPromise(apiPromise, (rawStream) => {\r\n ensureAsyncIterable(rawStream, \"chat.completions.stream\");\r\n const wrappedStreamInner = wrapStreamForUsageTap(rawStream, async () => {\r\n const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints);\r\n if (usage) {\r\n ctx.setUsage(usage);\r\n }\r\n }, ctx);\r\n return wrappedStreamInner;\r\n });\r\n\r\n return wrappedPromise as unknown as ChatCompletionCreateReturn;\r\n }, withUsage) as ChatCompletionCreateReturn;\r\n }\r\n : undefined;\r\n\r\n const handler: ProxyHandler<ChatCompletionsResource> = {\r\n get(target, prop, receiver) {\r\n if (prop === \"create\") {\r\n return wrappedCreate;\r\n }\r\n\r\n if (prop === \"stream\" && wrappedStream) {\r\n return wrappedStream;\r\n }\r\n\r\n return Reflect.get(target as object, prop, receiver) as unknown;\r\n },\r\n };\r\n\r\n return new Proxy(resource, handler) as unknown as WrappedChatCompletions;\r\n}\r\n\r\ninterface SplitUsageOptionsResult {\r\n requestOptions?: Record<string, unknown>;\r\n usageContext?: Partial<WrapOpenAIContext>;\r\n withUsage?: WithUsageOptions;\r\n}\r\n\r\nfunction splitUsageOptions(options: unknown): SplitUsageOptionsResult {\r\n if (!options || typeof options !== \"object\") {\r\n return {};\r\n }\r\n\r\n const { usageTap, withUsage, ...rest } = options as {\r\n usageTap?: Partial<WrapOpenAIContext>;\r\n withUsage?: WithUsageOptions;\r\n } & Record<string, unknown>;\r\n\r\n const requestOptions = Object.keys(rest).length ? cloneRequestOptions(rest) : undefined;\r\n\r\n return {\r\n requestOptions,\r\n usageContext: usageTap,\r\n withUsage,\r\n } satisfies SplitUsageOptionsResult;\r\n}\r\n\r\nfunction resolveBeginRequest(\r\n defaults: Partial<WrapOpenAIContext> | undefined,\r\n override: Partial<WrapOpenAIContext> | undefined,\r\n): BeginCallRequest {\r\n const base = defaults ?? {};\r\n const current = override ?? {};\r\n const customerId = current.customerId ?? base.customerId;\r\n\r\n if (!customerId) {\r\n throw new UsageTapError(\r\n \"USAGETAP_BAD_REQUEST\",\r\n \"wrapOpenAI requires usageTap.customerId (provide defaultContext or options.usageTap)\",\r\n );\r\n }\r\n\r\n const tags = mergeTags(base.tags, current.tags);\r\n const begin: BeginCallRequest = { customerId } satisfies BeginCallRequest;\r\n\r\n const requested = current.requested ?? base.requested;\r\n if (requested) begin.requested = requested;\r\n\r\n const feature = current.feature ?? base.feature;\r\n if (feature) begin.feature = feature;\r\n\r\n const idempotency = current.idempotency ?? base.idempotency;\r\n if (idempotency) begin.idempotency = idempotency;\r\n\r\n const customerName = current.customerName ?? base.customerName;\r\n if (customerName) begin.customerName = customerName;\r\n\r\n const customerEmail = current.customerEmail ?? base.customerEmail;\r\n if (customerEmail) begin.customerEmail = customerEmail;\r\n\r\n if (tags?.length) {\r\n begin.tags = tags;\r\n }\r\n\r\n return begin;\r\n}\r\n\r\ntype PromiseLikeOrValue<T> = PromiseLike<T> | T;\r\n\r\nfunction transformApiPromise<TValue, TResult>(\r\n apiPromise: PromiseLikeOrValue<TValue>,\r\n onResolve: (value: TValue) => PromiseLike<TResult> | TResult,\r\n): Promise<TResult> {\r\n const resolvedPromise = Promise.resolve(apiPromise).then(onResolve);\r\n\r\n if (isObjectRecord(apiPromise)) {\r\n const proto = Object.getPrototypeOf(apiPromise) as object | null;\r\n if (proto) {\r\n Object.setPrototypeOf(resolvedPromise, proto);\r\n }\r\n\r\n for (const key of Reflect.ownKeys(apiPromise)) {\r\n if (key === \"then\" || key === \"catch\" || key === \"finally\") {\r\n continue;\r\n }\r\n\r\n try {\r\n const descriptor = Object.getOwnPropertyDescriptor(apiPromise, key);\r\n if (descriptor) {\r\n Reflect.defineProperty(resolvedPromise, key, descriptor);\r\n }\r\n } catch {\r\n /* ignore non-configurable properties */\r\n }\r\n }\r\n }\r\n\r\n return resolvedPromise;\r\n}\r\n\r\nfunction isObjectRecord(value: unknown): value is Record<PropertyKey, unknown> {\r\n return typeof value === \"object\" && value !== null;\r\n}\r\n\r\nfunction cloneRecord(value: unknown): Record<string, unknown> {\r\n return isObjectRecord(value) ? { ...(value as Record<string, unknown>) } : {};\r\n}\r\n\r\nfunction isStringTuple(value: unknown): value is [string, string] {\r\n return Array.isArray(value) && value.length >= 2 && typeof value[0] === \"string\" && typeof value[1] === \"string\";\r\n}\r\n\r\nfunction cloneRequestOptions(source: Record<string, unknown>): Record<string, unknown> {\r\n const clone: Record<string, unknown> = { ...source };\r\n\r\n if (\"headers\" in clone) {\r\n clone.headers = normalizeHeaders((clone as { headers?: unknown }).headers);\r\n }\r\n\r\n return clone;\r\n}\r\n\r\nfunction attachCorrelationHeader(\r\n options: Record<string, unknown> | undefined,\r\n correlationId: string,\r\n): Record<string, unknown> | undefined {\r\n const normalized = normalizeHeaders(options?.headers);\r\n\r\n if (correlationId && !normalized[USAGETAP_CORRELATION_HEADER]) {\r\n normalized[USAGETAP_CORRELATION_HEADER] = correlationId;\r\n }\r\n\r\n if (!options) {\r\n return Object.keys(normalized).length\r\n ? ({ headers: normalized } satisfies Record<string, unknown>)\r\n : undefined;\r\n }\r\n\r\n const next = { ...options } satisfies Record<string, unknown>;\r\n if (Object.keys(normalized).length) {\r\n next.headers = normalized;\r\n }\r\n return next;\r\n}\r\n\r\nfunction normalizeHeaders(headers: unknown): Record<string, string> {\r\n if (!headers) {\r\n return {};\r\n }\r\n\r\n if (headers instanceof Headers) {\r\n const result: Record<string, string> = {};\r\n headers.forEach((value, key) => {\r\n result[key.toLowerCase()] = value;\r\n });\r\n return result;\r\n }\r\n\r\n if (Array.isArray(headers)) {\r\n const result: Record<string, string> = {};\r\n for (const entry of headers) {\r\n if (!isStringTuple(entry)) {\r\n continue;\r\n }\r\n const [key, value] = entry;\r\n result[key.toLowerCase()] = value;\r\n }\r\n return result;\r\n }\r\n\r\n if (isObjectRecord(headers)) {\r\n const result: Record<string, string> = {};\r\n const record = headers as Record<string, unknown>;\r\n for (const key of Object.keys(record)) {\r\n const value = record[key];\r\n if (value !== undefined && value !== null) {\r\n result[key.toLowerCase()] = String(value);\r\n }\r\n }\r\n return result;\r\n }\r\n\r\n return {};\r\n}\r\n\r\nfunction mergeTags(a?: string[], b?: string[]): string[] | undefined {\r\n const values = [...(a ?? []), ...(b ?? [])]\r\n .map((value) => (typeof value === \"string\" ? value.trim() : \"\"))\r\n .filter(Boolean);\r\n\r\n if (!values.length) {\r\n return undefined;\r\n }\r\n\r\n return dedupeStrings(values);\r\n}\r\n\r\nfunction dedupeStrings(values: string[]): string[] {\r\n return Array.from(new Set(values));\r\n}\r\n\r\nfunction isStreamingRequest(params: unknown): boolean {\r\n if (!params || typeof params !== \"object\") {\r\n return false;\r\n }\r\n\r\n const stream = (params as { stream?: unknown }).stream;\r\n if (typeof stream === \"boolean\") {\r\n return stream;\r\n }\r\n\r\n return stream != null;\r\n}\r\n\r\nfunction applyChatVendorHints(\r\n params: ChatCompletionCreateParams,\r\n hints: VendorHints | undefined,\r\n): ChatCompletionCreateParams {\r\n if (!hints) {\r\n return params;\r\n }\r\n\r\n const next = cloneRecord(params);\r\n\r\n if (hints.preferredModel && (next.model === undefined || next.model === null)) {\r\n next.model = hints.preferredModel;\r\n }\r\n\r\n if (typeof hints.maxResponseTokens === \"number\" && next.max_tokens == null) {\r\n next.max_tokens = hints.maxResponseTokens;\r\n }\r\n\r\n if (typeof hints.maxInputTokens === \"number\" && (next as { max_input_tokens?: unknown }).max_input_tokens == null) {\r\n (next as { max_input_tokens?: number }).max_input_tokens = hints.maxInputTokens;\r\n }\r\n\r\n return next as unknown as ChatCompletionCreateParams;\r\n}\r\n\r\nfunction applyResponsesVendorHints(\r\n params: ResponsesCreateParams,\r\n hints: VendorHints | undefined,\r\n): ResponsesCreateParams {\r\n if (!hints) {\r\n return params;\r\n }\r\n\r\n const next = cloneRecord(params);\r\n\r\n if (hints.preferredModel && (next.model === undefined || next.model === null)) {\r\n next.model = hints.preferredModel;\r\n }\r\n\r\n if (typeof hints.maxResponseTokens === \"number\" && (next as { max_output_tokens?: unknown }).max_output_tokens == null) {\r\n (next as { max_output_tokens?: number }).max_output_tokens = hints.maxResponseTokens;\r\n }\r\n\r\n return next as unknown as ResponsesCreateParams;\r\n}\r\n\r\nasync function extractUsageFromStream(\r\n stream: unknown,\r\n hints: VendorHints | undefined,\r\n): Promise<Partial<Omit<EndCallRequest, \"callId\" | \"error\">> | undefined> {\r\n const finalPayload = await resolveStreamFinalPayload(stream);\r\n if (!finalPayload) {\r\n return undefined;\r\n }\r\n\r\n return inferUsageFromResponse(finalPayload, hints);\r\n}\r\n\r\nasync function resolveStreamFinalPayload(stream: unknown): Promise<unknown> {\r\n if (!stream || typeof stream !== \"object\") {\r\n return undefined;\r\n }\r\n\r\n const candidate = stream as {\r\n finalChatCompletion?: () => Promise<unknown>;\r\n finalCompletion?: () => Promise<unknown>;\r\n finalResponse?: () => Promise<unknown>;\r\n finalContent?: () => Promise<unknown>;\r\n };\r\n\r\n if (typeof candidate.finalChatCompletion === \"function\") {\r\n return candidate.finalChatCompletion();\r\n }\r\n\r\n if (typeof candidate.finalResponse === \"function\") {\r\n return candidate.finalResponse();\r\n }\r\n\r\n if (typeof candidate.finalCompletion === \"function\") {\r\n return candidate.finalCompletion();\r\n }\r\n\r\n if (typeof candidate.finalContent === \"function\") {\r\n return candidate.finalContent();\r\n }\r\n\r\n return undefined;\r\n}\r\n\r\nfunction ensureAsyncIterable(value: unknown, label: string): asserts value is AsyncIterable<unknown> {\r\n if (!value || typeof value !== \"object\" || typeof (value as AsyncIterable<unknown>)[Symbol.asyncIterator] !== \"function\") {\r\n throw new UsageTapError(\r\n \"USAGETAP_BAD_REQUEST\",\r\n `${label} expected an async iterable stream but received ${typeof value}`,\r\n );\r\n }\r\n}\r\n\r\nfunction chunkToText(chunk: unknown): string {\r\n if (chunk === undefined || chunk === null) {\r\n return \"\";\r\n }\r\n\r\n if (typeof chunk === \"string\") {\r\n return chunk;\r\n }\r\n\r\n if (typeof chunk === \"object\") {\r\n const candidate = chunk as {\r\n choices?: Array<{\r\n delta?: {\r\n content?: string | Array<{ text?: string }>;\r\n };\r\n }>;\r\n content?: string;\r\n };\r\n\r\n const delta = candidate.choices?.[0]?.delta;\r\n const content = delta?.content ?? candidate.content;\r\n\r\n if (typeof content === \"string\") {\r\n return content;\r\n }\r\n\r\n if (Array.isArray(content)) {\r\n return content\r\n .map((entry) => {\r\n if (!entry) return \"\";\r\n if (typeof entry === \"string\") return entry;\r\n if (typeof entry.text === \"string\") return entry.text;\r\n return \"\";\r\n })\r\n .join(\"\");\r\n }\r\n }\r\n\r\n return String(chunk);\r\n}\r\n\r\nfunction formatSsePayload(\r\n text: string,\r\n options: StreamToResponseOptions[\"sse\"],\r\n): string {\r\n if (!text) {\r\n return \"\";\r\n }\r\n\r\n const lines = text.split(/\\r?\\n/);\r\n const eventLine = options?.event ? `event: ${options.event}\\n` : \"\";\r\n const retryLine = options?.retry ? `retry: ${options.retry}\\n` : \"\";\r\n const dataLines = lines.map((line) => `data: ${line}`).join(\"\\n\");\r\n return `${eventLine}${retryLine}${dataLines}\\n\\n`;\r\n}\r\n\r\nfunction setHeaderIfPossible(res: NodeResponseLike, key: string, value: string): void {\r\n if (typeof res.setHeader === \"function\" && res.headersSent !== true) {\r\n res.setHeader(key, value);\r\n }\r\n}\r\n\r\nfunction tryInferUsage<TResponse>(\r\n response: TResponse,\r\n hints: VendorHints | undefined,\r\n extractor: OpenAIInvokeParams<TResponse>[\"extractUsage\"],\r\n ctx: WithUsageContext,\r\n): void {\r\n const explicit = extractor?.(response);\r\n const inferred = explicit ?? inferUsageFromResponse(response, hints);\r\n\r\n if (inferred) {\r\n ctx.setUsage(inferred);\r\n }\r\n}\r\n\r\nfunction inferUsageFromResponse(\r\n response: unknown,\r\n hints: VendorHints | undefined,\r\n): Partial<Omit<EndCallRequest, \"callId\" | \"error\">> | undefined {\r\n if (!response || typeof response !== \"object\") {\r\n return undefined;\r\n }\r\n\r\n const candidate = response as {\r\n usage?: {\r\n prompt_tokens?: number;\r\n completion_tokens?: number;\r\n total_tokens?: number;\r\n cached_tokens?: number;\r\n };\r\n model?: string;\r\n };\r\n\r\n if (!candidate.usage) {\r\n return undefined;\r\n }\r\n\r\n return {\r\n modelUsed: candidate.model ?? hints?.preferredModel,\r\n inputTokens: candidate.usage.prompt_tokens,\r\n responseTokens: candidate.usage.completion_tokens,\r\n cachedTokens: candidate.usage.cached_tokens,\r\n } satisfies Partial<Omit<EndCallRequest, \"callId\" | \"error\">>;\r\n}\r\n\r\nfunction wrapStreamForUsageTap<TStream>(\r\n source: UsageTapStream<TStream> | AsyncIterable<TStream>,\r\n finalize: () => Promise<void> | void,\r\n ctx: WithUsageContext,\r\n): AsyncIterable<TStream> & { __usageTapFinalize: () => Promise<void> } {\r\n const getIterator = source[Symbol.asyncIterator];\r\n if (typeof getIterator !== \"function\") {\r\n throw new TypeError(\"Stream is not async iterable\");\r\n }\r\n\r\n const iterator = getIterator.call(source) as AsyncIterator<TStream>;\r\n let completed = false;\r\n\r\n const invokeFinalize = async (): Promise<void> => {\r\n if (completed) return;\r\n completed = true;\r\n try {\r\n await finalize();\r\n } catch (error) {\r\n ctx.setError({\r\n code: \"USAGE_FINALIZE_ERROR\",\r\n message: error instanceof Error ? error.message : String(error),\r\n });\r\n throw error;\r\n }\r\n };\r\n\r\n const prototype = (Object.getPrototypeOf(source as object) as object | null) ?? Object.prototype;\r\n const wrapped = Object.create(prototype) as unknown as UsageTapIterableIterator<TStream>;\r\n\r\n for (const key of Reflect.ownKeys(source as object)) {\r\n try {\r\n const descriptor = Object.getOwnPropertyDescriptor(source as object, key);\r\n if (descriptor) {\r\n Object.defineProperty(wrapped, key, descriptor);\r\n }\r\n } catch {\r\n /* ignore non-configurable properties */\r\n }\r\n }\r\n\r\n Object.defineProperty(wrapped, Symbol.asyncIterator, {\r\n value(): UsageTapIterableIterator<TStream> {\r\n return this as UsageTapIterableIterator<TStream>;\r\n },\r\n configurable: true,\r\n });\r\n\r\n Object.defineProperty(wrapped, \"next\", {\r\n value: async (...args: Parameters<AsyncIterator<TStream>[\"next\"]>): Promise<IteratorResult<TStream>> => {\r\n try {\r\n const result = await iterator.next(...args);\r\n if (result.done) {\r\n await invokeFinalize();\r\n }\r\n return result;\r\n } catch (error) {\r\n await invokeFinalize().catch(() => undefined);\r\n throw error;\r\n }\r\n },\r\n configurable: true,\r\n writable: true,\r\n });\r\n\r\n Object.defineProperty(wrapped, \"return\", {\r\n value: async (value?: TStream): Promise<IteratorResult<TStream>> => {\r\n if (typeof iterator.return === \"function\") {\r\n const rawResult: unknown = await iterator.return(value);\r\n if (!isIteratorResult<TStream>(rawResult)) {\r\n throw new TypeError(\"Iterator.return() returned an invalid result\");\r\n }\r\n await invokeFinalize();\r\n return rawResult;\r\n }\r\n await invokeFinalize();\r\n return { done: true, value } as IteratorResult<TStream>;\r\n },\r\n configurable: true,\r\n writable: true,\r\n });\r\n\r\n Object.defineProperty(wrapped, \"throw\", {\r\n value: async (error?: unknown): Promise<IteratorResult<TStream>> => {\r\n if (typeof iterator.throw === \"function\") {\r\n const rawResult: unknown = await iterator.throw(error);\r\n if (!isIteratorResult<TStream>(rawResult)) {\r\n throw new TypeError(\"Iterator.throw() returned an invalid result\");\r\n }\r\n await invokeFinalize();\r\n return rawResult;\r\n }\r\n await invokeFinalize();\r\n throw error;\r\n },\r\n configurable: true,\r\n writable: true,\r\n });\r\n\r\n Object.defineProperty(wrapped, \"__usageTapFinalize\", {\r\n value: async (): Promise<void> => {\r\n await invokeFinalize();\r\n },\r\n configurable: true,\r\n });\r\n\r\n return wrapped;\r\n}\r\n\r\nfunction isIteratorResult<T>(value: unknown): value is IteratorResult<T> {\r\n return isObjectRecord(value) && \"done\" in value;\r\n}\r\n","import type OpenAI from \"openai\";\r\nimport { UsageTapClient } from \"../client\";\r\nimport type { OpenAIAdapter } from \"./openai\";\r\nimport { createOpenAIAdapter } from \"./openai\";\r\n\r\nexport interface OpenRouterAdapterInit {\r\n client: OpenAI;\r\n usageTap: UsageTapClient;\r\n}\r\n\r\nexport function createOpenRouterAdapter(init: OpenRouterAdapterInit): OpenAIAdapter {\r\n return createOpenAIAdapter(init);\r\n}\r\n"]}
1
+ {"version":3,"sources":["../../src/adapters/openai.ts","../../src/adapters/openrouter.ts"],"names":[],"mappings":"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","file":"openrouter.mjs","sourcesContent":["import type OpenAI from \"openai\";\r\nimport { UsageTapClient } from \"../client\";\r\nimport { UsageTapError } from \"../errors\";\r\nimport type {\r\n BeginCallRequest,\r\n BeginCallResponseBody,\r\n EndCallRequest,\r\n UsageTapSuccessResponse,\r\n VendorHints,\r\n WithUsageContext,\r\n WithUsageOptions,\r\n} from \"../types\";\r\n\r\nexport interface OpenAIAdapterInit {\r\n client: OpenAI;\r\n usageTap: UsageTapClient;\r\n}\r\n\r\nexport interface OpenAIRequestContext {\r\n hints?: VendorHints;\r\n begin: UsageTapSuccessResponse<BeginCallResponseBody>;\r\n}\r\n\r\nexport interface OpenAIInvokeParams<TResponse> {\r\n begin: BeginCallRequest;\r\n call: (client: OpenAI, ctx: OpenAIRequestContext) => Promise<TResponse>;\r\n extractUsage?: (response: TResponse) => Partial<Omit<EndCallRequest, \"callId\" | \"error\">> | void;\r\n withUsageOptions?: WithUsageOptions;\r\n}\r\n\r\nexport interface OpenAIInvokeResult<TResponse> {\r\n data: TResponse;\r\n begin: UsageTapSuccessResponse<BeginCallResponseBody>;\r\n}\r\n\r\nexport interface OpenAIStreamCallResult<TStream> {\r\n stream: AsyncIterable<TStream>;\r\n onComplete?: () => Promise<Partial<Omit<EndCallRequest, \"callId\" | \"error\">> | void> | Partial<Omit<EndCallRequest, \"callId\" | \"error\">> | void;\r\n}\r\n\r\nexport interface OpenAIStreamParams<TStream> {\r\n begin: BeginCallRequest;\r\n call: (client: OpenAI, ctx: OpenAIRequestContext) => Promise<OpenAIStreamCallResult<TStream>>;\r\n withUsageOptions?: WithUsageOptions;\r\n}\r\n\r\nexport interface OpenAIStreamResult<TStream> {\r\n stream: AsyncIterable<TStream> & { __usageTapFinalize?: () => Promise<void> };\r\n begin: UsageTapSuccessResponse<BeginCallResponseBody>;\r\n finalize: () => Promise<void>;\r\n}\r\n\r\nexport interface OpenAIAdapter {\r\n invoke<TResponse>(params: OpenAIInvokeParams<TResponse>): Promise<OpenAIInvokeResult<TResponse>>;\r\n invokeStream<TStream>(params: OpenAIStreamParams<TStream>): Promise<OpenAIStreamResult<TStream>>;\r\n}\r\n\r\ntype ReplaceProperty<T, K extends keyof T, V> = Omit<T, K> & Record<K, V>;\r\n\r\nexport type WrapOpenAIContext = BeginCallRequest;\r\n\r\nexport interface WrapOpenAIOptions {\r\n defaultContext?: Partial<WrapOpenAIContext>;\r\n applyVendorHints?: boolean;\r\n}\r\n\r\ntype ChatCompletionsResource = OpenAI[\"chat\"][\"completions\"];\r\ntype ChatCompletionCreate = ChatCompletionsResource[\"create\"];\r\ntype ChatCompletionCreateParams = Parameters<ChatCompletionCreate>[0];\r\ntype ChatCompletionCreateOptions = Parameters<ChatCompletionCreate>[1];\r\ntype ChatCompletionCreateReturn = ReturnType<ChatCompletionCreate>;\r\n\r\nexport type WrapOpenAICallOptions = (ChatCompletionCreateOptions extends undefined\r\n ? { usageTap?: Partial<WrapOpenAIContext>; withUsage?: WithUsageOptions }\r\n : ChatCompletionCreateOptions & { usageTap?: Partial<WrapOpenAIContext>; withUsage?: WithUsageOptions });\r\n\r\ninterface WrappedChatCompletions extends Omit<ChatCompletionsResource, \"create\"> {\r\n create: (\r\n params: ChatCompletionCreateParams,\r\n options?: WrapOpenAICallOptions,\r\n ) => ChatCompletionCreateReturn;\r\n}\r\n\r\ntype ResponsesResource = OpenAI extends { responses: infer R } ? R : never;\r\ntype ResponsesCreate = ResponsesResource extends { create: infer T } ? T : never;\r\ntype ResponsesCreateParams = ResponsesCreate extends (...args: infer P) => unknown ? P[0] : never;\r\ntype ResponsesCreateOptions = ResponsesCreate extends (...args: infer P) => unknown ? P[1] : never;\r\ntype ResponsesCreateReturn = ResponsesCreate extends (...args: unknown[]) => infer R ? R : never;\r\n\r\nexport type WrapOpenAIResponseCallOptions = (ResponsesCreateOptions extends undefined\r\n ? { usageTap?: Partial<WrapOpenAIContext>; withUsage?: WithUsageOptions }\r\n : ResponsesCreateOptions & { usageTap?: Partial<WrapOpenAIContext>; withUsage?: WithUsageOptions });\r\n\r\ntype WrappedResponses = ResponsesResource extends undefined\r\n ? undefined\r\n : Omit<NonNullable<ResponsesResource>, \"create\"> & {\r\n create: (\r\n params: ResponsesCreateParams,\r\n options?: WrapOpenAIResponseCallOptions,\r\n ) => ResponsesCreateReturn;\r\n };\r\n\r\nexport type WrappedOpenAI = OpenAI & {\r\n chat: ReplaceProperty<OpenAI[\"chat\"], \"completions\", WrappedChatCompletions>;\r\n} & (ResponsesResource extends undefined\r\n ? { responses?: undefined }\r\n : { responses: WrappedResponses }) & {\r\n toNextResponse: typeof toNextResponse;\r\n pipeToResponse: typeof pipeToResponse;\r\n unwrap: () => OpenAI;\r\n };\r\n\r\nexport interface StreamOpenAIRouteOptions {\r\n getRequest: (req: Request) => Promise<{\r\n params: ChatCompletionCreateParams;\r\n usageTap?: Partial<WrapOpenAIContext>;\r\n withUsage?: WithUsageOptions;\r\n }>;\r\n wrapOptions?: WrapOpenAIOptions;\r\n defaultContext?: Partial<WrapOpenAIContext>;\r\n stream?: {\r\n mode?: StreamMode;\r\n headers?: Record<string, string>;\r\n responseInit?: ResponseInit;\r\n };\r\n}\r\n\r\nexport type StreamMode = \"text\" | \"sse\";\r\n\r\nexport interface StreamToResponseOptions {\r\n mode?: StreamMode;\r\n headers?: Record<string, string>;\r\n contentType?: string;\r\n sse?: {\r\n event?: string;\r\n retry?: number;\r\n };\r\n}\r\n\r\nexport function createOpenAIAdapter(init: OpenAIAdapterInit): OpenAIAdapter {\r\n const { client, usageTap } = init;\r\n\r\n return {\r\n async invoke<TResponse>(params: OpenAIInvokeParams<TResponse>): Promise<OpenAIInvokeResult<TResponse>> {\r\n const result = await usageTap.withUsage<OpenAIInvokeResult<TResponse>>(\r\n params.begin,\r\n async (ctx) => {\r\n const response = await params.call(client, {\r\n hints: ctx.begin.data.vendorHints,\r\n begin: ctx.begin,\r\n });\r\n\r\n tryInferUsage(response, ctx.begin.data.vendorHints, params.extractUsage, ctx);\r\n\r\n return {\r\n data: response,\r\n begin: ctx.begin,\r\n } satisfies OpenAIInvokeResult<TResponse>;\r\n },\r\n params.withUsageOptions,\r\n );\r\n\r\n return result;\r\n },\r\n\r\n async invokeStream<TStream>(params: OpenAIStreamParams<TStream>): Promise<OpenAIStreamResult<TStream>> {\r\n const result = await usageTap.withUsage<OpenAIStreamResult<TStream>>(\r\n params.begin,\r\n async (ctx) => {\r\n const { stream, onComplete } = await params.call(client, {\r\n hints: ctx.begin.data.vendorHints,\r\n begin: ctx.begin,\r\n });\r\n\r\n const wrapped = wrapStreamForUsageTap(stream, async () => {\r\n if (!onComplete) return;\r\n try {\r\n const maybeUsage = await onComplete();\r\n if (maybeUsage) {\r\n ctx.setUsage(maybeUsage);\r\n }\r\n } catch (error) {\r\n ctx.setError({\r\n code: \"USAGE_FINALIZE_ERROR\",\r\n message: error instanceof Error ? error.message : String(error),\r\n });\r\n throw error;\r\n }\r\n }, ctx);\r\n\r\n const finalize = async (): Promise<void> => {\r\n await wrapped.__usageTapFinalize?.();\r\n };\r\n\r\n return {\r\n stream: wrapped,\r\n begin: ctx.begin,\r\n finalize,\r\n } satisfies OpenAIStreamResult<TStream>;\r\n },\r\n params.withUsageOptions,\r\n );\r\n\r\n return result;\r\n },\r\n };\r\n}\r\n\r\nexport type UsageTapStream<T> = AsyncIterable<T> & { __usageTapFinalize?: () => Promise<void> };\r\ntype UsageTapIterableIterator<T> = AsyncIterator<T> & UsageTapStream<T> & {\r\n __usageTapFinalize: () => Promise<void>;\r\n};\r\n\r\nexport function toNextResponse<T>(\r\n stream: UsageTapStream<T>,\r\n options: StreamToResponseOptions = {},\r\n): Response {\r\n const mode = options.mode ?? \"text\";\r\n const headers = new Headers(options.headers ?? {});\r\n\r\n if (mode === \"sse\") {\r\n headers.set(\"content-type\", \"text/event-stream; charset=utf-8\");\r\n headers.set(\"cache-control\", \"no-cache, no-transform\");\r\n headers.set(\"connection\", \"keep-alive\");\r\n headers.set(\"x-accel-buffering\", \"no\");\r\n } else {\r\n headers.set(\"content-type\", options.contentType ?? \"text/plain; charset=utf-8\");\r\n }\r\n\r\n const encoder = new TextEncoder();\r\n let iterator: AsyncIterator<T> | undefined;\r\n\r\n const body = new ReadableStream<Uint8Array>({\r\n async start(controller: ReadableStreamDefaultController<Uint8Array>): Promise<void> {\r\n try {\r\n const getIterator = stream[Symbol.asyncIterator];\r\n if (typeof getIterator !== \"function\") {\r\n controller.close();\r\n return;\r\n }\r\n\r\n iterator = getIterator.call(stream);\r\n\r\n while (true) {\r\n const result = await iterator.next();\r\n if (result.done) {\r\n break;\r\n }\r\n\r\n const text = chunkToText(result.value);\r\n if (!text) {\r\n continue;\r\n }\r\n\r\n if (mode === \"sse\") {\r\n controller.enqueue(encoder.encode(formatSsePayload(text, options.sse)));\r\n } else {\r\n controller.enqueue(encoder.encode(text));\r\n }\r\n }\r\n controller.close();\r\n } catch (error) {\r\n controller.error(error);\r\n } finally {\r\n await stream.__usageTapFinalize?.();\r\n }\r\n },\r\n async cancel(): Promise<void> {\r\n if (!iterator) {\r\n const getIterator = stream[Symbol.asyncIterator];\r\n if (typeof getIterator === \"function\") {\r\n iterator = getIterator.call(stream);\r\n }\r\n }\r\n\r\n if (iterator && typeof iterator.return === \"function\") {\r\n await iterator.return();\r\n }\r\n await stream.__usageTapFinalize?.();\r\n },\r\n });\r\n\r\n return new Response(body, { headers });\r\n}\r\n\r\nexport async function pipeToResponse<T>(\r\n stream: UsageTapStream<T>,\r\n res: NodeResponseLike,\r\n options: StreamToResponseOptions = {},\r\n): Promise<void> {\r\n const mode = options.mode ?? \"text\";\r\n\r\n if (mode === \"sse\") {\r\n setHeaderIfPossible(res, \"Content-Type\", \"text/event-stream; charset=utf-8\");\r\n setHeaderIfPossible(res, \"Cache-Control\", \"no-cache, no-transform\");\r\n setHeaderIfPossible(res, \"Connection\", \"keep-alive\");\r\n setHeaderIfPossible(res, \"X-Accel-Buffering\", \"no\");\r\n } else {\r\n setHeaderIfPossible(res, \"Content-Type\", options.contentType ?? \"text/plain; charset=utf-8\");\r\n }\r\n\r\n const encoder = new TextEncoder();\r\n const iterator = stream[Symbol.asyncIterator]();\r\n\r\n try {\r\n while (true) {\r\n const result = await iterator.next();\r\n if (result.done) {\r\n break;\r\n }\r\n const text = chunkToText(result.value);\r\n if (!text) {\r\n continue;\r\n }\r\n\r\n const payload = mode === \"sse\" ? formatSsePayload(text, options.sse) : text;\r\n res.write(Buffer.from(encoder.encode(payload)));\r\n res.flush?.();\r\n }\r\n } finally {\r\n res.end();\r\n await stream.__usageTapFinalize?.();\r\n }\r\n}\r\n\r\nconst USAGETAP_CORRELATION_HEADER = \"x-usage-correlation-id\";\r\n\r\nexport function wrapOpenAI(\r\n client: OpenAI,\r\n usageTap: UsageTapClient,\r\n options: WrapOpenAIOptions = {},\r\n): WrappedOpenAI {\r\n if (!client) {\r\n throw new UsageTapError(\"USAGETAP_BAD_REQUEST\", \"wrapOpenAI requires an OpenAI client instance\");\r\n }\r\n\r\n const defaultContext = options.defaultContext;\r\n const applyVendorHints = options.applyVendorHints !== false;\r\n\r\n const proxiedChat = client.chat\r\n ? createChatProxy(client.chat, usageTap, defaultContext, applyVendorHints)\r\n : undefined;\r\n\r\n const proxiedResponses = typeof client.responses !== \"undefined\"\r\n ? createResponsesProxy(client.responses, usageTap, defaultContext, applyVendorHints)\r\n : undefined;\r\n\r\n const handler: ProxyHandler<OpenAI> = {\r\n get(target, prop, receiver) {\r\n if (prop === \"chat\" && proxiedChat) {\r\n return proxiedChat;\r\n }\r\n\r\n if (prop === \"responses\" && typeof target.responses !== \"undefined\") {\r\n return proxiedResponses ?? (Reflect.get(target as object, prop, receiver) as unknown);\r\n }\r\n\r\n if (prop === \"toNextResponse\") {\r\n return toNextResponse;\r\n }\r\n\r\n if (prop === \"pipeToResponse\") {\r\n return pipeToResponse;\r\n }\r\n\r\n if (prop === \"unwrap\") {\r\n return () => target;\r\n }\r\n\r\n return Reflect.get(target as object, prop, receiver) as unknown;\r\n },\r\n };\r\n\r\n return new Proxy(client, handler) as WrappedOpenAI;\r\n}\r\n\r\nexport function streamOpenAIRoute(\r\n usageTap: UsageTapClient,\r\n openai: OpenAI,\r\n options: StreamOpenAIRouteOptions,\r\n): (req: Request) => Promise<Response> {\r\n if (!options?.getRequest) {\r\n throw new UsageTapError(\"USAGETAP_BAD_REQUEST\", \"streamOpenAIRoute requires a getRequest function\");\r\n }\r\n\r\n const wrapConfig: WrapOpenAIOptions | undefined = options.wrapOptions || options.defaultContext\r\n ? {\r\n ...(options.wrapOptions ?? {}),\r\n defaultContext: options.defaultContext ?? options.wrapOptions?.defaultContext,\r\n }\r\n : undefined;\r\n\r\n const wrappedClient = wrapConfig\r\n ? wrapOpenAI(openai, usageTap, wrapConfig)\r\n : wrapOpenAI(openai, usageTap);\r\n\r\n return async (req: Request): Promise<Response> => {\r\n const requestConfig = await options.getRequest(req);\r\n const mergedParams: ChatCompletionCreateParams = {\r\n ...requestConfig.params,\r\n stream: true,\r\n };\r\n\r\n const callOptions: Partial<WrapOpenAICallOptions> = {};\r\n if (requestConfig.usageTap) {\r\n callOptions.usageTap = requestConfig.usageTap;\r\n }\r\n if (requestConfig.withUsage) {\r\n callOptions.withUsage = requestConfig.withUsage;\r\n }\r\n\r\n const stream = await wrappedClient.chat.completions.create(\r\n mergedParams,\r\n Object.keys(callOptions).length ? (callOptions as WrapOpenAICallOptions) : undefined,\r\n );\r\n\r\n const baseResponse = toNextResponse(stream as UsageTapStream<unknown>, {\r\n mode: options.stream?.mode ?? \"sse\",\r\n headers: options.stream?.headers,\r\n });\r\n\r\n const init = options.stream?.responseInit;\r\n if (!init) {\r\n return baseResponse;\r\n }\r\n\r\n const mergedHeaders = new Headers(baseResponse.headers);\r\n if (init.headers) {\r\n const extra = normalizeHeaders(init.headers);\r\n for (const [key, value] of Object.entries(extra)) {\r\n mergedHeaders.set(key, value);\r\n }\r\n }\r\n\r\n return new Response(baseResponse.body, {\r\n status: init.status ?? baseResponse.status,\r\n statusText: init.statusText ?? baseResponse.statusText,\r\n headers: mergedHeaders,\r\n });\r\n };\r\n}\r\n\r\nexport interface NodeResponseLike {\r\n write(chunk: string | Uint8Array | Buffer): unknown;\r\n end(chunk?: string | Uint8Array | Buffer): unknown;\r\n setHeader?(name: string, value: string): void;\r\n headersSent?: boolean;\r\n statusCode?: number;\r\n status?(code: number): void;\r\n flush?(): void;\r\n}\r\n\r\nfunction createChatProxy(\r\n resource: OpenAI[\"chat\"],\r\n usageTap: UsageTapClient,\r\n defaultContext: Partial<WrapOpenAIContext> | undefined,\r\n applyVendorHints: boolean,\r\n): ReplaceProperty<OpenAI[\"chat\"], \"completions\", WrappedChatCompletions> {\r\n const completions = createChatCompletionsProxy(\r\n resource.completions,\r\n usageTap,\r\n defaultContext,\r\n applyVendorHints,\r\n );\r\n\r\n const handler: ProxyHandler<OpenAI[\"chat\"]> = {\r\n get(target, prop, receiver) {\r\n if (prop === \"completions\") {\r\n return completions;\r\n }\r\n return Reflect.get(target as object, prop, receiver) as unknown;\r\n },\r\n };\r\n\r\n return new Proxy(resource, handler) as ReplaceProperty<OpenAI[\"chat\"], \"completions\", WrappedChatCompletions>;\r\n}\r\n\r\nfunction createResponsesProxy(\r\n resource: ResponsesResource,\r\n usageTap: UsageTapClient,\r\n defaultContext: Partial<WrapOpenAIContext> | undefined,\r\n applyVendorHints: boolean,\r\n): WrappedResponses | undefined {\r\n if (!resource || typeof resource !== \"object\") {\r\n return undefined;\r\n }\r\n\r\n if (!(\"create\" in resource) || typeof (resource as { create?: unknown }).create !== \"function\") {\r\n return resource as unknown as WrappedResponses;\r\n }\r\n\r\n const originalCreate = (resource as { create: (...args: unknown[]) => unknown }).create.bind(resource);\r\n\r\n const wrappedCreate = (\r\n params: ResponsesCreateParams,\r\n options?: WrapOpenAIResponseCallOptions,\r\n ): ResponsesCreateReturn => {\r\n const { requestOptions, usageContext, withUsage } = splitUsageOptions(options);\r\n const beginRequest = resolveBeginRequest(defaultContext, usageContext);\r\n const wantsStream = isStreamingRequest(params);\r\n\r\n return usageTap.withUsage(beginRequest, (ctx) => {\r\n const finalParams = applyVendorHints\r\n ? applyResponsesVendorHints(params, ctx.begin.data.vendorHints)\r\n : params;\r\n const request = attachCorrelationHeader(requestOptions, ctx.begin.correlationId) as ResponsesCreateOptions;\r\n\r\n if (wantsStream) {\r\n const apiPromise = originalCreate(finalParams, request);\r\n const wrappedPromise = transformApiPromise(apiPromise, (rawStream) => {\r\n ensureAsyncIterable(rawStream, \"responses.create\");\r\n const wrappedStream = wrapStreamForUsageTap(rawStream, async () => {\r\n const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints);\r\n if (usage) {\r\n ctx.setUsage(usage);\r\n }\r\n }, ctx);\r\n return wrappedStream;\r\n });\r\n\r\n return wrappedPromise as unknown as ResponsesCreateReturn;\r\n }\r\n\r\n const apiPromise = originalCreate(finalParams, request);\r\n const wrappedPromise = transformApiPromise(apiPromise, (response) => {\r\n tryInferUsage(response, ctx.begin.data.vendorHints, undefined, ctx);\r\n return response;\r\n });\r\n return wrappedPromise as unknown as ResponsesCreateReturn;\r\n }, withUsage) as ResponsesCreateReturn;\r\n };\r\n\r\n const handler: ProxyHandler<object> = {\r\n get(target, prop, receiver) {\r\n if (prop === \"create\") {\r\n return wrappedCreate;\r\n }\r\n return Reflect.get(target, prop, receiver) as unknown;\r\n },\r\n };\r\n\r\n return new Proxy(resource as object, handler) as WrappedResponses;\r\n}\r\n\r\nfunction createChatCompletionsProxy(\r\n resource: ChatCompletionsResource,\r\n usageTap: UsageTapClient,\r\n defaultContext: Partial<WrapOpenAIContext> | undefined,\r\n applyVendorHints: boolean,\r\n): WrappedChatCompletions {\r\n const originalCreate = resource.create.bind(resource);\r\n const streamCandidate = (resource as { stream?: unknown }).stream;\r\n const originalStream = typeof streamCandidate === \"function\"\r\n ? (streamCandidate as (...args: unknown[]) => unknown).bind(resource)\r\n : undefined;\r\n\r\n const wrappedCreate = (\r\n params: ChatCompletionCreateParams,\r\n options?: WrapOpenAICallOptions,\r\n ): ChatCompletionCreateReturn => {\r\n const { requestOptions, usageContext, withUsage } = splitUsageOptions(options);\r\n const beginRequest = resolveBeginRequest(defaultContext, usageContext);\r\n const wantsStream = isStreamingRequest(params);\r\n\r\n return usageTap.withUsage(beginRequest, (ctx) => {\r\n const finalParams = applyVendorHints\r\n ? applyChatVendorHints(params, ctx.begin.data.vendorHints)\r\n : params;\r\n const request = attachCorrelationHeader(requestOptions, ctx.begin.correlationId) as ChatCompletionCreateOptions;\r\n\r\n if (wantsStream) {\r\n const apiPromise = originalCreate(finalParams, request);\r\n const wrappedPromise = transformApiPromise(apiPromise, (rawStream) => {\r\n ensureAsyncIterable(rawStream, \"chat.completions.create\");\r\n const wrappedStream = wrapStreamForUsageTap(rawStream, async () => {\r\n const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints);\r\n if (usage) {\r\n ctx.setUsage(usage);\r\n }\r\n }, ctx);\r\n return wrappedStream;\r\n });\r\n\r\n return wrappedPromise as unknown as ChatCompletionCreateReturn;\r\n }\r\n\r\n const apiPromise = originalCreate(finalParams, request);\r\n const wrappedPromise = transformApiPromise(apiPromise, (response) => {\r\n tryInferUsage(response, ctx.begin.data.vendorHints, undefined, ctx);\r\n return response;\r\n });\r\n return wrappedPromise as unknown as ChatCompletionCreateReturn;\r\n }, withUsage) as ChatCompletionCreateReturn;\r\n };\r\n\r\n const wrappedStream = originalStream\r\n ? (\r\n params: ChatCompletionCreateParams,\r\n options?: WrapOpenAICallOptions,\r\n ): ChatCompletionCreateReturn => {\r\n const { requestOptions, usageContext, withUsage } = splitUsageOptions(options);\r\n const beginRequest = resolveBeginRequest(defaultContext, usageContext);\r\n\r\n return usageTap.withUsage(beginRequest, (ctx) => {\r\n const finalParams = applyVendorHints\r\n ? applyChatVendorHints(params, ctx.begin.data.vendorHints)\r\n : params;\r\n const request = attachCorrelationHeader(requestOptions, ctx.begin.correlationId) as ChatCompletionCreateOptions;\r\n\r\n const apiPromise = originalStream(finalParams, request);\r\n const wrappedPromise = transformApiPromise(apiPromise, (rawStream) => {\r\n ensureAsyncIterable(rawStream, \"chat.completions.stream\");\r\n const wrappedStreamInner = wrapStreamForUsageTap(rawStream, async () => {\r\n const usage = await extractUsageFromStream(rawStream, ctx.begin.data.vendorHints);\r\n if (usage) {\r\n ctx.setUsage(usage);\r\n }\r\n }, ctx);\r\n return wrappedStreamInner;\r\n });\r\n\r\n return wrappedPromise as unknown as ChatCompletionCreateReturn;\r\n }, withUsage) as ChatCompletionCreateReturn;\r\n }\r\n : undefined;\r\n\r\n const handler: ProxyHandler<ChatCompletionsResource> = {\r\n get(target, prop, receiver) {\r\n if (prop === \"create\") {\r\n return wrappedCreate;\r\n }\r\n\r\n if (prop === \"stream\" && wrappedStream) {\r\n return wrappedStream;\r\n }\r\n\r\n return Reflect.get(target as object, prop, receiver) as unknown;\r\n },\r\n };\r\n\r\n return new Proxy(resource, handler) as unknown as WrappedChatCompletions;\r\n}\r\n\r\ninterface SplitUsageOptionsResult {\r\n requestOptions?: Record<string, unknown>;\r\n usageContext?: Partial<WrapOpenAIContext>;\r\n withUsage?: WithUsageOptions;\r\n}\r\n\r\nfunction splitUsageOptions(options: unknown): SplitUsageOptionsResult {\r\n if (!options || typeof options !== \"object\") {\r\n return {};\r\n }\r\n\r\n const { usageTap, withUsage, ...rest } = options as {\r\n usageTap?: Partial<WrapOpenAIContext>;\r\n withUsage?: WithUsageOptions;\r\n } & Record<string, unknown>;\r\n\r\n const requestOptions = Object.keys(rest).length ? cloneRequestOptions(rest) : undefined;\r\n\r\n return {\r\n requestOptions,\r\n usageContext: usageTap,\r\n withUsage,\r\n } satisfies SplitUsageOptionsResult;\r\n}\r\n\r\nfunction resolveBeginRequest(\r\n defaults: Partial<WrapOpenAIContext> | undefined,\r\n override: Partial<WrapOpenAIContext> | undefined,\r\n): BeginCallRequest {\r\n const base = defaults ?? {};\r\n const current = override ?? {};\r\n const customerId = current.customerId ?? base.customerId;\r\n\r\n if (!customerId) {\r\n throw new UsageTapError(\r\n \"USAGETAP_BAD_REQUEST\",\r\n \"wrapOpenAI requires usageTap.customerId (provide defaultContext or options.usageTap)\",\r\n );\r\n }\r\n\r\n const tags = mergeTags(base.tags, current.tags);\r\n const begin: BeginCallRequest = { customerId } satisfies BeginCallRequest;\r\n\r\n const requested = current.requested ?? base.requested;\r\n if (requested) begin.requested = requested;\r\n\r\n const feature = current.feature ?? base.feature;\r\n if (feature) begin.feature = feature;\r\n\r\n const idempotency = current.idempotency ?? base.idempotency;\r\n if (idempotency) begin.idempotency = idempotency;\r\n\r\n const customerName = current.customerName ?? base.customerName;\r\n if (customerName) begin.customerName = customerName;\r\n\r\n const customerEmail = current.customerEmail ?? base.customerEmail;\r\n if (customerEmail) begin.customerEmail = customerEmail;\r\n\r\n if (tags?.length) {\r\n begin.tags = tags;\r\n }\r\n\r\n return begin;\r\n}\r\n\r\ntype PromiseLikeOrValue<T> = PromiseLike<T> | T;\r\n\r\nfunction transformApiPromise<TValue, TResult>(\r\n apiPromise: PromiseLikeOrValue<TValue>,\r\n onResolve: (value: TValue) => PromiseLike<TResult> | TResult,\r\n): Promise<TResult> {\r\n const resolvedPromise = Promise.resolve(apiPromise).then(onResolve);\r\n\r\n if (isObjectRecord(apiPromise)) {\r\n const proto = Object.getPrototypeOf(apiPromise) as object | null;\r\n if (proto) {\r\n Object.setPrototypeOf(resolvedPromise, proto);\r\n }\r\n\r\n for (const key of Reflect.ownKeys(apiPromise)) {\r\n if (key === \"then\" || key === \"catch\" || key === \"finally\") {\r\n continue;\r\n }\r\n\r\n try {\r\n const descriptor = Object.getOwnPropertyDescriptor(apiPromise, key);\r\n if (descriptor) {\r\n Reflect.defineProperty(resolvedPromise, key, descriptor);\r\n }\r\n } catch {\r\n /* ignore non-configurable properties */\r\n }\r\n }\r\n }\r\n\r\n return resolvedPromise;\r\n}\r\n\r\nfunction isObjectRecord(value: unknown): value is Record<PropertyKey, unknown> {\r\n return typeof value === \"object\" && value !== null;\r\n}\r\n\r\nfunction cloneRecord(value: unknown): Record<string, unknown> {\r\n return isObjectRecord(value) ? { ...(value as Record<string, unknown>) } : {};\r\n}\r\n\r\nfunction isStringTuple(value: unknown): value is [string, string] {\r\n return Array.isArray(value) && value.length >= 2 && typeof value[0] === \"string\" && typeof value[1] === \"string\";\r\n}\r\n\r\nfunction cloneRequestOptions(source: Record<string, unknown>): Record<string, unknown> {\r\n const clone: Record<string, unknown> = { ...source };\r\n\r\n if (\"headers\" in clone) {\r\n clone.headers = normalizeHeaders((clone as { headers?: unknown }).headers);\r\n }\r\n\r\n return clone;\r\n}\r\n\r\nfunction attachCorrelationHeader(\r\n options: Record<string, unknown> | undefined,\r\n correlationId: string,\r\n): Record<string, unknown> | undefined {\r\n const normalized = normalizeHeaders(options?.headers);\r\n\r\n if (correlationId && !normalized[USAGETAP_CORRELATION_HEADER]) {\r\n normalized[USAGETAP_CORRELATION_HEADER] = correlationId;\r\n }\r\n\r\n if (!options) {\r\n return Object.keys(normalized).length\r\n ? ({ headers: normalized } satisfies Record<string, unknown>)\r\n : undefined;\r\n }\r\n\r\n const next = { ...options } satisfies Record<string, unknown>;\r\n if (Object.keys(normalized).length) {\r\n next.headers = normalized;\r\n }\r\n return next;\r\n}\r\n\r\nfunction normalizeHeaders(headers: unknown): Record<string, string> {\r\n if (!headers) {\r\n return {};\r\n }\r\n\r\n if (headers instanceof Headers) {\r\n const result: Record<string, string> = {};\r\n headers.forEach((value, key) => {\r\n result[key.toLowerCase()] = value;\r\n });\r\n return result;\r\n }\r\n\r\n if (Array.isArray(headers)) {\r\n const result: Record<string, string> = {};\r\n for (const entry of headers) {\r\n if (!isStringTuple(entry)) {\r\n continue;\r\n }\r\n const [key, value] = entry;\r\n result[key.toLowerCase()] = value;\r\n }\r\n return result;\r\n }\r\n\r\n if (isObjectRecord(headers)) {\r\n const result: Record<string, string> = {};\r\n const record = headers as Record<string, unknown>;\r\n for (const key of Object.keys(record)) {\r\n const value = record[key];\r\n if (value !== undefined && value !== null) {\r\n result[key.toLowerCase()] = String(value);\r\n }\r\n }\r\n return result;\r\n }\r\n\r\n return {};\r\n}\r\n\r\nfunction mergeTags(a?: string[], b?: string[]): string[] | undefined {\r\n const values = [...(a ?? []), ...(b ?? [])]\r\n .map((value) => (typeof value === \"string\" ? value.trim() : \"\"))\r\n .filter(Boolean);\r\n\r\n if (!values.length) {\r\n return undefined;\r\n }\r\n\r\n return dedupeStrings(values);\r\n}\r\n\r\nfunction dedupeStrings(values: string[]): string[] {\r\n return Array.from(new Set(values));\r\n}\r\n\r\nfunction isStreamingRequest(params: unknown): boolean {\r\n if (!params || typeof params !== \"object\") {\r\n return false;\r\n }\r\n\r\n const stream = (params as { stream?: unknown }).stream;\r\n if (typeof stream === \"boolean\") {\r\n return stream;\r\n }\r\n\r\n return stream != null;\r\n}\r\n\r\nfunction applyChatVendorHints(\r\n params: ChatCompletionCreateParams,\r\n hints: VendorHints | undefined,\r\n): ChatCompletionCreateParams {\r\n if (!hints) {\r\n return params;\r\n }\r\n\r\n const next = cloneRecord(params);\r\n\r\n if (hints.preferredModel && (next.model === undefined || next.model === null)) {\r\n next.model = hints.preferredModel;\r\n }\r\n\r\n if (typeof hints.maxResponseTokens === \"number\" && next.max_tokens == null) {\r\n next.max_tokens = hints.maxResponseTokens;\r\n }\r\n\r\n if (typeof hints.maxInputTokens === \"number\" && (next as { max_input_tokens?: unknown }).max_input_tokens == null) {\r\n (next as { max_input_tokens?: number }).max_input_tokens = hints.maxInputTokens;\r\n }\r\n\r\n return next as unknown as ChatCompletionCreateParams;\r\n}\r\n\r\nfunction applyResponsesVendorHints(\r\n params: ResponsesCreateParams,\r\n hints: VendorHints | undefined,\r\n): ResponsesCreateParams {\r\n if (!hints) {\r\n return params;\r\n }\r\n\r\n const next = cloneRecord(params);\r\n\r\n if (hints.preferredModel && (next.model === undefined || next.model === null)) {\r\n next.model = hints.preferredModel;\r\n }\r\n\r\n if (typeof hints.maxResponseTokens === \"number\" && (next as { max_output_tokens?: unknown }).max_output_tokens == null) {\r\n (next as { max_output_tokens?: number }).max_output_tokens = hints.maxResponseTokens;\r\n }\r\n\r\n return next as unknown as ResponsesCreateParams;\r\n}\r\n\r\nasync function extractUsageFromStream(\r\n stream: unknown,\r\n hints: VendorHints | undefined,\r\n): Promise<Partial<Omit<EndCallRequest, \"callId\" | \"error\">> | undefined> {\r\n const finalPayload = await resolveStreamFinalPayload(stream);\r\n if (!finalPayload) {\r\n return undefined;\r\n }\r\n\r\n return inferUsageFromResponse(finalPayload, hints);\r\n}\r\n\r\nasync function resolveStreamFinalPayload(stream: unknown): Promise<unknown> {\r\n if (!stream || typeof stream !== \"object\") {\r\n return undefined;\r\n }\r\n\r\n const candidate = stream as {\r\n finalChatCompletion?: () => Promise<unknown>;\r\n finalCompletion?: () => Promise<unknown>;\r\n finalResponse?: () => Promise<unknown>;\r\n finalContent?: () => Promise<unknown>;\r\n };\r\n\r\n if (typeof candidate.finalChatCompletion === \"function\") {\r\n return candidate.finalChatCompletion();\r\n }\r\n\r\n if (typeof candidate.finalResponse === \"function\") {\r\n return candidate.finalResponse();\r\n }\r\n\r\n if (typeof candidate.finalCompletion === \"function\") {\r\n return candidate.finalCompletion();\r\n }\r\n\r\n if (typeof candidate.finalContent === \"function\") {\r\n return candidate.finalContent();\r\n }\r\n\r\n return undefined;\r\n}\r\n\r\nfunction ensureAsyncIterable(value: unknown, label: string): asserts value is AsyncIterable<unknown> {\r\n if (!value || typeof value !== \"object\" || typeof (value as AsyncIterable<unknown>)[Symbol.asyncIterator] !== \"function\") {\r\n throw new UsageTapError(\r\n \"USAGETAP_BAD_REQUEST\",\r\n `${label} expected an async iterable stream but received ${typeof value}`,\r\n );\r\n }\r\n}\r\n\r\nfunction chunkToText(chunk: unknown): string {\r\n if (chunk === undefined || chunk === null) {\r\n return \"\";\r\n }\r\n\r\n if (typeof chunk === \"string\") {\r\n return chunk;\r\n }\r\n\r\n if (typeof chunk === \"object\") {\r\n const candidate = chunk as {\r\n choices?: Array<{\r\n delta?: {\r\n content?: string | Array<{ text?: string }>;\r\n };\r\n }>;\r\n content?: string;\r\n };\r\n\r\n const delta = candidate.choices?.[0]?.delta;\r\n const content = delta?.content ?? candidate.content;\r\n\r\n if (typeof content === \"string\") {\r\n return content;\r\n }\r\n\r\n if (Array.isArray(content)) {\r\n return content\r\n .map((entry) => {\r\n if (!entry) return \"\";\r\n if (typeof entry === \"string\") return entry;\r\n if (typeof entry.text === \"string\") return entry.text;\r\n return \"\";\r\n })\r\n .join(\"\");\r\n }\r\n }\r\n\r\n return String(chunk);\r\n}\r\n\r\nfunction formatSsePayload(\r\n text: string,\r\n options: StreamToResponseOptions[\"sse\"],\r\n): string {\r\n if (!text) {\r\n return \"\";\r\n }\r\n\r\n const lines = text.split(/\\r?\\n/);\r\n const eventLine = options?.event ? `event: ${options.event}\\n` : \"\";\r\n const retryLine = options?.retry ? `retry: ${options.retry}\\n` : \"\";\r\n const dataLines = lines.map((line) => `data: ${line}`).join(\"\\n\");\r\n return `${eventLine}${retryLine}${dataLines}\\n\\n`;\r\n}\r\n\r\nfunction setHeaderIfPossible(res: NodeResponseLike, key: string, value: string): void {\r\n if (typeof res.setHeader === \"function\" && res.headersSent !== true) {\r\n res.setHeader(key, value);\r\n }\r\n}\r\n\r\nfunction tryInferUsage<TResponse>(\r\n response: TResponse,\r\n hints: VendorHints | undefined,\r\n extractor: OpenAIInvokeParams<TResponse>[\"extractUsage\"],\r\n ctx: WithUsageContext,\r\n): void {\r\n const explicit = extractor?.(response);\r\n const inferred = explicit ?? inferUsageFromResponse(response, hints);\r\n\r\n if (inferred) {\r\n ctx.setUsage(inferred);\r\n }\r\n}\r\n\r\nfunction inferUsageFromResponse(\r\n response: unknown,\r\n hints: VendorHints | undefined,\r\n): Partial<Omit<EndCallRequest, \"callId\" | \"error\">> | undefined {\r\n if (!response || typeof response !== \"object\") {\r\n return undefined;\r\n }\r\n\r\n const candidate = response as {\r\n usage?: {\r\n prompt_tokens?: number;\r\n completion_tokens?: number;\r\n total_tokens?: number;\r\n cached_tokens?: number;\r\n prompt_tokens_details?: {\r\n cached_tokens?: number;\r\n };\r\n };\r\n model?: string;\r\n };\r\n\r\n if (!candidate.usage) {\r\n return undefined;\r\n }\r\n\r\n const cachedInputTokens =\r\n candidate.usage.prompt_tokens_details?.cached_tokens ??\r\n candidate.usage.cached_tokens;\r\n\r\n return {\r\n modelUsed: candidate.model ?? hints?.preferredModel,\r\n inputTokens: candidate.usage.prompt_tokens,\r\n responseTokens: candidate.usage.completion_tokens,\r\n cachedInputTokens,\r\n } satisfies Partial<Omit<EndCallRequest, \"callId\" | \"error\">>;\r\n}\r\n\r\nfunction wrapStreamForUsageTap<TStream>(\r\n source: UsageTapStream<TStream> | AsyncIterable<TStream>,\r\n finalize: () => Promise<void> | void,\r\n ctx: WithUsageContext,\r\n): AsyncIterable<TStream> & { __usageTapFinalize: () => Promise<void> } {\r\n const getIterator = source[Symbol.asyncIterator];\r\n if (typeof getIterator !== \"function\") {\r\n throw new TypeError(\"Stream is not async iterable\");\r\n }\r\n\r\n const iterator = getIterator.call(source) as AsyncIterator<TStream>;\r\n let completed = false;\r\n\r\n const invokeFinalize = async (): Promise<void> => {\r\n if (completed) return;\r\n completed = true;\r\n try {\r\n await finalize();\r\n } catch (error) {\r\n ctx.setError({\r\n code: \"USAGE_FINALIZE_ERROR\",\r\n message: error instanceof Error ? error.message : String(error),\r\n });\r\n throw error;\r\n }\r\n };\r\n\r\n const prototype = (Object.getPrototypeOf(source as object) as object | null) ?? Object.prototype;\r\n const wrapped = Object.create(prototype) as unknown as UsageTapIterableIterator<TStream>;\r\n\r\n for (const key of Reflect.ownKeys(source as object)) {\r\n try {\r\n const descriptor = Object.getOwnPropertyDescriptor(source as object, key);\r\n if (descriptor) {\r\n Object.defineProperty(wrapped, key, descriptor);\r\n }\r\n } catch {\r\n /* ignore non-configurable properties */\r\n }\r\n }\r\n\r\n Object.defineProperty(wrapped, Symbol.asyncIterator, {\r\n value(): UsageTapIterableIterator<TStream> {\r\n return this as UsageTapIterableIterator<TStream>;\r\n },\r\n configurable: true,\r\n });\r\n\r\n Object.defineProperty(wrapped, \"next\", {\r\n value: async (...args: Parameters<AsyncIterator<TStream>[\"next\"]>): Promise<IteratorResult<TStream>> => {\r\n try {\r\n const result = await iterator.next(...args);\r\n if (result.done) {\r\n await invokeFinalize();\r\n }\r\n return result;\r\n } catch (error) {\r\n await invokeFinalize().catch(() => undefined);\r\n throw error;\r\n }\r\n },\r\n configurable: true,\r\n writable: true,\r\n });\r\n\r\n Object.defineProperty(wrapped, \"return\", {\r\n value: async (value?: TStream): Promise<IteratorResult<TStream>> => {\r\n if (typeof iterator.return === \"function\") {\r\n const rawResult: unknown = await iterator.return(value);\r\n if (!isIteratorResult<TStream>(rawResult)) {\r\n throw new TypeError(\"Iterator.return() returned an invalid result\");\r\n }\r\n await invokeFinalize();\r\n return rawResult;\r\n }\r\n await invokeFinalize();\r\n return { done: true, value } as IteratorResult<TStream>;\r\n },\r\n configurable: true,\r\n writable: true,\r\n });\r\n\r\n Object.defineProperty(wrapped, \"throw\", {\r\n value: async (error?: unknown): Promise<IteratorResult<TStream>> => {\r\n if (typeof iterator.throw === \"function\") {\r\n const rawResult: unknown = await iterator.throw(error);\r\n if (!isIteratorResult<TStream>(rawResult)) {\r\n throw new TypeError(\"Iterator.throw() returned an invalid result\");\r\n }\r\n await invokeFinalize();\r\n return rawResult;\r\n }\r\n await invokeFinalize();\r\n throw error;\r\n },\r\n configurable: true,\r\n writable: true,\r\n });\r\n\r\n Object.defineProperty(wrapped, \"__usageTapFinalize\", {\r\n value: async (): Promise<void> => {\r\n await invokeFinalize();\r\n },\r\n configurable: true,\r\n });\r\n\r\n return wrapped;\r\n}\r\n\r\nfunction isIteratorResult<T>(value: unknown): value is IteratorResult<T> {\r\n return isObjectRecord(value) && \"done\" in value;\r\n}\r\n","import type OpenAI from \"openai\";\r\nimport { UsageTapClient } from \"../client\";\r\nimport type { OpenAIAdapter } from \"./openai\";\r\nimport { createOpenAIAdapter } from \"./openai\";\r\n\r\nexport interface OpenRouterAdapterInit {\r\n client: OpenAI;\r\n usageTap: UsageTapClient;\r\n}\r\n\r\nexport function createOpenRouterAdapter(init: OpenRouterAdapterInit): OpenAIAdapter {\r\n return createOpenAIAdapter(init);\r\n}\r\n"]}
@@ -21,10 +21,14 @@ interface EntitlementHints {
21
21
  downgrade?: EntitlementDowngradeHint;
22
22
  }
23
23
  interface MeterSummary {
24
- remaining: number | null;
24
+ /** Always numeric. Check the `unlimited` flag to detect unbounded meters. */
25
+ remaining: number;
25
26
  limit: number | null;
26
- used: number | null;
27
+ /** Always numeric (0 when no usage recorded). */
28
+ used: number;
29
+ /** When true this meter is unbounded; `remaining` is informational only. */
27
30
  unlimited: boolean;
31
+ /** remaining / limit (0-1). null when unlimited or limit is null/0. */
28
32
  ratio: number | null;
29
33
  label?: string;
30
34
  }
@@ -65,6 +69,11 @@ interface BeginCallRequest {
65
69
  requested?: RequestedEntitlements;
66
70
  feature?: string;
67
71
  tags?: string[];
72
+ /**
73
+ * Optional PAYG hold hint (USD) to authorize upfront during call_begin.
74
+ * When provided, overrides the plan's default hold for this call only.
75
+ */
76
+ holdUsd?: number;
68
77
  /**
69
78
  * Idempotency key for safe retries. When omitted, UsageTap derives a key deterministically.
70
79
  * @deprecated Use idempotencyKey instead
@@ -77,6 +86,14 @@ interface BeginCallRequest {
77
86
  customerName?: string;
78
87
  customerEmail?: string;
79
88
  stripeCustomerId?: string;
89
+ /**
90
+ * Whether this call uses batch pricing (typically 50% of standard rates).
91
+ */
92
+ batch?: boolean;
93
+ /**
94
+ * Explicit pricing mode for this call.
95
+ */
96
+ pricingMode?: "batch" | "standard";
80
97
  }
81
98
  interface BalanceSummary {
82
99
  standardCallsRemaining?: number;
@@ -87,6 +104,23 @@ interface BalanceSummary {
87
104
  customMeter1Remaining?: number;
88
105
  customMeter2Remaining?: number;
89
106
  }
107
+ type SpendVelocityWindowKey = "hour" | "day";
108
+ interface SpendVelocityWindow {
109
+ bucket: string;
110
+ windowMinutes: number;
111
+ startedAt: string;
112
+ endedAt: string;
113
+ completedCostUsd: number;
114
+ completedCalls: number;
115
+ }
116
+ interface SpendVelocitySnapshot {
117
+ currency: "USD";
118
+ source: "usage_aggregate";
119
+ generatedAt: string;
120
+ customerId: string;
121
+ currentCallCostUsd: number;
122
+ windows: Record<SpendVelocityWindowKey, SpendVelocityWindow>;
123
+ }
90
124
  interface PlanSummary {
91
125
  id: string | null;
92
126
  name: string | null;
@@ -111,13 +145,28 @@ interface BeginCallResponseBody {
111
145
  plan?: PlanSummary;
112
146
  balances?: BalanceSummary;
113
147
  stripeCustomerId?: string | null;
148
+ batch?: boolean;
149
+ pricingMode?: "batch" | "standard";
114
150
  }
115
151
  interface EndCallRequest {
116
152
  callId: string;
153
+ /** Optional customer ID for metric tracking. Not sent to API, used for onUsageMetric callback. */
154
+ customerId?: string;
155
+ /** Optional feature for metric tracking. Not sent to API, used for onUsageMetric callback. */
156
+ feature?: string;
157
+ /** Optional tags for metric tracking. Not sent to API, used for onUsageMetric callback. */
158
+ tags?: string[];
117
159
  modelUsed?: string;
160
+ /**
161
+ * Total prompt/input tokens for the call (including any cached prompt tokens).
162
+ */
118
163
  inputTokens?: number;
119
164
  responseTokens?: number;
120
- cachedTokens?: number;
165
+ /**
166
+ * Prompt/input tokens served from cache.
167
+ * Preferred over legacy aliases.
168
+ */
169
+ cachedInputTokens?: number;
121
170
  reasoningTokens?: number;
122
171
  searches?: number;
123
172
  audio?: number;
@@ -127,6 +176,14 @@ interface EndCallRequest {
127
176
  message: string;
128
177
  };
129
178
  stripeCustomerId?: string;
179
+ /**
180
+ * Whether this call uses batch pricing (typically 50% of standard rates).
181
+ */
182
+ batch?: boolean;
183
+ /**
184
+ * Explicit pricing mode for this call.
185
+ */
186
+ pricingMode?: "batch" | "standard";
130
187
  }
131
188
  interface MeteredUsage {
132
189
  calls?: number;
@@ -139,9 +196,19 @@ interface MeteredUsage {
139
196
  interface EndCallResponseBody {
140
197
  callId: string;
141
198
  costUSD: number;
199
+ usage?: {
200
+ inputTokens: number;
201
+ cachedInputTokens: number;
202
+ billableInputTokens: number;
203
+ responseTokens: number;
204
+ reasoningTokens: number;
205
+ };
142
206
  metered?: MeteredUsage;
207
+ spendVelocity?: SpendVelocitySnapshot;
143
208
  balances?: BalanceSummary;
144
209
  stripeCustomerId?: string | null;
210
+ batch?: boolean;
211
+ pricingMode?: "batch" | "standard";
145
212
  }
146
213
  type UsageTapResultStatus = "ACCEPTED" | "ERROR";
147
214
  interface UsageTapResultEnvelope {
@@ -211,6 +278,11 @@ interface UsageTapClientOptions {
211
278
  */
212
279
  autoIdempotency?: boolean;
213
280
  onLog?: (entry: UsageTapLogEntry) => void;
281
+ /**
282
+ * Callback invoked when usage metrics are recorded (call_end, custom_meter).
283
+ * Use this to export metrics to OpenTelemetry or other observability systems.
284
+ */
285
+ onUsageMetric?: (event: UsageMetricEvent) => void;
214
286
  /**
215
287
  * Override the default Authorization header with x-api-key when true.
216
288
  */
@@ -315,6 +387,42 @@ interface IncrementCustomMeterResponseBody {
315
387
  meter: MeterSummary;
316
388
  blocked: boolean;
317
389
  }
390
+ /**
391
+ * Event emitted when usage metrics are recorded.
392
+ * Use with `onUsageMetric` callback to export to OpenTelemetry or other observability systems.
393
+ */
394
+ interface UsageMetricEvent {
395
+ /** Type of metric event */
396
+ type: "call_end" | "custom_meter";
397
+ /** Timestamp in ISO 8601 format */
398
+ timestamp: string;
399
+ /** Customer identifier */
400
+ customerId: string;
401
+ /** Unique call identifier (for call_end events) */
402
+ callId?: string;
403
+ /** Feature tag */
404
+ feature?: string;
405
+ /** Additional tags */
406
+ tags?: string[];
407
+ /** Model used for the call */
408
+ modelUsed?: string;
409
+ /** Usage metrics */
410
+ metrics: {
411
+ inputTokens?: number;
412
+ cachedInputTokens?: number;
413
+ responseTokens?: number;
414
+ reasoningTokens?: number;
415
+ searches?: number;
416
+ audioSeconds?: number;
417
+ costUsd?: number;
418
+ /** Custom meter slot (for custom_meter events) */
419
+ customMeterSlot?: CustomMeterSlot;
420
+ /** Custom meter amount (for custom_meter events) */
421
+ customMeterAmount?: number;
422
+ };
423
+ /** Correlation ID for tracing */
424
+ correlationId?: string;
425
+ }
318
426
 
319
427
  declare class UsageTapClient {
320
428
  private readonly apiKey;
@@ -326,6 +434,7 @@ declare class UsageTapClient {
326
434
  private readonly retryDefaults;
327
435
  private readonly idempotencyGenerator;
328
436
  private readonly logFn?;
437
+ private readonly metricFn?;
329
438
  private readonly authHeader;
330
439
  private readonly autoIdempotency;
331
440
  constructor(options: UsageTapClientOptions);
@@ -341,10 +450,11 @@ declare class UsageTapClient {
341
450
  private performFetch;
342
451
  private composeHeaders;
343
452
  private log;
453
+ private emitUsageMetric;
344
454
  private mergeTags;
345
455
  private shouldRetry;
346
456
  private toHttpError;
347
457
  private toApiError;
348
458
  }
349
459
 
350
- export { type AllowedEntitlements as A, type BeginCallRequest as B, type CreateCustomerOptions as C, type UsageTapResultStatus as D, type EndCallRequest as E, type UsageTapLogEntry as F, type WithUsageContext as G, type ModelHints as H, type IncrementCustomMeterOptions as I, type IdempotencyMetadata as J, type LimitType as L, type MeterSummary as M, type PlanSummary as P, type RemainingRatios as R, type SubscriptionSnapshot as S, UsageTapClient as U, type VendorHints as V, type WithUsageOptions as W, type UsageTapSuccessResponse as a, type BeginCallResponseBody as b, type BeginCallOptions as c, type CreateCustomerRequest as d, type CreateCustomerResponseBody as e, type CheckUsageOptions as f, type CheckUsageRequest as g, type CheckUsageResponseBody as h, type ChangePlanOptions as i, type ChangePlanRequest as j, type ChangePlanResponseBody as k, type ChangePlanStrategy as l, type IncrementCustomMeterRequest as m, type IncrementCustomMeterResponseBody as n, type CustomMeterSlot as o, type EndCallOptions as p, type EndCallResponseBody as q, type BalanceSummary as r, type EntitlementHints as s, type MeterSnapshot as t, type MeteredUsage as u, type RequestedEntitlements as v, type RetryOptions as w, type UsageTapClientOptions as x, type UsageTapErrorResponse as y, type UsageTapResultEnvelope as z };
460
+ export { type AllowedEntitlements as A, type BeginCallRequest as B, type CreateCustomerOptions as C, type UsageTapErrorResponse as D, type EndCallRequest as E, type UsageTapResultEnvelope as F, type UsageTapResultStatus as G, type UsageTapLogEntry as H, type IncrementCustomMeterOptions as I, type WithUsageContext as J, type SubscriptionSnapshot as K, type LimitType as L, type MeterSummary as M, type ModelHints as N, type IdempotencyMetadata as O, type PlanSummary as P, type UsageMetricEvent as Q, type RemainingRatios as R, type SpendVelocitySnapshot as S, UsageTapClient as U, type VendorHints as V, type WithUsageOptions as W, type UsageTapSuccessResponse as a, type BeginCallResponseBody as b, type BeginCallOptions as c, type CreateCustomerRequest as d, type CreateCustomerResponseBody as e, type CheckUsageOptions as f, type CheckUsageRequest as g, type CheckUsageResponseBody as h, type ChangePlanOptions as i, type ChangePlanRequest as j, type ChangePlanResponseBody as k, type ChangePlanStrategy as l, type IncrementCustomMeterRequest as m, type IncrementCustomMeterResponseBody as n, type CustomMeterSlot as o, type EndCallOptions as p, type EndCallResponseBody as q, type BalanceSummary as r, type SpendVelocityWindow as s, type SpendVelocityWindowKey as t, type EntitlementHints as u, type MeterSnapshot as v, type MeteredUsage as w, type RequestedEntitlements as x, type RetryOptions as y, type UsageTapClientOptions as z };