langwatch 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/.eslintrc.cjs +37 -0
  2. package/README.md +3 -0
  3. package/dist/chunk-GOA2HL4A.mjs +269 -0
  4. package/dist/chunk-GOA2HL4A.mjs.map +1 -0
  5. package/dist/index.d.mts +82 -0
  6. package/dist/index.d.ts +82 -0
  7. package/dist/index.js +940 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/index.mjs +666 -0
  10. package/dist/index.mjs.map +1 -0
  11. package/dist/utils-s3gGR6vj.d.mts +209 -0
  12. package/dist/utils-s3gGR6vj.d.ts +209 -0
  13. package/dist/utils.d.mts +3 -0
  14. package/dist/utils.d.ts +3 -0
  15. package/dist/utils.js +263 -0
  16. package/dist/utils.js.map +1 -0
  17. package/dist/utils.mjs +7 -0
  18. package/dist/utils.mjs.map +1 -0
  19. package/example/.env.example +12 -0
  20. package/example/.eslintrc.json +26 -0
  21. package/example/LICENSE +13 -0
  22. package/example/README.md +10 -0
  23. package/example/app/(chat)/chat/[id]/page.tsx +60 -0
  24. package/example/app/(chat)/layout.tsx +14 -0
  25. package/example/app/(chat)/page.tsx +22 -0
  26. package/example/app/actions.ts +156 -0
  27. package/example/app/globals.css +76 -0
  28. package/example/app/layout.tsx +64 -0
  29. package/example/app/login/actions.ts +71 -0
  30. package/example/app/login/page.tsx +18 -0
  31. package/example/app/new/page.tsx +5 -0
  32. package/example/app/opengraph-image.png +0 -0
  33. package/example/app/share/[id]/page.tsx +58 -0
  34. package/example/app/signup/actions.ts +111 -0
  35. package/example/app/signup/page.tsx +18 -0
  36. package/example/app/twitter-image.png +0 -0
  37. package/example/auth.config.ts +42 -0
  38. package/example/auth.ts +45 -0
  39. package/example/components/button-scroll-to-bottom.tsx +36 -0
  40. package/example/components/chat-history.tsx +49 -0
  41. package/example/components/chat-list.tsx +52 -0
  42. package/example/components/chat-message-actions.tsx +40 -0
  43. package/example/components/chat-message.tsx +80 -0
  44. package/example/components/chat-panel.tsx +139 -0
  45. package/example/components/chat-share-dialog.tsx +95 -0
  46. package/example/components/chat.tsx +84 -0
  47. package/example/components/clear-history.tsx +75 -0
  48. package/example/components/empty-screen.tsx +38 -0
  49. package/example/components/external-link.tsx +29 -0
  50. package/example/components/footer.tsx +19 -0
  51. package/example/components/header.tsx +80 -0
  52. package/example/components/login-button.tsx +42 -0
  53. package/example/components/login-form.tsx +97 -0
  54. package/example/components/markdown.tsx +9 -0
  55. package/example/components/prompt-form.tsx +115 -0
  56. package/example/components/providers.tsx +17 -0
  57. package/example/components/sidebar-actions.tsx +125 -0
  58. package/example/components/sidebar-desktop.tsx +19 -0
  59. package/example/components/sidebar-footer.tsx +16 -0
  60. package/example/components/sidebar-item.tsx +124 -0
  61. package/example/components/sidebar-items.tsx +42 -0
  62. package/example/components/sidebar-list.tsx +38 -0
  63. package/example/components/sidebar-mobile.tsx +31 -0
  64. package/example/components/sidebar-toggle.tsx +24 -0
  65. package/example/components/sidebar.tsx +21 -0
  66. package/example/components/signup-form.tsx +95 -0
  67. package/example/components/stocks/events-skeleton.tsx +31 -0
  68. package/example/components/stocks/events.tsx +30 -0
  69. package/example/components/stocks/index.tsx +36 -0
  70. package/example/components/stocks/message.tsx +134 -0
  71. package/example/components/stocks/spinner.tsx +16 -0
  72. package/example/components/stocks/stock-purchase.tsx +146 -0
  73. package/example/components/stocks/stock-skeleton.tsx +22 -0
  74. package/example/components/stocks/stock.tsx +210 -0
  75. package/example/components/stocks/stocks-skeleton.tsx +9 -0
  76. package/example/components/stocks/stocks.tsx +67 -0
  77. package/example/components/tailwind-indicator.tsx +14 -0
  78. package/example/components/theme-toggle.tsx +31 -0
  79. package/example/components/ui/alert-dialog.tsx +141 -0
  80. package/example/components/ui/badge.tsx +36 -0
  81. package/example/components/ui/button.tsx +57 -0
  82. package/example/components/ui/codeblock.tsx +148 -0
  83. package/example/components/ui/dialog.tsx +122 -0
  84. package/example/components/ui/dropdown-menu.tsx +205 -0
  85. package/example/components/ui/icons.tsx +507 -0
  86. package/example/components/ui/input.tsx +25 -0
  87. package/example/components/ui/label.tsx +26 -0
  88. package/example/components/ui/select.tsx +164 -0
  89. package/example/components/ui/separator.tsx +31 -0
  90. package/example/components/ui/sheet.tsx +140 -0
  91. package/example/components/ui/sonner.tsx +31 -0
  92. package/example/components/ui/switch.tsx +29 -0
  93. package/example/components/ui/textarea.tsx +24 -0
  94. package/example/components/ui/tooltip.tsx +30 -0
  95. package/example/components/user-menu.tsx +53 -0
  96. package/example/components.json +17 -0
  97. package/example/lib/chat/actions.tsx +606 -0
  98. package/example/lib/hooks/use-copy-to-clipboard.tsx +33 -0
  99. package/example/lib/hooks/use-enter-submit.tsx +23 -0
  100. package/example/lib/hooks/use-local-storage.ts +24 -0
  101. package/example/lib/hooks/use-scroll-anchor.tsx +86 -0
  102. package/example/lib/hooks/use-sidebar.tsx +60 -0
  103. package/example/lib/hooks/use-streamable-text.ts +25 -0
  104. package/example/lib/types.ts +41 -0
  105. package/example/lib/utils.ts +89 -0
  106. package/example/middleware.ts +8 -0
  107. package/example/next-env.d.ts +5 -0
  108. package/example/next.config.js +13 -0
  109. package/example/package-lock.json +9249 -0
  110. package/example/package.json +77 -0
  111. package/example/pnpm-lock.yaml +5712 -0
  112. package/example/postcss.config.js +6 -0
  113. package/example/prettier.config.cjs +34 -0
  114. package/example/public/apple-touch-icon.png +0 -0
  115. package/example/public/favicon-16x16.png +0 -0
  116. package/example/public/favicon.ico +0 -0
  117. package/example/public/next.svg +1 -0
  118. package/example/public/thirteen.svg +1 -0
  119. package/example/public/vercel.svg +1 -0
  120. package/example/tailwind.config.ts +81 -0
  121. package/example/tsconfig.json +35 -0
  122. package/package.json +45 -0
  123. package/src/helpers.ts +64 -0
  124. package/src/index.test.ts +255 -0
  125. package/src/index.ts +397 -0
  126. package/src/server/types/.gitkeep +0 -0
  127. package/src/types.ts +69 -0
  128. package/src/utils.ts +134 -0
  129. package/ts-to-zod.config.js +18 -0
  130. package/tsconfig.json +32 -0
  131. package/tsup.config.ts +10 -0
  132. package/vitest.config.ts +8 -0
