diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7755709050..9c243e4bda 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2052,7 +2052,7 @@ importers: dependencies: vitepress: specifier: 'catalog:' - version: 2.0.0-alpha.15(@types/node@24.10.2)(axios@1.13.5)(esbuild@0.27.0)(jiti@2.6.1)(less@4.4.2)(nprogress@0.2.0)(oxc-minify@0.105.0)(postcss@8.5.6)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.2) + version: 2.0.0-alpha.15(@types/node@24.10.2)(esbuild@0.27.0)(jiti@2.6.1)(less@4.4.2)(nprogress@0.2.0)(oxc-minify@0.105.0)(postcss@8.5.6)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.2) vitepress-plugin-llms: specifier: 'catalog:' version: 1.10.0 @@ -2080,6 +2080,43 @@ importers: specifier: 'catalog:' version: 5.9.3 + tegg/core/agent-tracing: + dependencies: + '@eggjs/background-task': + specifier: workspace:* + version: link:../background-task + '@eggjs/core-decorator': + specifier: workspace:* + version: link:../core-decorator + '@eggjs/tegg-types': + specifier: workspace:* + version: link:../types + onelogger: + specifier: 'catalog:' + version: 1.0.1 + devDependencies: + '@anthropic-ai/claude-agent-sdk': + specifier: ^0.2.52 + version: 0.2.59(zod@4.3.6) + '@anthropic-ai/sdk': + specifier: ^0.78.0 + version: 0.78.0(zod@4.3.6) + '@eggjs/tegg-common-util': + specifier: workspace:* + version: link:../common-util + '@langchain/core': + specifier: ^1.1.29 + version: 1.1.29(openai@6.25.0(zod@4.3.6)) + '@langchain/langgraph': + specifier: ^0.2.74 + version: 0.2.74(@langchain/core@1.1.29(openai@6.25.0(zod@4.3.6)))(zod-to-json-schema@3.25.1(zod@4.3.6)) + '@types/node': + specifier: 'catalog:' + version: 24.10.2 + typescript: + specifier: 'catalog:' + version: 5.9.3 + tegg/core/ajv-decorator: dependencies: ajv: @@ -2233,7 +2270,7 @@ importers: version: link:../types '@modelcontextprotocol/sdk': specifier: ^1.23.0 - version: 1.26.0(@cfworker/json-schema@4.1.1)(zod@4.3.6) + version: 1.27.1(@cfworker/json-schema@4.1.1)(zod@4.3.6) is-type-of: specifier: 'catalog:' version: 2.2.0 @@ -2489,17 +2526,17 @@ importers: specifier: workspace:* version: link:../types '@langchain/core': - specifier: ^1.1.1 - version: 1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76)) + specifier: ^1.1.29 + version: 1.1.29(openai@6.25.0(zod@3.25.76)) '@langchain/langgraph': specifier: ^1.0.2 - version: 1.1.5(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76)))(zod-to-json-schema@3.25.1(zod@3.25.76))(zod@3.25.76) + version: 1.2.0(@langchain/core@1.1.29(openai@6.25.0(zod@3.25.76)))(zod-to-json-schema@3.25.1(zod@3.25.76))(zod@3.25.76) '@langchain/openai': specifier: ^1.1.0 - version: 1.2.8(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76)))(ws@8.19.0) + version: 1.2.11(@langchain/core@1.1.29(openai@6.25.0(zod@3.25.76))) langchain: specifier: ^1.1.2 - version: 1.2.25(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76)))(openai@6.22.0(ws@8.19.0)(zod@3.25.76))(zod-to-json-schema@3.25.1(zod@3.25.76)) + version: 1.2.28(@langchain/core@1.1.29(openai@6.25.0(zod@3.25.76)))(openai@6.25.0(zod@3.25.76))(zod-to-json-schema@3.25.1(zod@3.25.76)) devDependencies: '@eggjs/controller-decorator': specifier: workspace:* @@ -2568,10 +2605,10 @@ importers: version: link:../types '@langchain/mcp-adapters': specifier: ^1.0.0 - version: 1.1.3(@cfworker/json-schema@4.1.1)(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76)))(@langchain/langgraph@1.1.5(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76)))(zod-to-json-schema@3.25.1(zod@3.25.76))(zod@3.25.76)) + version: 1.1.3(@cfworker/json-schema@4.1.1)(@langchain/core@1.1.29(openai@6.25.0(zod@3.25.76)))(@langchain/langgraph@1.2.0(@langchain/core@1.1.29(openai@6.25.0(zod@3.25.76)))(zod-to-json-schema@3.25.1(zod@3.25.76))(zod@3.25.76)) '@modelcontextprotocol/sdk': specifier: ^1.23.0 - version: 1.26.0(@cfworker/json-schema@4.1.1)(zod@3.25.76) + version: 1.27.1(@cfworker/json-schema@4.1.1)(zod@3.25.76) urllib: specifier: 'catalog:' version: 4.8.2 @@ -2828,7 +2865,7 @@ importers: dependencies: '@modelcontextprotocol/sdk': specifier: ^1.23.0 - version: 1.26.0(@cfworker/json-schema@4.1.1)(zod@4.3.6) + version: 1.27.1(@cfworker/json-schema@4.1.1)(zod@4.3.6) devDependencies: '@types/node': specifier: 'catalog:' @@ -3022,7 +3059,7 @@ importers: version: link:../../core/types '@modelcontextprotocol/sdk': specifier: ^1.23.0 - version: 1.26.0(@cfworker/json-schema@4.1.1)(zod@4.3.6) + version: 1.27.1(@cfworker/json-schema@4.1.1)(zod@4.3.6) await-event: specifier: 'catalog:' version: 2.1.0 @@ -3222,20 +3259,20 @@ importers: specifier: workspace:* version: link:../../core/types '@langchain/core': - specifier: ^1.1.1 - version: 1.1.26(openai@6.22.0(ws@8.19.0)(zod@4.3.6)) + specifier: ^1.1.29 + version: 1.1.29(openai@6.25.0(zod@4.3.6)) '@langchain/langgraph': specifier: ^1.0.2 - version: 1.1.5(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@4.3.6)))(zod-to-json-schema@3.25.1(zod@4.3.6))(zod@4.3.6) + version: 1.2.0(@langchain/core@1.1.29(openai@6.25.0(zod@4.3.6)))(zod-to-json-schema@3.25.1(zod@4.3.6))(zod@4.3.6) '@langchain/mcp-adapters': specifier: ^1.0.0 - version: 1.1.3(@cfworker/json-schema@4.1.1)(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@4.3.6)))(@langchain/langgraph@1.1.5(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@4.3.6)))(zod-to-json-schema@3.25.1(zod@4.3.6))(zod@4.3.6)) + version: 1.1.3(@cfworker/json-schema@4.1.1)(@langchain/core@1.1.29(openai@6.25.0(zod@4.3.6)))(@langchain/langgraph@1.2.0(@langchain/core@1.1.29(openai@6.25.0(zod@4.3.6)))(zod-to-json-schema@3.25.1(zod@4.3.6))(zod@4.3.6)) '@langchain/openai': specifier: ^1.0.0 - version: 1.2.8(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@4.3.6)))(ws@8.19.0) + version: 1.2.11(@langchain/core@1.1.29(openai@6.25.0(zod@4.3.6))) langchain: specifier: ^1.1.2 - version: 1.2.25(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@4.3.6)))(openai@6.22.0(ws@8.19.0)(zod@4.3.6))(zod-to-json-schema@3.25.1(zod@4.3.6)) + version: 1.2.28(@langchain/core@1.1.29(openai@6.25.0(zod@4.3.6)))(openai@6.25.0(zod@4.3.6))(zod-to-json-schema@3.25.1(zod@4.3.6)) urllib: specifier: 'catalog:' version: 4.8.2 @@ -3269,7 +3306,7 @@ importers: version: link:../../../plugins/tracer '@modelcontextprotocol/sdk': specifier: ^1.23.0 - version: 1.26.0(@cfworker/json-schema@4.1.1)(zod@4.3.6) + version: 1.27.1(@cfworker/json-schema@4.1.1)(zod@4.3.6) '@types/node': specifier: 'catalog:' version: 24.10.2 @@ -3296,7 +3333,7 @@ importers: version: link:../../core/runtime '@modelcontextprotocol/sdk': specifier: ^1.23.0 - version: 1.26.0(@cfworker/json-schema@4.1.1)(zod@3.25.76) + version: 1.27.1(@cfworker/json-schema@4.1.1)(zod@3.25.76) egg: specifier: workspace:* version: link:../../../packages/egg @@ -3333,7 +3370,7 @@ importers: version: link:../../core/types '@modelcontextprotocol/sdk': specifier: ^1.23.0 - version: 1.26.0(@cfworker/json-schema@4.1.1)(zod@4.3.6) + version: 1.27.1(@cfworker/json-schema@4.1.1)(zod@4.3.6) await-event: specifier: 'catalog:' version: 2.1.0 @@ -3792,6 +3829,21 @@ importers: packages: + '@anthropic-ai/claude-agent-sdk@0.2.59': + resolution: {integrity: sha512-xPOUZZimZI5ChaO791olWGXqaRvCwOfj9/1micu42EL9czdcwiDm0WK1OGsqb2mZ7LSCoYWBB0ZHVKOxehemDA==} + engines: {node: '>=18.0.0'} + peerDependencies: + zod: ^4.0.0 + + '@anthropic-ai/sdk@0.78.0': + resolution: {integrity: sha512-PzQhR715td/m1UaaN5hHXjYB8Gl2lF9UVhrrGrZeysiF6Rb74Wc9GCB8hzLdzmQtBd1qe89F9OptgB9Za1Ib5w==} + hasBin: true + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + peerDependenciesMeta: + zod: + optional: true + '@babel/generator@7.28.5': resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==} engines: {node: '>=6.9.0'} @@ -3809,6 +3861,10 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + '@babel/runtime@7.28.6': + resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} + engines: {node: '>=6.9.0'} + '@babel/types@7.28.5': resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} engines: {node: '>=6.9.0'} @@ -4206,6 +4262,105 @@ packages: '@iconify/types@2.0.0': resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} + '@img/sharp-darwin-arm64@0.34.5': + resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.34.5': + resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.2.4': + resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.2.4': + resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.2.4': + resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-arm@1.2.4': + resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-x64@1.2.4': + resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-linux-arm64@0.34.5': + resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-arm@0.34.5': + resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-x64@0.34.5': + resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-linuxmusl-arm64@0.34.5': + resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-linuxmusl-x64@0.34.5': + resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-win32-arm64@0.34.5': + resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-x64@0.34.5': + resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + '@ioredis/as-callback@3.0.0': resolution: {integrity: sha512-Kqv1rZ3WbgOrS+hgzJ5xG5WQuhvzzSTRYvNeyPMLOAM78MHSnuKI20JeJGbpuAt//LCuP0vsexZcorqW7kWhJg==} @@ -4265,19 +4420,38 @@ packages: '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} - '@langchain/core@1.1.26': - resolution: {integrity: sha512-Xnwi4xEKEtZcGwjW5xpZVP/Dc+WckFxULMShETuCpD6TxNFS6yRM+FhNUO1DDCkRkGn9b1fuzVZrNYb9W7F32A==} + '@langchain/core@1.1.29': + resolution: {integrity: sha512-BPoegTtIdZX4gl2kxcSXAlLrrJFl1cxeRsk9DM/wlIuvyPrFwjWqrEK5NwF5diDt5XSArhQxIFaifGAl4F7fgw==} engines: {node: '>=20'} + '@langchain/langgraph-checkpoint@0.0.18': + resolution: {integrity: sha512-IS7zJj36VgY+4pf8ZjsVuUWef7oTwt1y9ylvwu0aLuOn1d0fg05Om9DLm3v2GZ2Df6bhLV1kfWAM0IAl9O5rQQ==} + engines: {node: '>=18'} + peerDependencies: + '@langchain/core': '>=0.2.31 <0.4.0' + '@langchain/langgraph-checkpoint@1.0.0': resolution: {integrity: sha512-xrclBGvNCXDmi0Nz28t3vjpxSH6UYx6w5XAXSiiB1WEdc2xD2iY/a913I3x3a31XpInUW/GGfXXfePfaghV54A==} engines: {node: '>=18'} peerDependencies: '@langchain/core': ^1.0.1 - '@langchain/langgraph-sdk@2.0.0': - resolution: {integrity: sha512-Xdkl1hve84ZGQ7fgpiBIBvjODhtjbPPccY4snOtYgSdzRXZkESsi2Y7RDKgFe1nC9+DbX+QaYom0raD/XFBKAw==} - deprecated: This version is not intended for use. Please use 1.x versions of @langchain/langgraph-sdk instead + '@langchain/langgraph-sdk@0.0.112': + resolution: {integrity: sha512-/9W5HSWCqYgwma6EoOspL4BGYxGxeJP6lIquPSF4FA0JlKopaUv58ucZC3vAgdJyCgg6sorCIV/qg7SGpEcCLw==} + peerDependencies: + '@langchain/core': '>=0.2.31 <0.4.0' + react: ^18 || ^19 + react-dom: ^18 || ^19 + peerDependenciesMeta: + '@langchain/core': + optional: true + react: + optional: true + react-dom: + optional: true + + '@langchain/langgraph-sdk@1.6.5': + resolution: {integrity: sha512-JjprmbhgCnoNJ9DUKcvrEU+C9FfKsNGyT3ooqWxAY5Cx2qofhXmDJOpTCqqbxfDHPKG0RjTs5HgVK3WW5M6Big==} peerDependencies: '@langchain/core': ^1.1.16 react: ^18 || ^19 @@ -4290,8 +4464,18 @@ packages: react-dom: optional: true - '@langchain/langgraph@1.1.5': - resolution: {integrity: sha512-uJC/asydf/GoHpo9x42lf9hs8ufCkMuJ9sDle5ybP7sMD0XryOfE0E4J3deARk9ZadCCt6zeCoCNu/mTbx8+Sg==} + '@langchain/langgraph@0.2.74': + resolution: {integrity: sha512-oHpEi5sTZTPaeZX1UnzfM2OAJ21QGQrwReTV6+QnX7h8nDCBzhtipAw1cK616S+X8zpcVOjgOtJuaJhXa4mN8w==} + engines: {node: '>=18'} + peerDependencies: + '@langchain/core': '>=0.2.36 <0.3.0 || >=0.3.40 < 0.4.0' + zod-to-json-schema: ^3.x + peerDependenciesMeta: + zod-to-json-schema: + optional: true + + '@langchain/langgraph@1.2.0': + resolution: {integrity: sha512-wyqKIzXTAfXX3L1d8R7icM+HmRQBTbuNLWtUlpRJ/JP/ux1ei/sOSt6p8f90ARoOP2iJVlM70wOBYWaGErdBlA==} engines: {node: '>=18'} peerDependencies: '@langchain/core': ^1.1.16 @@ -4308,14 +4492,14 @@ packages: '@langchain/core': ^1.0.0 '@langchain/langgraph': ^1.0.0 - '@langchain/openai@1.2.8': - resolution: {integrity: sha512-qliwC7sb7/Kw0tsl/EiMchMThKt62rZbyofKXtxPwYBte3BMzMXo2HKaEFvAN2QHVOuDi4voqQ7ZlRXc/o2e8w==} + '@langchain/openai@1.2.11': + resolution: {integrity: sha512-//a3OcvHKuz9QXAn3QJjASdtSyyF1JmsZKVzuUa2uR2YLFAS+XMZYLfel9RaNdt8LWkwhSpCiVvegqUGRBU+uw==} engines: {node: '>=20'} peerDependencies: - '@langchain/core': ^1.0.0 + '@langchain/core': ^1.1.29 - '@modelcontextprotocol/sdk@1.26.0': - resolution: {integrity: sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==} + '@modelcontextprotocol/sdk@1.27.1': + resolution: {integrity: sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA==} engines: {node: '>=18'} peerDependencies: '@cfworker/json-schema': ^4.1.1 @@ -5374,6 +5558,9 @@ packages: '@types/range-parser@1.2.7': resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + '@types/retry@0.12.0': + resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==} + '@types/safe-timers@1.1.2': resolution: {integrity: sha512-NkLK2vnHShzLyef0eCMT5ocUJOQNB3Ppq3nKOCHPoxCs5PAJV/AH5DyWTKYHpaGlicE9qmC3MwNRNvt3oML76w==} @@ -5709,6 +5896,10 @@ packages: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + ansi-styles@6.2.3: resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} engines: {node: '>=12'} @@ -5808,9 +5999,6 @@ packages: resolution: {integrity: sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==} engines: {node: '>= 6.0.0'} - axios@1.13.5: - resolution: {integrity: sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==} - bail@2.0.2: resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} @@ -6755,15 +6943,6 @@ packages: focus-trap@7.6.6: resolution: {integrity: sha512-v/Z8bvMCajtx4mEXmOo7QEsIzlIOqRXTIwgUfsFOF9gEsespdbD0AkPIka1bSXZ8Y8oZ+2IVDQZePkTfEHZl7Q==} - follow-redirects@1.15.11: - resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} - engines: {node: '>=4.0'} - peerDependencies: - debug: '*' - peerDependenciesMeta: - debug: - optional: true - for-each@0.3.5: resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} engines: {node: '>= 0.4'} @@ -6775,8 +6954,8 @@ packages: form-data-encoder@1.9.0: resolution: {integrity: sha512-rahaRMkN8P8d/tgK/BLPX+WBVM27NbvdXBxqQujBtkDAIFspaRqN7Od7lfdGQA6KAD+f82fYCLBq1ipvcu8qLw==} - form-data@4.0.5: - resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} + form-data@4.0.4: + resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==} engines: {node: '>= 6'} format@0.2.2: @@ -6995,8 +7174,8 @@ packages: heredoc@1.3.1: resolution: {integrity: sha512-VL/rh/EXkhgpIWSNNde3OPc067oQiorfY+Nhkwgo2jAAIgrLLb5N92wmOblTyMWwCcIKo+8aQzk5s5YxLbVJPQ==} - hono@4.11.10: - resolution: {integrity: sha512-kyWP5PAiMooEvGrA9jcD3IXF7ATu8+o7B3KCbPXid5se52NPqnOpM/r9qeW2heMnOekF4kqR1fXJqCYeCLKrZg==} + hono@4.12.3: + resolution: {integrity: sha512-SFsVSjp8sj5UumXOOFlkZOG6XS9SJDKw0TbwFeV+AJ8xlST8kxK5Z/5EYa111UY8732lK2S/xB653ceuaoGwpg==} engines: {node: '>=16.9.0'} hookable@5.5.3: @@ -7248,8 +7427,8 @@ packages: resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} engines: {node: '>= 0.4'} - is-network-error@1.3.0: - resolution: {integrity: sha512-6oIwpsgRfnDiyEDLMay/GqCl3HoAtH5+RUKW29gYkL0QA+ipzpDLA16yQs7/RHCSu+BwgbJaOUqa4A99qNVQVw==} + is-network-error@1.3.1: + resolution: {integrity: sha512-6QCxa49rQbmUWLfk0nuGqzql9U8uaV2H6279bRErPBHe/109hCzsLUBUHfbEtvLIHBd6hyXbgedBSHevm43Edw==} engines: {node: '>=16'} is-number-object@1.1.1: @@ -7440,6 +7619,10 @@ packages: resolution: {integrity: sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + json-schema-to-ts@3.1.1: + resolution: {integrity: sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==} + engines: {node: '>=16'} + json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} @@ -7517,14 +7700,14 @@ packages: resolution: {integrity: sha512-UqyYyH5YEXaJrf9S8E23GoJFQZXkBVJ9zYYMPGz919MSX1KuvAcycIuS0ci150HCoPf4XQVhQ84Qf8xRPWxFaQ==} engines: {node: '>= 7.6.0'} - langchain@1.2.25: - resolution: {integrity: sha512-29qay7nZxkmkH3PRp8cjBpZmGCmA3EW8JwEYqZa0a5CW38nvO2Tr1rbFd26cnlLcxtA5hBRH57/XSPoWLjHJSw==} + langchain@1.2.28: + resolution: {integrity: sha512-MDJO14BMXtWYchBEmMaYU2CNe1b82L+6Oba4QyIr5Z1S4oCseMtqguZiKCIkv/K99ZGoe1iVtxPtN1Ja3mHvDQ==} engines: {node: '>=20'} peerDependencies: - '@langchain/core': ^1.1.26 + '@langchain/core': ^1.1.29 - langsmith@0.5.4: - resolution: {integrity: sha512-qYkNIoKpf0ZYt+cYzrDV+XI3FCexApmZmp8EMs3eDTMv0OvrHMLoxJ9IpkeoXJSX24+GPk0/jXjKx2hWerpy9w==} + langsmith@0.5.7: + resolution: {integrity: sha512-FjYf2oBGMoSXnaT4SRaFguIiGJaonZ5VKWKJDPl9awLZjz2RkN29AcQWceecSINVzXzTvtRWPOjAWT+XggqNNg==} peerDependencies: '@opentelemetry/api': '*' '@opentelemetry/exporter-trace-otlp-proto': '*' @@ -8280,8 +8463,8 @@ packages: oniguruma-to-es@4.3.3: resolution: {integrity: sha512-rPiZhzC3wXwE59YQMRDodUwwT9FZ9nNBwQQfsd1wfdtlKEyCdRV0avrTcSZ5xlIvGRVPd/cx6ZN45ECmS39xvg==} - openai@6.22.0: - resolution: {integrity: sha512-7Yvy17F33Bi9RutWbsaYt5hJEEJ/krRPOrwan+f9aCPuMat1WVsb2VNSII5W1EksKT6fF69TG/xj4XzodK3JZw==} + openai@6.25.0: + resolution: {integrity: sha512-mEh6VZ2ds2AGGokWARo18aPISI1OhlgdEIC1ewhkZr8pSIT31dec0ecr9Nhxx0JlybyOgoAT1sWeKtwPZzJyww==} hasBin: true peerDependencies: ws: ^8.18.0 @@ -8387,6 +8570,10 @@ packages: resolution: {integrity: sha512-O/ZPaXuQV29uSLbxWBGGZO1mCQXV2BLIwUr59JUU9SoH76mnYvtms7aafH/isNSNGwuEfP6W/4xD0/TJXxrizw==} engines: {node: '>=20'} + p-retry@4.6.2: + resolution: {integrity: sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==} + engines: {node: '>=8'} + p-retry@7.1.1: resolution: {integrity: sha512-J5ApzjyRkkf601HpEeykoiCvzHQjWxPAHhyjFcEUP2SWq0+35NKh8TLhpLw+Dkq5TZBFvUM6UigdE9hIVYTl5w==} engines: {node: '>=20'} @@ -8615,9 +8802,6 @@ packages: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} - proxy-from-env@1.1.0: - resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} - prr@1.0.1: resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==} @@ -8779,6 +8963,10 @@ packages: resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} engines: {node: '>= 4'} + retry@0.13.1: + resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} + engines: {node: '>= 4'} + reusify@1.1.0: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} @@ -9355,6 +9543,9 @@ packages: trough@2.2.0: resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + ts-algebra@2.0.0: + resolution: {integrity: sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==} + ts-node@10.9.2: resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} hasBin: true @@ -9573,10 +9764,18 @@ packages: resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==} hasBin: true + uuid@11.1.0: + resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} + hasBin: true + uuid@13.0.0: resolution: {integrity: sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==} hasBin: true + uuid@9.0.1: + resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} + hasBin: true + v8-compile-cache-lib@3.0.1: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} @@ -9767,18 +9966,6 @@ packages: resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - ws@8.19.0: - resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - xml2js@0.6.2: resolution: {integrity: sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==} engines: {node: '>=4.0.0'} @@ -9861,6 +10048,26 @@ packages: snapshots: + '@anthropic-ai/claude-agent-sdk@0.2.59(zod@4.3.6)': + dependencies: + zod: 4.3.6 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.34.5 + '@img/sharp-darwin-x64': 0.34.5 + '@img/sharp-linux-arm': 0.34.5 + '@img/sharp-linux-arm64': 0.34.5 + '@img/sharp-linux-x64': 0.34.5 + '@img/sharp-linuxmusl-arm64': 0.34.5 + '@img/sharp-linuxmusl-x64': 0.34.5 + '@img/sharp-win32-arm64': 0.34.5 + '@img/sharp-win32-x64': 0.34.5 + + '@anthropic-ai/sdk@0.78.0(zod@4.3.6)': + dependencies: + json-schema-to-ts: 3.1.1 + optionalDependencies: + zod: 4.3.6 + '@babel/generator@7.28.5': dependencies: '@babel/parser': 7.28.5 @@ -9877,6 +10084,8 @@ snapshots: dependencies: '@babel/types': 7.28.5 + '@babel/runtime@7.28.6': {} + '@babel/types@7.28.5': dependencies: '@babel/helper-string-parser': 7.27.1 @@ -10112,9 +10321,9 @@ snapshots: '@hapi/bourne@3.0.0': {} - '@hono/node-server@1.19.9(hono@4.11.10)': + '@hono/node-server@1.19.9(hono@4.12.3)': dependencies: - hono: 4.11.10 + hono: 4.12.3 '@iconify-json/simple-icons@1.2.60': dependencies: @@ -10122,6 +10331,68 @@ snapshots: '@iconify/types@2.0.0': {} + '@img/sharp-darwin-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.2.4 + optional: true + + '@img/sharp-darwin-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.2.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.2.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + optional: true + + '@img/sharp-linux-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.2.4 + optional: true + + '@img/sharp-linux-arm@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.2.4 + optional: true + + '@img/sharp-linux-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + optional: true + + '@img/sharp-win32-arm64@0.34.5': + optional: true + + '@img/sharp-win32-x64@0.34.5': + optional: true + '@ioredis/as-callback@3.0.0': {} '@ioredis/commands@1.4.0': {} @@ -10195,17 +10466,17 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76))': + '@langchain/core@1.1.29(openai@6.25.0(zod@3.25.76))': dependencies: '@cfworker/json-schema': 4.1.1 - ansi-styles: 6.2.3 + ansi-styles: 5.2.0 camelcase: 6.3.0 decamelize: 1.2.0 js-tiktoken: 1.0.21 - langsmith: 0.5.4(openai@6.22.0(ws@8.19.0)(zod@3.25.76)) + langsmith: 0.5.7(openai@6.25.0(zod@3.25.76)) mustache: 4.2.0 p-queue: 6.6.2 - uuid: 10.0.0 + uuid: 11.1.0 zod: 4.3.6 transitivePeerDependencies: - '@opentelemetry/api' @@ -10213,17 +10484,17 @@ snapshots: - '@opentelemetry/sdk-trace-base' - openai - '@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@4.3.6))': + '@langchain/core@1.1.29(openai@6.25.0(zod@4.3.6))': dependencies: '@cfworker/json-schema': 4.1.1 - ansi-styles: 6.2.3 + ansi-styles: 5.2.0 camelcase: 6.3.0 decamelize: 1.2.0 js-tiktoken: 1.0.21 - langsmith: 0.5.4(openai@6.22.0(ws@8.19.0)(zod@4.3.6)) + langsmith: 0.5.7(openai@6.25.0(zod@4.3.6)) mustache: 4.2.0 p-queue: 6.6.2 - uuid: 10.0.0 + uuid: 11.1.0 zod: 4.3.6 transitivePeerDependencies: - '@opentelemetry/api' @@ -10231,39 +10502,66 @@ snapshots: - '@opentelemetry/sdk-trace-base' - openai - '@langchain/langgraph-checkpoint@1.0.0(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76)))': + '@langchain/langgraph-checkpoint@0.0.18(@langchain/core@1.1.29(openai@6.25.0(zod@4.3.6)))': dependencies: - '@langchain/core': 1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76)) + '@langchain/core': 1.1.29(openai@6.25.0(zod@4.3.6)) uuid: 10.0.0 - '@langchain/langgraph-checkpoint@1.0.0(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@4.3.6)))': + '@langchain/langgraph-checkpoint@1.0.0(@langchain/core@1.1.29(openai@6.25.0(zod@3.25.76)))': dependencies: - '@langchain/core': 1.1.26(openai@6.22.0(ws@8.19.0)(zod@4.3.6)) + '@langchain/core': 1.1.29(openai@6.25.0(zod@3.25.76)) uuid: 10.0.0 - '@langchain/langgraph-sdk@2.0.0(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76)))': + '@langchain/langgraph-checkpoint@1.0.0(@langchain/core@1.1.29(openai@6.25.0(zod@4.3.6)))': + dependencies: + '@langchain/core': 1.1.29(openai@6.25.0(zod@4.3.6)) + uuid: 10.0.0 + + '@langchain/langgraph-sdk@0.0.112(@langchain/core@1.1.29(openai@6.25.0(zod@4.3.6)))': + dependencies: + '@types/json-schema': 7.0.15 + p-queue: 6.6.2 + p-retry: 4.6.2 + uuid: 9.0.1 + optionalDependencies: + '@langchain/core': 1.1.29(openai@6.25.0(zod@4.3.6)) + + '@langchain/langgraph-sdk@1.6.5(@langchain/core@1.1.29(openai@6.25.0(zod@3.25.76)))': dependencies: '@types/json-schema': 7.0.15 p-queue: 9.1.0 p-retry: 7.1.1 uuid: 13.0.0 optionalDependencies: - '@langchain/core': 1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76)) + '@langchain/core': 1.1.29(openai@6.25.0(zod@3.25.76)) - '@langchain/langgraph-sdk@2.0.0(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@4.3.6)))': + '@langchain/langgraph-sdk@1.6.5(@langchain/core@1.1.29(openai@6.25.0(zod@4.3.6)))': dependencies: '@types/json-schema': 7.0.15 p-queue: 9.1.0 p-retry: 7.1.1 uuid: 13.0.0 optionalDependencies: - '@langchain/core': 1.1.26(openai@6.22.0(ws@8.19.0)(zod@4.3.6)) + '@langchain/core': 1.1.29(openai@6.25.0(zod@4.3.6)) + + '@langchain/langgraph@0.2.74(@langchain/core@1.1.29(openai@6.25.0(zod@4.3.6)))(zod-to-json-schema@3.25.1(zod@4.3.6))': + dependencies: + '@langchain/core': 1.1.29(openai@6.25.0(zod@4.3.6)) + '@langchain/langgraph-checkpoint': 0.0.18(@langchain/core@1.1.29(openai@6.25.0(zod@4.3.6))) + '@langchain/langgraph-sdk': 0.0.112(@langchain/core@1.1.29(openai@6.25.0(zod@4.3.6))) + uuid: 10.0.0 + zod: 3.25.76 + optionalDependencies: + zod-to-json-schema: 3.25.1(zod@4.3.6) + transitivePeerDependencies: + - react + - react-dom - '@langchain/langgraph@1.1.5(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76)))(zod-to-json-schema@3.25.1(zod@3.25.76))(zod@3.25.76)': + '@langchain/langgraph@1.2.0(@langchain/core@1.1.29(openai@6.25.0(zod@3.25.76)))(zod-to-json-schema@3.25.1(zod@3.25.76))(zod@3.25.76)': dependencies: - '@langchain/core': 1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76)) - '@langchain/langgraph-checkpoint': 1.0.0(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76))) - '@langchain/langgraph-sdk': 2.0.0(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76))) + '@langchain/core': 1.1.29(openai@6.25.0(zod@3.25.76)) + '@langchain/langgraph-checkpoint': 1.0.0(@langchain/core@1.1.29(openai@6.25.0(zod@3.25.76))) + '@langchain/langgraph-sdk': 1.6.5(@langchain/core@1.1.29(openai@6.25.0(zod@3.25.76))) '@standard-schema/spec': 1.1.0 uuid: 10.0.0 zod: 3.25.76 @@ -10273,11 +10571,11 @@ snapshots: - react - react-dom - '@langchain/langgraph@1.1.5(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76)))(zod-to-json-schema@3.25.1(zod@3.25.76))(zod@4.3.6)': + '@langchain/langgraph@1.2.0(@langchain/core@1.1.29(openai@6.25.0(zod@3.25.76)))(zod-to-json-schema@3.25.1(zod@3.25.76))(zod@4.3.6)': dependencies: - '@langchain/core': 1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76)) - '@langchain/langgraph-checkpoint': 1.0.0(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76))) - '@langchain/langgraph-sdk': 2.0.0(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76))) + '@langchain/core': 1.1.29(openai@6.25.0(zod@3.25.76)) + '@langchain/langgraph-checkpoint': 1.0.0(@langchain/core@1.1.29(openai@6.25.0(zod@3.25.76))) + '@langchain/langgraph-sdk': 1.6.5(@langchain/core@1.1.29(openai@6.25.0(zod@3.25.76))) '@standard-schema/spec': 1.1.0 uuid: 10.0.0 zod: 4.3.6 @@ -10287,11 +10585,11 @@ snapshots: - react - react-dom - '@langchain/langgraph@1.1.5(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@4.3.6)))(zod-to-json-schema@3.25.1(zod@4.3.6))(zod@4.3.6)': + '@langchain/langgraph@1.2.0(@langchain/core@1.1.29(openai@6.25.0(zod@4.3.6)))(zod-to-json-schema@3.25.1(zod@4.3.6))(zod@4.3.6)': dependencies: - '@langchain/core': 1.1.26(openai@6.22.0(ws@8.19.0)(zod@4.3.6)) - '@langchain/langgraph-checkpoint': 1.0.0(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@4.3.6))) - '@langchain/langgraph-sdk': 2.0.0(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@4.3.6))) + '@langchain/core': 1.1.29(openai@6.25.0(zod@4.3.6)) + '@langchain/langgraph-checkpoint': 1.0.0(@langchain/core@1.1.29(openai@6.25.0(zod@4.3.6))) + '@langchain/langgraph-sdk': 1.6.5(@langchain/core@1.1.29(openai@6.25.0(zod@4.3.6))) '@standard-schema/spec': 1.1.0 uuid: 10.0.0 zod: 4.3.6 @@ -10301,11 +10599,11 @@ snapshots: - react - react-dom - '@langchain/mcp-adapters@1.1.3(@cfworker/json-schema@4.1.1)(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76)))(@langchain/langgraph@1.1.5(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76)))(zod-to-json-schema@3.25.1(zod@3.25.76))(zod@3.25.76))': + '@langchain/mcp-adapters@1.1.3(@cfworker/json-schema@4.1.1)(@langchain/core@1.1.29(openai@6.25.0(zod@3.25.76)))(@langchain/langgraph@1.2.0(@langchain/core@1.1.29(openai@6.25.0(zod@3.25.76)))(zod-to-json-schema@3.25.1(zod@3.25.76))(zod@3.25.76))': dependencies: - '@langchain/core': 1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76)) - '@langchain/langgraph': 1.1.5(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76)))(zod-to-json-schema@3.25.1(zod@3.25.76))(zod@3.25.76) - '@modelcontextprotocol/sdk': 1.26.0(@cfworker/json-schema@4.1.1)(zod@4.3.6) + '@langchain/core': 1.1.29(openai@6.25.0(zod@3.25.76)) + '@langchain/langgraph': 1.2.0(@langchain/core@1.1.29(openai@6.25.0(zod@3.25.76)))(zod-to-json-schema@3.25.1(zod@3.25.76))(zod@3.25.76) + '@modelcontextprotocol/sdk': 1.27.1(@cfworker/json-schema@4.1.1)(zod@4.3.6) debug: 4.4.3(supports-color@8.1.1) zod: 4.3.6 optionalDependencies: @@ -10314,11 +10612,11 @@ snapshots: - '@cfworker/json-schema' - supports-color - '@langchain/mcp-adapters@1.1.3(@cfworker/json-schema@4.1.1)(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@4.3.6)))(@langchain/langgraph@1.1.5(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@4.3.6)))(zod-to-json-schema@3.25.1(zod@4.3.6))(zod@4.3.6))': + '@langchain/mcp-adapters@1.1.3(@cfworker/json-schema@4.1.1)(@langchain/core@1.1.29(openai@6.25.0(zod@4.3.6)))(@langchain/langgraph@1.2.0(@langchain/core@1.1.29(openai@6.25.0(zod@4.3.6)))(zod-to-json-schema@3.25.1(zod@4.3.6))(zod@4.3.6))': dependencies: - '@langchain/core': 1.1.26(openai@6.22.0(ws@8.19.0)(zod@4.3.6)) - '@langchain/langgraph': 1.1.5(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@4.3.6)))(zod-to-json-schema@3.25.1(zod@4.3.6))(zod@4.3.6) - '@modelcontextprotocol/sdk': 1.26.0(@cfworker/json-schema@4.1.1)(zod@4.3.6) + '@langchain/core': 1.1.29(openai@6.25.0(zod@4.3.6)) + '@langchain/langgraph': 1.2.0(@langchain/core@1.1.29(openai@6.25.0(zod@4.3.6)))(zod-to-json-schema@3.25.1(zod@4.3.6))(zod@4.3.6) + '@modelcontextprotocol/sdk': 1.27.1(@cfworker/json-schema@4.1.1)(zod@4.3.6) debug: 4.4.3(supports-color@8.1.1) zod: 4.3.6 optionalDependencies: @@ -10327,27 +10625,27 @@ snapshots: - '@cfworker/json-schema' - supports-color - '@langchain/openai@1.2.8(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76)))(ws@8.19.0)': + '@langchain/openai@1.2.11(@langchain/core@1.1.29(openai@6.25.0(zod@3.25.76)))': dependencies: - '@langchain/core': 1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76)) + '@langchain/core': 1.1.29(openai@6.25.0(zod@3.25.76)) js-tiktoken: 1.0.21 - openai: 6.22.0(ws@8.19.0)(zod@4.3.6) + openai: 6.25.0(zod@4.3.6) zod: 4.3.6 transitivePeerDependencies: - ws - '@langchain/openai@1.2.8(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@4.3.6)))(ws@8.19.0)': + '@langchain/openai@1.2.11(@langchain/core@1.1.29(openai@6.25.0(zod@4.3.6)))': dependencies: - '@langchain/core': 1.1.26(openai@6.22.0(ws@8.19.0)(zod@4.3.6)) + '@langchain/core': 1.1.29(openai@6.25.0(zod@4.3.6)) js-tiktoken: 1.0.21 - openai: 6.22.0(ws@8.19.0)(zod@4.3.6) + openai: 6.25.0(zod@4.3.6) zod: 4.3.6 transitivePeerDependencies: - ws - '@modelcontextprotocol/sdk@1.26.0(@cfworker/json-schema@4.1.1)(zod@3.25.76)': + '@modelcontextprotocol/sdk@1.27.1(@cfworker/json-schema@4.1.1)(zod@3.25.76)': dependencies: - '@hono/node-server': 1.19.9(hono@4.11.10) + '@hono/node-server': 1.19.9(hono@4.12.3) ajv: 8.17.1 ajv-formats: 3.0.1(ajv@8.17.1) content-type: 1.0.5 @@ -10357,7 +10655,7 @@ snapshots: eventsource-parser: 3.0.6 express: 5.2.1 express-rate-limit: 8.2.1(express@5.2.1) - hono: 4.11.10 + hono: 4.12.3 jose: 6.1.3 json-schema-typed: 8.0.2 pkce-challenge: 5.0.1 @@ -10369,9 +10667,9 @@ snapshots: transitivePeerDependencies: - supports-color - '@modelcontextprotocol/sdk@1.26.0(@cfworker/json-schema@4.1.1)(zod@4.3.6)': + '@modelcontextprotocol/sdk@1.27.1(@cfworker/json-schema@4.1.1)(zod@4.3.6)': dependencies: - '@hono/node-server': 1.19.9(hono@4.11.10) + '@hono/node-server': 1.19.9(hono@4.12.3) ajv: 8.17.1 ajv-formats: 3.0.1(ajv@8.17.1) content-type: 1.0.5 @@ -10381,7 +10679,7 @@ snapshots: eventsource-parser: 3.0.6 express: 5.2.1 express-rate-limit: 8.2.1(express@5.2.1) - hono: 4.11.10 + hono: 4.12.3 jose: 6.1.3 json-schema-typed: 8.0.2 pkce-challenge: 5.0.1 @@ -11258,6 +11556,8 @@ snapshots: '@types/range-parser@1.2.7': {} + '@types/retry@0.12.0': {} + '@types/safe-timers@1.1.2': {} '@types/send@0.17.5': @@ -11286,7 +11586,7 @@ snapshots: '@types/cookiejar': 2.1.5 '@types/methods': 1.1.4 '@types/node': 24.10.2 - form-data: 4.0.5 + form-data: 4.0.4 '@types/type-is@1.6.7': dependencies: @@ -11497,13 +11797,12 @@ snapshots: '@vueuse/shared': 14.0.0(vue@3.5.25(typescript@5.9.3)) vue: 3.5.25(typescript@5.9.3) - '@vueuse/integrations@14.0.0(axios@1.13.5)(focus-trap@7.6.6)(nprogress@0.2.0)(vue@3.5.25(typescript@5.9.3))': + '@vueuse/integrations@14.0.0(focus-trap@7.6.6)(nprogress@0.2.0)(vue@3.5.25(typescript@5.9.3))': dependencies: '@vueuse/core': 14.0.0(vue@3.5.25(typescript@5.9.3)) '@vueuse/shared': 14.0.0(vue@3.5.25(typescript@5.9.3)) vue: 3.5.25(typescript@5.9.3) optionalDependencies: - axios: 1.13.5 focus-trap: 7.6.6 nprogress: 0.2.0 @@ -11600,6 +11899,8 @@ snapshots: dependencies: color-convert: 2.0.1 + ansi-styles@5.2.0: {} + ansi-styles@6.2.3: {} ansis@3.17.0: {} @@ -11680,15 +11981,6 @@ snapshots: aws-ssl-profiles@1.1.2: {} - axios@1.13.5: - dependencies: - follow-redirects: 1.15.11 - form-data: 4.0.5 - proxy-from-env: 1.1.0 - transitivePeerDependencies: - - debug - optional: true - bail@2.0.2: {} balanced-match@1.0.2: {} @@ -12845,9 +13137,6 @@ snapshots: dependencies: tabbable: 6.3.0 - follow-redirects@1.15.11: - optional: true - for-each@0.3.5: dependencies: is-callable: 1.2.7 @@ -12859,7 +13148,7 @@ snapshots: form-data-encoder@1.9.0: {} - form-data@4.0.5: + form-data@4.0.4: dependencies: asynckit: 0.4.0 combined-stream: 1.0.8 @@ -13119,7 +13408,7 @@ snapshots: heredoc@1.3.1: {} - hono@4.11.10: {} + hono@4.12.3: {} hookable@5.5.3: {} @@ -13361,7 +13650,7 @@ snapshots: is-map@2.0.3: {} - is-network-error@1.3.0: {} + is-network-error@1.3.1: {} is-number-object@1.1.1: dependencies: @@ -13540,6 +13829,11 @@ snapshots: json-parse-even-better-errors@3.0.2: {} + json-schema-to-ts@3.1.1: + dependencies: + '@babel/runtime': 7.28.6 + ts-algebra: 2.0.0 + json-schema-traverse@1.0.0: {} json-schema-typed@8.0.2: {} @@ -13610,13 +13904,13 @@ snapshots: transitivePeerDependencies: - supports-color - langchain@1.2.25(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76)))(openai@6.22.0(ws@8.19.0)(zod@3.25.76))(zod-to-json-schema@3.25.1(zod@3.25.76)): + langchain@1.2.28(@langchain/core@1.1.29(openai@6.25.0(zod@3.25.76)))(openai@6.25.0(zod@3.25.76))(zod-to-json-schema@3.25.1(zod@3.25.76)): dependencies: - '@langchain/core': 1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76)) - '@langchain/langgraph': 1.1.5(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76)))(zod-to-json-schema@3.25.1(zod@3.25.76))(zod@4.3.6) - '@langchain/langgraph-checkpoint': 1.0.0(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@3.25.76))) - langsmith: 0.5.4(openai@6.22.0(ws@8.19.0)(zod@3.25.76)) - uuid: 10.0.0 + '@langchain/core': 1.1.29(openai@6.25.0(zod@3.25.76)) + '@langchain/langgraph': 1.2.0(@langchain/core@1.1.29(openai@6.25.0(zod@3.25.76)))(zod-to-json-schema@3.25.1(zod@3.25.76))(zod@4.3.6) + '@langchain/langgraph-checkpoint': 1.0.0(@langchain/core@1.1.29(openai@6.25.0(zod@3.25.76))) + langsmith: 0.5.7(openai@6.25.0(zod@3.25.76)) + uuid: 11.1.0 zod: 4.3.6 transitivePeerDependencies: - '@opentelemetry/api' @@ -13627,13 +13921,13 @@ snapshots: - react-dom - zod-to-json-schema - langchain@1.2.25(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@4.3.6)))(openai@6.22.0(ws@8.19.0)(zod@4.3.6))(zod-to-json-schema@3.25.1(zod@4.3.6)): + langchain@1.2.28(@langchain/core@1.1.29(openai@6.25.0(zod@4.3.6)))(openai@6.25.0(zod@4.3.6))(zod-to-json-schema@3.25.1(zod@4.3.6)): dependencies: - '@langchain/core': 1.1.26(openai@6.22.0(ws@8.19.0)(zod@4.3.6)) - '@langchain/langgraph': 1.1.5(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@4.3.6)))(zod-to-json-schema@3.25.1(zod@4.3.6))(zod@4.3.6) - '@langchain/langgraph-checkpoint': 1.0.0(@langchain/core@1.1.26(openai@6.22.0(ws@8.19.0)(zod@4.3.6))) - langsmith: 0.5.4(openai@6.22.0(ws@8.19.0)(zod@4.3.6)) - uuid: 10.0.0 + '@langchain/core': 1.1.29(openai@6.25.0(zod@4.3.6)) + '@langchain/langgraph': 1.2.0(@langchain/core@1.1.29(openai@6.25.0(zod@4.3.6)))(zod-to-json-schema@3.25.1(zod@4.3.6))(zod@4.3.6) + '@langchain/langgraph-checkpoint': 1.0.0(@langchain/core@1.1.29(openai@6.25.0(zod@4.3.6))) + langsmith: 0.5.7(openai@6.25.0(zod@4.3.6)) + uuid: 11.1.0 zod: 4.3.6 transitivePeerDependencies: - '@opentelemetry/api' @@ -13644,27 +13938,27 @@ snapshots: - react-dom - zod-to-json-schema - langsmith@0.5.4(openai@6.22.0(ws@8.19.0)(zod@3.25.76)): + langsmith@0.5.7(openai@6.25.0(zod@3.25.76)): dependencies: '@types/uuid': 10.0.0 - chalk: 4.1.2 + chalk: 5.6.2 console-table-printer: 2.15.0 p-queue: 6.6.2 semver: 7.7.3 uuid: 10.0.0 optionalDependencies: - openai: 6.22.0(ws@8.19.0)(zod@3.25.76) + openai: 6.25.0(zod@3.25.76) - langsmith@0.5.4(openai@6.22.0(ws@8.19.0)(zod@4.3.6)): + langsmith@0.5.7(openai@6.25.0(zod@4.3.6)): dependencies: '@types/uuid': 10.0.0 - chalk: 4.1.2 + chalk: 5.6.2 console-table-printer: 2.15.0 p-queue: 6.6.2 semver: 7.7.3 uuid: 10.0.0 optionalDependencies: - openai: 6.22.0(ws@8.19.0)(zod@4.3.6) + openai: 6.25.0(zod@4.3.6) leoric@2.13.8(mysql2@3.15.2): dependencies: @@ -14572,15 +14866,13 @@ snapshots: regex: 6.0.1 regex-recursion: 6.0.2 - openai@6.22.0(ws@8.19.0)(zod@3.25.76): + openai@6.25.0(zod@3.25.76): optionalDependencies: - ws: 8.19.0 zod: 3.25.76 optional: true - openai@6.22.0(ws@8.19.0)(zod@4.3.6): + openai@6.25.0(zod@4.3.6): optionalDependencies: - ws: 8.19.0 zod: 4.3.6 ora@4.1.1: @@ -14736,9 +15028,14 @@ snapshots: eventemitter3: 5.0.1 p-timeout: 7.0.1 + p-retry@4.6.2: + dependencies: + '@types/retry': 0.12.0 + retry: 0.13.1 + p-retry@7.1.1: dependencies: - is-network-error: 1.3.0 + is-network-error: 1.3.1 p-timeout@3.2.0: dependencies: @@ -14934,9 +15231,6 @@ snapshots: forwarded: 0.2.0 ipaddr.js: 1.9.1 - proxy-from-env@1.1.0: - optional: true - prr@1.0.1: optional: true @@ -15120,6 +15414,8 @@ snapshots: retry@0.12.0: {} + retry@0.13.1: {} + reusify@1.1.0: {} rfdc@1.4.1: {} @@ -15634,7 +15930,7 @@ snapshots: cookiejar: 2.1.4 debug: 4.4.3(supports-color@8.1.1) fast-safe-stringify: 2.1.1 - form-data: 4.0.5 + form-data: 4.0.4 formidable: 3.5.4 methods: 1.1.2 mime: 2.6.0 @@ -15755,6 +16051,8 @@ snapshots: trough@2.2.0: {} + ts-algebra@2.0.0: {} + ts-node@10.9.2(@swc/core@1.15.3)(@types/node@24.10.2)(typescript@5.9.3): dependencies: '@cspotcode/source-map-support': 0.8.1 @@ -15983,7 +16281,7 @@ snapshots: urllib@4.8.2: dependencies: - form-data: 4.0.5 + form-data: 4.0.4 formstream: 1.5.2 mime-types: 2.1.35 qs: 6.15.0 @@ -16011,8 +16309,12 @@ snapshots: uuid@10.0.0: {} + uuid@11.1.0: {} + uuid@13.0.0: {} + uuid@9.0.1: {} + v8-compile-cache-lib@3.0.1: {} v8-to-istanbul@9.3.0: @@ -16065,7 +16367,7 @@ snapshots: transitivePeerDependencies: - supports-color - vitepress@2.0.0-alpha.15(@types/node@24.10.2)(axios@1.13.5)(esbuild@0.27.0)(jiti@2.6.1)(less@4.4.2)(nprogress@0.2.0)(oxc-minify@0.105.0)(postcss@8.5.6)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.2): + vitepress@2.0.0-alpha.15(@types/node@24.10.2)(esbuild@0.27.0)(jiti@2.6.1)(less@4.4.2)(nprogress@0.2.0)(oxc-minify@0.105.0)(postcss@8.5.6)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.2): dependencies: '@docsearch/css': 4.3.2 '@docsearch/js': 4.3.2 @@ -16078,7 +16380,7 @@ snapshots: '@vue/devtools-api': 8.0.5 '@vue/shared': 3.5.25 '@vueuse/core': 14.0.0(vue@3.5.25(typescript@5.9.3)) - '@vueuse/integrations': 14.0.0(axios@1.13.5)(focus-trap@7.6.6)(nprogress@0.2.0)(vue@3.5.25(typescript@5.9.3)) + '@vueuse/integrations': 14.0.0(focus-trap@7.6.6)(nprogress@0.2.0)(vue@3.5.25(typescript@5.9.3)) focus-trap: 7.6.6 mark.js: 8.11.1 minisearch: 7.2.0 @@ -16276,9 +16578,6 @@ snapshots: imurmurhash: 0.1.4 signal-exit: 4.1.0 - ws@8.19.0: - optional: true - xml2js@0.6.2: dependencies: sax: 1.4.3 diff --git a/tegg/core/agent-tracing/package.json b/tegg/core/agent-tracing/package.json new file mode 100644 index 0000000000..92253bb234 --- /dev/null +++ b/tegg/core/agent-tracing/package.json @@ -0,0 +1,85 @@ +{ + "name": "@eggjs/agent-tracing", + "version": "4.0.2-beta.3", + "description": "Tracing support for AI agents (LangGraph, Claude Agent SDK)", + "keywords": [ + "agent", + "claude", + "egg", + "langchain", + "langgraph", + "tegg", + "tracing", + "typescript" + ], + "homepage": "https://github.com/eggjs/egg/tree/next/tegg/core/agent-tracing", + "bugs": { + "url": "https://github.com/eggjs/egg/issues" + }, + "license": "MIT", + "author": "killagu ", + "repository": { + "type": "git", + "url": "git+https://github.com/eggjs/egg.git", + "directory": "tegg/core/agent-tracing" + }, + "files": [ + "dist" + ], + "type": "module", + "main": "./dist/index.js", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": "./src/index.ts", + "./claude": "./src/claude.ts", + "./langgraph": "./src/langgraph.ts", + "./package.json": "./package.json" + }, + "publishConfig": { + "access": "public", + "exports": { + ".": "./dist/index.js", + "./claude": "./dist/claude.js", + "./langgraph": "./dist/langgraph.js", + "./package.json": "./package.json" + } + }, + "scripts": { + "typecheck": "tsgo --noEmit", + "test": "vitest run" + }, + "dependencies": { + "@eggjs/background-task": "workspace:*", + "@eggjs/core-decorator": "workspace:*", + "@eggjs/tegg-types": "workspace:*", + "onelogger": "catalog:" + }, + "devDependencies": { + "@anthropic-ai/claude-agent-sdk": "^0.2.52", + "@anthropic-ai/sdk": "^0.78.0", + "@eggjs/tegg-common-util": "workspace:*", + "@langchain/core": "^1.1.29", + "@langchain/langgraph": "^0.2.74", + "@types/node": "catalog:", + "typescript": "catalog:" + }, + "peerDependencies": { + "@anthropic-ai/claude-agent-sdk": ">=0.2.52", + "@langchain/core": "^1.1.29" + }, + "peerDependenciesMeta": { + "@anthropic-ai/claude-agent-sdk": { + "optional": true + }, + "@langchain/core": { + "optional": true + } + }, + "engines": { + "node": ">=22.18.0" + }, + "eggModule": { + "name": "teggAgentTracing" + } +} diff --git a/tegg/core/agent-tracing/src/AbstractLogServiceClient.ts b/tegg/core/agent-tracing/src/AbstractLogServiceClient.ts new file mode 100644 index 0000000000..5b3fb4de37 --- /dev/null +++ b/tegg/core/agent-tracing/src/AbstractLogServiceClient.ts @@ -0,0 +1,31 @@ +/** + * Abstract log service client for dependency injection. + * + * To enable log service syncing in TracingService, implement this class in your application + * and register it with Tegg IoC. The implementation class MUST be named `LogServiceClient` + * (or use `@SingletonProto({ name: 'logServiceClient' })`) so the container can resolve it. + * + * @example + * ```typescript + * import { SingletonProto } from '@eggjs/core-decorator'; + * import { AccessLevel } from '@eggjs/tegg-types'; + * import { AbstractLogServiceClient } from '@eggjs/agent-tracing'; + * + * // Class name must be LogServiceClient (registers as 'logServiceClient' in the IoC container) + * @SingletonProto({ accessLevel: AccessLevel.PUBLIC }) + * export class LogServiceClient extends AbstractLogServiceClient { + * async send(log: string): Promise { + * await fetch('https://log.example.com/api', { + * method: 'POST', + * headers: { 'content-type': 'application/json' }, + * body: JSON.stringify({ log }), + * }); + * } + * } + * ``` + * + * If no implementation is registered, log service syncing is silently skipped. + */ +export abstract class AbstractLogServiceClient { + abstract send(log: string): Promise; +} diff --git a/tegg/core/agent-tracing/src/AbstractOssClient.ts b/tegg/core/agent-tracing/src/AbstractOssClient.ts new file mode 100644 index 0000000000..f0ea395ad1 --- /dev/null +++ b/tegg/core/agent-tracing/src/AbstractOssClient.ts @@ -0,0 +1,27 @@ +/** + * Abstract OSS client for dependency injection. + * + * To enable OSS uploads in TracingService, implement this class in your application + * and register it with Tegg IoC. The implementation class MUST be named `OssClient` + * (or use `@SingletonProto({ name: 'ossClient' })`) so the container can resolve it. + * + * @example + * ```typescript + * import { SingletonProto } from '@eggjs/core-decorator'; + * import { AccessLevel } from '@eggjs/tegg-types'; + * import { AbstractOssClient } from '@eggjs/agent-tracing'; + * + * // Class name must be OssClient (registers as 'ossClient' in the IoC container) + * @SingletonProto({ accessLevel: AccessLevel.PUBLIC }) + * export class OssClient extends AbstractOssClient { + * async put(key: string, content: string | Buffer): Promise { + * // your OSS implementation here + * } + * } + * ``` + * + * If no implementation is registered, OSS uploads are silently skipped. + */ +export abstract class AbstractOssClient { + abstract put(key: string, content: string | Buffer): Promise; +} diff --git a/tegg/core/agent-tracing/src/ClaudeAgentTracer.ts b/tegg/core/agent-tracing/src/ClaudeAgentTracer.ts new file mode 100644 index 0000000000..eba8ae2555 --- /dev/null +++ b/tegg/core/agent-tracing/src/ClaudeAgentTracer.ts @@ -0,0 +1,515 @@ +import { randomUUID } from 'node:crypto'; + +import type { SDKMessage, SDKResultMessage } from '@anthropic-ai/claude-agent-sdk'; +import { SingletonProto, Inject } from '@eggjs/core-decorator'; +import { AccessLevel } from '@eggjs/tegg-types'; +import type { Logger } from '@eggjs/tegg-types'; +import type { Run } from '@langchain/core/tracers/base'; + +import type { TracingService } from './TracingService.ts'; +import { + type ClaudeMessage, + type ClaudeContentBlock, + type ClaudeTokenUsage, + type IRunCost, + RunStatus, + type TracerConfig, + applyTracerConfig, +} from './types.ts'; + +/** + * TraceSession - Manages state for a single agent execution with streaming support. + * Allows processing messages one-by-one and logging them immediately. + */ +export class TraceSession { + #traceId: string; + #rootRun: Run | null = null; + #rootRunId: string; + #startTime: number; + #executionOrder = 2; // Start at 2, root is 1 + #pendingToolUses = new Map(); + #tracer: ClaudeAgentTracer; + + constructor(tracer: ClaudeAgentTracer, sessionId?: string) { + this.#tracer = tracer; + this.#traceId = sessionId || randomUUID(); + this.#rootRunId = randomUUID(); + this.#startTime = Date.now(); + } + + /** + * Process a single SDK message and log it immediately. + * Non-tracing message types (tool_progress, stream_event, status, etc.) are automatically ignored. + */ + async processMessage(message: SDKMessage): Promise { + try { + const converted = this.#tracer.convertSDKMessage(message); + if (!converted) return; + + if (converted.type === 'system' && converted.subtype === 'init') { + this.handleInit(converted); + } else if (converted.type === 'assistant') { + this.handleAssistant(converted); + } else if (converted.type === 'user') { + this.handleUser(converted); + } else if (converted.type === 'result') { + this.handleResult(converted); + } + } catch (e) { + this.#tracer.logger.warn('[ClaudeAgentTracer] processMessage error:', e); + } + } + + private handleInit(message: ClaudeMessage): void { + this.#traceId = message.session_id || this.#traceId; + this.#rootRun = this.#tracer.createRootRunInternal(message, this.#startTime, this.#traceId, this.#rootRunId); + this.#tracer.logTrace(this.#rootRun, RunStatus.START); + } + + private handleAssistant(message: ClaudeMessage): void { + if (!this.#rootRun) { + this.#tracer.logger.warn('[ClaudeAgentTracer] Received assistant message before init'); + return; + } + + const content = message.message?.content || []; + const hasToolUse = content.some((c) => c.type === 'tool_use'); + const hasText = content.some((c) => c.type === 'text'); + + if (hasToolUse) { + const eventTime = Date.now(); + // Create LLM run that initiated tool calls + const llmRun = this.#tracer.createLLMRunInternal( + message, + this.#rootRunId, + this.#traceId, + this.#executionOrder++, + eventTime, + true, + ); + this.#rootRun.child_runs.push(llmRun); + this.#tracer.logTrace(llmRun, RunStatus.END); + + // Create tool runs (will be completed when tool_result arrives) + for (const block of content) { + if (block.type === 'tool_use') { + const toolRun = this.#tracer.createToolRunStartInternal( + block, + this.#rootRunId, + this.#traceId, + this.#executionOrder++, + eventTime, + ); + this.#rootRun.child_runs.push(toolRun); + this.#pendingToolUses.set(block.id, toolRun); + this.#tracer.logTrace(toolRun, RunStatus.START); + } + } + } else if (hasText) { + // Text-only response + const llmRun = this.#tracer.createLLMRunInternal( + message, + this.#rootRunId, + this.#traceId, + this.#executionOrder++, + Date.now(), + false, + ); + this.#rootRun.child_runs.push(llmRun); + this.#tracer.logTrace(llmRun, RunStatus.END); + } + } + + private handleUser(message: ClaudeMessage): void { + if (!message.message?.content) return; + + for (const block of message.message.content) { + if (block.type === 'tool_result') { + const toolRun = this.#pendingToolUses.get(block.tool_use_id); + if (toolRun) { + this.#tracer.completeToolRunInternal(toolRun, block, Date.now()); + const status = block.is_error ? RunStatus.ERROR : RunStatus.END; + this.#tracer.logTrace(toolRun, status); + this.#pendingToolUses.delete(block.tool_use_id); + } + } + } + } + + private handleResult(message: ClaudeMessage): void { + if (!this.#rootRun) { + this.#tracer.logger.warn('[ClaudeAgentTracer] Received result message before init'); + return; + } + + // Complete any pending tool runs + for (const [toolUseId, toolRun] of this.#pendingToolUses) { + this.#tracer.logger.warn(`[ClaudeAgentTracer] Tool run ${toolUseId} did not receive result`); + toolRun.end_time = Date.now(); + this.#tracer.logTrace(toolRun, RunStatus.ERROR); + } + this.#pendingToolUses.clear(); + + // Update and log root run end + this.#rootRun.end_time = this.#startTime + (message.duration_ms || 0); + this.#rootRun.outputs = { + result: message.result, + is_error: message.is_error, + num_turns: message.num_turns, + }; + + if (message.usage || message.modelUsage) { + const cost = this.#tracer.createRunCostInternal(message); + if (this.#rootRun.outputs) { + (this.#rootRun.outputs as any).llmOutput = cost; + } + } + + if (message.is_error) { + this.#rootRun.error = message.result; + } + + this.#rootRun.child_execution_order = this.#executionOrder - 1; + const status = message.is_error ? RunStatus.ERROR : RunStatus.END; + this.#tracer.logTrace(this.#rootRun, status); + } + + /** + * Get current trace ID + */ + getTraceId(): string { + return this.#traceId; + } +} + +/** + * ClaudeAgentTracer - Converts Claude SDK messages to LangChain Run format + * and logs them to the same remote logging system as LangGraphTracer. + * + * Supports both batch processing (processMessages) and streaming (createSession). + */ +@SingletonProto({ + accessLevel: AccessLevel.PUBLIC, +}) +export class ClaudeAgentTracer { + /** @internal */ + @Inject() + readonly logger: Logger; + + @Inject() + private tracingService: TracingService; + + name = 'ClaudeAgentTracer'; + agentName = ''; + + /** + * Configure the tracer with agent name and service credentials. + */ + configure(config: TracerConfig): void { + applyTracerConfig(this, config); + } + + /** + * Create a new trace session for streaming message processing. + * Use this for real-time tracing where messages arrive one-by-one. + * + * @example + * const session = claudeTracer.createSession(); + * for await (const message of agent.run('task')) { + * await session.processMessage(message); + * } + */ + public createSession(sessionId?: string): TraceSession { + return new TraceSession(this, sessionId); + } + + /** + * Main entry point - convert SDK messages to Run trees and log them. + * Use this when you have all messages collected (batch processing). + * For real-time streaming, use createSession() instead. + * + * Non-tracing message types (tool_progress, stream_event, status, etc.) are automatically filtered out. + */ + public async processMessages(sdkMessages: SDKMessage[]): Promise { + try { + if (!sdkMessages || sdkMessages.length === 0) { + this.logger.warn('[ClaudeAgentTracer] No messages to process'); + return; + } + + // Pre-validate: ensure there is an init message before creating session + const hasInit = sdkMessages.some((m) => m.type === 'system' && 'subtype' in m && m.subtype === 'init'); + if (!hasInit) { + this.logger.warn('[ClaudeAgentTracer] No system/init message found'); + return; + } + + // Delegate to TraceSession for message processing + const session = this.createSession(); + for (const msg of sdkMessages) { + await session.processMessage(msg); + } + } catch (e) { + this.logger.warn('[ClaudeAgentTracer] processMessages error:', e); + } + } + + /** + * @internal + * Convert an SDKMessage to internal ClaudeMessage format. + * Returns null for message types that are not relevant to tracing. + */ + convertSDKMessage(msg: SDKMessage): ClaudeMessage | null { + // SDKSystemMessage (init) + if (msg.type === 'system' && 'subtype' in msg && msg.subtype === 'init') { + return msg as unknown as ClaudeMessage; + } + + // SDKAssistantMessage + if (msg.type === 'assistant' && 'message' in msg && 'parent_tool_use_id' in msg) { + return { + type: 'assistant', + uuid: msg.uuid, + session_id: msg.session_id, + message: msg.message as any, + parent_tool_use_id: msg.parent_tool_use_id, + }; + } + + // SDKUserMessage (tool results, not replay) + if (msg.type === 'user' && 'message' in msg && !('isReplay' in msg && (msg as any).isReplay)) { + return { + type: 'user', + uuid: msg.uuid || randomUUID(), + session_id: msg.session_id, + message: msg.message as any, + parent_tool_use_id: (msg as any).parent_tool_use_id, + }; + } + + // SDKResultMessage (success or error) + if (msg.type === 'result') { + const resultMsg = msg as SDKResultMessage; + const isSuccess = resultMsg.subtype === 'success'; + return { + type: 'result', + subtype: isSuccess ? 'success' : 'error', + is_error: resultMsg.is_error, + duration_ms: resultMsg.duration_ms, + duration_api_ms: resultMsg.duration_api_ms, + num_turns: resultMsg.num_turns, + result: isSuccess ? (resultMsg as any).result : (resultMsg as any).errors?.join('; ') || 'Unknown error', + session_id: resultMsg.session_id, + total_cost_usd: resultMsg.total_cost_usd, + usage: resultMsg.usage as any, + modelUsage: resultMsg.modelUsage as any, + uuid: resultMsg.uuid, + }; + } + + // Ignore all other SDK message types (tool_progress, stream_event, status, hook, etc.) + return null; + } + + /** + * @internal + * Create root run from init message (used by TraceSession) + */ + createRootRunInternal(initMsg: ClaudeMessage, startTime: number, traceId: string, rootRunId?: string): Run { + const runId = rootRunId || initMsg.uuid || randomUUID(); + + return { + id: runId, + name: this.name, + run_type: 'chain', + inputs: { + tools: initMsg.tools || [], + model: initMsg.model, + session_id: initMsg.session_id, + mcp_servers: initMsg.mcp_servers, + agents: initMsg.agents, + slash_commands: initMsg.slash_commands, + }, + outputs: undefined, + start_time: startTime, + end_time: undefined, + execution_order: 1, + child_execution_order: 1, + child_runs: [], + events: [], + trace_id: traceId, + parent_run_id: undefined, + tags: [], + extra: { + metadata: { + thread_id: initMsg.session_id, + }, + apiKeySource: initMsg.apiKeySource, + claude_code_version: initMsg.claude_code_version, + output_style: initMsg.output_style, + permissionMode: initMsg.permissionMode, + }, + } as Run; + } + + /** + * @internal + * Create LLM run from assistant message (used by TraceSession) + */ + createLLMRunInternal( + msg: ClaudeMessage, + rootRunId: string, + traceId: string, + order: number, + startTime: number, + isToolCall: boolean, + ): Run { + const runId = msg.uuid || randomUUID(); + const content = msg.message?.content || []; + + const textBlocks = content.filter((c) => c.type === 'text'); + const toolBlocks = content.filter((c) => c.type === 'tool_use'); + + const inputs = { + messages: textBlocks.map((c) => (c as any).text).filter(Boolean), + }; + + const outputs: any = {}; + if (isToolCall) { + outputs.tool_calls = toolBlocks.map((c) => ({ + id: (c as any).id, + name: (c as any).name, + input: (c as any).input, + })); + } else { + outputs.content = textBlocks.map((c) => (c as any).text).join(''); + } + + if (msg.message?.usage) { + outputs.llmOutput = this.extractTokenUsage(msg.message.usage); + } + + return { + id: runId, + name: 'LLM', + run_type: 'llm', + inputs, + outputs, + start_time: startTime, + end_time: startTime, + execution_order: order, + child_execution_order: order, + child_runs: [], + events: [], + trace_id: traceId, + parent_run_id: rootRunId, + tags: [], + extra: { + model: msg.message?.model, + }, + } as Run; + } + + /** + * @internal + * Create tool run at start (before result, used by TraceSession) + */ + createToolRunStartInternal( + toolUseBlock: ClaudeContentBlock, + rootRunId: string, + traceId: string, + order: number, + startTime: number, + ): Run { + const toolUse = toolUseBlock as any; + const runId = randomUUID(); + + return { + id: runId, + name: toolUse.name || 'Tool', + run_type: 'tool', + inputs: { + tool_use_id: toolUse.id, + ...toolUse.input, + }, + outputs: undefined, + start_time: startTime, + end_time: undefined, + execution_order: order, + child_execution_order: order, + child_runs: [], + events: [], + trace_id: traceId, + parent_run_id: rootRunId, + tags: [], + extra: { + tool_use_id: toolUse.id, + }, + } as Run; + } + + /** + * @internal + * Complete tool run with result (used by TraceSession) + */ + completeToolRunInternal(toolRun: Run, toolResultBlock: ClaudeContentBlock, startTime: number): void { + const result = toolResultBlock as any; + toolRun.end_time = startTime; + toolRun.outputs = { + content: result.content, + }; + + if (result.is_error) { + toolRun.error = typeof result.content === 'string' ? result.content : JSON.stringify(result.content); + } + } + + /** + * Extract token usage from Claude SDK usage object into IRunCost format. + */ + private extractTokenUsage(usage: ClaudeTokenUsage): IRunCost { + const result: IRunCost = {}; + + if (usage.input_tokens !== undefined) { + result.promptTokens = usage.input_tokens; + } + if (usage.output_tokens !== undefined) { + result.completionTokens = usage.output_tokens; + } + if (usage.cache_creation_input_tokens !== undefined) { + result.cacheCreationInputTokens = usage.cache_creation_input_tokens; + } + if (usage.cache_read_input_tokens !== undefined) { + result.cacheReadInputTokens = usage.cache_read_input_tokens; + } + + const totalTokens = (usage.input_tokens || 0) + (usage.output_tokens || 0); + if (totalTokens > 0) { + result.totalTokens = totalTokens; + } + + return result; + } + + /** + * @internal + * Create run cost from result message (used by TraceSession) + */ + createRunCostInternal(resultMsg: ClaudeMessage): IRunCost { + const cost: IRunCost = resultMsg.usage ? this.extractTokenUsage(resultMsg.usage) : {}; + + if (resultMsg.total_cost_usd !== undefined) { + cost.totalCost = resultMsg.total_cost_usd; + } + + return cost; + } + + /** + * @internal + * Log trace - delegates to TracingService (used by TraceSession) + */ + logTrace(run: Run, status: RunStatus): void { + this.tracingService.logTrace(run, status, this.name, this.agentName); + } +} diff --git a/tegg/core/agent-tracing/src/LangGraphTracer.ts b/tegg/core/agent-tracing/src/LangGraphTracer.ts new file mode 100644 index 0000000000..8601ba86a0 --- /dev/null +++ b/tegg/core/agent-tracing/src/LangGraphTracer.ts @@ -0,0 +1,82 @@ +import { SingletonProto, Inject } from '@eggjs/core-decorator'; +import { AccessLevel } from '@eggjs/tegg-types'; +import { BaseTracer } from '@langchain/core/tracers/base'; +import type { Run } from '@langchain/core/tracers/base'; + +import type { TracingService } from './TracingService.ts'; +import { RunStatus, type TracerConfig, applyTracerConfig } from './types.ts'; + +@SingletonProto({ + accessLevel: AccessLevel.PUBLIC, +}) +export class LangGraphTracer extends BaseTracer { + @Inject() + private tracingService: TracingService; + + name = 'LangGraphTracer'; + + agentName = ''; + + /** + * Configure the tracer with agent name and service credentials. + */ + configure(config: TracerConfig): void { + applyTracerConfig(this, config); + } + + protected persistRun(_: Run): Promise { + return Promise.resolve(undefined); + } + + private logTrace(run: Run, status: RunStatus): void { + this.tracingService.logTrace(run, status, this.name, this.agentName); + } + + onChainStart(run: Run): void | Promise { + this.logTrace(run, RunStatus.START); + } + onChainEnd(run: Run): void | Promise { + this.logTrace(run, RunStatus.END); + } + onChainError(run: Run): void | Promise { + this.logTrace(run, RunStatus.ERROR); + } + + onToolStart(run: Run): void | Promise { + this.logTrace(run, RunStatus.START); + } + onToolEnd(run: Run): void | Promise { + this.logTrace(run, RunStatus.END); + } + + onToolError(run: Run): void | Promise { + this.logTrace(run, RunStatus.ERROR); + } + + onLLMStart(run: Run): void | Promise { + this.logTrace(run, RunStatus.START); + } + onLLMEnd(run: Run): void | Promise { + this.logTrace(run, RunStatus.END); + } + onLLMError(run: Run): void | Promise { + this.logTrace(run, RunStatus.ERROR); + } + + onRetrieverStart(run: Run): void | Promise { + this.logTrace(run, RunStatus.START); + } + onRetrieverEnd(run: Run): void | Promise { + this.logTrace(run, RunStatus.END); + } + onRetrieverError(run: Run): void | Promise { + this.logTrace(run, RunStatus.ERROR); + } + + onAgentAction(run: Run): void | Promise { + this.logTrace(run, RunStatus.START); + } + onAgentEnd(run: Run): void | Promise { + this.logTrace(run, RunStatus.END); + } +} diff --git a/tegg/core/agent-tracing/src/TracingService.ts b/tegg/core/agent-tracing/src/TracingService.ts new file mode 100644 index 0000000000..a31f32a1fe --- /dev/null +++ b/tegg/core/agent-tracing/src/TracingService.ts @@ -0,0 +1,156 @@ +import type { BackgroundTaskHelper } from '@eggjs/background-task'; +import { SingletonProto, Inject, InjectOptional } from '@eggjs/core-decorator'; +import { AccessLevel } from '@eggjs/tegg-types'; +import type { Logger } from '@eggjs/tegg-types'; +import type { Run } from '@langchain/core/tracers/base'; +import { getCustomLogger } from 'onelogger'; + +import { AbstractLogServiceClient } from './AbstractLogServiceClient.ts'; +import { AbstractOssClient } from './AbstractOssClient.ts'; +import { FIELDS_TO_OSS, type IResource, RunStatus } from './types.ts'; + +/** + * TracingService - Shared service for common tracing operations. + * Used by both LangGraphTracer and ClaudeAgentTracer to avoid code duplication. + */ +@SingletonProto({ + accessLevel: AccessLevel.PUBLIC, +}) +export class TracingService { + @Inject() + public readonly logger: Logger; + + @Inject() + private backgroundTaskHelper: BackgroundTaskHelper; + + @InjectOptional() + private readonly ossClient: AbstractOssClient; + + @InjectOptional() + private readonly logServiceClient: AbstractLogServiceClient; + + /** + * Get the current environment (local, pre, prod, gray) + */ + getEnv(): string { + const env = process.env.FAAS_ENV || process.env.SERVER_ENV || 'local'; + if (env === 'prepub') { + return 'pre'; + } + return env; + } + + /** + * Check if running in online environment (prod, pre, gray) + */ + isOnlineEnv(): boolean { + const env = this.getEnv(); + return ['prod', 'pre', 'gray'].includes(env); + } + + /** + * Generate log info prefix for a run + */ + getLogInfoPrefix(run: Run, status: RunStatus, name: string): string { + const env = this.getEnv(); + const envSegment = process.env.FAAS_ENV || env === 'local' ? '' : `env=${env},`; + const threadId = (run.extra as Record)?.metadata?.thread_id ?? 'unknown'; + return ( + `[agent_run][${name}]:` + + `traceId=${run.trace_id},` + + `threadId=${threadId},` + + `type=${run.parent_run_id ? 'child_run' : 'root_run'},` + + `status=${status},` + + `${envSegment}` + + `run_id=${run.id},` + + `parent_run_id=${run.parent_run_id ?? ''}` + ); + } + + /** + * Upload content to OSS using the injected AbstractOssClient implementation. + * Gracefully skips if no AbstractOssClient is provided. + */ + async uploadToOss(key: string, fileContent: string): Promise { + if (!this.ossClient) { + this.logger.warn('[TracingService] OSS client not configured. Provide an AbstractOssClient implementation.'); + return; + } + this.logger.info(`Uploading to OSS with key: ${key}`); + await this.ossClient.put(key, Buffer.from(fileContent)); + this.logger.info(`Upload completed for key: ${key}`); + } + + /** + * Sync local tracing logs to the injected AbstractLogServiceClient implementation. + * Silently skips if no AbstractLogServiceClient is registered. + */ + async syncLocalToLogService(log: string, agentName: string): Promise { + if (!this.logServiceClient) { + return; + } + if (!agentName) { + this.logger.warn('[TraceLogErr] syncLocalToLogService: agentName is empty'); + return; + } + try { + await this.logServiceClient.send(`[${agentName}]${log}`); + } catch (e) { + this.logger.warn('[TraceLogErr] syncLocalToLogService error:', e); + } + } + + /** + * Log trace run data with OSS upload for large fields + */ + logTrace(run: Run, status: RunStatus, name: string, agentName: string): void { + try { + const { child_runs: childs, ...runData } = run; + if (runData.tags?.includes('langsmith:hidden')) { + return; + } + + const env = this.getEnv(); + FIELDS_TO_OSS.forEach((field) => { + if (!runData[field]) { + return; + } + const jsonstr = JSON.stringify(runData[field]); + if (field === 'outputs') { + (runData as any).cost = runData?.outputs?.llmOutput; + } + delete runData[field]; + const key = `agents/${name}/${env}/traces/${run.trace_id}/runs/${run.id}/${field}`; + this.backgroundTaskHelper.run(async () => { + try { + await this.uploadToOss(key, jsonstr); + } catch (e) { + this.logger.warn( + `[TraceLogErr] Failed to upload run data to OSS for run_id=${run.id}, field=${field}, error:`, + e, + ); + } + }); + (runData as any)[field] = { compress: 'none', key } as IResource; + }); + + const runJSON = JSON.stringify({ ...runData, child_run_ids: childs?.map((child) => child.id) }); + const logInfo = this.getLogInfoPrefix(run, status, name) + `,run=${runJSON}`; + + if (process.env.FAAS_ENV) { + this.logger.info(logInfo); + } else { + const logger = getCustomLogger('agentTraceLogger') || this.logger; + logger.info(`[${agentName}]${logInfo}`); + } + + if (env === 'local') { + this.backgroundTaskHelper.run(async () => { + await this.syncLocalToLogService(logInfo, agentName); + }); + } + } catch (e) { + this.logger.warn('[TraceLogErr] logTrace error:', e); + } + } +} diff --git a/tegg/core/agent-tracing/src/claude.ts b/tegg/core/agent-tracing/src/claude.ts new file mode 100644 index 0000000000..b519ebeb27 --- /dev/null +++ b/tegg/core/agent-tracing/src/claude.ts @@ -0,0 +1,2 @@ +export * from './index.ts'; +export { ClaudeAgentTracer, TraceSession } from './ClaudeAgentTracer.ts'; diff --git a/tegg/core/agent-tracing/src/index.ts b/tegg/core/agent-tracing/src/index.ts new file mode 100644 index 0000000000..c3b07e4719 --- /dev/null +++ b/tegg/core/agent-tracing/src/index.ts @@ -0,0 +1,3 @@ +export * from './types.ts'; +export { AbstractOssClient } from './AbstractOssClient.ts'; +export { AbstractLogServiceClient } from './AbstractLogServiceClient.ts'; diff --git a/tegg/core/agent-tracing/src/langgraph.ts b/tegg/core/agent-tracing/src/langgraph.ts new file mode 100644 index 0000000000..3bd67ac4bc --- /dev/null +++ b/tegg/core/agent-tracing/src/langgraph.ts @@ -0,0 +1,2 @@ +export * from './index.ts'; +export { LangGraphTracer } from './LangGraphTracer.ts'; diff --git a/tegg/core/agent-tracing/src/types.ts b/tegg/core/agent-tracing/src/types.ts new file mode 100644 index 0000000000..949a53851a --- /dev/null +++ b/tegg/core/agent-tracing/src/types.ts @@ -0,0 +1,147 @@ +// Claude SDK Message Types + +export interface ClaudeTextContent { + type: 'text'; + text: string; +} + +export interface ClaudeToolUseContent { + type: 'tool_use'; + id: string; + name: string; + input?: Record; +} + +export interface ClaudeToolResultContent { + type: 'tool_result'; + content: string | ClaudeToolResultContent[]; + tool_use_id: string; + is_error?: boolean; +} + +export type ClaudeContentBlock = ClaudeTextContent | ClaudeToolUseContent | ClaudeToolResultContent; + +export interface ClaudeTokenUsage { + input_tokens: number; + output_tokens: number; + cache_creation_input_tokens?: number; + cache_read_input_tokens?: number; + server_tool_use?: { + web_search_requests?: number; + web_fetch_requests?: number; + }; + service_tier?: string; + cache_creation?: { + ephemeral_1h_input_tokens?: number; + ephemeral_5m_input_tokens?: number; + }; +} + +export interface ClaudeMessageContent { + id?: string; + type?: string; + role?: string; + content?: ClaudeContentBlock[]; + model?: string; + usage?: ClaudeTokenUsage; + context_management?: any; + stop_reason?: string; + stop_sequence?: string; + container?: any; + [key: string]: any; +} + +export interface ClaudeModelUsage { + [modelName: string]: { + inputTokens: number; + outputTokens: number; + cacheReadInputTokens?: number; + cacheCreationInputTokens?: number; + webSearchRequests?: number; + costUSD?: number; + contextWindow?: number; + maxOutputTokens?: number; + }; +} + +export interface ClaudeMessage { + type: 'system' | 'assistant' | 'user' | 'result'; + subtype?: 'init' | 'success' | 'error'; + session_id?: string; + uuid: string; + message?: ClaudeMessageContent; + + // System/init message fields + cwd?: string; + tools?: string[]; + mcp_servers?: Array<{ name: string; status: string }>; + model?: string; + permissionMode?: string; + slash_commands?: string[]; + apiKeySource?: string; + claude_code_version?: string; + output_style?: string; + agents?: string[]; + skills?: any[]; + plugins?: any[]; + + // Result message fields + is_error?: boolean; + duration_ms?: number; + duration_api_ms?: number; + num_turns?: number; + result?: string; + total_cost_usd?: number; + usage?: ClaudeTokenUsage; + modelUsage?: ClaudeModelUsage; + permission_denials?: any[]; + + // User message fields (tool results) + parent_tool_use_id?: string | null; + tool_use_id?: string; + tool_use_result?: string; + + // Error fields + error?: string; + + [key: string]: any; +} + +// Shared tracing types (from LangGraphTracer) + +export interface IResource { + compress: 'none' | 'gzip'; + key: string; +} + +export interface IRunCost { + promptTokens?: number; + completionTokens?: number; + totalTokens?: number; + cacheCreationInputTokens?: number; + cacheReadInputTokens?: number; + totalCost?: number; +} + +const FIELDS_TO_OSS = ['inputs', 'outputs', 'attachments', 'serialized', 'events'] as const; + +export const RunStatus = { + START: 'start', + END: 'end', + ERROR: 'error', +} as const; +export type RunStatus = (typeof RunStatus)[keyof typeof RunStatus]; + +/** User-facing config passed to tracer.configure() */ +export interface TracerConfig { + agentName?: string; +} + +/** Apply user-facing TracerConfig to a tracer instance. */ +export function applyTracerConfig(tracer: { agentName: string }, config: TracerConfig): void { + if (config.agentName !== undefined) { + tracer.agentName = config.agentName; + } +} + +export { FIELDS_TO_OSS }; diff --git a/tegg/core/agent-tracing/test/ClaudeAgentTracer.test.ts b/tegg/core/agent-tracing/test/ClaudeAgentTracer.test.ts new file mode 100644 index 0000000000..cfc3d9fdb5 --- /dev/null +++ b/tegg/core/agent-tracing/test/ClaudeAgentTracer.test.ts @@ -0,0 +1,436 @@ +import assert from 'node:assert/strict'; + +import type { SDKMessage } from '@anthropic-ai/claude-agent-sdk'; +import { afterEach, beforeEach, describe, it } from 'vitest'; + +import { ClaudeAgentTracer } from '../src/ClaudeAgentTracer.ts'; +import { RunStatus } from '../src/types.ts'; +import { createMockLogger, createCapturingTracingService } from './TestUtils.ts'; + +// ---------- Shared setup ---------- + +function createTestEnv() { + const { tracingService, capturedRuns } = createCapturingTracingService(); + const mockLogger = createMockLogger(); + + const claudeTracer = new ClaudeAgentTracer(); + (claudeTracer as any).logger = mockLogger; + (claudeTracer as any).tracingService = tracingService; + + return { claudeTracer, capturedRuns }; +} + +// ---------- Mock data factories ---------- + +function createMockInit(overrides?: Partial): SDKMessage { + return { + type: 'system', + subtype: 'init', + session_id: 'test-session-001', + uuid: 'uuid-init', + tools: ['Bash', 'Read'], + model: 'claude-sonnet-4-5-20250929', + cwd: '/test', + mcp_servers: [], + permissionMode: 'default', + apiKeySource: 'api_key', + claude_code_version: '1.0.0', + output_style: 'text', + slash_commands: [], + skills: [], + plugins: [], + ...overrides, + } as unknown as SDKMessage; +} + +function createMockAssistantWithTool(overrides?: Partial): SDKMessage { + return { + type: 'assistant', + uuid: 'uuid-assistant-tool', + session_id: 'test-session-001', + parent_tool_use_id: null, + message: { + id: 'msg_1', + type: 'message', + role: 'assistant', + content: [ + { type: 'text', text: 'Let me run that command for you.' }, + { type: 'tool_use', id: 'tu_1', name: 'Bash', input: { command: 'echo hello' } }, + ], + model: 'claude-sonnet-4-5-20250929', + usage: { input_tokens: 100, output_tokens: 50 }, + stop_reason: 'tool_use', + }, + ...overrides, + } as unknown as SDKMessage; +} + +function createMockUserToolResult(overrides?: Partial): SDKMessage { + return { + type: 'user', + uuid: 'uuid-user-result', + session_id: 'test-session-001', + parent_tool_use_id: null, + message: { + role: 'user', + content: [ + { + type: 'tool_result', + tool_use_id: 'tu_1', + content: 'hello', + is_error: false, + }, + ], + }, + ...overrides, + } as unknown as SDKMessage; +} + +function createMockAssistantTextOnly(overrides?: Partial): SDKMessage { + return { + type: 'assistant', + uuid: 'uuid-assistant-text', + session_id: 'test-session-001', + parent_tool_use_id: null, + message: { + id: 'msg_2', + type: 'message', + role: 'assistant', + content: [{ type: 'text', text: 'The answer is 21.' }], + model: 'claude-sonnet-4-5-20250929', + usage: { input_tokens: 80, output_tokens: 30 }, + stop_reason: 'end_turn', + }, + ...overrides, + } as unknown as SDKMessage; +} + +function createMockResult(overrides?: Partial): SDKMessage { + return { + type: 'result', + subtype: 'success', + session_id: 'test-session-001', + uuid: 'uuid-result', + is_error: false, + duration_ms: 1500, + duration_api_ms: 1200, + num_turns: 1, + result: 'hello', + stop_reason: null, + total_cost_usd: 0.003, + usage: { + input_tokens: 100, + output_tokens: 50, + cache_creation_input_tokens: 0, + cache_read_input_tokens: 0, + }, + modelUsage: {}, + permission_denials: [], + ...overrides, + } as unknown as SDKMessage; +} + +// Noise messages that should be filtered out +function createMockToolProgress(): SDKMessage { + return { + type: 'tool_progress', + tool_use_id: 'tu_1', + tool_name: 'Bash', + parent_tool_use_id: null, + elapsed_time_seconds: 0.5, + uuid: 'uuid-progress', + session_id: 'test-session-001', + } as unknown as SDKMessage; +} + +function createMockStreamEvent(): SDKMessage { + return { + type: 'stream_event', + event: { type: 'content_block_start', index: 0, content_block: { type: 'text', text: '' } }, + parent_tool_use_id: null, + uuid: 'uuid-stream', + session_id: 'test-session-001', + } as unknown as SDKMessage; +} + +// ---------- Tests ---------- + +describe('test/ClaudeAgentTracer.test.ts', () => { + let originalFaasEnv: string | undefined; + + beforeEach(() => { + originalFaasEnv = process.env.FAAS_ENV; + process.env.FAAS_ENV = 'dev'; + }); + + afterEach(() => { + if (originalFaasEnv === undefined) { + delete process.env.FAAS_ENV; + } else { + process.env.FAAS_ENV = originalFaasEnv; + } + }); + + describe('Streaming mode + tool use', () => { + it('should trace tool execution with session.processMessage', async () => { + const { claudeTracer, capturedRuns } = createTestEnv(); + const session = claudeTracer.createSession(); + + // Feed messages one-by-one, including noise messages that should be filtered + const messages: SDKMessage[] = [ + createMockInit(), + createMockStreamEvent(), // noise — should be ignored + createMockAssistantWithTool(), + createMockToolProgress(), // noise — should be ignored + createMockUserToolResult(), + createMockResult(), + ]; + + for (const msg of messages) { + await session.processMessage(msg); + } + + // Root run start + end + const rootStart = capturedRuns.find((e) => !e.run.parent_run_id && e.status === RunStatus.START); + assert(rootStart, 'Should have root_run start'); + assert.strictEqual(rootStart.run.run_type, 'chain'); + + const rootEnd = capturedRuns.find((e) => !e.run.parent_run_id && e.status === RunStatus.END); + assert(rootEnd, 'Should have root_run end'); + + // LLM child run + const llmRuns = capturedRuns.filter((e) => !!e.run.parent_run_id && e.run.run_type === 'llm'); + assert(llmRuns.length >= 1, `Should have >= 1 LLM run, got ${llmRuns.length}`); + + // Tool child run start + end + const toolRuns = capturedRuns.filter((e) => !!e.run.parent_run_id && e.run.run_type === 'tool'); + assert(toolRuns.length >= 2, `Should have >= 2 tool run entries (start+end), got ${toolRuns.length}`); + + const toolStart = toolRuns.find((e) => e.status === RunStatus.START); + assert(toolStart, 'Should have tool start'); + assert.strictEqual(toolStart.run.name, 'Bash'); + + const toolEnd = toolRuns.find((e) => e.status === RunStatus.END); + assert(toolEnd, 'Should have tool end'); + + // All runs share the same trace_id = session_id + const traceIds = new Set(capturedRuns.map((e) => e.run.trace_id)); + assert.strictEqual(traceIds.size, 1, `All runs should share one trace_id, got ${traceIds.size}`); + assert.strictEqual([...traceIds][0], 'test-session-001', 'trace_id should match session_id'); + + // Root run should carry session_id as thread_id in extra.metadata + const rootExtra = rootStart.run.extra as Record; + assert.strictEqual(rootExtra?.metadata?.thread_id, 'test-session-001', 'thread_id should match session_id'); + + // Child runs reference root run as parent + const childEntries = capturedRuns.filter((e) => !!e.run.parent_run_id); + for (const child of childEntries) { + assert.strictEqual( + child.run.parent_run_id, + rootStart.run.id, + `Child run ${child.run.name} should reference root as parent`, + ); + } + + // Cost data on root end + const llmOutput = (rootEnd.run.outputs as any)?.llmOutput; + assert(llmOutput, 'Root end should have llmOutput'); + assert.strictEqual(llmOutput.promptTokens, 100); + assert.strictEqual(llmOutput.completionTokens, 50); + assert.strictEqual(llmOutput.totalCost, 0.003); + }); + }); + + describe('Batch mode + text-only', () => { + it('should trace a text-only response via processMessages', async () => { + const { claudeTracer, capturedRuns } = createTestEnv(); + + const messages: SDKMessage[] = [ + createMockInit(), + createMockAssistantTextOnly(), + createMockResult({ + usage: { + input_tokens: 80, + output_tokens: 30, + cache_creation_input_tokens: 0, + cache_read_input_tokens: 0, + }, + total_cost_usd: 0.002, + }), + ]; + + await claudeTracer.processMessages(messages); + + assert(capturedRuns.length > 0, 'Should have tracing entries'); + + // Root run start + end + const rootEntries = capturedRuns.filter((e) => !e.run.parent_run_id); + assert(rootEntries.length >= 2, `Should have root start + end, got ${rootEntries.length}`); + + const rootEnd = rootEntries.find((e) => e.status === RunStatus.END); + assert(rootEnd, 'Should have root end'); + + // LLM child run with text content + const llmRuns = capturedRuns.filter((e) => !!e.run.parent_run_id && e.run.run_type === 'llm'); + assert(llmRuns.length >= 1, `Should have >= 1 LLM run, got ${llmRuns.length}`); + + // No tool runs + const toolRuns = capturedRuns.filter((e) => !!e.run.parent_run_id && e.run.run_type === 'tool'); + assert.strictEqual(toolRuns.length, 0, 'Should have no tool runs for text-only'); + + // Cost and token counts + const llmOutput = (rootEnd.run.outputs as any)?.llmOutput; + assert(llmOutput, 'Should have llmOutput'); + assert.strictEqual(llmOutput.promptTokens, 80); + assert.strictEqual(llmOutput.completionTokens, 30); + assert.strictEqual(llmOutput.totalTokens, 110); + assert.strictEqual(llmOutput.totalCost, 0.002); + + // trace_id consistency + const traceIds = new Set(capturedRuns.map((e) => e.run.trace_id)); + assert.strictEqual(traceIds.size, 1, 'All runs should share one trace_id'); + }); + }); + + describe('Error scenario', () => { + it('should trace an error result with ERROR status', async () => { + const { claudeTracer, capturedRuns } = createTestEnv(); + const session = claudeTracer.createSession(); + + const messages: SDKMessage[] = [ + createMockInit(), + createMockAssistantTextOnly(), + { + type: 'result', + subtype: 'error_during_execution', + session_id: 'test-session-001', + uuid: 'uuid-result-err', + is_error: true, + duration_ms: 500, + duration_api_ms: 400, + num_turns: 1, + stop_reason: null, + total_cost_usd: 0.001, + errors: ['Something went wrong', 'Another error'], + usage: { + input_tokens: 50, + output_tokens: 10, + cache_creation_input_tokens: 0, + cache_read_input_tokens: 0, + }, + modelUsage: {}, + permission_denials: [], + } as unknown as SDKMessage, + ]; + + for (const msg of messages) { + await session.processMessage(msg); + } + + // Root run should end with ERROR status + const rootError = capturedRuns.find((e) => !e.run.parent_run_id && e.status === RunStatus.ERROR); + assert(rootError, 'Should have root_run with error status'); + assert(rootError.run, 'Root error run should exist'); + }); + }); + + describe('Guard clauses — messages before init', () => { + it('should warn and ignore assistant message received before init', async () => { + const { claudeTracer, capturedRuns } = createTestEnv(); + const session = claudeTracer.createSession(); + + // Send assistant message without a preceding init + await session.processMessage(createMockAssistantWithTool()); + + // Nothing should have been traced + assert.strictEqual(capturedRuns.length, 0, 'No runs should be captured before init'); + }); + + it('should warn and ignore result message received before init', async () => { + const { claudeTracer, capturedRuns } = createTestEnv(); + const session = claudeTracer.createSession(); + + // Send result message without a preceding init + await session.processMessage(createMockResult()); + + // Nothing should have been traced + assert.strictEqual(capturedRuns.length, 0, 'No runs should be captured before init'); + }); + }); + + describe('Pending tool runs cleanup on result', () => { + it('should log ERROR for pending tool runs that never received a result', async () => { + const { claudeTracer, capturedRuns } = createTestEnv(); + const session = claudeTracer.createSession(); + + // Init → assistant calls a tool → result arrives WITHOUT the tool_result + const messages: SDKMessage[] = [ + createMockInit(), + createMockAssistantWithTool(), // creates a pending tool run (tu_1) + createMockResult(), // result arrives before tool_result + ]; + + for (const msg of messages) { + await session.processMessage(msg); + } + + // The pending tool run should have been force-closed with ERROR status + const toolErrors = capturedRuns.filter((e) => e.run.run_type === 'tool' && e.status === RunStatus.ERROR); + assert(toolErrors.length >= 1, `Should have at least one tool ERROR entry, got ${toolErrors.length}`); + }); + }); + + describe('processMessages edge cases', () => { + it('should warn and return early for empty message array', async () => { + const { claudeTracer, capturedRuns } = createTestEnv(); + + await claudeTracer.processMessages([]); + + assert.strictEqual(capturedRuns.length, 0, 'No runs should be captured for empty input'); + }); + + it('should warn and return early when no init message is present', async () => { + const { claudeTracer, capturedRuns } = createTestEnv(); + + // Only assistant and result, no system/init + const messages: SDKMessage[] = [createMockAssistantTextOnly(), createMockResult()]; + + await claudeTracer.processMessages(messages); + + assert.strictEqual(capturedRuns.length, 0, 'No runs should be captured without an init message'); + }); + }); + + describe('Internal error handling', () => { + it('should catch errors thrown inside processMessage without propagating', async () => { + const { claudeTracer } = createTestEnv(); + const session = claudeTracer.createSession(); + + // Force an error by replacing logTrace with a throwing stub + (claudeTracer as any).tracingService = { + logTrace: () => { + throw new Error('unexpected logTrace error'); + }, + }; + + // Should NOT throw — error is swallowed by the catch block + await assert.doesNotReject(async () => { + await session.processMessage(createMockInit()); + }); + }); + + it('should catch errors thrown inside processMessages without propagating', async () => { + const { claudeTracer } = createTestEnv(); + + // Replace createSession with a throwing stub to trigger the outer catch + (claudeTracer as any).createSession = () => { + throw new Error('unexpected createSession error'); + }; + + // Should NOT throw — error is swallowed by the catch block + await assert.doesNotReject(async () => { + await claudeTracer.processMessages([createMockInit()]); + }); + }); + }); +}); diff --git a/tegg/core/agent-tracing/test/Configure.test.ts b/tegg/core/agent-tracing/test/Configure.test.ts new file mode 100644 index 0000000000..18dffd7e93 --- /dev/null +++ b/tegg/core/agent-tracing/test/Configure.test.ts @@ -0,0 +1,65 @@ +import assert from 'node:assert/strict'; + +import { describe, it } from 'vitest'; + +import { ClaudeAgentTracer } from '../src/ClaudeAgentTracer.ts'; +import { LangGraphTracer } from '../src/LangGraphTracer.ts'; +import { createMockLogger, createCapturingTracingService } from './TestUtils.ts'; + +describe('test/Configure.test.ts', () => { + describe('LangGraphTracer.configure()', () => { + it('should set agentName and delegate to TracingService', () => { + const { tracingService } = createCapturingTracingService(); + const tracer = new LangGraphTracer(); + (tracer as any).tracingService = tracingService; + + tracer.configure({ + agentName: 'MyAgent', + }); + + assert.strictEqual(tracer.agentName, 'MyAgent'); + assert.strictEqual(tracer.name, 'LangGraphTracer', 'name should remain default'); + }); + + it('should not change agentName when not provided', () => { + const { tracingService } = createCapturingTracingService(); + const tracer = new LangGraphTracer(); + (tracer as any).tracingService = tracingService; + tracer.agentName = 'existing'; + + tracer.configure({}); + + assert.strictEqual(tracer.agentName, 'existing'); + }); + }); + + describe('ClaudeAgentTracer.configure()', () => { + it('should set agentName and delegate to TracingService', () => { + const { tracingService } = createCapturingTracingService(); + const mockLogger = createMockLogger(); + const tracer = new ClaudeAgentTracer(); + (tracer as any).logger = mockLogger; + (tracer as any).tracingService = tracingService; + + tracer.configure({ + agentName: 'MyClaude', + }); + + assert.strictEqual(tracer.agentName, 'MyClaude'); + assert.strictEqual(tracer.name, 'ClaudeAgentTracer', 'name should remain default'); + }); + + it('should not change agentName when not provided', () => { + const { tracingService } = createCapturingTracingService(); + const mockLogger = createMockLogger(); + const tracer = new ClaudeAgentTracer(); + (tracer as any).logger = mockLogger; + (tracer as any).tracingService = tracingService; + tracer.agentName = 'existing'; + + tracer.configure({}); + + assert.strictEqual(tracer.agentName, 'existing'); + }); + }); +}); diff --git a/tegg/core/agent-tracing/test/LangGraphTracer.test.ts b/tegg/core/agent-tracing/test/LangGraphTracer.test.ts new file mode 100644 index 0000000000..a3dc9268b8 --- /dev/null +++ b/tegg/core/agent-tracing/test/LangGraphTracer.test.ts @@ -0,0 +1,316 @@ +import assert from 'node:assert/strict'; + +import type { Run } from '@langchain/core/tracers/base'; +import { FakeLLM } from '@langchain/core/utils/testing'; +import { StateGraph, Annotation, START, END } from '@langchain/langgraph'; +import { afterEach, beforeEach, describe, it } from 'vitest'; + +import { LangGraphTracer } from '../src/LangGraphTracer.ts'; +import { RunStatus } from '../src/types.ts'; +import { type CapturedEntry, createCapturingTracingService, createMockRun } from './TestUtils.ts'; + +const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); + +/** Shared state schema for test graphs */ +const GraphState = Annotation.Root({ + query: Annotation, + result: Annotation, +}); + +describe('test/LangGraphTracer.test.ts', () => { + let tracer: LangGraphTracer; + let capturedRuns: CapturedEntry[]; + let originalFaasEnv: string | undefined; + + beforeEach(() => { + originalFaasEnv = process.env.FAAS_ENV; + process.env.FAAS_ENV = 'dev'; + + const capturing = createCapturingTracingService(); + capturedRuns = capturing.capturedRuns; + + tracer = new LangGraphTracer(); + (tracer as any).tracingService = capturing.tracingService; + }); + + afterEach(() => { + if (originalFaasEnv === undefined) { + delete process.env.FAAS_ENV; + } else { + process.env.FAAS_ENV = originalFaasEnv; + } + }); + + describe('Single-node StateGraph triggers chain lifecycle hooks', () => { + it('should trigger onChainStart and onChainEnd via graph.invoke', async () => { + const graph = new StateGraph(GraphState) + .addNode('process', (state: typeof GraphState.State) => { + return { result: `processed: ${state.query}` }; + }) + .addEdge(START, 'process') + .addEdge('process', END) + .compile(); + + await graph.invoke({ query: 'hello', result: '' }, { callbacks: [tracer] }); + + await sleep(500); + + const startEntries = capturedRuns.filter((e) => e.status === RunStatus.START); + const endEntries = capturedRuns.filter((e) => e.status === RunStatus.END); + + assert(startEntries.length >= 1, `Should have at least one start entry, got ${startEntries.length}`); + assert(endEntries.length >= 1, `Should have at least one end entry, got ${endEntries.length}`); + + // Verify a chain run is present with run_type=chain + const chainStart = startEntries.find((e) => e.run.run_type === 'chain'); + assert(chainStart, 'Should have a chain start entry with run_type=chain'); + }); + + it('should produce Run with valid id, trace_id, and run_type fields', async () => { + const graph = new StateGraph(GraphState) + .addNode('echo', (state: typeof GraphState.State) => { + return { result: state.query }; + }) + .addEdge(START, 'echo') + .addEdge('echo', END) + .compile(); + + await graph.invoke({ query: 'test', result: '' }, { callbacks: [tracer] }); + + await sleep(500); + + assert(capturedRuns.length > 0, 'Should have captured runs'); + + for (const entry of capturedRuns) { + assert(entry.run.id, 'Run should have an id'); + assert(entry.run.trace_id, 'Run should have a trace_id'); + assert(entry.run.run_type, 'Run should have a run_type'); + } + }); + }); + + describe('Multi-node linear StateGraph triggers parent-child chain hooks', () => { + it('should generate root and child chain runs with parent_run_id relationship', async () => { + const graph = new StateGraph(GraphState) + .addNode('preprocess', (state: typeof GraphState.State) => { + return { query: state.query.toUpperCase() }; + }) + .addNode('respond', (state: typeof GraphState.State) => { + return { result: `answer to ${state.query}` }; + }) + .addEdge(START, 'preprocess') + .addEdge('preprocess', 'respond') + .addEdge('respond', END) + .compile(); + + await graph.invoke({ query: 'question', result: '' }, { callbacks: [tracer] }); + + await sleep(500); + + const chainEntries = capturedRuns.filter((e) => e.run.run_type === 'chain'); + assert( + chainEntries.length >= 2, + `Should have at least 2 chain runs (root + child nodes), got ${chainEntries.length}`, + ); + + // The graph root run should have no parent_run_id + const rootRun = chainEntries.find((e) => !e.run.parent_run_id); + assert(rootRun, 'Should have a root chain run (no parent_run_id)'); + + // Child node runs should have parent_run_id + const childRuns = chainEntries.filter((e) => e.run.parent_run_id); + assert(childRuns.length >= 1, 'Should have at least one child chain run with parent_run_id'); + + // Verify root vs child distinction + const rootEntries = capturedRuns.filter((e) => !e.run.parent_run_id); + const childEntries = capturedRuns.filter((e) => !!e.run.parent_run_id); + assert(rootEntries.length >= 1, 'Should have root run entries'); + assert(childEntries.length >= 1, 'Should have child run entries'); + }); + + it('should share the same trace_id across all runs in a graph invocation', async () => { + const graph = new StateGraph(GraphState) + .addNode('step1', (state: typeof GraphState.State) => { + return { query: `[step1] ${state.query}` }; + }) + .addNode('step2', (state: typeof GraphState.State) => { + return { result: `[step2] ${state.query}` }; + }) + .addEdge(START, 'step1') + .addEdge('step1', 'step2') + .addEdge('step2', END) + .compile(); + + await graph.invoke({ query: 'hello', result: '' }, { callbacks: [tracer] }); + + await sleep(500); + + const traceIds = new Set(capturedRuns.map((e) => e.run.trace_id).filter(Boolean)); + assert.strictEqual( + traceIds.size, + 1, + `All runs should share the same trace_id, got ${traceIds.size} distinct trace_ids`, + ); + }); + }); + + describe('StateGraph with LLM node triggers both chain and LLM hooks', () => { + it('should trace both chain runs (graph) and LLM runs (FakeLLM inside node)', async () => { + const llm = new FakeLLM({ response: 'llm answer' }); + + const graph = new StateGraph(GraphState) + .addNode('ask_llm', async (state: typeof GraphState.State) => { + const response = await llm.invoke(state.query); + return { result: response }; + }) + .addEdge(START, 'ask_llm') + .addEdge('ask_llm', END) + .compile(); + + await graph.invoke({ query: 'what is LangGraph?', result: '' }, { callbacks: [tracer] }); + + await sleep(500); + + // Should have chain runs from the graph itself + const chainEntries = capturedRuns.filter((e) => e.run.run_type === 'chain'); + assert(chainEntries.length >= 1, `Should have at least one chain run, got ${chainEntries.length}`); + + // Should have LLM runs from the FakeLLM invocation inside the node + const llmEntries = capturedRuns.filter((e) => e.run.run_type === 'llm'); + assert(llmEntries.length >= 2, `Should have at least 2 LLM entries (start + end), got ${llmEntries.length}`); + }); + }); + + describe('StateGraph node error triggers onChainError', () => { + it('should trigger error hook and include error status in captured runs', async () => { + const graph = new StateGraph(GraphState) + .addNode('fail_node', () => { + throw new Error('Node execution failed'); + }) + .addEdge(START, 'fail_node') + .addEdge('fail_node', END) + .compile(); + + try { + await graph.invoke({ query: 'trigger error', result: '' }, { callbacks: [tracer] }); + } catch { + // Expected error + } + + await sleep(500); + + // Should have a start entry + const startEntries = capturedRuns.filter((e) => e.status === RunStatus.START); + assert(startEntries.length >= 1, 'Should have at least one start entry before the error'); + + // Should have an error entry + const errorEntries = capturedRuns.filter((e) => e.status === RunStatus.ERROR); + assert(errorEntries.length >= 1, `Should have at least one error entry, got ${errorEntries.length}`); + + // Verify the error run has the error field set + assert(errorEntries[0].run, 'Error run should exist'); + assert(errorEntries[0].run.error, 'Error run should have error field set'); + }); + }); + + describe('Direct hook invocation (unit coverage)', () => { + it('should log tool hooks: onToolStart, onToolEnd, onToolError', () => { + const run = createMockRun({ run_type: 'tool', name: 'BashTool' }); + tracer.onToolStart(run); + tracer.onToolEnd(run); + tracer.onToolError({ ...run, error: 'tool failed' } as Run); + + const toolEntries = capturedRuns.filter((e) => e.run.run_type === 'tool'); + assert.strictEqual(toolEntries.length, 3); + assert.strictEqual(toolEntries[0].status, RunStatus.START); + assert.strictEqual(toolEntries[1].status, RunStatus.END); + assert.strictEqual(toolEntries[2].status, RunStatus.ERROR); + }); + + it('should log LLM hooks: onLLMStart, onLLMEnd, onLLMError', () => { + const run = createMockRun({ run_type: 'llm', name: 'claude-3' }); + tracer.onLLMStart(run); + tracer.onLLMEnd(run); + tracer.onLLMError({ ...run, error: 'llm error' } as Run); + + const llmEntries = capturedRuns.filter((e) => e.run.run_type === 'llm'); + assert.strictEqual(llmEntries.length, 3); + assert.strictEqual(llmEntries[0].status, RunStatus.START); + assert.strictEqual(llmEntries[1].status, RunStatus.END); + assert.strictEqual(llmEntries[2].status, RunStatus.ERROR); + }); + + it('should log retriever hooks: onRetrieverStart, onRetrieverEnd, onRetrieverError', () => { + const run = createMockRun({ run_type: 'retriever', name: 'VectorRetriever' }); + tracer.onRetrieverStart(run); + tracer.onRetrieverEnd(run); + tracer.onRetrieverError({ ...run, error: 'retriever error' } as Run); + + const retrieverEntries = capturedRuns.filter((e) => e.run.run_type === 'retriever'); + assert.strictEqual(retrieverEntries.length, 3); + assert.strictEqual(retrieverEntries[0].status, RunStatus.START); + assert.strictEqual(retrieverEntries[1].status, RunStatus.END); + assert.strictEqual(retrieverEntries[2].status, RunStatus.ERROR); + }); + + it('should log agent hooks: onAgentAction, onAgentEnd', () => { + const run = createMockRun({ run_type: 'chain', name: 'AgentExecutor' }); + tracer.onAgentAction(run); + tracer.onAgentEnd(run); + + assert.strictEqual(capturedRuns[0].status, RunStatus.START); + assert.strictEqual(capturedRuns[1].status, RunStatus.END); + }); + }); + + describe('Run data completeness via graph.invoke', () => { + it('should produce runs with all required fields populated', async () => { + const graph = new StateGraph(GraphState) + .addNode('check', (state: typeof GraphState.State) => { + return { result: `checked: ${state.query}` }; + }) + .addEdge(START, 'check') + .addEdge('check', END) + .compile(); + + await graph.invoke({ query: 'check fields', result: '' }, { callbacks: [tracer] }); + + await sleep(500); + + assert(capturedRuns.length > 0, 'Should have captured runs'); + + for (const entry of capturedRuns) { + assert(typeof entry.run.id === 'string' && entry.run.id.length > 0, 'Run must have non-empty id'); + assert( + typeof entry.run.trace_id === 'string' && entry.run.trace_id.length > 0, + 'Run must have non-empty trace_id', + ); + assert(typeof entry.run.run_type === 'string', 'Run must have run_type'); + assert(typeof entry.run.name === 'string', 'Run must have name'); + } + }); + + it('should produce end runs with outputs present', async () => { + const graph = new StateGraph(GraphState) + .addNode('output_node', (state: typeof GraphState.State) => { + return { result: `output for ${state.query}` }; + }) + .addEdge(START, 'output_node') + .addEdge('output_node', END) + .compile(); + + await graph.invoke({ query: 'output test', result: '' }, { callbacks: [tracer] }); + + await sleep(500); + + const endEntries = capturedRuns.filter((e) => e.status === RunStatus.END); + assert(endEntries.length >= 1, 'Should have end entries'); + + for (const entry of endEntries) { + if (entry.run.outputs) { + assert(typeof entry.run.outputs === 'object', 'End run outputs should be an object'); + } + } + }); + }); +}); diff --git a/tegg/core/agent-tracing/test/TestUtils.ts b/tegg/core/agent-tracing/test/TestUtils.ts new file mode 100644 index 0000000000..59343f2903 --- /dev/null +++ b/tegg/core/agent-tracing/test/TestUtils.ts @@ -0,0 +1,64 @@ +import type { Logger } from '@eggjs/tegg-types'; +import type { Run } from '@langchain/core/tracers/base'; + +import { TracingService } from '../src/TracingService.ts'; + +export interface CapturedEntry { + run: Run; + status: string; + name: string; + agentName: string; +} + +export function createMockRun(overrides?: Partial): Run { + return { + id: 'run-001', + name: 'TestRun', + run_type: 'chain', + inputs: {}, + outputs: {}, + start_time: Date.now(), + end_time: Date.now() + 100, + execution_order: 1, + child_execution_order: 1, + child_runs: [], + events: [], + trace_id: 'trace-001', + parent_run_id: undefined, + tags: [], + extra: {}, + error: undefined, + ...overrides, + } as Run; +} + +export function createMockLogger(logs?: string[]): Logger { + return { + info: (msg: string) => { + logs?.push(msg); + }, + warn: (msg: string) => { + logs?.push(msg); + }, + error: (msg: string) => { + logs?.push(msg); + }, + } as unknown as Logger; +} + +/** + * Create a mock TracingService that captures Run objects directly. + * Use capturedRuns to assert on traced runs without parsing log strings. + */ +export function createCapturingTracingService(): { + tracingService: TracingService; + capturedRuns: CapturedEntry[]; +} { + const capturedRuns: CapturedEntry[] = []; + const tracingService = { + logTrace: (run: Run, status: string, name: string, agentName: string) => { + capturedRuns.push({ run, status, name, agentName }); + }, + } as unknown as TracingService; + return { tracingService, capturedRuns }; +} diff --git a/tegg/core/agent-tracing/test/TracingService.test.ts b/tegg/core/agent-tracing/test/TracingService.test.ts new file mode 100644 index 0000000000..2300207ca1 --- /dev/null +++ b/tegg/core/agent-tracing/test/TracingService.test.ts @@ -0,0 +1,343 @@ +import assert from 'node:assert/strict'; + +import { afterEach, beforeEach, describe, it } from 'vitest'; + +import { TracingService } from '../src/TracingService.ts'; +import { RunStatus } from '../src/types.ts'; +import { createMockRun } from './TestUtils.ts'; + +// ---------- Helpers ---------- + +function makeTracingService({ + withOss = true, + withLogService = true, +}: { + withOss?: boolean; + withLogService?: boolean; +} = {}) { + const infoLogs: string[] = []; + const warnLogs: string[] = []; + const ossPuts: Array<{ key: string; content: string }> = []; + const logServiceSends: string[] = []; + + const service = new TracingService(); + + (service as any).logger = { + info: (msg: string) => infoLogs.push(msg), + warn: (...args: unknown[]) => warnLogs.push(args.join(' ')), + error: (msg: string) => warnLogs.push(msg), + }; + + (service as any).backgroundTaskHelper = { + run: async (fn: () => Promise) => fn(), + }; + + if (withOss) { + (service as any).ossClient = { + put: async (key: string, content: Buffer) => { + ossPuts.push({ key, content: content.toString() }); + }, + }; + } + + if (withLogService) { + (service as any).logServiceClient = { + send: async (log: string) => { + logServiceSends.push(log); + }, + }; + } + + return { service, infoLogs, warnLogs, ossPuts, logServiceSends }; +} + +// ---------- Tests ---------- + +describe('test/TracingService.test.ts', () => { + let originalFaasEnv: string | undefined; + let originalServerEnv: string | undefined; + + beforeEach(() => { + originalFaasEnv = process.env.FAAS_ENV; + originalServerEnv = process.env.SERVER_ENV; + }); + + afterEach(() => { + if (originalFaasEnv === undefined) { + delete process.env.FAAS_ENV; + } else { + process.env.FAAS_ENV = originalFaasEnv; + } + if (originalServerEnv === undefined) { + delete process.env.SERVER_ENV; + } else { + process.env.SERVER_ENV = originalServerEnv; + } + }); + + describe('getEnv()', () => { + it('should return FAAS_ENV when set', () => { + process.env.FAAS_ENV = 'prod'; + delete process.env.SERVER_ENV; + const { service } = makeTracingService(); + assert.strictEqual(service.getEnv(), 'prod'); + }); + + it('should fall back to SERVER_ENV when FAAS_ENV is not set', () => { + delete process.env.FAAS_ENV; + process.env.SERVER_ENV = 'gray'; + const { service } = makeTracingService(); + assert.strictEqual(service.getEnv(), 'gray'); + }); + + it('should normalize prepub to pre', () => { + delete process.env.FAAS_ENV; + process.env.SERVER_ENV = 'prepub'; + const { service } = makeTracingService(); + assert.strictEqual(service.getEnv(), 'pre'); + }); + + it('should default to local when neither env var is set', () => { + delete process.env.FAAS_ENV; + delete process.env.SERVER_ENV; + const { service } = makeTracingService(); + assert.strictEqual(service.getEnv(), 'local'); + }); + }); + + describe('isOnlineEnv()', () => { + it('should return true for prod', () => { + process.env.FAAS_ENV = 'prod'; + const { service } = makeTracingService(); + assert.strictEqual(service.isOnlineEnv(), true); + }); + + it('should return true for pre', () => { + process.env.FAAS_ENV = 'pre'; + const { service } = makeTracingService(); + assert.strictEqual(service.isOnlineEnv(), true); + }); + + it('should return true for gray', () => { + process.env.FAAS_ENV = 'gray'; + const { service } = makeTracingService(); + assert.strictEqual(service.isOnlineEnv(), true); + }); + + it('should return false for local', () => { + delete process.env.FAAS_ENV; + delete process.env.SERVER_ENV; + const { service } = makeTracingService(); + assert.strictEqual(service.isOnlineEnv(), false); + }); + + it('should return false for dev', () => { + process.env.FAAS_ENV = 'dev'; + const { service } = makeTracingService(); + assert.strictEqual(service.isOnlineEnv(), false); + }); + }); + + describe('getLogInfoPrefix()', () => { + it('should format prefix for root run with FAAS_ENV set', () => { + process.env.FAAS_ENV = 'prod'; + const { service } = makeTracingService(); + const run = createMockRun({ trace_id: 'trace-xyz', id: 'run-123', parent_run_id: undefined }); + const prefix = service.getLogInfoPrefix(run, RunStatus.START, 'MyAgent'); + assert(prefix.includes('[agent_run][MyAgent]')); + assert(prefix.includes('traceId=trace-xyz')); + assert(prefix.includes('threadId=unknown')); + assert(prefix.includes('type=root_run')); + assert(prefix.includes('status=start')); + assert(prefix.includes('run_id=run-123')); + assert(prefix.includes('parent_run_id=')); + }); + + it('should include threadId from run.extra.metadata when available', () => { + process.env.FAAS_ENV = 'dev'; + const { service } = makeTracingService(); + const run = createMockRun({ extra: { metadata: { thread_id: 'thread-abc' } } }); + const prefix = service.getLogInfoPrefix(run, RunStatus.START, 'MyAgent'); + assert(prefix.includes('threadId=thread-abc')); + }); + + it('should mark child run when parent_run_id is set', () => { + process.env.FAAS_ENV = 'dev'; + const { service } = makeTracingService(); + const run = createMockRun({ parent_run_id: 'parent-001' }); + const prefix = service.getLogInfoPrefix(run, RunStatus.END, 'MyAgent'); + assert(prefix.includes('type=child_run')); + assert(prefix.includes('parent_run_id=parent-001')); + }); + + it('should include env segment when SERVER_ENV is set and FAAS_ENV is not', () => { + delete process.env.FAAS_ENV; + process.env.SERVER_ENV = 'pre'; + const { service } = makeTracingService(); + const run = createMockRun(); + const prefix = service.getLogInfoPrefix(run, RunStatus.END, 'MyAgent'); + assert(prefix.includes('env=pre')); + }); + }); + + describe('uploadToOss()', () => { + it('should upload content to OSS client', async () => { + process.env.FAAS_ENV = 'dev'; + const { service, ossPuts } = makeTracingService({ withOss: true }); + await service.uploadToOss('my/key', 'hello content'); + assert.strictEqual(ossPuts.length, 1); + assert.strictEqual(ossPuts[0].key, 'my/key'); + assert.strictEqual(ossPuts[0].content, 'hello content'); + }); + + it('should warn and skip when no OSS client is configured', async () => { + const { service, warnLogs } = makeTracingService({ withOss: false }); + await service.uploadToOss('my/key', 'content'); + assert(warnLogs.some((log) => log.includes('OSS client not configured'))); + }); + }); + + describe('syncLocalToLogService()', () => { + it('should send log to log service with agentName prefix', async () => { + const { service, logServiceSends } = makeTracingService({ withLogService: true }); + await service.syncLocalToLogService('trace log line', 'MyAgent'); + assert.strictEqual(logServiceSends.length, 1); + assert(logServiceSends[0].includes('[MyAgent]')); + assert(logServiceSends[0].includes('trace log line')); + }); + + it('should skip silently when no log service client configured', async () => { + const { service, logServiceSends } = makeTracingService({ withLogService: false }); + await service.syncLocalToLogService('trace log line', 'MyAgent'); + assert.strictEqual(logServiceSends.length, 0); + }); + + it('should warn when agentName is empty', async () => { + const { service, warnLogs } = makeTracingService({ withLogService: true }); + await service.syncLocalToLogService('log', ''); + assert(warnLogs.some((log) => log.includes('agentName is empty'))); + }); + + it('should handle log service errors gracefully', async () => { + const { service, warnLogs } = makeTracingService({ withLogService: false }); + (service as any).logServiceClient = { + send: async () => { + throw new Error('network error'); + }, + }; + await service.syncLocalToLogService('log', 'MyAgent'); + assert(warnLogs.some((log) => log.includes('syncLocalToLogService error'))); + }); + }); + + describe('logTrace()', () => { + it('should log trace via logger.info when FAAS_ENV is set', () => { + process.env.FAAS_ENV = 'dev'; + const { service, infoLogs } = makeTracingService(); + const run = createMockRun({ outputs: undefined }); + service.logTrace(run, RunStatus.END, 'LangGraphTracer', 'MyAgent'); + assert(infoLogs.some((log) => log.includes('[agent_run]'))); + }); + + it('should skip runs tagged with langsmith:hidden', () => { + process.env.FAAS_ENV = 'dev'; + const { service, infoLogs } = makeTracingService(); + const run = createMockRun({ tags: ['langsmith:hidden'] }); + service.logTrace(run, RunStatus.END, 'LangGraphTracer', 'MyAgent'); + assert.strictEqual(infoLogs.length, 0); + }); + + it('should upload outputs field to OSS and replace with IResource', async () => { + process.env.FAAS_ENV = 'dev'; + const { service, ossPuts, infoLogs } = makeTracingService({ withOss: true }); + const run = createMockRun({ outputs: { result: 'data', llmOutput: { promptTokens: 10 } } }); + service.logTrace(run, RunStatus.END, 'LangGraphTracer', 'MyAgent'); + // backgroundTaskHelper runs synchronously in mock, so OSS put should be done + assert(ossPuts.length >= 1, 'Should have uploaded to OSS'); + // The logged run should have outputs replaced with IResource + const logLine = infoLogs.find((log) => log.includes('[agent_run]')); + assert(logLine, 'Should have a log line'); + const runJson = logLine!.match(/,run=({.*})$/)?.[1]; + assert(runJson, 'Should have run JSON in log'); + const parsed = JSON.parse(runJson); + assert(parsed.outputs?.key, 'outputs should be replaced with IResource'); + assert.strictEqual(parsed.cost?.promptTokens, 10, 'cost should be extracted from llmOutput'); + }); + + it('should sync to log service when env is local', async () => { + delete process.env.FAAS_ENV; + delete process.env.SERVER_ENV; + const { service, logServiceSends } = makeTracingService({ withLogService: true }); + const run = createMockRun({ outputs: undefined }); + service.logTrace(run, RunStatus.END, 'LangGraphTracer', 'MyAgent'); + assert(logServiceSends.length >= 1, 'Should have synced to log service in local env'); + }); + + it('should include child run ids in logged json', () => { + process.env.FAAS_ENV = 'dev'; + const { service, infoLogs } = makeTracingService(); + const childRun = createMockRun({ id: 'child-001' }); + const run = createMockRun({ child_runs: [childRun], outputs: undefined }); + service.logTrace(run, RunStatus.END, 'LangGraphTracer', 'MyAgent'); + const logLine = infoLogs.find((log) => log.includes('[agent_run]')); + const runJson = logLine?.match(/,run=({.*})$/)?.[1]; + const parsed = JSON.parse(runJson!); + assert.deepStrictEqual(parsed.child_run_ids, ['child-001']); + }); + + it('should warn when OSS upload fails inside backgroundTask', async () => { + process.env.FAAS_ENV = 'dev'; + const { service, warnLogs } = makeTracingService({ withOss: false }); + + // Make the OSS client throw on put + (service as any).ossClient = { + put: async () => { + throw new Error('oss upload failed'); + }, + }; + + // Track background tasks so we can await them + const pendingTasks: Array> = []; + (service as any).backgroundTaskHelper = { + run: (fn: () => Promise) => { + pendingTasks.push(fn()); + }, + }; + + const run = createMockRun({ outputs: { result: 'data' } }); + service.logTrace(run, RunStatus.END, 'LangGraphTracer', 'MyAgent'); + + // Wait for all background tasks (the catch block inside fn() calls logger.warn) + await Promise.allSettled(pendingTasks); + + assert( + warnLogs.some((log) => log.includes('Failed to upload run data to OSS')), + 'Should warn about OSS upload failure', + ); + }); + + it('should catch and warn when logTrace itself throws', () => { + process.env.FAAS_ENV = 'dev'; + const { service, warnLogs } = makeTracingService(); + + // Make backgroundTaskHelper.run throw synchronously to trigger the outer catch + (service as any).backgroundTaskHelper = { + run: () => { + throw new Error('backgroundTask error'); + }, + }; + + const run = createMockRun({ outputs: { result: 'data' } }); + + // Should NOT throw — the outer catch block in logTrace swallows the error + assert.doesNotThrow(() => { + service.logTrace(run, RunStatus.END, 'LangGraphTracer', 'MyAgent'); + }); + + assert( + warnLogs.some((log) => log.includes('logTrace error')), + 'Should warn about logTrace error', + ); + }); + }); +}); diff --git a/tegg/core/agent-tracing/tsconfig.json b/tegg/core/agent-tracing/tsconfig.json new file mode 100644 index 0000000000..618c6c3e97 --- /dev/null +++ b/tegg/core/agent-tracing/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../../../tsconfig.json" +} diff --git a/tegg/core/agent-tracing/vitest.config.ts b/tegg/core/agent-tracing/vitest.config.ts new file mode 100644 index 0000000000..2b417434e4 --- /dev/null +++ b/tegg/core/agent-tracing/vitest.config.ts @@ -0,0 +1,9 @@ +import { defineProject } from 'vitest/config'; + +export default defineProject({ + test: { + testTimeout: 15000, + include: ['test/**/*.test.ts'], + exclude: ['**/test/fixtures/**', '**/node_modules/**', '**/dist/**'], + }, +}); diff --git a/tegg/core/langchain-decorator/package.json b/tegg/core/langchain-decorator/package.json index 18ed074543..ab6680eb93 100644 --- a/tegg/core/langchain-decorator/package.json +++ b/tegg/core/langchain-decorator/package.json @@ -92,7 +92,7 @@ "@eggjs/core-decorator": "workspace:*", "@eggjs/tegg-common-util": "workspace:*", "@eggjs/tegg-types": "workspace:*", - "@langchain/core": "^1.1.1", + "@langchain/core": "^1.1.29", "@langchain/langgraph": "^1.0.2", "@langchain/openai": "^1.1.0", "langchain": "^1.1.2" diff --git a/tegg/plugin/langchain/package.json b/tegg/plugin/langchain/package.json index 3254ceb190..f4a35e79ac 100644 --- a/tegg/plugin/langchain/package.json +++ b/tegg/plugin/langchain/package.json @@ -75,7 +75,7 @@ "@eggjs/tegg-common-util": "workspace:*", "@eggjs/tegg-runtime": "workspace:*", "@eggjs/tegg-types": "workspace:*", - "@langchain/core": "^1.1.1", + "@langchain/core": "^1.1.29", "@langchain/langgraph": "^1.0.2", "@langchain/mcp-adapters": "^1.0.0", "@langchain/openai": "^1.0.0", diff --git a/tsconfig.json b/tsconfig.json index 0a167c7226..c2cdac1259 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -118,6 +118,9 @@ }, { "path": "./tegg/core/agent-runtime" + }, + { + "path": "./tegg/core/agent-tracing" } ] }