From bc91a15905aa12f9568cf69bf14cd2da85d1d239 Mon Sep 17 00:00:00 2001 From: Sam Rolfe Date: Mon, 3 Aug 2026 20:09:58 +1000 Subject: [PATCH] =?UTF-8?q?Add=20pi-langfuse=20extension=20(Langfuse=20LLM?= =?UTF-8?q?=20observability=20for=20pi)=20=E2=80=94=20vendored=20from=20np?= =?UTF-8?q?m=201.5.8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- extensions/pi-langfuse/README.md | 278 + extensions/pi-langfuse/README_CN.md | 210 + extensions/pi-langfuse/image.png | Bin 0 -> 135345 bytes extensions/pi-langfuse/index.ts | 215 + extensions/pi-langfuse/package-lock.json | 5074 +++++++++++++++++ extensions/pi-langfuse/package.json | 69 + extensions/pi-langfuse/skills-lock.json | 11 + extensions/pi-langfuse/src/capture-policy.ts | 141 + extensions/pi-langfuse/src/commands.ts | 423 ++ extensions/pi-langfuse/src/config.ts | 169 + extensions/pi-langfuse/src/constants.ts | 13 + extensions/pi-langfuse/src/handlers/agent.ts | 173 + .../pi-langfuse/src/handlers/generation.ts | 246 + extensions/pi-langfuse/src/handlers/tool.ts | 156 + extensions/pi-langfuse/src/handlers/turn.ts | 55 + extensions/pi-langfuse/src/langfuse.ts | 756 +++ extensions/pi-langfuse/src/limits.ts | 97 + extensions/pi-langfuse/src/observation.ts | 21 + extensions/pi-langfuse/src/redaction.ts | 119 + extensions/pi-langfuse/src/source-metadata.ts | 160 + extensions/pi-langfuse/src/state.ts | 164 + extensions/pi-langfuse/src/types.ts | 139 + extensions/pi-langfuse/src/utils.ts | 427 ++ extensions/pi-langfuse/tsconfig.json | 16 + .../types/langfuse-runtime-shims.d.ts | 49 + extensions/pi-langfuse/types/node-shims.d.ts | 29 + .../pi-langfuse/types/pi-coding-agent.d.ts | 9 + 27 files changed, 9219 insertions(+) create mode 100644 extensions/pi-langfuse/README.md create mode 100644 extensions/pi-langfuse/README_CN.md create mode 100644 extensions/pi-langfuse/image.png create mode 100644 extensions/pi-langfuse/index.ts create mode 100644 extensions/pi-langfuse/package-lock.json create mode 100644 extensions/pi-langfuse/package.json create mode 100644 extensions/pi-langfuse/skills-lock.json create mode 100644 extensions/pi-langfuse/src/capture-policy.ts create mode 100644 extensions/pi-langfuse/src/commands.ts create mode 100644 extensions/pi-langfuse/src/config.ts create mode 100644 extensions/pi-langfuse/src/constants.ts create mode 100644 extensions/pi-langfuse/src/handlers/agent.ts create mode 100644 extensions/pi-langfuse/src/handlers/generation.ts create mode 100644 extensions/pi-langfuse/src/handlers/tool.ts create mode 100644 extensions/pi-langfuse/src/handlers/turn.ts create mode 100644 extensions/pi-langfuse/src/langfuse.ts create mode 100644 extensions/pi-langfuse/src/limits.ts create mode 100644 extensions/pi-langfuse/src/observation.ts create mode 100644 extensions/pi-langfuse/src/redaction.ts create mode 100644 extensions/pi-langfuse/src/source-metadata.ts create mode 100644 extensions/pi-langfuse/src/state.ts create mode 100644 extensions/pi-langfuse/src/types.ts create mode 100644 extensions/pi-langfuse/src/utils.ts create mode 100644 extensions/pi-langfuse/tsconfig.json create mode 100644 extensions/pi-langfuse/types/langfuse-runtime-shims.d.ts create mode 100644 extensions/pi-langfuse/types/node-shims.d.ts create mode 100644 extensions/pi-langfuse/types/pi-coding-agent.d.ts diff --git a/extensions/pi-langfuse/README.md b/extensions/pi-langfuse/README.md new file mode 100644 index 0000000..953bd52 --- /dev/null +++ b/extensions/pi-langfuse/README.md @@ -0,0 +1,278 @@ +# pi-langfuse + +[![npm version](https://img.shields.io/npm/v/pi-langfuse)](https://www.npmjs.com/package/pi-langfuse) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) + +[**English**](./README.md) | [**简体中文**](./README_CN.md) + +Langfuse observability extension for [Pi Coding Agent](https://github.com/earendil-works/pi-coding-agent). It sends complete Pi runs to [Langfuse](https://langfuse.com) so the prompt, agent workflow, LLM generations, tool calls, final response, usage, cost, and health scores appear in one trace. + +## What This Adds to Pi + +- One Langfuse trace per user prompt, grouped by Pi session. +- Root `agent`, per-request `generation`, and per-tool `tool` observations. +- Final assistant output capture, tool error visibility, and trace-level scores. +- Privacy controls for inputs, outputs, tool I/O, system prompt, and cwd. +- Secret redaction and local path hashing before upload. +- REST fallback for self-hosted Langfuse setups where OTel spans arrive but traces do not materialize. + +## Prerequisites + +- **Node.js** >= 22 +- **Pi Coding Agent** installed and configured +- A **Langfuse** account ([cloud](https://cloud.langfuse.com) or self-hosted) + +## Quick Start + +1. Install the extension: + + ```bash + pi install npm:pi-langfuse + ``` + +2. Run Pi once. If no credentials are configured yet, Pi prompts for: + - Langfuse public key, starting with `pk-lf-...` + - Langfuse secret key, starting with `sk-lf-...` + - Langfuse host, defaulting to `https://cloud.langfuse.com` + +3. Run Pi normally: + + ```bash + pi "Explain the architecture of Redis" + ``` + +4. Open Langfuse and inspect the new trace. + +## Configuration + +Langfuse API keys are available in **Langfuse Cloud** -> **Settings** -> **API Keys**. + +### Method 1: Interactive setup + +Run any `pi` command with the extension loaded. On first run without configuration, Pi prompts in the CLI or TUI and saves the result to `~/.pi/agent/pi-langfuse/config.json`. + +To run setup again: + +```text +/langfuse-setup +``` + +To inspect the active configuration without exposing secrets: + +```text +/langfuse-status +``` + +The status command reports the config source, host, masked public key, capture policy, active-run state, config path, and last runtime error. + +### Method 2: Environment variables + +Set these before starting Pi: + +```bash +export LANGFUSE_PUBLIC_KEY="pk-lf-xxxx" +export LANGFUSE_SECRET_KEY="sk-lf-xxxx" +export LANGFUSE_BASE_URL="https://cloud.langfuse.com" # optional; LANGFUSE_HOST is also supported +``` + +Saved config takes precedence. Environment variables are only used when `~/.pi/agent/pi-langfuse/config.json` is missing or incomplete. + +Privacy controls can also be set through environment variables: + +```bash +export LANGFUSE_PRIVACY_PRESET="full-debug" +``` + +Available presets: + +| Preset | Captures | +|--------|----------| +| `metadata-only` | Metadata only; omits inputs, outputs, tool I/O, system prompt, and cwd | +| `prompts-only` | Prompt/provider inputs plus metadata | +| `conversations` | Inputs and assistant outputs, but omits tool I/O, system prompt, and cwd | +| `full-debug` | Full trace detail; this is the default | + +Fine-grained flags override presets: + +```bash +export LANGFUSE_CAPTURE_INPUTS=true +export LANGFUSE_CAPTURE_OUTPUTS=true +export LANGFUSE_CAPTURE_TOOL_IO=false +export LANGFUSE_CAPTURE_SYSTEM_PROMPT=false +export LANGFUSE_CAPTURE_CWD=false +``` + +All captured payloads are redacted before upload. The extension masks common API keys, bearer tokens, passwords, cookies, private keys, Langfuse keys, GitHub/npm/AWS-style tokens, and local absolute paths. + +### Payload limits + +Before upload, payloads are shaped: strings are truncated and deeply nested or +very wide structures are trimmed. These caps keep traces small and protect the +Langfuse ingestion pipeline. Override any of them (no rebuild needed): + +```bash +export PI_LANGFUSE_MAX_STRING_LENGTH=12000 # per-string chars (system prompt, inputs) +export PI_LANGFUSE_MAX_TOOL_PAYLOAD_LENGTH=24000 # per tool input/output chars +export PI_LANGFUSE_MAX_DEPTH=6 # max nesting depth +export PI_LANGFUSE_MAX_ARRAY_ITEMS=50 # max array elements kept +export PI_LANGFUSE_MAX_OBJECT_KEYS=80 # max object keys kept +export PI_LANGFUSE_MAX_PAYLOAD_NODES=2000 # max total nodes per payload +``` + +Set any limit to `0`, `off`, `none`, or `unlimited` to disable that cap +entirely (captures the full value). Unset or invalid values fall back to the +defaults shown above. To capture a very large system prompt or big tool +payloads in full, raise or disable the relevant limit (e.g. +`PI_LANGFUSE_MAX_STRING_LENGTH=off`). + +### Method 3: Persistent `config.json` + +Create or update `~/.pi/agent/pi-langfuse/config.json`: + +```json +{ + "publicKey": "pk-lf-xxxx", + "secretKey": "sk-lf-xxxx", + "host": "https://cloud.langfuse.com", + "privacyPreset": "conversations" +} +``` + +Fine-grained capture flags can also be persisted: + +```json +{ + "publicKey": "pk-lf-xxxx", + "secretKey": "sk-lf-xxxx", + "host": "https://cloud.langfuse.com", + "capture": { + "LANGFUSE_PRIVACY_PRESET": "metadata-only", + "LANGFUSE_CAPTURE_INPUTS": "true" + } +} +``` + +> **Security**: Keep `~/.pi/agent/pi-langfuse/config.json` private. Never commit API keys to version control. +> When the extension writes this file itself, it creates the config directory with `0700` permissions and the file with `0600` permissions where the host filesystem supports POSIX modes. + +## Verify the Extension + +Check that Pi has loaded the package: + +```bash +pi list +``` + +`pi-langfuse` should appear in the installed package list. + +To verify the Langfuse host and API keys from inside Pi, run: + +```text +/langfuse-test +``` + +This command makes a timeout-bounded authenticated request to Langfuse and, if it succeeds, sends a small test trace. + +## What Appears in Langfuse + +- Each Pi session gets its own Langfuse session ID. +- Each user prompt within that session becomes a separate trace. +- The trace contains the final assistant output shown in Pi. +- Tool runs appear as tool observations with arguments, results, and error state. +- LLM requests appear as generation observations, including usage and cost when the provider exposes them. +- Trace-level scores include tool counts, tool success rate, and whether the run had errors. + +The package also includes a Langfuse CLI skill, so Langfuse data can be queried directly from Pi: + +```text +/pi-langfuse-langfuse +``` + +## Source Metadata + +Local prototype note: source metadata support in this installed package is a local prototype patch. A durable solution should be shipped through an upstream PR, a fork, or a maintained package version so reinstalling the extension does not lose the behavior. + +For Git-backed runs, the extension attaches safe source metadata to traces: + +```json +{ + "source_type": "git-repo", + "repo_identity": "owner/repo", + "repo_owner": "owner", + "repo_name": "repo", + "repo_root_name": "repo", + "git_branch": "main", + "git_commit": "abc123", + "git_remote_host": "github.com", + "git_remote_path": "owner/repo", + "metadata_source": "git-detection" +} +``` + +`repo_identity` is `owner/repo`. `repo_name` is the repo name only and must not contain a slash. + +A Git repo may optionally provide `.pi-langfuse.metadata.json`. Overrides are whitelist-only; unknown keys are ignored. Allowed keys are: + +```text +repo_identity +repo_owner +repo_name +source_type +service_name +project_slug +environment +observability_owner +``` + +Repo-local overrides are used only after the working directory is confirmed to be inside a usable Git repo. If Git detection fails for any reason, including a missing Git command, corrupted repo, or non-Git folder, the extension ignores repo-local identity files and emits only: + +```json +{ + "source_type": "non-git", + "metadata_source": "non-git" +} +``` + +The extension must not upload raw absolute local paths, credentialed remotes, tokens, unknown override keys, or folder names for non-Git folders. + +## Troubleshooting + +### No traces appearing? + +- Verify the API keys and run `/langfuse-setup` again if needed. +- Run `/langfuse-status` to confirm the loaded host, config source, privacy mode, and last runtime error. +- Confirm the Langfuse project is active and accepts writes. +- Confirm the keys have write permission. +- Look for `📊 Langfuse:` log messages in Pi output. + +### Extension not loading? + +```bash +pi list +pi install npm:pi-langfuse +``` + +### "Missing config" on startup? + +- Run `/langfuse-setup`. +- Or set `LANGFUSE_PUBLIC_KEY` and `LANGFUSE_SECRET_KEY` before starting Pi. + +### Model or cost not showing? + +- Some providers do not expose cost information. +- Inspect the raw observation data in Langfuse traces. +- The `model` field can come from provider events, finalized assistant messages, `model_select`, or `ctx.model`. + +### API key errors? + +- Public keys start with `pk-lf-`. +- Secret keys start with `sk-lf-`. +- For self-hosted deployments, verify the host URL. + +## Development Docs + +Development setup, source installation, runtime architecture, trace model, tracked fields, and validation steps are documented in [DEVELOPMENT.md](./DEVELOPMENT.md) and [DEVELOPMENT_CN.md](./DEVELOPMENT_CN.md). + +## License + +MIT diff --git a/extensions/pi-langfuse/README_CN.md b/extensions/pi-langfuse/README_CN.md new file mode 100644 index 0000000..0a9f1e6 --- /dev/null +++ b/extensions/pi-langfuse/README_CN.md @@ -0,0 +1,210 @@ +# pi-langfuse + +[![npm version](https://img.shields.io/npm/v/pi-langfuse)](https://www.npmjs.com/package/pi-langfuse) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) + +[**English**](./README.md) | [**简体中文**](./README_CN.md) + +[Pi Coding Agent](https://github.com/earendil-works/pi-coding-agent) 的 Langfuse 可观测性扩展。它会将完整的 Pi 运行发送到 [Langfuse](https://langfuse.com),在一个 trace 中展示提示词、代理工作流、LLM 生成、工具调用、最终回复、用量、成本和健康分数。 + +## 这个插件提供什么 + +- 每个用户提示词对应一个 Langfuse trace,并按 Pi 会话分组。 +- 为根代理创建 `agent` 观察节点,为每次模型请求创建 `generation`,为每次工具调用创建 `tool`。 +- 记录最终助手输出、工具错误状态和追踪级别分数。 +- 提供输入、输出、工具 I/O、system prompt 和 cwd 的隐私采集开关。 +- 上传前脱敏常见密钥,并对本地绝对路径做 hash。 +- 针对自托管 Langfuse 提供 REST 兜底,覆盖 OTel span 已到达但 trace 未可见的场景。 + +## 前提条件 + +- **Node.js** >= 22 +- **Pi Coding Agent** 已安装并完成基础配置 +- **Langfuse** 账户,支持 [云服务](https://cloud.langfuse.com) 和自托管 + +## 快速开始 + +1. 安装扩展: + + ```bash + pi install npm:pi-langfuse + ``` + +2. 首次运行 Pi 时,如果尚未配置凭据,Pi 会提示输入: + - Langfuse 公钥,以 `pk-lf-...` 开头 + - Langfuse 密钥,以 `sk-lf-...` 开头 + - Langfuse 主机地址,默认 `https://cloud.langfuse.com` + +3. 正常运行 Pi: + + ```bash + pi "解释 Redis 的架构" + ``` + +4. 打开 Langfuse,查看新生成的 trace。 + +## 配置 + +Langfuse API 密钥可在 **Langfuse Cloud** -> **Settings** -> **API Keys** 中获取。 + +### 方式 1:交互式设置 + +加载扩展后运行任意 `pi` 命令。首次运行且未配置时,Pi 会在 CLI 或 TUI 中提示输入,并将结果保存到 `~/.pi/agent/pi-langfuse/config.json`。 + +如需重新执行设置: + +```text +/langfuse-setup +``` + +如需查看当前配置状态且不泄漏密钥: + +```text +/langfuse-status +``` + +状态命令会显示配置来源、主机地址、脱敏后的公钥、采集策略、是否有活跃运行、配置文件路径,以及最近一次运行时错误。 + +### 方式 2:环境变量 + +在启动 Pi 前设置: + +```bash +export LANGFUSE_PUBLIC_KEY="pk-lf-xxxx" +export LANGFUSE_SECRET_KEY="sk-lf-xxxx" +export LANGFUSE_BASE_URL="https://cloud.langfuse.com" # 可选;也支持 LANGFUSE_HOST +``` + +保存的配置优先级更高。只有当 `~/.pi/agent/pi-langfuse/config.json` 缺失或不完整时,扩展才会使用环境变量。 + +隐私采集策略也可以通过环境变量设置: + +```bash +export LANGFUSE_PRIVACY_PRESET="full-debug" +``` + +可用预设: + +| 预设 | 采集内容 | +|------|----------| +| `metadata-only` | 仅采集元数据;不采集输入、输出、工具 I/O、system prompt 和 cwd | +| `prompts-only` | 采集提示词或提供商输入,以及元数据 | +| `conversations` | 采集输入和助手输出,但不采集工具 I/O、system prompt 和 cwd | +| `full-debug` | 完整追踪细节;默认值 | + +细粒度开关会覆盖预设: + +```bash +export LANGFUSE_CAPTURE_INPUTS=true +export LANGFUSE_CAPTURE_OUTPUTS=true +export LANGFUSE_CAPTURE_TOOL_IO=false +export LANGFUSE_CAPTURE_SYSTEM_PROMPT=false +export LANGFUSE_CAPTURE_CWD=false +``` + +所有被采集的负载在上传前仍会脱敏。扩展会隐藏常见 API key、Bearer token、密码、Cookie、私钥、Langfuse key、GitHub/npm/AWS 风格 token,并对本地绝对路径做 hash。 + +### 方式 3:持久化 `config.json` + +创建或更新 `~/.pi/agent/pi-langfuse/config.json`: + +```json +{ + "publicKey": "pk-lf-xxxx", + "secretKey": "sk-lf-xxxx", + "host": "https://cloud.langfuse.com", + "privacyPreset": "conversations" +} +``` + +也可以持久化细粒度采集开关: + +```json +{ + "publicKey": "pk-lf-xxxx", + "secretKey": "sk-lf-xxxx", + "host": "https://cloud.langfuse.com", + "capture": { + "LANGFUSE_PRIVACY_PRESET": "metadata-only", + "LANGFUSE_CAPTURE_INPUTS": "true" + } +} +``` + +> **安全提醒**:`~/.pi/agent/pi-langfuse/config.json` 包含敏感信息,不应提交到版本控制。 +> 扩展自行写入该文件时,会在支持 POSIX 权限的文件系统上使用 `0700` 创建配置目录,并使用 `0600` 写入配置文件。 + +## 验证扩展是否已加载 + +执行: + +```bash +pi list +``` + +已安装包列表中应出现 `pi-langfuse`。 + +如需在 Pi 内验证 Langfuse 主机地址和 API key: + +```text +/langfuse-test +``` + +该命令会先发起一次带超时的认证请求;认证通过后,再发送一条小的测试 trace。 + +## 在 Langfuse 中会看到什么 + +- 每个 Pi 会话对应一个独立的 Langfuse session ID。 +- 该会话中的每个用户提示词都会生成一个独立 trace。 +- trace 中会包含 Pi 实际显示的最终助手回复。 +- 工具执行会以工具观察节点展示参数、结果和错误状态。 +- 模型请求会以生成观察节点展示;如果提供商暴露相关信息,还会包含用量和成本。 +- trace 级别会记录工具调用次数、工具成功率和是否出现错误。 + +此包还包含一个内置 Langfuse 技能,可直接在 Pi 中查询 Langfuse 数据: + +```text +/pi-langfuse-langfuse <查询内容> +``` + +## 故障排除 + +### 没有看到 trace + +- 先检查 API 密钥是否正确,必要时重新执行 `/langfuse-setup`。 +- 执行 `/langfuse-status`,确认当前加载的主机、配置来源、隐私模式和最近一次运行时错误。 +- 确认 Langfuse 项目处于可写状态。 +- 确认密钥具备写权限。 +- 在 Pi 输出中查找 `📊 Langfuse:` 日志。 + +### 扩展未加载 + +```bash +pi list +pi install npm:pi-langfuse +``` + +### 启动时显示 `Missing config` + +- 执行 `/langfuse-setup`。 +- 或在启动 Pi 前设置 `LANGFUSE_PUBLIC_KEY` 和 `LANGFUSE_SECRET_KEY`。 + +### 模型或成本未显示 + +- 并非所有提供商都会返回成本信息。 +- 可在 Langfuse trace 中查看原始观察数据。 +- `model` 字段可能来自提供商事件、已定型的助手消息、`model_select` 或 `ctx.model`。 + +### API 密钥错误 + +- 公钥以 `pk-lf-` 开头。 +- 密钥以 `sk-lf-` 开头。 +- 使用自托管时,还需要确认主机地址是否正确。 + +## 开发文档 + +源码安装、开发流程、运行时架构、追踪模型、字段明细和验证步骤已迁移到 [DEVELOPMENT.md](./DEVELOPMENT.md) 与 [DEVELOPMENT_CN.md](./DEVELOPMENT_CN.md)。 + +## 许可证 + +MIT diff --git a/extensions/pi-langfuse/image.png b/extensions/pi-langfuse/image.png new file mode 100644 index 0000000000000000000000000000000000000000..2c502b943c0a8fbcc734e3da0ee400933f153cd1 GIT binary patch literal 135345 zcmb6Aby!=^6Yve=R@@3L6f00%g1b{7xVr~;FK)$YaT45J3KVH^mjo^D?iAMoPrkqV zeedh}_qnd~N6zfbp4pw*os)bvF={IEI9Ttn5D*Y>6h6ynARu6Y5D?HvF_7Vs2qRAp z1Oy~3HK3O4>+9>|zt{W6e~*uk7dH=^`{!E+=UAL}SJ$`V;_|1bX9oudxNljkJmX$o zUT<%2$N#J%tJOX~KX(pJ_4M?fUEW3htxg_zei2N+#@f z|M(J@^Gn~zy6*QRt+elX^}q4S>H5}wub3(^gP7#3s-xo*B~?RJxjoA-`FS;i_xJZv z$;ET?i?%+AegR+O6H>B^8{#S!De3ta){lmV$AaT>ca9%2%X(H;){^o&s;cWM8@e3a zg3Fu6*m&d$3QM(hjC-4(ot-@t6;*t^zd-+N1$?h}2})`E-Fd5kkd>Y95K`RSG2GD9 zVhysN+`4NV-cL=-GW5v$-7{8H-L|-QcXs)BdHc^R0!(Vf zD7v^^xr}~)Xp$jGg!rS(BanlYHZX$#b60qMeHUffxEC>k!p9X-4P%iQR^^-I2Q1dR~ zG9PGm!crb?Y{YkGWS{3He#Zpwu9h4u2y#)negJbkJhafkCG0wT2Tlb+BE--CPKJR9 zAxdcvj&Y&@`Tw7FHt#&?dT|uH!
CW*a=$-*aK-h;u*PJH-HgIJd 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

