@usagetap/observer 0.1.0 → 0.1.2

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/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";var v=Object.defineProperty;var P=Object.getOwnPropertyDescriptor;var x=Object.getOwnPropertyNames;var C=Object.prototype.hasOwnProperty;var A=(n,e)=>{for(var r in e)v(n,r,{get:e[r],enumerable:!0})},D=(n,e,r,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let t of x(e))!C.call(n,t)&&t!==r&&v(n,t,{get:()=>e[t],enumerable:!(o=P(e,t))||o.enumerable});return n};var K=n=>D(v({},"__esModule",{value:!0}),n);var M={};A(M,{UsageTapObserver:()=>w,extractAnthropicUsage:()=>R,extractOpenAIUsage:()=>_,observeAnthropic:()=>B,observeOpenAI:()=>q});module.exports=K(M);var S="https://api.usagetap.com",j="application/vnd.usagetap.v1+json";function l(n){return!!n&&typeof n=="object"&&!Array.isArray(n)}function c(n){return typeof n=="number"&&Number.isFinite(n)&&n>=0?Math.round(n):void 0}function g(n,e){let r=n[e];return l(r)?r:void 0}function T(n,e){let r=n[e];return typeof r=="string"&&r.trim()?r:void 0}function _(n,e="openai"){if(!l(n))return;let r=g(n,"usage");if(!r)return;let o=g(r,"prompt_tokens_details")??g(r,"input_tokens_details"),t=g(r,"completion_tokens_details")??g(r,"output_tokens_details");return{providerUsed:e,modelUsed:T(n,"model"),inputTokens:c(r.prompt_tokens)??c(r.input_tokens),responseTokens:c(r.completion_tokens)??c(r.output_tokens),cachedInputTokens:c(o?.cached_tokens)??c(r.cached_tokens),cacheWriteInputTokens:c(o?.cache_write_tokens),reasoningTokens:c(t?.reasoning_tokens)}}function R(n,e="anthropic"){if(!l(n))return;let r=g(n,"usage");if(r)return{providerUsed:e,modelUsed:T(n,"model"),inputTokens:c(r.input_tokens),responseTokens:c(r.output_tokens),cachedInputTokens:c(r.cache_read_input_tokens),cacheWriteInputTokens:c(r.cache_creation_input_tokens)}}function $(n){return n instanceof Error?{code:n.name.slice(0,80)||"PROVIDER_ERROR",message:n.message.slice(0,500)||"Provider request failed"}:{code:"PROVIDER_ERROR",message:"Provider request failed"}}function I(){return typeof globalThis.crypto?.randomUUID=="function"?globalThis.crypto.randomUUID():`${Date.now()}-${Math.random().toString(36).slice(2)}`}var w=class{apiKey;customerId;baseUrl;defaultFeature;defaultTags;fetchImpl;onTelemetryError;constructor(e){let r=e.apiKey??(typeof process<"u"?process.env?.USAGETAP_API_KEY:void 0);if(!r?.trim())throw new Error("UsageTap observer requires an apiKey");if(!e.customerId?.trim())throw new Error("UsageTap observer requires a customerId");this.apiKey=r.trim(),this.customerId=e.customerId.trim(),this.baseUrl=(e.baseUrl??S).replace(/\/$/,""),this.defaultFeature=e.feature,this.defaultTags=e.tags,this.fetchImpl=e.fetch??globalThis.fetch,this.onTelemetryError=e.onTelemetryError}telemetryError(e){let r=e instanceof Error?e:new Error(String(e));this.onTelemetryError?.(r)}async post(e,r){let o=await this.fetchImpl(`${this.baseUrl}/${e}`,{method:"POST",headers:{Accept:j,"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`,"Idempotency-Key":I(),"X-Usage-SDK":"observer-js/0.1.0"},body:JSON.stringify(r)}),t=await o.json();if(!o.ok||!t.data)throw new Error(t.error?.message??t.result?.message??`UsageTap returned ${o.status}`);return t.data}async track(e,r,o){let t;try{t=await this.post("call_begin",{customerId:this.customerId,feature:e.feature??this.defaultFeature,tags:e.tags??this.defaultTags,idempotencyKey:I()})}catch(s){this.telemetryError(s)}try{let s=await r();if(t?.callId)try{await this.post("call_end",{callId:t.callId,providerUsed:e.provider,modelUsed:e.model,...o(s)})}catch(a){this.telemetryError(a)}return s}catch(s){if(t?.callId)try{await this.post("call_end",{callId:t.callId,providerUsed:e.provider,modelUsed:e.model,error:$(s)})}catch(a){this.telemetryError(a)}throw s}}};function q(n,e){let r=new w(e),o=e.provider??"openai",t=n,s=d=>{if(!d)return;let i=d.create;return typeof i!="function"?d:new Proxy(d,{get(p,h,y){return h!=="create"?Reflect.get(p,h,y):async(k,...E)=>{let b=l(k)?k:{},U=()=>Reflect.apply(i,p,[k,...E]);return b.stream===!0?U():r.track({provider:o,model:T(b,"model")},()=>Promise.resolve(U()),O=>_(O,o))}}})},a=l(t.chat)?t.chat:void 0,m=s(a&&l(a.completions)?a.completions:void 0),f=a&&m?new Proxy(a,{get(d,i,p){return i==="completions"?m:Reflect.get(d,i,p)}}):a,u=s(l(t.responses)?t.responses:void 0);return new Proxy(n,{get(d,i,p){return i==="chat"&&f?f:i==="responses"&&u?u:i==="unwrap"?()=>n:Reflect.get(d,i,p)}})}function B(n,e){let r=new w(e),o=e.provider??"anthropic",t=n,s=l(t.messages)?t.messages:void 0,a=s?.create,m=s&&typeof a=="function"?new Proxy(s,{get(f,u,d){return u!=="create"?Reflect.get(f,u,d):async(i,...p)=>{let h=l(i)?i:{},y=()=>Reflect.apply(a,f,[i,...p]);return h.stream===!0?y():r.track({provider:o,model:T(h,"model")},()=>Promise.resolve(y()),k=>R(k,o))}}}):s;return new Proxy(n,{get(f,u,d){return u==="messages"&&m?m:u==="unwrap"?()=>n:Reflect.get(f,u,d)}})}0&&(module.exports={UsageTapObserver,extractAnthropicUsage,extractOpenAIUsage,observeAnthropic,observeOpenAI});
1
+ "use strict";var v=Object.defineProperty;var P=Object.getOwnPropertyDescriptor;var x=Object.getOwnPropertyNames;var C=Object.prototype.hasOwnProperty;var A=(n,e)=>{for(var r in e)v(n,r,{get:e[r],enumerable:!0})},D=(n,e,r,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let t of x(e))!C.call(n,t)&&t!==r&&v(n,t,{get:()=>e[t],enumerable:!(o=P(e,t))||o.enumerable});return n};var K=n=>D(v({},"__esModule",{value:!0}),n);var M={};A(M,{UsageTapObserver:()=>w,extractAnthropicUsage:()=>R,extractOpenAIUsage:()=>_,observeAnthropic:()=>B,observeOpenAI:()=>q});module.exports=K(M);var S="https://api.usagetap.com",j="application/vnd.usagetap.v1+json";function l(n){return!!n&&typeof n=="object"&&!Array.isArray(n)}function c(n){return typeof n=="number"&&Number.isFinite(n)&&n>=0?Math.round(n):void 0}function g(n,e){let r=n[e];return l(r)?r:void 0}function T(n,e){let r=n[e];return typeof r=="string"&&r.trim()?r:void 0}function _(n,e="openai"){if(!l(n))return;let r=g(n,"usage");if(!r)return;let o=g(r,"prompt_tokens_details")??g(r,"input_tokens_details"),t=g(r,"completion_tokens_details")??g(r,"output_tokens_details");return{providerUsed:e,modelUsed:T(n,"model"),inputTokens:c(r.prompt_tokens)??c(r.input_tokens),responseTokens:c(r.completion_tokens)??c(r.output_tokens),cachedInputTokens:c(o?.cached_tokens)??c(r.cached_tokens),cacheWriteInputTokens:c(o?.cache_write_tokens),reasoningTokens:c(t?.reasoning_tokens)}}function R(n,e="anthropic"){if(!l(n))return;let r=g(n,"usage");if(r)return{providerUsed:e,modelUsed:T(n,"model"),inputTokens:c(r.input_tokens),responseTokens:c(r.output_tokens),cachedInputTokens:c(r.cache_read_input_tokens),cacheWriteInputTokens:c(r.cache_creation_input_tokens)}}function $(n){return n instanceof Error?{code:n.name.slice(0,80)||"PROVIDER_ERROR",message:n.message.slice(0,500)||"Provider request failed"}:{code:"PROVIDER_ERROR",message:"Provider request failed"}}function I(){return typeof globalThis.crypto?.randomUUID=="function"?globalThis.crypto.randomUUID():`${Date.now()}-${Math.random().toString(36).slice(2)}`}var w=class{apiKey;customerId;baseUrl;defaultFeature;defaultTags;fetchImpl;onTelemetryError;constructor(e){let r=e.apiKey??(typeof process<"u"?process.env?.USAGETAP_API_KEY:void 0);if(!r?.trim())throw new Error("UsageTap observer requires an apiKey");if(!e.customerId?.trim())throw new Error("UsageTap observer requires a customerId");this.apiKey=r.trim(),this.customerId=e.customerId.trim(),this.baseUrl=(e.baseUrl??S).replace(/\/$/,""),this.defaultFeature=e.feature,this.defaultTags=e.tags,this.fetchImpl=e.fetch??globalThis.fetch,this.onTelemetryError=e.onTelemetryError}telemetryError(e){let r=e instanceof Error?e:new Error(String(e));this.onTelemetryError?.(r)}async post(e,r){let o=await this.fetchImpl(`${this.baseUrl}/${e}`,{method:"POST",headers:{Accept:j,"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`,"Idempotency-Key":I(),"X-Usage-SDK":"observer-js/0.1.2"},body:JSON.stringify(r)}),t=await o.json();if(!o.ok||!t.data)throw new Error(t.error?.message??t.result?.message??`UsageTap returned ${o.status}`);return t.data}async track(e,r,o){let t;try{t=await this.post("call_begin",{customerId:this.customerId,feature:e.feature??this.defaultFeature,tags:e.tags??this.defaultTags,idempotencyKey:I()})}catch(s){this.telemetryError(s)}try{let s=await r();if(t?.callId)try{await this.post("call_end",{callId:t.callId,providerUsed:e.provider,modelUsed:e.model,...o(s)})}catch(a){this.telemetryError(a)}return s}catch(s){if(t?.callId)try{await this.post("call_end",{callId:t.callId,providerUsed:e.provider,modelUsed:e.model,error:$(s)})}catch(a){this.telemetryError(a)}throw s}}};function q(n,e){let r=new w(e),o=e.provider??"openai",t=n,s=d=>{if(!d)return;let i=d.create;return typeof i!="function"?d:new Proxy(d,{get(p,h,y){return h!=="create"?Reflect.get(p,h,y):async(k,...E)=>{let b=l(k)?k:{},U=()=>Reflect.apply(i,p,[k,...E]);return b.stream===!0?U():r.track({provider:o,model:T(b,"model")},()=>Promise.resolve(U()),O=>_(O,o))}}})},a=l(t.chat)?t.chat:void 0,m=s(a&&l(a.completions)?a.completions:void 0),f=a&&m?new Proxy(a,{get(d,i,p){return i==="completions"?m:Reflect.get(d,i,p)}}):a,u=s(l(t.responses)?t.responses:void 0);return new Proxy(n,{get(d,i,p){return i==="chat"&&f?f:i==="responses"&&u?u:i==="unwrap"?()=>n:Reflect.get(d,i,p)}})}function B(n,e){let r=new w(e),o=e.provider??"anthropic",t=n,s=l(t.messages)?t.messages:void 0,a=s?.create,m=s&&typeof a=="function"?new Proxy(s,{get(f,u,d){return u!=="create"?Reflect.get(f,u,d):async(i,...p)=>{let h=l(i)?i:{},y=()=>Reflect.apply(a,f,[i,...p]);return h.stream===!0?y():r.track({provider:o,model:T(h,"model")},()=>Promise.resolve(y()),k=>R(k,o))}}}):s;return new Proxy(n,{get(f,u,d){return u==="messages"&&m?m:u==="unwrap"?()=>n:Reflect.get(f,u,d)}})}0&&(module.exports={UsageTapObserver,extractAnthropicUsage,extractOpenAIUsage,observeAnthropic,observeOpenAI});
2
2
  //# sourceMappingURL=index.cjs.map
@@ -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.0\",\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 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"]}
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- var R="https://api.usagetap.com",E="application/vnd.usagetap.v1+json";function l(r){return!!r&&typeof r=="object"&&!Array.isArray(r)}function c(r){return typeof r=="number"&&Number.isFinite(r)&&r>=0?Math.round(r):void 0}function g(r,n){let e=r[n];return l(e)?e:void 0}function T(r,n){let e=r[n];return typeof e=="string"&&e.trim()?e:void 0}function O(r,n="openai"){if(!l(r))return;let e=g(r,"usage");if(!e)return;let s=g(e,"prompt_tokens_details")??g(e,"input_tokens_details"),t=g(e,"completion_tokens_details")??g(e,"output_tokens_details");return{providerUsed:n,modelUsed:T(r,"model"),inputTokens:c(e.prompt_tokens)??c(e.input_tokens),responseTokens:c(e.completion_tokens)??c(e.output_tokens),cachedInputTokens:c(s?.cached_tokens)??c(e.cached_tokens),cacheWriteInputTokens:c(s?.cache_write_tokens),reasoningTokens:c(t?.reasoning_tokens)}}function P(r,n="anthropic"){if(!l(r))return;let e=g(r,"usage");if(e)return{providerUsed:n,modelUsed:T(r,"model"),inputTokens:c(e.input_tokens),responseTokens:c(e.output_tokens),cachedInputTokens:c(e.cache_read_input_tokens),cacheWriteInputTokens:c(e.cache_creation_input_tokens)}}function x(r){return r instanceof Error?{code:r.name.slice(0,80)||"PROVIDER_ERROR",message:r.message.slice(0,500)||"Provider request failed"}:{code:"PROVIDER_ERROR",message:"Provider request failed"}}function U(){return typeof globalThis.crypto?.randomUUID=="function"?globalThis.crypto.randomUUID():`${Date.now()}-${Math.random().toString(36).slice(2)}`}var y=class{apiKey;customerId;baseUrl;defaultFeature;defaultTags;fetchImpl;onTelemetryError;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");if(!n.customerId?.trim())throw new Error("UsageTap observer requires a customerId");this.apiKey=e.trim(),this.customerId=n.customerId.trim(),this.baseUrl=(n.baseUrl??R).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)}async post(n,e){let s=await this.fetchImpl(`${this.baseUrl}/${n}`,{method:"POST",headers:{Accept:E,"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`,"Idempotency-Key":U(),"X-Usage-SDK":"observer-js/0.1.0"},body:JSON.stringify(e)}),t=await s.json();if(!s.ok||!t.data)throw new Error(t.error?.message??t.result?.message??`UsageTap returned ${s.status}`);return t.data}async track(n,e,s){let t;try{t=await this.post("call_begin",{customerId:this.customerId,feature:n.feature??this.defaultFeature,tags:n.tags??this.defaultTags,idempotencyKey:U()})}catch(o){this.telemetryError(o)}try{let o=await e();if(t?.callId)try{await this.post("call_end",{callId:t.callId,providerUsed:n.provider,modelUsed:n.model,...s(o)})}catch(a){this.telemetryError(a)}return o}catch(o){if(t?.callId)try{await this.post("call_end",{callId:t.callId,providerUsed:n.provider,modelUsed:n.model,error:x(o)})}catch(a){this.telemetryError(a)}throw o}}};function C(r,n){let e=new y(n),s=n.provider??"openai",t=r,o=d=>{if(!d)return;let i=d.create;return typeof i!="function"?d:new Proxy(d,{get(p,h,w){return h!=="create"?Reflect.get(p,h,w):async(k,...I)=>{let v=l(k)?k:{},b=()=>Reflect.apply(i,p,[k,...I]);return v.stream===!0?b():e.track({provider:s,model:T(v,"model")},()=>Promise.resolve(b()),_=>O(_,s))}}})},a=l(t.chat)?t.chat:void 0,m=o(a&&l(a.completions)?a.completions:void 0),f=a&&m?new Proxy(a,{get(d,i,p){return i==="completions"?m:Reflect.get(d,i,p)}}):a,u=o(l(t.responses)?t.responses:void 0);return new Proxy(r,{get(d,i,p){return i==="chat"&&f?f:i==="responses"&&u?u:i==="unwrap"?()=>r:Reflect.get(d,i,p)}})}function A(r,n){let e=new y(n),s=n.provider??"anthropic",t=r,o=l(t.messages)?t.messages:void 0,a=o?.create,m=o&&typeof a=="function"?new Proxy(o,{get(f,u,d){return u!=="create"?Reflect.get(f,u,d):async(i,...p)=>{let h=l(i)?i:{},w=()=>Reflect.apply(a,f,[i,...p]);return h.stream===!0?w():e.track({provider:s,model:T(h,"model")},()=>Promise.resolve(w()),k=>P(k,s))}}}):o;return new Proxy(r,{get(f,u,d){return u==="messages"&&m?m:u==="unwrap"?()=>r:Reflect.get(f,u,d)}})}export{y as UsageTapObserver,P as extractAnthropicUsage,O as extractOpenAIUsage,A as observeAnthropic,C as observeOpenAI};
1
+ var R="https://api.usagetap.com",E="application/vnd.usagetap.v1+json";function l(r){return!!r&&typeof r=="object"&&!Array.isArray(r)}function c(r){return typeof r=="number"&&Number.isFinite(r)&&r>=0?Math.round(r):void 0}function g(r,n){let e=r[n];return l(e)?e:void 0}function T(r,n){let e=r[n];return typeof e=="string"&&e.trim()?e:void 0}function O(r,n="openai"){if(!l(r))return;let e=g(r,"usage");if(!e)return;let s=g(e,"prompt_tokens_details")??g(e,"input_tokens_details"),t=g(e,"completion_tokens_details")??g(e,"output_tokens_details");return{providerUsed:n,modelUsed:T(r,"model"),inputTokens:c(e.prompt_tokens)??c(e.input_tokens),responseTokens:c(e.completion_tokens)??c(e.output_tokens),cachedInputTokens:c(s?.cached_tokens)??c(e.cached_tokens),cacheWriteInputTokens:c(s?.cache_write_tokens),reasoningTokens:c(t?.reasoning_tokens)}}function P(r,n="anthropic"){if(!l(r))return;let e=g(r,"usage");if(e)return{providerUsed:n,modelUsed:T(r,"model"),inputTokens:c(e.input_tokens),responseTokens:c(e.output_tokens),cachedInputTokens:c(e.cache_read_input_tokens),cacheWriteInputTokens:c(e.cache_creation_input_tokens)}}function x(r){return r instanceof Error?{code:r.name.slice(0,80)||"PROVIDER_ERROR",message:r.message.slice(0,500)||"Provider request failed"}:{code:"PROVIDER_ERROR",message:"Provider request failed"}}function U(){return typeof globalThis.crypto?.randomUUID=="function"?globalThis.crypto.randomUUID():`${Date.now()}-${Math.random().toString(36).slice(2)}`}var y=class{apiKey;customerId;baseUrl;defaultFeature;defaultTags;fetchImpl;onTelemetryError;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");if(!n.customerId?.trim())throw new Error("UsageTap observer requires a customerId");this.apiKey=e.trim(),this.customerId=n.customerId.trim(),this.baseUrl=(n.baseUrl??R).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)}async post(n,e){let s=await this.fetchImpl(`${this.baseUrl}/${n}`,{method:"POST",headers:{Accept:E,"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`,"Idempotency-Key":U(),"X-Usage-SDK":"observer-js/0.1.2"},body:JSON.stringify(e)}),t=await s.json();if(!s.ok||!t.data)throw new Error(t.error?.message??t.result?.message??`UsageTap returned ${s.status}`);return t.data}async track(n,e,s){let t;try{t=await this.post("call_begin",{customerId:this.customerId,feature:n.feature??this.defaultFeature,tags:n.tags??this.defaultTags,idempotencyKey:U()})}catch(o){this.telemetryError(o)}try{let o=await e();if(t?.callId)try{await this.post("call_end",{callId:t.callId,providerUsed:n.provider,modelUsed:n.model,...s(o)})}catch(a){this.telemetryError(a)}return o}catch(o){if(t?.callId)try{await this.post("call_end",{callId:t.callId,providerUsed:n.provider,modelUsed:n.model,error:x(o)})}catch(a){this.telemetryError(a)}throw o}}};function C(r,n){let e=new y(n),s=n.provider??"openai",t=r,o=d=>{if(!d)return;let i=d.create;return typeof i!="function"?d:new Proxy(d,{get(p,h,w){return h!=="create"?Reflect.get(p,h,w):async(k,...I)=>{let v=l(k)?k:{},b=()=>Reflect.apply(i,p,[k,...I]);return v.stream===!0?b():e.track({provider:s,model:T(v,"model")},()=>Promise.resolve(b()),_=>O(_,s))}}})},a=l(t.chat)?t.chat:void 0,m=o(a&&l(a.completions)?a.completions:void 0),f=a&&m?new Proxy(a,{get(d,i,p){return i==="completions"?m:Reflect.get(d,i,p)}}):a,u=o(l(t.responses)?t.responses:void 0);return new Proxy(r,{get(d,i,p){return i==="chat"&&f?f:i==="responses"&&u?u:i==="unwrap"?()=>r:Reflect.get(d,i,p)}})}function A(r,n){let e=new y(n),s=n.provider??"anthropic",t=r,o=l(t.messages)?t.messages:void 0,a=o?.create,m=o&&typeof a=="function"?new Proxy(o,{get(f,u,d){return u!=="create"?Reflect.get(f,u,d):async(i,...p)=>{let h=l(i)?i:{},w=()=>Reflect.apply(a,f,[i,...p]);return h.stream===!0?w():e.track({provider:s,model:T(h,"model")},()=>Promise.resolve(w()),k=>P(k,s))}}}):o;return new Proxy(r,{get(f,u,d){return u==="messages"&&m?m:u==="unwrap"?()=>r:Reflect.get(f,u,d)}})}export{y as UsageTapObserver,P as extractAnthropicUsage,O as extractOpenAIUsage,A as observeAnthropic,C as observeOpenAI};
2
2
  //# sourceMappingURL=index.mjs.map
@@ -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.0\",\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 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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usagetap/observer",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "A tiny metadata-only UsageTap observer for AI usage and cost trials.",
5
5
  "license": "MIT",
6
6
  "repository": {