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
@@ -0,0 +1,6 @@
1
+ module.exports = {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ }
@@ -0,0 +1,34 @@
1
+ /** @type {import('prettier').Config} */
2
+ module.exports = {
3
+ endOfLine: 'lf',
4
+ semi: false,
5
+ useTabs: false,
6
+ singleQuote: true,
7
+ arrowParens: 'avoid',
8
+ tabWidth: 2,
9
+ trailingComma: 'none',
10
+ importOrder: [
11
+ '^(react/(.*)$)|^(react$)',
12
+ '^(next/(.*)$)|^(next$)',
13
+ '<THIRD_PARTY_MODULES>',
14
+ '',
15
+ '^types$',
16
+ '^@/types/(.*)$',
17
+ '^@/config/(.*)$',
18
+ '^@/lib/(.*)$',
19
+ '^@/hooks/(.*)$',
20
+ '^@/components/ui/(.*)$',
21
+ '^@/components/(.*)$',
22
+ '^@/registry/(.*)$',
23
+ '^@/styles/(.*)$',
24
+ '^@/app/(.*)$',
25
+ '',
26
+ '^[./]'
27
+ ],
28
+ importOrderSeparation: false,
29
+ importOrderSortSpecifiers: true,
30
+ importOrderBuiltinModulesToTop: true,
31
+ importOrderParserPlugins: ['typescript', 'jsx', 'decorators-legacy'],
32
+ importOrderMergeDuplicateImports: true,
33
+ importOrderCombineTypeAndValueImports: true
34
+ }
Binary file
Binary file
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="40" height="31" fill="none"><g opacity=".9"><path fill="url(#a)" d="M13 .4v29.3H7V6.3h-.2L0 10.5V5L7.2.4H13Z"/><path fill="url(#b)" d="M28.8 30.1c-2.2 0-4-.3-5.7-1-1.7-.8-3-1.8-4-3.1a7.7 7.7 0 0 1-1.4-4.6h6.2c0 .8.3 1.4.7 2 .4.5 1 .9 1.7 1.2.7.3 1.6.4 2.5.4 1 0 1.7-.2 2.5-.5.7-.3 1.3-.8 1.7-1.4.4-.6.6-1.2.6-2s-.2-1.5-.7-2.1c-.4-.6-1-1-1.8-1.4-.8-.4-1.8-.5-2.9-.5h-2.7v-4.6h2.7a6 6 0 0 0 2.5-.5 4 4 0 0 0 1.7-1.3c.4-.6.6-1.3.6-2a3.5 3.5 0 0 0-2-3.3 5.6 5.6 0 0 0-4.5 0 4 4 0 0 0-1.7 1.2c-.4.6-.6 1.2-.6 2h-6c0-1.7.6-3.2 1.5-4.5 1-1.3 2.2-2.3 3.8-3C25 .4 26.8 0 28.8 0s3.8.4 5.3 1.1c1.5.7 2.7 1.7 3.6 3a7.2 7.2 0 0 1 1.2 4.2c0 1.6-.5 3-1.5 4a7 7 0 0 1-4 2.2v.2c2.2.3 3.8 1 5 2.2a6.4 6.4 0 0 1 1.6 4.6c0 1.7-.5 3.1-1.4 4.4a9.7 9.7 0 0 1-4 3.1c-1.7.8-3.7 1.1-5.8 1.1Z"/></g><defs><linearGradient id="a" x1="20" x2="20" y1="0" y2="30.1" gradientUnits="userSpaceOnUse"><stop/><stop offset="1" stop-color="#3D3D3D"/></linearGradient><linearGradient id="b" x1="20" x2="20" y1="0" y2="30.1" gradientUnits="userSpaceOnUse"><stop/><stop offset="1" stop-color="#3D3D3D"/></linearGradient></defs></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 283 64"><path fill="black" d="M141 16c-11 0-19 7-19 18s9 18 20 18c7 0 13-3 16-7l-7-5c-2 3-6 4-9 4-5 0-9-3-10-7h28v-3c0-11-8-18-19-18zm-9 15c1-4 4-7 9-7s8 3 9 7h-18zm117-15c-11 0-19 7-19 18s9 18 20 18c6 0 12-3 16-7l-8-5c-2 3-5 4-8 4-5 0-9-3-11-7h28l1-3c0-11-8-18-19-18zm-10 15c2-4 5-7 10-7s8 3 9 7h-19zm-39 3c0 6 4 10 10 10 4 0 7-2 9-5l8 5c-3 5-9 8-17 8-11 0-19-7-19-18s8-18 19-18c8 0 14 3 17 8l-8 5c-2-3-5-5-9-5-6 0-10 4-10 10zm83-29v46h-9V5h9zM37 0l37 64H0L37 0zm92 5-27 48L74 5h10l18 30 17-30h10zm59 12v10l-3-1c-6 0-10 4-10 10v15h-9V17h9v9c0-5 6-9 13-9z"/></svg>
@@ -0,0 +1,81 @@
1
+ /** @type {import('tailwindcss').Config} */
2
+ module.exports = {
3
+ darkMode: ['class'],
4
+ content: [
5
+ './pages/**/*.{ts,tsx}',
6
+ './components/**/*.{ts,tsx}',
7
+ './app/**/*.{ts,tsx}',
8
+ './src/**/*.{ts,tsx}'
9
+ ],
10
+ prefix: '',
11
+ theme: {
12
+ container: {
13
+ center: true,
14
+ padding: '2rem',
15
+ screens: {
16
+ '2xl': '1400px'
17
+ }
18
+ },
19
+ extend: {
20
+ fontFamily: {
21
+ sans: ['var(--font-geist-sans)'],
22
+ mono: ['var(--font-geist-mono)']
23
+ },
24
+ colors: {
25
+ border: 'hsl(var(--border))',
26
+ input: 'hsl(var(--input))',
27
+ ring: 'hsl(var(--ring))',
28
+ background: 'hsl(var(--background))',
29
+ foreground: 'hsl(var(--foreground))',
30
+ primary: {
31
+ DEFAULT: 'hsl(var(--primary))',
32
+ foreground: 'hsl(var(--primary-foreground))'
33
+ },
34
+ secondary: {
35
+ DEFAULT: 'hsl(var(--secondary))',
36
+ foreground: 'hsl(var(--secondary-foreground))'
37
+ },
38
+ destructive: {
39
+ DEFAULT: 'hsl(var(--destructive))',
40
+ foreground: 'hsl(var(--destructive-foreground))'
41
+ },
42
+ muted: {
43
+ DEFAULT: 'hsl(var(--muted))',
44
+ foreground: 'hsl(var(--muted-foreground))'
45
+ },
46
+ accent: {
47
+ DEFAULT: 'hsl(var(--accent))',
48
+ foreground: 'hsl(var(--accent-foreground))'
49
+ },
50
+ popover: {
51
+ DEFAULT: 'hsl(var(--popover))',
52
+ foreground: 'hsl(var(--popover-foreground))'
53
+ },
54
+ card: {
55
+ DEFAULT: 'hsl(var(--card))',
56
+ foreground: 'hsl(var(--card-foreground))'
57
+ }
58
+ },
59
+ borderRadius: {
60
+ lg: 'var(--radius)',
61
+ md: 'calc(var(--radius) - 2px)',
62
+ sm: 'calc(var(--radius) - 4px)'
63
+ },
64
+ keyframes: {
65
+ 'accordion-down': {
66
+ from: { height: '0' },
67
+ to: { height: 'var(--radix-accordion-content-height)' }
68
+ },
69
+ 'accordion-up': {
70
+ from: { height: 'var(--radix-accordion-content-height)' },
71
+ to: { height: '0' }
72
+ }
73
+ },
74
+ animation: {
75
+ 'accordion-down': 'accordion-down 0.2s ease-out',
76
+ 'accordion-up': 'accordion-up 0.2s ease-out'
77
+ }
78
+ }
79
+ },
80
+ plugins: [require('tailwindcss-animate'), require('@tailwindcss/typography')]
81
+ }
@@ -0,0 +1,35 @@
1
+ {
2
+ "compilerOptions": {
3
+ "lib": ["dom", "dom.iterable", "esnext"],
4
+ "allowJs": true,
5
+ "skipLibCheck": true,
6
+ "strict": true,
7
+ "forceConsistentCasingInFileNames": true,
8
+ "noEmit": true,
9
+ "incremental": true,
10
+ "esModuleInterop": true,
11
+ "module": "esnext",
12
+ "moduleResolution": "node",
13
+ "resolveJsonModule": true,
14
+ "isolatedModules": true,
15
+ "jsx": "preserve",
16
+ "baseUrl": ".",
17
+ "paths": {
18
+ "@/*": ["./*"]
19
+ },
20
+ "plugins": [
21
+ {
22
+ "name": "next"
23
+ }
24
+ ],
25
+ "strictNullChecks": true
26
+ },
27
+ "include": [
28
+ "next-env.d.ts",
29
+ "next-auth.d.ts",
30
+ "**/*.ts",
31
+ "**/*.tsx",
32
+ ".next/types/**/*.ts"
33
+ ],
34
+ "exclude": ["node_modules"]
35
+ }
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "langwatch",
3
+ "version": "0.0.1",
4
+ "description": "",
5
+ "main": "dist/src/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/langwatch/langwatch.git",
11
+ "directory": "typescript-sdk"
12
+ },
13
+ "scripts": {
14
+ "postinstall": "cp ../langwatch/src/server/tracer/types.ts src/server/types/tracer.ts && ts-to-zod src/server/types/tracer.ts src/server/types/tracer.generated.ts",
15
+ "test": "vitest",
16
+ "build": "tsup",
17
+ "prepublish": "npm run build"
18
+ },
19
+ "author": "",
20
+ "license": "Apache-2.0",
21
+ "engines": {
22
+ "node": ">=18.0.0"
23
+ },
24
+ "devDependencies": {
25
+ "@ai-sdk/openai": "^0.0.18",
26
+ "@eslint/js": "^9.4.0",
27
+ "@types/debug": "^4.1.12",
28
+ "@types/eslint__js": "^8.42.3",
29
+ "@types/node": "^16.0.0",
30
+ "eslint": "^8.57.0",
31
+ "ts-to-zod": "^3.4.1",
32
+ "tsup": "^8.1.0",
33
+ "typescript": "^4.9.5",
34
+ "typescript-eslint": "^7.11.0",
35
+ "vitest": "^0.5.0"
36
+ },
37
+ "dependencies": {
38
+ "ai": "^3.1.23",
39
+ "llm-cost": "^1.0.4",
40
+ "nanoid": "^5.0.7",
41
+ "openai": "^4.47.3",
42
+ "zod": "^3.22.4",
43
+ "zod-validation-error": "^3.3.0"
44
+ }
45
+ }
package/src/helpers.ts ADDED
@@ -0,0 +1,64 @@
1
+ export type Strict<T> = T & { [K in Exclude<keyof any, keyof T>]: never };
2
+
3
+ type SnakeToCamelCase<S extends string> = S extends `${infer T}_${infer U}`
4
+ ? `${T}${Capitalize<SnakeToCamelCase<U>>}`
5
+ : S;
6
+
7
+ export type SnakeToCamelCaseNested<T> = T extends object
8
+ ? T extends (infer U)[]
9
+ ? U extends object
10
+ ? {
11
+ [K in keyof U as SnakeToCamelCase<
12
+ K & string
13
+ >]: SnakeToCamelCaseNested<U[K]>;
14
+ }[]
15
+ : T
16
+ : {
17
+ [K in keyof T as SnakeToCamelCase<K & string>]: SnakeToCamelCaseNested<
18
+ T[K]
19
+ >;
20
+ }
21
+ : T;
22
+
23
+ type CamelToSnakeCase<S extends string> = S extends `${infer T}${infer U}`
24
+ ? `${T extends Capitalize<T> ? "_" : ""}${Lowercase<T>}${CamelToSnakeCase<U>}`
25
+ : S;
26
+
27
+ export type CamelToSnakeCaseNested<T> = T extends object
28
+ ? T extends (infer U)[]
29
+ ? U extends object
30
+ ? {
31
+ [K in keyof U as CamelToSnakeCase<
32
+ K & string
33
+ >]: CamelToSnakeCaseNested<U[K]>;
34
+ }[]
35
+ : T
36
+ : {
37
+ [K in keyof T as CamelToSnakeCase<K & string>]: CamelToSnakeCaseNested<
38
+ T[K]
39
+ >;
40
+ }
41
+ : T;
42
+
43
+ function camelToSnakeCase(str: string): string {
44
+ return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
45
+ }
46
+
47
+ export function camelToSnakeCaseNested<T>(obj: T): CamelToSnakeCaseNested<T> {
48
+ if (Array.isArray(obj)) {
49
+ return obj.map((item) =>
50
+ camelToSnakeCaseNested(item)
51
+ ) as CamelToSnakeCaseNested<T>;
52
+ } else if (typeof obj === "object" && obj !== null) {
53
+ const newObj: any = {};
54
+ for (const key in obj) {
55
+ if (obj.hasOwnProperty(key)) {
56
+ const newKey = camelToSnakeCase(key);
57
+ newObj[newKey] = camelToSnakeCaseNested((obj as any)[key]);
58
+ }
59
+ }
60
+ return newObj as CamelToSnakeCaseNested<T>;
61
+ } else {
62
+ return obj as CamelToSnakeCaseNested<T>;
63
+ }
64
+ }
@@ -0,0 +1,255 @@
1
+ import { OpenAI } from "openai";
2
+ import { LangWatch, convertFromVercelAIMessages } from "./index";
3
+ import {
4
+ describe,
5
+ it,
6
+ expect,
7
+ vi,
8
+ beforeEach,
9
+ type SpyInstanceFn,
10
+ } from "vitest";
11
+ import { openai } from "@ai-sdk/openai";
12
+ import { generateText, type CoreMessage } from "ai";
13
+
14
+ describe("LangWatch tracer", () => {
15
+ let mockFetch: SpyInstanceFn;
16
+
17
+ beforeEach(() => {
18
+ const originalFetch = global.fetch;
19
+ // Mocking fetch to test the right data was sent to the server
20
+ mockFetch = vi.fn((url, ...args) => {
21
+ if (url.includes("localhost.test")) {
22
+ return Promise.resolve({
23
+ ok: true,
24
+ status: 200,
25
+ json: () => Promise.resolve({ message: "Trace sent" }),
26
+ });
27
+ }
28
+ return originalFetch(url, ...args);
29
+ });
30
+ // @ts-ignore
31
+ global.fetch = mockFetch;
32
+ });
33
+
34
+ it("captures traces correctly", async () => {
35
+ const langwatch = new LangWatch({
36
+ apiKey: "test",
37
+ endpoint: "http://localhost.test",
38
+ });
39
+ const trace = langwatch.getTrace();
40
+ trace.update({
41
+ metadata: { threadId: "123", userId: "123", labels: ["foo"] },
42
+ });
43
+ trace.update({ metadata: { userId: "456", labels: ["bar"] } });
44
+
45
+ const span = trace.startSpan({
46
+ name: "weather_function",
47
+ input: {
48
+ type: "json",
49
+ value: {
50
+ city: "Tokyo",
51
+ },
52
+ },
53
+ });
54
+ span.end({
55
+ outputs: [
56
+ {
57
+ type: "json",
58
+ value: {
59
+ weather: "sunny",
60
+ },
61
+ },
62
+ ],
63
+ });
64
+
65
+ expect(trace.metadata).toEqual({
66
+ threadId: "123",
67
+ userId: "456",
68
+ labels: ["foo", "bar"],
69
+ });
70
+ expect(span.timestamps.startedAt).toBeDefined();
71
+ expect(span.timestamps.finishedAt).toBeDefined();
72
+
73
+ const ragSpan = trace.startRAGSpan({
74
+ name: "my-vectordb-retrieval",
75
+ input: { type: "text", value: "search query" },
76
+ });
77
+ ragSpan.end({
78
+ contexts: [
79
+ {
80
+ documentId: "doc1",
81
+ content: "document chunk 1",
82
+ },
83
+ {
84
+ documentId: "doc2",
85
+ content: "document chunk 2",
86
+ },
87
+ ],
88
+ });
89
+
90
+ const llmSpan = ragSpan.startLLMSpan({
91
+ name: "llm",
92
+ model: "gpt-3.5-turbo",
93
+ input: {
94
+ type: "chat_messages",
95
+ value: [
96
+ { role: "system", content: "You are a helpful assistant." },
97
+ { role: "user", content: "What is the weather in Tokyo?" },
98
+ ],
99
+ },
100
+ });
101
+ llmSpan.end({
102
+ outputs: [
103
+ {
104
+ type: "chat_messages",
105
+ value: [
106
+ {
107
+ role: "assistant",
108
+ content: "It's cloudy in Tokyo.",
109
+ },
110
+ ],
111
+ },
112
+ ],
113
+ });
114
+
115
+ ragSpan.end();
116
+
117
+ await trace.sendSpans();
118
+
119
+ expect(mockFetch).toHaveBeenCalled();
120
+ expect(mockFetch).toHaveBeenCalledWith(
121
+ "http://localhost.test/api/collector",
122
+ expect.objectContaining({
123
+ method: "POST",
124
+ headers: {
125
+ "X-Auth-Token": "test",
126
+ "Content-Type": "application/json",
127
+ },
128
+ body: expect.any(String),
129
+ })
130
+ );
131
+
132
+ const firstCall: any = mockFetch.mock.calls[0];
133
+ const requestBody = JSON.parse(firstCall[1].body);
134
+ expect(requestBody.trace_id).toBeDefined();
135
+ expect(requestBody.metadata).toEqual({
136
+ thread_id: "123",
137
+ user_id: "456",
138
+ labels: ["foo", "bar"],
139
+ });
140
+ expect(requestBody.spans.length).toBe(3);
141
+ });
142
+
143
+ it.skip("captures openai llm call", async () => {
144
+ const langwatch = new LangWatch({
145
+ apiKey: "test",
146
+ endpoint: "http://localhost.test",
147
+ });
148
+ const trace = langwatch.getTrace();
149
+
150
+ // Model to be used and messages that will be sent to the LLM
151
+ const model = "gpt-4o";
152
+ const messages: OpenAI.Chat.ChatCompletionMessageParam[] = [
153
+ { role: "system", content: "You are a helpful assistant." },
154
+ {
155
+ role: "user",
156
+ content: "Write a tweet-size vegetarian lasagna recipe for 4 people.",
157
+ },
158
+ ];
159
+
160
+ // Capture the llm call with a span
161
+ const span = trace.startLLMSpan({
162
+ name: "llm",
163
+ model: model,
164
+ input: {
165
+ type: "chat_messages",
166
+ value: messages,
167
+ },
168
+ });
169
+
170
+ // Continue with the LLM call normally
171
+ const openai = new OpenAI();
172
+ const chatCompletion = await openai.chat.completions.create({
173
+ messages: messages,
174
+ model: model,
175
+ });
176
+
177
+ span.end({
178
+ outputs: chatCompletion.choices.map((choice) => ({
179
+ type: "chat_messages",
180
+ value: [choice.message],
181
+ })),
182
+ });
183
+
184
+ await trace.sendSpans();
185
+
186
+ expect(mockFetch).toHaveBeenCalled();
187
+ expect(mockFetch).toHaveBeenCalledWith(
188
+ "http://localhost.test/api/collector",
189
+ expect.objectContaining({
190
+ method: "POST",
191
+ headers: {
192
+ "X-Auth-Token": "test",
193
+ "Content-Type": "application/json",
194
+ },
195
+ body: expect.any(String),
196
+ })
197
+ );
198
+ });
199
+
200
+ it.skip("captures vercel ai sdk call", async () => {
201
+ const langwatch = new LangWatch({
202
+ apiKey: "test",
203
+ endpoint: "http://localhost.test",
204
+ });
205
+ const trace = langwatch.getTrace();
206
+
207
+ // Model to be used and messages that will be sent to the LLM
208
+ const model = openai("gpt-4o");
209
+ const messages: CoreMessage[] = [
210
+ { role: "system", content: "You are a helpful assistant." },
211
+ {
212
+ role: "user",
213
+ content: "Write a tweet-size vegetarian lasagna recipe for 4 people.",
214
+ },
215
+ ];
216
+
217
+ const span = trace.startLLMSpan({
218
+ name: "llm",
219
+ model: model.modelId,
220
+ input: {
221
+ type: "chat_messages",
222
+ value: convertFromVercelAIMessages(messages),
223
+ },
224
+ });
225
+
226
+ const response = await generateText({
227
+ model: model,
228
+ messages: messages,
229
+ });
230
+
231
+ span.end({
232
+ outputs: [
233
+ {
234
+ type: "chat_messages",
235
+ value: convertFromVercelAIMessages(response.responseMessages),
236
+ },
237
+ ],
238
+ });
239
+
240
+ await trace.sendSpans();
241
+
242
+ expect(mockFetch).toHaveBeenCalled();
243
+ expect(mockFetch).toHaveBeenCalledWith(
244
+ "http://localhost.test/api/collector",
245
+ expect.objectContaining({
246
+ method: "POST",
247
+ headers: {
248
+ "X-Auth-Token": "test",
249
+ "Content-Type": "application/json",
250
+ },
251
+ body: expect.any(String),
252
+ })
253
+ );
254
+ });
255
+ });