nd{}WqDgRM~Cp4#(& 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 );>n^FY-Bz74 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 literal 0 HcmV?d00001 diff --git a/extensions/pi-langfuse/index.ts b/extensions/pi-langfuse/index.ts new file mode 100644 index 0000000..6979c06 --- /dev/null +++ b/extensions/pi-langfuse/index.ts @@ -0,0 +1,215 @@ +/** + * Langfuse Observability Extension for Pi Coding Agent + * + * Sends one complete Langfuse trace per Pi agent run: + * - root agent observation for the user prompt and final assistant response + * - one generation observation per provider request + * - one tool observation per tool call, keyed by toolCallId + */ + +import { basename } from "node:path"; +import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; + +import { state, resetRunState, runWithSession, setCurrentSession } from "./src/state.js"; +import { ensureConfig, promptForConfig, loadConfig } from "./src/config.js"; +import { shutdownRuntime } from "./src/langfuse.js"; +import { handleLangfusePrivacyCommand, handleLangfuseStatusCommand, handleLangfuseTestCommand } from "./src/commands.js"; +import { getMessageFromEvent, extractAssistantOutput, getCapturePolicy } from "./src/utils.js"; +import { applyCapturePolicy } from "./src/capture-policy.js"; +import { startAgentRun, finishAgentRun } from "./src/handlers/agent.js"; +import { startTurnObservation, finishTurnObservation } from "./src/handlers/turn.js"; +import { + startGeneration, + updateGenerationMetadata, + finishGenerationFromMessage, + createFallbackGenerationFromTurn, + recordTTFT, +} from "./src/handlers/generation.js"; +import { + startToolObservation, + finishToolObservation, + closeDanglingObservations, +} from "./src/handlers/tool.js"; + +// ============================================ +// Extension +// ============================================ + +export default async function (pi: ExtensionAPI) { + if (!state.config) { + state.config = loadConfig(); + } + + if (state.config) { + console.log("📊 Langfuse: Tracing enabled →", state.config.host); + } else { + console.log("📊 Langfuse: Waiting for first-run setup"); + } + + pi.registerCommand("langfuse-setup", { + description: "Configure Langfuse API keys for this extension", + handler: async (_args, ctx) => { + await promptForConfig(ctx); + }, + }); + + pi.registerCommand("langfuse-test", { + description: "Send a test trace to Langfuse to verify configuration", + handler: async (args, ctx) => { + await handleLangfuseTestCommand(String(args ?? ""), ctx); + }, + }); + + pi.registerCommand("langfuse-status", { + description: "Show Langfuse configuration and runtime status", + handler: async (args, ctx) => { + await handleLangfuseStatusCommand(String(args ?? ""), ctx); + }, + }); + + pi.registerCommand("langfuse-privacy", { + description: "View or set Langfuse telemetry privacy preset", + handler: async (args, ctx) => { + await handleLangfusePrivacyCommand(String(args ?? ""), ctx); + }, + }); + + const getSessionId = (ctx?: any) => { + try { + const sessionFile = ctx?.sessionManager?.getSessionFile?.(); + return sessionFile ? basename(sessionFile, ".jsonl") : undefined; + } catch { + return undefined; + } + }; + + const withSession = (ctx: any, fn: () => T): T => runWithSession(getSessionId(ctx) ?? state.currentSessionId, fn); + + pi.on("session_start", async (_event, ctx) => withSession(ctx, async () => { + state.setupAttemptedThisSession = false; + await ensureConfig(ctx); + resetRunState(); + })); + + pi.on("model_select", async (event, ctx) => withSession(ctx, async () => { + state.currentModel = event.model?.id || ""; + state.currentProvider = event.model?.provider || ""; + })); + + pi.on("before_agent_start", async (event, ctx) => withSession(ctx, async () => { + await startAgentRun(event, ctx); + })); + + pi.on("agent_start", async (event, ctx) => withSession(ctx, async () => { + if (!state.agentState?.root) { + await startAgentRun(event, ctx); + } + })); + + pi.on("turn_start", async (event, ctx) => withSession(ctx, async () => { + await startTurnObservation(event); + })); + + pi.on("before_provider_request", async (event, ctx) => withSession(ctx, async () => { + await startGeneration(event); + })); + + pi.on("after_provider_response", async (event, ctx) => withSession(ctx, async () => { + updateGenerationMetadata(event); + })); + + pi.on("message_update", async (event, ctx) => withSession(ctx, async () => { + recordTTFT(event); + const message = getMessageFromEvent(event); + if (message?.role === "assistant" && state.agentState) { + state.agentState.latestAssistantOutput = extractAssistantOutput(message); + } + })); + + pi.on("message_end", async (event, ctx) => withSession(ctx, async () => { + await finishGenerationFromMessage(event); + })); + + pi.on("tool_execution_start", async (event, ctx) => withSession(ctx, async () => { + await startToolObservation(event); + })); + + pi.on("tool_call", async (event, ctx) => withSession(ctx, async () => { + await startToolObservation(event); + })); + + pi.on("tool_result", async (event, ctx) => withSession(ctx, async () => { + await finishToolObservation(event); + })); + + pi.on("tool_execution_end", async (event, ctx) => withSession(ctx, async () => { + await finishToolObservation(event); + })); + + pi.on("turn_end", async (event, ctx) => withSession(ctx, async () => { + state.turnCount++; + const message = getMessageFromEvent(event); + if (message?.role === "assistant") { + await createFallbackGenerationFromTurn(event, message); + await finishGenerationFromMessage(event); + } + finishTurnObservation(event); + })); + + pi.on("agent_end", async (event, ctx) => withSession(ctx, async () => { + await finishAgentRun(event); + const sessionId = state.currentSessionId; + setTimeout(() => { + shutdownRuntime(sessionId).catch((error) => { + console.warn("📊 Langfuse: Deferred shutdown failed", error); + }); + }, 0); + })); + + const handleSessionInterruption = (reason: string) => { + if (state.agentState?.root) { + closeDanglingObservations(reason); + state.agentState.root.update({ metadata: { completed: false, cancelled: true } }).end(); + } + resetRunState(); + }; + + pi.on("session_before_switch", async (_event, ctx) => { + const sessionId = getSessionId(ctx); + if (sessionId) { + setCurrentSession(sessionId); + } + }); + + pi.on("session_before_fork", async (_event, ctx) => { + const sessionId = getSessionId(ctx); + if (sessionId) { + setCurrentSession(sessionId); + } + }); + + pi.on("session_compact", async (event, ctx) => withSession(ctx, async () => { + if (state.agentState?.root) { + const parent = state.agentState.activeTurn ?? state.agentState.root; + try { + const observation = parent.startObservation ? parent.startObservation( + "session_compact", + { + level: "DEFAULT", + statusMessage: "Context was compacted", + metadata: applyCapturePolicy({ metadata: { ...event } }, getCapturePolicy()).metadata + }, + { asType: "span" } + ) : undefined; + observation?.end(); + } catch (e) { + // ignore + } + } + })); + + pi.on("session_shutdown", async (_event, ctx) => withSession(ctx, async () => { + handleSessionInterruption("Session shutdown before agent completed"); + await shutdownRuntime(); + })); +} diff --git a/extensions/pi-langfuse/package-lock.json b/extensions/pi-langfuse/package-lock.json new file mode 100644 index 0000000..37cdda2 --- /dev/null +++ b/extensions/pi-langfuse/package-lock.json @@ -0,0 +1,5074 @@ +{ + "name": "pi-langfuse", + "version": "1.5.8", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "pi-langfuse", + "version": "1.5.8", + "license": "MIT", + "dependencies": { + "@langfuse/client": "^5.3.0", + "@langfuse/otel": "^5.3.0", + "@langfuse/tracing": "^5.3.0", + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/context-async-hooks": "^2.7.1", + "@opentelemetry/sdk-node": "^0.218.0", + "@opentelemetry/sdk-trace-base": "^2.0.1" + }, + "devDependencies": { + "tsx": "^4.19.0", + "typescript": "^6.0.3" + }, + "engines": { + "node": ">=22" + }, + "peerDependencies": { + "@earendil-works/pi-coding-agent": "*" + } + }, + "node_modules/@earendil-works/pi-coding-agent": { + "version": "0.83.0", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-coding-agent/-/pi-coding-agent-0.83.0.tgz", + "integrity": "sha512-uYhF+FsZxogoSX/AxBcUdiY+ZklubwaXyAoEGA2eQwsHcyEAhUYIKh/WLXe/a8+k8eTCmxb+ZN2Zo9mzQtzbWw==", + "hasShrinkwrap": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@earendil-works/pi-agent-core": "^0.83.0", + "@earendil-works/pi-ai": "^0.83.0", + "@earendil-works/pi-tui": "^0.83.0", + "@silvia-odwyer/photon-node": "0.3.4", + "chalk": "5.6.2", + "cross-spawn": "7.0.6", + "diff": "8.0.4", + "glob": "13.0.6", + "highlight.js": "10.7.3", + "hosted-git-info": "9.0.3", + "ignore": "7.0.5", + "jiti": "2.7.0", + "minimatch": "10.2.5", + "proper-lockfile": "4.1.2", + "semver": "7.8.0", + "typebox": "1.3.7", + "undici": "8.5.0", + "yaml": "2.9.0" + }, + "bin": { + "pi": "dist/cli.js" + }, + "engines": { + "node": ">=22.19.0" + }, + "optionalDependencies": { + "@mariozechner/clipboard": "0.3.9" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@anthropic-ai/sdk": { + "version": "0.91.1", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.91.1.tgz", + "integrity": "sha512-LAmu761tSN9r66ixvmciswUj/ZC+1Q4iAfpedTfSVLeswRwnY3n2Nb6Tsk+cLPP28aLOPWeMgIuTuCcMC6W/iw==", + "license": "MIT", + "peer": true, + "dependencies": { + "json-schema-to-ts": "^3.1.1" + }, + "bin": { + "anthropic-ai-sdk": "bin/cli" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-crypto/crc32": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/client-bedrock-runtime": { + "version": "3.1048.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-bedrock-runtime/-/client-bedrock-runtime-3.1048.0.tgz", + "integrity": "sha512-u+NT61JZEkRFtpL0CAw1N1dwxnaLgwVXQl/zjJxTGgLyS/jTIdg2SdoEoCTHxgDyCnqa1HEi9QOoE9/pYRNpOQ==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.974.11", + "@aws-sdk/credential-provider-node": "^3.972.42", + "@aws-sdk/eventstream-handler-node": "^3.972.16", + "@aws-sdk/middleware-eventstream": "^3.972.12", + "@aws-sdk/middleware-websocket": "^3.972.19", + "@aws-sdk/token-providers": "3.1048.0", + "@aws-sdk/types": "^3.973.8", + "@smithy/core": "^3.24.2", + "@smithy/fetch-http-handler": "^5.4.2", + "@smithy/node-http-handler": "^4.7.2", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/core": { + "version": "3.974.11", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.974.11.tgz", + "integrity": "sha512-QpnINq5FZH6EOaDEkmHdT7eUunbvD27pDNQypaWjFyYz7Zl1q3UCMQErBZxpmfGfI7MvI2TlK8KTkgNpv8b1ug==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@aws-sdk/types": "^3.973.8", + "@aws-sdk/xml-builder": "^3.972.24", + "@aws/lambda-invoke-store": "^0.2.2", + "@smithy/core": "^3.24.2", + "@smithy/signature-v4": "^5.4.2", + "@smithy/types": "^4.14.1", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-env": { + "version": "3.972.37", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.37.tgz", + "integrity": "sha512-/jpPvEh6f7ntmIzf7dNxoNX6Q8vt8UpesCjbW6mFfk4V1NW6bIy9qxcQ6WbA8As5yQhsZOe+xeNd4xHX8kdY2Q==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@aws-sdk/core": "^3.974.11", + "@aws-sdk/types": "^3.973.8", + "@smithy/core": "^3.24.2", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.972.39", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.39.tgz", + "integrity": "sha512-pIgTpisWyWg7X1bUbzSjuUYosYTD0Ghz2M0hkSTmb3a6i3qV3uU+NYJPI/E2XSC0HcsZh5rsLPzeXrkb2DS0Cg==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@aws-sdk/core": "^3.974.11", + "@aws-sdk/types": "^3.973.8", + "@smithy/core": "^3.24.2", + "@smithy/fetch-http-handler": "^5.4.2", + "@smithy/node-http-handler": "^4.7.2", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.972.41", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.41.tgz", + "integrity": "sha512-u2tyjaxJJzW8UtW4SM1ZcPMDwO6y+kV+llvou+Adts0FAKyzes5jG4izQN+KX3yE8ZROpS5y1LJ//xL2iSf76w==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@aws-sdk/core": "^3.974.11", + "@aws-sdk/credential-provider-env": "^3.972.37", + "@aws-sdk/credential-provider-http": "^3.972.39", + "@aws-sdk/credential-provider-login": "^3.972.41", + "@aws-sdk/credential-provider-process": "^3.972.37", + "@aws-sdk/credential-provider-sso": "^3.972.41", + "@aws-sdk/credential-provider-web-identity": "^3.972.41", + "@aws-sdk/nested-clients": "^3.997.9", + "@aws-sdk/types": "^3.973.8", + "@smithy/core": "^3.24.2", + "@smithy/credential-provider-imds": "^4.3.2", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-login": { + "version": "3.972.41", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.41.tgz", + "integrity": "sha512-0LBitxXiAiaE5nlFPfpNIww/8FRY/I7WIndWsc9GmNFOM7cE1wNpVNQEGEk9Outg5l8xl+3vybxFyUy4l9q/LQ==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@aws-sdk/core": "^3.974.11", + "@aws-sdk/nested-clients": "^3.997.9", + "@aws-sdk/types": "^3.973.8", + "@smithy/core": "^3.24.2", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.972.42", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.42.tgz", + "integrity": "sha512-D4oon2zbqqsWOJUM99Gm3/ZyJ0IJvTXVN3PyloGb3kQEyI36fjCZheZj422lAgTWWd6TSHgiImLt3RIaLdv3dQ==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@aws-sdk/credential-provider-env": "^3.972.37", + "@aws-sdk/credential-provider-http": "^3.972.39", + "@aws-sdk/credential-provider-ini": "^3.972.41", + "@aws-sdk/credential-provider-process": "^3.972.37", + "@aws-sdk/credential-provider-sso": "^3.972.41", + "@aws-sdk/credential-provider-web-identity": "^3.972.41", + "@aws-sdk/types": "^3.973.8", + "@smithy/core": "^3.24.2", + "@smithy/credential-provider-imds": "^4.3.2", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-process": { + "version": "3.972.37", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.37.tgz", + "integrity": "sha512-7nVaHBUaWIddASYfVaA9O4D5ZVjewU3sCol9WqZPGfW0nR+0WqE0xHZnD/U2L33PlOB8KNXGKZ6wOES/QijKzg==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@aws-sdk/core": "^3.974.11", + "@aws-sdk/types": "^3.973.8", + "@smithy/core": "^3.24.2", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.972.41", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.41.tgz", + "integrity": "sha512-IOWAWEHe5LkjSKkkUUX9ciV6Y1scHTsnfEkdt5yyC4Slrc7AGbkLPrpntjqh18ksJAMOaVhoBsO8p2WyTcY2wQ==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@aws-sdk/core": "^3.974.11", + "@aws-sdk/nested-clients": "^3.997.9", + "@aws-sdk/token-providers": "3.1048.0", + "@aws-sdk/types": "^3.973.8", + "@smithy/core": "^3.24.2", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.972.41", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.41.tgz", + "integrity": "sha512-mbACk9Yypa8nm4iGZLs0PofOXEcTDOUw6wDnsPXNDNSd2WNXs1tSo+6nc/fh0jLYdfVZThhBL98PHW4aXFsG5A==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@aws-sdk/core": "^3.974.11", + "@aws-sdk/nested-clients": "^3.997.9", + "@aws-sdk/types": "^3.973.8", + "@smithy/core": "^3.24.2", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/eventstream-handler-node": { + "version": "3.972.16", + "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-handler-node/-/eventstream-handler-node-3.972.16.tgz", + "integrity": "sha512-yedpPgKftqjU5SlPFHfqWpOw6xSCRieWRG1euWOlXn4WJxt2VX92VprCa2PpSOXjVCAeK6dTjW9eJRXVig9yGA==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@aws-sdk/types": "^3.973.8", + "@smithy/core": "^3.24.2", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/middleware-eventstream": { + "version": "3.972.12", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-eventstream/-/middleware-eventstream-3.972.12.tgz", + "integrity": "sha512-tHTHHCHNrq6XklQvlzHBDJG4Iuhh7NVPRdtmvP+nHFA+5sxPlIDzlAHHgfoYHGvT3NXP1yVP/L5c3opUn6T3Qg==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@aws-sdk/types": "^3.973.8", + "@smithy/core": "^3.24.2", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/middleware-websocket": { + "version": "3.972.19", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-websocket/-/middleware-websocket-3.972.19.tgz", + "integrity": "sha512-mkEhOGYozqKQkbFaVrjwr0faiwwZza1v5/jSY6Tucm3bD+uKTazIUH/4Yo6aMnQD2ua2W9cMP6s8mvwTcjtqHw==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@aws-sdk/core": "^3.974.11", + "@aws-sdk/types": "^3.973.8", + "@smithy/core": "^3.24.2", + "@smithy/fetch-http-handler": "^5.4.2", + "@smithy/signature-v4": "^5.4.2", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/nested-clients": { + "version": "3.997.9", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.9.tgz", + "integrity": "sha512-jPR3rnmRI4hWYyzfmTGBr7NblMp8QYYeflHXba1H6+7CGrWVqWKQzaXFQ4qbExqPRsXN3T3L3JxFhr6aouXUGQ==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.974.11", + "@aws-sdk/signature-v4-multi-region": "^3.996.27", + "@aws-sdk/types": "^3.973.8", + "@smithy/core": "^3.24.2", + "@smithy/fetch-http-handler": "^5.4.2", + "@smithy/node-http-handler": "^4.7.2", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.996.27", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.27.tgz", + "integrity": "sha512-0Phbz4t6HI3D3skxvG2uI+VWU034/nSIw1T8d+FPzzQG9EQTrw94o9mOKO2Gv3n3Oc8P7JD7RAUxkoneLWv5Eg==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@aws-sdk/types": "^3.973.8", + "@smithy/core": "^3.24.2", + "@smithy/signature-v4": "^5.4.2", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/token-providers": { + "version": "3.1048.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1048.0.tgz", + "integrity": "sha512-k0y/GcuesuSfWyUM0WamrGyeZmltRYaPbHO82UDA6mZ/doB+FOHKutikPAtSXMn/hDz970cF+iRuuiYO9VEbAA==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@aws-sdk/core": "^3.974.11", + "@aws-sdk/nested-clients": "^3.997.9", + "@aws-sdk/types": "^3.973.8", + "@smithy/core": "^3.24.2", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/types": { + "version": "3.973.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.8.tgz", + "integrity": "sha512-gjlAdtHMbtR9X5iIhVUvbVcy55KnznpC6bkDUWW9z915bi0ckdUr5cjf16Kp6xq0bP5HBD2xzgbL9F9Quv5vUw==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/util-locate-window": { + "version": "3.965.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.5.tgz", + "integrity": "sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws-sdk/xml-builder": { + "version": "3.972.24", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.24.tgz", + "integrity": "sha512-V8z5YcDPfsvzrBlj0xR1vhRtocblhYbqdreCJB/voGd4Sr5zjNAeWxexbnqVtskTJe0vFb5KMqbSL++ePl+zRw==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@nodable/entities": "2.1.0", + "@smithy/types": "^4.14.1", + "fast-xml-parser": "5.7.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@aws/lambda-invoke-store": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.4.tgz", + "integrity": "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==", + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-agent-core": { + "version": "0.83.0", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-agent-core/-/pi-agent-core-0.83.0.tgz", + "license": "MIT", + "peer": true, + "dependencies": { + "@earendil-works/pi-ai": "^0.83.0", + "diff": "8.0.4", + "ignore": "7.0.5", + "typebox": "1.3.7", + "yaml": "2.9.0" + }, + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-ai": { + "version": "0.83.0", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-ai/-/pi-ai-0.83.0.tgz", + "license": "MIT", + "peer": true, + "dependencies": { + "@anthropic-ai/sdk": "0.91.1", + "@aws-sdk/client-bedrock-runtime": "3.1048.0", + "@google/genai": "1.52.0", + "@mistralai/mistralai": "2.2.6", + "@opentelemetry/api": "1.9.0", + "@smithy/node-http-handler": "4.7.3", + "http-proxy-agent": "7.0.2", + "https-proxy-agent": "7.0.6", + "openai": "6.26.0", + "partial-json": "0.1.7", + "typebox": "1.3.7" + }, + "bin": { + "pi-ai": "dist/cli.js" + }, + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-tui": { + "version": "0.83.0", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.83.0.tgz", + "license": "MIT", + "peer": true, + "dependencies": { + "get-east-asian-width": "1.6.0", + "marked": "18.0.5" + }, + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@google/genai": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.52.0.tgz", + "integrity": "sha512-gwSvbpiN/17O9TbsqSsE/OzZcpv5Fo4RQjdngGgogtuB9RsyJ8ZHhX5KjHj1bp5N9snN2eK8LDGXSaWW2hof8Q==", + "hasInstallScript": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "google-auth-library": "^10.3.0", + "p-retry": "^4.6.2", + "protobufjs": "^7.5.4", + "ws": "^8.18.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@modelcontextprotocol/sdk": "^1.25.2" + }, + "peerDependenciesMeta": { + "@modelcontextprotocol/sdk": { + "optional": true + } + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard/-/clipboard-0.3.9.tgz", + "integrity": "sha512-ABnA53mdfkGZwOFUdZNv2S0CWGO/EIuPj8Vv9xmBFmSYg/qFc7ihO6q5FcQjvoE67kZpWkEc4AhD6B/os04yuA==", + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@mariozechner/clipboard-darwin-arm64": "0.3.9", + "@mariozechner/clipboard-darwin-universal": "0.3.9", + "@mariozechner/clipboard-darwin-x64": "0.3.9", + "@mariozechner/clipboard-linux-arm64-gnu": "0.3.9", + "@mariozechner/clipboard-linux-arm64-musl": "0.3.9", + "@mariozechner/clipboard-linux-riscv64-gnu": "0.3.9", + "@mariozechner/clipboard-linux-x64-gnu": "0.3.9", + "@mariozechner/clipboard-linux-x64-musl": "0.3.9", + "@mariozechner/clipboard-win32-arm64-msvc": "0.3.9", + "@mariozechner/clipboard-win32-x64-msvc": "0.3.9" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-darwin-arm64": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-arm64/-/clipboard-darwin-arm64-0.3.9.tgz", + "integrity": "sha512-BfgV7vCEWZwJwZJw03r6bP5+tf0iI/ANuQYCxi9RNn7FrWB3yzGuMKCrNLRl6V761vXRdL8+OqZ0wd4TqlsNOQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-darwin-universal": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-universal/-/clipboard-darwin-universal-0.3.9.tgz", + "integrity": "sha512-BGGR4iA9Z2shAjI65eI5xtyb3LYNlDW9X3gxKxDbqtbnREohsrqznov6zpKoIrsRWpzlYVEdKphS7ksJ0/ndSQ==", + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-darwin-x64": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-darwin-x64/-/clipboard-darwin-x64-0.3.9.tgz", + "integrity": "sha512-4kURmCbS6nt8uYhtmWpUcJWyPHfmAr5dTpXD1nO3pIfa+TSQ9DbrGOYCKH+aEFW47XhQ4Vp8ZTszie+wfFvDKg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-linux-arm64-gnu": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-arm64-gnu/-/clipboard-linux-arm64-gnu-0.3.9.tgz", + "integrity": "sha512-g59OkUGP2DDfCOIKypHeYgv2M55u/cKvXa5dSxFbEJ34XvIQMdcVmpKCkGUro3ZgefXiGVdwguvTMQGpHWzIXw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-linux-arm64-musl": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-arm64-musl/-/clipboard-linux-arm64-musl-0.3.9.tgz", + "integrity": "sha512-AGuJdgKsmJdm4Pych7kv3sqe591ERRaAHW3xjLooiFzn8J+PxUyof++7YZrB5Y5tpnTO+K18Og3taj2NpluCRQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-linux-riscv64-gnu": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-riscv64-gnu/-/clipboard-linux-riscv64-gnu-0.3.9.tgz", + "integrity": "sha512-DXBEAiuMpk7dhS1a9NzNxVAFi1vaKoPu7rQNgY8LIDLGrK3lnIp3nT10DUum+PKVJoJppIP+NAA8IZe4DMNDPw==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-linux-x64-gnu": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-x64-gnu/-/clipboard-linux-x64-gnu-0.3.9.tgz", + "integrity": "sha512-WORrMLd6EpElEME7JRKfSaY34nW1P5LbdgK5YNCS1ncG2LqmITsSMEJ8nh2mpvxb3TxqbOOKgY7k9eMJYlW9Mw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-linux-x64-musl": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-linux-x64-musl/-/clipboard-linux-x64-musl-0.3.9.tgz", + "integrity": "sha512-/DHn+1DrfL6oRaPPWXaOKvonFFrni666fxd+zFqiQEfvBH0tsHVWjq9iqBk0oDp0qaPA72lIMy5BptxISBEhZQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-win32-arm64-msvc": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-win32-arm64-msvc/-/clipboard-win32-arm64-msvc-0.3.9.tgz", + "integrity": "sha512-O5FHD3ErkMwMhNzAfu3ggy0ug4z7btZuoQgwwxlzPrwV2bxlD6WDpqBY4NCgICAgZdDKdp+loUEKVAVt8aYnhQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@mariozechner/clipboard-win32-x64-msvc": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@mariozechner/clipboard-win32-x64-msvc/-/clipboard-win32-x64-msvc-0.3.9.tgz", + "integrity": "sha512-ihQC3EufqEY81vhXBgVBtK4prL+wc62zJsSvxrgz7K1hsdt6OObz6v9p3Rn1OG3GJksTTKMJF0u/guMISHPhSA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@mistralai/mistralai": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-2.2.6.tgz", + "integrity": "sha512-W8pX7zHxjJvMIpw8JMxeJEleapXX0Q9NPszdNzqkM3MIEoIGPObdodujj+WHteXEvGfaP/AMwlNyRfEzSY6dQQ==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.40.0", + "ws": "^8.18.0", + "zod": "^3.25.0 || ^4.0.0", + "zod-to-json-schema": "^3.25.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.9.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + } + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@nodable/entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/nodable" + } + ], + "license": "MIT", + "peer": true + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@opentelemetry/semantic-conventions": { + "version": "1.41.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.41.1.tgz", + "integrity": "sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==", + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/codegen": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", + "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/eventemitter": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", + "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/fetch": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", + "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.1" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@protobufjs/utf8": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz", + "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@silvia-odwyer/photon-node": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@silvia-odwyer/photon-node/-/photon-node-0.3.4.tgz", + "integrity": "sha512-bnly4BKB3KDTFxrUIcgCLbaeVVS8lrAkri1pEzskpmxu9MdfGQTy8b8EgcD83ywD3RPMsIulY8xJH5Awa+t9fA==", + "license": "Apache-2.0", + "peer": true + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/core": { + "version": "3.24.3", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.24.3.tgz", + "integrity": "sha512-Ep/7tPamGY8mgESE3LyLKtxJyy6U52WWAqr/3wial47Sj4u3PiIF73AOGI27UyLy9duTkhZbgzodOfLV4TduZg==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^4.14.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/credential-provider-imds": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.3.3.tgz", + "integrity": "sha512-I2Bti0DKFo2IJyN28ijCsx51BAumEYR4/1yZ1FXyBygy9MqbnMqCev4JPth/MbpRfBSRAX35hITSnAdJRo1u5w==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@smithy/core": "^3.24.3", + "@smithy/types": "^4.14.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/fetch-http-handler": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.4.3.tgz", + "integrity": "sha512-F+DRf8IJazRJgYog2A/yJK7eYVc0rqTlRzO+5ZxjJd4WkZoKz0IJRncf7G6t1pdVT3kryJcwuTFhN1c5m6N47A==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@smithy/core": "^3.24.3", + "@smithy/types": "^4.14.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/node-http-handler": { + "version": "4.7.3", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.7.3.tgz", + "integrity": "sha512-/jPhevcTFPMVl6KNjbaI47iOg1zxC7IsnX4PQDGVZKMFceOXtB8IEYaB7a9VvkP/3oC60WzTeKocvSI7vLT0vA==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@smithy/core": "^3.24.3", + "@smithy/types": "^4.14.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/signature-v4": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.4.3.tgz", + "integrity": "sha512-53+75QuPl6DL+ct6vVEB51FDO5oulXr20TPV46VvJZg76lIlXNWfxi8j+G2V/t0I2qxCBOa3vX/8bmjrpFVo9g==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@smithy/core": "^3.24.3", + "@smithy/types": "^4.14.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/types": { + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.14.2.tgz", + "integrity": "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/@types/node": { + "version": "22.19.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.19.tgz", + "integrity": "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==", + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "peer": true, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "peer": true + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": "*" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/bowser": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", + "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", + "license": "MIT", + "peer": true + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/brace-expansion": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", + "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", + "license": "MIT", + "peer": true, + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "peer": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "peer": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/diff": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", + "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==", + "license": "BSD-3-Clause", + "peer": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT", + "peer": true + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/fast-xml-builder": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.2.0.tgz", + "integrity": "sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "path-expression-matcher": "^1.5.0", + "xml-naming": "^0.1.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/fast-xml-parser": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.7.3.tgz", + "integrity": "sha512-C0AaNuC+mscy6vrAQKAc/rMq+zAPHodfHGZu4sGVehvAQt/JLG1O5zEcYcXSY5zSqr4YVgxsB+pHXTq0i7eDlg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "@nodable/entities": "^2.1.0", + "fast-xml-builder": "^1.1.7", + "path-expression-matcher": "^1.5.0", + "strnum": "^2.2.3" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "license": "MIT", + "peer": true, + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/gaxios": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.4.tgz", + "integrity": "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/gcp-metadata": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", + "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "gaxios": "^7.0.0", + "google-logging-utils": "^1.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "license": "BlueOak-1.0.0", + "peer": true, + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/google-auth-library": { + "version": "10.6.2", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.6.2.tgz", + "integrity": "sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^7.1.4", + "gcp-metadata": "8.1.2", + "google-logging-utils": "1.1.3", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/google-logging-utils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", + "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC", + "peer": true + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "license": "BSD-3-Clause", + "peer": true, + "engines": { + "node": "*" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/hosted-git-info": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.3.tgz", + "integrity": "sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg==", + "license": "ISC", + "peer": true, + "dependencies": { + "lru-cache": "^11.1.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "license": "MIT", + "peer": true, + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "peer": true, + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC", + "peer": true + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "license": "MIT", + "peer": true, + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/json-schema-to-ts": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz", + "integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.18.3", + "ts-algebra": "^2.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "peer": true, + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "peer": true, + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0", + "peer": true + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/lru-cache": { + "version": "11.4.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.4.0.tgz", + "integrity": "sha512-W+R+kFL4HgVxONq2bhXPi3bGpzGe/yEhVOp233qw9wCRtgncJ15P3bC+e4zZMu4Cq7d+WAJjXGW0uUkifhcatA==", + "license": "BlueOak-1.0.0", + "peer": true, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/marked": { + "version": "18.0.5", + "resolved": "https://registry.npmjs.org/marked/-/marked-18.0.5.tgz", + "integrity": "sha512-S6GcvALHg6K4ohtu4E7x0a1AqhAjp6cV8KhLSyN9qVapnzJkusVBxZRcIU9AeYsbe6P1hKDusSbEOzGyyuce6w==", + "license": "MIT", + "peer": true, + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "license": "BlueOak-1.0.0", + "peer": true, + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", + "peer": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT", + "peer": true + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", + "peer": true, + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/openai": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-6.26.0.tgz", + "integrity": "sha512-zd23dbWTjiJ6sSAX6s0HrCZi41JwTA1bQVs0wLQPZ2/5o2gxOJA5wh7yOAUgwYybfhDXyhwlpeQf7Mlgx8EOCA==", + "license": "Apache-2.0", + "peer": true, + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "ws": "^8.18.0", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/p-retry/node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "license": "MIT", + "peer": true + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/partial-json": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/partial-json/-/partial-json-0.1.7.tgz", + "integrity": "sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==", + "license": "MIT", + "peer": true + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/path-expression-matcher": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.5.0.tgz", + "integrity": "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "license": "BlueOak-1.0.0", + "peer": true, + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "license": "MIT", + "peer": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/proper-lockfile/node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/protobufjs": { + "version": "7.6.5", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.5.tgz", + "integrity": "sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.5", + "@protobufjs/eventemitter": "^1.1.1", + "@protobufjs/fetch": "^1.1.1", + "@protobufjs/float": "^1.0.2", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.1", + "@types/node": ">=13.7.0", + "long": "^5.3.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "peer": true + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/semver": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "peer": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC", + "peer": true + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/strnum": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.3.0.tgz", + "integrity": "sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "peer": true + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/ts-algebra": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz", + "integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==", + "license": "MIT", + "peer": true + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "peer": true + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/typebox": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/typebox/-/typebox-1.3.7.tgz", + "integrity": "sha512-meKuifc33Pccx0O6PdIzYMq3Og8zvP4TIi/a+Bw3AEMZMxOD0+RHGQvpglEe6Zdy3wZ8nqn/j95h8LUZLk/6Hg==", + "license": "MIT", + "peer": true + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/undici": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-8.5.0.tgz", + "integrity": "sha512-xamtWoB1EshgjpmlXd7GGm2VfdDtw1+rD8uhry8pSNW3If6S8E0m2T2+orSKeZXEn/aPJMviCpDBA65WJt8zhg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT", + "peer": true + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "peer": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/ws": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/xml-naming": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.1.0.tgz", + "integrity": "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "peer": true, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "license": "ISC", + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@earendil-works/pi-coding-agent/node_modules/zod-to-json-schema": { + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", + "license": "ISC", + "peer": true, + "peerDependencies": { + "zod": "^3.25.28 || ^4" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@grpc/grpc-js": { + "version": "1.14.4", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.4.tgz", + "integrity": "sha512-k9Dj3DV/itK9D06Y8f190Qgop7/Ui+D0njFV3LHMPwPT75DpXLQohE9Wmz0QElrJnzsjB7KPWiKJbOl7IPDArQ==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/proto-loader": "^0.8.0", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.1.tgz", + "integrity": "sha512-wtF6h+DY6M3YaDBPAmvuuA6jV8Sif9MjtOI5euKFWRgCDl5PeDpPsHR9u2l6St5ceY8AZgoNDww5+HvEsXFsGg==", + "license": "Apache-2.0", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.5.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/@langfuse/client": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/@langfuse/client/-/client-5.10.0.tgz", + "integrity": "sha512-WI//PooxMyFEFtDy77XNv6WxOpd52U6HNgI8cqhPX+S2aJ8Mz2UTWz7olwzLlrwToJpsy5aZNtpdhsol2Umubw==", + "license": "MIT", + "dependencies": { + "@langfuse/core": "^5.10.0", + "@langfuse/tracing": "^5.10.0", + "mustache": "^4.2.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.9.0" + } + }, + "node_modules/@langfuse/core": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/@langfuse/core/-/core-5.10.0.tgz", + "integrity": "sha512-XXz1DBEwGMpNXz8DZHFsixqo+3vBkfYpat3oLLldRMuT8ur6OWQ81Kgh5Obh0CZ31adaR4FrLTHh4N9eBXo64A==", + "license": "MIT", + "peerDependencies": { + "@opentelemetry/api": "^1.9.0" + } + }, + "node_modules/@langfuse/otel": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/@langfuse/otel/-/otel-5.10.0.tgz", + "integrity": "sha512-q1expgCm3QwjWII0PeCJJSyEV5o96FQ4VNzL5hgziWGmCPCd9TrrWxWxHdSY7cpZWddEJCp71zdh1nq0xISOpQ==", + "license": "MIT", + "dependencies": { + "@langfuse/core": "^5.10.0" + }, + "engines": { + "node": ">=20" + }, + "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" + } + }, + "node_modules/@langfuse/tracing": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/@langfuse/tracing/-/tracing-5.10.0.tgz", + "integrity": "sha512-//S9dcZszmEk2yx+h7eg10mEMswyU7MYLkGIJlO6gUfrXA+3Y5pvNiZNZU3cfDus94OAr16dHQpg3nNFiziZjg==", + "license": "MIT", + "dependencies": { + "@langfuse/core": "^5.10.0" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.9.0" + } + }, + "node_modules/@opentelemetry/api": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.1.tgz", + "integrity": "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/api-logs": { + "version": "0.221.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.221.0.tgz", + "integrity": "sha512-OlanaW1vv7ufTqQ3/fPLI4arGt5ZoM+P8abOMki6uEYnpRazepSWDwDnnw+la7kE26SHVC18//SMccrDvLKOXQ==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/configuration": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/configuration/-/configuration-0.218.0.tgz", + "integrity": "sha512-W8wIz7H2R1pufR5jfjb3gU2XkMpm2x/7b1RJcsuzvd70Il/rWWE+g5/Od7hQKrxRTSrTrOWlru101PWXz5I1EQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "yaml": "^2.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.9.0" + } + }, + "node_modules/@opentelemetry/configuration/node_modules/@opentelemetry/core": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.7.1.tgz", + "integrity": "sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/context-async-hooks": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.10.0.tgz", + "integrity": "sha512-bvyMcgLEkozzSzpEEEo1OMoeQ97bxj6Qs2uN3mPrSdDvObMI1myffD/BPqcLlzZO9//d1SqQA/WPw7Cz2AiqhA==", + "license": "Apache-2.0", + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/core": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.10.0.tgz", + "integrity": "sha512-/wNZ8twnEQQA4HoHu22+vcsdru6pWPWxW+7w+FlxT6Id7PE/WIbZmVKkte+PF72e0F2dnImFeHD2syyE1Mw6MQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-grpc": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-grpc/-/exporter-logs-otlp-grpc-0.218.0.tgz", + "integrity": "sha512-hoxrNH1l/Xy6F9WTJ5IK+6j1r9nQFlPOmrnTlhYHTySdunfXLmUCPv3bQtKYntxag9h3wLYBZQ2HI6FOx+BT2g==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/grpc-js": "^1.14.3", + "@opentelemetry/core": "2.7.1", + "@opentelemetry/otlp-exporter-base": "0.218.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.218.0", + "@opentelemetry/otlp-transformer": "0.218.0", + "@opentelemetry/sdk-logs": "0.218.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-grpc/node_modules/@opentelemetry/api-logs": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.218.0.tgz", + "integrity": "sha512-fmEWp5kXlGEc3i/lR698Hz41DfGyN4Tbe4g7L1AxSc7fF8Xeh/FQ9Quqpa9dVA413Q1Ad43QOLzU4JoXgbFPWw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-grpc/node_modules/@opentelemetry/core": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.7.1.tgz", + "integrity": "sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-grpc/node_modules/@opentelemetry/otlp-exporter-base": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.218.0.tgz", + "integrity": "sha512-ZwqpkNL5W7RyGJPDZ9g06DvKp8KFTWPJPN12anpMQYSKpTSU0z3EIZuPq9vPGpS8siFyOqDYDAuCwlNO9FqgbA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/otlp-transformer": "0.218.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-grpc/node_modules/@opentelemetry/otlp-transformer": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.218.0.tgz", + "integrity": "sha512-CFaKH87WAzjuJ4awowTTLzUvMfaRfiOFG5+qm5S5ncyalRtN4ecQ+YmuANJSCrVPuvZFEkUgKhBPBndxi3rHsQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.218.0", + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/sdk-logs": "0.218.0", + "@opentelemetry/sdk-metrics": "2.7.1", + "@opentelemetry/sdk-trace-base": "2.7.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-grpc/node_modules/@opentelemetry/resources": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.7.1.tgz", + "integrity": "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-grpc/node_modules/@opentelemetry/sdk-logs": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.218.0.tgz", + "integrity": "sha512-QvnNdugatFTVCJXH0Mcu7GOOJSylA9j127kIezOE4YwTI4YbowRons2K4WZTv5FMS8T4q9P0NdaRHdkSmeAIag==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.218.0", + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.4.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-grpc/node_modules/@opentelemetry/sdk-metrics": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.7.1.tgz", + "integrity": "sha512-MpDJdkiFDs3Pm1RHO3KByuZbuBdJEXEAkiC0+yJdsZGVCdf1RpHR6n+LHDcS7ffmfrt5kVCzJSCfm4z2C7v0uQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-grpc/node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.7.1.tgz", + "integrity": "sha512-NAYIlsF8MPUsKqJMiDQJTMPOmlbawC1Iz/omMLygZ1C9am8fTKYjTaI+OZM+WTY3t3Glo0wnOg/6/pac6RGPPw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-http": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-http/-/exporter-logs-otlp-http-0.218.0.tgz", + "integrity": "sha512-Qx+4rpVHzgg89dawcWRHyt+XRXeLnhFz/qBtvggmjkcgPUdr+NAB0/u/eIPA8yAeJV0J80Vz43JZCh/XFvZFGw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.218.0", + "@opentelemetry/core": "2.7.1", + "@opentelemetry/otlp-exporter-base": "0.218.0", + "@opentelemetry/otlp-transformer": "0.218.0", + "@opentelemetry/sdk-logs": "0.218.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-http/node_modules/@opentelemetry/api-logs": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.218.0.tgz", + "integrity": "sha512-fmEWp5kXlGEc3i/lR698Hz41DfGyN4Tbe4g7L1AxSc7fF8Xeh/FQ9Quqpa9dVA413Q1Ad43QOLzU4JoXgbFPWw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-http/node_modules/@opentelemetry/core": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.7.1.tgz", + "integrity": "sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-http/node_modules/@opentelemetry/otlp-exporter-base": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.218.0.tgz", + "integrity": "sha512-ZwqpkNL5W7RyGJPDZ9g06DvKp8KFTWPJPN12anpMQYSKpTSU0z3EIZuPq9vPGpS8siFyOqDYDAuCwlNO9FqgbA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/otlp-transformer": "0.218.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-http/node_modules/@opentelemetry/otlp-transformer": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.218.0.tgz", + "integrity": "sha512-CFaKH87WAzjuJ4awowTTLzUvMfaRfiOFG5+qm5S5ncyalRtN4ecQ+YmuANJSCrVPuvZFEkUgKhBPBndxi3rHsQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.218.0", + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/sdk-logs": "0.218.0", + "@opentelemetry/sdk-metrics": "2.7.1", + "@opentelemetry/sdk-trace-base": "2.7.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-http/node_modules/@opentelemetry/resources": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.7.1.tgz", + "integrity": "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-http/node_modules/@opentelemetry/sdk-logs": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.218.0.tgz", + "integrity": "sha512-QvnNdugatFTVCJXH0Mcu7GOOJSylA9j127kIezOE4YwTI4YbowRons2K4WZTv5FMS8T4q9P0NdaRHdkSmeAIag==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.218.0", + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.4.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-http/node_modules/@opentelemetry/sdk-metrics": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.7.1.tgz", + "integrity": "sha512-MpDJdkiFDs3Pm1RHO3KByuZbuBdJEXEAkiC0+yJdsZGVCdf1RpHR6n+LHDcS7ffmfrt5kVCzJSCfm4z2C7v0uQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-http/node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.7.1.tgz", + "integrity": "sha512-NAYIlsF8MPUsKqJMiDQJTMPOmlbawC1Iz/omMLygZ1C9am8fTKYjTaI+OZM+WTY3t3Glo0wnOg/6/pac6RGPPw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-proto": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-proto/-/exporter-logs-otlp-proto-0.218.0.tgz", + "integrity": "sha512-1/noQNsp9gXD75HPzgjBrcF1+XTtry7pFAUfxVEJgg7mPv2AawKQuYkhMmJ8qjxz4Ubc3Y8bwvfxevXsKTq4cg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.218.0", + "@opentelemetry/core": "2.7.1", + "@opentelemetry/otlp-exporter-base": "0.218.0", + "@opentelemetry/otlp-transformer": "0.218.0", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/sdk-logs": "0.218.0", + "@opentelemetry/sdk-trace-base": "2.7.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-proto/node_modules/@opentelemetry/api-logs": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.218.0.tgz", + "integrity": "sha512-fmEWp5kXlGEc3i/lR698Hz41DfGyN4Tbe4g7L1AxSc7fF8Xeh/FQ9Quqpa9dVA413Q1Ad43QOLzU4JoXgbFPWw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-proto/node_modules/@opentelemetry/core": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.7.1.tgz", + "integrity": "sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-proto/node_modules/@opentelemetry/otlp-exporter-base": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.218.0.tgz", + "integrity": "sha512-ZwqpkNL5W7RyGJPDZ9g06DvKp8KFTWPJPN12anpMQYSKpTSU0z3EIZuPq9vPGpS8siFyOqDYDAuCwlNO9FqgbA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/otlp-transformer": "0.218.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-proto/node_modules/@opentelemetry/otlp-transformer": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.218.0.tgz", + "integrity": "sha512-CFaKH87WAzjuJ4awowTTLzUvMfaRfiOFG5+qm5S5ncyalRtN4ecQ+YmuANJSCrVPuvZFEkUgKhBPBndxi3rHsQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.218.0", + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/sdk-logs": "0.218.0", + "@opentelemetry/sdk-metrics": "2.7.1", + "@opentelemetry/sdk-trace-base": "2.7.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-proto/node_modules/@opentelemetry/resources": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.7.1.tgz", + "integrity": "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-proto/node_modules/@opentelemetry/sdk-logs": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.218.0.tgz", + "integrity": "sha512-QvnNdugatFTVCJXH0Mcu7GOOJSylA9j127kIezOE4YwTI4YbowRons2K4WZTv5FMS8T4q9P0NdaRHdkSmeAIag==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.218.0", + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.4.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-proto/node_modules/@opentelemetry/sdk-metrics": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.7.1.tgz", + "integrity": "sha512-MpDJdkiFDs3Pm1RHO3KByuZbuBdJEXEAkiC0+yJdsZGVCdf1RpHR6n+LHDcS7ffmfrt5kVCzJSCfm4z2C7v0uQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-proto/node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.7.1.tgz", + "integrity": "sha512-NAYIlsF8MPUsKqJMiDQJTMPOmlbawC1Iz/omMLygZ1C9am8fTKYjTaI+OZM+WTY3t3Glo0wnOg/6/pac6RGPPw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-grpc": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-grpc/-/exporter-metrics-otlp-grpc-0.218.0.tgz", + "integrity": "sha512-YapQ9vNMX0NSZF6LK5pWAFfjpJleV2O9uYWfYGeb/5F1Kb9rPGK8tZDMJFa/sOksgdFuflDvYuA0B4qjDB4fjQ==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/grpc-js": "^1.14.3", + "@opentelemetry/core": "2.7.1", + "@opentelemetry/exporter-metrics-otlp-http": "0.218.0", + "@opentelemetry/otlp-exporter-base": "0.218.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.218.0", + "@opentelemetry/otlp-transformer": "0.218.0", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/sdk-metrics": "2.7.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-grpc/node_modules/@opentelemetry/api-logs": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.218.0.tgz", + "integrity": "sha512-fmEWp5kXlGEc3i/lR698Hz41DfGyN4Tbe4g7L1AxSc7fF8Xeh/FQ9Quqpa9dVA413Q1Ad43QOLzU4JoXgbFPWw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-grpc/node_modules/@opentelemetry/core": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.7.1.tgz", + "integrity": "sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-grpc/node_modules/@opentelemetry/otlp-exporter-base": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.218.0.tgz", + "integrity": "sha512-ZwqpkNL5W7RyGJPDZ9g06DvKp8KFTWPJPN12anpMQYSKpTSU0z3EIZuPq9vPGpS8siFyOqDYDAuCwlNO9FqgbA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/otlp-transformer": "0.218.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-grpc/node_modules/@opentelemetry/otlp-transformer": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.218.0.tgz", + "integrity": "sha512-CFaKH87WAzjuJ4awowTTLzUvMfaRfiOFG5+qm5S5ncyalRtN4ecQ+YmuANJSCrVPuvZFEkUgKhBPBndxi3rHsQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.218.0", + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/sdk-logs": "0.218.0", + "@opentelemetry/sdk-metrics": "2.7.1", + "@opentelemetry/sdk-trace-base": "2.7.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-grpc/node_modules/@opentelemetry/resources": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.7.1.tgz", + "integrity": "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-grpc/node_modules/@opentelemetry/sdk-logs": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.218.0.tgz", + "integrity": "sha512-QvnNdugatFTVCJXH0Mcu7GOOJSylA9j127kIezOE4YwTI4YbowRons2K4WZTv5FMS8T4q9P0NdaRHdkSmeAIag==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.218.0", + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.4.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-grpc/node_modules/@opentelemetry/sdk-metrics": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.7.1.tgz", + "integrity": "sha512-MpDJdkiFDs3Pm1RHO3KByuZbuBdJEXEAkiC0+yJdsZGVCdf1RpHR6n+LHDcS7ffmfrt5kVCzJSCfm4z2C7v0uQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-grpc/node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.7.1.tgz", + "integrity": "sha512-NAYIlsF8MPUsKqJMiDQJTMPOmlbawC1Iz/omMLygZ1C9am8fTKYjTaI+OZM+WTY3t3Glo0wnOg/6/pac6RGPPw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-http": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-http/-/exporter-metrics-otlp-http-0.218.0.tgz", + "integrity": "sha512-bV7d2OuMpZu2+gAaxUAhzfZ0h3WVZk8ETQUEE3DNSntbTaMpuITjtm8I0rNyHFdm7Ax57K6ty7SgFXlBmOLIvQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/otlp-exporter-base": "0.218.0", + "@opentelemetry/otlp-transformer": "0.218.0", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/sdk-metrics": "2.7.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-http/node_modules/@opentelemetry/api-logs": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.218.0.tgz", + "integrity": "sha512-fmEWp5kXlGEc3i/lR698Hz41DfGyN4Tbe4g7L1AxSc7fF8Xeh/FQ9Quqpa9dVA413Q1Ad43QOLzU4JoXgbFPWw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-http/node_modules/@opentelemetry/core": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.7.1.tgz", + "integrity": "sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-http/node_modules/@opentelemetry/otlp-exporter-base": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.218.0.tgz", + "integrity": "sha512-ZwqpkNL5W7RyGJPDZ9g06DvKp8KFTWPJPN12anpMQYSKpTSU0z3EIZuPq9vPGpS8siFyOqDYDAuCwlNO9FqgbA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/otlp-transformer": "0.218.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-http/node_modules/@opentelemetry/otlp-transformer": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.218.0.tgz", + "integrity": "sha512-CFaKH87WAzjuJ4awowTTLzUvMfaRfiOFG5+qm5S5ncyalRtN4ecQ+YmuANJSCrVPuvZFEkUgKhBPBndxi3rHsQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.218.0", + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/sdk-logs": "0.218.0", + "@opentelemetry/sdk-metrics": "2.7.1", + "@opentelemetry/sdk-trace-base": "2.7.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-http/node_modules/@opentelemetry/resources": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.7.1.tgz", + "integrity": "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-http/node_modules/@opentelemetry/sdk-logs": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.218.0.tgz", + "integrity": "sha512-QvnNdugatFTVCJXH0Mcu7GOOJSylA9j127kIezOE4YwTI4YbowRons2K4WZTv5FMS8T4q9P0NdaRHdkSmeAIag==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.218.0", + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.4.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-http/node_modules/@opentelemetry/sdk-metrics": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.7.1.tgz", + "integrity": "sha512-MpDJdkiFDs3Pm1RHO3KByuZbuBdJEXEAkiC0+yJdsZGVCdf1RpHR6n+LHDcS7ffmfrt5kVCzJSCfm4z2C7v0uQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-http/node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.7.1.tgz", + "integrity": "sha512-NAYIlsF8MPUsKqJMiDQJTMPOmlbawC1Iz/omMLygZ1C9am8fTKYjTaI+OZM+WTY3t3Glo0wnOg/6/pac6RGPPw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-proto": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-proto/-/exporter-metrics-otlp-proto-0.218.0.tgz", + "integrity": "sha512-ubLddKjWULhla9YZRCj/rTBeppjJYE4e9w0icx5mTu3eFhWjQzbV75NYjXuIlEG+NJsBl6d+sTFw5Qu+oej4oQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/exporter-metrics-otlp-http": "0.218.0", + "@opentelemetry/otlp-exporter-base": "0.218.0", + "@opentelemetry/otlp-transformer": "0.218.0", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/sdk-metrics": "2.7.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-proto/node_modules/@opentelemetry/api-logs": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.218.0.tgz", + "integrity": "sha512-fmEWp5kXlGEc3i/lR698Hz41DfGyN4Tbe4g7L1AxSc7fF8Xeh/FQ9Quqpa9dVA413Q1Ad43QOLzU4JoXgbFPWw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-proto/node_modules/@opentelemetry/core": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.7.1.tgz", + "integrity": "sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-proto/node_modules/@opentelemetry/otlp-exporter-base": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.218.0.tgz", + "integrity": "sha512-ZwqpkNL5W7RyGJPDZ9g06DvKp8KFTWPJPN12anpMQYSKpTSU0z3EIZuPq9vPGpS8siFyOqDYDAuCwlNO9FqgbA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/otlp-transformer": "0.218.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-proto/node_modules/@opentelemetry/otlp-transformer": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.218.0.tgz", + "integrity": "sha512-CFaKH87WAzjuJ4awowTTLzUvMfaRfiOFG5+qm5S5ncyalRtN4ecQ+YmuANJSCrVPuvZFEkUgKhBPBndxi3rHsQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.218.0", + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/sdk-logs": "0.218.0", + "@opentelemetry/sdk-metrics": "2.7.1", + "@opentelemetry/sdk-trace-base": "2.7.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-proto/node_modules/@opentelemetry/resources": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.7.1.tgz", + "integrity": "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-proto/node_modules/@opentelemetry/sdk-logs": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.218.0.tgz", + "integrity": "sha512-QvnNdugatFTVCJXH0Mcu7GOOJSylA9j127kIezOE4YwTI4YbowRons2K4WZTv5FMS8T4q9P0NdaRHdkSmeAIag==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.218.0", + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.4.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-proto/node_modules/@opentelemetry/sdk-metrics": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.7.1.tgz", + "integrity": "sha512-MpDJdkiFDs3Pm1RHO3KByuZbuBdJEXEAkiC0+yJdsZGVCdf1RpHR6n+LHDcS7ffmfrt5kVCzJSCfm4z2C7v0uQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-proto/node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.7.1.tgz", + "integrity": "sha512-NAYIlsF8MPUsKqJMiDQJTMPOmlbawC1Iz/omMLygZ1C9am8fTKYjTaI+OZM+WTY3t3Glo0wnOg/6/pac6RGPPw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-prometheus": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-prometheus/-/exporter-prometheus-0.218.0.tgz", + "integrity": "sha512-RT5oEyu1kddZJ1vt7/BUo5wV+P7hpNAESsR3dUd3+8deHuX7gWNoCOZn+SfDT+hJHlIJ5h/AxiCLXIrutswDJg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/sdk-metrics": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-prometheus/node_modules/@opentelemetry/core": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.7.1.tgz", + "integrity": "sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-prometheus/node_modules/@opentelemetry/resources": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.7.1.tgz", + "integrity": "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-prometheus/node_modules/@opentelemetry/sdk-metrics": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.7.1.tgz", + "integrity": "sha512-MpDJdkiFDs3Pm1RHO3KByuZbuBdJEXEAkiC0+yJdsZGVCdf1RpHR6n+LHDcS7ffmfrt5kVCzJSCfm4z2C7v0uQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-grpc": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-grpc/-/exporter-trace-otlp-grpc-0.218.0.tgz", + "integrity": "sha512-3fXxVQEj9TNAFaCi79JeFKfeLd0sDtInaR3gaZDVlzNSPHtz8PZuCV34JKWjD4XXzT20IdMe8IpX6mRVNDA4Tw==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/grpc-js": "^1.14.3", + "@opentelemetry/core": "2.7.1", + "@opentelemetry/otlp-exporter-base": "0.218.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.218.0", + "@opentelemetry/otlp-transformer": "0.218.0", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/sdk-trace-base": "2.7.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-grpc/node_modules/@opentelemetry/api-logs": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.218.0.tgz", + "integrity": "sha512-fmEWp5kXlGEc3i/lR698Hz41DfGyN4Tbe4g7L1AxSc7fF8Xeh/FQ9Quqpa9dVA413Q1Ad43QOLzU4JoXgbFPWw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-grpc/node_modules/@opentelemetry/core": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.7.1.tgz", + "integrity": "sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-grpc/node_modules/@opentelemetry/otlp-exporter-base": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.218.0.tgz", + "integrity": "sha512-ZwqpkNL5W7RyGJPDZ9g06DvKp8KFTWPJPN12anpMQYSKpTSU0z3EIZuPq9vPGpS8siFyOqDYDAuCwlNO9FqgbA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/otlp-transformer": "0.218.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-grpc/node_modules/@opentelemetry/otlp-transformer": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.218.0.tgz", + "integrity": "sha512-CFaKH87WAzjuJ4awowTTLzUvMfaRfiOFG5+qm5S5ncyalRtN4ecQ+YmuANJSCrVPuvZFEkUgKhBPBndxi3rHsQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.218.0", + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/sdk-logs": "0.218.0", + "@opentelemetry/sdk-metrics": "2.7.1", + "@opentelemetry/sdk-trace-base": "2.7.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-grpc/node_modules/@opentelemetry/resources": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.7.1.tgz", + "integrity": "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-grpc/node_modules/@opentelemetry/sdk-logs": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.218.0.tgz", + "integrity": "sha512-QvnNdugatFTVCJXH0Mcu7GOOJSylA9j127kIezOE4YwTI4YbowRons2K4WZTv5FMS8T4q9P0NdaRHdkSmeAIag==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.218.0", + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.4.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-grpc/node_modules/@opentelemetry/sdk-metrics": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.7.1.tgz", + "integrity": "sha512-MpDJdkiFDs3Pm1RHO3KByuZbuBdJEXEAkiC0+yJdsZGVCdf1RpHR6n+LHDcS7ffmfrt5kVCzJSCfm4z2C7v0uQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-grpc/node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.7.1.tgz", + "integrity": "sha512-NAYIlsF8MPUsKqJMiDQJTMPOmlbawC1Iz/omMLygZ1C9am8fTKYjTaI+OZM+WTY3t3Glo0wnOg/6/pac6RGPPw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-http": { + "version": "0.221.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.221.0.tgz", + "integrity": "sha512-AySXiKoC+meiWm6zdVj5T2LnPDZuatveBby1cMOeQteIWsYXAUxs8Sru13G2pVSPrUXz6vF+og7QVBX6GdC/oQ==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/otlp-exporter-base": "0.221.0", + "@opentelemetry/otlp-transformer": "0.221.0", + "@opentelemetry/sdk-trace": "2.10.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-proto": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-proto/-/exporter-trace-otlp-proto-0.218.0.tgz", + "integrity": "sha512-r1Msf8SNLRmwh9J6XQ5uh82D7CdDWMNHnPB7LAVHjzut0TkSeKc5KcIvr4SvHvfk/xwN5gxC+VLKQ1k0o8PSPw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/otlp-exporter-base": "0.218.0", + "@opentelemetry/otlp-transformer": "0.218.0", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/sdk-trace-base": "2.7.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-proto/node_modules/@opentelemetry/api-logs": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.218.0.tgz", + "integrity": "sha512-fmEWp5kXlGEc3i/lR698Hz41DfGyN4Tbe4g7L1AxSc7fF8Xeh/FQ9Quqpa9dVA413Q1Ad43QOLzU4JoXgbFPWw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-proto/node_modules/@opentelemetry/core": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.7.1.tgz", + "integrity": "sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-proto/node_modules/@opentelemetry/otlp-exporter-base": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.218.0.tgz", + "integrity": "sha512-ZwqpkNL5W7RyGJPDZ9g06DvKp8KFTWPJPN12anpMQYSKpTSU0z3EIZuPq9vPGpS8siFyOqDYDAuCwlNO9FqgbA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/otlp-transformer": "0.218.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-proto/node_modules/@opentelemetry/otlp-transformer": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.218.0.tgz", + "integrity": "sha512-CFaKH87WAzjuJ4awowTTLzUvMfaRfiOFG5+qm5S5ncyalRtN4ecQ+YmuANJSCrVPuvZFEkUgKhBPBndxi3rHsQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.218.0", + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/sdk-logs": "0.218.0", + "@opentelemetry/sdk-metrics": "2.7.1", + "@opentelemetry/sdk-trace-base": "2.7.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-proto/node_modules/@opentelemetry/resources": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.7.1.tgz", + "integrity": "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-proto/node_modules/@opentelemetry/sdk-logs": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.218.0.tgz", + "integrity": "sha512-QvnNdugatFTVCJXH0Mcu7GOOJSylA9j127kIezOE4YwTI4YbowRons2K4WZTv5FMS8T4q9P0NdaRHdkSmeAIag==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.218.0", + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.4.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-proto/node_modules/@opentelemetry/sdk-metrics": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.7.1.tgz", + "integrity": "sha512-MpDJdkiFDs3Pm1RHO3KByuZbuBdJEXEAkiC0+yJdsZGVCdf1RpHR6n+LHDcS7ffmfrt5kVCzJSCfm4z2C7v0uQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-proto/node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.7.1.tgz", + "integrity": "sha512-NAYIlsF8MPUsKqJMiDQJTMPOmlbawC1Iz/omMLygZ1C9am8fTKYjTaI+OZM+WTY3t3Glo0wnOg/6/pac6RGPPw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-zipkin": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-zipkin/-/exporter-zipkin-2.7.1.tgz", + "integrity": "sha512-mfsD9bKAxcKrh5+y08TPodvClBO0CznBE3p79YAGnO81WI4LrdsGA65T53e4iTSbCalW4WaUpkbeJcbpyIUHfg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/sdk-trace-base": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0" + } + }, + "node_modules/@opentelemetry/exporter-zipkin/node_modules/@opentelemetry/core": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.7.1.tgz", + "integrity": "sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-zipkin/node_modules/@opentelemetry/resources": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.7.1.tgz", + "integrity": "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-zipkin/node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.7.1.tgz", + "integrity": "sha512-NAYIlsF8MPUsKqJMiDQJTMPOmlbawC1Iz/omMLygZ1C9am8fTKYjTaI+OZM+WTY3t3Glo0wnOg/6/pac6RGPPw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/instrumentation": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.218.0.tgz", + "integrity": "sha512-mIZil8Es+sYDK5m+DQiwAwF57F14TF2YlEqvIjZ/RQWcxDBwRGsKfdK2Tv65OU9meQKCMzSIFS9mxAcnAb6Bkg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.218.0", + "import-in-the-middle": "^3.0.0", + "require-in-the-middle": "^8.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation/node_modules/@opentelemetry/api-logs": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.218.0.tgz", + "integrity": "sha512-fmEWp5kXlGEc3i/lR698Hz41DfGyN4Tbe4g7L1AxSc7fF8Xeh/FQ9Quqpa9dVA413Q1Ad43QOLzU4JoXgbFPWw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/otlp-exporter-base": { + "version": "0.221.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.221.0.tgz", + "integrity": "sha512-UFPIq80OH3Ns/oPFHRj14d4DTOxUo+MUFU8hUiCq5jTqFhdeJnfVSANHT+xp92409cA+oxzvlZCe6NM1wvCuBA==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "2.10.0", + "@opentelemetry/otlp-transformer": "0.221.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-grpc-exporter-base": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-grpc-exporter-base/-/otlp-grpc-exporter-base-0.218.0.tgz", + "integrity": "sha512-H/lCGJ536N98VpYJOaWTQOkv4Dx6TnmStK6Rqfu1W7KkFbPAx04hjdYEMZF/YbnHzPUSIK4kM6OE2GKGBTpV9A==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/grpc-js": "^1.14.3", + "@opentelemetry/core": "2.7.1", + "@opentelemetry/otlp-exporter-base": "0.218.0", + "@opentelemetry/otlp-transformer": "0.218.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-grpc-exporter-base/node_modules/@opentelemetry/api-logs": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.218.0.tgz", + "integrity": "sha512-fmEWp5kXlGEc3i/lR698Hz41DfGyN4Tbe4g7L1AxSc7fF8Xeh/FQ9Quqpa9dVA413Q1Ad43QOLzU4JoXgbFPWw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/otlp-grpc-exporter-base/node_modules/@opentelemetry/core": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.7.1.tgz", + "integrity": "sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/otlp-grpc-exporter-base/node_modules/@opentelemetry/otlp-exporter-base": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.218.0.tgz", + "integrity": "sha512-ZwqpkNL5W7RyGJPDZ9g06DvKp8KFTWPJPN12anpMQYSKpTSU0z3EIZuPq9vPGpS8siFyOqDYDAuCwlNO9FqgbA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/otlp-transformer": "0.218.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-grpc-exporter-base/node_modules/@opentelemetry/otlp-transformer": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.218.0.tgz", + "integrity": "sha512-CFaKH87WAzjuJ4awowTTLzUvMfaRfiOFG5+qm5S5ncyalRtN4ecQ+YmuANJSCrVPuvZFEkUgKhBPBndxi3rHsQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.218.0", + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/sdk-logs": "0.218.0", + "@opentelemetry/sdk-metrics": "2.7.1", + "@opentelemetry/sdk-trace-base": "2.7.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-grpc-exporter-base/node_modules/@opentelemetry/resources": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.7.1.tgz", + "integrity": "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/otlp-grpc-exporter-base/node_modules/@opentelemetry/sdk-logs": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.218.0.tgz", + "integrity": "sha512-QvnNdugatFTVCJXH0Mcu7GOOJSylA9j127kIezOE4YwTI4YbowRons2K4WZTv5FMS8T4q9P0NdaRHdkSmeAIag==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.218.0", + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.4.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/otlp-grpc-exporter-base/node_modules/@opentelemetry/sdk-metrics": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.7.1.tgz", + "integrity": "sha512-MpDJdkiFDs3Pm1RHO3KByuZbuBdJEXEAkiC0+yJdsZGVCdf1RpHR6n+LHDcS7ffmfrt5kVCzJSCfm4z2C7v0uQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/otlp-grpc-exporter-base/node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.7.1.tgz", + "integrity": "sha512-NAYIlsF8MPUsKqJMiDQJTMPOmlbawC1Iz/omMLygZ1C9am8fTKYjTaI+OZM+WTY3t3Glo0wnOg/6/pac6RGPPw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/otlp-transformer": { + "version": "0.221.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.221.0.tgz", + "integrity": "sha512-lg6lkOU08Az23jVcn/0Els9HP+V8PnR4Km6p0KgpTggS0n/WuhnmY64rSh83Of9iR9nD+dpWr6adlcX8KzAwjg==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/api-logs": "0.221.0", + "@opentelemetry/core": "2.10.0", + "@opentelemetry/resources": "2.10.0", + "@opentelemetry/sdk-logs": "0.221.0", + "@opentelemetry/sdk-metrics": "2.10.0", + "@opentelemetry/sdk-trace": "2.10.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/propagator-b3": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-b3/-/propagator-b3-2.7.1.tgz", + "integrity": "sha512-RJid6E2CKyeGfKBzXKF21ejabGMHypFkPAh3qZ+NvI+SGjuIye79t3PmiqcDgtRzdKH6ynXzbfslQ8DfpRUg2A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/propagator-b3/node_modules/@opentelemetry/core": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.7.1.tgz", + "integrity": "sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/propagator-jaeger": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-jaeger/-/propagator-jaeger-2.7.1.tgz", + "integrity": "sha512-KMjVBHzP4N60bOzxja76M1F1hZZ43lGPga5ix+mkv9+kk1nx9SbkxSvJsMbuVUxdPQmsPTqGShmhN8ulrMOg6Q==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/propagator-jaeger/node_modules/@opentelemetry/core": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.7.1.tgz", + "integrity": "sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/resources": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.10.0.tgz", + "integrity": "sha512-q6MMm2zhggzsHVNbabYwut+a6nbuQQe3URUoxaojM/8K1IBfwwPzvxIjNi2/lI1TFe+fMHMW9MWhrtDLEXEnkA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.10.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-logs": { + "version": "0.221.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.221.0.tgz", + "integrity": "sha512-FaDcazjyMp7TZZZAsqbo4IkovP0UegoCu0EBkiNt+qCqvUf7FPAsfcrZ3+ZEkKgXZ/jHafop+JoGPDk3A0SmLg==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/api-logs": "0.221.0", + "@opentelemetry/core": "2.10.0", + "@opentelemetry/resources": "2.10.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.4.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-metrics": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.10.0.tgz", + "integrity": "sha512-t6r1VSvXNtSDnPXU1FbZeetJb7yyovHmgu0wRSoftxtE0g2rSNhQZQUy69sRUCL+iioJpX8SN/S6wq6ZtvLySQ==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@opentelemetry/core": "2.10.0", + "@opentelemetry/resources": "2.10.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-node": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-node/-/sdk-node-0.218.0.tgz", + "integrity": "sha512-tPMjHrLV5gsfNdYqoRHjeGbCAZBXXD9c1Qo/2ut7VwnUABDNh76xNxrT0SEhkIIJuCN45bbN1vZnYL1gY0IkOg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.218.0", + "@opentelemetry/configuration": "0.218.0", + "@opentelemetry/context-async-hooks": "2.7.1", + "@opentelemetry/core": "2.7.1", + "@opentelemetry/exporter-logs-otlp-grpc": "0.218.0", + "@opentelemetry/exporter-logs-otlp-http": "0.218.0", + "@opentelemetry/exporter-logs-otlp-proto": "0.218.0", + "@opentelemetry/exporter-metrics-otlp-grpc": "0.218.0", + "@opentelemetry/exporter-metrics-otlp-http": "0.218.0", + "@opentelemetry/exporter-metrics-otlp-proto": "0.218.0", + "@opentelemetry/exporter-prometheus": "0.218.0", + "@opentelemetry/exporter-trace-otlp-grpc": "0.218.0", + "@opentelemetry/exporter-trace-otlp-http": "0.218.0", + "@opentelemetry/exporter-trace-otlp-proto": "0.218.0", + "@opentelemetry/exporter-zipkin": "2.7.1", + "@opentelemetry/instrumentation": "0.218.0", + "@opentelemetry/otlp-exporter-base": "0.218.0", + "@opentelemetry/propagator-b3": "2.7.1", + "@opentelemetry/propagator-jaeger": "2.7.1", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/sdk-logs": "0.218.0", + "@opentelemetry/sdk-metrics": "2.7.1", + "@opentelemetry/sdk-trace-base": "2.7.1", + "@opentelemetry/sdk-trace-node": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/api-logs": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.218.0.tgz", + "integrity": "sha512-fmEWp5kXlGEc3i/lR698Hz41DfGyN4Tbe4g7L1AxSc7fF8Xeh/FQ9Quqpa9dVA413Q1Ad43QOLzU4JoXgbFPWw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/context-async-hooks": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.7.1.tgz", + "integrity": "sha512-OPFBYuXEn1E4ja3Y6eeA7O+ZnLBNcXTV5Cgsn1VaqBZ6hC5FnpZPLBNme1LJY8ZtF4aOujPKFoeWN4ik487KuQ==", + "license": "Apache-2.0", + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/core": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.7.1.tgz", + "integrity": "sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/exporter-trace-otlp-http": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.218.0.tgz", + "integrity": "sha512-8dqezsmPhtKitIK/eTipZhYl9EX2/gNQ5zUMhaz3uxEURwfkNf8IPvo6yNfrzbxdtpAOybS/+h7wmIWYqFSpiw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/otlp-exporter-base": "0.218.0", + "@opentelemetry/otlp-transformer": "0.218.0", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/sdk-trace-base": "2.7.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/otlp-exporter-base": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.218.0.tgz", + "integrity": "sha512-ZwqpkNL5W7RyGJPDZ9g06DvKp8KFTWPJPN12anpMQYSKpTSU0z3EIZuPq9vPGpS8siFyOqDYDAuCwlNO9FqgbA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/otlp-transformer": "0.218.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/otlp-transformer": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.218.0.tgz", + "integrity": "sha512-CFaKH87WAzjuJ4awowTTLzUvMfaRfiOFG5+qm5S5ncyalRtN4ecQ+YmuANJSCrVPuvZFEkUgKhBPBndxi3rHsQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.218.0", + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/sdk-logs": "0.218.0", + "@opentelemetry/sdk-metrics": "2.7.1", + "@opentelemetry/sdk-trace-base": "2.7.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/resources": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.7.1.tgz", + "integrity": "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/sdk-logs": { + "version": "0.218.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.218.0.tgz", + "integrity": "sha512-QvnNdugatFTVCJXH0Mcu7GOOJSylA9j127kIezOE4YwTI4YbowRons2K4WZTv5FMS8T4q9P0NdaRHdkSmeAIag==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.218.0", + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.4.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/sdk-metrics": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.7.1.tgz", + "integrity": "sha512-MpDJdkiFDs3Pm1RHO3KByuZbuBdJEXEAkiC0+yJdsZGVCdf1RpHR6n+LHDcS7ffmfrt5kVCzJSCfm4z2C7v0uQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-node/node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.7.1.tgz", + "integrity": "sha512-NAYIlsF8MPUsKqJMiDQJTMPOmlbawC1Iz/omMLygZ1C9am8fTKYjTaI+OZM+WTY3t3Glo0wnOg/6/pac6RGPPw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace/-/sdk-trace-2.10.0.tgz", + "integrity": "sha512-MfQGq3GRmTh5fM/y+OjaO0vj6+luCB1XO2gfXCalKCfgKw0eHL++sm75DNweC6ohlp+aFvACqeE0fYayqdRaoQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.10.0", + "@opentelemetry/resources": "2.10.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.10.0.tgz", + "integrity": "sha512-GuYQQT7QD2EeO8lcZLRQzcbOyhqAzL+6WWTKTU9mSUBYBazkEDl+VrQcXQhbB08OWM9anD1aHleVadzulpOaUQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.10.0", + "@opentelemetry/resources": "2.10.0", + "@opentelemetry/sdk-trace": "2.10.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-node": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-2.7.1.tgz", + "integrity": "sha512-pCpQxU68lV+I9s9svqMyVu5iHdDDUnqUpSxqwyCU8A9ejEsSnMPCbearwsUO4yk08ZJzAIUCFuReMdVQvHrdvg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/context-async-hooks": "2.7.1", + "@opentelemetry/core": "2.7.1", + "@opentelemetry/sdk-trace-base": "2.7.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-node/node_modules/@opentelemetry/context-async-hooks": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.7.1.tgz", + "integrity": "sha512-OPFBYuXEn1E4ja3Y6eeA7O+ZnLBNcXTV5Cgsn1VaqBZ6hC5FnpZPLBNme1LJY8ZtF4aOujPKFoeWN4ik487KuQ==", + "license": "Apache-2.0", + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-node/node_modules/@opentelemetry/core": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.7.1.tgz", + "integrity": "sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-node/node_modules/@opentelemetry/resources": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.7.1.tgz", + "integrity": "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-node/node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.7.1.tgz", + "integrity": "sha512-NAYIlsF8MPUsKqJMiDQJTMPOmlbawC1Iz/omMLygZ1C9am8fTKYjTaI+OZM+WTY3t3Glo0wnOg/6/pac6RGPPw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/resources": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/semantic-conventions": { + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.43.0.tgz", + "integrity": "sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", + "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", + "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", + "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.2.tgz", + "integrity": "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==", + "license": "BSD-3-Clause" + }, + "node_modules/@types/node": { + "version": "26.1.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.2.tgz", + "integrity": "sha512-Vu4a5UFA9rIIFJ7rB/Vaafh9lrCQszopTCx6KjFboXTGQbPNasehVR5TEiithSDGyd1DEiUByggTZsg8jukeIg==", + "license": "MIT", + "dependencies": { + "undici-types": "~8.3.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cjs-module-lexer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", + "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==", + "license": "MIT" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/es-module-lexer": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.1.tgz", + "integrity": "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==", + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/import-in-the-middle": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-3.3.3.tgz", + "integrity": "sha512-AiohS3H80sXO6owEltjGX+glb7qXaDhBoJb9XcQVH4UI207xu/bDLUcadVKp7Qe576reg9yr/PXZjV5qx8gfbA==", + "license": "Apache-2.0", + "dependencies": { + "cjs-module-lexer": "^2.2.0", + "es-module-lexer": "^2.2.0", + "module-details-from-path": "^1.0.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "license": "MIT" + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, + "node_modules/module-details-from-path": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.4.tgz", + "integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/mustache": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "license": "MIT", + "bin": { + "mustache": "bin/mustache" + } + }, + "node_modules/protobufjs": { + "version": "7.6.5", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.5.tgz", + "integrity": "sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.5", + "@protobufjs/eventemitter": "^1.1.1", + "@protobufjs/fetch": "^1.1.1", + "@protobufjs/float": "^1.0.2", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.1", + "@types/node": ">=13.7.0", + "long": "^5.3.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-in-the-middle": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-8.0.1.tgz", + "integrity": "sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "module-details-from-path": "^1.0.3" + }, + "engines": { + "node": ">=9.3.0 || >=8.10.0 <9.0.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tsx": { + "version": "4.23.5", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.5.tgz", + "integrity": "sha512-rw55FUaqOoI7RvlQwLbhO4nSDApnQ4/CykPuiQ/EPvtrX3WA9Ig55jIt9VvbBJbzJuj12ueRu4PMZ2SxPVbihg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.28.0" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", + "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + } + } +} diff --git a/extensions/pi-langfuse/package.json b/extensions/pi-langfuse/package.json new file mode 100644 index 0000000..8a1097c --- /dev/null +++ b/extensions/pi-langfuse/package.json @@ -0,0 +1,69 @@ +{ + "name": "pi-langfuse", + "version": "1.5.8", + "description": "Langfuse extension for Pi coding agent", + "repository": { + "type": "git", + "url": "git+https://github.com/gooyoung/pi-langfuse.git" + }, + "bugs": { + "url": "https://github.com/gooyoung/pi-langfuse/issues" + }, + "homepage": "https://github.com/gooyoung/pi-langfuse#readme", + "type": "module", + "packageManager": "npm@11.12.1", + "main": "index.ts", + "files": [ + "index.ts", + "src/", + "types/", + "README.md", + "README_CN.md", + "image.png", + "skills-lock.json", + "tsconfig.json" + ], + "scripts": { + "typecheck": "tsc --noEmit", + "test": "tsx --test test/*.test.ts" + }, + "keywords": [ + "pi-package", + "langfuse", + "observability", + "tracing", + "monitoring", + "pi-coding-agent", + "extension" + ], + "pi": { + "extensions": [ + "./index.ts" + ], + "image": "https://github.com/gooyoung/pi-langfuse/blob/main/image.png?raw=true" + }, + "dependencies": { + "@langfuse/client": "^5.3.0", + "@langfuse/otel": "^5.3.0", + "@langfuse/tracing": "^5.3.0", + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/context-async-hooks": "^2.7.1", + "@opentelemetry/sdk-node": "^0.218.0", + "@opentelemetry/sdk-trace-base": "^2.0.1" + }, + "peerDependencies": { + "@earendil-works/pi-coding-agent": "*" + }, + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org" + }, + "license": "MIT", + "engines": { + "node": ">=22" + }, + "devDependencies": { + "tsx": "^4.19.0", + "typescript": "^6.0.3" + } +} diff --git a/extensions/pi-langfuse/skills-lock.json b/extensions/pi-langfuse/skills-lock.json new file mode 100644 index 0000000..28dcafb --- /dev/null +++ b/extensions/pi-langfuse/skills-lock.json @@ -0,0 +1,11 @@ +{ + "version": 1, + "skills": { + "langfuse": { + "source": "langfuse/skills", + "sourceType": "github", + "skillPath": "skills/langfuse/SKILL.md", + "computedHash": "ccb3e0bee034850742b4387983e83c9cf2d8d8283a5a4264f0ecdfd03db01755" + } + } +} diff --git a/extensions/pi-langfuse/src/capture-policy.ts b/extensions/pi-langfuse/src/capture-policy.ts new file mode 100644 index 0000000..fd8bb90 --- /dev/null +++ b/extensions/pi-langfuse/src/capture-policy.ts @@ -0,0 +1,141 @@ +import { redactValue } from "./redaction.js"; + +export interface CapturePolicy { + readonly captureInputs: boolean; + readonly captureOutputs: boolean; + readonly captureToolIo: boolean; + readonly captureSystemPrompt: boolean; + readonly captureCwd: boolean; +} + +export type PrivacyPreset = "metadata-only" | "prompts-only" | "conversations" | "full-debug"; +export type EnvLike = Readonly>; + +export interface RawTelemetryPayload { + input?: unknown; + output?: unknown; + toolInput?: unknown; + toolOutput?: unknown; + systemPrompt?: unknown; + metadata?: Record; +} + +export interface CapturedTelemetryPayload { + input?: unknown; + output?: unknown; + toolInput?: unknown; + toolOutput?: unknown; + systemPrompt?: unknown; + metadata?: Record; +} + +const PRESETS: Record = { + "metadata-only": { + captureInputs: false, + captureOutputs: false, + captureToolIo: false, + captureSystemPrompt: false, + captureCwd: false, + }, + "prompts-only": { + captureInputs: true, + captureOutputs: false, + captureToolIo: false, + captureSystemPrompt: false, + captureCwd: false, + }, + conversations: { + captureInputs: true, + captureOutputs: true, + captureToolIo: false, + captureSystemPrompt: false, + captureCwd: false, + }, + "full-debug": { + captureInputs: true, + captureOutputs: true, + captureToolIo: true, + captureSystemPrompt: true, + captureCwd: true, + }, +}; + +const FLAG_TO_FIELD = { + LANGFUSE_CAPTURE_INPUTS: "captureInputs", + LANGFUSE_CAPTURE_OUTPUTS: "captureOutputs", + LANGFUSE_CAPTURE_TOOL_IO: "captureToolIo", + LANGFUSE_CAPTURE_SYSTEM_PROMPT: "captureSystemPrompt", + LANGFUSE_CAPTURE_CWD: "captureCwd", +} as const; + +function parseFlag(value: string | undefined): boolean | undefined { + if (value === undefined) { + return undefined; + } + if (/^(1|true|yes|on)$/i.test(value)) { + return true; + } + if (/^(0|false|no|off)$/i.test(value)) { + return false; + } + return undefined; +} + +function normalizePreset(value: string | undefined): PrivacyPreset { + return value && value in PRESETS ? (value as PrivacyPreset) : "full-debug"; +} + +export function createCapturePolicy(env: EnvLike = process.env as EnvLike): CapturePolicy { + const policy: CapturePolicy = { ...PRESETS[normalizePreset(env.LANGFUSE_PRIVACY_PRESET)] }; + for (const [envName, field] of Object.entries(FLAG_TO_FIELD) as Array< + [keyof typeof FLAG_TO_FIELD, (typeof FLAG_TO_FIELD)[keyof typeof FLAG_TO_FIELD]] + >) { + const override = parseFlag(env[envName]); + if (override !== undefined) { + (policy as Record)[field] = override; + } + } + return policy; +} + +function redactMetadata(metadata: Record | undefined, policy: CapturePolicy) { + if (!metadata) { + return undefined; + } + + const output: Record = {}; + for (const [key, value] of Object.entries(metadata)) { + if (key === "cwd" && !policy.captureCwd) { + continue; + } + output[key] = redactValue(value); + } + return Object.keys(output).length > 0 ? output : undefined; +} + +export function applyCapturePolicy( + payload: RawTelemetryPayload, + policy: CapturePolicy = createCapturePolicy(), +): CapturedTelemetryPayload { + const captured: CapturedTelemetryPayload = { + metadata: redactMetadata(payload.metadata, policy), + }; + + if (policy.captureInputs && "input" in payload) { + captured.input = redactValue(payload.input); + } + if (policy.captureOutputs && "output" in payload) { + captured.output = redactValue(payload.output); + } + if (policy.captureToolIo && "toolInput" in payload) { + captured.toolInput = redactValue(payload.toolInput); + } + if (policy.captureToolIo && "toolOutput" in payload) { + captured.toolOutput = redactValue(payload.toolOutput); + } + if (policy.captureSystemPrompt && "systemPrompt" in payload) { + captured.systemPrompt = redactValue(payload.systemPrompt); + } + + return captured; +} diff --git a/extensions/pi-langfuse/src/commands.ts b/extensions/pi-langfuse/src/commands.ts new file mode 100644 index 0000000..2942a6b --- /dev/null +++ b/extensions/pi-langfuse/src/commands.ts @@ -0,0 +1,423 @@ +import { existsSync, readFileSync } from "node:fs"; + +import { CONFIG_PATH } from "./constants.js"; +import { + loadConfig, + loadConfigFromEnv, + loadConfigFromFile, + sanitizeConfigForLog, + saveConfig, + ensureConfig, +} from "./config.js"; +import { createCapturePolicy, type PrivacyPreset, type CapturePolicy } from "./capture-policy.js"; +import { getRuntime, getLastRuntimeError, forceShutdownRuntime as shutdownLangfuseRuntime } from "./langfuse.js"; +import { state } from "./state.js"; +import type { Config, LangfuseRuntime } from "./types.js"; + +const PRIVACY_PRESETS = ["metadata-only", "prompts-only", "conversations", "full-debug"] as const; + +export interface CommandContextLike { + hasUI?: boolean; + ui?: { + notify?: (message: string, level?: "info" | "warning" | "error") => void; + select?: (title: string, options: string[]) => Promise; + }; +} + +interface CommandDeps { + configPath?: string; + getRuntime?: () => Promise; + forceShutdownRuntime?: () => Promise; + env?: Record; + checkConnectivity?: (config: Config) => Promise; +} + +interface ConnectivityResult { + ok: boolean; + message: string; +} + +function notify(ctx: CommandContextLike, message: string, level: "info" | "warning" | "error" = "info") { + if (ctx.hasUI && ctx.ui?.notify) { + ctx.ui.notify(message, level); + return; + } + const prefix = level === "error" ? "❌" : level === "warning" ? "⚠️" : "📊"; + console.log(`${prefix} Langfuse: ${message}`); +} + +function parseCommandArgs(args: string): { values: Record; positional: string[]; malformed: string[] } { + const values: Record = {}; + const positional: string[] = []; + const malformed: string[] = []; + + for (const part of args.trim().split(/\s+/)) { + if (!part) { + continue; + } + const eq = part.indexOf("="); + if (eq === -1) { + positional.push(part); + continue; + } + if (eq === 0) { + malformed.push(part); + continue; + } + values[part.slice(0, eq)] = part.slice(eq + 1); + } + + return { values, positional, malformed }; +} + +function isPrivacyPreset(value: string | undefined): value is PrivacyPreset { + return PRIVACY_PRESETS.includes(value as PrivacyPreset); +} + +function inferPreset(policy: CapturePolicy): PrivacyPreset | "custom" { + const entries: Array<[PrivacyPreset, CapturePolicy]> = [ + [ + "metadata-only", + { + captureInputs: false, + captureOutputs: false, + captureToolIo: false, + captureSystemPrompt: false, + captureCwd: false, + }, + ], + [ + "prompts-only", + { + captureInputs: true, + captureOutputs: false, + captureToolIo: false, + captureSystemPrompt: false, + captureCwd: false, + }, + ], + [ + "conversations", + { + captureInputs: true, + captureOutputs: true, + captureToolIo: false, + captureSystemPrompt: false, + captureCwd: false, + }, + ], + [ + "full-debug", + { + captureInputs: true, + captureOutputs: true, + captureToolIo: true, + captureSystemPrompt: true, + captureCwd: true, + }, + ], + ]; + + for (const [preset, presetPolicy] of entries) { + if ( + policy.captureInputs === presetPolicy.captureInputs && + policy.captureOutputs === presetPolicy.captureOutputs && + policy.captureToolIo === presetPolicy.captureToolIo && + policy.captureSystemPrompt === presetPolicy.captureSystemPrompt && + policy.captureCwd === presetPolicy.captureCwd + ) { + return preset; + } + } + return "custom"; +} + +function describePolicy(policy: CapturePolicy) { + return [ + `captureInputs: ${policy.captureInputs}`, + `captureOutputs: ${policy.captureOutputs}`, + `captureToolIo: ${policy.captureToolIo}`, + `captureSystemPrompt: ${policy.captureSystemPrompt}`, + `captureCwd: ${policy.captureCwd}`, + ].join("\n"); +} + +function flag(value: boolean): "on" | "off" { + return value ? "on" : "off"; +} + +function readPersistedConfig(path: string) { + if (!existsSync(path)) { + return {}; + } + try { + return JSON.parse(readFileSync(path, "utf-8")) as Record; + } catch { + return {}; + } +} + +function hasActiveAgentObservation() { + for (const sessionState of state.sessionStates.values()) { + if (sessionState.agentState?.root) { + return true; + } + } + return false; +} + +function configSource(env: Record, configPath: string): string { + const fileConfig = loadConfigFromFile(configPath, env); + const envConfig = loadConfigFromEnv(env); + if (fileConfig && envConfig) { + return "config file (env capture flags may override saved privacy)"; + } + if (fileConfig) { + return "config file"; + } + if (envConfig) { + return "environment variables"; + } + return "none"; +} + +function lastErrorSummary() { + const lastError = getLastRuntimeError(); + if (!lastError) { + return "none"; + } + return `${lastError.scope}: ${lastError.message} (${lastError.timestamp.toISOString()})`; +} + +function formatStatus(configPath: string, env: Record) { + const config = loadConfig(env, configPath); + if (!config) { + return [ + "pi-langfuse status:", + "State: not configured", + `Config file: ${configPath}`, + "Action: run /langfuse-setup or set LANGFUSE_PUBLIC_KEY / LANGFUSE_SECRET_KEY", + `Last error: ${lastErrorSummary()}`, + ].join("\n"); + } + + const safeConfig = sanitizeConfigForLog(config); + const policy = config.capturePolicy ?? createCapturePolicy(env); + return [ + "pi-langfuse status:", + "State: configured", + `Source: ${configSource(env, configPath)}`, + `Host: ${safeConfig?.host ?? config.host}`, + `Public key: ${safeConfig?.publicKey ?? "[REDACTED_SECRET]"}`, + `Config file: ${configPath}`, + `Privacy preset: ${inferPreset(policy)}`, + "Capture:", + ` inputs: ${flag(policy.captureInputs)}`, + ` outputs: ${flag(policy.captureOutputs)}`, + ` tool IO: ${flag(policy.captureToolIo)}`, + ` system prompt: ${flag(policy.captureSystemPrompt)}`, + ` cwd: ${flag(policy.captureCwd)}`, + `Active run: ${hasActiveAgentObservation() ? "yes" : "no"}`, + `Last error: ${lastErrorSummary()}`, + ].join("\n"); +} + +async function checkLangfuseConnectivity(config: Config): Promise { + const host = config.host.replace(/\/+$/, ""); + const auth = Buffer.from(`${config.publicKey}:${config.secretKey}`).toString("base64"); + + try { + const response = await fetch(`${host}/api/public/projects`, { + headers: { + Authorization: `Basic ${auth}`, + }, + signal: AbortSignal.timeout(10_000), + }); + + if (response.ok) { + return { ok: true, message: `Connected to ${config.host}` }; + } + + return { + ok: false, + message: `${config.host} returned ${response.status} ${response.statusText}`.trim(), + }; + } catch (error) { + return { + ok: false, + message: error instanceof Error ? error.message : String(error), + }; + } +} + +export async function handleLangfuseStatusCommand( + args: string, + ctx: CommandContextLike, + deps: CommandDeps = {}, +): Promise { + const parsed = parseCommandArgs(args); + const unexpected = parsed.malformed[0] ?? parsed.positional[0] ?? Object.keys(parsed.values)[0]; + if (unexpected) { + notify(ctx, `Unexpected argument '${unexpected}'. Usage: /langfuse-status`, "warning"); + return false; + } + + const env = deps.env ?? process.env; + const configPath = deps.configPath ?? CONFIG_PATH; + notify(ctx, formatStatus(configPath, env)); + return true; +} + +function savePrivacyPreset( + requestedPreset: PrivacyPreset, + ctx: CommandContextLike, + configPath: string, +): boolean { + const existing = readPersistedConfig(configPath); + const loaded = state.config ?? loadConfig(process.env, configPath); + + const publicKey = existing.publicKey ?? loaded?.publicKey; + const secretKey = existing.secretKey ?? loaded?.secretKey; + const host = existing.host ?? loaded?.host; + + if (!publicKey || !secretKey || !host) { + notify(ctx, "Langfuse is not configured yet. Run /langfuse-setup before changing privacy settings.", "warning"); + return false; + } + + const nextConfig = { + publicKey: String(publicKey), + secretKey: String(secretKey), + host: String(host), + privacyPreset: requestedPreset, + }; + saveConfig(nextConfig, configPath); + state.config = loadConfig(process.env, configPath); + + notify(ctx, `Langfuse privacy preset saved: ${requestedPreset}\n${describePolicy(state.config?.capturePolicy ?? createCapturePolicy())}`); + return true; +} + +export async function handleLangfusePrivacyCommand( + args: string, + ctx: CommandContextLike, + deps: CommandDeps = {}, +): Promise { + const configPath = deps.configPath ?? CONFIG_PATH; + const parsed = parseCommandArgs(args); + if (parsed.malformed.length > 0) { + notify(ctx, `Couldn't understand '${parsed.malformed[0]}'. Use /langfuse-privacy preset=metadata-only.`, "warning"); + return false; + } + + const requestedPreset = parsed.values.preset ?? parsed.positional[0]; + if (!requestedPreset) { + state.config = state.config ?? loadConfig(process.env, configPath); + const policy = state.config?.capturePolicy ?? createCapturePolicy(); + if (ctx.hasUI && ctx.ui?.select) { + const currentPreset = inferPreset(policy); + const selectedPreset = await ctx.ui.select( + `Langfuse privacy preset (current: ${currentPreset})`, + [...PRIVACY_PRESETS], + ); + if (!selectedPreset) { + notify(ctx, `Current Langfuse privacy preset: ${currentPreset}\n${describePolicy(policy)}`); + return true; + } + if (!isPrivacyPreset(selectedPreset)) { + notify(ctx, `Unknown privacy preset '${selectedPreset}'. Use one of: ${PRIVACY_PRESETS.join(", ")}.`, "warning"); + return false; + } + return savePrivacyPreset(selectedPreset, ctx, configPath); + } + notify(ctx, `Current Langfuse privacy preset: ${inferPreset(policy)}\n${describePolicy(policy)}`); + return true; + } + + if (!isPrivacyPreset(requestedPreset)) { + notify( + ctx, + `Unknown privacy preset '${requestedPreset}'. Use one of: ${PRIVACY_PRESETS.join(", ")}.`, + "warning", + ); + return false; + } + + return savePrivacyPreset(requestedPreset, ctx, configPath); +} + +export async function handleLangfuseTestCommand( + args: string, + ctx: CommandContextLike, + deps: CommandDeps = {}, +): Promise { + const parsed = parseCommandArgs(args); + const unexpected = parsed.malformed[0] ?? parsed.positional[0] ?? Object.keys(parsed.values)[0]; + if (unexpected) { + notify(ctx, `Unexpected argument '${unexpected}'. Usage: /langfuse-test`, "warning"); + return false; + } + + if (!state.config && !(await ensureConfig(ctx))) { + notify(ctx, "Langfuse is not configured yet. Run /langfuse-setup first.", "warning"); + return false; + } + + if (hasActiveAgentObservation()) { + notify(ctx, "Langfuse test skipped because an agent run is active. Try again after the run finishes.", "warning"); + return false; + } + + const config = state.config; + if (!config) { + notify(ctx, "Langfuse is not configured yet. Run /langfuse-setup first.", "warning"); + return false; + } + + const connectivity = await (deps.checkConnectivity ?? checkLangfuseConnectivity)(config); + if (!connectivity.ok) { + notify(ctx, `Langfuse connectivity check failed: ${connectivity.message}`, "error"); + return false; + } + + let runtimeInitialized = false; + try { + const rt = await (deps.getRuntime ?? getRuntime)(); + runtimeInitialized = true; + rt.propagateAttributes( + { + traceName: "pi-langfuse-test", + metadata: { + source: "pi-langfuse", + command: "langfuse-test", + }, + }, + () => { + const observation = rt.startObservation( + "pi-langfuse-test", + { + input: { command: "/langfuse-test" }, + output: "ok", + metadata: { + source: "pi-langfuse", + command: "langfuse-test", + }, + }, + { asType: "span" }, + ); + observation.end(); + return observation; + }, + ); + notify(ctx, `Langfuse test succeeded. ${connectivity.message}; test trace sent to ${config.host}.`); + return true; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + notify(ctx, `Langfuse test failed: ${message}`, "error"); + return false; + } finally { + if (runtimeInitialized) { + await (deps.forceShutdownRuntime ?? shutdownLangfuseRuntime)(); + } + } +} diff --git a/extensions/pi-langfuse/src/config.ts b/extensions/pi-langfuse/src/config.ts new file mode 100644 index 0000000..6f210f5 --- /dev/null +++ b/extensions/pi-langfuse/src/config.ts @@ -0,0 +1,169 @@ +import { chmodSync, mkdirSync, readFileSync, existsSync, writeFileSync } from "node:fs"; +import { dirname } from "node:path"; +import type { Config } from "./types.js"; +import { CONFIG_PATH, DEFAULT_LANGFUSE_HOST } from "./constants.js"; +import { state } from "./state.js"; +import { forceShutdownRuntime } from "./langfuse.js"; +import { createCapturePolicy, type EnvLike } from "./capture-policy.js"; +import { createPayloadLimits } from "./limits.js"; + +export function loadConfigFromFile(path = CONFIG_PATH, env: EnvLike = process.env as EnvLike): Config | null { + if (existsSync(path)) { + try { + const content = readFileSync(path, "utf-8"); + const config = JSON.parse(content) as Config & { capture?: EnvLike; privacyPreset?: string }; + if (config.publicKey && config.secretKey) { + const captureSource: EnvLike = { + ...(config.capture ?? {}), + ...(config.privacyPreset ? { LANGFUSE_PRIVACY_PRESET: config.privacyPreset } : {}), + ...env, + }; + return { + publicKey: config.publicKey, + secretKey: config.secretKey, + host: config.host || DEFAULT_LANGFUSE_HOST, + capturePolicy: createCapturePolicy(captureSource), + limits: createPayloadLimits(env), + }; + } + } catch (e) { + console.warn("📊 Langfuse: Failed to load config.json", e); + } + } + + return null; +} + +export function loadConfigFromEnv(env: EnvLike = process.env as EnvLike): Config | null { + const publicKey = env.LANGFUSE_PUBLIC_KEY || ""; + const secretKey = env.LANGFUSE_SECRET_KEY || ""; + if (!publicKey || !secretKey) { + return null; + } + + return { + publicKey, + secretKey, + host: env.LANGFUSE_BASE_URL || env.LANGFUSE_HOST || DEFAULT_LANGFUSE_HOST, + capturePolicy: createCapturePolicy(env), + limits: createPayloadLimits(env), + }; +} + +export function loadConfig(env: EnvLike = process.env as EnvLike, path = CONFIG_PATH): Config | null { + return loadConfigFromFile(path, env) || loadConfigFromEnv(env); +} + +export function saveConfig(config: Config, path = CONFIG_PATH) { + mkdirSync(dirname(path), { recursive: true, mode: 0o700 }); + chmodSync(dirname(path), 0o700); + writeFileSync(path, `${JSON.stringify(config, null, 2)}\n`, { encoding: "utf-8", mode: 0o600 }); + chmodSync(path, 0o600); +} + +function maskPublicKey(value: string): string { + if (value.length <= 9) { + return "[REDACTED_SECRET]"; + } + return `${value.slice(0, 6)}...${value.slice(-4)}`; +} + +export function sanitizeConfigForLog(config: Pick | null): { + publicKey: string; + secretKey: string; + host: string; +} | null { + if (!config) { + return null; + } + + return { + publicKey: maskPublicKey(config.publicKey), + secretKey: "[REDACTED_SECRET]", + host: config.host || DEFAULT_LANGFUSE_HOST, + }; +} + +async function collectConfigFromUI(ctx: any, reason: string): Promise { + if (!ctx.hasUI) { + console.log(`📊 Langfuse: ${reason}. Run this extension in Pi UI to complete setup, or set LANGFUSE_PUBLIC_KEY / LANGFUSE_SECRET_KEY / LANGFUSE_BASE_URL.`); + return null; + } + + ctx.ui.notify("Langfuse setup required. Enter your API keys to enable tracing.", "info"); + + const publicKey = (await ctx.ui.input("Langfuse public key:", "pk-lf-..."))?.trim(); + if (!publicKey) { + ctx.ui.notify("Langfuse setup cancelled.", "warning"); + return null; + } + + const secretKey = (await ctx.ui.input("Langfuse secret key:", "sk-lf-..."))?.trim(); + if (!secretKey) { + ctx.ui.notify("Langfuse setup cancelled.", "warning"); + return null; + } + + const hostInput = (await ctx.ui.input("Langfuse host:", DEFAULT_LANGFUSE_HOST))?.trim(); + return { + publicKey, + secretKey, + host: hostInput || DEFAULT_LANGFUSE_HOST, + capturePolicy: createCapturePolicy(), + }; +} + +async function saveConfigFromUI(ctx: any, config: Config): Promise { + state.config = config; + + try { + saveConfig(state.config); + ctx.ui.notify(`Langfuse config saved to ${CONFIG_PATH}`, "info"); + return true; + } catch (error) { + console.warn("📊 Langfuse: Failed to save config.json", error); + ctx.ui.notify(`Failed to save Langfuse config.json to ${CONFIG_PATH}. Check Pi config directory permissions.`, "error"); + state.config = null; + return false; + } +} + +export async function ensureConfig(ctx: any): Promise { + if (!state.config) { + state.config = loadConfig(); + } + + if (state.config) { + return true; + } + + if (state.setupAttemptedThisSession) { + return false; + } + state.setupAttemptedThisSession = true; + + const config = await collectConfigFromUI(ctx, "Missing config"); + if (!config) { + return false; + } + + return saveConfigFromUI(ctx, config); +} + +export async function promptForConfig(ctx: any): Promise { + state.setupAttemptedThisSession = false; + state.config = null; + await forceShutdownRuntime(); + + const config = await collectConfigFromUI(ctx, "Manual setup requested"); + if (!config) { + state.config = loadConfig(); + return false; + } + + const saved = await saveConfigFromUI(ctx, config); + if (saved) { + ctx.ui.notify("Langfuse tracing enabled for future agent runs.", "info"); + } + return saved; +} diff --git a/extensions/pi-langfuse/src/constants.ts b/extensions/pi-langfuse/src/constants.ts new file mode 100644 index 0000000..79b5206 --- /dev/null +++ b/extensions/pi-langfuse/src/constants.ts @@ -0,0 +1,13 @@ +import { homedir } from "node:os"; +import { resolve } from "node:path"; + +export const CONFIG_DIR = resolve(homedir(), ".pi", "agent", "pi-langfuse"); +export const CONFIG_PATH = resolve(CONFIG_DIR, "config.json"); +export const DEFAULT_LANGFUSE_HOST = "https://cloud.langfuse.com"; + +export const MAX_STRING_LENGTH = 12_000; +export const MAX_TOOL_PAYLOAD_LENGTH = 24_000; +export const MAX_DEPTH = 6; +export const MAX_ARRAY_ITEMS = 50; +export const MAX_OBJECT_KEYS = 80; +export const MAX_PAYLOAD_NODES = 2_000; diff --git a/extensions/pi-langfuse/src/handlers/agent.ts b/extensions/pi-langfuse/src/handlers/agent.ts new file mode 100644 index 0000000..bdbceda --- /dev/null +++ b/extensions/pi-langfuse/src/handlers/agent.ts @@ -0,0 +1,173 @@ +import { state, resetRunState, computeEvaluationScores } from "../state.js"; +import { getRuntime, sendScore } from "../langfuse.js"; +import { ensureConfig } from "../config.js"; +import { shapePayload, truncate, extractFinalAssistant, extractAssistantOutput, getCapturePolicy, getLimits } from "../utils.js"; +import { closeDanglingObservations } from "./tool.js"; +import { applyCapturePolicy } from "../capture-policy.js"; +import { collectSourceMetadata } from "../source-metadata.js"; + +function stringMetadata(metadata: Record | undefined): Record | undefined { + if (!metadata) { + return undefined; + } + + const output: Record = {}; + for (const [key, value] of Object.entries(metadata)) { + if (typeof value === "string") { + output[key] = value; + } else if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") { + output[key] = String(value); + } + } + return Object.keys(output).length > 0 ? output : undefined; +} + +export function updateTraceIO(input?: unknown, output?: unknown) { + const root = state.agentState?.root; + if (!root?.setTraceIO) { + return; + } + + try { + root.setTraceIO({ input, output }); + } catch { + // Older SDKs may omit setTraceIO; root IO still mirrors trace IO in current Langfuse. + } +} + +export async function startAgentRun(event: Record, ctx: any) { + if (!(await ensureConfig(ctx))) { + state.isTracingDisabled = true; + return; + } + + try { + const rt = await getRuntime(); + const cwd = String( + (event.systemPromptOptions && typeof event.systemPromptOptions === "object" + ? (event.systemPromptOptions as Record).cwd + : undefined) ?? process.cwd(), + ); + + if (!state.currentModel && ctx.model) { + state.currentModel = ctx.model.id || ""; + state.currentProvider = ctx.model.provider || ""; + } + + let systemPrompt = undefined; + try { + if (ctx.getSystemPrompt) { + systemPrompt = await ctx.getSystemPrompt(); + } + } catch { + // Ignore if getSystemPrompt is not available or fails + } + + const rawPromptInput = shapePayload({ + prompt: event.prompt, + images: event.images, + context: event.context ?? event.attachments, + }); + const sourceMetadata = collectSourceMetadata(cwd); + const captured = applyCapturePolicy( + { + input: rawPromptInput, + metadata: { + cwd, + ...sourceMetadata, + ...(state.currentModel ? { model: state.currentModel } : {}), + ...(state.currentProvider ? { provider: state.currentProvider } : {}), + sessionId: state.currentSessionId || undefined, + }, + systemPrompt: systemPrompt ? truncate(String(systemPrompt), getLimits().maxString) : undefined, + }, + getCapturePolicy(), + ); + + state.agentState = { + cwd, + promptInput: captured.input, + generationSeq: 0, + activeGenerations: new Map(), + generationOrder: [], + activeTools: new Map(), + sourceMetadata, + providerMetadataByRequest: new Map(), + }; + + const root = rt.propagateAttributes( + { + sessionId: state.currentSessionId ? truncate(state.currentSessionId, 200) : undefined, + traceName: "pi-agent", + metadata: stringMetadata(captured.metadata), + }, + () => + rt.startObservation( + "pi-agent", + { + input: captured.input, + metadata: { + ...(captured.metadata ?? {}), + ...(captured.systemPrompt ? { systemPrompt: captured.systemPrompt } : {}), + }, + }, + { asType: "agent" }, + ), + ); + + state.agentState.root = root; + state.agentState.traceId = root.traceId; + updateTraceIO(captured.input, undefined); + } catch (e) { + console.warn("📊 Langfuse: Failed to create agent observation", e); + state.isTracingDisabled = true; + } +} + +export async function finishAgentRun(event: Record = {}) { + if (!state.agentState?.root) { + resetRunState(); + return; + } + + const lastAssistant = extractFinalAssistant(event.messages); + const rawOutput = lastAssistant ? extractAssistantOutput(lastAssistant) : state.agentState.latestAssistantOutput; + const captured = applyCapturePolicy( + { + output: rawOutput, + metadata: { + cwd: state.agentState.cwd, + ...(state.agentState.sourceMetadata ?? {}), + completed: true, + model: state.currentModel || undefined, + provider: state.currentProvider || undefined, + totalTools: state.toolCallCount, + ...computeEvaluationScores(), + }, + }, + getCapturePolicy(), + ); + const scores = computeEvaluationScores(); + + closeDanglingObservations("Agent run ended before observation finalized"); + + try { + state.agentState.root + .update({ + output: captured.output, + metadata: captured.metadata, + }) + .end(); + updateTraceIO(state.agentState.promptInput, captured.output); + + await sendScore("tool_call_count", scores.tool_call_count, { traceId: state.agentState.traceId }); + await sendScore("turn_count", scores.turn_count, { traceId: state.agentState.traceId }); + await sendScore("total_tool_errors", scores.total_tool_errors, { traceId: state.agentState.traceId }); + await sendScore("tool_success_rate", scores.tool_success_rate, { traceId: state.agentState.traceId }); + await sendScore("session_had_errors", scores.session_had_errors, { traceId: state.agentState.traceId }); + } catch (e) { + console.warn("📊 Langfuse: Failed to finish agent observation", e); + } finally { + resetRunState(); + } +} diff --git a/extensions/pi-langfuse/src/handlers/generation.ts b/extensions/pi-langfuse/src/handlers/generation.ts new file mode 100644 index 0000000..5f2b464 --- /dev/null +++ b/extensions/pi-langfuse/src/handlers/generation.ts @@ -0,0 +1,246 @@ +import { state } from "../state.js"; +import { getRuntime } from "../langfuse.js"; +import { startChildObservation } from "../observation.js"; +import { + getRequestKey, + getProviderPayload, + shapePayload, + extractResponseMetadata, + getMessageFromEvent, + extractAssistantOutput, + extractUsage, + extractCostDetails, + getCapturePolicy, + extractModelParameters, +} from "../utils.js"; +import type { GenerationState, ObservationUpdate } from "../types.js"; +import { applyCapturePolicy } from "../capture-policy.js"; + +export function getOpenGeneration(): GenerationState | undefined { + if (state.isTracingDisabled || !state.agentState) { + return undefined; + } + + for (let i = state.agentState.generationOrder.length - 1; i >= 0; i--) { + const key = state.agentState.generationOrder[i]; + const genState = state.agentState.activeGenerations.get(key); + if (genState && !genState.ended) { + return genState; + } + } + + return undefined; +} + +export async function startGeneration(event: Record) { + if (state.isTracingDisabled || !state.agentState?.root) { + return; + } + + try { + const key = getRequestKey(event, `generation-${++state.agentState.generationSeq}`); + const payload = getProviderPayload(event); + const modelParameters = extractModelParameters(payload); + const model = String(event.model ?? event.modelId ?? state.currentModel ?? ""); + const provider = String(event.provider ?? state.currentProvider ?? ""); + const metadata = shapePayload({ + provider, + requestId: key, + url: event.url, + method: event.method, + }) as Record; + const captured = applyCapturePolicy( + { + input: shapePayload(payload), + metadata, + }, + getCapturePolicy(), + ); + + const parent = state.agentState.activeTurn ?? state.agentState.root; + const generation = await startChildObservation({ + parent, + runtime: getRuntime, + name: "llm-generation", + body: { + input: captured.input, + model: model || undefined, + modelParameters, + metadata: captured.metadata, + }, + asType: "generation", + }); + + state.agentState.activeGenerations.set(key, { + observation: generation, + requestKey: key, + ended: false, + metadata: captured.metadata ?? {}, + modelParameters, + }); + state.agentState.generationOrder.push(key); + } catch (e) { + console.warn("📊 Langfuse: Failed to start generation", e); + } +} + +export function updateGenerationMetadata(event: Record) { + if (state.isTracingDisabled || !state.agentState) { + return; + } + + const key = getRequestKey(event, ""); + const metadata = applyCapturePolicy({ metadata: extractResponseMetadata(event) }, getCapturePolicy()).metadata ?? {}; + if (!key) { + const generation = getOpenGeneration(); + if (generation) { + generation.metadata = { ...generation.metadata, ...metadata }; + + const isError = + (typeof metadata.status === "number" && metadata.status >= 400) || + event.error || + event.isError; + + if (isError) { + generation.observation.update({ + metadata: generation.metadata, + level: "ERROR", + statusMessage: String(event.error ?? metadata.statusMessage ?? "Provider request failed") + }).end(); + generation.ended = true; + } else { + generation.observation.update({ metadata: generation.metadata }); + } + } + return; + } + + const generation = state.agentState.activeGenerations.get(key) ?? getOpenGeneration(); + if (generation) { + generation.metadata = { ...generation.metadata, ...metadata }; + + const isError = + (typeof metadata.status === "number" && metadata.status >= 400) || + event.error || + event.isError; + + if (isError) { + generation.observation.update({ + metadata: generation.metadata, + level: "ERROR", + statusMessage: String(event.error ?? metadata.statusMessage ?? "Provider request failed") + }).end(); + generation.ended = true; + } else { + generation.observation.update({ metadata: generation.metadata }); + } + } +} + +export function recordTTFT(event: Record) { + if (state.isTracingDisabled || !state.agentState) { + return; + } + + const key = getRequestKey(event, ""); + const generation = key ? state.agentState.activeGenerations.get(key) : getOpenGeneration(); + + if (generation && !generation.ttftRecorded && !generation.ended) { + generation.ttftRecorded = true; + try { + generation.observation.update({ completionStartTime: new Date() }); + } catch (e) { + // Ignore + } + } +} + +export async function finishGenerationFromMessage(event: Record) { + if (state.isTracingDisabled || !state.agentState) { + return; + } + + const message = getMessageFromEvent(event); + if (!message || message.role !== "assistant") { + return; + } + + const generation = getOpenGeneration(); + const rawOutput = extractAssistantOutput(message); + const captured = applyCapturePolicy({ output: rawOutput }, getCapturePolicy()); + const output = captured.output; + state.agentState.latestAssistantOutput = output; + + if (!generation) { + return; + } + + const usageDetails = extractUsage({ ...event, message }); + const costDetails = extractCostDetails({ ...event, message }); + const modelParameters = extractModelParameters(getProviderPayload(event)) ?? generation.modelParameters; + const model = String(message.model ?? event.model ?? state.currentModel ?? ""); + const update: ObservationUpdate = { + output, + model: model || undefined, + modelParameters, + usageDetails, + ...(costDetails ? { costDetails } : {}), + metadata: { + ...generation.metadata, + finishReason: message.finishReason ?? message.stopReason ?? event.finishReason, + }, + }; + update.metadata = applyCapturePolicy({ metadata: update.metadata }, getCapturePolicy()).metadata; + + try { + generation.observation.update(update).end(); + generation.ended = true; + } catch (e) { + console.warn("📊 Langfuse: Failed to finish generation", e); + } +} + +export async function createFallbackGenerationFromTurn(event: Record, message: Record) { + if (state.isTracingDisabled || !state.agentState?.root || state.agentState.generationOrder.length > 0) { + return; + } + + try { + const usageDetails = extractUsage({ ...event, message }); + const costDetails = extractCostDetails({ ...event, message }); + const modelParameters = extractModelParameters(getProviderPayload(event)); + const model = String(message.model ?? event.model ?? state.currentModel ?? ""); + const captured = applyCapturePolicy( + { + input: state.agentState.promptInput, + output: extractAssistantOutput(message), + metadata: { + provider: state.currentProvider || undefined, + sourceEvent: "turn_end", + }, + }, + getCapturePolicy(), + ); + const parent = state.agentState.activeTurn ?? state.agentState.root; + const generation = await startChildObservation({ + parent, + runtime: getRuntime, + name: "llm-generation", + body: { + input: captured.input, + output: captured.output, + model: model || undefined, + modelParameters, + usageDetails, + ...(costDetails ? { costDetails } : {}), + metadata: captured.metadata, + }, + asType: "generation", + }); + + generation.end(); + state.agentState.generationOrder.push("turn-end-fallback"); + } catch (e) { + console.warn("📊 Langfuse: Failed to create fallback generation", e); + } +} diff --git a/extensions/pi-langfuse/src/handlers/tool.ts b/extensions/pi-langfuse/src/handlers/tool.ts new file mode 100644 index 0000000..9730f7c --- /dev/null +++ b/extensions/pi-langfuse/src/handlers/tool.ts @@ -0,0 +1,156 @@ +import { state } from "../state.js"; +import { getRuntime, sendScore } from "../langfuse.js"; +import { startChildObservation } from "../observation.js"; +import { + getToolCallId, + getToolName, + getToolInput, + shapePayload, + extractTextContent, + truncate, + estimatePayloadBytes, + getCapturePolicy, + getLimits, +} from "../utils.js"; +import { applyCapturePolicy } from "../capture-policy.js"; +import { redactString } from "../redaction.js"; + +export async function startToolObservation(event: Record) { + if (state.isTracingDisabled || !state.agentState?.root) { + return; + } + + const toolCallId = getToolCallId(event); + if (!toolCallId || state.agentState.activeTools.has(toolCallId)) { + return; + } + + try { + const toolName = getToolName(event); + const toolInput = getToolInput(event); + const shapedInput = shapePayload(toolInput, { maxString: getLimits().maxToolPayload }); + const captured = applyCapturePolicy( + { + toolInput: shapedInput, + metadata: { toolName, toolCallId }, + }, + getCapturePolicy(), + ); + const inputBytes = estimatePayloadBytes(captured.toolInput, getLimits().maxToolPayload); + const parent = state.agentState.activeTurn ?? state.agentState.root; + const tool = await startChildObservation({ + parent, + runtime: getRuntime, + name: toolName, + body: { + input: captured.toolInput, + metadata: { ...(captured.metadata ?? {}), inputBytes }, + }, + asType: "tool", + }); + + state.toolCallCount++; + state.agentState.activeTools.set(toolCallId, { + observation: tool, + toolName, + ended: false, + startedAt: Date.now(), + inputBytes, + }); + } catch (e) { + console.warn("📊 Langfuse: Failed to start tool observation", e); + } +} + +export async function finishToolObservation(event: Record) { + if (state.isTracingDisabled || !state.agentState) { + return; + } + + const toolCallId = getToolCallId(event); + if (!toolCallId) { + return; + } + + const activeTool = state.agentState.activeTools.get(toolCallId); + if (!activeTool || activeTool.ended) { + return; + } + + const isError = Boolean(event.isError ?? event.error ?? event.status === "error"); + const output = + extractTextContent(event.content, getLimits().maxToolPayload) ?? + event.output ?? + event.result ?? + event.error ?? + event.content ?? + event; + + try { + const shapedOutput = shapePayload(output, { maxString: getLimits().maxToolPayload }); + const captured = applyCapturePolicy( + { + toolOutput: shapedOutput, + metadata: { + toolName: activeTool.toolName, + toolCallId, + isError, + }, + }, + getCapturePolicy(), + ); + const outputBytes = estimatePayloadBytes(captured.toolOutput, getLimits().maxToolPayload); + const durationMs = Math.max(0, Date.now() - activeTool.startedAt); + + activeTool.observation + .update({ + output: captured.toolOutput, + level: isError ? "ERROR" : "DEFAULT", + statusMessage: isError ? redactString(truncate(String(event.error ?? output), 1_000)) : undefined, + metadata: { + ...(captured.metadata ?? {}), + durationMs, + inputBytes: activeTool.inputBytes, + outputBytes, + }, + }) + .end(); + activeTool.ended = true; + + if (isError) { + state.errorCount++; + await sendScore("tool_is_error", 1, { + traceId: state.agentState.traceId, + observationId: activeTool.observation.id, + }); + } + } catch (e) { + console.warn("📊 Langfuse: Failed to finish tool observation", e); + } finally { + state.agentState.activeTools.delete(toolCallId); + } +} + +export function closeDanglingObservations(statusMessage: string) { + if (state.isTracingDisabled || !state.agentState) { + return; + } + + for (const activeTool of state.agentState.activeTools.values()) { + if (!activeTool.ended) { + activeTool.observation + .update({ level: "WARNING", statusMessage, metadata: { toolName: activeTool.toolName, cancelled: true } }) + .end(); + activeTool.ended = true; + } + } + + for (const generation of state.agentState.activeGenerations.values()) { + if (!generation.ended) { + generation.observation.update({ level: "WARNING", statusMessage, metadata: { ...generation.metadata, cancelled: true } }).end(); + generation.ended = true; + } + } + + state.agentState.activeTools.clear(); +} diff --git a/extensions/pi-langfuse/src/handlers/turn.ts b/extensions/pi-langfuse/src/handlers/turn.ts new file mode 100644 index 0000000..bf88aca --- /dev/null +++ b/extensions/pi-langfuse/src/handlers/turn.ts @@ -0,0 +1,55 @@ +import { state } from "../state.js"; +import { getRuntime } from "../langfuse.js"; +import { startChildObservation } from "../observation.js"; +import { shapePayload, getCapturePolicy } from "../utils.js"; +import { applyCapturePolicy } from "../capture-policy.js"; + +export async function startTurnObservation(event: Record) { + if (state.isTracingDisabled || !state.agentState?.root) { + return; + } + + // If a turn is already active, close it (fallback safety) + if (state.agentState.activeTurn) { + state.agentState.activeTurn.end(); + state.agentState.activeTurn = undefined; + } + + try { + const turnIndex = event.turnIndex ?? state.turnCount; + const captured = applyCapturePolicy( + { + input: shapePayload(event.context ?? event), + metadata: { turnIndex }, + }, + getCapturePolicy(), + ); + const observation = await startChildObservation({ + parent: state.agentState.root, + runtime: getRuntime, + name: "turn", + body: { + input: captured.input, + metadata: captured.metadata, + }, + asType: "span", + }); + + state.agentState.activeTurn = observation; + } catch (e) { + console.warn("📊 Langfuse: Failed to start turn observation", e); + } +} + +export function finishTurnObservation(_event?: Record) { + if (state.isTracingDisabled || !state.agentState?.activeTurn) { + return; + } + + try { + state.agentState.activeTurn.end(); + state.agentState.activeTurn = undefined; + } catch (e) { + console.warn("📊 Langfuse: Failed to finish turn observation", e); + } +} diff --git a/extensions/pi-langfuse/src/langfuse.ts b/extensions/pi-langfuse/src/langfuse.ts new file mode 100644 index 0000000..b6b1a31 --- /dev/null +++ b/extensions/pi-langfuse/src/langfuse.ts @@ -0,0 +1,756 @@ +import type { LangfuseRuntime, LangfuseScoreClient, PendingScore } from "./types.js"; +import { state } from "./state.js"; +import { randomUUID } from "node:crypto"; + +let runtime: LangfuseRuntime | null = null; +let registeredContextManager: OtelContextManager | null = null; +const activeSessions = new Set(); +let lastRuntimeError: { scope: string; message: string; timestamp: Date } | null = null; + +type FallbackObservationType = "SPAN" | "GENERATION"; + +interface OtelContextManager { + enable(): OtelContextManager; + disable(): void; +} + +interface OtelContextApi { + setGlobalContextManager(contextManager: OtelContextManager): boolean; +} + +type AsyncHooksContextManagerCtor = new () => OtelContextManager; + +interface RestFallbackTrace { + id: string; + timestamp: string; + name: string; + input?: unknown; + output?: unknown; + sessionId?: string; + metadata?: Record; +} + +interface RestFallbackObservation { + id: string; + traceId: string; + type: FallbackObservationType; + name: string; + startTime: string; + endTime?: string; + parentObservationId?: string; + input?: unknown; + output?: unknown; + metadata?: Record; + model?: string; + modelParameters?: Record; + usageDetails?: Record; + costDetails?: Record; + level?: "DEBUG" | "DEFAULT" | "WARNING" | "ERROR"; + statusMessage?: string; + completionStartTime?: string; +} + +interface RestFallbackStore { + trace?: RestFallbackTrace; + observations: RestFallbackObservation[]; + observationById: Map; + attempted: boolean; +} + +const OTEL_VISIBILITY_TIMEOUT_MS = 1_500; +const OTEL_VISIBILITY_POLL_INTERVAL_MS = 200; +const DEFAULT_SHUTDOWN_STEP_TIMEOUT_MS = 2_000; +const DEFAULT_LANGFUSE_REQUEST_TIMEOUT_SECONDS = 5; +const DEFAULT_SCORE_FLUSH_AT = 10; +const DEFAULT_SCORE_FLUSH_INTERVAL_MS = 1_000; +const MAX_SCORE_QUEUE_SIZE = 100_000; +const MAX_SCORE_BATCH_SIZE = 100; + +let shutdownStepTimeoutMs = DEFAULT_SHUTDOWN_STEP_TIMEOUT_MS; + +function nowIso() { + return new Date().toISOString(); +} + +function resolvePositiveEnvNumber(name: string, fallback: number, integer = false): number { + const parsed = Number(process.env[name]); + if (!Number.isFinite(parsed) || parsed <= 0) { + return fallback; + } + return integer ? Math.floor(parsed) : parsed; +} + +function delay(ms: number, signal?: AbortSignal) { + return new Promise((resolve, reject) => { + if (signal?.aborted) { + reject(signal.reason); + return; + } + + const timeout = setTimeout(resolve, ms); + signal?.addEventListener("abort", () => { + clearTimeout(timeout); + reject(signal.reason); + }, { once: true }); + }); +} + +function debugLog(message: string) { + if (process.env.PI_LANGFUSE_DEBUG === "1" || process.env.PI_LANGFUSE_DEBUG === "true") { + console.log(message); + } +} + +export function ensureOtelContextManager( + contextApi: OtelContextApi, + AsyncHooksContextManager: AsyncHooksContextManagerCtor, +): boolean { + if (registeredContextManager) { + return true; + } + + const contextManager = new AsyncHooksContextManager().enable(); + if (contextApi.setGlobalContextManager(contextManager)) { + registeredContextManager = contextManager; + return true; + } + + contextManager.disable(); + return false; +} + +function rememberRuntimeError(scope: string, error: unknown) { + lastRuntimeError = { + scope, + message: error instanceof Error ? error.message : String(error), + timestamp: new Date(), + }; +} + +export function getLastRuntimeError(): { scope: string; message: string; timestamp: Date } | null { + return lastRuntimeError; +} + +async function withShutdownDeadline(label: string, startOperation: () => Promise | undefined, deadline: number): Promise { + const remainingMs = deadline - Date.now(); + if (remainingMs <= 0) { + debugLog(`📊 Langfuse: Skipped ${label}; shutdown deadline elapsed`); + return undefined; + } + + const operation = startOperation(); + if (!operation) { + return undefined; + } + + let timeout: NodeJS.Timeout | undefined; + try { + return await Promise.race([ + operation, + new Promise((resolve) => { + timeout = setTimeout(() => { + debugLog(`📊 Langfuse: ${label} timed out; shutdown deadline elapsed`); + resolve(undefined); + }, remainingMs); + }), + ]); + } finally { + if (timeout) { + clearTimeout(timeout); + } + } +} + +function getRuntimeConfig(rt: LangfuseRuntime) { + return rt.runtimeConfig ?? state.config; +} + +function ingestionHeaders(rt: LangfuseRuntime): Record { + const config = getRuntimeConfig(rt); + if (!config) { + throw new Error("Langfuse runtime config is unavailable"); + } + + const auth = Buffer.from(`${config.publicKey}:${config.secretKey}`).toString("base64"); + return { + Authorization: `Basic ${auth}`, + "Content-Type": "application/json", + }; +} + +async function ingestBatch(rt: LangfuseRuntime, batch: unknown[], signal: AbortSignal): Promise { + const config = getRuntimeConfig(rt); + if (!config) { + throw new Error("Langfuse runtime config is unavailable"); + } + + const response = await fetch(`${config.host.replace(/\/$/, "")}/api/public/ingestion`, { + method: "POST", + headers: ingestionHeaders(rt), + body: JSON.stringify({ batch }), + signal, + }); + if (!response.ok) { + throw new Error(`Langfuse ingestion failed with HTTP ${response.status}`); + } + + const text = await response.text(); + if (!text) { + return []; + } + + const responseBody = JSON.parse(text) as { errors?: unknown[] }; + return Array.isArray(responseBody.errors) ? responseBody.errors : []; +} + +async function flushPendingScores(rt: LangfuseRuntime, signal: AbortSignal): Promise { + const pendingScores = rt.pendingScores; + if (!pendingScores || pendingScores.length === 0) { + return; + } + + while (pendingScores.length > 0) { + const scores = pendingScores.slice(0, MAX_SCORE_BATCH_SIZE); + try { + const errors = await ingestBatch( + rt, + scores.map((score) => ({ + type: "score-create", + id: randomUUID(), + timestamp: nowIso(), + body: score, + })), + signal, + ); + pendingScores.splice(0, scores.length); + if (errors.length > 0) { + rememberRuntimeError("score ingestion", new Error(JSON.stringify(errors))); + console.warn("📊 Langfuse: Score ingestion reported errors", errors); + } + } catch (error) { + if ((error as { name?: string }).name !== "AbortError") { + rememberRuntimeError("score ingestion", error); + console.warn("📊 Langfuse: Failed to flush scores", error); + } + return; + } + } +} + +function clearScoreFlushTimer(rt: LangfuseRuntime) { + if (rt.scoreFlushTimer) { + clearTimeout(rt.scoreFlushTimer); + rt.scoreFlushTimer = undefined; + } +} + +function scheduleScoreFlush(rt: LangfuseRuntime) { + if ( + rt.scoreFlushStopped + || rt.scoreFlushTimer + || rt.scoreFlushPromise + || !rt.pendingScores?.length + ) { + return; + } + + rt.scoreFlushTimer = setTimeout(() => { + rt.scoreFlushTimer = undefined; + void startScoreFlush(rt); + }, rt.scoreFlushIntervalMs ?? DEFAULT_SCORE_FLUSH_INTERVAL_MS); + rt.scoreFlushTimer.unref?.(); +} + +function startScoreFlush(rt: LangfuseRuntime): Promise { + if (rt.scoreFlushPromise) { + return rt.scoreFlushPromise; + } + + clearScoreFlushTimer(rt); + const controller = new AbortController(); + const timeout = setTimeout( + () => controller.abort(new DOMException("Langfuse score request timed out", "AbortError")), + rt.scoreRequestTimeoutMs ?? DEFAULT_LANGFUSE_REQUEST_TIMEOUT_SECONDS * 1_000, + ); + timeout.unref?.(); + rt.scoreFlushController = controller; + + const promise = flushPendingScores(rt, controller.signal).finally(() => { + clearTimeout(timeout); + if (rt.scoreFlushPromise === promise) { + rt.scoreFlushPromise = undefined; + } + if (rt.scoreFlushController === controller) { + rt.scoreFlushController = undefined; + } + scheduleScoreFlush(rt); + }); + rt.scoreFlushPromise = promise; + return promise; +} + +function stopScoreFlush(rt: LangfuseRuntime) { + rt.scoreFlushStopped = true; + clearScoreFlushTimer(rt); + rt.scoreFlushController?.abort( + new DOMException("Langfuse score flushing stopped", "AbortError"), + ); +} + +function toIso(value: unknown): string | undefined { + if (!value) { + return undefined; + } + if (value instanceof Date) { + return value.toISOString(); + } + if (typeof value === "string") { + return value; + } + return undefined; +} + +function mergeMetadata(current: Record | undefined, next: Record | undefined) { + return next ? { ...(current ?? {}), ...next } : current; +} + +function applyObservationUpdate(record: RestFallbackObservation, body: Record | undefined) { + if (!body) { + return; + } + + if ("input" in body) record.input = body.input; + if ("output" in body) record.output = body.output; + if ("metadata" in body && body.metadata && typeof body.metadata === "object") { + record.metadata = mergeMetadata(record.metadata, body.metadata as Record); + } + if (typeof body.model === "string") record.model = body.model; + if (body.modelParameters && typeof body.modelParameters === "object") { + record.modelParameters = body.modelParameters as Record; + } + if (body.usageDetails && typeof body.usageDetails === "object") { + record.usageDetails = body.usageDetails as Record; + } + if (body.costDetails && typeof body.costDetails === "object") { + record.costDetails = body.costDetails as Record; + } + if (typeof body.level === "string") record.level = body.level as RestFallbackObservation["level"]; + if (typeof body.statusMessage === "string") record.statusMessage = body.statusMessage; + const completionStartTime = toIso(body.completionStartTime); + if (completionStartTime) record.completionStartTime = completionStartTime; +} + +function applyTraceUpdate(store: RestFallbackStore, body: Record | undefined) { + if (!store.trace || !body) { + return; + } + + if ("input" in body) store.trace.input = body.input; + if ("output" in body) store.trace.output = body.output; + if ("metadata" in body && body.metadata && typeof body.metadata === "object") { + store.trace.metadata = mergeMetadata(store.trace.metadata, body.metadata as Record); + } +} + +function observationType(asType?: string): FallbackObservationType { + return asType === "generation" ? "GENERATION" : "SPAN"; +} + +function wrapObservation( + observation: any, + store: RestFallbackStore, + name: string, + body: Record | undefined, + asType?: string, + parentObservationId?: string, +): any { + const id = observation.id || randomUUID(); + const traceId = observation.traceId || store.trace?.id || randomUUID(); + const metadata = body?.metadata && typeof body.metadata === "object" ? body.metadata as Record : undefined; + const record: RestFallbackObservation = { + id, + traceId, + name, + type: observationType(asType), + startTime: nowIso(), + parentObservationId, + metadata: mergeMetadata(metadata, asType && asType !== "generation" && asType !== "span" ? { langfuseObservationType: asType } : undefined), + }; + applyObservationUpdate(record, body); + + store.observations.push(record); + store.observationById.set(id, record); + + if (!parentObservationId && !store.trace) { + store.trace = { + id: traceId, + timestamp: record.startTime, + name, + input: body?.input, + sessionId: typeof metadata?.sessionId === "string" ? metadata.sessionId : state.currentSessionId || undefined, + metadata, + }; + } + + return { + ...observation, + id, + traceId, + update(updateBody?: Record) { + applyObservationUpdate(record, updateBody); + if (!parentObservationId) { + applyTraceUpdate(store, updateBody); + } + const updated = observation.update(updateBody); + return updated === observation ? this : updated; + }, + end(endBody?: Record) { + if (endBody && typeof endBody === "object") { + applyObservationUpdate(record, endBody); + if (!parentObservationId) { + applyTraceUpdate(store, endBody); + } + } + record.endTime = nowIso(); + return observation.end(); + }, + startObservation(childName: string, childBody?: Record, options?: { asType?: string }) { + const child = observation.startObservation(childName, childBody, options); + return wrapObservation(child, store, childName, childBody, options?.asType, id); + }, + setTraceIO(traceBody?: { input?: unknown; output?: unknown }) { + applyTraceUpdate(store, traceBody); + return observation.setTraceIO?.(traceBody); + }, + }; +} + +async function traceExists(rt: LangfuseRuntime, traceId: string, signal: AbortSignal): Promise { + const config = getRuntimeConfig(rt); + if (!config) { + return false; + } + + try { + const response = await fetch( + `${config.host.replace(/\/$/, "")}/api/public/traces/${encodeURIComponent(traceId)}`, + { + headers: ingestionHeaders(rt), + signal, + }, + ); + if (response.status === 404) { + return false; + } + if (!response.ok) { + throw new Error(`Langfuse trace visibility check failed with HTTP ${response.status}`); + } + return true; + } catch (error) { + if (signal.aborted) { + throw error; + } + return false; + } +} + +async function waitForTraceVisibility(rt: LangfuseRuntime, traceId: string, signal: AbortSignal): Promise { + const deadline = Date.now() + OTEL_VISIBILITY_TIMEOUT_MS; + + while (true) { + if (await traceExists(rt, traceId, signal)) { + return true; + } + + const remainingMs = deadline - Date.now(); + if (remainingMs <= 0) { + return false; + } + + await delay(Math.min(OTEL_VISIBILITY_POLL_INTERVAL_MS, remainingMs), signal); + } +} + +function eventTimestamp(record: { endTime?: string; startTime?: string; timestamp?: string }) { + return record.endTime ?? record.startTime ?? record.timestamp ?? nowIso(); +} + +async function fallbackToRestIngestion(rt: LangfuseRuntime, signal: AbortSignal) { + const store = rt.restFallback as RestFallbackStore | undefined; + if (!store?.trace || store.attempted) { + return; + } + store.attempted = true; + + if (await waitForTraceVisibility(rt, store.trace.id, signal)) { + return; + } + + const trace = store.trace; + const batch: any[] = [ + { + type: "trace-create", + id: randomUUID(), + timestamp: eventTimestamp(trace), + body: { + id: trace.id, + timestamp: trace.timestamp, + name: trace.name, + input: trace.input, + output: trace.output, + sessionId: trace.sessionId, + metadata: trace.metadata, + }, + }, + ]; + + for (const observation of store.observations) { + const body = { + id: observation.id, + traceId: observation.traceId, + name: observation.name, + startTime: observation.startTime, + endTime: observation.endTime, + input: observation.input, + output: observation.output, + metadata: observation.metadata, + level: observation.level, + statusMessage: observation.statusMessage, + parentObservationId: observation.parentObservationId, + ...(observation.type === "GENERATION" + ? { + completionStartTime: observation.completionStartTime, + model: observation.model, + modelParameters: observation.modelParameters, + usageDetails: observation.usageDetails, + costDetails: observation.costDetails, + } + : {}), + }; + batch.push({ + type: observation.type === "GENERATION" ? "generation-create" : "span-create", + id: randomUUID(), + timestamp: eventTimestamp(observation), + body, + }); + } + + const errors = await ingestBatch(rt, batch, signal); + if (errors.length > 0) { + rememberRuntimeError("REST fallback ingestion", new Error(JSON.stringify(errors))); + console.warn("📊 Langfuse: REST fallback ingestion reported errors", errors); + } else { + debugLog(`📊 Langfuse: OTel trace ${trace.id} was not visible; wrote fallback trace via REST ingestion`); + } +} + +export async function getRuntime(): Promise { + if (!state.config) { + throw new Error("Langfuse config is not set"); + } + + // Track the current session as a runtime consumer. + // Multiple sessions can share the same runtime; shutdown is deferred + // until the last session releases it. + const sessionId = state.currentSessionId; + if (sessionId) { + activeSessions.add(sessionId); + } + + if (!runtime) { + const [ + { BasicTracerProvider }, + { context }, + { AsyncHooksContextManager }, + { LangfuseSpanProcessor }, + tracing, + { LangfuseClient }, + ] = await Promise.all([ + import("@opentelemetry/sdk-trace-base"), + import("@opentelemetry/api"), + import("@opentelemetry/context-async-hooks"), + import("@langfuse/otel"), + import("@langfuse/tracing"), + import("@langfuse/client"), + ]); + + const restFallback: RestFallbackStore = { + observations: [], + observationById: new Map(), + attempted: false, + }; + + try { + ensureOtelContextManager(context, AsyncHooksContextManager); + const scoreFlushAt = resolvePositiveEnvNumber("LANGFUSE_FLUSH_AT", DEFAULT_SCORE_FLUSH_AT, true); + const scoreFlushIntervalMs = + resolvePositiveEnvNumber("LANGFUSE_FLUSH_INTERVAL", DEFAULT_SCORE_FLUSH_INTERVAL_MS / 1_000) * 1_000; + const scoreRequestTimeoutMs = + resolvePositiveEnvNumber("LANGFUSE_TIMEOUT", DEFAULT_LANGFUSE_REQUEST_TIMEOUT_SECONDS) * 1_000; + const spanProcessor = new LangfuseSpanProcessor({ + publicKey: state.config.publicKey, + secretKey: state.config.secretKey, + baseUrl: state.config.host, + }); + const tracerProvider = new BasicTracerProvider({ spanProcessors: [spanProcessor] }); + tracing.setLangfuseTracerProvider(tracerProvider); + + runtime = { + startObservation: ((name: string, body?: Record, options?: { asType?: string }) => { + const observation = (tracing as any).startObservation(name, body, options); + return wrapObservation(observation, restFallback, name, body, options?.asType); + }) as unknown as LangfuseRuntime["startObservation"], + propagateAttributes: tracing.propagateAttributes as unknown as LangfuseRuntime["propagateAttributes"], + scoreClient: new LangfuseClient({ + publicKey: state.config.publicKey, + secretKey: state.config.secretKey, + baseUrl: state.config.host, + }) as LangfuseScoreClient, + spanProcessor, + tracerProvider, + clearTracerProvider: () => tracing.setLangfuseTracerProvider(null), + restFallback, + pendingScores: [], + scoreFlushAt, + scoreFlushIntervalMs, + scoreRequestTimeoutMs, + scoreFlushStopped: false, + runtimeConfig: { + publicKey: state.config.publicKey, + secretKey: state.config.secretKey, + host: state.config.host, + }, + }; + lastRuntimeError = null; + } catch (e) { + rememberRuntimeError("runtime init", e); + throw e; + } + } + + return runtime as LangfuseRuntime; +} + +function doShutdownRuntime(): Promise { + return (async () => { + if (!runtime) { + return; + } + + const rt = runtime; + runtime = null; + const deadline = Date.now() + shutdownStepTimeoutMs; + const controller = new AbortController(); + const abortTimeout = setTimeout(() => controller.abort(), shutdownStepTimeoutMs); + stopScoreFlush(rt); + + try { + await withShutdownDeadline( + "Active score flush", + () => rt.scoreFlushPromise, + deadline, + ); + await withShutdownDeadline("OTel force flush", () => rt.tracerProvider?.forceFlush?.(), deadline); + await withShutdownDeadline( + "REST fallback ingestion", + () => fallbackToRestIngestion(rt, controller.signal), + deadline, + ); + await flushPendingScores(rt, controller.signal); + await withShutdownDeadline("Langfuse score flush", () => rt.scoreClient.flush?.(), deadline); + await withShutdownDeadline("Langfuse client shutdown", () => rt.scoreClient.shutdown?.(), deadline); + await withShutdownDeadline("OTel tracer shutdown", () => rt.tracerProvider?.shutdown?.(), deadline); + } catch (e) { + rememberRuntimeError("runtime shutdown", e); + console.warn("📊 Langfuse: Failed to flush/shutdown cleanly", e); + } finally { + clearTimeout(abortTimeout); + clearScoreFlushTimer(rt); + rt.scoreFlushController?.abort(); + rt.scoreFlushController = undefined; + rt.scoreFlushPromise = undefined; + if (!runtime) { + rt.clearTracerProvider?.(); + } + } + })(); +} + +/** + * Release the current session's reference to the Langfuse runtime. + * Only actually shuts down the runtime when the last session releases it. + * Accepts an optional sessionId for use outside of withSession (e.g. deferred callbacks). + */ +export async function shutdownRuntime(sessionId?: string): Promise { + const sid = sessionId ?? state.currentSessionId; + if (sid) { + activeSessions.delete(sid); + } + + // Still have active sessions — keep the runtime alive. + if (activeSessions.size > 0) { + return; + } + + await doShutdownRuntime(); +} + +/** + * Force-shutdown the Langfuse runtime regardless of active session references. + * Used when the user manually reconfigures (e.g. /langfuse-setup) and needs + * a fresh runtime with new credentials. + */ +export async function forceShutdownRuntime(): Promise { + activeSessions.clear(); + await doShutdownRuntime(); +} + +export function __setRuntimeForTest(rt: LangfuseRuntime | null, timeoutMs = DEFAULT_SHUTDOWN_STEP_TIMEOUT_MS): void { + if (runtime && runtime !== rt) { + stopScoreFlush(runtime); + } + runtime = rt; + if (rt) { + rt.scoreFlushStopped = false; + } + shutdownStepTimeoutMs = timeoutMs; + activeSessions.clear(); +} + +export async function sendScore(name: string, value: number, options: { traceId?: string; observationId?: string } = {}) { + try { + const rt = await getRuntime(); + const score: PendingScore = { + name, + value, + dataType: name === "session_had_errors" || name === "tool_is_error" ? "BOOLEAN" : "NUMERIC", + traceId: options.traceId, + observationId: options.observationId, + sessionId: options.traceId ? undefined : state.currentSessionId || undefined, + ...(process.env.LANGFUSE_TRACING_ENVIRONMENT + ? { environment: process.env.LANGFUSE_TRACING_ENVIRONMENT } + : {}), + }; + if (!rt.pendingScores) { + return; + } + if (rt.pendingScores.length >= MAX_SCORE_QUEUE_SIZE) { + const error = new Error( + `Langfuse score queue is full (${MAX_SCORE_QUEUE_SIZE}); dropping score`, + ); + rememberRuntimeError("score queue", error); + console.warn(`📊 Langfuse: ${error.message}`); + return; + } + + rt.pendingScores.push(score); + if (rt.pendingScores.length >= (rt.scoreFlushAt ?? DEFAULT_SCORE_FLUSH_AT)) { + void startScoreFlush(rt); + } else { + scheduleScoreFlush(rt); + } + } catch (e) { + rememberRuntimeError(`score ${name}`, e); + console.warn(`📊 Langfuse: Failed to send score ${name}`, e); + } +} diff --git a/extensions/pi-langfuse/src/limits.ts b/extensions/pi-langfuse/src/limits.ts new file mode 100644 index 0000000..8c3bf0b --- /dev/null +++ b/extensions/pi-langfuse/src/limits.ts @@ -0,0 +1,97 @@ +import { + MAX_ARRAY_ITEMS, + MAX_DEPTH, + MAX_OBJECT_KEYS, + MAX_PAYLOAD_NODES, + MAX_STRING_LENGTH, + MAX_TOOL_PAYLOAD_LENGTH, +} from "./constants.js"; +import type { EnvLike } from "./capture-policy.js"; +import { state } from "./state.js"; + +/** + * Payload-shaping limits. Every field is a positive integer, or + * `Number.POSITIVE_INFINITY` to disable that limit entirely (capture everything). + * Resolved once from the environment and stored on the loaded config; consumers + * read the resolved values via `getLimits()` rather than the raw constants. + */ +export interface PayloadLimits { + /** Max characters kept per captured string (generation/agent inputs, outputs, system prompt). */ + readonly maxString: number; + /** Max characters kept for tool inputs/outputs (their payloads run larger than chat strings). */ + readonly maxToolPayload: number; + /** Max nesting depth walked when shaping a structured payload. */ + readonly maxDepth: number; + /** Max array elements kept per array. */ + readonly maxArrayItems: number; + /** Max own-keys kept per object. */ + readonly maxObjectKeys: number; + /** Max total nodes visited across a whole payload before bailing with `[payload too large]`. */ + readonly maxNodes: number; +} + +export const DEFAULT_LIMITS: PayloadLimits = { + maxString: MAX_STRING_LENGTH, + maxToolPayload: MAX_TOOL_PAYLOAD_LENGTH, + maxDepth: MAX_DEPTH, + maxArrayItems: MAX_ARRAY_ITEMS, + maxObjectKeys: MAX_OBJECT_KEYS, + maxNodes: MAX_PAYLOAD_NODES, +}; + +/** Words that mean "no limit" when supplied as an env value. */ +const UNLIMITED_WORDS = new Set(["off", "none", "false", "no", "unlimited", "inf", "infinity"]); + +/** + * Parse one limit env value. + * - unset / blank / unparseable -> `fallback` (the built-in default) + * - "off"/"none"/"unlimited"/... or a value <= 0 -> `Infinity` (limit removed) + * - a positive number -> that integer + */ +export function parseLimit(raw: string | undefined, fallback: number): number { + if (raw === undefined) { + return fallback; + } + const trimmed = raw.trim().toLowerCase(); + if (trimmed === "") { + return fallback; + } + if (UNLIMITED_WORDS.has(trimmed)) { + return Number.POSITIVE_INFINITY; + } + const parsed = Number(trimmed); + if (!Number.isFinite(parsed)) { + return fallback; + } + if (parsed <= 0) { + return Number.POSITIVE_INFINITY; + } + return Math.floor(parsed); +} + +/** + * Resolve payload limits from the environment. Each `PI_LANGFUSE_MAX_*` var overrides + * the corresponding default; set any to `0`/`off`/`unlimited` to remove that limit. + * Namespaced `PI_LANGFUSE_*` (not `LANGFUSE_*`) to avoid clashing with Langfuse + * server env vars such as `LANGFUSE_MAX_EVENT_SIZE_BYTES`. + */ +export function createPayloadLimits(env: EnvLike = process.env as EnvLike): PayloadLimits { + return { + maxString: parseLimit(env.PI_LANGFUSE_MAX_STRING_LENGTH, DEFAULT_LIMITS.maxString), + maxToolPayload: parseLimit(env.PI_LANGFUSE_MAX_TOOL_PAYLOAD_LENGTH, DEFAULT_LIMITS.maxToolPayload), + maxDepth: parseLimit(env.PI_LANGFUSE_MAX_DEPTH, DEFAULT_LIMITS.maxDepth), + maxArrayItems: parseLimit(env.PI_LANGFUSE_MAX_ARRAY_ITEMS, DEFAULT_LIMITS.maxArrayItems), + maxObjectKeys: parseLimit(env.PI_LANGFUSE_MAX_OBJECT_KEYS, DEFAULT_LIMITS.maxObjectKeys), + maxNodes: parseLimit(env.PI_LANGFUSE_MAX_PAYLOAD_NODES, DEFAULT_LIMITS.maxNodes), + }; +} + +/** + * Resolved limits for the current session: the config-loaded values when a + * config is active, otherwise a fresh resolve from the environment. Every + * capture/redaction path reads limits through this so a single env change + * (or config) governs truncation everywhere. + */ +export function getLimits(): PayloadLimits { + return state.config?.limits ?? createPayloadLimits(); +} diff --git a/extensions/pi-langfuse/src/observation.ts b/extensions/pi-langfuse/src/observation.ts new file mode 100644 index 0000000..380acbd --- /dev/null +++ b/extensions/pi-langfuse/src/observation.ts @@ -0,0 +1,21 @@ +import type { LangfuseObservation, LangfuseRuntime, ObservationUpdate } from "./types.js"; + +export async function startChildObservation({ + parent, + runtime, + name, + body, + asType, +}: { + parent: LangfuseObservation; + runtime: () => Promise; + name: string; + body?: ObservationUpdate; + asType: "generation" | "tool" | "span"; +}): Promise { + if (parent.startObservation) { + return parent.startObservation(name, body, { asType }); + } + + return (await runtime()).startObservation(name, body, { asType }); +} diff --git a/extensions/pi-langfuse/src/redaction.ts b/extensions/pi-langfuse/src/redaction.ts new file mode 100644 index 0000000..60bf009 --- /dev/null +++ b/extensions/pi-langfuse/src/redaction.ts @@ -0,0 +1,119 @@ +import { createHash } from "node:crypto"; +import { getLimits } from "./limits.js"; + +export const REDACTED = "[REDACTED_SECRET]"; + +export interface RedactOptions { + maxDepth: number; + maxArrayItems: number; + maxObjectKeys: number; + maxStringLength: number; +} + +function defaultOptions(): RedactOptions { + const limits = getLimits(); + return { + maxDepth: limits.maxDepth, + maxArrayItems: limits.maxArrayItems, + maxObjectKeys: limits.maxObjectKeys, + maxStringLength: limits.maxString, + }; +} + +const SECRET_ASSIGNMENT_RE = + /\b([A-Z0-9_]*(?:SECRET|TOKEN|PASSWORD|PASS|API[_-]?KEY|PRIVATE[_-]?KEY|AUTH|COOKIE)[A-Z0-9_]*)\s*=\s*([^\s"'`]+)/gi; +const PRIVATE_KEY_RE = /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g; +const BEARER_RE = /\bBearer\s+[A-Za-z0-9._~+/=-]{12,}/gi; +const KNOWN_TOKEN_RE = + /\b(?:sk-(?:lf|ant|proj|live|test)[A-Za-z0-9_-]*|pk-lf-[A-Za-z0-9_-]+|gh[pousr]_[A-Za-z0-9_]{20,}|npm_[A-Za-z0-9_-]{20,}|AKIA[0-9A-Z]{16})\b/g; +const ABSOLUTE_PATH_RE = + /(?:\/Users\/[^/\s]+|\/home\/[^/\s]+|\/private\/tmp|\/tmp|[A-Za-z]:\\Users\\[^\\\s]+)(?:[^\s"'`]*)/g; +const SENSITIVE_FIELD_RE = + /^(authorization|cookie|setcookie|xapikey|apikey|token|accesstoken|refreshtoken|secret|secretkey|password|passwd|privatekey)$/; + +export function hashPath(path: string): string { + return `[PATH_HASH:${createHash("sha256").update(path).digest("hex").slice(0, 12)}]`; +} + +function truncate(value: string, maxStringLength: number): string { + return value.length > maxStringLength ? `${value.slice(0, maxStringLength)}... [truncated]` : value; +} + +export function redactString(value: string, options: Partial = {}): string { + const merged = { ...defaultOptions(), ...options }; + const truncated = truncate(value, merged.maxStringLength); + return truncated + .replace(PRIVATE_KEY_RE, REDACTED) + .replace(BEARER_RE, REDACTED) + .replace(KNOWN_TOKEN_RE, REDACTED) + .replace(SECRET_ASSIGNMENT_RE, (_match, key: string) => `${key}=${REDACTED}`) + .replace(ABSOLUTE_PATH_RE, (path: string) => { + const envSuffix = path.match(/([/\\]\.env(?:\.[A-Za-z0-9_-]+)?)$/)?.[1]; + return `${hashPath(envSuffix ? path.slice(0, -envSuffix.length) : path)}${envSuffix ?? ""}`; + }); +} + +function visit(value: unknown, options: RedactOptions, depth: number, seen: WeakSet): unknown { + if (value === null || value === undefined || typeof value === "number" || typeof value === "boolean") { + return value; + } + + if (typeof value === "bigint") { + return value.toString(); + } + + if (typeof value === "string") { + return redactString(value, options); + } + + if (typeof value === "function" || typeof value === "symbol") { + return `[${typeof value}]`; + } + + if (depth <= 0) { + return `[max depth ${options.maxDepth} reached]`; + } + + if (value instanceof Error) { + return { + name: redactString(value.name, options), + message: redactString(value.message, options), + stack: value.stack ? redactString(value.stack, options) : undefined, + }; + } + + if (typeof value !== "object") { + return redactString(String(value), options); + } + + if (seen.has(value)) { + return "[circular]"; + } + seen.add(value); + + if (Array.isArray(value)) { + const output = value + .slice(0, options.maxArrayItems) + .map((item) => visit(item, options, depth - 1, seen)); + if (value.length > options.maxArrayItems) { + output.push(`[${value.length - options.maxArrayItems} truncated items]`); + } + return output; + } + + const entries = Object.entries(value as Record); + const output: Record = {}; + for (const [key, item] of entries.slice(0, options.maxObjectKeys)) { + const normalizedKey = key.replace(/[^a-zA-Z0-9]/g, "").toLowerCase(); + output[key] = SENSITIVE_FIELD_RE.test(normalizedKey) ? REDACTED : visit(item, options, depth - 1, seen); + } + if (entries.length > options.maxObjectKeys) { + output.__truncatedKeys = entries.length - options.maxObjectKeys; + } + return output; +} + +export function redactValue(value: unknown, options: Partial = {}): unknown { + const merged: RedactOptions = { ...defaultOptions(), ...options }; + return visit(value, merged, merged.maxDepth, new WeakSet()); +} diff --git a/extensions/pi-langfuse/src/source-metadata.ts b/extensions/pi-langfuse/src/source-metadata.ts new file mode 100644 index 0000000..6c7a1a3 --- /dev/null +++ b/extensions/pi-langfuse/src/source-metadata.ts @@ -0,0 +1,160 @@ +import { existsSync, readFileSync } from "node:fs"; +import { basename, dirname, join } from "node:path"; +import { execFileSync } from "node:child_process"; + +export type SourceMetadata = Record; + +const OVERRIDE_KEYS = new Set([ + "repo_identity", + "repo_owner", + "repo_name", + "source_type", + "service_name", + "project_slug", + "environment", + "observability_owner", +]); + +function nonGitMetadata(): SourceMetadata { + return { + source_type: "non-git", + metadata_source: "non-git", + }; +} + +function runGit(cwd: string, args: string[]): string { + return execFileSync("git", args, { + cwd, + encoding: "utf8", + stdio: ["ignore", "pipe", "ignore"], + timeout: 2000, + }).trim(); +} + +function firstLine(value: string): string { + return value.split(/\r?\n/).map((line) => line.trim()).find(Boolean) ?? ""; +} + +export function sanitizeGitRemote(remoteUrl: string): Partial> { + const raw = firstLine(remoteUrl).replace(/\.git$/i, ""); + if (!raw) { + return {}; + } + + try { + const parsed = new URL(raw); + const path = parsed.pathname.replace(/^\/+/, "").replace(/\.git$/i, ""); + return parsed.hostname && path ? { git_remote_host: parsed.hostname, git_remote_path: path } : {}; + } catch { + // Continue with scp-like SSH syntax, for example git@github.com:owner/repo.git. + } + + const scpLike = raw.match(/^(?:[^@\s/:]+@)?([^:\s]+):(.+)$/); + if (scpLike) { + const host = scpLike[1]; + const path = scpLike[2].replace(/^\/+/, "").replace(/\.git$/i, ""); + return host && path && !path.includes("@") ? { git_remote_host: host, git_remote_path: path } : {}; + } + + return {}; +} + +function deriveIdentity(remotePath: string | undefined): Partial> { + if (!remotePath) { + return {}; + } + const parts = remotePath.split("/").filter(Boolean); + if (parts.length < 2) { + return {}; + } + const repoName = parts[parts.length - 1]; + const owner = parts[parts.length - 2]; + if (!repoName || !owner || repoName.includes("/")) { + return {}; + } + return { + repo_identity: `${owner}/${repoName}`, + repo_owner: owner, + repo_name: repoName, + }; +} + +function findRepoMetadataFile(cwd: string, gitRoot: string): string | undefined { + let current = cwd; + const root = gitRoot; + while (true) { + const candidate = join(current, ".pi-langfuse.metadata.json"); + if (existsSync(candidate)) { + return candidate; + } + if (current === root) { + break; + } + const parent = dirname(current); + if (parent === current) { + break; + } + current = parent; + } + return undefined; +} + +function readWhitelistedOverrides(cwd: string, gitRoot: string): SourceMetadata { + const path = findRepoMetadataFile(cwd, gitRoot); + if (!path) { + return {}; + } + try { + const parsed = JSON.parse(readFileSync(path, "utf8")) as Record; + const output: SourceMetadata = {}; + for (const [key, value] of Object.entries(parsed)) { + if (OVERRIDE_KEYS.has(key) && typeof value === "string" && value.trim()) { + output[key] = value.trim(); + } + } + if (output.repo_name?.includes("/")) { + delete output.repo_name; + } + return output; + } catch { + return {}; + } +} + +export function collectSourceMetadata(cwd: string): SourceMetadata { + try { + const inside = runGit(cwd, ["rev-parse", "--is-inside-work-tree"]); + if (inside !== "true") { + return nonGitMetadata(); + } + + const gitRoot = runGit(cwd, ["rev-parse", "--show-toplevel"]); + const commit = runGit(cwd, ["rev-parse", "HEAD"]); + const branch = runGit(cwd, ["branch", "--show-current"]); + const remote = runGit(cwd, ["config", "--get", "remote.origin.url"]); + const remoteMetadata = sanitizeGitRemote(remote); + const derivedIdentity = deriveIdentity(remoteMetadata.git_remote_path); + const overrides = readWhitelistedOverrides(cwd, gitRoot); + + const metadata: SourceMetadata = { + source_type: "git-repo", + repo_root_name: basename(gitRoot), + ...(branch ? { git_branch: branch } : {}), + git_commit: commit, + ...remoteMetadata, + ...derivedIdentity, + ...overrides, + metadata_source: Object.keys(overrides).length > 0 ? "repo-file" : "git-detection", + }; + + if (metadata.repo_name?.includes("/")) { + delete metadata.repo_name; + } + if (!metadata.repo_identity && metadata.repo_owner && metadata.repo_name) { + metadata.repo_identity = `${metadata.repo_owner}/${metadata.repo_name}`; + } + return metadata; + } catch { + return nonGitMetadata(); + } +} diff --git a/extensions/pi-langfuse/src/state.ts b/extensions/pi-langfuse/src/state.ts new file mode 100644 index 0000000..e27627d --- /dev/null +++ b/extensions/pi-langfuse/src/state.ts @@ -0,0 +1,164 @@ +import { AsyncLocalStorage } from "node:async_hooks"; +import type { Config, AgentState } from "./types.js"; + +export interface SessionRunState { + currentModel: string; + currentProvider: string; + agentState: AgentState | null; + toolCallCount: number; + errorCount: number; + turnCount: number; + tracingDisabled: boolean; + setupAttemptedThisSession: boolean; +} + +const DEFAULT_SESSION_ID = "__pi_langfuse_default_session__"; + +let activeSessionId = DEFAULT_SESSION_ID; +const sessionScope = new AsyncLocalStorage(); + +function createSessionRunState(): SessionRunState { + return { + currentModel: "", + currentProvider: "", + agentState: null, + toolCallCount: 0, + errorCount: 0, + turnCount: 0, + tracingDisabled: false, + setupAttemptedThisSession: false, + }; +} + +function normalizeSessionId(sessionId?: string) { + return sessionId || DEFAULT_SESSION_ID; +} + +function getActiveSessionId() { + return sessionScope.getStore() ?? activeSessionId; +} + +export function getSessionRunState(sessionId = getActiveSessionId()): SessionRunState { + const normalizedSessionId = normalizeSessionId(sessionId); + let sessionState = state.sessionStates.get(normalizedSessionId); + if (!sessionState) { + sessionState = createSessionRunState(); + state.sessionStates.set(normalizedSessionId, sessionState); + } + return sessionState; +} + +export function setCurrentSession(sessionId?: string) { + activeSessionId = normalizeSessionId(sessionId); + getSessionRunState(activeSessionId); +} + +export function runWithSession(sessionId: string | undefined, fn: () => T): T { + const normalizedSessionId = normalizeSessionId(sessionId); + setCurrentSession(normalizedSessionId); + return sessionScope.run(normalizedSessionId, fn); +} + +export const state = { + config: null as Config | null, + sessionStates: new Map(), + + get currentSessionId() { + const sessionId = getActiveSessionId(); + return sessionId === DEFAULT_SESSION_ID ? "" : sessionId; + }, + set currentSessionId(sessionId: string) { + setCurrentSession(sessionId); + }, + + get currentModel() { + return getSessionRunState().currentModel; + }, + set currentModel(model: string) { + getSessionRunState().currentModel = model; + }, + + get currentProvider() { + return getSessionRunState().currentProvider; + }, + set currentProvider(provider: string) { + getSessionRunState().currentProvider = provider; + }, + + get agentState() { + return getSessionRunState().agentState; + }, + set agentState(agentState: AgentState | null) { + getSessionRunState().agentState = agentState; + }, + + get toolCallCount() { + return getSessionRunState().toolCallCount; + }, + set toolCallCount(toolCallCount: number) { + getSessionRunState().toolCallCount = toolCallCount; + }, + + get errorCount() { + return getSessionRunState().errorCount; + }, + set errorCount(errorCount: number) { + getSessionRunState().errorCount = errorCount; + }, + + get turnCount() { + return getSessionRunState().turnCount; + }, + set turnCount(turnCount: number) { + getSessionRunState().turnCount = turnCount; + }, + + get isTracingDisabled() { + return getSessionRunState().tracingDisabled; + }, + set isTracingDisabled(disabled: boolean) { + getSessionRunState().tracingDisabled = disabled; + }, + + get setupAttemptedThisSession() { + return getSessionRunState().setupAttemptedThisSession; + }, + set setupAttemptedThisSession(attempted: boolean) { + getSessionRunState().setupAttemptedThisSession = attempted; + }, +}; + +export function resetRunState(sessionId = getActiveSessionId()) { + const normalizedSessionId = normalizeSessionId(sessionId); + const setupAttemptedThisSession = + state.sessionStates.get(normalizedSessionId)?.setupAttemptedThisSession ?? false; + state.sessionStates.set(normalizedSessionId, { + ...createSessionRunState(), + setupAttemptedThisSession, + }); +} + +export function clearAllSessionStates() { + state.sessionStates.clear(); + activeSessionId = DEFAULT_SESSION_ID; + getSessionRunState(); +} + +export function computeEvaluationScores(sessionId = getActiveSessionId()) { + const sessionState = getSessionRunState(sessionId); + const toolSuccessRate = + sessionState.toolCallCount > 0 + ? (sessionState.toolCallCount - sessionState.errorCount) / sessionState.toolCallCount + : 1; + const sessionHadErrors = sessionState.errorCount > 0; + + return { + tool_call_count: sessionState.toolCallCount, + turn_count: sessionState.turnCount, + total_tool_errors: sessionState.errorCount, + tool_success_rate: toolSuccessRate, + session_had_errors: sessionHadErrors ? 1 : 0, + }; +} + +getSessionRunState(); diff --git a/extensions/pi-langfuse/src/types.ts b/extensions/pi-langfuse/src/types.ts new file mode 100644 index 0000000..531537b --- /dev/null +++ b/extensions/pi-langfuse/src/types.ts @@ -0,0 +1,139 @@ +import type { CapturePolicy } from "./capture-policy.js"; +import type { PayloadLimits } from "./limits.js"; + +export interface Config { + publicKey: string; + secretKey: string; + host: string; + capturePolicy?: CapturePolicy; + limits?: PayloadLimits; +} + +export interface LangfuseObservation { + id?: string; + traceId?: string; + update(body?: ObservationUpdate): LangfuseObservation; + end(body?: ObservationUpdate): void; + startObservation?( + name: string, + body?: ObservationUpdate, + options?: { asType?: "agent" | "generation" | "tool" | "span" }, + ): LangfuseObservation; + setTraceIO?(body?: { input?: unknown; output?: unknown }): void; +} + +export interface ObservationUpdate { + input?: unknown; + output?: unknown; + metadata?: Record; + model?: string; + modelParameters?: Record; + usageDetails?: Record; + usage?: Record; + costDetails?: Record; + level?: "DEBUG" | "DEFAULT" | "WARNING" | "ERROR"; + statusMessage?: string; + completionStartTime?: Date; +} + +export interface LangfuseScoreClient { + api?: { + trace?: { + get?: (traceId: string) => Promise; + }; + ingestion?: { + batch?: (request: unknown) => Promise; + }; + }; + score?: { + create(body: { + traceId?: string; + sessionId?: string; + observationId?: string; + name: string; + value: number; + dataType?: "NUMERIC" | "BOOLEAN"; + }): unknown; + }; + flush?: () => Promise; + shutdown?: () => Promise; +} + +export interface PendingScore { + traceId?: string; + sessionId?: string; + observationId?: string; + name: string; + value: number; + dataType?: "NUMERIC" | "BOOLEAN"; + environment?: string; +} + +export interface LangfuseRuntimeConfig { + publicKey: string; + secretKey: string; + host: string; +} + +export interface LangfuseRuntime { + startObservation: ( + name: string, + body?: ObservationUpdate, + options?: { asType?: "agent" | "generation" | "tool" | "span" }, + ) => LangfuseObservation; + propagateAttributes: ( + params: { + sessionId?: string; + traceName?: string; + metadata?: Record; + tags?: string[]; + }, + fn: () => LangfuseObservation, + ) => LangfuseObservation; + scoreClient: LangfuseScoreClient; + spanProcessor?: { forceFlush?: () => Promise; shutdown?: () => Promise }; + tracerProvider?: { forceFlush?: () => Promise; shutdown?: () => Promise }; + clearTracerProvider?: () => void; + restFallback?: unknown; + pendingScores?: PendingScore[]; + scoreFlushAt?: number; + scoreFlushIntervalMs?: number; + scoreRequestTimeoutMs?: number; + scoreFlushTimer?: NodeJS.Timeout; + scoreFlushPromise?: Promise; + scoreFlushController?: AbortController; + scoreFlushStopped?: boolean; + runtimeConfig?: LangfuseRuntimeConfig; +} + +export interface GenerationState { + observation: LangfuseObservation; + requestKey: string; + ended: boolean; + metadata: Record; + modelParameters?: Record; + ttftRecorded?: boolean; +} + +export interface ToolState { + observation: LangfuseObservation; + toolName: string; + ended: boolean; + startedAt: number; + inputBytes: number; +} + +export interface AgentState { + root?: LangfuseObservation; + activeTurn?: LangfuseObservation; + traceId?: string; + promptInput?: unknown; + cwd?: string; + generationSeq: number; + activeGenerations: Map; + generationOrder: string[]; + activeTools: Map; + latestAssistantOutput?: unknown; + sourceMetadata?: Record; + providerMetadataByRequest: Map>; +} diff --git a/extensions/pi-langfuse/src/utils.ts b/extensions/pi-langfuse/src/utils.ts new file mode 100644 index 0000000..1f70387 --- /dev/null +++ b/extensions/pi-langfuse/src/utils.ts @@ -0,0 +1,427 @@ +import { getLimits } from "./limits.js"; +import { createCapturePolicy, type CapturePolicy } from "./capture-policy.js"; +import { redactValue } from "./redaction.js"; +import { state } from "./state.js"; + +export function getCapturePolicy(): CapturePolicy { + return state.config?.capturePolicy ?? createCapturePolicy(); +} + +export { getLimits }; + +export function truncate(value: string, maxLength = getLimits().maxString): string { + return value.length > maxLength ? `${value.slice(0, maxLength)}... [truncated]` : value; +} + +export function tryParseJson(value: string): unknown { + const trimmed = value.trim(); + if (!trimmed || !["{", "["].includes(trimmed[0])) { + return value; + } + + try { + return JSON.parse(trimmed); + } catch { + return value; + } +} + +const PAYLOAD_TOO_LARGE = "[payload too large]"; + +export function shapePayload( + value: unknown, + options: { + maxString?: number; + depth?: number; + maxNodes?: number; + maxArrayItems?: number; + maxObjectKeys?: number; + redact?: boolean; + parseJson?: boolean; + } = {}, +): unknown { + const limits = getLimits(); + const maxString = options.maxString ?? limits.maxString; + const depth = options.depth ?? limits.maxDepth; + const maxNodes = options.maxNodes ?? limits.maxNodes; + const maxArrayItems = options.maxArrayItems ?? limits.maxArrayItems; + const maxObjectKeys = options.maxObjectKeys ?? limits.maxObjectKeys; + const budget = { exhausted: false, nodeCount: 0 }; + + function visit(item: unknown, remainingDepth: number, seen: WeakSet): unknown { + if (budget.exhausted) { + return PAYLOAD_TOO_LARGE; + } + + budget.nodeCount++; + if (budget.nodeCount > maxNodes) { + budget.exhausted = true; + return PAYLOAD_TOO_LARGE; + } + + if (typeof item === "string") { + const truncated = truncate(item, maxString); + if (options.parseJson === false) { + return truncated; + } + const parsed = tryParseJson(truncated); + if (parsed === truncated) { + return truncated; + } + return visit(parsed, remainingDepth - 1, seen); + } + + if ( + item === null || + typeof item === "undefined" || + typeof item === "number" || + typeof item === "boolean" + ) { + return item; + } + + if (typeof item === "bigint") { + return item.toString(); + } + + if (typeof item === "function" || typeof item === "symbol") { + return `[${typeof item}]`; + } + + if (remainingDepth <= 0) { + return `[max depth ${depth} reached]`; + } + + if (Array.isArray(item)) { + const output: unknown[] = []; + const limit = Math.min(item.length, maxArrayItems); + for (let index = 0; index < limit; index++) { + output.push(visit(item[index], remainingDepth - 1, seen)); + if (budget.exhausted) { + break; + } + } + return output; + } + + if (item instanceof Error) { + return { + name: item.name, + message: item.message, + stack: item.stack ? truncate(item.stack, maxString) : undefined, + }; + } + + if (typeof item === "object") { + if (seen.has(item)) { + return "[circular]"; + } + seen.add(item); + + const output: Record = {}; + let keyCount = 0; + for (const key in item as Record) { + if (!Object.hasOwn(item, key)) { + continue; + } + output[key] = visit((item as Record)[key], remainingDepth - 1, seen); + keyCount++; + if (budget.exhausted || keyCount >= maxObjectKeys) { + break; + } + } + return output; + } + + return String(item); + } + + const shaped = visit(value, depth, new WeakSet()); + return options.redact === false + ? shaped + : redactValue(shaped, { + maxDepth: depth, + maxStringLength: maxString, + maxArrayItems, + maxObjectKeys, + }); +} + +export function safeSerialize(value: unknown, maxLength = getLimits().maxToolPayload): string { + try { + return truncate(JSON.stringify(shapePayload(value, { maxString: maxLength }), null, 2), maxLength); + } catch { + return `[unserializable ${typeof value}]`; + } +} + +export function estimatePayloadBytes(value: unknown, maxLength = getLimits().maxToolPayload): number { + return new TextEncoder().encode(safeSerialize(value, maxLength)).length; +} + +export function extractTextContent(content: unknown, maxLength?: number): string | undefined { + if (typeof content === "string") { + return maxLength ? truncate(content, maxLength) : content; + } + + if (!Array.isArray(content)) { + return undefined; + } + + const text = content + .map((item) => { + if (!item || typeof item !== "object") return ""; + const block = item as { type?: string; text?: string; thinking?: string }; + return block.type === "text" && block.text ? block.text : ""; + }) + .filter(Boolean) + .join("\n"); + + if (!text) { + return undefined; + } + + return maxLength ? truncate(text, maxLength) : text; +} + +export function normalizeContentForLangfuse(content: unknown, api?: string): unknown { + if (!Array.isArray(content)) { + return content; + } + + const toolCallItems = content.filter((item) => { + return item && typeof item === "object" && (item as { type?: string }).type === "toolCall"; + }); + if (toolCallItems.length === 0) { + return content; + } + + const text = content + .map((item) => { + if (!item || typeof item !== "object") return ""; + const block = item as { type?: string; text?: string }; + return block.type === "text" && typeof block.text === "string" ? block.text : ""; + }) + .filter(Boolean) + .join(""); + + if (api === "anthropic-messages") { + const blocks: unknown[] = []; + if (text) { + blocks.push({ type: "text", text }); + } + for (const item of toolCallItems) { + const toolCall = item as { id?: unknown; name?: unknown; arguments?: unknown }; + const toolInput = shapePayload(toolCall.arguments, { parseJson: false }); + blocks.push({ + type: "tool_use", + id: String(toolCall.id ?? ""), + name: String(toolCall.name ?? "tool"), + input: toolInput, + }); + } + return blocks; + } + + return { + role: "assistant", + content: text || null, + tool_calls: toolCallItems.map((item) => { + const toolCall = item as { id?: unknown; name?: unknown; arguments?: unknown }; + const toolArguments = shapePayload(toolCall.arguments ?? {}, { parseJson: false }); + return { + id: String(toolCall.id ?? ""), + type: "function", + function: { + name: String(toolCall.name ?? "tool"), + arguments: typeof toolArguments === "string" ? toolArguments : JSON.stringify(toolArguments), + }, + }; + }), + }; +} + +export function extractToolCalls(message: Record): unknown | undefined { + return ( + message.toolCalls ?? + message.tool_calls ?? + message.function_calls ?? + (message.content && Array.isArray(message.content) + ? message.content.filter((block) => { + return block && typeof block === "object" && ["tool_use", "tool_call", "toolCall"].includes(String((block as { type?: string }).type)); + }) + : undefined) + ); +} + +export function extractAssistantOutput(message: unknown): unknown | undefined { + if (!message || typeof message !== "object") { + return undefined; + } + + const msg = message as Record; + const normalizedContent = normalizeContentForLangfuse(msg.content, typeof msg.api === "string" ? msg.api : undefined); + if (normalizedContent !== msg.content) { + return shapePayload(normalizedContent, { parseJson: false }); + } + + const text = extractTextContent(msg.content); + if (text) { + return text; + } + + const toolCalls = extractToolCalls(msg); + if (Array.isArray(toolCalls) && toolCalls.length > 0) { + return { toolCalls: shapePayload(toolCalls) }; + } + + if (toolCalls) { + return { toolCalls: shapePayload(toolCalls) }; + } + + return shapePayload(msg); +} + +export function extractFinalAssistant(messages: unknown): Record | undefined { + if (!Array.isArray(messages)) { + return undefined; + } + return messages.filter((message) => message?.role === "assistant").pop() as Record | undefined; +} + +export function getRequestKey(event: Record, fallback: string): string { + return String( + event.requestId ?? + event.providerRequestId ?? + event.messageId ?? + event.turnId ?? + event.turnIndex ?? + event.id ?? + fallback, + ); +} + +export function getToolCallId(event: Record): string | undefined { + const id = event.toolCallId ?? event.id ?? event.callId ?? event.tool_use_id ?? event.toolUseId; + return id === undefined || id === null ? undefined : String(id); +} + +export function getToolName(event: Record): string { + return String( + event.toolName ?? + event.name ?? + event.tool ?? + event.functionName ?? + (event.call && typeof event.call === "object" ? (event.call as Record).name : undefined) ?? + "tool", + ); +} + +export function getToolInput(event: Record): unknown { + return ( + event.input ?? + event.args ?? + event.arguments ?? + event.params ?? + (event.call && typeof event.call === "object" ? (event.call as Record).input : undefined) ?? + event + ); +} + +export function getProviderPayload(event: Record): unknown { + return event.request ?? event.payload ?? event.body ?? event.providerPayload ?? event.messages ?? event; +} + +export function extractModelParameters(payload: unknown): Record | undefined { + if (!payload || typeof payload !== "object" || Array.isArray(payload)) { + return undefined; + } + + const params: Record = {}; + const record = payload as Record; + for (const key of [ + "temperature", + "top_p", + "topP", + "max_tokens", + "maxTokens", + "max_completion_tokens", + "presence_penalty", + "frequency_penalty", + "reasoning_effort", + ]) { + const value = record[key]; + if (typeof value === "string" || typeof value === "number") { + params[key] = value; + } + } + + return Object.keys(params).length > 0 ? params : undefined; +} + +export function getMessageFromEvent(event: Record): Record | undefined { + if (event.message && typeof event.message === "object") { + return event.message as Record; + } + if (event.role || event.content) { + return event; + } + return undefined; +} + +export function extractUsage(messageOrEvent: Record): Record | undefined { + const usage = (messageOrEvent.usage ?? + (messageOrEvent.message && typeof messageOrEvent.message === "object" + ? (messageOrEvent.message as Record).usage + : undefined)) as Record | undefined; + if (!usage || typeof usage !== "object") { + return undefined; + } + + const input = Number(usage.input ?? usage.inputTokens ?? usage.prompt_tokens ?? usage.promptTokens ?? 0); + const output = Number(usage.output ?? usage.outputTokens ?? usage.completion_tokens ?? usage.completionTokens ?? 0); + const total = Number(usage.total ?? usage.totalTokens ?? usage.total_tokens ?? input + output); + const cacheRead = Number(usage.cacheRead ?? usage.cache_read ?? usage.cachedTokens ?? 0); + const cacheWrite = Number(usage.cacheWrite ?? usage.cache_write ?? 0); + + return { + input, + output, + total, + ...(cacheRead ? { cacheRead } : {}), + ...(cacheWrite ? { cacheWrite } : {}), + }; +} + +export function extractCostDetails(messageOrEvent: Record): Record | undefined { + const usage = (messageOrEvent.usage ?? + (messageOrEvent.message && typeof messageOrEvent.message === "object" + ? (messageOrEvent.message as Record).usage + : undefined)) as Record | undefined; + const cost = (messageOrEvent.cost ?? usage?.cost ?? messageOrEvent.costDetails) as Record | undefined; + if (!cost || typeof cost !== "object") { + return undefined; + } + + const input = Number(cost.input ?? cost.inputCost ?? 0); + const output = Number(cost.output ?? cost.outputCost ?? 0); + const total = Number(cost.total ?? cost.totalCost ?? input + output); + if (input === 0 && output === 0 && total === 0) { + return undefined; + } + + return { input, output, total }; +} + +export function extractResponseMetadata(event: Record): Record { + return shapePayload( + { + status: event.status ?? event.statusCode ?? event.httpStatus, + headers: event.headers, + responseHeaders: event.responseHeaders, + providerMetadata: event.providerMetadata ?? event.metadata, + requestId: event.requestId ?? event.providerRequestId, + }, + { depth: 4, maxString: 4_000 }, + ) as Record; +} diff --git a/extensions/pi-langfuse/tsconfig.json b/extensions/pi-langfuse/tsconfig.json new file mode 100644 index 0000000..798ac93 --- /dev/null +++ b/extensions/pi-langfuse/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "strict": true, + "noEmit": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + }, + "include": [ + "index.ts", + "src/**/*.ts", + "types/**/*.d.ts" + ] +} diff --git a/extensions/pi-langfuse/types/langfuse-runtime-shims.d.ts b/extensions/pi-langfuse/types/langfuse-runtime-shims.d.ts new file mode 100644 index 0000000..287cf27 --- /dev/null +++ b/extensions/pi-langfuse/types/langfuse-runtime-shims.d.ts @@ -0,0 +1,49 @@ +declare module "@opentelemetry/sdk-trace-base" { + export class BasicTracerProvider { + constructor(options?: { spanProcessors?: unknown[] }); + forceFlush?(): Promise; + shutdown?(): Promise; + } +} + +declare module "@langfuse/otel" { + export class LangfuseSpanProcessor { + constructor(options: { + publicKey: string; + secretKey: string; + baseUrl: string; + }); + forceFlush?(): Promise; + shutdown?(): Promise; + } +} + +declare module "@langfuse/tracing" { + export function setLangfuseTracerProvider(provider: unknown): void; + + export function startObservation( + name: string, + body?: Record, + options?: { asType?: string }, + ): unknown; + + export function propagateAttributes( + params: { + sessionId?: string; + traceName?: string; + metadata?: Record; + tags?: string[]; + }, + fn: () => T, + ): T; +} + +declare module "@langfuse/client" { + export class LangfuseClient { + constructor(options: { + publicKey: string; + secretKey: string; + baseUrl: string; + }); + } +} diff --git a/extensions/pi-langfuse/types/node-shims.d.ts b/extensions/pi-langfuse/types/node-shims.d.ts new file mode 100644 index 0000000..56e1120 --- /dev/null +++ b/extensions/pi-langfuse/types/node-shims.d.ts @@ -0,0 +1,29 @@ +declare module "node:fs" { + export function mkdirSync(path: string, options?: { recursive?: boolean }): void; + export function readFileSync(path: string, encoding: string): string; + export function existsSync(path: string): boolean; + export function writeFileSync(path: string, data: string, encoding: string): void; +} + +declare module "node:crypto" { + export function randomUUID(): string; +} + +declare module "node:os" { + export function homedir(): string; +} + +declare module "node:path" { + export function resolve(...paths: string[]): string; + export function dirname(path: string): string; + export function basename(path: string, suffix?: string): string; +} + +declare module "node:url" { + export function fileURLToPath(url: string | URL): string; +} + +declare const process: { + cwd(): string; + env: Record; +}; diff --git a/extensions/pi-langfuse/types/pi-coding-agent.d.ts b/extensions/pi-langfuse/types/pi-coding-agent.d.ts new file mode 100644 index 0000000..3f7e7aa --- /dev/null +++ b/extensions/pi-langfuse/types/pi-coding-agent.d.ts @@ -0,0 +1,9 @@ +declare module "@earendil-works/pi-coding-agent" { + export interface ExtensionAPI { + on(event: string, handler: (event: any, ctx: any) => unknown): void; + registerCommand( + name: string, + options: { description?: string; handler: (args: string, ctx: any) => unknown }, + ): void; + } +}