package/src/index.ts ADDED
@@ -0,0 +1,397 @@
1
+ import EventEmitter from "events";
2
+ import { nanoid } from "nanoid";
3
+ import { ZodError } from "zod";
4
+ import { fromZodError } from "zod-validation-error";
5
+ import { camelToSnakeCaseNested, type Strict } from "./helpers";
6
+ import {
7
+ type CollectorRESTParams,
8
+ type Span as ServerSpan,
9
+ type SpanTypes,
10
+ } from "./server/types/tracer";
11
+ import {
12
+ collectorRESTParamsSchema,
13
+ spanSchema,
14
+ } from "./server/types/tracer.generated";
15
+ import {
16
+ type BaseSpan,
17
+ type ChatMessage,
18
+ type ChatRichContent,
19
+ type LLMSpan,
20
+ type Metadata,
21
+ type PendingBaseSpan,
22
+ type PendingLLMSpan,
23
+ type PendingRAGSpan,
24
+ type RAGSpan,
25
+ type SpanInputOutput,
26
+ } from "./types";
27
+ import { convertFromVercelAIMessages } from "./utils";
28
+
29
+ export type {
30
+ BaseSpan,
31
+ ChatMessage as ChatMessage,
32
+ ChatRichContent,
33
+ LLMSpan,
34
+ Metadata,
35
+ PendingBaseSpan,
36
+ PendingLLMSpan,
37
+ PendingRAGSpan,
38
+ RAGSpan,
39
+ SpanInputOutput,
40
+ };
41
+
42
+ export { convertFromVercelAIMessages };
43
+
44
+ export class LangWatch extends EventEmitter {
45
+ apiKey: string | undefined;
46
+ endpoint: string;
47
+
48
+ constructor({
49
+ apiKey,
50
+ endpoint = process.env.LANGWATCH_ENDPOINT ?? "https://app.langwatch.ai",
51
+ }: {
52
+ apiKey?: string;
53
+ endpoint?: string;
54
+ } = {}) {
55
+ super();
56
+ const apiKey_ = apiKey ?? process.env.LANGWATCH_API_KEY;
57
+ if (!apiKey_) {
58
+ console.warn(
59
+ "[LangWatch] ⚠️ LangWatch API key is not set, please set the LANGWATCH_API_KEY environment variable or pass it in the constructor. Traces will not be captured."
60
+ );
61
+ }
62
+ this.apiKey = apiKey_;
63
+ this.endpoint = endpoint;
64
+ }
65
+
66
+ getTrace({
67
+ traceId,
68
+ metadata,
69
+ }: { traceId?: string; metadata?: Metadata } = {}) {
70
+ return new LangWatchTrace({
71
+ client: this,
72
+ traceId: traceId ?? `trace_${nanoid()}`,
73
+ metadata,
74
+ });
75
+ }
76
+
77
+ async sendTrace(params: CollectorRESTParams) {
78
+ const backoff = [1000, 2000, 4000, 8000, 16000];
79
+ for (const backoffTime of backoff) {
80
+ try {
81
+ await this._sendTrace(params);
82
+ return;
83
+ } catch (e) {
84
+ console.warn(
85
+ `[LangWatch] ⚠️ Failed to send trace, retrying in ${
86
+ backoffTime / 1000
87
+ }s`
88
+ );
89
+ await new Promise((resolve) => setTimeout(resolve, backoffTime));
90
+ }
91
+ }
92
+ console.warn("[LangWatch] ⚠️ Failed to send trace, giving up");
93
+ }
94
+
95
+ async _sendTrace(params: CollectorRESTParams) {
96
+ if (params.spans.length === 0) {
97
+ return;
98
+ }
99
+
100
+ if (!this.apiKey) {
101
+ const error = new Error(
102
+ "[LangWatch] ⚠️ LangWatch API key is not set, LLMs traces will not be sent, go to https://langwatch.ai to set it up"
103
+ );
104
+ this.emit("error", error);
105
+ console.warn(error.message);
106
+ return;
107
+ }
108
+
109
+ const response = await fetch(`${this.endpoint}/api/collector`, {
110
+ method: "POST",
111
+ headers: {
112
+ "X-Auth-Token": this.apiKey,
113
+ "Content-Type": "application/json",
114
+ },
115
+ body: JSON.stringify(params),
116
+ });
117
+
118
+ if (response.status === 429) {
119
+ const error = new Error(
120
+ "[LangWatch] ⚠️ Rate limit exceeded, dropping message from being sent to LangWatch. Please check your dashboard to upgrade your plan."
121
+ );
122
+ this.emit("error", error);
123
+ console.warn(error.message);
124
+ return;
125
+ }
126
+ if (!response.ok) {
127
+ const error = new Error(
128
+ `[LangWatch] ⚠️ Failed to send trace, status: ${response.status}`
129
+ );
130
+ this.emit("error", error);
131
+ throw error;
132
+ }
133
+ }
134
+ }
135
+
136
+ export class LangWatchTrace {
137
+ client: LangWatch;
138
+ traceId: string;
139
+ metadata?: Metadata;
140
+ finishedSpans: Record<string, ServerSpan> = {};
141
+ timeoutRef?: NodeJS.Timeout;
142
+
143
+ constructor({
144
+ client,
145
+ traceId,
146
+ metadata,
147
+ }: {
148
+ client: LangWatch;
149
+ traceId: string;
150
+ metadata?: Metadata;
151
+ }) {
152
+ this.client = client;
153
+ this.traceId = traceId;
154
+ this.metadata = metadata;
155
+ }
156
+
157
+ update({ metadata }: { metadata: Metadata }) {
158
+ this.metadata = {
159
+ ...this.metadata,
160
+ ...metadata,
161
+ ...(typeof metadata.labels !== "undefined"
162
+ ? { labels: [...(this.metadata?.labels ?? []), ...metadata.labels] }
163
+ : {}),
164
+ };
165
+ }
166
+
167
+ startSpan(params: Omit<Partial<PendingBaseSpan>, "parentId">) {
168
+ const span = new LangWatchSpan({
169
+ trace: this,
170
+ ...params,
171
+ });
172
+ return span;
173
+ }
174
+
175
+ startLLMSpan(params: Omit<Partial<PendingLLMSpan>, "parentId">) {
176
+ const span = new LangWatchLLMSpan({
177
+ trace: this,
178
+ ...params,
179
+ });
180
+ return span;
181
+ }
182
+
183
+ startRAGSpan(params: Omit<Partial<PendingRAGSpan>, "parentId">) {
184
+ const span = new LangWatchRAGSpan({
185
+ trace: this,
186
+ ...params,
187
+ });
188
+ return span;
189
+ }
190
+
191
+ onEnd(span: ServerSpan) {
192
+ this.finishedSpans[span.span_id] = span;
193
+ this.delayedSendSpans();
194
+ }
195
+
196
+ delayedSendSpans() {
197
+ clearTimeout(this.timeoutRef);
198
+ this.timeoutRef = setTimeout(() => {
199
+ void this.sendSpans();
200
+ }, 1000);
201
+ }
202
+
203
+ async sendSpans() {
204
+ clearTimeout(this.timeoutRef);
205
+
206
+ let trace: CollectorRESTParams | undefined = undefined;
207
+ try {
208
+ trace = collectorRESTParamsSchema.parse({
209
+ trace_id: this.traceId,
210
+ metadata: camelToSnakeCaseNested(this.metadata),
211
+ spans: Object.values(this.finishedSpans),
212
+ } as Strict<CollectorRESTParams>);
213
+ } catch (error) {
214
+ if (error instanceof ZodError) {
215
+ console.warn("[LangWatch] ⚠️ Failed to parse trace");
216
+ console.warn(fromZodError(error).message);
217
+ } else {
218
+ console.warn(error);
219
+ }
220
+ this.client.emit("error", error);
221
+ }
222
+
223
+ if (trace) {
224
+ await this.client.sendTrace(trace);
225
+ }
226
+ }
227
+ }
228
+
229
+ export class LangWatchSpan implements PendingBaseSpan {
230
+ trace: LangWatchTrace;
231
+
232
+ spanId: string;
233
+ parentId?: string | null;
234
+ type: SpanTypes;
235
+ name?: string | null;
236
+ input: PendingBaseSpan["input"];
237
+ outputs: PendingBaseSpan["outputs"];
238
+ error?: PendingBaseSpan["error"];
239
+ timestamps: PendingBaseSpan["timestamps"];
240
+ metrics: PendingBaseSpan["metrics"];
241
+
242
+ constructor({
243
+ trace,
244
+ spanId,
245
+ parentId,
246
+ type,
247
+ name,
248
+ input,
249
+ outputs,
250
+ error,
251
+ timestamps,
252
+ metrics,
253
+ }: Partial<PendingBaseSpan> & { trace: LangWatchTrace }) {
254
+ this.spanId = spanId ?? `span_${nanoid()}`;
255
+ this.parentId = parentId;
256
+ this.trace = trace;
257
+ this.type = type ?? "span";
258
+ this.name = name;
259
+ this.input = input;
260
+ this.outputs = outputs ?? [];
261
+ this.error = error;
262
+ this.timestamps = timestamps ?? {
263
+ startedAt: Date.now(),
264
+ };
265
+ this.metrics = metrics;
266
+ }
267
+
268
+ update(params: Partial<Omit<PendingBaseSpan, "spanId" | "parentId">>) {
269
+ if (params.type) {
270
+ this.type = params.type;
271
+ }
272
+ if ("name" in params) {
273
+ this.name = params.name;
274
+ }
275
+ if ("input" in params) {
276
+ this.input = params.input;
277
+ }
278
+ if (params.outputs) {
279
+ this.outputs = params.outputs;
280
+ }
281
+ if ("error" in params) {
282
+ this.error = params.error;
283
+ }
284
+ if (params.timestamps) {
285
+ this.timestamps = params.timestamps;
286
+ }
287
+ if ("metrics" in params) {
288
+ this.metrics = params.metrics;
289
+ }
290
+ }
291
+
292
+ startSpan(params: Omit<Partial<PendingBaseSpan>, "parentId">) {
293
+ const span = new LangWatchSpan({
294
+ trace: this.trace,
295
+ parentId: this.spanId,
296
+ ...params,
297
+ });
298
+ return span;
299
+ }
300
+
301
+ startLLMSpan(params: Omit<Partial<PendingLLMSpan>, "parentId">) {
302
+ const span = new LangWatchLLMSpan({
303
+ trace: this.trace,
304
+ parentId: this.spanId,
305
+ ...params,
306
+ });
307
+ return span;
308
+ }
309
+
310
+ startRAGSpan(params: Omit<Partial<PendingRAGSpan>, "parentId">) {
311
+ const span = new LangWatchRAGSpan({
312
+ trace: this.trace,
313
+ parentId: this.spanId,
314
+ ...params,
315
+ });
316
+ return span;
317
+ }
318
+
319
+ end(params?: Partial<Omit<PendingBaseSpan, "spanId" | "parentId">>) {
320
+ this.timestamps.finishedAt = Date.now();
321
+ if (params) {
322
+ this.update(params);
323
+ }
324
+
325
+ try {
326
+ const finalSpan = spanSchema.parse(
327
+ camelToSnakeCaseNested({
328
+ ...this,
329
+ trace: undefined,
330
+ traceId: this.trace.traceId,
331
+ timestamps: {
332
+ ...this.timestamps,
333
+ finishedAt: this.timestamps.finishedAt,
334
+ },
335
+ }) as ServerSpan
336
+ );
337
+ this.trace.onEnd(finalSpan);
338
+ } catch (error) {
339
+ if (error instanceof ZodError) {
340
+ console.warn("[LangWatch] ⚠️ Failed to parse span");
341
+ console.warn(fromZodError(error).message);
342
+ } else {
343
+ console.warn(error);
344
+ }
345
+ this.trace.client.emit("error", error);
346
+ }
347
+ }
348
+ }
349
+
350
+ export class LangWatchLLMSpan extends LangWatchSpan implements PendingLLMSpan {
351
+ type: "llm";
352
+ model: PendingLLMSpan["model"];
353
+ params: PendingLLMSpan["params"];
354
+
355
+ constructor(params: Partial<PendingLLMSpan> & { trace: LangWatchTrace }) {
356
+ super({ ...params });
357
+ this.type = "llm";
358
+ this.model = params.model ?? "unknown";
359
+ this.params = params.params ?? {};
360
+ }
361
+
362
+ update(params: Partial<PendingLLMSpan>) {
363
+ super.update(params);
364
+ if (params.model) {
365
+ this.model = params.model;
366
+ }
367
+ if (params.params) {
368
+ this.params = params.params;
369
+ }
370
+ }
371
+
372
+ end(params?: Partial<PendingLLMSpan>) {
373
+ super.end(params);
374
+ }
375
+ }
376
+
377
+ export class LangWatchRAGSpan extends LangWatchSpan implements PendingRAGSpan {
378
+ type: "rag";
379
+ contexts: PendingRAGSpan["contexts"];
380
+
381
+ constructor(params: Partial<PendingRAGSpan> & { trace: LangWatchTrace }) {
382
+ super({ ...params });
383
+ this.type = "rag";
384
+ this.contexts = params.contexts ?? [];
385
+ }
386
+
387
+ update(params: Partial<PendingRAGSpan>) {
388
+ super.update(params);
389
+ if (params.contexts) {
390
+ this.contexts = params.contexts;
391
+ }
392
+ }
393
+
394
+ end(params?: Partial<PendingRAGSpan>) {
395
+ super.end(params);
396
+ }
397
+ }
File without changes
package/src/types.ts ADDED
@@ -0,0 +1,69 @@
1
+ import type modelPrices from "llm-cost/model_prices_and_context_window.json";
2
+ import type { OpenAI } from "openai";
3
+ import { type SnakeToCamelCaseNested } from "./helpers";
4
+ import {
5
+ type BaseSpan as ServerBaseSpan,
6
+ type ChatMessage as ServerChatMessage,
7
+ type ChatRichContent as ServerChatRichContent,
8
+ type LLMSpan as ServerLLMSpan,
9
+ type RAGSpan as ServerRAGSpan,
10
+ type SpanInputOutput as ServerSpanInputOutput,
11
+ type TypedValueChatMessages,
12
+ type Trace,
13
+ } from "./server/types/tracer";
14
+
15
+ export type Metadata = SnakeToCamelCaseNested<Trace["metadata"]>;
16
+
17
+ export type ChatMessage = ServerChatMessage;
18
+
19
+ export type ChatRichContent = ServerChatRichContent;
20
+
21
+ // Check to see if out ChatMessage type is compatible with OpenAIChatCompletion messages
22
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
23
+ ({}) as OpenAI.Chat.ChatCompletionMessageParam satisfies ChatMessage;
24
+ // Check to see spans input/output is still compatible with OpenAIChatCompletion messages to avoid camelCase/snake_case issues
25
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
26
+ ({}) as {
27
+ type: "chat_messages";
28
+ value: OpenAI.Chat.ChatCompletionMessageParam[];
29
+ } satisfies BaseSpan["input"];
30
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
31
+ ({}) as {
32
+ type: "chat_messages";
33
+ value: OpenAI.Chat.ChatCompletionMessageParam[];
34
+ }[] satisfies BaseSpan["outputs"];
35
+
36
+ // Keep the input/output types signatures as snake case to match the official openai nodejs api
37
+ export type SpanInputOutput =
38
+ | SnakeToCamelCaseNested<
39
+ Exclude<ServerSpanInputOutput, TypedValueChatMessages>
40
+ >
41
+ | (TypedValueChatMessages & { type: ChatMessage });
42
+
43
+ export type ConvertServerSpan<T extends ServerBaseSpan> =
44
+ SnakeToCamelCaseNested<Omit<T, "input" | "outputs">> & {
45
+ input?: SpanInputOutput | null;
46
+ outputs: SpanInputOutput[];
47
+ };
48
+
49
+ export type PendingSpan<T extends BaseSpan> = Omit<
50
+ T,
51
+ "traceId" | "timestamps"
52
+ > & {
53
+ timestamps: Omit<T["timestamps"], "finishedAt"> & {
54
+ finishedAt?: number | null;
55
+ };
56
+ };
57
+
58
+ export type BaseSpan = ConvertServerSpan<ServerBaseSpan>;
59
+
60
+ export type PendingBaseSpan = PendingSpan<BaseSpan>;
61
+
62
+ // vendor is deprecated, and we try to force the available models here
63
+ export type LLMSpan = ConvertServerSpan<
64
+ Omit<ServerLLMSpan, "vendor" | "model">
65
+ > & { model: keyof typeof modelPrices | (string & NonNullable<unknown>) };
66
+ export type PendingLLMSpan = PendingSpan<LLMSpan>;
67
+
68
+ export type RAGSpan = ConvertServerSpan<ServerRAGSpan>;
69
+ export type PendingRAGSpan = PendingSpan<RAGSpan>;
package/src/utils.ts ADDED
@@ -0,0 +1,134 @@
1
+ import { convertUint8ArrayToBase64 } from "@ai-sdk/provider-utils";
2
+ import { type ImagePart, type CoreMessage } from "ai";
3
+ import { type ChatMessage } from "./types";
4
+
5
+ const convertImageToUrl = (
6
+ image: ImagePart["image"],
7
+ mimeType: string | undefined
8
+ ) => {
9
+ try {
10
+ return image instanceof URL
11
+ ? image.toString()
12
+ : typeof image === "string"
13
+ ? image
14
+ : `data:${mimeType ?? "image/jpeg"};base64,${convertUint8ArrayToBase64(
15
+ image as any
16
+ )}`;
17
+ } catch (e) {
18
+ console.error("[LangWatch] error converting vercel ui image to url:", e);
19
+ return "";
20
+ }
21
+ };
22
+
23
+ // Mostly copied from https://github.com/vercel/ai/blob/main/packages/openai/src/convert-to-openai-chat-messages.ts
24
+ export function convertFromVercelAIMessages(
25
+ messages: CoreMessage[]
26
+ ): ChatMessage[] {
27
+ const lwMessages: ChatMessage[] = [];
28
+
29
+ for (const { role, content } of messages) {
30
+ switch (role) {
31
+ case "system": {
32
+ lwMessages.push({ role: "system", content });
33
+ break;
34
+ }
35
+
36
+ case "user": {
37
+ if (
38
+ Array.isArray(content) &&
39
+ content.length === 1 &&
40
+ content[0]?.type === "text"
41
+ ) {
42
+ lwMessages.push({ role: "user", content: content[0].text });
43
+ break;
44
+ }
45
+
46
+ lwMessages.push({
47
+ role: "user",
48
+ content: Array.isArray(content)
49
+ ? content.map((part) => {
50
+ switch (part.type) {
51
+ case "text": {
52
+ return { type: "text", text: part.text };
53
+ }
54
+ case "image": {
55
+ return {
56
+ type: "image_url",
57
+ image_url: {
58
+ url: convertImageToUrl(part.image, part.mimeType),
59
+ },
60
+ };
61
+ }
62
+ }
63
+ })
64
+ : content,
65
+ });
66
+
67
+ break;
68
+ }
69
+
70
+ case "assistant": {
71
+ let text = "";
72
+ const toolCalls: Array<{
73
+ id: string;
74
+ type: "function";
75
+ function: { name: string; arguments: string };
76
+ }> = [];
77
+
78
+ if (Array.isArray(content)) {
79
+ for (const part of content) {
80
+ switch (part.type) {
81
+ case "text": {
82
+ text += part.text;
83
+ break;
84
+ }
85
+ case "tool-call": {
86
+ toolCalls.push({
87
+ id: part.toolCallId,
88
+ type: "function",
89
+ function: {
90
+ name: part.toolName,
91
+ arguments: JSON.stringify(part.args),
92
+ },
93
+ });
94
+ break;
95
+ }
96
+ default: {
97
+ const _exhaustiveCheck = part;
98
+ throw new Error(`Unsupported part: ${_exhaustiveCheck as any}`);
99
+ }
100
+ }
101
+ }
102
+ } else {
103
+ text = content;
104
+ }
105
+
106
+ lwMessages.push({
107
+ role: "assistant",
108
+ content: text,
109
+ tool_calls: toolCalls.length > 0 ? toolCalls : undefined,
110
+ });
111
+
112
+ break;
113
+ }
114
+
115
+ case "tool": {
116
+ for (const toolResponse of content) {
117
+ lwMessages.push({
118
+ role: "tool",
119
+ tool_call_id: toolResponse.toolCallId,
120
+ content: JSON.stringify(toolResponse.result),
121
+ });
122
+ }
123
+ break;
124
+ }
125
+
126
+ default: {
127
+ const _exhaustiveCheck = role;
128
+ throw new Error(`Unsupported role: ${_exhaustiveCheck as any}`);
129
+ }
130
+ }
131
+ }
132
+
133
+ return lwMessages;
134
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * ts-to-zod configuration.
3
+ *
4
+ * @type {import("ts-to-zod").TsToZodConfig}
5
+ */
6
+ module.exports = {
7
+ input: "server/tracer/types.ts",
8
+ output: "server/tracer/types.zod.ts",
9
+ nameFilter: (name) =>
10
+ ![
11
+ "ElasticSearchSpan",
12
+ "ElasticSearchInputOutput",
13
+ "EvaluatorDefinition",
14
+ "TraceCheckJob",
15
+ "AnalyticsMetric",
16
+ "NewDatasetEntries",
17
+ ].includes(name),
18
+ };
package/tsconfig.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es2017",
4
+ "module": "ESNext",
5
+ "declaration": true,
6
+ "declarationMap": true,
7
+ "outDir": "./dist",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "resolveJsonModule": true,
11
+ "allowJs": true,
12
+ "checkJs": true,
13
+ "skipLibCheck": true,
14
+ "forceConsistentCasingInFileNames": true,
15
+ "moduleResolution": "node",
16
+ "isolatedModules": true,
17
+ "jsx": "preserve",
18
+ "incremental": true,
19
+ "noUncheckedIndexedAccess": true,
20
+ "baseUrl": ".",
21
+ "tsBuildInfoFile": "./tsconfig.tsbuildinfo"
22
+ },
23
+ "include": [
24
+ ".eslintrc.cjs",
25
+ "next-env.d.ts",
26
+ "**/*.ts",
27
+ "**/*.tsx",
28
+ "**/*.cjs",
29
+ "**/*.mjs"
30
+ ],
31
+ "exclude": ["node_modules", "./dist/**/*", "./example/**/*"]
32
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,10 @@
1
+ import { defineConfig } from "tsup";
2
+
3
+ export default defineConfig([
4
+ {
5
+ entry: ["src/index.ts", "src/utils.ts"],
6
+ format: ["cjs", "esm"],
7
+ dts: true,
8
+ sourcemap: true,
9
+ },
10
+ ]);
@@ -0,0 +1,8 @@
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ testTimeout: 30_000,
6
+ hookTimeout: 30_000,
7
+ },
8
+ });