@usagetap/sdk 1.3.1 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +1058 -863
  2. package/dist/adapters/anthropic.cjs +990 -24
  3. package/dist/adapters/anthropic.cjs.map +1 -1
  4. package/dist/adapters/anthropic.d.cts +45 -3
  5. package/dist/adapters/anthropic.d.ts +45 -3
  6. package/dist/adapters/anthropic.mjs +990 -25
  7. package/dist/adapters/anthropic.mjs.map +1 -1
  8. package/dist/adapters/openai.cjs +1164 -44
  9. package/dist/adapters/openai.cjs.map +1 -1
  10. package/dist/adapters/openai.d.cts +46 -3
  11. package/dist/adapters/openai.d.ts +46 -3
  12. package/dist/adapters/openai.mjs +1164 -45
  13. package/dist/adapters/openai.mjs.map +1 -1
  14. package/dist/adapters/openrouter.cjs +3899 -22
  15. package/dist/adapters/openrouter.cjs.map +1 -1
  16. package/dist/adapters/openrouter.d.cts +6 -3
  17. package/dist/adapters/openrouter.d.ts +6 -3
  18. package/dist/adapters/openrouter.mjs +3897 -23
  19. package/dist/adapters/openrouter.mjs.map +1 -1
  20. package/dist/anthropic/index.cjs +990 -24
  21. package/dist/anthropic/index.cjs.map +1 -1
  22. package/dist/anthropic/index.d.cts +2 -2
  23. package/dist/anthropic/index.d.ts +2 -2
  24. package/dist/anthropic/index.mjs +990 -25
  25. package/dist/anthropic/index.mjs.map +1 -1
  26. package/dist/client-CExQ8e1T.d.cts +1225 -0
  27. package/dist/client-CExQ8e1T.d.ts +1225 -0
  28. package/dist/express/index.cjs +421 -29
  29. package/dist/express/index.cjs.map +1 -1
  30. package/dist/express/index.d.cts +2 -2
  31. package/dist/express/index.d.ts +2 -2
  32. package/dist/express/index.mjs +421 -29
  33. package/dist/express/index.mjs.map +1 -1
  34. package/dist/index.cjs +680 -15
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.d.cts +5 -3
  37. package/dist/index.d.ts +5 -3
  38. package/dist/index.mjs +680 -15
  39. package/dist/index.mjs.map +1 -1
  40. package/dist/openai/index.cjs +1165 -45
  41. package/dist/openai/index.cjs.map +1 -1
  42. package/dist/openai/index.d.cts +2 -2
  43. package/dist/openai/index.d.ts +2 -2
  44. package/dist/openai/index.mjs +1165 -46
  45. package/dist/openai/index.mjs.map +1 -1
  46. package/dist/openrouter/index.cjs +1182 -47
  47. package/dist/openrouter/index.cjs.map +1 -1
  48. package/dist/openrouter/index.d.cts +3 -3
  49. package/dist/openrouter/index.d.ts +3 -3
  50. package/dist/openrouter/index.mjs +1180 -46
  51. package/dist/openrouter/index.mjs.map +1 -1
  52. package/dist/react/index.cjs +19 -1
  53. package/dist/react/index.cjs.map +1 -1
  54. package/dist/react/index.d.cts +17 -4
  55. package/dist/react/index.d.ts +17 -4
  56. package/dist/react/index.mjs +19 -1
  57. package/dist/react/index.mjs.map +1 -1
  58. package/package.json +84 -84
  59. package/dist/client-BD8O2J8Z.d.cts +0 -668
  60. package/dist/client-BD8O2J8Z.d.ts +0 -668
