From 9861162a727f6fdb2d0d30a1d5c3ab5c8be1760f Mon Sep 17 00:00:00 2001 From: Raphael Lechner Date: Mon, 13 Apr 2026 16:37:05 +0200 Subject: [PATCH 1/6] feat: add optional Langfuse observability for Claude Agent SDK calls Auto-instrument all Claude Agent SDK query() calls via OpenTelemetry when LANGFUSE_PUBLIC_KEY and LANGFUSE_SECRET_KEY env vars are set. Zero overhead when not configured. - Add observability module with AsyncLocalStorage context propagation - Instrument query() via @arizeai/openinference-instrumentation-claude-agent-sdk - Initialize at server and CLI entry points, flush on shutdown - Propagate conversationId, platformType, workflowName to traces - Widen withFirstMessageTimeout to accept AsyncIterable (instrumented query returns AsyncIterable, not AsyncGenerator) --- .env.example | 7 + bun.lock | 192 ++++++++++++++++++ packages/cli/src/cli.ts | 8 +- .../src/orchestrator/orchestrator-agent.ts | 13 ++ packages/providers/package.json | 11 +- packages/providers/src/claude/provider.ts | 14 +- packages/providers/src/index.ts | 9 + packages/providers/src/observability.test.ts | 175 ++++++++++++++++ packages/providers/src/observability.ts | 150 ++++++++++++++ packages/server/src/index.ts | 11 + packages/workflows/src/dag-executor.ts | 41 ++++ 11 files changed, 622 insertions(+), 9 deletions(-) create mode 100644 packages/providers/src/observability.test.ts create mode 100644 packages/providers/src/observability.ts diff --git a/.env.example b/.env.example index 3c42151aee..79a37105b4 100644 --- a/.env.example +++ b/.env.example @@ -168,6 +168,13 @@ PORT=3000 # CLI can override with --quiet (error) or --verbose (debug) # LOG_LEVEL=info +# Observability (Optional - Langfuse) +# AI model call tracing via Langfuse (https://langfuse.com or self-hosted) +# When not set, observability is disabled with zero overhead +# LANGFUSE_PUBLIC_KEY=pk-lf-... +# LANGFUSE_SECRET_KEY=sk-lf-... +# LANGFUSE_BASE_URL=https://cloud.langfuse.com + # Concurrency MAX_CONCURRENT_CONVERSATIONS=10 # Maximum concurrent AI conversations (default: 10) diff --git a/bun.lock b/bun.lock index 356a76ed8d..e4ef6ed2f6 100644 --- a/bun.lock +++ b/bun.lock @@ -129,7 +129,11 @@ "dependencies": { "@anthropic-ai/claude-agent-sdk": "^0.2.89", "@archon/paths": "workspace:*", + "@arizeai/openinference-instrumentation-claude-agent-sdk": "^0.2.2", + "@langfuse/otel": "^5.1.0", + "@langfuse/tracing": "^5.1.0", "@openai/codex-sdk": "^0.116.0", + "@opentelemetry/sdk-node": "^0.214.0", }, "devDependencies": { "pino": "^9", @@ -256,6 +260,12 @@ "@archon/workflows": ["@archon/workflows@workspace:packages/workflows"], + "@arizeai/openinference-core": ["@arizeai/openinference-core@2.0.7", "", { "dependencies": { "@arizeai/openinference-semantic-conventions": "2.2.0", "@opentelemetry/api": "^1.9.0", "@opentelemetry/core": "^1.25.1" } }, "sha512-h9AX9/s3N/2xXxbMvUU61BrL4HcoakxouOZaWLtCW2Gr+52Q+G1QfxH53hbey7gL6XDN9xeBcCMqqFXcOP+pjQ=="], + + "@arizeai/openinference-instrumentation-claude-agent-sdk": ["@arizeai/openinference-instrumentation-claude-agent-sdk@0.2.2", "", { "dependencies": { "@arizeai/openinference-core": "2.0.7", "@arizeai/openinference-semantic-conventions": "2.2.0", "@opentelemetry/api": "^1.9.0", "@opentelemetry/core": "^1.25.1", "@opentelemetry/instrumentation": "^0.46.0" } }, "sha512-FEdNHpRFv4elYtTE1Fur6wI7VfWbws2XpD3jkJ91mo2HsIanq5FxK3fbKnBlpTYkPG8rYRDguGVBYmBq4yUDMQ=="], + + "@arizeai/openinference-semantic-conventions": ["@arizeai/openinference-semantic-conventions@2.2.0", "", {}, "sha512-jyVeggDSuVFyOXWXqShKsRxHc5RrE7jRN2D0LGMGYDbo4oHeCJCgUezHQITT7t7FPNQxj5O+NcqERhznnDx6UQ=="], + "@asteasolutions/zod-to-openapi": ["@asteasolutions/zod-to-openapi@7.3.4", "", { "dependencies": { "openapi3-ts": "^4.1.2" }, "peerDependencies": { "zod": "^3.20.2" } }, "sha512-/2rThQ5zPi9OzVwes6U7lK1+Yvug0iXu25olp7S0XsYmOqnyMfxH7gdSQjn/+DSOHRg7wnotwGJSyL+fBKdnEA=="], "@astrojs/compiler": ["@astrojs/compiler@3.0.1", "", {}, "sha512-z97oYbdebO5aoWzuJ/8q5hLK232+17KcLZ7cJ8BCWk6+qNzVxn/gftC0KzMBUTD8WAaBkPpNSQK6PXLnNrZ0CA=="], @@ -452,6 +462,10 @@ "@floating-ui/utils": ["@floating-ui/utils@0.2.11", "", {}, "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg=="], + "@grpc/grpc-js": ["@grpc/grpc-js@1.14.3", "", { "dependencies": { "@grpc/proto-loader": "^0.8.0", "@js-sdsl/ordered-map": "^4.4.2" } }, "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA=="], + + "@grpc/proto-loader": ["@grpc/proto-loader@0.8.0", "", { "dependencies": { "lodash.camelcase": "^4.3.0", "long": "^5.0.0", "protobufjs": "^7.5.3", "yargs": "^17.7.2" }, "bin": { "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" } }, "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ=="], + "@hono/node-server": ["@hono/node-server@1.19.11", "", { "peerDependencies": { "hono": "^4" } }, "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g=="], "@hono/zod-openapi": ["@hono/zod-openapi@0.19.10", "", { "dependencies": { "@asteasolutions/zod-to-openapi": "^7.3.0", "@hono/zod-validator": "^0.7.1", "openapi3-ts": "^4.5.0" }, "peerDependencies": { "hono": ">=4.3.6", "zod": ">=3.0.0" } }, "sha512-dpoS6DenvoJyvxtQ7Kd633FRZ/Qf74+4+o9s+zZI8pEqnbjdF/DtxIib08WDpCaWabMEJOL5TXpMgNEZvb7hpA=="], @@ -536,6 +550,14 @@ "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + "@js-sdsl/ordered-map": ["@js-sdsl/ordered-map@4.4.2", "", {}, "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw=="], + + "@langfuse/core": ["@langfuse/core@5.1.0", "", { "peerDependencies": { "@opentelemetry/api": "^1.9.0" } }, "sha512-yFvC67HBtrY4B3tyzF8+RJaIqK79LBVXtAgtmEc2vhpKauecvSW0zevRnRynFX+ajUHqi9TN7tnD91FJszFLgQ=="], + + "@langfuse/otel": ["@langfuse/otel@5.1.0", "", { "dependencies": { "@langfuse/core": "^5.1.0" }, "peerDependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/core": "^2.0.1", "@opentelemetry/exporter-trace-otlp-http": ">=0.202.0 <1.0.0", "@opentelemetry/sdk-trace-base": "^2.0.1" } }, "sha512-pvaXgZHMHqjsRjn+Gs5amrrq61w0Rxz1OChmLr2FfQzlymNl7+MxSXsWBj5dZQlufGbhyG+LT3wdx3MV8aLXHQ=="], + + "@langfuse/tracing": ["@langfuse/tracing@5.1.0", "", { "dependencies": { "@langfuse/core": "^5.1.0" }, "peerDependencies": { "@opentelemetry/api": "^1.9.0" } }, "sha512-ScwYnQzqLZOaMPZkCsWizx139eb02GI8tD5yxs5XVjGNGZxKdw1DfRPTIONSlOhaAYCY9ILGTJdkqAtNTzsbRg=="], + "@mdx-js/mdx": ["@mdx-js/mdx@3.1.1", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdx": "^2.0.0", "acorn": "^8.0.0", "collapse-white-space": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "estree-util-scope": "^1.0.0", "estree-walker": "^3.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "markdown-extensions": "^2.0.0", "recma-build-jsx": "^1.0.0", "recma-jsx": "^1.0.0", "recma-stringify": "^1.0.0", "rehype-recma": "^1.0.0", "remark-mdx": "^3.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "source-map": "^0.7.0", "unified": "^11.0.0", "unist-util-position-from-estree": "^2.0.0", "unist-util-stringify-position": "^4.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ=="], "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.27.1", "", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA=="], @@ -600,6 +622,64 @@ "@openai/codex-win32-x64": ["@openai/codex@0.116.0-win32-x64", "", { "os": "win32", "cpu": "x64" }, "sha512-6sBIMOoA9FNuxQvCCnK0P548Wqrlk3I9SMdtOCUg2zYzYU7jOF2mWS1VpRQ6R+Jvo2x50dxeJZ+W37dBmXfprw=="], + "@opentelemetry/api": ["@opentelemetry/api@1.9.1", "", {}, "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q=="], + + "@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.214.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-40lSJeqYO8Uz2Yj7u94/SJWE/wONa7rmMKjI1ZcIjgf3MHNHv1OZUCrCETGuaRF62d5pQD1wKIW+L4lmSMTzZA=="], + + "@opentelemetry/configuration": ["@opentelemetry/configuration@0.214.0", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "yaml": "^2.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.9.0" } }, "sha512-Q+awuEwxhETwIAXuxHvIY5ZMEP0ZqvxLTi9kclrkyVJppEUXYL3Bhiw3jYrxdHYMh0Y0tVInQH9FEZ1aMinvLA=="], + + "@opentelemetry/context-async-hooks": ["@opentelemetry/context-async-hooks@2.6.1", "", { "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-XHzhwRNkBpeP8Fs/qjGrAf9r9PRv67wkJQ/7ZPaBQQ68DYlTBBx5MF9LvPx7mhuXcDessKK2b+DcxqwpgkcivQ=="], + + "@opentelemetry/core": ["@opentelemetry/core@1.30.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "1.28.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ=="], + + "@opentelemetry/exporter-logs-otlp-grpc": ["@opentelemetry/exporter-logs-otlp-grpc@0.214.0", "", { "dependencies": { "@grpc/grpc-js": "^1.14.3", "@opentelemetry/core": "2.6.1", "@opentelemetry/otlp-exporter-base": "0.214.0", "@opentelemetry/otlp-grpc-exporter-base": "0.214.0", "@opentelemetry/otlp-transformer": "0.214.0", "@opentelemetry/sdk-logs": "0.214.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-SwmFRwO8mi6nndzbsjPgSFg7qy1WeNHRFD+s6uCsdiUDUt3+yzI2qiHE3/ub2f37+/CbeGcG+Ugc8Gwr6nu2Aw=="], + + "@opentelemetry/exporter-logs-otlp-http": ["@opentelemetry/exporter-logs-otlp-http@0.214.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.214.0", "@opentelemetry/core": "2.6.1", "@opentelemetry/otlp-exporter-base": "0.214.0", "@opentelemetry/otlp-transformer": "0.214.0", "@opentelemetry/sdk-logs": "0.214.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-9qv2Tl/Hq6qc5pJCbzFJnzA0uvlb9DgM70yGJPYf3bA5LlLkRCpcn81i4JbcIH4grlQIWY6A+W7YG0LLvS1BAw=="], + + "@opentelemetry/exporter-logs-otlp-proto": ["@opentelemetry/exporter-logs-otlp-proto@0.214.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.214.0", "@opentelemetry/core": "2.6.1", "@opentelemetry/otlp-exporter-base": "0.214.0", "@opentelemetry/otlp-transformer": "0.214.0", "@opentelemetry/resources": "2.6.1", "@opentelemetry/sdk-logs": "0.214.0", "@opentelemetry/sdk-trace-base": "2.6.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-IWAVvCO1TlpotRjFmhQFz9RSfQy5BsLtDRBtptSrXZRwfyRPpuql/RMe5zdmu0Gxl3ERDFwOzOqkf3bwy7Jzcw=="], + + "@opentelemetry/exporter-metrics-otlp-grpc": ["@opentelemetry/exporter-metrics-otlp-grpc@0.214.0", "", { "dependencies": { "@grpc/grpc-js": "^1.14.3", "@opentelemetry/core": "2.6.1", "@opentelemetry/exporter-metrics-otlp-http": "0.214.0", "@opentelemetry/otlp-exporter-base": "0.214.0", "@opentelemetry/otlp-grpc-exporter-base": "0.214.0", "@opentelemetry/otlp-transformer": "0.214.0", "@opentelemetry/resources": "2.6.1", "@opentelemetry/sdk-metrics": "2.6.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-0NGxWHVYHgbp51SEzmsP+Hdups81eRs229STcSWHo3WO0aqY6RpJ9csxfyEtFgaNrBDv6UfOh0je4ss/ROS6XA=="], + + "@opentelemetry/exporter-metrics-otlp-http": ["@opentelemetry/exporter-metrics-otlp-http@0.214.0", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/otlp-exporter-base": "0.214.0", "@opentelemetry/otlp-transformer": "0.214.0", "@opentelemetry/resources": "2.6.1", "@opentelemetry/sdk-metrics": "2.6.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-Tx/59RmjBgkXJ3qnsD04rpDrVWL53LU/czpgLJh+Ab98nAroe91I7vZ3uGN9mxwPS0jsZEnmqmHygVwB2vRMlA=="], + + "@opentelemetry/exporter-metrics-otlp-proto": ["@opentelemetry/exporter-metrics-otlp-proto@0.214.0", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/exporter-metrics-otlp-http": "0.214.0", "@opentelemetry/otlp-exporter-base": "0.214.0", "@opentelemetry/otlp-transformer": "0.214.0", "@opentelemetry/resources": "2.6.1", "@opentelemetry/sdk-metrics": "2.6.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-pJIcghFGhx3VSCgP5U+yZx+OMNj0t+ttnhC8IjL5Wza7vWIczctF6t3AGcVQffi2dEqX+ZHANoBwoPR8y6RMKA=="], + + "@opentelemetry/exporter-prometheus": ["@opentelemetry/exporter-prometheus@0.214.0", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1", "@opentelemetry/sdk-metrics": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-4TGYoZKebUWVuYkV6r5wS2dUF4zH7EbWFw/Uqz1ZM1tGHQeFT9wzHGXq3iSIXMUrwu5jRdxjfMaXrYejPu2kpQ=="], + + "@opentelemetry/exporter-trace-otlp-grpc": ["@opentelemetry/exporter-trace-otlp-grpc@0.214.0", "", { "dependencies": { "@grpc/grpc-js": "^1.14.3", "@opentelemetry/core": "2.6.1", "@opentelemetry/otlp-exporter-base": "0.214.0", "@opentelemetry/otlp-grpc-exporter-base": "0.214.0", "@opentelemetry/otlp-transformer": "0.214.0", "@opentelemetry/resources": "2.6.1", "@opentelemetry/sdk-trace-base": "2.6.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-FWRZ7AWoTryYhthralHkfXUuyO3l7cRsnr49WcDio1orl2a7KxT8aDZdwQtV1adzoUvZ9Gfo+IstElghCS4zfw=="], + + "@opentelemetry/exporter-trace-otlp-http": ["@opentelemetry/exporter-trace-otlp-http@0.214.0", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/otlp-exporter-base": "0.214.0", "@opentelemetry/otlp-transformer": "0.214.0", "@opentelemetry/resources": "2.6.1", "@opentelemetry/sdk-trace-base": "2.6.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-kIN8nTBMgV2hXzV/a20BCFilPZdAIMYYJGSgfMMRm/Xa+07y5hRDS2Vm12A/z8Cdu3Sq++ZvJfElokX2rkgGgw=="], + + "@opentelemetry/exporter-trace-otlp-proto": ["@opentelemetry/exporter-trace-otlp-proto@0.214.0", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/otlp-exporter-base": "0.214.0", "@opentelemetry/otlp-transformer": "0.214.0", "@opentelemetry/resources": "2.6.1", "@opentelemetry/sdk-trace-base": "2.6.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-ON0spYWb2yAdQ9b+ItNyK0c6qdtcs+0eVR4YFJkhJL7agfT8sHFg0e5EesauSRiTHPZHiDobI92k77q0lwAmqg=="], + + "@opentelemetry/exporter-zipkin": ["@opentelemetry/exporter-zipkin@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1", "@opentelemetry/sdk-trace-base": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0" } }, "sha512-km2/hD3inLTqtLnUAHDGz7ZP/VOyZNslrC/iN66x4jkmpckwlONW54LRPNI6fm09/musDtZga9EWsxgwnjGUlw=="], + + "@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.46.0", "", { "dependencies": { "@types/shimmer": "^1.0.2", "import-in-the-middle": "1.7.1", "require-in-the-middle": "^7.1.1", "semver": "^7.5.2", "shimmer": "^1.2.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-a9TijXZZbk0vI5TGLZl+0kxyFfrXHhX6Svtz7Pp2/VBlCSKrazuULEyoJQrOknJyFWNMEmbbJgOciHCCpQcisw=="], + + "@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.214.0", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/otlp-transformer": "0.214.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-u1Gdv0/E9wP+apqWf7Wv2npXmgJtxsW2XL0TEv9FZloTZRuMBKmu8cYVXwS4Hm3q/f/3FuCnPTgiwYvIqRSpRg=="], + + "@opentelemetry/otlp-grpc-exporter-base": ["@opentelemetry/otlp-grpc-exporter-base@0.214.0", "", { "dependencies": { "@grpc/grpc-js": "^1.14.3", "@opentelemetry/core": "2.6.1", "@opentelemetry/otlp-exporter-base": "0.214.0", "@opentelemetry/otlp-transformer": "0.214.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-IDP6zcyA24RhNZ289MP6eToIZcinlmirHjX8v3zKCQ2ZhPpt5cGwkN91tCth337lqHIgWcTy90uKRiX/SzALDw=="], + + "@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.214.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.214.0", "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1", "@opentelemetry/sdk-logs": "0.214.0", "@opentelemetry/sdk-metrics": "2.6.1", "@opentelemetry/sdk-trace-base": "2.6.1", "protobufjs": "^7.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-DSaYcuBRh6uozfsWN3R8HsN0yDhCuWP7tOFdkUOVaWD1KVJg8m4qiLUsg/tNhTLS9HUYUcwNpwL2eroLtsZZ/w=="], + + "@opentelemetry/propagator-b3": ["@opentelemetry/propagator-b3@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Dvz9TA6cPqIbxolSzQ5x9br6iQlqdGhVYrm+oYc7pfJ7LaVXz8F0XIqhWbnKB5YvfZ6SUmabBUUxnvHs/9uhxA=="], + + "@opentelemetry/propagator-jaeger": ["@opentelemetry/propagator-jaeger@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-kKFMxBcjBZAC1vBch5mtZ/dJQvcAEKWga+c+q5iGgRLPIE6Mc649zEwMaCIQCzalziMJQiyUadFYMHmELB7AFw=="], + + "@opentelemetry/resources": ["@opentelemetry/resources@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA=="], + + "@opentelemetry/sdk-logs": ["@opentelemetry/sdk-logs@0.214.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.214.0", "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, "sha512-zf6acnScjhsaBUU22zXZ/sLWim1dfhUAbGXdMmHmNG3LfBnQ3DKsOCITb2IZwoUsNNMTogqFKBnlIPPftUgGwA=="], + + "@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-9t9hJHX15meBy2NmTJxL+NJfXmnausR2xUDvE19XQce0Qi/GBtDGamU8nS1RMbdgDmhgpm3VaOu2+fiS/SfTpQ=="], + + "@opentelemetry/sdk-node": ["@opentelemetry/sdk-node@0.214.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.214.0", "@opentelemetry/configuration": "0.214.0", "@opentelemetry/context-async-hooks": "2.6.1", "@opentelemetry/core": "2.6.1", "@opentelemetry/exporter-logs-otlp-grpc": "0.214.0", "@opentelemetry/exporter-logs-otlp-http": "0.214.0", "@opentelemetry/exporter-logs-otlp-proto": "0.214.0", "@opentelemetry/exporter-metrics-otlp-grpc": "0.214.0", "@opentelemetry/exporter-metrics-otlp-http": "0.214.0", "@opentelemetry/exporter-metrics-otlp-proto": "0.214.0", "@opentelemetry/exporter-prometheus": "0.214.0", "@opentelemetry/exporter-trace-otlp-grpc": "0.214.0", "@opentelemetry/exporter-trace-otlp-http": "0.214.0", "@opentelemetry/exporter-trace-otlp-proto": "0.214.0", "@opentelemetry/exporter-zipkin": "2.6.1", "@opentelemetry/instrumentation": "0.214.0", "@opentelemetry/otlp-exporter-base": "0.214.0", "@opentelemetry/propagator-b3": "2.6.1", "@opentelemetry/propagator-jaeger": "2.6.1", "@opentelemetry/resources": "2.6.1", "@opentelemetry/sdk-logs": "0.214.0", "@opentelemetry/sdk-metrics": "2.6.1", "@opentelemetry/sdk-trace-base": "2.6.1", "@opentelemetry/sdk-trace-node": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-gl2XvQBJuPjhGcw9SsnQO5qxChAPMuGRPFaD8lqtF+Cey91NgGUQ0sio2vlDFOSm3JOLzc44vL+OAfx1dXuZjg=="], + + "@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-r86ut4T1e8vNwB35CqCcKd45yzqH6/6Wzvpk2/cZB8PsPLlZFTvrh8yfOS3CYZYcUmAx4hHTZJ8AO8Dj8nrdhw=="], + + "@opentelemetry/sdk-trace-node": ["@opentelemetry/sdk-trace-node@2.6.1", "", { "dependencies": { "@opentelemetry/context-async-hooks": "2.6.1", "@opentelemetry/core": "2.6.1", "@opentelemetry/sdk-trace-base": "2.6.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Hh2i4FwHWRFhnO2Q/p6svMxy8MPsNCG0uuzUY3glqm0rwM0nQvbTO1dXSp9OqQoTKXcQzaz9q1f65fsurmOhNw=="], + + "@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.40.0", "", {}, "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw=="], + "@oslojs/encoding": ["@oslojs/encoding@1.1.0", "", {}, "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ=="], "@pagefind/darwin-arm64": ["@pagefind/darwin-arm64@1.4.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-2vMqkbv3lbx1Awea90gTaBsvpzgRs7MuSgKDxW0m9oV1GPZCZbZBJg/qL83GIUEN2BFlY46dtUZi54pwH+/pTQ=="], @@ -618,6 +698,26 @@ "@pinojs/redact": ["@pinojs/redact@0.4.0", "", {}, "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg=="], + "@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="], + + "@protobufjs/base64": ["@protobufjs/base64@1.1.2", "", {}, "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="], + + "@protobufjs/codegen": ["@protobufjs/codegen@2.0.4", "", {}, "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg=="], + + "@protobufjs/eventemitter": ["@protobufjs/eventemitter@1.1.0", "", {}, "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q=="], + + "@protobufjs/fetch": ["@protobufjs/fetch@1.1.0", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.1", "@protobufjs/inquire": "^1.1.0" } }, "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ=="], + + "@protobufjs/float": ["@protobufjs/float@1.0.2", "", {}, "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="], + + "@protobufjs/inquire": ["@protobufjs/inquire@1.1.0", "", {}, "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q=="], + + "@protobufjs/path": ["@protobufjs/path@1.1.2", "", {}, "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="], + + "@protobufjs/pool": ["@protobufjs/pool@1.1.0", "", {}, "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="], + + "@protobufjs/utf8": ["@protobufjs/utf8@1.1.0", "", {}, "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="], + "@radix-ui/number": ["@radix-ui/number@1.1.1", "", {}, "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g=="], "@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="], @@ -954,6 +1054,8 @@ "@types/serve-static": ["@types/serve-static@2.2.0", "", { "dependencies": { "@types/http-errors": "*", "@types/node": "*" } }, "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ=="], + "@types/shimmer": ["@types/shimmer@1.2.0", "", {}, "sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg=="], + "@types/statuses": ["@types/statuses@2.0.6", "", {}, "sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA=="], "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], @@ -998,6 +1100,10 @@ "acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], + "acorn-import-assertions": ["acorn-import-assertions@1.9.0", "", { "peerDependencies": { "acorn": "^8" } }, "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA=="], + + "acorn-import-attributes": ["acorn-import-attributes@1.9.5", "", { "peerDependencies": { "acorn": "^8" } }, "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ=="], + "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], @@ -1106,6 +1212,8 @@ "ci-info": ["ci-info@4.4.0", "", {}, "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg=="], + "cjs-module-lexer": ["cjs-module-lexer@1.4.3", "", {}, "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q=="], + "class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="], "classcat": ["classcat@5.0.5", "", {}, "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w=="], @@ -1536,6 +1644,8 @@ "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], + "import-in-the-middle": ["import-in-the-middle@1.7.1", "", { "dependencies": { "acorn": "^8.8.2", "acorn-import-assertions": "^1.9.0", "cjs-module-lexer": "^1.2.2", "module-details-from-path": "^1.0.3" } }, "sha512-1LrZPDtW+atAxH42S6288qyDFNQ2YCty+2mxEPRtfazH6Z5QwkaBSTS2ods7hnVJioF6rkRfNoA6A/MstpFXLg=="], + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], "index-to-position": ["index-to-position@1.2.0", "", {}, "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw=="], @@ -1558,6 +1668,8 @@ "is-buffer": ["is-buffer@2.0.5", "", {}, "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ=="], + "is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="], + "is-decimal": ["is-decimal@2.0.1", "", {}, "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A=="], "is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="], @@ -1680,6 +1792,8 @@ "lodash": ["lodash@4.17.23", "", {}, "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w=="], + "lodash.camelcase": ["lodash.camelcase@4.3.0", "", {}, "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="], + "lodash.includes": ["lodash.includes@4.3.0", "", {}, "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="], "lodash.isboolean": ["lodash.isboolean@3.0.3", "", {}, "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="], @@ -1702,6 +1816,8 @@ "log-update": ["log-update@6.1.0", "", { "dependencies": { "ansi-escapes": "^7.0.0", "cli-cursor": "^5.0.0", "slice-ansi": "^7.1.0", "strip-ansi": "^7.1.0", "wrap-ansi": "^9.0.0" } }, "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w=="], + "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], + "longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="], "lowlight": ["lowlight@3.3.0", "", { "dependencies": { "@types/hast": "^3.0.0", "devlop": "^1.0.0", "highlight.js": "~11.11.0" } }, "sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ=="], @@ -1856,6 +1972,8 @@ "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], + "module-details-from-path": ["module-details-from-path@1.0.4", "", {}, "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w=="], + "mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="], "mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="], @@ -1964,6 +2082,8 @@ "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], + "path-to-regexp": ["path-to-regexp@8.3.0", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="], "pg": ["pg@8.20.0", "", { "dependencies": { "pg-connection-string": "^2.12.0", "pg-pool": "^3.13.0", "pg-protocol": "^1.13.0", "pg-types": "2.2.0", "pgpass": "1.0.5" }, "optionalDependencies": { "pg-cloudflare": "^1.3.0" }, "peerDependencies": { "pg-native": ">=3.0.1" }, "optionalPeers": ["pg-native"] }, "sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA=="], @@ -2032,6 +2152,8 @@ "property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="], + "protobufjs": ["protobufjs@7.5.4", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg=="], + "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="], "proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="], @@ -2132,6 +2254,10 @@ "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], + "require-in-the-middle": ["require-in-the-middle@7.5.2", "", { "dependencies": { "debug": "^4.3.5", "module-details-from-path": "^1.0.3", "resolve": "^1.22.8" } }, "sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ=="], + + "resolve": ["resolve@1.22.12", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA=="], + "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], "restore-cursor": ["restore-cursor@5.1.0", "", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="], @@ -2196,6 +2322,8 @@ "shiki": ["shiki@4.0.2", "", { "dependencies": { "@shikijs/core": "4.0.2", "@shikijs/engine-javascript": "4.0.2", "@shikijs/engine-oniguruma": "4.0.2", "@shikijs/langs": "4.0.2", "@shikijs/themes": "4.0.2", "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-eAVKTMedR5ckPo4xne/PjYQYrU3qx78gtJZ+sHlXEg5IHhhoQhMfZVzetTYuaJS0L2Ef3AcCRzCHV8T0WI6nIQ=="], + "shimmer": ["shimmer@1.2.1", "", {}, "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw=="], + "side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="], "side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="], @@ -2256,6 +2384,8 @@ "supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="], + "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], + "svgo": ["svgo@4.0.1", "", { "dependencies": { "commander": "^11.1.0", "css-select": "^5.1.0", "css-tree": "^3.0.1", "css-what": "^6.1.0", "csso": "^5.0.5", "picocolors": "^1.1.1", "sax": "^1.5.0" }, "bin": "./bin/svgo.js" }, "sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w=="], "tagged-tag": ["tagged-tag@1.0.0", "", {}, "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng=="], @@ -2514,6 +2644,8 @@ "@inquirer/core/wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="], + "@langfuse/otel/@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], + "@mdx-js/mdx/estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], "@mdx-js/mdx/remark-parse": ["remark-parse@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "micromark-util-types": "^2.0.0", "unified": "^11.0.0" } }, "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA=="], @@ -2524,6 +2656,56 @@ "@modelcontextprotocol/sdk/ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="], + "@opentelemetry/configuration/@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], + + "@opentelemetry/core/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.28.0", "", {}, "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA=="], + + "@opentelemetry/exporter-logs-otlp-grpc/@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], + + "@opentelemetry/exporter-logs-otlp-http/@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], + + "@opentelemetry/exporter-logs-otlp-proto/@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], + + "@opentelemetry/exporter-metrics-otlp-grpc/@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], + + "@opentelemetry/exporter-metrics-otlp-http/@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], + + "@opentelemetry/exporter-metrics-otlp-proto/@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], + + "@opentelemetry/exporter-prometheus/@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], + + "@opentelemetry/exporter-trace-otlp-grpc/@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], + + "@opentelemetry/exporter-trace-otlp-http/@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], + + "@opentelemetry/exporter-trace-otlp-proto/@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], + + "@opentelemetry/exporter-zipkin/@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], + + "@opentelemetry/otlp-exporter-base/@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], + + "@opentelemetry/otlp-grpc-exporter-base/@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], + + "@opentelemetry/otlp-transformer/@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], + + "@opentelemetry/propagator-b3/@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], + + "@opentelemetry/propagator-jaeger/@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], + + "@opentelemetry/resources/@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], + + "@opentelemetry/sdk-logs/@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], + + "@opentelemetry/sdk-metrics/@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], + + "@opentelemetry/sdk-node/@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], + + "@opentelemetry/sdk-node/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.214.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.214.0", "import-in-the-middle": "^3.0.0", "require-in-the-middle": "^8.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-MHqEX5Dk59cqVah5LiARMACku7jXSVk9iVDWOea4x3cr7VfdByeDCURK6o1lntT1JS/Tsovw01UJrBhN3/uC5w=="], + + "@opentelemetry/sdk-trace-base/@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], + + "@opentelemetry/sdk-trace-node/@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], + "@redocly/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], "@redocly/openapi-core/colorette": ["colorette@1.4.0", "", {}, "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g=="], @@ -2648,6 +2830,8 @@ "prompts/kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="], + "protobufjs/@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], + "react-markdown/remark-parse": ["remark-parse@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "micromark-util-types": "^2.0.0", "unified": "^11.0.0" } }, "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA=="], "react-markdown/unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="], @@ -2808,6 +2992,10 @@ "@modelcontextprotocol/sdk/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + "@opentelemetry/sdk-node/@opentelemetry/instrumentation/import-in-the-middle": ["import-in-the-middle@3.0.1", "", { "dependencies": { "acorn": "^8.15.0", "acorn-import-attributes": "^1.9.5", "cjs-module-lexer": "^2.2.0", "module-details-from-path": "^1.0.4" } }, "sha512-pYkiyXVL2Mf3pozdlDGV6NAObxQx13Ae8knZk1UJRJ6uRW/ZRmTGHlQYtrsSl7ubuE5F8CD1z+s1n4RHNuTtuA=="], + + "@opentelemetry/sdk-node/@opentelemetry/instrumentation/require-in-the-middle": ["require-in-the-middle@8.0.1", "", { "dependencies": { "debug": "^4.3.5", "module-details-from-path": "^1.0.3" } }, "sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ=="], + "@redocly/openapi-core/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], "@slack/web-api/p-queue/eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="], @@ -2886,6 +3074,8 @@ "parse-entities/is-alphanumerical/is-alphabetical": ["is-alphabetical@1.0.4", "", {}, "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg=="], + "protobufjs/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + "react-markdown/unified/bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="], "react-markdown/unified/is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="], @@ -3092,6 +3282,8 @@ "@inquirer/core/wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "@opentelemetry/sdk-node/@opentelemetry/instrumentation/import-in-the-middle/cjs-module-lexer": ["cjs-module-lexer@2.2.0", "", {}, "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ=="], + "@ts-morph/common/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], "@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index 5b66262435..fcf5c6b724 100755 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -82,6 +82,7 @@ import { BUNDLED_VERSION, } from '@archon/paths'; import * as git from '@archon/git'; +import { initLangfuse, shutdownLangfuse, isLangfuseEnabled } from '@archon/providers'; /** Lazy-initialized logger (deferred so test mocks can intercept createLogger) */ let cachedLog: ReturnType | undefined; @@ -253,6 +254,11 @@ async function main(): Promise { setLogLevel('debug'); } + // Initialize Langfuse observability (no-op when env vars not set) + if (isLangfuseEnabled()) { + await initLangfuse(); + } + // Note: orphaned run cleanup moved to `workflow cleanup` command only. // Running it on every CLI startup killed parallel workflow runs (all // 'running' status rows were marked failed by each new process). @@ -573,7 +579,7 @@ async function main(): Promise { } return 1; } finally { - // Always close database connection + await shutdownLangfuse(); await closeDb(); } } diff --git a/packages/core/src/orchestrator/orchestrator-agent.ts b/packages/core/src/orchestrator/orchestrator-agent.ts index 8c38adc810..e7831e153f 100644 --- a/packages/core/src/orchestrator/orchestrator-agent.ts +++ b/packages/core/src/orchestrator/orchestrator-agent.ts @@ -43,6 +43,7 @@ import type { MergedConfig } from '../config/config-types'; import { generateAndSetTitle } from '../services/title-generator'; import { validateAndResolveIsolation, dispatchBackgroundWorkflow } from './orchestrator'; import { IsolationBlockedError } from '@archon/isolation'; +import { withObservabilityContext } from '@archon/providers/observability'; import { buildOrchestratorPrompt, buildProjectScopedPrompt } from './prompt-builder'; import * as workflowDb from '../db/workflows'; import * as workflowEventDb from '../db/workflow-events'; @@ -500,6 +501,18 @@ export async function handleMessage( conversationId: string, message: string, context?: HandleMessageContext +): Promise { + return withObservabilityContext( + { conversationId, platformType: platform.getPlatformType() }, + () => handleMessageInner(platform, conversationId, message, context) + ); +} + +async function handleMessageInner( + platform: IPlatformAdapter, + conversationId: string, + message: string, + context?: HandleMessageContext ): Promise { const { issueContext, threadContext, parentConversationId, isolationHints, attachedFiles } = context ?? {}; diff --git a/packages/providers/package.json b/packages/providers/package.json index cbe4a4617a..cae9935859 100644 --- a/packages/providers/package.json +++ b/packages/providers/package.json @@ -13,16 +13,21 @@ "./codex/config": "./src/codex/config.ts", "./codex/binary-resolver": "./src/codex/binary-resolver.ts", "./errors": "./src/errors.ts", - "./registry": "./src/registry.ts" + "./registry": "./src/registry.ts", + "./observability": "./src/observability.ts" }, "scripts": { - "test": "bun test src/claude/provider.test.ts && bun test src/codex/provider.test.ts && bun test src/registry.test.ts && bun test src/codex/binary-guard.test.ts && bun test src/codex/binary-resolver.test.ts && bun test src/codex/binary-resolver-dev.test.ts", + "test": "bun test src/claude/provider.test.ts && bun test src/codex/provider.test.ts && bun test src/registry.test.ts && bun test src/codex/binary-guard.test.ts && bun test src/codex/binary-resolver.test.ts && bun test src/codex/binary-resolver-dev.test.ts && bun test src/observability.test.ts", "type-check": "bun x tsc --noEmit" }, "dependencies": { "@anthropic-ai/claude-agent-sdk": "^0.2.89", "@archon/paths": "workspace:*", - "@openai/codex-sdk": "^0.116.0" + "@arizeai/openinference-instrumentation-claude-agent-sdk": "^0.2.2", + "@langfuse/otel": "^5.1.0", + "@langfuse/tracing": "^5.1.0", + "@openai/codex-sdk": "^0.116.0", + "@opentelemetry/sdk-node": "^0.214.0" }, "devDependencies": { "pino": "^9" diff --git a/packages/providers/src/claude/provider.ts b/packages/providers/src/claude/provider.ts index b4769e66ec..0e801110ac 100644 --- a/packages/providers/src/claude/provider.ts +++ b/packages/providers/src/claude/provider.ts @@ -13,11 +13,11 @@ * - Not set: Auto-detect - use tokens if present in env, otherwise global auth */ import { - query, type Options, type HookCallback, type HookCallbackMatcher, } from '@anthropic-ai/claude-agent-sdk'; +import { getQuery } from '../observability'; import cliPath from '@anthropic-ai/claude-agent-sdk/embed'; import type { IAgentProvider, @@ -147,16 +147,17 @@ class FirstEventTimeoutError extends Error {} * within `timeoutMs`. If it doesn't, aborts the controller and throws. */ export async function* withFirstMessageTimeout( - gen: AsyncGenerator, + iterable: AsyncIterable, controller: AbortController, timeoutMs: number, diagnostics: Record ): AsyncGenerator { + const iter = iterable[Symbol.asyncIterator](); let timerId: ReturnType | undefined; let firstValue: IteratorResult; try { firstValue = await Promise.race([ - gen.next(), + iter.next(), new Promise((_, reject) => { timerId = setTimeout(() => { reject(new FirstEventTimeoutError()); @@ -182,7 +183,10 @@ export async function* withFirstMessageTimeout( if (firstValue.done) return; yield firstValue.value; - yield* gen; + // Continue yielding remaining items from the iterator + for await (const item of { [Symbol.asyncIterator]: () => iter }) { + yield item; + } } /** @@ -908,7 +912,7 @@ export class ClaudeProvider implements IAgentProvider { try { // 4. Run query with first-event timeout protection - const rawEvents = query({ prompt, options }); + const rawEvents = getQuery()({ prompt, options }); const timeoutMs = getFirstEventTimeoutMs(); const diagnostics = buildFirstEventHangDiagnostics( options.env as Record, diff --git a/packages/providers/src/index.ts b/packages/providers/src/index.ts index e24bb630eb..657f5e5d45 100644 --- a/packages/providers/src/index.ts +++ b/packages/providers/src/index.ts @@ -43,3 +43,12 @@ export { parseCodexConfig, type CodexProviderDefaults } from './codex/config'; // Utilities (needed by consumers) export { resetCodexSingleton } from './codex/provider'; export { resolveCodexBinaryPath, fileExists } from './codex/binary-resolver'; + +// Observability (optional Langfuse integration) +export { + initLangfuse, + shutdownLangfuse, + isLangfuseEnabled, + withObservabilityContext, + type ObservabilityAttrs, +} from './observability'; diff --git a/packages/providers/src/observability.test.ts b/packages/providers/src/observability.test.ts new file mode 100644 index 0000000000..fe3e9730c5 --- /dev/null +++ b/packages/providers/src/observability.test.ts @@ -0,0 +1,175 @@ +import { describe, test, expect, beforeEach, afterEach } from 'bun:test'; +import { + withObservabilityContext, + getObservabilityContext, + isLangfuseEnabled, + getQuery, + initLangfuse, + shutdownLangfuse, +} from './observability'; + +describe('observability', () => { + // ─── Context Propagation ──────────────────────────────────────────────── + + describe('withObservabilityContext', () => { + test('sets context for synchronous callback', () => { + let captured: ReturnType; + + withObservabilityContext({ conversationId: 'conv-1' }, () => { + captured = getObservabilityContext(); + }); + + expect(captured!).toEqual({ conversationId: 'conv-1' }); + }); + + test('sets context for async callback', async () => { + let captured: ReturnType; + + await withObservabilityContext( + { conversationId: 'conv-2', platformType: 'web' }, + async () => { + await new Promise(r => setTimeout(r, 10)); + captured = getObservabilityContext(); + } + ); + + expect(captured!).toEqual({ conversationId: 'conv-2', platformType: 'web' }); + }); + + test('nested calls merge attributes (inner overrides outer)', () => { + let outerCtx: ReturnType; + let innerCtx: ReturnType; + + withObservabilityContext({ conversationId: 'conv-1', platformType: 'slack' }, () => { + outerCtx = getObservabilityContext(); + withObservabilityContext({ workflowName: 'assist', platformType: 'cli' }, () => { + innerCtx = getObservabilityContext(); + }); + }); + + expect(outerCtx!).toEqual({ conversationId: 'conv-1', platformType: 'slack' }); + expect(innerCtx!).toEqual({ + conversationId: 'conv-1', + platformType: 'cli', + workflowName: 'assist', + }); + }); + + test('returns callback return value', () => { + const result = withObservabilityContext({ conversationId: 'c' }, () => 42); + expect(result).toBe(42); + }); + + test('returns async callback return value', async () => { + const result = await withObservabilityContext({ conversationId: 'c' }, async () => { + await new Promise(r => setTimeout(r, 1)); + return 'hello'; + }); + expect(result).toBe('hello'); + }); + }); + + describe('getObservabilityContext', () => { + test('returns undefined outside any context', () => { + expect(getObservabilityContext()).toBeUndefined(); + }); + }); + + // ─── isLangfuseEnabled ────────────────────────────────────────────────── + + describe('isLangfuseEnabled', () => { + const origPK = process.env.LANGFUSE_PUBLIC_KEY; + const origSK = process.env.LANGFUSE_SECRET_KEY; + + afterEach(() => { + // Restore original env + if (origPK !== undefined) process.env.LANGFUSE_PUBLIC_KEY = origPK; + else delete process.env.LANGFUSE_PUBLIC_KEY; + if (origSK !== undefined) process.env.LANGFUSE_SECRET_KEY = origSK; + else delete process.env.LANGFUSE_SECRET_KEY; + }); + + test('returns false when no env vars set', () => { + delete process.env.LANGFUSE_PUBLIC_KEY; + delete process.env.LANGFUSE_SECRET_KEY; + expect(isLangfuseEnabled()).toBe(false); + }); + + test('returns false when only public key set', () => { + process.env.LANGFUSE_PUBLIC_KEY = 'pk-test'; + delete process.env.LANGFUSE_SECRET_KEY; + expect(isLangfuseEnabled()).toBe(false); + }); + + test('returns false when only secret key set', () => { + delete process.env.LANGFUSE_PUBLIC_KEY; + process.env.LANGFUSE_SECRET_KEY = 'sk-test'; + expect(isLangfuseEnabled()).toBe(false); + }); + + test('returns true when both keys set', () => { + process.env.LANGFUSE_PUBLIC_KEY = 'pk-test'; + process.env.LANGFUSE_SECRET_KEY = 'sk-test'; + expect(isLangfuseEnabled()).toBe(true); + }); + + test('returns false for empty string keys', () => { + process.env.LANGFUSE_PUBLIC_KEY = ''; + process.env.LANGFUSE_SECRET_KEY = ''; + expect(isLangfuseEnabled()).toBe(false); + }); + }); + + // ─── initLangfuse ────────────────────────────────────────────────────── + + describe('initLangfuse', () => { + const origPK = process.env.LANGFUSE_PUBLIC_KEY; + const origSK = process.env.LANGFUSE_SECRET_KEY; + + afterEach(async () => { + await shutdownLangfuse(); + if (origPK !== undefined) process.env.LANGFUSE_PUBLIC_KEY = origPK; + else delete process.env.LANGFUSE_PUBLIC_KEY; + if (origSK !== undefined) process.env.LANGFUSE_SECRET_KEY = origSK; + else delete process.env.LANGFUSE_SECRET_KEY; + }); + + test('returns false when env vars not set', async () => { + delete process.env.LANGFUSE_PUBLIC_KEY; + delete process.env.LANGFUSE_SECRET_KEY; + const result = await initLangfuse(); + expect(result).toBe(false); + }); + }); + + // ─── getQuery ────────────────────────────────────────────────────────── + + describe('getQuery', () => { + test('returns a function', () => { + const queryFn = getQuery(); + expect(typeof queryFn).toBe('function'); + }); + + test('returns the SDK query function when Langfuse not initialized', () => { + const queryFn = getQuery(); + // Should be a callable function (original SDK query) + expect(typeof queryFn).toBe('function'); + // Should NOT be the instrumented wrapper + expect(queryFn.name).not.toBe('wrappedQuery'); + }); + }); + + // ─── shutdownLangfuse ────────────────────────────────────────────────── + + describe('shutdownLangfuse', () => { + test('is safe to call when not initialized', async () => { + // Should not throw + await shutdownLangfuse(); + }); + + test('is safe to call multiple times', async () => { + await shutdownLangfuse(); + await shutdownLangfuse(); + }); + }); +}); diff --git a/packages/providers/src/observability.ts b/packages/providers/src/observability.ts new file mode 100644 index 0000000000..616ec6a58b --- /dev/null +++ b/packages/providers/src/observability.ts @@ -0,0 +1,150 @@ +/** + * Observability module — optional Langfuse integration via OpenTelemetry. + * + * When LANGFUSE_PUBLIC_KEY + LANGFUSE_SECRET_KEY are set, initializes: + * - OTel NodeSDK with LangfuseSpanProcessor + * - Auto-instrumentation for @anthropic-ai/claude-agent-sdk + * + * When not configured, all exports are safe no-ops with zero overhead. + */ +import { AsyncLocalStorage } from 'async_hooks'; +import { createLogger } from '@archon/paths'; + +/** Lazy-initialized logger (deferred so test mocks can intercept createLogger) */ +let cachedLog: ReturnType | undefined; +function getLog(): ReturnType { + if (!cachedLog) cachedLog = createLogger('observability'); + return cachedLog; +} + +// ─── Context Propagation ──────────────────────────────────────────────────── + +/** Attributes propagated through async call chains via AsyncLocalStorage */ +export interface ObservabilityAttrs { + conversationId?: string; + platformType?: string; + workflowName?: string; +} + +const store = new AsyncLocalStorage(); + +/** + * Run a callback with observability attributes attached. + * Nested calls merge: inner attrs override outer attrs for the same key. + */ +export function withObservabilityContext(attrs: ObservabilityAttrs, fn: () => T): T { + const parent = store.getStore(); + const merged = parent ? { ...parent, ...attrs } : attrs; + return store.run(merged, fn); +} + +/** Read the current observability context (undefined when outside any scope) */ +export function getObservabilityContext(): ObservabilityAttrs | undefined { + return store.getStore(); +} + +// ─── Langfuse Initialization ──────────────────────────────────────────────── + +let initialized = false; + +type QueryFn = typeof import('@anthropic-ai/claude-agent-sdk').query; +let instrumentedQueryFn: QueryFn | null = null; + +// Hold a reference to the OTel SDK for clean shutdown +let otelSdk: { shutdown: () => Promise } | null = null; + +/** Check whether Langfuse env vars are present */ +export function isLangfuseEnabled(): boolean { + return !!(process.env.LANGFUSE_PUBLIC_KEY && process.env.LANGFUSE_SECRET_KEY); +} + +/** + * Initialize Langfuse observability. + * + * - Uses dynamic imports so the packages are only loaded when needed + * - Gracefully degrades: logs a warning and continues if anything fails + * - Idempotent: calling multiple times is safe + */ +export async function initLangfuse(): Promise { + if (initialized) return true; + if (!isLangfuseEnabled()) return false; + + try { + const [otelSdkMod, langfuseOtel, claudeInstr, claudeSdk] = await Promise.all([ + import('@opentelemetry/sdk-node'), + import('@langfuse/otel'), + import('@arizeai/openinference-instrumentation-claude-agent-sdk'), + import('@anthropic-ai/claude-agent-sdk'), + ]); + + // Create a mutable copy of the SDK module for instrumentation. + // Type assertion needed: the instrumentation expects its own peer dep type, + // which may differ from our pinned SDK version at the type level. + const sdkCopy = { ...claudeSdk }; + const instrumentation = new claudeInstr.ClaudeAgentSDKInstrumentation(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- bridging SDK version types for auto-instrumentation + instrumentation.manuallyInstrument(sdkCopy as any); + instrumentedQueryFn = sdkCopy.query; + + const publicKey = process.env.LANGFUSE_PUBLIC_KEY ?? ''; + const secretKey = process.env.LANGFUSE_SECRET_KEY ?? ''; + const spanProcessor = new langfuseOtel.LangfuseSpanProcessor({ + publicKey, + secretKey, + baseUrl: process.env.LANGFUSE_BASE_URL ?? 'https://cloud.langfuse.com', + // Export all spans — the default filter (isDefaultExportSpan) may not + // recognize the Arize Claude Agent SDK instrumentation spans. + shouldExportSpan: (): boolean => true, + }); + + const sdk = new otelSdkMod.NodeSDK({ + spanProcessors: [spanProcessor], + instrumentations: [instrumentation], + }); + sdk.start(); + otelSdk = sdk; + + initialized = true; + getLog().info( + { baseUrl: process.env.LANGFUSE_BASE_URL ?? 'https://cloud.langfuse.com' }, + 'langfuse.init_completed' + ); + return true; + } catch (error) { + const err = error as Error; + getLog().warn({ error: err.message, errorType: err.constructor.name }, 'langfuse.init_failed'); + return false; + } +} + +/** Flush pending spans and shut down the OTel SDK */ +export async function shutdownLangfuse(): Promise { + if (!otelSdk) return; + try { + await otelSdk.shutdown(); + getLog().debug('langfuse.shutdown_completed'); + } catch (error) { + const err = error as Error; + getLog().warn({ error: err.message }, 'langfuse.shutdown_failed'); + } finally { + otelSdk = null; + initialized = false; + instrumentedQueryFn = null; + } +} + +// ─── Instrumented Query ───────────────────────────────────────────────────── + +/** + * Return the instrumented `query` function when Langfuse is active, + * otherwise fall back to the original SDK export. + */ +export function getQuery(): QueryFn { + if (instrumentedQueryFn) return instrumentedQueryFn; + + // Fallback: return the original, un-instrumented query + // This import is resolved at module load time by Bun (static reference) + // eslint-disable-next-line @typescript-eslint/no-require-imports + const { query } = require('@anthropic-ai/claude-agent-sdk') as { query: QueryFn }; + return query; +} diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index d8b1a4c4c8..27e7e24c6a 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -67,6 +67,7 @@ import { MessagePersistence } from './adapters/web/persistence'; import { SSETransport } from './adapters/web/transport'; import { WorkflowEventBridge } from './adapters/web/workflow-bridge'; import { registerApiRoutes } from './routes/api'; +import { initLangfuse, shutdownLangfuse, isLangfuseEnabled } from '@archon/providers'; import { handleMessage, pool, @@ -205,6 +206,11 @@ export async function startServer(opts: ServerOptions = {}): Promise { const config = await loadConfig(); logConfig(config); + // Initialize Langfuse observability (no-op when env vars not set) + if (isLangfuseEnabled()) { + await initLangfuse(); + } + // Start cleanup scheduler startCleanupScheduler(); @@ -617,6 +623,11 @@ export async function startServer(opts: ServerOptions = {}): Promise { stopCleanupScheduler(); persistence.stopPeriodicFlush(); + // Flush Langfuse spans before stopping adapters + shutdownLangfuse().catch((e: unknown) => { + getLog().error({ err: e }, 'langfuse.shutdown_failed'); + }); + // Flush all buffered messages before stopping adapters persistence .flushAll() diff --git a/packages/workflows/src/dag-executor.ts b/packages/workflows/src/dag-executor.ts index aef51bc764..f6ca702d45 100644 --- a/packages/workflows/src/dag-executor.ts +++ b/packages/workflows/src/dag-executor.ts @@ -21,6 +21,7 @@ import type { TokenUsage, } from '@archon/providers/types'; import { getProviderCapabilities } from '@archon/providers'; +import { withObservabilityContext } from '@archon/providers/observability'; import type { DagNode, ApprovalNode, @@ -2123,6 +2124,46 @@ export async function executeDagWorkflow( configuredCommandFolder?: string, issueContext?: string, priorCompletedNodes?: Map +): Promise { + return withObservabilityContext({ workflowName: workflow.name, conversationId }, () => + executeDagWorkflowInner( + deps, + platform, + conversationId, + cwd, + workflow, + workflowRun, + workflowProvider, + workflowModel, + artifactsDir, + logDir, + baseBranch, + docsDir, + config, + configuredCommandFolder, + issueContext, + priorCompletedNodes + ) + ); +} + +async function executeDagWorkflowInner( + deps: WorkflowDeps, + platform: IWorkflowPlatform, + conversationId: string, + cwd: string, + workflow: { name: string; nodes: readonly DagNode[] } & WorkflowLevelOptions, + workflowRun: WorkflowRun, + workflowProvider: 'claude' | 'codex', + workflowModel: string | undefined, + artifactsDir: string, + logDir: string, + baseBranch: string, + docsDir: string, + config: WorkflowConfig, + configuredCommandFolder?: string, + issueContext?: string, + priorCompletedNodes?: Map ): Promise { const dagStartTime = Date.now(); const workflowLevelOptions = { From 1e7267222303a330216fc2217c3260160437e0d6 Mon Sep 17 00:00:00 2001 From: Raphael Lechner Date: Mon, 13 Apr 2026 16:40:54 +0200 Subject: [PATCH 2/6] fix: restore comment removed during Langfuse integration --- packages/cli/src/cli.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index fcf5c6b724..391e4fc157 100755 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -580,6 +580,7 @@ async function main(): Promise { return 1; } finally { await shutdownLangfuse(); + // Always close database connection await closeDb(); } } From b9404e292b706bc60c5f2f847eeb8a216d83e779 Mon Sep 17 00:00:00 2001 From: Raphael Lechner Date: Mon, 13 Apr 2026 17:03:51 +0200 Subject: [PATCH 3/6] =?UTF-8?q?fix:=20address=20review=20=E2=80=94=20reset?= =?UTF-8?q?=20state=20on=20init=20failure,=20await=20Langfuse=20shutdown?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Reset instrumentedQueryFn/otelSdk/initialized in catch block so getQuery() falls back to the original SDK after a partial init failure - Chain shutdownLangfuse() before persistence.flushAll() in the server shutdown handler so spans are flushed before process.exit() - Allow shutdownLangfuse() to always reset state even if otelSdk is null --- packages/providers/src/observability.ts | 11 ++++++++--- packages/server/src/index.ts | 14 ++++++-------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/packages/providers/src/observability.ts b/packages/providers/src/observability.ts index 616ec6a58b..a9528e1d65 100644 --- a/packages/providers/src/observability.ts +++ b/packages/providers/src/observability.ts @@ -113,16 +113,21 @@ export async function initLangfuse(): Promise { } catch (error) { const err = error as Error; getLog().warn({ error: err.message, errorType: err.constructor.name }, 'langfuse.init_failed'); + // Reset partial state so getQuery() falls back to the original SDK + otelSdk = null; + initialized = false; + instrumentedQueryFn = null; return false; } } /** Flush pending spans and shut down the OTel SDK */ export async function shutdownLangfuse(): Promise { - if (!otelSdk) return; try { - await otelSdk.shutdown(); - getLog().debug('langfuse.shutdown_completed'); + if (otelSdk) { + await otelSdk.shutdown(); + getLog().debug('langfuse.shutdown_completed'); + } } catch (error) { const err = error as Error; getLog().warn({ error: err.message }, 'langfuse.shutdown_failed'); diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 27e7e24c6a..9790de24d5 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -623,14 +623,12 @@ export async function startServer(opts: ServerOptions = {}): Promise { stopCleanupScheduler(); persistence.stopPeriodicFlush(); - // Flush Langfuse spans before stopping adapters - shutdownLangfuse().catch((e: unknown) => { - getLog().error({ err: e }, 'langfuse.shutdown_failed'); - }); - - // Flush all buffered messages before stopping adapters - persistence - .flushAll() + // Flush Langfuse spans, then buffered messages before stopping adapters + shutdownLangfuse() + .catch((e: unknown) => { + getLog().error({ err: e }, 'langfuse.shutdown_failed'); + }) + .then(() => persistence.flushAll()) .catch((e: unknown) => { getLog().error({ err: e }, 'shutdown_flush_failed'); }) From 079baac8b8b3c25dca39adc62e989890ff04fa67 Mon Sep 17 00:00:00 2001 From: Raphael Lechner Date: Mon, 13 Apr 2026 18:05:50 +0200 Subject: [PATCH 4/6] refactor: replace auto-instrumentation with manual tracing for both providers The subprocess-based Claude Agent SDK can't be auto-instrumented for input/output capture. Replace with manual traceQuery() wrapper that: - Captures prompt as input and collected response as output - Records token usage, cost, and num_turns from result events - Creates child "tool" spans for each tool call with input/output - Works for both Claude and Codex providers - Remove @arizeai/openinference-instrumentation-claude-agent-sdk dep Also adds unit tests for tool call tracking, empty generators, and cost/usage passthrough (19 tests total). --- bun.lock | 89 +------- packages/providers/package.json | 1 - packages/providers/src/claude/provider.ts | 9 +- packages/providers/src/codex/provider.ts | 15 +- packages/providers/src/observability.test.ts | 106 +++++++-- packages/providers/src/observability.ts | 213 +++++++++++++++---- 6 files changed, 290 insertions(+), 143 deletions(-) diff --git a/bun.lock b/bun.lock index e4ef6ed2f6..c4762917e3 100644 --- a/bun.lock +++ b/bun.lock @@ -129,7 +129,6 @@ "dependencies": { "@anthropic-ai/claude-agent-sdk": "^0.2.89", "@archon/paths": "workspace:*", - "@arizeai/openinference-instrumentation-claude-agent-sdk": "^0.2.2", "@langfuse/otel": "^5.1.0", "@langfuse/tracing": "^5.1.0", "@openai/codex-sdk": "^0.116.0", @@ -260,12 +259,6 @@ "@archon/workflows": ["@archon/workflows@workspace:packages/workflows"], - "@arizeai/openinference-core": ["@arizeai/openinference-core@2.0.7", "", { "dependencies": { "@arizeai/openinference-semantic-conventions": "2.2.0", "@opentelemetry/api": "^1.9.0", "@opentelemetry/core": "^1.25.1" } }, "sha512-h9AX9/s3N/2xXxbMvUU61BrL4HcoakxouOZaWLtCW2Gr+52Q+G1QfxH53hbey7gL6XDN9xeBcCMqqFXcOP+pjQ=="], - - "@arizeai/openinference-instrumentation-claude-agent-sdk": ["@arizeai/openinference-instrumentation-claude-agent-sdk@0.2.2", "", { "dependencies": { "@arizeai/openinference-core": "2.0.7", "@arizeai/openinference-semantic-conventions": "2.2.0", "@opentelemetry/api": "^1.9.0", "@opentelemetry/core": "^1.25.1", "@opentelemetry/instrumentation": "^0.46.0" } }, "sha512-FEdNHpRFv4elYtTE1Fur6wI7VfWbws2XpD3jkJ91mo2HsIanq5FxK3fbKnBlpTYkPG8rYRDguGVBYmBq4yUDMQ=="], - - "@arizeai/openinference-semantic-conventions": ["@arizeai/openinference-semantic-conventions@2.2.0", "", {}, "sha512-jyVeggDSuVFyOXWXqShKsRxHc5RrE7jRN2D0LGMGYDbo4oHeCJCgUezHQITT7t7FPNQxj5O+NcqERhznnDx6UQ=="], - "@asteasolutions/zod-to-openapi": ["@asteasolutions/zod-to-openapi@7.3.4", "", { "dependencies": { "openapi3-ts": "^4.1.2" }, "peerDependencies": { "zod": "^3.20.2" } }, "sha512-/2rThQ5zPi9OzVwes6U7lK1+Yvug0iXu25olp7S0XsYmOqnyMfxH7gdSQjn/+DSOHRg7wnotwGJSyL+fBKdnEA=="], "@astrojs/compiler": ["@astrojs/compiler@3.0.1", "", {}, "sha512-z97oYbdebO5aoWzuJ/8q5hLK232+17KcLZ7cJ8BCWk6+qNzVxn/gftC0KzMBUTD8WAaBkPpNSQK6PXLnNrZ0CA=="], @@ -630,7 +623,7 @@ "@opentelemetry/context-async-hooks": ["@opentelemetry/context-async-hooks@2.6.1", "", { "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-XHzhwRNkBpeP8Fs/qjGrAf9r9PRv67wkJQ/7ZPaBQQ68DYlTBBx5MF9LvPx7mhuXcDessKK2b+DcxqwpgkcivQ=="], - "@opentelemetry/core": ["@opentelemetry/core@1.30.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "1.28.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ=="], + "@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], "@opentelemetry/exporter-logs-otlp-grpc": ["@opentelemetry/exporter-logs-otlp-grpc@0.214.0", "", { "dependencies": { "@grpc/grpc-js": "^1.14.3", "@opentelemetry/core": "2.6.1", "@opentelemetry/otlp-exporter-base": "0.214.0", "@opentelemetry/otlp-grpc-exporter-base": "0.214.0", "@opentelemetry/otlp-transformer": "0.214.0", "@opentelemetry/sdk-logs": "0.214.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-SwmFRwO8mi6nndzbsjPgSFg7qy1WeNHRFD+s6uCsdiUDUt3+yzI2qiHE3/ub2f37+/CbeGcG+Ugc8Gwr6nu2Aw=="], @@ -654,7 +647,7 @@ "@opentelemetry/exporter-zipkin": ["@opentelemetry/exporter-zipkin@2.6.1", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1", "@opentelemetry/sdk-trace-base": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0" } }, "sha512-km2/hD3inLTqtLnUAHDGz7ZP/VOyZNslrC/iN66x4jkmpckwlONW54LRPNI6fm09/musDtZga9EWsxgwnjGUlw=="], - "@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.46.0", "", { "dependencies": { "@types/shimmer": "^1.0.2", "import-in-the-middle": "1.7.1", "require-in-the-middle": "^7.1.1", "semver": "^7.5.2", "shimmer": "^1.2.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-a9TijXZZbk0vI5TGLZl+0kxyFfrXHhX6Svtz7Pp2/VBlCSKrazuULEyoJQrOknJyFWNMEmbbJgOciHCCpQcisw=="], + "@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.214.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.214.0", "import-in-the-middle": "^3.0.0", "require-in-the-middle": "^8.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-MHqEX5Dk59cqVah5LiARMACku7jXSVk9iVDWOea4x3cr7VfdByeDCURK6o1lntT1JS/Tsovw01UJrBhN3/uC5w=="], "@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.214.0", "", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/otlp-transformer": "0.214.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-u1Gdv0/E9wP+apqWf7Wv2npXmgJtxsW2XL0TEv9FZloTZRuMBKmu8cYVXwS4Hm3q/f/3FuCnPTgiwYvIqRSpRg=="], @@ -1054,8 +1047,6 @@ "@types/serve-static": ["@types/serve-static@2.2.0", "", { "dependencies": { "@types/http-errors": "*", "@types/node": "*" } }, "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ=="], - "@types/shimmer": ["@types/shimmer@1.2.0", "", {}, "sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg=="], - "@types/statuses": ["@types/statuses@2.0.6", "", {}, "sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA=="], "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], @@ -1100,8 +1091,6 @@ "acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], - "acorn-import-assertions": ["acorn-import-assertions@1.9.0", "", { "peerDependencies": { "acorn": "^8" } }, "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA=="], - "acorn-import-attributes": ["acorn-import-attributes@1.9.5", "", { "peerDependencies": { "acorn": "^8" } }, "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ=="], "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], @@ -1212,7 +1201,7 @@ "ci-info": ["ci-info@4.4.0", "", {}, "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg=="], - "cjs-module-lexer": ["cjs-module-lexer@1.4.3", "", {}, "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q=="], + "cjs-module-lexer": ["cjs-module-lexer@2.2.0", "", {}, "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ=="], "class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="], @@ -1644,7 +1633,7 @@ "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], - "import-in-the-middle": ["import-in-the-middle@1.7.1", "", { "dependencies": { "acorn": "^8.8.2", "acorn-import-assertions": "^1.9.0", "cjs-module-lexer": "^1.2.2", "module-details-from-path": "^1.0.3" } }, "sha512-1LrZPDtW+atAxH42S6288qyDFNQ2YCty+2mxEPRtfazH6Z5QwkaBSTS2ods7hnVJioF6rkRfNoA6A/MstpFXLg=="], + "import-in-the-middle": ["import-in-the-middle@3.0.1", "", { "dependencies": { "acorn": "^8.15.0", "acorn-import-attributes": "^1.9.5", "cjs-module-lexer": "^2.2.0", "module-details-from-path": "^1.0.4" } }, "sha512-pYkiyXVL2Mf3pozdlDGV6NAObxQx13Ae8knZk1UJRJ6uRW/ZRmTGHlQYtrsSl7ubuE5F8CD1z+s1n4RHNuTtuA=="], "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], @@ -1668,8 +1657,6 @@ "is-buffer": ["is-buffer@2.0.5", "", {}, "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ=="], - "is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="], - "is-decimal": ["is-decimal@2.0.1", "", {}, "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A=="], "is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="], @@ -2082,8 +2069,6 @@ "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], - "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], - "path-to-regexp": ["path-to-regexp@8.3.0", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="], "pg": ["pg@8.20.0", "", { "dependencies": { "pg-connection-string": "^2.12.0", "pg-pool": "^3.13.0", "pg-protocol": "^1.13.0", "pg-types": "2.2.0", "pgpass": "1.0.5" }, "optionalDependencies": { "pg-cloudflare": "^1.3.0" }, "peerDependencies": { "pg-native": ">=3.0.1" }, "optionalPeers": ["pg-native"] }, "sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA=="], @@ -2254,9 +2239,7 @@ "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], - "require-in-the-middle": ["require-in-the-middle@7.5.2", "", { "dependencies": { "debug": "^4.3.5", "module-details-from-path": "^1.0.3", "resolve": "^1.22.8" } }, "sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ=="], - - "resolve": ["resolve@1.22.12", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA=="], + "require-in-the-middle": ["require-in-the-middle@8.0.1", "", { "dependencies": { "debug": "^4.3.5", "module-details-from-path": "^1.0.3" } }, "sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ=="], "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], @@ -2322,8 +2305,6 @@ "shiki": ["shiki@4.0.2", "", { "dependencies": { "@shikijs/core": "4.0.2", "@shikijs/engine-javascript": "4.0.2", "@shikijs/engine-oniguruma": "4.0.2", "@shikijs/langs": "4.0.2", "@shikijs/themes": "4.0.2", "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-eAVKTMedR5ckPo4xne/PjYQYrU3qx78gtJZ+sHlXEg5IHhhoQhMfZVzetTYuaJS0L2Ef3AcCRzCHV8T0WI6nIQ=="], - "shimmer": ["shimmer@1.2.1", "", {}, "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw=="], - "side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="], "side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="], @@ -2384,8 +2365,6 @@ "supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="], - "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], - "svgo": ["svgo@4.0.1", "", { "dependencies": { "commander": "^11.1.0", "css-select": "^5.1.0", "css-tree": "^3.0.1", "css-what": "^6.1.0", "csso": "^5.0.5", "picocolors": "^1.1.1", "sax": "^1.5.0" }, "bin": "./bin/svgo.js" }, "sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w=="], "tagged-tag": ["tagged-tag@1.0.0", "", {}, "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng=="], @@ -2644,8 +2623,6 @@ "@inquirer/core/wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="], - "@langfuse/otel/@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], - "@mdx-js/mdx/estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], "@mdx-js/mdx/remark-parse": ["remark-parse@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "micromark-util-types": "^2.0.0", "unified": "^11.0.0" } }, "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA=="], @@ -2656,56 +2633,6 @@ "@modelcontextprotocol/sdk/ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="], - "@opentelemetry/configuration/@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], - - "@opentelemetry/core/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.28.0", "", {}, "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA=="], - - "@opentelemetry/exporter-logs-otlp-grpc/@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], - - "@opentelemetry/exporter-logs-otlp-http/@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], - - "@opentelemetry/exporter-logs-otlp-proto/@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], - - "@opentelemetry/exporter-metrics-otlp-grpc/@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], - - "@opentelemetry/exporter-metrics-otlp-http/@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], - - "@opentelemetry/exporter-metrics-otlp-proto/@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], - - "@opentelemetry/exporter-prometheus/@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], - - "@opentelemetry/exporter-trace-otlp-grpc/@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], - - "@opentelemetry/exporter-trace-otlp-http/@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], - - "@opentelemetry/exporter-trace-otlp-proto/@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], - - "@opentelemetry/exporter-zipkin/@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], - - "@opentelemetry/otlp-exporter-base/@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], - - "@opentelemetry/otlp-grpc-exporter-base/@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], - - "@opentelemetry/otlp-transformer/@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], - - "@opentelemetry/propagator-b3/@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], - - "@opentelemetry/propagator-jaeger/@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], - - "@opentelemetry/resources/@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], - - "@opentelemetry/sdk-logs/@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], - - "@opentelemetry/sdk-metrics/@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], - - "@opentelemetry/sdk-node/@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], - - "@opentelemetry/sdk-node/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.214.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.214.0", "import-in-the-middle": "^3.0.0", "require-in-the-middle": "^8.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-MHqEX5Dk59cqVah5LiARMACku7jXSVk9iVDWOea4x3cr7VfdByeDCURK6o1lntT1JS/Tsovw01UJrBhN3/uC5w=="], - - "@opentelemetry/sdk-trace-base/@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], - - "@opentelemetry/sdk-trace-node/@opentelemetry/core": ["@opentelemetry/core@2.6.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="], - "@redocly/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], "@redocly/openapi-core/colorette": ["colorette@1.4.0", "", {}, "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g=="], @@ -2992,10 +2919,6 @@ "@modelcontextprotocol/sdk/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], - "@opentelemetry/sdk-node/@opentelemetry/instrumentation/import-in-the-middle": ["import-in-the-middle@3.0.1", "", { "dependencies": { "acorn": "^8.15.0", "acorn-import-attributes": "^1.9.5", "cjs-module-lexer": "^2.2.0", "module-details-from-path": "^1.0.4" } }, "sha512-pYkiyXVL2Mf3pozdlDGV6NAObxQx13Ae8knZk1UJRJ6uRW/ZRmTGHlQYtrsSl7ubuE5F8CD1z+s1n4RHNuTtuA=="], - - "@opentelemetry/sdk-node/@opentelemetry/instrumentation/require-in-the-middle": ["require-in-the-middle@8.0.1", "", { "dependencies": { "debug": "^4.3.5", "module-details-from-path": "^1.0.3" } }, "sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ=="], - "@redocly/openapi-core/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], "@slack/web-api/p-queue/eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="], @@ -3282,8 +3205,6 @@ "@inquirer/core/wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "@opentelemetry/sdk-node/@opentelemetry/instrumentation/import-in-the-middle/cjs-module-lexer": ["cjs-module-lexer@2.2.0", "", {}, "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ=="], - "@ts-morph/common/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], "@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], diff --git a/packages/providers/package.json b/packages/providers/package.json index cae9935859..9b529f94d8 100644 --- a/packages/providers/package.json +++ b/packages/providers/package.json @@ -23,7 +23,6 @@ "dependencies": { "@anthropic-ai/claude-agent-sdk": "^0.2.89", "@archon/paths": "workspace:*", - "@arizeai/openinference-instrumentation-claude-agent-sdk": "^0.2.2", "@langfuse/otel": "^5.1.0", "@langfuse/tracing": "^5.1.0", "@openai/codex-sdk": "^0.116.0", diff --git a/packages/providers/src/claude/provider.ts b/packages/providers/src/claude/provider.ts index 0e801110ac..c6ea9b704b 100644 --- a/packages/providers/src/claude/provider.ts +++ b/packages/providers/src/claude/provider.ts @@ -13,11 +13,12 @@ * - Not set: Auto-detect - use tokens if present in env, otherwise global auth */ import { + query, type Options, type HookCallback, type HookCallbackMatcher, } from '@anthropic-ai/claude-agent-sdk'; -import { getQuery } from '../observability'; +import { traceQuery } from '../observability'; import cliPath from '@anthropic-ai/claude-agent-sdk/embed'; import type { IAgentProvider, @@ -912,7 +913,7 @@ export class ClaudeProvider implements IAgentProvider { try { // 4. Run query with first-event timeout protection - const rawEvents = getQuery()({ prompt, options }); + const rawEvents = query({ prompt, options }); const timeoutMs = getFirstEventTimeoutMs(); const diagnostics = buildFirstEventHangDiagnostics( options.env as Record, @@ -920,8 +921,8 @@ export class ClaudeProvider implements IAgentProvider { ); const events = withFirstMessageTimeout(rawEvents, controller, timeoutMs, diagnostics); - // 5. Stream normalized events - yield* streamClaudeMessages(events, toolResultQueue); + // 5. Stream normalized events (with optional Langfuse tracing) + yield* traceQuery(prompt, options.model, streamClaudeMessages(events, toolResultQueue)); return; } catch (error) { const err = error as Error; diff --git a/packages/providers/src/codex/provider.ts b/packages/providers/src/codex/provider.ts index b9e1d493e9..0d1f7181c6 100644 --- a/packages/providers/src/codex/provider.ts +++ b/packages/providers/src/codex/provider.ts @@ -16,6 +16,7 @@ import type { ProviderCapabilities, } from '../types'; import { parseCodexConfig } from './config'; +import { traceQuery } from '../observability'; import { CODEX_CAPABILITIES } from './capabilities'; import { resolveCodexBinaryPath } from './binary-resolver'; import { createLogger } from '@archon/paths'; @@ -584,11 +585,15 @@ export class CodexProvider implements IAgentProvider { const result = await thread.runStreamed(prompt, turnOptions); // 5. Stream normalized events (fresh state per attempt to avoid dedup leaks) - yield* streamCodexEvents( - result.events as AsyncIterable>, - hasOutputFormat, - thread.id, - requestOptions?.abortSignal + yield* traceQuery( + prompt, + requestOptions?.model, + streamCodexEvents( + result.events as AsyncIterable>, + hasOutputFormat, + thread.id, + requestOptions?.abortSignal + ) ); return; } catch (error) { diff --git a/packages/providers/src/observability.test.ts b/packages/providers/src/observability.test.ts index fe3e9730c5..bf43b4b2af 100644 --- a/packages/providers/src/observability.test.ts +++ b/packages/providers/src/observability.test.ts @@ -3,10 +3,11 @@ import { withObservabilityContext, getObservabilityContext, isLangfuseEnabled, - getQuery, initLangfuse, shutdownLangfuse, + traceQuery, } from './observability'; +import type { MessageChunk } from './types'; describe('observability', () => { // ─── Context Propagation ──────────────────────────────────────────────── @@ -142,20 +143,101 @@ describe('observability', () => { }); }); - // ─── getQuery ────────────────────────────────────────────────────────── + // ─── traceQuery ───────────────────────────────────────────────────────── - describe('getQuery', () => { - test('returns a function', () => { - const queryFn = getQuery(); - expect(typeof queryFn).toBe('function'); + describe('traceQuery', () => { + async function* fakeGenerator(): AsyncGenerator { + yield { type: 'assistant', content: 'Hello ' }; + yield { type: 'assistant', content: 'world' }; + yield { type: 'result', sessionId: 'sess-1', tokens: { input: 10, output: 5 } }; + } + + test('passes through all chunks when Langfuse not initialized', async () => { + const chunks: MessageChunk[] = []; + for await (const chunk of traceQuery('test prompt', 'sonnet', fakeGenerator())) { + chunks.push(chunk); + } + + expect(chunks).toHaveLength(3); + expect(chunks[0]).toEqual({ type: 'assistant', content: 'Hello ' }); + expect(chunks[1]).toEqual({ type: 'assistant', content: 'world' }); + expect(chunks[2]).toMatchObject({ type: 'result', sessionId: 'sess-1' }); + }); + + test('passes through tool call and tool result chunks', async () => { + async function* toolGenerator(): AsyncGenerator { + yield { type: 'tool', toolName: 'Read', toolInput: { path: '/file.ts' } }; + yield { type: 'tool_result', toolName: 'Read', toolOutput: 'file content here' }; + yield { type: 'assistant', content: 'I read the file.' }; + yield { type: 'result', sessionId: 'sess-2', tokens: { input: 50, output: 20 } }; + } + + const chunks: MessageChunk[] = []; + for await (const chunk of traceQuery('read a file', 'sonnet', toolGenerator())) { + chunks.push(chunk); + } + + expect(chunks).toHaveLength(4); + expect(chunks[0]).toMatchObject({ type: 'tool', toolName: 'Read' }); + expect(chunks[1]).toMatchObject({ type: 'tool_result', toolName: 'Read' }); + expect(chunks[2]).toEqual({ type: 'assistant', content: 'I read the file.' }); + expect(chunks[3]).toMatchObject({ type: 'result', sessionId: 'sess-2' }); + }); + + test('handles empty generator', async () => { + async function* emptyGenerator(): AsyncGenerator { + // yields nothing + } + + const chunks: MessageChunk[] = []; + for await (const chunk of traceQuery('empty', 'sonnet', emptyGenerator())) { + chunks.push(chunk); + } + + expect(chunks).toHaveLength(0); }); - test('returns the SDK query function when Langfuse not initialized', () => { - const queryFn = getQuery(); - // Should be a callable function (original SDK query) - expect(typeof queryFn).toBe('function'); - // Should NOT be the instrumented wrapper - expect(queryFn.name).not.toBe('wrappedQuery'); + test('passes through cost and numTurns from result', async () => { + async function* costGenerator(): AsyncGenerator { + yield { type: 'assistant', content: 'done' }; + yield { + type: 'result', + sessionId: 'sess-3', + tokens: { input: 100, output: 50, total: 150 }, + cost: 0.0042, + numTurns: 3, + }; + } + + const chunks: MessageChunk[] = []; + for await (const chunk of traceQuery('test', 'opus', costGenerator())) { + chunks.push(chunk); + } + + expect(chunks).toHaveLength(2); + const result = chunks[1]; + expect(result).toMatchObject({ + type: 'result', + cost: 0.0042, + numTurns: 3, + }); + }); + + test('preserves generator error propagation', async () => { + async function* errorGenerator(): AsyncGenerator { + yield { type: 'assistant', content: 'partial' }; + throw new Error('stream failed'); + } + + const chunks: MessageChunk[] = []; + await expect(async () => { + for await (const chunk of traceQuery('test', 'sonnet', errorGenerator())) { + chunks.push(chunk); + } + }).toThrow('stream failed'); + + expect(chunks).toHaveLength(1); + expect(chunks[0]).toEqual({ type: 'assistant', content: 'partial' }); }); }); diff --git a/packages/providers/src/observability.ts b/packages/providers/src/observability.ts index a9528e1d65..3b21db3e16 100644 --- a/packages/providers/src/observability.ts +++ b/packages/providers/src/observability.ts @@ -3,12 +3,13 @@ * * When LANGFUSE_PUBLIC_KEY + LANGFUSE_SECRET_KEY are set, initializes: * - OTel NodeSDK with LangfuseSpanProcessor - * - Auto-instrumentation for @anthropic-ai/claude-agent-sdk + * - Manual tracing of Claude Agent SDK calls with input/output/usage/tool calls * * When not configured, all exports are safe no-ops with zero overhead. */ import { AsyncLocalStorage } from 'async_hooks'; import { createLogger } from '@archon/paths'; +import type { MessageChunk, TokenUsage } from './types'; /** Lazy-initialized logger (deferred so test mocks can intercept createLogger) */ let cachedLog: ReturnType | undefined; @@ -47,12 +48,16 @@ export function getObservabilityContext(): ObservabilityAttrs | undefined { let initialized = false; -type QueryFn = typeof import('@anthropic-ai/claude-agent-sdk').query; -let instrumentedQueryFn: QueryFn | null = null; - // Hold a reference to the OTel SDK for clean shutdown let otelSdk: { shutdown: () => Promise } | null = null; +// Lazily-loaded tracing functions (populated by initLangfuse) +let tracingStartActiveObservation: + | typeof import('@langfuse/tracing').startActiveObservation + | null = null; +let tracingPropagateAttributes: typeof import('@langfuse/tracing').propagateAttributes | null = + null; + /** Check whether Langfuse env vars are present */ export function isLangfuseEnabled(): boolean { return !!(process.env.LANGFUSE_PUBLIC_KEY && process.env.LANGFUSE_SECRET_KEY); @@ -70,21 +75,14 @@ export async function initLangfuse(): Promise { if (!isLangfuseEnabled()) return false; try { - const [otelSdkMod, langfuseOtel, claudeInstr, claudeSdk] = await Promise.all([ + const [otelSdkMod, langfuseOtel, langfuseTracing] = await Promise.all([ import('@opentelemetry/sdk-node'), import('@langfuse/otel'), - import('@arizeai/openinference-instrumentation-claude-agent-sdk'), - import('@anthropic-ai/claude-agent-sdk'), + import('@langfuse/tracing'), ]); - // Create a mutable copy of the SDK module for instrumentation. - // Type assertion needed: the instrumentation expects its own peer dep type, - // which may differ from our pinned SDK version at the type level. - const sdkCopy = { ...claudeSdk }; - const instrumentation = new claudeInstr.ClaudeAgentSDKInstrumentation(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any -- bridging SDK version types for auto-instrumentation - instrumentation.manuallyInstrument(sdkCopy as any); - instrumentedQueryFn = sdkCopy.query; + tracingStartActiveObservation = langfuseTracing.startActiveObservation; + tracingPropagateAttributes = langfuseTracing.propagateAttributes; const publicKey = process.env.LANGFUSE_PUBLIC_KEY ?? ''; const secretKey = process.env.LANGFUSE_SECRET_KEY ?? ''; @@ -92,14 +90,10 @@ export async function initLangfuse(): Promise { publicKey, secretKey, baseUrl: process.env.LANGFUSE_BASE_URL ?? 'https://cloud.langfuse.com', - // Export all spans — the default filter (isDefaultExportSpan) may not - // recognize the Arize Claude Agent SDK instrumentation spans. - shouldExportSpan: (): boolean => true, }); const sdk = new otelSdkMod.NodeSDK({ spanProcessors: [spanProcessor], - instrumentations: [instrumentation], }); sdk.start(); otelSdk = sdk; @@ -113,10 +107,7 @@ export async function initLangfuse(): Promise { } catch (error) { const err = error as Error; getLog().warn({ error: err.message, errorType: err.constructor.name }, 'langfuse.init_failed'); - // Reset partial state so getQuery() falls back to the original SDK - otelSdk = null; - initialized = false; - instrumentedQueryFn = null; + resetState(); return false; } } @@ -132,24 +123,172 @@ export async function shutdownLangfuse(): Promise { const err = error as Error; getLog().warn({ error: err.message }, 'langfuse.shutdown_failed'); } finally { - otelSdk = null; - initialized = false; - instrumentedQueryFn = null; + resetState(); } } -// ─── Instrumented Query ───────────────────────────────────────────────────── +function resetState(): void { + otelSdk = null; + initialized = false; + tracingStartActiveObservation = null; + tracingPropagateAttributes = null; +} + +// ─── Query Tracing ────────────────────────────────────────────────────────── /** - * Return the instrumented `query` function when Langfuse is active, - * otherwise fall back to the original SDK export. + * Wrap a MessageChunk async generator with Langfuse tracing. + * + * Creates a parent "generation" observation with: + * - Input: the prompt sent to Claude + * - Output: collected assistant text + * - Usage: token counts and cost from the result event + * - Children: one "tool" span per tool call with name and input/output + * + * When Langfuse is not initialized, returns the generator unchanged (zero overhead). */ -export function getQuery(): QueryFn { - if (instrumentedQueryFn) return instrumentedQueryFn; - - // Fallback: return the original, un-instrumented query - // This import is resolved at module load time by Bun (static reference) - // eslint-disable-next-line @typescript-eslint/no-require-imports - const { query } = require('@anthropic-ai/claude-agent-sdk') as { query: QueryFn }; - return query; +export async function* traceQuery( + prompt: string, + model: string | undefined, + generator: AsyncIterable +): AsyncGenerator { + if (!initialized || !tracingStartActiveObservation || !tracingPropagateAttributes) { + yield* generator as AsyncGenerator; + return; + } + + const startActiveObservation = tracingStartActiveObservation; + const propagateAttributes = tracingPropagateAttributes; + const ctx = getObservabilityContext(); + + // Collect output data as we stream + const textParts: string[] = []; + const toolCalls: { name: string; input?: unknown; output?: string; durationMs?: number }[] = []; + let usage: TokenUsage | undefined; + let cost: number | undefined; + let numTurns: number | undefined; + let sessionId: string | undefined; + let traceError: Error | undefined; + let activeToolCall: { name: string; input?: unknown; startTime: number } | null = null; + + const chunks: MessageChunk[] = []; + let streamError: Error | undefined; + + // Consume the generator and collect chunks + try { + for await (const chunk of generator) { + chunks.push(chunk); + + if (chunk.type === 'assistant') { + textParts.push(chunk.content); + } else if (chunk.type === 'tool') { + // New tool call starting — close any previous one + if (activeToolCall) { + toolCalls.push({ + name: activeToolCall.name, + input: activeToolCall.input, + durationMs: Date.now() - activeToolCall.startTime, + }); + } + activeToolCall = { name: chunk.toolName, input: chunk.toolInput, startTime: Date.now() }; + } else if (chunk.type === 'tool_result') { + if (activeToolCall) { + toolCalls.push({ + name: activeToolCall.name, + input: activeToolCall.input, + output: + typeof chunk.toolOutput === 'string' + ? chunk.toolOutput.slice(0, 1000) + : JSON.stringify(chunk.toolOutput).slice(0, 1000), + durationMs: Date.now() - activeToolCall.startTime, + }); + activeToolCall = null; + } + } else if (chunk.type === 'result') { + // Close any remaining open tool call + if (activeToolCall) { + toolCalls.push({ + name: activeToolCall.name, + input: activeToolCall.input, + durationMs: Date.now() - activeToolCall.startTime, + }); + activeToolCall = null; + } + if (chunk.tokens) usage = chunk.tokens; + if (chunk.cost !== undefined) cost = chunk.cost; + if (chunk.numTurns !== undefined) numTurns = chunk.numTurns; + if (chunk.sessionId) sessionId = chunk.sessionId; + } + } + } catch (err) { + streamError = err as Error; + traceError = streamError; + } + + // Create the trace with the collected data + try { + await propagateAttributes( + { + ...(ctx?.conversationId ? { sessionId: ctx.conversationId } : {}), + tags: [ + ...(ctx?.platformType ? [ctx.platformType] : []), + ...(ctx?.workflowName ? [ctx.workflowName] : []), + ], + }, + async () => { + await startActiveObservation( + 'claude-agent-query', + async obs => { + // Create child spans for tool calls + for (const tool of toolCalls) { + const toolObs = obs.startObservation( + tool.name, + { + input: tool.input, + output: tool.output, + }, + { asType: 'tool' } + ); + toolObs.end(); + } + + const usageDetails: Record = {}; + if (usage) { + if (usage.input) usageDetails.input = usage.input; + if (usage.output) usageDetails.output = usage.output; + if (usage.total) usageDetails.total = usage.total; + } + + obs.update({ + input: prompt, + output: textParts.join(''), + model: model ?? 'claude-sonnet-4-20250514', + usageDetails: Object.keys(usageDetails).length > 0 ? usageDetails : undefined, + metadata: { + ...(cost !== undefined ? { totalCostUsd: cost } : {}), + ...(numTurns !== undefined ? { numTurns } : {}), + ...(sessionId ? { claudeSessionId: sessionId } : {}), + ...(toolCalls.length > 0 ? { toolCallCount: toolCalls.length } : {}), + ...(traceError ? { error: traceError.message } : {}), + }, + level: traceError ? 'ERROR' : undefined, + }); + }, + { asType: 'generation' } + ); + } + ); + } catch (traceErr) { + getLog().debug({ error: (traceErr as Error).message }, 'langfuse.trace_failed'); + } + + // Yield all collected chunks to the consumer + for (const chunk of chunks) { + yield chunk; + } + + // Re-throw the stream error if one occurred + if (streamError) { + throw streamError; + } } From 331585b484163abd023fb9cd05c3a6da34d521e7 Mon Sep 17 00:00:00 2001 From: Raphael Lechner Date: Mon, 13 Apr 2026 18:17:18 +0200 Subject: [PATCH 5/6] =?UTF-8?q?fix:=20address=20review=20=E2=80=94=20strea?= =?UTF-8?q?ming,=20toolCallId=20correlation,=20provider-neutral=20names?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Yield chunks immediately instead of buffering (preserves streaming semantics) - Correlate tool calls by toolCallId via Map instead of single active slot - Rename trace from 'claude-agent-query' to 'agent-query' (provider-neutral) - Make model optional instead of defaulting to claude-sonnet --- packages/providers/src/observability.test.ts | 7 +- packages/providers/src/observability.ts | 79 ++++++++++---------- 2 files changed, 43 insertions(+), 43 deletions(-) diff --git a/packages/providers/src/observability.test.ts b/packages/providers/src/observability.test.ts index bf43b4b2af..0ceb2da1db 100644 --- a/packages/providers/src/observability.test.ts +++ b/packages/providers/src/observability.test.ts @@ -230,12 +230,15 @@ describe('observability', () => { } const chunks: MessageChunk[] = []; - await expect(async () => { + try { for await (const chunk of traceQuery('test', 'sonnet', errorGenerator())) { chunks.push(chunk); } - }).toThrow('stream failed'); + } catch (err) { + expect((err as Error).message).toBe('stream failed'); + } + // Chunk was yielded immediately (streaming preserved), before error expect(chunks).toHaveLength(1); expect(chunks[0]).toEqual({ type: 'assistant', content: 'partial' }); }); diff --git a/packages/providers/src/observability.ts b/packages/providers/src/observability.ts index 3b21db3e16..150a4edce9 100644 --- a/packages/providers/src/observability.ts +++ b/packages/providers/src/observability.ts @@ -161,7 +161,7 @@ export async function* traceQuery( const propagateAttributes = tracingPropagateAttributes; const ctx = getObservabilityContext(); - // Collect output data as we stream + // Collect metadata as we stream (chunks are yielded immediately) const textParts: string[] = []; const toolCalls: { name: string; input?: unknown; output?: string; durationMs?: number }[] = []; let usage: TokenUsage | undefined; @@ -169,63 +169,70 @@ export async function* traceQuery( let numTurns: number | undefined; let sessionId: string | undefined; let traceError: Error | undefined; - let activeToolCall: { name: string; input?: unknown; startTime: number } | null = null; - const chunks: MessageChunk[] = []; + // Track active tool calls by ID for correct pairing (tools can overlap) + const activeToolCalls = new Map(); + const unnamedToolQueue: { name: string; input?: unknown; startTime: number }[] = []; + let streamError: Error | undefined; - // Consume the generator and collect chunks + // Stream chunks through — yield immediately, collect metadata for trace try { for await (const chunk of generator) { - chunks.push(chunk); - if (chunk.type === 'assistant') { textParts.push(chunk.content); } else if (chunk.type === 'tool') { - // New tool call starting — close any previous one - if (activeToolCall) { - toolCalls.push({ - name: activeToolCall.name, - input: activeToolCall.input, - durationMs: Date.now() - activeToolCall.startTime, - }); - } - activeToolCall = { name: chunk.toolName, input: chunk.toolInput, startTime: Date.now() }; + const call = { name: chunk.toolName, input: chunk.toolInput, startTime: Date.now() }; + if (chunk.toolCallId) activeToolCalls.set(chunk.toolCallId, call); + else unnamedToolQueue.push(call); } else if (chunk.type === 'tool_result') { - if (activeToolCall) { + const matched = chunk.toolCallId + ? activeToolCalls.get(chunk.toolCallId) + : unnamedToolQueue.shift(); + if (matched) { toolCalls.push({ - name: activeToolCall.name, - input: activeToolCall.input, + name: matched.name, + input: matched.input, output: typeof chunk.toolOutput === 'string' ? chunk.toolOutput.slice(0, 1000) : JSON.stringify(chunk.toolOutput).slice(0, 1000), - durationMs: Date.now() - activeToolCall.startTime, + durationMs: Date.now() - matched.startTime, }); - activeToolCall = null; + if (chunk.toolCallId) activeToolCalls.delete(chunk.toolCallId); } } else if (chunk.type === 'result') { - // Close any remaining open tool call - if (activeToolCall) { + // Flush remaining unmatched tool calls + for (const pending of activeToolCalls.values()) { toolCalls.push({ - name: activeToolCall.name, - input: activeToolCall.input, - durationMs: Date.now() - activeToolCall.startTime, + name: pending.name, + input: pending.input, + durationMs: Date.now() - pending.startTime, + }); + } + activeToolCalls.clear(); + for (let pending = unnamedToolQueue.shift(); pending; pending = unnamedToolQueue.shift()) { + toolCalls.push({ + name: pending.name, + input: pending.input, + durationMs: Date.now() - pending.startTime, }); - activeToolCall = null; } if (chunk.tokens) usage = chunk.tokens; if (chunk.cost !== undefined) cost = chunk.cost; if (chunk.numTurns !== undefined) numTurns = chunk.numTurns; if (chunk.sessionId) sessionId = chunk.sessionId; } + + // Yield immediately to preserve streaming semantics + yield chunk; } } catch (err) { streamError = err as Error; traceError = streamError; } - // Create the trace with the collected data + // Create the trace after stream completes try { await propagateAttributes( { @@ -237,16 +244,12 @@ export async function* traceQuery( }, async () => { await startActiveObservation( - 'claude-agent-query', + 'agent-query', async obs => { - // Create child spans for tool calls for (const tool of toolCalls) { const toolObs = obs.startObservation( tool.name, - { - input: tool.input, - output: tool.output, - }, + { input: tool.input, output: tool.output }, { asType: 'tool' } ); toolObs.end(); @@ -262,12 +265,12 @@ export async function* traceQuery( obs.update({ input: prompt, output: textParts.join(''), - model: model ?? 'claude-sonnet-4-20250514', + ...(model ? { model } : {}), usageDetails: Object.keys(usageDetails).length > 0 ? usageDetails : undefined, metadata: { ...(cost !== undefined ? { totalCostUsd: cost } : {}), ...(numTurns !== undefined ? { numTurns } : {}), - ...(sessionId ? { claudeSessionId: sessionId } : {}), + ...(sessionId ? { sessionId } : {}), ...(toolCalls.length > 0 ? { toolCallCount: toolCalls.length } : {}), ...(traceError ? { error: traceError.message } : {}), }, @@ -282,12 +285,6 @@ export async function* traceQuery( getLog().debug({ error: (traceErr as Error).message }, 'langfuse.trace_failed'); } - // Yield all collected chunks to the consumer - for (const chunk of chunks) { - yield chunk; - } - - // Re-throw the stream error if one occurred if (streamError) { throw streamError; } From 1dc9f68c7ee6853de1fddd12cd026113a2bacb55 Mon Sep 17 00:00:00 2001 From: Raphael Lechner Date: Mon, 13 Apr 2026 18:41:08 +0200 Subject: [PATCH 6/6] fix: buffer chunks before yielding to ensure traces are created Post-yield code in async generators is not reliably executed through multiple yield* delegation layers in Bun. Revert to buffered approach: collect all chunks, create Langfuse trace, then yield chunks. Also adds explicit forceFlush() before OTel SDK shutdown and a regression test simulating the yield* delegation chain. --- packages/providers/src/observability.test.ts | 40 ++++++++++++++++++++ packages/providers/src/observability.ts | 35 ++++++++++++----- 2 files changed, 66 insertions(+), 9 deletions(-) diff --git a/packages/providers/src/observability.test.ts b/packages/providers/src/observability.test.ts index 0ceb2da1db..8729e3a2b4 100644 --- a/packages/providers/src/observability.test.ts +++ b/packages/providers/src/observability.test.ts @@ -223,6 +223,46 @@ describe('observability', () => { }); }); + test('delivers all chunks when consumed via yield* delegation chain', async () => { + // Regression test: traceQuery must deliver all chunks even when consumed + // through multiple layers of yield* delegation (as in sendQuery → DAG executor). + // A previous streaming implementation lost traces because post-yield code + // in async generators is not reliably executed through yield* chains in Bun. + async function* innerGenerator(): AsyncGenerator { + yield { type: 'tool', toolName: 'Read', toolInput: { path: '/a.ts' } }; + yield { type: 'tool_result', toolName: 'Read', toolOutput: 'content-a' }; + yield { type: 'assistant', content: 'Analysis: ' }; + yield { type: 'tool', toolName: 'Bash', toolInput: { command: 'ls' } }; + yield { type: 'tool_result', toolName: 'Bash', toolOutput: 'file1\nfile2' }; + yield { type: 'assistant', content: 'done.' }; + yield { type: 'result', sessionId: 'sess-chain', tokens: { input: 200, output: 80 } }; + } + + // Simulate yield* delegation chain (sendQuery pattern) + async function* middleLayer(): AsyncGenerator { + yield* traceQuery('analyze code', 'sonnet', innerGenerator()); + } + + async function* outerLayer(): AsyncGenerator { + yield* middleLayer(); + } + + const chunks: MessageChunk[] = []; + for await (const chunk of outerLayer()) { + chunks.push(chunk); + } + + // All 7 chunks must arrive — none lost in the delegation chain + expect(chunks).toHaveLength(7); + expect(chunks[0]).toMatchObject({ type: 'tool', toolName: 'Read' }); + expect(chunks[1]).toMatchObject({ type: 'tool_result', toolName: 'Read' }); + expect(chunks[2]).toEqual({ type: 'assistant', content: 'Analysis: ' }); + expect(chunks[3]).toMatchObject({ type: 'tool', toolName: 'Bash' }); + expect(chunks[4]).toMatchObject({ type: 'tool_result', toolName: 'Bash' }); + expect(chunks[5]).toEqual({ type: 'assistant', content: 'done.' }); + expect(chunks[6]).toMatchObject({ type: 'result', sessionId: 'sess-chain' }); + }); + test('preserves generator error propagation', async () => { async function* errorGenerator(): AsyncGenerator { yield { type: 'assistant', content: 'partial' }; diff --git a/packages/providers/src/observability.ts b/packages/providers/src/observability.ts index 150a4edce9..0ba511bed3 100644 --- a/packages/providers/src/observability.ts +++ b/packages/providers/src/observability.ts @@ -48,8 +48,9 @@ export function getObservabilityContext(): ObservabilityAttrs | undefined { let initialized = false; -// Hold a reference to the OTel SDK for clean shutdown +// Hold references for clean shutdown let otelSdk: { shutdown: () => Promise } | null = null; +let spanProcessor: { forceFlush: () => Promise } | null = null; // Lazily-loaded tracing functions (populated by initLangfuse) let tracingStartActiveObservation: @@ -86,17 +87,18 @@ export async function initLangfuse(): Promise { const publicKey = process.env.LANGFUSE_PUBLIC_KEY ?? ''; const secretKey = process.env.LANGFUSE_SECRET_KEY ?? ''; - const spanProcessor = new langfuseOtel.LangfuseSpanProcessor({ + const processor = new langfuseOtel.LangfuseSpanProcessor({ publicKey, secretKey, baseUrl: process.env.LANGFUSE_BASE_URL ?? 'https://cloud.langfuse.com', }); const sdk = new otelSdkMod.NodeSDK({ - spanProcessors: [spanProcessor], + spanProcessors: [processor], }); sdk.start(); otelSdk = sdk; + spanProcessor = processor; initialized = true; getLog().info( @@ -115,6 +117,10 @@ export async function initLangfuse(): Promise { /** Flush pending spans and shut down the OTel SDK */ export async function shutdownLangfuse(): Promise { try { + // Explicit flush before shutdown to ensure all spans are exported + if (spanProcessor) { + await spanProcessor.forceFlush(); + } if (otelSdk) { await otelSdk.shutdown(); getLog().debug('langfuse.shutdown_completed'); @@ -129,6 +135,7 @@ export async function shutdownLangfuse(): Promise { function resetState(): void { otelSdk = null; + spanProcessor = null; initialized = false; tracingStartActiveObservation = null; tracingPropagateAttributes = null; @@ -161,7 +168,9 @@ export async function* traceQuery( const propagateAttributes = tracingPropagateAttributes; const ctx = getObservabilityContext(); - // Collect metadata as we stream (chunks are yielded immediately) + // Collect all chunks first, then create trace, then yield. + // We buffer because post-yield code in async generators is not reliably + // executed when delegated through multiple yield* layers in Bun. const textParts: string[] = []; const toolCalls: { name: string; input?: unknown; output?: string; durationMs?: number }[] = []; let usage: TokenUsage | undefined; @@ -174,11 +183,14 @@ export async function* traceQuery( const activeToolCalls = new Map(); const unnamedToolQueue: { name: string; input?: unknown; startTime: number }[] = []; + const chunks: MessageChunk[] = []; let streamError: Error | undefined; - // Stream chunks through — yield immediately, collect metadata for trace + // Consume the stream and collect metadata try { for await (const chunk of generator) { + chunks.push(chunk); + if (chunk.type === 'assistant') { textParts.push(chunk.content); } else if (chunk.type === 'tool') { @@ -223,9 +235,6 @@ export async function* traceQuery( if (chunk.numTurns !== undefined) numTurns = chunk.numTurns; if (chunk.sessionId) sessionId = chunk.sessionId; } - - // Yield immediately to preserve streaming semantics - yield chunk; } } catch (err) { streamError = err as Error; @@ -282,7 +291,15 @@ export async function* traceQuery( } ); } catch (traceErr) { - getLog().debug({ error: (traceErr as Error).message }, 'langfuse.trace_failed'); + getLog().warn( + { error: (traceErr as Error).message, errorType: (traceErr as Error).constructor.name }, + 'langfuse.trace_failed' + ); + } + + // Yield all collected chunks to the consumer + for (const chunk of chunks) { + yield chunk; } if (streamError) {