├── .gitignore ├── .vscode └── settings.json ├── README.md ├── examples ├── package-lock.json ├── package.json ├── src │ ├── openai-responses.ts │ ├── openai.ts │ ├── rag.ts │ └── vercel-ai-sdk.ts └── tsconfig.json ├── package-lock.json ├── package.json ├── src ├── client.ts ├── constants.ts ├── express.ts ├── index.ts ├── instruments │ ├── openai.ts │ ├── pinecone.ts │ └── vercel-ai.ts ├── trace.ts ├── types │ ├── index.ts │ ├── openai.ts │ ├── pinecone.ts │ └── vercel-ai.ts └── utils │ ├── index.ts │ └── logger.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | .pnp 6 | .pnp.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | /dist 23 | 24 | # misc 25 | .DS_Store 26 | *.pem 27 | 28 | # debug 29 | npm-debug.log* 30 | yarn-debug.log* 31 | yarn-error.log* 32 | .pnpm-debug.log* 33 | 34 | # env files (can opt-in for committing if needed) 35 | .env* 36 | 37 | # vercel 38 | .vercel 39 | 40 | # typescript 41 | *.tsbuildinfo 42 | next-env.d.ts 43 | 44 | *.local 45 | 46 | # npm 47 | /package 48 | *.tgz -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[typescript][typescriptreact][javascript][javascriptreact]": { 3 | "editor.defaultFormatter": "esbenp.prettier-vscode", 4 | "editor.formatOnSave": true, 5 | "editor.codeActionsOnSave": { 6 | "source.organizeImports": "always" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Obsy 2 | 3 | Obsy is an open-source observability platform for AI. _See what your AI is really doing with just a few lines of code._ 4 | 5 | ## Features 6 | 7 | - 🔍 **Comprehensive Tracing**: Track every AI operation in your application with detailed timing and metadata 8 | - 🤖 **Multi-Provider Support**: Works seamlessly with OpenAI, Vercel AI SDK, Pinecone, and more 9 | - 📊 **Continuous Monitoring**: Track performance metrics, costs, and usage patterns throughout your application lifecycle 10 | - 🚀 **Easy Integration**: Get started with just 3 lines of code 11 | - ⚡ **Express.js Middleware**: Drop-in middleware for automatic request tracing in Express applications 12 | - 🔄 **Stream-Aware**: Monitor streaming responses with chunk-by-chunk visibility 13 | - 🧠 **RAG Insights**: Track and analyze your entire retrieval-augmented generation pipeline 14 | - 📝 **Detailed Logging**: Capture full context of requests, responses, and metadata for every AI interaction 15 | 16 | ## Installation 17 | 18 | ```bash 19 | npm install @obsy-ai/sdk 20 | ``` 21 | 22 | ## Quick Start 23 | 24 | ### Basic OpenAI Integration 25 | 26 | ```ts 27 | import { ObsyClient, obsyExpress } from "@obsy-ai/sdk"; 28 | import OpenAI from "openai"; 29 | 30 | // 1. Create Obsy client 31 | const obsy = new ObsyClient({ 32 | apiKey: "", 33 | projectId: "", 34 | }); 35 | 36 | // 2. Instrument OpenAI client 37 | obsy.instrument(openai); 38 | 39 | // 3. Enable tracing for each request 40 | app.use(obsyExpress({ client: obsy })); 41 | ``` 42 | 43 | ### Vercel AI SDK Integration 44 | 45 | ```ts 46 | import { ObsyClient, obsyExpress } from "@obsy-ai/sdk"; 47 | import { generateText, streamText } from "ai"; 48 | 49 | // 1. Initialize Obsy client 50 | const obsy = new ObsyClient({ 51 | apiKey: "", 52 | projectId: "", 53 | }); 54 | 55 | // 2. Instrument Vercel AI SDK functions 56 | const { generateText, streamText } = obsy.instrumentVercelAI({ 57 | generateText, 58 | streamText, 59 | }); 60 | 61 | // 3. Enable tracing 62 | app.use(obsyExpress({ client: obsy })); 63 | ``` 64 | 65 | ### RAG Pipeline Monitoring 66 | 67 | ```ts 68 | import { ObsyClient, obsyExpress } from "@obsy-ai/sdk"; 69 | import { Pinecone } from "@pinecone-database/pinecone"; 70 | import OpenAI from "openai"; 71 | 72 | // 1. Initialize Obsy client 73 | const obsy = new ObsyClient({ 74 | apiKey: "", 75 | projectId: "", 76 | }); 77 | 78 | // 2. Instrument multiple clients 79 | obsy.instrument(openai).instrument(pinecone); 80 | 81 | // 3. Enable tracing 82 | app.use(obsyExpress({ client: obsy })); 83 | ``` 84 | 85 | ## Examples 86 | 87 | Check out our comprehensive examples: 88 | 89 | - [OpenAI Integration](examples/src/openai.ts) 90 | - [Vercel AI SDK Integration](examples/src/vercel-ai-sdk.ts) 91 | - [RAG Implementation](examples/src/rag.ts) 92 | 93 | ## License 94 | 95 | MIT 96 | -------------------------------------------------------------------------------- /examples/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "obsy-examples", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "obsy-examples", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@ai-sdk/openai": "^1.3.1", 13 | "@obsy-ai/sdk": "file:../obsy-ai-sdk-0.0.3.tgz", 14 | "ai": "^4.2.2", 15 | "express": "^4.21.2", 16 | "openai": "^4.89.0", 17 | "zod": "^3.24.2" 18 | }, 19 | "devDependencies": { 20 | "@types/express": "^5.0.1", 21 | "@types/node": "^22.13.11", 22 | "dotenv": "^16.4.7", 23 | "tsx": "^4.19.3", 24 | "typescript": "^5.8.2" 25 | } 26 | }, 27 | "node_modules/@ai-sdk/openai": { 28 | "version": "1.3.1", 29 | "resolved": "https://registry.npmjs.org/@ai-sdk/openai/-/openai-1.3.1.tgz", 30 | "integrity": "sha512-Fhb9IYEJdS9vIFwOxr9igiWF8x0xYSqAB7eYuHtZ9D6ovo4RB2JO7lSg0qQWGIGYxtwoipXwLjgF/ov25W23Hw==", 31 | "license": "Apache-2.0", 32 | "dependencies": { 33 | "@ai-sdk/provider": "1.1.0", 34 | "@ai-sdk/provider-utils": "2.2.1" 35 | }, 36 | "engines": { 37 | "node": ">=18" 38 | }, 39 | "peerDependencies": { 40 | "zod": "^3.0.0" 41 | } 42 | }, 43 | "node_modules/@ai-sdk/provider": { 44 | "version": "1.1.0", 45 | "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-1.1.0.tgz", 46 | "integrity": "sha512-0M+qjp+clUD0R1E5eWQFhxEvWLNaOtGQRUaBn8CUABnSKredagq92hUS9VjOzGsTm37xLfpaxl97AVtbeOsHew==", 47 | "license": "Apache-2.0", 48 | "dependencies": { 49 | "json-schema": "^0.4.0" 50 | }, 51 | "engines": { 52 | "node": ">=18" 53 | } 54 | }, 55 | "node_modules/@ai-sdk/provider-utils": { 56 | "version": "2.2.1", 57 | "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-2.2.1.tgz", 58 | "integrity": "sha512-BuExLp+NcpwsAVj1F4bgJuQkSqO/+roV9wM7RdIO+NVrcT8RBUTdXzf5arHt5T58VpK7bZyB2V9qigjaPHE+Dg==", 59 | "license": "Apache-2.0", 60 | "dependencies": { 61 | "@ai-sdk/provider": "1.1.0", 62 | "nanoid": "^3.3.8", 63 | "secure-json-parse": "^2.7.0" 64 | }, 65 | "engines": { 66 | "node": ">=18" 67 | }, 68 | "peerDependencies": { 69 | "zod": "^3.23.8" 70 | } 71 | }, 72 | "node_modules/@ai-sdk/react": { 73 | "version": "1.2.1", 74 | "resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-1.2.1.tgz", 75 | "integrity": "sha512-ZC1GlQ/NRMMAl66nbcqiKmn2DrJb7faDQxvdE8W0aUZYNsGCwFLiNcbGkV7i6Y3VM0nFq9p8joOqAs1cv7zt9g==", 76 | "license": "Apache-2.0", 77 | "dependencies": { 78 | "@ai-sdk/provider-utils": "2.2.1", 79 | "@ai-sdk/ui-utils": "1.2.1", 80 | "swr": "^2.2.5", 81 | "throttleit": "2.1.0" 82 | }, 83 | "engines": { 84 | "node": ">=18" 85 | }, 86 | "peerDependencies": { 87 | "react": "^18 || ^19 || ^19.0.0-rc", 88 | "zod": "^3.23.8" 89 | }, 90 | "peerDependenciesMeta": { 91 | "zod": { 92 | "optional": true 93 | } 94 | } 95 | }, 96 | "node_modules/@ai-sdk/ui-utils": { 97 | "version": "1.2.1", 98 | "resolved": "https://registry.npmjs.org/@ai-sdk/ui-utils/-/ui-utils-1.2.1.tgz", 99 | "integrity": "sha512-BzvMbYm7LHBlbWuLlcG1jQh4eu14MGpz7L+wrGO1+F4oQ+O0fAjgUSNwPWGlZpKmg4NrcVq/QLmxiVJrx2R4Ew==", 100 | "license": "Apache-2.0", 101 | "dependencies": { 102 | "@ai-sdk/provider": "1.1.0", 103 | "@ai-sdk/provider-utils": "2.2.1", 104 | "zod-to-json-schema": "^3.24.1" 105 | }, 106 | "engines": { 107 | "node": ">=18" 108 | }, 109 | "peerDependencies": { 110 | "zod": "^3.23.8" 111 | } 112 | }, 113 | "node_modules/@esbuild/aix-ppc64": { 114 | "version": "0.25.1", 115 | "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.1.tgz", 116 | "integrity": "sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==", 117 | "cpu": [ 118 | "ppc64" 119 | ], 120 | "dev": true, 121 | "license": "MIT", 122 | "optional": true, 123 | "os": [ 124 | "aix" 125 | ], 126 | "engines": { 127 | "node": ">=18" 128 | } 129 | }, 130 | "node_modules/@esbuild/android-arm": { 131 | "version": "0.25.1", 132 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.1.tgz", 133 | "integrity": "sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==", 134 | "cpu": [ 135 | "arm" 136 | ], 137 | "dev": true, 138 | "license": "MIT", 139 | "optional": true, 140 | "os": [ 141 | "android" 142 | ], 143 | "engines": { 144 | "node": ">=18" 145 | } 146 | }, 147 | "node_modules/@esbuild/android-arm64": { 148 | "version": "0.25.1", 149 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.1.tgz", 150 | "integrity": "sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==", 151 | "cpu": [ 152 | "arm64" 153 | ], 154 | "dev": true, 155 | "license": "MIT", 156 | "optional": true, 157 | "os": [ 158 | "android" 159 | ], 160 | "engines": { 161 | "node": ">=18" 162 | } 163 | }, 164 | "node_modules/@esbuild/android-x64": { 165 | "version": "0.25.1", 166 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.1.tgz", 167 | "integrity": "sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==", 168 | "cpu": [ 169 | "x64" 170 | ], 171 | "dev": true, 172 | "license": "MIT", 173 | "optional": true, 174 | "os": [ 175 | "android" 176 | ], 177 | "engines": { 178 | "node": ">=18" 179 | } 180 | }, 181 | "node_modules/@esbuild/darwin-arm64": { 182 | "version": "0.25.1", 183 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.1.tgz", 184 | "integrity": "sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==", 185 | "cpu": [ 186 | "arm64" 187 | ], 188 | "dev": true, 189 | "license": "MIT", 190 | "optional": true, 191 | "os": [ 192 | "darwin" 193 | ], 194 | "engines": { 195 | "node": ">=18" 196 | } 197 | }, 198 | "node_modules/@esbuild/darwin-x64": { 199 | "version": "0.25.1", 200 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.1.tgz", 201 | "integrity": "sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==", 202 | "cpu": [ 203 | "x64" 204 | ], 205 | "dev": true, 206 | "license": "MIT", 207 | "optional": true, 208 | "os": [ 209 | "darwin" 210 | ], 211 | "engines": { 212 | "node": ">=18" 213 | } 214 | }, 215 | "node_modules/@esbuild/freebsd-arm64": { 216 | "version": "0.25.1", 217 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.1.tgz", 218 | "integrity": "sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==", 219 | "cpu": [ 220 | "arm64" 221 | ], 222 | "dev": true, 223 | "license": "MIT", 224 | "optional": true, 225 | "os": [ 226 | "freebsd" 227 | ], 228 | "engines": { 229 | "node": ">=18" 230 | } 231 | }, 232 | "node_modules/@esbuild/freebsd-x64": { 233 | "version": "0.25.1", 234 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.1.tgz", 235 | "integrity": "sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==", 236 | "cpu": [ 237 | "x64" 238 | ], 239 | "dev": true, 240 | "license": "MIT", 241 | "optional": true, 242 | "os": [ 243 | "freebsd" 244 | ], 245 | "engines": { 246 | "node": ">=18" 247 | } 248 | }, 249 | "node_modules/@esbuild/linux-arm": { 250 | "version": "0.25.1", 251 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.1.tgz", 252 | "integrity": "sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==", 253 | "cpu": [ 254 | "arm" 255 | ], 256 | "dev": true, 257 | "license": "MIT", 258 | "optional": true, 259 | "os": [ 260 | "linux" 261 | ], 262 | "engines": { 263 | "node": ">=18" 264 | } 265 | }, 266 | "node_modules/@esbuild/linux-arm64": { 267 | "version": "0.25.1", 268 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.1.tgz", 269 | "integrity": "sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==", 270 | "cpu": [ 271 | "arm64" 272 | ], 273 | "dev": true, 274 | "license": "MIT", 275 | "optional": true, 276 | "os": [ 277 | "linux" 278 | ], 279 | "engines": { 280 | "node": ">=18" 281 | } 282 | }, 283 | "node_modules/@esbuild/linux-ia32": { 284 | "version": "0.25.1", 285 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.1.tgz", 286 | "integrity": "sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==", 287 | "cpu": [ 288 | "ia32" 289 | ], 290 | "dev": true, 291 | "license": "MIT", 292 | "optional": true, 293 | "os": [ 294 | "linux" 295 | ], 296 | "engines": { 297 | "node": ">=18" 298 | } 299 | }, 300 | "node_modules/@esbuild/linux-loong64": { 301 | "version": "0.25.1", 302 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.1.tgz", 303 | "integrity": "sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==", 304 | "cpu": [ 305 | "loong64" 306 | ], 307 | "dev": true, 308 | "license": "MIT", 309 | "optional": true, 310 | "os": [ 311 | "linux" 312 | ], 313 | "engines": { 314 | "node": ">=18" 315 | } 316 | }, 317 | "node_modules/@esbuild/linux-mips64el": { 318 | "version": "0.25.1", 319 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.1.tgz", 320 | "integrity": "sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==", 321 | "cpu": [ 322 | "mips64el" 323 | ], 324 | "dev": true, 325 | "license": "MIT", 326 | "optional": true, 327 | "os": [ 328 | "linux" 329 | ], 330 | "engines": { 331 | "node": ">=18" 332 | } 333 | }, 334 | "node_modules/@esbuild/linux-ppc64": { 335 | "version": "0.25.1", 336 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.1.tgz", 337 | "integrity": "sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==", 338 | "cpu": [ 339 | "ppc64" 340 | ], 341 | "dev": true, 342 | "license": "MIT", 343 | "optional": true, 344 | "os": [ 345 | "linux" 346 | ], 347 | "engines": { 348 | "node": ">=18" 349 | } 350 | }, 351 | "node_modules/@esbuild/linux-riscv64": { 352 | "version": "0.25.1", 353 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.1.tgz", 354 | "integrity": "sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==", 355 | "cpu": [ 356 | "riscv64" 357 | ], 358 | "dev": true, 359 | "license": "MIT", 360 | "optional": true, 361 | "os": [ 362 | "linux" 363 | ], 364 | "engines": { 365 | "node": ">=18" 366 | } 367 | }, 368 | "node_modules/@esbuild/linux-s390x": { 369 | "version": "0.25.1", 370 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.1.tgz", 371 | "integrity": "sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==", 372 | "cpu": [ 373 | "s390x" 374 | ], 375 | "dev": true, 376 | "license": "MIT", 377 | "optional": true, 378 | "os": [ 379 | "linux" 380 | ], 381 | "engines": { 382 | "node": ">=18" 383 | } 384 | }, 385 | "node_modules/@esbuild/linux-x64": { 386 | "version": "0.25.1", 387 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.1.tgz", 388 | "integrity": "sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA==", 389 | "cpu": [ 390 | "x64" 391 | ], 392 | "dev": true, 393 | "license": "MIT", 394 | "optional": true, 395 | "os": [ 396 | "linux" 397 | ], 398 | "engines": { 399 | "node": ">=18" 400 | } 401 | }, 402 | "node_modules/@esbuild/netbsd-arm64": { 403 | "version": "0.25.1", 404 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.1.tgz", 405 | "integrity": "sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g==", 406 | "cpu": [ 407 | "arm64" 408 | ], 409 | "dev": true, 410 | "license": "MIT", 411 | "optional": true, 412 | "os": [ 413 | "netbsd" 414 | ], 415 | "engines": { 416 | "node": ">=18" 417 | } 418 | }, 419 | "node_modules/@esbuild/netbsd-x64": { 420 | "version": "0.25.1", 421 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.1.tgz", 422 | "integrity": "sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==", 423 | "cpu": [ 424 | "x64" 425 | ], 426 | "dev": true, 427 | "license": "MIT", 428 | "optional": true, 429 | "os": [ 430 | "netbsd" 431 | ], 432 | "engines": { 433 | "node": ">=18" 434 | } 435 | }, 436 | "node_modules/@esbuild/openbsd-arm64": { 437 | "version": "0.25.1", 438 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.1.tgz", 439 | "integrity": "sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg==", 440 | "cpu": [ 441 | "arm64" 442 | ], 443 | "dev": true, 444 | "license": "MIT", 445 | "optional": true, 446 | "os": [ 447 | "openbsd" 448 | ], 449 | "engines": { 450 | "node": ">=18" 451 | } 452 | }, 453 | "node_modules/@esbuild/openbsd-x64": { 454 | "version": "0.25.1", 455 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.1.tgz", 456 | "integrity": "sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==", 457 | "cpu": [ 458 | "x64" 459 | ], 460 | "dev": true, 461 | "license": "MIT", 462 | "optional": true, 463 | "os": [ 464 | "openbsd" 465 | ], 466 | "engines": { 467 | "node": ">=18" 468 | } 469 | }, 470 | "node_modules/@esbuild/sunos-x64": { 471 | "version": "0.25.1", 472 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.1.tgz", 473 | "integrity": "sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==", 474 | "cpu": [ 475 | "x64" 476 | ], 477 | "dev": true, 478 | "license": "MIT", 479 | "optional": true, 480 | "os": [ 481 | "sunos" 482 | ], 483 | "engines": { 484 | "node": ">=18" 485 | } 486 | }, 487 | "node_modules/@esbuild/win32-arm64": { 488 | "version": "0.25.1", 489 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.1.tgz", 490 | "integrity": "sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==", 491 | "cpu": [ 492 | "arm64" 493 | ], 494 | "dev": true, 495 | "license": "MIT", 496 | "optional": true, 497 | "os": [ 498 | "win32" 499 | ], 500 | "engines": { 501 | "node": ">=18" 502 | } 503 | }, 504 | "node_modules/@esbuild/win32-ia32": { 505 | "version": "0.25.1", 506 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.1.tgz", 507 | "integrity": "sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==", 508 | "cpu": [ 509 | "ia32" 510 | ], 511 | "dev": true, 512 | "license": "MIT", 513 | "optional": true, 514 | "os": [ 515 | "win32" 516 | ], 517 | "engines": { 518 | "node": ">=18" 519 | } 520 | }, 521 | "node_modules/@esbuild/win32-x64": { 522 | "version": "0.25.1", 523 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.1.tgz", 524 | "integrity": "sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==", 525 | "cpu": [ 526 | "x64" 527 | ], 528 | "dev": true, 529 | "license": "MIT", 530 | "optional": true, 531 | "os": [ 532 | "win32" 533 | ], 534 | "engines": { 535 | "node": ">=18" 536 | } 537 | }, 538 | "node_modules/@obsy-ai/sdk": { 539 | "version": "0.0.3", 540 | "resolved": "file:../obsy-ai-sdk-0.0.3.tgz", 541 | "integrity": "sha512-aCdv7i/xKl2hKMBgxmFuS6NVsLGCz39oKmJuzG+vD7vjl1boVoUtt8tyzy2sNVtsSAWzL3RCpP89AGnDKiYjHQ==", 542 | "license": "ISC" 543 | }, 544 | "node_modules/@opentelemetry/api": { 545 | "version": "1.9.0", 546 | "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", 547 | "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", 548 | "license": "Apache-2.0", 549 | "engines": { 550 | "node": ">=8.0.0" 551 | } 552 | }, 553 | "node_modules/@types/body-parser": { 554 | "version": "1.19.5", 555 | "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", 556 | "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", 557 | "dev": true, 558 | "license": "MIT", 559 | "dependencies": { 560 | "@types/connect": "*", 561 | "@types/node": "*" 562 | } 563 | }, 564 | "node_modules/@types/connect": { 565 | "version": "3.4.38", 566 | "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", 567 | "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", 568 | "dev": true, 569 | "license": "MIT", 570 | "dependencies": { 571 | "@types/node": "*" 572 | } 573 | }, 574 | "node_modules/@types/diff-match-patch": { 575 | "version": "1.0.36", 576 | "resolved": "https://registry.npmjs.org/@types/diff-match-patch/-/diff-match-patch-1.0.36.tgz", 577 | "integrity": "sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==", 578 | "license": "MIT" 579 | }, 580 | "node_modules/@types/express": { 581 | "version": "5.0.1", 582 | "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.1.tgz", 583 | "integrity": "sha512-UZUw8vjpWFXuDnjFTh7/5c2TWDlQqeXHi6hcN7F2XSVT5P+WmUnnbFS3KA6Jnc6IsEqI2qCVu2bK0R0J4A8ZQQ==", 584 | "dev": true, 585 | "license": "MIT", 586 | "dependencies": { 587 | "@types/body-parser": "*", 588 | "@types/express-serve-static-core": "^5.0.0", 589 | "@types/serve-static": "*" 590 | } 591 | }, 592 | "node_modules/@types/express-serve-static-core": { 593 | "version": "5.0.6", 594 | "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.6.tgz", 595 | "integrity": "sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==", 596 | "dev": true, 597 | "license": "MIT", 598 | "dependencies": { 599 | "@types/node": "*", 600 | "@types/qs": "*", 601 | "@types/range-parser": "*", 602 | "@types/send": "*" 603 | } 604 | }, 605 | "node_modules/@types/http-errors": { 606 | "version": "2.0.4", 607 | "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", 608 | "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", 609 | "dev": true, 610 | "license": "MIT" 611 | }, 612 | "node_modules/@types/mime": { 613 | "version": "1.3.5", 614 | "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", 615 | "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", 616 | "dev": true, 617 | "license": "MIT" 618 | }, 619 | "node_modules/@types/node": { 620 | "version": "22.13.11", 621 | "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.11.tgz", 622 | "integrity": "sha512-iEUCUJoU0i3VnrCmgoWCXttklWcvoCIx4jzcP22fioIVSdTmjgoEvmAO/QPw6TcS9k5FrNgn4w7q5lGOd1CT5g==", 623 | "license": "MIT", 624 | "dependencies": { 625 | "undici-types": "~6.20.0" 626 | } 627 | }, 628 | "node_modules/@types/node-fetch": { 629 | "version": "2.6.12", 630 | "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", 631 | "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", 632 | "license": "MIT", 633 | "dependencies": { 634 | "@types/node": "*", 635 | "form-data": "^4.0.0" 636 | } 637 | }, 638 | "node_modules/@types/qs": { 639 | "version": "6.9.18", 640 | "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.18.tgz", 641 | "integrity": "sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==", 642 | "dev": true, 643 | "license": "MIT" 644 | }, 645 | "node_modules/@types/range-parser": { 646 | "version": "1.2.7", 647 | "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", 648 | "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", 649 | "dev": true, 650 | "license": "MIT" 651 | }, 652 | "node_modules/@types/send": { 653 | "version": "0.17.4", 654 | "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", 655 | "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", 656 | "dev": true, 657 | "license": "MIT", 658 | "dependencies": { 659 | "@types/mime": "^1", 660 | "@types/node": "*" 661 | } 662 | }, 663 | "node_modules/@types/serve-static": { 664 | "version": "1.15.7", 665 | "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", 666 | "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", 667 | "dev": true, 668 | "license": "MIT", 669 | "dependencies": { 670 | "@types/http-errors": "*", 671 | "@types/node": "*", 672 | "@types/send": "*" 673 | } 674 | }, 675 | "node_modules/abort-controller": { 676 | "version": "3.0.0", 677 | "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", 678 | "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", 679 | "license": "MIT", 680 | "dependencies": { 681 | "event-target-shim": "^5.0.0" 682 | }, 683 | "engines": { 684 | "node": ">=6.5" 685 | } 686 | }, 687 | "node_modules/accepts": { 688 | "version": "1.3.8", 689 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", 690 | "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", 691 | "license": "MIT", 692 | "dependencies": { 693 | "mime-types": "~2.1.34", 694 | "negotiator": "0.6.3" 695 | }, 696 | "engines": { 697 | "node": ">= 0.6" 698 | } 699 | }, 700 | "node_modules/agentkeepalive": { 701 | "version": "4.6.0", 702 | "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", 703 | "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", 704 | "license": "MIT", 705 | "dependencies": { 706 | "humanize-ms": "^1.2.1" 707 | }, 708 | "engines": { 709 | "node": ">= 8.0.0" 710 | } 711 | }, 712 | "node_modules/ai": { 713 | "version": "4.2.2", 714 | "resolved": "https://registry.npmjs.org/ai/-/ai-4.2.2.tgz", 715 | "integrity": "sha512-lSQyQCP13/CYuPQ95vnMGgZBUPnqNrEwenreoO/qpPei0NJNDbYYIyUYceQa4WmOUnq2rkP2y4eU0WRrbZoQBQ==", 716 | "license": "Apache-2.0", 717 | "dependencies": { 718 | "@ai-sdk/provider": "1.1.0", 719 | "@ai-sdk/provider-utils": "2.2.1", 720 | "@ai-sdk/react": "1.2.1", 721 | "@ai-sdk/ui-utils": "1.2.1", 722 | "@opentelemetry/api": "1.9.0", 723 | "jsondiffpatch": "0.6.0" 724 | }, 725 | "engines": { 726 | "node": ">=18" 727 | }, 728 | "peerDependencies": { 729 | "react": "^18 || ^19 || ^19.0.0-rc", 730 | "zod": "^3.23.8" 731 | }, 732 | "peerDependenciesMeta": { 733 | "react": { 734 | "optional": true 735 | } 736 | } 737 | }, 738 | "node_modules/array-flatten": { 739 | "version": "1.1.1", 740 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 741 | "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", 742 | "license": "MIT" 743 | }, 744 | "node_modules/asynckit": { 745 | "version": "0.4.0", 746 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 747 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", 748 | "license": "MIT" 749 | }, 750 | "node_modules/body-parser": { 751 | "version": "1.20.3", 752 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", 753 | "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", 754 | "license": "MIT", 755 | "dependencies": { 756 | "bytes": "3.1.2", 757 | "content-type": "~1.0.5", 758 | "debug": "2.6.9", 759 | "depd": "2.0.0", 760 | "destroy": "1.2.0", 761 | "http-errors": "2.0.0", 762 | "iconv-lite": "0.4.24", 763 | "on-finished": "2.4.1", 764 | "qs": "6.13.0", 765 | "raw-body": "2.5.2", 766 | "type-is": "~1.6.18", 767 | "unpipe": "1.0.0" 768 | }, 769 | "engines": { 770 | "node": ">= 0.8", 771 | "npm": "1.2.8000 || >= 1.4.16" 772 | } 773 | }, 774 | "node_modules/bytes": { 775 | "version": "3.1.2", 776 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 777 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", 778 | "license": "MIT", 779 | "engines": { 780 | "node": ">= 0.8" 781 | } 782 | }, 783 | "node_modules/call-bind-apply-helpers": { 784 | "version": "1.0.2", 785 | "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", 786 | "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", 787 | "license": "MIT", 788 | "dependencies": { 789 | "es-errors": "^1.3.0", 790 | "function-bind": "^1.1.2" 791 | }, 792 | "engines": { 793 | "node": ">= 0.4" 794 | } 795 | }, 796 | "node_modules/call-bound": { 797 | "version": "1.0.4", 798 | "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", 799 | "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", 800 | "license": "MIT", 801 | "dependencies": { 802 | "call-bind-apply-helpers": "^1.0.2", 803 | "get-intrinsic": "^1.3.0" 804 | }, 805 | "engines": { 806 | "node": ">= 0.4" 807 | }, 808 | "funding": { 809 | "url": "https://github.com/sponsors/ljharb" 810 | } 811 | }, 812 | "node_modules/chalk": { 813 | "version": "5.4.1", 814 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", 815 | "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", 816 | "license": "MIT", 817 | "engines": { 818 | "node": "^12.17.0 || ^14.13 || >=16.0.0" 819 | }, 820 | "funding": { 821 | "url": "https://github.com/chalk/chalk?sponsor=1" 822 | } 823 | }, 824 | "node_modules/combined-stream": { 825 | "version": "1.0.8", 826 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 827 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 828 | "license": "MIT", 829 | "dependencies": { 830 | "delayed-stream": "~1.0.0" 831 | }, 832 | "engines": { 833 | "node": ">= 0.8" 834 | } 835 | }, 836 | "node_modules/content-disposition": { 837 | "version": "0.5.4", 838 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", 839 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", 840 | "license": "MIT", 841 | "dependencies": { 842 | "safe-buffer": "5.2.1" 843 | }, 844 | "engines": { 845 | "node": ">= 0.6" 846 | } 847 | }, 848 | "node_modules/content-type": { 849 | "version": "1.0.5", 850 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", 851 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", 852 | "license": "MIT", 853 | "engines": { 854 | "node": ">= 0.6" 855 | } 856 | }, 857 | "node_modules/cookie": { 858 | "version": "0.7.1", 859 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", 860 | "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", 861 | "license": "MIT", 862 | "engines": { 863 | "node": ">= 0.6" 864 | } 865 | }, 866 | "node_modules/cookie-signature": { 867 | "version": "1.0.6", 868 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 869 | "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", 870 | "license": "MIT" 871 | }, 872 | "node_modules/debug": { 873 | "version": "2.6.9", 874 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 875 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 876 | "license": "MIT", 877 | "dependencies": { 878 | "ms": "2.0.0" 879 | } 880 | }, 881 | "node_modules/delayed-stream": { 882 | "version": "1.0.0", 883 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 884 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", 885 | "license": "MIT", 886 | "engines": { 887 | "node": ">=0.4.0" 888 | } 889 | }, 890 | "node_modules/depd": { 891 | "version": "2.0.0", 892 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 893 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", 894 | "license": "MIT", 895 | "engines": { 896 | "node": ">= 0.8" 897 | } 898 | }, 899 | "node_modules/dequal": { 900 | "version": "2.0.3", 901 | "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", 902 | "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", 903 | "license": "MIT", 904 | "engines": { 905 | "node": ">=6" 906 | } 907 | }, 908 | "node_modules/destroy": { 909 | "version": "1.2.0", 910 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", 911 | "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", 912 | "license": "MIT", 913 | "engines": { 914 | "node": ">= 0.8", 915 | "npm": "1.2.8000 || >= 1.4.16" 916 | } 917 | }, 918 | "node_modules/diff-match-patch": { 919 | "version": "1.0.5", 920 | "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz", 921 | "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==", 922 | "license": "Apache-2.0" 923 | }, 924 | "node_modules/dotenv": { 925 | "version": "16.4.7", 926 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", 927 | "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", 928 | "dev": true, 929 | "license": "BSD-2-Clause", 930 | "engines": { 931 | "node": ">=12" 932 | }, 933 | "funding": { 934 | "url": "https://dotenvx.com" 935 | } 936 | }, 937 | "node_modules/dunder-proto": { 938 | "version": "1.0.1", 939 | "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", 940 | "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", 941 | "license": "MIT", 942 | "dependencies": { 943 | "call-bind-apply-helpers": "^1.0.1", 944 | "es-errors": "^1.3.0", 945 | "gopd": "^1.2.0" 946 | }, 947 | "engines": { 948 | "node": ">= 0.4" 949 | } 950 | }, 951 | "node_modules/ee-first": { 952 | "version": "1.1.1", 953 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 954 | "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", 955 | "license": "MIT" 956 | }, 957 | "node_modules/encodeurl": { 958 | "version": "2.0.0", 959 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", 960 | "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", 961 | "license": "MIT", 962 | "engines": { 963 | "node": ">= 0.8" 964 | } 965 | }, 966 | "node_modules/es-define-property": { 967 | "version": "1.0.1", 968 | "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", 969 | "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", 970 | "license": "MIT", 971 | "engines": { 972 | "node": ">= 0.4" 973 | } 974 | }, 975 | "node_modules/es-errors": { 976 | "version": "1.3.0", 977 | "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", 978 | "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", 979 | "license": "MIT", 980 | "engines": { 981 | "node": ">= 0.4" 982 | } 983 | }, 984 | "node_modules/es-object-atoms": { 985 | "version": "1.1.1", 986 | "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", 987 | "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", 988 | "license": "MIT", 989 | "dependencies": { 990 | "es-errors": "^1.3.0" 991 | }, 992 | "engines": { 993 | "node": ">= 0.4" 994 | } 995 | }, 996 | "node_modules/es-set-tostringtag": { 997 | "version": "2.1.0", 998 | "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", 999 | "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", 1000 | "license": "MIT", 1001 | "dependencies": { 1002 | "es-errors": "^1.3.0", 1003 | "get-intrinsic": "^1.2.6", 1004 | "has-tostringtag": "^1.0.2", 1005 | "hasown": "^2.0.2" 1006 | }, 1007 | "engines": { 1008 | "node": ">= 0.4" 1009 | } 1010 | }, 1011 | "node_modules/esbuild": { 1012 | "version": "0.25.1", 1013 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.1.tgz", 1014 | "integrity": "sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==", 1015 | "dev": true, 1016 | "hasInstallScript": true, 1017 | "license": "MIT", 1018 | "bin": { 1019 | "esbuild": "bin/esbuild" 1020 | }, 1021 | "engines": { 1022 | "node": ">=18" 1023 | }, 1024 | "optionalDependencies": { 1025 | "@esbuild/aix-ppc64": "0.25.1", 1026 | "@esbuild/android-arm": "0.25.1", 1027 | "@esbuild/android-arm64": "0.25.1", 1028 | "@esbuild/android-x64": "0.25.1", 1029 | "@esbuild/darwin-arm64": "0.25.1", 1030 | "@esbuild/darwin-x64": "0.25.1", 1031 | "@esbuild/freebsd-arm64": "0.25.1", 1032 | "@esbuild/freebsd-x64": "0.25.1", 1033 | "@esbuild/linux-arm": "0.25.1", 1034 | "@esbuild/linux-arm64": "0.25.1", 1035 | "@esbuild/linux-ia32": "0.25.1", 1036 | "@esbuild/linux-loong64": "0.25.1", 1037 | "@esbuild/linux-mips64el": "0.25.1", 1038 | "@esbuild/linux-ppc64": "0.25.1", 1039 | "@esbuild/linux-riscv64": "0.25.1", 1040 | "@esbuild/linux-s390x": "0.25.1", 1041 | "@esbuild/linux-x64": "0.25.1", 1042 | "@esbuild/netbsd-arm64": "0.25.1", 1043 | "@esbuild/netbsd-x64": "0.25.1", 1044 | "@esbuild/openbsd-arm64": "0.25.1", 1045 | "@esbuild/openbsd-x64": "0.25.1", 1046 | "@esbuild/sunos-x64": "0.25.1", 1047 | "@esbuild/win32-arm64": "0.25.1", 1048 | "@esbuild/win32-ia32": "0.25.1", 1049 | "@esbuild/win32-x64": "0.25.1" 1050 | } 1051 | }, 1052 | "node_modules/escape-html": { 1053 | "version": "1.0.3", 1054 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 1055 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", 1056 | "license": "MIT" 1057 | }, 1058 | "node_modules/etag": { 1059 | "version": "1.8.1", 1060 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 1061 | "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", 1062 | "license": "MIT", 1063 | "engines": { 1064 | "node": ">= 0.6" 1065 | } 1066 | }, 1067 | "node_modules/event-target-shim": { 1068 | "version": "5.0.1", 1069 | "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", 1070 | "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", 1071 | "license": "MIT", 1072 | "engines": { 1073 | "node": ">=6" 1074 | } 1075 | }, 1076 | "node_modules/express": { 1077 | "version": "4.21.2", 1078 | "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", 1079 | "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", 1080 | "license": "MIT", 1081 | "dependencies": { 1082 | "accepts": "~1.3.8", 1083 | "array-flatten": "1.1.1", 1084 | "body-parser": "1.20.3", 1085 | "content-disposition": "0.5.4", 1086 | "content-type": "~1.0.4", 1087 | "cookie": "0.7.1", 1088 | "cookie-signature": "1.0.6", 1089 | "debug": "2.6.9", 1090 | "depd": "2.0.0", 1091 | "encodeurl": "~2.0.0", 1092 | "escape-html": "~1.0.3", 1093 | "etag": "~1.8.1", 1094 | "finalhandler": "1.3.1", 1095 | "fresh": "0.5.2", 1096 | "http-errors": "2.0.0", 1097 | "merge-descriptors": "1.0.3", 1098 | "methods": "~1.1.2", 1099 | "on-finished": "2.4.1", 1100 | "parseurl": "~1.3.3", 1101 | "path-to-regexp": "0.1.12", 1102 | "proxy-addr": "~2.0.7", 1103 | "qs": "6.13.0", 1104 | "range-parser": "~1.2.1", 1105 | "safe-buffer": "5.2.1", 1106 | "send": "0.19.0", 1107 | "serve-static": "1.16.2", 1108 | "setprototypeof": "1.2.0", 1109 | "statuses": "2.0.1", 1110 | "type-is": "~1.6.18", 1111 | "utils-merge": "1.0.1", 1112 | "vary": "~1.1.2" 1113 | }, 1114 | "engines": { 1115 | "node": ">= 0.10.0" 1116 | }, 1117 | "funding": { 1118 | "type": "opencollective", 1119 | "url": "https://opencollective.com/express" 1120 | } 1121 | }, 1122 | "node_modules/finalhandler": { 1123 | "version": "1.3.1", 1124 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", 1125 | "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", 1126 | "license": "MIT", 1127 | "dependencies": { 1128 | "debug": "2.6.9", 1129 | "encodeurl": "~2.0.0", 1130 | "escape-html": "~1.0.3", 1131 | "on-finished": "2.4.1", 1132 | "parseurl": "~1.3.3", 1133 | "statuses": "2.0.1", 1134 | "unpipe": "~1.0.0" 1135 | }, 1136 | "engines": { 1137 | "node": ">= 0.8" 1138 | } 1139 | }, 1140 | "node_modules/form-data": { 1141 | "version": "4.0.2", 1142 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", 1143 | "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", 1144 | "license": "MIT", 1145 | "dependencies": { 1146 | "asynckit": "^0.4.0", 1147 | "combined-stream": "^1.0.8", 1148 | "es-set-tostringtag": "^2.1.0", 1149 | "mime-types": "^2.1.12" 1150 | }, 1151 | "engines": { 1152 | "node": ">= 6" 1153 | } 1154 | }, 1155 | "node_modules/form-data-encoder": { 1156 | "version": "1.7.2", 1157 | "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", 1158 | "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", 1159 | "license": "MIT" 1160 | }, 1161 | "node_modules/formdata-node": { 1162 | "version": "4.4.1", 1163 | "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", 1164 | "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", 1165 | "license": "MIT", 1166 | "dependencies": { 1167 | "node-domexception": "1.0.0", 1168 | "web-streams-polyfill": "4.0.0-beta.3" 1169 | }, 1170 | "engines": { 1171 | "node": ">= 12.20" 1172 | } 1173 | }, 1174 | "node_modules/forwarded": { 1175 | "version": "0.2.0", 1176 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 1177 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", 1178 | "license": "MIT", 1179 | "engines": { 1180 | "node": ">= 0.6" 1181 | } 1182 | }, 1183 | "node_modules/fresh": { 1184 | "version": "0.5.2", 1185 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 1186 | "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", 1187 | "license": "MIT", 1188 | "engines": { 1189 | "node": ">= 0.6" 1190 | } 1191 | }, 1192 | "node_modules/fsevents": { 1193 | "version": "2.3.3", 1194 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 1195 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 1196 | "dev": true, 1197 | "hasInstallScript": true, 1198 | "license": "MIT", 1199 | "optional": true, 1200 | "os": [ 1201 | "darwin" 1202 | ], 1203 | "engines": { 1204 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 1205 | } 1206 | }, 1207 | "node_modules/function-bind": { 1208 | "version": "1.1.2", 1209 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", 1210 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", 1211 | "license": "MIT", 1212 | "funding": { 1213 | "url": "https://github.com/sponsors/ljharb" 1214 | } 1215 | }, 1216 | "node_modules/get-intrinsic": { 1217 | "version": "1.3.0", 1218 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", 1219 | "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", 1220 | "license": "MIT", 1221 | "dependencies": { 1222 | "call-bind-apply-helpers": "^1.0.2", 1223 | "es-define-property": "^1.0.1", 1224 | "es-errors": "^1.3.0", 1225 | "es-object-atoms": "^1.1.1", 1226 | "function-bind": "^1.1.2", 1227 | "get-proto": "^1.0.1", 1228 | "gopd": "^1.2.0", 1229 | "has-symbols": "^1.1.0", 1230 | "hasown": "^2.0.2", 1231 | "math-intrinsics": "^1.1.0" 1232 | }, 1233 | "engines": { 1234 | "node": ">= 0.4" 1235 | }, 1236 | "funding": { 1237 | "url": "https://github.com/sponsors/ljharb" 1238 | } 1239 | }, 1240 | "node_modules/get-proto": { 1241 | "version": "1.0.1", 1242 | "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", 1243 | "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", 1244 | "license": "MIT", 1245 | "dependencies": { 1246 | "dunder-proto": "^1.0.1", 1247 | "es-object-atoms": "^1.0.0" 1248 | }, 1249 | "engines": { 1250 | "node": ">= 0.4" 1251 | } 1252 | }, 1253 | "node_modules/get-tsconfig": { 1254 | "version": "4.10.0", 1255 | "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.0.tgz", 1256 | "integrity": "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==", 1257 | "dev": true, 1258 | "license": "MIT", 1259 | "dependencies": { 1260 | "resolve-pkg-maps": "^1.0.0" 1261 | }, 1262 | "funding": { 1263 | "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" 1264 | } 1265 | }, 1266 | "node_modules/gopd": { 1267 | "version": "1.2.0", 1268 | "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", 1269 | "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", 1270 | "license": "MIT", 1271 | "engines": { 1272 | "node": ">= 0.4" 1273 | }, 1274 | "funding": { 1275 | "url": "https://github.com/sponsors/ljharb" 1276 | } 1277 | }, 1278 | "node_modules/has-symbols": { 1279 | "version": "1.1.0", 1280 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", 1281 | "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", 1282 | "license": "MIT", 1283 | "engines": { 1284 | "node": ">= 0.4" 1285 | }, 1286 | "funding": { 1287 | "url": "https://github.com/sponsors/ljharb" 1288 | } 1289 | }, 1290 | "node_modules/has-tostringtag": { 1291 | "version": "1.0.2", 1292 | "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", 1293 | "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", 1294 | "license": "MIT", 1295 | "dependencies": { 1296 | "has-symbols": "^1.0.3" 1297 | }, 1298 | "engines": { 1299 | "node": ">= 0.4" 1300 | }, 1301 | "funding": { 1302 | "url": "https://github.com/sponsors/ljharb" 1303 | } 1304 | }, 1305 | "node_modules/hasown": { 1306 | "version": "2.0.2", 1307 | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", 1308 | "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", 1309 | "license": "MIT", 1310 | "dependencies": { 1311 | "function-bind": "^1.1.2" 1312 | }, 1313 | "engines": { 1314 | "node": ">= 0.4" 1315 | } 1316 | }, 1317 | "node_modules/http-errors": { 1318 | "version": "2.0.0", 1319 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 1320 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 1321 | "license": "MIT", 1322 | "dependencies": { 1323 | "depd": "2.0.0", 1324 | "inherits": "2.0.4", 1325 | "setprototypeof": "1.2.0", 1326 | "statuses": "2.0.1", 1327 | "toidentifier": "1.0.1" 1328 | }, 1329 | "engines": { 1330 | "node": ">= 0.8" 1331 | } 1332 | }, 1333 | "node_modules/humanize-ms": { 1334 | "version": "1.2.1", 1335 | "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", 1336 | "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", 1337 | "license": "MIT", 1338 | "dependencies": { 1339 | "ms": "^2.0.0" 1340 | } 1341 | }, 1342 | "node_modules/iconv-lite": { 1343 | "version": "0.4.24", 1344 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 1345 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 1346 | "license": "MIT", 1347 | "dependencies": { 1348 | "safer-buffer": ">= 2.1.2 < 3" 1349 | }, 1350 | "engines": { 1351 | "node": ">=0.10.0" 1352 | } 1353 | }, 1354 | "node_modules/inherits": { 1355 | "version": "2.0.4", 1356 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 1357 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 1358 | "license": "ISC" 1359 | }, 1360 | "node_modules/ipaddr.js": { 1361 | "version": "1.9.1", 1362 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 1363 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", 1364 | "license": "MIT", 1365 | "engines": { 1366 | "node": ">= 0.10" 1367 | } 1368 | }, 1369 | "node_modules/json-schema": { 1370 | "version": "0.4.0", 1371 | "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", 1372 | "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", 1373 | "license": "(AFL-2.1 OR BSD-3-Clause)" 1374 | }, 1375 | "node_modules/jsondiffpatch": { 1376 | "version": "0.6.0", 1377 | "resolved": "https://registry.npmjs.org/jsondiffpatch/-/jsondiffpatch-0.6.0.tgz", 1378 | "integrity": "sha512-3QItJOXp2AP1uv7waBkao5nCvhEv+QmJAd38Ybq7wNI74Q+BBmnLn4EDKz6yI9xGAIQoUF87qHt+kc1IVxB4zQ==", 1379 | "license": "MIT", 1380 | "dependencies": { 1381 | "@types/diff-match-patch": "^1.0.36", 1382 | "chalk": "^5.3.0", 1383 | "diff-match-patch": "^1.0.5" 1384 | }, 1385 | "bin": { 1386 | "jsondiffpatch": "bin/jsondiffpatch.js" 1387 | }, 1388 | "engines": { 1389 | "node": "^18.0.0 || >=20.0.0" 1390 | } 1391 | }, 1392 | "node_modules/math-intrinsics": { 1393 | "version": "1.1.0", 1394 | "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", 1395 | "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", 1396 | "license": "MIT", 1397 | "engines": { 1398 | "node": ">= 0.4" 1399 | } 1400 | }, 1401 | "node_modules/media-typer": { 1402 | "version": "0.3.0", 1403 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 1404 | "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", 1405 | "license": "MIT", 1406 | "engines": { 1407 | "node": ">= 0.6" 1408 | } 1409 | }, 1410 | "node_modules/merge-descriptors": { 1411 | "version": "1.0.3", 1412 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", 1413 | "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", 1414 | "license": "MIT", 1415 | "funding": { 1416 | "url": "https://github.com/sponsors/sindresorhus" 1417 | } 1418 | }, 1419 | "node_modules/methods": { 1420 | "version": "1.1.2", 1421 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 1422 | "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", 1423 | "license": "MIT", 1424 | "engines": { 1425 | "node": ">= 0.6" 1426 | } 1427 | }, 1428 | "node_modules/mime": { 1429 | "version": "1.6.0", 1430 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 1431 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", 1432 | "license": "MIT", 1433 | "bin": { 1434 | "mime": "cli.js" 1435 | }, 1436 | "engines": { 1437 | "node": ">=4" 1438 | } 1439 | }, 1440 | "node_modules/mime-db": { 1441 | "version": "1.52.0", 1442 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 1443 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 1444 | "license": "MIT", 1445 | "engines": { 1446 | "node": ">= 0.6" 1447 | } 1448 | }, 1449 | "node_modules/mime-types": { 1450 | "version": "2.1.35", 1451 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 1452 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 1453 | "license": "MIT", 1454 | "dependencies": { 1455 | "mime-db": "1.52.0" 1456 | }, 1457 | "engines": { 1458 | "node": ">= 0.6" 1459 | } 1460 | }, 1461 | "node_modules/ms": { 1462 | "version": "2.0.0", 1463 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 1464 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", 1465 | "license": "MIT" 1466 | }, 1467 | "node_modules/nanoid": { 1468 | "version": "3.3.11", 1469 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", 1470 | "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", 1471 | "funding": [ 1472 | { 1473 | "type": "github", 1474 | "url": "https://github.com/sponsors/ai" 1475 | } 1476 | ], 1477 | "license": "MIT", 1478 | "bin": { 1479 | "nanoid": "bin/nanoid.cjs" 1480 | }, 1481 | "engines": { 1482 | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 1483 | } 1484 | }, 1485 | "node_modules/negotiator": { 1486 | "version": "0.6.3", 1487 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", 1488 | "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", 1489 | "license": "MIT", 1490 | "engines": { 1491 | "node": ">= 0.6" 1492 | } 1493 | }, 1494 | "node_modules/node-domexception": { 1495 | "version": "1.0.0", 1496 | "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", 1497 | "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", 1498 | "funding": [ 1499 | { 1500 | "type": "github", 1501 | "url": "https://github.com/sponsors/jimmywarting" 1502 | }, 1503 | { 1504 | "type": "github", 1505 | "url": "https://paypal.me/jimmywarting" 1506 | } 1507 | ], 1508 | "license": "MIT", 1509 | "engines": { 1510 | "node": ">=10.5.0" 1511 | } 1512 | }, 1513 | "node_modules/node-fetch": { 1514 | "version": "2.7.0", 1515 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", 1516 | "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", 1517 | "license": "MIT", 1518 | "dependencies": { 1519 | "whatwg-url": "^5.0.0" 1520 | }, 1521 | "engines": { 1522 | "node": "4.x || >=6.0.0" 1523 | }, 1524 | "peerDependencies": { 1525 | "encoding": "^0.1.0" 1526 | }, 1527 | "peerDependenciesMeta": { 1528 | "encoding": { 1529 | "optional": true 1530 | } 1531 | } 1532 | }, 1533 | "node_modules/object-inspect": { 1534 | "version": "1.13.4", 1535 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", 1536 | "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", 1537 | "license": "MIT", 1538 | "engines": { 1539 | "node": ">= 0.4" 1540 | }, 1541 | "funding": { 1542 | "url": "https://github.com/sponsors/ljharb" 1543 | } 1544 | }, 1545 | "node_modules/on-finished": { 1546 | "version": "2.4.1", 1547 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", 1548 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", 1549 | "license": "MIT", 1550 | "dependencies": { 1551 | "ee-first": "1.1.1" 1552 | }, 1553 | "engines": { 1554 | "node": ">= 0.8" 1555 | } 1556 | }, 1557 | "node_modules/openai": { 1558 | "version": "4.89.0", 1559 | "resolved": "https://registry.npmjs.org/openai/-/openai-4.89.0.tgz", 1560 | "integrity": "sha512-XNI0q2l8/Os6jmojxaID5EhyQjxZgzR2gWcpEjYWK5hGKwE7AcifxEY7UNwFDDHJQXqeiosQ0CJwQN+rvnwdjA==", 1561 | "license": "Apache-2.0", 1562 | "dependencies": { 1563 | "@types/node": "^18.11.18", 1564 | "@types/node-fetch": "^2.6.4", 1565 | "abort-controller": "^3.0.0", 1566 | "agentkeepalive": "^4.2.1", 1567 | "form-data-encoder": "1.7.2", 1568 | "formdata-node": "^4.3.2", 1569 | "node-fetch": "^2.6.7" 1570 | }, 1571 | "bin": { 1572 | "openai": "bin/cli" 1573 | }, 1574 | "peerDependencies": { 1575 | "ws": "^8.18.0", 1576 | "zod": "^3.23.8" 1577 | }, 1578 | "peerDependenciesMeta": { 1579 | "ws": { 1580 | "optional": true 1581 | }, 1582 | "zod": { 1583 | "optional": true 1584 | } 1585 | } 1586 | }, 1587 | "node_modules/openai/node_modules/@types/node": { 1588 | "version": "18.19.81", 1589 | "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.81.tgz", 1590 | "integrity": "sha512-7KO9oZ2//ivtSsryp0LQUqq79zyGXzwq1WqfywpC9ucjY7YyltMMmxWgtRFRKCxwa7VPxVBVy4kHf5UC1E8Lug==", 1591 | "license": "MIT", 1592 | "dependencies": { 1593 | "undici-types": "~5.26.4" 1594 | } 1595 | }, 1596 | "node_modules/openai/node_modules/undici-types": { 1597 | "version": "5.26.5", 1598 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", 1599 | "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", 1600 | "license": "MIT" 1601 | }, 1602 | "node_modules/parseurl": { 1603 | "version": "1.3.3", 1604 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 1605 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", 1606 | "license": "MIT", 1607 | "engines": { 1608 | "node": ">= 0.8" 1609 | } 1610 | }, 1611 | "node_modules/path-to-regexp": { 1612 | "version": "0.1.12", 1613 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", 1614 | "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", 1615 | "license": "MIT" 1616 | }, 1617 | "node_modules/proxy-addr": { 1618 | "version": "2.0.7", 1619 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 1620 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 1621 | "license": "MIT", 1622 | "dependencies": { 1623 | "forwarded": "0.2.0", 1624 | "ipaddr.js": "1.9.1" 1625 | }, 1626 | "engines": { 1627 | "node": ">= 0.10" 1628 | } 1629 | }, 1630 | "node_modules/qs": { 1631 | "version": "6.13.0", 1632 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", 1633 | "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", 1634 | "license": "BSD-3-Clause", 1635 | "dependencies": { 1636 | "side-channel": "^1.0.6" 1637 | }, 1638 | "engines": { 1639 | "node": ">=0.6" 1640 | }, 1641 | "funding": { 1642 | "url": "https://github.com/sponsors/ljharb" 1643 | } 1644 | }, 1645 | "node_modules/range-parser": { 1646 | "version": "1.2.1", 1647 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 1648 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", 1649 | "license": "MIT", 1650 | "engines": { 1651 | "node": ">= 0.6" 1652 | } 1653 | }, 1654 | "node_modules/raw-body": { 1655 | "version": "2.5.2", 1656 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", 1657 | "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", 1658 | "license": "MIT", 1659 | "dependencies": { 1660 | "bytes": "3.1.2", 1661 | "http-errors": "2.0.0", 1662 | "iconv-lite": "0.4.24", 1663 | "unpipe": "1.0.0" 1664 | }, 1665 | "engines": { 1666 | "node": ">= 0.8" 1667 | } 1668 | }, 1669 | "node_modules/react": { 1670 | "version": "19.0.0", 1671 | "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", 1672 | "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==", 1673 | "license": "MIT", 1674 | "peer": true, 1675 | "engines": { 1676 | "node": ">=0.10.0" 1677 | } 1678 | }, 1679 | "node_modules/resolve-pkg-maps": { 1680 | "version": "1.0.0", 1681 | "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", 1682 | "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", 1683 | "dev": true, 1684 | "license": "MIT", 1685 | "funding": { 1686 | "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" 1687 | } 1688 | }, 1689 | "node_modules/safe-buffer": { 1690 | "version": "5.2.1", 1691 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 1692 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 1693 | "funding": [ 1694 | { 1695 | "type": "github", 1696 | "url": "https://github.com/sponsors/feross" 1697 | }, 1698 | { 1699 | "type": "patreon", 1700 | "url": "https://www.patreon.com/feross" 1701 | }, 1702 | { 1703 | "type": "consulting", 1704 | "url": "https://feross.org/support" 1705 | } 1706 | ], 1707 | "license": "MIT" 1708 | }, 1709 | "node_modules/safer-buffer": { 1710 | "version": "2.1.2", 1711 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 1712 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 1713 | "license": "MIT" 1714 | }, 1715 | "node_modules/secure-json-parse": { 1716 | "version": "2.7.0", 1717 | "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", 1718 | "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==", 1719 | "license": "BSD-3-Clause" 1720 | }, 1721 | "node_modules/send": { 1722 | "version": "0.19.0", 1723 | "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", 1724 | "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", 1725 | "license": "MIT", 1726 | "dependencies": { 1727 | "debug": "2.6.9", 1728 | "depd": "2.0.0", 1729 | "destroy": "1.2.0", 1730 | "encodeurl": "~1.0.2", 1731 | "escape-html": "~1.0.3", 1732 | "etag": "~1.8.1", 1733 | "fresh": "0.5.2", 1734 | "http-errors": "2.0.0", 1735 | "mime": "1.6.0", 1736 | "ms": "2.1.3", 1737 | "on-finished": "2.4.1", 1738 | "range-parser": "~1.2.1", 1739 | "statuses": "2.0.1" 1740 | }, 1741 | "engines": { 1742 | "node": ">= 0.8.0" 1743 | } 1744 | }, 1745 | "node_modules/send/node_modules/encodeurl": { 1746 | "version": "1.0.2", 1747 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 1748 | "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", 1749 | "license": "MIT", 1750 | "engines": { 1751 | "node": ">= 0.8" 1752 | } 1753 | }, 1754 | "node_modules/send/node_modules/ms": { 1755 | "version": "2.1.3", 1756 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 1757 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 1758 | "license": "MIT" 1759 | }, 1760 | "node_modules/serve-static": { 1761 | "version": "1.16.2", 1762 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", 1763 | "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", 1764 | "license": "MIT", 1765 | "dependencies": { 1766 | "encodeurl": "~2.0.0", 1767 | "escape-html": "~1.0.3", 1768 | "parseurl": "~1.3.3", 1769 | "send": "0.19.0" 1770 | }, 1771 | "engines": { 1772 | "node": ">= 0.8.0" 1773 | } 1774 | }, 1775 | "node_modules/setprototypeof": { 1776 | "version": "1.2.0", 1777 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 1778 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", 1779 | "license": "ISC" 1780 | }, 1781 | "node_modules/side-channel": { 1782 | "version": "1.1.0", 1783 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", 1784 | "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", 1785 | "license": "MIT", 1786 | "dependencies": { 1787 | "es-errors": "^1.3.0", 1788 | "object-inspect": "^1.13.3", 1789 | "side-channel-list": "^1.0.0", 1790 | "side-channel-map": "^1.0.1", 1791 | "side-channel-weakmap": "^1.0.2" 1792 | }, 1793 | "engines": { 1794 | "node": ">= 0.4" 1795 | }, 1796 | "funding": { 1797 | "url": "https://github.com/sponsors/ljharb" 1798 | } 1799 | }, 1800 | "node_modules/side-channel-list": { 1801 | "version": "1.0.0", 1802 | "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", 1803 | "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", 1804 | "license": "MIT", 1805 | "dependencies": { 1806 | "es-errors": "^1.3.0", 1807 | "object-inspect": "^1.13.3" 1808 | }, 1809 | "engines": { 1810 | "node": ">= 0.4" 1811 | }, 1812 | "funding": { 1813 | "url": "https://github.com/sponsors/ljharb" 1814 | } 1815 | }, 1816 | "node_modules/side-channel-map": { 1817 | "version": "1.0.1", 1818 | "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", 1819 | "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", 1820 | "license": "MIT", 1821 | "dependencies": { 1822 | "call-bound": "^1.0.2", 1823 | "es-errors": "^1.3.0", 1824 | "get-intrinsic": "^1.2.5", 1825 | "object-inspect": "^1.13.3" 1826 | }, 1827 | "engines": { 1828 | "node": ">= 0.4" 1829 | }, 1830 | "funding": { 1831 | "url": "https://github.com/sponsors/ljharb" 1832 | } 1833 | }, 1834 | "node_modules/side-channel-weakmap": { 1835 | "version": "1.0.2", 1836 | "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", 1837 | "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", 1838 | "license": "MIT", 1839 | "dependencies": { 1840 | "call-bound": "^1.0.2", 1841 | "es-errors": "^1.3.0", 1842 | "get-intrinsic": "^1.2.5", 1843 | "object-inspect": "^1.13.3", 1844 | "side-channel-map": "^1.0.1" 1845 | }, 1846 | "engines": { 1847 | "node": ">= 0.4" 1848 | }, 1849 | "funding": { 1850 | "url": "https://github.com/sponsors/ljharb" 1851 | } 1852 | }, 1853 | "node_modules/statuses": { 1854 | "version": "2.0.1", 1855 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 1856 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", 1857 | "license": "MIT", 1858 | "engines": { 1859 | "node": ">= 0.8" 1860 | } 1861 | }, 1862 | "node_modules/swr": { 1863 | "version": "2.3.3", 1864 | "resolved": "https://registry.npmjs.org/swr/-/swr-2.3.3.tgz", 1865 | "integrity": "sha512-dshNvs3ExOqtZ6kJBaAsabhPdHyeY4P2cKwRCniDVifBMoG/SVI7tfLWqPXriVspf2Rg4tPzXJTnwaihIeFw2A==", 1866 | "license": "MIT", 1867 | "dependencies": { 1868 | "dequal": "^2.0.3", 1869 | "use-sync-external-store": "^1.4.0" 1870 | }, 1871 | "peerDependencies": { 1872 | "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" 1873 | } 1874 | }, 1875 | "node_modules/throttleit": { 1876 | "version": "2.1.0", 1877 | "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-2.1.0.tgz", 1878 | "integrity": "sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==", 1879 | "license": "MIT", 1880 | "engines": { 1881 | "node": ">=18" 1882 | }, 1883 | "funding": { 1884 | "url": "https://github.com/sponsors/sindresorhus" 1885 | } 1886 | }, 1887 | "node_modules/toidentifier": { 1888 | "version": "1.0.1", 1889 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 1890 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", 1891 | "license": "MIT", 1892 | "engines": { 1893 | "node": ">=0.6" 1894 | } 1895 | }, 1896 | "node_modules/tr46": { 1897 | "version": "0.0.3", 1898 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", 1899 | "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", 1900 | "license": "MIT" 1901 | }, 1902 | "node_modules/tsx": { 1903 | "version": "4.19.3", 1904 | "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.3.tgz", 1905 | "integrity": "sha512-4H8vUNGNjQ4V2EOoGw005+c+dGuPSnhpPBPHBtsZdGZBk/iJb4kguGlPWaZTZ3q5nMtFOEsY0nRDlh9PJyd6SQ==", 1906 | "dev": true, 1907 | "license": "MIT", 1908 | "dependencies": { 1909 | "esbuild": "~0.25.0", 1910 | "get-tsconfig": "^4.7.5" 1911 | }, 1912 | "bin": { 1913 | "tsx": "dist/cli.mjs" 1914 | }, 1915 | "engines": { 1916 | "node": ">=18.0.0" 1917 | }, 1918 | "optionalDependencies": { 1919 | "fsevents": "~2.3.3" 1920 | } 1921 | }, 1922 | "node_modules/type-is": { 1923 | "version": "1.6.18", 1924 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 1925 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 1926 | "license": "MIT", 1927 | "dependencies": { 1928 | "media-typer": "0.3.0", 1929 | "mime-types": "~2.1.24" 1930 | }, 1931 | "engines": { 1932 | "node": ">= 0.6" 1933 | } 1934 | }, 1935 | "node_modules/typescript": { 1936 | "version": "5.8.2", 1937 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", 1938 | "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", 1939 | "dev": true, 1940 | "license": "Apache-2.0", 1941 | "bin": { 1942 | "tsc": "bin/tsc", 1943 | "tsserver": "bin/tsserver" 1944 | }, 1945 | "engines": { 1946 | "node": ">=14.17" 1947 | } 1948 | }, 1949 | "node_modules/undici-types": { 1950 | "version": "6.20.0", 1951 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", 1952 | "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", 1953 | "license": "MIT" 1954 | }, 1955 | "node_modules/unpipe": { 1956 | "version": "1.0.0", 1957 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 1958 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", 1959 | "license": "MIT", 1960 | "engines": { 1961 | "node": ">= 0.8" 1962 | } 1963 | }, 1964 | "node_modules/use-sync-external-store": { 1965 | "version": "1.4.0", 1966 | "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz", 1967 | "integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==", 1968 | "license": "MIT", 1969 | "peerDependencies": { 1970 | "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" 1971 | } 1972 | }, 1973 | "node_modules/utils-merge": { 1974 | "version": "1.0.1", 1975 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 1976 | "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", 1977 | "license": "MIT", 1978 | "engines": { 1979 | "node": ">= 0.4.0" 1980 | } 1981 | }, 1982 | "node_modules/vary": { 1983 | "version": "1.1.2", 1984 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 1985 | "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", 1986 | "license": "MIT", 1987 | "engines": { 1988 | "node": ">= 0.8" 1989 | } 1990 | }, 1991 | "node_modules/web-streams-polyfill": { 1992 | "version": "4.0.0-beta.3", 1993 | "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", 1994 | "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", 1995 | "license": "MIT", 1996 | "engines": { 1997 | "node": ">= 14" 1998 | } 1999 | }, 2000 | "node_modules/webidl-conversions": { 2001 | "version": "3.0.1", 2002 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", 2003 | "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", 2004 | "license": "BSD-2-Clause" 2005 | }, 2006 | "node_modules/whatwg-url": { 2007 | "version": "5.0.0", 2008 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", 2009 | "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", 2010 | "license": "MIT", 2011 | "dependencies": { 2012 | "tr46": "~0.0.3", 2013 | "webidl-conversions": "^3.0.0" 2014 | } 2015 | }, 2016 | "node_modules/zod": { 2017 | "version": "3.24.2", 2018 | "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz", 2019 | "integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==", 2020 | "license": "MIT", 2021 | "funding": { 2022 | "url": "https://github.com/sponsors/colinhacks" 2023 | } 2024 | }, 2025 | "node_modules/zod-to-json-schema": { 2026 | "version": "3.24.5", 2027 | "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz", 2028 | "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==", 2029 | "license": "ISC", 2030 | "peerDependencies": { 2031 | "zod": "^3.24.1" 2032 | } 2033 | } 2034 | } 2035 | } 2036 | -------------------------------------------------------------------------------- /examples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "obsy-examples", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "check": "tsc --noEmit", 8 | "build-sdk": "cd ../ && npm run build && npm pack && cd examples && npm install ../obsy-ai-sdk-*.tgz", 9 | "eg-openai": "tsx watch src/openai.ts", 10 | "eg-openai-responses": "tsx watch src/openai-responses.ts", 11 | "eg-rag": "tsx watch src/rag.ts", 12 | "eg-vercel-ai-sdk": "tsx watch src/vercel-ai-sdk.ts" 13 | }, 14 | "keywords": [], 15 | "author": "", 16 | "license": "ISC", 17 | "type": "module", 18 | "dependencies": { 19 | "@ai-sdk/openai": "^1.3.1", 20 | "@obsy-ai/sdk": "file:../obsy-ai-sdk-0.0.3.tgz", 21 | "ai": "^4.2.2", 22 | "express": "^4.21.2", 23 | "openai": "^4.89.0", 24 | "zod": "^3.24.2" 25 | }, 26 | "devDependencies": { 27 | "@types/express": "^5.0.1", 28 | "@types/node": "^22.13.11", 29 | "dotenv": "^16.4.7", 30 | "tsx": "^4.19.3", 31 | "typescript": "^5.8.2" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/src/openai-responses.ts: -------------------------------------------------------------------------------- 1 | import { ObsyClient, obsyExpress } from "@obsy-ai/sdk"; 2 | import "dotenv/config"; 3 | import express from "express"; 4 | import OpenAI from "openai"; 5 | 6 | // use zod instead of this shit 7 | const env = { 8 | OPENAI_API_KEY: process.env.OPENAI_API_KEY!, 9 | OBSY_API_KEY: process.env.OBSY_API_KEY!, 10 | OBSY_PROJECT_ID: process.env.OBSY_PROJECT_ID!, 11 | }; 12 | 13 | const app = express(); 14 | 15 | app.use(express.json()); 16 | 17 | const openai = new OpenAI({ 18 | apiKey: env.OPENAI_API_KEY, 19 | }); 20 | 21 | // 1. create Obsy client 22 | const obsy = new ObsyClient({ 23 | apiKey: env.OBSY_API_KEY!, 24 | projectId: env.OBSY_PROJECT_ID!, 25 | }); 26 | 27 | // 2. instrument OpenAI client 28 | obsy.instrument(openai); 29 | 30 | // 3. enable tracing for each request 31 | app.use(obsyExpress({ client: obsy })); 32 | 33 | app.post("/chat", async (req, res) => { 34 | const { message } = req.body; 35 | if (!message) { 36 | res.status(400).json({ error: "no message provided" }); 37 | return; 38 | } 39 | 40 | const response = await openai.responses.create({ 41 | model: "gpt-4o-mini", 42 | instructions: "You are a coding assistant that talks like a pirate", 43 | input: "Are semicolons optional in JavaScript?", 44 | }); 45 | 46 | res.json(response.output_text); 47 | }); 48 | 49 | const PORT = 6969; 50 | app.listen(PORT, () => { 51 | console.log(`Server is running on port ${PORT}`); 52 | }); 53 | -------------------------------------------------------------------------------- /examples/src/openai.ts: -------------------------------------------------------------------------------- 1 | import { ObsyClient, obsyExpress } from "@obsy-ai/sdk"; 2 | import "dotenv/config"; 3 | import express from "express"; 4 | import OpenAI from "openai"; 5 | 6 | // use zod instead of this shit 7 | const env = { 8 | GROQ_API_KEY: process.env.GROQ_API_KEY!, 9 | 10 | OBSY_API_KEY: process.env.OBSY_API_KEY!, 11 | OBSY_PROJECT_ID: process.env.OBSY_PROJECT_ID!, 12 | }; 13 | 14 | const openai = new OpenAI({ 15 | apiKey: env.GROQ_API_KEY, 16 | baseURL: "https://api.groq.com/openai/v1", 17 | }); 18 | 19 | const app = express(); 20 | 21 | app.use(express.json()); 22 | 23 | // 1. create Obsy client 24 | const obsy = new ObsyClient({ 25 | apiKey: env.OBSY_API_KEY!, 26 | projectId: env.OBSY_PROJECT_ID!, 27 | }); 28 | 29 | // 2. instrument OpenAI client 30 | obsy.instrument(openai); 31 | 32 | // 3. enable tracing for each request 33 | app.use(obsyExpress({ client: obsy })); 34 | 35 | app.post("/chat", async (req, res) => { 36 | const { message } = req.body; 37 | if (!message) { 38 | res.status(400).json({ error: "no message provided" }); 39 | return; 40 | } 41 | 42 | const response = await openai.chat.completions.create({ 43 | messages: [{ role: "user", content: message }], 44 | model: "llama3-8b-8192", 45 | stream: true, // supports both streaming and non-streaming requests 46 | }); 47 | 48 | for await (const chunk of response) { 49 | res.write(JSON.stringify(chunk)); 50 | } 51 | 52 | res.end(); 53 | }); 54 | 55 | const PORT = 6969; 56 | app.listen(PORT, () => { 57 | console.log(`Server is running on port ${PORT}`); 58 | }); 59 | -------------------------------------------------------------------------------- /examples/src/rag.ts: -------------------------------------------------------------------------------- 1 | import { ObsyClient, obsyExpress } from "@obsy-ai/sdk"; 2 | import { Pinecone } from "@pinecone-database/pinecone"; 3 | import "dotenv/config"; 4 | import express from "express"; 5 | import OpenAI from "openai"; 6 | import type { ChatCompletionChunk, ChatCompletionMessageParam } from "openai/resources/chat/completions"; 7 | 8 | // use zod instead of this shit 9 | const env = { 10 | GROQ_API_KEY: process.env.GROQ_API_KEY!, 11 | 12 | OBSY_API_KEY: process.env.OBSY_API_KEY!, 13 | OBSY_PROJECT_ID: process.env.OBSY_PROJECT_ID!, 14 | 15 | PINECONE_API_KEY: process.env.PINECONE_API_KEY!, 16 | PINECONE_INDEX: process.env.PINECONE_INDEX!, 17 | PINECONE_NAMESPACE: process.env.PINECONE_NAMESPACE!, 18 | }; 19 | 20 | const app = express(); 21 | 22 | app.use(express.json()); 23 | 24 | // initialize OpenAI client 25 | const openai = new OpenAI({ 26 | apiKey: env.GROQ_API_KEY, 27 | baseURL: "https://api.groq.com/openai/v1", 28 | }); 29 | 30 | // initialize Pinecone client 31 | const pinecone = new Pinecone({ 32 | apiKey: env.PINECONE_API_KEY!, 33 | }); 34 | 35 | // 1. initialize Obsy client with local server URL 36 | const obsy = new ObsyClient({ 37 | apiKey: env.OBSY_API_KEY!, 38 | projectId: env.OBSY_PROJECT_ID!, 39 | }); 40 | 41 | // 2. instrument OpenAI and Pinecone clients 42 | obsy.instrument(openai).instrument(pinecone); 43 | 44 | // 3. enable tracing for each request 45 | app.use(obsyExpress({ client: obsy })); 46 | 47 | // store conversation history for RAG demo 48 | let messageHistory: ChatCompletionMessageParam[] = []; 49 | 50 | const pineconeNs = pinecone.index(env.PINECONE_INDEX!).namespace(env.PINECONE_NAMESPACE!); 51 | 52 | app.post("/rag", async (req, res) => { 53 | const { message } = req.body; 54 | 55 | // add user message to history 56 | messageHistory.push({ role: "user", content: message }); 57 | 58 | // generate search query using LLM 59 | const searchQueryResponse = await openai.chat.completions.create({ 60 | messages: [ 61 | { 62 | role: "system", 63 | content: 64 | "Generate a CONCISE search query, like how people search on Google, to find relevant information for the user's question. Focus on key terms and concepts. Just return the search query, no other text.", 65 | }, 66 | ...messageHistory, 67 | ], 68 | model: "llama3-8b-8192", 69 | stream: false, 70 | }); 71 | 72 | const searchQuery = searchQueryResponse.choices[0].message.content || ""; 73 | 74 | // generate embeddings for the search query 75 | const embeddings = await pinecone.inference.embed("llama-text-embed-v2", [searchQuery], { 76 | inputType: "passage", 77 | truncate: "END", 78 | }); 79 | 80 | const vector = 81 | embeddings.data[0].vectorType === "dense" ? embeddings.data[0].values : embeddings.data[0].sparseValues; 82 | 83 | // query Pinecone (retrieval) 84 | const results = await pineconeNs.query({ 85 | vector, 86 | topK: 3, 87 | includeMetadata: true, 88 | }); 89 | 90 | // finally "augment" the generation lol 91 | const docs = results.matches.map((match) => match.metadata?.text || "").join("\n\n"); 92 | const finalResponseStream = await openai.chat.completions.create({ 93 | messages: [ 94 | { 95 | role: "system", 96 | content: `You are a helpful assistant. Answer the user's question based on the following context:\n\n${ 97 | docs || "" 98 | }\n\nIf the answer is not available in the context, say "I don't know".`, 99 | }, 100 | ...messageHistory, 101 | ], 102 | model: "llama3-8b-8192", 103 | stream: true, 104 | }); 105 | 106 | const chunks: ChatCompletionChunk[] = []; 107 | 108 | // set response headers 109 | res.setHeader("Content-Type", "text/event-stream"); 110 | res.setHeader("Cache-Control", "no-cache"); 111 | res.setHeader("Connection", "keep-alive"); 112 | 113 | for await (const chunk of finalResponseStream) { 114 | // write it in EventStream format 115 | res.write(`data: ${JSON.stringify(chunk)}\n\n`); 116 | chunks.push(chunk); 117 | } 118 | 119 | res.end(); 120 | 121 | // merge chunks and add to history 122 | messageHistory.push({ 123 | role: "assistant", 124 | content: chunks.map((c) => c.choices[0].delta.content).join(""), 125 | }); 126 | }); 127 | 128 | app.get("/", (req, res) => { 129 | res.sendFile(path.resolve(import.meta.dirname, "./index.html")); 130 | }); 131 | 132 | const PORT = 6969; 133 | app.listen(PORT, () => { 134 | console.log(`Server is running on port ${PORT}`); 135 | }); 136 | 137 | // the following code is just for you to generate embeddings from the kb.txt file 138 | // and store them in Pinecone 139 | 140 | import fs from "node:fs/promises"; 141 | import path from "node:path"; 142 | 143 | async function consume(filePath: string) { 144 | try { 145 | // Read file content 146 | const fileContent = await fs.readFile(filePath, "utf-8"); 147 | 148 | // Split into paragraphs (split on double newline) 149 | const paragraphs = fileContent.split(/\n\s*\n/).filter((p) => p.trim()); 150 | 151 | // Process each paragraph 152 | for (let i = 0; i < paragraphs.length; i += 4) { 153 | let paragraph = paragraphs[i]; 154 | for (let j = 1; j < 4; j++) { 155 | if (paragraphs[i + j]) { 156 | paragraph += `\n\n${paragraphs[i + j]}`; 157 | } 158 | } 159 | 160 | // generate embeddings docs: https://docs.pinecone.io/guides/inference/generate-embeddings 161 | const embeddings = await pinecone.inference.embed("llama-text-embed-v2", [paragraph], { 162 | inputType: "passage", 163 | truncate: "END", 164 | }); 165 | 166 | const vector = 167 | embeddings.data[0].vectorType === "dense" ? embeddings.data[0].values : embeddings.data[0].sparseValues; 168 | 169 | // Store in Pinecone 170 | await pineconeNs.upsert([ 171 | { 172 | id: `doc_${i}`, 173 | values: vector, 174 | 175 | metadata: { 176 | text: paragraph, 177 | source: filePath, 178 | }, 179 | }, 180 | ]); 181 | 182 | console.log(`Processed paragraph ${i + 1} of ${paragraphs.length}`); 183 | } 184 | 185 | console.log("File processing complete"); 186 | } catch (error) { 187 | console.error("Error processing file:", error); 188 | throw error; 189 | } 190 | } 191 | 192 | // const kb = path.join(import.meta.dirname, "./kb.txt"); 193 | // consume(kb); 194 | -------------------------------------------------------------------------------- /examples/src/vercel-ai-sdk.ts: -------------------------------------------------------------------------------- 1 | import { openai } from "@ai-sdk/openai"; 2 | import { ObsyClient, obsyExpress } from "@obsy-ai/sdk"; 3 | import { 4 | generateObject as aiGenerateObject, 5 | generateText as aiGenerateText, 6 | streamObject as aiStreamObject, 7 | streamText as aiStreamText, 8 | } from "ai"; 9 | import "dotenv/config"; 10 | import express from "express"; 11 | import { z } from "zod"; 12 | 13 | const app = express(); 14 | 15 | const env = { 16 | OPENAI_API_KEY: process.env.OPENAI_API_KEY!, 17 | 18 | OBSY_API_KEY: process.env.OBSY_API_KEY!, 19 | OBSY_PROJECT_ID: process.env.OBSY_PROJECT_ID!, 20 | }; 21 | 22 | app.use(express.json()); 23 | 24 | // 1. initialize Obsy client with local server URL 25 | const obsy = new ObsyClient({ 26 | apiKey: env.OBSY_API_KEY!, 27 | projectId: env.OBSY_PROJECT_ID!, 28 | }); 29 | 30 | // 2. instrument functions from Vercel AI SDK 31 | const { generateText, streamText, generateObject, streamObject } = obsy.instrumentVercelAI({ 32 | generateText: aiGenerateText, 33 | streamText: aiStreamText, 34 | generateObject: aiGenerateObject, 35 | streamObject: aiStreamObject, 36 | }); 37 | 38 | // 3. enable tracing for each request 39 | app.use(obsyExpress({ client: obsy })); 40 | 41 | // docs: https://sdk.vercel.ai/docs/reference/ai-sdk-core/generate-text 42 | app.post("/generate-text", async (req, res) => { 43 | const { message } = req.body; 44 | 45 | const result = await generateText({ 46 | model: openai("gpt-4o-mini"), 47 | prompt: message, 48 | }); 49 | 50 | res.json(result); 51 | }); 52 | 53 | // docs: https://sdk.vercel.ai/docs/reference/ai-sdk-core/stream-text 54 | app.post("/stream-text", async (req, res) => { 55 | const { message } = req.body; 56 | 57 | const result = streamText({ 58 | model: openai("gpt-4o-mini"), 59 | prompt: message, 60 | }); 61 | 62 | for await (const chunk of result.textStream) { 63 | res.write(chunk); 64 | } 65 | 66 | res.end(); 67 | }); 68 | 69 | // docs: https://sdk.vercel.ai/docs/reference/ai-sdk-core/generate-object 70 | app.post("/generate-object", async (req, res) => { 71 | const { foodItem } = req.body; 72 | 73 | const result = await generateObject({ 74 | model: openai("gpt-4o-mini"), 75 | schema: z.object({ 76 | recipe: z.object({ 77 | name: z.string(), 78 | ingredients: z.array( 79 | z.object({ 80 | name: z.string(), 81 | amount: z.string(), 82 | }) 83 | ), 84 | steps: z.array(z.string()), 85 | }), 86 | }), 87 | prompt: `Generate a recipe for ${foodItem}.`, 88 | }); 89 | 90 | res.json(result); 91 | }); 92 | 93 | // docs: https://sdk.vercel.ai/docs/reference/ai-sdk-core/stream-object 94 | app.post("/stream-object", async (req, res) => { 95 | const { foodItem } = req.body; 96 | 97 | const result = streamObject({ 98 | model: openai("gpt-4o-mini"), 99 | schema: z.object({ 100 | recipe: z.object({ 101 | name: z.string(), 102 | ingredients: z.array( 103 | z.object({ 104 | name: z.string(), 105 | amount: z.string(), 106 | }) 107 | ), 108 | steps: z.array(z.string()), 109 | }), 110 | }), 111 | prompt: `Generate a recipe for ${foodItem}.`, 112 | }); 113 | 114 | let lastPartialObject: any | undefined = undefined; 115 | for await (const partialObject of result.partialObjectStream) { 116 | console.clear(); 117 | console.log(partialObject); 118 | lastPartialObject = partialObject; 119 | } 120 | 121 | res.json(lastPartialObject); 122 | }); 123 | 124 | const PORT = 6969; 125 | app.listen(PORT, () => { 126 | console.log(`Server is running on port ${PORT}`); 127 | }); 128 | -------------------------------------------------------------------------------- /examples/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "Node16", 4 | "moduleResolution": "node16", 5 | "target": "ESNext", 6 | "outDir": "./dist", 7 | "strict": true, 8 | "baseUrl": "." 9 | }, 10 | "include": ["src/**/*"] 11 | } 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@obsy-ai/sdk", 3 | "description": "See what your AI is really doing.", 4 | "license": "ISC", 5 | "author": "Biraj the Obsy daddy rocks ", 6 | "homepage": "https://obsy.dev/", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/obsy-ai/sdk-node.git" 10 | }, 11 | "version": "0.0.3", 12 | "main": "dist/index.js", 13 | "module": "dist/index.mjs", 14 | "types": "dist/index.d.js", 15 | "type": "module", 16 | "scripts": { 17 | "build": "rm -f obsy-ai-sdk-*.tgz && tsup", 18 | "publish": "npm run build && npm publish --access public", 19 | "check": "tsc --noEmit", 20 | "start": "npm run build && node dist/index.js" 21 | }, 22 | "imports": { 23 | "#src/*": "./dist/*" 24 | }, 25 | "keywords": [ 26 | "ai", 27 | "observability", 28 | "tracing", 29 | "llm", 30 | "openai", 31 | "tracing" 32 | ], 33 | "files": [ 34 | "/dist" 35 | ], 36 | "tsup": { 37 | "entry": [ 38 | "src/index.ts" 39 | ], 40 | "format": [ 41 | "cjs", 42 | "esm" 43 | ], 44 | "dts": true, 45 | "shims": true, 46 | "skipNodeModulesBundle": true, 47 | "splitting": false, 48 | "sourcemap": true, 49 | "clean": true 50 | }, 51 | "devDependencies": { 52 | "@pinecone-database/pinecone": "^5.1.1", 53 | "@types/express": "^5.0.1", 54 | "@types/json-schema": "^7.0.15", 55 | "@types/node": "^22.12.0", 56 | "ai": "^4.2.2", 57 | "openai": "^4.89.0", 58 | "tsup": "^8.4.0", 59 | "typescript": "^5.7.3" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/client.ts: -------------------------------------------------------------------------------- 1 | import { AsyncLocalStorage } from "node:async_hooks"; 2 | 3 | import { Pinecone } from "@pinecone-database/pinecone"; 4 | import OpenAI from "openai"; 5 | 6 | import { DEFAULT_SENSITIVE_KEYS } from "./constants.js"; 7 | import { instrumentOpenAI } from "./instruments/openai.js"; 8 | import { instrumentPinecone } from "./instruments/pinecone.js"; 9 | import { instrumentVercelAI, type VercelAIFunctionsArg } from "./instruments/vercel-ai.js"; 10 | import { ObsyTrace } from "./trace.js"; 11 | import type { AnyFunction, ObsyClientOptions, ObsyTraceOptions, OpCall, OpTracerFn } from "./types/index.js"; 12 | import { logger } from "./utils/logger.js"; 13 | 14 | interface TraceContext { 15 | /** 16 | * Reference to the trace object 17 | */ 18 | trace: ObsyTrace; 19 | } 20 | 21 | export class ObsyClient { 22 | readonly projectId: string; 23 | readonly sensitiveKeys: Set; 24 | readonly #apiKey: string; 25 | readonly #sinkUrl: string; 26 | readonly #storage: AsyncLocalStorage; 27 | readonly #pendingTraces: Map; 28 | 29 | constructor(options: ObsyClientOptions) { 30 | this.#apiKey = options.apiKey; 31 | this.projectId = options.projectId; 32 | this.sensitiveKeys = options.sensitiveKeys ?? ObsyClient.getDefaultSensitiveKeys(); 33 | this.#sinkUrl = options.sinkUrl ?? "https://api.obsy.com"; 34 | this.#sinkUrl = `${this.#sinkUrl}/v1/sdk/${this.projectId}/traces`; 35 | this.#storage = new AsyncLocalStorage(); 36 | this.#pendingTraces = new Map(); 37 | this.onUncaughtException(); 38 | } 39 | 40 | onUncaughtException() { 41 | process.on("uncaughtException", async (err) => { 42 | logger.error("uncaught exception", err); 43 | 44 | // be extra paranoid and exit if we fail to send pending traces 45 | // this should (ideally) never happen, but i'm paranoid 46 | process.on("uncaughtException", (err) => { 47 | logger.error("uncaught exception while sending pending traces", err); 48 | process.exit(1); 49 | }); 50 | 51 | const pendingTraces = Array.from(this.#pendingTraces.values()); 52 | if (pendingTraces.length > 0) { 53 | logger.info(`sending ${pendingTraces.length} pending traces`); 54 | 55 | const pendingTracesPromises = []; 56 | for (const trace of pendingTraces) { 57 | pendingTracesPromises.push(trace.end()); 58 | } 59 | 60 | await Promise.all(pendingTracesPromises); 61 | logger.info("done sending pending traces"); 62 | } 63 | 64 | process.exit(1); 65 | }); 66 | } 67 | 68 | static getDefaultSensitiveKeys() { 69 | return new Set(DEFAULT_SENSITIVE_KEYS); 70 | } 71 | 72 | runInContext(trace: ObsyTrace, fn: (...args: any[]) => T) { 73 | const context: TraceContext = { 74 | trace, 75 | }; 76 | this.#pendingTraces.set(trace.getId(), trace); 77 | return this.#storage.run(context, fn); 78 | } 79 | 80 | /** 81 | * Must be called after the trace is sent to the server. 82 | * This will remove it from the list of pending traces. 83 | * 84 | * @param trace 85 | */ 86 | #traceSent(trace: ObsyTrace) { 87 | this.#pendingTraces.delete(trace.getId()); 88 | } 89 | 90 | /** 91 | * Create a new trace 92 | * 93 | * @param options - options to create the trace with 94 | * @returns A new `ObsyTrace` instance 95 | */ 96 | createNewTrace(options: ObsyTraceOptions = {}) { 97 | return new ObsyTrace(this, options); 98 | } 99 | 100 | /** 101 | * Get the currently active trace. Must be called only from functions whose 102 | * root invocation is wrapped in `runInContext`. 103 | * 104 | * @returns The current trace or `undefined` if no trace is active 105 | */ 106 | getCurrentTrace() { 107 | return this.#storage.getStore()?.trace; 108 | } 109 | 110 | async sendTrace(trace: ObsyTrace) { 111 | try { 112 | const traceJSON = trace.toJSONSerializable(); 113 | if (traceJSON.operations.length === 0) { 114 | return; 115 | } 116 | 117 | const response = await fetch(this.#sinkUrl, { 118 | method: "POST", 119 | headers: { 120 | "Content-Type": "application/json", 121 | "x-api-key": this.#apiKey, 122 | }, 123 | body: JSON.stringify(traceJSON), 124 | }); 125 | 126 | if (!response.ok) { 127 | const error = await response.json(); 128 | logger.error("failed to send trace:", error); 129 | } 130 | } catch (error) { 131 | logger.error("error sending trace:", error); 132 | } finally { 133 | this.#traceSent(trace); 134 | } 135 | } 136 | 137 | #traceOp: OpTracerFn = (opCall: OpCall) => { 138 | const trace = this.getCurrentTrace(); 139 | if (!trace) { 140 | throw new Error("no trace found for instrumenting client"); 141 | } 142 | 143 | return trace.traceOp(opCall); 144 | }; 145 | 146 | instrument(obj: OpenAI | Pinecone) { 147 | if (obj instanceof OpenAI) { 148 | instrumentOpenAI(obj, this.#traceOp.bind(this)); 149 | } else if (obj instanceof Pinecone) { 150 | instrumentPinecone(obj, this.#traceOp.bind(this)); 151 | } else { 152 | throw new Error("unsupported client"); 153 | } 154 | 155 | return this; 156 | } 157 | 158 | instrumentVercelAI(functions: T) { 159 | return instrumentVercelAI(functions, this.#traceOp.bind(this)); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Default sensitive keys to redact from instrumented data. 3 | * 4 | * DO NOT MUTATE. 5 | */ 6 | export const DEFAULT_SENSITIVE_KEYS = new Set([ 7 | "api_key", 8 | "api_secret", 9 | "authorization", 10 | "cookie", 11 | "password", 12 | "proxy-authorization", 13 | "secret", 14 | "token", 15 | "x-amz-security-token", 16 | "x-api-key", 17 | ]); 18 | -------------------------------------------------------------------------------- /src/express.ts: -------------------------------------------------------------------------------- 1 | import type { NextFunction, Request, Response } from "express"; 2 | 3 | import { type ObsyClient } from "./client.js"; 4 | import { ObsyTrace } from "./trace.js"; 5 | 6 | declare global { 7 | namespace Express { 8 | interface Request { 9 | trace: ObsyTrace; 10 | } 11 | } 12 | } 13 | 14 | interface ObsyExpressOptions { 15 | client: ObsyClient; 16 | } 17 | 18 | /** 19 | * Express middleware to automatically start a new Obsy trace for each request. 20 | * 21 | * @param client Obsy client 22 | */ 23 | export function obsyExpress(options: ObsyExpressOptions) { 24 | return (req: Request, res: Response, next: NextFunction) => { 25 | const trace = options.client.createNewTrace({ 26 | httpRequest: { 27 | url: req.url, 28 | method: req.method, 29 | query: req.query, 30 | headers: req.headers, 31 | body: req.body, 32 | }, 33 | }); 34 | 35 | req.trace = trace; 36 | 37 | // auto-end trace on response finish 38 | res.on("finish", () => { 39 | trace.setHttpResponse({ 40 | statusCode: res.statusCode, 41 | headers: res.getHeaders(), 42 | }); 43 | trace.end(); 44 | }); 45 | trace.runInContext(next); 46 | }; 47 | } 48 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./client.js"; 2 | export * from "./express.js"; 3 | export * from "./types/index.js"; 4 | -------------------------------------------------------------------------------- /src/instruments/openai.ts: -------------------------------------------------------------------------------- 1 | import type OpenAI from "openai"; 2 | 3 | import type { OpTracerFn } from "#src/types/index.js"; 4 | import type { OaiCompletionCreateType, OaiResponsesCreateType } from "#src/types/openai.js"; 5 | 6 | /** 7 | * Instruments an OpenAI client to trace its operations. 8 | * 9 | * Supports 10 | * - `openai.chat.completions.create` 11 | * - `openai.responses.create` (new) 12 | */ 13 | export function instrumentOpenAI( 14 | client: OpenAI, 15 | opTracer: OpTracerFn 16 | ): OpenAI { 17 | // create a proxy for the completions method to replace the original 18 | const completionsOrig = client.chat.completions.create.bind(client.chat.completions); 19 | client.chat.completions.create = new Proxy(completionsOrig, { 20 | apply: (target, thisArg, args: Parameters) => { 21 | return opTracer({ 22 | type: "openai.chat.completions.create", 23 | fn: target, 24 | thisArg, 25 | args, 26 | label: args[0].stream ? "openai-chat-stream" : "openai-chat-completion", 27 | }); 28 | }, 29 | }); 30 | 31 | // create a proxy for the responses method to replace the original 32 | const responsesOrig = client.responses.create.bind(client.responses); 33 | client.responses.create = new Proxy(responsesOrig, { 34 | apply: (target, thisArg, args: Parameters) => { 35 | return opTracer({ 36 | type: "openai.responses.create", 37 | fn: target, 38 | thisArg, 39 | args, 40 | label: "openai-responses-create", 41 | }); 42 | }, 43 | }); 44 | 45 | return client; 46 | } 47 | -------------------------------------------------------------------------------- /src/instruments/pinecone.ts: -------------------------------------------------------------------------------- 1 | import type { Pinecone } from "@pinecone-database/pinecone"; 2 | 3 | import type { OpTracerFn } from "#src/types/index.js"; 4 | import type { PineconeIndexQueryType } from "#src/types/pinecone.js"; 5 | 6 | /** 7 | * Instruments a Pinecone client to trace its operations. 8 | * 9 | * Supports 10 | * - `pinecone.index("").query` 11 | * - `pinecone.index("").namespace("").query` 12 | */ 13 | export function instrumentPinecone(client: Pinecone, opTracer: OpTracerFn): Pinecone { 14 | // first create a proxy for the index method 15 | const indexOrig = client.index.bind(client); 16 | client.index = new Proxy(indexOrig, { 17 | apply: (target, thisArg, args: any) => { 18 | const index = target.apply(thisArg, args); 19 | 20 | // now we can proxy the query method on the returned index 21 | const queryOrig = index.query.bind(index); 22 | index.query = new Proxy(queryOrig, { 23 | apply: (target, thisArg, args: any) => { 24 | return opTracer({ 25 | type: "pinecone.index.query", 26 | fn: target, 27 | thisArg, 28 | args, 29 | label: "pinecone-query", 30 | }); 31 | }, 32 | }); 33 | 34 | // we also have to proxy index.namespace.query 35 | const namespaceOrig = index.namespace.bind(index); 36 | index.namespace = new Proxy(namespaceOrig, { 37 | apply: (target, thisArg, args: any) => { 38 | const ns = target.apply(thisArg, args); 39 | 40 | // now we can proxy the query method on the returned index with namespace 41 | const queryOrig = ns.query.bind(ns); 42 | ns.query = new Proxy(queryOrig, { 43 | apply: (target, thisArg, args: any) => { 44 | return opTracer({ 45 | type: "pinecone.index.namespace.query", 46 | fn: target, 47 | thisArg, 48 | args, 49 | label: "pinecone-namespace-query", 50 | }); 51 | }, 52 | }); 53 | 54 | return ns; 55 | }, 56 | }); 57 | return index; 58 | }, 59 | }); 60 | 61 | return client; 62 | } 63 | -------------------------------------------------------------------------------- /src/instruments/vercel-ai.ts: -------------------------------------------------------------------------------- 1 | import { 2 | embedMany as aiEmbedMany, 3 | generateObject as aiGenerateObject, 4 | generateText as aiGenerateText, 5 | streamObject as aiStreamObject, 6 | streamText as aiStreamText, 7 | } from "ai"; 8 | 9 | import type { OpTracerFn, VercelAiOpType } from "#src/types/index.js"; 10 | import type { 11 | VercelAIEmbedManyType, 12 | VercelAIGenerateObjectType, 13 | VercelAIGenerateTextType, 14 | VercelAIInstrumentedFunctions, 15 | VercelAIStreamObjectType, 16 | VercelAIStreamTextType, 17 | } from "#src/types/vercel-ai.js"; 18 | 19 | export type VercelAIFunctionsArg = Partial<{ 20 | embedMany: VercelAIEmbedManyType; 21 | generateText: VercelAIGenerateTextType; 22 | streamText: VercelAIStreamTextType; 23 | generateObject: VercelAIGenerateObjectType; 24 | streamObject: VercelAIStreamObjectType; 25 | }>; 26 | 27 | export function instrumentVercelAI( 28 | functions: T, 29 | opTracer: OpTracerFn 30 | ): T { 31 | if (Object.keys(functions).length === 0) { 32 | throw new Error("no functions to instrument"); 33 | } 34 | 35 | const { embedMany, generateText, streamText, generateObject, streamObject } = functions; 36 | const result = {} as T; 37 | 38 | const createProxy = (params: { 39 | fn: F; 40 | originalFn: Function; 41 | type: VercelAiOpType; 42 | label: string; 43 | }) => { 44 | const { fn, originalFn, type, label } = params; 45 | 46 | if (fn !== originalFn) { 47 | throw new Error(`given ${fn.name} is not a function from Vercel's AI SDK`); 48 | } 49 | 50 | return new Proxy(fn, { 51 | apply: (target, thisArg, args: Parameters) => { 52 | return opTracer({ 53 | type, 54 | fn: target, 55 | thisArg, 56 | args, 57 | label, 58 | }); 59 | }, 60 | }); 61 | }; 62 | 63 | if (embedMany) { 64 | result.embedMany = createProxy({ 65 | fn: embedMany, 66 | originalFn: aiEmbedMany, 67 | type: "ai.embedMany", 68 | label: "vercel-ai-embed-many", 69 | }); 70 | } 71 | 72 | if (generateText) { 73 | result.generateText = createProxy({ 74 | fn: generateText, 75 | originalFn: aiGenerateText, 76 | type: "ai.generateText", 77 | label: "vercel-ai-generate-text", 78 | }); 79 | } 80 | 81 | if (streamText) { 82 | result.streamText = createProxy({ 83 | fn: streamText, 84 | originalFn: aiStreamText, 85 | type: "ai.streamText", 86 | label: "vercel-ai-stream-text", 87 | }); 88 | } 89 | 90 | if (generateObject) { 91 | result.generateObject = createProxy({ 92 | fn: generateObject, 93 | originalFn: aiGenerateObject, 94 | type: "ai.generateObject", 95 | label: "vercel-ai-generate-object", 96 | }); 97 | } 98 | 99 | if (streamObject) { 100 | result.streamObject = createProxy({ 101 | fn: streamObject, 102 | originalFn: aiStreamObject, 103 | type: "ai.streamObject", 104 | label: "vercel-ai-stream-object", 105 | }); 106 | } 107 | 108 | return result; 109 | } 110 | -------------------------------------------------------------------------------- /src/trace.ts: -------------------------------------------------------------------------------- 1 | import crypto from "node:crypto"; 2 | 3 | import type { ChatCompletionChunk } from "openai/resources/chat/completions"; 4 | import { ResponseStreamEvent } from "openai/resources/responses/responses"; 5 | import { Stream } from "openai/streaming"; 6 | 7 | import type { ObsyClient } from "./client.js"; 8 | import type { 9 | AnyFunction, 10 | ObsyTraceOptions, 11 | OpCall, 12 | OpTracerFn, 13 | OpType, 14 | Operation, 15 | OperationVendor, 16 | TraceHttpRequest, 17 | TraceHttpResponse, 18 | } from "./types/index.js"; 19 | import type { OaiCompletionCreateReturnType, OaiCompletionCreateType, OaiResponsesCreateType } from "./types/openai.js"; 20 | import type { PineconeIndexQueryType } from "./types/pinecone.js"; 21 | import type { 22 | VercelAIEmbedManyType, 23 | VercelAIGenerateObjectReturnType, 24 | VercelAIGenerateObjectType, 25 | VercelAIGenerateTextReturnType, 26 | VercelAIGenerateTextType, 27 | VercelAIStreamObjectReturnType, 28 | VercelAIStreamObjectType, 29 | VercelAIStreamTextType, 30 | } from "./types/vercel-ai.js"; 31 | import { redactSensitiveKeys } from "./utils/index.js"; 32 | import { logger } from "./utils/logger.js"; 33 | 34 | export class ObsyTrace { 35 | readonly #id: string; 36 | readonly #client: ObsyClient; 37 | readonly #startedAt: number; 38 | #endedAt: number | null; 39 | #duration: number | null; 40 | #operations: Operation[]; 41 | readonly #httpRequest?: TraceHttpRequest; 42 | #httpResponse?: TraceHttpResponse; 43 | #metadata?: Record; 44 | #opTracers: Record>; 45 | 46 | constructor(client: ObsyClient, options: ObsyTraceOptions = {}) { 47 | this.#id = crypto.randomUUID(); 48 | this.#client = client; 49 | this.#startedAt = Date.now(); 50 | this.#endedAt = null; 51 | this.#duration = null; 52 | this.#operations = []; 53 | this.#httpRequest = options.httpRequest; 54 | this.#metadata = options.metadata; 55 | this.#opTracers = { 56 | "openai.responses.create": this.traceOpenAi.bind(this), 57 | "openai.chat.completions.create": this.traceOpenAi.bind(this), 58 | "pinecone.index.query": this.tracePineconeQuery.bind(this), 59 | "pinecone.index.namespace.query": this.tracePineconeQuery.bind(this), 60 | "ai.embedMany": this.traceVercelAIEmbedMany.bind(this), 61 | "ai.generateText": this.traceVercelAIGenerateTextOrObject.bind(this), 62 | "ai.streamText": this.traceVercelAIStreamText.bind(this), 63 | "ai.generateObject": this.traceVercelAIGenerateTextOrObject.bind(this), 64 | "ai.streamObject": this.traceVercelAIStreamObject.bind(this), 65 | }; 66 | } 67 | 68 | getId() { 69 | return this.#id; 70 | } 71 | 72 | traceOp(opCall: OpCall) { 73 | const tracer = this.#opTracers[opCall.type]; 74 | if (!tracer) { 75 | throw new Error(`no tracer found for type ${opCall.type}`); 76 | } 77 | 78 | return tracer(opCall); 79 | } 80 | 81 | private async traceOpenAi(opCall: OpCall) { 82 | const op = this.createOperation(opCall.label, "openai", opCall.type, opCall.args); 83 | op.result = { 84 | value: undefined, 85 | model: opCall.args[0].model, 86 | usage: undefined, 87 | }; 88 | 89 | try { 90 | const result: Awaited = await ( 91 | opCall.fn as any 92 | ).apply(opCall.thisArg, opCall.args); 93 | 94 | if (result instanceof Stream) { 95 | // traceOpenAiStream will asynchronously handle the stream & save the operation, hence 96 | // we're passing the operation object to it and returning the user's stream 97 | return this.traceOpenAiStream(result, op); 98 | } 99 | 100 | op.result.value = result; 101 | op.result.usage = result.usage; 102 | return result; 103 | } catch (err) { 104 | op.error = err; 105 | } finally { 106 | op.endedAt = Date.now(); 107 | op.duration = op.endedAt - op.startedAt; 108 | if (op.error) { 109 | throw op.error; 110 | } 111 | } 112 | } 113 | 114 | private traceOpenAiStream( 115 | stream: Stream, 116 | op: Operation 117 | ): Stream { 118 | const opResult = op.result; 119 | if (!opResult) { 120 | throw new Error("traceOpenAiStream(): operation.result must be initialized"); 121 | } 122 | 123 | if (!opResult.model) { 124 | throw new Error("traceOpenAiStream(): model must be present in operation.result"); 125 | } 126 | 127 | // split the stream into two - one for processing and one for the user 128 | const [processingStream, userStream] = stream.tee(); 129 | 130 | const chunks: Array = []; 131 | opResult.value = chunks; 132 | 133 | // asynchronously process our copy of the stream 134 | (async () => { 135 | try { 136 | for await (const chunk of processingStream) { 137 | if ("type" in chunk) { 138 | if (chunk.type === "response.completed") { 139 | opResult.usage = chunk.response.usage; 140 | } 141 | 142 | chunks.push(chunk); 143 | continue; 144 | } 145 | 146 | // when using Groq API, the usage is returned in the `x_groq` field 147 | if (!opResult.usage && (chunk.usage || (chunk as any).x_groq?.usage)) { 148 | opResult.usage = chunk.usage || (chunk as any).x_groq.usage; 149 | } 150 | 151 | chunks.push(chunk); 152 | } 153 | } catch (err: unknown) { 154 | op.error = err; 155 | } finally { 156 | op.endedAt = Date.now(); 157 | op.duration = op.endedAt - op.startedAt; 158 | if (op.error) { 159 | throw op.error; 160 | } 161 | } 162 | })(); 163 | 164 | // return the user's copy of the stream 165 | return userStream; 166 | } 167 | 168 | async tracePineconeQuery(opCall: OpCall) { 169 | // copy args to remove the big ass vector array from the trace payload 170 | const argsCopyForSavingInDb = [...opCall.args]; 171 | argsCopyForSavingInDb[0] = { 172 | ...argsCopyForSavingInDb[0], 173 | vector: "", 174 | } as any; 175 | 176 | const op = this.createOperation(opCall.label, "pinecone", opCall.type, argsCopyForSavingInDb); 177 | op.result = { 178 | value: undefined, 179 | usage: undefined, 180 | }; 181 | 182 | try { 183 | const result = await opCall.fn.apply(opCall.thisArg, opCall.args); 184 | op.result.value = result; 185 | op.result.usage = result.usage; 186 | return result; 187 | } catch (err) { 188 | op.error = err; 189 | throw err; 190 | } finally { 191 | op.endedAt = Date.now(); 192 | op.duration = op.endedAt - op.startedAt; 193 | if (op.error) { 194 | throw op.error; 195 | } 196 | } 197 | } 198 | 199 | async traceVercelAIEmbedMany(opCall: OpCall) { 200 | // TODO: implement 201 | } 202 | 203 | async traceVercelAIGenerateTextOrObject(opCall: OpCall) { 204 | const op = this.createOperation(opCall.label, "vercel", opCall.type, opCall.args); 205 | op.result = { 206 | value: undefined, 207 | model: opCall.args[0].model.modelId, 208 | usage: undefined, 209 | }; 210 | 211 | try { 212 | const result: Awaited = await ( 213 | opCall.fn as any 214 | ).apply(opCall.thisArg as any, opCall.args as any); 215 | op.result.value = result; 216 | op.result.usage = result.usage; 217 | return result; 218 | } catch (err) { 219 | op.error = err; 220 | } finally { 221 | op.endedAt = Date.now(); 222 | op.duration = op.endedAt - op.startedAt; 223 | if (op.error) { 224 | throw op.error; 225 | } 226 | } 227 | } 228 | 229 | traceVercelAIStreamText(opCall: OpCall) { 230 | const op = this.createOperation(opCall.label, "vercel", opCall.type, opCall.args); 231 | op.result = { 232 | value: undefined, 233 | model: opCall.args[0].model.modelId, 234 | usage: undefined, 235 | }; 236 | 237 | const chunks: Array = []; 238 | op.result.value = chunks; 239 | 240 | try { 241 | const result = opCall.fn.apply(opCall.thisArg, opCall.args); 242 | 243 | const [processingStream, userStream] = result.textStream.tee(); 244 | 245 | (async () => { 246 | try { 247 | for await (const chunk of processingStream) { 248 | chunks.push(chunk); 249 | } 250 | 251 | op.result!.usage = await result.usage; 252 | } catch (err) { 253 | op.error = err; 254 | throw err; 255 | } finally { 256 | op.endedAt = Date.now(); 257 | op.duration = op.endedAt - op.startedAt; 258 | if (op.error) { 259 | throw op.error; 260 | } 261 | } 262 | })(); 263 | 264 | return { 265 | ...result, 266 | textStream: userStream, 267 | }; 268 | } catch (err) { 269 | op.error = err; 270 | op.endedAt = Date.now(); 271 | op.duration = op.endedAt - op.startedAt; 272 | throw err; 273 | } 274 | } 275 | 276 | traceVercelAIStreamObject(opCall: OpCall): VercelAIStreamObjectReturnType { 277 | const op = this.createOperation(opCall.label, "vercel", opCall.type, opCall.args); 278 | op.result = { 279 | value: undefined, 280 | model: opCall.args[0].model.modelId, 281 | usage: undefined, 282 | }; 283 | 284 | try { 285 | const result = opCall.fn.apply(opCall.thisArg, opCall.args); 286 | 287 | const [processingStream, userStream] = result.partialObjectStream.tee(); 288 | 289 | (async () => { 290 | try { 291 | for await (const chunk of processingStream) { 292 | op.result!.value = chunk; 293 | } 294 | 295 | op.result!.usage = await result.usage; 296 | } catch (err) { 297 | op.error = err; 298 | throw err; 299 | } finally { 300 | op.endedAt = Date.now(); 301 | op.duration = op.endedAt - op.startedAt; 302 | if (op.error) { 303 | throw op.error; 304 | } 305 | } 306 | })(); 307 | 308 | return { 309 | ...result, 310 | partialObjectStream: userStream, 311 | }; 312 | } catch (err) { 313 | op.error = err; 314 | op.endedAt = Date.now(); 315 | op.duration = op.endedAt - op.startedAt; 316 | throw err; 317 | } 318 | } 319 | 320 | /** 321 | * Set the HTTP response for this trace. To be called when the HTTP response is finished. 322 | * 323 | * @param response - The HTTP response to set 324 | */ 325 | setHttpResponse(response: TraceHttpResponse) { 326 | this.#httpResponse = response; 327 | } 328 | 329 | async end() { 330 | try { 331 | this.#endedAt = Date.now(); 332 | this.#duration = this.#endedAt - this.#startedAt; 333 | await this.#client.sendTrace(this); 334 | } catch (err) { 335 | logger.error("failed to send trace:", err); 336 | } 337 | } 338 | 339 | /** 340 | * Creates a new operation in this trace. 341 | * 342 | * @param label - The label for the operation 343 | * @param vendor - The vendor of the operation 344 | * @param type - The type of operation 345 | * @param args - The arguments for the operation 346 | * @returns The created operation 347 | */ 348 | createOperation(label: string, vendor: OperationVendor, type: OpType, args: unknown[]) { 349 | const op: Operation = { 350 | traceId: this.#id, 351 | label, 352 | vendor, 353 | type, 354 | inputs: args, 355 | startedAt: Date.now(), 356 | }; 357 | 358 | this.#operations.push(op); 359 | return op; 360 | } 361 | 362 | /** 363 | * Runs a function in the context of this trace. Any traceable operations 364 | * called within the function will be recorded in this trace. 365 | * 366 | * @param fn - The function to run in the context of this trace. 367 | * @returns The result of the function. 368 | */ 369 | runInContext(fn: (...args: any[]) => T) { 370 | return this.#client.runInContext(this, fn); 371 | } 372 | 373 | /** 374 | * Convert the trace to a JSON serializable object. 375 | * 376 | * Some operations in the trace might have failed and thus have an Error object, which is not JSON serializable. 377 | * Use this method to get a JSON serializable object that can be sent to the server. 378 | * 379 | * @returns A JSON serializable object 380 | */ 381 | toJSONSerializable() { 382 | const trace = { 383 | id: this.#id, 384 | startedAt: this.#startedAt, 385 | endedAt: this.#endedAt, 386 | duration: this.#duration, 387 | operations: this.#operations, 388 | httpRequest: this.#httpRequest, 389 | httpResponse: this.#httpResponse, 390 | metadata: this.#metadata, 391 | }; 392 | 393 | // an operation might fail and it might have an Error object, which is not JSON serializable 394 | trace.operations.forEach((op) => { 395 | if (op.error instanceof Error) { 396 | op.error = { 397 | message: op.error.message, 398 | stack: op.error.stack, 399 | }; 400 | } 401 | }); 402 | 403 | // redact sensitive data before sending 404 | return redactSensitiveKeys(trace, this.#client.sensitiveKeys); 405 | } 406 | } 407 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains all the types that are shared between more than one file, 3 | * or have the potential to be used in more than one file, or imported 4 | * by the SDK users. 5 | * 6 | * Or the types that I just felt should be here. 7 | */ 8 | 9 | // ===== General or utility types ===== 10 | 11 | /** 12 | * A function that can be called with any arguments and return any value. 13 | */ 14 | export type AnyFunction = (...args: any[]) => any; 15 | 16 | // ===== Trace and operation types ===== 17 | 18 | /** 19 | * The vendor of the operation. 20 | * 21 | * If we want to trace OpenAI's completion for example, we would set the vendor to `openai`. 22 | */ 23 | export type OperationVendor = "openai" | "pinecone" | "vercel"; 24 | 25 | // make sure to update the list below in OperationType's JSDoc if you modify operation types 26 | export type OpenAiOpType = "openai.chat.completions.create" | "openai.responses.create"; 27 | export type PineconeOpType = "pinecone.index.query" | "pinecone.index.namespace.query"; 28 | export type VercelAiOpType = 29 | | "ai.embedMany" 30 | | "ai.generateText" 31 | | "ai.streamText" 32 | | "ai.generateObject" 33 | | "ai.streamObject"; 34 | 35 | /** 36 | * The type of operation. 37 | * 38 | * If we want to trace OpenAI's completion for example, we would set the type to `openai.chat.completions.create`. 39 | * 40 | * Supported types: 41 | * 1. OpenAI: 42 | * - `openai.chat.completions.create` 43 | * - `openai.responses.create` 44 | * 2. Pinecone: 45 | * - `pinecone.index.query` 46 | * - `pinecone.index.namespace.query` 47 | * 3. Vercel AI: 48 | * - `ai.embedMany` 49 | * - `ai.generateText` 50 | * - `ai.streamText` 51 | * - `ai.generateObject` 52 | * - `ai.streamObject` 53 | */ 54 | export type OpType = OpenAiOpType | PineconeOpType | VercelAiOpType; 55 | 56 | /** 57 | * This type represents a function call with its arguments and the context in which it is called. 58 | * It will be automatically traced by Obsy. 59 | */ 60 | export type OpCall = { 61 | /** 62 | * The type of operation 63 | */ 64 | type: OpType; 65 | 66 | /** 67 | * The function that is to be called 68 | */ 69 | fn: F; 70 | 71 | /** 72 | * The context in which the function should be called 73 | */ 74 | thisArg: ThisParameterType; 75 | 76 | /** 77 | * The arguments to the function 78 | */ 79 | args: Parameters; 80 | 81 | /** 82 | * The label to use for this operation 83 | */ 84 | label: string; 85 | }; 86 | 87 | export interface TraceHttpRequest { 88 | url: string; 89 | method: string; 90 | query: Record; 91 | headers: Record; 92 | body: Record; 93 | } 94 | 95 | export interface TraceHttpResponse { 96 | statusCode: number; 97 | headers: Record; 98 | body?: string; 99 | } 100 | 101 | export interface Operation { 102 | traceId: string; 103 | label: string; 104 | vendor: OperationVendor; 105 | type: OpType; 106 | inputs: unknown[]; 107 | startedAt: number; 108 | endedAt?: number; 109 | duration?: number; 110 | result?: { 111 | value?: any; 112 | model?: string; 113 | usage?: any; 114 | }; 115 | error?: any; 116 | } 117 | 118 | export interface ObsyTraceOptions { 119 | httpRequest?: TraceHttpRequest; 120 | metadata?: Record; 121 | } 122 | 123 | export type OpTracerFn = (op: OpCall) => ReturnType; 124 | 125 | // ===== Client types ===== 126 | 127 | /** 128 | * Options to initialize the Obsy client 129 | */ 130 | export interface ObsyClientOptions { 131 | /** 132 | * Your Obsy API key 133 | */ 134 | apiKey: string; 135 | 136 | /** 137 | * Your Obsy project ID 138 | */ 139 | projectId: string; 140 | 141 | /** 142 | * Optional URL to send traces to (defaults to https://api.obsy.com) 143 | */ 144 | sinkUrl?: string; 145 | 146 | /** 147 | * Set of sensitive keys to redact from instrumented data. See `getDefaultSensitiveKeys()` static method for default values. 148 | */ 149 | sensitiveKeys?: Set; 150 | } 151 | -------------------------------------------------------------------------------- /src/types/openai.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Types from OpenAI SDK. 3 | */ 4 | 5 | import type OpenAI from "openai"; 6 | 7 | /** 8 | * type of `OpenAI.prototype.chat.completions.create` method from OpenAI 9 | */ 10 | export type OaiCompletionCreateType = typeof OpenAI.prototype.chat.completions.create; 11 | 12 | /** 13 | * The return type of `OpenAI.prototype.chat.completions.create` method from OpenAI 14 | */ 15 | export type OaiCompletionCreateReturnType = ReturnType; 16 | 17 | /** 18 | * type of `OpenAI.prototype.responses.create` method from OpenAI 19 | */ 20 | export type OaiResponsesCreateType = typeof OpenAI.prototype.responses.create; 21 | 22 | /** 23 | * The return type of `OpenAI.prototype.responses.create` method from OpenAI 24 | */ 25 | export type OaiResponsesCreateReturnType = ReturnType; 26 | -------------------------------------------------------------------------------- /src/types/pinecone.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Types from Pinecone SDK. 3 | */ 4 | 5 | import type { Index } from "@pinecone-database/pinecone"; 6 | 7 | /** 8 | * The type of `Index.prototype.query` method from Pinecone 9 | */ 10 | export type PineconeIndexQueryType = typeof Index.prototype.query; 11 | -------------------------------------------------------------------------------- /src/types/vercel-ai.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Types from Vercel's AI SDK. 3 | */ 4 | 5 | import type { embedMany, generateObject, generateText, streamObject, streamText } from "ai"; 6 | 7 | /** 8 | * The type of the `embedMany` function from Vercel's `ai` package. 9 | */ 10 | export type VercelAIEmbedManyType = typeof embedMany; 11 | 12 | /** 13 | * The type of the `generateText` function from Vercel's `ai` package. 14 | */ 15 | export type VercelAIGenerateTextType = typeof generateText; 16 | 17 | /** 18 | * The return type of the `generateText` function from Vercel's `ai` package. 19 | */ 20 | export type VercelAIGenerateTextReturnType = ReturnType; 21 | 22 | /** 23 | * The type of the `streamText` function from Vercel's `ai` package. 24 | */ 25 | export type VercelAIStreamTextType = typeof streamText; 26 | 27 | /** 28 | * The type of the `generateObject` function from Vercel's `ai` package. 29 | */ 30 | export type VercelAIGenerateObjectType = typeof generateObject; 31 | 32 | /** 33 | * The return type of the `generateObject` function from Vercel's `ai` package. 34 | */ 35 | export type VercelAIGenerateObjectReturnType = ReturnType; 36 | 37 | /** 38 | * The type of the `streamObject` function from Vercel's `ai` package. 39 | */ 40 | export type VercelAIStreamObjectType = typeof streamObject; 41 | 42 | /** 43 | * The return type of the `streamObject` function from Vercel's `ai` package. 44 | */ 45 | export type VercelAIStreamObjectReturnType = ReturnType; 46 | 47 | /** 48 | * A union type of all the instrumented functions from Vercel's `ai` package. 49 | */ 50 | export type VercelAIInstrumentedFunctions = 51 | | VercelAIEmbedManyType 52 | | VercelAIGenerateTextType 53 | | VercelAIStreamTextType 54 | | VercelAIGenerateObjectType 55 | | VercelAIStreamObjectType; 56 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Checks if an object is an async iterable. 3 | * 4 | * @param obj - Object to check 5 | * @returns `true` if the object is an async iterable, `false` otherwise 6 | */ 7 | export function isAsyncIterable(obj: any): obj is AsyncIterable { 8 | return obj?.[Symbol.asyncIterator] !== undefined; 9 | } 10 | 11 | /** 12 | * Recursively replaces sensitive keys in an object with `"***"`. 13 | * 14 | * *Note: This function *mutates* the input object.* 15 | * 16 | * @param obj - Object to redact 17 | * @param sensitiveKeys - Set of sensitive keys to redact 18 | * @returns Redacted object 19 | */ 20 | export function redactSensitiveKeys>(obj: T, sensitiveKeys: Set) { 21 | for (const key in obj) { 22 | if (sensitiveKeys.has(key)) { 23 | (obj as any)[key] = "***"; 24 | continue; 25 | } 26 | 27 | const value = obj[key]; 28 | if (Array.isArray(value)) { 29 | redactSensitiveKeysArray(value, sensitiveKeys); 30 | } else if (typeof value === "object" && value !== null) { 31 | redactSensitiveKeys(value, sensitiveKeys); 32 | } 33 | } 34 | 35 | return obj; 36 | } 37 | 38 | function redactSensitiveKeysArray(arr: any[], sensitiveKeys: Set) { 39 | for (const item of arr) { 40 | if (Array.isArray(item)) { 41 | redactSensitiveKeysArray(item, sensitiveKeys); 42 | } else if (typeof item === "object" && item !== null) { 43 | redactSensitiveKeys(item, sensitiveKeys); 44 | } 45 | } 46 | 47 | return arr; 48 | } 49 | -------------------------------------------------------------------------------- /src/utils/logger.ts: -------------------------------------------------------------------------------- 1 | enum LogLevel { 2 | DEBUG = "DEBUG", 3 | INFO = "INFO", 4 | WARNING = "WARNING", 5 | ERROR = "ERROR", 6 | FATAL = "FATAL", 7 | } 8 | 9 | interface LogEntry { 10 | level: LogLevel; 11 | message: string; 12 | timestamp: string; 13 | label?: string; 14 | } 15 | 16 | class Logger { 17 | private label?: string; 18 | 19 | constructor(label?: string) { 20 | this.label = label; 21 | } 22 | 23 | private static formatParam(p: unknown): string { 24 | if (p instanceof Error) { 25 | // Format Errors to include stack trace 26 | return p.stack || p.toString(); 27 | } 28 | try { 29 | // Try to stringify other types 30 | return JSON.stringify(p); 31 | } catch (e) { 32 | // Fallback for objects that cannot be stringified (e.g., circular references) 33 | return String(p); 34 | } 35 | } 36 | 37 | private log(level: LogLevel, message: string, ...additional: any[]): void { 38 | const logEntry: LogEntry = { 39 | // add the label if it exists 40 | ...(this.label && { label: this.label }), 41 | level, 42 | // combine the main message and any additional params, formatting them appropriately 43 | message: `${message}${additional.length > 0 ? " " + additional.map(Logger.formatParam).join(" ") : ""}`.trim(), 44 | timestamp: new Date().toISOString(), 45 | }; 46 | 47 | const output = JSON.stringify(logEntry); 48 | switch (level) { 49 | case LogLevel.DEBUG: 50 | case LogLevel.INFO: 51 | console.log(output); 52 | break; 53 | case LogLevel.WARNING: 54 | case LogLevel.ERROR: 55 | case LogLevel.FATAL: 56 | console.error(output); 57 | break; 58 | default: 59 | // fallback to console.error for any unknown levels 60 | console.error(output); 61 | } 62 | } 63 | 64 | /** 65 | * Writes a debug log message to stdout. 66 | */ 67 | public debug(message: string, ...additional: any[]): void { 68 | this.log(LogLevel.DEBUG, message, ...additional); 69 | } 70 | 71 | /** 72 | * Writes an info log message to stdout. 73 | */ 74 | public info(message: string, ...additional: any[]): void { 75 | this.log(LogLevel.INFO, message, ...additional); 76 | } 77 | 78 | /** 79 | * Writes a warning log message to stderr. 80 | */ 81 | public warn(message: string, ...additional: any[]): void { 82 | this.log(LogLevel.WARNING, message, ...additional); 83 | } 84 | 85 | /** 86 | * Writes an error log message to stderr. 87 | */ 88 | public error(message: string, ...additional: any[]): void { 89 | this.log(LogLevel.ERROR, message, ...additional); 90 | } 91 | 92 | /** 93 | * Writes a fatal log message to stderr. 94 | */ 95 | public fatal(message: string, ...additional: any[]): void { 96 | this.log(LogLevel.FATAL, message, ...additional); 97 | } 98 | } 99 | 100 | export const logger = new Logger("@obsy-ai/sdk"); 101 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "Node16", 4 | "moduleResolution": "node16", 5 | "target": "ESNext", 6 | "outDir": "./dist", 7 | "strict": true, 8 | "baseUrl": ".", 9 | "paths": { 10 | "#src/*": ["src/*"] 11 | } 12 | }, 13 | "include": ["src/**/*"] 14 | } 15 | --------------------------------------------------------------------------------