@usagetap/observer 0.1.2 → 0.1.4
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.
- package/README.md +19 -10
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -5
- package/dist/index.d.ts +16 -5
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,8 +9,6 @@ import OpenAI from "openai";
|
|
|
9
9
|
import { observeOpenAI } from "@usagetap/observer";
|
|
10
10
|
|
|
11
11
|
const openai = observeOpenAI(new OpenAI(), {
|
|
12
|
-
apiKey: process.env.USAGETAP_API_KEY,
|
|
13
|
-
customerId: process.env.USAGETAP_CUSTOMER_ID!,
|
|
14
12
|
feature: "support-assistant",
|
|
15
13
|
});
|
|
16
14
|
|
|
@@ -26,13 +24,24 @@ Anthropic uses the same options:
|
|
|
26
24
|
import Anthropic from "@anthropic-ai/sdk";
|
|
27
25
|
import { observeAnthropic } from "@usagetap/observer";
|
|
28
26
|
|
|
29
|
-
const anthropic = observeAnthropic(new Anthropic()
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
27
|
+
const anthropic = observeAnthropic(new Anthropic());
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Set `USAGETAP_API_KEY` as a server-side environment variable. Trial keys infer
|
|
31
|
+
their private workspace automatically. Full accounts can optionally pass a
|
|
32
|
+
`customerId` when one key reports usage for multiple end customers.
|
|
33
|
+
|
|
34
|
+
The observer supports OpenAI-compatible chat completions, Responses API calls,
|
|
35
|
+
and Anthropic Messages calls. Streams pass through while the observer reads only
|
|
36
|
+
metadata-bearing usage events; it never buffers or sends content chunks.
|
|
37
|
+
|
|
38
|
+
UsageTap network requests are kept off the provider response path. In ordinary
|
|
39
|
+
long-running Node processes, queued metadata is delivered in the background. In
|
|
40
|
+
short-lived jobs or serverless handlers, flush before the process exits:
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
await openai.flushUsageTap();
|
|
33
44
|
```
|
|
34
45
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
Move to `@usagetap/sdk` when you need streaming telemetry, Anthropic adapters,
|
|
38
|
-
limits, gateway routing, sampling, or compression.
|
|
46
|
+
Move to `@usagetap/sdk` when you deliberately need customer or user attribution,
|
|
47
|
+
limits, gateway routing, sampling, managed compression, alerts, or forecasts.
|
package/dist/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var R=Object.defineProperty;var K=Object.getOwnPropertyDescriptor;var q=Object.getOwnPropertyNames;var F=Object.prototype.hasOwnProperty;var M=(r,e)=>{for(var n in e)R(r,n,{get:e[n],enumerable:!0})},B=(r,e,n,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of q(e))!F.call(r,i)&&i!==n&&R(r,i,{get:()=>e[i],enumerable:!(o=K(e,i))||o.enumerable});return r};var $=r=>B(R({},"__esModule",{value:!0}),r);var X={};M(X,{UsageTapObserver:()=>b,extractAnthropicUsage:()=>O,extractOpenAIUsage:()=>_,observeAnthropic:()=>J,observeOpenAI:()=>G});module.exports=$(X);var N="https://api.usagetap.com",W="application/vnd.usagetap.v1+json";function f(r){return!!r&&typeof r=="object"&&!Array.isArray(r)}function m(r){return typeof r=="number"&&Number.isFinite(r)&&r>=0?Math.round(r):void 0}function g(r,e){let n=r[e];return f(n)?n:void 0}function k(r,e){let n=r[e];return typeof n=="string"&&n.trim()?n:void 0}function _(r,e="openai"){if(!f(r))return;let n=g(r,"usage");if(!n)return;let o=g(n,"prompt_tokens_details")??g(n,"input_tokens_details"),i=g(n,"completion_tokens_details")??g(n,"output_tokens_details");return{providerUsed:e,modelUsed:k(r,"model"),inputTokens:m(n.prompt_tokens)??m(n.input_tokens),responseTokens:m(n.completion_tokens)??m(n.output_tokens),cachedInputTokens:m(o?.cached_tokens)??m(n.cached_tokens),cacheWriteInputTokens:m(o?.cache_write_tokens),reasoningTokens:m(i?.reasoning_tokens)}}function O(r,e="anthropic"){if(!f(r))return;let n=g(r,"usage");if(n)return{providerUsed:e,modelUsed:k(r,"model"),inputTokens:m(n.input_tokens),responseTokens:m(n.output_tokens),cachedInputTokens:m(n.cache_read_input_tokens),cacheWriteInputTokens:m(n.cache_creation_input_tokens)}}function x(r,e){if(!e)return r;let n=Object.fromEntries(Object.entries(e).filter(([,o])=>o!==void 0));return{...r,...n}}function z(r,e){if(!f(r))return;let o=g(r,"response")??r,i=_(o,e),s=k(o,"model");return i??(s?{providerUsed:e,modelUsed:s}:void 0)}function A(r,e){if(!f(r))return;let n=g(r,"message");if(n)return O(n,e);let o=g(r,"usage"),i=g(r,"delta"),s=i?g(i,"usage"):void 0,u=o??s;if(u)return O({model:k(r,"model"),usage:u},e)}function L(r){return!f(r)||!f(r.usage)?!1:Array.isArray(r.choices)&&r.choices.length===0}function V(r,e,n,o,i){let s=r[Symbol.asyncIterator];return typeof s!="function"?r:new Proxy(r,{get(u,c,p){if(c===Symbol.asyncIterator)return()=>{let d=s.call(u);return{async next(){try{for(;;){let t=await d.next();if(t.done)return await n(),t;if(e(t.value),!i?.(t.value))return t}}catch(t){throw await o(t),t}},async return(t){try{let a=d.return?await d.return(t):{done:!0,value:t};return await n(),a}catch(a){throw await o(a),a}},async throw(t){try{if(d.throw)return await d.throw(t);throw t}catch(a){throw await o(a),a}},[Symbol.asyncIterator](){return this}}};let l=Reflect.get(u,c,u);return typeof l=="function"&&(c==="toReadableStream"||c==="tee")?l.bind(p):typeof l=="function"?l.bind(u):l}})}function I(r){return r instanceof Error?{code:r.name.slice(0,80)||"PROVIDER_ERROR",message:"Provider request failed"}:{code:"PROVIDER_ERROR",message:"Provider request failed"}}function Y(r){if(!f(r))return!1;let e=k(r,"type")?.toLowerCase();return e==="error"||e?.endsWith(".failed")===!0}function P(){return typeof globalThis.crypto?.randomUUID=="function"?globalThis.crypto.randomUUID():`${Date.now()}-${Math.random().toString(36).slice(2)}`}var b=class{apiKey;customerId;baseUrl;defaultFeature;defaultTags;fetchImpl;onTelemetryError;pendingTelemetry=new Set;constructor(e={}){let n=e.apiKey??(typeof process<"u"?process.env?.USAGETAP_API_KEY:void 0);if(!n?.trim())throw new Error("UsageTap observer requires an apiKey");this.apiKey=n.trim(),this.customerId=e.customerId?.trim()||void 0,this.baseUrl=(e.baseUrl??N).replace(/\/$/,""),this.defaultFeature=e.feature,this.defaultTags=e.tags,this.fetchImpl=e.fetch??globalThis.fetch,this.onTelemetryError=e.onTelemetryError}telemetryError(e){let n=e instanceof Error?e:new Error(String(e));this.onTelemetryError?.(n)}queueTelemetry(e){let n=e.then(()=>{}).catch(o=>this.telemetryError(o)).finally(()=>this.pendingTelemetry.delete(n));this.pendingTelemetry.add(n)}async flush(){for(;this.pendingTelemetry.size;)await Promise.allSettled([...this.pendingTelemetry])}async post(e,n){let o=await this.fetchImpl(`${this.baseUrl}/${e}`,{method:"POST",headers:{Accept:W,"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`,"Idempotency-Key":P(),"X-Usage-SDK":"observer-js/0.1.4"},body:JSON.stringify(n)}),i=await o.json();if(!o.ok||!i.data)throw new Error(i.error?.message??i.result?.message??`UsageTap returned ${o.status}`);return i.data}async track(e,n,o){let i=this.post("call_begin",{...this.customerId?{customerId:this.customerId}:{},feature:e.feature??this.defaultFeature,tags:e.tags??this.defaultTags,idempotencyKey:P()}).catch(s=>{this.telemetryError(s)});try{let s=await n(),u=o(s);return this.queueTelemetry(i.then(c=>{if(c?.callId)return this.post("call_end",{callId:c.callId,providerUsed:e.provider,modelUsed:e.model,...u})})),s}catch(s){throw this.queueTelemetry(i.then(u=>{if(u?.callId)return this.post("call_end",{callId:u.callId,providerUsed:e.provider,modelUsed:e.model,error:I(s)})})),s}}async trackStream(e,n,o,i){let s=this.post("call_begin",{...this.customerId?{customerId:this.customerId}:{},feature:e.feature??this.defaultFeature,tags:e.tags??this.defaultTags,idempotencyKey:P()}).catch(t=>{this.telemetryError(t)}),u;try{u=await n()}catch(t){let a=await s;if(a?.callId)try{await this.post("call_end",{callId:a.callId,providerUsed:e.provider,modelUsed:e.model,error:I(t)})}catch(h){this.telemetryError(h)}throw t}let c={providerUsed:e.provider,modelUsed:e.model},p=!1,l=!1,d=t=>(l||(l=!0,this.queueTelemetry(s.then(a=>{if(a?.callId)return this.post("call_end",{callId:a.callId,...c,...t===void 0?{}:{error:I(t)}})}))),Promise.resolve());return V(u,t=>{c=x(c,o(t)),p||=Y(t)},()=>d(p?new Error("Provider stream failed"):void 0),t=>d(t),i)}trackEmitterStream(e,n,o){let s=n.on;if(typeof s!="function")return n;let u=this.post("call_begin",{...this.customerId?{customerId:this.customerId}:{},feature:e.feature??this.defaultFeature,tags:e.tags??this.defaultTags,idempotencyKey:P()}).catch(t=>{this.telemetryError(t)}),c={providerUsed:e.provider,modelUsed:e.model},p=!1,l=t=>(p||(p=!0,this.queueTelemetry(u.then(a=>{if(a?.callId)return this.post("call_end",{callId:a.callId,...c,...t===void 0?{}:{error:I(t)}})}))),Promise.resolve()),d=t=>{c=x(c,o(t))};return Reflect.apply(s,n,["streamEvent",d]),Reflect.apply(s,n,["message",d]),Reflect.apply(s,n,["end",()=>void l()]),Reflect.apply(s,n,["error",t=>void l(t)]),Reflect.apply(s,n,["abort",()=>void l(new Error("Provider stream aborted"))]),n}};function G(r,e={}){let n=new b(e),o=e.provider??"openai",i=r,s=(d,t)=>{if(!d)return;let a=d.create;return typeof a!="function"?d:new Proxy(d,{get(h,y,v){return y!=="create"?Reflect.get(h,y,v):async(w,...E)=>{let T=f(w)?w:{};if(T.stream===!0){let U=g(T,"stream_options"),S=U?.include_usage===!0,j=t==="chat"&&!S?{...T,stream_options:{...U,include_usage:!0}}:w;return n.trackStream({provider:o,model:k(T,"model")},()=>Promise.resolve(Reflect.apply(a,h,[j,...E])),D=>z(D,o),t==="chat"&&!S?L:void 0)}let C=()=>Reflect.apply(a,h,[w,...E]);return n.track({provider:o,model:k(T,"model")},()=>Promise.resolve(C()),U=>_(U,o))}}})},u=f(i.chat)?i.chat:void 0,c=s(u&&f(u.completions)?u.completions:void 0,"chat"),p=u&&c?new Proxy(u,{get(d,t,a){return t==="completions"?c:Reflect.get(d,t,a)}}):u,l=s(f(i.responses)?i.responses:void 0,"responses");return new Proxy(r,{get(d,t,a){return t==="chat"&&p?p:t==="responses"&&l?l:t==="unwrap"?()=>r:t==="flushUsageTap"?()=>n.flush():Reflect.get(d,t,a)}})}function J(r,e={}){let n=new b(e),o=e.provider??"anthropic",i=r,s=f(i.messages)?i.messages:void 0,u=s?.create,c=s?.stream,p=s&&(typeof u=="function"||typeof c=="function")?new Proxy(s,{get(l,d,t){return d==="stream"&&typeof c=="function"?(a,...h)=>{let y=f(a)?a:{},v=Reflect.apply(c,l,[a,...h]);return f(v)?n.trackEmitterStream({provider:o,model:k(y,"model")},v,w=>A(w,o)):v}:d!=="create"||typeof u!="function"?Reflect.get(l,d,t):async(a,...h)=>{let y=f(a)?a:{};if(y.stream===!0)return n.trackStream({provider:o,model:k(y,"model")},()=>Promise.resolve(Reflect.apply(u,l,[a,...h])),w=>A(w,o));let v=()=>Reflect.apply(u,l,[a,...h]);return n.track({provider:o,model:k(y,"model")},()=>Promise.resolve(v()),w=>O(w,o))}}}):s;return new Proxy(r,{get(l,d,t){return d==="messages"&&p?p:d==="unwrap"?()=>r:d==="flushUsageTap"?()=>n.flush():Reflect.get(l,d,t)}})}0&&(module.exports={UsageTapObserver,extractAnthropicUsage,extractOpenAIUsage,observeAnthropic,observeOpenAI});
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["const DEFAULT_BASE_URL = \"https://api.usagetap.com\";\nconst MEDIA_TYPE = \"application/vnd.usagetap.v1+json\";\n\ntype UnknownRecord = Record<string, unknown>;\n\nexport type ObserverMetadata = {\n provider: string;\n model?: string;\n feature?: string;\n tags?: string[];\n};\n\nexport type ObservedUsage = {\n providerUsed?: string;\n modelUsed?: string;\n inputTokens?: number;\n responseTokens?: number;\n cachedInputTokens?: number;\n cacheWriteInputTokens?: number;\n reasoningTokens?: number;\n};\n\nexport type UsageTapObserverOptions = {\n apiKey?: string;\n customerId: string;\n baseUrl?: string;\n feature?: string;\n tags?: string[];\n fetch?: typeof globalThis.fetch;\n onTelemetryError?: (error: Error) => void;\n};\n\nexport type ObserveOpenAIOptions = UsageTapObserverOptions & {\n provider?: string;\n};\n\nexport type ObserveAnthropicOptions = UsageTapObserverOptions & {\n provider?: string;\n};\n\ntype CallBeginData = {\n callId: string;\n allowed?: { standard?: boolean; premium?: boolean };\n};\n\ntype Envelope<T> = {\n data?: T;\n error?: { message?: string };\n result?: { message?: string };\n};\n\nfunction isRecord(value: unknown): value is UnknownRecord {\n return Boolean(value) && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction finiteNonNegative(value: unknown): number | undefined {\n return typeof value === \"number\" && Number.isFinite(value) && value >= 0\n ? Math.round(value)\n : undefined;\n}\n\nfunction readRecord(parent: UnknownRecord, key: string): UnknownRecord | undefined {\n const value = parent[key];\n return isRecord(value) ? value : undefined;\n}\n\nfunction readString(parent: UnknownRecord, key: string): string | undefined {\n const value = parent[key];\n return typeof value === \"string\" && value.trim() ? value : undefined;\n}\n\nexport function extractOpenAIUsage(\n response: unknown,\n provider = \"openai\",\n): ObservedUsage | undefined {\n if (!isRecord(response)) return undefined;\n const usage = readRecord(response, \"usage\");\n if (!usage) return undefined;\n const promptDetails =\n readRecord(usage, \"prompt_tokens_details\") ??\n readRecord(usage, \"input_tokens_details\");\n const completionDetails =\n readRecord(usage, \"completion_tokens_details\") ??\n readRecord(usage, \"output_tokens_details\");\n\n return {\n providerUsed: provider,\n modelUsed: readString(response, \"model\"),\n inputTokens:\n finiteNonNegative(usage.prompt_tokens) ??\n finiteNonNegative(usage.input_tokens),\n responseTokens:\n finiteNonNegative(usage.completion_tokens) ??\n finiteNonNegative(usage.output_tokens),\n cachedInputTokens:\n finiteNonNegative(promptDetails?.cached_tokens) ??\n finiteNonNegative(usage.cached_tokens),\n cacheWriteInputTokens: finiteNonNegative(promptDetails?.cache_write_tokens),\n reasoningTokens: finiteNonNegative(completionDetails?.reasoning_tokens),\n };\n}\n\nexport function extractAnthropicUsage(\n response: unknown,\n provider = \"anthropic\",\n): ObservedUsage | undefined {\n if (!isRecord(response)) return undefined;\n const usage = readRecord(response, \"usage\");\n if (!usage) return undefined;\n return {\n providerUsed: provider,\n modelUsed: readString(response, \"model\"),\n inputTokens: finiteNonNegative(usage.input_tokens),\n responseTokens: finiteNonNegative(usage.output_tokens),\n cachedInputTokens: finiteNonNegative(usage.cache_read_input_tokens),\n cacheWriteInputTokens: finiteNonNegative(usage.cache_creation_input_tokens),\n };\n}\n\nfunction cleanError(error: unknown): { code: string; message: string } {\n if (error instanceof Error) {\n return {\n code: error.name.slice(0, 80) || \"PROVIDER_ERROR\",\n message: error.message.slice(0, 500) || \"Provider request failed\",\n };\n }\n return { code: \"PROVIDER_ERROR\", message: \"Provider request failed\" };\n}\n\nfunction randomId(): string {\n return typeof globalThis.crypto?.randomUUID === \"function\"\n ? globalThis.crypto.randomUUID()\n : `${Date.now()}-${Math.random().toString(36).slice(2)}`;\n}\n\nexport class UsageTapObserver {\n private readonly apiKey: string;\n private readonly customerId: string;\n private readonly baseUrl: string;\n private readonly defaultFeature?: string;\n private readonly defaultTags?: string[];\n private readonly fetchImpl: typeof globalThis.fetch;\n private readonly onTelemetryError?: (error: Error) => void;\n\n constructor(options: UsageTapObserverOptions) {\n const apiKey = options.apiKey ??\n (typeof process !== \"undefined\" ? process.env?.USAGETAP_API_KEY : undefined);\n if (!apiKey?.trim()) throw new Error(\"UsageTap observer requires an apiKey\");\n if (!options.customerId?.trim()) {\n throw new Error(\"UsageTap observer requires a customerId\");\n }\n this.apiKey = apiKey.trim();\n this.customerId = options.customerId.trim();\n this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/$/, \"\");\n this.defaultFeature = options.feature;\n this.defaultTags = options.tags;\n this.fetchImpl = options.fetch ?? globalThis.fetch;\n this.onTelemetryError = options.onTelemetryError;\n }\n\n private telemetryError(error: unknown): void {\n const normalized = error instanceof Error ? error : new Error(String(error));\n this.onTelemetryError?.(normalized);\n }\n\n private async post<T>(path: string, body: UnknownRecord): Promise<T> {\n const result = await this.fetchImpl(`${this.baseUrl}/${path}`, {\n method: \"POST\",\n headers: {\n Accept: MEDIA_TYPE,\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.apiKey}`,\n \"Idempotency-Key\": randomId(),\n \"X-Usage-SDK\": \"observer-js/0.1.2\",\n },\n body: JSON.stringify(body),\n });\n const envelope = (await result.json()) as Envelope<T>;\n if (!result.ok || !envelope.data) {\n throw new Error(\n envelope.error?.message ?? envelope.result?.message ?? `UsageTap returned ${result.status}`,\n );\n }\n return envelope.data;\n }\n\n /**\n * Observe one provider call. The callback receives no UsageTap data and the\n * observer never serializes the callback arguments, prompt, or response.\n * Telemetry fails open so provider behavior is unchanged.\n */\n async track<T>(\n metadata: ObserverMetadata,\n call: () => Promise<T>,\n extractUsage: (response: T) => ObservedUsage | undefined,\n ): Promise<T> {\n let begin: CallBeginData | undefined;\n try {\n begin = await this.post<CallBeginData>(\"call_begin\", {\n customerId: this.customerId,\n feature: metadata.feature ?? this.defaultFeature,\n tags: metadata.tags ?? this.defaultTags,\n idempotencyKey: randomId(),\n });\n } catch (error) {\n this.telemetryError(error);\n }\n\n try {\n const result = await call();\n if (begin?.callId) {\n try {\n await this.post(\"call_end\", {\n callId: begin.callId,\n providerUsed: metadata.provider,\n modelUsed: metadata.model,\n ...extractUsage(result),\n });\n } catch (error) {\n this.telemetryError(error);\n }\n }\n return result;\n } catch (error) {\n if (begin?.callId) {\n try {\n await this.post(\"call_end\", {\n callId: begin.callId,\n providerUsed: metadata.provider,\n modelUsed: metadata.model,\n error: cleanError(error),\n });\n } catch (telemetryError) {\n this.telemetryError(telemetryError);\n }\n }\n throw error;\n }\n }\n}\n\n/**\n * Wrap an OpenAI-compatible client without importing OpenAI or retaining\n * prompts. Non-streaming chat completions and Responses API calls are observed.\n * Streaming requests pass through unchanged in this first lightweight build.\n */\nexport function observeOpenAI<TClient extends object>(\n client: TClient,\n options: ObserveOpenAIOptions,\n): TClient & { unwrap(): TClient } {\n const observer = new UsageTapObserver(options);\n const provider = options.provider ?? \"openai\";\n const root = client as UnknownRecord;\n\n const wrapCreate = (\n resource: UnknownRecord | undefined,\n ): UnknownRecord | undefined => {\n if (!resource) return undefined;\n const original = resource.create;\n if (typeof original !== \"function\") return resource;\n return new Proxy(resource, {\n get(target, property, receiver): unknown {\n if (property !== \"create\") {\n return Reflect.get(target, property, receiver) as unknown;\n }\n return async (params: unknown, ...rest: unknown[]): Promise<unknown> => {\n const request = isRecord(params) ? params : {};\n const invoke = (): unknown =>\n Reflect.apply(original, target, [params, ...rest]) as unknown;\n if (request.stream === true) {\n return invoke();\n }\n return observer.track(\n {\n provider,\n model: readString(request, \"model\"),\n },\n () => Promise.resolve(invoke()),\n (response) => extractOpenAIUsage(response, provider),\n );\n };\n },\n });\n };\n\n const chat = isRecord(root.chat) ? root.chat : undefined;\n const wrappedCompletions = wrapCreate(\n chat && isRecord(chat.completions) ? chat.completions : undefined,\n );\n const wrappedChat = chat && wrappedCompletions\n ? new Proxy(chat, {\n get(target, property, receiver): unknown {\n return property === \"completions\"\n ? wrappedCompletions\n : Reflect.get(target, property, receiver) as unknown;\n },\n })\n : chat;\n const wrappedResponses = wrapCreate(\n isRecord(root.responses) ? root.responses : undefined,\n );\n\n return new Proxy(client, {\n get(target, property, receiver) {\n if (property === \"chat\" && wrappedChat) return wrappedChat;\n if (property === \"responses\" && wrappedResponses) return wrappedResponses;\n if (property === \"unwrap\") return () => client;\n return Reflect.get(target, property, receiver);\n },\n }) as TClient & { unwrap(): TClient };\n}\n\n/** Wrap a non-streaming Anthropic Messages client with metadata-only usage observation. */\nexport function observeAnthropic<TClient extends object>(\n client: TClient,\n options: ObserveAnthropicOptions,\n): TClient & { unwrap(): TClient } {\n const observer = new UsageTapObserver(options);\n const provider = options.provider ?? \"anthropic\";\n const root = client as UnknownRecord;\n const messages = isRecord(root.messages) ? root.messages : undefined;\n const original = messages?.create;\n const wrappedMessages =\n messages && typeof original === \"function\"\n ? new Proxy(messages, {\n get(target, property, receiver): unknown {\n if (property !== \"create\") {\n return Reflect.get(target, property, receiver) as unknown;\n }\n return async (\n params: unknown,\n ...rest: unknown[]\n ): Promise<unknown> => {\n const request = isRecord(params) ? params : {};\n const invoke = (): unknown =>\n Reflect.apply(original, target, [params, ...rest]) as unknown;\n if (request.stream === true) {\n return invoke();\n }\n return observer.track(\n {\n provider,\n model: readString(request, \"model\"),\n },\n () => Promise.resolve(invoke()),\n (response) => extractAnthropicUsage(response, provider),\n );\n };\n },\n })\n : messages;\n\n return new Proxy(client, {\n get(target, property, receiver) {\n if (property === \"messages\" && wrappedMessages) return wrappedMessages;\n if (property === \"unwrap\") return () => client;\n return Reflect.get(target, property, receiver);\n },\n }) as TClient & { unwrap(): TClient };\n}\n"],"mappings":"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","names":["src_exports","__export","UsageTapObserver","extractAnthropicUsage","extractOpenAIUsage","observeAnthropic","observeOpenAI","__toCommonJS","DEFAULT_BASE_URL","MEDIA_TYPE","isRecord","value","finiteNonNegative","readRecord","parent","key","readString","response","provider","usage","promptDetails","completionDetails","cleanError","error","randomId","options","apiKey","normalized","path","body","result","envelope","metadata","call","extractUsage","begin","telemetryError","client","observer","root","wrapCreate","resource","original","target","property","receiver","params","rest","request","invoke","chat","wrappedCompletions","wrappedChat","wrappedResponses","messages","wrappedMessages"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["const DEFAULT_BASE_URL = \"https://api.usagetap.com\";\nconst MEDIA_TYPE = \"application/vnd.usagetap.v1+json\";\n\ntype UnknownRecord = Record<string, unknown>;\n\nexport type ObserverMetadata = {\n provider: string;\n model?: string;\n feature?: string;\n tags?: string[];\n};\n\nexport type ObservedUsage = {\n providerUsed?: string;\n modelUsed?: string;\n inputTokens?: number;\n responseTokens?: number;\n cachedInputTokens?: number;\n cacheWriteInputTokens?: number;\n reasoningTokens?: number;\n};\n\nexport type UsageTapObserverOptions = {\n apiKey?: string;\n /** Optional multi-customer attribution. Trial keys infer their workspace. */\n customerId?: string;\n baseUrl?: string;\n feature?: string;\n tags?: string[];\n fetch?: typeof globalThis.fetch;\n onTelemetryError?: (error: Error) => void;\n};\n\nexport type ObserveOpenAIOptions = UsageTapObserverOptions & {\n provider?: string;\n};\n\nexport type ObserveAnthropicOptions = UsageTapObserverOptions & {\n provider?: string;\n};\n\ntype CallBeginData = {\n callId: string;\n allowed?: { standard?: boolean; premium?: boolean };\n};\n\ntype Envelope<T> = {\n data?: T;\n error?: { message?: string };\n result?: { message?: string };\n};\n\nfunction isRecord(value: unknown): value is UnknownRecord {\n return Boolean(value) && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction finiteNonNegative(value: unknown): number | undefined {\n return typeof value === \"number\" && Number.isFinite(value) && value >= 0\n ? Math.round(value)\n : undefined;\n}\n\nfunction readRecord(parent: UnknownRecord, key: string): UnknownRecord | undefined {\n const value = parent[key];\n return isRecord(value) ? value : undefined;\n}\n\nfunction readString(parent: UnknownRecord, key: string): string | undefined {\n const value = parent[key];\n return typeof value === \"string\" && value.trim() ? value : undefined;\n}\n\nexport function extractOpenAIUsage(\n response: unknown,\n provider = \"openai\",\n): ObservedUsage | undefined {\n if (!isRecord(response)) return undefined;\n const usage = readRecord(response, \"usage\");\n if (!usage) return undefined;\n const promptDetails =\n readRecord(usage, \"prompt_tokens_details\") ??\n readRecord(usage, \"input_tokens_details\");\n const completionDetails =\n readRecord(usage, \"completion_tokens_details\") ??\n readRecord(usage, \"output_tokens_details\");\n\n return {\n providerUsed: provider,\n modelUsed: readString(response, \"model\"),\n inputTokens:\n finiteNonNegative(usage.prompt_tokens) ??\n finiteNonNegative(usage.input_tokens),\n responseTokens:\n finiteNonNegative(usage.completion_tokens) ??\n finiteNonNegative(usage.output_tokens),\n cachedInputTokens:\n finiteNonNegative(promptDetails?.cached_tokens) ??\n finiteNonNegative(usage.cached_tokens),\n cacheWriteInputTokens: finiteNonNegative(promptDetails?.cache_write_tokens),\n reasoningTokens: finiteNonNegative(completionDetails?.reasoning_tokens),\n };\n}\n\nexport function extractAnthropicUsage(\n response: unknown,\n provider = \"anthropic\",\n): ObservedUsage | undefined {\n if (!isRecord(response)) return undefined;\n const usage = readRecord(response, \"usage\");\n if (!usage) return undefined;\n return {\n providerUsed: provider,\n modelUsed: readString(response, \"model\"),\n inputTokens: finiteNonNegative(usage.input_tokens),\n responseTokens: finiteNonNegative(usage.output_tokens),\n cachedInputTokens: finiteNonNegative(usage.cache_read_input_tokens),\n cacheWriteInputTokens: finiteNonNegative(usage.cache_creation_input_tokens),\n };\n}\n\nfunction mergeObservedUsage(\n current: ObservedUsage,\n next: ObservedUsage | undefined,\n): ObservedUsage {\n if (!next) return current;\n const definedNext = Object.fromEntries(\n Object.entries(next).filter(([, value]) => value !== undefined),\n ) as ObservedUsage;\n return { ...current, ...definedNext };\n}\n\nfunction extractOpenAIStreamUsage(\n event: unknown,\n provider: string,\n): ObservedUsage | undefined {\n if (!isRecord(event)) return undefined;\n const response = readRecord(event, \"response\");\n const source = response ?? event;\n const usage = extractOpenAIUsage(source, provider);\n const modelUsed = readString(source, \"model\");\n return usage ?? (modelUsed ? { providerUsed: provider, modelUsed } : undefined);\n}\n\nfunction extractAnthropicStreamUsage(\n event: unknown,\n provider: string,\n): ObservedUsage | undefined {\n if (!isRecord(event)) return undefined;\n const message = readRecord(event, \"message\");\n if (message) return extractAnthropicUsage(message, provider);\n const usage = readRecord(event, \"usage\");\n const delta = readRecord(event, \"delta\");\n const deltaUsage = delta ? readRecord(delta, \"usage\") : undefined;\n const sourceUsage = usage ?? deltaUsage;\n if (!sourceUsage) return undefined;\n return extractAnthropicUsage(\n { model: readString(event, \"model\"), usage: sourceUsage },\n provider,\n );\n}\n\nfunction isOpenAIUsageOnlyChunk(event: unknown): boolean {\n if (!isRecord(event) || !isRecord(event.usage)) return false;\n return Array.isArray(event.choices) && event.choices.length === 0;\n}\n\nfunction wrapAsyncIterableStream<TStream extends object>(\n stream: TStream,\n onValue: (value: unknown) => void,\n onComplete: () => Promise<void>,\n onError: (error: unknown) => Promise<void>,\n suppress?: (value: unknown) => boolean,\n): TStream {\n const iteratorFactory = (stream as AsyncIterable<unknown>)[Symbol.asyncIterator];\n if (typeof iteratorFactory !== \"function\") return stream;\n\n return new Proxy(stream, {\n get(target, property, receiver): unknown {\n if (property === Symbol.asyncIterator) {\n return (): AsyncIterableIterator<unknown> => {\n const iterator = iteratorFactory.call(target);\n return {\n async next(): Promise<IteratorResult<unknown>> {\n try {\n while (true) {\n const step = await iterator.next();\n if (step.done) {\n await onComplete();\n return step;\n }\n onValue(step.value);\n if (!suppress?.(step.value)) return step;\n }\n } catch (error) {\n await onError(error);\n throw error;\n }\n },\n async return(value?: unknown): Promise<IteratorResult<unknown>> {\n try {\n const result = iterator.return\n ? await iterator.return(value)\n : { done: true as const, value };\n await onComplete();\n return result;\n } catch (error) {\n await onError(error);\n throw error;\n }\n },\n async throw(error?: unknown): Promise<IteratorResult<unknown>> {\n try {\n if (iterator.throw) return await iterator.throw(error);\n throw error;\n } catch (caught) {\n await onError(caught);\n throw caught;\n }\n },\n [Symbol.asyncIterator]() {\n return this;\n },\n };\n };\n }\n const value = Reflect.get(target, property, target) as unknown;\n if (\n typeof value === \"function\" &&\n (property === \"toReadableStream\" || property === \"tee\")\n ) {\n // These OpenAI helpers consume `this[Symbol.asyncIterator]()`. Bind to\n // the proxy so their reads still pass through the metadata observer.\n return (value as (...args: unknown[]) => unknown).bind(receiver);\n }\n return typeof value === \"function\"\n ? (value as (...args: unknown[]) => unknown).bind(target)\n : value;\n },\n });\n}\n\nfunction cleanError(error: unknown): { code: string; message: string } {\n if (error instanceof Error) {\n return {\n code: error.name.slice(0, 80) || \"PROVIDER_ERROR\",\n // Provider messages can echo prompt or response content. Keep telemetry\n // metadata-only by reporting a stable generic message instead.\n message: \"Provider request failed\",\n };\n }\n return { code: \"PROVIDER_ERROR\", message: \"Provider request failed\" };\n}\n\nfunction isStreamFailureEvent(event: unknown): boolean {\n if (!isRecord(event)) return false;\n const type = readString(event, \"type\")?.toLowerCase();\n return type === \"error\" || type?.endsWith(\".failed\") === true;\n}\n\nfunction randomId(): string {\n return typeof globalThis.crypto?.randomUUID === \"function\"\n ? globalThis.crypto.randomUUID()\n : `${Date.now()}-${Math.random().toString(36).slice(2)}`;\n}\n\nexport class UsageTapObserver {\n private readonly apiKey: string;\n private readonly customerId?: string;\n private readonly baseUrl: string;\n private readonly defaultFeature?: string;\n private readonly defaultTags?: string[];\n private readonly fetchImpl: typeof globalThis.fetch;\n private readonly onTelemetryError?: (error: Error) => void;\n private readonly pendingTelemetry = new Set<Promise<void>>();\n\n constructor(options: UsageTapObserverOptions = {}) {\n const apiKey = options.apiKey ??\n (typeof process !== \"undefined\" ? process.env?.USAGETAP_API_KEY : undefined);\n if (!apiKey?.trim()) throw new Error(\"UsageTap observer requires an apiKey\");\n this.apiKey = apiKey.trim();\n this.customerId = options.customerId?.trim() || undefined;\n this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/$/, \"\");\n this.defaultFeature = options.feature;\n this.defaultTags = options.tags;\n this.fetchImpl = options.fetch ?? globalThis.fetch;\n this.onTelemetryError = options.onTelemetryError;\n }\n\n private telemetryError(error: unknown): void {\n const normalized = error instanceof Error ? error : new Error(String(error));\n this.onTelemetryError?.(normalized);\n }\n\n private queueTelemetry(task: Promise<unknown>): void {\n const tracked: Promise<void> = task\n .then(() => undefined)\n .catch((error: unknown) => this.telemetryError(error))\n .finally(() => this.pendingTelemetry.delete(tracked));\n this.pendingTelemetry.add(tracked);\n }\n\n /** Wait for queued metadata delivery before a short-lived process exits. */\n async flush(): Promise<void> {\n while (this.pendingTelemetry.size) {\n await Promise.allSettled([...this.pendingTelemetry]);\n }\n }\n\n private async post<T>(path: string, body: UnknownRecord): Promise<T> {\n const result = await this.fetchImpl(`${this.baseUrl}/${path}`, {\n method: \"POST\",\n headers: {\n Accept: MEDIA_TYPE,\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.apiKey}`,\n \"Idempotency-Key\": randomId(),\n \"X-Usage-SDK\": \"observer-js/0.1.4\",\n },\n body: JSON.stringify(body),\n });\n const envelope = (await result.json()) as Envelope<T>;\n if (!result.ok || !envelope.data) {\n throw new Error(\n envelope.error?.message ?? envelope.result?.message ?? `UsageTap returned ${result.status}`,\n );\n }\n return envelope.data;\n }\n\n /**\n * Observe one provider call. The callback receives no UsageTap data and the\n * observer never serializes the callback arguments, prompt, or response.\n * Telemetry fails open so provider behavior is unchanged.\n */\n async track<T>(\n metadata: ObserverMetadata,\n call: () => Promise<T>,\n extractUsage: (response: T) => ObservedUsage | undefined,\n ): Promise<T> {\n const beginPromise = this.post<CallBeginData>(\"call_begin\", {\n ...(this.customerId ? { customerId: this.customerId } : {}),\n feature: metadata.feature ?? this.defaultFeature,\n tags: metadata.tags ?? this.defaultTags,\n idempotencyKey: randomId(),\n }).catch((error: unknown) => {\n this.telemetryError(error);\n return undefined;\n });\n\n try {\n const result = await call();\n const usage = extractUsage(result);\n this.queueTelemetry(\n beginPromise.then((begin) => {\n if (!begin?.callId) return undefined;\n return this.post(\"call_end\", {\n callId: begin.callId,\n providerUsed: metadata.provider,\n modelUsed: metadata.model,\n ...usage,\n });\n }),\n );\n return result;\n } catch (error) {\n this.queueTelemetry(\n beginPromise.then((begin) => {\n if (!begin?.callId) return undefined;\n return this.post(\"call_end\", {\n callId: begin.callId,\n providerUsed: metadata.provider,\n modelUsed: metadata.model,\n error: cleanError(error),\n });\n }),\n );\n throw error;\n }\n }\n\n /** Observe metadata-bearing stream events without retaining content chunks. */\n async trackStream<TStream extends object>(\n metadata: ObserverMetadata,\n call: () => Promise<TStream>,\n extractUsage: (event: unknown) => ObservedUsage | undefined,\n suppress?: (event: unknown) => boolean,\n ): Promise<TStream> {\n const beginPromise = this.post<CallBeginData>(\"call_begin\", {\n ...(this.customerId ? { customerId: this.customerId } : {}),\n feature: metadata.feature ?? this.defaultFeature,\n tags: metadata.tags ?? this.defaultTags,\n idempotencyKey: randomId(),\n }).catch((error: unknown) => {\n this.telemetryError(error);\n return undefined;\n });\n\n let stream: TStream;\n try {\n stream = await call();\n } catch (error) {\n const begin = await beginPromise;\n if (begin?.callId) {\n try {\n await this.post(\"call_end\", {\n callId: begin.callId,\n providerUsed: metadata.provider,\n modelUsed: metadata.model,\n error: cleanError(error),\n });\n } catch (telemetryError) {\n this.telemetryError(telemetryError);\n }\n }\n throw error;\n }\n\n let usage: ObservedUsage = {\n providerUsed: metadata.provider,\n modelUsed: metadata.model,\n };\n let streamFailed = false;\n let finished = false;\n const finish = (error?: unknown): Promise<void> => {\n if (finished) return Promise.resolve();\n finished = true;\n this.queueTelemetry(\n beginPromise.then((begin) => {\n if (!begin?.callId) return undefined;\n return this.post(\"call_end\", {\n callId: begin.callId,\n ...usage,\n ...(error === undefined ? {} : { error: cleanError(error) }),\n });\n }),\n );\n return Promise.resolve();\n };\n\n return wrapAsyncIterableStream(\n stream,\n (event) => {\n usage = mergeObservedUsage(usage, extractUsage(event));\n streamFailed ||= isStreamFailureEvent(event);\n },\n () => finish(streamFailed ? new Error(\"Provider stream failed\") : undefined),\n (error) => finish(error),\n suppress,\n );\n }\n\n /** Observe SDK stream helpers that expose lifecycle events synchronously. */\n trackEmitterStream<TStream extends object>(\n metadata: ObserverMetadata,\n stream: TStream,\n extractUsage: (event: unknown) => ObservedUsage | undefined,\n ): TStream {\n const emitter = stream as UnknownRecord;\n const on = emitter.on;\n if (typeof on !== \"function\") return stream;\n\n const beginPromise = this.post<CallBeginData>(\"call_begin\", {\n ...(this.customerId ? { customerId: this.customerId } : {}),\n feature: metadata.feature ?? this.defaultFeature,\n tags: metadata.tags ?? this.defaultTags,\n idempotencyKey: randomId(),\n }).catch((error: unknown) => {\n this.telemetryError(error);\n return undefined;\n });\n let usage: ObservedUsage = {\n providerUsed: metadata.provider,\n modelUsed: metadata.model,\n };\n let finished = false;\n const finish = (error?: unknown): Promise<void> => {\n if (finished) return Promise.resolve();\n finished = true;\n this.queueTelemetry(\n beginPromise.then((begin) => {\n if (!begin?.callId) return undefined;\n return this.post(\"call_end\", {\n callId: begin.callId,\n ...usage,\n ...(error === undefined ? {} : { error: cleanError(error) }),\n });\n }),\n );\n return Promise.resolve();\n };\n const observe = (event: unknown): void => {\n usage = mergeObservedUsage(usage, extractUsage(event));\n };\n Reflect.apply(on, stream, [\"streamEvent\", observe]);\n Reflect.apply(on, stream, [\"message\", observe]);\n Reflect.apply(on, stream, [\"end\", () => void finish()]);\n Reflect.apply(on, stream, [\"error\", (error: unknown) => void finish(error)]);\n Reflect.apply(on, stream, [\"abort\", () => void finish(new Error(\"Provider stream aborted\"))]);\n return stream;\n }\n}\n\n/**\n * Wrap an OpenAI-compatible client without importing OpenAI or retaining\n * prompts. Non-streaming chat completions and Responses API calls are observed.\n * Streaming requests pass through unchanged in this first lightweight build.\n */\nexport function observeOpenAI<TClient extends object>(\n client: TClient,\n options: ObserveOpenAIOptions = {},\n): TClient & { unwrap(): TClient; flushUsageTap(): Promise<void> } {\n const observer = new UsageTapObserver(options);\n const provider = options.provider ?? \"openai\";\n const root = client as UnknownRecord;\n\n const wrapCreate = (\n resource: UnknownRecord | undefined,\n kind: \"chat\" | \"responses\",\n ): UnknownRecord | undefined => {\n if (!resource) return undefined;\n const original = resource.create;\n if (typeof original !== \"function\") return resource;\n return new Proxy(resource, {\n get(target, property, receiver): unknown {\n if (property !== \"create\") {\n return Reflect.get(target, property, receiver) as unknown;\n }\n return async (params: unknown, ...rest: unknown[]): Promise<unknown> => {\n const request = isRecord(params) ? params : {};\n if (request.stream === true) {\n const streamOptions = readRecord(request, \"stream_options\");\n const alreadyIncludesUsage = streamOptions?.include_usage === true;\n const providerParams = kind === \"chat\" && !alreadyIncludesUsage\n ? {\n ...request,\n stream_options: { ...streamOptions, include_usage: true },\n }\n : params;\n return observer.trackStream(\n { provider, model: readString(request, \"model\") },\n () => Promise.resolve(\n Reflect.apply(original, target, [providerParams, ...rest]) as object,\n ),\n (event) => extractOpenAIStreamUsage(event, provider),\n kind === \"chat\" && !alreadyIncludesUsage\n ? isOpenAIUsageOnlyChunk\n : undefined,\n );\n }\n const invoke = (): unknown =>\n Reflect.apply(original, target, [params, ...rest]) as unknown;\n return observer.track(\n {\n provider,\n model: readString(request, \"model\"),\n },\n () => Promise.resolve(invoke()),\n (response) => extractOpenAIUsage(response, provider),\n );\n };\n },\n });\n };\n\n const chat = isRecord(root.chat) ? root.chat : undefined;\n const wrappedCompletions = wrapCreate(\n chat && isRecord(chat.completions) ? chat.completions : undefined,\n \"chat\",\n );\n const wrappedChat = chat && wrappedCompletions\n ? new Proxy(chat, {\n get(target, property, receiver): unknown {\n return property === \"completions\"\n ? wrappedCompletions\n : Reflect.get(target, property, receiver) as unknown;\n },\n })\n : chat;\n const wrappedResponses = wrapCreate(\n isRecord(root.responses) ? root.responses : undefined,\n \"responses\",\n );\n\n return new Proxy(client, {\n get(target, property, receiver) {\n if (property === \"chat\" && wrappedChat) return wrappedChat;\n if (property === \"responses\" && wrappedResponses) return wrappedResponses;\n if (property === \"unwrap\") return () => client;\n if (property === \"flushUsageTap\") return () => observer.flush();\n return Reflect.get(target, property, receiver);\n },\n }) as TClient & { unwrap(): TClient; flushUsageTap(): Promise<void> };\n}\n\n/** Wrap a non-streaming Anthropic Messages client with metadata-only usage observation. */\nexport function observeAnthropic<TClient extends object>(\n client: TClient,\n options: ObserveAnthropicOptions = {},\n): TClient & { unwrap(): TClient; flushUsageTap(): Promise<void> } {\n const observer = new UsageTapObserver(options);\n const provider = options.provider ?? \"anthropic\";\n const root = client as UnknownRecord;\n const messages = isRecord(root.messages) ? root.messages : undefined;\n const original = messages?.create;\n const originalStream = messages?.stream;\n const wrappedMessages =\n messages && (typeof original === \"function\" || typeof originalStream === \"function\")\n ? new Proxy(messages, {\n get(target, property, receiver): unknown {\n if (property === \"stream\" && typeof originalStream === \"function\") {\n return (params: unknown, ...rest: unknown[]): unknown => {\n const request = isRecord(params) ? params : {};\n const stream = Reflect.apply(originalStream, target, [\n params,\n ...rest,\n ]) as unknown;\n if (!isRecord(stream)) return stream;\n return observer.trackEmitterStream(\n { provider, model: readString(request, \"model\") },\n stream,\n (event) => extractAnthropicStreamUsage(event, provider),\n );\n };\n }\n if (property !== \"create\" || typeof original !== \"function\") {\n return Reflect.get(target, property, receiver) as unknown;\n }\n return async (\n params: unknown,\n ...rest: unknown[]\n ): Promise<unknown> => {\n const request = isRecord(params) ? params : {};\n if (request.stream === true) {\n return observer.trackStream(\n { provider, model: readString(request, \"model\") },\n () => Promise.resolve(\n Reflect.apply(original, target, [params, ...rest]) as object,\n ),\n (event) => extractAnthropicStreamUsage(event, provider),\n );\n }\n const invoke = (): unknown =>\n Reflect.apply(original, target, [params, ...rest]) as unknown;\n return observer.track(\n {\n provider,\n model: readString(request, \"model\"),\n },\n () => Promise.resolve(invoke()),\n (response) => extractAnthropicUsage(response, provider),\n );\n };\n },\n })\n : messages;\n\n return new Proxy(client, {\n get(target, property, receiver) {\n if (property === \"messages\" && wrappedMessages) return wrappedMessages;\n if (property === \"unwrap\") return () => client;\n if (property === \"flushUsageTap\") return () => observer.flush();\n return Reflect.get(target, property, receiver);\n },\n }) as TClient & { unwrap(): TClient; flushUsageTap(): Promise<void> };\n}\n"],"mappings":"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","names":["src_exports","__export","UsageTapObserver","extractAnthropicUsage","extractOpenAIUsage","observeAnthropic","observeOpenAI","__toCommonJS","DEFAULT_BASE_URL","MEDIA_TYPE","isRecord","value","finiteNonNegative","readRecord","parent","key","readString","response","provider","usage","promptDetails","completionDetails","mergeObservedUsage","current","next","definedNext","extractOpenAIStreamUsage","event","source","modelUsed","extractAnthropicStreamUsage","message","delta","deltaUsage","sourceUsage","isOpenAIUsageOnlyChunk","wrapAsyncIterableStream","stream","onValue","onComplete","onError","suppress","iteratorFactory","target","property","receiver","iterator","step","error","result","caught","cleanError","isStreamFailureEvent","type","randomId","options","apiKey","normalized","task","tracked","path","body","envelope","metadata","call","extractUsage","beginPromise","begin","telemetryError","streamFailed","finished","finish","on","observe","client","observer","root","wrapCreate","resource","kind","original","params","rest","request","streamOptions","alreadyIncludesUsage","providerParams","invoke","chat","wrappedCompletions","wrappedChat","wrappedResponses","messages","originalStream","wrappedMessages"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -15,7 +15,8 @@ type ObservedUsage = {
|
|
|
15
15
|
};
|
|
16
16
|
type UsageTapObserverOptions = {
|
|
17
17
|
apiKey?: string;
|
|
18
|
-
|
|
18
|
+
/** Optional multi-customer attribution. Trial keys infer their workspace. */
|
|
19
|
+
customerId?: string;
|
|
19
20
|
baseUrl?: string;
|
|
20
21
|
feature?: string;
|
|
21
22
|
tags?: string[];
|
|
@@ -32,14 +33,18 @@ declare function extractOpenAIUsage(response: unknown, provider?: string): Obser
|
|
|
32
33
|
declare function extractAnthropicUsage(response: unknown, provider?: string): ObservedUsage | undefined;
|
|
33
34
|
declare class UsageTapObserver {
|
|
34
35
|
private readonly apiKey;
|
|
35
|
-
private readonly customerId
|
|
36
|
+
private readonly customerId?;
|
|
36
37
|
private readonly baseUrl;
|
|
37
38
|
private readonly defaultFeature?;
|
|
38
39
|
private readonly defaultTags?;
|
|
39
40
|
private readonly fetchImpl;
|
|
40
41
|
private readonly onTelemetryError?;
|
|
41
|
-
|
|
42
|
+
private readonly pendingTelemetry;
|
|
43
|
+
constructor(options?: UsageTapObserverOptions);
|
|
42
44
|
private telemetryError;
|
|
45
|
+
private queueTelemetry;
|
|
46
|
+
/** Wait for queued metadata delivery before a short-lived process exits. */
|
|
47
|
+
flush(): Promise<void>;
|
|
43
48
|
private post;
|
|
44
49
|
/**
|
|
45
50
|
* Observe one provider call. The callback receives no UsageTap data and the
|
|
@@ -47,18 +52,24 @@ declare class UsageTapObserver {
|
|
|
47
52
|
* Telemetry fails open so provider behavior is unchanged.
|
|
48
53
|
*/
|
|
49
54
|
track<T>(metadata: ObserverMetadata, call: () => Promise<T>, extractUsage: (response: T) => ObservedUsage | undefined): Promise<T>;
|
|
55
|
+
/** Observe metadata-bearing stream events without retaining content chunks. */
|
|
56
|
+
trackStream<TStream extends object>(metadata: ObserverMetadata, call: () => Promise<TStream>, extractUsage: (event: unknown) => ObservedUsage | undefined, suppress?: (event: unknown) => boolean): Promise<TStream>;
|
|
57
|
+
/** Observe SDK stream helpers that expose lifecycle events synchronously. */
|
|
58
|
+
trackEmitterStream<TStream extends object>(metadata: ObserverMetadata, stream: TStream, extractUsage: (event: unknown) => ObservedUsage | undefined): TStream;
|
|
50
59
|
}
|
|
51
60
|
/**
|
|
52
61
|
* Wrap an OpenAI-compatible client without importing OpenAI or retaining
|
|
53
62
|
* prompts. Non-streaming chat completions and Responses API calls are observed.
|
|
54
63
|
* Streaming requests pass through unchanged in this first lightweight build.
|
|
55
64
|
*/
|
|
56
|
-
declare function observeOpenAI<TClient extends object>(client: TClient, options
|
|
65
|
+
declare function observeOpenAI<TClient extends object>(client: TClient, options?: ObserveOpenAIOptions): TClient & {
|
|
57
66
|
unwrap(): TClient;
|
|
67
|
+
flushUsageTap(): Promise<void>;
|
|
58
68
|
};
|
|
59
69
|
/** Wrap a non-streaming Anthropic Messages client with metadata-only usage observation. */
|
|
60
|
-
declare function observeAnthropic<TClient extends object>(client: TClient, options
|
|
70
|
+
declare function observeAnthropic<TClient extends object>(client: TClient, options?: ObserveAnthropicOptions): TClient & {
|
|
61
71
|
unwrap(): TClient;
|
|
72
|
+
flushUsageTap(): Promise<void>;
|
|
62
73
|
};
|
|
63
74
|
|
|
64
75
|
export { type ObserveAnthropicOptions, type ObserveOpenAIOptions, type ObservedUsage, type ObserverMetadata, UsageTapObserver, type UsageTapObserverOptions, extractAnthropicUsage, extractOpenAIUsage, observeAnthropic, observeOpenAI };
|
package/dist/index.d.ts
CHANGED
|
@@ -15,7 +15,8 @@ type ObservedUsage = {
|
|
|
15
15
|
};
|
|
16
16
|
type UsageTapObserverOptions = {
|
|
17
17
|
apiKey?: string;
|
|
18
|
-
|
|
18
|
+
/** Optional multi-customer attribution. Trial keys infer their workspace. */
|
|
19
|
+
customerId?: string;
|
|
19
20
|
baseUrl?: string;
|
|
20
21
|
feature?: string;
|
|
21
22
|
tags?: string[];
|
|
@@ -32,14 +33,18 @@ declare function extractOpenAIUsage(response: unknown, provider?: string): Obser
|
|
|
32
33
|
declare function extractAnthropicUsage(response: unknown, provider?: string): ObservedUsage | undefined;
|
|
33
34
|
declare class UsageTapObserver {
|
|
34
35
|
private readonly apiKey;
|
|
35
|
-
private readonly customerId
|
|
36
|
+
private readonly customerId?;
|
|
36
37
|
private readonly baseUrl;
|
|
37
38
|
private readonly defaultFeature?;
|
|
38
39
|
private readonly defaultTags?;
|
|
39
40
|
private readonly fetchImpl;
|
|
40
41
|
private readonly onTelemetryError?;
|
|
41
|
-
|
|
42
|
+
private readonly pendingTelemetry;
|
|
43
|
+
constructor(options?: UsageTapObserverOptions);
|
|
42
44
|
private telemetryError;
|
|
45
|
+
private queueTelemetry;
|
|
46
|
+
/** Wait for queued metadata delivery before a short-lived process exits. */
|
|
47
|
+
flush(): Promise<void>;
|
|
43
48
|
private post;
|
|
44
49
|
/**
|
|
45
50
|
* Observe one provider call. The callback receives no UsageTap data and the
|
|
@@ -47,18 +52,24 @@ declare class UsageTapObserver {
|
|
|
47
52
|
* Telemetry fails open so provider behavior is unchanged.
|
|
48
53
|
*/
|
|
49
54
|
track<T>(metadata: ObserverMetadata, call: () => Promise<T>, extractUsage: (response: T) => ObservedUsage | undefined): Promise<T>;
|
|
55
|
+
/** Observe metadata-bearing stream events without retaining content chunks. */
|
|
56
|
+
trackStream<TStream extends object>(metadata: ObserverMetadata, call: () => Promise<TStream>, extractUsage: (event: unknown) => ObservedUsage | undefined, suppress?: (event: unknown) => boolean): Promise<TStream>;
|
|
57
|
+
/** Observe SDK stream helpers that expose lifecycle events synchronously. */
|
|
58
|
+
trackEmitterStream<TStream extends object>(metadata: ObserverMetadata, stream: TStream, extractUsage: (event: unknown) => ObservedUsage | undefined): TStream;
|
|
50
59
|
}
|
|
51
60
|
/**
|
|
52
61
|
* Wrap an OpenAI-compatible client without importing OpenAI or retaining
|
|
53
62
|
* prompts. Non-streaming chat completions and Responses API calls are observed.
|
|
54
63
|
* Streaming requests pass through unchanged in this first lightweight build.
|
|
55
64
|
*/
|
|
56
|
-
declare function observeOpenAI<TClient extends object>(client: TClient, options
|
|
65
|
+
declare function observeOpenAI<TClient extends object>(client: TClient, options?: ObserveOpenAIOptions): TClient & {
|
|
57
66
|
unwrap(): TClient;
|
|
67
|
+
flushUsageTap(): Promise<void>;
|
|
58
68
|
};
|
|
59
69
|
/** Wrap a non-streaming Anthropic Messages client with metadata-only usage observation. */
|
|
60
|
-
declare function observeAnthropic<TClient extends object>(client: TClient, options
|
|
70
|
+
declare function observeAnthropic<TClient extends object>(client: TClient, options?: ObserveAnthropicOptions): TClient & {
|
|
61
71
|
unwrap(): TClient;
|
|
72
|
+
flushUsageTap(): Promise<void>;
|
|
62
73
|
};
|
|
63
74
|
|
|
64
75
|
export { type ObserveAnthropicOptions, type ObserveOpenAIOptions, type ObservedUsage, type ObserverMetadata, UsageTapObserver, type UsageTapObserverOptions, extractAnthropicUsage, extractOpenAIUsage, observeAnthropic, observeOpenAI };
|
package/dist/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var
|
|
1
|
+
var D="https://api.usagetap.com",K="application/vnd.usagetap.v1+json";function f(r){return!!r&&typeof r=="object"&&!Array.isArray(r)}function m(r){return typeof r=="number"&&Number.isFinite(r)&&r>=0?Math.round(r):void 0}function g(r,n){let e=r[n];return f(e)?e:void 0}function k(r,n){let e=r[n];return typeof e=="string"&&e.trim()?e:void 0}function x(r,n="openai"){if(!f(r))return;let e=g(r,"usage");if(!e)return;let o=g(e,"prompt_tokens_details")??g(e,"input_tokens_details"),d=g(e,"completion_tokens_details")??g(e,"output_tokens_details");return{providerUsed:n,modelUsed:k(r,"model"),inputTokens:m(e.prompt_tokens)??m(e.input_tokens),responseTokens:m(e.completion_tokens)??m(e.output_tokens),cachedInputTokens:m(o?.cached_tokens)??m(e.cached_tokens),cacheWriteInputTokens:m(o?.cache_write_tokens),reasoningTokens:m(d?.reasoning_tokens)}}function O(r,n="anthropic"){if(!f(r))return;let e=g(r,"usage");if(e)return{providerUsed:n,modelUsed:k(r,"model"),inputTokens:m(e.input_tokens),responseTokens:m(e.output_tokens),cachedInputTokens:m(e.cache_read_input_tokens),cacheWriteInputTokens:m(e.cache_creation_input_tokens)}}function E(r,n){if(!n)return r;let e=Object.fromEntries(Object.entries(n).filter(([,o])=>o!==void 0));return{...r,...e}}function q(r,n){if(!f(r))return;let o=g(r,"response")??r,d=x(o,n),s=k(o,"model");return d??(s?{providerUsed:n,modelUsed:s}:void 0)}function S(r,n){if(!f(r))return;let e=g(r,"message");if(e)return O(e,n);let o=g(r,"usage"),d=g(r,"delta"),s=d?g(d,"usage"):void 0,a=o??s;if(a)return O({model:k(r,"model"),usage:a},n)}function F(r){return!f(r)||!f(r.usage)?!1:Array.isArray(r.choices)&&r.choices.length===0}function M(r,n,e,o,d){let s=r[Symbol.asyncIterator];return typeof s!="function"?r:new Proxy(r,{get(a,c,p){if(c===Symbol.asyncIterator)return()=>{let u=s.call(a);return{async next(){try{for(;;){let t=await u.next();if(t.done)return await e(),t;if(n(t.value),!d?.(t.value))return t}}catch(t){throw await o(t),t}},async return(t){try{let i=u.return?await u.return(t):{done:!0,value:t};return await e(),i}catch(i){throw await o(i),i}},async throw(t){try{if(u.throw)return await u.throw(t);throw t}catch(i){throw await o(i),i}},[Symbol.asyncIterator](){return this}}};let l=Reflect.get(a,c,a);return typeof l=="function"&&(c==="toReadableStream"||c==="tee")?l.bind(p):typeof l=="function"?l.bind(a):l}})}function U(r){return r instanceof Error?{code:r.name.slice(0,80)||"PROVIDER_ERROR",message:"Provider request failed"}:{code:"PROVIDER_ERROR",message:"Provider request failed"}}function B(r){if(!f(r))return!1;let n=k(r,"type")?.toLowerCase();return n==="error"||n?.endsWith(".failed")===!0}function I(){return typeof globalThis.crypto?.randomUUID=="function"?globalThis.crypto.randomUUID():`${Date.now()}-${Math.random().toString(36).slice(2)}`}var P=class{apiKey;customerId;baseUrl;defaultFeature;defaultTags;fetchImpl;onTelemetryError;pendingTelemetry=new Set;constructor(n={}){let e=n.apiKey??(typeof process<"u"?process.env?.USAGETAP_API_KEY:void 0);if(!e?.trim())throw new Error("UsageTap observer requires an apiKey");this.apiKey=e.trim(),this.customerId=n.customerId?.trim()||void 0,this.baseUrl=(n.baseUrl??D).replace(/\/$/,""),this.defaultFeature=n.feature,this.defaultTags=n.tags,this.fetchImpl=n.fetch??globalThis.fetch,this.onTelemetryError=n.onTelemetryError}telemetryError(n){let e=n instanceof Error?n:new Error(String(n));this.onTelemetryError?.(e)}queueTelemetry(n){let e=n.then(()=>{}).catch(o=>this.telemetryError(o)).finally(()=>this.pendingTelemetry.delete(e));this.pendingTelemetry.add(e)}async flush(){for(;this.pendingTelemetry.size;)await Promise.allSettled([...this.pendingTelemetry])}async post(n,e){let o=await this.fetchImpl(`${this.baseUrl}/${n}`,{method:"POST",headers:{Accept:K,"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`,"Idempotency-Key":I(),"X-Usage-SDK":"observer-js/0.1.4"},body:JSON.stringify(e)}),d=await o.json();if(!o.ok||!d.data)throw new Error(d.error?.message??d.result?.message??`UsageTap returned ${o.status}`);return d.data}async track(n,e,o){let d=this.post("call_begin",{...this.customerId?{customerId:this.customerId}:{},feature:n.feature??this.defaultFeature,tags:n.tags??this.defaultTags,idempotencyKey:I()}).catch(s=>{this.telemetryError(s)});try{let s=await e(),a=o(s);return this.queueTelemetry(d.then(c=>{if(c?.callId)return this.post("call_end",{callId:c.callId,providerUsed:n.provider,modelUsed:n.model,...a})})),s}catch(s){throw this.queueTelemetry(d.then(a=>{if(a?.callId)return this.post("call_end",{callId:a.callId,providerUsed:n.provider,modelUsed:n.model,error:U(s)})})),s}}async trackStream(n,e,o,d){let s=this.post("call_begin",{...this.customerId?{customerId:this.customerId}:{},feature:n.feature??this.defaultFeature,tags:n.tags??this.defaultTags,idempotencyKey:I()}).catch(t=>{this.telemetryError(t)}),a;try{a=await e()}catch(t){let i=await s;if(i?.callId)try{await this.post("call_end",{callId:i.callId,providerUsed:n.provider,modelUsed:n.model,error:U(t)})}catch(h){this.telemetryError(h)}throw t}let c={providerUsed:n.provider,modelUsed:n.model},p=!1,l=!1,u=t=>(l||(l=!0,this.queueTelemetry(s.then(i=>{if(i?.callId)return this.post("call_end",{callId:i.callId,...c,...t===void 0?{}:{error:U(t)}})}))),Promise.resolve());return M(a,t=>{c=E(c,o(t)),p||=B(t)},()=>u(p?new Error("Provider stream failed"):void 0),t=>u(t),d)}trackEmitterStream(n,e,o){let s=e.on;if(typeof s!="function")return e;let a=this.post("call_begin",{...this.customerId?{customerId:this.customerId}:{},feature:n.feature??this.defaultFeature,tags:n.tags??this.defaultTags,idempotencyKey:I()}).catch(t=>{this.telemetryError(t)}),c={providerUsed:n.provider,modelUsed:n.model},p=!1,l=t=>(p||(p=!0,this.queueTelemetry(a.then(i=>{if(i?.callId)return this.post("call_end",{callId:i.callId,...c,...t===void 0?{}:{error:U(t)}})}))),Promise.resolve()),u=t=>{c=E(c,o(t))};return Reflect.apply(s,e,["streamEvent",u]),Reflect.apply(s,e,["message",u]),Reflect.apply(s,e,["end",()=>void l()]),Reflect.apply(s,e,["error",t=>void l(t)]),Reflect.apply(s,e,["abort",()=>void l(new Error("Provider stream aborted"))]),e}};function $(r,n={}){let e=new P(n),o=n.provider??"openai",d=r,s=(u,t)=>{if(!u)return;let i=u.create;return typeof i!="function"?u:new Proxy(u,{get(h,y,v){return y!=="create"?Reflect.get(h,y,v):async(w,...R)=>{let T=f(w)?w:{};if(T.stream===!0){let b=g(T,"stream_options"),_=b?.include_usage===!0,C=t==="chat"&&!_?{...T,stream_options:{...b,include_usage:!0}}:w;return e.trackStream({provider:o,model:k(T,"model")},()=>Promise.resolve(Reflect.apply(i,h,[C,...R])),j=>q(j,o),t==="chat"&&!_?F:void 0)}let A=()=>Reflect.apply(i,h,[w,...R]);return e.track({provider:o,model:k(T,"model")},()=>Promise.resolve(A()),b=>x(b,o))}}})},a=f(d.chat)?d.chat:void 0,c=s(a&&f(a.completions)?a.completions:void 0,"chat"),p=a&&c?new Proxy(a,{get(u,t,i){return t==="completions"?c:Reflect.get(u,t,i)}}):a,l=s(f(d.responses)?d.responses:void 0,"responses");return new Proxy(r,{get(u,t,i){return t==="chat"&&p?p:t==="responses"&&l?l:t==="unwrap"?()=>r:t==="flushUsageTap"?()=>e.flush():Reflect.get(u,t,i)}})}function N(r,n={}){let e=new P(n),o=n.provider??"anthropic",d=r,s=f(d.messages)?d.messages:void 0,a=s?.create,c=s?.stream,p=s&&(typeof a=="function"||typeof c=="function")?new Proxy(s,{get(l,u,t){return u==="stream"&&typeof c=="function"?(i,...h)=>{let y=f(i)?i:{},v=Reflect.apply(c,l,[i,...h]);return f(v)?e.trackEmitterStream({provider:o,model:k(y,"model")},v,w=>S(w,o)):v}:u!=="create"||typeof a!="function"?Reflect.get(l,u,t):async(i,...h)=>{let y=f(i)?i:{};if(y.stream===!0)return e.trackStream({provider:o,model:k(y,"model")},()=>Promise.resolve(Reflect.apply(a,l,[i,...h])),w=>S(w,o));let v=()=>Reflect.apply(a,l,[i,...h]);return e.track({provider:o,model:k(y,"model")},()=>Promise.resolve(v()),w=>O(w,o))}}}):s;return new Proxy(r,{get(l,u,t){return u==="messages"&&p?p:u==="unwrap"?()=>r:u==="flushUsageTap"?()=>e.flush():Reflect.get(l,u,t)}})}export{P as UsageTapObserver,O as extractAnthropicUsage,x as extractOpenAIUsage,N as observeAnthropic,$ as observeOpenAI};
|
|
2
2
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["const DEFAULT_BASE_URL = \"https://api.usagetap.com\";\nconst MEDIA_TYPE = \"application/vnd.usagetap.v1+json\";\n\ntype UnknownRecord = Record<string, unknown>;\n\nexport type ObserverMetadata = {\n provider: string;\n model?: string;\n feature?: string;\n tags?: string[];\n};\n\nexport type ObservedUsage = {\n providerUsed?: string;\n modelUsed?: string;\n inputTokens?: number;\n responseTokens?: number;\n cachedInputTokens?: number;\n cacheWriteInputTokens?: number;\n reasoningTokens?: number;\n};\n\nexport type UsageTapObserverOptions = {\n apiKey?: string;\n customerId: string;\n baseUrl?: string;\n feature?: string;\n tags?: string[];\n fetch?: typeof globalThis.fetch;\n onTelemetryError?: (error: Error) => void;\n};\n\nexport type ObserveOpenAIOptions = UsageTapObserverOptions & {\n provider?: string;\n};\n\nexport type ObserveAnthropicOptions = UsageTapObserverOptions & {\n provider?: string;\n};\n\ntype CallBeginData = {\n callId: string;\n allowed?: { standard?: boolean; premium?: boolean };\n};\n\ntype Envelope<T> = {\n data?: T;\n error?: { message?: string };\n result?: { message?: string };\n};\n\nfunction isRecord(value: unknown): value is UnknownRecord {\n return Boolean(value) && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction finiteNonNegative(value: unknown): number | undefined {\n return typeof value === \"number\" && Number.isFinite(value) && value >= 0\n ? Math.round(value)\n : undefined;\n}\n\nfunction readRecord(parent: UnknownRecord, key: string): UnknownRecord | undefined {\n const value = parent[key];\n return isRecord(value) ? value : undefined;\n}\n\nfunction readString(parent: UnknownRecord, key: string): string | undefined {\n const value = parent[key];\n return typeof value === \"string\" && value.trim() ? value : undefined;\n}\n\nexport function extractOpenAIUsage(\n response: unknown,\n provider = \"openai\",\n): ObservedUsage | undefined {\n if (!isRecord(response)) return undefined;\n const usage = readRecord(response, \"usage\");\n if (!usage) return undefined;\n const promptDetails =\n readRecord(usage, \"prompt_tokens_details\") ??\n readRecord(usage, \"input_tokens_details\");\n const completionDetails =\n readRecord(usage, \"completion_tokens_details\") ??\n readRecord(usage, \"output_tokens_details\");\n\n return {\n providerUsed: provider,\n modelUsed: readString(response, \"model\"),\n inputTokens:\n finiteNonNegative(usage.prompt_tokens) ??\n finiteNonNegative(usage.input_tokens),\n responseTokens:\n finiteNonNegative(usage.completion_tokens) ??\n finiteNonNegative(usage.output_tokens),\n cachedInputTokens:\n finiteNonNegative(promptDetails?.cached_tokens) ??\n finiteNonNegative(usage.cached_tokens),\n cacheWriteInputTokens: finiteNonNegative(promptDetails?.cache_write_tokens),\n reasoningTokens: finiteNonNegative(completionDetails?.reasoning_tokens),\n };\n}\n\nexport function extractAnthropicUsage(\n response: unknown,\n provider = \"anthropic\",\n): ObservedUsage | undefined {\n if (!isRecord(response)) return undefined;\n const usage = readRecord(response, \"usage\");\n if (!usage) return undefined;\n return {\n providerUsed: provider,\n modelUsed: readString(response, \"model\"),\n inputTokens: finiteNonNegative(usage.input_tokens),\n responseTokens: finiteNonNegative(usage.output_tokens),\n cachedInputTokens: finiteNonNegative(usage.cache_read_input_tokens),\n cacheWriteInputTokens: finiteNonNegative(usage.cache_creation_input_tokens),\n };\n}\n\nfunction cleanError(error: unknown): { code: string; message: string } {\n if (error instanceof Error) {\n return {\n code: error.name.slice(0, 80) || \"PROVIDER_ERROR\",\n message: error.message.slice(0, 500) || \"Provider request failed\",\n };\n }\n return { code: \"PROVIDER_ERROR\", message: \"Provider request failed\" };\n}\n\nfunction randomId(): string {\n return typeof globalThis.crypto?.randomUUID === \"function\"\n ? globalThis.crypto.randomUUID()\n : `${Date.now()}-${Math.random().toString(36).slice(2)}`;\n}\n\nexport class UsageTapObserver {\n private readonly apiKey: string;\n private readonly customerId: string;\n private readonly baseUrl: string;\n private readonly defaultFeature?: string;\n private readonly defaultTags?: string[];\n private readonly fetchImpl: typeof globalThis.fetch;\n private readonly onTelemetryError?: (error: Error) => void;\n\n constructor(options: UsageTapObserverOptions) {\n const apiKey = options.apiKey ??\n (typeof process !== \"undefined\" ? process.env?.USAGETAP_API_KEY : undefined);\n if (!apiKey?.trim()) throw new Error(\"UsageTap observer requires an apiKey\");\n if (!options.customerId?.trim()) {\n throw new Error(\"UsageTap observer requires a customerId\");\n }\n this.apiKey = apiKey.trim();\n this.customerId = options.customerId.trim();\n this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/$/, \"\");\n this.defaultFeature = options.feature;\n this.defaultTags = options.tags;\n this.fetchImpl = options.fetch ?? globalThis.fetch;\n this.onTelemetryError = options.onTelemetryError;\n }\n\n private telemetryError(error: unknown): void {\n const normalized = error instanceof Error ? error : new Error(String(error));\n this.onTelemetryError?.(normalized);\n }\n\n private async post<T>(path: string, body: UnknownRecord): Promise<T> {\n const result = await this.fetchImpl(`${this.baseUrl}/${path}`, {\n method: \"POST\",\n headers: {\n Accept: MEDIA_TYPE,\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.apiKey}`,\n \"Idempotency-Key\": randomId(),\n \"X-Usage-SDK\": \"observer-js/0.1.2\",\n },\n body: JSON.stringify(body),\n });\n const envelope = (await result.json()) as Envelope<T>;\n if (!result.ok || !envelope.data) {\n throw new Error(\n envelope.error?.message ?? envelope.result?.message ?? `UsageTap returned ${result.status}`,\n );\n }\n return envelope.data;\n }\n\n /**\n * Observe one provider call. The callback receives no UsageTap data and the\n * observer never serializes the callback arguments, prompt, or response.\n * Telemetry fails open so provider behavior is unchanged.\n */\n async track<T>(\n metadata: ObserverMetadata,\n call: () => Promise<T>,\n extractUsage: (response: T) => ObservedUsage | undefined,\n ): Promise<T> {\n let begin: CallBeginData | undefined;\n try {\n begin = await this.post<CallBeginData>(\"call_begin\", {\n customerId: this.customerId,\n feature: metadata.feature ?? this.defaultFeature,\n tags: metadata.tags ?? this.defaultTags,\n idempotencyKey: randomId(),\n });\n } catch (error) {\n this.telemetryError(error);\n }\n\n try {\n const result = await call();\n if (begin?.callId) {\n try {\n await this.post(\"call_end\", {\n callId: begin.callId,\n providerUsed: metadata.provider,\n modelUsed: metadata.model,\n ...extractUsage(result),\n });\n } catch (error) {\n this.telemetryError(error);\n }\n }\n return result;\n } catch (error) {\n if (begin?.callId) {\n try {\n await this.post(\"call_end\", {\n callId: begin.callId,\n providerUsed: metadata.provider,\n modelUsed: metadata.model,\n error: cleanError(error),\n });\n } catch (telemetryError) {\n this.telemetryError(telemetryError);\n }\n }\n throw error;\n }\n }\n}\n\n/**\n * Wrap an OpenAI-compatible client without importing OpenAI or retaining\n * prompts. Non-streaming chat completions and Responses API calls are observed.\n * Streaming requests pass through unchanged in this first lightweight build.\n */\nexport function observeOpenAI<TClient extends object>(\n client: TClient,\n options: ObserveOpenAIOptions,\n): TClient & { unwrap(): TClient } {\n const observer = new UsageTapObserver(options);\n const provider = options.provider ?? \"openai\";\n const root = client as UnknownRecord;\n\n const wrapCreate = (\n resource: UnknownRecord | undefined,\n ): UnknownRecord | undefined => {\n if (!resource) return undefined;\n const original = resource.create;\n if (typeof original !== \"function\") return resource;\n return new Proxy(resource, {\n get(target, property, receiver): unknown {\n if (property !== \"create\") {\n return Reflect.get(target, property, receiver) as unknown;\n }\n return async (params: unknown, ...rest: unknown[]): Promise<unknown> => {\n const request = isRecord(params) ? params : {};\n const invoke = (): unknown =>\n Reflect.apply(original, target, [params, ...rest]) as unknown;\n if (request.stream === true) {\n return invoke();\n }\n return observer.track(\n {\n provider,\n model: readString(request, \"model\"),\n },\n () => Promise.resolve(invoke()),\n (response) => extractOpenAIUsage(response, provider),\n );\n };\n },\n });\n };\n\n const chat = isRecord(root.chat) ? root.chat : undefined;\n const wrappedCompletions = wrapCreate(\n chat && isRecord(chat.completions) ? chat.completions : undefined,\n );\n const wrappedChat = chat && wrappedCompletions\n ? new Proxy(chat, {\n get(target, property, receiver): unknown {\n return property === \"completions\"\n ? wrappedCompletions\n : Reflect.get(target, property, receiver) as unknown;\n },\n })\n : chat;\n const wrappedResponses = wrapCreate(\n isRecord(root.responses) ? root.responses : undefined,\n );\n\n return new Proxy(client, {\n get(target, property, receiver) {\n if (property === \"chat\" && wrappedChat) return wrappedChat;\n if (property === \"responses\" && wrappedResponses) return wrappedResponses;\n if (property === \"unwrap\") return () => client;\n return Reflect.get(target, property, receiver);\n },\n }) as TClient & { unwrap(): TClient };\n}\n\n/** Wrap a non-streaming Anthropic Messages client with metadata-only usage observation. */\nexport function observeAnthropic<TClient extends object>(\n client: TClient,\n options: ObserveAnthropicOptions,\n): TClient & { unwrap(): TClient } {\n const observer = new UsageTapObserver(options);\n const provider = options.provider ?? \"anthropic\";\n const root = client as UnknownRecord;\n const messages = isRecord(root.messages) ? root.messages : undefined;\n const original = messages?.create;\n const wrappedMessages =\n messages && typeof original === \"function\"\n ? new Proxy(messages, {\n get(target, property, receiver): unknown {\n if (property !== \"create\") {\n return Reflect.get(target, property, receiver) as unknown;\n }\n return async (\n params: unknown,\n ...rest: unknown[]\n ): Promise<unknown> => {\n const request = isRecord(params) ? params : {};\n const invoke = (): unknown =>\n Reflect.apply(original, target, [params, ...rest]) as unknown;\n if (request.stream === true) {\n return invoke();\n }\n return observer.track(\n {\n provider,\n model: readString(request, \"model\"),\n },\n () => Promise.resolve(invoke()),\n (response) => extractAnthropicUsage(response, provider),\n );\n };\n },\n })\n : messages;\n\n return new Proxy(client, {\n get(target, property, receiver) {\n if (property === \"messages\" && wrappedMessages) return wrappedMessages;\n if (property === \"unwrap\") return () => client;\n return Reflect.get(target, property, receiver);\n },\n }) as TClient & { unwrap(): TClient };\n}\n"],"mappings":"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","names":["DEFAULT_BASE_URL","MEDIA_TYPE","isRecord","value","finiteNonNegative","readRecord","parent","key","readString","extractOpenAIUsage","response","provider","usage","promptDetails","completionDetails","extractAnthropicUsage","cleanError","error","randomId","UsageTapObserver","options","apiKey","normalized","path","body","result","envelope","metadata","call","extractUsage","begin","telemetryError","observeOpenAI","client","observer","root","wrapCreate","resource","original","target","property","receiver","params","rest","request","invoke","chat","wrappedCompletions","wrappedChat","wrappedResponses","observeAnthropic","messages","wrappedMessages"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["const DEFAULT_BASE_URL = \"https://api.usagetap.com\";\nconst MEDIA_TYPE = \"application/vnd.usagetap.v1+json\";\n\ntype UnknownRecord = Record<string, unknown>;\n\nexport type ObserverMetadata = {\n provider: string;\n model?: string;\n feature?: string;\n tags?: string[];\n};\n\nexport type ObservedUsage = {\n providerUsed?: string;\n modelUsed?: string;\n inputTokens?: number;\n responseTokens?: number;\n cachedInputTokens?: number;\n cacheWriteInputTokens?: number;\n reasoningTokens?: number;\n};\n\nexport type UsageTapObserverOptions = {\n apiKey?: string;\n /** Optional multi-customer attribution. Trial keys infer their workspace. */\n customerId?: string;\n baseUrl?: string;\n feature?: string;\n tags?: string[];\n fetch?: typeof globalThis.fetch;\n onTelemetryError?: (error: Error) => void;\n};\n\nexport type ObserveOpenAIOptions = UsageTapObserverOptions & {\n provider?: string;\n};\n\nexport type ObserveAnthropicOptions = UsageTapObserverOptions & {\n provider?: string;\n};\n\ntype CallBeginData = {\n callId: string;\n allowed?: { standard?: boolean; premium?: boolean };\n};\n\ntype Envelope<T> = {\n data?: T;\n error?: { message?: string };\n result?: { message?: string };\n};\n\nfunction isRecord(value: unknown): value is UnknownRecord {\n return Boolean(value) && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction finiteNonNegative(value: unknown): number | undefined {\n return typeof value === \"number\" && Number.isFinite(value) && value >= 0\n ? Math.round(value)\n : undefined;\n}\n\nfunction readRecord(parent: UnknownRecord, key: string): UnknownRecord | undefined {\n const value = parent[key];\n return isRecord(value) ? value : undefined;\n}\n\nfunction readString(parent: UnknownRecord, key: string): string | undefined {\n const value = parent[key];\n return typeof value === \"string\" && value.trim() ? value : undefined;\n}\n\nexport function extractOpenAIUsage(\n response: unknown,\n provider = \"openai\",\n): ObservedUsage | undefined {\n if (!isRecord(response)) return undefined;\n const usage = readRecord(response, \"usage\");\n if (!usage) return undefined;\n const promptDetails =\n readRecord(usage, \"prompt_tokens_details\") ??\n readRecord(usage, \"input_tokens_details\");\n const completionDetails =\n readRecord(usage, \"completion_tokens_details\") ??\n readRecord(usage, \"output_tokens_details\");\n\n return {\n providerUsed: provider,\n modelUsed: readString(response, \"model\"),\n inputTokens:\n finiteNonNegative(usage.prompt_tokens) ??\n finiteNonNegative(usage.input_tokens),\n responseTokens:\n finiteNonNegative(usage.completion_tokens) ??\n finiteNonNegative(usage.output_tokens),\n cachedInputTokens:\n finiteNonNegative(promptDetails?.cached_tokens) ??\n finiteNonNegative(usage.cached_tokens),\n cacheWriteInputTokens: finiteNonNegative(promptDetails?.cache_write_tokens),\n reasoningTokens: finiteNonNegative(completionDetails?.reasoning_tokens),\n };\n}\n\nexport function extractAnthropicUsage(\n response: unknown,\n provider = \"anthropic\",\n): ObservedUsage | undefined {\n if (!isRecord(response)) return undefined;\n const usage = readRecord(response, \"usage\");\n if (!usage) return undefined;\n return {\n providerUsed: provider,\n modelUsed: readString(response, \"model\"),\n inputTokens: finiteNonNegative(usage.input_tokens),\n responseTokens: finiteNonNegative(usage.output_tokens),\n cachedInputTokens: finiteNonNegative(usage.cache_read_input_tokens),\n cacheWriteInputTokens: finiteNonNegative(usage.cache_creation_input_tokens),\n };\n}\n\nfunction mergeObservedUsage(\n current: ObservedUsage,\n next: ObservedUsage | undefined,\n): ObservedUsage {\n if (!next) return current;\n const definedNext = Object.fromEntries(\n Object.entries(next).filter(([, value]) => value !== undefined),\n ) as ObservedUsage;\n return { ...current, ...definedNext };\n}\n\nfunction extractOpenAIStreamUsage(\n event: unknown,\n provider: string,\n): ObservedUsage | undefined {\n if (!isRecord(event)) return undefined;\n const response = readRecord(event, \"response\");\n const source = response ?? event;\n const usage = extractOpenAIUsage(source, provider);\n const modelUsed = readString(source, \"model\");\n return usage ?? (modelUsed ? { providerUsed: provider, modelUsed } : undefined);\n}\n\nfunction extractAnthropicStreamUsage(\n event: unknown,\n provider: string,\n): ObservedUsage | undefined {\n if (!isRecord(event)) return undefined;\n const message = readRecord(event, \"message\");\n if (message) return extractAnthropicUsage(message, provider);\n const usage = readRecord(event, \"usage\");\n const delta = readRecord(event, \"delta\");\n const deltaUsage = delta ? readRecord(delta, \"usage\") : undefined;\n const sourceUsage = usage ?? deltaUsage;\n if (!sourceUsage) return undefined;\n return extractAnthropicUsage(\n { model: readString(event, \"model\"), usage: sourceUsage },\n provider,\n );\n}\n\nfunction isOpenAIUsageOnlyChunk(event: unknown): boolean {\n if (!isRecord(event) || !isRecord(event.usage)) return false;\n return Array.isArray(event.choices) && event.choices.length === 0;\n}\n\nfunction wrapAsyncIterableStream<TStream extends object>(\n stream: TStream,\n onValue: (value: unknown) => void,\n onComplete: () => Promise<void>,\n onError: (error: unknown) => Promise<void>,\n suppress?: (value: unknown) => boolean,\n): TStream {\n const iteratorFactory = (stream as AsyncIterable<unknown>)[Symbol.asyncIterator];\n if (typeof iteratorFactory !== \"function\") return stream;\n\n return new Proxy(stream, {\n get(target, property, receiver): unknown {\n if (property === Symbol.asyncIterator) {\n return (): AsyncIterableIterator<unknown> => {\n const iterator = iteratorFactory.call(target);\n return {\n async next(): Promise<IteratorResult<unknown>> {\n try {\n while (true) {\n const step = await iterator.next();\n if (step.done) {\n await onComplete();\n return step;\n }\n onValue(step.value);\n if (!suppress?.(step.value)) return step;\n }\n } catch (error) {\n await onError(error);\n throw error;\n }\n },\n async return(value?: unknown): Promise<IteratorResult<unknown>> {\n try {\n const result = iterator.return\n ? await iterator.return(value)\n : { done: true as const, value };\n await onComplete();\n return result;\n } catch (error) {\n await onError(error);\n throw error;\n }\n },\n async throw(error?: unknown): Promise<IteratorResult<unknown>> {\n try {\n if (iterator.throw) return await iterator.throw(error);\n throw error;\n } catch (caught) {\n await onError(caught);\n throw caught;\n }\n },\n [Symbol.asyncIterator]() {\n return this;\n },\n };\n };\n }\n const value = Reflect.get(target, property, target) as unknown;\n if (\n typeof value === \"function\" &&\n (property === \"toReadableStream\" || property === \"tee\")\n ) {\n // These OpenAI helpers consume `this[Symbol.asyncIterator]()`. Bind to\n // the proxy so their reads still pass through the metadata observer.\n return (value as (...args: unknown[]) => unknown).bind(receiver);\n }\n return typeof value === \"function\"\n ? (value as (...args: unknown[]) => unknown).bind(target)\n : value;\n },\n });\n}\n\nfunction cleanError(error: unknown): { code: string; message: string } {\n if (error instanceof Error) {\n return {\n code: error.name.slice(0, 80) || \"PROVIDER_ERROR\",\n // Provider messages can echo prompt or response content. Keep telemetry\n // metadata-only by reporting a stable generic message instead.\n message: \"Provider request failed\",\n };\n }\n return { code: \"PROVIDER_ERROR\", message: \"Provider request failed\" };\n}\n\nfunction isStreamFailureEvent(event: unknown): boolean {\n if (!isRecord(event)) return false;\n const type = readString(event, \"type\")?.toLowerCase();\n return type === \"error\" || type?.endsWith(\".failed\") === true;\n}\n\nfunction randomId(): string {\n return typeof globalThis.crypto?.randomUUID === \"function\"\n ? globalThis.crypto.randomUUID()\n : `${Date.now()}-${Math.random().toString(36).slice(2)}`;\n}\n\nexport class UsageTapObserver {\n private readonly apiKey: string;\n private readonly customerId?: string;\n private readonly baseUrl: string;\n private readonly defaultFeature?: string;\n private readonly defaultTags?: string[];\n private readonly fetchImpl: typeof globalThis.fetch;\n private readonly onTelemetryError?: (error: Error) => void;\n private readonly pendingTelemetry = new Set<Promise<void>>();\n\n constructor(options: UsageTapObserverOptions = {}) {\n const apiKey = options.apiKey ??\n (typeof process !== \"undefined\" ? process.env?.USAGETAP_API_KEY : undefined);\n if (!apiKey?.trim()) throw new Error(\"UsageTap observer requires an apiKey\");\n this.apiKey = apiKey.trim();\n this.customerId = options.customerId?.trim() || undefined;\n this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/$/, \"\");\n this.defaultFeature = options.feature;\n this.defaultTags = options.tags;\n this.fetchImpl = options.fetch ?? globalThis.fetch;\n this.onTelemetryError = options.onTelemetryError;\n }\n\n private telemetryError(error: unknown): void {\n const normalized = error instanceof Error ? error : new Error(String(error));\n this.onTelemetryError?.(normalized);\n }\n\n private queueTelemetry(task: Promise<unknown>): void {\n const tracked: Promise<void> = task\n .then(() => undefined)\n .catch((error: unknown) => this.telemetryError(error))\n .finally(() => this.pendingTelemetry.delete(tracked));\n this.pendingTelemetry.add(tracked);\n }\n\n /** Wait for queued metadata delivery before a short-lived process exits. */\n async flush(): Promise<void> {\n while (this.pendingTelemetry.size) {\n await Promise.allSettled([...this.pendingTelemetry]);\n }\n }\n\n private async post<T>(path: string, body: UnknownRecord): Promise<T> {\n const result = await this.fetchImpl(`${this.baseUrl}/${path}`, {\n method: \"POST\",\n headers: {\n Accept: MEDIA_TYPE,\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.apiKey}`,\n \"Idempotency-Key\": randomId(),\n \"X-Usage-SDK\": \"observer-js/0.1.4\",\n },\n body: JSON.stringify(body),\n });\n const envelope = (await result.json()) as Envelope<T>;\n if (!result.ok || !envelope.data) {\n throw new Error(\n envelope.error?.message ?? envelope.result?.message ?? `UsageTap returned ${result.status}`,\n );\n }\n return envelope.data;\n }\n\n /**\n * Observe one provider call. The callback receives no UsageTap data and the\n * observer never serializes the callback arguments, prompt, or response.\n * Telemetry fails open so provider behavior is unchanged.\n */\n async track<T>(\n metadata: ObserverMetadata,\n call: () => Promise<T>,\n extractUsage: (response: T) => ObservedUsage | undefined,\n ): Promise<T> {\n const beginPromise = this.post<CallBeginData>(\"call_begin\", {\n ...(this.customerId ? { customerId: this.customerId } : {}),\n feature: metadata.feature ?? this.defaultFeature,\n tags: metadata.tags ?? this.defaultTags,\n idempotencyKey: randomId(),\n }).catch((error: unknown) => {\n this.telemetryError(error);\n return undefined;\n });\n\n try {\n const result = await call();\n const usage = extractUsage(result);\n this.queueTelemetry(\n beginPromise.then((begin) => {\n if (!begin?.callId) return undefined;\n return this.post(\"call_end\", {\n callId: begin.callId,\n providerUsed: metadata.provider,\n modelUsed: metadata.model,\n ...usage,\n });\n }),\n );\n return result;\n } catch (error) {\n this.queueTelemetry(\n beginPromise.then((begin) => {\n if (!begin?.callId) return undefined;\n return this.post(\"call_end\", {\n callId: begin.callId,\n providerUsed: metadata.provider,\n modelUsed: metadata.model,\n error: cleanError(error),\n });\n }),\n );\n throw error;\n }\n }\n\n /** Observe metadata-bearing stream events without retaining content chunks. */\n async trackStream<TStream extends object>(\n metadata: ObserverMetadata,\n call: () => Promise<TStream>,\n extractUsage: (event: unknown) => ObservedUsage | undefined,\n suppress?: (event: unknown) => boolean,\n ): Promise<TStream> {\n const beginPromise = this.post<CallBeginData>(\"call_begin\", {\n ...(this.customerId ? { customerId: this.customerId } : {}),\n feature: metadata.feature ?? this.defaultFeature,\n tags: metadata.tags ?? this.defaultTags,\n idempotencyKey: randomId(),\n }).catch((error: unknown) => {\n this.telemetryError(error);\n return undefined;\n });\n\n let stream: TStream;\n try {\n stream = await call();\n } catch (error) {\n const begin = await beginPromise;\n if (begin?.callId) {\n try {\n await this.post(\"call_end\", {\n callId: begin.callId,\n providerUsed: metadata.provider,\n modelUsed: metadata.model,\n error: cleanError(error),\n });\n } catch (telemetryError) {\n this.telemetryError(telemetryError);\n }\n }\n throw error;\n }\n\n let usage: ObservedUsage = {\n providerUsed: metadata.provider,\n modelUsed: metadata.model,\n };\n let streamFailed = false;\n let finished = false;\n const finish = (error?: unknown): Promise<void> => {\n if (finished) return Promise.resolve();\n finished = true;\n this.queueTelemetry(\n beginPromise.then((begin) => {\n if (!begin?.callId) return undefined;\n return this.post(\"call_end\", {\n callId: begin.callId,\n ...usage,\n ...(error === undefined ? {} : { error: cleanError(error) }),\n });\n }),\n );\n return Promise.resolve();\n };\n\n return wrapAsyncIterableStream(\n stream,\n (event) => {\n usage = mergeObservedUsage(usage, extractUsage(event));\n streamFailed ||= isStreamFailureEvent(event);\n },\n () => finish(streamFailed ? new Error(\"Provider stream failed\") : undefined),\n (error) => finish(error),\n suppress,\n );\n }\n\n /** Observe SDK stream helpers that expose lifecycle events synchronously. */\n trackEmitterStream<TStream extends object>(\n metadata: ObserverMetadata,\n stream: TStream,\n extractUsage: (event: unknown) => ObservedUsage | undefined,\n ): TStream {\n const emitter = stream as UnknownRecord;\n const on = emitter.on;\n if (typeof on !== \"function\") return stream;\n\n const beginPromise = this.post<CallBeginData>(\"call_begin\", {\n ...(this.customerId ? { customerId: this.customerId } : {}),\n feature: metadata.feature ?? this.defaultFeature,\n tags: metadata.tags ?? this.defaultTags,\n idempotencyKey: randomId(),\n }).catch((error: unknown) => {\n this.telemetryError(error);\n return undefined;\n });\n let usage: ObservedUsage = {\n providerUsed: metadata.provider,\n modelUsed: metadata.model,\n };\n let finished = false;\n const finish = (error?: unknown): Promise<void> => {\n if (finished) return Promise.resolve();\n finished = true;\n this.queueTelemetry(\n beginPromise.then((begin) => {\n if (!begin?.callId) return undefined;\n return this.post(\"call_end\", {\n callId: begin.callId,\n ...usage,\n ...(error === undefined ? {} : { error: cleanError(error) }),\n });\n }),\n );\n return Promise.resolve();\n };\n const observe = (event: unknown): void => {\n usage = mergeObservedUsage(usage, extractUsage(event));\n };\n Reflect.apply(on, stream, [\"streamEvent\", observe]);\n Reflect.apply(on, stream, [\"message\", observe]);\n Reflect.apply(on, stream, [\"end\", () => void finish()]);\n Reflect.apply(on, stream, [\"error\", (error: unknown) => void finish(error)]);\n Reflect.apply(on, stream, [\"abort\", () => void finish(new Error(\"Provider stream aborted\"))]);\n return stream;\n }\n}\n\n/**\n * Wrap an OpenAI-compatible client without importing OpenAI or retaining\n * prompts. Non-streaming chat completions and Responses API calls are observed.\n * Streaming requests pass through unchanged in this first lightweight build.\n */\nexport function observeOpenAI<TClient extends object>(\n client: TClient,\n options: ObserveOpenAIOptions = {},\n): TClient & { unwrap(): TClient; flushUsageTap(): Promise<void> } {\n const observer = new UsageTapObserver(options);\n const provider = options.provider ?? \"openai\";\n const root = client as UnknownRecord;\n\n const wrapCreate = (\n resource: UnknownRecord | undefined,\n kind: \"chat\" | \"responses\",\n ): UnknownRecord | undefined => {\n if (!resource) return undefined;\n const original = resource.create;\n if (typeof original !== \"function\") return resource;\n return new Proxy(resource, {\n get(target, property, receiver): unknown {\n if (property !== \"create\") {\n return Reflect.get(target, property, receiver) as unknown;\n }\n return async (params: unknown, ...rest: unknown[]): Promise<unknown> => {\n const request = isRecord(params) ? params : {};\n if (request.stream === true) {\n const streamOptions = readRecord(request, \"stream_options\");\n const alreadyIncludesUsage = streamOptions?.include_usage === true;\n const providerParams = kind === \"chat\" && !alreadyIncludesUsage\n ? {\n ...request,\n stream_options: { ...streamOptions, include_usage: true },\n }\n : params;\n return observer.trackStream(\n { provider, model: readString(request, \"model\") },\n () => Promise.resolve(\n Reflect.apply(original, target, [providerParams, ...rest]) as object,\n ),\n (event) => extractOpenAIStreamUsage(event, provider),\n kind === \"chat\" && !alreadyIncludesUsage\n ? isOpenAIUsageOnlyChunk\n : undefined,\n );\n }\n const invoke = (): unknown =>\n Reflect.apply(original, target, [params, ...rest]) as unknown;\n return observer.track(\n {\n provider,\n model: readString(request, \"model\"),\n },\n () => Promise.resolve(invoke()),\n (response) => extractOpenAIUsage(response, provider),\n );\n };\n },\n });\n };\n\n const chat = isRecord(root.chat) ? root.chat : undefined;\n const wrappedCompletions = wrapCreate(\n chat && isRecord(chat.completions) ? chat.completions : undefined,\n \"chat\",\n );\n const wrappedChat = chat && wrappedCompletions\n ? new Proxy(chat, {\n get(target, property, receiver): unknown {\n return property === \"completions\"\n ? wrappedCompletions\n : Reflect.get(target, property, receiver) as unknown;\n },\n })\n : chat;\n const wrappedResponses = wrapCreate(\n isRecord(root.responses) ? root.responses : undefined,\n \"responses\",\n );\n\n return new Proxy(client, {\n get(target, property, receiver) {\n if (property === \"chat\" && wrappedChat) return wrappedChat;\n if (property === \"responses\" && wrappedResponses) return wrappedResponses;\n if (property === \"unwrap\") return () => client;\n if (property === \"flushUsageTap\") return () => observer.flush();\n return Reflect.get(target, property, receiver);\n },\n }) as TClient & { unwrap(): TClient; flushUsageTap(): Promise<void> };\n}\n\n/** Wrap a non-streaming Anthropic Messages client with metadata-only usage observation. */\nexport function observeAnthropic<TClient extends object>(\n client: TClient,\n options: ObserveAnthropicOptions = {},\n): TClient & { unwrap(): TClient; flushUsageTap(): Promise<void> } {\n const observer = new UsageTapObserver(options);\n const provider = options.provider ?? \"anthropic\";\n const root = client as UnknownRecord;\n const messages = isRecord(root.messages) ? root.messages : undefined;\n const original = messages?.create;\n const originalStream = messages?.stream;\n const wrappedMessages =\n messages && (typeof original === \"function\" || typeof originalStream === \"function\")\n ? new Proxy(messages, {\n get(target, property, receiver): unknown {\n if (property === \"stream\" && typeof originalStream === \"function\") {\n return (params: unknown, ...rest: unknown[]): unknown => {\n const request = isRecord(params) ? params : {};\n const stream = Reflect.apply(originalStream, target, [\n params,\n ...rest,\n ]) as unknown;\n if (!isRecord(stream)) return stream;\n return observer.trackEmitterStream(\n { provider, model: readString(request, \"model\") },\n stream,\n (event) => extractAnthropicStreamUsage(event, provider),\n );\n };\n }\n if (property !== \"create\" || typeof original !== \"function\") {\n return Reflect.get(target, property, receiver) as unknown;\n }\n return async (\n params: unknown,\n ...rest: unknown[]\n ): Promise<unknown> => {\n const request = isRecord(params) ? params : {};\n if (request.stream === true) {\n return observer.trackStream(\n { provider, model: readString(request, \"model\") },\n () => Promise.resolve(\n Reflect.apply(original, target, [params, ...rest]) as object,\n ),\n (event) => extractAnthropicStreamUsage(event, provider),\n );\n }\n const invoke = (): unknown =>\n Reflect.apply(original, target, [params, ...rest]) as unknown;\n return observer.track(\n {\n provider,\n model: readString(request, \"model\"),\n },\n () => Promise.resolve(invoke()),\n (response) => extractAnthropicUsage(response, provider),\n );\n };\n },\n })\n : messages;\n\n return new Proxy(client, {\n get(target, property, receiver) {\n if (property === \"messages\" && wrappedMessages) return wrappedMessages;\n if (property === \"unwrap\") return () => client;\n if (property === \"flushUsageTap\") return () => observer.flush();\n return Reflect.get(target, property, receiver);\n },\n }) as TClient & { unwrap(): TClient; flushUsageTap(): Promise<void> };\n}\n"],"mappings":"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","names":["DEFAULT_BASE_URL","MEDIA_TYPE","isRecord","value","finiteNonNegative","readRecord","parent","key","readString","extractOpenAIUsage","response","provider","usage","promptDetails","completionDetails","extractAnthropicUsage","mergeObservedUsage","current","next","definedNext","extractOpenAIStreamUsage","event","source","modelUsed","extractAnthropicStreamUsage","message","delta","deltaUsage","sourceUsage","isOpenAIUsageOnlyChunk","wrapAsyncIterableStream","stream","onValue","onComplete","onError","suppress","iteratorFactory","target","property","receiver","iterator","step","error","result","caught","cleanError","isStreamFailureEvent","type","randomId","UsageTapObserver","options","apiKey","normalized","task","tracked","path","body","envelope","metadata","call","extractUsage","beginPromise","begin","telemetryError","streamFailed","finished","finish","on","observe","observeOpenAI","client","observer","root","wrapCreate","resource","kind","original","params","rest","request","streamOptions","alreadyIncludesUsage","providerParams","invoke","chat","wrappedCompletions","wrappedChat","wrappedResponses","observeAnthropic","messages","originalStream","wrappedMessages"]}
|