@@ -72,6 +72,9 @@ function useChatWithUsage(options) {
72
72
  const {
73
73
  api,
74
74
  customerId,
75
+ customerUserId,
76
+ customerUserName,
77
+ customerUserEmail,
75
78
  feature,
76
79
  tags,
77
80
  initialMessages = [],
@@ -103,6 +106,9 @@ function useChatWithUsage(options) {
103
106
  body: JSON.stringify({
104
107
  messages: newMessages,
105
108
  customerId,
109
+ customerUserId,
110
+ customerUserName,
111
+ customerUserEmail,
106
112
  feature,
107
113
  tags
108
114
  }),
@@ -177,7 +183,19 @@ function useChatWithUsage(options) {
177
183
  abortControllerRef.current = null;
178
184
  }
179
185
  },
180
- [messages, api, customerId, feature, tags, customHeaders, onError, onFinish]
186
+ [
187
+ messages,
188
+ api,
189
+ customerId,
190
+ customerUserId,
191
+ customerUserName,
192
+ customerUserEmail,
193
+ feature,
194
+ tags,
195
+ customHeaders,
196
+ onError,
197
+ onFinish
198
+ ]
181
199
  );
182
200
  const handleSubmit = react.useCallback(
183
201
  async (e) => {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/react/useChatWithUsage.ts"],"names":["useState","useRef","useCallback","error","useEffect"],"mappings":"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","file":"index.cjs","sourcesContent":["import { useState, useCallback, useRef, useEffect } from \"react\";\r\nimport type { FormEvent } from \"react\";\r\n\r\ntype JsonRecord = Record<string, unknown>;\r\n\r\nfunction isJsonRecord(value: unknown): value is JsonRecord {\r\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\r\n}\r\n\r\nfunction extractAssistantContent(payload: unknown): string {\r\n if (!isJsonRecord(payload)) {\r\n return \"\";\r\n }\r\n\r\n const choices = payload.choices;\r\n if (Array.isArray(choices)) {\r\n for (const choice of choices) {\r\n if (!isJsonRecord(choice)) continue;\r\n const message = choice.message;\r\n if (!isJsonRecord(message)) continue;\r\n const content = message.content;\r\n if (typeof content === \"string\") {\r\n return content;\r\n }\r\n }\r\n }\r\n\r\n const directContent = payload.content;\r\n return typeof directContent === \"string\" ? directContent : \"\";\r\n}\r\n\r\nfunction extractStreamingContent(payload: unknown): string {\r\n if (!isJsonRecord(payload)) {\r\n return \"\";\r\n }\r\n\r\n const choices = payload.choices;\r\n if (Array.isArray(choices)) {\r\n for (const choice of choices) {\r\n if (!isJsonRecord(choice)) continue;\r\n const delta = choice.delta;\r\n if (!isJsonRecord(delta)) continue;\r\n const content = delta.content;\r\n if (typeof content === \"string\") {\r\n return content;\r\n }\r\n\r\n if (Array.isArray(content)) {\r\n return content\r\n .map((entry) => {\r\n if (!entry) return \"\";\r\n if (typeof entry === \"string\") return entry;\r\n if (isJsonRecord(entry) && typeof entry.text === \"string\") return entry.text;\r\n return \"\";\r\n })\r\n .join(\"\");\r\n }\r\n }\r\n }\r\n\r\n const directContent = payload.content;\r\n return typeof directContent === \"string\" ? directContent : \"\";\r\n}\r\n\r\nfunction tryParseJson(text: string): unknown {\r\n try {\r\n return JSON.parse(text) as unknown;\r\n } catch {\r\n return undefined;\r\n }\r\n}\r\n\r\nfunction isAbortError(error: unknown): boolean {\r\n if (typeof DOMException !== \"undefined\" && error instanceof DOMException) {\r\n return error.name === \"AbortError\";\r\n }\r\n\r\n if (isJsonRecord(error) && typeof error.name === \"string\") {\r\n return error.name === \"AbortError\";\r\n }\r\n\r\n return false;\r\n}\r\n\r\nexport interface Message {\r\n role: \"user\" | \"assistant\" | \"system\";\r\n content: string;\r\n id?: string;\r\n}\r\n\r\nexport interface UseChatWithUsageOptions {\r\n /**\r\n * API endpoint that handles the chat completion.\r\n * Should be a server-side route that uses UsageTap.\r\n */\r\n api: string;\r\n \r\n /**\r\n * Customer ID to track usage for.\r\n * This will be sent to the API endpoint.\r\n */\r\n customerId: string;\r\n \r\n /**\r\n * Optional feature name for usage tracking.\r\n */\r\n feature?: string;\r\n \r\n /**\r\n * Optional tags for usage tracking.\r\n */\r\n tags?: string[];\r\n \r\n /**\r\n * Initial messages to populate the chat.\r\n */\r\n initialMessages?: Message[];\r\n \r\n /**\r\n * Called when an error occurs.\r\n */\r\n onError?: (error: Error) => void;\r\n \r\n /**\r\n * Called when a response is finished.\r\n */\r\n onFinish?: (message: Message) => void;\r\n \r\n /**\r\n * Additional headers to send with requests.\r\n */\r\n headers?: Record<string, string>;\r\n}\r\n\r\nexport interface UseChatWithUsageReturn {\r\n /**\r\n * Current messages in the chat.\r\n */\r\n messages: Message[];\r\n \r\n /**\r\n * Current input value.\r\n */\r\n input: string;\r\n \r\n /**\r\n * Set the input value.\r\n */\r\n setInput: (input: string) => void;\r\n \r\n /**\r\n * Whether a request is in progress.\r\n */\r\n isLoading: boolean;\r\n \r\n /**\r\n * Current error, if any.\r\n */\r\n error: Error | null;\r\n \r\n /**\r\n * Append a user message and get a response.\r\n */\r\n append: (message: Message) => Promise<void>;\r\n \r\n /**\r\n * Submit the current input as a user message.\r\n */\r\n handleSubmit: (e?: React.FormEvent<HTMLFormElement>) => Promise<void>;\r\n \r\n /**\r\n * Reload the last assistant response.\r\n */\r\n reload: () => Promise<void>;\r\n \r\n /**\r\n * Stop the current streaming response.\r\n */\r\n stop: () => void;\r\n \r\n /**\r\n * Clear all messages.\r\n */\r\n clear: () => void;\r\n}\r\n\r\n/**\r\n * React hook for chat interfaces with UsageTap integration.\r\n * \r\n * The API endpoint should be a server-side route that:\r\n * 1. Receives messages, customerId, feature, and tags\r\n * 2. Uses UsageTap SDK to wrap OpenAI calls\r\n * 3. Returns streaming or non-streaming responses\r\n * \r\n * @example\r\n * ```tsx\r\n * import { useChatWithUsage } from \"@usagetap/sdk/react\";\r\n *\r\n * function ChatComponent({ userId }) {\r\n * const { messages, input, setInput, handleSubmit, isLoading } = useChatWithUsage({\r\n * api: \"/api/chat\",\r\n * customerId: userId,\r\n * feature: \"chat.assistant\",\r\n * });\r\n * \r\n * return (\r\n * <div>\r\n * {messages.map((m) => (\r\n * <div key={m.id}>\r\n * <strong>{m.role}:</strong> {m.content}\r\n * </div>\r\n * ))}\r\n * <form onSubmit={handleSubmit}>\r\n * <input\r\n * value={input}\r\n * onChange={(e) => setInput(e.target.value)}\r\n * disabled={isLoading}\r\n * />\r\n * <button type=\"submit\" disabled={isLoading}>\r\n * Send\r\n * </button>\r\n * </form>\r\n * </div>\r\n * );\r\n * }\r\n * ```\r\n * \r\n * Server route example:\r\n * ```ts\r\n * // app/api/chat/route.ts\r\n * import { streamOpenAIRoute } from \"@usagetap/sdk/openai\";\r\n * \r\n * export const POST = streamOpenAIRoute(usageTap, openai, {\r\n * getRequest: async (req) => {\r\n * const body = await req.json();\r\n * return {\r\n * params: { model: \"gpt-4o-mini\", messages: body.messages },\r\n * context: {\r\n * customerId: body.customerId,\r\n * feature: body.feature,\r\n * tags: body.tags,\r\n * },\r\n * };\r\n * },\r\n * });\r\n * ```\r\n */\r\nexport function useChatWithUsage(options: UseChatWithUsageOptions): UseChatWithUsageReturn {\r\n const {\r\n api,\r\n customerId,\r\n feature,\r\n tags,\r\n initialMessages = [],\r\n onError,\r\n onFinish,\r\n headers: customHeaders = {},\r\n } = options;\r\n\r\n const [messages, setMessages] = useState<Message[]>(initialMessages);\r\n const [input, setInput] = useState(\"\");\r\n const [isLoading, setIsLoading] = useState(false);\r\n const [error, setError] = useState<Error | null>(null);\r\n const abortControllerRef = useRef<AbortController | null>(null);\r\n\r\n const generateId = (): string => `msg_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;\r\n\r\n const append = useCallback(\r\n async (message: Message): Promise<void> => {\r\n setIsLoading(true);\r\n setError(null);\r\n\r\n const messageWithId = { ...message, id: message.id || generateId() };\r\n const newMessages = [...messages, messageWithId];\r\n setMessages(newMessages);\r\n\r\n abortControllerRef.current = new AbortController();\r\n\r\n try {\r\n const response = await fetch(api, {\r\n method: \"POST\",\r\n headers: {\r\n \"Content-Type\": \"application/json\",\r\n ...customHeaders,\r\n },\r\n body: JSON.stringify({\r\n messages: newMessages,\r\n customerId,\r\n feature,\r\n tags,\r\n }),\r\n signal: abortControllerRef.current.signal,\r\n });\r\n\r\n if (!response.ok) {\r\n throw new Error(`HTTP error! status: ${response.status}`);\r\n }\r\n\r\n const contentType = response.headers.get(\"content-type\");\r\n const isStream = contentType?.includes(\"text/event-stream\") || contentType?.includes(\"text/plain\");\r\n\r\n if (isStream && response.body) {\r\n // Handle streaming response\r\n const reader = response.body.getReader();\r\n const decoder = new TextDecoder();\r\n let assistantMessage = \"\";\r\n const assistantId = generateId();\r\n\r\n while (true) {\r\n const readResult = await reader.read();\r\n if (readResult.done) break;\r\n\r\n const chunkValue: Uint8Array = readResult.value instanceof Uint8Array\r\n ? readResult.value\r\n : new Uint8Array();\r\n\r\n const chunk = decoder.decode(chunkValue, { stream: true });\r\n const lines = chunk.split(\"\\n\");\r\n\r\n for (const line of lines) {\r\n if (line.startsWith(\"data: \")) {\r\n const data = line.slice(6);\r\n if (data === \"[DONE]\") continue;\r\n\r\n const parsed = tryParseJson(data);\r\n if (parsed) {\r\n const content = extractStreamingContent(parsed);\r\n if (content) {\r\n assistantMessage += content;\r\n setMessages([\r\n ...newMessages,\r\n { role: \"assistant\", content: assistantMessage, id: assistantId },\r\n ]);\r\n }\r\n } else {\r\n // Plain text streaming\r\n assistantMessage += data;\r\n setMessages([\r\n ...newMessages,\r\n { role: \"assistant\", content: assistantMessage, id: assistantId },\r\n ]);\r\n }\r\n }\r\n }\r\n }\r\n\r\n const finalMessage: Message = {\r\n role: \"assistant\",\r\n content: assistantMessage,\r\n id: assistantId,\r\n };\r\n \r\n setMessages([...newMessages, finalMessage]);\r\n onFinish?.(finalMessage);\r\n } else {\r\n // Handle non-streaming response\r\n const payload = (await response.json()) as unknown;\r\n const assistantMessage: Message = {\r\n role: \"assistant\",\r\n content: extractAssistantContent(payload),\r\n id: generateId(),\r\n };\r\n \r\n setMessages([...newMessages, assistantMessage]);\r\n onFinish?.(assistantMessage);\r\n }\r\n } catch (err) {\r\n if (isAbortError(err)) {\r\n // Request was aborted, don't treat as error\r\n return;\r\n }\r\n \r\n const error = err instanceof Error ? err : new Error(String(err));\r\n setError(error);\r\n onError?.(error);\r\n } finally {\r\n setIsLoading(false);\r\n abortControllerRef.current = null;\r\n }\r\n },\r\n [messages, api, customerId, feature, tags, customHeaders, onError, onFinish],\r\n );\r\n\r\n const handleSubmit = useCallback(\r\n async (e?: FormEvent<HTMLFormElement>): Promise<void> => {\r\n e?.preventDefault();\r\n if (!input.trim() || isLoading) return;\r\n\r\n const userMessage: Message = {\r\n role: \"user\",\r\n content: input,\r\n };\r\n\r\n setInput(\"\");\r\n await append(userMessage);\r\n },\r\n [input, isLoading, append],\r\n );\r\n\r\n const reload = useCallback(async (): Promise<void> => {\r\n if (messages.length === 0 || isLoading) return;\r\n\r\n // Remove last assistant message and resend last user message\r\n const messagesWithoutLast = messages.slice(0, -1);\r\n const lastUserMessage = [...messagesWithoutLast].reverse().find((m) => m.role === \"user\");\r\n\r\n if (lastUserMessage) {\r\n setMessages(messagesWithoutLast);\r\n await append(lastUserMessage);\r\n }\r\n }, [messages, isLoading, append]);\r\n\r\n const stop = useCallback((): void => {\r\n if (abortControllerRef.current) {\r\n abortControllerRef.current.abort();\r\n abortControllerRef.current = null;\r\n setIsLoading(false);\r\n }\r\n }, []);\r\n\r\n const clear = useCallback((): void => {\r\n setMessages([]);\r\n setInput(\"\");\r\n setError(null);\r\n }, []);\r\n\r\n // Cleanup on unmount\r\n useEffect(() => {\r\n return () => {\r\n if (abortControllerRef.current) {\r\n abortControllerRef.current.abort();\r\n }\r\n };\r\n }, []);\r\n\r\n return {\r\n messages,\r\n input,\r\n setInput,\r\n isLoading,\r\n error,\r\n append,\r\n handleSubmit,\r\n reload,\r\n stop,\r\n clear,\r\n };\r\n}\r\n"]}
1
+ {"version":3,"sources":["../../src/react/useChatWithUsage.ts"],"names":["useState","useRef","useCallback","error","useEffect"],"mappings":"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","file":"index.cjs","sourcesContent":["import { useState, useCallback, useRef, useEffect } from \"react\";\nimport type { FormEvent } from \"react\";\n\ntype JsonRecord = Record<string, unknown>;\n\nfunction isJsonRecord(value: unknown): value is JsonRecord {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction extractAssistantContent(payload: unknown): string {\n if (!isJsonRecord(payload)) {\n return \"\";\n }\n\n const choices = payload.choices;\n if (Array.isArray(choices)) {\n for (const choice of choices) {\n if (!isJsonRecord(choice)) continue;\n const message = choice.message;\n if (!isJsonRecord(message)) continue;\n const content = message.content;\n if (typeof content === \"string\") {\n return content;\n }\n }\n }\n\n const directContent = payload.content;\n return typeof directContent === \"string\" ? directContent : \"\";\n}\n\nfunction extractStreamingContent(payload: unknown): string {\n if (!isJsonRecord(payload)) {\n return \"\";\n }\n\n const choices = payload.choices;\n if (Array.isArray(choices)) {\n for (const choice of choices) {\n if (!isJsonRecord(choice)) continue;\n const delta = choice.delta;\n if (!isJsonRecord(delta)) continue;\n const content = delta.content;\n if (typeof content === \"string\") {\n return content;\n }\n\n if (Array.isArray(content)) {\n return content\n .map((entry) => {\n if (!entry) return \"\";\n if (typeof entry === \"string\") return entry;\n if (isJsonRecord(entry) && typeof entry.text === \"string\") return entry.text;\n return \"\";\n })\n .join(\"\");\n }\n }\n }\n\n const directContent = payload.content;\n return typeof directContent === \"string\" ? directContent : \"\";\n}\n\nfunction tryParseJson(text: string): unknown {\n try {\n return JSON.parse(text) as unknown;\n } catch {\n return undefined;\n }\n}\n\nfunction isAbortError(error: unknown): boolean {\n if (typeof DOMException !== \"undefined\" && error instanceof DOMException) {\n return error.name === \"AbortError\";\n }\n\n if (isJsonRecord(error) && typeof error.name === \"string\") {\n return error.name === \"AbortError\";\n }\n\n return false;\n}\n\nexport interface Message {\n role: \"user\" | \"assistant\" | \"system\";\n content: string;\n id?: string;\n}\n\nexport interface UseChatWithUsageOptions {\n /**\n * API endpoint that handles the chat completion.\n * Should be a server-side route that uses UsageTap.\n */\n api: string;\n \n /**\n * Customer ID to track usage for.\n * This will be sent to the API endpoint.\n */\n customerId: string;\n\n /**\n * Optional end-user ID forwarded to your application API endpoint.\n * The server must validate or replace this value from trusted auth context\n * before passing it to UsageTap.\n */\n customerUserId?: string;\n\n /** Optional end-user display name forwarded to your application endpoint. */\n customerUserName?: string;\n\n /** Optional end-user display email forwarded to your application endpoint. */\n customerUserEmail?: string;\n \n /**\n * Optional feature name for usage tracking.\n */\n feature?: string;\n \n /**\n * Optional tags for usage tracking.\n */\n tags?: string[];\n \n /**\n * Initial messages to populate the chat.\n */\n initialMessages?: Message[];\n \n /**\n * Called when an error occurs.\n */\n onError?: (error: Error) => void;\n \n /**\n * Called when a response is finished.\n */\n onFinish?: (message: Message) => void;\n \n /**\n * Additional headers to send with requests.\n */\n headers?: Record<string, string>;\n}\n\nexport interface UseChatWithUsageReturn {\n /**\n * Current messages in the chat.\n */\n messages: Message[];\n \n /**\n * Current input value.\n */\n input: string;\n \n /**\n * Set the input value.\n */\n setInput: (input: string) => void;\n \n /**\n * Whether a request is in progress.\n */\n isLoading: boolean;\n \n /**\n * Current error, if any.\n */\n error: Error | null;\n \n /**\n * Append a user message and get a response.\n */\n append: (message: Message) => Promise<void>;\n \n /**\n * Submit the current input as a user message.\n */\n handleSubmit: (e?: React.FormEvent<HTMLFormElement>) => Promise<void>;\n \n /**\n * Reload the last assistant response.\n */\n reload: () => Promise<void>;\n \n /**\n * Stop the current streaming response.\n */\n stop: () => void;\n \n /**\n * Clear all messages.\n */\n clear: () => void;\n}\n\n/**\n * React hook for chat interfaces with UsageTap integration.\n * \n * The API endpoint should be a server-side route that:\n * 1. Receives messages, customerId, optional end-user hints, feature, and tags\n * 2. Uses UsageTap SDK to wrap OpenAI calls\n * 3. Returns streaming or non-streaming responses\n * \n * @example\n * ```tsx\n * import { useChatWithUsage } from \"@usagetap/sdk/react\";\n *\n * function ChatComponent({ customerId, currentUser }) {\n * const { messages, input, setInput, handleSubmit, isLoading } = useChatWithUsage({\n * api: \"/api/chat\",\n * customerId,\n * customerUserId: currentUser.id,\n * feature: \"chat.assistant\",\n * });\n * \n * return (\n * <div>\n * {messages.map((m) => (\n * <div key={m.id}>\n * <strong>{m.role}:</strong> {m.content}\n * </div>\n * ))}\n * <form onSubmit={handleSubmit}>\n * <input\n * value={input}\n * onChange={(e) => setInput(e.target.value)}\n * disabled={isLoading}\n * />\n * <button type=\"submit\" disabled={isLoading}>\n * Send\n * </button>\n * </form>\n * </div>\n * );\n * }\n * ```\n * \n * Server route example:\n * ```ts\n * // app/api/chat/route.ts\n * import { streamOpenAIRoute } from \"@usagetap/sdk/openai\";\n * \n * export const POST = streamOpenAIRoute(usageTap, openai, {\n * getRequest: async (req) => {\n * const body = await req.json();\n * return {\n * params: { model: \"gpt-5.6-luna\", messages: body.messages },\n * context: {\n * customerId: body.customerId,\n * // Validate against the authenticated server session first.\n * customerUserId: authenticatedUser.id,\n * feature: body.feature,\n * tags: body.tags,\n * },\n * };\n * },\n * });\n * ```\n */\nexport function useChatWithUsage(options: UseChatWithUsageOptions): UseChatWithUsageReturn {\n const {\n api,\n customerId,\n customerUserId,\n customerUserName,\n customerUserEmail,\n feature,\n tags,\n initialMessages = [],\n onError,\n onFinish,\n headers: customHeaders = {},\n } = options;\n\n const [messages, setMessages] = useState<Message[]>(initialMessages);\n const [input, setInput] = useState(\"\");\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n const abortControllerRef = useRef<AbortController | null>(null);\n\n const generateId = (): string => `msg_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;\n\n const append = useCallback(\n async (message: Message): Promise<void> => {\n setIsLoading(true);\n setError(null);\n\n const messageWithId = { ...message, id: message.id || generateId() };\n const newMessages = [...messages, messageWithId];\n setMessages(newMessages);\n\n abortControllerRef.current = new AbortController();\n\n try {\n const response = await fetch(api, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...customHeaders,\n },\n body: JSON.stringify({\n messages: newMessages,\n customerId,\n customerUserId,\n customerUserName,\n customerUserEmail,\n feature,\n tags,\n }),\n signal: abortControllerRef.current.signal,\n });\n\n if (!response.ok) {\n throw new Error(`HTTP error! status: ${response.status}`);\n }\n\n const contentType = response.headers.get(\"content-type\");\n const isStream = contentType?.includes(\"text/event-stream\") || contentType?.includes(\"text/plain\");\n\n if (isStream && response.body) {\n // Handle streaming response\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let assistantMessage = \"\";\n const assistantId = generateId();\n\n while (true) {\n const readResult = await reader.read();\n if (readResult.done) break;\n\n const chunkValue: Uint8Array = readResult.value instanceof Uint8Array\n ? readResult.value\n : new Uint8Array();\n\n const chunk = decoder.decode(chunkValue, { stream: true });\n const lines = chunk.split(\"\\n\");\n\n for (const line of lines) {\n if (line.startsWith(\"data: \")) {\n const data = line.slice(6);\n if (data === \"[DONE]\") continue;\n\n const parsed = tryParseJson(data);\n if (parsed) {\n const content = extractStreamingContent(parsed);\n if (content) {\n assistantMessage += content;\n setMessages([\n ...newMessages,\n { role: \"assistant\", content: assistantMessage, id: assistantId },\n ]);\n }\n } else {\n // Plain text streaming\n assistantMessage += data;\n setMessages([\n ...newMessages,\n { role: \"assistant\", content: assistantMessage, id: assistantId },\n ]);\n }\n }\n }\n }\n\n const finalMessage: Message = {\n role: \"assistant\",\n content: assistantMessage,\n id: assistantId,\n };\n \n setMessages([...newMessages, finalMessage]);\n onFinish?.(finalMessage);\n } else {\n // Handle non-streaming response\n const payload = (await response.json()) as unknown;\n const assistantMessage: Message = {\n role: \"assistant\",\n content: extractAssistantContent(payload),\n id: generateId(),\n };\n \n setMessages([...newMessages, assistantMessage]);\n onFinish?.(assistantMessage);\n }\n } catch (err) {\n if (isAbortError(err)) {\n // Request was aborted, don't treat as error\n return;\n }\n \n const error = err instanceof Error ? err : new Error(String(err));\n setError(error);\n onError?.(error);\n } finally {\n setIsLoading(false);\n abortControllerRef.current = null;\n }\n },\n [\n messages,\n api,\n customerId,\n customerUserId,\n customerUserName,\n customerUserEmail,\n feature,\n tags,\n customHeaders,\n onError,\n onFinish,\n ],\n );\n\n const handleSubmit = useCallback(\n async (e?: FormEvent<HTMLFormElement>): Promise<void> => {\n e?.preventDefault();\n if (!input.trim() || isLoading) return;\n\n const userMessage: Message = {\n role: \"user\",\n content: input,\n };\n\n setInput(\"\");\n await append(userMessage);\n },\n [input, isLoading, append],\n );\n\n const reload = useCallback(async (): Promise<void> => {\n if (messages.length === 0 || isLoading) return;\n\n // Remove last assistant message and resend last user message\n const messagesWithoutLast = messages.slice(0, -1);\n const lastUserMessage = [...messagesWithoutLast].reverse().find((m) => m.role === \"user\");\n\n if (lastUserMessage) {\n setMessages(messagesWithoutLast);\n await append(lastUserMessage);\n }\n }, [messages, isLoading, append]);\n\n const stop = useCallback((): void => {\n if (abortControllerRef.current) {\n abortControllerRef.current.abort();\n abortControllerRef.current = null;\n setIsLoading(false);\n }\n }, []);\n\n const clear = useCallback((): void => {\n setMessages([]);\n setInput(\"\");\n setError(null);\n }, []);\n\n // Cleanup on unmount\n useEffect(() => {\n return () => {\n if (abortControllerRef.current) {\n abortControllerRef.current.abort();\n }\n };\n }, []);\n\n return {\n messages,\n input,\n setInput,\n isLoading,\n error,\n append,\n handleSubmit,\n reload,\n stop,\n clear,\n };\n}\n"]}
@@ -14,6 +14,16 @@ interface UseChatWithUsageOptions {
14
14
  * This will be sent to the API endpoint.
15
15
  */
16
16
  customerId: string;
17
+ /**
18
+ * Optional end-user ID forwarded to your application API endpoint.
19
+ * The server must validate or replace this value from trusted auth context
20
+ * before passing it to UsageTap.
21
+ */
22
+ customerUserId?: string;
23
+ /** Optional end-user display name forwarded to your application endpoint. */
24
+ customerUserName?: string;
25
+ /** Optional end-user display email forwarded to your application endpoint. */
26
+ customerUserEmail?: string;
17
27
  /**
18
28
  * Optional feature name for usage tracking.
19
29
  */
@@ -85,7 +95,7 @@ interface UseChatWithUsageReturn {
85
95
  * React hook for chat interfaces with UsageTap integration.
86
96
  *
87
97
  * The API endpoint should be a server-side route that:
88
- * 1. Receives messages, customerId, feature, and tags
98
+ * 1. Receives messages, customerId, optional end-user hints, feature, and tags
89
99
  * 2. Uses UsageTap SDK to wrap OpenAI calls
90
100
  * 3. Returns streaming or non-streaming responses
91
101
  *
@@ -93,10 +103,11 @@ interface UseChatWithUsageReturn {
93
103
  * ```tsx
94
104
  * import { useChatWithUsage } from "@usagetap/sdk/react";
95
105
  *
96
- * function ChatComponent({ userId }) {
106
+ * function ChatComponent({ customerId, currentUser }) {
97
107
  * const { messages, input, setInput, handleSubmit, isLoading } = useChatWithUsage({
98
108
  * api: "/api/chat",
99
- * customerId: userId,
109
+ * customerId,
110
+ * customerUserId: currentUser.id,
100
111
  * feature: "chat.assistant",
101
112
  * });
102
113
  *
@@ -131,9 +142,11 @@ interface UseChatWithUsageReturn {
131
142
  * getRequest: async (req) => {
132
143
  * const body = await req.json();
133
144
  * return {
134
- * params: { model: "gpt-4o-mini", messages: body.messages },
145
+ * params: { model: "gpt-5.6-luna", messages: body.messages },
135
146
  * context: {
136
147
  * customerId: body.customerId,
148
+ * // Validate against the authenticated server session first.
149
+ * customerUserId: authenticatedUser.id,
137
150
  * feature: body.feature,
138
151
  * tags: body.tags,
139
152
  * },
@@ -14,6 +14,16 @@ interface UseChatWithUsageOptions {
14
14
  * This will be sent to the API endpoint.
15
15
  */
16
16
  customerId: string;
17
+ /**
18
+ * Optional end-user ID forwarded to your application API endpoint.
19
+ * The server must validate or replace this value from trusted auth context
20
+ * before passing it to UsageTap.
21
+ */
22
+ customerUserId?: string;
23
+ /** Optional end-user display name forwarded to your application endpoint. */
24
+ customerUserName?: string;
25
+ /** Optional end-user display email forwarded to your application endpoint. */
26
+ customerUserEmail?: string;
17
27
  /**
18
28
  * Optional feature name for usage tracking.
19
29
  */
@@ -85,7 +95,7 @@ interface UseChatWithUsageReturn {
85
95
  * React hook for chat interfaces with UsageTap integration.
86
96
  *
87
97
  * The API endpoint should be a server-side route that:
88
- * 1. Receives messages, customerId, feature, and tags
98
+ * 1. Receives messages, customerId, optional end-user hints, feature, and tags
89
99
  * 2. Uses UsageTap SDK to wrap OpenAI calls
90
100
  * 3. Returns streaming or non-streaming responses
91
101
  *
@@ -93,10 +103,11 @@ interface UseChatWithUsageReturn {
93
103
  * ```tsx
94
104
  * import { useChatWithUsage } from "@usagetap/sdk/react";
95
105
  *
96
- * function ChatComponent({ userId }) {
106
+ * function ChatComponent({ customerId, currentUser }) {
97
107
  * const { messages, input, setInput, handleSubmit, isLoading } = useChatWithUsage({
98
108
  * api: "/api/chat",
99
- * customerId: userId,
109
+ * customerId,
110
+ * customerUserId: currentUser.id,
100
111
  * feature: "chat.assistant",
101
112
  * });
102
113
  *
@@ -131,9 +142,11 @@ interface UseChatWithUsageReturn {
131
142
  * getRequest: async (req) => {
132
143
  * const body = await req.json();
133
144
  * return {
134
- * params: { model: "gpt-4o-mini", messages: body.messages },
145
+ * params: { model: "gpt-5.6-luna", messages: body.messages },
135
146
  * context: {
136
147
  * customerId: body.customerId,
148
+ * // Validate against the authenticated server session first.
149
+ * customerUserId: authenticatedUser.id,
137
150
  * feature: body.feature,
138
151
  * tags: body.tags,
139
152
  * },
@@ -70,6 +70,9 @@ function useChatWithUsage(options) {
70
70
  const {
71
71
  api,
72
72
  customerId,
73
+ customerUserId,
74
+ customerUserName,
75
+ customerUserEmail,
73
76
  feature,
74
77
  tags,
75
78
  initialMessages = [],
@@ -101,6 +104,9 @@ function useChatWithUsage(options) {
101
104
  body: JSON.stringify({
102
105
  messages: newMessages,
103
106
  customerId,
107
+ customerUserId,
108
+ customerUserName,
109
+ customerUserEmail,
104
110
  feature,
105
111
  tags
106
112
  }),
@@ -175,7 +181,19 @@ function useChatWithUsage(options) {
175
181
  abortControllerRef.current = null;
176
182
  }
177
183
  },
178
- [messages, api, customerId, feature, tags, customHeaders, onError, onFinish]
184
+ [
185
+ messages,
186
+ api,
187
+ customerId,
188
+ customerUserId,
189
+ customerUserName,
190
+ customerUserEmail,
191
+ feature,
192
+ tags,
193
+ customHeaders,
194
+ onError,
195
+ onFinish
196
+ ]
179
197
  );
180
198
  const handleSubmit = useCallback(
181
199
  async (e) => {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/react/useChatWithUsage.ts"],"names":["error"],"mappings":"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","file":"index.mjs","sourcesContent":["import { useState, useCallback, useRef, useEffect } from \"react\";\r\nimport type { FormEvent } from \"react\";\r\n\r\ntype JsonRecord = Record<string, unknown>;\r\n\r\nfunction isJsonRecord(value: unknown): value is JsonRecord {\r\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\r\n}\r\n\r\nfunction extractAssistantContent(payload: unknown): string {\r\n if (!isJsonRecord(payload)) {\r\n return \"\";\r\n }\r\n\r\n const choices = payload.choices;\r\n if (Array.isArray(choices)) {\r\n for (const choice of choices) {\r\n if (!isJsonRecord(choice)) continue;\r\n const message = choice.message;\r\n if (!isJsonRecord(message)) continue;\r\n const content = message.content;\r\n if (typeof content === \"string\") {\r\n return content;\r\n }\r\n }\r\n }\r\n\r\n const directContent = payload.content;\r\n return typeof directContent === \"string\" ? directContent : \"\";\r\n}\r\n\r\nfunction extractStreamingContent(payload: unknown): string {\r\n if (!isJsonRecord(payload)) {\r\n return \"\";\r\n }\r\n\r\n const choices = payload.choices;\r\n if (Array.isArray(choices)) {\r\n for (const choice of choices) {\r\n if (!isJsonRecord(choice)) continue;\r\n const delta = choice.delta;\r\n if (!isJsonRecord(delta)) continue;\r\n const content = delta.content;\r\n if (typeof content === \"string\") {\r\n return content;\r\n }\r\n\r\n if (Array.isArray(content)) {\r\n return content\r\n .map((entry) => {\r\n if (!entry) return \"\";\r\n if (typeof entry === \"string\") return entry;\r\n if (isJsonRecord(entry) && typeof entry.text === \"string\") return entry.text;\r\n return \"\";\r\n })\r\n .join(\"\");\r\n }\r\n }\r\n }\r\n\r\n const directContent = payload.content;\r\n return typeof directContent === \"string\" ? directContent : \"\";\r\n}\r\n\r\nfunction tryParseJson(text: string): unknown {\r\n try {\r\n return JSON.parse(text) as unknown;\r\n } catch {\r\n return undefined;\r\n }\r\n}\r\n\r\nfunction isAbortError(error: unknown): boolean {\r\n if (typeof DOMException !== \"undefined\" && error instanceof DOMException) {\r\n return error.name === \"AbortError\";\r\n }\r\n\r\n if (isJsonRecord(error) && typeof error.name === \"string\") {\r\n return error.name === \"AbortError\";\r\n }\r\n\r\n return false;\r\n}\r\n\r\nexport interface Message {\r\n role: \"user\" | \"assistant\" | \"system\";\r\n content: string;\r\n id?: string;\r\n}\r\n\r\nexport interface UseChatWithUsageOptions {\r\n /**\r\n * API endpoint that handles the chat completion.\r\n * Should be a server-side route that uses UsageTap.\r\n */\r\n api: string;\r\n \r\n /**\r\n * Customer ID to track usage for.\r\n * This will be sent to the API endpoint.\r\n */\r\n customerId: string;\r\n \r\n /**\r\n * Optional feature name for usage tracking.\r\n */\r\n feature?: string;\r\n \r\n /**\r\n * Optional tags for usage tracking.\r\n */\r\n tags?: string[];\r\n \r\n /**\r\n * Initial messages to populate the chat.\r\n */\r\n initialMessages?: Message[];\r\n \r\n /**\r\n * Called when an error occurs.\r\n */\r\n onError?: (error: Error) => void;\r\n \r\n /**\r\n * Called when a response is finished.\r\n */\r\n onFinish?: (message: Message) => void;\r\n \r\n /**\r\n * Additional headers to send with requests.\r\n */\r\n headers?: Record<string, string>;\r\n}\r\n\r\nexport interface UseChatWithUsageReturn {\r\n /**\r\n * Current messages in the chat.\r\n */\r\n messages: Message[];\r\n \r\n /**\r\n * Current input value.\r\n */\r\n input: string;\r\n \r\n /**\r\n * Set the input value.\r\n */\r\n setInput: (input: string) => void;\r\n \r\n /**\r\n * Whether a request is in progress.\r\n */\r\n isLoading: boolean;\r\n \r\n /**\r\n * Current error, if any.\r\n */\r\n error: Error | null;\r\n \r\n /**\r\n * Append a user message and get a response.\r\n */\r\n append: (message: Message) => Promise<void>;\r\n \r\n /**\r\n * Submit the current input as a user message.\r\n */\r\n handleSubmit: (e?: React.FormEvent<HTMLFormElement>) => Promise<void>;\r\n \r\n /**\r\n * Reload the last assistant response.\r\n */\r\n reload: () => Promise<void>;\r\n \r\n /**\r\n * Stop the current streaming response.\r\n */\r\n stop: () => void;\r\n \r\n /**\r\n * Clear all messages.\r\n */\r\n clear: () => void;\r\n}\r\n\r\n/**\r\n * React hook for chat interfaces with UsageTap integration.\r\n * \r\n * The API endpoint should be a server-side route that:\r\n * 1. Receives messages, customerId, feature, and tags\r\n * 2. Uses UsageTap SDK to wrap OpenAI calls\r\n * 3. Returns streaming or non-streaming responses\r\n * \r\n * @example\r\n * ```tsx\r\n * import { useChatWithUsage } from \"@usagetap/sdk/react\";\r\n *\r\n * function ChatComponent({ userId }) {\r\n * const { messages, input, setInput, handleSubmit, isLoading } = useChatWithUsage({\r\n * api: \"/api/chat\",\r\n * customerId: userId,\r\n * feature: \"chat.assistant\",\r\n * });\r\n * \r\n * return (\r\n * <div>\r\n * {messages.map((m) => (\r\n * <div key={m.id}>\r\n * <strong>{m.role}:</strong> {m.content}\r\n * </div>\r\n * ))}\r\n * <form onSubmit={handleSubmit}>\r\n * <input\r\n * value={input}\r\n * onChange={(e) => setInput(e.target.value)}\r\n * disabled={isLoading}\r\n * />\r\n * <button type=\"submit\" disabled={isLoading}>\r\n * Send\r\n * </button>\r\n * </form>\r\n * </div>\r\n * );\r\n * }\r\n * ```\r\n * \r\n * Server route example:\r\n * ```ts\r\n * // app/api/chat/route.ts\r\n * import { streamOpenAIRoute } from \"@usagetap/sdk/openai\";\r\n * \r\n * export const POST = streamOpenAIRoute(usageTap, openai, {\r\n * getRequest: async (req) => {\r\n * const body = await req.json();\r\n * return {\r\n * params: { model: \"gpt-4o-mini\", messages: body.messages },\r\n * context: {\r\n * customerId: body.customerId,\r\n * feature: body.feature,\r\n * tags: body.tags,\r\n * },\r\n * };\r\n * },\r\n * });\r\n * ```\r\n */\r\nexport function useChatWithUsage(options: UseChatWithUsageOptions): UseChatWithUsageReturn {\r\n const {\r\n api,\r\n customerId,\r\n feature,\r\n tags,\r\n initialMessages = [],\r\n onError,\r\n onFinish,\r\n headers: customHeaders = {},\r\n } = options;\r\n\r\n const [messages, setMessages] = useState<Message[]>(initialMessages);\r\n const [input, setInput] = useState(\"\");\r\n const [isLoading, setIsLoading] = useState(false);\r\n const [error, setError] = useState<Error | null>(null);\r\n const abortControllerRef = useRef<AbortController | null>(null);\r\n\r\n const generateId = (): string => `msg_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;\r\n\r\n const append = useCallback(\r\n async (message: Message): Promise<void> => {\r\n setIsLoading(true);\r\n setError(null);\r\n\r\n const messageWithId = { ...message, id: message.id || generateId() };\r\n const newMessages = [...messages, messageWithId];\r\n setMessages(newMessages);\r\n\r\n abortControllerRef.current = new AbortController();\r\n\r\n try {\r\n const response = await fetch(api, {\r\n method: \"POST\",\r\n headers: {\r\n \"Content-Type\": \"application/json\",\r\n ...customHeaders,\r\n },\r\n body: JSON.stringify({\r\n messages: newMessages,\r\n customerId,\r\n feature,\r\n tags,\r\n }),\r\n signal: abortControllerRef.current.signal,\r\n });\r\n\r\n if (!response.ok) {\r\n throw new Error(`HTTP error! status: ${response.status}`);\r\n }\r\n\r\n const contentType = response.headers.get(\"content-type\");\r\n const isStream = contentType?.includes(\"text/event-stream\") || contentType?.includes(\"text/plain\");\r\n\r\n if (isStream && response.body) {\r\n // Handle streaming response\r\n const reader = response.body.getReader();\r\n const decoder = new TextDecoder();\r\n let assistantMessage = \"\";\r\n const assistantId = generateId();\r\n\r\n while (true) {\r\n const readResult = await reader.read();\r\n if (readResult.done) break;\r\n\r\n const chunkValue: Uint8Array = readResult.value instanceof Uint8Array\r\n ? readResult.value\r\n : new Uint8Array();\r\n\r\n const chunk = decoder.decode(chunkValue, { stream: true });\r\n const lines = chunk.split(\"\\n\");\r\n\r\n for (const line of lines) {\r\n if (line.startsWith(\"data: \")) {\r\n const data = line.slice(6);\r\n if (data === \"[DONE]\") continue;\r\n\r\n const parsed = tryParseJson(data);\r\n if (parsed) {\r\n const content = extractStreamingContent(parsed);\r\n if (content) {\r\n assistantMessage += content;\r\n setMessages([\r\n ...newMessages,\r\n { role: \"assistant\", content: assistantMessage, id: assistantId },\r\n ]);\r\n }\r\n } else {\r\n // Plain text streaming\r\n assistantMessage += data;\r\n setMessages([\r\n ...newMessages,\r\n { role: \"assistant\", content: assistantMessage, id: assistantId },\r\n ]);\r\n }\r\n }\r\n }\r\n }\r\n\r\n const finalMessage: Message = {\r\n role: \"assistant\",\r\n content: assistantMessage,\r\n id: assistantId,\r\n };\r\n \r\n setMessages([...newMessages, finalMessage]);\r\n onFinish?.(finalMessage);\r\n } else {\r\n // Handle non-streaming response\r\n const payload = (await response.json()) as unknown;\r\n const assistantMessage: Message = {\r\n role: \"assistant\",\r\n content: extractAssistantContent(payload),\r\n id: generateId(),\r\n };\r\n \r\n setMessages([...newMessages, assistantMessage]);\r\n onFinish?.(assistantMessage);\r\n }\r\n } catch (err) {\r\n if (isAbortError(err)) {\r\n // Request was aborted, don't treat as error\r\n return;\r\n }\r\n \r\n const error = err instanceof Error ? err : new Error(String(err));\r\n setError(error);\r\n onError?.(error);\r\n } finally {\r\n setIsLoading(false);\r\n abortControllerRef.current = null;\r\n }\r\n },\r\n [messages, api, customerId, feature, tags, customHeaders, onError, onFinish],\r\n );\r\n\r\n const handleSubmit = useCallback(\r\n async (e?: FormEvent<HTMLFormElement>): Promise<void> => {\r\n e?.preventDefault();\r\n if (!input.trim() || isLoading) return;\r\n\r\n const userMessage: Message = {\r\n role: \"user\",\r\n content: input,\r\n };\r\n\r\n setInput(\"\");\r\n await append(userMessage);\r\n },\r\n [input, isLoading, append],\r\n );\r\n\r\n const reload = useCallback(async (): Promise<void> => {\r\n if (messages.length === 0 || isLoading) return;\r\n\r\n // Remove last assistant message and resend last user message\r\n const messagesWithoutLast = messages.slice(0, -1);\r\n const lastUserMessage = [...messagesWithoutLast].reverse().find((m) => m.role === \"user\");\r\n\r\n if (lastUserMessage) {\r\n setMessages(messagesWithoutLast);\r\n await append(lastUserMessage);\r\n }\r\n }, [messages, isLoading, append]);\r\n\r\n const stop = useCallback((): void => {\r\n if (abortControllerRef.current) {\r\n abortControllerRef.current.abort();\r\n abortControllerRef.current = null;\r\n setIsLoading(false);\r\n }\r\n }, []);\r\n\r\n const clear = useCallback((): void => {\r\n setMessages([]);\r\n setInput(\"\");\r\n setError(null);\r\n }, []);\r\n\r\n // Cleanup on unmount\r\n useEffect(() => {\r\n return () => {\r\n if (abortControllerRef.current) {\r\n abortControllerRef.current.abort();\r\n }\r\n };\r\n }, []);\r\n\r\n return {\r\n messages,\r\n input,\r\n setInput,\r\n isLoading,\r\n error,\r\n append,\r\n handleSubmit,\r\n reload,\r\n stop,\r\n clear,\r\n };\r\n}\r\n"]}
1
+ {"version":3,"sources":["../../src/react/useChatWithUsage.ts"],"names":["error"],"mappings":"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","file":"index.mjs","sourcesContent":["import { useState, useCallback, useRef, useEffect } from \"react\";\nimport type { FormEvent } from \"react\";\n\ntype JsonRecord = Record<string, unknown>;\n\nfunction isJsonRecord(value: unknown): value is JsonRecord {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction extractAssistantContent(payload: unknown): string {\n if (!isJsonRecord(payload)) {\n return \"\";\n }\n\n const choices = payload.choices;\n if (Array.isArray(choices)) {\n for (const choice of choices) {\n if (!isJsonRecord(choice)) continue;\n const message = choice.message;\n if (!isJsonRecord(message)) continue;\n const content = message.content;\n if (typeof content === \"string\") {\n return content;\n }\n }\n }\n\n const directContent = payload.content;\n return typeof directContent === \"string\" ? directContent : \"\";\n}\n\nfunction extractStreamingContent(payload: unknown): string {\n if (!isJsonRecord(payload)) {\n return \"\";\n }\n\n const choices = payload.choices;\n if (Array.isArray(choices)) {\n for (const choice of choices) {\n if (!isJsonRecord(choice)) continue;\n const delta = choice.delta;\n if (!isJsonRecord(delta)) continue;\n const content = delta.content;\n if (typeof content === \"string\") {\n return content;\n }\n\n if (Array.isArray(content)) {\n return content\n .map((entry) => {\n if (!entry) return \"\";\n if (typeof entry === \"string\") return entry;\n if (isJsonRecord(entry) && typeof entry.text === \"string\") return entry.text;\n return \"\";\n })\n .join(\"\");\n }\n }\n }\n\n const directContent = payload.content;\n return typeof directContent === \"string\" ? directContent : \"\";\n}\n\nfunction tryParseJson(text: string): unknown {\n try {\n return JSON.parse(text) as unknown;\n } catch {\n return undefined;\n }\n}\n\nfunction isAbortError(error: unknown): boolean {\n if (typeof DOMException !== \"undefined\" && error instanceof DOMException) {\n return error.name === \"AbortError\";\n }\n\n if (isJsonRecord(error) && typeof error.name === \"string\") {\n return error.name === \"AbortError\";\n }\n\n return false;\n}\n\nexport interface Message {\n role: \"user\" | \"assistant\" | \"system\";\n content: string;\n id?: string;\n}\n\nexport interface UseChatWithUsageOptions {\n /**\n * API endpoint that handles the chat completion.\n * Should be a server-side route that uses UsageTap.\n */\n api: string;\n \n /**\n * Customer ID to track usage for.\n * This will be sent to the API endpoint.\n */\n customerId: string;\n\n /**\n * Optional end-user ID forwarded to your application API endpoint.\n * The server must validate or replace this value from trusted auth context\n * before passing it to UsageTap.\n */\n customerUserId?: string;\n\n /** Optional end-user display name forwarded to your application endpoint. */\n customerUserName?: string;\n\n /** Optional end-user display email forwarded to your application endpoint. */\n customerUserEmail?: string;\n \n /**\n * Optional feature name for usage tracking.\n */\n feature?: string;\n \n /**\n * Optional tags for usage tracking.\n */\n tags?: string[];\n \n /**\n * Initial messages to populate the chat.\n */\n initialMessages?: Message[];\n \n /**\n * Called when an error occurs.\n */\n onError?: (error: Error) => void;\n \n /**\n * Called when a response is finished.\n */\n onFinish?: (message: Message) => void;\n \n /**\n * Additional headers to send with requests.\n */\n headers?: Record<string, string>;\n}\n\nexport interface UseChatWithUsageReturn {\n /**\n * Current messages in the chat.\n */\n messages: Message[];\n \n /**\n * Current input value.\n */\n input: string;\n \n /**\n * Set the input value.\n */\n setInput: (input: string) => void;\n \n /**\n * Whether a request is in progress.\n */\n isLoading: boolean;\n \n /**\n * Current error, if any.\n */\n error: Error | null;\n \n /**\n * Append a user message and get a response.\n */\n append: (message: Message) => Promise<void>;\n \n /**\n * Submit the current input as a user message.\n */\n handleSubmit: (e?: React.FormEvent<HTMLFormElement>) => Promise<void>;\n \n /**\n * Reload the last assistant response.\n */\n reload: () => Promise<void>;\n \n /**\n * Stop the current streaming response.\n */\n stop: () => void;\n \n /**\n * Clear all messages.\n */\n clear: () => void;\n}\n\n/**\n * React hook for chat interfaces with UsageTap integration.\n * \n * The API endpoint should be a server-side route that:\n * 1. Receives messages, customerId, optional end-user hints, feature, and tags\n * 2. Uses UsageTap SDK to wrap OpenAI calls\n * 3. Returns streaming or non-streaming responses\n * \n * @example\n * ```tsx\n * import { useChatWithUsage } from \"@usagetap/sdk/react\";\n *\n * function ChatComponent({ customerId, currentUser }) {\n * const { messages, input, setInput, handleSubmit, isLoading } = useChatWithUsage({\n * api: \"/api/chat\",\n * customerId,\n * customerUserId: currentUser.id,\n * feature: \"chat.assistant\",\n * });\n * \n * return (\n * <div>\n * {messages.map((m) => (\n * <div key={m.id}>\n * <strong>{m.role}:</strong> {m.content}\n * </div>\n * ))}\n * <form onSubmit={handleSubmit}>\n * <input\n * value={input}\n * onChange={(e) => setInput(e.target.value)}\n * disabled={isLoading}\n * />\n * <button type=\"submit\" disabled={isLoading}>\n * Send\n * </button>\n * </form>\n * </div>\n * );\n * }\n * ```\n * \n * Server route example:\n * ```ts\n * // app/api/chat/route.ts\n * import { streamOpenAIRoute } from \"@usagetap/sdk/openai\";\n * \n * export const POST = streamOpenAIRoute(usageTap, openai, {\n * getRequest: async (req) => {\n * const body = await req.json();\n * return {\n * params: { model: \"gpt-5.6-luna\", messages: body.messages },\n * context: {\n * customerId: body.customerId,\n * // Validate against the authenticated server session first.\n * customerUserId: authenticatedUser.id,\n * feature: body.feature,\n * tags: body.tags,\n * },\n * };\n * },\n * });\n * ```\n */\nexport function useChatWithUsage(options: UseChatWithUsageOptions): UseChatWithUsageReturn {\n const {\n api,\n customerId,\n customerUserId,\n customerUserName,\n customerUserEmail,\n feature,\n tags,\n initialMessages = [],\n onError,\n onFinish,\n headers: customHeaders = {},\n } = options;\n\n const [messages, setMessages] = useState<Message[]>(initialMessages);\n const [input, setInput] = useState(\"\");\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n const abortControllerRef = useRef<AbortController | null>(null);\n\n const generateId = (): string => `msg_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;\n\n const append = useCallback(\n async (message: Message): Promise<void> => {\n setIsLoading(true);\n setError(null);\n\n const messageWithId = { ...message, id: message.id || generateId() };\n const newMessages = [...messages, messageWithId];\n setMessages(newMessages);\n\n abortControllerRef.current = new AbortController();\n\n try {\n const response = await fetch(api, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...customHeaders,\n },\n body: JSON.stringify({\n messages: newMessages,\n customerId,\n customerUserId,\n customerUserName,\n customerUserEmail,\n feature,\n tags,\n }),\n signal: abortControllerRef.current.signal,\n });\n\n if (!response.ok) {\n throw new Error(`HTTP error! status: ${response.status}`);\n }\n\n const contentType = response.headers.get(\"content-type\");\n const isStream = contentType?.includes(\"text/event-stream\") || contentType?.includes(\"text/plain\");\n\n if (isStream && response.body) {\n // Handle streaming response\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let assistantMessage = \"\";\n const assistantId = generateId();\n\n while (true) {\n const readResult = await reader.read();\n if (readResult.done) break;\n\n const chunkValue: Uint8Array = readResult.value instanceof Uint8Array\n ? readResult.value\n : new Uint8Array();\n\n const chunk = decoder.decode(chunkValue, { stream: true });\n const lines = chunk.split(\"\\n\");\n\n for (const line of lines) {\n if (line.startsWith(\"data: \")) {\n const data = line.slice(6);\n if (data === \"[DONE]\") continue;\n\n const parsed = tryParseJson(data);\n if (parsed) {\n const content = extractStreamingContent(parsed);\n if (content) {\n assistantMessage += content;\n setMessages([\n ...newMessages,\n { role: \"assistant\", content: assistantMessage, id: assistantId },\n ]);\n }\n } else {\n // Plain text streaming\n assistantMessage += data;\n setMessages([\n ...newMessages,\n { role: \"assistant\", content: assistantMessage, id: assistantId },\n ]);\n }\n }\n }\n }\n\n const finalMessage: Message = {\n role: \"assistant\",\n content: assistantMessage,\n id: assistantId,\n };\n \n setMessages([...newMessages, finalMessage]);\n onFinish?.(finalMessage);\n } else {\n // Handle non-streaming response\n const payload = (await response.json()) as unknown;\n const assistantMessage: Message = {\n role: \"assistant\",\n content: extractAssistantContent(payload),\n id: generateId(),\n };\n \n setMessages([...newMessages, assistantMessage]);\n onFinish?.(assistantMessage);\n }\n } catch (err) {\n if (isAbortError(err)) {\n // Request was aborted, don't treat as error\n return;\n }\n \n const error = err instanceof Error ? err : new Error(String(err));\n setError(error);\n onError?.(error);\n } finally {\n setIsLoading(false);\n abortControllerRef.current = null;\n }\n },\n [\n messages,\n api,\n customerId,\n customerUserId,\n customerUserName,\n customerUserEmail,\n feature,\n tags,\n customHeaders,\n onError,\n onFinish,\n ],\n );\n\n const handleSubmit = useCallback(\n async (e?: FormEvent<HTMLFormElement>): Promise<void> => {\n e?.preventDefault();\n if (!input.trim() || isLoading) return;\n\n const userMessage: Message = {\n role: \"user\",\n content: input,\n };\n\n setInput(\"\");\n await append(userMessage);\n },\n [input, isLoading, append],\n );\n\n const reload = useCallback(async (): Promise<void> => {\n if (messages.length === 0 || isLoading) return;\n\n // Remove last assistant message and resend last user message\n const messagesWithoutLast = messages.slice(0, -1);\n const lastUserMessage = [...messagesWithoutLast].reverse().find((m) => m.role === \"user\");\n\n if (lastUserMessage) {\n setMessages(messagesWithoutLast);\n await append(lastUserMessage);\n }\n }, [messages, isLoading, append]);\n\n const stop = useCallback((): void => {\n if (abortControllerRef.current) {\n abortControllerRef.current.abort();\n abortControllerRef.current = null;\n setIsLoading(false);\n }\n }, []);\n\n const clear = useCallback((): void => {\n setMessages([]);\n setInput(\"\");\n setError(null);\n }, []);\n\n // Cleanup on unmount\n useEffect(() => {\n return () => {\n if (abortControllerRef.current) {\n abortControllerRef.current.abort();\n }\n };\n }, []);\n\n return {\n messages,\n input,\n setInput,\n isLoading,\n error,\n append,\n handleSubmit,\n reload,\n stop,\n clear,\n };\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
- {
2
- "name": "@usagetap/sdk",
3
- "version": "1.3.1",
1
+ {
2
+ "name": "@usagetap/sdk",
3
+ "version": "1.4.0",
4
4
  "description": "UsageTap SDK core client plus optional React helpers.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -9,10 +9,10 @@
9
9
  "directory": "packages/usagetap-sdk"
10
10
  },
11
11
  "type": "module",
12
- "main": "./dist/index.cjs",
13
- "module": "./dist/index.mjs",
14
- "types": "./dist/index.d.ts",
15
- "exports": {
12
+ "main": "./dist/index.cjs",
13
+ "module": "./dist/index.mjs",
14
+ "types": "./dist/index.d.ts",
15
+ "exports": {
16
16
  ".": {
17
17
  "import": {
18
18
  "types": "./dist/index.d.ts",
@@ -103,80 +103,80 @@
103
103
  "default": "./dist/adapters/openrouter.cjs"
104
104
  }
105
105
  },
106
- "./package.json": "./package.json"
107
- },
108
- "typesVersions": {
109
- "*": {
110
- "react": [
111
- "dist/react/index.d.ts"
112
- ],
113
- "express": [
114
- "dist/express/index.d.ts"
115
- ],
116
- "openai": [
117
- "dist/openai/index.d.ts"
118
- ],
119
- "anthropic": [
120
- "dist/anthropic/index.d.ts"
121
- ],
122
- "openrouter": [
123
- "dist/openrouter/index.d.ts"
124
- ],
125
- "adapters/openai": [
126
- "dist/adapters/openai.d.ts"
127
- ],
128
- "adapters/anthropic": [
129
- "dist/adapters/anthropic.d.ts"
130
- ],
131
- "adapters/openrouter": [
132
- "dist/adapters/openrouter.d.ts"
133
- ]
134
- }
135
- },
136
- "sideEffects": false,
137
- "files": [
138
- "dist",
139
- "README.md"
140
- ],
141
- "scripts": {
142
- "build": "tsup",
143
- "clean": "rimraf dist",
144
- "test": "vitest run --coverage",
145
- "test:watch": "vitest",
146
- "lint": "eslint --config ../../libraries.config.mjs --max-warnings=0 --ignore-pattern \"src/**/*.test.ts\" --ignore-pattern \"src/**/*.test.tsx\" --ignore-pattern \"src/**/*.spec.ts\" --ignore-pattern \"src/**/*.spec.tsx\" \"src/**/*.{ts,tsx}\"",
147
- "lint:fix": "eslint --config ../../libraries.config.mjs --fix --ignore-pattern \"src/**/*.test.ts\" --ignore-pattern \"src/**/*.test.tsx\" --ignore-pattern \"src/**/*.spec.ts\" --ignore-pattern \"src/**/*.spec.tsx\" \"src/**/*.{ts,tsx}\"",
148
- "docs": "npm run build && api-extractor run --local --verbose && api-documenter markdown --input-folder temp --output-folder docs",
149
- "prepare": "npm run build"
150
- },
151
- "engines": {
152
- "node": ">=18.17"
153
- },
154
- "peerDependencies": {
155
- "@anthropic-ai/sdk": ">=0.20.0",
156
- "express": ">=4.0.0",
157
- "openai": ">=4.0.0",
158
- "react": ">=18.0.0"
159
- },
160
- "peerDependenciesMeta": {
161
- "@anthropic-ai/sdk": {
162
- "optional": true
163
- },
164
- "openai": {
165
- "optional": true
166
- },
167
- "express": {
168
- "optional": true
169
- },
170
- "react": {
171
- "optional": true
172
- }
173
- },
174
- "devDependencies": {
175
- "@types/express": "^5.0.3",
176
- "@types/react": "^19.2.0",
177
- "openai": "^4.76.0"
178
- },
179
- "dependencies": {
180
- "lucide-react": "^0.544.0"
181
- }
182
- }
106
+ "./package.json": "./package.json"
107
+ },
108
+ "typesVersions": {
109
+ "*": {
110
+ "react": [
111
+ "dist/react/index.d.ts"
112
+ ],
113
+ "express": [
114
+ "dist/express/index.d.ts"
115
+ ],
116
+ "openai": [
117
+ "dist/openai/index.d.ts"
118
+ ],
119
+ "anthropic": [
120
+ "dist/anthropic/index.d.ts"
121
+ ],
122
+ "openrouter": [
123
+ "dist/openrouter/index.d.ts"
124
+ ],
125
+ "adapters/openai": [
126
+ "dist/adapters/openai.d.ts"
127
+ ],
128
+ "adapters/anthropic": [
129
+ "dist/adapters/anthropic.d.ts"
130
+ ],
131
+ "adapters/openrouter": [
132
+ "dist/adapters/openrouter.d.ts"
133
+ ]
134
+ }
135
+ },
136
+ "sideEffects": false,
137
+ "files": [
138
+ "dist",
139
+ "README.md"
140
+ ],
141
+ "scripts": {
142
+ "build": "tsup",
143
+ "clean": "rimraf dist",
144
+ "test": "vitest run --coverage",
145
+ "test:watch": "vitest",
146
+ "lint": "eslint --config ../../libraries.config.mjs --max-warnings=0 --ignore-pattern \"src/**/*.test.ts\" --ignore-pattern \"src/**/*.test.tsx\" --ignore-pattern \"src/**/*.spec.ts\" --ignore-pattern \"src/**/*.spec.tsx\" \"src/**/*.{ts,tsx}\"",
147
+ "lint:fix": "eslint --config ../../libraries.config.mjs --fix --ignore-pattern \"src/**/*.test.ts\" --ignore-pattern \"src/**/*.test.tsx\" --ignore-pattern \"src/**/*.spec.ts\" --ignore-pattern \"src/**/*.spec.tsx\" \"src/**/*.{ts,tsx}\"",
148
+ "docs": "npm run build && api-extractor run --local --verbose && api-documenter markdown --input-folder temp --output-folder docs",
149
+ "prepare": "npm run build"
150
+ },
151
+ "engines": {
152
+ "node": ">=18.17"
153
+ },
154
+ "peerDependencies": {
155
+ "@anthropic-ai/sdk": ">=0.20.0",
156
+ "express": ">=4.0.0",
157
+ "openai": ">=4.0.0",
158
+ "react": ">=18.0.0"
159
+ },
160
+ "peerDependenciesMeta": {
161
+ "@anthropic-ai/sdk": {
162
+ "optional": true
163
+ },
164
+ "openai": {
165
+ "optional": true
166
+ },
167
+ "express": {
168
+ "optional": true
169
+ },
170
+ "react": {
171
+ "optional": true
172
+ }
173
+ },
174
+ "devDependencies": {
175
+ "@types/express": "^5.0.3",
176
+ "@types/react": "^19.2.0",
177
+ "openai": "^4.76.0"
178
+ },
179
+ "dependencies": {
180
+ "lucide-react": "^0.544.0"
181
+ }
182
+ }