@usagetap/sdk 1.3.2 → 1.7.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.
- package/README.md +372 -39
- package/dist/adapters/anthropic.cjs +995 -69
- package/dist/adapters/anthropic.cjs.map +1 -1
- package/dist/adapters/anthropic.d.cts +45 -3
- package/dist/adapters/anthropic.d.ts +45 -3
- package/dist/adapters/anthropic.mjs +995 -70
- package/dist/adapters/anthropic.mjs.map +1 -1
- package/dist/adapters/openai.cjs +1208 -106
- package/dist/adapters/openai.cjs.map +1 -1
- package/dist/adapters/openai.d.cts +46 -3
- package/dist/adapters/openai.d.ts +46 -3
- package/dist/adapters/openai.mjs +1208 -107
- package/dist/adapters/openai.mjs.map +1 -1
- package/dist/adapters/openrouter.cjs +3912 -53
- package/dist/adapters/openrouter.cjs.map +1 -1
- package/dist/adapters/openrouter.d.cts +6 -3
- package/dist/adapters/openrouter.d.ts +6 -3
- package/dist/adapters/openrouter.mjs +3910 -54
- package/dist/adapters/openrouter.mjs.map +1 -1
- package/dist/anthropic/index.cjs +995 -69
- package/dist/anthropic/index.cjs.map +1 -1
- package/dist/anthropic/index.d.cts +2 -2
- package/dist/anthropic/index.d.ts +2 -2
- package/dist/anthropic/index.mjs +995 -70
- package/dist/anthropic/index.mjs.map +1 -1
- package/dist/client-C0UiaqVB.d.cts +1305 -0
- package/dist/client-C0UiaqVB.d.ts +1305 -0
- package/dist/express/index.cjs +399 -64
- package/dist/express/index.cjs.map +1 -1
- package/dist/express/index.d.cts +2 -2
- package/dist/express/index.d.ts +2 -2
- package/dist/express/index.mjs +399 -64
- package/dist/express/index.mjs.map +1 -1
- package/dist/index.cjs +1044 -163
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -5
- package/dist/index.d.ts +16 -5
- package/dist/index.mjs +1044 -163
- package/dist/index.mjs.map +1 -1
- package/dist/openai/index.cjs +1209 -107
- package/dist/openai/index.cjs.map +1 -1
- package/dist/openai/index.d.cts +2 -2
- package/dist/openai/index.d.ts +2 -2
- package/dist/openai/index.mjs +1209 -108
- package/dist/openai/index.mjs.map +1 -1
- package/dist/openrouter/index.cjs +1226 -109
- package/dist/openrouter/index.cjs.map +1 -1
- package/dist/openrouter/index.d.cts +3 -3
- package/dist/openrouter/index.d.ts +3 -3
- package/dist/openrouter/index.mjs +1224 -108
- package/dist/openrouter/index.mjs.map +1 -1
- package/dist/react/index.cjs +19 -1
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +17 -4
- package/dist/react/index.d.ts +17 -4
- package/dist/react/index.mjs +19 -1
- package/dist/react/index.mjs.map +1 -1
- package/package.json +2 -2
- package/dist/client-BD8O2J8Z.d.cts +0 -668
- package/dist/client-BD8O2J8Z.d.ts +0 -668
package/dist/react/index.cjs
CHANGED
|
@@ -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
|
-
[
|
|
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) => {
|
package/dist/react/index.cjs.map
CHANGED
|
@@ -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\";\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 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, 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({ userId }) {\n * const { messages, input, setInput, handleSubmit, isLoading } = useChatWithUsage({\n * api: \"/api/chat\",\n * customerId: userId,\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-4o-mini\", messages: body.messages },\n * context: {\n * customerId: body.customerId,\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 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 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 [messages, api, customerId, feature, tags, customHeaders, onError, onFinish],\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"]}
|
|
1
|
+
{"version":3,"sources":["../../src/react/useChatWithUsage.ts"],"names":["useState","useRef","useCallback","error","useEffect"],"mappings":";;;;;AAKA,SAAS,aAAa,KAAA,EAAqC;AACzD,EAAA,OAAO,OAAO,UAAU,QAAA,IAAY,KAAA,KAAU,QAAQ,CAAC,KAAA,CAAM,QAAQ,KAAK,CAAA;AAC5E;AAEA,SAAS,wBAAwB,OAAA,EAA0B;AACzD,EAAA,IAAI,CAAC,YAAA,CAAa,OAAO,CAAA,EAAG;AAC1B,IAAA,OAAO,EAAA;AAAA,EACT;AAEA,EAAA,MAAM,UAAU,OAAA,CAAQ,OAAA;AACxB,EAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,OAAO,CAAA,EAAG;AAC1B,IAAA,KAAA,MAAW,UAAU,OAAA,EAAS;AAC5B,MAAA,IAAI,CAAC,YAAA,CAAa,MAAM,CAAA,EAAG;AAC3B,MAAA,MAAM,UAAU,MAAA,CAAO,OAAA;AACvB,MAAA,IAAI,CAAC,YAAA,CAAa,OAAO,CAAA,EAAG;AAC5B,MAAA,MAAM,UAAU,OAAA,CAAQ,OAAA;AACxB,MAAA,IAAI,OAAO,YAAY,QAAA,EAAU;AAC/B,QAAA,OAAO,OAAA;AAAA,MACT;AAAA,IACF;AAAA,EACF;AAEA,EAAA,MAAM,gBAAgB,OAAA,CAAQ,OAAA;AAC9B,EAAA,OAAO,OAAO,aAAA,KAAkB,QAAA,GAAW,aAAA,GAAgB,EAAA;AAC7D;AAEA,SAAS,wBAAwB,OAAA,EAA0B;AACzD,EAAA,IAAI,CAAC,YAAA,CAAa,OAAO,CAAA,EAAG;AAC1B,IAAA,OAAO,EAAA;AAAA,EACT;AAEA,EAAA,MAAM,UAAU,OAAA,CAAQ,OAAA;AACxB,EAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,OAAO,CAAA,EAAG;AAC1B,IAAA,KAAA,MAAW,UAAU,OAAA,EAAS;AAC5B,MAAA,IAAI,CAAC,YAAA,CAAa,MAAM,CAAA,EAAG;AAC3B,MAAA,MAAM,QAAQ,MAAA,CAAO,KAAA;AACrB,MAAA,IAAI,CAAC,YAAA,CAAa,KAAK,CAAA,EAAG;AAC1B,MAAA,MAAM,UAAU,KAAA,CAAM,OAAA;AACtB,MAAA,IAAI,OAAO,YAAY,QAAA,EAAU;AAC/B,QAAA,OAAO,OAAA;AAAA,MACT;AAEA,MAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,OAAO,CAAA,EAAG;AAC1B,QAAA,OAAO,OAAA,CACJ,GAAA,CAAI,CAAC,KAAA,KAAU;AACd,UAAA,IAAI,CAAC,OAAO,OAAO,EAAA;AACnB,UAAA,IAAI,OAAO,KAAA,KAAU,QAAA,EAAU,OAAO,KAAA;AACtC,UAAA,IAAI,YAAA,CAAa,KAAK,CAAA,IAAK,OAAO,MAAM,IAAA,KAAS,QAAA,SAAiB,KAAA,CAAM,IAAA;AACxE,UAAA,OAAO,EAAA;AAAA,QACT,CAAC,CAAA,CACA,IAAA,CAAK,EAAE,CAAA;AAAA,MACZ;AAAA,IACF;AAAA,EACF;AAEA,EAAA,MAAM,gBAAgB,OAAA,CAAQ,OAAA;AAC9B,EAAA,OAAO,OAAO,aAAA,KAAkB,QAAA,GAAW,aAAA,GAAgB,EAAA;AAC7D;AAEA,SAAS,aAAa,IAAA,EAAuB;AAC3C,EAAA,IAAI;AACF,IAAA,OAAO,IAAA,CAAK,MAAM,IAAI,CAAA;AAAA,EACxB,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,MAAA;AAAA,EACT;AACF;AAEA,SAAS,aAAa,KAAA,EAAyB;AAC7C,EAAA,IAAI,OAAO,YAAA,KAAiB,WAAA,IAAe,KAAA,YAAiB,YAAA,EAAc;AACxE,IAAA,OAAO,MAAM,IAAA,KAAS,YAAA;AAAA,EACxB;AAEA,EAAA,IAAI,aAAa,KAAK,CAAA,IAAK,OAAO,KAAA,CAAM,SAAS,QAAA,EAAU;AACzD,IAAA,OAAO,MAAM,IAAA,KAAS,YAAA;AAAA,EACxB;AAEA,EAAA,OAAO,KAAA;AACT;AAqLO,SAAS,iBAAiB,OAAA,EAA0D;AACzF,EAAA,MAAM;AAAA,IACJ,GAAA;AAAA,IACA,UAAA;AAAA,IACA,cAAA;AAAA,IACA,gBAAA;AAAA,IACA,iBAAA;AAAA,IACA,OAAA;AAAA,IACA,IAAA;AAAA,IACA,kBAAkB,EAAC;AAAA,IACnB,OAAA;AAAA,IACA,QAAA;AAAA,IACA,OAAA,EAAS,gBAAgB;AAAC,GAC5B,GAAI,OAAA;AAEJ,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAIA,eAAoB,eAAe,CAAA;AACnE,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAIA,eAAS,EAAE,CAAA;AACrC,EAAA,MAAM,CAAC,SAAA,EAAW,YAAY,CAAA,GAAIA,eAAS,KAAK,CAAA;AAChD,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAIA,eAAuB,IAAI,CAAA;AACrD,EAAA,MAAM,kBAAA,GAAqBC,aAA+B,IAAI,CAAA;AAE9D,EAAA,MAAM,aAAa,MAAc,CAAA,IAAA,EAAO,IAAA,CAAK,GAAA,EAAK,CAAA,CAAA,EAAI,IAAA,CAAK,MAAA,EAAO,CAAE,SAAS,EAAE,CAAA,CAAE,KAAA,CAAM,CAAA,EAAG,CAAC,CAAC,CAAA,CAAA;AAE5F,EAAA,MAAM,MAAA,GAASC,iBAAA;AAAA,IACb,OAAO,OAAA,KAAoC;AACzC,MAAA,YAAA,CAAa,IAAI,CAAA;AACjB,MAAA,QAAA,CAAS,IAAI,CAAA;AAEb,MAAA,MAAM,aAAA,GAAgB,EAAE,GAAG,OAAA,EAAS,IAAI,OAAA,CAAQ,EAAA,IAAM,YAAW,EAAE;AACnE,MAAA,MAAM,WAAA,GAAc,CAAC,GAAG,QAAA,EAAU,aAAa,CAAA;AAC/C,MAAA,WAAA,CAAY,WAAW,CAAA;AAEvB,MAAA,kBAAA,CAAmB,OAAA,GAAU,IAAI,eAAA,EAAgB;AAEjD,MAAA,IAAI;AACF,QAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,GAAA,EAAK;AAAA,UAChC,MAAA,EAAQ,MAAA;AAAA,UACR,OAAA,EAAS;AAAA,YACP,cAAA,EAAgB,kBAAA;AAAA,YAChB,GAAG;AAAA,WACL;AAAA,UACA,IAAA,EAAM,KAAK,SAAA,CAAU;AAAA,YACnB,QAAA,EAAU,WAAA;AAAA,YACV,UAAA;AAAA,YACA,cAAA;AAAA,YACA,gBAAA;AAAA,YACA,iBAAA;AAAA,YACA,OAAA;AAAA,YACA;AAAA,WACD,CAAA;AAAA,UACD,MAAA,EAAQ,mBAAmB,OAAA,CAAQ;AAAA,SACpC,CAAA;AAED,QAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,UAAA,MAAM,IAAI,KAAA,CAAM,CAAA,oBAAA,EAAuB,QAAA,CAAS,MAAM,CAAA,CAAE,CAAA;AAAA,QAC1D;AAEA,QAAA,MAAM,WAAA,GAAc,QAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,cAAc,CAAA;AACvD,QAAA,MAAM,WAAW,WAAA,EAAa,QAAA,CAAS,mBAAmB,CAAA,IAAK,WAAA,EAAa,SAAS,YAAY,CAAA;AAEjG,QAAA,IAAI,QAAA,IAAY,SAAS,IAAA,EAAM;AAE7B,UAAA,MAAM,MAAA,GAAS,QAAA,CAAS,IAAA,CAAK,SAAA,EAAU;AACvC,UAAA,MAAM,OAAA,GAAU,IAAI,WAAA,EAAY;AAChC,UAAA,IAAI,gBAAA,GAAmB,EAAA;AACvB,UAAA,MAAM,cAAc,UAAA,EAAW;AAE/B,UAAA,OAAO,IAAA,EAAM;AACX,YAAA,MAAM,UAAA,GAAa,MAAM,MAAA,CAAO,IAAA,EAAK;AACrC,YAAA,IAAI,WAAW,IAAA,EAAM;AAErB,YAAA,MAAM,aAAyB,UAAA,CAAW,KAAA,YAAiB,aACvD,UAAA,CAAW,KAAA,GACX,IAAI,UAAA,EAAW;AAEnB,YAAA,MAAM,QAAQ,OAAA,CAAQ,MAAA,CAAO,YAAY,EAAE,MAAA,EAAQ,MAAM,CAAA;AACzD,YAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,KAAA,CAAM,IAAI,CAAA;AAE9B,YAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACxB,cAAA,IAAI,IAAA,CAAK,UAAA,CAAW,QAAQ,CAAA,EAAG;AAC7B,gBAAA,MAAM,IAAA,GAAO,IAAA,CAAK,KAAA,CAAM,CAAC,CAAA;AACzB,gBAAA,IAAI,SAAS,QAAA,EAAU;AAEvB,gBAAA,MAAM,MAAA,GAAS,aAAa,IAAI,CAAA;AAChC,gBAAA,IAAI,MAAA,EAAQ;AACV,kBAAA,MAAM,OAAA,GAAU,wBAAwB,MAAM,CAAA;AAC9C,kBAAA,IAAI,OAAA,EAAS;AACX,oBAAA,gBAAA,IAAoB,OAAA;AACpB,oBAAA,WAAA,CAAY;AAAA,sBACV,GAAG,WAAA;AAAA,sBACH,EAAE,IAAA,EAAM,WAAA,EAAa,OAAA,EAAS,gBAAA,EAAkB,IAAI,WAAA;AAAY,qBACjE,CAAA;AAAA,kBACH;AAAA,gBACF,CAAA,MAAO;AAEL,kBAAA,gBAAA,IAAoB,IAAA;AACpB,kBAAA,WAAA,CAAY;AAAA,oBACV,GAAG,WAAA;AAAA,oBACH,EAAE,IAAA,EAAM,WAAA,EAAa,OAAA,EAAS,gBAAA,EAAkB,IAAI,WAAA;AAAY,mBACjE,CAAA;AAAA,gBACH;AAAA,cACF;AAAA,YACF;AAAA,UACF;AAEA,UAAA,MAAM,YAAA,GAAwB;AAAA,YAC5B,IAAA,EAAM,WAAA;AAAA,YACN,OAAA,EAAS,gBAAA;AAAA,YACT,EAAA,EAAI;AAAA,WACN;AAEA,UAAA,WAAA,CAAY,CAAC,GAAG,WAAA,EAAa,YAAY,CAAC,CAAA;AAC1C,UAAA,QAAA,GAAW,YAAY,CAAA;AAAA,QACzB,CAAA,MAAO;AAEL,UAAA,MAAM,OAAA,GAAW,MAAM,QAAA,CAAS,IAAA,EAAK;AACrC,UAAA,MAAM,gBAAA,GAA4B;AAAA,YAChC,IAAA,EAAM,WAAA;AAAA,YACN,OAAA,EAAS,wBAAwB,OAAO,CAAA;AAAA,YACxC,IAAI,UAAA;AAAW,WACjB;AAEA,UAAA,WAAA,CAAY,CAAC,GAAG,WAAA,EAAa,gBAAgB,CAAC,CAAA;AAC9C,UAAA,QAAA,GAAW,gBAAgB,CAAA;AAAA,QAC7B;AAAA,MACF,SAAS,GAAA,EAAK;AACZ,QAAA,IAAI,YAAA,CAAa,GAAG,CAAA,EAAG;AAErB,UAAA;AAAA,QACF;AAEA,QAAA,MAAMC,MAAAA,GAAQ,eAAe,KAAA,GAAQ,GAAA,GAAM,IAAI,KAAA,CAAM,MAAA,CAAO,GAAG,CAAC,CAAA;AAChE,QAAA,QAAA,CAASA,MAAK,CAAA;AACd,QAAA,OAAA,GAAUA,MAAK,CAAA;AAAA,MACjB,CAAA,SAAE;AACA,QAAA,YAAA,CAAa,KAAK,CAAA;AAClB,QAAA,kBAAA,CAAmB,OAAA,GAAU,IAAA;AAAA,MAC/B;AAAA,IACF,CAAA;AAAA,IACA;AAAA,MACE,QAAA;AAAA,MACA,GAAA;AAAA,MACA,UAAA;AAAA,MACA,cAAA;AAAA,MACA,gBAAA;AAAA,MACA,iBAAA;AAAA,MACA,OAAA;AAAA,MACA,IAAA;AAAA,MACA,aAAA;AAAA,MACA,OAAA;AAAA,MACA;AAAA;AACF,GACF;AAEA,EAAA,MAAM,YAAA,GAAeD,iBAAA;AAAA,IACrB,OAAO,CAAA,KAAkD;AACrD,MAAA,CAAA,EAAG,cAAA,EAAe;AAClB,MAAA,IAAI,CAAC,KAAA,CAAM,IAAA,EAAK,IAAK,SAAA,EAAW;AAEhC,MAAA,MAAM,WAAA,GAAuB;AAAA,QAC3B,IAAA,EAAM,MAAA;AAAA,QACN,OAAA,EAAS;AAAA,OACX;AAEA,MAAA,QAAA,CAAS,EAAE,CAAA;AACX,MAAA,MAAM,OAAO,WAAW,CAAA;AAAA,IAC1B,CAAA;AAAA,IACA,CAAC,KAAA,EAAO,SAAA,EAAW,MAAM;AAAA,GAC3B;AAEA,EAAA,MAAM,MAAA,GAASA,kBAAY,YAA2B;AACpD,IAAA,IAAI,QAAA,CAAS,MAAA,KAAW,CAAA,IAAK,SAAA,EAAW;AAGxC,IAAA,MAAM,mBAAA,GAAsB,QAAA,CAAS,KAAA,CAAM,CAAA,EAAG,EAAE,CAAA;AAChD,IAAA,MAAM,eAAA,GAAkB,CAAC,GAAG,mBAAmB,CAAA,CAAE,OAAA,EAAQ,CAAE,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,IAAA,KAAS,MAAM,CAAA;AAExF,IAAA,IAAI,eAAA,EAAiB;AACnB,MAAA,WAAA,CAAY,mBAAmB,CAAA;AAC/B,MAAA,MAAM,OAAO,eAAe,CAAA;AAAA,IAC9B;AAAA,EACF,CAAA,EAAG,CAAC,QAAA,EAAU,SAAA,EAAW,MAAM,CAAC,CAAA;AAEhC,EAAA,MAAM,IAAA,GAAOA,kBAAY,MAAY;AACnC,IAAA,IAAI,mBAAmB,OAAA,EAAS;AAC9B,MAAA,kBAAA,CAAmB,QAAQ,KAAA,EAAM;AACjC,MAAA,kBAAA,CAAmB,OAAA,GAAU,IAAA;AAC7B,MAAA,YAAA,CAAa,KAAK,CAAA;AAAA,IACpB;AAAA,EACF,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,KAAA,GAAQA,kBAAY,MAAY;AACpC,IAAA,WAAA,CAAY,EAAE,CAAA;AACd,IAAA,QAAA,CAAS,EAAE,CAAA;AACX,IAAA,QAAA,CAAS,IAAI,CAAA;AAAA,EACf,CAAA,EAAG,EAAE,CAAA;AAGL,EAAAE,eAAA,CAAU,MAAM;AACd,IAAA,OAAO,MAAM;AACX,MAAA,IAAI,mBAAmB,OAAA,EAAS;AAC9B,QAAA,kBAAA,CAAmB,QAAQ,KAAA,EAAM;AAAA,MACnC;AAAA,IACF,CAAA;AAAA,EACF,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,OAAO;AAAA,IACL,QAAA;AAAA,IACA,KAAA;AAAA,IACA,QAAA;AAAA,IACA,SAAA;AAAA,IACA,KAAA;AAAA,IACA,MAAA;AAAA,IACA,YAAA;AAAA,IACA,MAAA;AAAA,IACA,IAAA;AAAA,IACA;AAAA,GACF;AACF","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"]}
|
package/dist/react/index.d.cts
CHANGED
|
@@ -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({
|
|
106
|
+
* function ChatComponent({ customerId, currentUser }) {
|
|
97
107
|
* const { messages, input, setInput, handleSubmit, isLoading } = useChatWithUsage({
|
|
98
108
|
* api: "/api/chat",
|
|
99
|
-
* customerId
|
|
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-
|
|
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
|
* },
|
package/dist/react/index.d.ts
CHANGED
|
@@ -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({
|
|
106
|
+
* function ChatComponent({ customerId, currentUser }) {
|
|
97
107
|
* const { messages, input, setInput, handleSubmit, isLoading } = useChatWithUsage({
|
|
98
108
|
* api: "/api/chat",
|
|
99
|
-
* customerId
|
|
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-
|
|
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
|
* },
|
package/dist/react/index.mjs
CHANGED
|
@@ -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
|
-
[
|
|
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) => {
|
package/dist/react/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/react/useChatWithUsage.ts"],"names":["error"],"mappings":";;;AAKA,SAAS,aAAa,KAAA,EAAqC;AACzD,EAAA,OAAO,OAAO,UAAU,QAAA,IAAY,KAAA,KAAU,QAAQ,CAAC,KAAA,CAAM,QAAQ,KAAK,CAAA;AAC5E;AAEA,SAAS,wBAAwB,OAAA,EAA0B;AACzD,EAAA,IAAI,CAAC,YAAA,CAAa,OAAO,CAAA,EAAG;AAC1B,IAAA,OAAO,EAAA;AAAA,EACT;AAEA,EAAA,MAAM,UAAU,OAAA,CAAQ,OAAA;AACxB,EAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,OAAO,CAAA,EAAG;AAC1B,IAAA,KAAA,MAAW,UAAU,OAAA,EAAS;AAC5B,MAAA,IAAI,CAAC,YAAA,CAAa,MAAM,CAAA,EAAG;AAC3B,MAAA,MAAM,UAAU,MAAA,CAAO,OAAA;AACvB,MAAA,IAAI,CAAC,YAAA,CAAa,OAAO,CAAA,EAAG;AAC5B,MAAA,MAAM,UAAU,OAAA,CAAQ,OAAA;AACxB,MAAA,IAAI,OAAO,YAAY,QAAA,EAAU;AAC/B,QAAA,OAAO,OAAA;AAAA,MACT;AAAA,IACF;AAAA,EACF;AAEA,EAAA,MAAM,gBAAgB,OAAA,CAAQ,OAAA;AAC9B,EAAA,OAAO,OAAO,aAAA,KAAkB,QAAA,GAAW,aAAA,GAAgB,EAAA;AAC7D;AAEA,SAAS,wBAAwB,OAAA,EAA0B;AACzD,EAAA,IAAI,CAAC,YAAA,CAAa,OAAO,CAAA,EAAG;AAC1B,IAAA,OAAO,EAAA;AAAA,EACT;AAEA,EAAA,MAAM,UAAU,OAAA,CAAQ,OAAA;AACxB,EAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,OAAO,CAAA,EAAG;AAC1B,IAAA,KAAA,MAAW,UAAU,OAAA,EAAS;AAC5B,MAAA,IAAI,CAAC,YAAA,CAAa,MAAM,CAAA,EAAG;AAC3B,MAAA,MAAM,QAAQ,MAAA,CAAO,KAAA;AACrB,MAAA,IAAI,CAAC,YAAA,CAAa,KAAK,CAAA,EAAG;AAC1B,MAAA,MAAM,UAAU,KAAA,CAAM,OAAA;AACtB,MAAA,IAAI,OAAO,YAAY,QAAA,EAAU;AAC/B,QAAA,OAAO,OAAA;AAAA,MACT;AAEA,MAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,OAAO,CAAA,EAAG;AAC1B,QAAA,OAAO,OAAA,CACJ,GAAA,CAAI,CAAC,KAAA,KAAU;AACd,UAAA,IAAI,CAAC,OAAO,OAAO,EAAA;AACnB,UAAA,IAAI,OAAO,KAAA,KAAU,QAAA,EAAU,OAAO,KAAA;AACtC,UAAA,IAAI,YAAA,CAAa,KAAK,CAAA,IAAK,OAAO,MAAM,IAAA,KAAS,QAAA,SAAiB,KAAA,CAAM,IAAA;AACxE,UAAA,OAAO,EAAA;AAAA,QACT,CAAC,CAAA,CACA,IAAA,CAAK,EAAE,CAAA;AAAA,MACZ;AAAA,IACF;AAAA,EACF;AAEA,EAAA,MAAM,gBAAgB,OAAA,CAAQ,OAAA;AAC9B,EAAA,OAAO,OAAO,aAAA,KAAkB,QAAA,GAAW,aAAA,GAAgB,EAAA;AAC7D;AAEA,SAAS,aAAa,IAAA,EAAuB;AAC3C,EAAA,IAAI;AACF,IAAA,OAAO,IAAA,CAAK,MAAM,IAAI,CAAA;AAAA,EACxB,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,MAAA;AAAA,EACT;AACF;AAEA,SAAS,aAAa,KAAA,EAAyB;AAC7C,EAAA,IAAI,OAAO,YAAA,KAAiB,WAAA,IAAe,KAAA,YAAiB,YAAA,EAAc;AACxE,IAAA,OAAO,MAAM,IAAA,KAAS,YAAA;AAAA,EACxB;AAEA,EAAA,IAAI,aAAa,KAAK,CAAA,IAAK,OAAO,KAAA,CAAM,SAAS,QAAA,EAAU;AACzD,IAAA,OAAO,MAAM,IAAA,KAAS,YAAA;AAAA,EACxB;AAEA,EAAA,OAAO,KAAA;AACT;AAqKO,SAAS,iBAAiB,OAAA,EAA0D;AACzF,EAAA,MAAM;AAAA,IACJ,GAAA;AAAA,IACA,UAAA;AAAA,IACA,OAAA;AAAA,IACA,IAAA;AAAA,IACA,kBAAkB,EAAC;AAAA,IACnB,OAAA;AAAA,IACA,QAAA;AAAA,IACA,OAAA,EAAS,gBAAgB;AAAC,GAC5B,GAAI,OAAA;AAEJ,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAI,SAAoB,eAAe,CAAA;AACnE,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,SAAS,EAAE,CAAA;AACrC,EAAA,MAAM,CAAC,SAAA,EAAW,YAAY,CAAA,GAAI,SAAS,KAAK,CAAA;AAChD,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,SAAuB,IAAI,CAAA;AACrD,EAAA,MAAM,kBAAA,GAAqB,OAA+B,IAAI,CAAA;AAE9D,EAAA,MAAM,aAAa,MAAc,CAAA,IAAA,EAAO,IAAA,CAAK,GAAA,EAAK,CAAA,CAAA,EAAI,IAAA,CAAK,MAAA,EAAO,CAAE,SAAS,EAAE,CAAA,CAAE,KAAA,CAAM,CAAA,EAAG,CAAC,CAAC,CAAA,CAAA;AAE5F,EAAA,MAAM,MAAA,GAAS,WAAA;AAAA,IACb,OAAO,OAAA,KAAoC;AACzC,MAAA,YAAA,CAAa,IAAI,CAAA;AACjB,MAAA,QAAA,CAAS,IAAI,CAAA;AAEb,MAAA,MAAM,aAAA,GAAgB,EAAE,GAAG,OAAA,EAAS,IAAI,OAAA,CAAQ,EAAA,IAAM,YAAW,EAAE;AACnE,MAAA,MAAM,WAAA,GAAc,CAAC,GAAG,QAAA,EAAU,aAAa,CAAA;AAC/C,MAAA,WAAA,CAAY,WAAW,CAAA;AAEvB,MAAA,kBAAA,CAAmB,OAAA,GAAU,IAAI,eAAA,EAAgB;AAEjD,MAAA,IAAI;AACF,QAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,GAAA,EAAK;AAAA,UAChC,MAAA,EAAQ,MAAA;AAAA,UACR,OAAA,EAAS;AAAA,YACP,cAAA,EAAgB,kBAAA;AAAA,YAChB,GAAG;AAAA,WACL;AAAA,UACA,IAAA,EAAM,KAAK,SAAA,CAAU;AAAA,YACnB,QAAA,EAAU,WAAA;AAAA,YACV,UAAA;AAAA,YACA,OAAA;AAAA,YACA;AAAA,WACD,CAAA;AAAA,UACD,MAAA,EAAQ,mBAAmB,OAAA,CAAQ;AAAA,SACpC,CAAA;AAED,QAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,UAAA,MAAM,IAAI,KAAA,CAAM,CAAA,oBAAA,EAAuB,QAAA,CAAS,MAAM,CAAA,CAAE,CAAA;AAAA,QAC1D;AAEA,QAAA,MAAM,WAAA,GAAc,QAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,cAAc,CAAA;AACvD,QAAA,MAAM,WAAW,WAAA,EAAa,QAAA,CAAS,mBAAmB,CAAA,IAAK,WAAA,EAAa,SAAS,YAAY,CAAA;AAEjG,QAAA,IAAI,QAAA,IAAY,SAAS,IAAA,EAAM;AAE7B,UAAA,MAAM,MAAA,GAAS,QAAA,CAAS,IAAA,CAAK,SAAA,EAAU;AACvC,UAAA,MAAM,OAAA,GAAU,IAAI,WAAA,EAAY;AAChC,UAAA,IAAI,gBAAA,GAAmB,EAAA;AACvB,UAAA,MAAM,cAAc,UAAA,EAAW;AAE/B,UAAA,OAAO,IAAA,EAAM;AACX,YAAA,MAAM,UAAA,GAAa,MAAM,MAAA,CAAO,IAAA,EAAK;AACrC,YAAA,IAAI,WAAW,IAAA,EAAM;AAErB,YAAA,MAAM,aAAyB,UAAA,CAAW,KAAA,YAAiB,aACvD,UAAA,CAAW,KAAA,GACX,IAAI,UAAA,EAAW;AAEnB,YAAA,MAAM,QAAQ,OAAA,CAAQ,MAAA,CAAO,YAAY,EAAE,MAAA,EAAQ,MAAM,CAAA;AACzD,YAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,KAAA,CAAM,IAAI,CAAA;AAE9B,YAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACxB,cAAA,IAAI,IAAA,CAAK,UAAA,CAAW,QAAQ,CAAA,EAAG;AAC7B,gBAAA,MAAM,IAAA,GAAO,IAAA,CAAK,KAAA,CAAM,CAAC,CAAA;AACzB,gBAAA,IAAI,SAAS,QAAA,EAAU;AAEvB,gBAAA,MAAM,MAAA,GAAS,aAAa,IAAI,CAAA;AAChC,gBAAA,IAAI,MAAA,EAAQ;AACV,kBAAA,MAAM,OAAA,GAAU,wBAAwB,MAAM,CAAA;AAC9C,kBAAA,IAAI,OAAA,EAAS;AACX,oBAAA,gBAAA,IAAoB,OAAA;AACpB,oBAAA,WAAA,CAAY;AAAA,sBACV,GAAG,WAAA;AAAA,sBACH,EAAE,IAAA,EAAM,WAAA,EAAa,OAAA,EAAS,gBAAA,EAAkB,IAAI,WAAA;AAAY,qBACjE,CAAA;AAAA,kBACH;AAAA,gBACF,CAAA,MAAO;AAEL,kBAAA,gBAAA,IAAoB,IAAA;AACpB,kBAAA,WAAA,CAAY;AAAA,oBACV,GAAG,WAAA;AAAA,oBACH,EAAE,IAAA,EAAM,WAAA,EAAa,OAAA,EAAS,gBAAA,EAAkB,IAAI,WAAA;AAAY,mBACjE,CAAA;AAAA,gBACH;AAAA,cACF;AAAA,YACF;AAAA,UACF;AAEA,UAAA,MAAM,YAAA,GAAwB;AAAA,YAC5B,IAAA,EAAM,WAAA;AAAA,YACN,OAAA,EAAS,gBAAA;AAAA,YACT,EAAA,EAAI;AAAA,WACN;AAEA,UAAA,WAAA,CAAY,CAAC,GAAG,WAAA,EAAa,YAAY,CAAC,CAAA;AAC1C,UAAA,QAAA,GAAW,YAAY,CAAA;AAAA,QACzB,CAAA,MAAO;AAEL,UAAA,MAAM,OAAA,GAAW,MAAM,QAAA,CAAS,IAAA,EAAK;AACrC,UAAA,MAAM,gBAAA,GAA4B;AAAA,YAChC,IAAA,EAAM,WAAA;AAAA,YACN,OAAA,EAAS,wBAAwB,OAAO,CAAA;AAAA,YACxC,IAAI,UAAA;AAAW,WACjB;AAEA,UAAA,WAAA,CAAY,CAAC,GAAG,WAAA,EAAa,gBAAgB,CAAC,CAAA;AAC9C,UAAA,QAAA,GAAW,gBAAgB,CAAA;AAAA,QAC7B;AAAA,MACF,SAAS,GAAA,EAAK;AACZ,QAAA,IAAI,YAAA,CAAa,GAAG,CAAA,EAAG;AAErB,UAAA;AAAA,QACF;AAEA,QAAA,MAAMA,MAAAA,GAAQ,eAAe,KAAA,GAAQ,GAAA,GAAM,IAAI,KAAA,CAAM,MAAA,CAAO,GAAG,CAAC,CAAA;AAChE,QAAA,QAAA,CAASA,MAAK,CAAA;AACd,QAAA,OAAA,GAAUA,MAAK,CAAA;AAAA,MACjB,CAAA,SAAE;AACA,QAAA,YAAA,CAAa,KAAK,CAAA;AAClB,QAAA,kBAAA,CAAmB,OAAA,GAAU,IAAA;AAAA,MAC/B;AAAA,IACF,CAAA;AAAA,IACA,CAAC,UAAU,GAAA,EAAK,UAAA,EAAY,SAAS,IAAA,EAAM,aAAA,EAAe,SAAS,QAAQ;AAAA,GAC7E;AAEA,EAAA,MAAM,YAAA,GAAe,WAAA;AAAA,IACrB,OAAO,CAAA,KAAkD;AACrD,MAAA,CAAA,EAAG,cAAA,EAAe;AAClB,MAAA,IAAI,CAAC,KAAA,CAAM,IAAA,EAAK,IAAK,SAAA,EAAW;AAEhC,MAAA,MAAM,WAAA,GAAuB;AAAA,QAC3B,IAAA,EAAM,MAAA;AAAA,QACN,OAAA,EAAS;AAAA,OACX;AAEA,MAAA,QAAA,CAAS,EAAE,CAAA;AACX,MAAA,MAAM,OAAO,WAAW,CAAA;AAAA,IAC1B,CAAA;AAAA,IACA,CAAC,KAAA,EAAO,SAAA,EAAW,MAAM;AAAA,GAC3B;AAEA,EAAA,MAAM,MAAA,GAAS,YAAY,YAA2B;AACpD,IAAA,IAAI,QAAA,CAAS,MAAA,KAAW,CAAA,IAAK,SAAA,EAAW;AAGxC,IAAA,MAAM,mBAAA,GAAsB,QAAA,CAAS,KAAA,CAAM,CAAA,EAAG,EAAE,CAAA;AAChD,IAAA,MAAM,eAAA,GAAkB,CAAC,GAAG,mBAAmB,CAAA,CAAE,OAAA,EAAQ,CAAE,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,IAAA,KAAS,MAAM,CAAA;AAExF,IAAA,IAAI,eAAA,EAAiB;AACnB,MAAA,WAAA,CAAY,mBAAmB,CAAA;AAC/B,MAAA,MAAM,OAAO,eAAe,CAAA;AAAA,IAC9B;AAAA,EACF,CAAA,EAAG,CAAC,QAAA,EAAU,SAAA,EAAW,MAAM,CAAC,CAAA;AAEhC,EAAA,MAAM,IAAA,GAAO,YAAY,MAAY;AACnC,IAAA,IAAI,mBAAmB,OAAA,EAAS;AAC9B,MAAA,kBAAA,CAAmB,QAAQ,KAAA,EAAM;AACjC,MAAA,kBAAA,CAAmB,OAAA,GAAU,IAAA;AAC7B,MAAA,YAAA,CAAa,KAAK,CAAA;AAAA,IACpB;AAAA,EACF,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,KAAA,GAAQ,YAAY,MAAY;AACpC,IAAA,WAAA,CAAY,EAAE,CAAA;AACd,IAAA,QAAA,CAAS,EAAE,CAAA;AACX,IAAA,QAAA,CAAS,IAAI,CAAA;AAAA,EACf,CAAA,EAAG,EAAE,CAAA;AAGL,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,OAAO,MAAM;AACX,MAAA,IAAI,mBAAmB,OAAA,EAAS;AAC9B,QAAA,kBAAA,CAAmB,QAAQ,KAAA,EAAM;AAAA,MACnC;AAAA,IACF,CAAA;AAAA,EACF,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,OAAO;AAAA,IACL,QAAA;AAAA,IACA,KAAA;AAAA,IACA,QAAA;AAAA,IACA,SAAA;AAAA,IACA,KAAA;AAAA,IACA,MAAA;AAAA,IACA,YAAA;AAAA,IACA,MAAA;AAAA,IACA,IAAA;AAAA,IACA;AAAA,GACF;AACF","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 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, 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({ userId }) {\n * const { messages, input, setInput, handleSubmit, isLoading } = useChatWithUsage({\n * api: \"/api/chat\",\n * customerId: userId,\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-4o-mini\", messages: body.messages },\n * context: {\n * customerId: body.customerId,\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 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 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 [messages, api, customerId, feature, tags, customHeaders, onError, onFinish],\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"]}
|
|
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
1
|
{
|
|
2
2
|
"name": "@usagetap/sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "UsageTap SDK core client plus optional React helpers.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -174,7 +174,7 @@
|
|
|
174
174
|
"devDependencies": {
|
|
175
175
|
"@types/express": "^5.0.3",
|
|
176
176
|
"@types/react": "^19.2.0",
|
|
177
|
-
"openai": "^
|
|
177
|
+
"openai": "^7.5.0"
|
|
178
178
|
},
|
|
179
179
|
"dependencies": {
|
|
180
180
|
"lucide-react": "^0.544.0"
|