@usagetap/sdk 0.1.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.
@@ -0,0 +1,147 @@
1
+ interface Message {
2
+ role: "user" | "assistant" | "system";
3
+ content: string;
4
+ id?: string;
5
+ }
6
+ interface UseChatWithUsageOptions {
7
+ /**
8
+ * API endpoint that handles the chat completion.
9
+ * Should be a server-side route that uses UsageTap.
10
+ */
11
+ api: string;
12
+ /**
13
+ * Customer ID to track usage for.
14
+ * This will be sent to the API endpoint.
15
+ */
16
+ customerId: string;
17
+ /**
18
+ * Optional feature name for usage tracking.
19
+ */
20
+ feature?: string;
21
+ /**
22
+ * Optional tags for usage tracking.
23
+ */
24
+ tags?: string[];
25
+ /**
26
+ * Initial messages to populate the chat.
27
+ */
28
+ initialMessages?: Message[];
29
+ /**
30
+ * Called when an error occurs.
31
+ */
32
+ onError?: (error: Error) => void;
33
+ /**
34
+ * Called when a response is finished.
35
+ */
36
+ onFinish?: (message: Message) => void;
37
+ /**
38
+ * Additional headers to send with requests.
39
+ */
40
+ headers?: Record<string, string>;
41
+ }
42
+ interface UseChatWithUsageReturn {
43
+ /**
44
+ * Current messages in the chat.
45
+ */
46
+ messages: Message[];
47
+ /**
48
+ * Current input value.
49
+ */
50
+ input: string;
51
+ /**
52
+ * Set the input value.
53
+ */
54
+ setInput: (input: string) => void;
55
+ /**
56
+ * Whether a request is in progress.
57
+ */
58
+ isLoading: boolean;
59
+ /**
60
+ * Current error, if any.
61
+ */
62
+ error: Error | null;
63
+ /**
64
+ * Append a user message and get a response.
65
+ */
66
+ append: (message: Message) => Promise<void>;
67
+ /**
68
+ * Submit the current input as a user message.
69
+ */
70
+ handleSubmit: (e?: React.FormEvent<HTMLFormElement>) => Promise<void>;
71
+ /**
72
+ * Reload the last assistant response.
73
+ */
74
+ reload: () => Promise<void>;
75
+ /**
76
+ * Stop the current streaming response.
77
+ */
78
+ stop: () => void;
79
+ /**
80
+ * Clear all messages.
81
+ */
82
+ clear: () => void;
83
+ }
84
+ /**
85
+ * React hook for chat interfaces with UsageTap integration.
86
+ *
87
+ * The API endpoint should be a server-side route that:
88
+ * 1. Receives messages, customerId, feature, and tags
89
+ * 2. Uses UsageTap SDK to wrap OpenAI calls
90
+ * 3. Returns streaming or non-streaming responses
91
+ *
92
+ * @example
93
+ * ```tsx
94
+ * import { useChatWithUsage } from "@usagetap/sdk/react";
95
+ *
96
+ * function ChatComponent({ userId }) {
97
+ * const { messages, input, setInput, handleSubmit, isLoading } = useChatWithUsage({
98
+ * api: "/api/chat",
99
+ * customerId: userId,
100
+ * feature: "chat.assistant",
101
+ * });
102
+ *
103
+ * return (
104
+ * <div>
105
+ * {messages.map((m) => (
106
+ * <div key={m.id}>
107
+ * <strong>{m.role}:</strong> {m.content}
108
+ * </div>
109
+ * ))}
110
+ * <form onSubmit={handleSubmit}>
111
+ * <input
112
+ * value={input}
113
+ * onChange={(e) => setInput(e.target.value)}
114
+ * disabled={isLoading}
115
+ * />
116
+ * <button type="submit" disabled={isLoading}>
117
+ * Send
118
+ * </button>
119
+ * </form>
120
+ * </div>
121
+ * );
122
+ * }
123
+ * ```
124
+ *
125
+ * Server route example:
126
+ * ```ts
127
+ * // app/api/chat/route.ts
128
+ * import { streamOpenAIRoute } from "@usagetap/sdk";
129
+ *
130
+ * export const POST = streamOpenAIRoute(usageTap, openai, {
131
+ * getRequest: async (req) => {
132
+ * const body = await req.json();
133
+ * return {
134
+ * params: { model: "gpt-4o-mini", messages: body.messages },
135
+ * context: {
136
+ * customerId: body.customerId,
137
+ * feature: body.feature,
138
+ * tags: body.tags,
139
+ * },
140
+ * };
141
+ * },
142
+ * });
143
+ * ```
144
+ */
145
+ declare function useChatWithUsage(options: UseChatWithUsageOptions): UseChatWithUsageReturn;
146
+
147
+ export { type Message, type UseChatWithUsageOptions, type UseChatWithUsageReturn, useChatWithUsage };
@@ -0,0 +1,147 @@
1
+ interface Message {
2
+ role: "user" | "assistant" | "system";
3
+ content: string;
4
+ id?: string;
5
+ }
6
+ interface UseChatWithUsageOptions {
7
+ /**
8
+ * API endpoint that handles the chat completion.
9
+ * Should be a server-side route that uses UsageTap.
10
+ */
11
+ api: string;
12
+ /**
13
+ * Customer ID to track usage for.
14
+ * This will be sent to the API endpoint.
15
+ */
16
+ customerId: string;
17
+ /**
18
+ * Optional feature name for usage tracking.
19
+ */
20
+ feature?: string;
21
+ /**
22
+ * Optional tags for usage tracking.
23
+ */
24
+ tags?: string[];
25
+ /**
26
+ * Initial messages to populate the chat.
27
+ */
28
+ initialMessages?: Message[];
29
+ /**
30
+ * Called when an error occurs.
31
+ */
32
+ onError?: (error: Error) => void;
33
+ /**
34
+ * Called when a response is finished.
35
+ */
36
+ onFinish?: (message: Message) => void;
37
+ /**
38
+ * Additional headers to send with requests.
39
+ */
40
+ headers?: Record<string, string>;
41
+ }
42
+ interface UseChatWithUsageReturn {
43
+ /**
44
+ * Current messages in the chat.
45
+ */
46
+ messages: Message[];
47
+ /**
48
+ * Current input value.
49
+ */
50
+ input: string;
51
+ /**
52
+ * Set the input value.
53
+ */
54
+ setInput: (input: string) => void;
55
+ /**
56
+ * Whether a request is in progress.
57
+ */
58
+ isLoading: boolean;
59
+ /**
60
+ * Current error, if any.
61
+ */
62
+ error: Error | null;
63
+ /**
64
+ * Append a user message and get a response.
65
+ */
66
+ append: (message: Message) => Promise<void>;
67
+ /**
68
+ * Submit the current input as a user message.
69
+ */
70
+ handleSubmit: (e?: React.FormEvent<HTMLFormElement>) => Promise<void>;
71
+ /**
72
+ * Reload the last assistant response.
73
+ */
74
+ reload: () => Promise<void>;
75
+ /**
76
+ * Stop the current streaming response.
77
+ */
78
+ stop: () => void;
79
+ /**
80
+ * Clear all messages.
81
+ */
82
+ clear: () => void;
83
+ }
84
+ /**
85
+ * React hook for chat interfaces with UsageTap integration.
86
+ *
87
+ * The API endpoint should be a server-side route that:
88
+ * 1. Receives messages, customerId, feature, and tags
89
+ * 2. Uses UsageTap SDK to wrap OpenAI calls
90
+ * 3. Returns streaming or non-streaming responses
91
+ *
92
+ * @example
93
+ * ```tsx
94
+ * import { useChatWithUsage } from "@usagetap/sdk/react";
95
+ *
96
+ * function ChatComponent({ userId }) {
97
+ * const { messages, input, setInput, handleSubmit, isLoading } = useChatWithUsage({
98
+ * api: "/api/chat",
99
+ * customerId: userId,
100
+ * feature: "chat.assistant",
101
+ * });
102
+ *
103
+ * return (
104
+ * <div>
105
+ * {messages.map((m) => (
106
+ * <div key={m.id}>
107
+ * <strong>{m.role}:</strong> {m.content}
108
+ * </div>
109
+ * ))}
110
+ * <form onSubmit={handleSubmit}>
111
+ * <input
112
+ * value={input}
113
+ * onChange={(e) => setInput(e.target.value)}
114
+ * disabled={isLoading}
115
+ * />
116
+ * <button type="submit" disabled={isLoading}>
117
+ * Send
118
+ * </button>
119
+ * </form>
120
+ * </div>
121
+ * );
122
+ * }
123
+ * ```
124
+ *
125
+ * Server route example:
126
+ * ```ts
127
+ * // app/api/chat/route.ts
128
+ * import { streamOpenAIRoute } from "@usagetap/sdk";
129
+ *
130
+ * export const POST = streamOpenAIRoute(usageTap, openai, {
131
+ * getRequest: async (req) => {
132
+ * const body = await req.json();
133
+ * return {
134
+ * params: { model: "gpt-4o-mini", messages: body.messages },
135
+ * context: {
136
+ * customerId: body.customerId,
137
+ * feature: body.feature,
138
+ * tags: body.tags,
139
+ * },
140
+ * };
141
+ * },
142
+ * });
143
+ * ```
144
+ */
145
+ declare function useChatWithUsage(options: UseChatWithUsageOptions): UseChatWithUsageReturn;
146
+
147
+ export { type Message, type UseChatWithUsageOptions, type UseChatWithUsageReturn, useChatWithUsage };
@@ -0,0 +1,237 @@
1
+ import { useState, useRef, useCallback, useEffect } from 'react';
2
+
3
+ // src/react/useChatWithUsage.ts
4
+ function isJsonRecord(value) {
5
+ return typeof value === "object" && value !== null && !Array.isArray(value);
6
+ }
7
+ function extractAssistantContent(payload) {
8
+ if (!isJsonRecord(payload)) {
9
+ return "";
10
+ }
11
+ const choices = payload.choices;
12
+ if (Array.isArray(choices)) {
13
+ for (const choice of choices) {
14
+ if (!isJsonRecord(choice)) continue;
15
+ const message = choice.message;
16
+ if (!isJsonRecord(message)) continue;
17
+ const content = message.content;
18
+ if (typeof content === "string") {
19
+ return content;
20
+ }
21
+ }
22
+ }
23
+ const directContent = payload.content;
24
+ return typeof directContent === "string" ? directContent : "";
25
+ }
26
+ function extractStreamingContent(payload) {
27
+ if (!isJsonRecord(payload)) {
28
+ return "";
29
+ }
30
+ const choices = payload.choices;
31
+ if (Array.isArray(choices)) {
32
+ for (const choice of choices) {
33
+ if (!isJsonRecord(choice)) continue;
34
+ const delta = choice.delta;
35
+ if (!isJsonRecord(delta)) continue;
36
+ const content = delta.content;
37
+ if (typeof content === "string") {
38
+ return content;
39
+ }
40
+ if (Array.isArray(content)) {
41
+ return content.map((entry) => {
42
+ if (!entry) return "";
43
+ if (typeof entry === "string") return entry;
44
+ if (isJsonRecord(entry) && typeof entry.text === "string") return entry.text;
45
+ return "";
46
+ }).join("");
47
+ }
48
+ }
49
+ }
50
+ const directContent = payload.content;
51
+ return typeof directContent === "string" ? directContent : "";
52
+ }
53
+ function tryParseJson(text) {
54
+ try {
55
+ return JSON.parse(text);
56
+ } catch {
57
+ return void 0;
58
+ }
59
+ }
60
+ function isAbortError(error) {
61
+ if (typeof DOMException !== "undefined" && error instanceof DOMException) {
62
+ return error.name === "AbortError";
63
+ }
64
+ if (isJsonRecord(error) && typeof error.name === "string") {
65
+ return error.name === "AbortError";
66
+ }
67
+ return false;
68
+ }
69
+ function useChatWithUsage(options) {
70
+ const {
71
+ api,
72
+ customerId,
73
+ feature,
74
+ tags,
75
+ initialMessages = [],
76
+ onError,
77
+ onFinish,
78
+ headers: customHeaders = {}
79
+ } = options;
80
+ const [messages, setMessages] = useState(initialMessages);
81
+ const [input, setInput] = useState("");
82
+ const [isLoading, setIsLoading] = useState(false);
83
+ const [error, setError] = useState(null);
84
+ const abortControllerRef = useRef(null);
85
+ const generateId = () => `msg_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
86
+ const append = useCallback(
87
+ async (message) => {
88
+ setIsLoading(true);
89
+ setError(null);
90
+ const messageWithId = { ...message, id: message.id || generateId() };
91
+ const newMessages = [...messages, messageWithId];
92
+ setMessages(newMessages);
93
+ abortControllerRef.current = new AbortController();
94
+ try {
95
+ const response = await fetch(api, {
96
+ method: "POST",
97
+ headers: {
98
+ "Content-Type": "application/json",
99
+ ...customHeaders
100
+ },
101
+ body: JSON.stringify({
102
+ messages: newMessages,
103
+ customerId,
104
+ feature,
105
+ tags
106
+ }),
107
+ signal: abortControllerRef.current.signal
108
+ });
109
+ if (!response.ok) {
110
+ throw new Error(`HTTP error! status: ${response.status}`);
111
+ }
112
+ const contentType = response.headers.get("content-type");
113
+ const isStream = contentType?.includes("text/event-stream") || contentType?.includes("text/plain");
114
+ if (isStream && response.body) {
115
+ const reader = response.body.getReader();
116
+ const decoder = new TextDecoder();
117
+ let assistantMessage = "";
118
+ const assistantId = generateId();
119
+ while (true) {
120
+ const readResult = await reader.read();
121
+ if (readResult.done) break;
122
+ const chunkValue = readResult.value instanceof Uint8Array ? readResult.value : new Uint8Array();
123
+ const chunk = decoder.decode(chunkValue, { stream: true });
124
+ const lines = chunk.split("\n");
125
+ for (const line of lines) {
126
+ if (line.startsWith("data: ")) {
127
+ const data = line.slice(6);
128
+ if (data === "[DONE]") continue;
129
+ const parsed = tryParseJson(data);
130
+ if (parsed) {
131
+ const content = extractStreamingContent(parsed);
132
+ if (content) {
133
+ assistantMessage += content;
134
+ setMessages([
135
+ ...newMessages,
136
+ { role: "assistant", content: assistantMessage, id: assistantId }
137
+ ]);
138
+ }
139
+ } else {
140
+ assistantMessage += data;
141
+ setMessages([
142
+ ...newMessages,
143
+ { role: "assistant", content: assistantMessage, id: assistantId }
144
+ ]);
145
+ }
146
+ }
147
+ }
148
+ }
149
+ const finalMessage = {
150
+ role: "assistant",
151
+ content: assistantMessage,
152
+ id: assistantId
153
+ };
154
+ setMessages([...newMessages, finalMessage]);
155
+ onFinish?.(finalMessage);
156
+ } else {
157
+ const payload = await response.json();
158
+ const assistantMessage = {
159
+ role: "assistant",
160
+ content: extractAssistantContent(payload),
161
+ id: generateId()
162
+ };
163
+ setMessages([...newMessages, assistantMessage]);
164
+ onFinish?.(assistantMessage);
165
+ }
166
+ } catch (err) {
167
+ if (isAbortError(err)) {
168
+ return;
169
+ }
170
+ const error2 = err instanceof Error ? err : new Error(String(err));
171
+ setError(error2);
172
+ onError?.(error2);
173
+ } finally {
174
+ setIsLoading(false);
175
+ abortControllerRef.current = null;
176
+ }
177
+ },
178
+ [messages, api, customerId, feature, tags, customHeaders, onError, onFinish]
179
+ );
180
+ const handleSubmit = useCallback(
181
+ async (e) => {
182
+ e?.preventDefault();
183
+ if (!input.trim() || isLoading) return;
184
+ const userMessage = {
185
+ role: "user",
186
+ content: input
187
+ };
188
+ setInput("");
189
+ await append(userMessage);
190
+ },
191
+ [input, isLoading, append]
192
+ );
193
+ const reload = useCallback(async () => {
194
+ if (messages.length === 0 || isLoading) return;
195
+ const messagesWithoutLast = messages.slice(0, -1);
196
+ const lastUserMessage = [...messagesWithoutLast].reverse().find((m) => m.role === "user");
197
+ if (lastUserMessage) {
198
+ setMessages(messagesWithoutLast);
199
+ await append(lastUserMessage);
200
+ }
201
+ }, [messages, isLoading, append]);
202
+ const stop = useCallback(() => {
203
+ if (abortControllerRef.current) {
204
+ abortControllerRef.current.abort();
205
+ abortControllerRef.current = null;
206
+ setIsLoading(false);
207
+ }
208
+ }, []);
209
+ const clear = useCallback(() => {
210
+ setMessages([]);
211
+ setInput("");
212
+ setError(null);
213
+ }, []);
214
+ useEffect(() => {
215
+ return () => {
216
+ if (abortControllerRef.current) {
217
+ abortControllerRef.current.abort();
218
+ }
219
+ };
220
+ }, []);
221
+ return {
222
+ messages,
223
+ input,
224
+ setInput,
225
+ isLoading,
226
+ error,
227
+ append,
228
+ handleSubmit,
229
+ reload,
230
+ stop,
231
+ clear
232
+ };
233
+ }
234
+
235
+ export { useChatWithUsage };
236
+ //# sourceMappingURL=index.js.map
237
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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.js","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\";\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"]}
package/package.json ADDED
@@ -0,0 +1,80 @@
1
+ {
2
+ "name": "@usagetap/sdk",
3
+ "version": "0.1.0",
4
+ "description": "UsageTap SDK core client plus optional React helpers.",
5
+ "license": "UNLICENSED",
6
+ "type": "module",
7
+ "main": "./dist/index.cjs",
8
+ "module": "./dist/index.mjs",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.mjs",
14
+ "require": "./dist/index.cjs"
15
+ },
16
+ "./react": {
17
+ "types": "./dist/react/index.d.ts",
18
+ "import": "./dist/react/index.mjs",
19
+ "require": "./dist/react/index.cjs"
20
+ },
21
+ "./adapters/openai": {
22
+ "types": "./dist/adapters/openai.d.ts",
23
+ "import": "./dist/adapters/openai.mjs",
24
+ "require": "./dist/adapters/openai.cjs"
25
+ },
26
+ "./adapters/openrouter": {
27
+ "types": "./dist/adapters/openrouter.d.ts",
28
+ "import": "./dist/adapters/openrouter.mjs",
29
+ "require": "./dist/adapters/openrouter.cjs"
30
+ },
31
+ "./package.json": "./package.json"
32
+ },
33
+ "typesVersions": {
34
+ "*": {
35
+ "react": ["dist/react/index.d.ts"]
36
+ }
37
+ },
38
+ "sideEffects": false,
39
+ "files": [
40
+ "dist",
41
+ "README.md"
42
+ ],
43
+ "scripts": {
44
+ "build": "tsup",
45
+ "clean": "rimraf dist",
46
+ "test": "vitest run --coverage",
47
+ "test:watch": "vitest",
48
+ "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}\"",
49
+ "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}\"",
50
+ "docs": "npm run build && api-extractor run --local --verbose && api-documenter markdown --input-folder temp --output-folder docs",
51
+ "prepare": "npm run build"
52
+ },
53
+ "engines": {
54
+ "node": ">=18.17"
55
+ },
56
+ "peerDependencies": {
57
+ "express": ">=4.0.0",
58
+ "openai": ">=4.0.0",
59
+ "react": ">=18.0.0"
60
+ },
61
+ "peerDependenciesMeta": {
62
+ "openai": {
63
+ "optional": true
64
+ },
65
+ "express": {
66
+ "optional": true
67
+ },
68
+ "react": {
69
+ "optional": true
70
+ }
71
+ },
72
+ "devDependencies": {
73
+ "@types/express": "^5.0.3",
74
+ "@types/react": "^19.2.0",
75
+ "openai": "^4.76.0"
76
+ },
77
+ "dependencies": {
78
+ "lucide-react": "^0.544.0"
79
+ }
80
+ }