From cca28de3759afe39fb402fd81af227b5508ca165 Mon Sep 17 00:00:00 2001 From: erlinerd Date: Sun, 13 Sep 2026 00:28:36 +0800 Subject: [PATCH 01/13] feat(langfuse-observability): add Langfuse observability plugin --- marketplace.json | 18 ++++ .../.zcode-plugin/plugin.json | 86 ++++++++++++++++++ plugins/langfuse-observability/README.md | 64 +++++++++++++ plugins/langfuse-observability/README_CN.md | 60 ++++++++++++ .../langfuse-observability/hooks/hooks.json | 91 +++++++++++++++++++ 5 files changed, 319 insertions(+) create mode 100644 plugins/langfuse-observability/.zcode-plugin/plugin.json create mode 100644 plugins/langfuse-observability/README.md create mode 100644 plugins/langfuse-observability/README_CN.md create mode 100644 plugins/langfuse-observability/hooks/hooks.json diff --git a/marketplace.json b/marketplace.json index 4e87656..9836563 100644 --- a/marketplace.json +++ b/marketplace.json @@ -532,6 +532,24 @@ "cli", "oauth2" ] + }, + { + "name": "langfuse-observability", + "source": "./plugins/langfuse-observability", + "description": "Fail-open Langfuse tracing for ZCode sessions, prompts, tool calls, and assistant responses.", + "version": "0.1.0", + "category": "developer-tools", + "tags": [ + "langfuse", + "tracing", + "telemetry", + "hooks" + ], + "strict": true, + "description_i18n": { + "en": "Fail-open Langfuse tracing for ZCode sessions, prompts, tool calls, and assistant responses.", + "zh-CN": "为 ZCode 会话、提示词、工具调用和助手回复提供 fail-open 的 Langfuse 追踪。" + } } ] } diff --git a/plugins/langfuse-observability/.zcode-plugin/plugin.json b/plugins/langfuse-observability/.zcode-plugin/plugin.json new file mode 100644 index 0000000..8edc854 --- /dev/null +++ b/plugins/langfuse-observability/.zcode-plugin/plugin.json @@ -0,0 +1,86 @@ +{ + "name": "langfuse-observability", + "description": "Fail-open Langfuse tracing for ZCode sessions, prompts, tool calls, and assistant responses.", + "description_i18n": { + "en": "Fail-open Langfuse tracing for ZCode sessions, prompts, tool calls, and assistant responses.", + "zh-CN": "为 ZCode 会话、提示词、工具调用和助手回复提供 fail-open 的 Langfuse 追踪。" + }, + "version": "0.1.0", + "author": { + "name": "erlinerd", + "url": "https://github.com/erlinerd" + }, + "license": "MIT", + "keywords": [ + "langfuse", + "observability", + "tracing", + "telemetry", + "hooks" + ], + "userConfig": { + "langfuse_public_key": { + "title": "Langfuse public key", + "description": "Langfuse project public key. You can also provide LANGFUSE_PUBLIC_KEY in the ZCode process environment.", + "type": "string", + "required": false + }, + "langfuse_secret_key": { + "title": "Langfuse secret key", + "description": "Langfuse project secret key. It is used only by the local hook process and is never written to hook output.", + "type": "string", + "required": false, + "sensitive": true + }, + "langfuse_base_url": { + "title": "Langfuse base URL", + "description": "Langfuse host, for example https://cloud.langfuse.com or a self-hosted URL.", + "type": "string", + "default": "https://cloud.langfuse.com" + }, + "langfuse_user_id": { + "title": "Langfuse user ID", + "description": "Optional stable user identifier attached to traces. Leave empty to omit it.", + "type": "string", + "required": false + }, + "langfuse_environment": { + "title": "Langfuse environment", + "description": "Environment label attached to traces.", + "type": "string", + "default": "development" + }, + "capture_prompts": { + "title": "Capture prompts", + "description": "Include user prompts and assistant responses in Langfuse. Disable for metadata-only tracing.", + "type": "boolean", + "default": true + }, + "capture_tool_inputs": { + "title": "Capture tool inputs", + "description": "Include tool names and inputs in Langfuse spans.", + "type": "boolean", + "default": true + }, + "capture_tool_outputs": { + "title": "Capture tool outputs", + "description": "Include tool results and errors in Langfuse spans.", + "type": "boolean", + "default": true + }, + "max_capture_chars": { + "title": "Maximum captured characters", + "description": "Maximum characters per captured prompt, tool payload, or response. Larger values are truncated.", + "type": "number", + "default": 20000 + }, + "debug": { + "title": "Debug diagnostics", + "description": "Write non-sensitive lifecycle diagnostics to stderr. Disabled by default.", + "type": "boolean", + "default": false + } + }, + "homepage": "https://github.com/erlinerd/zcode-langfuse-plugin", + "repository": "https://github.com/erlinerd/zcode-langfuse-plugin" +} diff --git a/plugins/langfuse-observability/README.md b/plugins/langfuse-observability/README.md new file mode 100644 index 0000000..2aff522 --- /dev/null +++ b/plugins/langfuse-observability/README.md @@ -0,0 +1,64 @@ +# Langfuse Observability for ZCode + +[中文文档](./README_CN.md) + +A fail-open ZCode plugin that sends one Langfuse trace named `ZCode Turn` for +each completed turn. Tool calls are spans and the assistant response is a +generation. + +## Behavior + +The plugin listens to `SessionStart`, `UserPromptSubmit`, `PreToolUse`, +`PostToolUse`, `PostToolUseFailure`, and `Stop`. It publishes only at `Stop`. +It reads only fields delivered on ZCode Hook stdin. It never reads transcript +files and never collects hidden chain-of-thought. + +Missing credentials, malformed Hook input, local-state errors, and Langfuse +errors are fail-open and must not block ZCode. Session state is stored under +`ZCODE_PLUGIN_DATA` when available and is cleaned up after a completed `Stop`. + +## Privacy and configuration + +Content capture is bounded and controlled independently for prompts, tool input, +and tool output. Set these environment variables to disable content capture: + +```text +LANGFUSE_CAPTURE_PROMPTS=false +LANGFUSE_CAPTURE_TOOL_INPUTS=false +LANGFUSE_CAPTURE_TOOL_OUTPUTS=false +``` + +Configuration precedence is process environment, persisted ZCode plugin options, +then defaults. Supported configuration includes: + +```text +LANGFUSE_PUBLIC_KEY +LANGFUSE_SECRET_KEY +LANGFUSE_BASE_URL +LANGFUSE_USER_ID +LANGFUSE_ENVIRONMENT +LANGFUSE_RELEASE +LANGFUSE_ENABLED +LANGFUSE_CAPTURE_PROMPTS +LANGFUSE_CAPTURE_TOOL_INPUTS +LANGFUSE_CAPTURE_TOOL_OUTPUTS +LANGFUSE_MAX_CAPTURE_CHARS +LANGFUSE_DEBUG +``` + +`LANGFUSE_BASE_URL` defaults to `https://cloud.langfuse.com`. The plugin sends +telemetry only to that configured Langfuse endpoint and writes only its bounded +local session state. Never commit credentials or private Hook payloads. + +## Files and dependencies + +The process Hook is declared in [`hooks/hooks.json`](./hooks/hooks.json) and +runs the bundled runtime at `dist/hooks/entry.mjs`. The runtime bundles the +official `langfuse` JavaScript SDK. No runtime installation step is required. + +## Source and license + +Source repository: + +Licensed under MIT. See the source repository for architecture, tests, release +checks, and the complete development history. diff --git a/plugins/langfuse-observability/README_CN.md b/plugins/langfuse-observability/README_CN.md new file mode 100644 index 0000000..64cd93e --- /dev/null +++ b/plugins/langfuse-observability/README_CN.md @@ -0,0 +1,60 @@ +# ZCode Langfuse 观测插件 + +[English](./README.md) + +这是一个 fail-open 的 ZCode 插件:每个完成的 turn 发送一条名为 +`ZCode Turn` 的 Langfuse trace。工具调用记录为 span,助手回复记录为 +generation。 + +## 行为 + +插件监听 `SessionStart`、`UserPromptSubmit`、`PreToolUse`、`PostToolUse`、 +`PostToolUseFailure` 和 `Stop`,只在 `Stop` 时发布 trace。插件只读取 +ZCode Hook stdin 传入的字段,不读取 transcript 文件,也不采集隐藏完整思维链。 + +缺少凭据、Hook 输入损坏、本地状态错误和 Langfuse 错误都采用 fail-open, +不能阻塞 ZCode。可用时,会在 `ZCODE_PLUGIN_DATA` 下保存会话状态,并在 +完成 `Stop` 后清理。 + +## 隐私与配置 + +内容采集有大小限制,并分别控制 prompt、工具输入和工具输出。关闭内容采集: + +```text +LANGFUSE_CAPTURE_PROMPTS=false +LANGFUSE_CAPTURE_TOOL_INPUTS=false +LANGFUSE_CAPTURE_TOOL_OUTPUTS=false +``` + +配置优先级为进程环境变量、持久化的 ZCode 插件选项、默认值。支持的配置包括: + +```text +LANGFUSE_PUBLIC_KEY +LANGFUSE_SECRET_KEY +LANGFUSE_BASE_URL +LANGFUSE_USER_ID +LANGFUSE_ENVIRONMENT +LANGFUSE_RELEASE +LANGFUSE_ENABLED +LANGFUSE_CAPTURE_PROMPTS +LANGFUSE_CAPTURE_TOOL_INPUTS +LANGFUSE_CAPTURE_TOOL_OUTPUTS +LANGFUSE_MAX_CAPTURE_CHARS +LANGFUSE_DEBUG +``` + +`LANGFUSE_BASE_URL` 默认是 `https://cloud.langfuse.com`。插件只向配置的 +Langfuse endpoint 发送观测数据,只写入有大小限制的本地会话状态。不要提交 +凭据或私有 Hook payload。 + +## 文件与依赖 + +进程 Hook 声明在 [`hooks/hooks.json`](./hooks/hooks.json),运行 +`dist/hooks/entry.mjs` 中的 bundle。运行时已包含官方 `langfuse` +JavaScript SDK,不需要额外安装运行时依赖。 + +## 来源与许可证 + +源码仓库: + +本插件采用 MIT 许可证。架构、测试、发布检查和完整开发历史见源码仓库。 diff --git a/plugins/langfuse-observability/hooks/hooks.json b/plugins/langfuse-observability/hooks/hooks.json new file mode 100644 index 0000000..37b12fb --- /dev/null +++ b/plugins/langfuse-observability/hooks/hooks.json @@ -0,0 +1,91 @@ +{ + "description": "Langfuse observability hooks for ZCode.", + "hooks": { + "SessionStart": [ + { + "hooks": [ + { + "type": "process", + "command": "node", + "args": [ + "${ZCODE_PLUGIN_ROOT}/dist/hooks/entry.mjs" + ], + "timeoutMs": 5000, + "statusMessage": "Langfuse: preparing session tracing…" + } + ] + } + ], + "UserPromptSubmit": [ + { + "hooks": [ + { + "type": "process", + "command": "node", + "args": [ + "${ZCODE_PLUGIN_ROOT}/dist/hooks/entry.mjs" + ], + "timeoutMs": 5000 + } + ] + } + ], + "PreToolUse": [ + { + "hooks": [ + { + "type": "process", + "command": "node", + "args": [ + "${ZCODE_PLUGIN_ROOT}/dist/hooks/entry.mjs" + ], + "timeoutMs": 5000 + } + ] + } + ], + "PostToolUse": [ + { + "hooks": [ + { + "type": "process", + "command": "node", + "args": [ + "${ZCODE_PLUGIN_ROOT}/dist/hooks/entry.mjs" + ], + "timeoutMs": 5000 + } + ] + } + ], + "PostToolUseFailure": [ + { + "hooks": [ + { + "type": "process", + "command": "node", + "args": [ + "${ZCODE_PLUGIN_ROOT}/dist/hooks/entry.mjs" + ], + "timeoutMs": 5000 + } + ] + } + ], + "Stop": [ + { + "hooks": [ + { + "type": "process", + "command": "node", + "args": [ + "${ZCODE_PLUGIN_ROOT}/dist/hooks/entry.mjs" + ], + "timeoutMs": 20000, + "statusMessage": "Langfuse: sending trace…" + } + ] + } + ] + } +} From 268e9f7bf1cefc2cbb7fb77cea990f5b28478544 Mon Sep 17 00:00:00 2001 From: erlinerd Date: Sun, 13 Sep 2026 00:29:31 +0800 Subject: [PATCH 02/13] fix(langfuse-observability): include bundled hook runtime --- plugins/langfuse-observability/LICENSE | 21 +++++++++++++++++++ plugins/langfuse-observability/README.md | 2 +- plugins/langfuse-observability/README_CN.md | 2 +- .../langfuse-observability/hooks/hooks.json | 12 +++++------ 4 files changed, 29 insertions(+), 8 deletions(-) create mode 100644 plugins/langfuse-observability/LICENSE diff --git a/plugins/langfuse-observability/LICENSE b/plugins/langfuse-observability/LICENSE new file mode 100644 index 0000000..b2f7956 --- /dev/null +++ b/plugins/langfuse-observability/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Community contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/plugins/langfuse-observability/README.md b/plugins/langfuse-observability/README.md index 2aff522..484b83a 100644 --- a/plugins/langfuse-observability/README.md +++ b/plugins/langfuse-observability/README.md @@ -53,7 +53,7 @@ local session state. Never commit credentials or private Hook payloads. ## Files and dependencies The process Hook is declared in [`hooks/hooks.json`](./hooks/hooks.json) and -runs the bundled runtime at `dist/hooks/entry.mjs`. The runtime bundles the +runs the bundled runtime at `payload/dist/hooks/entry.mjs`. The runtime bundles the official `langfuse` JavaScript SDK. No runtime installation step is required. ## Source and license diff --git a/plugins/langfuse-observability/README_CN.md b/plugins/langfuse-observability/README_CN.md index 64cd93e..5028c08 100644 --- a/plugins/langfuse-observability/README_CN.md +++ b/plugins/langfuse-observability/README_CN.md @@ -50,7 +50,7 @@ Langfuse endpoint 发送观测数据,只写入有大小限制的本地会话 ## 文件与依赖 进程 Hook 声明在 [`hooks/hooks.json`](./hooks/hooks.json),运行 -`dist/hooks/entry.mjs` 中的 bundle。运行时已包含官方 `langfuse` +`payload/dist/hooks/entry.mjs` 中的 bundle。运行时已包含官方 `langfuse` JavaScript SDK,不需要额外安装运行时依赖。 ## 来源与许可证 diff --git a/plugins/langfuse-observability/hooks/hooks.json b/plugins/langfuse-observability/hooks/hooks.json index 37b12fb..1b179bb 100644 --- a/plugins/langfuse-observability/hooks/hooks.json +++ b/plugins/langfuse-observability/hooks/hooks.json @@ -8,7 +8,7 @@ "type": "process", "command": "node", "args": [ - "${ZCODE_PLUGIN_ROOT}/dist/hooks/entry.mjs" + "${ZCODE_PLUGIN_ROOT}/payload/dist/hooks/entry.mjs" ], "timeoutMs": 5000, "statusMessage": "Langfuse: preparing session tracing…" @@ -23,7 +23,7 @@ "type": "process", "command": "node", "args": [ - "${ZCODE_PLUGIN_ROOT}/dist/hooks/entry.mjs" + "${ZCODE_PLUGIN_ROOT}/payload/dist/hooks/entry.mjs" ], "timeoutMs": 5000 } @@ -37,7 +37,7 @@ "type": "process", "command": "node", "args": [ - "${ZCODE_PLUGIN_ROOT}/dist/hooks/entry.mjs" + "${ZCODE_PLUGIN_ROOT}/payload/dist/hooks/entry.mjs" ], "timeoutMs": 5000 } @@ -51,7 +51,7 @@ "type": "process", "command": "node", "args": [ - "${ZCODE_PLUGIN_ROOT}/dist/hooks/entry.mjs" + "${ZCODE_PLUGIN_ROOT}/payload/dist/hooks/entry.mjs" ], "timeoutMs": 5000 } @@ -65,7 +65,7 @@ "type": "process", "command": "node", "args": [ - "${ZCODE_PLUGIN_ROOT}/dist/hooks/entry.mjs" + "${ZCODE_PLUGIN_ROOT}/payload/dist/hooks/entry.mjs" ], "timeoutMs": 5000 } @@ -79,7 +79,7 @@ "type": "process", "command": "node", "args": [ - "${ZCODE_PLUGIN_ROOT}/dist/hooks/entry.mjs" + "${ZCODE_PLUGIN_ROOT}/payload/dist/hooks/entry.mjs" ], "timeoutMs": 20000, "statusMessage": "Langfuse: sending trace…" From cc3794e8ee6cdbe16c2d4aac9a75639dad0c7f1d Mon Sep 17 00:00:00 2001 From: erlinerd Date: Sun, 13 Sep 2026 00:29:46 +0800 Subject: [PATCH 03/13] fix(langfuse-observability): track bundled runtime files --- .../payload/dist/hooks/entry.mjs | 4744 +++++++++++++++++ .../payload/dist/hooks/entry.mjs.map | 7 + .../payload/dist/hooks/hooks.json | 91 + .../payload/dist/plugin.json | 83 + 4 files changed, 4925 insertions(+) create mode 100644 plugins/langfuse-observability/payload/dist/hooks/entry.mjs create mode 100644 plugins/langfuse-observability/payload/dist/hooks/entry.mjs.map create mode 100644 plugins/langfuse-observability/payload/dist/hooks/hooks.json create mode 100644 plugins/langfuse-observability/payload/dist/plugin.json diff --git a/plugins/langfuse-observability/payload/dist/hooks/entry.mjs b/plugins/langfuse-observability/payload/dist/hooks/entry.mjs new file mode 100644 index 0000000..0a495e3 --- /dev/null +++ b/plugins/langfuse-observability/payload/dist/hooks/entry.mjs @@ -0,0 +1,4744 @@ +// Generated by npm run build. Edit src/, not dist/. + +// src/hooks/entry.ts +import { randomUUID as randomUUID2 } from "node:crypto"; + +// src/application/config.ts +import { readFileSync } from "node:fs"; +import { homedir } from "node:os"; +import { join } from "node:path"; +var DEFAULT_BASE_URL = "https://cloud.langfuse.com"; +var DEFAULT_RELEASE = "0.1.0"; +var DEFAULT_MAX_CAPTURE_CHARS = 2e4; +function asText(value) { + if (value === void 0) return void 0; + return typeof value === "string" ? value : String(value); +} +function firstNonEmpty(...values) { + return values.map(asText).find((value) => value !== void 0 && value.trim().length > 0)?.trim(); +} +function userConfig(env, name) { + const normalized = name.toUpperCase(); + return firstNonEmpty( + env[`ZCODE_USER_CONFIG_${normalized}`], + env[`ZCODE_PLUGIN_CONFIG_${normalized}`] + ); +} +function isStoredOption(value) { + return typeof value === "string" || typeof value === "number" || typeof value === "boolean"; +} +function parseStoredOptions(value) { + if (typeof value !== "object" || value === null || Array.isArray(value)) + return {}; + const options = {}; + for (const [key, option2] of Object.entries(value)) { + if (isStoredOption(option2)) options[key] = option2; + } + return options; +} +function readStoredOptions(env) { + const configPaths = [ + env.ZCODE_CONFIG_PATH, + join(homedir(), ".zcode", "cli", "config.json") + ].filter((path) => Boolean(path)); + const configuredPluginId = env.ZCODE_PLUGIN_ID; + for (const configPath of configPaths) { + try { + const parsed = JSON.parse(readFileSync(configPath, "utf8")); + if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) + continue; + const plugins = parsed.plugins; + if (typeof plugins !== "object" || plugins === null || Array.isArray(plugins)) + continue; + const options = plugins.options; + if (typeof options !== "object" || options === null || Array.isArray(options)) + continue; + const optionEntries = options; + const pluginId = configuredPluginId && optionEntries[configuredPluginId] ? configuredPluginId : Object.keys(optionEntries).find( + (key) => key.startsWith("langfuse-observability@") + ); + if (pluginId) return parseStoredOptions(optionEntries[pluginId]); + } catch { + } + } + return {}; +} +function option(env, storedOptions, name, environmentName) { + return firstNonEmpty( + userConfig(env, name), + storedOptions[name], + env[environmentName] + ); +} +function parseBoolean(value, fallback) { + if (!value) return fallback; + if (["1", "true", "yes", "on"].includes(value.toLowerCase())) return true; + if (["0", "false", "no", "off"].includes(value.toLowerCase())) return false; + return fallback; +} +function parsePositiveInteger(value, fallback) { + if (!value) return fallback; + const parsed = Number.parseInt(value, 10); + if (!Number.isFinite(parsed) || parsed <= 0) return fallback; + return Math.min(parsed, 1e6); +} +function normalizeBaseUrl(value) { + const candidate = value ?? DEFAULT_BASE_URL; + try { + const url = new URL(candidate); + if (url.protocol !== "http:" && url.protocol !== "https:") + return DEFAULT_BASE_URL; + return candidate.replace(/\/+$/, ""); + } catch { + return DEFAULT_BASE_URL; + } +} +function readConfig(env = process.env, storedOptions = readStoredOptions(env)) { + const enabled = parseBoolean( + option(env, storedOptions, "enabled", "LANGFUSE_ENABLED"), + true + ); + const publicKey = option(env, storedOptions, "langfuse_public_key", "LANGFUSE_PUBLIC_KEY") ?? null; + const secretKey = option(env, storedOptions, "langfuse_secret_key", "LANGFUSE_SECRET_KEY") ?? null; + return { + publicKey, + secretKey, + baseUrl: normalizeBaseUrl( + option(env, storedOptions, "langfuse_base_url", "LANGFUSE_BASE_URL") + ), + userId: option(env, storedOptions, "langfuse_user_id", "LANGFUSE_USER_ID") ?? null, + environment: option( + env, + storedOptions, + "langfuse_environment", + "LANGFUSE_ENVIRONMENT" + ) ?? "development", + release: option(env, storedOptions, "langfuse_release", "LANGFUSE_RELEASE") ?? DEFAULT_RELEASE, + enabled, + capturePrompts: parseBoolean( + option(env, storedOptions, "capture_prompts", "LANGFUSE_CAPTURE_PROMPTS"), + true + ), + captureToolInputs: parseBoolean( + option( + env, + storedOptions, + "capture_tool_inputs", + "LANGFUSE_CAPTURE_TOOL_INPUTS" + ), + true + ), + captureToolOutputs: parseBoolean( + option( + env, + storedOptions, + "capture_tool_outputs", + "LANGFUSE_CAPTURE_TOOL_OUTPUTS" + ), + true + ), + maxCaptureChars: parsePositiveInteger( + option( + env, + storedOptions, + "max_capture_chars", + "LANGFUSE_MAX_CAPTURE_CHARS" + ), + DEFAULT_MAX_CAPTURE_CHARS + ), + debug: parseBoolean( + option(env, storedOptions, "debug", "LANGFUSE_DEBUG"), + false + ) + }; +} +function isConfigured(config) { + return config.enabled && Boolean(config.publicKey && config.secretKey); +} + +// src/domain/extract.ts +function toJsonValue(value) { + if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") { + return value; + } + if (Array.isArray(value)) { + const values = value.map(toJsonValue); + return values.every((item) => item !== void 0) ? values : void 0; + } + if (typeof value === "object") { + const entries = Object.entries(value); + const result = {}; + for (const [key, item] of entries) { + const parsed = toJsonValue(item); + if (parsed === void 0) return void 0; + result[key] = parsed; + } + return result; + } + return void 0; +} +function valueAt(payload, ...keys) { + for (const key of keys) { + const value = toJsonValue(payload[key]); + if (value !== void 0 && value !== null) return value; + } + return void 0; +} +function asString(value) { + if (typeof value === "string") return value; + if (typeof value === "number" || typeof value === "boolean") + return String(value); + return null; +} +function stringifyValue(value) { + if (typeof value === "string") return value; + if (value === void 0) return ""; + const json = JSON.stringify(value); + return json ?? String(value); +} +function eventName(payload) { + return asString( + valueAt(payload, "hook_event_name", "hookEventName", "event", "event_name") + ); +} +function sessionId(payload) { + const value = asString(valueAt(payload, "session_id", "sessionId")); + return value?.trim() || null; +} +function prompt(payload) { + const value = valueAt( + payload, + "prompt", + "user_prompt", + "userPrompt", + "message" + ); + return value === void 0 ? null : stringifyValue(value); +} +function assistantMessage(payload) { + const value = valueAt( + payload, + "last_assistant_message", + "lastAssistantMessage" + ); + return value === void 0 ? null : stringifyValue(value); +} +function toolId(payload) { + const value = asString( + valueAt(payload, "tool_use_id", "toolUseId", "tool_id", "toolId", "id") + ); + return value?.trim() || null; +} +function toolName(payload) { + return asString( + valueAt(payload, "tool_name", "toolName", "name", "tool") + )?.trim() ?? "unknown"; +} +function toolInput(payload) { + return valueAt(payload, "tool_input", "toolInput", "input", "arguments") ?? null; +} +function toolOutput(payload) { + return valueAt( + payload, + "tool_output", + "toolOutput", + "output", + "result", + "tool_response" + ) ?? null; +} +function toolError(payload) { + return stringifyValue( + valueAt(payload, "error", "error_message", "errorMessage", "message") ?? "Tool failed" + ); +} + +// src/application/turn-tracker.ts +function createSession(session, now) { + return { + version: 1, + sessionId: session, + startedAt: now, + updatedAt: now, + currentTurn: null + }; +} +function createTurn(id, now, userPrompt) { + return { + id, + prompt: userPrompt, + startedAt: now, + tools: [] + }; +} +function truncate(value, maxChars) { + if (value.length <= maxChars) return value; + return `${value.slice(0, Math.max(0, maxChars - 18))}\u2026 [truncated]`; +} +function boundedValue(value, maxChars) { + const serialized = JSON.stringify(value); + if (serialized.length <= maxChars) return value; + return truncate(serialized, maxChars); +} +function sanitizeTurn(turn, config) { + return { + ...turn, + prompt: config.capturePrompts && turn.prompt !== null ? truncate(turn.prompt, config.maxCaptureChars) : null, + assistantMessage: config.capturePrompts && turn.assistantMessage !== null ? truncate(turn.assistantMessage, config.maxCaptureChars) : null, + tools: turn.tools.map((tool) => ({ + ...tool, + name: truncate(tool.name, 256), + input: config.captureToolInputs ? boundedValue(tool.input, config.maxCaptureChars) : null, + output: config.captureToolOutputs && tool.output !== null ? boundedValue(tool.output, config.maxCaptureChars) : null, + error: config.captureToolOutputs && tool.error !== null ? truncate(tool.error, config.maxCaptureChars) : null + })) + }; +} +function findTool(turn, id, name) { + if (id) { + const byId = turn.tools.find((tool) => tool.id === id); + if (byId) return byId; + } + return [...turn.tools].reverse().find((tool) => tool.endedAt === null && tool.name === name) ?? [...turn.tools].reverse().find((tool) => tool.endedAt === null) ?? null; +} +var TurnTracker = class { + constructor(stateStore, traceSink, config, clock, idGenerator) { + this.stateStore = stateStore; + this.traceSink = traceSink; + this.config = config; + this.clock = clock; + this.idGenerator = idGenerator; + } + async handle(payload) { + const name = eventName(payload); + const session = sessionId(payload); + if (!name || !session) return; + let completed = null; + await this.stateStore.withSessionLock(session, async () => { + const now = this.clock.now().toISOString(); + const existing = await this.stateStore.load(session) ?? createSession(session, now); + switch (name) { + case "SessionStart": + existing.updatedAt = now; + await this.stateStore.save(existing); + return; + case "UserPromptSubmit": { + const userPrompt = prompt(payload); + existing.currentTurn = createTurn( + this.idGenerator.next(), + now, + this.config.capturePrompts && userPrompt !== null ? truncate(userPrompt, this.config.maxCaptureChars) : null + ); + existing.updatedAt = now; + await this.stateStore.save(existing); + return; + } + case "PreToolUse": + this.recordToolStart(existing, payload, now); + await this.stateStore.save(existing); + return; + case "PostToolUse": + this.recordToolOutput(existing, payload, now, false); + await this.stateStore.save(existing); + return; + case "PostToolUseFailure": + this.recordToolOutput(existing, payload, now, true); + await this.stateStore.save(existing); + return; + case "Stop": + completed = sanitizeTurn( + { + sessionId: session, + turnId: existing.currentTurn?.id ?? this.idGenerator.next(), + prompt: existing.currentTurn?.prompt ?? null, + assistantMessage: assistantMessage(payload), + startedAt: existing.currentTurn?.startedAt ?? now, + endedAt: now, + tools: existing.currentTurn?.tools ?? [] + }, + this.config + ); + await this.stateStore.clear(session); + return; + default: + return; + } + }); + if (completed) await this.traceSink.publishTurn(completed); + } + recordToolStart(state, payload, now) { + const turn = state.currentTurn ?? createTurn(this.idGenerator.next(), now, null); + state.currentTurn = turn; + turn.tools.push({ + id: toolId(payload) ?? this.idGenerator.next(), + name: toolName(payload), + input: this.config.captureToolInputs ? boundedValue(toolInput(payload), this.config.maxCaptureChars) : null, + output: null, + error: null, + startedAt: now, + endedAt: null + }); + state.updatedAt = now; + } + recordToolOutput(state, payload, now, failed) { + const turn = state.currentTurn ?? createTurn(this.idGenerator.next(), now, null); + state.currentTurn = turn; + const name = toolName(payload); + const tool = findTool(turn, toolId(payload), name); + const output = !failed && this.config.captureToolOutputs ? boundedValue(toolOutput(payload), this.config.maxCaptureChars) : null; + const error = failed && this.config.captureToolOutputs ? truncate(toolError(payload), this.config.maxCaptureChars) : null; + if (tool) { + tool.output = output; + tool.error = error; + tool.endedAt = now; + } else { + turn.tools.push({ + id: toolId(payload) ?? this.idGenerator.next(), + name, + input: null, + output, + error, + startedAt: now, + endedAt: now + }); + } + state.updatedAt = now; + } +}; +var NoopTraceSink = class { + async publishTurn(_turn) { + } +}; + +// src/adapters/json-state-store.ts +import { createHash, randomUUID } from "node:crypto"; +import { + mkdir, + open, + readFile, + rename, + rm, + stat, + writeFile +} from "node:fs/promises"; +import { homedir as homedir2, tmpdir } from "node:os"; +import { join as join2 } from "node:path"; +var LOCK_WAIT_MS = 25; +var LOCK_ATTEMPTS = 160; +var STALE_LOCK_MS = 6e4; +function isRecord(value) { + return typeof value === "object" && value !== null && !Array.isArray(value); +} +function isJsonValue(value) { + if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") { + return true; + } + if (Array.isArray(value)) return value.every(isJsonValue); + return isRecord(value) && Object.values(value).every(isJsonValue); +} +function parseToolCall(value) { + if (!isRecord(value)) return null; + if (typeof value.id !== "string" || typeof value.name !== "string" || !isJsonValue(value.input) || value.output !== null && !isJsonValue(value.output) || value.error !== null && typeof value.error !== "string" || typeof value.startedAt !== "string" || value.endedAt !== null && typeof value.endedAt !== "string") { + return null; + } + return { + id: value.id, + name: value.name, + input: value.input, + output: value.output, + error: value.error, + startedAt: value.startedAt, + endedAt: value.endedAt + }; +} +function parseTurn(value) { + if (!isRecord(value)) return null; + if (typeof value.id !== "string" || value.prompt !== null && typeof value.prompt !== "string" || typeof value.startedAt !== "string" || !Array.isArray(value.tools)) { + return null; + } + const tools = value.tools.map(parseToolCall); + if (tools.some((tool) => tool === null)) return null; + return { + id: value.id, + prompt: value.prompt, + startedAt: value.startedAt, + tools: tools.filter((tool) => tool !== null) + }; +} +function parseState(value) { + if (!isRecord(value)) return null; + if (value.version !== 1 || typeof value.sessionId !== "string" || typeof value.startedAt !== "string" || typeof value.updatedAt !== "string") { + return null; + } + const currentTurn = value.currentTurn === null ? null : parseTurn(value.currentTurn); + if (value.currentTurn !== null && currentTurn === null) return null; + return { + version: 1, + sessionId: value.sessionId, + startedAt: value.startedAt, + updatedAt: value.updatedAt, + currentTurn + }; +} +function sessionKey(sessionId2) { + return createHash("sha256").update(sessionId2).digest("hex"); +} +function defaultDataDir() { + return join2( + homedir2() || tmpdir(), + ".zcode", + "cli", + "plugins", + "data", + "langfuse-observability" + ); +} +var JsonStateStore = class { + dataDir; + constructor(dataDir = process.env.ZCODE_PLUGIN_DATA || defaultDataDir()) { + this.dataDir = dataDir; + } + async load(sessionId2) { + try { + const contents = await readFile(this.statePath(sessionId2), "utf8"); + return parseState(JSON.parse(contents)); + } catch { + return null; + } + } + async save(state) { + await mkdir(this.dataDir, { recursive: true, mode: 448 }); + const path = this.statePath(state.sessionId); + const temporaryPath = `${path}.${randomUUID()}.tmp`; + await writeFile(temporaryPath, JSON.stringify(state), { + encoding: "utf8", + mode: 384 + }); + await rename(temporaryPath, path); + } + async clear(sessionId2) { + await rm(this.statePath(sessionId2), { force: true }); + } + async withSessionLock(sessionId2, task) { + await mkdir(this.dataDir, { recursive: true, mode: 448 }); + const lockPath = this.lockPath(sessionId2); + let acquired = false; + for (let attempt = 0; attempt < LOCK_ATTEMPTS; attempt += 1) { + try { + const handle = await open(lockPath, "wx", 384); + await handle.close(); + acquired = true; + break; + } catch { + await this.removeStaleLock(lockPath); + await new Promise((resolve) => setTimeout(resolve, LOCK_WAIT_MS)); + } + } + if (!acquired) + throw new Error("Timed out acquiring the Langfuse state lock"); + try { + return await task(); + } finally { + await rm(lockPath, { force: true }); + } + } + statePath(sessionId2) { + return join2(this.dataDir, `${sessionKey(sessionId2)}.json`); + } + lockPath(sessionId2) { + return join2(this.dataDir, `${sessionKey(sessionId2)}.lock`); + } + async removeStaleLock(lockPath) { + try { + const details = await stat(lockPath); + if (Date.now() - details.mtimeMs > STALE_LOCK_MS) + await rm(lockPath, { force: true }); + } catch { + } + } +}; + +// node_modules/mustache/mustache.mjs +var objectToString = Object.prototype.toString; +var isArray = Array.isArray || function isArrayPolyfill(object) { + return objectToString.call(object) === "[object Array]"; +}; +function isFunction(object) { + return typeof object === "function"; +} +function typeStr(obj) { + return isArray(obj) ? "array" : typeof obj; +} +function escapeRegExp(string) { + return string.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&"); +} +function hasProperty(obj, propName) { + return obj != null && typeof obj === "object" && propName in obj; +} +function primitiveHasOwnProperty(primitive, propName) { + return primitive != null && typeof primitive !== "object" && primitive.hasOwnProperty && primitive.hasOwnProperty(propName); +} +var regExpTest = RegExp.prototype.test; +function testRegExp(re, string) { + return regExpTest.call(re, string); +} +var nonSpaceRe = /\S/; +function isWhitespace(string) { + return !testRegExp(nonSpaceRe, string); +} +var entityMap = { + "&": "&", + "<": "<", + ">": ">", + '"': """, + "'": "'", + "/": "/", + "`": "`", + "=": "=" +}; +function escapeHtml(string) { + return String(string).replace(/[&<>"'`=\/]/g, function fromEntityMap(s) { + return entityMap[s]; + }); +} +var whiteRe = /\s*/; +var spaceRe = /\s+/; +var equalsRe = /\s*=/; +var curlyRe = /\s*\}/; +var tagRe = /#|\^|\/|>|\{|&|=|!/; +function parseTemplate(template, tags) { + if (!template) + return []; + var lineHasNonSpace = false; + var sections = []; + var tokens = []; + var spaces = []; + var hasTag = false; + var nonSpace = false; + var indentation = ""; + var tagIndex = 0; + function stripSpace() { + if (hasTag && !nonSpace) { + while (spaces.length) + delete tokens[spaces.pop()]; + } else { + spaces = []; + } + hasTag = false; + nonSpace = false; + } + var openingTagRe, closingTagRe, closingCurlyRe; + function compileTags(tagsToCompile) { + if (typeof tagsToCompile === "string") + tagsToCompile = tagsToCompile.split(spaceRe, 2); + if (!isArray(tagsToCompile) || tagsToCompile.length !== 2) + throw new Error("Invalid tags: " + tagsToCompile); + openingTagRe = new RegExp(escapeRegExp(tagsToCompile[0]) + "\\s*"); + closingTagRe = new RegExp("\\s*" + escapeRegExp(tagsToCompile[1])); + closingCurlyRe = new RegExp("\\s*" + escapeRegExp("}" + tagsToCompile[1])); + } + compileTags(tags || mustache.tags); + var scanner = new Scanner(template); + var start, type, value, chr, token, openSection; + while (!scanner.eos()) { + start = scanner.pos; + value = scanner.scanUntil(openingTagRe); + if (value) { + for (var i = 0, valueLength = value.length; i < valueLength; ++i) { + chr = value.charAt(i); + if (isWhitespace(chr)) { + spaces.push(tokens.length); + indentation += chr; + } else { + nonSpace = true; + lineHasNonSpace = true; + indentation += " "; + } + tokens.push(["text", chr, start, start + 1]); + start += 1; + if (chr === "\n") { + stripSpace(); + indentation = ""; + tagIndex = 0; + lineHasNonSpace = false; + } + } + } + if (!scanner.scan(openingTagRe)) + break; + hasTag = true; + type = scanner.scan(tagRe) || "name"; + scanner.scan(whiteRe); + if (type === "=") { + value = scanner.scanUntil(equalsRe); + scanner.scan(equalsRe); + scanner.scanUntil(closingTagRe); + } else if (type === "{") { + value = scanner.scanUntil(closingCurlyRe); + scanner.scan(curlyRe); + scanner.scanUntil(closingTagRe); + type = "&"; + } else { + value = scanner.scanUntil(closingTagRe); + } + if (!scanner.scan(closingTagRe)) + throw new Error("Unclosed tag at " + scanner.pos); + if (type == ">") { + token = [type, value, start, scanner.pos, indentation, tagIndex, lineHasNonSpace]; + } else { + token = [type, value, start, scanner.pos]; + } + tagIndex++; + tokens.push(token); + if (type === "#" || type === "^") { + sections.push(token); + } else if (type === "/") { + openSection = sections.pop(); + if (!openSection) + throw new Error('Unopened section "' + value + '" at ' + start); + if (openSection[1] !== value) + throw new Error('Unclosed section "' + openSection[1] + '" at ' + start); + } else if (type === "name" || type === "{" || type === "&") { + nonSpace = true; + } else if (type === "=") { + compileTags(value); + } + } + stripSpace(); + openSection = sections.pop(); + if (openSection) + throw new Error('Unclosed section "' + openSection[1] + '" at ' + scanner.pos); + return nestTokens(squashTokens(tokens)); +} +function squashTokens(tokens) { + var squashedTokens = []; + var token, lastToken; + for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) { + token = tokens[i]; + if (token) { + if (token[0] === "text" && lastToken && lastToken[0] === "text") { + lastToken[1] += token[1]; + lastToken[3] = token[3]; + } else { + squashedTokens.push(token); + lastToken = token; + } + } + } + return squashedTokens; +} +function nestTokens(tokens) { + var nestedTokens = []; + var collector = nestedTokens; + var sections = []; + var token, section; + for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) { + token = tokens[i]; + switch (token[0]) { + case "#": + case "^": + collector.push(token); + sections.push(token); + collector = token[4] = []; + break; + case "/": + section = sections.pop(); + section[5] = token[2]; + collector = sections.length > 0 ? sections[sections.length - 1][4] : nestedTokens; + break; + default: + collector.push(token); + } + } + return nestedTokens; +} +function Scanner(string) { + this.string = string; + this.tail = string; + this.pos = 0; +} +Scanner.prototype.eos = function eos() { + return this.tail === ""; +}; +Scanner.prototype.scan = function scan(re) { + var match = this.tail.match(re); + if (!match || match.index !== 0) + return ""; + var string = match[0]; + this.tail = this.tail.substring(string.length); + this.pos += string.length; + return string; +}; +Scanner.prototype.scanUntil = function scanUntil(re) { + var index = this.tail.search(re), match; + switch (index) { + case -1: + match = this.tail; + this.tail = ""; + break; + case 0: + match = ""; + break; + default: + match = this.tail.substring(0, index); + this.tail = this.tail.substring(index); + } + this.pos += match.length; + return match; +}; +function Context(view, parentContext) { + this.view = view; + this.cache = { ".": this.view }; + this.parent = parentContext; +} +Context.prototype.push = function push(view) { + return new Context(view, this); +}; +Context.prototype.lookup = function lookup(name) { + var cache = this.cache; + var value; + if (cache.hasOwnProperty(name)) { + value = cache[name]; + } else { + var context = this, intermediateValue, names, index, lookupHit = false; + while (context) { + if (name.indexOf(".") > 0) { + intermediateValue = context.view; + names = name.split("."); + index = 0; + while (intermediateValue != null && index < names.length) { + if (index === names.length - 1) + lookupHit = hasProperty(intermediateValue, names[index]) || primitiveHasOwnProperty(intermediateValue, names[index]); + intermediateValue = intermediateValue[names[index++]]; + } + } else { + intermediateValue = context.view[name]; + lookupHit = hasProperty(context.view, name); + } + if (lookupHit) { + value = intermediateValue; + break; + } + context = context.parent; + } + cache[name] = value; + } + if (isFunction(value)) + value = value.call(this.view); + return value; +}; +function Writer() { + this.templateCache = { + _cache: {}, + set: function set(key, value) { + this._cache[key] = value; + }, + get: function get(key) { + return this._cache[key]; + }, + clear: function clear() { + this._cache = {}; + } + }; +} +Writer.prototype.clearCache = function clearCache() { + if (typeof this.templateCache !== "undefined") { + this.templateCache.clear(); + } +}; +Writer.prototype.parse = function parse(template, tags) { + var cache = this.templateCache; + var cacheKey = template + ":" + (tags || mustache.tags).join(":"); + var isCacheEnabled = typeof cache !== "undefined"; + var tokens = isCacheEnabled ? cache.get(cacheKey) : void 0; + if (tokens == void 0) { + tokens = parseTemplate(template, tags); + isCacheEnabled && cache.set(cacheKey, tokens); + } + return tokens; +}; +Writer.prototype.render = function render(template, view, partials, config) { + var tags = this.getConfigTags(config); + var tokens = this.parse(template, tags); + var context = view instanceof Context ? view : new Context(view, void 0); + return this.renderTokens(tokens, context, partials, template, config); +}; +Writer.prototype.renderTokens = function renderTokens(tokens, context, partials, originalTemplate, config) { + var buffer = ""; + var token, symbol, value; + for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) { + value = void 0; + token = tokens[i]; + symbol = token[0]; + if (symbol === "#") value = this.renderSection(token, context, partials, originalTemplate, config); + else if (symbol === "^") value = this.renderInverted(token, context, partials, originalTemplate, config); + else if (symbol === ">") value = this.renderPartial(token, context, partials, config); + else if (symbol === "&") value = this.unescapedValue(token, context); + else if (symbol === "name") value = this.escapedValue(token, context, config); + else if (symbol === "text") value = this.rawValue(token); + if (value !== void 0) + buffer += value; + } + return buffer; +}; +Writer.prototype.renderSection = function renderSection(token, context, partials, originalTemplate, config) { + var self = this; + var buffer = ""; + var value = context.lookup(token[1]); + function subRender(template) { + return self.render(template, context, partials, config); + } + if (!value) return; + if (isArray(value)) { + for (var j = 0, valueLength = value.length; j < valueLength; ++j) { + buffer += this.renderTokens(token[4], context.push(value[j]), partials, originalTemplate, config); + } + } else if (typeof value === "object" || typeof value === "string" || typeof value === "number") { + buffer += this.renderTokens(token[4], context.push(value), partials, originalTemplate, config); + } else if (isFunction(value)) { + if (typeof originalTemplate !== "string") + throw new Error("Cannot use higher-order sections without the original template"); + value = value.call(context.view, originalTemplate.slice(token[3], token[5]), subRender); + if (value != null) + buffer += value; + } else { + buffer += this.renderTokens(token[4], context, partials, originalTemplate, config); + } + return buffer; +}; +Writer.prototype.renderInverted = function renderInverted(token, context, partials, originalTemplate, config) { + var value = context.lookup(token[1]); + if (!value || isArray(value) && value.length === 0) + return this.renderTokens(token[4], context, partials, originalTemplate, config); +}; +Writer.prototype.indentPartial = function indentPartial(partial, indentation, lineHasNonSpace) { + var filteredIndentation = indentation.replace(/[^ \t]/g, ""); + var partialByNl = partial.split("\n"); + for (var i = 0; i < partialByNl.length; i++) { + if (partialByNl[i].length && (i > 0 || !lineHasNonSpace)) { + partialByNl[i] = filteredIndentation + partialByNl[i]; + } + } + return partialByNl.join("\n"); +}; +Writer.prototype.renderPartial = function renderPartial(token, context, partials, config) { + if (!partials) return; + var tags = this.getConfigTags(config); + var value = isFunction(partials) ? partials(token[1]) : partials[token[1]]; + if (value != null) { + var lineHasNonSpace = token[6]; + var tagIndex = token[5]; + var indentation = token[4]; + var indentedValue = value; + if (tagIndex == 0 && indentation) { + indentedValue = this.indentPartial(value, indentation, lineHasNonSpace); + } + var tokens = this.parse(indentedValue, tags); + return this.renderTokens(tokens, context, partials, indentedValue, config); + } +}; +Writer.prototype.unescapedValue = function unescapedValue(token, context) { + var value = context.lookup(token[1]); + if (value != null) + return value; +}; +Writer.prototype.escapedValue = function escapedValue(token, context, config) { + var escape = this.getConfigEscape(config) || mustache.escape; + var value = context.lookup(token[1]); + if (value != null) + return typeof value === "number" && escape === mustache.escape ? String(value) : escape(value); +}; +Writer.prototype.rawValue = function rawValue(token) { + return token[1]; +}; +Writer.prototype.getConfigTags = function getConfigTags(config) { + if (isArray(config)) { + return config; + } else if (config && typeof config === "object") { + return config.tags; + } else { + return void 0; + } +}; +Writer.prototype.getConfigEscape = function getConfigEscape(config) { + if (config && typeof config === "object" && !isArray(config)) { + return config.escape; + } else { + return void 0; + } +}; +var mustache = { + name: "mustache.js", + version: "4.2.0", + tags: ["{{", "}}"], + clearCache: void 0, + escape: void 0, + parse: void 0, + render: void 0, + Scanner: void 0, + Context: void 0, + Writer: void 0, + /** + * Allows a user to override the default caching strategy, by providing an + * object with set, get and clear methods. This can also be used to disable + * the cache by setting it to the literal `undefined`. + */ + set templateCache(cache) { + defaultWriter.templateCache = cache; + }, + /** + * Gets the default or overridden caching object from the default writer. + */ + get templateCache() { + return defaultWriter.templateCache; + } +}; +var defaultWriter = new Writer(); +mustache.clearCache = function clearCache2() { + return defaultWriter.clearCache(); +}; +mustache.parse = function parse2(template, tags) { + return defaultWriter.parse(template, tags); +}; +mustache.render = function render2(template, view, partials, config) { + if (typeof template !== "string") { + throw new TypeError('Invalid template! Template should be a "string" but "' + typeStr(template) + '" was given as the first argument for mustache#render(template, view, partials)'); + } + return defaultWriter.render(template, view, partials, config); +}; +mustache.escape = escapeHtml; +mustache.Scanner = Scanner; +mustache.Context = Context; +mustache.Writer = Writer; +var mustache_default = mustache; + +// node_modules/langfuse-core/lib/index.mjs +var SimpleEventEmitter = class { + constructor() { + this.events = {}; + this.events = {}; + } + on(event, listener) { + if (!this.events[event]) { + this.events[event] = []; + } + this.events[event].push(listener); + return () => { + this.events[event] = this.events[event].filter((x) => x !== listener); + }; + } + emit(event, payload) { + for (const listener of this.events[event] || []) { + listener(payload); + } + for (const listener of this.events["*"] || []) { + listener(event, payload); + } + } +}; +var DEFAULT_PROMPT_CACHE_TTL_SECONDS = 60; +var LangfusePromptCacheItem = class { + constructor(value, ttlSeconds) { + this.value = value; + this._expiry = Date.now() + ttlSeconds * 1e3; + } + get isExpired() { + return Date.now() > this._expiry; + } +}; +var LangfusePromptCache = class { + constructor() { + this._cache = /* @__PURE__ */ new Map(); + this._defaultTtlSeconds = DEFAULT_PROMPT_CACHE_TTL_SECONDS; + this._refreshingKeys = /* @__PURE__ */ new Map(); + } + getIncludingExpired(key) { + return this._cache.get(key) ?? null; + } + set(key, value, ttlSeconds) { + const effectiveTtlSeconds = ttlSeconds ?? this._defaultTtlSeconds; + this._cache.set(key, new LangfusePromptCacheItem(value, effectiveTtlSeconds)); + } + addRefreshingPromise(key, promise) { + this._refreshingKeys.set(key, promise); + promise.then(() => { + this._refreshingKeys.delete(key); + }).catch(() => { + this._refreshingKeys.delete(key); + }); + } + isRefreshing(key) { + return this._refreshingKeys.has(key); + } + invalidate(promptName) { + console.log("invalidating", promptName, this._cache.keys()); + for (const key of this._cache.keys()) { + if (key.startsWith(promptName)) { + this._cache.delete(key); + } + } + } +}; +var LangfusePersistedProperty; +(function(LangfusePersistedProperty2) { + LangfusePersistedProperty2["Props"] = "props"; + LangfusePersistedProperty2["Queue"] = "queue"; + LangfusePersistedProperty2["OptedOut"] = "opted_out"; +})(LangfusePersistedProperty || (LangfusePersistedProperty = {})); +var ChatMessageType; +(function(ChatMessageType2) { + ChatMessageType2["ChatMessage"] = "chatmessage"; + ChatMessageType2["Placeholder"] = "placeholder"; +})(ChatMessageType || (ChatMessageType = {})); +mustache_default.escape = function(text) { + return text; +}; +var BasePromptClient = class { + constructor(prompt2, isFallback = false, type) { + this.name = prompt2.name; + this.version = prompt2.version; + this.config = prompt2.config; + this.labels = prompt2.labels; + this.tags = prompt2.tags; + this.isFallback = isFallback; + this.type = type; + this.commitMessage = prompt2.commitMessage; + } + _transformToLangchainVariables(content) { + const jsonEscapedContent = this.escapeJsonForLangchain(content); + return jsonEscapedContent.replace(/\{\{(\w+)\}\}/g, "{$1}"); + } + /** + * Escapes every curly brace that is part of a JSON object by doubling it. + * + * A curly brace is considered “JSON-related” when, after skipping any immediate + * whitespace, the next non-whitespace character is a single (') or double (") quote. + * + * Braces that are already doubled (e.g. `{{variable}}` placeholders) are left untouched. + * + * @param text - Input string that may contain JSON snippets. + * @returns The string with JSON-related braces doubled. + */ + escapeJsonForLangchain(text) { + const out = []; + const stack = []; + let i = 0; + const n = text.length; + while (i < n) { + const ch = text[i]; + if (ch === "{") { + if (i + 1 < n && text[i + 1] === "{") { + out.push("{{"); + i += 2; + continue; + } + let j = i + 1; + while (j < n && /\s/.test(text[j])) { + j++; + } + const isJson = j < n && (text[j] === "'" || text[j] === '"'); + out.push(isJson ? "{{" : "{"); + stack.push(isJson); + i += 1; + continue; + } + if (ch === "}") { + if (i + 1 < n && text[i + 1] === "}") { + out.push("}}"); + i += 2; + continue; + } + const isJson = stack.pop() ?? false; + out.push(isJson ? "}}" : "}"); + i += 1; + continue; + } + out.push(ch); + i += 1; + } + return out.join(""); + } +}; +var TextPromptClient = class extends BasePromptClient { + constructor(prompt2, isFallback = false) { + super(prompt2, isFallback, "text"); + this.promptResponse = prompt2; + this.prompt = prompt2.prompt; + } + compile(variables, _placeholders) { + return mustache_default.render(this.promptResponse.prompt, variables ?? {}); + } + getLangchainPrompt(_options) { + return this._transformToLangchainVariables(this.prompt); + } + toJSON() { + return JSON.stringify({ + name: this.name, + prompt: this.prompt, + version: this.version, + isFallback: this.isFallback, + tags: this.tags, + labels: this.labels, + type: this.type, + config: this.config + }); + } +}; +var ChatPromptClient = class _ChatPromptClient extends BasePromptClient { + constructor(prompt2, isFallback = false) { + const normalizedPrompt = _ChatPromptClient.normalizePrompt(prompt2.prompt); + const typedPrompt = { + ...prompt2, + prompt: normalizedPrompt + }; + super(typedPrompt, isFallback, "chat"); + this.promptResponse = typedPrompt; + this.prompt = normalizedPrompt; + } + static normalizePrompt(prompt2) { + return prompt2.map((item) => { + if ("type" in item) { + return item; + } else { + return { + type: ChatMessageType.ChatMessage, + ...item + }; + } + }); + } + compile(variables, placeholders) { + const messagesWithPlaceholdersReplaced = []; + const placeholderValues = placeholders ?? {}; + for (const item of this.prompt) { + if ("type" in item && item.type === ChatMessageType.Placeholder) { + const placeholderValue = placeholderValues[item.name]; + if (Array.isArray(placeholderValue) && placeholderValue.length > 0 && placeholderValue.every((msg) => typeof msg === "object" && "role" in msg && "content" in msg)) { + messagesWithPlaceholdersReplaced.push(...placeholderValue); + } else if (Array.isArray(placeholderValue) && placeholderValue.length === 0) ; + else if (placeholderValue !== void 0) { + messagesWithPlaceholdersReplaced.push(JSON.stringify(placeholderValue)); + } else { + messagesWithPlaceholdersReplaced.push(item); + } + } else if ("role" in item && "content" in item && item.type === ChatMessageType.ChatMessage) { + messagesWithPlaceholdersReplaced.push({ + role: item.role, + content: item.content + }); + } + } + return messagesWithPlaceholdersReplaced.map((item) => { + if (typeof item === "object" && item !== null && "role" in item && "content" in item) { + return { + ...item, + content: mustache_default.render(item.content, variables ?? {}) + }; + } else { + return item; + } + }); + } + getLangchainPrompt(options) { + const messagesWithPlaceholdersReplaced = []; + const placeholderValues = options?.placeholders ?? {}; + for (const item of this.prompt) { + if ("type" in item && item.type === ChatMessageType.Placeholder) { + const placeholderValue = placeholderValues[item.name]; + if (Array.isArray(placeholderValue) && placeholderValue.length > 0 && placeholderValue.every((msg) => typeof msg === "object" && "role" in msg && "content" in msg)) { + messagesWithPlaceholdersReplaced.push(...placeholderValue.map((msg) => { + return { + role: msg.role, + content: this._transformToLangchainVariables(msg.content) + }; + })); + } else if (Array.isArray(placeholderValue) && placeholderValue.length === 0) ; + else if (placeholderValue !== void 0) { + messagesWithPlaceholdersReplaced.push(JSON.stringify(placeholderValue)); + } else { + messagesWithPlaceholdersReplaced.push({ + variableName: item.name, + optional: false + }); + } + } else if ("role" in item && "content" in item && item.type === ChatMessageType.ChatMessage) { + messagesWithPlaceholdersReplaced.push({ + role: item.role, + content: this._transformToLangchainVariables(item.content) + }); + } + } + return messagesWithPlaceholdersReplaced; + } + toJSON() { + return JSON.stringify({ + name: this.name, + prompt: this.promptResponse.prompt.map((item) => { + if ("type" in item && item.type === ChatMessageType.ChatMessage) { + const { + type: _, + ...messageWithoutType + } = item; + return messageWithoutType; + } + return item; + }), + version: this.version, + isFallback: this.isFallback, + tags: this.tags, + labels: this.labels, + type: this.type, + config: this.config + }); + } +}; +function assert(truthyValue, message) { + if (!truthyValue) { + throw new Error(message); + } +} +function removeTrailingSlash(url) { + return url?.replace(/\/+$/, ""); +} +async function retriable(fn, props = {}, log) { + const { + retryCount = 3, + retryDelay = 5e3, + retryCheck = () => true + } = props; + let lastError = null; + for (let i = 0; i < retryCount + 1; i++) { + if (i > 0) { + await new Promise((resolve) => setTimeout(resolve, retryDelay)); + log(`Retrying ${i + 1} of ${retryCount + 1}`); + } + try { + const res = await fn(); + return res; + } catch (e) { + lastError = e; + if (!retryCheck(e)) { + throw e; + } + log(`Retriable error: ${JSON.stringify(e)}`); + } + } + throw lastError; +} +function generateUUID(globalThis2) { + let d = (/* @__PURE__ */ new Date()).getTime(); + let d2 = globalThis2 && globalThis2.performance && globalThis2.performance.now && globalThis2.performance.now() * 1e3 || 0; + return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) { + let r = Math.random() * 16; + if (d > 0) { + r = (d + r) % 16 | 0; + d = Math.floor(d / 16); + } else { + r = (d2 + r) % 16 | 0; + d2 = Math.floor(d2 / 16); + } + return (c === "x" ? r : r & 3 | 8).toString(16); + }); +} +function currentTimestamp() { + return (/* @__PURE__ */ new Date()).getTime(); +} +function currentISOTime() { + return (/* @__PURE__ */ new Date()).toISOString(); +} +function safeSetTimeout(fn, timeout) { + const t = setTimeout(fn, timeout); + t?.unref && t?.unref(); + return t; +} +function getEnv(key) { + if (typeof process !== "undefined" && process.env[key]) { + return process.env[key]; + } else if (typeof globalThis !== "undefined") { + return globalThis[key]; + } + return; +} +function configLangfuseSDK(params, secretRequired = true) { + const { + publicKey, + secretKey, + ...coreOptions + } = params ?? {}; + const finalPublicKey = publicKey ?? getEnv("LANGFUSE_PUBLIC_KEY"); + const finalSecretKey = secretRequired ? secretKey ?? getEnv("LANGFUSE_SECRET_KEY") : void 0; + const finalBaseUrl = coreOptions.baseUrl ?? getEnv("LANGFUSE_BASEURL"); + const finalCoreOptions = { + ...coreOptions, + baseUrl: finalBaseUrl + }; + return { + publicKey: finalPublicKey, + ...secretRequired ? { + secretKey: finalSecretKey + } : void 0, + ...finalCoreOptions + }; +} +var encodeQueryParams = (params) => { + const queryParams = new URLSearchParams(); + Object.entries(params ?? {}).forEach(([key, value]) => { + if (value !== void 0 && value !== null) { + if (value instanceof Date) { + queryParams.append(key, value.toISOString()); + } else { + queryParams.append(key, value.toString()); + } + } + }); + return queryParams.toString(); +}; +var utils = /* @__PURE__ */ Object.freeze({ + __proto__: null, + assert, + configLangfuseSDK, + currentISOTime, + currentTimestamp, + encodeQueryParams, + generateUUID, + getEnv, + removeTrailingSlash, + retriable, + safeSetTimeout +}); +var common_release_envs = [ + // Vercel + "VERCEL_GIT_COMMIT_SHA", + "NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA", + // Netlify + "COMMIT_REF", + // Render + "RENDER_GIT_COMMIT", + // GitLab CI + "CI_COMMIT_SHA", + // CicleCI + "CIRCLE_SHA1", + // Cloudflare pages + "CF_PAGES_COMMIT_SHA", + // AWS Amplify + "REACT_APP_GIT_SHA", + // Heroku + "SOURCE_VERSION", + // Trigger.dev + "TRIGGER_DEPLOYMENT_ID" +]; +function getCommonReleaseEnvs() { + for (const key of common_release_envs) { + const value = getEnv(key); + if (value) { + return value; + } + } + return void 0; +} +var fs = null; +var cryptoModule = null; +var dynamicImport = (module) => { + return import( + /* webpackIgnore: true */ + module + ); +}; +if (typeof globalThis.Deno !== "undefined") { + Promise.all([dynamicImport("node:fs"), dynamicImport("node:crypto")]).then(([importedFs, importedCrypto]) => { + fs = importedFs; + cryptoModule = importedCrypto; + }).catch(); +} else if (typeof process !== "undefined" && process.versions?.node) { + Promise.all([dynamicImport("fs"), dynamicImport("crypto")]).then(([importedFs, importedCrypto]) => { + fs = importedFs; + cryptoModule = importedCrypto; + }).catch(); +} else if (typeof crypto !== "undefined") { + cryptoModule = crypto; +} +var LangfuseMedia = class _LangfuseMedia { + constructor(params) { + const { + obj, + base64DataUri, + contentType, + contentBytes, + filePath + } = params; + this.obj = obj; + this._mediaId = void 0; + if (base64DataUri) { + const [contentBytesParsed, contentTypeParsed] = this.parseBase64DataUri(base64DataUri); + this._contentBytes = contentBytesParsed; + this._contentType = contentTypeParsed; + this._source = "base64_data_uri"; + } else if (contentBytes && contentType) { + this._contentBytes = contentBytes; + this._contentType = contentType; + this._source = "bytes"; + } else if (filePath && contentType) { + if (!fs) { + throw new Error("File system support is not available in this environment"); + } + if (!fs.existsSync(filePath)) { + throw new Error(`File at path ${filePath} does not exist`); + } + this._contentBytes = this.readFile(filePath); + this._contentType = this._contentBytes ? contentType : void 0; + this._source = this._contentBytes ? "file" : void 0; + } else { + console.error("base64DataUri, or contentBytes and contentType, or filePath must be provided to LangfuseMedia"); + } + } + readFile(filePath) { + try { + if (!fs) { + throw new Error("File system support is not available in this environment"); + } + return fs.readFileSync(filePath); + } catch (error) { + console.error(`Error reading file at path ${filePath}`, error); + return void 0; + } + } + parseBase64DataUri(data) { + try { + if (!data || typeof data !== "string") { + throw new Error("Data URI is not a string"); + } + if (!data.startsWith("data:")) { + throw new Error("Data URI does not start with 'data:'"); + } + const [header, actualData] = data.slice(5).split(",", 2); + if (!header || !actualData) { + throw new Error("Invalid URI"); + } + const headerParts = header.split(";"); + if (!headerParts.includes("base64")) { + throw new Error("Data is not base64 encoded"); + } + const contentType = headerParts[0]; + if (!contentType) { + throw new Error("Content type is empty"); + } + return [Buffer.from(actualData, "base64"), contentType]; + } catch (error) { + console.error("Error parsing base64 data URI", error); + return [void 0, void 0]; + } + } + get contentLength() { + return this._contentBytes?.length; + } + get contentSha256Hash() { + if (!this._contentBytes) { + return void 0; + } + if (!cryptoModule) { + console.error("Crypto support is not available in this environment"); + return void 0; + } + const sha256Hash = cryptoModule.createHash("sha256").update(this._contentBytes).digest("base64"); + return sha256Hash; + } + toJSON() { + if (!this._contentType || !this._source || !this._mediaId) { + return ``; + } + return `@@@langfuseMedia:type=${this._contentType}|id=${this._mediaId}|source=${this._source}@@@`; + } + /** + * Parses a media reference string into a ParsedMediaReference. + * + * Example reference string: + * "@@@langfuseMedia:type=image/jpeg|id=some-uuid|source=base64DataUri@@@" + * + * @param referenceString - The reference string to parse. + * @returns An object with the mediaId, source, and contentType. + * + * @throws Error if the reference string is invalid or missing required fields. + */ + static parseReferenceString(referenceString) { + const prefix = "@@@langfuseMedia:"; + const suffix = "@@@"; + if (!referenceString.startsWith(prefix)) { + throw new Error("Reference string does not start with '@@@langfuseMedia:type='"); + } + if (!referenceString.endsWith(suffix)) { + throw new Error("Reference string does not end with '@@@'"); + } + const content = referenceString.slice(prefix.length, -suffix.length); + const pairs = content.split("|"); + const parsedData = {}; + for (const pair of pairs) { + const [key, value] = pair.split("=", 2); + parsedData[key] = value; + } + if (!("type" in parsedData && "id" in parsedData && "source" in parsedData)) { + throw new Error("Missing required fields in reference string"); + } + return { + mediaId: parsedData["id"], + source: parsedData["source"], + contentType: parsedData["type"] + }; + } + /** + * Replaces the media reference strings in an object with base64 data URIs for the media content. + * + * This method recursively traverses an object (up to a maximum depth of 10) looking for media reference strings + * in the format "@@@langfuseMedia:...@@@". When found, it fetches the actual media content using the provided + * Langfuse client and replaces the reference string with a base64 data URI. + * + * If fetching media content fails for a reference string, a warning is logged and the reference string is left unchanged. + * + * @param params - Configuration object + * @param params.obj - The object to process. Can be a primitive value, array, or nested object + * @param params.langfuseClient - Langfuse client instance used to fetch media content + * @param params.resolveWith - The representation of the media content to replace the media reference string with. Currently only "base64DataUri" is supported. + * @param params.maxDepth - Optional. Default is 10. The maximum depth to traverse the object. + * + * @returns A deep copy of the input object with all media references replaced with base64 data URIs where possible + * + * @example + * ```typescript + * const obj = { + * image: "@@@langfuseMedia:type=image/jpeg|id=123|source=bytes@@@", + * nested: { + * pdf: "@@@langfuseMedia:type=application/pdf|id=456|source=bytes@@@" + * } + * }; + * + * const result = await LangfuseMedia.resolveMediaReferences({ + * obj, + * langfuseClient + * }); + * + * // Result: + * // { + * // image: "data:image/jpeg;base64,/9j/4AAQSkZJRg...", + * // nested: { + * // pdf: "data:application/pdf;base64,JVBERi0xLjcK..." + * // } + * // } + * ``` + */ + static async resolveMediaReferences(params) { + const { + obj, + langfuseClient, + maxDepth = 10 + } = params; + async function traverse(obj2, depth) { + if (depth > maxDepth) { + return obj2; + } + if (typeof obj2 === "string") { + const regex = /@@@langfuseMedia:.+?@@@/g; + const referenceStringMatches = obj2.match(regex); + if (!referenceStringMatches) { + return obj2; + } + let result = obj2; + const referenceStringToMediaContentMap = /* @__PURE__ */ new Map(); + await Promise.all(referenceStringMatches.map(async (referenceString) => { + try { + const parsedMediaReference = _LangfuseMedia.parseReferenceString(referenceString); + const mediaData = await langfuseClient.fetchMedia(parsedMediaReference.mediaId); + const mediaContent = await langfuseClient.fetch(mediaData.url, { + method: "GET", + headers: {} + }); + if (mediaContent.status !== 200) { + throw new Error("Failed to fetch media content"); + } + const base64MediaContent = Buffer.from(await mediaContent.arrayBuffer()).toString("base64"); + const base64DataUri = `data:${mediaData.contentType};base64,${base64MediaContent}`; + referenceStringToMediaContentMap.set(referenceString, base64DataUri); + } catch (error) { + console.warn("Error fetching media content for reference string", referenceString, error); + } + })); + for (const [referenceString, base64MediaContent] of referenceStringToMediaContentMap.entries()) { + result = result.replaceAll(referenceString, base64MediaContent); + } + return result; + } + if (Array.isArray(obj2)) { + return Promise.all(obj2.map(async (item) => await traverse(item, depth + 1))); + } + if (typeof obj2 === "object" && obj2 !== null) { + return Object.fromEntries(await Promise.all(Object.entries(obj2).map(async ([key, value]) => [key, await traverse(value, depth + 1)]))); + } + return obj2; + } + return traverse(obj, 0); + } +}; +function isInSample(input, sampleRate) { + if (sampleRate === void 0) { + return true; + } else if (sampleRate === 0) { + return false; + } + if (sampleRate < 0 || sampleRate > 1 || isNaN(sampleRate)) { + console.warn("Sample rate must be between 0 and 1. Ignoring setting."); + return true; + } + return simpleHash(input) < sampleRate; +} +function simpleHash(str) { + let hash = 0; + const prime = 31; + for (let i = 0; i < str.length; i++) { + hash = hash * prime + str.charCodeAt(i) >>> 0; + } + hash = (hash >>> 16 ^ hash) * 73244475; + hash = (hash >>> 16 ^ hash) * 73244475; + hash = hash >>> 16 ^ hash; + return Math.abs(hash) / 2147483647; +} +var MAX_EVENT_SIZE_BYTES = getEnv("LANGFUSE_MAX_EVENT_SIZE_BYTES") ? Number(getEnv("LANGFUSE_MAX_EVENT_SIZE_BYTES")) : 1e6; +var MAX_BATCH_SIZE_BYTES = getEnv("LANGFUSE_MAX_BATCH_SIZE_BYTES") ? Number(getEnv("LANGFUSE_MAX_BATCH_SIZE_BYTES")) : 25e5; +var ENVIRONMENT_PATTERN = /^(?!langfuse)[a-z0-9_-]+$/; +var WHITELISTED_LANGFUSE_INTERNAL_ENVIRONMENTS = ["langfuse-prompt-experiment"]; +var LangfuseFetchHttpError = class extends Error { + constructor(response, body) { + super("HTTP error while fetching Langfuse: " + response.status + " and body: " + body); + this.response = response; + this.name = "LangfuseFetchHttpError"; + } +}; +var LangfuseFetchNetworkError = class extends Error { + constructor(error) { + super("Network error while fetching Langfuse", error instanceof Error ? { + cause: error + } : {}); + this.error = error; + this.name = "LangfuseFetchNetworkError"; + } +}; +function isLangfuseFetchHttpError(error) { + return typeof error === "object" && error.name === "LangfuseFetchHttpError"; +} +function isLangfuseFetchNetworkError(error) { + return typeof error === "object" && error.name === "LangfuseFetchNetworkError"; +} +function isLangfuseFetchError(err) { + return isLangfuseFetchHttpError(err) || isLangfuseFetchNetworkError(err); +} +var SUPPORT_URL = "https://langfuse.com/support"; +var API_DOCS_URL = "https://api.reference.langfuse.com"; +var RBAC_DOCS_URL = "https://langfuse.com/docs/rbac"; +var INSTALLATION_DOCS_URL = "https://langfuse.com/docs/sdk/typescript/guide"; +var RATE_LIMITS_URL = "https://langfuse.com/faq/all/api-limits"; +var NPM_PACKAGE_URL = "https://www.npmjs.com/package/langfuse"; +var updatePromptResponse = `Make sure to keep your SDK updated, refer to ${NPM_PACKAGE_URL} for details.`; +var defaultServerErrorPrompt = `This is an unusual occurrence and we are monitoring it closely. For help, please contact support: ${SUPPORT_URL}.`; +var defaultErrorResponse = `Unexpected error occurred. Please check your request and contact support: ${SUPPORT_URL}.`; +var errorResponseByCode = /* @__PURE__ */ new Map([ + // Internal error category: 5xx errors, 404 error + [500, `Internal server error occurred. For help, please contact support: ${SUPPORT_URL}`], + [501, `Not implemented. Please check your request and contact support for help: ${SUPPORT_URL}.`], + [502, `Bad gateway. ${defaultServerErrorPrompt}`], + [503, `Service unavailable. ${defaultServerErrorPrompt}`], + [504, `Gateway timeout. ${defaultServerErrorPrompt}`], + [404, `Internal error occurred. ${defaultServerErrorPrompt}`], + // Client error category: 4xx errors, excluding 404 + [400, `Bad request. Please check your request for any missing or incorrect parameters. Refer to our API docs: ${API_DOCS_URL} for details.`], + [401, `Unauthorized. Please check your public/private host settings. Refer to our installation and setup guide: ${INSTALLATION_DOCS_URL} for details on SDK configuration.`], + [403, `Forbidden. Please check your access control settings. Refer to our RBAC docs: ${RBAC_DOCS_URL} for details.`], + [429, `Rate limit exceeded. For more information on rate limits please see: ${RATE_LIMITS_URL}`] +]); +function getErrorResponseByCode(code) { + if (!code) { + return `${defaultErrorResponse} ${updatePromptResponse}`; + } + const errorResponse = errorResponseByCode.get(code) || defaultErrorResponse; + return `${code}: ${errorResponse} ${updatePromptResponse}`; +} +function logIngestionError(error) { + if (isLangfuseFetchHttpError(error)) { + const code = error.response.status; + const errorResponse = getErrorResponseByCode(code); + console.error("[Langfuse SDK]", errorResponse, `Error details: ${error}`); + } else if (isLangfuseFetchNetworkError(error)) { + console.error("[Langfuse SDK] Network error: ", error); + } else { + console.error("[Langfuse SDK] Unknown error:", error); + } +} +var LangfuseCoreStateless = class { + constructor(params) { + this.additionalHeaders = {}; + this.debugMode = false; + this.pendingEventProcessingPromises = {}; + this.pendingIngestionPromises = {}; + this.localEventExportMap = /* @__PURE__ */ new Map(); + this._events = new SimpleEventEmitter(); + const { + publicKey, + secretKey, + enabled, + _projectId, + _isLocalEventExportEnabled, + ...options + } = params; + this._events.on("error", (payload) => { + console.error(`[Langfuse SDK] ${typeof payload === "string" ? payload : JSON.stringify(payload)}`); + }); + this.enabled = enabled === false ? false : true; + this.publicKey = publicKey ?? ""; + this.secretKey = secretKey; + this.baseUrl = removeTrailingSlash(options?.baseUrl || "https://cloud.langfuse.com"); + this.additionalHeaders = options?.additionalHeaders || {}; + this.flushAt = options?.flushAt ? Math.max(options?.flushAt, 1) : 15; + this.flushInterval = options?.flushInterval ?? 1e4; + this.release = options?.release ?? getEnv("LANGFUSE_RELEASE") ?? getCommonReleaseEnvs() ?? void 0; + this.mask = options?.mask; + this.sampleRate = options?.sampleRate ?? (getEnv("LANGFUSE_SAMPLE_RATE") ? Number(getEnv("LANGFUSE_SAMPLE_RATE")) : void 0); + if (this.sampleRate) { + this._events.emit("debug", `Langfuse trace sampling enabled with sampleRate ${this.sampleRate}.`); + } + this.environment = options?.environment ?? getEnv("LANGFUSE_TRACING_ENVIRONMENT"); + if (this.environment && !(ENVIRONMENT_PATTERN.test(this.environment) || WHITELISTED_LANGFUSE_INTERNAL_ENVIRONMENTS.includes(this.environment)) && !_isLocalEventExportEnabled) { + this._events.emit("error", `Invalid tracing environment set: ${this.environment} . Environment must match regex ${ENVIRONMENT_PATTERN}. Events will be rejected by Langfuse server.`); + } + this._retryOptions = { + retryCount: options?.fetchRetryCount ?? 3, + retryDelay: options?.fetchRetryDelay ?? 3e3, + retryCheck: isLangfuseFetchError + }; + this.requestTimeout = options?.requestTimeout ?? 5e3; + this.sdkIntegration = options?.sdkIntegration ?? "DEFAULT"; + this.isLocalEventExportEnabled = _isLocalEventExportEnabled ?? false; + if (this.isLocalEventExportEnabled && !_projectId) { + this._events.emit("error", "Local event export is enabled, but no project ID was provided. Disabling local export."); + this.isLocalEventExportEnabled = false; + return; + } else if (!this.isLocalEventExportEnabled && _projectId) { + this._events.emit("error", "Local event export is disabled, but a project ID was provided. Disabling local export."); + this.isLocalEventExportEnabled = false; + return; + } else { + this.projectId = _projectId; + } + } + getSdkIntegration() { + return this.sdkIntegration; + } + getCommonEventProperties() { + return { + $lib: this.getLibraryId(), + $lib_version: this.getLibraryVersion() + }; + } + on(event, cb) { + return this._events.on(event, cb); + } + debug(enabled = true) { + this.removeDebugCallback?.(); + this.debugMode = enabled; + if (enabled) { + this.removeDebugCallback = this.on("*", (event, payload) => { + if (event === "error") { + return; + } + console.log("[Langfuse Debug]", event, JSON.stringify(payload)); + }); + } + } + /*** + *** Handlers for each object type + ***/ + traceStateless(body) { + const { + id: bodyId, + timestamp: bodyTimestamp, + release: bodyRelease, + ...rest + } = body; + const id = bodyId ?? generateUUID(); + const release = bodyRelease ?? this.release; + const parsedBody = { + id, + release, + timestamp: bodyTimestamp ?? /* @__PURE__ */ new Date(), + environment: this.environment, + ...rest + }; + this.enqueue("trace-create", parsedBody); + return id; + } + eventStateless(body) { + const { + id: bodyId, + startTime: bodyStartTime, + ...rest + } = body; + const id = bodyId ?? generateUUID(); + const parsedBody = { + id, + startTime: bodyStartTime ?? /* @__PURE__ */ new Date(), + environment: this.environment, + ...rest + }; + this.enqueue("event-create", parsedBody); + return id; + } + spanStateless(body) { + const { + id: bodyId, + startTime: bodyStartTime, + ...rest + } = body; + const id = bodyId || generateUUID(); + const parsedBody = { + id, + startTime: bodyStartTime ?? /* @__PURE__ */ new Date(), + environment: this.environment, + ...rest + }; + this.enqueue("span-create", parsedBody); + return id; + } + generationStateless(body) { + const { + id: bodyId, + startTime: bodyStartTime, + prompt: prompt2, + ...rest + } = body; + const promptDetails = prompt2 && !prompt2.isFallback ? { + promptName: prompt2.name, + promptVersion: prompt2.version + } : {}; + const id = bodyId || generateUUID(); + const parsedBody = { + id, + startTime: bodyStartTime ?? /* @__PURE__ */ new Date(), + environment: this.environment, + ...promptDetails, + ...rest + }; + this.enqueue("generation-create", parsedBody); + return id; + } + scoreStateless(body) { + const { + id: bodyId, + ...rest + } = body; + const id = bodyId || generateUUID(); + const parsedBody = { + id, + environment: this.environment, + ...rest + }; + this.enqueue("score-create", parsedBody); + return id; + } + updateSpanStateless(body) { + this.enqueue("span-update", body); + return body.id; + } + updateGenerationStateless(body) { + const { + prompt: prompt2, + ...rest + } = body; + const promptDetails = prompt2 && !prompt2.isFallback ? { + promptName: prompt2.name, + promptVersion: prompt2.version + } : {}; + const parsedBody = { + ...promptDetails, + ...rest + }; + this.enqueue("generation-update", parsedBody); + return body.id; + } + async _getDataset(name) { + const encodedName = encodeURIComponent(name); + return this.fetchAndLogErrors(`${this.baseUrl}/api/public/v2/datasets/${encodedName}`, this._getFetchOptions({ + method: "GET" + })); + } + async _getDatasetItems(query) { + const params = new URLSearchParams(); + Object.entries(query ?? {}).forEach(([key, value]) => { + if (value !== void 0 && value !== null) { + params.append(key, value.toString()); + } + }); + return this.fetchAndLogErrors(`${this.baseUrl}/api/public/dataset-items?${params}`, this._getFetchOptions({ + method: "GET" + })); + } + async _fetchMedia(id) { + return this.fetchAndLogErrors(`${this.baseUrl}/api/public/media/${id}`, this._getFetchOptions({ + method: "GET" + })); + } + async fetchTraces(query) { + const { + data, + meta + } = await this.fetchAndLogErrors(`${this.baseUrl}/api/public/traces?${encodeQueryParams(query)}`, this._getFetchOptions({ + method: "GET" + })); + return { + data, + meta + }; + } + async fetchTrace(traceId) { + const res = await this.fetchAndLogErrors(`${this.baseUrl}/api/public/traces/${traceId}`, this._getFetchOptions({ + method: "GET" + })); + return { + data: res + }; + } + async fetchObservations(query) { + const { + data, + meta + } = await this.fetchAndLogErrors(`${this.baseUrl}/api/public/observations?${encodeQueryParams(query)}`, this._getFetchOptions({ + method: "GET" + })); + return { + data, + meta + }; + } + async fetchObservation(observationId) { + const res = await this.fetchAndLogErrors(`${this.baseUrl}/api/public/observations/${observationId}`, this._getFetchOptions({ + method: "GET" + })); + return { + data: res + }; + } + async fetchSessions(query) { + const { + data, + meta + } = await this.fetchAndLogErrors(`${this.baseUrl}/api/public/sessions?${encodeQueryParams(query)}`, this._getFetchOptions({ + method: "GET" + })); + return { + data, + meta + }; + } + async getDatasetRun(params) { + const encodedDatasetName = encodeURIComponent(params.datasetName); + const encodedRunName = encodeURIComponent(params.runName); + return this.fetchAndLogErrors(`${this.baseUrl}/api/public/datasets/${encodedDatasetName}/runs/${encodedRunName}`, this._getFetchOptions({ + method: "GET" + })); + } + async getDatasetRuns(datasetName, query) { + return this.fetchAndLogErrors(`${this.baseUrl}/api/public/datasets/${encodeURIComponent(datasetName)}/runs?${encodeQueryParams(query)}`, this._getFetchOptions({ + method: "GET" + })); + } + async createDatasetRunItem(body) { + return this.fetchAndLogErrors(`${this.baseUrl}/api/public/dataset-run-items`, this._getFetchOptions({ + method: "POST", + body: JSON.stringify(body) + })); + } + /** + * Creates a dataset. Upserts the dataset if it already exists. + * + * @param dataset Can be either a string (name) or an object with name, description and metadata + * @returns A promise that resolves to the response of the create operation. + */ + async createDataset(dataset) { + const body = typeof dataset === "string" ? { + name: dataset + } : dataset; + return this.fetchAndLogErrors(`${this.baseUrl}/api/public/datasets`, this._getFetchOptions({ + method: "POST", + body: JSON.stringify(body) + })); + } + /** + * Creates a dataset item. Upserts the item if it already exists. + * @param body The body of the dataset item to be created. + * @returns A promise that resolves to the response of the create operation. + */ + async createDatasetItem(body) { + return this.fetchAndLogErrors(`${this.baseUrl}/api/public/dataset-items`, this._getFetchOptions({ + method: "POST", + body: JSON.stringify(body) + })); + } + async getDatasetItem(id) { + return this.fetchAndLogErrors(`${this.baseUrl}/api/public/dataset-items/${id}`, this._getFetchOptions({ + method: "GET" + })); + } + _parsePayload(response) { + try { + return JSON.parse(response); + } catch { + return response; + } + } + async createPromptStateless(body) { + return this.fetchAndLogErrors(`${this.baseUrl}/api/public/v2/prompts`, this._getFetchOptions({ + method: "POST", + body: JSON.stringify(body) + })); + } + async updatePromptStateless(body) { + return this.fetchAndLogErrors(`${this.baseUrl}/api/public/v2/prompts/${encodeURIComponent(body.name)}/versions/${encodeURIComponent(body.version)}`, this._getFetchOptions({ + method: "PATCH", + body: JSON.stringify(body) + })); + } + async getPromptStateless(name, version2, label, maxRetries, requestTimeout) { + const encodedName = encodeURIComponent(name); + const params = new URLSearchParams(); + if (version2 && label) { + throw new Error("Provide either version or label, not both."); + } + if (version2) { + params.append("version", version2.toString()); + } + if (label) { + params.append("label", label); + } + const url = `${this.baseUrl}/api/public/v2/prompts/${encodedName}${params.size ? "?" + params : ""}`; + const boundedMaxRetries = this._getBoundedMaxRetries({ + maxRetries, + defaultMaxRetries: 2, + maxRetriesUpperBound: 4 + }); + const retryOptions = { + ...this._retryOptions, + retryCount: boundedMaxRetries, + retryDelay: 500 + }; + const retryLogger = (string) => this._events.emit("retry", string + ", " + url + ", " + JSON.stringify(retryOptions)); + return retriable(async () => { + const res = await this.fetch(url, this._getFetchOptions({ + method: "GET", + fetchTimeout: requestTimeout ?? this.requestTimeout + })).catch((e) => { + if (e.name === "AbortError") { + throw new LangfuseFetchNetworkError("Fetch request timed out"); + } + throw new LangfuseFetchNetworkError(e); + }); + if (res.status >= 500) { + throw new LangfuseFetchHttpError(res, await res.text()); + } + const data = await res.json(); + return { + fetchResult: res.status === 200 ? "success" : "failure", + data + }; + }, retryOptions, retryLogger); + } + _getBoundedMaxRetries(params) { + const defaultMaxRetries = Math.max(params.defaultMaxRetries ?? 2, 0); + const maxRetriesUpperBound = Math.max(params.maxRetriesUpperBound ?? 4, 0); + if (params.maxRetries === void 0) { + return defaultMaxRetries; + } + return Math.min(Math.max(params.maxRetries, 0), maxRetriesUpperBound); + } + /*** + *** QUEUEING AND FLUSHING + ***/ + enqueue(type, body) { + if (!this.enabled) { + return; + } + const traceId = this.parseTraceId(type, body); + if (!traceId) { + this._events.emit("warning", "Failed to parse traceID for sampling. Please open a Github issue in https://github.com/langfuse/langfuse/issues/new/choose"); + } else if (!isInSample(traceId, this.sampleRate)) { + this._events.emit("debug", `Event with trace ID ${traceId} is out of sample. Skipping.`); + return; + } + const promise = this.processEnqueueEvent(type, body); + const promiseId = generateUUID(); + this.pendingEventProcessingPromises[promiseId] = promise; + promise.catch((e) => { + this._events.emit("error", e); + }).finally(() => { + delete this.pendingEventProcessingPromises[promiseId]; + }); + } + async processEnqueueEvent(type, body) { + this.maskEventBodyInPlace(body); + await this.processMediaInEvent(type, body); + const finalEventBody = this.truncateEventBody(body, MAX_EVENT_SIZE_BYTES); + try { + JSON.stringify(finalEventBody); + } catch (e) { + this._events.emit("error", `Event Body for ${type} is not JSON-serializable: ${e}`); + return; + } + const queue = this.getPersistedProperty(LangfusePersistedProperty.Queue) || []; + queue.push({ + id: generateUUID(), + type, + timestamp: currentISOTime(), + body: finalEventBody, + // TODO: fix typecast. EventBody is not correctly narrowed to the correct type dictated by the 'type' property. This should be part of a larger type cleanup. + metadata: void 0 + }); + this.setPersistedProperty(LangfusePersistedProperty.Queue, queue); + this._events.emit(type, finalEventBody); + if (queue.length >= this.flushAt) { + this.flush(); + } + if (this.flushInterval && !this._flushTimer) { + this._flushTimer = safeSetTimeout(() => this.flush(), this.flushInterval); + } + } + maskEventBodyInPlace(body) { + if (!this.mask) { + return; + } + const maskableKeys = ["input", "output"]; + for (const key of maskableKeys) { + if (key in body) { + try { + body[key] = this.mask({ + data: body[key] + }); + } catch (e) { + this._events.emit("error", `Error masking ${key}: ${e}`); + body[key] = ""; + } + } + } + } + /** + * Truncates the event body if its byte size exceeds the specified maximum byte size. + * Emits a warning event if truncation occurs. + * The fields that may be truncated are: "input", "output", and "metadata". + * The fields are truncated in the order of their size, from largest to smallest until the total byte size is within the limit. + */ + truncateEventBody(body, maxByteSize) { + const bodySize = this.getByteSize(body); + if (bodySize <= maxByteSize) { + return body; + } + this._events.emit("warning", `Event Body is too large (${bodySize} bytes) and will be truncated`); + const keysToCheck = ["input", "output", "metadata"]; + const keySizes = keysToCheck.map((key) => ({ + key, + size: key in body ? this.getByteSize(body[key]) : 0 + })).sort((a, b) => b.size - a.size); + let result = { + ...body + }; + let currentSize = bodySize; + for (const { + key, + size + } of keySizes) { + if (currentSize > maxByteSize && Object.prototype.hasOwnProperty.call(result, key)) { + result = { + ...result, + [key]: "" + }; + this._events.emit("warning", `Truncated ${key} due to total size exceeding limit`); + currentSize -= size; + } + } + return result; + } + getByteSize(obj) { + const serialized = JSON.stringify(obj); + if (typeof TextEncoder !== "undefined") { + return new TextEncoder().encode(serialized).length; + } else { + return encodeURIComponent(serialized).replace(/%[A-F\d]{2}/g, "U").length; + } + } + async processMediaInEvent(type, body) { + if (!body) { + return; + } + const traceId = this.parseTraceId(type, body); + if (!traceId) { + this._events.emit("warning", "traceId is required for media upload"); + return; + } + const observationId = (type.includes("generation") || type.includes("span")) && body.id ? body.id : void 0; + await Promise.all(["input", "output", "metadata"].map(async (field) => { + if (body[field]) { + body[field] = await this.findAndProcessMedia({ + data: body[field], + traceId, + observationId, + field + }).catch((e) => { + this._events.emit("error", `Error processing multimodal event: ${e}`); + }) ?? body[field]; + } + })); + } + parseTraceId(type, body) { + return "traceId" in body ? body.traceId : type.includes("trace") ? body.id : void 0; + } + async findAndProcessMedia({ + data, + traceId, + observationId, + field + }) { + const seenObjects = /* @__PURE__ */ new WeakMap(); + const maxLevels = 10; + const processRecursively = async (data2, level) => { + if (typeof data2 === "string" && data2.startsWith("data:")) { + const media = new LangfuseMedia({ + base64DataUri: data2 + }); + await this.processMediaItem({ + media, + traceId, + observationId, + field + }); + return media; + } + if (typeof data2 !== "object" || data2 === null) { + return data2; + } + if (seenObjects.has(data2) || level > maxLevels) { + return data2; + } + seenObjects.set(data2, true); + if (data2 instanceof LangfuseMedia || Object.prototype.toString.call(data2) === "[object LangfuseMedia]") { + await this.processMediaItem({ + media: data2, + traceId, + observationId, + field + }); + return data2; + } + if (Array.isArray(data2)) { + return await Promise.all(data2.map((item) => processRecursively(item, level + 1))); + } + if (typeof data2 === "object" && data2 !== null) { + if ("input_audio" in data2 && typeof data2["input_audio"] === "object" && "data" in data2.input_audio) { + const media = new LangfuseMedia({ + base64DataUri: `data:audio/${data2.input_audio["format"] || "wav"};base64,${data2.input_audio.data}` + }); + await this.processMediaItem({ + media, + traceId, + observationId, + field + }); + return { + ...data2, + input_audio: { + ...data2.input_audio, + data: media + } + }; + } + if ("audio" in data2 && typeof data2["audio"] === "object" && "data" in data2.audio) { + const media = new LangfuseMedia({ + base64DataUri: `data:audio/${data2.audio["format"] || "wav"};base64,${data2.audio.data}` + }); + await this.processMediaItem({ + media, + traceId, + observationId, + field + }); + return { + ...data2, + audio: { + ...data2.audio, + data: media + } + }; + } + return Object.fromEntries(await Promise.all(Object.entries(data2).map(async ([key, value]) => [key, await processRecursively(value, level + 1)]))); + } + return data2; + }; + return await processRecursively(data, 1); + } + async processMediaItem({ + media, + traceId, + observationId, + field + }) { + try { + if (!media.contentLength || !media._contentType || !media.contentSha256Hash || !media._contentBytes) { + return; + } + const getUploadUrlBody = { + contentLength: media.contentLength, + traceId, + observationId, + field, + contentType: media._contentType, + sha256Hash: media.contentSha256Hash + }; + const fetchResponse = await this.fetch(`${this.baseUrl}/api/public/media`, this._getFetchOptions({ + method: "POST", + body: JSON.stringify(getUploadUrlBody) + })); + const uploadUrlResponse = await fetchResponse.json(); + const { + uploadUrl, + mediaId + } = uploadUrlResponse; + media._mediaId = mediaId; + if (uploadUrl) { + this._events.emit("debug", `Uploading media ${mediaId}`); + const startTime = Date.now(); + const uploadResponse = await this.uploadMediaWithBackoff({ + uploadUrl, + contentBytes: media._contentBytes, + contentType: media._contentType, + contentSha256Hash: media.contentSha256Hash, + maxRetries: 3, + baseDelay: 1e3 + }); + if (!uploadResponse) { + throw Error("Media upload process failed"); + } + const patchMediaBody = { + uploadedAt: (/* @__PURE__ */ new Date()).toISOString(), + uploadHttpStatus: uploadResponse.status, + uploadHttpError: await uploadResponse.text(), + uploadTimeMs: Date.now() - startTime + }; + await this.fetch(`${this.baseUrl}/api/public/media/${mediaId}`, this._getFetchOptions({ + method: "PATCH", + body: JSON.stringify(patchMediaBody) + })); + this._events.emit("debug", `Media upload status reported for ${mediaId}`); + } else { + this._events.emit("debug", `Media ${mediaId} already uploaded`); + } + } catch (err) { + this._events.emit("error", `Error processing media item: ${err}`); + } + } + async uploadMediaWithBackoff(params) { + const { + uploadUrl, + contentType, + contentSha256Hash, + contentBytes, + maxRetries, + baseDelay + } = params; + for (let attempt = 0; attempt <= maxRetries; attempt++) { + try { + const uploadResponse = await this.fetch(uploadUrl, { + method: "PUT", + body: contentBytes, + headers: { + "Content-Type": contentType, + "x-amz-checksum-sha256": contentSha256Hash, + "x-ms-blob-type": "BlockBlob" + } + }); + if (attempt < maxRetries && uploadResponse.status !== 200 && uploadResponse.status !== 201) { + throw new Error(`Upload failed with status ${uploadResponse.status}`); + } + return uploadResponse; + } catch (e) { + if (attempt === maxRetries) { + throw e; + } + const delay = baseDelay * Math.pow(2, attempt); + const jitter = Math.random() * 1e3; + await new Promise((resolve) => setTimeout(resolve, delay + jitter)); + } + } + } + /** + * Asynchronously flushes all events that are not yet sent to the server. + * This function always resolves, even if there were errors when flushing. + * Errors are emitted as "error" events and the promise resolves. + * + * @returns {Promise} A promise that resolves when the flushing is completed. + */ + async flushAsync() { + await Promise.all(Object.values(this.pendingEventProcessingPromises)).catch((e) => { + logIngestionError(e); + }); + return new Promise((resolve, _reject) => { + try { + this.flush((err, data) => { + if (err) { + logIngestionError(err); + resolve(); + } else { + resolve(data); + } + }); + } catch (e) { + console.error("[Langfuse SDK] Error while flushing Langfuse", e); + } + }); + } + // Flushes all events that are not yet sent to the server + flush(callback) { + if (this._flushTimer) { + clearTimeout(this._flushTimer); + this._flushTimer = null; + } + const queue = this.getPersistedProperty(LangfusePersistedProperty.Queue) || []; + if (!queue.length) { + return callback?.(); + } + const items = queue.splice(0, this.flushAt); + const { + processedItems, + remainingItems + } = this.processQueueItems(items, MAX_EVENT_SIZE_BYTES, MAX_BATCH_SIZE_BYTES); + this.setPersistedProperty(LangfusePersistedProperty.Queue, [...remainingItems, ...queue]); + const promiseUUID = generateUUID(); + const done = (err) => { + if (err) { + this._events.emit("warning", err); + } + callback?.(err, items); + this._events.emit("flush", items); + }; + if (this.isLocalEventExportEnabled && this.projectId) { + if (!this.localEventExportMap.has(this.projectId)) { + this.localEventExportMap.set(this.projectId, [...items]); + } else { + this.localEventExportMap.get(this.projectId)?.push(...items); + } + done(); + return; + } + const payload = JSON.stringify({ + batch: processedItems, + metadata: { + batch_size: processedItems.length, + sdk_integration: this.sdkIntegration, + sdk_version: this.getLibraryVersion(), + sdk_variant: this.getLibraryId(), + public_key: this.publicKey, + sdk_name: "langfuse-js" + } + }); + const url = `${this.baseUrl}/api/public/ingestion`; + const fetchOptions = this._getFetchOptions({ + method: "POST", + body: payload + }); + const requestPromise = this.fetchWithRetry(url, fetchOptions).then(() => done()).catch((err) => { + done(err); + }); + this.pendingIngestionPromises[promiseUUID] = requestPromise; + requestPromise.finally(() => { + delete this.pendingIngestionPromises[promiseUUID]; + }); + } + processQueueItems(queue, MAX_MSG_SIZE, BATCH_SIZE_LIMIT) { + let totalSize = 0; + const processedItems = []; + const remainingItems = []; + for (let i = 0; i < queue.length; i++) { + try { + const itemSize = new Blob([JSON.stringify(queue[i])]).size; + if (itemSize > MAX_MSG_SIZE) { + console.warn(`Item exceeds size limit (size: ${itemSize}), dropping item.`); + continue; + } + if (totalSize + itemSize >= BATCH_SIZE_LIMIT) { + console.debug(`hit batch size limit (size: ${totalSize + itemSize})`); + remainingItems.push(...queue.slice(i)); + console.log(`Remaining items: ${remainingItems.length}`); + console.log(`processes items: ${processedItems.length}`); + break; + } + totalSize += itemSize; + processedItems.push(queue[i]); + } catch (error) { + this._events.emit("error", error); + remainingItems.push(...queue.slice(i)); + break; + } + } + return { + processedItems, + remainingItems + }; + } + _getFetchOptions(p) { + const fetchOptions = { + method: p.method, + headers: { + "Content-Type": "application/json", + "X-Langfuse-Sdk-Name": "langfuse-js", + "X-Langfuse-Sdk-Version": this.getLibraryVersion(), + "X-Langfuse-Sdk-Variant": this.getLibraryId(), + "X-Langfuse-Sdk-Integration": this.sdkIntegration, + "X-Langfuse-Public-Key": this.publicKey, + ...this.additionalHeaders, + ...this.constructAuthorizationHeader(this.publicKey, this.secretKey) + }, + body: p.body, + ...p.fetchTimeout !== void 0 ? { + signal: AbortSignal.timeout(p.fetchTimeout) + } : {} + }; + return fetchOptions; + } + constructAuthorizationHeader(publicKey, secretKey) { + if (secretKey === void 0) { + return { + Authorization: "Bearer " + publicKey + }; + } else { + const encodedCredentials = typeof btoa === "function" ? ( + // btoa() is available, the code is running in a browser or edge environment + btoa(publicKey + ":" + secretKey) + ) : ( + // btoa() is not available, the code is running in Node.js + Buffer.from(publicKey + ":" + secretKey).toString("base64") + ); + return { + Authorization: "Basic " + encodedCredentials + }; + } + } + async fetchWithRetry(url, options, retryOptions) { + AbortSignal.timeout ??= function timeout(ms) { + const ctrl = new AbortController(); + setTimeout(() => ctrl.abort(), ms); + return ctrl.signal; + }; + return await retriable(async () => { + let res = null; + try { + res = await this.fetch(url, { + signal: AbortSignal.timeout(this.requestTimeout), + ...options + }); + } catch (e) { + throw new LangfuseFetchNetworkError(e); + } + if (res.status < 200 || res.status >= 400) { + const body = await res.json(); + throw new LangfuseFetchHttpError(res, JSON.stringify(body)); + } + const returnBody = await res.json(); + if (res.status === 207 && returnBody.errors.length > 0) { + throw new LangfuseFetchHttpError(res, JSON.stringify(returnBody.errors)); + } + return res; + }, { + ...this._retryOptions, + ...retryOptions + }, (string) => this._events.emit("retry", string + ", " + url + ", " + JSON.stringify(options))); + } + async fetchAndLogErrors(url, options) { + const res = await this.fetch(url, options); + const data = res.status === 429 ? await res.text() : await res.json(); + if (res.status < 200 || res.status >= 400) { + logIngestionError(new LangfuseFetchHttpError(res, JSON.stringify(data))); + } + return data; + } + async shutdownAsync() { + clearTimeout(this._flushTimer); + try { + await this.flushAsync(); + await Promise.all(Object.values(this.pendingIngestionPromises).map((x) => x.catch(() => { + }))); + await this.flushAsync(); + } catch (e) { + console.error("[Langfuse SDK] Error while shutting down Langfuse", e); + } + } + async _exportLocalEvents(projectId) { + if (this.isLocalEventExportEnabled) { + clearTimeout(this._flushTimer); + await this.flushAsync(); + const events = this.localEventExportMap.get(projectId) ?? []; + this.localEventExportMap.delete(projectId); + return events; + } else { + this._events.emit("error", "Local event exports are disabled, but _exportLocalEvents() was called."); + return []; + } + } + shutdown() { + console.warn("shutdown() is deprecated. It does not wait for all events to be processed. Please use shutdownAsync() instead."); + void this.shutdownAsync(); + } + async awaitAllQueuedAndPendingRequests() { + clearTimeout(this._flushTimer); + await this.flushAsync(); + await Promise.all(Object.values(this.pendingIngestionPromises)); + } +}; +var LangfuseCore = class extends LangfuseCoreStateless { + constructor(params) { + const { + publicKey, + secretKey, + enabled, + _isLocalEventExportEnabled + } = params; + let isObservabilityEnabled = enabled === false ? false : true; + if (_isLocalEventExportEnabled) { + isObservabilityEnabled = true; + } else if (!secretKey) { + isObservabilityEnabled = false; + if (enabled !== false) { + console.warn("Langfuse secret key was not passed to constructor or not set as 'LANGFUSE_SECRET_KEY' environment variable. No observability data will be sent to Langfuse."); + } + } else if (!publicKey) { + isObservabilityEnabled = false; + if (enabled !== false) { + console.warn("Langfuse public key was not passed to constructor or not set as 'LANGFUSE_PUBLIC_KEY' environment variable. No observability data will be sent to Langfuse."); + } + } + super({ + ...params, + enabled: isObservabilityEnabled + }); + this._promptCache = new LangfusePromptCache(); + } + trace(body) { + const id = this.traceStateless(body ?? {}); + const t = new LangfuseTraceClient(this, id); + if (getEnv("DEFER") && body) { + try { + const deferRuntime = getEnv("__deferRuntime"); + if (deferRuntime) { + deferRuntime.langfuseTraces([{ + id, + name: body.name || "", + url: t.getTraceUrl() + }]); + } + } catch { + } + } + return t; + } + span(body) { + const traceId = body.traceId || this.traceStateless({ + name: body.name + }); + const id = this.spanStateless({ + ...body, + traceId + }); + return new LangfuseSpanClient(this, id, traceId); + } + generation(body) { + const traceId = body.traceId || this.traceStateless({ + name: body.name + }); + const id = this.generationStateless({ + ...body, + traceId + }); + return new LangfuseGenerationClient(this, id, traceId); + } + event(body) { + const traceId = body.traceId || this.traceStateless({ + name: body.name + }); + const id = this.eventStateless({ + ...body, + traceId + }); + return new LangfuseEventClient(this, id, traceId); + } + score(body) { + this.scoreStateless(body); + return this; + } + async getDataset(name, options) { + const dataset = await this._getDataset(name); + const items = []; + let page = 1; + while (true) { + const itemsResponse = await this._getDatasetItems({ + datasetName: name, + limit: options?.fetchItemsPageSize ?? 50, + page + }); + items.push(...itemsResponse.data); + if (itemsResponse.meta.totalPages <= page) { + break; + } + page++; + } + const returnDataset = { + ...dataset, + description: dataset.description ?? void 0, + metadata: dataset.metadata ?? void 0, + items: items.map((item) => ({ + ...item, + link: async (obj, runName, runArgs) => { + await this.awaitAllQueuedAndPendingRequests(); + const data = await this.createDatasetRunItem({ + runName, + datasetItemId: item.id, + observationId: obj.observationId, + traceId: obj.traceId, + runDescription: runArgs?.description, + metadata: runArgs?.metadata + }); + return data; + } + })) + }; + return returnDataset; + } + async createPrompt(body) { + const labels = body.labels ?? []; + const promptResponse = body.type === "chat" ? await this.createPromptStateless({ + ...body, + prompt: body.prompt.map((item) => { + if ("type" in item && item.type === ChatMessageType.Placeholder) { + return { + type: ChatMessageType.Placeholder, + name: item.name + }; + } else { + return { + type: ChatMessageType.ChatMessage, + ...item + }; + } + }), + labels: body.isActive ? [.../* @__PURE__ */ new Set([...labels, "production"])] : labels + // backward compatibility for isActive + }) : await this.createPromptStateless({ + ...body, + type: body.type ?? "text", + labels: body.isActive ? [.../* @__PURE__ */ new Set([...labels, "production"])] : labels + // backward compatibility for isActive + }); + if (promptResponse.type === "chat") { + return new ChatPromptClient(promptResponse); + } + return new TextPromptClient(promptResponse); + } + async updatePrompt(body) { + const newPrompt = await this.updatePromptStateless(body); + this._promptCache.invalidate(body.name); + return newPrompt; + } + async getPrompt(name, version2, options) { + const cacheKey = this._getPromptCacheKey({ + name, + version: version2, + label: options?.label + }); + const cachedPrompt = this._promptCache.getIncludingExpired(cacheKey); + if (!cachedPrompt || options?.cacheTtlSeconds === 0) { + try { + return await this._fetchPromptAndUpdateCache({ + name, + version: version2, + label: options?.label, + cacheTtlSeconds: options?.cacheTtlSeconds, + maxRetries: options?.maxRetries, + fetchTimeout: options?.fetchTimeoutMs + }); + } catch (err) { + if (options?.fallback) { + const sharedFallbackParams = { + name, + version: version2 ?? 0, + labels: options.label ? [options.label] : [], + cacheTtlSeconds: options?.cacheTtlSeconds, + config: {}, + tags: [] + }; + if (options.type === "chat") { + return new ChatPromptClient({ + ...sharedFallbackParams, + type: "chat", + prompt: options.fallback.map((msg) => ({ + type: ChatMessageType.ChatMessage, + ...msg + })) + }, true); + } else { + return new TextPromptClient({ + ...sharedFallbackParams, + type: "text", + prompt: options.fallback + }, true); + } + } + throw err; + } + } + if (cachedPrompt.isExpired) { + if (!this._promptCache.isRefreshing(cacheKey)) { + const refreshPromptPromise = this._fetchPromptAndUpdateCache({ + name, + version: version2, + label: options?.label, + cacheTtlSeconds: options?.cacheTtlSeconds, + maxRetries: options?.maxRetries, + fetchTimeout: options?.fetchTimeoutMs + }).catch(() => { + console.warn(`Failed to refresh prompt cache '${cacheKey}', stale cache will be used until next refresh succeeds.`); + }); + this._promptCache.addRefreshingPromise(cacheKey, refreshPromptPromise); + } + return cachedPrompt.value; + } + return cachedPrompt.value; + } + _getPromptCacheKey(params) { + const { + name, + version: version2, + label + } = params; + const parts = [name]; + if (version2 !== void 0) { + parts.push("version:" + version2.toString()); + } else if (label !== void 0) { + parts.push("label:" + label); + } else { + parts.push("label:production"); + } + return parts.join("-"); + } + async _fetchPromptAndUpdateCache(params) { + const cacheKey = this._getPromptCacheKey(params); + try { + const { + name, + version: version2, + cacheTtlSeconds, + label, + maxRetries, + fetchTimeout + } = params; + const { + data, + fetchResult + } = await this.getPromptStateless(name, version2, label, maxRetries, fetchTimeout); + if (fetchResult === "failure") { + throw Error(data.message ?? "Internal error while fetching prompt"); + } + let prompt2; + if (data.type === "chat") { + prompt2 = new ChatPromptClient(data); + } else { + prompt2 = new TextPromptClient(data); + } + this._promptCache.set(cacheKey, prompt2, cacheTtlSeconds); + return prompt2; + } catch (error) { + console.error(`[Langfuse SDK] Error while fetching prompt '${cacheKey}':`, error); + throw error; + } + } + async fetchMedia(id) { + return await this._fetchMedia(id); + } + /** + * Replaces the media reference strings in an object with base64 data URIs for the media content. + * + * This method recursively traverses an object (up to a maximum depth of 10) looking for media reference strings + * in the format "@@@langfuseMedia:...@@@". When found, it fetches the actual media content using the provided + * Langfuse client and replaces the reference string with a base64 data URI. + * + * If fetching media content fails for a reference string, a warning is logged and the reference string is left unchanged. + * + * @param params - Configuration object + * @param params.obj - The object to process. Can be a primitive value, array, or nested object + * @param params.langfuseClient - Langfuse client instance used to fetch media content + * @param params.resolveWith - The representation of the media content to replace the media reference string with. Currently only "base64DataUri" is supported. + * @param params.maxDepth - Optional. Default is 10. The maximum depth to traverse the object. + * + * @returns A deep copy of the input object with all media references replaced with base64 data URIs where possible + * + * @example + * ```typescript + * const obj = { + * image: "@@@langfuseMedia:type=image/jpeg|id=123|source=bytes@@@", + * nested: { + * pdf: "@@@langfuseMedia:type=application/pdf|id=456|source=bytes@@@" + * } + * }; + * + * const result = await LangfuseMedia.resolveMediaReferences({ + * obj, + * langfuseClient + * }); + * + * // Result: + * // { + * // image: "data:image/jpeg;base64,/9j/4AAQSkZJRg...", + * // nested: { + * // pdf: "data:application/pdf;base64,JVBERi0xLjcK..." + * // } + * // } + * ``` + */ + async resolveMediaReferences(params) { + const { + obj, + ...rest + } = params; + return LangfuseMedia.resolveMediaReferences({ + ...rest, + langfuseClient: this, + obj + }); + } + _updateSpan(body) { + this.updateSpanStateless(body); + return this; + } + _updateGeneration(body) { + this.updateGenerationStateless(body); + return this; + } +}; +var LangfuseObjectClient = class { + constructor({ + client, + id, + traceId, + observationId + }) { + this.client = client; + this.id = id; + this.traceId = traceId; + this.observationId = observationId; + } + event(body) { + return this.client.event({ + ...body, + traceId: this.traceId, + parentObservationId: this.observationId + }); + } + span(body) { + return this.client.span({ + ...body, + traceId: this.traceId, + parentObservationId: this.observationId + }); + } + generation(body) { + return this.client.generation({ + ...body, + traceId: this.traceId, + parentObservationId: this.observationId + }); + } + score(body) { + this.client.score({ + ...body, + traceId: this.traceId, + observationId: this.observationId + }); + return this; + } + getTraceUrl() { + return `${this.client.baseUrl}/trace/${this.traceId}`; + } +}; +var LangfuseTraceClient = class extends LangfuseObjectClient { + constructor(client, traceId) { + super({ + client, + id: traceId, + traceId, + observationId: null + }); + } + update(body) { + this.client.trace({ + ...body, + id: this.id + }); + return this; + } +}; +var LangfuseObservationClient = class extends LangfuseObjectClient { + constructor(client, id, traceId) { + super({ + client, + id, + traceId, + observationId: id + }); + } +}; +var LangfuseSpanClient = class extends LangfuseObservationClient { + constructor(client, id, traceId) { + super(client, id, traceId); + } + update(body) { + this.client._updateSpan({ + ...body, + id: this.id, + traceId: this.traceId + }); + return this; + } + end(body) { + this.client._updateSpan({ + ...body, + id: this.id, + traceId: this.traceId, + endTime: /* @__PURE__ */ new Date() + }); + return this; + } +}; +var LangfuseGenerationClient = class extends LangfuseObservationClient { + constructor(client, id, traceId) { + super(client, id, traceId); + } + update(body) { + this.client._updateGeneration({ + ...body, + id: this.id, + traceId: this.traceId + }); + return this; + } + end(body) { + this.client._updateGeneration({ + ...body, + id: this.id, + traceId: this.traceId, + endTime: /* @__PURE__ */ new Date() + }); + return this; + } +}; +var LangfuseEventClient = class extends LangfuseObservationClient { + constructor(client, id, traceId) { + super(client, id, traceId); + } +}; + +// node_modules/langfuse/lib/index.mjs +var cookieStore = { + getItem(key) { + try { + const nameEQ = key + "="; + const ca = document.cookie.split(";"); + for (let i = 0; i < ca.length; i++) { + let c = ca[i]; + while (c.charAt(0) == " ") { + c = c.substring(1, c.length); + } + if (c.indexOf(nameEQ) === 0) { + return decodeURIComponent(c.substring(nameEQ.length, c.length)); + } + } + } catch (err) { + } + return null; + }, + setItem(key, value) { + try { + const cdomain = "", expires = "", secure = ""; + const new_cookie_val = key + "=" + encodeURIComponent(value) + expires + "; path=/" + cdomain + secure; + document.cookie = new_cookie_val; + } catch (err) { + return; + } + }, + removeItem(name) { + try { + cookieStore.setItem(name, ""); + } catch (err) { + return; + } + }, + clear() { + document.cookie = ""; + }, + getAllKeys() { + const ca = document.cookie.split(";"); + const keys = []; + for (let i = 0; i < ca.length; i++) { + let c = ca[i]; + while (c.charAt(0) == " ") { + c = c.substring(1, c.length); + } + keys.push(c.split("=")[0]); + } + return keys; + } +}; +var createStorageLike = (store) => { + return { + getItem(key) { + return store.getItem(key); + }, + setItem(key, value) { + store.setItem(key, value); + }, + removeItem(key) { + store.removeItem(key); + }, + clear() { + store.clear(); + }, + getAllKeys() { + const keys = []; + for (const key in localStorage) { + keys.push(key); + } + return keys; + } + }; +}; +var checkStoreIsSupported = (storage, key = "__mplssupport__") => { + if (!window) { + return false; + } + try { + const val = "xyz"; + storage.setItem(key, val); + if (storage.getItem(key) !== val) { + return false; + } + storage.removeItem(key); + return true; + } catch (err) { + return false; + } +}; +var localStore = void 0; +var sessionStore = void 0; +var createMemoryStorage = () => { + const _cache = {}; + const store = { + getItem(key) { + return _cache[key]; + }, + setItem(key, value) { + _cache[key] = value !== null ? value : void 0; + }, + removeItem(key) { + delete _cache[key]; + }, + clear() { + for (const key in _cache) { + delete _cache[key]; + } + }, + getAllKeys() { + const keys = []; + for (const key in _cache) { + keys.push(key); + } + return keys; + } + }; + return store; +}; +var getStorage = (type, window2) => { + if (typeof window2 !== void 0 && window2) { + if (!localStorage) { + const _localStore = createStorageLike(window2.localStorage); + localStore = checkStoreIsSupported(_localStore) ? _localStore : void 0; + } + if (!sessionStore) { + const _sessionStore = createStorageLike(window2.sessionStorage); + sessionStore = checkStoreIsSupported(_sessionStore) ? _sessionStore : void 0; + } + } + switch (type) { + case "cookie": + return cookieStore || localStore || sessionStore || createMemoryStorage(); + case "localStorage": + return localStore || sessionStore || createMemoryStorage(); + case "sessionStorage": + return sessionStore || createMemoryStorage(); + case "memory": + return createMemoryStorage(); + default: + return createMemoryStorage(); + } +}; +var ContentType; +(function(ContentType2) { + ContentType2["Json"] = "application/json"; + ContentType2["FormData"] = "multipart/form-data"; + ContentType2["UrlEncoded"] = "application/x-www-form-urlencoded"; + ContentType2["Text"] = "text/plain"; +})(ContentType || (ContentType = {})); +var HttpClient = class { + constructor(apiConfig = {}) { + this.baseUrl = ""; + this.securityData = null; + this.abortControllers = /* @__PURE__ */ new Map(); + this.customFetch = (...fetchParams) => fetch(...fetchParams); + this.baseApiParams = { + credentials: "same-origin", + headers: {}, + redirect: "follow", + referrerPolicy: "no-referrer" + }; + this.setSecurityData = (data) => { + this.securityData = data; + }; + this.contentFormatters = { + [ContentType.Json]: (input) => input !== null && (typeof input === "object" || typeof input === "string") ? JSON.stringify(input) : input, + [ContentType.Text]: (input) => input !== null && typeof input !== "string" ? JSON.stringify(input) : input, + [ContentType.FormData]: (input) => Object.keys(input || {}).reduce((formData, key) => { + const property = input[key]; + formData.append(key, property instanceof Blob ? property : typeof property === "object" && property !== null ? JSON.stringify(property) : `${property}`); + return formData; + }, new FormData()), + [ContentType.UrlEncoded]: (input) => this.toQueryString(input) + }; + this.createAbortSignal = (cancelToken) => { + if (this.abortControllers.has(cancelToken)) { + const abortController2 = this.abortControllers.get(cancelToken); + if (abortController2) { + return abortController2.signal; + } + return void 0; + } + const abortController = new AbortController(); + this.abortControllers.set(cancelToken, abortController); + return abortController.signal; + }; + this.abortRequest = (cancelToken) => { + const abortController = this.abortControllers.get(cancelToken); + if (abortController) { + abortController.abort(); + this.abortControllers.delete(cancelToken); + } + }; + this.request = async ({ + body, + secure, + path, + type, + query, + format, + baseUrl, + cancelToken, + ...params + }) => { + const secureParams = (typeof secure === "boolean" ? secure : this.baseApiParams.secure) && this.securityWorker && await this.securityWorker(this.securityData) || {}; + const requestParams = this.mergeRequestParams(params, secureParams); + const queryString = query && this.toQueryString(query); + const payloadFormatter = this.contentFormatters[type || ContentType.Json]; + const responseFormat = format || requestParams.format; + return this.customFetch(`${baseUrl || this.baseUrl || ""}${path}${queryString ? `?${queryString}` : ""}`, { + ...requestParams, + headers: { + ...requestParams.headers || {}, + ...type && type !== ContentType.FormData ? { + "Content-Type": type + } : {} + }, + signal: (cancelToken ? this.createAbortSignal(cancelToken) : requestParams.signal) || null, + body: typeof body === "undefined" || body === null ? null : payloadFormatter(body) + }).then(async (response) => { + const r = response.clone(); + r.data = null; + r.error = null; + const data = !responseFormat ? r : await response[responseFormat]().then((data2) => { + if (r.ok) { + r.data = data2; + } else { + r.error = data2; + } + return r; + }).catch((e) => { + r.error = e; + return r; + }); + if (cancelToken) { + this.abortControllers.delete(cancelToken); + } + if (!response.ok) throw data; + return data.data; + }); + }; + Object.assign(this, apiConfig); + } + encodeQueryParam(key, value) { + const encodedKey = encodeURIComponent(key); + return `${encodedKey}=${encodeURIComponent(typeof value === "number" ? value : `${value}`)}`; + } + addQueryParam(query, key) { + return this.encodeQueryParam(key, query[key]); + } + addArrayQueryParam(query, key) { + const value = query[key]; + return value.map((v) => this.encodeQueryParam(key, v)).join("&"); + } + toQueryString(rawQuery) { + const query = rawQuery || {}; + const keys = Object.keys(query).filter((key) => "undefined" !== typeof query[key]); + return keys.map((key) => Array.isArray(query[key]) ? this.addArrayQueryParam(query, key) : this.addQueryParam(query, key)).join("&"); + } + addQueryParams(rawQuery) { + const queryString = this.toQueryString(rawQuery); + return queryString ? `?${queryString}` : ""; + } + mergeRequestParams(params1, params2) { + return { + ...this.baseApiParams, + ...params1, + ...params2 || {}, + headers: { + ...this.baseApiParams.headers || {}, + ...params1.headers || {}, + ...params2 && params2.headers || {} + } + }; + } +}; +var LangfusePublicApi = class extends HttpClient { + constructor() { + super(...arguments); + this.api = { + /** + * @description Add an item to an annotation queue + * + * @tags AnnotationQueues + * @name AnnotationQueuesCreateQueueItem + * @request POST:/api/public/annotation-queues/{queueId}/items + * @secure + */ + annotationQueuesCreateQueueItem: (queueId, data, params = {}) => this.request({ + path: `/api/public/annotation-queues/${queueId}/items`, + method: "POST", + body: data, + secure: true, + type: ContentType.Json, + format: "json", + ...params + }), + /** + * @description Remove an item from an annotation queue + * + * @tags AnnotationQueues + * @name AnnotationQueuesDeleteQueueItem + * @request DELETE:/api/public/annotation-queues/{queueId}/items/{itemId} + * @secure + */ + annotationQueuesDeleteQueueItem: (queueId, itemId, params = {}) => this.request({ + path: `/api/public/annotation-queues/${queueId}/items/${itemId}`, + method: "DELETE", + secure: true, + format: "json", + ...params + }), + /** + * @description Get an annotation queue by ID + * + * @tags AnnotationQueues + * @name AnnotationQueuesGetQueue + * @request GET:/api/public/annotation-queues/{queueId} + * @secure + */ + annotationQueuesGetQueue: (queueId, params = {}) => this.request({ + path: `/api/public/annotation-queues/${queueId}`, + method: "GET", + secure: true, + format: "json", + ...params + }), + /** + * @description Get a specific item from an annotation queue + * + * @tags AnnotationQueues + * @name AnnotationQueuesGetQueueItem + * @request GET:/api/public/annotation-queues/{queueId}/items/{itemId} + * @secure + */ + annotationQueuesGetQueueItem: (queueId, itemId, params = {}) => this.request({ + path: `/api/public/annotation-queues/${queueId}/items/${itemId}`, + method: "GET", + secure: true, + format: "json", + ...params + }), + /** + * @description Get items for a specific annotation queue + * + * @tags AnnotationQueues + * @name AnnotationQueuesListQueueItems + * @request GET:/api/public/annotation-queues/{queueId}/items + * @secure + */ + annotationQueuesListQueueItems: ({ + queueId, + ...query + }, params = {}) => this.request({ + path: `/api/public/annotation-queues/${queueId}/items`, + method: "GET", + query, + secure: true, + format: "json", + ...params + }), + /** + * @description Get all annotation queues + * + * @tags AnnotationQueues + * @name AnnotationQueuesListQueues + * @request GET:/api/public/annotation-queues + * @secure + */ + annotationQueuesListQueues: (query, params = {}) => this.request({ + path: `/api/public/annotation-queues`, + method: "GET", + query, + secure: true, + format: "json", + ...params + }), + /** + * @description Update an annotation queue item + * + * @tags AnnotationQueues + * @name AnnotationQueuesUpdateQueueItem + * @request PATCH:/api/public/annotation-queues/{queueId}/items/{itemId} + * @secure + */ + annotationQueuesUpdateQueueItem: (queueId, itemId, data, params = {}) => this.request({ + path: `/api/public/annotation-queues/${queueId}/items/${itemId}`, + method: "PATCH", + body: data, + secure: true, + type: ContentType.Json, + format: "json", + ...params + }), + /** + * @description Create a comment. Comments may be attached to different object types (trace, observation, session, prompt). + * + * @tags Comments + * @name CommentsCreate + * @request POST:/api/public/comments + * @secure + */ + commentsCreate: (data, params = {}) => this.request({ + path: `/api/public/comments`, + method: "POST", + body: data, + secure: true, + type: ContentType.Json, + format: "json", + ...params + }), + /** + * @description Get all comments + * + * @tags Comments + * @name CommentsGet + * @request GET:/api/public/comments + * @secure + */ + commentsGet: (query, params = {}) => this.request({ + path: `/api/public/comments`, + method: "GET", + query, + secure: true, + format: "json", + ...params + }), + /** + * @description Get a comment by id + * + * @tags Comments + * @name CommentsGetById + * @request GET:/api/public/comments/{commentId} + * @secure + */ + commentsGetById: (commentId, params = {}) => this.request({ + path: `/api/public/comments/${commentId}`, + method: "GET", + secure: true, + format: "json", + ...params + }), + /** + * @description Create a dataset item + * + * @tags DatasetItems + * @name DatasetItemsCreate + * @request POST:/api/public/dataset-items + * @secure + */ + datasetItemsCreate: (data, params = {}) => this.request({ + path: `/api/public/dataset-items`, + method: "POST", + body: data, + secure: true, + type: ContentType.Json, + format: "json", + ...params + }), + /** + * @description Delete a dataset item and all its run items. This action is irreversible. + * + * @tags DatasetItems + * @name DatasetItemsDelete + * @request DELETE:/api/public/dataset-items/{id} + * @secure + */ + datasetItemsDelete: (id, params = {}) => this.request({ + path: `/api/public/dataset-items/${id}`, + method: "DELETE", + secure: true, + format: "json", + ...params + }), + /** + * @description Get a dataset item + * + * @tags DatasetItems + * @name DatasetItemsGet + * @request GET:/api/public/dataset-items/{id} + * @secure + */ + datasetItemsGet: (id, params = {}) => this.request({ + path: `/api/public/dataset-items/${id}`, + method: "GET", + secure: true, + format: "json", + ...params + }), + /** + * @description Get dataset items + * + * @tags DatasetItems + * @name DatasetItemsList + * @request GET:/api/public/dataset-items + * @secure + */ + datasetItemsList: (query, params = {}) => this.request({ + path: `/api/public/dataset-items`, + method: "GET", + query, + secure: true, + format: "json", + ...params + }), + /** + * @description Create a dataset run item + * + * @tags DatasetRunItems + * @name DatasetRunItemsCreate + * @request POST:/api/public/dataset-run-items + * @secure + */ + datasetRunItemsCreate: (data, params = {}) => this.request({ + path: `/api/public/dataset-run-items`, + method: "POST", + body: data, + secure: true, + type: ContentType.Json, + format: "json", + ...params + }), + /** + * @description List dataset run items + * + * @tags DatasetRunItems + * @name DatasetRunItemsList + * @request GET:/api/public/dataset-run-items + * @secure + */ + datasetRunItemsList: (query, params = {}) => this.request({ + path: `/api/public/dataset-run-items`, + method: "GET", + query, + secure: true, + ...params + }), + /** + * @description Create a dataset + * + * @tags Datasets + * @name DatasetsCreate + * @request POST:/api/public/v2/datasets + * @secure + */ + datasetsCreate: (data, params = {}) => this.request({ + path: `/api/public/v2/datasets`, + method: "POST", + body: data, + secure: true, + type: ContentType.Json, + format: "json", + ...params + }), + /** + * @description Delete a dataset run and all its run items. This action is irreversible. + * + * @tags Datasets + * @name DatasetsDeleteRun + * @request DELETE:/api/public/datasets/{datasetName}/runs/{runName} + * @secure + */ + datasetsDeleteRun: (datasetName, runName, params = {}) => this.request({ + path: `/api/public/datasets/${datasetName}/runs/${runName}`, + method: "DELETE", + secure: true, + format: "json", + ...params + }), + /** + * @description Get a dataset + * + * @tags Datasets + * @name DatasetsGet + * @request GET:/api/public/v2/datasets/{datasetName} + * @secure + */ + datasetsGet: (datasetName, params = {}) => this.request({ + path: `/api/public/v2/datasets/${datasetName}`, + method: "GET", + secure: true, + format: "json", + ...params + }), + /** + * @description Get a dataset run and its items + * + * @tags Datasets + * @name DatasetsGetRun + * @request GET:/api/public/datasets/{datasetName}/runs/{runName} + * @secure + */ + datasetsGetRun: (datasetName, runName, params = {}) => this.request({ + path: `/api/public/datasets/${datasetName}/runs/${runName}`, + method: "GET", + secure: true, + format: "json", + ...params + }), + /** + * @description Get dataset runs + * + * @tags Datasets + * @name DatasetsGetRuns + * @request GET:/api/public/datasets/{datasetName}/runs + * @secure + */ + datasetsGetRuns: ({ + datasetName, + ...query + }, params = {}) => this.request({ + path: `/api/public/datasets/${datasetName}/runs`, + method: "GET", + query, + secure: true, + format: "json", + ...params + }), + /** + * @description Get all datasets + * + * @tags Datasets + * @name DatasetsList + * @request GET:/api/public/v2/datasets + * @secure + */ + datasetsList: (query, params = {}) => this.request({ + path: `/api/public/v2/datasets`, + method: "GET", + query, + secure: true, + format: "json", + ...params + }), + /** + * @description Check health of API and database + * + * @tags Health + * @name HealthHealth + * @request GET:/api/public/health + */ + healthHealth: (params = {}) => this.request({ + path: `/api/public/health`, + method: "GET", + format: "json", + ...params + }), + /** + * @description Batched ingestion for Langfuse Tracing. If you want to use tracing via the API, such as to build your own Langfuse client implementation, this is the only API route you need to implement. Within each batch, there can be multiple events. Each event has a type, an id, a timestamp, metadata and a body. Internally, we refer to this as the "event envelope" as it tells us something about the event but not the trace. We use the event id within this envelope to deduplicate messages to avoid processing the same event twice, i.e. the event id should be unique per request. The event.body.id is the ID of the actual trace and will be used for updates and will be visible within the Langfuse App. I.e. if you want to update a trace, you'd use the same body id, but separate event IDs. Notes: - Introduction to data model: https://langfuse.com/docs/tracing-data-model - Batch sizes are limited to 3.5 MB in total. You need to adjust the number of events per batch accordingly. - The API does not return a 4xx status code for input errors. Instead, it responds with a 207 status code, which includes a list of the encountered errors. + * + * @tags Ingestion + * @name IngestionBatch + * @request POST:/api/public/ingestion + * @secure + */ + ingestionBatch: (data, params = {}) => this.request({ + path: `/api/public/ingestion`, + method: "POST", + body: data, + secure: true, + type: ContentType.Json, + format: "json", + ...params + }), + /** + * @description Get a media record + * + * @tags Media + * @name MediaGet + * @request GET:/api/public/media/{mediaId} + * @secure + */ + mediaGet: (mediaId, params = {}) => this.request({ + path: `/api/public/media/${mediaId}`, + method: "GET", + secure: true, + format: "json", + ...params + }), + /** + * @description Get a presigned upload URL for a media record + * + * @tags Media + * @name MediaGetUploadUrl + * @request POST:/api/public/media + * @secure + */ + mediaGetUploadUrl: (data, params = {}) => this.request({ + path: `/api/public/media`, + method: "POST", + body: data, + secure: true, + type: ContentType.Json, + format: "json", + ...params + }), + /** + * @description Patch a media record + * + * @tags Media + * @name MediaPatch + * @request PATCH:/api/public/media/{mediaId} + * @secure + */ + mediaPatch: (mediaId, data, params = {}) => this.request({ + path: `/api/public/media/${mediaId}`, + method: "PATCH", + body: data, + secure: true, + type: ContentType.Json, + ...params + }), + /** + * @description Get metrics from the Langfuse project using a query object + * + * @tags Metrics + * @name MetricsMetrics + * @request GET:/api/public/metrics + * @secure + */ + metricsMetrics: (query, params = {}) => this.request({ + path: `/api/public/metrics`, + method: "GET", + query, + secure: true, + format: "json", + ...params + }), + /** + * @description Create a model + * + * @tags Models + * @name ModelsCreate + * @request POST:/api/public/models + * @secure + */ + modelsCreate: (data, params = {}) => this.request({ + path: `/api/public/models`, + method: "POST", + body: data, + secure: true, + type: ContentType.Json, + format: "json", + ...params + }), + /** + * @description Delete a model. Cannot delete models managed by Langfuse. You can create your own definition with the same modelName to override the definition though. + * + * @tags Models + * @name ModelsDelete + * @request DELETE:/api/public/models/{id} + * @secure + */ + modelsDelete: (id, params = {}) => this.request({ + path: `/api/public/models/${id}`, + method: "DELETE", + secure: true, + ...params + }), + /** + * @description Get a model + * + * @tags Models + * @name ModelsGet + * @request GET:/api/public/models/{id} + * @secure + */ + modelsGet: (id, params = {}) => this.request({ + path: `/api/public/models/${id}`, + method: "GET", + secure: true, + format: "json", + ...params + }), + /** + * @description Get all models + * + * @tags Models + * @name ModelsList + * @request GET:/api/public/models + * @secure + */ + modelsList: (query, params = {}) => this.request({ + path: `/api/public/models`, + method: "GET", + query, + secure: true, + format: "json", + ...params + }), + /** + * @description Get a observation + * + * @tags Observations + * @name ObservationsGet + * @request GET:/api/public/observations/{observationId} + * @secure + */ + observationsGet: (observationId, params = {}) => this.request({ + path: `/api/public/observations/${observationId}`, + method: "GET", + secure: true, + format: "json", + ...params + }), + /** + * @description Get a list of observations + * + * @tags Observations + * @name ObservationsGetMany + * @request GET:/api/public/observations + * @secure + */ + observationsGetMany: (query, params = {}) => this.request({ + path: `/api/public/observations`, + method: "GET", + query, + secure: true, + format: "json", + ...params + }), + /** + * @description Get all memberships for the organization associated with the API key (requires organization-scoped API key) + * + * @tags Organizations + * @name OrganizationsGetOrganizationMemberships + * @request GET:/api/public/organizations/memberships + * @secure + */ + organizationsGetOrganizationMemberships: (params = {}) => this.request({ + path: `/api/public/organizations/memberships`, + method: "GET", + secure: true, + format: "json", + ...params + }), + /** + * @description Get all projects for the organization associated with the API key (requires organization-scoped API key) + * + * @tags Organizations + * @name OrganizationsGetOrganizationProjects + * @request GET:/api/public/organizations/projects + * @secure + */ + organizationsGetOrganizationProjects: (params = {}) => this.request({ + path: `/api/public/organizations/projects`, + method: "GET", + secure: true, + format: "json", + ...params + }), + /** + * @description Get all memberships for a specific project (requires organization-scoped API key) + * + * @tags Organizations + * @name OrganizationsGetProjectMemberships + * @request GET:/api/public/projects/{projectId}/memberships + * @secure + */ + organizationsGetProjectMemberships: (projectId, params = {}) => this.request({ + path: `/api/public/projects/${projectId}/memberships`, + method: "GET", + secure: true, + format: "json", + ...params + }), + /** + * @description Create or update a membership for the organization associated with the API key (requires organization-scoped API key) + * + * @tags Organizations + * @name OrganizationsUpdateOrganizationMembership + * @request PUT:/api/public/organizations/memberships + * @secure + */ + organizationsUpdateOrganizationMembership: (data, params = {}) => this.request({ + path: `/api/public/organizations/memberships`, + method: "PUT", + body: data, + secure: true, + type: ContentType.Json, + format: "json", + ...params + }), + /** + * @description Create or update a membership for a specific project (requires organization-scoped API key). The user must already be a member of the organization. + * + * @tags Organizations + * @name OrganizationsUpdateProjectMembership + * @request PUT:/api/public/projects/{projectId}/memberships + * @secure + */ + organizationsUpdateProjectMembership: (projectId, data, params = {}) => this.request({ + path: `/api/public/projects/${projectId}/memberships`, + method: "PUT", + body: data, + secure: true, + type: ContentType.Json, + format: "json", + ...params + }), + /** + * @description Create a new project (requires organization-scoped API key) + * + * @tags Projects + * @name ProjectsCreate + * @request POST:/api/public/projects + * @secure + */ + projectsCreate: (data, params = {}) => this.request({ + path: `/api/public/projects`, + method: "POST", + body: data, + secure: true, + type: ContentType.Json, + format: "json", + ...params + }), + /** + * @description Create a new API key for a project (requires organization-scoped API key) + * + * @tags Projects + * @name ProjectsCreateApiKey + * @request POST:/api/public/projects/{projectId}/apiKeys + * @secure + */ + projectsCreateApiKey: (projectId, data, params = {}) => this.request({ + path: `/api/public/projects/${projectId}/apiKeys`, + method: "POST", + body: data, + secure: true, + type: ContentType.Json, + format: "json", + ...params + }), + /** + * @description Delete a project by ID (requires organization-scoped API key). Project deletion is processed asynchronously. + * + * @tags Projects + * @name ProjectsDelete + * @request DELETE:/api/public/projects/{projectId} + * @secure + */ + projectsDelete: (projectId, params = {}) => this.request({ + path: `/api/public/projects/${projectId}`, + method: "DELETE", + secure: true, + format: "json", + ...params + }), + /** + * @description Delete an API key for a project (requires organization-scoped API key) + * + * @tags Projects + * @name ProjectsDeleteApiKey + * @request DELETE:/api/public/projects/{projectId}/apiKeys/{apiKeyId} + * @secure + */ + projectsDeleteApiKey: (projectId, apiKeyId, params = {}) => this.request({ + path: `/api/public/projects/${projectId}/apiKeys/${apiKeyId}`, + method: "DELETE", + secure: true, + format: "json", + ...params + }), + /** + * @description Get Project associated with API key + * + * @tags Projects + * @name ProjectsGet + * @request GET:/api/public/projects + * @secure + */ + projectsGet: (params = {}) => this.request({ + path: `/api/public/projects`, + method: "GET", + secure: true, + format: "json", + ...params + }), + /** + * @description Get all API keys for a project (requires organization-scoped API key) + * + * @tags Projects + * @name ProjectsGetApiKeys + * @request GET:/api/public/projects/{projectId}/apiKeys + * @secure + */ + projectsGetApiKeys: (projectId, params = {}) => this.request({ + path: `/api/public/projects/${projectId}/apiKeys`, + method: "GET", + secure: true, + format: "json", + ...params + }), + /** + * @description Update a project by ID (requires organization-scoped API key). + * + * @tags Projects + * @name ProjectsUpdate + * @request PUT:/api/public/projects/{projectId} + * @secure + */ + projectsUpdate: (projectId, data, params = {}) => this.request({ + path: `/api/public/projects/${projectId}`, + method: "PUT", + body: data, + secure: true, + type: ContentType.Json, + format: "json", + ...params + }), + /** + * @description Create a new version for the prompt with the given `name` + * + * @tags Prompts + * @name PromptsCreate + * @request POST:/api/public/v2/prompts + * @secure + */ + promptsCreate: (data, params = {}) => this.request({ + path: `/api/public/v2/prompts`, + method: "POST", + body: data, + secure: true, + type: ContentType.Json, + format: "json", + ...params + }), + /** + * @description Get a prompt + * + * @tags Prompts + * @name PromptsGet + * @request GET:/api/public/v2/prompts/{promptName} + * @secure + */ + promptsGet: ({ + promptName, + ...query + }, params = {}) => this.request({ + path: `/api/public/v2/prompts/${promptName}`, + method: "GET", + query, + secure: true, + format: "json", + ...params + }), + /** + * @description Get a list of prompt names with versions and labels + * + * @tags Prompts + * @name PromptsList + * @request GET:/api/public/v2/prompts + * @secure + */ + promptsList: (query, params = {}) => this.request({ + path: `/api/public/v2/prompts`, + method: "GET", + query, + secure: true, + format: "json", + ...params + }), + /** + * @description Update labels for a specific prompt version + * + * @tags PromptVersion + * @name PromptVersionUpdate + * @request PATCH:/api/public/v2/prompts/{name}/versions/{version} + * @secure + */ + promptVersionUpdate: (name, version2, data, params = {}) => this.request({ + path: `/api/public/v2/prompts/${name}/versions/${version2}`, + method: "PATCH", + body: data, + secure: true, + type: ContentType.Json, + format: "json", + ...params + }), + /** + * @description Create a new user in the organization (requires organization-scoped API key) + * + * @tags Scim + * @name ScimCreateUser + * @request POST:/api/public/scim/Users + * @secure + */ + scimCreateUser: (data, params = {}) => this.request({ + path: `/api/public/scim/Users`, + method: "POST", + body: data, + secure: true, + type: ContentType.Json, + format: "json", + ...params + }), + /** + * @description Remove a user from the organization (requires organization-scoped API key). Note that this only removes the user from the organization but does not delete the user entity itself. + * + * @tags Scim + * @name ScimDeleteUser + * @request DELETE:/api/public/scim/Users/{userId} + * @secure + */ + scimDeleteUser: (userId, params = {}) => this.request({ + path: `/api/public/scim/Users/${userId}`, + method: "DELETE", + secure: true, + format: "json", + ...params + }), + /** + * @description Get SCIM Resource Types (requires organization-scoped API key) + * + * @tags Scim + * @name ScimGetResourceTypes + * @request GET:/api/public/scim/ResourceTypes + * @secure + */ + scimGetResourceTypes: (params = {}) => this.request({ + path: `/api/public/scim/ResourceTypes`, + method: "GET", + secure: true, + format: "json", + ...params + }), + /** + * @description Get SCIM Schemas (requires organization-scoped API key) + * + * @tags Scim + * @name ScimGetSchemas + * @request GET:/api/public/scim/Schemas + * @secure + */ + scimGetSchemas: (params = {}) => this.request({ + path: `/api/public/scim/Schemas`, + method: "GET", + secure: true, + format: "json", + ...params + }), + /** + * @description Get SCIM Service Provider Configuration (requires organization-scoped API key) + * + * @tags Scim + * @name ScimGetServiceProviderConfig + * @request GET:/api/public/scim/ServiceProviderConfig + * @secure + */ + scimGetServiceProviderConfig: (params = {}) => this.request({ + path: `/api/public/scim/ServiceProviderConfig`, + method: "GET", + secure: true, + format: "json", + ...params + }), + /** + * @description Get a specific user by ID (requires organization-scoped API key) + * + * @tags Scim + * @name ScimGetUser + * @request GET:/api/public/scim/Users/{userId} + * @secure + */ + scimGetUser: (userId, params = {}) => this.request({ + path: `/api/public/scim/Users/${userId}`, + method: "GET", + secure: true, + format: "json", + ...params + }), + /** + * @description List users in the organization (requires organization-scoped API key) + * + * @tags Scim + * @name ScimListUsers + * @request GET:/api/public/scim/Users + * @secure + */ + scimListUsers: (query, params = {}) => this.request({ + path: `/api/public/scim/Users`, + method: "GET", + query, + secure: true, + format: "json", + ...params + }), + /** + * @description Create a score configuration (config). Score configs are used to define the structure of scores + * + * @tags ScoreConfigs + * @name ScoreConfigsCreate + * @request POST:/api/public/score-configs + * @secure + */ + scoreConfigsCreate: (data, params = {}) => this.request({ + path: `/api/public/score-configs`, + method: "POST", + body: data, + secure: true, + type: ContentType.Json, + format: "json", + ...params + }), + /** + * @description Get all score configs + * + * @tags ScoreConfigs + * @name ScoreConfigsGet + * @request GET:/api/public/score-configs + * @secure + */ + scoreConfigsGet: (query, params = {}) => this.request({ + path: `/api/public/score-configs`, + method: "GET", + query, + secure: true, + format: "json", + ...params + }), + /** + * @description Get a score config + * + * @tags ScoreConfigs + * @name ScoreConfigsGetById + * @request GET:/api/public/score-configs/{configId} + * @secure + */ + scoreConfigsGetById: (configId, params = {}) => this.request({ + path: `/api/public/score-configs/${configId}`, + method: "GET", + secure: true, + format: "json", + ...params + }), + /** + * @description Create a score (supports both trace and session scores) + * + * @tags Score + * @name ScoreCreate + * @request POST:/api/public/scores + * @secure + */ + scoreCreate: (data, params = {}) => this.request({ + path: `/api/public/scores`, + method: "POST", + body: data, + secure: true, + type: ContentType.Json, + format: "json", + ...params + }), + /** + * @description Delete a score (supports both trace and session scores) + * + * @tags Score + * @name ScoreDelete + * @request DELETE:/api/public/scores/{scoreId} + * @secure + */ + scoreDelete: (scoreId, params = {}) => this.request({ + path: `/api/public/scores/${scoreId}`, + method: "DELETE", + secure: true, + ...params + }), + /** + * @description Get a list of scores (supports both trace and session scores) + * + * @tags ScoreV2 + * @name ScoreV2Get + * @request GET:/api/public/v2/scores + * @secure + */ + scoreV2Get: (query, params = {}) => this.request({ + path: `/api/public/v2/scores`, + method: "GET", + query, + secure: true, + format: "json", + ...params + }), + /** + * @description Get a score (supports both trace and session scores) + * + * @tags ScoreV2 + * @name ScoreV2GetById + * @request GET:/api/public/v2/scores/{scoreId} + * @secure + */ + scoreV2GetById: (scoreId, params = {}) => this.request({ + path: `/api/public/v2/scores/${scoreId}`, + method: "GET", + secure: true, + format: "json", + ...params + }), + /** + * @description Get a session. Please note that `traces` on this endpoint are not paginated, if you plan to fetch large sessions, consider `GET /api/public/traces?sessionId=` + * + * @tags Sessions + * @name SessionsGet + * @request GET:/api/public/sessions/{sessionId} + * @secure + */ + sessionsGet: (sessionId2, params = {}) => this.request({ + path: `/api/public/sessions/${sessionId2}`, + method: "GET", + secure: true, + format: "json", + ...params + }), + /** + * @description Get sessions + * + * @tags Sessions + * @name SessionsList + * @request GET:/api/public/sessions + * @secure + */ + sessionsList: (query, params = {}) => this.request({ + path: `/api/public/sessions`, + method: "GET", + query, + secure: true, + format: "json", + ...params + }), + /** + * @description Delete a specific trace + * + * @tags Trace + * @name TraceDelete + * @request DELETE:/api/public/traces/{traceId} + * @secure + */ + traceDelete: (traceId, params = {}) => this.request({ + path: `/api/public/traces/${traceId}`, + method: "DELETE", + secure: true, + format: "json", + ...params + }), + /** + * @description Delete multiple traces + * + * @tags Trace + * @name TraceDeleteMultiple + * @request DELETE:/api/public/traces + * @secure + */ + traceDeleteMultiple: (data, params = {}) => this.request({ + path: `/api/public/traces`, + method: "DELETE", + body: data, + secure: true, + type: ContentType.Json, + format: "json", + ...params + }), + /** + * @description Get a specific trace + * + * @tags Trace + * @name TraceGet + * @request GET:/api/public/traces/{traceId} + * @secure + */ + traceGet: (traceId, params = {}) => this.request({ + path: `/api/public/traces/${traceId}`, + method: "GET", + secure: true, + format: "json", + ...params + }), + /** + * @description Get list of traces + * + * @tags Trace + * @name TraceList + * @request GET:/api/public/traces + * @secure + */ + traceList: (query, params = {}) => this.request({ + path: `/api/public/traces`, + method: "GET", + query, + secure: true, + format: "json", + ...params + }) + }; + } +}; +var version = "3.38.20"; +var Langfuse = class extends LangfuseCore { + constructor(params) { + const langfuseConfig = utils.configLangfuseSDK(params); + super(langfuseConfig); + if (typeof window !== "undefined" && "Deno" in window === false) { + this._storageKey = params?.persistence_name ? `lf_${params.persistence_name}` : `lf_${langfuseConfig.publicKey}_langfuse`; + this._storage = getStorage(params?.persistence || "localStorage", window); + } else { + this._storageKey = `lf_${langfuseConfig.publicKey}_langfuse`; + this._storage = getStorage("memory", void 0); + } + this.api = new LangfusePublicApi({ + baseUrl: this.baseUrl, + baseApiParams: { + headers: { + "X-Langfuse-Sdk-Name": "langfuse-js", + "X-Langfuse-Sdk-Version": this.getLibraryVersion(), + "X-Langfuse-Sdk-Variant": this.getLibraryId(), + "X-Langfuse-Sdk-Integration": this.sdkIntegration, + "X-Langfuse-Public-Key": this.publicKey, + ...this.additionalHeaders, + ...this.constructAuthorizationHeader(this.publicKey, this.secretKey) + } + } + }).api; + } + getPersistedProperty(key) { + if (!this._storageCache) { + this._storageCache = JSON.parse(this._storage.getItem(this._storageKey) || "{}") || {}; + } + return this._storageCache[key]; + } + setPersistedProperty(key, value) { + if (!this._storageCache) { + this._storageCache = JSON.parse(this._storage.getItem(this._storageKey) || "{}") || {}; + } + if (value === null) { + delete this._storageCache[key]; + } else { + this._storageCache[key] = value; + } + this._storage.setItem(this._storageKey, JSON.stringify(this._storageCache)); + } + fetch(url, options) { + return fetch(url, options); + } + getLibraryId() { + return "langfuse"; + } + getLibraryVersion() { + return version; + } + getCustomUserAgent() { + return; + } +}; +var LangfuseSingleton = class _LangfuseSingleton { + /** + * Returns the singleton instance of the Langfuse client. + * @param params Optional parameters for initializing the Langfuse instance. Only used for the first call. + * @returns The singleton instance of the Langfuse client. + */ + static getInstance(params) { + if (!_LangfuseSingleton.instance) { + _LangfuseSingleton.instance = new Langfuse(params); + } + return _LangfuseSingleton.instance; + } +}; +LangfuseSingleton.instance = null; + +// src/adapters/langfuse-sink.ts +var SDK_INTEGRATION = "zcode-langfuse-observability"; +var TRACE_NAME = "ZCode Turn"; +var LangfuseTraceSink = class { + constructor(config) { + this.config = config; + } + async publishTurn(turn) { + if (!this.config.publicKey || !this.config.secretKey) return; + const client = new Langfuse({ + publicKey: this.config.publicKey, + secretKey: this.config.secretKey, + baseUrl: this.config.baseUrl, + release: this.config.release, + environment: this.config.environment, + sdkIntegration: SDK_INTEGRATION, + flushAt: 1, + fetchRetryCount: 1, + requestTimeout: 8e3 + }); + try { + const traceInput = { + name: TRACE_NAME, + sessionId: turn.sessionId, + input: turn.prompt, + metadata: { + source: "zcode", + turnId: turn.turnId, + toolCount: turn.tools.length + }, + tags: ["zcode", "zcode-hook"] + }; + if (this.config.userId) traceInput.userId = this.config.userId; + const trace = client.trace(traceInput); + for (const tool of turn.tools) { + const span = trace.span({ + name: `tool.${tool.name}`, + input: tool.input, + metadata: { + toolId: tool.id, + startedAt: tool.startedAt, + endedAt: tool.endedAt + } + }); + if (tool.error) { + span.end({ + output: { error: tool.error }, + level: "ERROR", + statusMessage: tool.error + }); + } else { + span.end({ output: tool.output }); + } + } + const generation = trace.generation({ + name: "zcode.assistant", + input: turn.prompt, + metadata: { + turnId: turn.turnId + } + }); + generation.end({ output: turn.assistantMessage }); + trace.update({ + output: turn.assistantMessage, + metadata: { + source: "zcode", + turnId: turn.turnId, + toolCount: turn.tools.length + } + }); + await client.flushAsync(); + } finally { + await client.shutdownAsync(); + } + } +}; + +// src/hooks/entry.ts +function isRecord2(value) { + return typeof value === "object" && value !== null && !Array.isArray(value); +} +function parsePayload(raw) { + if (!raw.trim()) return {}; + try { + const parsed = JSON.parse(raw); + return isRecord2(parsed) ? parsed : {}; + } catch { + return {}; + } +} +async function readStdin() { + let raw = ""; + process.stdin.setEncoding("utf8"); + for await (const chunk of process.stdin) raw += chunk; + return raw; +} +function diagnostics(enabled, message) { + if (enabled) process.stderr.write(`[langfuse-observability] ${message} +`); +} +async function main() { + const config = readConfig(); + const payload = parsePayload(await readStdin()); + const event = eventName(payload) ?? "unknown"; + const session = sessionId(payload) ?? "?"; + diagnostics(config.debug, `event=${event} session=${session}`); + const sink = isConfigured(config) ? new LangfuseTraceSink(config) : new NoopTraceSink(); + const tracker = new TurnTracker( + new JsonStateStore(), + sink, + config, + { now: () => /* @__PURE__ */ new Date() }, + { next: randomUUID2 } + ); + try { + await tracker.handle(payload); + } catch (error) { + const kind = error instanceof Error ? error.name : "unknown"; + diagnostics(config.debug, `hook failed open (${kind})`); + } + process.stdout.write("{}\n"); +} +await main(); +/*! Bundled license information: + +mustache/mustache.mjs: + (*! + * mustache.js - Logic-less {{mustache}} templates with JavaScript + * http://github.com/janl/mustache.js + *) +*/ +//# sourceMappingURL=entry.mjs.map diff --git a/plugins/langfuse-observability/payload/dist/hooks/entry.mjs.map b/plugins/langfuse-observability/payload/dist/hooks/entry.mjs.map new file mode 100644 index 0000000..e7e73d5 --- /dev/null +++ b/plugins/langfuse-observability/payload/dist/hooks/entry.mjs.map @@ -0,0 +1,7 @@ +{ + "version": 3, + "sources": ["../../src/hooks/entry.ts", "../../src/application/config.ts", "../../src/domain/extract.ts", "../../src/application/turn-tracker.ts", "../../src/adapters/json-state-store.ts", "../../node_modules/mustache/mustache.mjs", "../../node_modules/langfuse-core/src/eventemitter.ts", "../../node_modules/langfuse-core/src/prompts/promptCache.ts", "../../node_modules/langfuse-core/src/types.ts", "../../node_modules/langfuse-core/src/prompts/promptClients.ts", "../../node_modules/langfuse-core/src/utils.ts", "../../node_modules/langfuse-core/src/release-env.ts", "../../node_modules/langfuse-core/src/media/LangfuseMedia.ts", "../../node_modules/langfuse-core/src/sampling.ts", "../../node_modules/langfuse-core/src/storage-memory.ts", "../../node_modules/langfuse-core/src/index.ts", "../../node_modules/langfuse/src/storage.ts", "../../node_modules/langfuse/src/publicApi.ts", "../../node_modules/langfuse/src/langfuse.ts", "../../node_modules/langfuse/src/openai/LangfuseSingleton.ts", "../../node_modules/langfuse/src/openai/parseOpenAI.ts", "../../node_modules/langfuse/src/openai/utils.ts", "../../node_modules/langfuse/src/openai/traceMethod.ts", "../../node_modules/langfuse/src/openai/observeOpenAI.ts", "../../src/adapters/langfuse-sink.ts"], + "sourcesContent": ["import { randomUUID } from \"node:crypto\";\nimport { readConfig, isConfigured } from \"../application/config.js\";\nimport { NoopTraceSink, TurnTracker } from \"../application/turn-tracker.js\";\nimport { JsonStateStore } from \"../adapters/json-state-store.js\";\nimport { LangfuseTraceSink } from \"../adapters/langfuse-sink.js\";\nimport { eventName, sessionId } from \"../domain/extract.js\";\nimport type { Clock, HookPayload, IdGenerator } from \"../domain/types.js\";\n\nfunction isRecord(value: unknown): value is Record {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction parsePayload(raw: string): HookPayload {\n if (!raw.trim()) return {};\n try {\n const parsed: unknown = JSON.parse(raw);\n return isRecord(parsed) ? (parsed as HookPayload) : {};\n } catch {\n return {};\n }\n}\n\nasync function readStdin(): Promise {\n let raw = \"\";\n process.stdin.setEncoding(\"utf8\");\n for await (const chunk of process.stdin) raw += chunk;\n return raw;\n}\n\nfunction diagnostics(enabled: boolean, message: string): void {\n if (enabled) process.stderr.write(`[langfuse-observability] ${message}\\n`);\n}\n\nasync function main(): Promise {\n const config = readConfig();\n const payload = parsePayload(await readStdin());\n const event = eventName(payload) ?? \"unknown\";\n const session = sessionId(payload) ?? \"?\";\n\n diagnostics(config.debug, `event=${event} session=${session}`);\n\n const sink = isConfigured(config)\n ? new LangfuseTraceSink(config)\n : new NoopTraceSink();\n const tracker = new TurnTracker(\n new JsonStateStore(),\n sink,\n config,\n { now: () => new Date() } satisfies Clock,\n { next: randomUUID } satisfies IdGenerator,\n );\n\n try {\n await tracker.handle(payload);\n } catch (error) {\n const kind = error instanceof Error ? error.name : \"unknown\";\n diagnostics(config.debug, `hook failed open (${kind})`);\n }\n\n // Hooks must always emit one JSON object. Observability has no context to add.\n process.stdout.write(\"{}\\n\");\n}\n\nawait main();\n", "import { readFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { HookConfig } from \"../domain/types.js\";\n\nconst DEFAULT_BASE_URL = \"https://cloud.langfuse.com\";\nconst DEFAULT_RELEASE = \"0.1.0\";\nconst DEFAULT_MAX_CAPTURE_CHARS = 20_000;\n\nexport type StoredOption = string | number | boolean;\nexport type StoredOptions = Record;\ntype ConfigValue = StoredOption | undefined;\n\nfunction asText(value: ConfigValue): string | undefined {\n if (value === undefined) return undefined;\n return typeof value === \"string\" ? value : String(value);\n}\n\nfunction firstNonEmpty(...values: ConfigValue[]): string | undefined {\n return values\n .map(asText)\n .find((value) => value !== undefined && value.trim().length > 0)\n ?.trim();\n}\n\nfunction userConfig(env: NodeJS.ProcessEnv, name: string): string | undefined {\n const normalized = name.toUpperCase();\n return firstNonEmpty(\n env[`ZCODE_USER_CONFIG_${normalized}`],\n env[`ZCODE_PLUGIN_CONFIG_${normalized}`],\n );\n}\n\nfunction isStoredOption(value: unknown): value is StoredOption {\n return (\n typeof value === \"string\" ||\n typeof value === \"number\" ||\n typeof value === \"boolean\"\n );\n}\n\nfunction parseStoredOptions(value: unknown): StoredOptions {\n if (typeof value !== \"object\" || value === null || Array.isArray(value))\n return {};\n const options: StoredOptions = {};\n for (const [key, option] of Object.entries(value)) {\n if (isStoredOption(option)) options[key] = option;\n }\n return options;\n}\n\nfunction readStoredOptions(env: NodeJS.ProcessEnv): StoredOptions {\n const configPaths = [\n env.ZCODE_CONFIG_PATH,\n join(homedir(), \".zcode\", \"cli\", \"config.json\"),\n ].filter((path): path is string => Boolean(path));\n const configuredPluginId = env.ZCODE_PLUGIN_ID;\n\n for (const configPath of configPaths) {\n try {\n const parsed: unknown = JSON.parse(readFileSync(configPath, \"utf8\"));\n if (\n typeof parsed !== \"object\" ||\n parsed === null ||\n Array.isArray(parsed)\n )\n continue;\n const plugins = (parsed as Record).plugins;\n if (\n typeof plugins !== \"object\" ||\n plugins === null ||\n Array.isArray(plugins)\n )\n continue;\n const options = (plugins as Record).options;\n if (\n typeof options !== \"object\" ||\n options === null ||\n Array.isArray(options)\n )\n continue;\n const optionEntries = options as Record;\n const pluginId =\n configuredPluginId && optionEntries[configuredPluginId]\n ? configuredPluginId\n : Object.keys(optionEntries).find((key) =>\n key.startsWith(\"langfuse-observability@\"),\n );\n if (pluginId) return parseStoredOptions(optionEntries[pluginId]);\n } catch {\n // A missing or unreadable user config must not block a ZCode hook.\n }\n }\n return {};\n}\n\nfunction option(\n env: NodeJS.ProcessEnv,\n storedOptions: StoredOptions,\n name: string,\n environmentName: string,\n): string | undefined {\n return firstNonEmpty(\n userConfig(env, name),\n storedOptions[name],\n env[environmentName],\n );\n}\n\nfunction parseBoolean(value: string | undefined, fallback: boolean): boolean {\n if (!value) return fallback;\n if ([\"1\", \"true\", \"yes\", \"on\"].includes(value.toLowerCase())) return true;\n if ([\"0\", \"false\", \"no\", \"off\"].includes(value.toLowerCase())) return false;\n return fallback;\n}\n\nfunction parsePositiveInteger(\n value: string | undefined,\n fallback: number,\n): number {\n if (!value) return fallback;\n const parsed = Number.parseInt(value, 10);\n if (!Number.isFinite(parsed) || parsed <= 0) return fallback;\n return Math.min(parsed, 1_000_000);\n}\n\nfunction normalizeBaseUrl(value: string | undefined): string {\n const candidate = value ?? DEFAULT_BASE_URL;\n try {\n const url = new URL(candidate);\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\")\n return DEFAULT_BASE_URL;\n return candidate.replace(/\\/+$/, \"\");\n } catch {\n return DEFAULT_BASE_URL;\n }\n}\n\nexport function readConfig(\n env: NodeJS.ProcessEnv = process.env,\n storedOptions: StoredOptions = readStoredOptions(env),\n): HookConfig {\n const enabled = parseBoolean(\n option(env, storedOptions, \"enabled\", \"LANGFUSE_ENABLED\"),\n true,\n );\n const publicKey =\n option(env, storedOptions, \"langfuse_public_key\", \"LANGFUSE_PUBLIC_KEY\") ??\n null;\n const secretKey =\n option(env, storedOptions, \"langfuse_secret_key\", \"LANGFUSE_SECRET_KEY\") ??\n null;\n\n return {\n publicKey,\n secretKey,\n baseUrl: normalizeBaseUrl(\n option(env, storedOptions, \"langfuse_base_url\", \"LANGFUSE_BASE_URL\"),\n ),\n userId:\n option(env, storedOptions, \"langfuse_user_id\", \"LANGFUSE_USER_ID\") ??\n null,\n environment:\n option(\n env,\n storedOptions,\n \"langfuse_environment\",\n \"LANGFUSE_ENVIRONMENT\",\n ) ?? \"development\",\n release:\n option(env, storedOptions, \"langfuse_release\", \"LANGFUSE_RELEASE\") ??\n DEFAULT_RELEASE,\n enabled,\n capturePrompts: parseBoolean(\n option(env, storedOptions, \"capture_prompts\", \"LANGFUSE_CAPTURE_PROMPTS\"),\n true,\n ),\n captureToolInputs: parseBoolean(\n option(\n env,\n storedOptions,\n \"capture_tool_inputs\",\n \"LANGFUSE_CAPTURE_TOOL_INPUTS\",\n ),\n true,\n ),\n captureToolOutputs: parseBoolean(\n option(\n env,\n storedOptions,\n \"capture_tool_outputs\",\n \"LANGFUSE_CAPTURE_TOOL_OUTPUTS\",\n ),\n true,\n ),\n maxCaptureChars: parsePositiveInteger(\n option(\n env,\n storedOptions,\n \"max_capture_chars\",\n \"LANGFUSE_MAX_CAPTURE_CHARS\",\n ),\n DEFAULT_MAX_CAPTURE_CHARS,\n ),\n debug: parseBoolean(\n option(env, storedOptions, \"debug\", \"LANGFUSE_DEBUG\"),\n false,\n ),\n };\n}\n\nexport function isConfigured(config: HookConfig): boolean {\n return config.enabled && Boolean(config.publicKey && config.secretKey);\n}\n", "import type { HookEventName, HookPayload, JsonValue } from \"./types.js\";\n\nfunction toJsonValue(value: unknown): JsonValue | undefined {\n if (\n value === null ||\n typeof value === \"string\" ||\n typeof value === \"number\" ||\n typeof value === \"boolean\"\n ) {\n return value;\n }\n if (Array.isArray(value)) {\n const values = value.map(toJsonValue);\n return values.every((item): item is JsonValue => item !== undefined)\n ? values\n : undefined;\n }\n if (typeof value === \"object\") {\n const entries = Object.entries(value as Record);\n const result: { [key: string]: JsonValue } = {};\n for (const [key, item] of entries) {\n const parsed = toJsonValue(item);\n if (parsed === undefined) return undefined;\n result[key] = parsed;\n }\n return result;\n }\n return undefined;\n}\n\nfunction valueAt(\n payload: HookPayload,\n ...keys: string[]\n): JsonValue | undefined {\n for (const key of keys) {\n const value = toJsonValue(payload[key]);\n if (value !== undefined && value !== null) return value;\n }\n return undefined;\n}\n\nexport function asString(value: JsonValue | undefined): string | null {\n if (typeof value === \"string\") return value;\n if (typeof value === \"number\" || typeof value === \"boolean\")\n return String(value);\n return null;\n}\n\nexport function stringifyValue(value: JsonValue | undefined): string {\n if (typeof value === \"string\") return value;\n if (value === undefined) return \"\";\n const json = JSON.stringify(value);\n return json ?? String(value);\n}\n\nexport function eventName(payload: HookPayload): HookEventName | null {\n return asString(\n valueAt(payload, \"hook_event_name\", \"hookEventName\", \"event\", \"event_name\"),\n ) as HookEventName | null;\n}\n\nexport function sessionId(payload: HookPayload): string | null {\n const value = asString(valueAt(payload, \"session_id\", \"sessionId\"));\n return value?.trim() || null;\n}\n\nexport function prompt(payload: HookPayload): string | null {\n const value = valueAt(\n payload,\n \"prompt\",\n \"user_prompt\",\n \"userPrompt\",\n \"message\",\n );\n return value === undefined ? null : stringifyValue(value);\n}\n\nexport function assistantMessage(payload: HookPayload): string | null {\n const value = valueAt(\n payload,\n \"last_assistant_message\",\n \"lastAssistantMessage\",\n );\n return value === undefined ? null : stringifyValue(value);\n}\n\nexport function toolId(payload: HookPayload): string | null {\n const value = asString(\n valueAt(payload, \"tool_use_id\", \"toolUseId\", \"tool_id\", \"toolId\", \"id\"),\n );\n return value?.trim() || null;\n}\n\nexport function toolName(payload: HookPayload): string {\n return (\n asString(\n valueAt(payload, \"tool_name\", \"toolName\", \"name\", \"tool\"),\n )?.trim() ?? \"unknown\"\n );\n}\n\nexport function toolInput(payload: HookPayload): JsonValue {\n return (\n valueAt(payload, \"tool_input\", \"toolInput\", \"input\", \"arguments\") ?? null\n );\n}\n\nexport function toolOutput(payload: HookPayload): JsonValue {\n return (\n valueAt(\n payload,\n \"tool_output\",\n \"toolOutput\",\n \"output\",\n \"result\",\n \"tool_response\",\n ) ?? null\n );\n}\n\nexport function toolError(payload: HookPayload): string {\n return stringifyValue(\n valueAt(payload, \"error\", \"error_message\", \"errorMessage\", \"message\") ??\n \"Tool failed\",\n );\n}\n", "import {\n assistantMessage,\n eventName,\n prompt,\n sessionId,\n toolError,\n toolId,\n toolInput,\n toolName,\n toolOutput,\n} from \"../domain/extract.js\";\nimport type {\n Clock,\n CompletedTurn,\n HookConfig,\n HookPayload,\n IdGenerator,\n JsonValue,\n SessionState,\n StateStore,\n ToolCall,\n TraceSink,\n TurnState,\n} from \"../domain/types.js\";\n\nfunction createSession(session: string, now: string): SessionState {\n return {\n version: 1,\n sessionId: session,\n startedAt: now,\n updatedAt: now,\n currentTurn: null,\n };\n}\n\nfunction createTurn(\n id: string,\n now: string,\n userPrompt: string | null,\n): TurnState {\n return {\n id,\n prompt: userPrompt,\n startedAt: now,\n tools: [],\n };\n}\n\nfunction truncate(value: string, maxChars: number): string {\n if (value.length <= maxChars) return value;\n return `${value.slice(0, Math.max(0, maxChars - 18))}\u2026 [truncated]`;\n}\n\nfunction boundedValue(value: JsonValue, maxChars: number): JsonValue {\n const serialized = JSON.stringify(value);\n if (serialized.length <= maxChars) return value;\n return truncate(serialized, maxChars);\n}\n\nfunction sanitizeTurn(turn: CompletedTurn, config: HookConfig): CompletedTurn {\n return {\n ...turn,\n prompt:\n config.capturePrompts && turn.prompt !== null\n ? truncate(turn.prompt, config.maxCaptureChars)\n : null,\n assistantMessage:\n config.capturePrompts && turn.assistantMessage !== null\n ? truncate(turn.assistantMessage, config.maxCaptureChars)\n : null,\n tools: turn.tools.map((tool) => ({\n ...tool,\n name: truncate(tool.name, 256),\n input: config.captureToolInputs\n ? boundedValue(tool.input, config.maxCaptureChars)\n : null,\n output:\n config.captureToolOutputs && tool.output !== null\n ? boundedValue(tool.output, config.maxCaptureChars)\n : null,\n error:\n config.captureToolOutputs && tool.error !== null\n ? truncate(tool.error, config.maxCaptureChars)\n : null,\n })),\n };\n}\n\nfunction findTool(\n turn: TurnState,\n id: string | null,\n name: string,\n): ToolCall | null {\n if (id) {\n const byId = turn.tools.find((tool) => tool.id === id);\n if (byId) return byId;\n }\n return (\n [...turn.tools]\n .reverse()\n .find((tool) => tool.endedAt === null && tool.name === name) ??\n [...turn.tools].reverse().find((tool) => tool.endedAt === null) ??\n null\n );\n}\n\nexport class TurnTracker {\n constructor(\n private readonly stateStore: StateStore,\n private readonly traceSink: TraceSink,\n private readonly config: HookConfig,\n private readonly clock: Clock,\n private readonly idGenerator: IdGenerator,\n ) {}\n\n async handle(payload: HookPayload): Promise {\n const name = eventName(payload);\n const session = sessionId(payload);\n if (!name || !session) return;\n\n let completed: CompletedTurn | null = null;\n await this.stateStore.withSessionLock(session, async () => {\n const now = this.clock.now().toISOString();\n const existing =\n (await this.stateStore.load(session)) ?? createSession(session, now);\n\n switch (name) {\n case \"SessionStart\":\n existing.updatedAt = now;\n await this.stateStore.save(existing);\n return;\n case \"UserPromptSubmit\": {\n const userPrompt = prompt(payload);\n existing.currentTurn = createTurn(\n this.idGenerator.next(),\n now,\n this.config.capturePrompts && userPrompt !== null\n ? truncate(userPrompt, this.config.maxCaptureChars)\n : null,\n );\n existing.updatedAt = now;\n await this.stateStore.save(existing);\n return;\n }\n case \"PreToolUse\":\n this.recordToolStart(existing, payload, now);\n await this.stateStore.save(existing);\n return;\n case \"PostToolUse\":\n this.recordToolOutput(existing, payload, now, false);\n await this.stateStore.save(existing);\n return;\n case \"PostToolUseFailure\":\n this.recordToolOutput(existing, payload, now, true);\n await this.stateStore.save(existing);\n return;\n case \"Stop\":\n completed = sanitizeTurn(\n {\n sessionId: session,\n turnId: existing.currentTurn?.id ?? this.idGenerator.next(),\n prompt: existing.currentTurn?.prompt ?? null,\n assistantMessage: assistantMessage(payload),\n startedAt: existing.currentTurn?.startedAt ?? now,\n endedAt: now,\n tools: existing.currentTurn?.tools ?? [],\n },\n this.config,\n );\n await this.stateStore.clear(session);\n return;\n default:\n return;\n }\n });\n\n if (completed) await this.traceSink.publishTurn(completed);\n }\n\n private recordToolStart(\n state: SessionState,\n payload: HookPayload,\n now: string,\n ): void {\n const turn =\n state.currentTurn ?? createTurn(this.idGenerator.next(), now, null);\n state.currentTurn = turn;\n turn.tools.push({\n id: toolId(payload) ?? this.idGenerator.next(),\n name: toolName(payload),\n input: this.config.captureToolInputs\n ? boundedValue(toolInput(payload), this.config.maxCaptureChars)\n : null,\n output: null,\n error: null,\n startedAt: now,\n endedAt: null,\n });\n state.updatedAt = now;\n }\n\n private recordToolOutput(\n state: SessionState,\n payload: HookPayload,\n now: string,\n failed: boolean,\n ): void {\n const turn =\n state.currentTurn ?? createTurn(this.idGenerator.next(), now, null);\n state.currentTurn = turn;\n const name = toolName(payload);\n const tool = findTool(turn, toolId(payload), name);\n const output =\n !failed && this.config.captureToolOutputs\n ? boundedValue(toolOutput(payload), this.config.maxCaptureChars)\n : null;\n const error =\n failed && this.config.captureToolOutputs\n ? truncate(toolError(payload), this.config.maxCaptureChars)\n : null;\n if (tool) {\n tool.output = output;\n tool.error = error;\n tool.endedAt = now;\n } else {\n turn.tools.push({\n id: toolId(payload) ?? this.idGenerator.next(),\n name,\n input: null,\n output,\n error,\n startedAt: now,\n endedAt: now,\n });\n }\n state.updatedAt = now;\n }\n}\n\nexport class NoopTraceSink implements TraceSink {\n async publishTurn(_turn: CompletedTurn): Promise {\n // Missing credentials must never make ZCode hooks fail.\n }\n}\n", "import { createHash, randomUUID } from \"node:crypto\";\nimport {\n mkdir,\n open,\n readFile,\n rename,\n rm,\n stat,\n writeFile,\n} from \"node:fs/promises\";\nimport { homedir, tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type {\n JsonValue,\n SessionState,\n StateStore,\n ToolCall,\n TurnState,\n} from \"../domain/types.js\";\n\nconst LOCK_WAIT_MS = 25;\nconst LOCK_ATTEMPTS = 160;\nconst STALE_LOCK_MS = 60_000;\n\nfunction isRecord(value: unknown): value is Record {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction isJsonValue(value: unknown): value is JsonValue {\n if (\n value === null ||\n typeof value === \"string\" ||\n typeof value === \"number\" ||\n typeof value === \"boolean\"\n ) {\n return true;\n }\n if (Array.isArray(value)) return value.every(isJsonValue);\n return isRecord(value) && Object.values(value).every(isJsonValue);\n}\n\nfunction parseToolCall(value: unknown): ToolCall | null {\n if (!isRecord(value)) return null;\n if (\n typeof value.id !== \"string\" ||\n typeof value.name !== \"string\" ||\n !isJsonValue(value.input) ||\n (value.output !== null && !isJsonValue(value.output)) ||\n (value.error !== null && typeof value.error !== \"string\") ||\n typeof value.startedAt !== \"string\" ||\n (value.endedAt !== null && typeof value.endedAt !== \"string\")\n ) {\n return null;\n }\n return {\n id: value.id,\n name: value.name,\n input: value.input,\n output: value.output,\n error: value.error,\n startedAt: value.startedAt,\n endedAt: value.endedAt,\n };\n}\n\nfunction parseTurn(value: unknown): TurnState | null {\n if (!isRecord(value)) return null;\n if (\n typeof value.id !== \"string\" ||\n (value.prompt !== null && typeof value.prompt !== \"string\") ||\n typeof value.startedAt !== \"string\" ||\n !Array.isArray(value.tools)\n ) {\n return null;\n }\n const tools = value.tools.map(parseToolCall);\n if (tools.some((tool): tool is null => tool === null)) return null;\n return {\n id: value.id,\n prompt: value.prompt,\n startedAt: value.startedAt,\n tools: tools.filter((tool): tool is ToolCall => tool !== null),\n };\n}\n\nfunction parseState(value: unknown): SessionState | null {\n if (!isRecord(value)) return null;\n if (\n value.version !== 1 ||\n typeof value.sessionId !== \"string\" ||\n typeof value.startedAt !== \"string\" ||\n typeof value.updatedAt !== \"string\"\n ) {\n return null;\n }\n const currentTurn =\n value.currentTurn === null ? null : parseTurn(value.currentTurn);\n if (value.currentTurn !== null && currentTurn === null) return null;\n return {\n version: 1,\n sessionId: value.sessionId,\n startedAt: value.startedAt,\n updatedAt: value.updatedAt,\n currentTurn,\n };\n}\n\nfunction sessionKey(sessionId: string): string {\n return createHash(\"sha256\").update(sessionId).digest(\"hex\");\n}\n\nfunction defaultDataDir(): string {\n return join(\n homedir() || tmpdir(),\n \".zcode\",\n \"cli\",\n \"plugins\",\n \"data\",\n \"langfuse-observability\",\n );\n}\n\nexport class JsonStateStore implements StateStore {\n private readonly dataDir: string;\n\n constructor(dataDir = process.env.ZCODE_PLUGIN_DATA || defaultDataDir()) {\n this.dataDir = dataDir;\n }\n\n async load(sessionId: string): Promise {\n try {\n const contents = await readFile(this.statePath(sessionId), \"utf8\");\n return parseState(JSON.parse(contents));\n } catch {\n return null;\n }\n }\n\n async save(state: SessionState): Promise {\n await mkdir(this.dataDir, { recursive: true, mode: 0o700 });\n const path = this.statePath(state.sessionId);\n const temporaryPath = `${path}.${randomUUID()}.tmp`;\n await writeFile(temporaryPath, JSON.stringify(state), {\n encoding: \"utf8\",\n mode: 0o600,\n });\n await rename(temporaryPath, path);\n }\n\n async clear(sessionId: string): Promise {\n await rm(this.statePath(sessionId), { force: true });\n }\n\n async withSessionLock(\n sessionId: string,\n task: () => Promise,\n ): Promise {\n await mkdir(this.dataDir, { recursive: true, mode: 0o700 });\n const lockPath = this.lockPath(sessionId);\n let acquired = false;\n\n for (let attempt = 0; attempt < LOCK_ATTEMPTS; attempt += 1) {\n try {\n const handle = await open(lockPath, \"wx\", 0o600);\n await handle.close();\n acquired = true;\n break;\n } catch {\n await this.removeStaleLock(lockPath);\n await new Promise((resolve) => setTimeout(resolve, LOCK_WAIT_MS));\n }\n }\n\n if (!acquired)\n throw new Error(\"Timed out acquiring the Langfuse state lock\");\n\n try {\n return await task();\n } finally {\n await rm(lockPath, { force: true });\n }\n }\n\n private statePath(sessionId: string): string {\n return join(this.dataDir, `${sessionKey(sessionId)}.json`);\n }\n\n private lockPath(sessionId: string): string {\n return join(this.dataDir, `${sessionKey(sessionId)}.lock`);\n }\n\n private async removeStaleLock(lockPath: string): Promise {\n try {\n const details = await stat(lockPath);\n if (Date.now() - details.mtimeMs > STALE_LOCK_MS)\n await rm(lockPath, { force: true });\n } catch {\n // The lock may disappear between open(), stat(), and rm().\n }\n }\n}\n", "/*!\n * mustache.js - Logic-less {{mustache}} templates with JavaScript\n * http://github.com/janl/mustache.js\n */\n\nvar objectToString = Object.prototype.toString;\nvar isArray = Array.isArray || function isArrayPolyfill (object) {\n return objectToString.call(object) === '[object Array]';\n};\n\nfunction isFunction (object) {\n return typeof object === 'function';\n}\n\n/**\n * More correct typeof string handling array\n * which normally returns typeof 'object'\n */\nfunction typeStr (obj) {\n return isArray(obj) ? 'array' : typeof obj;\n}\n\nfunction escapeRegExp (string) {\n return string.replace(/[\\-\\[\\]{}()*+?.,\\\\\\^$|#\\s]/g, '\\\\$&');\n}\n\n/**\n * Null safe way of checking whether or not an object,\n * including its prototype, has a given property\n */\nfunction hasProperty (obj, propName) {\n return obj != null && typeof obj === 'object' && (propName in obj);\n}\n\n/**\n * Safe way of detecting whether or not the given thing is a primitive and\n * whether it has the given property\n */\nfunction primitiveHasOwnProperty (primitive, propName) {\n return (\n primitive != null\n && typeof primitive !== 'object'\n && primitive.hasOwnProperty\n && primitive.hasOwnProperty(propName)\n );\n}\n\n// Workaround for https://issues.apache.org/jira/browse/COUCHDB-577\n// See https://github.com/janl/mustache.js/issues/189\nvar regExpTest = RegExp.prototype.test;\nfunction testRegExp (re, string) {\n return regExpTest.call(re, string);\n}\n\nvar nonSpaceRe = /\\S/;\nfunction isWhitespace (string) {\n return !testRegExp(nonSpaceRe, string);\n}\n\nvar entityMap = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": ''',\n '/': '/',\n '`': '`',\n '=': '='\n};\n\nfunction escapeHtml (string) {\n return String(string).replace(/[&<>\"'`=\\/]/g, function fromEntityMap (s) {\n return entityMap[s];\n });\n}\n\nvar whiteRe = /\\s*/;\nvar spaceRe = /\\s+/;\nvar equalsRe = /\\s*=/;\nvar curlyRe = /\\s*\\}/;\nvar tagRe = /#|\\^|\\/|>|\\{|&|=|!/;\n\n/**\n * Breaks up the given `template` string into a tree of tokens. If the `tags`\n * argument is given here it must be an array with two string values: the\n * opening and closing tags used in the template (e.g. [ \"<%\", \"%>\" ]). Of\n * course, the default is to use mustaches (i.e. mustache.tags).\n *\n * A token is an array with at least 4 elements. The first element is the\n * mustache symbol that was used inside the tag, e.g. \"#\" or \"&\". If the tag\n * did not contain a symbol (i.e. {{myValue}}) this element is \"name\". For\n * all text that appears outside a symbol this element is \"text\".\n *\n * The second element of a token is its \"value\". For mustache tags this is\n * whatever else was inside the tag besides the opening symbol. For text tokens\n * this is the text itself.\n *\n * The third and fourth elements of the token are the start and end indices,\n * respectively, of the token in the original template.\n *\n * Tokens that are the root node of a subtree contain two more elements: 1) an\n * array of tokens in the subtree and 2) the index in the original template at\n * which the closing tag for that section begins.\n *\n * Tokens for partials also contain two more elements: 1) a string value of\n * indendation prior to that tag and 2) the index of that tag on that line -\n * eg a value of 2 indicates the partial is the third tag on this line.\n */\nfunction parseTemplate (template, tags) {\n if (!template)\n return [];\n var lineHasNonSpace = false;\n var sections = []; // Stack to hold section tokens\n var tokens = []; // Buffer to hold the tokens\n var spaces = []; // Indices of whitespace tokens on the current line\n var hasTag = false; // Is there a {{tag}} on the current line?\n var nonSpace = false; // Is there a non-space char on the current line?\n var indentation = ''; // Tracks indentation for tags that use it\n var tagIndex = 0; // Stores a count of number of tags encountered on a line\n\n // Strips all whitespace tokens array for the current line\n // if there was a {{#tag}} on it and otherwise only space.\n function stripSpace () {\n if (hasTag && !nonSpace) {\n while (spaces.length)\n delete tokens[spaces.pop()];\n } else {\n spaces = [];\n }\n\n hasTag = false;\n nonSpace = false;\n }\n\n var openingTagRe, closingTagRe, closingCurlyRe;\n function compileTags (tagsToCompile) {\n if (typeof tagsToCompile === 'string')\n tagsToCompile = tagsToCompile.split(spaceRe, 2);\n\n if (!isArray(tagsToCompile) || tagsToCompile.length !== 2)\n throw new Error('Invalid tags: ' + tagsToCompile);\n\n openingTagRe = new RegExp(escapeRegExp(tagsToCompile[0]) + '\\\\s*');\n closingTagRe = new RegExp('\\\\s*' + escapeRegExp(tagsToCompile[1]));\n closingCurlyRe = new RegExp('\\\\s*' + escapeRegExp('}' + tagsToCompile[1]));\n }\n\n compileTags(tags || mustache.tags);\n\n var scanner = new Scanner(template);\n\n var start, type, value, chr, token, openSection;\n while (!scanner.eos()) {\n start = scanner.pos;\n\n // Match any text between tags.\n value = scanner.scanUntil(openingTagRe);\n\n if (value) {\n for (var i = 0, valueLength = value.length; i < valueLength; ++i) {\n chr = value.charAt(i);\n\n if (isWhitespace(chr)) {\n spaces.push(tokens.length);\n indentation += chr;\n } else {\n nonSpace = true;\n lineHasNonSpace = true;\n indentation += ' ';\n }\n\n tokens.push([ 'text', chr, start, start + 1 ]);\n start += 1;\n\n // Check for whitespace on the current line.\n if (chr === '\\n') {\n stripSpace();\n indentation = '';\n tagIndex = 0;\n lineHasNonSpace = false;\n }\n }\n }\n\n // Match the opening tag.\n if (!scanner.scan(openingTagRe))\n break;\n\n hasTag = true;\n\n // Get the tag type.\n type = scanner.scan(tagRe) || 'name';\n scanner.scan(whiteRe);\n\n // Get the tag value.\n if (type === '=') {\n value = scanner.scanUntil(equalsRe);\n scanner.scan(equalsRe);\n scanner.scanUntil(closingTagRe);\n } else if (type === '{') {\n value = scanner.scanUntil(closingCurlyRe);\n scanner.scan(curlyRe);\n scanner.scanUntil(closingTagRe);\n type = '&';\n } else {\n value = scanner.scanUntil(closingTagRe);\n }\n\n // Match the closing tag.\n if (!scanner.scan(closingTagRe))\n throw new Error('Unclosed tag at ' + scanner.pos);\n\n if (type == '>') {\n token = [ type, value, start, scanner.pos, indentation, tagIndex, lineHasNonSpace ];\n } else {\n token = [ type, value, start, scanner.pos ];\n }\n tagIndex++;\n tokens.push(token);\n\n if (type === '#' || type === '^') {\n sections.push(token);\n } else if (type === '/') {\n // Check section nesting.\n openSection = sections.pop();\n\n if (!openSection)\n throw new Error('Unopened section \"' + value + '\" at ' + start);\n\n if (openSection[1] !== value)\n throw new Error('Unclosed section \"' + openSection[1] + '\" at ' + start);\n } else if (type === 'name' || type === '{' || type === '&') {\n nonSpace = true;\n } else if (type === '=') {\n // Set the tags for the next time around.\n compileTags(value);\n }\n }\n\n stripSpace();\n\n // Make sure there are no open sections when we're done.\n openSection = sections.pop();\n\n if (openSection)\n throw new Error('Unclosed section \"' + openSection[1] + '\" at ' + scanner.pos);\n\n return nestTokens(squashTokens(tokens));\n}\n\n/**\n * Combines the values of consecutive text tokens in the given `tokens` array\n * to a single token.\n */\nfunction squashTokens (tokens) {\n var squashedTokens = [];\n\n var token, lastToken;\n for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) {\n token = tokens[i];\n\n if (token) {\n if (token[0] === 'text' && lastToken && lastToken[0] === 'text') {\n lastToken[1] += token[1];\n lastToken[3] = token[3];\n } else {\n squashedTokens.push(token);\n lastToken = token;\n }\n }\n }\n\n return squashedTokens;\n}\n\n/**\n * Forms the given array of `tokens` into a nested tree structure where\n * tokens that represent a section have two additional items: 1) an array of\n * all tokens that appear in that section and 2) the index in the original\n * template that represents the end of that section.\n */\nfunction nestTokens (tokens) {\n var nestedTokens = [];\n var collector = nestedTokens;\n var sections = [];\n\n var token, section;\n for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) {\n token = tokens[i];\n\n switch (token[0]) {\n case '#':\n case '^':\n collector.push(token);\n sections.push(token);\n collector = token[4] = [];\n break;\n case '/':\n section = sections.pop();\n section[5] = token[2];\n collector = sections.length > 0 ? sections[sections.length - 1][4] : nestedTokens;\n break;\n default:\n collector.push(token);\n }\n }\n\n return nestedTokens;\n}\n\n/**\n * A simple string scanner that is used by the template parser to find\n * tokens in template strings.\n */\nfunction Scanner (string) {\n this.string = string;\n this.tail = string;\n this.pos = 0;\n}\n\n/**\n * Returns `true` if the tail is empty (end of string).\n */\nScanner.prototype.eos = function eos () {\n return this.tail === '';\n};\n\n/**\n * Tries to match the given regular expression at the current position.\n * Returns the matched text if it can match, the empty string otherwise.\n */\nScanner.prototype.scan = function scan (re) {\n var match = this.tail.match(re);\n\n if (!match || match.index !== 0)\n return '';\n\n var string = match[0];\n\n this.tail = this.tail.substring(string.length);\n this.pos += string.length;\n\n return string;\n};\n\n/**\n * Skips all text until the given regular expression can be matched. Returns\n * the skipped string, which is the entire tail if no match can be made.\n */\nScanner.prototype.scanUntil = function scanUntil (re) {\n var index = this.tail.search(re), match;\n\n switch (index) {\n case -1:\n match = this.tail;\n this.tail = '';\n break;\n case 0:\n match = '';\n break;\n default:\n match = this.tail.substring(0, index);\n this.tail = this.tail.substring(index);\n }\n\n this.pos += match.length;\n\n return match;\n};\n\n/**\n * Represents a rendering context by wrapping a view object and\n * maintaining a reference to the parent context.\n */\nfunction Context (view, parentContext) {\n this.view = view;\n this.cache = { '.': this.view };\n this.parent = parentContext;\n}\n\n/**\n * Creates a new context using the given view with this context\n * as the parent.\n */\nContext.prototype.push = function push (view) {\n return new Context(view, this);\n};\n\n/**\n * Returns the value of the given name in this context, traversing\n * up the context hierarchy if the value is absent in this context's view.\n */\nContext.prototype.lookup = function lookup (name) {\n var cache = this.cache;\n\n var value;\n if (cache.hasOwnProperty(name)) {\n value = cache[name];\n } else {\n var context = this, intermediateValue, names, index, lookupHit = false;\n\n while (context) {\n if (name.indexOf('.') > 0) {\n intermediateValue = context.view;\n names = name.split('.');\n index = 0;\n\n /**\n * Using the dot notion path in `name`, we descend through the\n * nested objects.\n *\n * To be certain that the lookup has been successful, we have to\n * check if the last object in the path actually has the property\n * we are looking for. We store the result in `lookupHit`.\n *\n * This is specially necessary for when the value has been set to\n * `undefined` and we want to avoid looking up parent contexts.\n *\n * In the case where dot notation is used, we consider the lookup\n * to be successful even if the last \"object\" in the path is\n * not actually an object but a primitive (e.g., a string, or an\n * integer), because it is sometimes useful to access a property\n * of an autoboxed primitive, such as the length of a string.\n **/\n while (intermediateValue != null && index < names.length) {\n if (index === names.length - 1)\n lookupHit = (\n hasProperty(intermediateValue, names[index])\n || primitiveHasOwnProperty(intermediateValue, names[index])\n );\n\n intermediateValue = intermediateValue[names[index++]];\n }\n } else {\n intermediateValue = context.view[name];\n\n /**\n * Only checking against `hasProperty`, which always returns `false` if\n * `context.view` is not an object. Deliberately omitting the check\n * against `primitiveHasOwnProperty` if dot notation is not used.\n *\n * Consider this example:\n * ```\n * Mustache.render(\"The length of a football field is {{#length}}{{length}}{{/length}}.\", {length: \"100 yards\"})\n * ```\n *\n * If we were to check also against `primitiveHasOwnProperty`, as we do\n * in the dot notation case, then render call would return:\n *\n * \"The length of a football field is 9.\"\n *\n * rather than the expected:\n *\n * \"The length of a football field is 100 yards.\"\n **/\n lookupHit = hasProperty(context.view, name);\n }\n\n if (lookupHit) {\n value = intermediateValue;\n break;\n }\n\n context = context.parent;\n }\n\n cache[name] = value;\n }\n\n if (isFunction(value))\n value = value.call(this.view);\n\n return value;\n};\n\n/**\n * A Writer knows how to take a stream of tokens and render them to a\n * string, given a context. It also maintains a cache of templates to\n * avoid the need to parse the same template twice.\n */\nfunction Writer () {\n this.templateCache = {\n _cache: {},\n set: function set (key, value) {\n this._cache[key] = value;\n },\n get: function get (key) {\n return this._cache[key];\n },\n clear: function clear () {\n this._cache = {};\n }\n };\n}\n\n/**\n * Clears all cached templates in this writer.\n */\nWriter.prototype.clearCache = function clearCache () {\n if (typeof this.templateCache !== 'undefined') {\n this.templateCache.clear();\n }\n};\n\n/**\n * Parses and caches the given `template` according to the given `tags` or\n * `mustache.tags` if `tags` is omitted, and returns the array of tokens\n * that is generated from the parse.\n */\nWriter.prototype.parse = function parse (template, tags) {\n var cache = this.templateCache;\n var cacheKey = template + ':' + (tags || mustache.tags).join(':');\n var isCacheEnabled = typeof cache !== 'undefined';\n var tokens = isCacheEnabled ? cache.get(cacheKey) : undefined;\n\n if (tokens == undefined) {\n tokens = parseTemplate(template, tags);\n isCacheEnabled && cache.set(cacheKey, tokens);\n }\n return tokens;\n};\n\n/**\n * High-level method that is used to render the given `template` with\n * the given `view`.\n *\n * The optional `partials` argument may be an object that contains the\n * names and templates of partials that are used in the template. It may\n * also be a function that is used to load partial templates on the fly\n * that takes a single argument: the name of the partial.\n *\n * If the optional `config` argument is given here, then it should be an\n * object with a `tags` attribute or an `escape` attribute or both.\n * If an array is passed, then it will be interpreted the same way as\n * a `tags` attribute on a `config` object.\n *\n * The `tags` attribute of a `config` object must be an array with two\n * string values: the opening and closing tags used in the template (e.g.\n * [ \"<%\", \"%>\" ]). The default is to mustache.tags.\n *\n * The `escape` attribute of a `config` object must be a function which\n * accepts a string as input and outputs a safely escaped string.\n * If an `escape` function is not provided, then an HTML-safe string\n * escaping function is used as the default.\n */\nWriter.prototype.render = function render (template, view, partials, config) {\n var tags = this.getConfigTags(config);\n var tokens = this.parse(template, tags);\n var context = (view instanceof Context) ? view : new Context(view, undefined);\n return this.renderTokens(tokens, context, partials, template, config);\n};\n\n/**\n * Low-level method that renders the given array of `tokens` using\n * the given `context` and `partials`.\n *\n * Note: The `originalTemplate` is only ever used to extract the portion\n * of the original template that was contained in a higher-order section.\n * If the template doesn't use higher-order sections, this argument may\n * be omitted.\n */\nWriter.prototype.renderTokens = function renderTokens (tokens, context, partials, originalTemplate, config) {\n var buffer = '';\n\n var token, symbol, value;\n for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) {\n value = undefined;\n token = tokens[i];\n symbol = token[0];\n\n if (symbol === '#') value = this.renderSection(token, context, partials, originalTemplate, config);\n else if (symbol === '^') value = this.renderInverted(token, context, partials, originalTemplate, config);\n else if (symbol === '>') value = this.renderPartial(token, context, partials, config);\n else if (symbol === '&') value = this.unescapedValue(token, context);\n else if (symbol === 'name') value = this.escapedValue(token, context, config);\n else if (symbol === 'text') value = this.rawValue(token);\n\n if (value !== undefined)\n buffer += value;\n }\n\n return buffer;\n};\n\nWriter.prototype.renderSection = function renderSection (token, context, partials, originalTemplate, config) {\n var self = this;\n var buffer = '';\n var value = context.lookup(token[1]);\n\n // This function is used to render an arbitrary template\n // in the current context by higher-order sections.\n function subRender (template) {\n return self.render(template, context, partials, config);\n }\n\n if (!value) return;\n\n if (isArray(value)) {\n for (var j = 0, valueLength = value.length; j < valueLength; ++j) {\n buffer += this.renderTokens(token[4], context.push(value[j]), partials, originalTemplate, config);\n }\n } else if (typeof value === 'object' || typeof value === 'string' || typeof value === 'number') {\n buffer += this.renderTokens(token[4], context.push(value), partials, originalTemplate, config);\n } else if (isFunction(value)) {\n if (typeof originalTemplate !== 'string')\n throw new Error('Cannot use higher-order sections without the original template');\n\n // Extract the portion of the original template that the section contains.\n value = value.call(context.view, originalTemplate.slice(token[3], token[5]), subRender);\n\n if (value != null)\n buffer += value;\n } else {\n buffer += this.renderTokens(token[4], context, partials, originalTemplate, config);\n }\n return buffer;\n};\n\nWriter.prototype.renderInverted = function renderInverted (token, context, partials, originalTemplate, config) {\n var value = context.lookup(token[1]);\n\n // Use JavaScript's definition of falsy. Include empty arrays.\n // See https://github.com/janl/mustache.js/issues/186\n if (!value || (isArray(value) && value.length === 0))\n return this.renderTokens(token[4], context, partials, originalTemplate, config);\n};\n\nWriter.prototype.indentPartial = function indentPartial (partial, indentation, lineHasNonSpace) {\n var filteredIndentation = indentation.replace(/[^ \\t]/g, '');\n var partialByNl = partial.split('\\n');\n for (var i = 0; i < partialByNl.length; i++) {\n if (partialByNl[i].length && (i > 0 || !lineHasNonSpace)) {\n partialByNl[i] = filteredIndentation + partialByNl[i];\n }\n }\n return partialByNl.join('\\n');\n};\n\nWriter.prototype.renderPartial = function renderPartial (token, context, partials, config) {\n if (!partials) return;\n var tags = this.getConfigTags(config);\n\n var value = isFunction(partials) ? partials(token[1]) : partials[token[1]];\n if (value != null) {\n var lineHasNonSpace = token[6];\n var tagIndex = token[5];\n var indentation = token[4];\n var indentedValue = value;\n if (tagIndex == 0 && indentation) {\n indentedValue = this.indentPartial(value, indentation, lineHasNonSpace);\n }\n var tokens = this.parse(indentedValue, tags);\n return this.renderTokens(tokens, context, partials, indentedValue, config);\n }\n};\n\nWriter.prototype.unescapedValue = function unescapedValue (token, context) {\n var value = context.lookup(token[1]);\n if (value != null)\n return value;\n};\n\nWriter.prototype.escapedValue = function escapedValue (token, context, config) {\n var escape = this.getConfigEscape(config) || mustache.escape;\n var value = context.lookup(token[1]);\n if (value != null)\n return (typeof value === 'number' && escape === mustache.escape) ? String(value) : escape(value);\n};\n\nWriter.prototype.rawValue = function rawValue (token) {\n return token[1];\n};\n\nWriter.prototype.getConfigTags = function getConfigTags (config) {\n if (isArray(config)) {\n return config;\n }\n else if (config && typeof config === 'object') {\n return config.tags;\n }\n else {\n return undefined;\n }\n};\n\nWriter.prototype.getConfigEscape = function getConfigEscape (config) {\n if (config && typeof config === 'object' && !isArray(config)) {\n return config.escape;\n }\n else {\n return undefined;\n }\n};\n\nvar mustache = {\n name: 'mustache.js',\n version: '4.2.0',\n tags: [ '{{', '}}' ],\n clearCache: undefined,\n escape: undefined,\n parse: undefined,\n render: undefined,\n Scanner: undefined,\n Context: undefined,\n Writer: undefined,\n /**\n * Allows a user to override the default caching strategy, by providing an\n * object with set, get and clear methods. This can also be used to disable\n * the cache by setting it to the literal `undefined`.\n */\n set templateCache (cache) {\n defaultWriter.templateCache = cache;\n },\n /**\n * Gets the default or overridden caching object from the default writer.\n */\n get templateCache () {\n return defaultWriter.templateCache;\n }\n};\n\n// All high-level mustache.* functions use this writer.\nvar defaultWriter = new Writer();\n\n/**\n * Clears all cached templates in the default writer.\n */\nmustache.clearCache = function clearCache () {\n return defaultWriter.clearCache();\n};\n\n/**\n * Parses and caches the given template in the default writer and returns the\n * array of tokens it contains. Doing this ahead of time avoids the need to\n * parse templates on the fly as they are rendered.\n */\nmustache.parse = function parse (template, tags) {\n return defaultWriter.parse(template, tags);\n};\n\n/**\n * Renders the `template` with the given `view`, `partials`, and `config`\n * using the default writer.\n */\nmustache.render = function render (template, view, partials, config) {\n if (typeof template !== 'string') {\n throw new TypeError('Invalid template! Template should be a \"string\" ' +\n 'but \"' + typeStr(template) + '\" was given as the first ' +\n 'argument for mustache#render(template, view, partials)');\n }\n\n return defaultWriter.render(template, view, partials, config);\n};\n\n// Export the escaping function so that the user may override it.\n// See https://github.com/janl/mustache.js/issues/244\nmustache.escape = escapeHtml;\n\n// Export these mainly for testing, but also for advanced usage.\nmustache.Scanner = Scanner;\nmustache.Context = Context;\nmustache.Writer = Writer;\n\nexport default mustache;\n", "export class SimpleEventEmitter {\n events: { [key: string]: ((...args: any[]) => void)[] } = {};\n\n constructor() {\n this.events = {};\n }\n\n on(event: string, listener: (...args: any[]) => void): () => void {\n if (!this.events[event]) {\n this.events[event] = [];\n }\n this.events[event].push(listener);\n\n return () => {\n this.events[event] = this.events[event].filter((x) => x !== listener);\n };\n }\n\n emit(event: string, payload: any): void {\n for (const listener of this.events[event] || []) {\n listener(payload);\n }\n for (const listener of this.events[\"*\"] || []) {\n listener(event, payload);\n }\n }\n}\n", "import type { LangfusePromptClient } from \"./promptClients\";\n\nexport const DEFAULT_PROMPT_CACHE_TTL_SECONDS = 60;\n\nclass LangfusePromptCacheItem {\n private _expiry: number;\n\n constructor(\n public value: LangfusePromptClient,\n ttlSeconds: number\n ) {\n this._expiry = Date.now() + ttlSeconds * 1000;\n }\n\n get isExpired(): boolean {\n return Date.now() > this._expiry;\n }\n}\nexport class LangfusePromptCache {\n private _cache: Map;\n private _defaultTtlSeconds: number;\n private _refreshingKeys: Map>;\n\n constructor() {\n this._cache = new Map();\n this._defaultTtlSeconds = DEFAULT_PROMPT_CACHE_TTL_SECONDS;\n this._refreshingKeys = new Map>();\n }\n\n public getIncludingExpired(key: string): LangfusePromptCacheItem | null {\n return this._cache.get(key) ?? null;\n }\n\n public set(key: string, value: LangfusePromptClient, ttlSeconds?: number): void {\n const effectiveTtlSeconds = ttlSeconds ?? this._defaultTtlSeconds;\n this._cache.set(key, new LangfusePromptCacheItem(value, effectiveTtlSeconds));\n }\n\n public addRefreshingPromise(key: string, promise: Promise): void {\n this._refreshingKeys.set(key, promise);\n promise\n .then(() => {\n this._refreshingKeys.delete(key);\n })\n .catch(() => {\n this._refreshingKeys.delete(key);\n });\n }\n\n public isRefreshing(key: string): boolean {\n return this._refreshingKeys.has(key);\n }\n\n public invalidate(promptName: string): void {\n console.log(\"invalidating\", promptName, this._cache.keys());\n for (const key of this._cache.keys()) {\n if (key.startsWith(promptName)) {\n this._cache.delete(key);\n }\n }\n }\n}\n", "import { type LangfuseObjectClient } from \"./index\";\nimport { type LangfusePromptClient } from \"./prompts/promptClients\";\nimport { type components, type paths } from \"./openapi/server\";\n\nexport type LangfuseCoreOptions = {\n // Langfuse API publicKey obtained from the Langfuse UI project settings\n publicKey?: string;\n // Langfuse API secretKey obtained from the Langfuse UI project settings\n secretKey?: string;\n // Langfuse API baseUrl (https://cloud.langfuse.com by default)\n baseUrl?: string;\n // Additional HTTP headers to send with each request\n additionalHeaders?: Record;\n // The number of events to queue before sending to Langfuse (flushing)\n flushAt?: number;\n // The interval in milliseconds between periodic flushes\n flushInterval?: number;\n // How many times we will retry HTTP requests\n fetchRetryCount?: number;\n // The delay between HTTP request retries\n fetchRetryDelay?: number;\n // Timeout in milliseconds for any calls. Defaults to 10 seconds.\n requestTimeout?: number;\n // release (version) of the application, defaults to env LANGFUSE_RELEASE\n release?: string;\n // integration type of the SDK.\n sdkIntegration?: string; // DEFAULT, LANGCHAIN, or any other custom value\n // Enabled switch for the SDK. If disabled, no observability data will be sent to Langfuse. Defaults to true.\n enabled?: boolean;\n // Mask function to mask data in the event body\n mask?: MaskFunction;\n // Trace sampling rate. Approx. sampleRate % traces will be sent to LF servers\n sampleRate?: number;\n // Environment from which traces originate\n environment?: string;\n // Project ID to use for the SDK in admin mode. This should never be set by users.\n _projectId?: string;\n // Whether to enable local event export. Defaults to false.\n _isLocalEventExportEnabled?: boolean;\n};\n\nexport enum LangfusePersistedProperty {\n Props = \"props\",\n Queue = \"queue\",\n OptedOut = \"opted_out\",\n}\n\nexport type LangfuseFetchOptions = {\n method: \"GET\" | \"POST\" | \"PUT\" | \"PATCH\";\n headers: { [key: string]: string };\n body?: string | Buffer;\n signal?: AbortSignal;\n};\n\nexport type LangfuseFetchResponse = {\n status: number;\n text: () => Promise;\n json: () => Promise;\n arrayBuffer: () => Promise;\n};\n\nexport type LangfuseObject = SingleIngestionEvent[\"type\"];\n\nexport type LangfuseQueueItem = SingleIngestionEvent & {\n callback?: (err: any) => void;\n};\n\nexport type SingleIngestionEvent =\n paths[\"/api/public/ingestion\"][\"post\"][\"requestBody\"][\"content\"][\"application/json\"][\"batch\"][number];\n\n// return type of ingestion endpoint defined on 200 status error in fern as 207 is not possible\nexport type IngestionReturnType =\n paths[\"/api/public/ingestion\"][\"post\"][\"responses\"][207][\"content\"][\"application/json\"];\n\nexport type LangfuseEventProperties = {\n [key: string]: any;\n};\n\nexport type LangfuseMetadataProperties = {\n [key: string]: any;\n};\n\n// ASYNC\nexport type CreateLangfuseTraceBody = FixTypes;\n\nexport type CreateLangfuseEventBody = FixTypes;\n\nexport type CreateLangfuseSpanBody = FixTypes;\nexport type UpdateLangfuseSpanBody = FixTypes;\nexport type EventBody =\n | CreateLangfuseTraceBody\n | CreateLangfuseEventBody\n | CreateLangfuseSpanBody\n | CreateLangfuseGenerationBody\n | CreateLangfuseScoreBody\n | UpdateLangfuseSpanBody\n | UpdateLangfuseGenerationBody;\n\nexport type Usage = FixTypes;\nexport type UsageDetails = FixTypes;\nexport type CreateLangfuseGenerationBody = FixTypes;\nexport type UpdateLangfuseGenerationBody = FixTypes;\n\nexport type CreateLangfuseScoreBody = FixTypes;\n\n// SYNC\nexport type GetLangfuseTracesQuery = FixTypes;\nexport type GetLangfuseTracesResponse = FixTypes<\n paths[\"/api/public/traces\"][\"get\"][\"responses\"][\"200\"][\"content\"][\"application/json\"]\n>;\nexport type GetLangfuseTraceResponse = FixTypes<\n paths[\"/api/public/traces/{traceId}\"][\"get\"][\"responses\"][\"200\"][\"content\"][\"application/json\"]\n>;\nexport type GetLangfuseObservationsQuery = FixTypes;\nexport type GetLangfuseObservationsResponse = FixTypes<\n paths[\"/api/public/observations\"][\"get\"][\"responses\"][\"200\"][\"content\"][\"application/json\"]\n>;\nexport type GetLangfuseObservationResponse = FixTypes<\n paths[\"/api/public/observations/{observationId}\"][\"get\"][\"responses\"][\"200\"][\"content\"][\"application/json\"]\n>;\nexport type GetLangfuseSessionsQuery = FixTypes;\nexport type GetLangfuseSessionsResponse = FixTypes<\n paths[\"/api/public/sessions\"][\"get\"][\"responses\"][\"200\"][\"content\"][\"application/json\"]\n>;\nexport type GetLangfuseDatasetParams = FixTypes<\n paths[\"/api/public/v2/datasets/{datasetName}\"][\"get\"][\"parameters\"][\"path\"]\n>;\nexport type GetLangfuseDatasetResponse = FixTypes<\n paths[\"/api/public/v2/datasets/{datasetName}\"][\"get\"][\"responses\"][\"200\"][\"content\"][\"application/json\"]\n>;\nexport type GetLangfuseDatasetItemsQuery = paths[\"/api/public/dataset-items\"][\"get\"][\"parameters\"][\"query\"];\nexport type GetLangfuseDatasetItemsResponse = FixTypes<\n paths[\"/api/public/dataset-items\"][\"get\"][\"responses\"][\"200\"][\"content\"][\"application/json\"]\n>;\nexport type CreateLangfuseDatasetRunItemBody = FixTypes<\n paths[\"/api/public/dataset-run-items\"][\"post\"][\"requestBody\"][\"content\"][\"application/json\"]\n>;\nexport type CreateLangfuseDatasetRunItemResponse = FixTypes<\n paths[\"/api/public/dataset-run-items\"][\"post\"][\"responses\"][\"200\"][\"content\"][\"application/json\"]\n>;\nexport type CreateLangfuseDatasetBody =\n paths[\"/api/public/v2/datasets\"][\"post\"][\"requestBody\"][\"content\"][\"application/json\"];\nexport type CreateLangfuseDatasetResponse = FixTypes<\n paths[\"/api/public/v2/datasets\"][\"post\"][\"responses\"][\"200\"][\"content\"][\"application/json\"]\n>;\nexport type CreateLangfuseDatasetItemBody = FixTypes<\n paths[\"/api/public/dataset-items\"][\"post\"][\"requestBody\"][\"content\"][\"application/json\"]\n>;\nexport type CreateLangfuseDatasetItemResponse = FixTypes<\n paths[\"/api/public/dataset-items\"][\"post\"][\"responses\"][\"200\"][\"content\"][\"application/json\"]\n>;\nexport type GetLangfuseDatasetRunParams = FixTypes<\n paths[\"/api/public/datasets/{datasetName}/runs/{runName}\"][\"get\"][\"parameters\"][\"path\"]\n>;\nexport type GetLangfuseDatasetRunResponse = FixTypes<\n paths[\"/api/public/datasets/{datasetName}/runs/{runName}\"][\"get\"][\"responses\"][\"200\"][\"content\"][\"application/json\"]\n>;\nexport type GetLangfuseDatasetRunsQuery =\n paths[\"/api/public/datasets/{datasetName}/runs\"][\"get\"][\"parameters\"][\"query\"];\nexport type GetLangfuseDatasetRunsPath = paths[\"/api/public/datasets/{datasetName}/runs\"][\"get\"][\"parameters\"][\"path\"];\n\nexport type GetLangfuseDatasetRunsResponse = FixTypes<\n paths[\"/api/public/datasets/{datasetName}/runs\"][\"get\"][\"responses\"][\"200\"][\"content\"][\"application/json\"]\n>;\nexport type CreateLangfusePromptBody = FixTypes<\n paths[\"/api/public/v2/prompts\"][\"post\"][\"requestBody\"][\"content\"][\"application/json\"]\n>;\nexport type UpdatePromptBody = FixTypes<\n paths[\"/api/public/v2/prompts/{name}/versions/{version}\"][\"patch\"][\"requestBody\"][\"content\"][\"application/json\"]\n>;\nexport type CreateLangfusePromptResponse =\n paths[\"/api/public/v2/prompts\"][\"post\"][\"responses\"][\"200\"][\"content\"][\"application/json\"];\n\nexport type GetLangfusePromptSuccessData =\n paths[\"/api/public/v2/prompts/{promptName}\"][\"get\"][\"responses\"][\"200\"][\"content\"][\"application/json\"];\n\nexport type GetLangfusePromptFailureData = { message?: string };\nexport type GetLangfusePromptResponse =\n | {\n fetchResult: \"success\";\n data: GetLangfusePromptSuccessData;\n }\n | { fetchResult: \"failure\"; data: GetLangfusePromptFailureData };\n\nexport type ChatMessage = FixTypes;\nexport type PlaceholderMessage = FixTypes;\nexport type ChatMessageWithPlaceholders = FixTypes;\nexport type ChatPrompt = FixTypes & { type: \"chat\" };\nexport type TextPrompt = FixTypes & { type: \"text\" };\n\n// Make ChatPromptClient constructor backwards compatible with normal chat messages (probably no one uses it that way, but still)\nexport type ChatPromptCompat = Omit & {\n prompt: ChatMessage[] | ChatMessageWithPlaceholders[];\n};\n\nexport enum ChatMessageType {\n ChatMessage = \"chatmessage\",\n Placeholder = \"placeholder\",\n}\n\nexport type ChatMessageOrPlaceholder = ChatMessage | ({ type: ChatMessageType.Placeholder } & PlaceholderMessage);\n\nexport type LangchainMessagesPlaceholder = {\n variableName: string;\n optional?: boolean;\n};\n\n// Media\nexport type GetMediaUploadUrlRequest = FixTypes;\nexport type GetMediaUploadUrlResponse = FixTypes;\nexport type MediaContentType = components[\"schemas\"][\"MediaContentType\"];\nexport type PatchMediaBody = FixTypes;\nexport type GetMediaResponse = FixTypes;\n\ntype CreateTextPromptRequest = FixTypes;\ntype CreateChatPromptRequest = FixTypes;\nexport type CreateTextPromptBody = { type?: \"text\" } & Omit & { isActive?: boolean }; // isActive is optional for backward compatibility\nexport type CreateChatPromptBody = { type: \"chat\" } & Omit & { isActive?: boolean }; // isActive is optional for backward compatibility\n\nexport type CreateChatPromptBodyWithPlaceholders = {\n type: \"chat\";\n} & Omit & {\n prompt: (ChatMessage | ChatMessageWithPlaceholders)[];\n isActive?: boolean;\n };\n\nexport type CreatePromptBody = CreateTextPromptBody | CreateChatPromptBody;\n\nexport type PromptInput = {\n prompt?: LangfusePromptRecord | LangfusePromptClient;\n};\n\nexport type JsonType = string | number | boolean | null | { [key: string]: JsonType } | Array;\n\ntype OptionalTypes = T extends null | undefined ? T : never;\n\ntype FixTypes = T extends undefined\n ? undefined\n : Omit<\n {\n [P in keyof T]: P extends\n | \"startTime\"\n | \"endTime\"\n | \"timestamp\"\n | \"completionStartTime\"\n | \"createdAt\"\n | \"updatedAt\"\n | \"fromTimestamp\"\n | \"toTimestamp\"\n | \"fromStartTime\"\n | \"toStartTime\"\n ? // Dates instead of strings\n Date | OptionalTypes\n : P extends \"metadata\" | \"input\" | \"output\" | \"completion\" | \"expectedOutput\"\n ? // JSON instead of strings\n any | OptionalTypes\n : T[P];\n },\n \"externalId\" | \"traceIdType\"\n >;\n\nexport type DeferRuntime = {\n langfuseTraces: (\n traces: {\n id: string;\n name: string;\n url: string;\n }[]\n ) => void;\n};\n\n// Datasets\nexport type DatasetItemData = GetLangfuseDatasetItemsResponse[\"data\"][number];\nexport type LinkDatasetItem = (\n obj: LangfuseObjectClient,\n runName: string,\n runArgs?: {\n description?: string;\n metadata?: any;\n }\n) => Promise<{ id: string }>;\nexport type DatasetItem = DatasetItemData & { link: LinkDatasetItem };\n\nexport type MaskFunction = (params: { data: any }) => any;\n\nexport type LangfusePromptRecord = (TextPrompt | ChatPrompt) & { isFallback: boolean };\n", "import mustache from \"mustache\";\n\nimport {\n type ChatMessage,\n type ChatMessageOrPlaceholder,\n ChatMessageType,\n type ChatPrompt,\n type ChatPromptCompat,\n type CreateLangfusePromptResponse,\n type LangchainMessagesPlaceholder,\n type TextPrompt,\n type ChatMessageWithPlaceholders,\n} from \"../types\";\n\nmustache.escape = function (text) {\n return text;\n};\n\nabstract class BasePromptClient {\n public readonly name: string;\n public readonly version: number;\n public readonly config: unknown;\n public readonly labels: string[];\n public readonly tags: string[];\n public readonly isFallback: boolean;\n public readonly type: \"text\" | \"chat\";\n public readonly commitMessage: string | null | undefined;\n\n constructor(prompt: CreateLangfusePromptResponse, isFallback = false, type: \"text\" | \"chat\") {\n this.name = prompt.name;\n this.version = prompt.version;\n this.config = prompt.config;\n this.labels = prompt.labels;\n this.tags = prompt.tags;\n this.isFallback = isFallback;\n this.type = type;\n this.commitMessage = prompt.commitMessage;\n }\n\n abstract get prompt(): string | ChatMessageWithPlaceholders[];\n abstract set prompt(value: string | ChatMessageWithPlaceholders[]);\n\n abstract compile(\n variables?: Record,\n placeholders?: Record\n ): string | ChatMessage[] | (ChatMessageOrPlaceholder | any)[];\n\n public abstract getLangchainPrompt(options?: {\n placeholders?: Record;\n }): string | ChatMessage[] | ChatMessageOrPlaceholder[] | (ChatMessage | LangchainMessagesPlaceholder | any)[];\n\n protected _transformToLangchainVariables(content: string): string {\n const jsonEscapedContent = this.escapeJsonForLangchain(content);\n\n return jsonEscapedContent.replace(/\\{\\{(\\w+)\\}\\}/g, \"{$1}\");\n }\n\n /**\n * Escapes every curly brace that is part of a JSON object by doubling it.\n *\n * A curly brace is considered “JSON-related” when, after skipping any immediate\n * whitespace, the next non-whitespace character is a single (') or double (\") quote.\n *\n * Braces that are already doubled (e.g. `{{variable}}` placeholders) are left untouched.\n *\n * @param text - Input string that may contain JSON snippets.\n * @returns The string with JSON-related braces doubled.\n */\n protected escapeJsonForLangchain(text: string): string {\n const out: string[] = []; // collected characters\n const stack: boolean[] = []; // true = “this { belongs to JSON”, false = normal “{”\n let i = 0;\n const n = text.length;\n\n while (i < n) {\n const ch = text[i];\n\n // ---------- opening brace ----------\n if (ch === \"{\") {\n // leave existing “{{ …” untouched\n if (i + 1 < n && text[i + 1] === \"{\") {\n out.push(\"{{\");\n i += 2;\n continue;\n }\n\n // look ahead to find the next non-space character\n let j = i + 1;\n while (j < n && /\\s/.test(text[j])) {\n j++;\n }\n\n const isJson = j < n && (text[j] === \"'\" || text[j] === '\"');\n out.push(isJson ? \"{{\" : \"{\");\n stack.push(isJson); // remember how this “{” was treated\n i += 1;\n continue;\n }\n\n // ---------- closing brace ----------\n if (ch === \"}\") {\n // leave existing “… }}” untouched\n if (i + 1 < n && text[i + 1] === \"}\") {\n out.push(\"}}\");\n i += 2;\n continue;\n }\n\n const isJson = stack.pop() ?? false;\n out.push(isJson ? \"}}\" : \"}\");\n i += 1;\n continue;\n }\n\n // ---------- any other character ----------\n out.push(ch);\n i += 1;\n }\n\n return out.join(\"\");\n }\n\n public abstract toJSON(): string;\n}\n\nexport class TextPromptClient extends BasePromptClient {\n public readonly promptResponse: TextPrompt;\n public readonly prompt: string;\n\n constructor(prompt: TextPrompt, isFallback = false) {\n super(prompt, isFallback, \"text\");\n this.promptResponse = prompt;\n this.prompt = prompt.prompt;\n }\n\n compile(variables?: Record, _placeholders?: Record): string {\n return mustache.render(this.promptResponse.prompt, variables ?? {});\n }\n\n public getLangchainPrompt(_options?: { placeholders?: Record }): string {\n /**\n * Converts Langfuse prompt into string compatible with Langchain PromptTemplate.\n *\n * It specifically adapts the mustache-style double curly braces {{variable}} used in Langfuse\n * to the single curly brace {variable} format expected by Langchain.\n *\n * @returns {string} The string that can be plugged into Langchain's PromptTemplate.\n */\n return this._transformToLangchainVariables(this.prompt);\n }\n\n public toJSON(): string {\n return JSON.stringify({\n name: this.name,\n prompt: this.prompt,\n version: this.version,\n isFallback: this.isFallback,\n tags: this.tags,\n labels: this.labels,\n type: this.type,\n config: this.config,\n });\n }\n}\n\nexport class ChatPromptClient extends BasePromptClient {\n public readonly promptResponse: ChatPrompt;\n public readonly prompt: ChatMessageWithPlaceholders[];\n\n constructor(prompt: ChatPromptCompat, isFallback = false) {\n const normalizedPrompt = ChatPromptClient.normalizePrompt(prompt.prompt);\n const typedPrompt: ChatPrompt = {\n ...prompt,\n prompt: normalizedPrompt,\n };\n\n super(typedPrompt, isFallback, \"chat\");\n this.promptResponse = typedPrompt;\n this.prompt = normalizedPrompt;\n }\n\n private static normalizePrompt(prompt: ChatMessage[] | ChatMessageWithPlaceholders[]): ChatMessageWithPlaceholders[] {\n // Convert ChatMessages to ChatMessageWithPlaceholders for backward compatibility\n return prompt.map((item): ChatMessageWithPlaceholders => {\n if (\"type\" in item) {\n // Already has type field (new format)\n return item as ChatMessageWithPlaceholders;\n } else {\n // Plain ChatMessage (legacy format) - add type field\n return { type: ChatMessageType.ChatMessage, ...item } as ChatMessageWithPlaceholders;\n }\n });\n }\n\n compile(variables?: Record, placeholders?: Record): (ChatMessageOrPlaceholder | any)[] {\n /**\n * Compiles the chat prompt by replacing placeholders and variables with provided values.\n *\n * First fills-in placeholders by from the provided placeholder parameter.\n * Then compiles variables into the message content.\n * Unresolved placeholders are included in the output as placeholder objects.\n * If you only want to fill-in placeholders, pass an empty object for variables.\n *\n * @param variables - Key-value pairs for Mustache variable substitution in message content\n * @param placeholders - Key-value pairs where keys are placeholder names and values can be ChatMessage arrays\n * @returns Array of ChatMessage objects and placeholder objects with placeholders replaced and variables rendered\n */\n const messagesWithPlaceholdersReplaced: (ChatMessageOrPlaceholder | any)[] = [];\n const placeholderValues = placeholders ?? {};\n\n for (const item of this.prompt) {\n if (\"type\" in item && item.type === ChatMessageType.Placeholder) {\n const placeholderValue = placeholderValues[item.name];\n if (\n Array.isArray(placeholderValue) &&\n placeholderValue.length > 0 &&\n placeholderValue.every((msg) => typeof msg === \"object\" && \"role\" in msg && \"content\" in msg)\n ) {\n messagesWithPlaceholdersReplaced.push(...(placeholderValue as ChatMessage[]));\n } else if (Array.isArray(placeholderValue) && placeholderValue.length === 0) {\n // Empty array provided - skip placeholder (don't include it)\n } else if (placeholderValue !== undefined) {\n // Non-standard placeholder value format, just stringfiy\n messagesWithPlaceholdersReplaced.push(JSON.stringify(placeholderValue));\n } else {\n // Keep unresolved placeholder in the output\n messagesWithPlaceholdersReplaced.push(item as { type: ChatMessageType.Placeholder } & typeof item);\n }\n } else if (\"role\" in item && \"content\" in item && item.type === ChatMessageType.ChatMessage) {\n messagesWithPlaceholdersReplaced.push({\n role: item.role,\n content: item.content,\n });\n }\n }\n\n return messagesWithPlaceholdersReplaced.map((item) => {\n if (typeof item === \"object\" && item !== null && \"role\" in item && \"content\" in item) {\n return {\n ...item,\n content: mustache.render(item.content, variables ?? {}),\n };\n } else {\n // Return placeholder or stringified value as-is\n return item;\n }\n });\n }\n\n public getLangchainPrompt(options?: {\n placeholders?: Record;\n }): (ChatMessage | LangchainMessagesPlaceholder | any)[] {\n /*\n * Converts Langfuse prompt into format compatible with Langchain PromptTemplate.\n *\n * Fills-in placeholders from provided values and converts unresolved ones to Langchain MessagesPlaceholder objects.\n * Transforms variables from {{var}} to {var} format for Langchain without rendering them.\n *\n * @param options - Configuration object\n * @param options.placeholders - Key-value pairs where keys are placeholder names and values can be ChatMessage arrays\n * @returns Array of ChatMessage objects and Langchain MessagesPlaceholder objects with variables transformed for Langchain compatibility.\n *\n * @example\n * ```typescript\n * const client = new ChatPromptClient(prompt);\n * client.getLangchainPrompt({ placeholders: { examples: [{ role: \"user\", content: \"Hello\" }] } });\n * ```\n */\n const messagesWithPlaceholdersReplaced: (ChatMessage | LangchainMessagesPlaceholder | any)[] = [];\n const placeholderValues = options?.placeholders ?? {};\n\n for (const item of this.prompt) {\n if (\"type\" in item && item.type === ChatMessageType.Placeholder) {\n const placeholderValue = placeholderValues[item.name];\n if (\n Array.isArray(placeholderValue) &&\n placeholderValue.length > 0 &&\n placeholderValue.every((msg) => typeof msg === \"object\" && \"role\" in msg && \"content\" in msg)\n ) {\n // Complete placeholder fill-in, replace with it\n messagesWithPlaceholdersReplaced.push(\n ...(placeholderValue as ChatMessage[]).map((msg) => {\n return {\n role: msg.role,\n content: this._transformToLangchainVariables(msg.content),\n };\n })\n );\n } else if (Array.isArray(placeholderValue) && placeholderValue.length === 0) {\n // Skip empty array placeholder\n } else if (placeholderValue !== undefined) {\n // Non-standard placeholder value, just stringify and add directly\n messagesWithPlaceholdersReplaced.push(JSON.stringify(placeholderValue));\n } else {\n // Convert unresolved placeholder to Langchain MessagesPlaceholder\n messagesWithPlaceholdersReplaced.push({\n variableName: item.name,\n optional: false,\n });\n }\n } else if (\"role\" in item && \"content\" in item && item.type === ChatMessageType.ChatMessage) {\n messagesWithPlaceholdersReplaced.push({\n role: item.role,\n content: this._transformToLangchainVariables(item.content),\n });\n }\n }\n\n return messagesWithPlaceholdersReplaced;\n }\n\n public toJSON(): string {\n return JSON.stringify({\n name: this.name,\n prompt: this.promptResponse.prompt.map((item) => {\n if (\"type\" in item && item.type === ChatMessageType.ChatMessage) {\n const { type: _, ...messageWithoutType } = item;\n return messageWithoutType;\n }\n return item;\n }),\n version: this.version,\n isFallback: this.isFallback,\n tags: this.tags,\n labels: this.labels,\n type: this.type,\n config: this.config,\n });\n }\n}\n\nexport type LangfusePromptClient = TextPromptClient | ChatPromptClient;\n", "import { type LangfuseCoreOptions } from \"./types\";\n\nexport function assert(truthyValue: any, message: string): void {\n if (!truthyValue) {\n throw new Error(message);\n }\n}\n\nexport function removeTrailingSlash(url: string): string {\n return url?.replace(/\\/+$/, \"\");\n}\n\nexport interface RetriableOptions {\n retryCount?: number;\n retryDelay?: number;\n retryCheck?: (err: any) => boolean;\n}\n\nexport async function retriable(\n fn: () => Promise,\n props: RetriableOptions = {},\n log: (msg: string) => void\n): Promise {\n const { retryCount = 3, retryDelay = 5000, retryCheck = () => true } = props;\n let lastError = null;\n\n for (let i = 0; i < retryCount + 1; i++) {\n if (i > 0) {\n // don't wait when it's the first try\n await new Promise((resolve) => setTimeout(resolve, retryDelay));\n log(`Retrying ${i + 1} of ${retryCount + 1}`);\n }\n\n try {\n const res = await fn();\n return res;\n } catch (e) {\n lastError = e;\n if (!retryCheck(e)) {\n throw e;\n }\n log(`Retriable error: ${JSON.stringify(e)}`);\n }\n }\n\n throw lastError;\n}\n\n// https://stackoverflow.com/a/8809472\nexport function generateUUID(globalThis?: any): string {\n // Public Domain/MIT\n let d = new Date().getTime(); //Timestamp\n let d2 =\n (globalThis && globalThis.performance && globalThis.performance.now && globalThis.performance.now() * 1000) || 0; //Time in microseconds since page-load or 0 if unsupported\n return \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, function (c) {\n let r = Math.random() * 16; //random number between 0 and 16\n if (d > 0) {\n //Use timestamp until depleted\n r = (d + r) % 16 | 0;\n d = Math.floor(d / 16);\n } else {\n //Use microseconds since page-load if supported\n r = (d2 + r) % 16 | 0;\n d2 = Math.floor(d2 / 16);\n }\n return (c === \"x\" ? r : (r & 0x3) | 0x8).toString(16);\n });\n}\n\nexport function currentTimestamp(): number {\n return new Date().getTime();\n}\n\nexport function currentISOTime(): string {\n return new Date().toISOString();\n}\n\nexport function safeSetTimeout(fn: () => void, timeout: number): any {\n // NOTE: we use this so rarely that it is totally fine to do `safeSetTimeout(fn, 0)``\n // rather than setImmediate.\n const t = setTimeout(fn, timeout) as any;\n // We unref if available to prevent Node.js hanging on exit\n t?.unref && t?.unref();\n return t;\n}\n\nexport function getEnv(key: string): T | undefined {\n if (typeof process !== \"undefined\" && process.env[key]) {\n return process.env[key] as T;\n } else if (typeof globalThis !== \"undefined\") {\n return (globalThis as any)[key];\n }\n return;\n}\n\nexport function configLangfuseSDK(params?: LangfuseCoreOptions, secretRequired: boolean = true): LangfuseCoreOptions {\n const { publicKey, secretKey, ...coreOptions } = params ?? {};\n\n // check environment variables if values not provided\n const finalPublicKey = publicKey ?? getEnv(\"LANGFUSE_PUBLIC_KEY\");\n const finalSecretKey = secretRequired ? secretKey ?? getEnv(\"LANGFUSE_SECRET_KEY\") : undefined;\n const finalBaseUrl = coreOptions.baseUrl ?? getEnv(\"LANGFUSE_BASEURL\");\n\n const finalCoreOptions = {\n ...coreOptions,\n baseUrl: finalBaseUrl,\n };\n\n return {\n publicKey: finalPublicKey,\n ...(secretRequired ? { secretKey: finalSecretKey } : undefined),\n ...finalCoreOptions,\n };\n}\n\nexport const encodeQueryParams = (params?: { [key: string]: any }): string => {\n const queryParams = new URLSearchParams();\n Object.entries(params ?? {}).forEach(([key, value]) => {\n if (value !== undefined && value !== null) {\n // check for date\n if (value instanceof Date) {\n queryParams.append(key, value.toISOString());\n } else {\n queryParams.append(key, value.toString());\n }\n }\n });\n return queryParams.toString();\n};\n", "import { getEnv } from \"./utils\";\n\nconst common_release_envs = [\n // Vercel\n \"VERCEL_GIT_COMMIT_SHA\",\n \"NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA\",\n // Netlify\n \"COMMIT_REF\",\n // Render\n \"RENDER_GIT_COMMIT\",\n // GitLab CI\n \"CI_COMMIT_SHA\",\n // CicleCI\n \"CIRCLE_SHA1\",\n // Cloudflare pages\n \"CF_PAGES_COMMIT_SHA\",\n // AWS Amplify\n \"REACT_APP_GIT_SHA\",\n // Heroku\n \"SOURCE_VERSION\",\n // Trigger.dev\n \"TRIGGER_DEPLOYMENT_ID\",\n] as const;\n\nexport function getCommonReleaseEnvs(): string | undefined {\n for (const key of common_release_envs) {\n const value = getEnv(key);\n if (value) {\n return value;\n }\n }\n return undefined;\n}\n", "import { type LangfuseCore } from \"../index\";\n\nlet fs: any = null;\nlet cryptoModule: any = null;\n\n// Use wrapper to prevent bundlers from trying to resolve the dynamic import\n// Otherwise, the import will be incorrectly resolved as a static import even though it's dynamic\n// Test for browser environment would fail because the import will be incorrectly resolved as a static import and fs and crypto will be unavailable\nconst dynamicImport = (module: string): Promise => {\n return import(/* webpackIgnore: true */ module);\n};\n\nif (typeof (globalThis as any).Deno !== \"undefined\") {\n // Deno\n Promise.all([dynamicImport(\"node:fs\"), dynamicImport(\"node:crypto\")])\n .then(([importedFs, importedCrypto]) => {\n fs = importedFs;\n cryptoModule = importedCrypto;\n })\n .catch(); // Errors are handled on runtime\n} else if (typeof process !== \"undefined\" && process.versions?.node) {\n // Node\n Promise.all([dynamicImport(\"fs\"), dynamicImport(\"crypto\")])\n .then(([importedFs, importedCrypto]) => {\n fs = importedFs;\n cryptoModule = importedCrypto;\n })\n .catch(); // Errors are handled on runtime\n} else if (typeof crypto !== \"undefined\") {\n // Edge runtime (Cloudflare Workers, Vercel Cloud Function)\n cryptoModule = crypto;\n}\n\nimport { type MediaContentType } from \"../types\";\n\ninterface ParsedMediaReference {\n mediaId: string;\n source: string;\n contentType: MediaContentType;\n}\n\nexport type LangfuseMediaResolveMediaReferencesParams = {\n obj: T;\n langfuseClient: LangfuseCore;\n resolveWith: \"base64DataUri\";\n maxDepth?: number;\n};\n\n/**\n * A class for wrapping media objects for upload to Langfuse.\n *\n * This class handles the preparation and formatting of media content for Langfuse,\n * supporting both base64 data URIs and raw content bytes.\n */\nclass LangfuseMedia {\n obj?: object;\n\n _contentBytes?: Buffer;\n _contentType?: MediaContentType;\n _source?: string;\n _mediaId?: string;\n\n constructor(params: {\n obj?: object;\n base64DataUri?: string;\n contentType?: MediaContentType;\n contentBytes?: Buffer;\n filePath?: string;\n }) {\n const { obj, base64DataUri, contentType, contentBytes, filePath } = params;\n\n this.obj = obj;\n this._mediaId = undefined;\n\n if (base64DataUri) {\n const [contentBytesParsed, contentTypeParsed] = this.parseBase64DataUri(base64DataUri);\n this._contentBytes = contentBytesParsed;\n this._contentType = contentTypeParsed;\n this._source = \"base64_data_uri\";\n } else if (contentBytes && contentType) {\n this._contentBytes = contentBytes;\n this._contentType = contentType;\n this._source = \"bytes\";\n } else if (filePath && contentType) {\n if (!fs) {\n throw new Error(\"File system support is not available in this environment\");\n }\n\n if (!fs.existsSync(filePath)) {\n throw new Error(`File at path ${filePath} does not exist`);\n }\n\n this._contentBytes = this.readFile(filePath);\n this._contentType = this._contentBytes ? contentType : undefined;\n this._source = this._contentBytes ? \"file\" : undefined;\n } else {\n console.error(\"base64DataUri, or contentBytes and contentType, or filePath must be provided to LangfuseMedia\");\n }\n }\n\n private readFile(filePath: string): Buffer | undefined {\n try {\n if (!fs) {\n throw new Error(\"File system support is not available in this environment\");\n }\n\n return fs.readFileSync(filePath);\n } catch (error) {\n console.error(`Error reading file at path ${filePath}`, error);\n return undefined;\n }\n }\n\n private parseBase64DataUri(data: string): [Buffer | undefined, MediaContentType | undefined] {\n try {\n if (!data || typeof data !== \"string\") {\n throw new Error(\"Data URI is not a string\");\n }\n\n if (!data.startsWith(\"data:\")) {\n throw new Error(\"Data URI does not start with 'data:'\");\n }\n\n const [header, actualData] = data.slice(5).split(\",\", 2);\n if (!header || !actualData) {\n throw new Error(\"Invalid URI\");\n }\n\n const headerParts = header.split(\";\");\n if (!headerParts.includes(\"base64\")) {\n throw new Error(\"Data is not base64 encoded\");\n }\n\n const contentType = headerParts[0];\n if (!contentType) {\n throw new Error(\"Content type is empty\");\n }\n\n return [Buffer.from(actualData, \"base64\"), contentType as MediaContentType];\n } catch (error) {\n console.error(\"Error parsing base64 data URI\", error);\n return [undefined, undefined];\n }\n }\n\n get contentLength(): number | undefined {\n return this._contentBytes?.length;\n }\n\n get contentSha256Hash(): string | undefined {\n if (!this._contentBytes) {\n return undefined;\n }\n\n if (!cryptoModule) {\n console.error(\"Crypto support is not available in this environment\");\n return undefined;\n }\n\n const sha256Hash = cryptoModule.createHash(\"sha256\").update(this._contentBytes).digest(\"base64\");\n return sha256Hash;\n }\n\n toJSON(): string | undefined {\n if (!this._contentType || !this._source || !this._mediaId) {\n return ``;\n }\n\n return `@@@langfuseMedia:type=${this._contentType}|id=${this._mediaId}|source=${this._source}@@@`;\n }\n\n /**\n * Parses a media reference string into a ParsedMediaReference.\n *\n * Example reference string:\n * \"@@@langfuseMedia:type=image/jpeg|id=some-uuid|source=base64DataUri@@@\"\n *\n * @param referenceString - The reference string to parse.\n * @returns An object with the mediaId, source, and contentType.\n *\n * @throws Error if the reference string is invalid or missing required fields.\n */\n public static parseReferenceString(referenceString: string): ParsedMediaReference {\n const prefix = \"@@@langfuseMedia:\";\n const suffix = \"@@@\";\n\n if (!referenceString.startsWith(prefix)) {\n throw new Error(\"Reference string does not start with '@@@langfuseMedia:type='\");\n }\n\n if (!referenceString.endsWith(suffix)) {\n throw new Error(\"Reference string does not end with '@@@'\");\n }\n\n const content = referenceString.slice(prefix.length, -suffix.length);\n\n const pairs = content.split(\"|\");\n const parsedData: { [key: string]: string } = {};\n\n for (const pair of pairs) {\n const [key, value] = pair.split(\"=\", 2);\n parsedData[key] = value;\n }\n\n if (!(\"type\" in parsedData && \"id\" in parsedData && \"source\" in parsedData)) {\n throw new Error(\"Missing required fields in reference string\");\n }\n\n return {\n mediaId: parsedData[\"id\"],\n source: parsedData[\"source\"],\n contentType: parsedData[\"type\"] as MediaContentType,\n };\n }\n\n /**\n * Replaces the media reference strings in an object with base64 data URIs for the media content.\n *\n * This method recursively traverses an object (up to a maximum depth of 10) looking for media reference strings\n * in the format \"@@@langfuseMedia:...@@@\". When found, it fetches the actual media content using the provided\n * Langfuse client and replaces the reference string with a base64 data URI.\n *\n * If fetching media content fails for a reference string, a warning is logged and the reference string is left unchanged.\n *\n * @param params - Configuration object\n * @param params.obj - The object to process. Can be a primitive value, array, or nested object\n * @param params.langfuseClient - Langfuse client instance used to fetch media content\n * @param params.resolveWith - The representation of the media content to replace the media reference string with. Currently only \"base64DataUri\" is supported.\n * @param params.maxDepth - Optional. Default is 10. The maximum depth to traverse the object.\n *\n * @returns A deep copy of the input object with all media references replaced with base64 data URIs where possible\n *\n * @example\n * ```typescript\n * const obj = {\n * image: \"@@@langfuseMedia:type=image/jpeg|id=123|source=bytes@@@\",\n * nested: {\n * pdf: \"@@@langfuseMedia:type=application/pdf|id=456|source=bytes@@@\"\n * }\n * };\n *\n * const result = await LangfuseMedia.resolveMediaReferences({\n * obj,\n * langfuseClient\n * });\n *\n * // Result:\n * // {\n * // image: \"data:image/jpeg;base64,/9j/4AAQSkZJRg...\",\n * // nested: {\n * // pdf: \"data:application/pdf;base64,JVBERi0xLjcK...\"\n * // }\n * // }\n * ```\n */\n public static async resolveMediaReferences(params: LangfuseMediaResolveMediaReferencesParams): Promise {\n const { obj, langfuseClient, maxDepth = 10 } = params;\n\n async function traverse(obj: T, depth: number): Promise {\n if (depth > maxDepth) {\n return obj;\n }\n\n // Handle string with potential media references\n if (typeof obj === \"string\") {\n const regex = /@@@langfuseMedia:.+?@@@/g;\n const referenceStringMatches = obj.match(regex);\n if (!referenceStringMatches) {\n return obj;\n }\n\n let result = obj;\n const referenceStringToMediaContentMap = new Map();\n\n await Promise.all(\n referenceStringMatches.map(async (referenceString) => {\n try {\n const parsedMediaReference = LangfuseMedia.parseReferenceString(referenceString);\n const mediaData = await langfuseClient.fetchMedia(parsedMediaReference.mediaId);\n const mediaContent = await langfuseClient.fetch(mediaData.url, { method: \"GET\", headers: {} });\n if (mediaContent.status !== 200) {\n throw new Error(\"Failed to fetch media content\");\n }\n\n const base64MediaContent = Buffer.from(await mediaContent.arrayBuffer()).toString(\"base64\");\n const base64DataUri = `data:${mediaData.contentType};base64,${base64MediaContent}`;\n\n referenceStringToMediaContentMap.set(referenceString, base64DataUri);\n } catch (error) {\n console.warn(\"Error fetching media content for reference string\", referenceString, error);\n // Do not replace the reference string if there's an error\n }\n })\n );\n\n for (const [referenceString, base64MediaContent] of referenceStringToMediaContentMap.entries()) {\n result = result.replaceAll(referenceString, base64MediaContent) as T & string;\n }\n\n return result;\n }\n\n // Handle arrays\n if (Array.isArray(obj)) {\n return Promise.all(obj.map(async (item) => await traverse(item, depth + 1))) as Promise;\n }\n\n // Handle objects\n if (typeof obj === \"object\" && obj !== null) {\n return Object.fromEntries(\n await Promise.all(Object.entries(obj).map(async ([key, value]) => [key, await traverse(value, depth + 1)]))\n );\n }\n\n return obj;\n }\n\n return traverse(obj, 0);\n }\n}\n\nexport { LangfuseMedia, type MediaContentType, type ParsedMediaReference };\n", "/**\n * A consistent sampling function that works for arbitrary strings across all JavaScript runtimes.\n */\nexport function isInSample(input: string, sampleRate: number | undefined): boolean {\n if (sampleRate === undefined) {\n return true;\n } else if (sampleRate === 0) {\n return false;\n }\n\n if (sampleRate < 0 || sampleRate > 1 || isNaN(sampleRate)) {\n console.warn(\"Sample rate must be between 0 and 1. Ignoring setting.\");\n\n return true;\n }\n\n return simpleHash(input) < sampleRate;\n}\n\n/**\n * Simple and consistent string hashing function.\n * Uses character codes and prime numbers for good distribution.\n */\nfunction simpleHash(str: string): number {\n let hash = 0;\n const prime = 31;\n\n for (let i = 0; i < str.length; i++) {\n // Use rolling multiplication instead of Math.pow\n hash = (hash * prime + str.charCodeAt(i)) >>> 0;\n }\n\n // Use bit operations for better distribution\n hash = ((hash >>> 16) ^ hash) * 0x45d9f3b;\n hash = ((hash >>> 16) ^ hash) * 0x45d9f3b;\n hash = (hash >>> 16) ^ hash;\n\n return Math.abs(hash) / 0x7fffffff;\n}\n", "import { type LangfusePersistedProperty } from \"./types\";\n\nexport class LangfuseMemoryStorage {\n private _memoryStorage: { [key: string]: any | undefined } = {};\n\n getProperty(key: LangfusePersistedProperty): any | undefined {\n return this._memoryStorage[key];\n }\n\n setProperty(key: LangfusePersistedProperty, value: any | null): void {\n this._memoryStorage[key] = value !== null ? value : undefined;\n }\n}\n", "import { SimpleEventEmitter } from \"./eventemitter\";\nimport { LangfusePromptCache } from \"./prompts/promptCache\";\nimport { ChatPromptClient, TextPromptClient, type LangfusePromptClient } from \"./prompts/promptClients\";\nimport { getCommonReleaseEnvs } from \"./release-env\";\nimport {\n ChatMessageType,\n LangfusePersistedProperty,\n type ChatMessage,\n type CreateLangfuseDatasetBody,\n type CreateLangfuseDatasetItemBody,\n type CreateLangfuseDatasetItemResponse,\n type CreateLangfuseDatasetResponse,\n type CreateLangfuseDatasetRunItemBody,\n type CreateLangfuseDatasetRunItemResponse,\n type CreateLangfuseEventBody,\n type CreateLangfuseGenerationBody,\n type CreateLangfusePromptBody,\n type CreateLangfusePromptResponse,\n type GetMediaUploadUrlRequest,\n type GetMediaUploadUrlResponse,\n type PatchMediaBody,\n type CreateLangfuseScoreBody,\n type CreateLangfuseSpanBody,\n type CreateLangfuseTraceBody,\n type CreateChatPromptBody,\n type CreateChatPromptBodyWithPlaceholders,\n type CreateTextPromptBody,\n type DatasetItem,\n type DeferRuntime,\n type EventBody,\n type GetLangfuseDatasetItemsQuery,\n type GetLangfuseDatasetItemsResponse,\n type GetLangfuseDatasetParams,\n type GetLangfuseDatasetResponse,\n type GetLangfuseDatasetRunParams,\n type GetLangfuseDatasetRunResponse,\n type GetLangfuseDatasetRunsQuery,\n type GetLangfuseDatasetRunsResponse,\n type GetLangfuseObservationResponse,\n type GetLangfuseObservationsQuery,\n type GetLangfuseObservationsResponse,\n type GetLangfusePromptResponse,\n type GetLangfuseSessionsQuery,\n type GetLangfuseSessionsResponse,\n type GetLangfuseTraceResponse,\n type GetLangfuseTracesQuery,\n type GetLangfuseTracesResponse,\n type GetMediaResponse,\n type IngestionReturnType,\n type LangfuseCoreOptions,\n type LangfuseFetchOptions,\n type LangfuseFetchResponse,\n type LangfuseObject,\n type LangfuseQueueItem,\n type MaskFunction,\n type PlaceholderMessage,\n type PromptInput,\n type SingleIngestionEvent,\n type UpdateLangfuseGenerationBody,\n type UpdateLangfuseSpanBody,\n type UpdatePromptBody,\n} from \"./types\";\nimport { LangfuseMedia, type LangfuseMediaResolveMediaReferencesParams } from \"./media/LangfuseMedia\";\nimport {\n currentISOTime,\n encodeQueryParams,\n generateUUID,\n getEnv,\n removeTrailingSlash,\n retriable,\n safeSetTimeout,\n type RetriableOptions,\n} from \"./utils\";\nimport { isInSample } from \"./sampling\";\n\nexport * from \"./prompts/promptClients\";\nexport * from \"./media/LangfuseMedia\";\n\nexport { LangfuseMemoryStorage } from \"./storage-memory\";\nexport type { LangfusePromptRecord } from \"./types\";\nexport * as utils from \"./utils\";\nexport type IngestionBody = SingleIngestionEvent[\"body\"];\n\nconst MAX_EVENT_SIZE_BYTES = getEnv(\"LANGFUSE_MAX_EVENT_SIZE_BYTES\")\n ? Number(getEnv(\"LANGFUSE_MAX_EVENT_SIZE_BYTES\"))\n : 1_000_000;\nconst MAX_BATCH_SIZE_BYTES = getEnv(\"LANGFUSE_MAX_BATCH_SIZE_BYTES\")\n ? Number(getEnv(\"LANGFUSE_MAX_BATCH_SIZE_BYTES\"))\n : 2_500_000;\nconst ENVIRONMENT_PATTERN = /^(?!langfuse)[a-z0-9_-]+$/;\n// NOTE: This list whitelists environments that are used for traces ingested by Langfuse. Please mirror edits to this list in the Langfuse ingestion schema.\nconst WHITELISTED_LANGFUSE_INTERNAL_ENVIRONMENTS = [\"langfuse-prompt-experiment\"];\n\nclass LangfuseFetchHttpError extends Error {\n name = \"LangfuseFetchHttpError\";\n body: string | undefined;\n\n constructor(\n public response: LangfuseFetchResponse,\n body: string\n ) {\n super(\"HTTP error while fetching Langfuse: \" + response.status + \" and body: \" + body);\n }\n}\n\nclass LangfuseFetchNetworkError extends Error {\n name = \"LangfuseFetchNetworkError\";\n\n constructor(public error: unknown) {\n super(\"Network error while fetching Langfuse\", error instanceof Error ? { cause: error } : {});\n }\n}\n\nfunction isLangfuseFetchHttpError(error: any): error is LangfuseFetchHttpError {\n return typeof error === \"object\" && error.name === \"LangfuseFetchHttpError\";\n}\n\nfunction isLangfuseFetchNetworkError(error: any): error is LangfuseFetchNetworkError {\n return typeof error === \"object\" && error.name === \"LangfuseFetchNetworkError\";\n}\n\nfunction isLangfuseFetchError(err: any): boolean {\n return isLangfuseFetchHttpError(err) || isLangfuseFetchNetworkError(err);\n}\n\n// Constants for URLs\nconst SUPPORT_URL = \"https://langfuse.com/support\";\nconst API_DOCS_URL = \"https://api.reference.langfuse.com\";\nconst RBAC_DOCS_URL = \"https://langfuse.com/docs/rbac\";\nconst INSTALLATION_DOCS_URL = \"https://langfuse.com/docs/sdk/typescript/guide\";\nconst RATE_LIMITS_URL = \"https://langfuse.com/faq/all/api-limits\";\nconst NPM_PACKAGE_URL = \"https://www.npmjs.com/package/langfuse\";\n\n// Error messages\nconst updatePromptResponse = `Make sure to keep your SDK updated, refer to ${NPM_PACKAGE_URL} for details.`;\nconst defaultServerErrorPrompt = `This is an unusual occurrence and we are monitoring it closely. For help, please contact support: ${SUPPORT_URL}.`;\nconst defaultErrorResponse = `Unexpected error occurred. Please check your request and contact support: ${SUPPORT_URL}.`;\n\n// Error response map\nconst errorResponseByCode = new Map([\n // Internal error category: 5xx errors, 404 error\n [500, `Internal server error occurred. For help, please contact support: ${SUPPORT_URL}`],\n [501, `Not implemented. Please check your request and contact support for help: ${SUPPORT_URL}.`],\n [502, `Bad gateway. ${defaultServerErrorPrompt}`],\n [503, `Service unavailable. ${defaultServerErrorPrompt}`],\n [504, `Gateway timeout. ${defaultServerErrorPrompt}`],\n [404, `Internal error occurred. ${defaultServerErrorPrompt}`],\n\n // Client error category: 4xx errors, excluding 404\n [\n 400,\n `Bad request. Please check your request for any missing or incorrect parameters. Refer to our API docs: ${API_DOCS_URL} for details.`,\n ],\n [\n 401,\n `Unauthorized. Please check your public/private host settings. Refer to our installation and setup guide: ${INSTALLATION_DOCS_URL} for details on SDK configuration.`,\n ],\n [403, `Forbidden. Please check your access control settings. Refer to our RBAC docs: ${RBAC_DOCS_URL} for details.`],\n [429, `Rate limit exceeded. For more information on rate limits please see: ${RATE_LIMITS_URL}`],\n]);\n\n// Returns a user-friendly error message based on the HTTP status code\nfunction getErrorResponseByCode(code: number | undefined): string {\n if (!code) {\n return `${defaultErrorResponse} ${updatePromptResponse}`;\n }\n\n const errorResponse = errorResponseByCode.get(code) || defaultErrorResponse;\n return `${code}: ${errorResponse} ${updatePromptResponse}`;\n}\n\nfunction logIngestionError(error: any): void {\n if (isLangfuseFetchHttpError(error)) {\n const code = error.response.status;\n const errorResponse = getErrorResponseByCode(code);\n console.error(\"[Langfuse SDK]\", errorResponse, `Error details: ${error}`);\n } else if (isLangfuseFetchNetworkError(error)) {\n console.error(\"[Langfuse SDK] Network error: \", error);\n } else {\n console.error(\"[Langfuse SDK] Unknown error:\", error);\n }\n}\n\nabstract class LangfuseCoreStateless {\n // options\n protected secretKey: string | undefined;\n protected publicKey: string;\n baseUrl: string;\n additionalHeaders: Record = {};\n private flushAt: number;\n private flushInterval: number;\n private requestTimeout: number;\n private removeDebugCallback?: () => void;\n private debugMode: boolean = false;\n private pendingEventProcessingPromises: Record> = {};\n private pendingIngestionPromises: Record> = {};\n private release: string | undefined;\n protected sdkIntegration: string;\n private enabled: boolean;\n protected isLocalEventExportEnabled: boolean;\n private localEventExportMap: Map = new Map();\n private projectId: string | undefined;\n private mask: MaskFunction | undefined;\n private sampleRate: number | undefined;\n private environment: string | undefined;\n\n // internal\n protected _events = new SimpleEventEmitter();\n protected _flushTimer?: any;\n protected _retryOptions: RetriableOptions;\n\n // Abstract methods to be overridden by implementations\n abstract fetch(url: string, options: LangfuseFetchOptions): Promise;\n abstract getLibraryId(): string;\n abstract getLibraryVersion(): string;\n\n // This is our abstracted storage. Each implementation should handle its own\n abstract getPersistedProperty(key: LangfusePersistedProperty): T | undefined;\n abstract setPersistedProperty(key: LangfusePersistedProperty, value: T | null): void;\n\n constructor(params: LangfuseCoreOptions) {\n const { publicKey, secretKey, enabled, _projectId, _isLocalEventExportEnabled, ...options } = params;\n\n this._events.on(\"error\", (payload) => {\n console.error(`[Langfuse SDK] ${typeof payload === \"string\" ? payload : JSON.stringify(payload)}`);\n });\n\n this.enabled = enabled === false ? false : true;\n this.publicKey = publicKey ?? \"\";\n this.secretKey = secretKey;\n this.baseUrl = removeTrailingSlash(options?.baseUrl || \"https://cloud.langfuse.com\");\n this.additionalHeaders = options?.additionalHeaders || {};\n this.flushAt = options?.flushAt ? Math.max(options?.flushAt, 1) : 15;\n this.flushInterval = options?.flushInterval ?? 10000;\n this.release = options?.release ?? getEnv(\"LANGFUSE_RELEASE\") ?? getCommonReleaseEnvs() ?? undefined;\n this.mask = options?.mask;\n this.sampleRate =\n options?.sampleRate ?? (getEnv(\"LANGFUSE_SAMPLE_RATE\") ? Number(getEnv(\"LANGFUSE_SAMPLE_RATE\")) : undefined);\n\n if (this.sampleRate) {\n this._events.emit(\"debug\", `Langfuse trace sampling enabled with sampleRate ${this.sampleRate}.`);\n }\n\n this.environment = options?.environment ?? getEnv(\"LANGFUSE_TRACING_ENVIRONMENT\");\n if (\n this.environment &&\n !(\n ENVIRONMENT_PATTERN.test(this.environment) ||\n WHITELISTED_LANGFUSE_INTERNAL_ENVIRONMENTS.includes(this.environment)\n ) &&\n !_isLocalEventExportEnabled\n ) {\n this._events.emit(\n \"error\",\n `Invalid tracing environment set: ${this.environment} . Environment must match regex ${ENVIRONMENT_PATTERN}. Events will be rejected by Langfuse server.`\n );\n }\n\n this._retryOptions = {\n retryCount: options?.fetchRetryCount ?? 3,\n retryDelay: options?.fetchRetryDelay ?? 3000,\n retryCheck: isLangfuseFetchError,\n };\n this.requestTimeout = options?.requestTimeout ?? 5000; // 5 seconds\n\n this.sdkIntegration = options?.sdkIntegration ?? \"DEFAULT\";\n\n this.isLocalEventExportEnabled = _isLocalEventExportEnabled ?? false;\n\n if (this.isLocalEventExportEnabled && !_projectId) {\n this._events.emit(\n \"error\",\n \"Local event export is enabled, but no project ID was provided. Disabling local export.\"\n );\n this.isLocalEventExportEnabled = false;\n return;\n } else if (!this.isLocalEventExportEnabled && _projectId) {\n this._events.emit(\n \"error\",\n \"Local event export is disabled, but a project ID was provided. Disabling local export.\"\n );\n this.isLocalEventExportEnabled = false;\n return;\n } else {\n this.projectId = _projectId;\n }\n }\n\n getSdkIntegration(): string {\n return this.sdkIntegration;\n }\n\n protected getCommonEventProperties(): any {\n return {\n $lib: this.getLibraryId(),\n $lib_version: this.getLibraryVersion(),\n };\n }\n\n on(event: string, cb: (...args: any[]) => void): () => void {\n return this._events.on(event, cb);\n }\n\n debug(enabled: boolean = true): void {\n this.removeDebugCallback?.();\n\n this.debugMode = enabled;\n\n if (enabled) {\n this.removeDebugCallback = this.on(\"*\", (event, payload) => {\n // we already have a logger attached to error events\n if (event === \"error\") {\n return;\n }\n\n console.log(\"[Langfuse Debug]\", event, JSON.stringify(payload));\n });\n }\n }\n\n /***\n *** Handlers for each object type\n ***/\n protected traceStateless(body: CreateLangfuseTraceBody): string {\n const { id: bodyId, timestamp: bodyTimestamp, release: bodyRelease, ...rest } = body;\n\n const id = bodyId ?? generateUUID();\n const release = bodyRelease ?? this.release;\n\n const parsedBody: CreateLangfuseTraceBody = {\n id,\n release,\n timestamp: bodyTimestamp ?? new Date(),\n environment: this.environment,\n ...rest,\n };\n this.enqueue(\"trace-create\", parsedBody);\n return id;\n }\n\n protected eventStateless(body: CreateLangfuseEventBody): string {\n const { id: bodyId, startTime: bodyStartTime, ...rest } = body;\n\n const id = bodyId ?? generateUUID();\n\n const parsedBody: CreateLangfuseEventBody = {\n id,\n startTime: bodyStartTime ?? new Date(),\n environment: this.environment,\n ...rest,\n };\n this.enqueue(\"event-create\", parsedBody);\n return id;\n }\n\n protected spanStateless(body: CreateLangfuseSpanBody): string {\n const { id: bodyId, startTime: bodyStartTime, ...rest } = body;\n\n const id = bodyId || generateUUID();\n\n const parsedBody: CreateLangfuseSpanBody = {\n id,\n startTime: bodyStartTime ?? new Date(),\n environment: this.environment,\n ...rest,\n };\n this.enqueue(\"span-create\", parsedBody);\n return id;\n }\n\n protected generationStateless(\n body: Omit & PromptInput\n ): string {\n const { id: bodyId, startTime: bodyStartTime, prompt, ...rest } = body;\n const promptDetails =\n prompt && !prompt.isFallback ? { promptName: prompt.name, promptVersion: prompt.version } : {};\n\n const id = bodyId || generateUUID();\n\n const parsedBody: CreateLangfuseGenerationBody = {\n id,\n startTime: bodyStartTime ?? new Date(),\n environment: this.environment,\n ...promptDetails,\n ...rest,\n };\n\n this.enqueue(\"generation-create\", parsedBody);\n return id;\n }\n\n protected scoreStateless(body: CreateLangfuseScoreBody): string {\n const { id: bodyId, ...rest } = body;\n\n const id = bodyId || generateUUID();\n\n const parsedBody: CreateLangfuseScoreBody = {\n id,\n environment: this.environment,\n ...rest,\n };\n this.enqueue(\"score-create\", parsedBody);\n return id;\n }\n\n protected updateSpanStateless(body: UpdateLangfuseSpanBody): string {\n this.enqueue(\"span-update\", body);\n return body.id;\n }\n\n protected updateGenerationStateless(\n body: Omit & PromptInput\n ): string {\n const { prompt, ...rest } = body;\n const promptDetails =\n prompt && !prompt.isFallback ? { promptName: prompt.name, promptVersion: prompt.version } : {};\n\n const parsedBody: UpdateLangfuseGenerationBody = {\n ...promptDetails,\n ...rest,\n };\n this.enqueue(\"generation-update\", parsedBody);\n return body.id;\n }\n\n protected async _getDataset(name: GetLangfuseDatasetParams[\"datasetName\"]): Promise {\n const encodedName = encodeURIComponent(name);\n return this.fetchAndLogErrors(\n `${this.baseUrl}/api/public/v2/datasets/${encodedName}`,\n this._getFetchOptions({ method: \"GET\" })\n );\n }\n\n protected async _getDatasetItems(query: GetLangfuseDatasetItemsQuery): Promise {\n const params = new URLSearchParams();\n Object.entries(query ?? {}).forEach(([key, value]) => {\n if (value !== undefined && value !== null) {\n params.append(key, value.toString());\n }\n });\n\n return this.fetchAndLogErrors(\n `${this.baseUrl}/api/public/dataset-items?${params}`,\n this._getFetchOptions({ method: \"GET\" })\n );\n }\n\n protected async _fetchMedia(id: string): Promise {\n return this.fetchAndLogErrors(`${this.baseUrl}/api/public/media/${id}`, this._getFetchOptions({ method: \"GET\" }));\n }\n\n async fetchTraces(query?: GetLangfuseTracesQuery): Promise {\n // destructure the response into data and meta to be explicit about the shape of the response and add type-warnings in case the API changes\n const { data, meta } = await this.fetchAndLogErrors(\n `${this.baseUrl}/api/public/traces?${encodeQueryParams(query)}`,\n this._getFetchOptions({ method: \"GET\" })\n );\n return { data, meta };\n }\n\n async fetchTrace(traceId: string): Promise<{ data: GetLangfuseTraceResponse }> {\n const res = await this.fetchAndLogErrors(\n `${this.baseUrl}/api/public/traces/${traceId}`,\n this._getFetchOptions({ method: \"GET\" })\n );\n return { data: res };\n }\n\n async fetchObservations(query?: GetLangfuseObservationsQuery): Promise {\n // destructure the response into data and meta to be explicit about the shape of the response and add type-warnings in case the API changes\n const { data, meta } = await this.fetchAndLogErrors(\n `${this.baseUrl}/api/public/observations?${encodeQueryParams(query)}`,\n this._getFetchOptions({ method: \"GET\" })\n );\n\n return { data, meta };\n }\n\n async fetchObservation(observationId: string): Promise<{ data: GetLangfuseObservationResponse }> {\n const res = await this.fetchAndLogErrors(\n `${this.baseUrl}/api/public/observations/${observationId}`,\n this._getFetchOptions({ method: \"GET\" })\n );\n\n return { data: res };\n }\n\n async fetchSessions(query?: GetLangfuseSessionsQuery): Promise {\n // destructure the response into data and meta to be explicit about the shape of the response and add type-warnings in case the API changes\n const { data, meta } = await this.fetchAndLogErrors(\n `${this.baseUrl}/api/public/sessions?${encodeQueryParams(query)}`,\n this._getFetchOptions({ method: \"GET\" })\n );\n\n return { data, meta };\n }\n\n async getDatasetRun(params: GetLangfuseDatasetRunParams): Promise {\n const encodedDatasetName = encodeURIComponent(params.datasetName);\n const encodedRunName = encodeURIComponent(params.runName);\n return this.fetchAndLogErrors(\n `${this.baseUrl}/api/public/datasets/${encodedDatasetName}/runs/${encodedRunName}`,\n this._getFetchOptions({ method: \"GET\" })\n );\n }\n\n async getDatasetRuns(\n datasetName: string,\n query?: GetLangfuseDatasetRunsQuery\n ): Promise {\n return this.fetchAndLogErrors(\n `${this.baseUrl}/api/public/datasets/${encodeURIComponent(datasetName)}/runs?${encodeQueryParams(query)}`,\n this._getFetchOptions({ method: \"GET\" })\n );\n }\n\n async createDatasetRunItem(body: CreateLangfuseDatasetRunItemBody): Promise {\n return this.fetchAndLogErrors(\n `${this.baseUrl}/api/public/dataset-run-items`,\n this._getFetchOptions({ method: \"POST\", body: JSON.stringify(body) })\n );\n }\n\n /**\n * Creates a dataset. Upserts the dataset if it already exists.\n *\n * @param dataset Can be either a string (name) or an object with name, description and metadata\n * @returns A promise that resolves to the response of the create operation.\n */\n async createDataset(\n dataset:\n | string // name\n | {\n name: string;\n description?: string;\n metadata?: any;\n }\n ): Promise {\n const body: CreateLangfuseDatasetBody = typeof dataset === \"string\" ? { name: dataset } : dataset;\n return this.fetchAndLogErrors(\n `${this.baseUrl}/api/public/datasets`,\n this._getFetchOptions({ method: \"POST\", body: JSON.stringify(body) })\n );\n }\n\n /**\n * Creates a dataset item. Upserts the item if it already exists.\n * @param body The body of the dataset item to be created.\n * @returns A promise that resolves to the response of the create operation.\n */\n async createDatasetItem(body: CreateLangfuseDatasetItemBody): Promise {\n return this.fetchAndLogErrors(\n `${this.baseUrl}/api/public/dataset-items`,\n this._getFetchOptions({ method: \"POST\", body: JSON.stringify(body) })\n );\n }\n\n async getDatasetItem(id: string): Promise {\n return this.fetchAndLogErrors(\n `${this.baseUrl}/api/public/dataset-items/${id}`,\n this._getFetchOptions({ method: \"GET\" })\n );\n }\n\n protected _parsePayload(response: any): any {\n try {\n return JSON.parse(response);\n } catch {\n return response;\n }\n }\n\n async createPromptStateless(body: CreateLangfusePromptBody): Promise {\n return this.fetchAndLogErrors(\n `${this.baseUrl}/api/public/v2/prompts`,\n this._getFetchOptions({ method: \"POST\", body: JSON.stringify(body) })\n );\n }\n\n async updatePromptStateless(\n body: UpdatePromptBody & { name: string; version: number }\n ): Promise {\n return this.fetchAndLogErrors(\n `${this.baseUrl}/api/public/v2/prompts/${encodeURIComponent(body.name)}/versions/${encodeURIComponent(body.version)}`,\n this._getFetchOptions({ method: \"PATCH\", body: JSON.stringify(body) })\n );\n }\n\n async getPromptStateless(\n name: string,\n version?: number,\n label?: string,\n maxRetries?: number,\n requestTimeout?: number // this will override the default requestTimeout for fetching prompts. Together with maxRetries, it can be used to fetch prompts fast when the first fetch is slow.\n ): Promise {\n const encodedName = encodeURIComponent(name);\n const params = new URLSearchParams();\n\n // Add parameters only if they are provided\n if (version && label) {\n throw new Error(\"Provide either version or label, not both.\");\n }\n\n if (version) {\n params.append(\"version\", version.toString());\n }\n\n if (label) {\n params.append(\"label\", label);\n }\n\n const url = `${this.baseUrl}/api/public/v2/prompts/${encodedName}${params.size ? \"?\" + params : \"\"}`;\n\n const boundedMaxRetries = this._getBoundedMaxRetries({ maxRetries, defaultMaxRetries: 2, maxRetriesUpperBound: 4 });\n const retryOptions = { ...this._retryOptions, retryCount: boundedMaxRetries, retryDelay: 500 };\n const retryLogger = (string: string): void =>\n this._events.emit(\"retry\", string + \", \" + url + \", \" + JSON.stringify(retryOptions));\n\n return retriable(\n async () => {\n const res = await this.fetch(\n url,\n this._getFetchOptions({ method: \"GET\", fetchTimeout: requestTimeout ?? this.requestTimeout })\n ).catch((e) => {\n if (e.name === \"AbortError\") {\n throw new LangfuseFetchNetworkError(\"Fetch request timed out\");\n }\n throw new LangfuseFetchNetworkError(e);\n });\n\n if (res.status >= 500) {\n throw new LangfuseFetchHttpError(res, await res.text());\n }\n\n const data = await res.json();\n\n return { fetchResult: res.status === 200 ? \"success\" : \"failure\", data };\n },\n retryOptions,\n retryLogger\n );\n }\n\n private _getBoundedMaxRetries(params: {\n maxRetries?: number;\n defaultMaxRetries?: number;\n maxRetriesUpperBound?: number;\n }): number {\n const defaultMaxRetries = Math.max(params.defaultMaxRetries ?? 2, 0);\n const maxRetriesUpperBound = Math.max(params.maxRetriesUpperBound ?? 4, 0);\n\n if (params.maxRetries === undefined) {\n return defaultMaxRetries;\n }\n\n return Math.min(Math.max(params.maxRetries, 0), maxRetriesUpperBound);\n }\n\n /***\n *** QUEUEING AND FLUSHING\n ***/\n protected enqueue(type: LangfuseObject, body: EventBody): void {\n if (!this.enabled) {\n return;\n }\n\n // Sampling\n const traceId = this.parseTraceId(type, body);\n if (!traceId) {\n this._events.emit(\n \"warning\",\n \"Failed to parse traceID for sampling. Please open a Github issue in https://github.com/langfuse/langfuse/issues/new/choose\"\n );\n } else if (!isInSample(traceId, this.sampleRate)) {\n this._events.emit(\"debug\", `Event with trace ID ${traceId} is out of sample. Skipping.`);\n\n return;\n }\n\n const promise = this.processEnqueueEvent(type, body);\n const promiseId = generateUUID();\n this.pendingEventProcessingPromises[promiseId] = promise;\n\n promise\n .catch((e) => {\n this._events.emit(\"error\", e);\n })\n .finally(() => {\n delete this.pendingEventProcessingPromises[promiseId];\n });\n }\n\n protected async processEnqueueEvent(type: LangfuseObject, body: EventBody): Promise {\n this.maskEventBodyInPlace(body);\n await this.processMediaInEvent(type, body);\n const finalEventBody = this.truncateEventBody(body, MAX_EVENT_SIZE_BYTES);\n\n try {\n JSON.stringify(finalEventBody);\n } catch (e) {\n this._events.emit(\"error\", `Event Body for ${type} is not JSON-serializable: ${e}`);\n return;\n }\n\n const queue = this.getPersistedProperty(LangfusePersistedProperty.Queue) || [];\n\n queue.push({\n id: generateUUID(),\n type,\n timestamp: currentISOTime(),\n body: finalEventBody as any, // TODO: fix typecast. EventBody is not correctly narrowed to the correct type dictated by the 'type' property. This should be part of a larger type cleanup.\n metadata: undefined,\n });\n this.setPersistedProperty(LangfusePersistedProperty.Queue, queue);\n\n this._events.emit(type, finalEventBody);\n\n // Flush queued events if we meet the flushAt length\n if (queue.length >= this.flushAt) {\n this.flush();\n }\n\n if (this.flushInterval && !this._flushTimer) {\n this._flushTimer = safeSetTimeout(() => this.flush(), this.flushInterval);\n }\n }\n\n private maskEventBodyInPlace(body: EventBody): void {\n if (!this.mask) {\n return;\n }\n\n const maskableKeys = [\"input\", \"output\"] as const;\n\n for (const key of maskableKeys) {\n if (key in body) {\n try {\n body[key as keyof EventBody] = this.mask({ data: body[key as keyof EventBody] });\n } catch (e) {\n this._events.emit(\"error\", `Error masking ${key}: ${e}`);\n body[key as keyof EventBody] = \"\";\n }\n }\n }\n }\n\n /**\n * Truncates the event body if its byte size exceeds the specified maximum byte size.\n * Emits a warning event if truncation occurs.\n * The fields that may be truncated are: \"input\", \"output\", and \"metadata\".\n * The fields are truncated in the order of their size, from largest to smallest until the total byte size is within the limit.\n */\n protected truncateEventBody(body: EventBody, maxByteSize: number): EventBody {\n const bodySize = this.getByteSize(body);\n\n if (bodySize <= maxByteSize) {\n return body;\n }\n\n this._events.emit(\"warning\", `Event Body is too large (${bodySize} bytes) and will be truncated`);\n\n // Sort keys by size and truncate the largest keys first\n const keysToCheck = [\"input\", \"output\", \"metadata\"] as const;\n const keySizes = keysToCheck\n .map((key) => ({ key, size: key in body ? this.getByteSize(body[key as keyof typeof body]) : 0 }))\n .sort((a, b) => b.size - a.size);\n\n let result = { ...body };\n let currentSize = bodySize;\n\n for (const { key, size } of keySizes) {\n if (currentSize > maxByteSize && Object.prototype.hasOwnProperty.call(result, key)) {\n result = { ...result, [key]: \"\" };\n\n this._events.emit(\"warning\", `Truncated ${key} due to total size exceeding limit`);\n\n currentSize -= size;\n }\n }\n\n return result;\n }\n\n private getByteSize(obj: any): number {\n const serialized = JSON.stringify(obj);\n\n // Use TextEncoder if available, otherwise fallback to encodeURIComponent\n if (typeof TextEncoder !== \"undefined\") {\n return new TextEncoder().encode(serialized).length;\n } else {\n return encodeURIComponent(serialized).replace(/%[A-F\\d]{2}/g, \"U\").length;\n }\n }\n\n protected async processMediaInEvent(type: LangfuseObject, body: EventBody): Promise {\n if (!body) {\n return;\n }\n\n const traceId = this.parseTraceId(type, body);\n\n if (!traceId) {\n this._events.emit(\"warning\", \"traceId is required for media upload\");\n return;\n }\n\n const observationId = (type.includes(\"generation\") || type.includes(\"span\")) && body.id ? body.id : undefined;\n\n await Promise.all(\n ([\"input\", \"output\", \"metadata\"] as const).map(async (field) => {\n if (body[field as keyof EventBody]) {\n body[field as keyof EventBody] =\n (await this.findAndProcessMedia({\n data: body[field as keyof EventBody],\n traceId,\n observationId,\n field,\n }).catch((e) => {\n this._events.emit(\"error\", `Error processing multimodal event: ${e}`);\n })) ?? body[field as keyof EventBody];\n }\n })\n );\n }\n\n protected parseTraceId(type: LangfuseObject, body: EventBody): string | null | undefined {\n return \"traceId\" in body ? body.traceId : type.includes(\"trace\") ? body.id : undefined;\n }\n\n protected async findAndProcessMedia({\n data,\n traceId,\n observationId,\n field,\n }: {\n data: any;\n traceId: string;\n observationId?: string;\n field: \"input\" | \"output\" | \"metadata\";\n }): Promise {\n const seenObjects = new WeakMap();\n const maxLevels = 10;\n\n const processRecursively = async (data: any, level: number): Promise => {\n if (typeof data === \"string\" && data.startsWith(\"data:\")) {\n const media = new LangfuseMedia({ base64DataUri: data });\n await this.processMediaItem({ media, traceId, observationId, field });\n\n return media;\n }\n if (typeof data !== \"object\" || data === null) {\n return data;\n }\n\n // Use WeakMap to detect cycles\n if (seenObjects.has(data) || level > maxLevels) {\n return data;\n }\n\n seenObjects.set(data, true);\n\n if (data instanceof LangfuseMedia || Object.prototype.toString.call(data) === \"[object LangfuseMedia]\") {\n await this.processMediaItem({ media: data, traceId, observationId, field });\n\n return data;\n }\n\n if (Array.isArray(data)) {\n return await Promise.all(data.map((item) => processRecursively(item, level + 1)));\n }\n\n // Parse OpenAI input audio data which is passed as base64 string NOT in the data uri format\n if (typeof data === \"object\" && data !== null) {\n if (\"input_audio\" in data && typeof data[\"input_audio\"] === \"object\" && \"data\" in data.input_audio) {\n const media = new LangfuseMedia({\n base64DataUri: `data:audio/${data.input_audio[\"format\"] || \"wav\"};base64,${data.input_audio.data}`,\n });\n\n await this.processMediaItem({ media, traceId, observationId, field });\n\n return {\n ...data,\n input_audio: {\n ...data.input_audio,\n data: media,\n },\n };\n }\n\n // OpenAI output audio data is passed as base64 string NOT in the data uri format\n if (\"audio\" in data && typeof data[\"audio\"] === \"object\" && \"data\" in data.audio) {\n const media = new LangfuseMedia({\n base64DataUri: `data:audio/${data.audio[\"format\"] || \"wav\"};base64,${data.audio.data}`,\n });\n\n await this.processMediaItem({ media, traceId, observationId, field });\n\n return {\n ...data,\n audio: {\n ...data.audio,\n data: media,\n },\n };\n }\n\n // Recursively process nested objects\n return Object.fromEntries(\n await Promise.all(\n Object.entries(data).map(async ([key, value]) => [key, await processRecursively(value, level + 1)])\n )\n );\n }\n\n return data;\n };\n\n return await processRecursively(data, 1);\n }\n\n private async processMediaItem({\n media,\n traceId,\n observationId,\n field,\n }: {\n media: LangfuseMedia;\n traceId: string;\n observationId?: string;\n field: string;\n }): Promise {\n try {\n if (!media.contentLength || !media._contentType || !media.contentSha256Hash || !media._contentBytes) {\n return;\n }\n\n const getUploadUrlBody: GetMediaUploadUrlRequest = {\n contentLength: media.contentLength,\n traceId,\n observationId,\n field,\n contentType: media._contentType,\n sha256Hash: media.contentSha256Hash,\n };\n\n const fetchResponse = await this.fetch(\n `${this.baseUrl}/api/public/media`,\n this._getFetchOptions({\n method: \"POST\",\n body: JSON.stringify(getUploadUrlBody),\n })\n );\n\n const uploadUrlResponse = (await fetchResponse.json()) as GetMediaUploadUrlResponse;\n\n const { uploadUrl, mediaId } = uploadUrlResponse;\n media._mediaId = mediaId;\n\n if (uploadUrl) {\n this._events.emit(\"debug\", `Uploading media ${mediaId}`);\n const startTime = Date.now();\n\n const uploadResponse = await this.uploadMediaWithBackoff({\n uploadUrl,\n contentBytes: media._contentBytes,\n contentType: media._contentType,\n contentSha256Hash: media.contentSha256Hash,\n maxRetries: 3,\n baseDelay: 1000,\n });\n\n if (!uploadResponse) {\n throw Error(\"Media upload process failed\");\n }\n\n const patchMediaBody: PatchMediaBody = {\n uploadedAt: new Date().toISOString(),\n uploadHttpStatus: uploadResponse.status,\n uploadHttpError: await uploadResponse.text(),\n uploadTimeMs: Date.now() - startTime,\n };\n\n await this.fetch(\n `${this.baseUrl}/api/public/media/${mediaId}`,\n this._getFetchOptions({ method: \"PATCH\", body: JSON.stringify(patchMediaBody) })\n );\n this._events.emit(\"debug\", `Media upload status reported for ${mediaId}`);\n } else {\n this._events.emit(\"debug\", `Media ${mediaId} already uploaded`);\n }\n } catch (err) {\n this._events.emit(\"error\", `Error processing media item: ${err}`);\n }\n }\n\n private async uploadMediaWithBackoff(params: {\n uploadUrl: string;\n contentType: string;\n contentSha256Hash: string;\n contentBytes: Buffer;\n maxRetries: number;\n baseDelay: number;\n }): Promise {\n const { uploadUrl, contentType, contentSha256Hash, contentBytes, maxRetries, baseDelay } = params;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n const uploadResponse = await this.fetch(uploadUrl, {\n method: \"PUT\",\n body: contentBytes,\n headers: {\n \"Content-Type\": contentType,\n \"x-amz-checksum-sha256\": contentSha256Hash,\n \"x-ms-blob-type\": \"BlockBlob\",\n },\n });\n\n if (attempt < maxRetries && uploadResponse.status !== 200 && uploadResponse.status !== 201) {\n throw new Error(`Upload failed with status ${uploadResponse.status}`);\n }\n\n return uploadResponse;\n } catch (e) {\n if (attempt === maxRetries) {\n throw e;\n }\n\n const delay = baseDelay * Math.pow(2, attempt);\n const jitter = Math.random() * 1000;\n\n await new Promise((resolve) => setTimeout(resolve, delay + jitter));\n }\n }\n }\n\n /**\n * Asynchronously flushes all events that are not yet sent to the server.\n * This function always resolves, even if there were errors when flushing.\n * Errors are emitted as \"error\" events and the promise resolves.\n *\n * @returns {Promise} A promise that resolves when the flushing is completed.\n */\n async flushAsync(): Promise {\n await Promise.all(Object.values(this.pendingEventProcessingPromises)).catch((e) => {\n logIngestionError(e);\n });\n\n return new Promise((resolve, _reject) => {\n try {\n this.flush((err, data) => {\n if (err) {\n logIngestionError(err);\n resolve();\n } else {\n resolve(data);\n }\n });\n // safeguard against unexpected synchronous errors\n } catch (e) {\n console.error(\"[Langfuse SDK] Error while flushing Langfuse\", e);\n }\n });\n }\n\n // Flushes all events that are not yet sent to the server\n flush(callback?: (err?: any, data?: any) => void): void {\n if (this._flushTimer) {\n clearTimeout(this._flushTimer);\n this._flushTimer = null;\n }\n\n const queue = this.getPersistedProperty(LangfusePersistedProperty.Queue) || [];\n\n if (!queue.length) {\n return callback?.();\n }\n\n const items = queue.splice(0, this.flushAt);\n\n const { processedItems, remainingItems } = this.processQueueItems(\n items,\n MAX_EVENT_SIZE_BYTES,\n MAX_BATCH_SIZE_BYTES\n );\n\n this.setPersistedProperty(LangfusePersistedProperty.Queue, [...remainingItems, ...queue]);\n\n const promiseUUID = generateUUID();\n\n const done = (err?: any): void => {\n if (err) {\n this._events.emit(\"warning\", err);\n }\n callback?.(err, items);\n this._events.emit(\"flush\", items);\n };\n\n // If local event export is enabled, we don't send the events to the server, but instead store them in the localEventExportMap\n if (this.isLocalEventExportEnabled && this.projectId) {\n if (!this.localEventExportMap.has(this.projectId)) {\n this.localEventExportMap.set(this.projectId, [...items]);\n } else {\n this.localEventExportMap.get(this.projectId)?.push(...items);\n }\n\n done();\n return;\n }\n\n const payload = JSON.stringify({\n batch: processedItems,\n metadata: {\n batch_size: processedItems.length,\n sdk_integration: this.sdkIntegration,\n sdk_version: this.getLibraryVersion(),\n sdk_variant: this.getLibraryId(),\n public_key: this.publicKey,\n sdk_name: \"langfuse-js\",\n },\n }); // implicit conversion also of dates to strings\n\n const url = `${this.baseUrl}/api/public/ingestion`;\n\n const fetchOptions = this._getFetchOptions({\n method: \"POST\",\n body: payload,\n });\n\n const requestPromise = this.fetchWithRetry(url, fetchOptions)\n .then(() => done())\n .catch((err) => {\n done(err);\n });\n this.pendingIngestionPromises[promiseUUID] = requestPromise;\n requestPromise.finally(() => {\n delete this.pendingIngestionPromises[promiseUUID];\n });\n }\n\n public processQueueItems(\n queue: LangfuseQueueItem[],\n MAX_MSG_SIZE: number,\n BATCH_SIZE_LIMIT: number\n ): { processedItems: LangfuseQueueItem[]; remainingItems: LangfuseQueueItem[] } {\n let totalSize = 0;\n const processedItems: LangfuseQueueItem[] = [];\n const remainingItems: LangfuseQueueItem[] = [];\n\n for (let i = 0; i < queue.length; i++) {\n try {\n const itemSize = new Blob([JSON.stringify(queue[i])]).size;\n\n // discard item if it exceeds the maximum size per event\n if (itemSize > MAX_MSG_SIZE) {\n console.warn(`Item exceeds size limit (size: ${itemSize}), dropping item.`);\n continue;\n }\n\n // if adding the next item would exceed the batch size limit, stop processing\n if (totalSize + itemSize >= BATCH_SIZE_LIMIT) {\n console.debug(`hit batch size limit (size: ${totalSize + itemSize})`);\n remainingItems.push(...queue.slice(i));\n console.log(`Remaining items: ${remainingItems.length}`);\n console.log(`processes items: ${processedItems.length}`);\n break;\n }\n\n // only add the item if it passes both requirements\n totalSize += itemSize;\n processedItems.push(queue[i]);\n } catch (error) {\n this._events.emit(\"error\", error);\n remainingItems.push(...queue.slice(i));\n break;\n }\n }\n\n return { processedItems, remainingItems };\n }\n\n _getFetchOptions(p: {\n method: LangfuseFetchOptions[\"method\"];\n body?: LangfuseFetchOptions[\"body\"];\n fetchTimeout?: number;\n }): LangfuseFetchOptions {\n const fetchOptions: LangfuseFetchOptions = {\n method: p.method,\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-Langfuse-Sdk-Name\": \"langfuse-js\",\n \"X-Langfuse-Sdk-Version\": this.getLibraryVersion(),\n \"X-Langfuse-Sdk-Variant\": this.getLibraryId(),\n \"X-Langfuse-Sdk-Integration\": this.sdkIntegration,\n \"X-Langfuse-Public-Key\": this.publicKey,\n ...this.additionalHeaders,\n ...this.constructAuthorizationHeader(this.publicKey, this.secretKey),\n },\n body: p.body,\n ...(p.fetchTimeout !== undefined ? { signal: AbortSignal.timeout(p.fetchTimeout) } : {}),\n };\n\n return fetchOptions;\n }\n\n protected constructAuthorizationHeader(\n publicKey: string,\n secretKey?: string\n ): {\n Authorization: string;\n } {\n if (secretKey === undefined) {\n return { Authorization: \"Bearer \" + publicKey };\n } else {\n const encodedCredentials =\n typeof btoa === \"function\"\n ? // btoa() is available, the code is running in a browser or edge environment\n btoa(publicKey + \":\" + secretKey)\n : // btoa() is not available, the code is running in Node.js\n Buffer.from(publicKey + \":\" + secretKey).toString(\"base64\");\n\n return { Authorization: \"Basic \" + encodedCredentials };\n }\n }\n\n private async fetchWithRetry(\n url: string,\n options: LangfuseFetchOptions,\n retryOptions?: RetriableOptions\n ): Promise {\n (AbortSignal as any).timeout ??= function timeout(ms: number) {\n const ctrl = new AbortController();\n setTimeout(() => ctrl.abort(), ms);\n return ctrl.signal;\n };\n\n return await retriable(\n async () => {\n let res: LangfuseFetchResponse | null = null;\n try {\n res = await this.fetch(url, {\n signal: AbortSignal.timeout(this.requestTimeout),\n ...options,\n });\n } catch (e) {\n // fetch will only throw on network errors or on timeouts\n throw new LangfuseFetchNetworkError(e);\n }\n\n if (res.status < 200 || res.status >= 400) {\n const body = await res.json();\n throw new LangfuseFetchHttpError(res, JSON.stringify(body));\n }\n const returnBody = await res.json();\n if (res.status === 207 && returnBody.errors.length > 0) {\n throw new LangfuseFetchHttpError(res, JSON.stringify(returnBody.errors));\n }\n\n return res;\n },\n { ...this._retryOptions, ...retryOptions },\n (string) => this._events.emit(\"retry\", string + \", \" + url + \", \" + JSON.stringify(options))\n );\n }\n\n private async fetchAndLogErrors(url: string, options: LangfuseFetchOptions): Promise {\n const res = await this.fetch(url, options);\n\n // 429 responses do not have a JSON body, so attempting to execute `json()`\n // will throw and error before the 429 is logged.\n const data = res.status === 429 ? await res.text() : await res.json();\n if (res.status < 200 || res.status >= 400) {\n logIngestionError(new LangfuseFetchHttpError(res, JSON.stringify(data)));\n }\n\n return data;\n }\n\n async shutdownAsync(): Promise {\n clearTimeout(this._flushTimer);\n try {\n await this.flushAsync();\n await Promise.all(\n Object.values(this.pendingIngestionPromises).map((x) =>\n x.catch(() => {\n // ignore errors as we are shutting down and can't deal with them anyways.\n })\n )\n );\n // flush again in case there are new events that were added while we were waiting for the pending promises to resolve\n await this.flushAsync();\n } catch (e) {\n console.error(\"[Langfuse SDK] Error while shutting down Langfuse\", e);\n }\n }\n\n async _exportLocalEvents(projectId: string): Promise {\n if (this.isLocalEventExportEnabled) {\n clearTimeout(this._flushTimer);\n await this.flushAsync();\n\n const events = this.localEventExportMap.get(projectId) ?? [];\n this.localEventExportMap.delete(projectId);\n\n return events;\n } else {\n this._events.emit(\"error\", \"Local event exports are disabled, but _exportLocalEvents() was called.\");\n return [];\n }\n }\n\n shutdown(): void {\n console.warn(\n \"shutdown() is deprecated. It does not wait for all events to be processed. Please use shutdownAsync() instead.\"\n );\n void this.shutdownAsync();\n }\n\n protected async awaitAllQueuedAndPendingRequests(): Promise {\n clearTimeout(this._flushTimer);\n await this.flushAsync();\n await Promise.all(Object.values(this.pendingIngestionPromises));\n }\n}\n\nexport abstract class LangfuseWebStateless extends LangfuseCoreStateless {\n constructor(params: Omit) {\n const { flushAt, flushInterval, publicKey, enabled, ...rest } = params;\n let isObservabilityEnabled = enabled === false ? false : true;\n\n if (isObservabilityEnabled && !publicKey) {\n isObservabilityEnabled = false;\n console.warn(\n \"Langfuse public key not passed to constructor and not set as 'LANGFUSE_PUBLIC_KEY' environment variable. No observability data will be sent to Langfuse.\"\n );\n }\n\n super({\n ...rest,\n publicKey,\n flushAt: flushAt ?? 1,\n flushInterval: flushInterval ?? 0,\n enabled: isObservabilityEnabled,\n });\n }\n\n async score(body: CreateLangfuseScoreBody): Promise {\n this.scoreStateless(body);\n await this.awaitAllQueuedAndPendingRequests();\n return this;\n }\n}\n\nexport abstract class LangfuseCore extends LangfuseCoreStateless {\n private _promptCache: LangfusePromptCache;\n\n constructor(params: LangfuseCoreOptions) {\n const { publicKey, secretKey, enabled, _isLocalEventExportEnabled } = params;\n let isObservabilityEnabled = enabled === false ? false : true;\n\n if (_isLocalEventExportEnabled) {\n isObservabilityEnabled = true;\n } else if (!secretKey) {\n isObservabilityEnabled = false;\n\n if (enabled !== false) {\n console.warn(\n \"Langfuse secret key was not passed to constructor or not set as 'LANGFUSE_SECRET_KEY' environment variable. No observability data will be sent to Langfuse.\"\n );\n }\n } else if (!publicKey) {\n isObservabilityEnabled = false;\n\n if (enabled !== false) {\n console.warn(\n \"Langfuse public key was not passed to constructor or not set as 'LANGFUSE_PUBLIC_KEY' environment variable. No observability data will be sent to Langfuse.\"\n );\n }\n }\n\n super({ ...params, enabled: isObservabilityEnabled });\n this._promptCache = new LangfusePromptCache();\n }\n\n trace(body?: CreateLangfuseTraceBody): LangfuseTraceClient {\n const id = this.traceStateless(body ?? {});\n const t = new LangfuseTraceClient(this, id);\n if (getEnv(\"DEFER\") && body) {\n try {\n const deferRuntime = getEnv(\"__deferRuntime\");\n if (deferRuntime) {\n deferRuntime.langfuseTraces([\n {\n id: id,\n name: body.name || \"\",\n url: t.getTraceUrl(),\n },\n ]);\n }\n } catch {}\n }\n return t;\n }\n\n span(body: CreateLangfuseSpanBody): LangfuseSpanClient {\n const traceId = body.traceId || this.traceStateless({ name: body.name });\n const id = this.spanStateless({ ...body, traceId });\n return new LangfuseSpanClient(this, id, traceId);\n }\n\n generation(\n body: Omit & PromptInput\n ): LangfuseGenerationClient {\n const traceId = body.traceId || this.traceStateless({ name: body.name });\n const id = this.generationStateless({ ...body, traceId });\n return new LangfuseGenerationClient(this, id, traceId);\n }\n\n event(body: CreateLangfuseEventBody): LangfuseEventClient {\n const traceId = body.traceId || this.traceStateless({ name: body.name });\n const id = this.eventStateless({ ...body, traceId });\n return new LangfuseEventClient(this, id, traceId);\n }\n\n score(body: CreateLangfuseScoreBody): this {\n this.scoreStateless(body);\n return this;\n }\n\n async getDataset(\n name: string,\n options?: {\n fetchItemsPageSize: number;\n }\n ): Promise<{\n id: string;\n name: string;\n description?: string;\n metadata?: any;\n projectId: string;\n items: DatasetItem[];\n }> {\n const dataset = await this._getDataset(name);\n const items: GetLangfuseDatasetItemsResponse[\"data\"] = [];\n\n let page = 1;\n while (true) {\n const itemsResponse = await this._getDatasetItems({\n datasetName: name,\n limit: options?.fetchItemsPageSize ?? 50,\n page,\n });\n items.push(...itemsResponse.data);\n if (itemsResponse.meta.totalPages <= page) {\n break;\n }\n page++;\n }\n\n const returnDataset = {\n ...dataset,\n description: dataset.description ?? undefined,\n metadata: dataset.metadata ?? undefined,\n items: items.map((item) => ({\n ...item,\n link: async (\n obj: LangfuseObjectClient,\n runName: string,\n runArgs?: {\n description?: string;\n metadata?: any;\n }\n ) => {\n await this.awaitAllQueuedAndPendingRequests();\n const data = await this.createDatasetRunItem({\n runName,\n datasetItemId: item.id,\n observationId: obj.observationId,\n traceId: obj.traceId,\n runDescription: runArgs?.description,\n metadata: runArgs?.metadata,\n });\n return data;\n },\n })),\n };\n\n return returnDataset;\n }\n\n async createPrompt(body: CreateChatPromptBodyWithPlaceholders): Promise;\n async createPrompt(body: CreateTextPromptBody): Promise;\n async createPrompt(body: CreateChatPromptBody): Promise;\n async createPrompt(\n body: CreateTextPromptBody | CreateChatPromptBody | CreateChatPromptBodyWithPlaceholders\n ): Promise {\n const labels = body.labels ?? [];\n\n const promptResponse =\n body.type === \"chat\" // necessary to get types right here\n ? await this.createPromptStateless({\n ...body,\n prompt: body.prompt.map((item) => {\n if (\"type\" in item && item.type === ChatMessageType.Placeholder) {\n return { type: ChatMessageType.Placeholder, name: (item as PlaceholderMessage).name };\n } else {\n // Handle regular ChatMessage (without type field) from API\n return { type: ChatMessageType.ChatMessage, ...item };\n }\n }),\n labels: body.isActive ? [...new Set([...labels, \"production\"])] : labels, // backward compatibility for isActive\n })\n : await this.createPromptStateless({\n ...body,\n type: body.type ?? \"text\",\n labels: body.isActive ? [...new Set([...labels, \"production\"])] : labels, // backward compatibility for isActive\n });\n\n if (promptResponse.type === \"chat\") {\n return new ChatPromptClient(promptResponse);\n }\n\n return new TextPromptClient(promptResponse);\n }\n\n async updatePrompt(body: { name: string; version: number; newLabels: string[] }): Promise {\n const newPrompt = await this.updatePromptStateless(body);\n this._promptCache.invalidate(body.name);\n return newPrompt;\n }\n\n async getPrompt(\n name: string,\n version?: number,\n options?: {\n label?: string;\n cacheTtlSeconds?: number;\n fallback?: string;\n maxRetries?: number;\n type?: \"text\";\n fetchTimeoutMs?: number;\n }\n ): Promise;\n async getPrompt(\n name: string,\n version?: number,\n options?: {\n label?: string;\n cacheTtlSeconds?: number;\n fallback?: ChatMessage[];\n maxRetries?: number;\n type: \"chat\";\n fetchTimeoutMs?: number;\n }\n ): Promise;\n async getPrompt(\n name: string,\n version?: number,\n options?: {\n label?: string;\n cacheTtlSeconds?: number;\n fallback?: ChatMessage[] | string;\n maxRetries?: number;\n type?: \"chat\" | \"text\";\n fetchTimeoutMs?: number;\n }\n ): Promise {\n const cacheKey = this._getPromptCacheKey({ name, version, label: options?.label });\n const cachedPrompt = this._promptCache.getIncludingExpired(cacheKey);\n if (!cachedPrompt || options?.cacheTtlSeconds === 0) {\n try {\n return await this._fetchPromptAndUpdateCache({\n name,\n version,\n label: options?.label,\n cacheTtlSeconds: options?.cacheTtlSeconds,\n maxRetries: options?.maxRetries,\n fetchTimeout: options?.fetchTimeoutMs,\n });\n } catch (err) {\n if (options?.fallback) {\n const sharedFallbackParams = {\n name,\n version: version ?? 0,\n labels: options.label ? [options.label] : [],\n cacheTtlSeconds: options?.cacheTtlSeconds,\n config: {},\n tags: [],\n };\n\n if (options.type === \"chat\") {\n return new ChatPromptClient(\n {\n ...sharedFallbackParams,\n type: \"chat\",\n prompt: (options.fallback as ChatMessage[]).map((msg) => ({\n type: ChatMessageType.ChatMessage,\n ...msg,\n })),\n },\n true\n );\n } else {\n return new TextPromptClient(\n {\n ...sharedFallbackParams,\n type: \"text\",\n prompt: options.fallback as string,\n },\n true\n );\n }\n }\n\n throw err;\n }\n }\n\n if (cachedPrompt.isExpired) {\n // If the cache is not currently being refreshed, start refreshing it and register the promise in the cache\n if (!this._promptCache.isRefreshing(cacheKey)) {\n const refreshPromptPromise = this._fetchPromptAndUpdateCache({\n name,\n version,\n label: options?.label,\n cacheTtlSeconds: options?.cacheTtlSeconds,\n maxRetries: options?.maxRetries,\n fetchTimeout: options?.fetchTimeoutMs,\n }).catch(() => {\n console.warn(\n `Failed to refresh prompt cache '${cacheKey}', stale cache will be used until next refresh succeeds.`\n );\n });\n this._promptCache.addRefreshingPromise(cacheKey, refreshPromptPromise);\n }\n\n return cachedPrompt.value;\n }\n\n return cachedPrompt.value;\n }\n\n private _getPromptCacheKey(params: { name: string; version?: number; label?: string }): string {\n const { name, version, label } = params;\n const parts = [name];\n\n if (version !== undefined) {\n parts.push(\"version:\" + version.toString());\n } else if (label !== undefined) {\n parts.push(\"label:\" + label);\n } else {\n parts.push(\"label:production\");\n }\n\n return parts.join(\"-\");\n }\n\n private async _fetchPromptAndUpdateCache(params: {\n name: string;\n version?: number;\n cacheTtlSeconds?: number;\n label?: string;\n maxRetries?: number;\n fetchTimeout?: number;\n }): Promise {\n const cacheKey = this._getPromptCacheKey(params);\n\n try {\n const { name, version, cacheTtlSeconds, label, maxRetries, fetchTimeout } = params;\n\n const { data, fetchResult } = await this.getPromptStateless(name, version, label, maxRetries, fetchTimeout);\n if (fetchResult === \"failure\") {\n throw Error(data.message ?? \"Internal error while fetching prompt\");\n }\n\n let prompt: LangfusePromptClient;\n if (data.type === \"chat\") {\n prompt = new ChatPromptClient(data);\n } else {\n prompt = new TextPromptClient(data);\n }\n\n this._promptCache.set(cacheKey, prompt, cacheTtlSeconds);\n\n return prompt;\n } catch (error) {\n console.error(`[Langfuse SDK] Error while fetching prompt '${cacheKey}':`, error);\n\n throw error;\n }\n }\n\n public async fetchMedia(id: string): Promise {\n return await this._fetchMedia(id);\n }\n\n /**\n * Replaces the media reference strings in an object with base64 data URIs for the media content.\n *\n * This method recursively traverses an object (up to a maximum depth of 10) looking for media reference strings\n * in the format \"@@@langfuseMedia:...@@@\". When found, it fetches the actual media content using the provided\n * Langfuse client and replaces the reference string with a base64 data URI.\n *\n * If fetching media content fails for a reference string, a warning is logged and the reference string is left unchanged.\n *\n * @param params - Configuration object\n * @param params.obj - The object to process. Can be a primitive value, array, or nested object\n * @param params.langfuseClient - Langfuse client instance used to fetch media content\n * @param params.resolveWith - The representation of the media content to replace the media reference string with. Currently only \"base64DataUri\" is supported.\n * @param params.maxDepth - Optional. Default is 10. The maximum depth to traverse the object.\n *\n * @returns A deep copy of the input object with all media references replaced with base64 data URIs where possible\n *\n * @example\n * ```typescript\n * const obj = {\n * image: \"@@@langfuseMedia:type=image/jpeg|id=123|source=bytes@@@\",\n * nested: {\n * pdf: \"@@@langfuseMedia:type=application/pdf|id=456|source=bytes@@@\"\n * }\n * };\n *\n * const result = await LangfuseMedia.resolveMediaReferences({\n * obj,\n * langfuseClient\n * });\n *\n * // Result:\n * // {\n * // image: \"data:image/jpeg;base64,/9j/4AAQSkZJRg...\",\n * // nested: {\n * // pdf: \"data:application/pdf;base64,JVBERi0xLjcK...\"\n * // }\n * // }\n * ```\n */\n public async resolveMediaReferences(\n params: Omit, \"langfuseClient\">\n ): Promise {\n const { obj, ...rest } = params;\n\n return LangfuseMedia.resolveMediaReferences({ ...rest, langfuseClient: this, obj });\n }\n _updateSpan(body: UpdateLangfuseSpanBody): this {\n this.updateSpanStateless(body);\n return this;\n }\n\n _updateGeneration(body: UpdateLangfuseGenerationBody): this {\n this.updateGenerationStateless(body);\n return this;\n }\n}\n\nexport abstract class LangfuseObjectClient {\n public readonly client: LangfuseCore;\n public readonly id: string; // id of item itself\n public readonly traceId: string; // id of trace, if traceClient this is the same as id\n public readonly observationId: string | null; // id of observation, if observationClient this is the same as id, if traceClient this is null\n\n constructor({\n client,\n id,\n traceId,\n observationId,\n }: {\n client: LangfuseCore;\n id: string;\n traceId: string;\n observationId: string | null;\n }) {\n this.client = client;\n this.id = id;\n this.traceId = traceId;\n this.observationId = observationId;\n }\n\n event(body: Omit): LangfuseEventClient {\n return this.client.event({\n ...body,\n traceId: this.traceId,\n parentObservationId: this.observationId,\n });\n }\n\n span(body: Omit): LangfuseSpanClient {\n return this.client.span({\n ...body,\n traceId: this.traceId,\n parentObservationId: this.observationId,\n });\n }\n\n generation(\n body: Omit &\n PromptInput\n ): LangfuseGenerationClient {\n return this.client.generation({\n ...body,\n traceId: this.traceId,\n parentObservationId: this.observationId,\n });\n }\n\n score(body: Omit): this {\n this.client.score({\n ...body,\n traceId: this.traceId,\n observationId: this.observationId,\n });\n return this;\n }\n\n getTraceUrl(): string {\n return `${this.client.baseUrl}/trace/${this.traceId}`;\n }\n}\n\nexport class LangfuseTraceClient extends LangfuseObjectClient {\n constructor(client: LangfuseCore, traceId: string) {\n super({ client, id: traceId, traceId, observationId: null });\n }\n\n update(body: Omit): this {\n this.client.trace({\n ...body,\n id: this.id,\n });\n return this;\n }\n}\n\nabstract class LangfuseObservationClient extends LangfuseObjectClient {\n constructor(client: LangfuseCore, id: string, traceId: string) {\n super({ client, id, traceId, observationId: id });\n }\n}\n\nexport class LangfuseSpanClient extends LangfuseObservationClient {\n constructor(client: LangfuseCore, id: string, traceId: string) {\n super(client, id, traceId);\n }\n\n update(body: Omit): this {\n this.client._updateSpan({\n ...body,\n id: this.id,\n traceId: this.traceId,\n });\n return this;\n }\n\n end(body?: Omit): this {\n this.client._updateSpan({\n ...body,\n id: this.id,\n traceId: this.traceId,\n endTime: new Date(),\n });\n return this;\n }\n}\n\nexport class LangfuseGenerationClient extends LangfuseObservationClient {\n constructor(client: LangfuseCore, id: string, traceId: string) {\n super(client, id, traceId);\n }\n\n update(\n body: Omit & PromptInput\n ): this {\n this.client._updateGeneration({\n ...body,\n id: this.id,\n traceId: this.traceId,\n });\n return this;\n }\n\n end(\n body?: Omit &\n PromptInput\n ): this {\n this.client._updateGeneration({\n ...body,\n id: this.id,\n traceId: this.traceId,\n endTime: new Date(),\n });\n return this;\n }\n}\n\nexport class LangfuseEventClient extends LangfuseObservationClient {\n constructor(client: LangfuseCore, id: string, traceId: string) {\n super(client, id, traceId);\n }\n}\n\nexport * from \"./types\";\nexport * from \"./openapi/server\";\n", "import { type LangfuseOptions } from \"./types\";\n\nexport type LangfuseStorage = {\n getItem: (key: string) => string | null | undefined;\n setItem: (key: string, value: string) => void;\n removeItem: (key: string) => void;\n clear: () => void;\n getAllKeys: () => readonly string[];\n};\n\n// Methods partially borrowed from quirksmode.org/js/cookies.html\nexport const cookieStore: LangfuseStorage = {\n getItem(key) {\n try {\n const nameEQ = key + \"=\";\n const ca = document.cookie.split(\";\");\n for (let i = 0; i < ca.length; i++) {\n let c = ca[i];\n while (c.charAt(0) == \" \") {\n c = c.substring(1, c.length);\n }\n if (c.indexOf(nameEQ) === 0) {\n return decodeURIComponent(c.substring(nameEQ.length, c.length));\n }\n }\n } catch (err) {}\n return null;\n },\n\n setItem(key: string, value: string) {\n try {\n const cdomain = \"\",\n expires = \"\",\n secure = \"\";\n\n const new_cookie_val = key + \"=\" + encodeURIComponent(value) + expires + \"; path=/\" + cdomain + secure;\n document.cookie = new_cookie_val;\n } catch (err) {\n return;\n }\n },\n\n removeItem(name) {\n try {\n cookieStore.setItem(name, \"\");\n } catch (err) {\n return;\n }\n },\n clear() {\n document.cookie = \"\";\n },\n getAllKeys() {\n const ca = document.cookie.split(\";\");\n const keys = [];\n\n for (let i = 0; i < ca.length; i++) {\n let c = ca[i];\n while (c.charAt(0) == \" \") {\n c = c.substring(1, c.length);\n }\n keys.push(c.split(\"=\")[0]);\n }\n\n return keys;\n },\n};\n\nconst createStorageLike = (store: any): LangfuseStorage => {\n return {\n getItem(key) {\n return store.getItem(key);\n },\n\n setItem(key, value) {\n store.setItem(key, value);\n },\n\n removeItem(key) {\n store.removeItem(key);\n },\n clear() {\n store.clear();\n },\n getAllKeys() {\n const keys = [];\n for (const key in localStorage) {\n keys.push(key);\n }\n return keys;\n },\n };\n};\n\nconst checkStoreIsSupported = (storage: LangfuseStorage, key = \"__mplssupport__\"): boolean => {\n if (!window) {\n return false;\n }\n try {\n const val = \"xyz\";\n storage.setItem(key, val);\n if (storage.getItem(key) !== val) {\n return false;\n }\n storage.removeItem(key);\n return true;\n } catch (err) {\n return false;\n }\n};\n\nlet localStore: LangfuseStorage | undefined = undefined;\nlet sessionStore: LangfuseStorage | undefined = undefined;\n\nconst createMemoryStorage = (): LangfuseStorage => {\n const _cache: { [key: string]: any | undefined } = {};\n\n const store: LangfuseStorage = {\n getItem(key) {\n return _cache[key];\n },\n\n setItem(key, value) {\n _cache[key] = value !== null ? value : undefined;\n },\n\n removeItem(key) {\n delete _cache[key];\n },\n clear() {\n for (const key in _cache) {\n delete _cache[key];\n }\n },\n getAllKeys() {\n const keys = [];\n for (const key in _cache) {\n keys.push(key);\n }\n return keys;\n },\n };\n return store;\n};\n\nexport const getStorage = (type: LangfuseOptions[\"persistence\"], window: Window | undefined): LangfuseStorage => {\n if (typeof window !== undefined && window) {\n if (!localStorage) {\n const _localStore = createStorageLike(window.localStorage);\n localStore = checkStoreIsSupported(_localStore) ? _localStore : undefined;\n }\n\n if (!sessionStore) {\n const _sessionStore = createStorageLike(window.sessionStorage);\n sessionStore = checkStoreIsSupported(_sessionStore) ? _sessionStore : undefined;\n }\n }\n\n switch (type) {\n case \"cookie\":\n return cookieStore || localStore || sessionStore || createMemoryStorage();\n case \"localStorage\":\n return localStore || sessionStore || createMemoryStorage();\n case \"sessionStorage\":\n return sessionStore || createMemoryStorage();\n case \"memory\":\n return createMemoryStorage();\n default:\n return createMemoryStorage();\n }\n};\n", "/* eslint-disable */\n/* tslint:disable */\n/*\n * ---------------------------------------------------------------\n * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ##\n * ## ##\n * ## AUTHOR: acacode ##\n * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##\n * ---------------------------------------------------------------\n */\n\n/** AnnotationQueueStatus */\nexport type ApiAnnotationQueueStatus = \"PENDING\" | \"COMPLETED\";\n\n/** AnnotationQueueObjectType */\nexport type ApiAnnotationQueueObjectType = \"TRACE\" | \"OBSERVATION\";\n\n/** AnnotationQueue */\nexport interface ApiAnnotationQueue {\n id: string;\n name: string;\n description?: string | null;\n scoreConfigIds: string[];\n /** @format date-time */\n createdAt: string;\n /** @format date-time */\n updatedAt: string;\n}\n\n/** AnnotationQueueItem */\nexport interface ApiAnnotationQueueItem {\n id: string;\n queueId: string;\n objectId: string;\n objectType: ApiAnnotationQueueObjectType;\n status: ApiAnnotationQueueStatus;\n /** @format date-time */\n completedAt?: string | null;\n /** @format date-time */\n createdAt: string;\n /** @format date-time */\n updatedAt: string;\n}\n\n/** PaginatedAnnotationQueues */\nexport interface ApiPaginatedAnnotationQueues {\n data: ApiAnnotationQueue[];\n meta: ApiUtilsMetaResponse;\n}\n\n/** PaginatedAnnotationQueueItems */\nexport interface ApiPaginatedAnnotationQueueItems {\n data: ApiAnnotationQueueItem[];\n meta: ApiUtilsMetaResponse;\n}\n\n/** CreateAnnotationQueueItemRequest */\nexport interface ApiCreateAnnotationQueueItemRequest {\n objectId: string;\n objectType: ApiAnnotationQueueObjectType;\n /** Defaults to PENDING for new queue items */\n status?: ApiAnnotationQueueStatus | null;\n}\n\n/** UpdateAnnotationQueueItemRequest */\nexport interface ApiUpdateAnnotationQueueItemRequest {\n status?: ApiAnnotationQueueStatus | null;\n}\n\n/** DeleteAnnotationQueueItemResponse */\nexport interface ApiDeleteAnnotationQueueItemResponse {\n success: boolean;\n message: string;\n}\n\n/** CreateCommentRequest */\nexport interface ApiCreateCommentRequest {\n /** The id of the project to attach the comment to. */\n projectId: string;\n /** The type of the object to attach the comment to (trace, observation, session, prompt). */\n objectType: string;\n /** The id of the object to attach the comment to. If this does not reference a valid existing object, an error will be thrown. */\n objectId: string;\n /** The content of the comment. May include markdown. Currently limited to 3000 characters. */\n content: string;\n /** The id of the user who created the comment. */\n authorUserId?: string | null;\n}\n\n/** CreateCommentResponse */\nexport interface ApiCreateCommentResponse {\n /** The id of the created object in Langfuse */\n id: string;\n}\n\n/** GetCommentsResponse */\nexport interface ApiGetCommentsResponse {\n data: ApiComment[];\n meta: ApiUtilsMetaResponse;\n}\n\n/** Trace */\nexport interface ApiTrace {\n /** The unique identifier of a trace */\n id: string;\n /**\n * The timestamp when the trace was created\n * @format date-time\n */\n timestamp: string;\n /** The name of the trace */\n name?: string | null;\n /** The input data of the trace. Can be any JSON. */\n input?: any;\n /** The output data of the trace. Can be any JSON. */\n output?: any;\n /** The session identifier associated with the trace */\n sessionId?: string | null;\n /** The release version of the application when the trace was created */\n release?: string | null;\n /** The version of the trace */\n version?: string | null;\n /** The user identifier associated with the trace */\n userId?: string | null;\n /** The metadata associated with the trace. Can be any JSON. */\n metadata?: any;\n /** The tags associated with the trace. Can be an array of strings or null. */\n tags?: string[] | null;\n /** Public traces are accessible via url without login */\n public?: boolean | null;\n /** The environment from which this trace originated. Can be any lowercase alphanumeric string with hyphens and underscores that does not start with 'langfuse'. */\n environment?: string | null;\n}\n\n/** TraceWithDetails */\nexport type ApiTraceWithDetails = ApiTrace & {\n /** Path of trace in Langfuse UI */\n htmlPath: string;\n /**\n * Latency of trace in seconds\n * @format double\n */\n latency: number;\n /**\n * Cost of trace in USD\n * @format double\n */\n totalCost: number;\n /** List of observation ids */\n observations: string[];\n /** List of score ids */\n scores: string[];\n};\n\n/** TraceWithFullDetails */\nexport type ApiTraceWithFullDetails = ApiTrace & {\n /** Path of trace in Langfuse UI */\n htmlPath: string;\n /**\n * Latency of trace in seconds\n * @format double\n */\n latency: number;\n /**\n * Cost of trace in USD\n * @format double\n */\n totalCost: number;\n /** List of observations */\n observations: ApiObservationsView[];\n /** List of scores */\n scores: ApiScoreV1[];\n};\n\n/** Session */\nexport interface ApiSession {\n id: string;\n /** @format date-time */\n createdAt: string;\n projectId: string;\n /** The environment from which this session originated. */\n environment?: string | null;\n}\n\n/** SessionWithTraces */\nexport type ApiSessionWithTraces = ApiSession & {\n traces: ApiTrace[];\n};\n\n/** Observation */\nexport interface ApiObservation {\n /** The unique identifier of the observation */\n id: string;\n /** The trace ID associated with the observation */\n traceId?: string | null;\n /** The type of the observation */\n type: string;\n /** The name of the observation */\n name?: string | null;\n /**\n * The start time of the observation\n * @format date-time\n */\n startTime: string;\n /**\n * The end time of the observation.\n * @format date-time\n */\n endTime?: string | null;\n /**\n * The completion start time of the observation\n * @format date-time\n */\n completionStartTime?: string | null;\n /** The model used for the observation */\n model?: string | null;\n /** The parameters of the model used for the observation */\n modelParameters?: Record;\n /** The input data of the observation */\n input?: any;\n /** The version of the observation */\n version?: string | null;\n /** Additional metadata of the observation */\n metadata?: any;\n /** The output data of the observation */\n output?: any;\n /** (Deprecated. Use usageDetails and costDetails instead.) The usage data of the observation */\n usage?: ApiUsage | null;\n /** The level of the observation */\n level: ApiObservationLevel;\n /** The status message of the observation */\n statusMessage?: string | null;\n /** The parent observation ID */\n parentObservationId?: string | null;\n /** The prompt ID associated with the observation */\n promptId?: string | null;\n /** The usage details of the observation. Key is the name of the usage metric, value is the number of units consumed. The total key is the sum of all (non-total) usage metrics or the total value ingested. */\n usageDetails?: Record;\n /** The cost details of the observation. Key is the name of the cost metric, value is the cost in USD. The total key is the sum of all (non-total) cost metrics or the total value ingested. */\n costDetails?: Record;\n /** The environment from which this observation originated. Can be any lowercase alphanumeric string with hyphens and underscores that does not start with 'langfuse'. */\n environment?: string | null;\n}\n\n/** ObservationsView */\nexport type ApiObservationsView = ApiObservation & {\n /** The name of the prompt associated with the observation */\n promptName?: string | null;\n /** The version of the prompt associated with the observation */\n promptVersion?: number | null;\n /** The unique identifier of the model */\n modelId?: string | null;\n /**\n * The price of the input in USD\n * @format double\n */\n inputPrice?: number | null;\n /**\n * The price of the output in USD.\n * @format double\n */\n outputPrice?: number | null;\n /**\n * The total price in USD.\n * @format double\n */\n totalPrice?: number | null;\n /**\n * (Deprecated. Use usageDetails and costDetails instead.) The calculated cost of the input in USD\n * @format double\n */\n calculatedInputCost?: number | null;\n /**\n * (Deprecated. Use usageDetails and costDetails instead.) The calculated cost of the output in USD\n * @format double\n */\n calculatedOutputCost?: number | null;\n /**\n * (Deprecated. Use usageDetails and costDetails instead.) The calculated total cost in USD\n * @format double\n */\n calculatedTotalCost?: number | null;\n /**\n * The latency in seconds.\n * @format double\n */\n latency?: number | null;\n /**\n * The time to the first token in seconds\n * @format double\n */\n timeToFirstToken?: number | null;\n};\n\n/**\n * Usage\n * (Deprecated. Use usageDetails and costDetails instead.) Standard interface for usage and cost\n */\nexport interface ApiUsage {\n /** Number of input units (e.g. tokens) */\n input?: number | null;\n /** Number of output units (e.g. tokens) */\n output?: number | null;\n /** Defaults to input+output if not set */\n total?: number | null;\n /** Unit of usage in Langfuse */\n unit?: ApiModelUsageUnit | null;\n /**\n * USD input cost\n * @format double\n */\n inputCost?: number | null;\n /**\n * USD output cost\n * @format double\n */\n outputCost?: number | null;\n /**\n * USD total cost, defaults to input+output\n * @format double\n */\n totalCost?: number | null;\n}\n\n/**\n * ScoreConfig\n * Configuration for a score\n */\nexport interface ApiScoreConfig {\n id: string;\n name: string;\n /** @format date-time */\n createdAt: string;\n /** @format date-time */\n updatedAt: string;\n projectId: string;\n dataType: ApiScoreDataType;\n /** Whether the score config is archived. Defaults to false */\n isArchived: boolean;\n /**\n * Sets minimum value for numerical scores. If not set, the minimum value defaults to -∞\n * @format double\n */\n minValue?: number | null;\n /**\n * Sets maximum value for numerical scores. If not set, the maximum value defaults to +∞\n * @format double\n */\n maxValue?: number | null;\n /** Configures custom categories for categorical scores */\n categories?: ApiConfigCategory[] | null;\n description?: string | null;\n}\n\n/** ConfigCategory */\nexport interface ApiConfigCategory {\n /** @format double */\n value: number;\n label: string;\n}\n\n/** BaseScoreV1 */\nexport interface ApiBaseScoreV1 {\n id: string;\n traceId: string;\n name: string;\n source: ApiScoreSource;\n observationId?: string | null;\n /** @format date-time */\n timestamp: string;\n /** @format date-time */\n createdAt: string;\n /** @format date-time */\n updatedAt: string;\n authorUserId?: string | null;\n comment?: string | null;\n metadata?: any;\n /** Reference a score config on a score. When set, config and score name must be equal and value must comply to optionally defined numerical range */\n configId?: string | null;\n /** Reference an annotation queue on a score. Populated if the score was initially created in an annotation queue. */\n queueId?: string | null;\n /** The environment from which this score originated. Can be any lowercase alphanumeric string with hyphens and underscores that does not start with 'langfuse'. */\n environment?: string | null;\n}\n\n/** NumericScoreV1 */\nexport type ApiNumericScoreV1 = ApiBaseScoreV1 & {\n /**\n * The numeric value of the score\n * @format double\n */\n value: number;\n};\n\n/** BooleanScoreV1 */\nexport type ApiBooleanScoreV1 = ApiBaseScoreV1 & {\n /**\n * The numeric value of the score. Equals 1 for \"True\" and 0 for \"False\"\n * @format double\n */\n value: number;\n /** The string representation of the score value. Is inferred from the numeric value and equals \"True\" or \"False\" */\n stringValue: string;\n};\n\n/** CategoricalScoreV1 */\nexport type ApiCategoricalScoreV1 = ApiBaseScoreV1 & {\n /**\n * Only defined if a config is linked. Represents the numeric category mapping of the stringValue\n * @format double\n */\n value?: number | null;\n /** The string representation of the score value. If no config is linked, can be any string. Otherwise, must map to a config category */\n stringValue: string;\n};\n\n/** ScoreV1 */\nexport type ApiScoreV1 =\n | ({\n dataType: \"NUMERIC\";\n } & ApiNumericScoreV1)\n | ({\n dataType: \"CATEGORICAL\";\n } & ApiCategoricalScoreV1)\n | ({\n dataType: \"BOOLEAN\";\n } & ApiBooleanScoreV1);\n\n/** BaseScore */\nexport interface ApiBaseScore {\n id: string;\n traceId?: string | null;\n sessionId?: string | null;\n observationId?: string | null;\n datasetRunId?: string | null;\n name: string;\n source: ApiScoreSource;\n /** @format date-time */\n timestamp: string;\n /** @format date-time */\n createdAt: string;\n /** @format date-time */\n updatedAt: string;\n authorUserId?: string | null;\n comment?: string | null;\n metadata?: any;\n /** Reference a score config on a score. When set, config and score name must be equal and value must comply to optionally defined numerical range */\n configId?: string | null;\n /** Reference an annotation queue on a score. Populated if the score was initially created in an annotation queue. */\n queueId?: string | null;\n /** The environment from which this score originated. Can be any lowercase alphanumeric string with hyphens and underscores that does not start with 'langfuse'. */\n environment?: string | null;\n}\n\n/** NumericScore */\nexport type ApiNumericScore = ApiBaseScore & {\n /**\n * The numeric value of the score\n * @format double\n */\n value: number;\n};\n\n/** BooleanScore */\nexport type ApiBooleanScore = ApiBaseScore & {\n /**\n * The numeric value of the score. Equals 1 for \"True\" and 0 for \"False\"\n * @format double\n */\n value: number;\n /** The string representation of the score value. Is inferred from the numeric value and equals \"True\" or \"False\" */\n stringValue: string;\n};\n\n/** CategoricalScore */\nexport type ApiCategoricalScore = ApiBaseScore & {\n /**\n * Only defined if a config is linked. Represents the numeric category mapping of the stringValue\n * @format double\n */\n value?: number | null;\n /** The string representation of the score value. If no config is linked, can be any string. Otherwise, must map to a config category */\n stringValue: string;\n};\n\n/** Score */\nexport type ApiScore =\n | ({\n dataType: \"NUMERIC\";\n } & ApiNumericScore)\n | ({\n dataType: \"CATEGORICAL\";\n } & ApiCategoricalScore)\n | ({\n dataType: \"BOOLEAN\";\n } & ApiBooleanScore);\n\n/**\n * CreateScoreValue\n * The value of the score. Must be passed as string for categorical scores, and numeric for boolean and numeric scores\n */\nexport type ApiCreateScoreValue = number | string;\n\n/** Comment */\nexport interface ApiComment {\n id: string;\n projectId: string;\n /** @format date-time */\n createdAt: string;\n /** @format date-time */\n updatedAt: string;\n objectType: ApiCommentObjectType;\n objectId: string;\n content: string;\n authorUserId?: string | null;\n}\n\n/** Dataset */\nexport interface ApiDataset {\n id: string;\n name: string;\n description?: string | null;\n metadata?: any;\n projectId: string;\n /** @format date-time */\n createdAt: string;\n /** @format date-time */\n updatedAt: string;\n}\n\n/** DatasetItem */\nexport interface ApiDatasetItem {\n id: string;\n status: ApiDatasetStatus;\n input?: any;\n expectedOutput?: any;\n metadata?: any;\n sourceTraceId?: string | null;\n sourceObservationId?: string | null;\n datasetId: string;\n datasetName: string;\n /** @format date-time */\n createdAt: string;\n /** @format date-time */\n updatedAt: string;\n}\n\n/** DatasetRunItem */\nexport interface ApiDatasetRunItem {\n id: string;\n datasetRunId: string;\n datasetRunName: string;\n datasetItemId: string;\n traceId: string;\n observationId?: string | null;\n /** @format date-time */\n createdAt: string;\n /** @format date-time */\n updatedAt: string;\n}\n\n/** DatasetRun */\nexport interface ApiDatasetRun {\n /** Unique identifier of the dataset run */\n id: string;\n /** Name of the dataset run */\n name: string;\n /** Description of the run */\n description?: string | null;\n /** Metadata of the dataset run */\n metadata?: any;\n /** Id of the associated dataset */\n datasetId: string;\n /** Name of the associated dataset */\n datasetName: string;\n /**\n * The date and time when the dataset run was created\n * @format date-time\n */\n createdAt: string;\n /**\n * The date and time when the dataset run was last updated\n * @format date-time\n */\n updatedAt: string;\n}\n\n/** DatasetRunWithItems */\nexport type ApiDatasetRunWithItems = ApiDatasetRun & {\n datasetRunItems: ApiDatasetRunItem[];\n};\n\n/**\n * Model\n * Model definition used for transforming usage into USD cost and/or tokenization.\n */\nexport interface ApiModel {\n id: string;\n /** Name of the model definition. If multiple with the same name exist, they are applied in the following order: (1) custom over built-in, (2) newest according to startTime where model.startTime;\n}\n\n/** ModelPrice */\nexport interface ApiModelPrice {\n /** @format double */\n price: number;\n}\n\n/**\n * ModelUsageUnit\n * Unit of usage in Langfuse\n */\nexport type ApiModelUsageUnit = \"CHARACTERS\" | \"TOKENS\" | \"MILLISECONDS\" | \"SECONDS\" | \"IMAGES\" | \"REQUESTS\";\n\n/** ObservationLevel */\nexport type ApiObservationLevel = \"DEBUG\" | \"DEFAULT\" | \"WARNING\" | \"ERROR\";\n\n/** MapValue */\nexport type ApiMapValue = string | null | number | null | boolean | null | string[] | null;\n\n/** CommentObjectType */\nexport type ApiCommentObjectType = \"TRACE\" | \"OBSERVATION\" | \"SESSION\" | \"PROMPT\";\n\n/** DatasetStatus */\nexport type ApiDatasetStatus = \"ACTIVE\" | \"ARCHIVED\";\n\n/** ScoreSource */\nexport type ApiScoreSource = \"ANNOTATION\" | \"API\" | \"EVAL\";\n\n/** ScoreDataType */\nexport type ApiScoreDataType = \"NUMERIC\" | \"BOOLEAN\" | \"CATEGORICAL\";\n\n/** DeleteDatasetItemResponse */\nexport interface ApiDeleteDatasetItemResponse {\n /** Success message after deletion */\n message: string;\n}\n\n/** CreateDatasetItemRequest */\nexport interface ApiCreateDatasetItemRequest {\n datasetName: string;\n input?: any;\n expectedOutput?: any;\n metadata?: any;\n sourceTraceId?: string | null;\n sourceObservationId?: string | null;\n /** Dataset items are upserted on their id. Id needs to be unique (project-level) and cannot be reused across datasets. */\n id?: string | null;\n /** Defaults to ACTIVE for newly created items */\n status?: ApiDatasetStatus | null;\n}\n\n/** PaginatedDatasetItems */\nexport interface ApiPaginatedDatasetItems {\n data: ApiDatasetItem[];\n meta: ApiUtilsMetaResponse;\n}\n\n/** CreateDatasetRunItemRequest */\nexport interface ApiCreateDatasetRunItemRequest {\n runName: string;\n /** Description of the run. If run exists, description will be updated. */\n runDescription?: string | null;\n /** Metadata of the dataset run, updates run if run already exists */\n metadata?: any;\n datasetItemId: string;\n observationId?: string | null;\n /** traceId should always be provided. For compatibility with older SDK versions it can also be inferred from the provided observationId. */\n traceId?: string | null;\n}\n\n/** PaginatedDatasetRunItems */\nexport interface ApiPaginatedDatasetRunItems {\n data: ApiDatasetRunItem[];\n meta: ApiUtilsMetaResponse;\n}\n\n/** PaginatedDatasets */\nexport interface ApiPaginatedDatasets {\n data: ApiDataset[];\n meta: ApiUtilsMetaResponse;\n}\n\n/** CreateDatasetRequest */\nexport interface ApiCreateDatasetRequest {\n name: string;\n description?: string | null;\n metadata?: any;\n}\n\n/** PaginatedDatasetRuns */\nexport interface ApiPaginatedDatasetRuns {\n data: ApiDatasetRun[];\n meta: ApiUtilsMetaResponse;\n}\n\n/** DeleteDatasetRunResponse */\nexport interface ApiDeleteDatasetRunResponse {\n message: string;\n}\n\n/** HealthResponse */\nexport interface ApiHealthResponse {\n /**\n * Langfuse server version\n * @example \"1.25.0\"\n */\n version: string;\n /** @example \"OK\" */\n status: string;\n}\n\n/** IngestionEvent */\nexport type ApiIngestionEvent =\n | ({\n type: \"trace-create\";\n } & ApiTraceEvent)\n | ({\n type: \"score-create\";\n } & ApiScoreEvent)\n | ({\n type: \"span-create\";\n } & ApiCreateSpanEvent)\n | ({\n type: \"span-update\";\n } & ApiUpdateSpanEvent)\n | ({\n type: \"generation-create\";\n } & ApiCreateGenerationEvent)\n | ({\n type: \"generation-update\";\n } & ApiUpdateGenerationEvent)\n | ({\n type: \"event-create\";\n } & ApiCreateEventEvent)\n | ({\n type: \"sdk-log\";\n } & ApiSDKLogEvent)\n | ({\n type: \"observation-create\";\n } & ApiCreateObservationEvent)\n | ({\n type: \"observation-update\";\n } & ApiUpdateObservationEvent);\n\n/** ObservationType */\nexport type ApiObservationType = \"SPAN\" | \"GENERATION\" | \"EVENT\";\n\n/** IngestionUsage */\nexport type ApiIngestionUsage = ApiUsage | ApiOpenAIUsage;\n\n/**\n * OpenAIUsage\n * Usage interface of OpenAI for improved compatibility.\n */\nexport interface ApiOpenAIUsage {\n promptTokens?: number | null;\n completionTokens?: number | null;\n totalTokens?: number | null;\n}\n\n/** OptionalObservationBody */\nexport interface ApiOptionalObservationBody {\n traceId?: string | null;\n name?: string | null;\n /** @format date-time */\n startTime?: string | null;\n metadata?: any;\n input?: any;\n output?: any;\n level?: ApiObservationLevel | null;\n statusMessage?: string | null;\n parentObservationId?: string | null;\n version?: string | null;\n environment?: string | null;\n}\n\n/** CreateEventBody */\nexport type ApiCreateEventBody = ApiOptionalObservationBody & {\n id?: string | null;\n};\n\n/** UpdateEventBody */\nexport type ApiUpdateEventBody = ApiOptionalObservationBody & {\n id: string;\n};\n\n/** CreateSpanBody */\nexport type ApiCreateSpanBody = ApiCreateEventBody & {\n /** @format date-time */\n endTime?: string | null;\n};\n\n/** UpdateSpanBody */\nexport type ApiUpdateSpanBody = ApiUpdateEventBody & {\n /** @format date-time */\n endTime?: string | null;\n};\n\n/** CreateGenerationBody */\nexport type ApiCreateGenerationBody = ApiCreateSpanBody & {\n /** @format date-time */\n completionStartTime?: string | null;\n model?: string | null;\n modelParameters?: Record;\n usage?: ApiIngestionUsage | null;\n usageDetails?: ApiUsageDetails | null;\n costDetails?: Record;\n promptName?: string | null;\n promptVersion?: number | null;\n};\n\n/** UpdateGenerationBody */\nexport type ApiUpdateGenerationBody = ApiUpdateSpanBody & {\n /** @format date-time */\n completionStartTime?: string | null;\n model?: string | null;\n modelParameters?: Record;\n usage?: ApiIngestionUsage | null;\n promptName?: string | null;\n usageDetails?: ApiUsageDetails | null;\n costDetails?: Record;\n promptVersion?: number | null;\n};\n\n/** ObservationBody */\nexport interface ApiObservationBody {\n id?: string | null;\n traceId?: string | null;\n type: ApiObservationType;\n name?: string | null;\n /** @format date-time */\n startTime?: string | null;\n /** @format date-time */\n endTime?: string | null;\n /** @format date-time */\n completionStartTime?: string | null;\n model?: string | null;\n modelParameters?: Record;\n input?: any;\n version?: string | null;\n metadata?: any;\n output?: any;\n /** (Deprecated. Use usageDetails and costDetails instead.) Standard interface for usage and cost */\n usage?: ApiUsage | null;\n level?: ApiObservationLevel | null;\n statusMessage?: string | null;\n parentObservationId?: string | null;\n environment?: string | null;\n}\n\n/** TraceBody */\nexport interface ApiTraceBody {\n id?: string | null;\n /** @format date-time */\n timestamp?: string | null;\n name?: string | null;\n userId?: string | null;\n input?: any;\n output?: any;\n sessionId?: string | null;\n release?: string | null;\n version?: string | null;\n metadata?: any;\n tags?: string[] | null;\n environment?: string | null;\n /** Make trace publicly accessible via url */\n public?: boolean | null;\n}\n\n/** SDKLogBody */\nexport interface ApiSDKLogBody {\n log: any;\n}\n\n/** ScoreBody */\nexport interface ApiScoreBody {\n id?: string | null;\n traceId?: string | null;\n sessionId?: string | null;\n observationId?: string | null;\n datasetRunId?: string | null;\n /** @example \"novelty\" */\n name: string;\n environment?: string | null;\n /** The value of the score. Must be passed as string for categorical scores, and numeric for boolean and numeric scores. Boolean score values must equal either 1 or 0 (true or false) */\n value: ApiCreateScoreValue;\n comment?: string | null;\n metadata?: any;\n /** When set, must match the score value's type. If not set, will be inferred from the score value or config */\n dataType?: ApiScoreDataType | null;\n /** Reference a score config on a score. When set, the score name must equal the config name and scores must comply with the config's range and data type. For categorical scores, the value must map to a config category. Numeric scores might be constrained by the score config's max and min values */\n configId?: string | null;\n}\n\n/** BaseEvent */\nexport interface ApiBaseEvent {\n /** UUID v4 that identifies the event */\n id: string;\n /** Datetime (ISO 8601) of event creation in client. Should be as close to actual event creation in client as possible, this timestamp will be used for ordering of events in future release. Resolution: milliseconds (required), microseconds (optimal). */\n timestamp: string;\n /** Optional. Metadata field used by the Langfuse SDKs for debugging. */\n metadata?: any;\n}\n\n/** TraceEvent */\nexport type ApiTraceEvent = ApiBaseEvent & {\n body: ApiTraceBody;\n};\n\n/** CreateObservationEvent */\nexport type ApiCreateObservationEvent = ApiBaseEvent & {\n body: ApiObservationBody;\n};\n\n/** UpdateObservationEvent */\nexport type ApiUpdateObservationEvent = ApiBaseEvent & {\n body: ApiObservationBody;\n};\n\n/** ScoreEvent */\nexport type ApiScoreEvent = ApiBaseEvent & {\n body: ApiScoreBody;\n};\n\n/** SDKLogEvent */\nexport type ApiSDKLogEvent = ApiBaseEvent & {\n body: ApiSDKLogBody;\n};\n\n/** CreateGenerationEvent */\nexport type ApiCreateGenerationEvent = ApiBaseEvent & {\n body: ApiCreateGenerationBody;\n};\n\n/** UpdateGenerationEvent */\nexport type ApiUpdateGenerationEvent = ApiBaseEvent & {\n body: ApiUpdateGenerationBody;\n};\n\n/** CreateSpanEvent */\nexport type ApiCreateSpanEvent = ApiBaseEvent & {\n body: ApiCreateSpanBody;\n};\n\n/** UpdateSpanEvent */\nexport type ApiUpdateSpanEvent = ApiBaseEvent & {\n body: ApiUpdateSpanBody;\n};\n\n/** CreateEventEvent */\nexport type ApiCreateEventEvent = ApiBaseEvent & {\n body: ApiCreateEventBody;\n};\n\n/** IngestionSuccess */\nexport interface ApiIngestionSuccess {\n id: string;\n status: number;\n}\n\n/** IngestionError */\nexport interface ApiIngestionError {\n id: string;\n status: number;\n message?: string | null;\n error?: any;\n}\n\n/** IngestionResponse */\nexport interface ApiIngestionResponse {\n successes: ApiIngestionSuccess[];\n errors: ApiIngestionError[];\n}\n\n/**\n * OpenAICompletionUsageSchema\n * OpenAI Usage schema from (Chat-)Completion APIs\n */\nexport interface ApiOpenAICompletionUsageSchema {\n prompt_tokens: number;\n completion_tokens: number;\n total_tokens: number;\n prompt_tokens_details?: Record;\n completion_tokens_details?: Record;\n}\n\n/**\n * OpenAIResponseUsageSchema\n * OpenAI Usage schema from Response API\n */\nexport interface ApiOpenAIResponseUsageSchema {\n input_tokens: number;\n output_tokens: number;\n total_tokens: number;\n input_tokens_details?: Record;\n output_tokens_details?: Record;\n}\n\n/** UsageDetails */\nexport type ApiUsageDetails = Record | ApiOpenAICompletionUsageSchema | ApiOpenAIResponseUsageSchema;\n\n/** GetMediaResponse */\nexport interface ApiGetMediaResponse {\n /** The unique langfuse identifier of a media record */\n mediaId: string;\n /** The MIME type of the media record */\n contentType: string;\n /** The size of the media record in bytes */\n contentLength: number;\n /**\n * The date and time when the media record was uploaded\n * @format date-time\n */\n uploadedAt: string;\n /** The download URL of the media record */\n url: string;\n /** The expiry date and time of the media record download URL */\n urlExpiry: string;\n}\n\n/** PatchMediaBody */\nexport interface ApiPatchMediaBody {\n /**\n * The date and time when the media record was uploaded\n * @format date-time\n */\n uploadedAt: string;\n /** The HTTP status code of the upload */\n uploadHttpStatus: number;\n /** The HTTP error message of the upload */\n uploadHttpError?: string | null;\n /** The time in milliseconds it took to upload the media record */\n uploadTimeMs?: number | null;\n}\n\n/** GetMediaUploadUrlRequest */\nexport interface ApiGetMediaUploadUrlRequest {\n /** The trace ID associated with the media record */\n traceId: string;\n /** The observation ID associated with the media record. If the media record is associated directly with a trace, this will be null. */\n observationId?: string | null;\n /** The MIME type of the media record */\n contentType: ApiMediaContentType;\n /** The size of the media record in bytes */\n contentLength: number;\n /** The SHA-256 hash of the media record */\n sha256Hash: string;\n /** The trace / observation field the media record is associated with. This can be one of `input`, `output`, `metadata` */\n field: string;\n}\n\n/** GetMediaUploadUrlResponse */\nexport interface ApiGetMediaUploadUrlResponse {\n /** The presigned upload URL. If the asset is already uploaded, this will be null */\n uploadUrl?: string | null;\n /** The unique langfuse identifier of a media record */\n mediaId: string;\n}\n\n/**\n * MediaContentType\n * The MIME type of the media record\n */\nexport type ApiMediaContentType =\n | \"image/png\"\n | \"image/jpeg\"\n | \"image/jpg\"\n | \"image/webp\"\n | \"image/gif\"\n | \"image/svg+xml\"\n | \"image/tiff\"\n | \"image/bmp\"\n | \"audio/mpeg\"\n | \"audio/mp3\"\n | \"audio/wav\"\n | \"audio/ogg\"\n | \"audio/oga\"\n | \"audio/aac\"\n | \"audio/mp4\"\n | \"audio/flac\"\n | \"video/mp4\"\n | \"video/webm\"\n | \"text/plain\"\n | \"text/html\"\n | \"text/css\"\n | \"text/csv\"\n | \"application/pdf\"\n | \"application/msword\"\n | \"application/vnd.ms-excel\"\n | \"application/zip\"\n | \"application/json\"\n | \"application/xml\"\n | \"application/octet-stream\";\n\n/** MetricsResponse */\nexport interface ApiMetricsResponse {\n /**\n * The metrics data. Each item in the list contains the metric values and dimensions requested in the query.\n * Format varies based on the query parameters.\n * Histograms will return an array with [lower, upper, height] tuples.\n */\n data: Record[];\n}\n\n/** PaginatedModels */\nexport interface ApiPaginatedModels {\n data: ApiModel[];\n meta: ApiUtilsMetaResponse;\n}\n\n/** CreateModelRequest */\nexport interface ApiCreateModelRequest {\n /** Name of the model definition. If multiple with the same name exist, they are applied in the following order: (1) custom over built-in, (2) newest according to startTime where model.startTime;\n /** @format date-time */\n createdAt: string;\n /** @format date-time */\n updatedAt: string;\n}\n\n/** OrganizationProjectsResponse */\nexport interface ApiOrganizationProjectsResponse {\n projects: ApiOrganizationProject[];\n}\n\n/** Projects */\nexport interface ApiProjects {\n data: ApiProject[];\n}\n\n/** Project */\nexport interface ApiProject {\n id: string;\n name: string;\n /** Metadata for the project */\n metadata: Record;\n /** Number of days to retain data. Null or 0 means no retention. Omitted if no retention is configured. */\n retentionDays?: number | null;\n}\n\n/** ProjectDeletionResponse */\nexport interface ApiProjectDeletionResponse {\n success: boolean;\n message: string;\n}\n\n/**\n * ApiKeyList\n * List of API keys for a project\n */\nexport interface ApiApiKeyList {\n apiKeys: ApiApiKeySummary[];\n}\n\n/**\n * ApiKeySummary\n * Summary of an API key\n */\nexport interface ApiApiKeySummary {\n id: string;\n /** @format date-time */\n createdAt: string;\n /** @format date-time */\n expiresAt?: string | null;\n /** @format date-time */\n lastUsedAt?: string | null;\n note?: string | null;\n publicKey: string;\n displaySecretKey: string;\n}\n\n/**\n * ApiKeyResponse\n * Response for API key creation\n */\nexport interface ApiApiKeyResponse {\n id: string;\n /** @format date-time */\n createdAt: string;\n publicKey: string;\n secretKey: string;\n displaySecretKey: string;\n note?: string | null;\n}\n\n/**\n * ApiKeyDeletionResponse\n * Response for API key deletion\n */\nexport interface ApiApiKeyDeletionResponse {\n success: boolean;\n}\n\n/** PromptMetaListResponse */\nexport interface ApiPromptMetaListResponse {\n data: ApiPromptMeta[];\n meta: ApiUtilsMetaResponse;\n}\n\n/** PromptMeta */\nexport interface ApiPromptMeta {\n name: string;\n versions: number[];\n labels: string[];\n tags: string[];\n /** @format date-time */\n lastUpdatedAt: string;\n /** Config object of the most recent prompt version that matches the filters (if any are provided) */\n lastConfig: any;\n}\n\n/** CreatePromptRequest */\nexport type ApiCreatePromptRequest =\n | ({\n type: \"chat\";\n } & ApiCreateChatPromptRequest)\n | ({\n type: \"text\";\n } & ApiCreateTextPromptRequest);\n\n/** CreateChatPromptRequest */\nexport interface ApiCreateChatPromptRequest {\n name: string;\n prompt: ApiChatMessageWithPlaceholders[];\n config?: any;\n /** List of deployment labels of this prompt version. */\n labels?: string[] | null;\n /** List of tags to apply to all versions of this prompt. */\n tags?: string[] | null;\n /** Commit message for this prompt version. */\n commitMessage?: string | null;\n}\n\n/** CreateTextPromptRequest */\nexport interface ApiCreateTextPromptRequest {\n name: string;\n prompt: string;\n config?: any;\n /** List of deployment labels of this prompt version. */\n labels?: string[] | null;\n /** List of tags to apply to all versions of this prompt. */\n tags?: string[] | null;\n /** Commit message for this prompt version. */\n commitMessage?: string | null;\n}\n\n/** Prompt */\nexport type ApiPrompt =\n | ({\n type: \"chat\";\n } & ApiChatPrompt)\n | ({\n type: \"text\";\n } & ApiTextPrompt);\n\n/** BasePrompt */\nexport interface ApiBasePrompt {\n name: string;\n version: number;\n config: any;\n /** List of deployment labels of this prompt version. */\n labels: string[];\n /** List of tags. Used to filter via UI and API. The same across versions of a prompt. */\n tags: string[];\n /** Commit message for this prompt version. */\n commitMessage?: string | null;\n /** The dependency resolution graph for the current prompt. Null if prompt has no dependencies. */\n resolutionGraph?: Record;\n}\n\n/** ChatMessageWithPlaceholders */\nexport type ApiChatMessageWithPlaceholders =\n | ({\n type: \"chatmessage\";\n } & ApiChatMessage)\n | ({\n type: \"placeholder\";\n } & ApiPlaceholderMessage);\n\n/** ChatMessage */\nexport interface ApiChatMessage {\n role: string;\n content: string;\n}\n\n/** PlaceholderMessage */\nexport interface ApiPlaceholderMessage {\n name: string;\n}\n\n/** TextPrompt */\nexport type ApiTextPrompt = ApiBasePrompt & {\n prompt: string;\n};\n\n/** ChatPrompt */\nexport type ApiChatPrompt = ApiBasePrompt & {\n prompt: ApiChatMessageWithPlaceholders[];\n};\n\n/** ServiceProviderConfig */\nexport interface ApiServiceProviderConfig {\n schemas: string[];\n documentationUri: string;\n patch: ApiScimFeatureSupport;\n bulk: ApiBulkConfig;\n filter: ApiFilterConfig;\n changePassword: ApiScimFeatureSupport;\n sort: ApiScimFeatureSupport;\n etag: ApiScimFeatureSupport;\n authenticationSchemes: ApiAuthenticationScheme[];\n meta: ApiResourceMeta;\n}\n\n/** ScimFeatureSupport */\nexport interface ApiScimFeatureSupport {\n supported: boolean;\n}\n\n/** BulkConfig */\nexport interface ApiBulkConfig {\n supported: boolean;\n maxOperations: number;\n maxPayloadSize: number;\n}\n\n/** FilterConfig */\nexport interface ApiFilterConfig {\n supported: boolean;\n maxResults: number;\n}\n\n/** ResourceMeta */\nexport interface ApiResourceMeta {\n resourceType: string;\n location: string;\n}\n\n/** AuthenticationScheme */\nexport interface ApiAuthenticationScheme {\n name: string;\n description: string;\n specUri: string;\n type: string;\n primary: boolean;\n}\n\n/** ResourceTypesResponse */\nexport interface ApiResourceTypesResponse {\n schemas: string[];\n totalResults: number;\n Resources: ApiResourceType[];\n}\n\n/** ResourceType */\nexport interface ApiResourceType {\n schemas?: string[] | null;\n id: string;\n name: string;\n endpoint: string;\n description: string;\n schema: string;\n schemaExtensions: ApiSchemaExtension[];\n meta: ApiResourceMeta;\n}\n\n/** SchemaExtension */\nexport interface ApiSchemaExtension {\n schema: string;\n required: boolean;\n}\n\n/** SchemasResponse */\nexport interface ApiSchemasResponse {\n schemas: string[];\n totalResults: number;\n Resources: ApiSchemaResource[];\n}\n\n/** SchemaResource */\nexport interface ApiSchemaResource {\n id: string;\n name: string;\n description: string;\n attributes: any[];\n meta: ApiResourceMeta;\n}\n\n/** ScimUsersListResponse */\nexport interface ApiScimUsersListResponse {\n schemas: string[];\n totalResults: number;\n startIndex: number;\n itemsPerPage: number;\n Resources: ApiScimUser[];\n}\n\n/** ScimUser */\nexport interface ApiScimUser {\n schemas: string[];\n id: string;\n userName: string;\n name: ApiScimName;\n emails: ApiScimEmail[];\n meta: ApiUserMeta;\n}\n\n/** UserMeta */\nexport interface ApiUserMeta {\n resourceType: string;\n created?: string | null;\n lastModified?: string | null;\n}\n\n/** ScimName */\nexport interface ApiScimName {\n formatted?: string | null;\n}\n\n/** ScimEmail */\nexport interface ApiScimEmail {\n primary: boolean;\n value: string;\n type: string;\n}\n\n/**\n * EmptyResponse\n * Empty response for 204 No Content responses\n */\nexport type ApiEmptyResponse = object;\n\n/** ScoreConfigs */\nexport interface ApiScoreConfigs {\n data: ApiScoreConfig[];\n meta: ApiUtilsMetaResponse;\n}\n\n/** CreateScoreConfigRequest */\nexport interface ApiCreateScoreConfigRequest {\n name: string;\n dataType: ApiScoreDataType;\n /** Configure custom categories for categorical scores. Pass a list of objects with `label` and `value` properties. Categories are autogenerated for boolean configs and cannot be passed */\n categories?: ApiConfigCategory[] | null;\n /**\n * Configure a minimum value for numerical scores. If not set, the minimum value defaults to -∞\n * @format double\n */\n minValue?: number | null;\n /**\n * Configure a maximum value for numerical scores. If not set, the maximum value defaults to +∞\n * @format double\n */\n maxValue?: number | null;\n /** Description is shown across the Langfuse UI and can be used to e.g. explain the config categories in detail, why a numeric range was set, or provide additional context on config name or usage */\n description?: string | null;\n}\n\n/** GetScoresResponseTraceData */\nexport interface ApiGetScoresResponseTraceData {\n /** The user ID associated with the trace referenced by score */\n userId?: string | null;\n /** A list of tags associated with the trace referenced by score */\n tags?: string[] | null;\n /** The environment of the trace referenced by score */\n environment?: string | null;\n}\n\n/** GetScoresResponseDataNumeric */\nexport type ApiGetScoresResponseDataNumeric = ApiNumericScore & {\n trace?: ApiGetScoresResponseTraceData | null;\n};\n\n/** GetScoresResponseDataCategorical */\nexport type ApiGetScoresResponseDataCategorical = ApiCategoricalScore & {\n trace?: ApiGetScoresResponseTraceData | null;\n};\n\n/** GetScoresResponseDataBoolean */\nexport type ApiGetScoresResponseDataBoolean = ApiBooleanScore & {\n trace?: ApiGetScoresResponseTraceData | null;\n};\n\n/** GetScoresResponseData */\nexport type ApiGetScoresResponseData =\n | ({\n dataType: \"NUMERIC\";\n } & ApiGetScoresResponseDataNumeric)\n | ({\n dataType: \"CATEGORICAL\";\n } & ApiGetScoresResponseDataCategorical)\n | ({\n dataType: \"BOOLEAN\";\n } & ApiGetScoresResponseDataBoolean);\n\n/** GetScoresResponse */\nexport interface ApiGetScoresResponse {\n data: ApiGetScoresResponseData[];\n meta: ApiUtilsMetaResponse;\n}\n\n/** CreateScoreRequest */\nexport interface ApiCreateScoreRequest {\n id?: string | null;\n traceId?: string | null;\n sessionId?: string | null;\n observationId?: string | null;\n datasetRunId?: string | null;\n /** @example \"novelty\" */\n name: string;\n /** The value of the score. Must be passed as string for categorical scores, and numeric for boolean and numeric scores. Boolean score values must equal either 1 or 0 (true or false) */\n value: ApiCreateScoreValue;\n comment?: string | null;\n metadata?: any;\n /** The environment of the score. Can be any lowercase alphanumeric string with hyphens and underscores that does not start with 'langfuse'. */\n environment?: string | null;\n /** The data type of the score. When passing a configId this field is inferred. Otherwise, this field must be passed or will default to numeric. */\n dataType?: ApiScoreDataType | null;\n /** Reference a score config on a score. The unique langfuse identifier of a score config. When passing this field, the dataType and stringValue fields are automatically populated. */\n configId?: string | null;\n}\n\n/** CreateScoreResponse */\nexport interface ApiCreateScoreResponse {\n /** The id of the created object in Langfuse */\n id: string;\n}\n\n/** PaginatedSessions */\nexport interface ApiPaginatedSessions {\n data: ApiSession[];\n meta: ApiUtilsMetaResponse;\n}\n\n/** Traces */\nexport interface ApiTraces {\n data: ApiTraceWithDetails[];\n meta: ApiUtilsMetaResponse;\n}\n\n/** DeleteTraceResponse */\nexport interface ApiDeleteTraceResponse {\n message: string;\n}\n\n/** Sort */\nexport interface ApiSort {\n id: string;\n}\n\n/** utilsMetaResponse */\nexport interface ApiUtilsMetaResponse {\n /** current page number */\n page: number;\n /** number of items per page */\n limit: number;\n /** number of total items given the current filters/selection (if any) */\n totalItems: number;\n /** number of total pages given the current limit */\n totalPages: number;\n}\n\nexport interface ApiAnnotationQueuesListQueuesParams {\n /** page number, starts at 1 */\n page?: number | null;\n /** limit of items per page */\n limit?: number | null;\n}\n\nexport interface ApiAnnotationQueuesListQueueItemsParams {\n /** Filter by status */\n status?: ApiAnnotationQueueStatus | null;\n /** page number, starts at 1 */\n page?: number | null;\n /** limit of items per page */\n limit?: number | null;\n /** The unique identifier of the annotation queue */\n queueId: string;\n}\n\nexport interface ApiCommentsGetParams {\n /** Page number, starts at 1. */\n page?: number | null;\n /** Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit */\n limit?: number | null;\n /** Filter comments by object type (trace, observation, session, prompt). */\n objectType?: string | null;\n /** Filter comments by object id. If objectType is not provided, an error will be thrown. */\n objectId?: string | null;\n /** Filter comments by author user id. */\n authorUserId?: string | null;\n}\n\nexport interface ApiDatasetItemsListParams {\n datasetName?: string | null;\n sourceTraceId?: string | null;\n sourceObservationId?: string | null;\n /** page number, starts at 1 */\n page?: number | null;\n /** limit of items per page */\n limit?: number | null;\n}\n\nexport interface ApiDatasetRunItemsListParams {\n datasetId: string;\n runName: string;\n /** page number, starts at 1 */\n page?: number | null;\n /** limit of items per page */\n limit?: number | null;\n response: ApiPaginatedDatasetRunItems;\n}\n\nexport interface ApiDatasetsListParams {\n /** page number, starts at 1 */\n page?: number | null;\n /** limit of items per page */\n limit?: number | null;\n}\n\nexport interface ApiDatasetsGetRunsParams {\n /** page number, starts at 1 */\n page?: number | null;\n /** limit of items per page */\n limit?: number | null;\n datasetName: string;\n}\n\nexport interface ApiIngestionBatchPayload {\n /** Batch of tracing events to be ingested. Discriminated by attribute `type`. */\n batch: ApiIngestionEvent[];\n /** Optional. Metadata field used by the Langfuse SDKs for debugging. */\n metadata?: any;\n}\n\nexport interface ApiMetricsMetricsParams {\n /**\n * JSON string containing the query parameters with the following structure:\n * ```json\n * {\n * \"view\": string, // Required. One of \"traces\", \"observations\", \"scores-numeric\", \"scores-categorical\"\n * \"dimensions\": [ // Optional. Default: []\n * {\n * \"field\": string // Field to group by, e.g. \"name\", \"userId\", \"sessionId\"\n * }\n * ],\n * \"metrics\": [ // Required. At least one metric must be provided\n * {\n * \"measure\": string, // What to measure, e.g. \"count\", \"latency\", \"value\"\n * \"aggregation\": string // How to aggregate, e.g. \"count\", \"sum\", \"avg\", \"p95\", \"histogram\"\n * }\n * ],\n * \"filters\": [ // Optional. Default: []\n * {\n * \"column\": string, // Column to filter on\n * \"operator\": string, // Operator, e.g. \"=\", \">\", \"<\", \"contains\"\n * \"value\": any, // Value to compare against\n * \"type\": string, // Data type, e.g. \"string\", \"number\", \"stringObject\"\n * \"key\": string // Required only when filtering on metadata\n * }\n * ],\n * \"timeDimension\": { // Optional. Default: null. If provided, results will be grouped by time\n * \"granularity\": string // One of \"minute\", \"hour\", \"day\", \"week\", \"month\", \"auto\"\n * },\n * \"fromTimestamp\": string, // Required. ISO datetime string for start of time range\n * \"toTimestamp\": string, // Required. ISO datetime string for end of time range\n * \"orderBy\": [ // Optional. Default: null\n * {\n * \"field\": string, // Field to order by\n * \"direction\": string // \"asc\" or \"desc\"\n * }\n * ],\n * \"config\": { // Optional. Query-specific configuration\n * \"bins\": number, // Optional. Number of bins for histogram (1-100), default: 10\n * \"row_limit\": number // Optional. Row limit for results (1-1000)\n * }\n * }\n * ```\n */\n query: string;\n}\n\nexport interface ApiModelsListParams {\n /** page number, starts at 1 */\n page?: number | null;\n /** limit of items per page */\n limit?: number | null;\n}\n\nexport interface ApiObservationsGetManyParams {\n /** Page number, starts at 1. */\n page?: number | null;\n /** Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit. */\n limit?: number | null;\n name?: string | null;\n userId?: string | null;\n type?: string | null;\n traceId?: string | null;\n parentObservationId?: string | null;\n /** Optional filter for observations where the environment is one of the provided values. */\n environment?: (string | null)[];\n /**\n * Retrieve only observations with a start_time on or after this datetime (ISO 8601).\n * @format date-time\n */\n fromStartTime?: string | null;\n /**\n * Retrieve only observations with a start_time before this datetime (ISO 8601).\n * @format date-time\n */\n toStartTime?: string | null;\n /** Optional filter to only include observations with a certain version. */\n version?: string | null;\n}\n\nexport interface ApiProjectsCreatePayload {\n name: string;\n /** Optional metadata for the project */\n metadata?: Record;\n /** Number of days to retain data. Must be 0 or at least 3 days. Requires data-retention entitlement for non-zero values. Optional. */\n retention: number;\n}\n\nexport interface ApiProjectsUpdatePayload {\n name: string;\n /** Optional metadata for the project */\n metadata?: Record;\n /** Number of days to retain data. Must be 0 or at least 3 days. Requires data-retention entitlement for non-zero values. Optional. */\n retention: number;\n}\n\nexport interface ApiProjectsCreateApiKeyPayload {\n /** Optional note for the API key */\n note?: string | null;\n}\n\nexport interface ApiPromptVersionUpdatePayload {\n /** New labels for the prompt version. Labels are unique across versions. The \"latest\" label is reserved and managed by Langfuse. */\n newLabels: string[];\n}\n\nexport interface ApiPromptsGetParams {\n /** Version of the prompt to be retrieved. */\n version?: number | null;\n /** Label of the prompt to be retrieved. Defaults to \"production\" if no label or version is set. */\n label?: string | null;\n /** The name of the prompt */\n promptName: string;\n}\n\nexport interface ApiPromptsListParams {\n name?: string | null;\n label?: string | null;\n tag?: string | null;\n /** page number, starts at 1 */\n page?: number | null;\n /** limit of items per page */\n limit?: number | null;\n /**\n * Optional filter to only include prompt versions created/updated on or after a certain datetime (ISO 8601)\n * @format date-time\n */\n fromUpdatedAt?: string | null;\n /**\n * Optional filter to only include prompt versions created/updated before a certain datetime (ISO 8601)\n * @format date-time\n */\n toUpdatedAt?: string | null;\n}\n\nexport interface ApiScimListUsersParams {\n /** Filter expression (e.g. userName eq \"value\") */\n filter?: string | null;\n /** 1-based index of the first result to return (default 1) */\n startIndex?: number | null;\n /** Maximum number of results to return (default 100) */\n count?: number | null;\n}\n\nexport interface ApiScimCreateUserPayload {\n /** User's email address (required) */\n userName: string;\n /** User's name information */\n name: ApiScimName;\n /** User's email addresses */\n emails?: ApiScimEmail[] | null;\n /** Whether the user is active */\n active?: boolean | null;\n /** Initial password for the user */\n password?: string | null;\n}\n\nexport interface ApiScoreConfigsGetParams {\n /** Page number, starts at 1. */\n page?: number | null;\n /** Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit */\n limit?: number | null;\n}\n\nexport interface ApiScoreV2GetParams {\n /** Page number, starts at 1. */\n page?: number | null;\n /** Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit. */\n limit?: number | null;\n /** Retrieve only scores with this userId associated to the trace. */\n userId?: string | null;\n /** Retrieve only scores with this name. */\n name?: string | null;\n /**\n * Optional filter to only include scores created on or after a certain datetime (ISO 8601)\n * @format date-time\n */\n fromTimestamp?: string | null;\n /**\n * Optional filter to only include scores created before a certain datetime (ISO 8601)\n * @format date-time\n */\n toTimestamp?: string | null;\n /** Optional filter for scores where the environment is one of the provided values. */\n environment?: (string | null)[];\n /** Retrieve only scores from a specific source. */\n source?: ApiScoreSource | null;\n /** Retrieve only scores with value. */\n operator?: string | null;\n /**\n * Retrieve only scores with value.\n * @format double\n */\n value?: number | null;\n /** Comma-separated list of score IDs to limit the results to. */\n scoreIds?: string | null;\n /** Retrieve only scores with a specific configId. */\n configId?: string | null;\n /** Retrieve only scores with a specific annotation queueId. */\n queueId?: string | null;\n /** Retrieve only scores with a specific dataType. */\n dataType?: ApiScoreDataType | null;\n /** Only scores linked to traces that include all of these tags will be returned. */\n traceTags?: (string | null)[];\n}\n\nexport interface ApiSessionsListParams {\n /** Page number, starts at 1 */\n page?: number | null;\n /** Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit. */\n limit?: number | null;\n /**\n * Optional filter to only include sessions created on or after a certain datetime (ISO 8601)\n * @format date-time\n */\n fromTimestamp?: string | null;\n /**\n * Optional filter to only include sessions created before a certain datetime (ISO 8601)\n * @format date-time\n */\n toTimestamp?: string | null;\n /** Optional filter for sessions where the environment is one of the provided values. */\n environment?: (string | null)[];\n}\n\nexport interface ApiTraceListParams {\n /** Page number, starts at 1 */\n page?: number | null;\n /** Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit. */\n limit?: number | null;\n userId?: string | null;\n name?: string | null;\n sessionId?: string | null;\n /**\n * Optional filter to only include traces with a trace.timestamp on or after a certain datetime (ISO 8601)\n * @format date-time\n */\n fromTimestamp?: string | null;\n /**\n * Optional filter to only include traces with a trace.timestamp before a certain datetime (ISO 8601)\n * @format date-time\n */\n toTimestamp?: string | null;\n /** Format of the string [field].[asc/desc]. Fields: id, timestamp, name, userId, release, version, public, bookmarked, sessionId. Example: timestamp.asc */\n orderBy?: string | null;\n /** Only traces that include all of these tags will be returned. */\n tags?: (string | null)[];\n /** Optional filter to only include traces with a certain version. */\n version?: string | null;\n /** Optional filter to only include traces with a certain release. */\n release?: string | null;\n /** Optional filter for traces where the environment is one of the provided values. */\n environment?: (string | null)[];\n /** Comma-separated list of fields to include in the response. Available field groups are 'core' (always included), 'io' (input, output, metadata), 'scores', 'observations', 'metrics'. If not provided, all fields are included. Example: 'core,scores,metrics' */\n fields?: string | null;\n}\n\nexport interface ApiTraceDeleteMultiplePayload {\n /** List of trace IDs to delete */\n traceIds: string[];\n}\n\nexport type QueryParamsType = Record;\nexport type ResponseFormat = keyof Omit;\n\nexport interface FullRequestParams extends Omit {\n /** set parameter to `true` for call `securityWorker` for this request */\n secure?: boolean;\n /** request path */\n path: string;\n /** content type of request body */\n type?: ContentType;\n /** query params */\n query?: QueryParamsType;\n /** format of response (i.e. response.json() -> format: \"json\") */\n format?: ResponseFormat;\n /** request body */\n body?: unknown;\n /** base url */\n baseUrl?: string;\n /** request cancellation token */\n cancelToken?: CancelToken;\n}\n\nexport type RequestParams = Omit;\n\nexport interface ApiConfig {\n baseUrl?: string;\n baseApiParams?: Omit;\n securityWorker?: (securityData: SecurityDataType | null) => Promise | RequestParams | void;\n customFetch?: typeof fetch;\n}\n\nexport interface HttpResponse extends Response {\n data: D;\n error: E;\n}\n\ntype CancelToken = Symbol | string | number;\n\nexport enum ContentType {\n Json = \"application/json\",\n FormData = \"multipart/form-data\",\n UrlEncoded = \"application/x-www-form-urlencoded\",\n Text = \"text/plain\",\n}\n\nexport class HttpClient {\n public baseUrl: string = \"\";\n private securityData: SecurityDataType | null = null;\n private securityWorker?: ApiConfig[\"securityWorker\"];\n private abortControllers = new Map();\n private customFetch = (...fetchParams: Parameters) => fetch(...fetchParams);\n\n private baseApiParams: RequestParams = {\n credentials: \"same-origin\",\n headers: {},\n redirect: \"follow\",\n referrerPolicy: \"no-referrer\",\n };\n\n constructor(apiConfig: ApiConfig = {}) {\n Object.assign(this, apiConfig);\n }\n\n public setSecurityData = (data: SecurityDataType | null) => {\n this.securityData = data;\n };\n\n protected encodeQueryParam(key: string, value: any) {\n const encodedKey = encodeURIComponent(key);\n return `${encodedKey}=${encodeURIComponent(typeof value === \"number\" ? value : `${value}`)}`;\n }\n\n protected addQueryParam(query: QueryParamsType, key: string) {\n return this.encodeQueryParam(key, query[key]);\n }\n\n protected addArrayQueryParam(query: QueryParamsType, key: string) {\n const value = query[key];\n return value.map((v: any) => this.encodeQueryParam(key, v)).join(\"&\");\n }\n\n protected toQueryString(rawQuery?: QueryParamsType): string {\n const query = rawQuery || {};\n const keys = Object.keys(query).filter((key) => \"undefined\" !== typeof query[key]);\n return keys\n .map((key) => (Array.isArray(query[key]) ? this.addArrayQueryParam(query, key) : this.addQueryParam(query, key)))\n .join(\"&\");\n }\n\n protected addQueryParams(rawQuery?: QueryParamsType): string {\n const queryString = this.toQueryString(rawQuery);\n return queryString ? `?${queryString}` : \"\";\n }\n\n private contentFormatters: Record any> = {\n [ContentType.Json]: (input: any) =>\n input !== null && (typeof input === \"object\" || typeof input === \"string\") ? JSON.stringify(input) : input,\n [ContentType.Text]: (input: any) => (input !== null && typeof input !== \"string\" ? JSON.stringify(input) : input),\n [ContentType.FormData]: (input: any) =>\n Object.keys(input || {}).reduce((formData, key) => {\n const property = input[key];\n formData.append(\n key,\n property instanceof Blob\n ? property\n : typeof property === \"object\" && property !== null\n ? JSON.stringify(property)\n : `${property}`\n );\n return formData;\n }, new FormData()),\n [ContentType.UrlEncoded]: (input: any) => this.toQueryString(input),\n };\n\n protected mergeRequestParams(params1: RequestParams, params2?: RequestParams): RequestParams {\n return {\n ...this.baseApiParams,\n ...params1,\n ...(params2 || {}),\n headers: {\n ...(this.baseApiParams.headers || {}),\n ...(params1.headers || {}),\n ...((params2 && params2.headers) || {}),\n },\n };\n }\n\n protected createAbortSignal = (cancelToken: CancelToken): AbortSignal | undefined => {\n if (this.abortControllers.has(cancelToken)) {\n const abortController = this.abortControllers.get(cancelToken);\n if (abortController) {\n return abortController.signal;\n }\n return void 0;\n }\n\n const abortController = new AbortController();\n this.abortControllers.set(cancelToken, abortController);\n return abortController.signal;\n };\n\n public abortRequest = (cancelToken: CancelToken) => {\n const abortController = this.abortControllers.get(cancelToken);\n\n if (abortController) {\n abortController.abort();\n this.abortControllers.delete(cancelToken);\n }\n };\n\n public request = async ({\n body,\n secure,\n path,\n type,\n query,\n format,\n baseUrl,\n cancelToken,\n ...params\n }: FullRequestParams): Promise => {\n const secureParams =\n ((typeof secure === \"boolean\" ? secure : this.baseApiParams.secure) &&\n this.securityWorker &&\n (await this.securityWorker(this.securityData))) ||\n {};\n const requestParams = this.mergeRequestParams(params, secureParams);\n const queryString = query && this.toQueryString(query);\n const payloadFormatter = this.contentFormatters[type || ContentType.Json];\n const responseFormat = format || requestParams.format;\n\n return this.customFetch(`${baseUrl || this.baseUrl || \"\"}${path}${queryString ? `?${queryString}` : \"\"}`, {\n ...requestParams,\n headers: {\n ...(requestParams.headers || {}),\n ...(type && type !== ContentType.FormData ? { \"Content-Type\": type } : {}),\n },\n signal: (cancelToken ? this.createAbortSignal(cancelToken) : requestParams.signal) || null,\n body: typeof body === \"undefined\" || body === null ? null : payloadFormatter(body),\n }).then(async (response) => {\n const r = response.clone() as HttpResponse;\n r.data = null as unknown as T;\n r.error = null as unknown as E;\n\n const data = !responseFormat\n ? r\n : await response[responseFormat]()\n .then((data) => {\n if (r.ok) {\n r.data = data;\n } else {\n r.error = data;\n }\n return r;\n })\n .catch((e) => {\n r.error = e;\n return r;\n });\n\n if (cancelToken) {\n this.abortControllers.delete(cancelToken);\n }\n\n if (!response.ok) throw data;\n return data.data;\n });\n };\n}\n\n/**\n * @title langfuse\n *\n * ## Authentication\n *\n * Authenticate with the API using [Basic Auth](https://en.wikipedia.org/wiki/Basic_access_authentication), get API keys in the project settings:\n *\n * - username: Langfuse Public Key\n * - password: Langfuse Secret Key\n *\n * ## Exports\n *\n * - OpenAPI spec: https://cloud.langfuse.com/generated/api/openapi.yml\n * - Postman collection: https://cloud.langfuse.com/generated/postman/collection.json\n */\nexport class LangfusePublicApi extends HttpClient {\n api = {\n /**\n * @description Add an item to an annotation queue\n *\n * @tags AnnotationQueues\n * @name AnnotationQueuesCreateQueueItem\n * @request POST:/api/public/annotation-queues/{queueId}/items\n * @secure\n */\n annotationQueuesCreateQueueItem: (\n queueId: string,\n data: ApiCreateAnnotationQueueItemRequest,\n params: RequestParams = {}\n ) =>\n this.request({\n path: `/api/public/annotation-queues/${queueId}/items`,\n method: \"POST\",\n body: data,\n secure: true,\n type: ContentType.Json,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Remove an item from an annotation queue\n *\n * @tags AnnotationQueues\n * @name AnnotationQueuesDeleteQueueItem\n * @request DELETE:/api/public/annotation-queues/{queueId}/items/{itemId}\n * @secure\n */\n annotationQueuesDeleteQueueItem: (queueId: string, itemId: string, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/annotation-queues/${queueId}/items/${itemId}`,\n method: \"DELETE\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get an annotation queue by ID\n *\n * @tags AnnotationQueues\n * @name AnnotationQueuesGetQueue\n * @request GET:/api/public/annotation-queues/{queueId}\n * @secure\n */\n annotationQueuesGetQueue: (queueId: string, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/annotation-queues/${queueId}`,\n method: \"GET\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get a specific item from an annotation queue\n *\n * @tags AnnotationQueues\n * @name AnnotationQueuesGetQueueItem\n * @request GET:/api/public/annotation-queues/{queueId}/items/{itemId}\n * @secure\n */\n annotationQueuesGetQueueItem: (queueId: string, itemId: string, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/annotation-queues/${queueId}/items/${itemId}`,\n method: \"GET\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get items for a specific annotation queue\n *\n * @tags AnnotationQueues\n * @name AnnotationQueuesListQueueItems\n * @request GET:/api/public/annotation-queues/{queueId}/items\n * @secure\n */\n annotationQueuesListQueueItems: (\n { queueId, ...query }: ApiAnnotationQueuesListQueueItemsParams,\n params: RequestParams = {}\n ) =>\n this.request({\n path: `/api/public/annotation-queues/${queueId}/items`,\n method: \"GET\",\n query: query,\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get all annotation queues\n *\n * @tags AnnotationQueues\n * @name AnnotationQueuesListQueues\n * @request GET:/api/public/annotation-queues\n * @secure\n */\n annotationQueuesListQueues: (query: ApiAnnotationQueuesListQueuesParams, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/annotation-queues`,\n method: \"GET\",\n query: query,\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Update an annotation queue item\n *\n * @tags AnnotationQueues\n * @name AnnotationQueuesUpdateQueueItem\n * @request PATCH:/api/public/annotation-queues/{queueId}/items/{itemId}\n * @secure\n */\n annotationQueuesUpdateQueueItem: (\n queueId: string,\n itemId: string,\n data: ApiUpdateAnnotationQueueItemRequest,\n params: RequestParams = {}\n ) =>\n this.request({\n path: `/api/public/annotation-queues/${queueId}/items/${itemId}`,\n method: \"PATCH\",\n body: data,\n secure: true,\n type: ContentType.Json,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Create a comment. Comments may be attached to different object types (trace, observation, session, prompt).\n *\n * @tags Comments\n * @name CommentsCreate\n * @request POST:/api/public/comments\n * @secure\n */\n commentsCreate: (data: ApiCreateCommentRequest, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/comments`,\n method: \"POST\",\n body: data,\n secure: true,\n type: ContentType.Json,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get all comments\n *\n * @tags Comments\n * @name CommentsGet\n * @request GET:/api/public/comments\n * @secure\n */\n commentsGet: (query: ApiCommentsGetParams, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/comments`,\n method: \"GET\",\n query: query,\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get a comment by id\n *\n * @tags Comments\n * @name CommentsGetById\n * @request GET:/api/public/comments/{commentId}\n * @secure\n */\n commentsGetById: (commentId: string, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/comments/${commentId}`,\n method: \"GET\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Create a dataset item\n *\n * @tags DatasetItems\n * @name DatasetItemsCreate\n * @request POST:/api/public/dataset-items\n * @secure\n */\n datasetItemsCreate: (data: ApiCreateDatasetItemRequest, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/dataset-items`,\n method: \"POST\",\n body: data,\n secure: true,\n type: ContentType.Json,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Delete a dataset item and all its run items. This action is irreversible.\n *\n * @tags DatasetItems\n * @name DatasetItemsDelete\n * @request DELETE:/api/public/dataset-items/{id}\n * @secure\n */\n datasetItemsDelete: (id: string, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/dataset-items/${id}`,\n method: \"DELETE\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get a dataset item\n *\n * @tags DatasetItems\n * @name DatasetItemsGet\n * @request GET:/api/public/dataset-items/{id}\n * @secure\n */\n datasetItemsGet: (id: string, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/dataset-items/${id}`,\n method: \"GET\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get dataset items\n *\n * @tags DatasetItems\n * @name DatasetItemsList\n * @request GET:/api/public/dataset-items\n * @secure\n */\n datasetItemsList: (query: ApiDatasetItemsListParams, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/dataset-items`,\n method: \"GET\",\n query: query,\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Create a dataset run item\n *\n * @tags DatasetRunItems\n * @name DatasetRunItemsCreate\n * @request POST:/api/public/dataset-run-items\n * @secure\n */\n datasetRunItemsCreate: (data: ApiCreateDatasetRunItemRequest, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/dataset-run-items`,\n method: \"POST\",\n body: data,\n secure: true,\n type: ContentType.Json,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description List dataset run items\n *\n * @tags DatasetRunItems\n * @name DatasetRunItemsList\n * @request GET:/api/public/dataset-run-items\n * @secure\n */\n datasetRunItemsList: (query: ApiDatasetRunItemsListParams, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/dataset-run-items`,\n method: \"GET\",\n query: query,\n secure: true,\n ...params,\n }),\n\n /**\n * @description Create a dataset\n *\n * @tags Datasets\n * @name DatasetsCreate\n * @request POST:/api/public/v2/datasets\n * @secure\n */\n datasetsCreate: (data: ApiCreateDatasetRequest, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/v2/datasets`,\n method: \"POST\",\n body: data,\n secure: true,\n type: ContentType.Json,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Delete a dataset run and all its run items. This action is irreversible.\n *\n * @tags Datasets\n * @name DatasetsDeleteRun\n * @request DELETE:/api/public/datasets/{datasetName}/runs/{runName}\n * @secure\n */\n datasetsDeleteRun: (datasetName: string, runName: string, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/datasets/${datasetName}/runs/${runName}`,\n method: \"DELETE\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get a dataset\n *\n * @tags Datasets\n * @name DatasetsGet\n * @request GET:/api/public/v2/datasets/{datasetName}\n * @secure\n */\n datasetsGet: (datasetName: string, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/v2/datasets/${datasetName}`,\n method: \"GET\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get a dataset run and its items\n *\n * @tags Datasets\n * @name DatasetsGetRun\n * @request GET:/api/public/datasets/{datasetName}/runs/{runName}\n * @secure\n */\n datasetsGetRun: (datasetName: string, runName: string, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/datasets/${datasetName}/runs/${runName}`,\n method: \"GET\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get dataset runs\n *\n * @tags Datasets\n * @name DatasetsGetRuns\n * @request GET:/api/public/datasets/{datasetName}/runs\n * @secure\n */\n datasetsGetRuns: ({ datasetName, ...query }: ApiDatasetsGetRunsParams, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/datasets/${datasetName}/runs`,\n method: \"GET\",\n query: query,\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get all datasets\n *\n * @tags Datasets\n * @name DatasetsList\n * @request GET:/api/public/v2/datasets\n * @secure\n */\n datasetsList: (query: ApiDatasetsListParams, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/v2/datasets`,\n method: \"GET\",\n query: query,\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Check health of API and database\n *\n * @tags Health\n * @name HealthHealth\n * @request GET:/api/public/health\n */\n healthHealth: (params: RequestParams = {}) =>\n this.request({\n path: `/api/public/health`,\n method: \"GET\",\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Batched ingestion for Langfuse Tracing. If you want to use tracing via the API, such as to build your own Langfuse client implementation, this is the only API route you need to implement. Within each batch, there can be multiple events. Each event has a type, an id, a timestamp, metadata and a body. Internally, we refer to this as the \"event envelope\" as it tells us something about the event but not the trace. We use the event id within this envelope to deduplicate messages to avoid processing the same event twice, i.e. the event id should be unique per request. The event.body.id is the ID of the actual trace and will be used for updates and will be visible within the Langfuse App. I.e. if you want to update a trace, you'd use the same body id, but separate event IDs. Notes: - Introduction to data model: https://langfuse.com/docs/tracing-data-model - Batch sizes are limited to 3.5 MB in total. You need to adjust the number of events per batch accordingly. - The API does not return a 4xx status code for input errors. Instead, it responds with a 207 status code, which includes a list of the encountered errors.\n *\n * @tags Ingestion\n * @name IngestionBatch\n * @request POST:/api/public/ingestion\n * @secure\n */\n ingestionBatch: (data: ApiIngestionBatchPayload, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/ingestion`,\n method: \"POST\",\n body: data,\n secure: true,\n type: ContentType.Json,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get a media record\n *\n * @tags Media\n * @name MediaGet\n * @request GET:/api/public/media/{mediaId}\n * @secure\n */\n mediaGet: (mediaId: string, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/media/${mediaId}`,\n method: \"GET\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get a presigned upload URL for a media record\n *\n * @tags Media\n * @name MediaGetUploadUrl\n * @request POST:/api/public/media\n * @secure\n */\n mediaGetUploadUrl: (data: ApiGetMediaUploadUrlRequest, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/media`,\n method: \"POST\",\n body: data,\n secure: true,\n type: ContentType.Json,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Patch a media record\n *\n * @tags Media\n * @name MediaPatch\n * @request PATCH:/api/public/media/{mediaId}\n * @secure\n */\n mediaPatch: (mediaId: string, data: ApiPatchMediaBody, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/media/${mediaId}`,\n method: \"PATCH\",\n body: data,\n secure: true,\n type: ContentType.Json,\n ...params,\n }),\n\n /**\n * @description Get metrics from the Langfuse project using a query object\n *\n * @tags Metrics\n * @name MetricsMetrics\n * @request GET:/api/public/metrics\n * @secure\n */\n metricsMetrics: (query: ApiMetricsMetricsParams, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/metrics`,\n method: \"GET\",\n query: query,\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Create a model\n *\n * @tags Models\n * @name ModelsCreate\n * @request POST:/api/public/models\n * @secure\n */\n modelsCreate: (data: ApiCreateModelRequest, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/models`,\n method: \"POST\",\n body: data,\n secure: true,\n type: ContentType.Json,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Delete a model. Cannot delete models managed by Langfuse. You can create your own definition with the same modelName to override the definition though.\n *\n * @tags Models\n * @name ModelsDelete\n * @request DELETE:/api/public/models/{id}\n * @secure\n */\n modelsDelete: (id: string, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/models/${id}`,\n method: \"DELETE\",\n secure: true,\n ...params,\n }),\n\n /**\n * @description Get a model\n *\n * @tags Models\n * @name ModelsGet\n * @request GET:/api/public/models/{id}\n * @secure\n */\n modelsGet: (id: string, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/models/${id}`,\n method: \"GET\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get all models\n *\n * @tags Models\n * @name ModelsList\n * @request GET:/api/public/models\n * @secure\n */\n modelsList: (query: ApiModelsListParams, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/models`,\n method: \"GET\",\n query: query,\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get a observation\n *\n * @tags Observations\n * @name ObservationsGet\n * @request GET:/api/public/observations/{observationId}\n * @secure\n */\n observationsGet: (observationId: string, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/observations/${observationId}`,\n method: \"GET\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get a list of observations\n *\n * @tags Observations\n * @name ObservationsGetMany\n * @request GET:/api/public/observations\n * @secure\n */\n observationsGetMany: (query: ApiObservationsGetManyParams, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/observations`,\n method: \"GET\",\n query: query,\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get all memberships for the organization associated with the API key (requires organization-scoped API key)\n *\n * @tags Organizations\n * @name OrganizationsGetOrganizationMemberships\n * @request GET:/api/public/organizations/memberships\n * @secure\n */\n organizationsGetOrganizationMemberships: (params: RequestParams = {}) =>\n this.request({\n path: `/api/public/organizations/memberships`,\n method: \"GET\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get all projects for the organization associated with the API key (requires organization-scoped API key)\n *\n * @tags Organizations\n * @name OrganizationsGetOrganizationProjects\n * @request GET:/api/public/organizations/projects\n * @secure\n */\n organizationsGetOrganizationProjects: (params: RequestParams = {}) =>\n this.request({\n path: `/api/public/organizations/projects`,\n method: \"GET\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get all memberships for a specific project (requires organization-scoped API key)\n *\n * @tags Organizations\n * @name OrganizationsGetProjectMemberships\n * @request GET:/api/public/projects/{projectId}/memberships\n * @secure\n */\n organizationsGetProjectMemberships: (projectId: string, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/projects/${projectId}/memberships`,\n method: \"GET\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Create or update a membership for the organization associated with the API key (requires organization-scoped API key)\n *\n * @tags Organizations\n * @name OrganizationsUpdateOrganizationMembership\n * @request PUT:/api/public/organizations/memberships\n * @secure\n */\n organizationsUpdateOrganizationMembership: (data: ApiMembershipRequest, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/organizations/memberships`,\n method: \"PUT\",\n body: data,\n secure: true,\n type: ContentType.Json,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Create or update a membership for a specific project (requires organization-scoped API key). The user must already be a member of the organization.\n *\n * @tags Organizations\n * @name OrganizationsUpdateProjectMembership\n * @request PUT:/api/public/projects/{projectId}/memberships\n * @secure\n */\n organizationsUpdateProjectMembership: (projectId: string, data: ApiMembershipRequest, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/projects/${projectId}/memberships`,\n method: \"PUT\",\n body: data,\n secure: true,\n type: ContentType.Json,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Create a new project (requires organization-scoped API key)\n *\n * @tags Projects\n * @name ProjectsCreate\n * @request POST:/api/public/projects\n * @secure\n */\n projectsCreate: (data: ApiProjectsCreatePayload, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/projects`,\n method: \"POST\",\n body: data,\n secure: true,\n type: ContentType.Json,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Create a new API key for a project (requires organization-scoped API key)\n *\n * @tags Projects\n * @name ProjectsCreateApiKey\n * @request POST:/api/public/projects/{projectId}/apiKeys\n * @secure\n */\n projectsCreateApiKey: (projectId: string, data: ApiProjectsCreateApiKeyPayload, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/projects/${projectId}/apiKeys`,\n method: \"POST\",\n body: data,\n secure: true,\n type: ContentType.Json,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Delete a project by ID (requires organization-scoped API key). Project deletion is processed asynchronously.\n *\n * @tags Projects\n * @name ProjectsDelete\n * @request DELETE:/api/public/projects/{projectId}\n * @secure\n */\n projectsDelete: (projectId: string, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/projects/${projectId}`,\n method: \"DELETE\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Delete an API key for a project (requires organization-scoped API key)\n *\n * @tags Projects\n * @name ProjectsDeleteApiKey\n * @request DELETE:/api/public/projects/{projectId}/apiKeys/{apiKeyId}\n * @secure\n */\n projectsDeleteApiKey: (projectId: string, apiKeyId: string, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/projects/${projectId}/apiKeys/${apiKeyId}`,\n method: \"DELETE\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get Project associated with API key\n *\n * @tags Projects\n * @name ProjectsGet\n * @request GET:/api/public/projects\n * @secure\n */\n projectsGet: (params: RequestParams = {}) =>\n this.request({\n path: `/api/public/projects`,\n method: \"GET\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get all API keys for a project (requires organization-scoped API key)\n *\n * @tags Projects\n * @name ProjectsGetApiKeys\n * @request GET:/api/public/projects/{projectId}/apiKeys\n * @secure\n */\n projectsGetApiKeys: (projectId: string, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/projects/${projectId}/apiKeys`,\n method: \"GET\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Update a project by ID (requires organization-scoped API key).\n *\n * @tags Projects\n * @name ProjectsUpdate\n * @request PUT:/api/public/projects/{projectId}\n * @secure\n */\n projectsUpdate: (projectId: string, data: ApiProjectsUpdatePayload, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/projects/${projectId}`,\n method: \"PUT\",\n body: data,\n secure: true,\n type: ContentType.Json,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Create a new version for the prompt with the given `name`\n *\n * @tags Prompts\n * @name PromptsCreate\n * @request POST:/api/public/v2/prompts\n * @secure\n */\n promptsCreate: (data: ApiCreatePromptRequest, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/v2/prompts`,\n method: \"POST\",\n body: data,\n secure: true,\n type: ContentType.Json,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get a prompt\n *\n * @tags Prompts\n * @name PromptsGet\n * @request GET:/api/public/v2/prompts/{promptName}\n * @secure\n */\n promptsGet: ({ promptName, ...query }: ApiPromptsGetParams, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/v2/prompts/${promptName}`,\n method: \"GET\",\n query: query,\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get a list of prompt names with versions and labels\n *\n * @tags Prompts\n * @name PromptsList\n * @request GET:/api/public/v2/prompts\n * @secure\n */\n promptsList: (query: ApiPromptsListParams, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/v2/prompts`,\n method: \"GET\",\n query: query,\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Update labels for a specific prompt version\n *\n * @tags PromptVersion\n * @name PromptVersionUpdate\n * @request PATCH:/api/public/v2/prompts/{name}/versions/{version}\n * @secure\n */\n promptVersionUpdate: (\n name: string,\n version: number,\n data: ApiPromptVersionUpdatePayload,\n params: RequestParams = {}\n ) =>\n this.request({\n path: `/api/public/v2/prompts/${name}/versions/${version}`,\n method: \"PATCH\",\n body: data,\n secure: true,\n type: ContentType.Json,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Create a new user in the organization (requires organization-scoped API key)\n *\n * @tags Scim\n * @name ScimCreateUser\n * @request POST:/api/public/scim/Users\n * @secure\n */\n scimCreateUser: (data: ApiScimCreateUserPayload, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/scim/Users`,\n method: \"POST\",\n body: data,\n secure: true,\n type: ContentType.Json,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Remove a user from the organization (requires organization-scoped API key). Note that this only removes the user from the organization but does not delete the user entity itself.\n *\n * @tags Scim\n * @name ScimDeleteUser\n * @request DELETE:/api/public/scim/Users/{userId}\n * @secure\n */\n scimDeleteUser: (userId: string, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/scim/Users/${userId}`,\n method: \"DELETE\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get SCIM Resource Types (requires organization-scoped API key)\n *\n * @tags Scim\n * @name ScimGetResourceTypes\n * @request GET:/api/public/scim/ResourceTypes\n * @secure\n */\n scimGetResourceTypes: (params: RequestParams = {}) =>\n this.request({\n path: `/api/public/scim/ResourceTypes`,\n method: \"GET\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get SCIM Schemas (requires organization-scoped API key)\n *\n * @tags Scim\n * @name ScimGetSchemas\n * @request GET:/api/public/scim/Schemas\n * @secure\n */\n scimGetSchemas: (params: RequestParams = {}) =>\n this.request({\n path: `/api/public/scim/Schemas`,\n method: \"GET\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get SCIM Service Provider Configuration (requires organization-scoped API key)\n *\n * @tags Scim\n * @name ScimGetServiceProviderConfig\n * @request GET:/api/public/scim/ServiceProviderConfig\n * @secure\n */\n scimGetServiceProviderConfig: (params: RequestParams = {}) =>\n this.request({\n path: `/api/public/scim/ServiceProviderConfig`,\n method: \"GET\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get a specific user by ID (requires organization-scoped API key)\n *\n * @tags Scim\n * @name ScimGetUser\n * @request GET:/api/public/scim/Users/{userId}\n * @secure\n */\n scimGetUser: (userId: string, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/scim/Users/${userId}`,\n method: \"GET\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description List users in the organization (requires organization-scoped API key)\n *\n * @tags Scim\n * @name ScimListUsers\n * @request GET:/api/public/scim/Users\n * @secure\n */\n scimListUsers: (query: ApiScimListUsersParams, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/scim/Users`,\n method: \"GET\",\n query: query,\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Create a score configuration (config). Score configs are used to define the structure of scores\n *\n * @tags ScoreConfigs\n * @name ScoreConfigsCreate\n * @request POST:/api/public/score-configs\n * @secure\n */\n scoreConfigsCreate: (data: ApiCreateScoreConfigRequest, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/score-configs`,\n method: \"POST\",\n body: data,\n secure: true,\n type: ContentType.Json,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get all score configs\n *\n * @tags ScoreConfigs\n * @name ScoreConfigsGet\n * @request GET:/api/public/score-configs\n * @secure\n */\n scoreConfigsGet: (query: ApiScoreConfigsGetParams, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/score-configs`,\n method: \"GET\",\n query: query,\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get a score config\n *\n * @tags ScoreConfigs\n * @name ScoreConfigsGetById\n * @request GET:/api/public/score-configs/{configId}\n * @secure\n */\n scoreConfigsGetById: (configId: string, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/score-configs/${configId}`,\n method: \"GET\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Create a score (supports both trace and session scores)\n *\n * @tags Score\n * @name ScoreCreate\n * @request POST:/api/public/scores\n * @secure\n */\n scoreCreate: (data: ApiCreateScoreRequest, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/scores`,\n method: \"POST\",\n body: data,\n secure: true,\n type: ContentType.Json,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Delete a score (supports both trace and session scores)\n *\n * @tags Score\n * @name ScoreDelete\n * @request DELETE:/api/public/scores/{scoreId}\n * @secure\n */\n scoreDelete: (scoreId: string, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/scores/${scoreId}`,\n method: \"DELETE\",\n secure: true,\n ...params,\n }),\n\n /**\n * @description Get a list of scores (supports both trace and session scores)\n *\n * @tags ScoreV2\n * @name ScoreV2Get\n * @request GET:/api/public/v2/scores\n * @secure\n */\n scoreV2Get: (query: ApiScoreV2GetParams, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/v2/scores`,\n method: \"GET\",\n query: query,\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get a score (supports both trace and session scores)\n *\n * @tags ScoreV2\n * @name ScoreV2GetById\n * @request GET:/api/public/v2/scores/{scoreId}\n * @secure\n */\n scoreV2GetById: (scoreId: string, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/v2/scores/${scoreId}`,\n method: \"GET\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get a session. Please note that `traces` on this endpoint are not paginated, if you plan to fetch large sessions, consider `GET /api/public/traces?sessionId=`\n *\n * @tags Sessions\n * @name SessionsGet\n * @request GET:/api/public/sessions/{sessionId}\n * @secure\n */\n sessionsGet: (sessionId: string, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/sessions/${sessionId}`,\n method: \"GET\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get sessions\n *\n * @tags Sessions\n * @name SessionsList\n * @request GET:/api/public/sessions\n * @secure\n */\n sessionsList: (query: ApiSessionsListParams, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/sessions`,\n method: \"GET\",\n query: query,\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Delete a specific trace\n *\n * @tags Trace\n * @name TraceDelete\n * @request DELETE:/api/public/traces/{traceId}\n * @secure\n */\n traceDelete: (traceId: string, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/traces/${traceId}`,\n method: \"DELETE\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Delete multiple traces\n *\n * @tags Trace\n * @name TraceDeleteMultiple\n * @request DELETE:/api/public/traces\n * @secure\n */\n traceDeleteMultiple: (data: ApiTraceDeleteMultiplePayload, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/traces`,\n method: \"DELETE\",\n body: data,\n secure: true,\n type: ContentType.Json,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get a specific trace\n *\n * @tags Trace\n * @name TraceGet\n * @request GET:/api/public/traces/{traceId}\n * @secure\n */\n traceGet: (traceId: string, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/traces/${traceId}`,\n method: \"GET\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get list of traces\n *\n * @tags Trace\n * @name TraceList\n * @request GET:/api/public/traces\n * @secure\n */\n traceList: (query: ApiTraceListParams, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/traces`,\n method: \"GET\",\n query: query,\n secure: true,\n format: \"json\",\n ...params,\n }),\n };\n}\n", "import {\n LangfuseCore,\n LangfuseWebStateless,\n type LangfuseFetchOptions,\n type LangfuseFetchResponse,\n type LangfusePersistedProperty,\n utils,\n} from \"langfuse-core\";\nimport { type LangfuseStorage, getStorage } from \"./storage\";\nimport { LangfusePublicApi } from \"./publicApi\";\nimport { version } from \"../package.json\";\nimport { type LangfuseOptions } from \"./types\";\n\nexport type * from \"./publicApi\";\nexport type {\n LangfusePromptClient,\n ChatPromptClient,\n TextPromptClient,\n LangfusePromptRecord,\n LangfuseTraceClient,\n LangfuseSpanClient,\n LangfuseEventClient,\n LangfuseGenerationClient,\n} from \"langfuse-core\";\n\n// Required when users pass these as typed arguments\nexport { LangfuseMedia } from \"langfuse-core\";\n\nexport class Langfuse extends LangfuseCore {\n private _storage: LangfuseStorage;\n private _storageCache: any;\n private _storageKey: string;\n public api: LangfusePublicApi[\"api\"];\n\n constructor(params?: { publicKey?: string; secretKey?: string } & LangfuseOptions) {\n const langfuseConfig = utils.configLangfuseSDK(params);\n super(langfuseConfig);\n\n if (typeof window !== \"undefined\" && \"Deno\" in window === false) {\n this._storageKey = params?.persistence_name\n ? `lf_${params.persistence_name}`\n : `lf_${langfuseConfig.publicKey}_langfuse`;\n this._storage = getStorage(params?.persistence || \"localStorage\", window);\n } else {\n this._storageKey = `lf_${langfuseConfig.publicKey}_langfuse`;\n this._storage = getStorage(\"memory\", undefined);\n }\n\n this.api = new LangfusePublicApi({\n baseUrl: this.baseUrl,\n baseApiParams: {\n headers: {\n \"X-Langfuse-Sdk-Name\": \"langfuse-js\",\n \"X-Langfuse-Sdk-Version\": this.getLibraryVersion(),\n \"X-Langfuse-Sdk-Variant\": this.getLibraryId(),\n \"X-Langfuse-Sdk-Integration\": this.sdkIntegration,\n \"X-Langfuse-Public-Key\": this.publicKey,\n ...this.additionalHeaders,\n ...this.constructAuthorizationHeader(this.publicKey, this.secretKey),\n },\n },\n }).api;\n }\n\n getPersistedProperty(key: LangfusePersistedProperty): T | undefined {\n if (!this._storageCache) {\n this._storageCache = JSON.parse(this._storage.getItem(this._storageKey) || \"{}\") || {};\n }\n\n return this._storageCache[key];\n }\n\n setPersistedProperty(key: LangfusePersistedProperty, value: T | null): void {\n if (!this._storageCache) {\n this._storageCache = JSON.parse(this._storage.getItem(this._storageKey) || \"{}\") || {};\n }\n\n if (value === null) {\n delete this._storageCache[key];\n } else {\n this._storageCache[key] = value;\n }\n\n this._storage.setItem(this._storageKey, JSON.stringify(this._storageCache));\n }\n\n fetch(url: string, options: LangfuseFetchOptions): Promise {\n return fetch(url, options);\n }\n\n getLibraryId(): string {\n return \"langfuse\";\n }\n\n getLibraryVersion(): string {\n return version;\n }\n\n getCustomUserAgent(): void {\n return;\n }\n}\n\nexport class LangfuseWeb extends LangfuseWebStateless {\n private _storage: LangfuseStorage;\n private _storageCache: any;\n private _storageKey: string;\n\n constructor(params?: Omit) {\n const langfuseConfig = utils.configLangfuseSDK(params, false);\n super(langfuseConfig);\n\n if (typeof window !== \"undefined\") {\n this._storageKey = params?.persistence_name\n ? `lf_${params.persistence_name}`\n : `lf_${langfuseConfig.publicKey}_langfuse`;\n this._storage = getStorage(params?.persistence || \"localStorage\", window);\n } else {\n this._storageKey = `lf_${langfuseConfig.publicKey}_langfuse`;\n this._storage = getStorage(\"memory\", undefined);\n }\n }\n\n getPersistedProperty(key: LangfusePersistedProperty): T | undefined {\n if (!this._storageCache) {\n this._storageCache = JSON.parse(this._storage.getItem(this._storageKey) || \"{}\") || {};\n }\n\n return this._storageCache[key];\n }\n\n setPersistedProperty(key: LangfusePersistedProperty, value: T | null): void {\n if (!this._storageCache) {\n this._storageCache = JSON.parse(this._storage.getItem(this._storageKey) || \"{}\") || {};\n }\n\n if (value === null) {\n delete this._storageCache[key];\n } else {\n this._storageCache[key] = value;\n }\n\n this._storage.setItem(this._storageKey, JSON.stringify(this._storageCache));\n }\n\n fetch(url: string, options: LangfuseFetchOptions): Promise {\n return fetch(url, options);\n }\n\n getLibraryId(): string {\n return \"langfuse-frontend\";\n }\n\n getLibraryVersion(): string {\n return version;\n }\n\n getCustomUserAgent(): void {\n return;\n }\n}\n", "import { Langfuse } from \"../langfuse\";\nimport type { LangfuseInitParams } from \"./types\";\n\n/**\n * Represents a singleton instance of the Langfuse client.\n */\nexport class LangfuseSingleton {\n private static instance: Langfuse | null = null; // Lazy initialization\n\n /**\n * Returns the singleton instance of the Langfuse client.\n * @param params Optional parameters for initializing the Langfuse instance. Only used for the first call.\n * @returns The singleton instance of the Langfuse client.\n */\n public static getInstance(params?: LangfuseInitParams): Langfuse {\n if (!LangfuseSingleton.instance) {\n LangfuseSingleton.instance = new Langfuse(params);\n }\n return LangfuseSingleton.instance;\n }\n}\n", "import type OpenAI from \"openai\";\nimport type { CreateLangfuseGenerationBody, Usage, UsageDetails } from \"langfuse-core\";\n\ntype ParsedOpenAIArguments = {\n model: string;\n input: Record | string;\n modelParameters: Record;\n};\n\nexport const parseInputArgs = (args: Record): ParsedOpenAIArguments => {\n let params: Record = {};\n params = {\n frequency_penalty: args.frequency_penalty,\n logit_bias: args.logit_bias,\n logprobs: args.logprobs,\n max_tokens: args.max_tokens,\n n: args.n,\n presence_penalty: args.presence_penalty,\n seed: args.seed,\n stop: args.stop,\n stream: args.stream,\n temperature: args.temperature,\n top_p: args.top_p,\n user: args.user,\n response_format: args.response_format,\n top_logprobs: args.top_logprobs,\n };\n\n let input: Record | string = args.input;\n\n if (args && typeof args === \"object\" && !Array.isArray(args) && \"messages\" in args) {\n input = {};\n input.messages = args.messages;\n if (\"function_call\" in args) {\n input.function_call = args.function_call;\n }\n if (\"functions\" in args) {\n input.functions = args.functions;\n }\n if (\"tools\" in args) {\n input.tools = args.tools;\n }\n\n if (\"tool_choice\" in args) {\n input.tool_choice = args.tool_choice;\n }\n } else if (!input) {\n input = args.prompt;\n }\n\n return {\n model: args.model,\n input: input,\n modelParameters: params,\n };\n};\n\nexport const parseCompletionOutput = (res: unknown): CreateLangfuseGenerationBody[\"output\"] => {\n if (res instanceof Object && \"output_text\" in res && res[\"output_text\"] !== \"\") {\n return res[\"output_text\"] as string;\n }\n\n if (typeof res === \"object\" && res && \"output\" in res && Array.isArray(res[\"output\"])) {\n const output = res[\"output\"];\n\n if (output.length > 1) {\n return output;\n }\n if (output.length === 1) {\n return output[0] as Record;\n }\n\n return null;\n }\n\n if (!(res instanceof Object && \"choices\" in res && Array.isArray(res.choices))) {\n return \"\";\n }\n\n return \"message\" in res.choices[0] ? res.choices[0].message : res.choices[0].text ?? \"\";\n};\n\nexport const parseUsage = (res: unknown): Usage | undefined => {\n if (hasCompletionUsage(res)) {\n const { prompt_tokens, completion_tokens, total_tokens } = res.usage;\n\n return {\n input: prompt_tokens,\n output: completion_tokens,\n total: total_tokens,\n };\n }\n};\n\nexport const parseUsageDetails = (completionUsage: OpenAI.CompletionUsage): UsageDetails | undefined => {\n if (\"prompt_tokens\" in completionUsage) {\n const { prompt_tokens, completion_tokens, total_tokens, completion_tokens_details, prompt_tokens_details } =\n completionUsage;\n\n return {\n input: prompt_tokens,\n output: completion_tokens,\n total: total_tokens,\n ...Object.fromEntries(\n Object.entries(prompt_tokens_details ?? {}).map(([key, value]) => [`input_${key}`, value as number])\n ),\n ...Object.fromEntries(\n Object.entries(completion_tokens_details ?? {}).map(([key, value]) => [`output_${key}`, value as number])\n ),\n };\n } else if (\"input_tokens\" in completionUsage) {\n const { input_tokens, output_tokens, total_tokens, input_tokens_details, output_tokens_details } = completionUsage;\n\n return {\n input: input_tokens,\n output: output_tokens,\n total: total_tokens,\n ...Object.fromEntries(\n Object.entries(input_tokens_details ?? {}).map(([key, value]) => [`input_${key}`, value as number])\n ),\n ...Object.fromEntries(\n Object.entries(output_tokens_details ?? {}).map(([key, value]) => [`output_${key}`, value as number])\n ),\n };\n }\n};\n\nexport const parseUsageDetailsFromResponse = (res: unknown): UsageDetails | undefined => {\n if (hasCompletionUsage(res)) {\n return parseUsageDetails(res.usage);\n }\n};\n\nexport const parseChunk = (\n rawChunk: unknown\n):\n | { isToolCall: false; data: string }\n | { isToolCall: true; data: OpenAI.Chat.Completions.ChatCompletionChunk.Choice.Delta.ToolCall } => {\n let isToolCall = false;\n const _chunk = rawChunk as OpenAI.ChatCompletionChunk | OpenAI.Completions.Completion;\n const chunkData = _chunk?.choices?.[0];\n\n try {\n if (\"delta\" in chunkData && \"tool_calls\" in chunkData.delta && Array.isArray(chunkData.delta.tool_calls)) {\n isToolCall = true;\n\n return { isToolCall, data: chunkData.delta.tool_calls[0] };\n }\n if (\"delta\" in chunkData) {\n return { isToolCall, data: chunkData.delta?.content || \"\" };\n }\n\n if (\"text\" in chunkData) {\n return { isToolCall, data: chunkData.text || \"\" };\n }\n } catch (e) {}\n\n return { isToolCall: false, data: \"\" };\n};\n\n// Type guard to check if an unknown object is a UsageResponse\nfunction hasCompletionUsage(obj: any): obj is { usage: OpenAI.CompletionUsage } {\n return (\n obj instanceof Object &&\n \"usage\" in obj &&\n obj.usage instanceof Object &&\n // Completion API Usage format\n ((typeof obj.usage.prompt_tokens === \"number\" &&\n typeof obj.usage.completion_tokens === \"number\" &&\n typeof obj.usage.total_tokens === \"number\") ||\n // Response API Usage format\n (typeof obj.usage.input_tokens === \"number\" &&\n typeof obj.usage.output_tokens === \"number\" &&\n typeof obj.usage.total_tokens === \"number\"))\n );\n}\n\nexport const getToolCallOutput = (\n toolCallChunks: OpenAI.Chat.Completions.ChatCompletionChunk.Choice.Delta.ToolCall[]\n): {\n tool_calls: {\n function: {\n name: string;\n arguments: string;\n };\n }[];\n} => {\n let name = \"\";\n let toolArguments = \"\";\n\n for (const toolCall of toolCallChunks) {\n name = toolCall.function?.name || name;\n toolArguments += toolCall.function?.arguments || \"\";\n }\n\n return {\n tool_calls: [\n {\n function: {\n name,\n arguments: toolArguments,\n },\n },\n ],\n };\n};\n\nexport const parseModelDataFromResponse = (\n res: unknown\n): {\n model: string | undefined;\n modelParameters: Record | undefined;\n metadata: Record | undefined;\n} => {\n if (typeof res !== \"object\" || res === null) {\n return {\n model: undefined,\n modelParameters: undefined,\n metadata: undefined,\n };\n }\n\n const model = \"model\" in res ? (res[\"model\"] as string) : undefined;\n const modelParameters: Record = {};\n const modelParamKeys = [\n \"max_output_tokens\",\n \"parallel_tool_calls\",\n \"store\",\n \"temperature\",\n \"tool_choice\",\n \"top_p\",\n \"truncation\",\n \"user\",\n ];\n\n const metadata: Record = {};\n const metadataKeys = [\n \"reasoning\",\n \"incomplete_details\",\n \"instructions\",\n \"previous_response_id\",\n \"tools\",\n \"metadata\",\n \"status\",\n \"error\",\n ];\n\n for (const key of modelParamKeys) {\n const val = key in res ? (res[key as keyof typeof res] as string | number) : null;\n if (val !== null && val !== undefined) {\n modelParameters[key as keyof typeof modelParameters] = val;\n }\n }\n\n for (const key of metadataKeys) {\n const val = key in res ? (res[key as keyof typeof res] as string | number) : null;\n if (val) {\n metadata[key as keyof typeof metadata] = val;\n }\n }\n\n return {\n model,\n modelParameters: Object.keys(modelParameters).length > 0 ? modelParameters : undefined,\n metadata: Object.keys(metadata).length > 0 ? metadata : undefined,\n };\n};\n", "export const isAsyncIterable = (x: unknown): x is AsyncIterable =>\n x != null && typeof x === \"object\" && typeof (x as any)[Symbol.asyncIterator] === \"function\";\n", "import type OpenAI from \"openai\";\n\nimport { type CreateLangfuseGenerationBody } from \"langfuse-core\";\n\nimport { LangfuseSingleton } from \"./LangfuseSingleton\";\nimport {\n getToolCallOutput,\n parseChunk,\n parseCompletionOutput,\n parseInputArgs,\n parseUsage,\n parseUsageDetails,\n parseModelDataFromResponse,\n parseUsageDetailsFromResponse,\n} from \"./parseOpenAI\";\nimport { isAsyncIterable } from \"./utils\";\nimport type { LangfuseConfig, LangfuseParent } from \"./types\";\n\ntype GenericMethod = (...args: unknown[]) => unknown;\n\nexport const withTracing = (\n tracedMethod: T,\n config?: LangfuseConfig & Required<{ generationName: string }>\n): ((...args: Parameters) => Promise>) => {\n return (...args) => wrapMethod(tracedMethod, config, ...args);\n};\n\nconst wrapMethod = (\n tracedMethod: T,\n config?: LangfuseConfig,\n ...args: Parameters\n): ReturnType | any => {\n const { model, input, modelParameters } = parseInputArgs(args[0] ?? {});\n\n const finalModelParams = { ...modelParameters, response_format: null };\n const finalMetadata = {\n ...config?.metadata,\n response_format: \"response_format\" in modelParameters ? modelParameters.response_format : undefined,\n };\n\n let observationData = {\n model,\n input,\n modelParameters: finalModelParams,\n name: config?.generationName,\n startTime: new Date(),\n promptName: config?.langfusePrompt?.name,\n promptVersion: config?.langfusePrompt?.version,\n metadata: finalMetadata,\n };\n\n let langfuseParent: LangfuseParent;\n const hasUserProvidedParent = config && \"parent\" in config;\n\n if (hasUserProvidedParent) {\n langfuseParent = config.parent;\n\n // Remove the parent from the config to avoid circular references in the generation body\n const filteredConfig = { ...config, parent: undefined };\n\n observationData = {\n ...filteredConfig,\n ...observationData,\n promptName: config?.promptName ?? config?.langfusePrompt?.name, // Maintain backward compatibility for users who use promptName\n promptVersion: config?.promptVersion ?? config?.langfusePrompt?.version, // Maintain backward compatibility for users who use promptVersion\n };\n } else {\n const langfuse = LangfuseSingleton.getInstance(config?.clientInitParams);\n langfuseParent = langfuse.trace({\n ...config,\n ...observationData,\n id: config?.traceId,\n name: config?.traceName,\n timestamp: observationData.startTime,\n });\n }\n\n try {\n const res = tracedMethod(...args);\n\n // Handle stream responses\n if (isAsyncIterable(res)) {\n return wrapAsyncIterable(res, langfuseParent, hasUserProvidedParent, observationData);\n }\n\n if (res instanceof Promise) {\n const wrappedPromise = res\n .then((result) => {\n if (isAsyncIterable(result)) {\n return wrapAsyncIterable(result, langfuseParent, hasUserProvidedParent, observationData);\n }\n\n const output = parseCompletionOutput(result);\n const usage = parseUsage(result);\n const usageDetails = parseUsageDetailsFromResponse(result);\n const {\n model: modelFromResponse,\n modelParameters: modelParametersFromResponse,\n metadata: metadataFromResponse,\n } = parseModelDataFromResponse(result);\n\n langfuseParent.generation({\n ...observationData,\n output,\n endTime: new Date(),\n usage,\n usageDetails,\n model: modelFromResponse || observationData.model,\n modelParameters: { ...observationData.modelParameters, ...modelParametersFromResponse },\n metadata: { ...observationData.metadata, ...metadataFromResponse },\n });\n\n if (!hasUserProvidedParent) {\n langfuseParent.update({ output });\n }\n\n return result;\n })\n .catch((err) => {\n langfuseParent.generation({\n ...observationData,\n endTime: new Date(),\n statusMessage: String(err),\n level: \"ERROR\",\n usage: {\n inputCost: 0,\n outputCost: 0,\n totalCost: 0,\n },\n costDetails: {\n input: 0,\n output: 0,\n total: 0,\n },\n });\n\n throw err;\n });\n\n return wrappedPromise;\n }\n\n return res;\n } catch (error) {\n langfuseParent.generation({\n ...observationData,\n endTime: new Date(),\n statusMessage: String(error),\n level: \"ERROR\",\n usage: {\n inputCost: 0,\n outputCost: 0,\n totalCost: 0,\n },\n costDetails: {\n input: 0,\n output: 0,\n total: 0,\n },\n });\n\n throw error;\n }\n};\n\nfunction wrapAsyncIterable(\n iterable: AsyncIterable,\n langfuseParent: LangfuseParent,\n hasUserProvidedParent: boolean | undefined,\n observationData: Record\n): R {\n async function* tracedOutputGenerator(): AsyncGenerator {\n const response = iterable;\n const textChunks: string[] = [];\n const toolCallChunks: OpenAI.Chat.Completions.ChatCompletionChunk.Choice.Delta.ToolCall[] = [];\n let completionStartTime: Date | null = null;\n let usage: OpenAI.CompletionUsage | null = null;\n let usageDetails: CreateLangfuseGenerationBody[\"usageDetails\"] = undefined;\n let output: CreateLangfuseGenerationBody[\"output\"] = null;\n\n for await (const rawChunk of response as AsyncIterable) {\n completionStartTime = completionStartTime ?? new Date();\n\n // Handle Response API chunks\n if (typeof rawChunk === \"object\" && rawChunk && \"response\" in rawChunk) {\n const result = rawChunk[\"response\"];\n output = parseCompletionOutput(result);\n usageDetails = parseUsageDetailsFromResponse(result);\n\n const {\n model: modelFromResponse,\n modelParameters: modelParametersFromResponse,\n metadata: metadataFromResponse,\n } = parseModelDataFromResponse(result);\n\n observationData[\"model\"] = modelFromResponse ?? observationData[\"model\"];\n observationData[\"modelParameters\"] = { ...observationData.modelParameters, ...modelParametersFromResponse };\n observationData[\"metadata\"] = { ...observationData.metadata, ...metadataFromResponse };\n }\n\n if (typeof rawChunk === \"object\" && rawChunk != null && \"usage\" in rawChunk) {\n usage = rawChunk.usage as OpenAI.CompletionUsage | null;\n }\n\n const processedChunk = parseChunk(rawChunk);\n\n if (!processedChunk.isToolCall) {\n textChunks.push(processedChunk.data);\n } else {\n toolCallChunks.push(processedChunk.data);\n }\n\n yield rawChunk;\n }\n\n output = output ?? (toolCallChunks.length > 0 ? getToolCallOutput(toolCallChunks) : textChunks.join(\"\"));\n\n langfuseParent.generation({\n ...observationData,\n output,\n endTime: new Date(),\n completionStartTime,\n usage: usage\n ? {\n input: \"prompt_tokens\" in usage ? usage.prompt_tokens : undefined,\n output: \"completion_tokens\" in usage ? usage.completion_tokens : undefined,\n total: \"total_tokens\" in usage ? usage.total_tokens : undefined,\n }\n : undefined,\n usageDetails: usageDetails ?? (usage ? parseUsageDetails(usage) : undefined),\n });\n\n if (!hasUserProvidedParent) {\n langfuseParent.update({ output });\n }\n }\n\n return tracedOutputGenerator() as R;\n}\n", "import type { LangfuseCore } from \"langfuse-core\";\n\nimport { LangfuseSingleton } from \"./LangfuseSingleton\";\nimport { withTracing } from \"./traceMethod\";\nimport type { LangfuseConfig, LangfuseExtension } from \"./types\";\n\n/**\n * Wraps an OpenAI SDK object with Langfuse tracing. Function calls are extended with a tracer that logs detailed information about the call, including the method name,\n * input parameters, and output.\n * \n * @param {T} sdk - The OpenAI SDK object to be wrapped.\n * @param {LangfuseConfig} [langfuseConfig] - Optional configuration object for the wrapper.\n * @param {string} [langfuseConfig.traceName] - The name to use for tracing. If not provided, a default name based on the SDK's constructor name and the method name will be used.\n * @param {string} [langfuseConfig.sessionId] - Optional session ID for tracing.\n * @param {string} [langfuseConfig.userId] - Optional user ID for tracing.\n * @param {string} [langfuseConfig.release] - Optional release version for tracing.\n * @param {string} [langfuseConfig.version] - Optional version for tracing.\n * @param {string} [langfuseConfig.metadata] - Optional metadata for tracing.\n * @param {string} [langfuseConfig.tags] - Optional tags for tracing.\n * @returns {T} - A proxy of the original SDK object with methods wrapped for tracing.\n *\n * @example\n * const client = new OpenAI();\n * const res = observeOpenAI(client, { traceName: \"My.OpenAI.Chat.Trace\" }).chat.completions.create({\n * messages: [{ role: \"system\", content: \"Say this is a test!\" }],\n model: \"gpt-3.5-turbo\",\n user: \"langfuse\",\n max_tokens: 300\n * });\n * */\nexport const observeOpenAI = (\n sdk: SDKType,\n langfuseConfig?: LangfuseConfig\n): SDKType & LangfuseExtension => {\n return new Proxy(sdk, {\n get(wrappedSdk, propKey, proxy) {\n const originalProperty = wrappedSdk[propKey as keyof SDKType];\n\n const defaultGenerationName = `${sdk.constructor?.name}.${propKey.toString()}`;\n const generationName = langfuseConfig?.generationName ?? defaultGenerationName;\n const traceName = langfuseConfig && \"traceName\" in langfuseConfig ? langfuseConfig.traceName : generationName;\n const config = { ...langfuseConfig, generationName, traceName };\n\n // Add a flushAsync method to the OpenAI SDK that flushes the Langfuse client\n if (propKey === \"flushAsync\") {\n let langfuseClient: LangfuseCore;\n\n // Flush the correct client depending on whether a parent client is provided\n if (langfuseConfig && \"parent\" in langfuseConfig) {\n langfuseClient = langfuseConfig.parent.client;\n } else {\n langfuseClient = LangfuseSingleton.getInstance();\n }\n\n return langfuseClient.flushAsync.bind(langfuseClient);\n }\n\n // Add a shutdownAsync method to the OpenAI SDK that flushes the Langfuse client\n if (propKey === \"shutdownAsync\") {\n let langfuseClient: LangfuseCore;\n\n // Flush the correct client depending on whether a parent client is provided\n if (langfuseConfig && \"parent\" in langfuseConfig) {\n langfuseClient = langfuseConfig.parent.client;\n } else {\n langfuseClient = LangfuseSingleton.getInstance();\n }\n\n return langfuseClient.shutdownAsync.bind(langfuseClient);\n }\n\n // Trace methods of the OpenAI SDK\n if (typeof originalProperty === \"function\") {\n return withTracing(originalProperty.bind(wrappedSdk), config);\n }\n\n const isNestedOpenAIObject =\n originalProperty &&\n !Array.isArray(originalProperty) &&\n !(originalProperty instanceof Date) &&\n typeof originalProperty === \"object\";\n\n // Recursively wrap nested objects to ensure all nested properties or methods are also traced\n if (isNestedOpenAIObject) {\n return observeOpenAI(originalProperty, config);\n }\n\n // Fallback to returning the original value\n return Reflect.get(wrappedSdk, propKey, proxy);\n },\n }) as SDKType & LangfuseExtension;\n};\n", "import { Langfuse } from \"langfuse\";\nimport type { CompletedTurn, HookConfig, TraceSink } from \"../domain/types.js\";\n\nconst SDK_INTEGRATION = \"zcode-langfuse-observability\";\nconst TRACE_NAME = \"ZCode Turn\";\n\nexport class LangfuseTraceSink implements TraceSink {\n constructor(private readonly config: HookConfig) {}\n\n async publishTurn(turn: CompletedTurn): Promise {\n if (!this.config.publicKey || !this.config.secretKey) return;\n\n const client = new Langfuse({\n publicKey: this.config.publicKey,\n secretKey: this.config.secretKey,\n baseUrl: this.config.baseUrl,\n release: this.config.release,\n environment: this.config.environment,\n sdkIntegration: SDK_INTEGRATION,\n flushAt: 1,\n fetchRetryCount: 1,\n requestTimeout: 8_000,\n });\n\n try {\n type TraceInput = NonNullable[0]>;\n const traceInput: TraceInput = {\n name: TRACE_NAME,\n sessionId: turn.sessionId,\n input: turn.prompt,\n metadata: {\n source: \"zcode\",\n turnId: turn.turnId,\n toolCount: turn.tools.length,\n },\n tags: [\"zcode\", \"zcode-hook\"],\n };\n if (this.config.userId) traceInput.userId = this.config.userId;\n const trace = client.trace(traceInput);\n\n for (const tool of turn.tools) {\n const span = trace.span({\n name: `tool.${tool.name}`,\n input: tool.input,\n metadata: {\n toolId: tool.id,\n startedAt: tool.startedAt,\n endedAt: tool.endedAt,\n },\n });\n if (tool.error) {\n span.end({\n output: { error: tool.error },\n level: \"ERROR\",\n statusMessage: tool.error,\n });\n } else {\n span.end({ output: tool.output });\n }\n }\n\n const generation = trace.generation({\n name: \"zcode.assistant\",\n input: turn.prompt,\n metadata: {\n turnId: turn.turnId,\n },\n });\n generation.end({ output: turn.assistantMessage });\n\n trace.update({\n output: turn.assistantMessage,\n metadata: {\n source: \"zcode\",\n turnId: turn.turnId,\n toolCount: turn.tools.length,\n },\n });\n\n await client.flushAsync();\n } finally {\n await client.shutdownAsync();\n }\n }\n}\n"], + "mappings": ";;;AAAA,SAAS,cAAAA,mBAAkB;;;ACA3B,SAAS,oBAAoB;AAC7B,SAAS,eAAe;AACxB,SAAS,YAAY;AAGrB,IAAM,mBAAmB;AACzB,IAAM,kBAAkB;AACxB,IAAM,4BAA4B;AAMlC,SAAS,OAAO,OAAwC;AACtD,MAAI,UAAU,OAAW,QAAO;AAChC,SAAO,OAAO,UAAU,WAAW,QAAQ,OAAO,KAAK;AACzD;AAEA,SAAS,iBAAiB,QAA2C;AACnE,SAAO,OACJ,IAAI,MAAM,EACV,KAAK,CAAC,UAAU,UAAU,UAAa,MAAM,KAAK,EAAE,SAAS,CAAC,GAC7D,KAAK;AACX;AAEA,SAAS,WAAW,KAAwB,MAAkC;AAC5E,QAAM,aAAa,KAAK,YAAY;AACpC,SAAO;AAAA,IACL,IAAI,qBAAqB,UAAU,EAAE;AAAA,IACrC,IAAI,uBAAuB,UAAU,EAAE;AAAA,EACzC;AACF;AAEA,SAAS,eAAe,OAAuC;AAC7D,SACE,OAAO,UAAU,YACjB,OAAO,UAAU,YACjB,OAAO,UAAU;AAErB;AAEA,SAAS,mBAAmB,OAA+B;AACzD,MAAI,OAAO,UAAU,YAAY,UAAU,QAAQ,MAAM,QAAQ,KAAK;AACpE,WAAO,CAAC;AACV,QAAM,UAAyB,CAAC;AAChC,aAAW,CAAC,KAAKC,OAAM,KAAK,OAAO,QAAQ,KAAK,GAAG;AACjD,QAAI,eAAeA,OAAM,EAAG,SAAQ,GAAG,IAAIA;AAAA,EAC7C;AACA,SAAO;AACT;AAEA,SAAS,kBAAkB,KAAuC;AAChE,QAAM,cAAc;AAAA,IAClB,IAAI;AAAA,IACJ,KAAK,QAAQ,GAAG,UAAU,OAAO,aAAa;AAAA,EAChD,EAAE,OAAO,CAAC,SAAyB,QAAQ,IAAI,CAAC;AAChD,QAAM,qBAAqB,IAAI;AAE/B,aAAW,cAAc,aAAa;AACpC,QAAI;AACF,YAAM,SAAkB,KAAK,MAAM,aAAa,YAAY,MAAM,CAAC;AACnE,UACE,OAAO,WAAW,YAClB,WAAW,QACX,MAAM,QAAQ,MAAM;AAEpB;AACF,YAAM,UAAW,OAAmC;AACpD,UACE,OAAO,YAAY,YACnB,YAAY,QACZ,MAAM,QAAQ,OAAO;AAErB;AACF,YAAM,UAAW,QAAoC;AACrD,UACE,OAAO,YAAY,YACnB,YAAY,QACZ,MAAM,QAAQ,OAAO;AAErB;AACF,YAAM,gBAAgB;AACtB,YAAM,WACJ,sBAAsB,cAAc,kBAAkB,IAClD,qBACA,OAAO,KAAK,aAAa,EAAE;AAAA,QAAK,CAAC,QAC/B,IAAI,WAAW,yBAAyB;AAAA,MAC1C;AACN,UAAI,SAAU,QAAO,mBAAmB,cAAc,QAAQ,CAAC;AAAA,IACjE,QAAQ;AAAA,IAER;AAAA,EACF;AACA,SAAO,CAAC;AACV;AAEA,SAAS,OACP,KACA,eACA,MACA,iBACoB;AACpB,SAAO;AAAA,IACL,WAAW,KAAK,IAAI;AAAA,IACpB,cAAc,IAAI;AAAA,IAClB,IAAI,eAAe;AAAA,EACrB;AACF;AAEA,SAAS,aAAa,OAA2B,UAA4B;AAC3E,MAAI,CAAC,MAAO,QAAO;AACnB,MAAI,CAAC,KAAK,QAAQ,OAAO,IAAI,EAAE,SAAS,MAAM,YAAY,CAAC,EAAG,QAAO;AACrE,MAAI,CAAC,KAAK,SAAS,MAAM,KAAK,EAAE,SAAS,MAAM,YAAY,CAAC,EAAG,QAAO;AACtE,SAAO;AACT;AAEA,SAAS,qBACP,OACA,UACQ;AACR,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,SAAS,OAAO,SAAS,OAAO,EAAE;AACxC,MAAI,CAAC,OAAO,SAAS,MAAM,KAAK,UAAU,EAAG,QAAO;AACpD,SAAO,KAAK,IAAI,QAAQ,GAAS;AACnC;AAEA,SAAS,iBAAiB,OAAmC;AAC3D,QAAM,YAAY,SAAS;AAC3B,MAAI;AACF,UAAM,MAAM,IAAI,IAAI,SAAS;AAC7B,QAAI,IAAI,aAAa,WAAW,IAAI,aAAa;AAC/C,aAAO;AACT,WAAO,UAAU,QAAQ,QAAQ,EAAE;AAAA,EACrC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,WACd,MAAyB,QAAQ,KACjC,gBAA+B,kBAAkB,GAAG,GACxC;AACZ,QAAM,UAAU;AAAA,IACd,OAAO,KAAK,eAAe,WAAW,kBAAkB;AAAA,IACxD;AAAA,EACF;AACA,QAAM,YACJ,OAAO,KAAK,eAAe,uBAAuB,qBAAqB,KACvE;AACF,QAAM,YACJ,OAAO,KAAK,eAAe,uBAAuB,qBAAqB,KACvE;AAEF,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,SAAS;AAAA,MACP,OAAO,KAAK,eAAe,qBAAqB,mBAAmB;AAAA,IACrE;AAAA,IACA,QACE,OAAO,KAAK,eAAe,oBAAoB,kBAAkB,KACjE;AAAA,IACF,aACE;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,KAAK;AAAA,IACP,SACE,OAAO,KAAK,eAAe,oBAAoB,kBAAkB,KACjE;AAAA,IACF;AAAA,IACA,gBAAgB;AAAA,MACd,OAAO,KAAK,eAAe,mBAAmB,0BAA0B;AAAA,MACxE;AAAA,IACF;AAAA,IACA,mBAAmB;AAAA,MACjB;AAAA,QACE;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MACA;AAAA,IACF;AAAA,IACA,oBAAoB;AAAA,MAClB;AAAA,QACE;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MACA;AAAA,IACF;AAAA,IACA,iBAAiB;AAAA,MACf;AAAA,QACE;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MACA;AAAA,IACF;AAAA,IACA,OAAO;AAAA,MACL,OAAO,KAAK,eAAe,SAAS,gBAAgB;AAAA,MACpD;AAAA,IACF;AAAA,EACF;AACF;AAEO,SAAS,aAAa,QAA6B;AACxD,SAAO,OAAO,WAAW,QAAQ,OAAO,aAAa,OAAO,SAAS;AACvE;;;ACnNA,SAAS,YAAY,OAAuC;AAC1D,MACE,UAAU,QACV,OAAO,UAAU,YACjB,OAAO,UAAU,YACjB,OAAO,UAAU,WACjB;AACA,WAAO;AAAA,EACT;AACA,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,UAAM,SAAS,MAAM,IAAI,WAAW;AACpC,WAAO,OAAO,MAAM,CAAC,SAA4B,SAAS,MAAS,IAC/D,SACA;AAAA,EACN;AACA,MAAI,OAAO,UAAU,UAAU;AAC7B,UAAM,UAAU,OAAO,QAAQ,KAAgC;AAC/D,UAAM,SAAuC,CAAC;AAC9C,eAAW,CAAC,KAAK,IAAI,KAAK,SAAS;AACjC,YAAM,SAAS,YAAY,IAAI;AAC/B,UAAI,WAAW,OAAW,QAAO;AACjC,aAAO,GAAG,IAAI;AAAA,IAChB;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEA,SAAS,QACP,YACG,MACoB;AACvB,aAAW,OAAO,MAAM;AACtB,UAAM,QAAQ,YAAY,QAAQ,GAAG,CAAC;AACtC,QAAI,UAAU,UAAa,UAAU,KAAM,QAAO;AAAA,EACpD;AACA,SAAO;AACT;AAEO,SAAS,SAAS,OAA6C;AACpE,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,MAAI,OAAO,UAAU,YAAY,OAAO,UAAU;AAChD,WAAO,OAAO,KAAK;AACrB,SAAO;AACT;AAEO,SAAS,eAAe,OAAsC;AACnE,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,MAAI,UAAU,OAAW,QAAO;AAChC,QAAM,OAAO,KAAK,UAAU,KAAK;AACjC,SAAO,QAAQ,OAAO,KAAK;AAC7B;AAEO,SAAS,UAAU,SAA4C;AACpE,SAAO;AAAA,IACL,QAAQ,SAAS,mBAAmB,iBAAiB,SAAS,YAAY;AAAA,EAC5E;AACF;AAEO,SAAS,UAAU,SAAqC;AAC7D,QAAM,QAAQ,SAAS,QAAQ,SAAS,cAAc,WAAW,CAAC;AAClE,SAAO,OAAO,KAAK,KAAK;AAC1B;AAEO,SAAS,OAAO,SAAqC;AAC1D,QAAM,QAAQ;AAAA,IACZ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACA,SAAO,UAAU,SAAY,OAAO,eAAe,KAAK;AAC1D;AAEO,SAAS,iBAAiB,SAAqC;AACpE,QAAM,QAAQ;AAAA,IACZ;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACA,SAAO,UAAU,SAAY,OAAO,eAAe,KAAK;AAC1D;AAEO,SAAS,OAAO,SAAqC;AAC1D,QAAM,QAAQ;AAAA,IACZ,QAAQ,SAAS,eAAe,aAAa,WAAW,UAAU,IAAI;AAAA,EACxE;AACA,SAAO,OAAO,KAAK,KAAK;AAC1B;AAEO,SAAS,SAAS,SAA8B;AACrD,SACE;AAAA,IACE,QAAQ,SAAS,aAAa,YAAY,QAAQ,MAAM;AAAA,EAC1D,GAAG,KAAK,KAAK;AAEjB;AAEO,SAAS,UAAU,SAAiC;AACzD,SACE,QAAQ,SAAS,cAAc,aAAa,SAAS,WAAW,KAAK;AAEzE;AAEO,SAAS,WAAW,SAAiC;AAC1D,SACE;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,KAAK;AAET;AAEO,SAAS,UAAU,SAA8B;AACtD,SAAO;AAAA,IACL,QAAQ,SAAS,SAAS,iBAAiB,gBAAgB,SAAS,KAClE;AAAA,EACJ;AACF;;;ACpGA,SAAS,cAAc,SAAiB,KAA2B;AACjE,SAAO;AAAA,IACL,SAAS;AAAA,IACT,WAAW;AAAA,IACX,WAAW;AAAA,IACX,WAAW;AAAA,IACX,aAAa;AAAA,EACf;AACF;AAEA,SAAS,WACP,IACA,KACA,YACW;AACX,SAAO;AAAA,IACL;AAAA,IACA,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,OAAO,CAAC;AAAA,EACV;AACF;AAEA,SAAS,SAAS,OAAe,UAA0B;AACzD,MAAI,MAAM,UAAU,SAAU,QAAO;AACrC,SAAO,GAAG,MAAM,MAAM,GAAG,KAAK,IAAI,GAAG,WAAW,EAAE,CAAC,CAAC;AACtD;AAEA,SAAS,aAAa,OAAkB,UAA6B;AACnE,QAAM,aAAa,KAAK,UAAU,KAAK;AACvC,MAAI,WAAW,UAAU,SAAU,QAAO;AAC1C,SAAO,SAAS,YAAY,QAAQ;AACtC;AAEA,SAAS,aAAa,MAAqB,QAAmC;AAC5E,SAAO;AAAA,IACL,GAAG;AAAA,IACH,QACE,OAAO,kBAAkB,KAAK,WAAW,OACrC,SAAS,KAAK,QAAQ,OAAO,eAAe,IAC5C;AAAA,IACN,kBACE,OAAO,kBAAkB,KAAK,qBAAqB,OAC/C,SAAS,KAAK,kBAAkB,OAAO,eAAe,IACtD;AAAA,IACN,OAAO,KAAK,MAAM,IAAI,CAAC,UAAU;AAAA,MAC/B,GAAG;AAAA,MACH,MAAM,SAAS,KAAK,MAAM,GAAG;AAAA,MAC7B,OAAO,OAAO,oBACV,aAAa,KAAK,OAAO,OAAO,eAAe,IAC/C;AAAA,MACJ,QACE,OAAO,sBAAsB,KAAK,WAAW,OACzC,aAAa,KAAK,QAAQ,OAAO,eAAe,IAChD;AAAA,MACN,OACE,OAAO,sBAAsB,KAAK,UAAU,OACxC,SAAS,KAAK,OAAO,OAAO,eAAe,IAC3C;AAAA,IACR,EAAE;AAAA,EACJ;AACF;AAEA,SAAS,SACP,MACA,IACA,MACiB;AACjB,MAAI,IAAI;AACN,UAAM,OAAO,KAAK,MAAM,KAAK,CAAC,SAAS,KAAK,OAAO,EAAE;AACrD,QAAI,KAAM,QAAO;AAAA,EACnB;AACA,SACE,CAAC,GAAG,KAAK,KAAK,EACX,QAAQ,EACR,KAAK,CAAC,SAAS,KAAK,YAAY,QAAQ,KAAK,SAAS,IAAI,KAC7D,CAAC,GAAG,KAAK,KAAK,EAAE,QAAQ,EAAE,KAAK,CAAC,SAAS,KAAK,YAAY,IAAI,KAC9D;AAEJ;AAEO,IAAM,cAAN,MAAkB;AAAA,EACvB,YACmB,YACA,WACA,QACA,OACA,aACjB;AALiB;AACA;AACA;AACA;AACA;AAAA,EAChB;AAAA,EAEH,MAAM,OAAO,SAAqC;AAChD,UAAM,OAAO,UAAU,OAAO;AAC9B,UAAM,UAAU,UAAU,OAAO;AACjC,QAAI,CAAC,QAAQ,CAAC,QAAS;AAEvB,QAAI,YAAkC;AACtC,UAAM,KAAK,WAAW,gBAAgB,SAAS,YAAY;AACzD,YAAM,MAAM,KAAK,MAAM,IAAI,EAAE,YAAY;AACzC,YAAM,WACH,MAAM,KAAK,WAAW,KAAK,OAAO,KAAM,cAAc,SAAS,GAAG;AAErE,cAAQ,MAAM;AAAA,QACZ,KAAK;AACH,mBAAS,YAAY;AACrB,gBAAM,KAAK,WAAW,KAAK,QAAQ;AACnC;AAAA,QACF,KAAK,oBAAoB;AACvB,gBAAM,aAAa,OAAO,OAAO;AACjC,mBAAS,cAAc;AAAA,YACrB,KAAK,YAAY,KAAK;AAAA,YACtB;AAAA,YACA,KAAK,OAAO,kBAAkB,eAAe,OACzC,SAAS,YAAY,KAAK,OAAO,eAAe,IAChD;AAAA,UACN;AACA,mBAAS,YAAY;AACrB,gBAAM,KAAK,WAAW,KAAK,QAAQ;AACnC;AAAA,QACF;AAAA,QACA,KAAK;AACH,eAAK,gBAAgB,UAAU,SAAS,GAAG;AAC3C,gBAAM,KAAK,WAAW,KAAK,QAAQ;AACnC;AAAA,QACF,KAAK;AACH,eAAK,iBAAiB,UAAU,SAAS,KAAK,KAAK;AACnD,gBAAM,KAAK,WAAW,KAAK,QAAQ;AACnC;AAAA,QACF,KAAK;AACH,eAAK,iBAAiB,UAAU,SAAS,KAAK,IAAI;AAClD,gBAAM,KAAK,WAAW,KAAK,QAAQ;AACnC;AAAA,QACF,KAAK;AACH,sBAAY;AAAA,YACV;AAAA,cACE,WAAW;AAAA,cACX,QAAQ,SAAS,aAAa,MAAM,KAAK,YAAY,KAAK;AAAA,cAC1D,QAAQ,SAAS,aAAa,UAAU;AAAA,cACxC,kBAAkB,iBAAiB,OAAO;AAAA,cAC1C,WAAW,SAAS,aAAa,aAAa;AAAA,cAC9C,SAAS;AAAA,cACT,OAAO,SAAS,aAAa,SAAS,CAAC;AAAA,YACzC;AAAA,YACA,KAAK;AAAA,UACP;AACA,gBAAM,KAAK,WAAW,MAAM,OAAO;AACnC;AAAA,QACF;AACE;AAAA,MACJ;AAAA,IACF,CAAC;AAED,QAAI,UAAW,OAAM,KAAK,UAAU,YAAY,SAAS;AAAA,EAC3D;AAAA,EAEQ,gBACN,OACA,SACA,KACM;AACN,UAAM,OACJ,MAAM,eAAe,WAAW,KAAK,YAAY,KAAK,GAAG,KAAK,IAAI;AACpE,UAAM,cAAc;AACpB,SAAK,MAAM,KAAK;AAAA,MACd,IAAI,OAAO,OAAO,KAAK,KAAK,YAAY,KAAK;AAAA,MAC7C,MAAM,SAAS,OAAO;AAAA,MACtB,OAAO,KAAK,OAAO,oBACf,aAAa,UAAU,OAAO,GAAG,KAAK,OAAO,eAAe,IAC5D;AAAA,MACJ,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,WAAW;AAAA,MACX,SAAS;AAAA,IACX,CAAC;AACD,UAAM,YAAY;AAAA,EACpB;AAAA,EAEQ,iBACN,OACA,SACA,KACA,QACM;AACN,UAAM,OACJ,MAAM,eAAe,WAAW,KAAK,YAAY,KAAK,GAAG,KAAK,IAAI;AACpE,UAAM,cAAc;AACpB,UAAM,OAAO,SAAS,OAAO;AAC7B,UAAM,OAAO,SAAS,MAAM,OAAO,OAAO,GAAG,IAAI;AACjD,UAAM,SACJ,CAAC,UAAU,KAAK,OAAO,qBACnB,aAAa,WAAW,OAAO,GAAG,KAAK,OAAO,eAAe,IAC7D;AACN,UAAM,QACJ,UAAU,KAAK,OAAO,qBAClB,SAAS,UAAU,OAAO,GAAG,KAAK,OAAO,eAAe,IACxD;AACN,QAAI,MAAM;AACR,WAAK,SAAS;AACd,WAAK,QAAQ;AACb,WAAK,UAAU;AAAA,IACjB,OAAO;AACL,WAAK,MAAM,KAAK;AAAA,QACd,IAAI,OAAO,OAAO,KAAK,KAAK,YAAY,KAAK;AAAA,QAC7C;AAAA,QACA,OAAO;AAAA,QACP;AAAA,QACA;AAAA,QACA,WAAW;AAAA,QACX,SAAS;AAAA,MACX,CAAC;AAAA,IACH;AACA,UAAM,YAAY;AAAA,EACpB;AACF;AAEO,IAAM,gBAAN,MAAyC;AAAA,EAC9C,MAAM,YAAY,OAAqC;AAAA,EAEvD;AACF;;;ACnPA,SAAS,YAAY,kBAAkB;AACvC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,WAAAC,UAAS,cAAc;AAChC,SAAS,QAAAC,aAAY;AASrB,IAAM,eAAe;AACrB,IAAM,gBAAgB;AACtB,IAAM,gBAAgB;AAEtB,SAAS,SAAS,OAAkD;AAClE,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAEA,SAAS,YAAY,OAAoC;AACvD,MACE,UAAU,QACV,OAAO,UAAU,YACjB,OAAO,UAAU,YACjB,OAAO,UAAU,WACjB;AACA,WAAO;AAAA,EACT;AACA,MAAI,MAAM,QAAQ,KAAK,EAAG,QAAO,MAAM,MAAM,WAAW;AACxD,SAAO,SAAS,KAAK,KAAK,OAAO,OAAO,KAAK,EAAE,MAAM,WAAW;AAClE;AAEA,SAAS,cAAc,OAAiC;AACtD,MAAI,CAAC,SAAS,KAAK,EAAG,QAAO;AAC7B,MACE,OAAO,MAAM,OAAO,YACpB,OAAO,MAAM,SAAS,YACtB,CAAC,YAAY,MAAM,KAAK,KACvB,MAAM,WAAW,QAAQ,CAAC,YAAY,MAAM,MAAM,KAClD,MAAM,UAAU,QAAQ,OAAO,MAAM,UAAU,YAChD,OAAO,MAAM,cAAc,YAC1B,MAAM,YAAY,QAAQ,OAAO,MAAM,YAAY,UACpD;AACA,WAAO;AAAA,EACT;AACA,SAAO;AAAA,IACL,IAAI,MAAM;AAAA,IACV,MAAM,MAAM;AAAA,IACZ,OAAO,MAAM;AAAA,IACb,QAAQ,MAAM;AAAA,IACd,OAAO,MAAM;AAAA,IACb,WAAW,MAAM;AAAA,IACjB,SAAS,MAAM;AAAA,EACjB;AACF;AAEA,SAAS,UAAU,OAAkC;AACnD,MAAI,CAAC,SAAS,KAAK,EAAG,QAAO;AAC7B,MACE,OAAO,MAAM,OAAO,YACnB,MAAM,WAAW,QAAQ,OAAO,MAAM,WAAW,YAClD,OAAO,MAAM,cAAc,YAC3B,CAAC,MAAM,QAAQ,MAAM,KAAK,GAC1B;AACA,WAAO;AAAA,EACT;AACA,QAAM,QAAQ,MAAM,MAAM,IAAI,aAAa;AAC3C,MAAI,MAAM,KAAK,CAAC,SAAuB,SAAS,IAAI,EAAG,QAAO;AAC9D,SAAO;AAAA,IACL,IAAI,MAAM;AAAA,IACV,QAAQ,MAAM;AAAA,IACd,WAAW,MAAM;AAAA,IACjB,OAAO,MAAM,OAAO,CAAC,SAA2B,SAAS,IAAI;AAAA,EAC/D;AACF;AAEA,SAAS,WAAW,OAAqC;AACvD,MAAI,CAAC,SAAS,KAAK,EAAG,QAAO;AAC7B,MACE,MAAM,YAAY,KAClB,OAAO,MAAM,cAAc,YAC3B,OAAO,MAAM,cAAc,YAC3B,OAAO,MAAM,cAAc,UAC3B;AACA,WAAO;AAAA,EACT;AACA,QAAM,cACJ,MAAM,gBAAgB,OAAO,OAAO,UAAU,MAAM,WAAW;AACjE,MAAI,MAAM,gBAAgB,QAAQ,gBAAgB,KAAM,QAAO;AAC/D,SAAO;AAAA,IACL,SAAS;AAAA,IACT,WAAW,MAAM;AAAA,IACjB,WAAW,MAAM;AAAA,IACjB,WAAW,MAAM;AAAA,IACjB;AAAA,EACF;AACF;AAEA,SAAS,WAAWC,YAA2B;AAC7C,SAAO,WAAW,QAAQ,EAAE,OAAOA,UAAS,EAAE,OAAO,KAAK;AAC5D;AAEA,SAAS,iBAAyB;AAChC,SAAOD;AAAA,IACLD,SAAQ,KAAK,OAAO;AAAA,IACpB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAEO,IAAM,iBAAN,MAA2C;AAAA,EAC/B;AAAA,EAEjB,YAAY,UAAU,QAAQ,IAAI,qBAAqB,eAAe,GAAG;AACvE,SAAK,UAAU;AAAA,EACjB;AAAA,EAEA,MAAM,KAAKE,YAAiD;AAC1D,QAAI;AACF,YAAM,WAAW,MAAM,SAAS,KAAK,UAAUA,UAAS,GAAG,MAAM;AACjE,aAAO,WAAW,KAAK,MAAM,QAAQ,CAAC;AAAA,IACxC,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAM,KAAK,OAAoC;AAC7C,UAAM,MAAM,KAAK,SAAS,EAAE,WAAW,MAAM,MAAM,IAAM,CAAC;AAC1D,UAAM,OAAO,KAAK,UAAU,MAAM,SAAS;AAC3C,UAAM,gBAAgB,GAAG,IAAI,IAAI,WAAW,CAAC;AAC7C,UAAM,UAAU,eAAe,KAAK,UAAU,KAAK,GAAG;AAAA,MACpD,UAAU;AAAA,MACV,MAAM;AAAA,IACR,CAAC;AACD,UAAM,OAAO,eAAe,IAAI;AAAA,EAClC;AAAA,EAEA,MAAM,MAAMA,YAAkC;AAC5C,UAAM,GAAG,KAAK,UAAUA,UAAS,GAAG,EAAE,OAAO,KAAK,CAAC;AAAA,EACrD;AAAA,EAEA,MAAM,gBACJA,YACA,MACY;AACZ,UAAM,MAAM,KAAK,SAAS,EAAE,WAAW,MAAM,MAAM,IAAM,CAAC;AAC1D,UAAM,WAAW,KAAK,SAASA,UAAS;AACxC,QAAI,WAAW;AAEf,aAAS,UAAU,GAAG,UAAU,eAAe,WAAW,GAAG;AAC3D,UAAI;AACF,cAAM,SAAS,MAAM,KAAK,UAAU,MAAM,GAAK;AAC/C,cAAM,OAAO,MAAM;AACnB,mBAAW;AACX;AAAA,MACF,QAAQ;AACN,cAAM,KAAK,gBAAgB,QAAQ;AACnC,cAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,YAAY,CAAC;AAAA,MAClE;AAAA,IACF;AAEA,QAAI,CAAC;AACH,YAAM,IAAI,MAAM,6CAA6C;AAE/D,QAAI;AACF,aAAO,MAAM,KAAK;AAAA,IACpB,UAAE;AACA,YAAM,GAAG,UAAU,EAAE,OAAO,KAAK,CAAC;AAAA,IACpC;AAAA,EACF;AAAA,EAEQ,UAAUA,YAA2B;AAC3C,WAAOD,MAAK,KAAK,SAAS,GAAG,WAAWC,UAAS,CAAC,OAAO;AAAA,EAC3D;AAAA,EAEQ,SAASA,YAA2B;AAC1C,WAAOD,MAAK,KAAK,SAAS,GAAG,WAAWC,UAAS,CAAC,OAAO;AAAA,EAC3D;AAAA,EAEA,MAAc,gBAAgB,UAAiC;AAC7D,QAAI;AACF,YAAM,UAAU,MAAM,KAAK,QAAQ;AACnC,UAAI,KAAK,IAAI,IAAI,QAAQ,UAAU;AACjC,cAAM,GAAG,UAAU,EAAE,OAAO,KAAK,CAAC;AAAA,IACtC,QAAQ;AAAA,IAER;AAAA,EACF;AACF;;;ACnMA,IAAI,iBAAiB,OAAO,UAAU;AACtC,IAAI,UAAU,MAAM,WAAW,SAAS,gBAAiB,QAAQ;AAC/D,SAAO,eAAe,KAAK,MAAM,MAAM;AACzC;AAEA,SAAS,WAAY,QAAQ;AAC3B,SAAO,OAAO,WAAW;AAC3B;AAMA,SAAS,QAAS,KAAK;AACrB,SAAO,QAAQ,GAAG,IAAI,UAAU,OAAO;AACzC;AAEA,SAAS,aAAc,QAAQ;AAC7B,SAAO,OAAO,QAAQ,+BAA+B,MAAM;AAC7D;AAMA,SAAS,YAAa,KAAK,UAAU;AACnC,SAAO,OAAO,QAAQ,OAAO,QAAQ,YAAa,YAAY;AAChE;AAMA,SAAS,wBAAyB,WAAW,UAAU;AACrD,SACE,aAAa,QACV,OAAO,cAAc,YACrB,UAAU,kBACV,UAAU,eAAe,QAAQ;AAExC;AAIA,IAAI,aAAa,OAAO,UAAU;AAClC,SAAS,WAAY,IAAI,QAAQ;AAC/B,SAAO,WAAW,KAAK,IAAI,MAAM;AACnC;AAEA,IAAI,aAAa;AACjB,SAAS,aAAc,QAAQ;AAC7B,SAAO,CAAC,WAAW,YAAY,MAAM;AACvC;AAEA,IAAI,YAAY;AAAA,EACd,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AACP;AAEA,SAAS,WAAY,QAAQ;AAC3B,SAAO,OAAO,MAAM,EAAE,QAAQ,gBAAgB,SAAS,cAAe,GAAG;AACvE,WAAO,UAAU,CAAC;AAAA,EACpB,CAAC;AACH;AAEA,IAAI,UAAU;AACd,IAAI,UAAU;AACd,IAAI,WAAW;AACf,IAAI,UAAU;AACd,IAAI,QAAQ;AA4BZ,SAAS,cAAe,UAAU,MAAM;AACtC,MAAI,CAAC;AACH,WAAO,CAAC;AACV,MAAI,kBAAkB;AACtB,MAAI,WAAW,CAAC;AAChB,MAAI,SAAS,CAAC;AACd,MAAI,SAAS,CAAC;AACd,MAAI,SAAS;AACb,MAAI,WAAW;AACf,MAAI,cAAc;AAClB,MAAI,WAAW;AAIf,WAAS,aAAc;AACrB,QAAI,UAAU,CAAC,UAAU;AACvB,aAAO,OAAO;AACZ,eAAO,OAAO,OAAO,IAAI,CAAC;AAAA,IAC9B,OAAO;AACL,eAAS,CAAC;AAAA,IACZ;AAEA,aAAS;AACT,eAAW;AAAA,EACb;AAEA,MAAI,cAAc,cAAc;AAChC,WAAS,YAAa,eAAe;AACnC,QAAI,OAAO,kBAAkB;AAC3B,sBAAgB,cAAc,MAAM,SAAS,CAAC;AAEhD,QAAI,CAAC,QAAQ,aAAa,KAAK,cAAc,WAAW;AACtD,YAAM,IAAI,MAAM,mBAAmB,aAAa;AAElD,mBAAe,IAAI,OAAO,aAAa,cAAc,CAAC,CAAC,IAAI,MAAM;AACjE,mBAAe,IAAI,OAAO,SAAS,aAAa,cAAc,CAAC,CAAC,CAAC;AACjE,qBAAiB,IAAI,OAAO,SAAS,aAAa,MAAM,cAAc,CAAC,CAAC,CAAC;AAAA,EAC3E;AAEA,cAAY,QAAQ,SAAS,IAAI;AAEjC,MAAI,UAAU,IAAI,QAAQ,QAAQ;AAElC,MAAI,OAAO,MAAM,OAAO,KAAK,OAAO;AACpC,SAAO,CAAC,QAAQ,IAAI,GAAG;AACrB,YAAQ,QAAQ;AAGhB,YAAQ,QAAQ,UAAU,YAAY;AAEtC,QAAI,OAAO;AACT,eAAS,IAAI,GAAG,cAAc,MAAM,QAAQ,IAAI,aAAa,EAAE,GAAG;AAChE,cAAM,MAAM,OAAO,CAAC;AAEpB,YAAI,aAAa,GAAG,GAAG;AACrB,iBAAO,KAAK,OAAO,MAAM;AACzB,yBAAe;AAAA,QACjB,OAAO;AACL,qBAAW;AACX,4BAAkB;AAClB,yBAAe;AAAA,QACjB;AAEA,eAAO,KAAK,CAAE,QAAQ,KAAK,OAAO,QAAQ,CAAE,CAAC;AAC7C,iBAAS;AAGT,YAAI,QAAQ,MAAM;AAChB,qBAAW;AACX,wBAAc;AACd,qBAAW;AACX,4BAAkB;AAAA,QACpB;AAAA,MACF;AAAA,IACF;AAGA,QAAI,CAAC,QAAQ,KAAK,YAAY;AAC5B;AAEF,aAAS;AAGT,WAAO,QAAQ,KAAK,KAAK,KAAK;AAC9B,YAAQ,KAAK,OAAO;AAGpB,QAAI,SAAS,KAAK;AAChB,cAAQ,QAAQ,UAAU,QAAQ;AAClC,cAAQ,KAAK,QAAQ;AACrB,cAAQ,UAAU,YAAY;AAAA,IAChC,WAAW,SAAS,KAAK;AACvB,cAAQ,QAAQ,UAAU,cAAc;AACxC,cAAQ,KAAK,OAAO;AACpB,cAAQ,UAAU,YAAY;AAC9B,aAAO;AAAA,IACT,OAAO;AACL,cAAQ,QAAQ,UAAU,YAAY;AAAA,IACxC;AAGA,QAAI,CAAC,QAAQ,KAAK,YAAY;AAC5B,YAAM,IAAI,MAAM,qBAAqB,QAAQ,GAAG;AAElD,QAAI,QAAQ,KAAK;AACf,cAAQ,CAAE,MAAM,OAAO,OAAO,QAAQ,KAAK,aAAa,UAAU,eAAgB;AAAA,IACpF,OAAO;AACL,cAAQ,CAAE,MAAM,OAAO,OAAO,QAAQ,GAAI;AAAA,IAC5C;AACA;AACA,WAAO,KAAK,KAAK;AAEjB,QAAI,SAAS,OAAO,SAAS,KAAK;AAChC,eAAS,KAAK,KAAK;AAAA,IACrB,WAAW,SAAS,KAAK;AAEvB,oBAAc,SAAS,IAAI;AAE3B,UAAI,CAAC;AACH,cAAM,IAAI,MAAM,uBAAuB,QAAQ,UAAU,KAAK;AAEhE,UAAI,YAAY,CAAC,MAAM;AACrB,cAAM,IAAI,MAAM,uBAAuB,YAAY,CAAC,IAAI,UAAU,KAAK;AAAA,IAC3E,WAAW,SAAS,UAAU,SAAS,OAAO,SAAS,KAAK;AAC1D,iBAAW;AAAA,IACb,WAAW,SAAS,KAAK;AAEvB,kBAAY,KAAK;AAAA,IACnB;AAAA,EACF;AAEA,aAAW;AAGX,gBAAc,SAAS,IAAI;AAE3B,MAAI;AACF,UAAM,IAAI,MAAM,uBAAuB,YAAY,CAAC,IAAI,UAAU,QAAQ,GAAG;AAE/E,SAAO,WAAW,aAAa,MAAM,CAAC;AACxC;AAMA,SAAS,aAAc,QAAQ;AAC7B,MAAI,iBAAiB,CAAC;AAEtB,MAAI,OAAO;AACX,WAAS,IAAI,GAAG,YAAY,OAAO,QAAQ,IAAI,WAAW,EAAE,GAAG;AAC7D,YAAQ,OAAO,CAAC;AAEhB,QAAI,OAAO;AACT,UAAI,MAAM,CAAC,MAAM,UAAU,aAAa,UAAU,CAAC,MAAM,QAAQ;AAC/D,kBAAU,CAAC,KAAK,MAAM,CAAC;AACvB,kBAAU,CAAC,IAAI,MAAM,CAAC;AAAA,MACxB,OAAO;AACL,uBAAe,KAAK,KAAK;AACzB,oBAAY;AAAA,MACd;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAQA,SAAS,WAAY,QAAQ;AAC3B,MAAI,eAAe,CAAC;AACpB,MAAI,YAAY;AAChB,MAAI,WAAW,CAAC;AAEhB,MAAI,OAAO;AACX,WAAS,IAAI,GAAG,YAAY,OAAO,QAAQ,IAAI,WAAW,EAAE,GAAG;AAC7D,YAAQ,OAAO,CAAC;AAEhB,YAAQ,MAAM,CAAC,GAAG;AAAA,MAChB,KAAK;AAAA,MACL,KAAK;AACH,kBAAU,KAAK,KAAK;AACpB,iBAAS,KAAK,KAAK;AACnB,oBAAY,MAAM,CAAC,IAAI,CAAC;AACxB;AAAA,MACF,KAAK;AACH,kBAAU,SAAS,IAAI;AACvB,gBAAQ,CAAC,IAAI,MAAM,CAAC;AACpB,oBAAY,SAAS,SAAS,IAAI,SAAS,SAAS,SAAS,CAAC,EAAE,CAAC,IAAI;AACrE;AAAA,MACF;AACE,kBAAU,KAAK,KAAK;AAAA,IACxB;AAAA,EACF;AAEA,SAAO;AACT;AAMA,SAAS,QAAS,QAAQ;AACxB,OAAK,SAAS;AACd,OAAK,OAAO;AACZ,OAAK,MAAM;AACb;AAKA,QAAQ,UAAU,MAAM,SAAS,MAAO;AACtC,SAAO,KAAK,SAAS;AACvB;AAMA,QAAQ,UAAU,OAAO,SAAS,KAAM,IAAI;AAC1C,MAAI,QAAQ,KAAK,KAAK,MAAM,EAAE;AAE9B,MAAI,CAAC,SAAS,MAAM,UAAU;AAC5B,WAAO;AAET,MAAI,SAAS,MAAM,CAAC;AAEpB,OAAK,OAAO,KAAK,KAAK,UAAU,OAAO,MAAM;AAC7C,OAAK,OAAO,OAAO;AAEnB,SAAO;AACT;AAMA,QAAQ,UAAU,YAAY,SAAS,UAAW,IAAI;AACpD,MAAI,QAAQ,KAAK,KAAK,OAAO,EAAE,GAAG;AAElC,UAAQ,OAAO;AAAA,IACb,KAAK;AACH,cAAQ,KAAK;AACb,WAAK,OAAO;AACZ;AAAA,IACF,KAAK;AACH,cAAQ;AACR;AAAA,IACF;AACE,cAAQ,KAAK,KAAK,UAAU,GAAG,KAAK;AACpC,WAAK,OAAO,KAAK,KAAK,UAAU,KAAK;AAAA,EACzC;AAEA,OAAK,OAAO,MAAM;AAElB,SAAO;AACT;AAMA,SAAS,QAAS,MAAM,eAAe;AACrC,OAAK,OAAO;AACZ,OAAK,QAAQ,EAAE,KAAK,KAAK,KAAK;AAC9B,OAAK,SAAS;AAChB;AAMA,QAAQ,UAAU,OAAO,SAAS,KAAM,MAAM;AAC5C,SAAO,IAAI,QAAQ,MAAM,IAAI;AAC/B;AAMA,QAAQ,UAAU,SAAS,SAAS,OAAQ,MAAM;AAChD,MAAI,QAAQ,KAAK;AAEjB,MAAI;AACJ,MAAI,MAAM,eAAe,IAAI,GAAG;AAC9B,YAAQ,MAAM,IAAI;AAAA,EACpB,OAAO;AACL,QAAI,UAAU,MAAM,mBAAmB,OAAO,OAAO,YAAY;AAEjE,WAAO,SAAS;AACd,UAAI,KAAK,QAAQ,GAAG,IAAI,GAAG;AACzB,4BAAoB,QAAQ;AAC5B,gBAAQ,KAAK,MAAM,GAAG;AACtB,gBAAQ;AAmBR,eAAO,qBAAqB,QAAQ,QAAQ,MAAM,QAAQ;AACxD,cAAI,UAAU,MAAM,SAAS;AAC3B,wBACE,YAAY,mBAAmB,MAAM,KAAK,CAAC,KACxC,wBAAwB,mBAAmB,MAAM,KAAK,CAAC;AAG9D,8BAAoB,kBAAkB,MAAM,OAAO,CAAC;AAAA,QACtD;AAAA,MACF,OAAO;AACL,4BAAoB,QAAQ,KAAK,IAAI;AAqBrC,oBAAY,YAAY,QAAQ,MAAM,IAAI;AAAA,MAC5C;AAEA,UAAI,WAAW;AACb,gBAAQ;AACR;AAAA,MACF;AAEA,gBAAU,QAAQ;AAAA,IACpB;AAEA,UAAM,IAAI,IAAI;AAAA,EAChB;AAEA,MAAI,WAAW,KAAK;AAClB,YAAQ,MAAM,KAAK,KAAK,IAAI;AAE9B,SAAO;AACT;AAOA,SAAS,SAAU;AACjB,OAAK,gBAAgB;AAAA,IACnB,QAAQ,CAAC;AAAA,IACT,KAAK,SAAS,IAAK,KAAK,OAAO;AAC7B,WAAK,OAAO,GAAG,IAAI;AAAA,IACrB;AAAA,IACA,KAAK,SAAS,IAAK,KAAK;AACtB,aAAO,KAAK,OAAO,GAAG;AAAA,IACxB;AAAA,IACA,OAAO,SAAS,QAAS;AACvB,WAAK,SAAS,CAAC;AAAA,IACjB;AAAA,EACF;AACF;AAKA,OAAO,UAAU,aAAa,SAAS,aAAc;AACnD,MAAI,OAAO,KAAK,kBAAkB,aAAa;AAC7C,SAAK,cAAc,MAAM;AAAA,EAC3B;AACF;AAOA,OAAO,UAAU,QAAQ,SAAS,MAAO,UAAU,MAAM;AACvD,MAAI,QAAQ,KAAK;AACjB,MAAI,WAAW,WAAW,OAAO,QAAQ,SAAS,MAAM,KAAK,GAAG;AAChE,MAAI,iBAAiB,OAAO,UAAU;AACtC,MAAI,SAAS,iBAAiB,MAAM,IAAI,QAAQ,IAAI;AAEpD,MAAI,UAAU,QAAW;AACvB,aAAS,cAAc,UAAU,IAAI;AACrC,sBAAkB,MAAM,IAAI,UAAU,MAAM;AAAA,EAC9C;AACA,SAAO;AACT;AAyBA,OAAO,UAAU,SAAS,SAAS,OAAQ,UAAU,MAAM,UAAU,QAAQ;AAC3E,MAAI,OAAO,KAAK,cAAc,MAAM;AACpC,MAAI,SAAS,KAAK,MAAM,UAAU,IAAI;AACtC,MAAI,UAAW,gBAAgB,UAAW,OAAO,IAAI,QAAQ,MAAM,MAAS;AAC5E,SAAO,KAAK,aAAa,QAAQ,SAAS,UAAU,UAAU,MAAM;AACtE;AAWA,OAAO,UAAU,eAAe,SAAS,aAAc,QAAQ,SAAS,UAAU,kBAAkB,QAAQ;AAC1G,MAAI,SAAS;AAEb,MAAI,OAAO,QAAQ;AACnB,WAAS,IAAI,GAAG,YAAY,OAAO,QAAQ,IAAI,WAAW,EAAE,GAAG;AAC7D,YAAQ;AACR,YAAQ,OAAO,CAAC;AAChB,aAAS,MAAM,CAAC;AAEhB,QAAI,WAAW,IAAK,SAAQ,KAAK,cAAc,OAAO,SAAS,UAAU,kBAAkB,MAAM;AAAA,aACxF,WAAW,IAAK,SAAQ,KAAK,eAAe,OAAO,SAAS,UAAU,kBAAkB,MAAM;AAAA,aAC9F,WAAW,IAAK,SAAQ,KAAK,cAAc,OAAO,SAAS,UAAU,MAAM;AAAA,aAC3E,WAAW,IAAK,SAAQ,KAAK,eAAe,OAAO,OAAO;AAAA,aAC1D,WAAW,OAAQ,SAAQ,KAAK,aAAa,OAAO,SAAS,MAAM;AAAA,aACnE,WAAW,OAAQ,SAAQ,KAAK,SAAS,KAAK;AAEvD,QAAI,UAAU;AACZ,gBAAU;AAAA,EACd;AAEA,SAAO;AACT;AAEA,OAAO,UAAU,gBAAgB,SAAS,cAAe,OAAO,SAAS,UAAU,kBAAkB,QAAQ;AAC3G,MAAI,OAAO;AACX,MAAI,SAAS;AACb,MAAI,QAAQ,QAAQ,OAAO,MAAM,CAAC,CAAC;AAInC,WAAS,UAAW,UAAU;AAC5B,WAAO,KAAK,OAAO,UAAU,SAAS,UAAU,MAAM;AAAA,EACxD;AAEA,MAAI,CAAC,MAAO;AAEZ,MAAI,QAAQ,KAAK,GAAG;AAClB,aAAS,IAAI,GAAG,cAAc,MAAM,QAAQ,IAAI,aAAa,EAAE,GAAG;AAChE,gBAAU,KAAK,aAAa,MAAM,CAAC,GAAG,QAAQ,KAAK,MAAM,CAAC,CAAC,GAAG,UAAU,kBAAkB,MAAM;AAAA,IAClG;AAAA,EACF,WAAW,OAAO,UAAU,YAAY,OAAO,UAAU,YAAY,OAAO,UAAU,UAAU;AAC9F,cAAU,KAAK,aAAa,MAAM,CAAC,GAAG,QAAQ,KAAK,KAAK,GAAG,UAAU,kBAAkB,MAAM;AAAA,EAC/F,WAAW,WAAW,KAAK,GAAG;AAC5B,QAAI,OAAO,qBAAqB;AAC9B,YAAM,IAAI,MAAM,gEAAgE;AAGlF,YAAQ,MAAM,KAAK,QAAQ,MAAM,iBAAiB,MAAM,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,GAAG,SAAS;AAEtF,QAAI,SAAS;AACX,gBAAU;AAAA,EACd,OAAO;AACL,cAAU,KAAK,aAAa,MAAM,CAAC,GAAG,SAAS,UAAU,kBAAkB,MAAM;AAAA,EACnF;AACA,SAAO;AACT;AAEA,OAAO,UAAU,iBAAiB,SAAS,eAAgB,OAAO,SAAS,UAAU,kBAAkB,QAAQ;AAC7G,MAAI,QAAQ,QAAQ,OAAO,MAAM,CAAC,CAAC;AAInC,MAAI,CAAC,SAAU,QAAQ,KAAK,KAAK,MAAM,WAAW;AAChD,WAAO,KAAK,aAAa,MAAM,CAAC,GAAG,SAAS,UAAU,kBAAkB,MAAM;AAClF;AAEA,OAAO,UAAU,gBAAgB,SAAS,cAAe,SAAS,aAAa,iBAAiB;AAC9F,MAAI,sBAAsB,YAAY,QAAQ,WAAW,EAAE;AAC3D,MAAI,cAAc,QAAQ,MAAM,IAAI;AACpC,WAAS,IAAI,GAAG,IAAI,YAAY,QAAQ,KAAK;AAC3C,QAAI,YAAY,CAAC,EAAE,WAAW,IAAI,KAAK,CAAC,kBAAkB;AACxD,kBAAY,CAAC,IAAI,sBAAsB,YAAY,CAAC;AAAA,IACtD;AAAA,EACF;AACA,SAAO,YAAY,KAAK,IAAI;AAC9B;AAEA,OAAO,UAAU,gBAAgB,SAAS,cAAe,OAAO,SAAS,UAAU,QAAQ;AACzF,MAAI,CAAC,SAAU;AACf,MAAI,OAAO,KAAK,cAAc,MAAM;AAEpC,MAAI,QAAQ,WAAW,QAAQ,IAAI,SAAS,MAAM,CAAC,CAAC,IAAI,SAAS,MAAM,CAAC,CAAC;AACzE,MAAI,SAAS,MAAM;AACjB,QAAI,kBAAkB,MAAM,CAAC;AAC7B,QAAI,WAAW,MAAM,CAAC;AACtB,QAAI,cAAc,MAAM,CAAC;AACzB,QAAI,gBAAgB;AACpB,QAAI,YAAY,KAAK,aAAa;AAChC,sBAAgB,KAAK,cAAc,OAAO,aAAa,eAAe;AAAA,IACxE;AACA,QAAI,SAAS,KAAK,MAAM,eAAe,IAAI;AAC3C,WAAO,KAAK,aAAa,QAAQ,SAAS,UAAU,eAAe,MAAM;AAAA,EAC3E;AACF;AAEA,OAAO,UAAU,iBAAiB,SAAS,eAAgB,OAAO,SAAS;AACzE,MAAI,QAAQ,QAAQ,OAAO,MAAM,CAAC,CAAC;AACnC,MAAI,SAAS;AACX,WAAO;AACX;AAEA,OAAO,UAAU,eAAe,SAAS,aAAc,OAAO,SAAS,QAAQ;AAC7E,MAAI,SAAS,KAAK,gBAAgB,MAAM,KAAK,SAAS;AACtD,MAAI,QAAQ,QAAQ,OAAO,MAAM,CAAC,CAAC;AACnC,MAAI,SAAS;AACX,WAAQ,OAAO,UAAU,YAAY,WAAW,SAAS,SAAU,OAAO,KAAK,IAAI,OAAO,KAAK;AACnG;AAEA,OAAO,UAAU,WAAW,SAAS,SAAU,OAAO;AACpD,SAAO,MAAM,CAAC;AAChB;AAEA,OAAO,UAAU,gBAAgB,SAAS,cAAe,QAAQ;AAC/D,MAAI,QAAQ,MAAM,GAAG;AACnB,WAAO;AAAA,EACT,WACS,UAAU,OAAO,WAAW,UAAU;AAC7C,WAAO,OAAO;AAAA,EAChB,OACK;AACH,WAAO;AAAA,EACT;AACF;AAEA,OAAO,UAAU,kBAAkB,SAAS,gBAAiB,QAAQ;AACnE,MAAI,UAAU,OAAO,WAAW,YAAY,CAAC,QAAQ,MAAM,GAAG;AAC5D,WAAO,OAAO;AAAA,EAChB,OACK;AACH,WAAO;AAAA,EACT;AACF;AAEA,IAAI,WAAW;AAAA,EACb,MAAM;AAAA,EACN,SAAS;AAAA,EACT,MAAM,CAAE,MAAM,IAAK;AAAA,EACnB,YAAY;AAAA,EACZ,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,SAAS;AAAA,EACT,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMR,IAAI,cAAe,OAAO;AACxB,kBAAc,gBAAgB;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA,EAIA,IAAI,gBAAiB;AACnB,WAAO,cAAc;AAAA,EACvB;AACF;AAGA,IAAI,gBAAgB,IAAI,OAAO;AAK/B,SAAS,aAAa,SAASC,cAAc;AAC3C,SAAO,cAAc,WAAW;AAClC;AAOA,SAAS,QAAQ,SAASC,OAAO,UAAU,MAAM;AAC/C,SAAO,cAAc,MAAM,UAAU,IAAI;AAC3C;AAMA,SAAS,SAAS,SAASC,QAAQ,UAAU,MAAM,UAAU,QAAQ;AACnE,MAAI,OAAO,aAAa,UAAU;AAChC,UAAM,IAAI,UAAU,0DACU,QAAQ,QAAQ,IAAI,iFAC0B;AAAA,EAC9E;AAEA,SAAO,cAAc,OAAO,UAAU,MAAM,UAAU,MAAM;AAC9D;AAIA,SAAS,SAAS;AAGlB,SAAS,UAAU;AACnB,SAAS,UAAU;AACnB,SAAS,SAAS;AAElB,IAAO,mBAAQ;;;IC3vBFC,2BAAkB;EAG7BC,cAAA;AAFA,SAAMC,SAAoD,CAAA;AAGxD,SAAKA,SAAS,CAAA;EAChB;EAEAC,GAAGC,OAAeC,UAAkC;AAClD,QAAI,CAAC,KAAKH,OAAOE,KAAK,GAAG;AACvB,WAAKF,OAAOE,KAAK,IAAI,CAAA;IACvB;AACA,SAAKF,OAAOE,KAAK,EAAEE,KAAKD,QAAQ;AAEhC,WAAO,MAAK;AACV,WAAKH,OAAOE,KAAK,IAAI,KAAKF,OAAOE,KAAK,EAAEG,OAAQC,OAAMA,MAAMH,QAAQ;;EAExE;EAEAI,KAAKL,OAAeM,SAAY;AAC9B,eAAWL,YAAY,KAAKH,OAAOE,KAAK,KAAK,CAAA,GAAI;AAC/CC,eAASK,OAAO;IAClB;AACA,eAAWL,YAAY,KAAKH,OAAO,GAAG,KAAK,CAAA,GAAI;AAC7CG,eAASD,OAAOM,OAAO;IACzB;EACF;AACD;ACxBM,IAAMC,mCAAmC;AAEhD,IAAMC,0BAAN,MAA6B;EAG3BX,YACSY,OACPC,YAAkB;AADX,SAAKD,QAALA;AAGP,SAAKE,UAAUC,KAAKC,IAAG,IAAKH,aAAa;EAC3C;EAEA,IAAII,YAAS;AACX,WAAOF,KAAKC,IAAG,IAAK,KAAKF;EAC3B;AACD;IACYI,4BAAmB;EAK9BlB,cAAA;AACE,SAAKmB,SAAS,oBAAIC,IAAG;AACrB,SAAKC,qBAAqBX;AAC1B,SAAKY,kBAAkB,oBAAIF,IAAG;EAChC;EAEOG,oBAAoBC,KAAW;AACpC,WAAO,KAAKL,OAAOM,IAAID,GAAG,KAAK;EACjC;EAEOE,IAAIF,KAAaZ,OAA6BC,YAAmB;AACtE,UAAMc,sBAAsBd,cAAc,KAAKQ;AAC/C,SAAKF,OAAOO,IAAIF,KAAK,IAAIb,wBAAwBC,OAAOe,mBAAmB,CAAC;EAC9E;EAEOC,qBAAqBJ,KAAaK,SAAqB;AAC5D,SAAKP,gBAAgBI,IAAIF,KAAKK,OAAO;AACrCA,YACGC,KAAK,MAAK;AACT,WAAKR,gBAAgBS,OAAOP,GAAG;IACjC,CAAC,EACAQ,MAAM,MAAK;AACV,WAAKV,gBAAgBS,OAAOP,GAAG;IACjC,CAAC;EACL;EAEOS,aAAaT,KAAW;AAC7B,WAAO,KAAKF,gBAAgBY,IAAIV,GAAG;EACrC;EAEOW,WAAWC,YAAkB;AAClCC,YAAQC,IAAI,gBAAgBF,YAAY,KAAKjB,OAAOoB,KAAI,CAAE;AAC1D,eAAWf,OAAO,KAAKL,OAAOoB,KAAI,GAAI;AACpC,UAAIf,IAAIgB,WAAWJ,UAAU,GAAG;AAC9B,aAAKjB,OAAOY,OAAOP,GAAG;MACxB;IACF;EACF;AACD;ICpBWiB;CAAZ,SAAYA,4BAAyB;AACnCA,EAAAA,2BAAA,OAAA,IAAA;AACAA,EAAAA,2BAAA,OAAA,IAAA;AACAA,EAAAA,2BAAA,UAAA,IAAA;AACF,GAJYA,8BAAAA,4BAIX,CAAA,EAAA;IAsJWC;CAAZ,SAAYA,kBAAe;AACzBA,EAAAA,iBAAA,aAAA,IAAA;AACAA,EAAAA,iBAAA,aAAA,IAAA;AACF,GAHYA,oBAAAA,kBAGX,CAAA,EAAA;ACxLDC,iBAASC,SAAS,SAAUC,MAAI;AAC9B,SAAOA;AACT;AAEA,IAAeC,mBAAf,MAA+B;EAU7B9C,YAAY+C,SAAsCC,aAAa,OAAOC,MAAqB;AACzF,SAAKC,OAAOH,QAAOG;AACnB,SAAKC,UAAUJ,QAAOI;AACtB,SAAKC,SAASL,QAAOK;AACrB,SAAKC,SAASN,QAAOM;AACrB,SAAKC,OAAOP,QAAOO;AACnB,SAAKN,aAAaA;AAClB,SAAKC,OAAOA;AACZ,SAAKM,gBAAgBR,QAAOQ;EAC9B;EAcUC,+BAA+BC,SAAe;AACtD,UAAMC,qBAAqB,KAAKC,uBAAuBF,OAAO;AAE9D,WAAOC,mBAAmBE,QAAQ,kBAAkB,MAAM;EAC5D;;;;;;;;;;;;EAaUD,uBAAuBd,MAAY;AAC3C,UAAMgB,MAAgB,CAAA;AACtB,UAAMC,QAAmB,CAAA;AACzB,QAAIC,IAAI;AACR,UAAMC,IAAInB,KAAKoB;AAEf,WAAOF,IAAIC,GAAG;AACZ,YAAME,KAAKrB,KAAKkB,CAAC;AAGjB,UAAIG,OAAO,KAAK;AAEd,YAAIH,IAAI,IAAIC,KAAKnB,KAAKkB,IAAI,CAAC,MAAM,KAAK;AACpCF,cAAIxD,KAAK,IAAI;AACb0D,eAAK;AACL;QACF;AAGA,YAAII,IAAIJ,IAAI;AACZ,eAAOI,IAAIH,KAAK,KAAKI,KAAKvB,KAAKsB,CAAC,CAAC,GAAG;AAClCA;QACF;AAEA,cAAME,SAASF,IAAIH,MAAMnB,KAAKsB,CAAC,MAAM,OAAOtB,KAAKsB,CAAC,MAAM;AACxDN,YAAIxD,KAAKgE,SAAS,OAAO,GAAG;AAC5BP,cAAMzD,KAAKgE,MAAM;AACjBN,aAAK;AACL;MACF;AAGA,UAAIG,OAAO,KAAK;AAEd,YAAIH,IAAI,IAAIC,KAAKnB,KAAKkB,IAAI,CAAC,MAAM,KAAK;AACpCF,cAAIxD,KAAK,IAAI;AACb0D,eAAK;AACL;QACF;AAEA,cAAMM,SAASP,MAAMQ,IAAG,KAAM;AAC9BT,YAAIxD,KAAKgE,SAAS,OAAO,GAAG;AAC5BN,aAAK;AACL;MACF;AAGAF,UAAIxD,KAAK6D,EAAE;AACXH,WAAK;IACP;AAEA,WAAOF,IAAIU,KAAK,EAAE;EACpB;AAGD;AAEK,IAAOC,mBAAP,cAAgC1B,iBAAgB;EAIpD9C,YAAY+C,SAAoBC,aAAa,OAAK;AAChD,UAAMD,SAAQC,YAAY,MAAM;AAChC,SAAKyB,iBAAiB1B;AACtB,SAAKA,SAASA,QAAOA;EACvB;EAEA2B,QAAQC,WAAoCC,eAAmC;AAC7E,WAAOjC,iBAASkC,OAAO,KAAKJ,eAAe1B,QAAQ4B,aAAa,CAAA,CAAE;EACpE;EAEOG,mBAAmBC,UAAiD;AASzE,WAAO,KAAKvB,+BAA+B,KAAKT,MAAM;EACxD;EAEOiC,SAAM;AACX,WAAOC,KAAKC,UAAU;MACpBhC,MAAM,KAAKA;MACXH,QAAQ,KAAKA;MACbI,SAAS,KAAKA;MACdH,YAAY,KAAKA;MACjBM,MAAM,KAAKA;MACXD,QAAQ,KAAKA;MACbJ,MAAM,KAAKA;MACXG,QAAQ,KAAKA;IACd,CAAA;EACH;AACD;AAEK,IAAO+B,mBAAP,MAAOA,0BAAyBrC,iBAAgB;EAIpD9C,YAAY+C,SAA0BC,aAAa,OAAK;AACtD,UAAMoC,mBAAmBD,kBAAiBE,gBAAgBtC,QAAOA,MAAM;AACvE,UAAMuC,cAA0B;MAC9B,GAAGvC;MACHA,QAAQqC;;AAGV,UAAME,aAAatC,YAAY,MAAM;AACrC,SAAKyB,iBAAiBa;AACtB,SAAKvC,SAASqC;EAChB;EAEQ,OAAOC,gBAAgBtC,SAAqD;AAElF,WAAOA,QAAOwC,IAAKC,UAAqC;AACtD,UAAI,UAAUA,MAAM;AAElB,eAAOA;MACT,OAAO;AAEL,eAAO;UAAEvC,MAAMP,gBAAgB+C;UAAa,GAAGD;;MACjD;IACF,CAAC;EACH;EAEAd,QAAQC,WAAoCe,cAAkC;AAa5E,UAAMC,mCAAuE,CAAA;AAC7E,UAAMC,oBAAoBF,gBAAgB,CAAA;AAE1C,eAAWF,QAAQ,KAAKzC,QAAQ;AAC9B,UAAI,UAAUyC,QAAQA,KAAKvC,SAASP,gBAAgBmD,aAAa;AAC/D,cAAMC,mBAAmBF,kBAAkBJ,KAAKtC,IAAI;AACpD,YACE6C,MAAMC,QAAQF,gBAAgB,KAC9BA,iBAAiB7B,SAAS,KAC1B6B,iBAAiBG,MAAOC,SAAQ,OAAOA,QAAQ,YAAY,UAAUA,OAAO,aAAaA,GAAG,GAC5F;AACAP,2CAAiCtF,KAAK,GAAIyF,gBAAkC;QAC9E,WAAWC,MAAMC,QAAQF,gBAAgB,KAAKA,iBAAiB7B,WAAW,EAAG;iBAElE6B,qBAAqBK,QAAW;AAEzCR,2CAAiCtF,KAAK4E,KAAKC,UAAUY,gBAAgB,CAAC;QACxE,OAAO;AAELH,2CAAiCtF,KAAKmF,IAA2D;QACnG;MACF,WAAW,UAAUA,QAAQ,aAAaA,QAAQA,KAAKvC,SAASP,gBAAgB+C,aAAa;AAC3FE,yCAAiCtF,KAAK;UACpC+F,MAAMZ,KAAKY;UACX3C,SAAS+B,KAAK/B;QACf,CAAA;MACH;IACF;AAEA,WAAOkC,iCAAiCJ,IAAKC,UAAQ;AACnD,UAAI,OAAOA,SAAS,YAAYA,SAAS,QAAQ,UAAUA,QAAQ,aAAaA,MAAM;AACpF,eAAO;UACL,GAAGA;UACH/B,SAASd,iBAASkC,OAAOW,KAAK/B,SAASkB,aAAa,CAAA,CAAE;;MAE1D,OAAO;AAEL,eAAOa;MACT;IACF,CAAC;EACH;EAEOV,mBAAmBuB,SAEzB;AAiBC,UAAMV,mCAAyF,CAAA;AAC/F,UAAMC,oBAAoBS,SAASX,gBAAgB,CAAA;AAEnD,eAAWF,QAAQ,KAAKzC,QAAQ;AAC9B,UAAI,UAAUyC,QAAQA,KAAKvC,SAASP,gBAAgBmD,aAAa;AAC/D,cAAMC,mBAAmBF,kBAAkBJ,KAAKtC,IAAI;AACpD,YACE6C,MAAMC,QAAQF,gBAAgB,KAC9BA,iBAAiB7B,SAAS,KAC1B6B,iBAAiBG,MAAOC,SAAQ,OAAOA,QAAQ,YAAY,UAAUA,OAAO,aAAaA,GAAG,GAC5F;AAEAP,2CAAiCtF,KAC/B,GAAIyF,iBAAmCP,IAAKW,SAAO;AACjD,mBAAO;cACLE,MAAMF,IAAIE;cACV3C,SAAS,KAAKD,+BAA+B0C,IAAIzC,OAAO;;UAE5D,CAAC,CAAC;QAEN,WAAWsC,MAAMC,QAAQF,gBAAgB,KAAKA,iBAAiB7B,WAAW,EAAG;iBAElE6B,qBAAqBK,QAAW;AAEzCR,2CAAiCtF,KAAK4E,KAAKC,UAAUY,gBAAgB,CAAC;QACxE,OAAO;AAELH,2CAAiCtF,KAAK;YACpCiG,cAAcd,KAAKtC;YACnBqD,UAAU;UACX,CAAA;QACH;MACF,WAAW,UAAUf,QAAQ,aAAaA,QAAQA,KAAKvC,SAASP,gBAAgB+C,aAAa;AAC3FE,yCAAiCtF,KAAK;UACpC+F,MAAMZ,KAAKY;UACX3C,SAAS,KAAKD,+BAA+BgC,KAAK/B,OAAO;QAC1D,CAAA;MACH;IACF;AAEA,WAAOkC;EACT;EAEOX,SAAM;AACX,WAAOC,KAAKC,UAAU;MACpBhC,MAAM,KAAKA;MACXH,QAAQ,KAAK0B,eAAe1B,OAAOwC,IAAKC,UAAQ;AAC9C,YAAI,UAAUA,QAAQA,KAAKvC,SAASP,gBAAgB+C,aAAa;AAC/D,gBAAM;YAAExC,MAAMuD;YAAG,GAAGC;UAAkB,IAAKjB;AAC3C,iBAAOiB;QACT;AACA,eAAOjB;MACT,CAAC;MACDrC,SAAS,KAAKA;MACdH,YAAY,KAAKA;MACjBM,MAAM,KAAKA;MACXD,QAAQ,KAAKA;MACbJ,MAAM,KAAKA;MACXG,QAAQ,KAAKA;IACd,CAAA;EACH;AACD;ACvUe,SAAAsD,OAAOC,aAAkBC,SAAe;AACtD,MAAI,CAACD,aAAa;AAChB,UAAM,IAAIE,MAAMD,OAAO;EACzB;AACF;AAEM,SAAUE,oBAAoBC,KAAW;AAC7C,SAAOA,KAAKnD,QAAQ,QAAQ,EAAE;AAChC;AAQO,eAAeoD,UACpBC,IACAC,QAA0B,CAAA,GAC1B5E,KAA0B;AAE1B,QAAM;IAAE6E,aAAa;IAAGC,aAAa;IAAMC,aAAaA,MAAM;EAAM,IAAGH;AACvE,MAAII,YAAY;AAEhB,WAASvD,IAAI,GAAGA,IAAIoD,aAAa,GAAGpD,KAAK;AACvC,QAAIA,IAAI,GAAG;AAET,YAAM,IAAIwD,QAASC,aAAYC,WAAWD,SAASJ,UAAU,CAAC;AAC9D9E,UAAI,YAAYyB,IAAI,CAAC,OAAOoD,aAAa,CAAC,EAAE;IAC9C;AAEA,QAAI;AACF,YAAMO,MAAM,MAAMT,GAAE;AACpB,aAAOS;aACAC,GAAG;AACVL,kBAAYK;AACZ,UAAI,CAACN,WAAWM,CAAC,GAAG;AAClB,cAAMA;MACR;AACArF,UAAI,oBAAoB2C,KAAKC,UAAUyC,CAAC,CAAC,EAAE;IAC7C;EACF;AAEA,QAAML;AACR;AAGM,SAAUM,aAAaC,aAAgB;AAE3C,MAAIC,KAAI,oBAAI/G,KAAI,GAAGgH,QAAO;AAC1B,MAAIC,KACDH,eAAcA,YAAWI,eAAeJ,YAAWI,YAAYjH,OAAO6G,YAAWI,YAAYjH,IAAG,IAAK,OAAS;AACjH,SAAO,uCAAuC4C,QAAQ,SAAS,SAAUsE,GAAC;AACxE,QAAIC,IAAIC,KAAKC,OAAM,IAAK;AACxB,QAAIP,IAAI,GAAG;AAETK,WAAKL,IAAIK,KAAK,KAAK;AACnBL,UAAIM,KAAKE,MAAMR,IAAI,EAAE;IACvB,OAAO;AAELK,WAAKH,KAAKG,KAAK,KAAK;AACpBH,WAAKI,KAAKE,MAAMN,KAAK,EAAE;IACzB;AACA,YAAQE,MAAM,MAAMC,IAAKA,IAAI,IAAO,GAAKI,SAAS,EAAE;EACtD,CAAC;AACH;SAEgBC,mBAAgB;AAC9B,UAAO,oBAAIzH,KAAI,GAAGgH,QAAO;AAC3B;SAEgBU,iBAAc;AAC5B,UAAO,oBAAI1H,KAAI,GAAG2H,YAAW;AAC/B;AAEgB,SAAAC,eAAe1B,IAAgB2B,SAAe;AAG5D,QAAMC,IAAIpB,WAAWR,IAAI2B,OAAO;AAEhCC,KAAGC,SAASD,GAAGC,MAAK;AACpB,SAAOD;AACT;AAEM,SAAUE,OAAmBvH,KAAW;AAC5C,MAAI,OAAOwH,YAAY,eAAeA,QAAQC,IAAIzH,GAAG,GAAG;AACtD,WAAOwH,QAAQC,IAAIzH,GAAG;EACxB,WAAW,OAAOqG,eAAe,aAAa;AAC5C,WAAQA,WAAmBrG,GAAG;EAChC;AACA;AACF;SAEgB0H,kBAAkBC,QAA8BC,iBAA0B,MAAI;AAC5F,QAAM;IAAEC;IAAWC;IAAW,GAAGC;EAAW,IAAKJ,UAAU,CAAA;AAG3D,QAAMK,iBAAiBH,aAAaN,OAAO,qBAAqB;AAChE,QAAMU,iBAAiBL,iBAAiBE,aAAaP,OAAO,qBAAqB,IAAI5C;AACrF,QAAMuD,eAAeH,YAAYI,WAAWZ,OAAO,kBAAkB;AAErE,QAAMa,mBAAmB;IACvB,GAAGL;IACHI,SAASD;;AAGX,SAAO;IACLL,WAAWG;IACX,GAAIJ,iBAAiB;MAAEE,WAAWG;QAAmBtD;IACrD,GAAGyD;;AAEP;AAEO,IAAMC,oBAAqBV,YAA2C;AAC3E,QAAMW,cAAc,IAAIC,gBAAe;AACvCC,SAAOC,QAAQd,UAAU,CAAA,CAAE,EAAEe,QAAQ,CAAC,CAAC1I,KAAKZ,KAAK,MAAK;AACpD,QAAIA,UAAUuF,UAAavF,UAAU,MAAM;AAEzC,UAAIA,iBAAiBG,MAAM;AACzB+I,oBAAYK,OAAO3I,KAAKZ,MAAM8H,YAAW,CAAE;MAC7C,OAAO;AACLoB,oBAAYK,OAAO3I,KAAKZ,MAAM2H,SAAQ,CAAE;MAC1C;IACF;EACF,CAAC;AACD,SAAOuB,YAAYvB,SAAQ;AAC7B;;;;;;;;;;;;;;AC9HA,IAAM6B,sBAAsB;;EAE1B;EACA;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;AAAuB;SAGTC,uBAAoB;AAClC,aAAW7I,OAAO4I,qBAAqB;AACrC,UAAMxJ,QAAQmI,OAAOvH,GAAG;AACxB,QAAIZ,OAAO;AACT,aAAOA;IACT;EACF;AACA,SAAOuF;AACT;AC9BA,IAAImE,KAAU;AACd,IAAIC,eAAoB;AAKxB,IAAMC,gBAAiBC,YAAgC;AACrD,SAAO;;IAAiCA;;AAC1C;AAEA,IAAI,OAAQ5C,WAAmB6C,SAAS,aAAa;AAEnDnD,UAAQoD,IAAI,CAACH,cAAc,SAAS,GAAGA,cAAc,aAAa,CAAC,CAAC,EACjE1I,KAAK,CAAC,CAAC8I,YAAYC,cAAc,MAAK;AACrCP,SAAKM;AACLL,mBAAeM;EACjB,CAAC,EACA7I,MAAK;AACV,WAAW,OAAOgH,YAAY,eAAeA,QAAQ8B,UAAUC,MAAM;AAEnExD,UAAQoD,IAAI,CAACH,cAAc,IAAI,GAAGA,cAAc,QAAQ,CAAC,CAAC,EACvD1I,KAAK,CAAC,CAAC8I,YAAYC,cAAc,MAAK;AACrCP,SAAKM;AACLL,mBAAeM;EACjB,CAAC,EACA7I,MAAK;AACV,WAAW,OAAOgJ,WAAW,aAAa;AAExCT,iBAAeS;AACjB;AAuBA,IAAMC,gBAAN,MAAMA,eAAa;EAQjBjL,YAAYmJ,QAMX;AACC,UAAM;MAAE+B;MAAKC;MAAeC;MAAaC;MAAcC;IAAU,IAAGnC;AAEpE,SAAK+B,MAAMA;AACX,SAAKK,WAAWpF;AAEhB,QAAIgF,eAAe;AACjB,YAAM,CAACK,oBAAoBC,iBAAiB,IAAI,KAAKC,mBAAmBP,aAAa;AACrF,WAAKQ,gBAAgBH;AACrB,WAAKI,eAAeH;AACpB,WAAKI,UAAU;IACjB,WAAWR,gBAAgBD,aAAa;AACtC,WAAKO,gBAAgBN;AACrB,WAAKO,eAAeR;AACpB,WAAKS,UAAU;IACjB,WAAWP,YAAYF,aAAa;AAClC,UAAI,CAACd,IAAI;AACP,cAAM,IAAIzD,MAAM,0DAA0D;MAC5E;AAEA,UAAI,CAACyD,GAAGwB,WAAWR,QAAQ,GAAG;AAC5B,cAAM,IAAIzE,MAAM,gBAAgByE,QAAQ,iBAAiB;MAC3D;AAEA,WAAKK,gBAAgB,KAAKI,SAAST,QAAQ;AAC3C,WAAKM,eAAe,KAAKD,gBAAgBP,cAAcjF;AACvD,WAAK0F,UAAU,KAAKF,gBAAgB,SAASxF;IAC/C,OAAO;AACL9D,cAAQ2J,MAAM,+FAA+F;IAC/G;EACF;EAEQD,SAAST,UAAgB;AAC/B,QAAI;AACF,UAAI,CAAChB,IAAI;AACP,cAAM,IAAIzD,MAAM,0DAA0D;MAC5E;AAEA,aAAOyD,GAAG2B,aAAaX,QAAQ;aACxBU,OAAO;AACd3J,cAAQ2J,MAAM,8BAA8BV,QAAQ,IAAIU,KAAK;AAC7D,aAAO7F;IACT;EACF;EAEQuF,mBAAmBQ,MAAY;AACrC,QAAI;AACF,UAAI,CAACA,QAAQ,OAAOA,SAAS,UAAU;AACrC,cAAM,IAAIrF,MAAM,0BAA0B;MAC5C;AAEA,UAAI,CAACqF,KAAK1J,WAAW,OAAO,GAAG;AAC7B,cAAM,IAAIqE,MAAM,sCAAsC;MACxD;AAEA,YAAM,CAACsF,QAAQC,UAAU,IAAIF,KAAKG,MAAM,CAAC,EAAEC,MAAM,KAAK,CAAC;AACvD,UAAI,CAACH,UAAU,CAACC,YAAY;AAC1B,cAAM,IAAIvF,MAAM,aAAa;MAC/B;AAEA,YAAM0F,cAAcJ,OAAOG,MAAM,GAAG;AACpC,UAAI,CAACC,YAAYC,SAAS,QAAQ,GAAG;AACnC,cAAM,IAAI3F,MAAM,4BAA4B;MAC9C;AAEA,YAAMuE,cAAcmB,YAAY,CAAC;AACjC,UAAI,CAACnB,aAAa;AAChB,cAAM,IAAIvE,MAAM,uBAAuB;MACzC;AAEA,aAAO,CAAC4F,OAAOC,KAAKN,YAAY,QAAQ,GAAGhB,WAA+B;aACnEY,OAAO;AACd3J,cAAQ2J,MAAM,iCAAiCA,KAAK;AACpD,aAAO,CAAC7F,QAAWA,MAAS;IAC9B;EACF;EAEA,IAAIwG,gBAAa;AACf,WAAO,KAAKhB,eAAe1H;EAC7B;EAEA,IAAI2I,oBAAiB;AACnB,QAAI,CAAC,KAAKjB,eAAe;AACvB,aAAOxF;IACT;AAEA,QAAI,CAACoE,cAAc;AACjBlI,cAAQ2J,MAAM,qDAAqD;AACnE,aAAO7F;IACT;AAEA,UAAM0G,aAAatC,aAAauC,WAAW,QAAQ,EAAEC,OAAO,KAAKpB,aAAa,EAAEqB,OAAO,QAAQ;AAC/F,WAAOH;EACT;EAEA7H,SAAM;AACJ,QAAI,CAAC,KAAK4G,gBAAgB,CAAC,KAAKC,WAAW,CAAC,KAAKN,UAAU;AACzD,aAAO,qDAAqD,KAAKK,YAAY;IAC/E;AAEA,WAAO,yBAAyB,KAAKA,YAAY,OAAO,KAAKL,QAAQ,WAAW,KAAKM,OAAO;EAC9F;;;;;;;;;;;;EAaO,OAAOoB,qBAAqBC,iBAAuB;AACxD,UAAMC,SAAS;AACf,UAAMC,SAAS;AAEf,QAAI,CAACF,gBAAgB1K,WAAW2K,MAAM,GAAG;AACvC,YAAM,IAAItG,MAAM,+DAA+D;IACjF;AAEA,QAAI,CAACqG,gBAAgBG,SAASD,MAAM,GAAG;AACrC,YAAM,IAAIvG,MAAM,0CAA0C;IAC5D;AAEA,UAAMpD,UAAUyJ,gBAAgBb,MAAMc,OAAOlJ,QAAQ,CAACmJ,OAAOnJ,MAAM;AAEnE,UAAMqJ,QAAQ7J,QAAQ6I,MAAM,GAAG;AAC/B,UAAMiB,aAAwC,CAAA;AAE9C,eAAWC,QAAQF,OAAO;AACxB,YAAM,CAAC9L,KAAKZ,KAAK,IAAI4M,KAAKlB,MAAM,KAAK,CAAC;AACtCiB,iBAAW/L,GAAG,IAAIZ;IACpB;AAEA,QAAI,EAAE,UAAU2M,cAAc,QAAQA,cAAc,YAAYA,aAAa;AAC3E,YAAM,IAAI1G,MAAM,6CAA6C;IAC/D;AAEA,WAAO;MACL4G,SAASF,WAAW,IAAI;MACxBG,QAAQH,WAAW,QAAQ;MAC3BnC,aAAamC,WAAW,MAAM;;EAElC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA0CO,aAAaI,uBAA0BxE,QAAoD;AAChG,UAAM;MAAE+B;MAAK0C;MAAgBC,WAAW;IAAE,IAAK1E;AAE/C,mBAAe2E,SAAY5C,MAAQ6C,OAAa;AAC9C,UAAIA,QAAQF,UAAU;AACpB,eAAO3C;MACT;AAGA,UAAI,OAAOA,SAAQ,UAAU;AAC3B,cAAM8C,QAAQ;AACd,cAAMC,yBAAyB/C,KAAIgD,MAAMF,KAAK;AAC9C,YAAI,CAACC,wBAAwB;AAC3B,iBAAO/C;QACT;AAEA,YAAIiD,SAASjD;AACb,cAAMkD,mCAAmC,oBAAIhN,IAAG;AAEhD,cAAMmG,QAAQoD,IACZsD,uBAAuB1I,IAAI,OAAO2H,oBAAmB;AACnD,cAAI;AACF,kBAAMmB,uBAAuBpD,eAAcgC,qBAAqBC,eAAe;AAC/E,kBAAMoB,YAAY,MAAMV,eAAeW,WAAWF,qBAAqBZ,OAAO;AAC9E,kBAAMe,eAAe,MAAMZ,eAAea,MAAMH,UAAUvH,KAAK;cAAE2H,QAAQ;cAAOC,SAAS,CAAA;YAAI,CAAA;AAC7F,gBAAIH,aAAaI,WAAW,KAAK;AAC/B,oBAAM,IAAI/H,MAAM,+BAA+B;YACjD;AAEA,kBAAMgI,qBAAqBpC,OAAOC,KAAK,MAAM8B,aAAaM,YAAW,CAAE,EAAEvG,SAAS,QAAQ;AAC1F,kBAAM4C,gBAAgB,QAAQmD,UAAUlD,WAAW,WAAWyD,kBAAkB;AAEhFT,6CAAiC1M,IAAIwL,iBAAiB/B,aAAa;mBAC5Da,OAAO;AACd3J,oBAAQ0M,KAAK,qDAAqD7B,iBAAiBlB,KAAK;UAE1F;QACF,CAAC,CAAC;AAGJ,mBAAW,CAACkB,iBAAiB2B,kBAAkB,KAAKT,iCAAiCnE,QAAO,GAAI;AAC9FkE,mBAASA,OAAOa,WAAW9B,iBAAiB2B,kBAAkB;QAChE;AAEA,eAAOV;MACT;AAGA,UAAIpI,MAAMC,QAAQkF,IAAG,GAAG;AACtB,eAAO3D,QAAQoD,IAAIO,KAAI3F,IAAI,OAAOC,SAAS,MAAMsI,SAAStI,MAAMuI,QAAQ,CAAC,CAAC,CAAC;MAC7E;AAGA,UAAI,OAAO7C,SAAQ,YAAYA,SAAQ,MAAM;AAC3C,eAAOlB,OAAOiF,YACZ,MAAM1H,QAAQoD,IAAIX,OAAOC,QAAQiB,IAAG,EAAE3F,IAAI,OAAO,CAAC/D,KAAKZ,KAAK,MAAM,CAACY,KAAK,MAAMsM,SAASlN,OAAOmN,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;MAE/G;AAEA,aAAO7C;IACT;AAEA,WAAO4C,SAAS5C,KAAK,CAAC;EACxB;AACD;AC5Te,SAAAgE,WAAWC,OAAeC,YAA8B;AACtE,MAAIA,eAAejJ,QAAW;AAC5B,WAAO;EACT,WAAWiJ,eAAe,GAAG;AAC3B,WAAO;EACT;AAEA,MAAIA,aAAa,KAAKA,aAAa,KAAKC,MAAMD,UAAU,GAAG;AACzD/M,YAAQ0M,KAAK,wDAAwD;AAErE,WAAO;EACT;AAEA,SAAOO,WAAWH,KAAK,IAAIC;AAC7B;AAMA,SAASE,WAAWC,KAAW;AAC7B,MAAIC,OAAO;AACX,QAAMC,QAAQ;AAEd,WAAS1L,IAAI,GAAGA,IAAIwL,IAAItL,QAAQF,KAAK;AAEnCyL,WAAQA,OAAOC,QAAQF,IAAIG,WAAW3L,CAAC,MAAO;EAChD;AAGAyL,UAASA,SAAS,KAAMA,QAAQ;AAChCA,UAASA,SAAS,KAAMA,QAAQ;AAChCA,SAAQA,SAAS,KAAMA;AAEvB,SAAOpH,KAAKuH,IAAIH,IAAI,IAAI;AAC1B;AE6CA,IAAMI,uBAAuBC,OAAO,+BAA+B,IAC/DC,OAAOD,OAAO,+BAA+B,CAAC,IAC9C;AACJ,IAAME,uBAAuBF,OAAO,+BAA+B,IAC/DC,OAAOD,OAAO,+BAA+B,CAAC,IAC9C;AACJ,IAAMG,sBAAsB;AAE5B,IAAMC,6CAA6C,CAAC,4BAA4B;AAEhF,IAAMC,yBAAN,cAAqCC,MAAK;EAIxCC,YACSC,UACPC,MAAY;AAEZ,UAAM,yCAAyCD,SAASE,SAAS,gBAAgBD,IAAI;AAH9E,SAAQD,WAARA;AAJT,SAAIG,OAAG;EAQP;AACD;AAED,IAAMC,4BAAN,cAAwCN,MAAK;EAG3CC,YAAmBM,OAAc;AAC/B,UAAM,yCAAyCA,iBAAiBP,QAAQ;MAAEQ,OAAOD;QAAU,CAAA,CAAE;AAD5E,SAAKA,QAALA;AAFnB,SAAIF,OAAG;EAIP;AACD;AAED,SAASI,yBAAyBF,OAAU;AAC1C,SAAO,OAAOA,UAAU,YAAYA,MAAMF,SAAS;AACrD;AAEA,SAASK,4BAA4BH,OAAU;AAC7C,SAAO,OAAOA,UAAU,YAAYA,MAAMF,SAAS;AACrD;AAEA,SAASM,qBAAqBC,KAAQ;AACpC,SAAOH,yBAAyBG,GAAG,KAAKF,4BAA4BE,GAAG;AACzE;AAGA,IAAMC,cAAc;AACpB,IAAMC,eAAe;AACrB,IAAMC,gBAAgB;AACtB,IAAMC,wBAAwB;AAC9B,IAAMC,kBAAkB;AACxB,IAAMC,kBAAkB;AAGxB,IAAMC,uBAAuB,gDAAgDD,eAAe;AAC5F,IAAME,2BAA2B,qGAAqGP,WAAW;AACjJ,IAAMQ,uBAAuB,6EAA6ER,WAAW;AAGrH,IAAMS,sBAAsB,oBAAIC,IAAoB;;EAElD,CAAC,KAAK,qEAAqEV,WAAW,EAAE;EACxF,CAAC,KAAK,4EAA4EA,WAAW,GAAG;EAChG,CAAC,KAAK,gBAAgBO,wBAAwB,EAAE;EAChD,CAAC,KAAK,wBAAwBA,wBAAwB,EAAE;EACxD,CAAC,KAAK,oBAAoBA,wBAAwB,EAAE;EACpD,CAAC,KAAK,4BAA4BA,wBAAwB,EAAE;;EAG5D,CACE,KACA,0GAA0GN,YAAY,eAAe;EAEvI,CACE,KACA,4GAA4GE,qBAAqB,oCAAoC;EAEvK,CAAC,KAAK,iFAAiFD,aAAa,eAAe;EACnH,CAAC,KAAK,wEAAwEE,eAAe,EAAE;AAAC,CACjG;AAGD,SAASO,uBAAuBC,MAAwB;AACtD,MAAI,CAACA,MAAM;AACT,WAAO,GAAGJ,oBAAoB,IAAIF,oBAAoB;EACxD;AAEA,QAAMO,gBAAgBJ,oBAAoBK,IAAIF,IAAI,KAAKJ;AACvD,SAAO,GAAGI,IAAI,KAAKC,aAAa,IAAIP,oBAAoB;AAC1D;AAEA,SAASS,kBAAkBrB,OAAU;AACnC,MAAIE,yBAAyBF,KAAK,GAAG;AACnC,UAAMkB,OAAOlB,MAAML,SAASE;AAC5B,UAAMsB,gBAAgBF,uBAAuBC,IAAI;AACjDI,YAAQtB,MAAM,kBAAkBmB,eAAe,kBAAkBnB,KAAK,EAAE;EAC1E,WAAWG,4BAA4BH,KAAK,GAAG;AAC7CsB,YAAQtB,MAAM,kCAAkCA,KAAK;EACvD,OAAO;AACLsB,YAAQtB,MAAM,iCAAiCA,KAAK;EACtD;AACF;AAEA,IAAeuB,wBAAf,MAAoC;EAqClC7B,YAAY8B,QAA2B;AAhCvC,SAAiBC,oBAA2B,CAAA;AAKpC,SAASC,YAAY;AACrB,SAA8BC,iCAAiC,CAAA;AAC/D,SAAwBC,2BAAiC,CAAA;AAKzD,SAAAC,sBAA2D,oBAAIb,IAAG;AAOhE,SAAAc,UAAU,IAAIC,mBAAkB;AAcxC,UAAM;MAAEC;MAAWC;MAAWC;MAASC;MAAYC;MAA4B,GAAGC;IAAS,IAAGb;AAE9F,SAAKM,QAAQQ,GAAG,SAAUC,aAAW;AACnCjB,cAAQtB,MAAM,kBAAkB,OAAOuC,YAAY,WAAWA,UAAUC,KAAKC,UAAUF,OAAO,CAAC,EAAE;IACnG,CAAC;AAED,SAAKL,UAAUA,YAAY,QAAQ,QAAQ;AAC3C,SAAKF,YAAYA,aAAa;AAC9B,SAAKC,YAAYA;AACjB,SAAKS,UAAUC,oBAAoBN,SAASK,WAAW,4BAA4B;AACnF,SAAKjB,oBAAoBY,SAASZ,qBAAqB,CAAA;AACvD,SAAKmB,UAAUP,SAASO,UAAUC,KAAKC,IAAIT,SAASO,SAAS,CAAC,IAAI;AAClE,SAAKG,gBAAgBV,SAASU,iBAAiB;AAC/C,SAAKC,UAAUX,SAASW,WAAW7D,OAAO,kBAAkB,KAAK8D,qBAAoB,KAAMC;AAC3F,SAAKC,OAAOd,SAASc;AACrB,SAAKC,aACHf,SAASe,eAAejE,OAAO,sBAAsB,IAAIC,OAAOD,OAAO,sBAAsB,CAAC,IAAI+D;AAEpG,QAAI,KAAKE,YAAY;AACnB,WAAKtB,QAAQuB,KAAK,SAAS,mDAAmD,KAAKD,UAAU,GAAG;IAClG;AAEA,SAAKE,cAAcjB,SAASiB,eAAenE,OAAO,8BAA8B;AAChF,QACE,KAAKmE,eACL,EACEhE,oBAAoBiE,KAAK,KAAKD,WAAW,KACzC/D,2CAA2CiE,SAAS,KAAKF,WAAW,MAEtE,CAAClB,4BACD;AACA,WAAKN,QAAQuB,KACX,SACA,oCAAoC,KAAKC,WAAW,mCAAmChE,mBAAmB,+CAA+C;IAE7J;AAEA,SAAKmE,gBAAgB;MACnBC,YAAYrB,SAASsB,mBAAmB;MACxCC,YAAYvB,SAASwB,mBAAmB;MACxCC,YAAY1D;;AAEd,SAAK2D,iBAAiB1B,SAAS0B,kBAAkB;AAEjD,SAAKC,iBAAiB3B,SAAS2B,kBAAkB;AAEjD,SAAKC,4BAA4B7B,8BAA8B;AAE/D,QAAI,KAAK6B,6BAA6B,CAAC9B,YAAY;AACjD,WAAKL,QAAQuB,KACX,SACA,wFAAwF;AAE1F,WAAKY,4BAA4B;AACjC;eACS,CAAC,KAAKA,6BAA6B9B,YAAY;AACxD,WAAKL,QAAQuB,KACX,SACA,wFAAwF;AAE1F,WAAKY,4BAA4B;AACjC;IACF,OAAO;AACL,WAAKC,YAAY/B;IACnB;EACF;EAEAgC,oBAAiB;AACf,WAAO,KAAKH;EACd;EAEUI,2BAAwB;AAChC,WAAO;MACLC,MAAM,KAAKC,aAAY;MACvBC,cAAc,KAAKC,kBAAiB;;EAExC;EAEAlC,GAAGmC,OAAeC,IAA4B;AAC5C,WAAO,KAAK5C,QAAQQ,GAAGmC,OAAOC,EAAE;EAClC;EAEAC,MAAMzC,UAAmB,MAAI;AAC3B,SAAK0C,sBAAmB;AAExB,SAAKlD,YAAYQ;AAEjB,QAAIA,SAAS;AACX,WAAK0C,sBAAsB,KAAKtC,GAAG,KAAK,CAACmC,OAAOlC,YAAW;AAEzD,YAAIkC,UAAU,SAAS;AACrB;QACF;AAEAnD,gBAAQuD,IAAI,oBAAoBJ,OAAOjC,KAAKC,UAAUF,OAAO,CAAC;MAChE,CAAC;IACH;EACF;;;;EAKUuC,eAAelF,MAA6B;AACpD,UAAM;MAAEmF,IAAIC;MAAQC,WAAWC;MAAelC,SAASmC;MAAa,GAAGC;IAAM,IAAGxF;AAEhF,UAAMmF,KAAKC,UAAUK,aAAY;AACjC,UAAMrC,UAAUmC,eAAe,KAAKnC;AAEpC,UAAMsC,aAAsC;MAC1CP;MACA/B;MACAiC,WAAWC,iBAAiB,oBAAIK,KAAI;MACpCjC,aAAa,KAAKA;MAClB,GAAG8B;;AAEL,SAAKI,QAAQ,gBAAgBF,UAAU;AACvC,WAAOP;EACT;EAEUU,eAAe7F,MAA6B;AACpD,UAAM;MAAEmF,IAAIC;MAAQU,WAAWC;MAAe,GAAGP;IAAM,IAAGxF;AAE1D,UAAMmF,KAAKC,UAAUK,aAAY;AAEjC,UAAMC,aAAsC;MAC1CP;MACAW,WAAWC,iBAAiB,oBAAIJ,KAAI;MACpCjC,aAAa,KAAKA;MAClB,GAAG8B;;AAEL,SAAKI,QAAQ,gBAAgBF,UAAU;AACvC,WAAOP;EACT;EAEUa,cAAchG,MAA4B;AAClD,UAAM;MAAEmF,IAAIC;MAAQU,WAAWC;MAAe,GAAGP;IAAM,IAAGxF;AAE1D,UAAMmF,KAAKC,UAAUK,aAAY;AAEjC,UAAMC,aAAqC;MACzCP;MACAW,WAAWC,iBAAiB,oBAAIJ,KAAI;MACpCjC,aAAa,KAAKA;MAClB,GAAG8B;;AAEL,SAAKI,QAAQ,eAAeF,UAAU;AACtC,WAAOP;EACT;EAEUc,oBACRjG,MAAsF;AAEtF,UAAM;MAAEmF,IAAIC;MAAQU,WAAWC;MAAeG,QAAAA;MAAQ,GAAGV;IAAM,IAAGxF;AAClE,UAAMmG,gBACJD,WAAU,CAACA,QAAOE,aAAa;MAAEC,YAAYH,QAAOhG;MAAMoG,eAAeJ,QAAOK;QAAY,CAAA;AAE9F,UAAMpB,KAAKC,UAAUK,aAAY;AAEjC,UAAMC,aAA2C;MAC/CP;MACAW,WAAWC,iBAAiB,oBAAIJ,KAAI;MACpCjC,aAAa,KAAKA;MAClB,GAAGyC;MACH,GAAGX;;AAGL,SAAKI,QAAQ,qBAAqBF,UAAU;AAC5C,WAAOP;EACT;EAEUqB,eAAexG,MAA6B;AACpD,UAAM;MAAEmF,IAAIC;MAAQ,GAAGI;IAAI,IAAKxF;AAEhC,UAAMmF,KAAKC,UAAUK,aAAY;AAEjC,UAAMC,aAAsC;MAC1CP;MACAzB,aAAa,KAAKA;MAClB,GAAG8B;;AAEL,SAAKI,QAAQ,gBAAgBF,UAAU;AACvC,WAAOP;EACT;EAEUsB,oBAAoBzG,MAA4B;AACxD,SAAK4F,QAAQ,eAAe5F,IAAI;AAChC,WAAOA,KAAKmF;EACd;EAEUuB,0BACR1G,MAAsF;AAEtF,UAAM;MAAEkG,QAAAA;MAAQ,GAAGV;IAAM,IAAGxF;AAC5B,UAAMmG,gBACJD,WAAU,CAACA,QAAOE,aAAa;MAAEC,YAAYH,QAAOhG;MAAMoG,eAAeJ,QAAOK;QAAY,CAAA;AAE9F,UAAMb,aAA2C;MAC/C,GAAGS;MACH,GAAGX;;AAEL,SAAKI,QAAQ,qBAAqBF,UAAU;AAC5C,WAAO1F,KAAKmF;EACd;EAEU,MAAMwB,YAAYzG,MAA6C;AACvE,UAAM0G,cAAcC,mBAAmB3G,IAAI;AAC3C,WAAO,KAAK4G,kBACV,GAAG,KAAKhE,OAAO,2BAA2B8D,WAAW,IACrD,KAAKG,iBAAiB;MAAEC,QAAQ;IAAK,CAAE,CAAC;EAE5C;EAEU,MAAMC,iBAAiBC,OAAmC;AAClE,UAAMtF,SAAS,IAAIuF,gBAAe;AAClCC,WAAOC,QAAQH,SAAS,CAAA,CAAE,EAAEI,QAAQ,CAAC,CAACC,KAAKC,KAAK,MAAK;AACnD,UAAIA,UAAUlE,UAAakE,UAAU,MAAM;AACzC5F,eAAO6F,OAAOF,KAAKC,MAAME,SAAQ,CAAE;MACrC;IACF,CAAC;AAED,WAAO,KAAKZ,kBACV,GAAG,KAAKhE,OAAO,6BAA6BlB,MAAM,IAClD,KAAKmF,iBAAiB;MAAEC,QAAQ;IAAK,CAAE,CAAC;EAE5C;EAEU,MAAMW,YAAYxC,IAAU;AACpC,WAAO,KAAK2B,kBAAkB,GAAG,KAAKhE,OAAO,qBAAqBqC,EAAE,IAAI,KAAK4B,iBAAiB;MAAEC,QAAQ;IAAK,CAAE,CAAC;EAClH;EAEA,MAAMY,YAAYV,OAA8B;AAE9C,UAAM;MAAEW;MAAMC;QAAS,MAAM,KAAKhB,kBAChC,GAAG,KAAKhE,OAAO,sBAAsBiF,kBAAkBb,KAAK,CAAC,IAC7D,KAAKH,iBAAiB;MAAEC,QAAQ;IAAO,CAAA,CAAC;AAE1C,WAAO;MAAEa;MAAMC;;EACjB;EAEA,MAAME,WAAWC,SAAe;AAC9B,UAAMC,MAAM,MAAM,KAAKpB,kBACrB,GAAG,KAAKhE,OAAO,sBAAsBmF,OAAO,IAC5C,KAAKlB,iBAAiB;MAAEC,QAAQ;IAAK,CAAE,CAAC;AAE1C,WAAO;MAAEa,MAAMK;;EACjB;EAEA,MAAMC,kBAAkBjB,OAAoC;AAE1D,UAAM;MAAEW;MAAMC;QAAS,MAAM,KAAKhB,kBAChC,GAAG,KAAKhE,OAAO,4BAA4BiF,kBAAkBb,KAAK,CAAC,IACnE,KAAKH,iBAAiB;MAAEC,QAAQ;IAAO,CAAA,CAAC;AAG1C,WAAO;MAAEa;MAAMC;;EACjB;EAEA,MAAMM,iBAAiBC,eAAqB;AAC1C,UAAMH,MAAM,MAAM,KAAKpB,kBACrB,GAAG,KAAKhE,OAAO,4BAA4BuF,aAAa,IACxD,KAAKtB,iBAAiB;MAAEC,QAAQ;IAAK,CAAE,CAAC;AAG1C,WAAO;MAAEa,MAAMK;;EACjB;EAEA,MAAMI,cAAcpB,OAAgC;AAElD,UAAM;MAAEW;MAAMC;QAAS,MAAM,KAAKhB,kBAChC,GAAG,KAAKhE,OAAO,wBAAwBiF,kBAAkBb,KAAK,CAAC,IAC/D,KAAKH,iBAAiB;MAAEC,QAAQ;IAAO,CAAA,CAAC;AAG1C,WAAO;MAAEa;MAAMC;;EACjB;EAEA,MAAMS,cAAc3G,QAAmC;AACrD,UAAM4G,qBAAqB3B,mBAAmBjF,OAAO6G,WAAW;AAChE,UAAMC,iBAAiB7B,mBAAmBjF,OAAO+G,OAAO;AACxD,WAAO,KAAK7B,kBACV,GAAG,KAAKhE,OAAO,wBAAwB0F,kBAAkB,SAASE,cAAc,IAChF,KAAK3B,iBAAiB;MAAEC,QAAQ;IAAK,CAAE,CAAC;EAE5C;EAEA,MAAM4B,eACJH,aACAvB,OAAmC;AAEnC,WAAO,KAAKJ,kBACV,GAAG,KAAKhE,OAAO,wBAAwB+D,mBAAmB4B,WAAW,CAAC,SAASV,kBAAkBb,KAAK,CAAC,IACvG,KAAKH,iBAAiB;MAAEC,QAAQ;IAAO,CAAA,CAAC;EAE5C;EAEA,MAAM6B,qBAAqB7I,MAAsC;AAC/D,WAAO,KAAK8G,kBACV,GAAG,KAAKhE,OAAO,iCACf,KAAKiE,iBAAiB;MAAEC,QAAQ;MAAQhH,MAAM4C,KAAKC,UAAU7C,IAAI;IAAG,CAAA,CAAC;EAEzE;;;;;;;EAQA,MAAM8I,cACJC,SAMK;AAEL,UAAM/I,OAAkC,OAAO+I,YAAY,WAAW;MAAE7I,MAAM6I;IAAO,IAAKA;AAC1F,WAAO,KAAKjC,kBACV,GAAG,KAAKhE,OAAO,wBACf,KAAKiE,iBAAiB;MAAEC,QAAQ;MAAQhH,MAAM4C,KAAKC,UAAU7C,IAAI;IAAG,CAAA,CAAC;EAEzE;;;;;;EAOA,MAAMgJ,kBAAkBhJ,MAAmC;AACzD,WAAO,KAAK8G,kBACV,GAAG,KAAKhE,OAAO,6BACf,KAAKiE,iBAAiB;MAAEC,QAAQ;MAAQhH,MAAM4C,KAAKC,UAAU7C,IAAI;IAAG,CAAA,CAAC;EAEzE;EAEA,MAAMiJ,eAAe9D,IAAU;AAC7B,WAAO,KAAK2B,kBACV,GAAG,KAAKhE,OAAO,6BAA6BqC,EAAE,IAC9C,KAAK4B,iBAAiB;MAAEC,QAAQ;IAAK,CAAE,CAAC;EAE5C;EAEUkC,cAAcnJ,UAAa;AACnC,QAAI;AACF,aAAO6C,KAAKuG,MAAMpJ,QAAQ;IAC5B,QAAQ;AACN,aAAOA;IACT;EACF;EAEA,MAAMqJ,sBAAsBpJ,MAA8B;AACxD,WAAO,KAAK8G,kBACV,GAAG,KAAKhE,OAAO,0BACf,KAAKiE,iBAAiB;MAAEC,QAAQ;MAAQhH,MAAM4C,KAAKC,UAAU7C,IAAI;IAAG,CAAA,CAAC;EAEzE;EAEA,MAAMqJ,sBACJrJ,MAA0D;AAE1D,WAAO,KAAK8G,kBACV,GAAG,KAAKhE,OAAO,0BAA0B+D,mBAAmB7G,KAAKE,IAAI,CAAC,aAAa2G,mBAAmB7G,KAAKuG,OAAO,CAAC,IACnH,KAAKQ,iBAAiB;MAAEC,QAAQ;MAAShH,MAAM4C,KAAKC,UAAU7C,IAAI;IAAG,CAAA,CAAC;EAE1E;EAEA,MAAMsJ,mBACJpJ,MACAqG,UACAgD,OACAC,YACArF;AAEA,UAAMyC,cAAcC,mBAAmB3G,IAAI;AAC3C,UAAM0B,SAAS,IAAIuF,gBAAe;AAGlC,QAAIZ,YAAWgD,OAAO;AACpB,YAAM,IAAI1J,MAAM,4CAA4C;IAC9D;AAEA,QAAI0G,UAAS;AACX3E,aAAO6F,OAAO,WAAWlB,SAAQmB,SAAQ,CAAE;IAC7C;AAEA,QAAI6B,OAAO;AACT3H,aAAO6F,OAAO,SAAS8B,KAAK;IAC9B;AAEA,UAAME,MAAM,GAAG,KAAK3G,OAAO,0BAA0B8D,WAAW,GAAGhF,OAAO8H,OAAO,MAAM9H,SAAS,EAAE;AAElG,UAAM+H,oBAAoB,KAAKC,sBAAsB;MAAEJ;MAAYK,mBAAmB;MAAGC,sBAAsB;IAAC,CAAE;AAClH,UAAMC,eAAe;MAAE,GAAG,KAAKlG;MAAeC,YAAY6F;MAAmB3F,YAAY;;AACzF,UAAMgG,cAAeC,YACnB,KAAK/H,QAAQuB,KAAK,SAASwG,SAAS,OAAOR,MAAM,OAAO7G,KAAKC,UAAUkH,YAAY,CAAC;AAEtF,WAAOG,UACL,YAAW;AACT,YAAMhC,MAAM,MAAM,KAAKiC,MACrBV,KACA,KAAK1C,iBAAiB;QAAEC,QAAQ;QAAOoD,cAAcjG,kBAAkB,KAAKA;MAAc,CAAE,CAAC,EAC7FkG,MAAOC,OAAK;AACZ,YAAIA,EAAEpK,SAAS,cAAc;AAC3B,gBAAM,IAAIC,0BAA0B,yBAAyB;QAC/D;AACA,cAAM,IAAIA,0BAA0BmK,CAAC;MACvC,CAAC;AAED,UAAIpC,IAAIjI,UAAU,KAAK;AACrB,cAAM,IAAIL,uBAAuBsI,KAAK,MAAMA,IAAIqC,KAAI,CAAE;MACxD;AAEA,YAAM1C,OAAO,MAAMK,IAAIsC,KAAI;AAE3B,aAAO;QAAEC,aAAavC,IAAIjI,WAAW,MAAM,YAAY;QAAW4H;;IACpE,GACAkC,cACAC,WAAW;EAEf;EAEQJ,sBAAsBhI,QAI7B;AACC,UAAMiI,oBAAoB5G,KAAKC,IAAItB,OAAOiI,qBAAqB,GAAG,CAAC;AACnE,UAAMC,uBAAuB7G,KAAKC,IAAItB,OAAOkI,wBAAwB,GAAG,CAAC;AAEzE,QAAIlI,OAAO4H,eAAelG,QAAW;AACnC,aAAOuG;IACT;AAEA,WAAO5G,KAAKyH,IAAIzH,KAAKC,IAAItB,OAAO4H,YAAY,CAAC,GAAGM,oBAAoB;EACtE;;;;EAKUlE,QAAQ+E,MAAsB3K,MAAe;AACrD,QAAI,CAAC,KAAKsC,SAAS;AACjB;IACF;AAGA,UAAM2F,UAAU,KAAK2C,aAAaD,MAAM3K,IAAI;AAC5C,QAAI,CAACiI,SAAS;AACZ,WAAK/F,QAAQuB,KACX,WACA,4HAA4H;eAErH,CAACoH,WAAW5C,SAAS,KAAKzE,UAAU,GAAG;AAChD,WAAKtB,QAAQuB,KAAK,SAAS,uBAAuBwE,OAAO,8BAA8B;AAEvF;IACF;AAEA,UAAM6C,UAAU,KAAKC,oBAAoBJ,MAAM3K,IAAI;AACnD,UAAMgL,YAAYvF,aAAY;AAC9B,SAAK1D,+BAA+BiJ,SAAS,IAAIF;AAEjDA,YACGT,MAAOC,OAAK;AACX,WAAKpI,QAAQuB,KAAK,SAAS6G,CAAC;IAC9B,CAAC,EACAW,QAAQ,MAAK;AACZ,aAAO,KAAKlJ,+BAA+BiJ,SAAS;IACtD,CAAC;EACL;EAEU,MAAMD,oBAAoBJ,MAAsB3K,MAAe;AACvE,SAAKkL,qBAAqBlL,IAAI;AAC9B,UAAM,KAAKmL,oBAAoBR,MAAM3K,IAAI;AACzC,UAAMoL,iBAAiB,KAAKC,kBAAkBrL,MAAMV,oBAAoB;AAExE,QAAI;AACFsD,WAAKC,UAAUuI,cAAc;aACtBd,GAAG;AACV,WAAKpI,QAAQuB,KAAK,SAAS,kBAAkBkH,IAAI,8BAA8BL,CAAC,EAAE;AAClF;IACF;AAEA,UAAMgB,QAAQ,KAAKC,qBAA0CC,0BAA0BC,KAAK,KAAK,CAAA;AAEjGH,UAAMI,KAAK;MACTvG,IAAIM,aAAY;MAChBkF;MACAtF,WAAWsG,eAAc;MACzB3L,MAAMoL;;MACNQ,UAAUtI;IACX,CAAA;AACD,SAAKuI,qBAA0CL,0BAA0BC,OAAOH,KAAK;AAErF,SAAKpJ,QAAQuB,KAAKkH,MAAMS,cAAc;AAGtC,QAAIE,MAAMQ,UAAU,KAAK9I,SAAS;AAChC,WAAK+I,MAAK;IACZ;AAEA,QAAI,KAAK5I,iBAAiB,CAAC,KAAK6I,aAAa;AAC3C,WAAKA,cAAcC,eAAe,MAAM,KAAKF,MAAK,GAAI,KAAK5I,aAAa;IAC1E;EACF;EAEQ+H,qBAAqBlL,MAAe;AAC1C,QAAI,CAAC,KAAKuD,MAAM;AACd;IACF;AAEA,UAAM2I,eAAe,CAAC,SAAS,QAAQ;AAEvC,eAAW3E,OAAO2E,cAAc;AAC9B,UAAI3E,OAAOvH,MAAM;AACf,YAAI;AACFA,eAAKuH,GAAsB,IAAI,KAAKhE,KAAK;YAAEsE,MAAM7H,KAAKuH,GAAsB;UAAC,CAAE;iBACxE+C,GAAG;AACV,eAAKpI,QAAQuB,KAAK,SAAS,iBAAiB8D,GAAG,KAAK+C,CAAC,EAAE;AACvDtK,eAAKuH,GAAsB,IAAI;QACjC;MACF;IACF;EACF;;;;;;;EAQU8D,kBAAkBrL,MAAiBmM,aAAmB;AAC9D,UAAMC,WAAW,KAAKC,YAAYrM,IAAI;AAEtC,QAAIoM,YAAYD,aAAa;AAC3B,aAAOnM;IACT;AAEA,SAAKkC,QAAQuB,KAAK,WAAW,4BAA4B2I,QAAQ,+BAA+B;AAGhG,UAAME,cAAc,CAAC,SAAS,UAAU,UAAU;AAClD,UAAMC,WAAWD,YACdE,IAAKjF,UAAS;MAAEA;MAAKmC,MAAMnC,OAAOvH,OAAO,KAAKqM,YAAYrM,KAAKuH,GAAwB,CAAC,IAAI;IAAC,EAAG,EAChGkF,KAAK,CAACC,GAAGC,MAAMA,EAAEjD,OAAOgD,EAAEhD,IAAI;AAEjC,QAAIkD,SAAS;MAAE,GAAG5M;;AAClB,QAAI6M,cAAcT;AAElB,eAAW;MAAE7E;MAAKmC;SAAU6C,UAAU;AACpC,UAAIM,cAAcV,eAAe/E,OAAO0F,UAAUC,eAAeC,KAAKJ,QAAQrF,GAAG,GAAG;AAClFqF,iBAAS;UAAE,GAAGA;UAAQ,CAACrF,GAAG,GAAG;;AAE7B,aAAKrF,QAAQuB,KAAK,WAAW,aAAa8D,GAAG,oCAAoC;AAEjFsF,uBAAenD;MACjB;IACF;AAEA,WAAOkD;EACT;EAEQP,YAAYY,KAAQ;AAC1B,UAAMC,aAAatK,KAAKC,UAAUoK,GAAG;AAGrC,QAAI,OAAOE,gBAAgB,aAAa;AACtC,aAAO,IAAIA,YAAW,EAAGC,OAAOF,UAAU,EAAEpB;IAC9C,OAAO;AACL,aAAOjF,mBAAmBqG,UAAU,EAAEG,QAAQ,gBAAgB,GAAG,EAAEvB;IACrE;EACF;EAEU,MAAMX,oBAAoBR,MAAsB3K,MAAe;AACvE,QAAI,CAACA,MAAM;AACT;IACF;AAEA,UAAMiI,UAAU,KAAK2C,aAAaD,MAAM3K,IAAI;AAE5C,QAAI,CAACiI,SAAS;AACZ,WAAK/F,QAAQuB,KAAK,WAAW,sCAAsC;AACnE;IACF;AAEA,UAAM4E,iBAAiBsC,KAAK/G,SAAS,YAAY,KAAK+G,KAAK/G,SAAS,MAAM,MAAM5D,KAAKmF,KAAKnF,KAAKmF,KAAK7B;AAEpG,UAAMgK,QAAQC,IACX,CAAC,SAAS,UAAU,UAAU,EAAYf,IAAI,OAAOgB,UAAS;AAC7D,UAAIxN,KAAKwN,KAAwB,GAAG;AAClCxN,aAAKwN,KAAwB,IAC1B,MAAM,KAAKC,oBAAoB;UAC9B5F,MAAM7H,KAAKwN,KAAwB;UACnCvF;UACAI;UACAmF;QACD,CAAA,EAAEnD,MAAOC,OAAK;AACb,eAAKpI,QAAQuB,KAAK,SAAS,sCAAsC6G,CAAC,EAAE;QACtE,CAAC,KAAMtK,KAAKwN,KAAwB;MACxC;IACF,CAAC,CAAC;EAEN;EAEU5C,aAAaD,MAAsB3K,MAAe;AAC1D,WAAO,aAAaA,OAAOA,KAAKiI,UAAU0C,KAAK/G,SAAS,OAAO,IAAI5D,KAAKmF,KAAK7B;EAC/E;EAEU,MAAMmK,oBAAoB;IAClC5F;IACAI;IACAI;IACAmF;EAMD,GAAA;AACC,UAAME,cAAc,oBAAIC,QAAO;AAC/B,UAAMC,YAAY;AAElB,UAAMC,qBAAqB,OAAOhG,OAAWiG,UAA+B;AAC1E,UAAI,OAAOjG,UAAS,YAAYA,MAAKkG,WAAW,OAAO,GAAG;AACxD,cAAMC,QAAQ,IAAIC,cAAc;UAAEC,eAAerG;QAAM,CAAA;AACvD,cAAM,KAAKsG,iBAAiB;UAAEH;UAAO/F;UAASI;UAAemF;QAAK,CAAE;AAEpE,eAAOQ;MACT;AACA,UAAI,OAAOnG,UAAS,YAAYA,UAAS,MAAM;AAC7C,eAAOA;MACT;AAGA,UAAI6F,YAAYU,IAAIvG,KAAI,KAAKiG,QAAQF,WAAW;AAC9C,eAAO/F;MACT;AAEA6F,kBAAYW,IAAIxG,OAAM,IAAI;AAE1B,UAAIA,iBAAgBoG,iBAAiB7G,OAAO0F,UAAUpF,SAASsF,KAAKnF,KAAI,MAAM,0BAA0B;AACtG,cAAM,KAAKsG,iBAAiB;UAAEH,OAAOnG;UAAMI;UAASI;UAAemF;QAAK,CAAE;AAE1E,eAAO3F;MACT;AAEA,UAAIyG,MAAMC,QAAQ1G,KAAI,GAAG;AACvB,eAAO,MAAMyF,QAAQC,IAAI1F,MAAK2E,IAAKgC,UAASX,mBAAmBW,MAAMV,QAAQ,CAAC,CAAC,CAAC;MAClF;AAGA,UAAI,OAAOjG,UAAS,YAAYA,UAAS,MAAM;AAC7C,YAAI,iBAAiBA,SAAQ,OAAOA,MAAK,aAAa,MAAM,YAAY,UAAUA,MAAK4G,aAAa;AAClG,gBAAMT,QAAQ,IAAIC,cAAc;YAC9BC,eAAe,cAAcrG,MAAK4G,YAAY,QAAQ,KAAK,KAAK,WAAW5G,MAAK4G,YAAY5G,IAAI;UACjG,CAAA;AAED,gBAAM,KAAKsG,iBAAiB;YAAEH;YAAO/F;YAASI;YAAemF;UAAK,CAAE;AAEpE,iBAAO;YACL,GAAG3F;YACH4G,aAAa;cACX,GAAG5G,MAAK4G;cACR5G,MAAMmG;YACP;;QAEL;AAGA,YAAI,WAAWnG,SAAQ,OAAOA,MAAK,OAAO,MAAM,YAAY,UAAUA,MAAK6G,OAAO;AAChF,gBAAMV,QAAQ,IAAIC,cAAc;YAC9BC,eAAe,cAAcrG,MAAK6G,MAAM,QAAQ,KAAK,KAAK,WAAW7G,MAAK6G,MAAM7G,IAAI;UACrF,CAAA;AAED,gBAAM,KAAKsG,iBAAiB;YAAEH;YAAO/F;YAASI;YAAemF;UAAK,CAAE;AAEpE,iBAAO;YACL,GAAG3F;YACH6G,OAAO;cACL,GAAG7G,MAAK6G;cACR7G,MAAMmG;YACP;;QAEL;AAGA,eAAO5G,OAAOuH,YACZ,MAAMrB,QAAQC,IACZnG,OAAOC,QAAQQ,KAAI,EAAE2E,IAAI,OAAO,CAACjF,KAAKC,KAAK,MAAM,CAACD,KAAK,MAAMsG,mBAAmBrG,OAAOsG,QAAQ,CAAC,CAAC,CAAC,CAAC,CACpG;MAEL;AAEA,aAAOjG;;AAGT,WAAO,MAAMgG,mBAAmBhG,MAAM,CAAC;EACzC;EAEQ,MAAMsG,iBAAiB;IAC7BH;IACA/F;IACAI;IACAmF;EAMD,GAAA;AACC,QAAI;AACF,UAAI,CAACQ,MAAMY,iBAAiB,CAACZ,MAAMa,gBAAgB,CAACb,MAAMc,qBAAqB,CAACd,MAAMe,eAAe;AACnG;MACF;AAEA,YAAMC,mBAA6C;QACjDJ,eAAeZ,MAAMY;QACrB3G;QACAI;QACAmF;QACAyB,aAAajB,MAAMa;QACnBK,YAAYlB,MAAMc;;AAGpB,YAAMK,gBAAgB,MAAM,KAAKhF,MAC/B,GAAG,KAAKrH,OAAO,qBACf,KAAKiE,iBAAiB;QACpBC,QAAQ;QACRhH,MAAM4C,KAAKC,UAAUmM,gBAAgB;MACtC,CAAA,CAAC;AAGJ,YAAMI,oBAAqB,MAAMD,cAAc3E,KAAI;AAEnD,YAAM;QAAE6E;QAAWC;MAAS,IAAGF;AAC/BpB,YAAMuB,WAAWD;AAEjB,UAAID,WAAW;AACb,aAAKnN,QAAQuB,KAAK,SAAS,mBAAmB6L,OAAO,EAAE;AACvD,cAAMxJ,YAAYH,KAAK6J,IAAG;AAE1B,cAAMC,iBAAiB,MAAM,KAAKC,uBAAuB;UACvDL;UACAM,cAAc3B,MAAMe;UACpBE,aAAajB,MAAMa;UACnBC,mBAAmBd,MAAMc;UACzBtF,YAAY;UACZoG,WAAW;QACZ,CAAA;AAED,YAAI,CAACH,gBAAgB;AACnB,gBAAM5P,MAAM,6BAA6B;QAC3C;AAEA,cAAMgQ,iBAAiC;UACrCC,aAAY,oBAAInK,KAAI,GAAGoK,YAAW;UAClCC,kBAAkBP,eAAexP;UACjCgQ,iBAAiB,MAAMR,eAAelF,KAAI;UAC1C2F,cAAcvK,KAAK6J,IAAG,IAAK1J;;AAG7B,cAAM,KAAKqE,MACT,GAAG,KAAKrH,OAAO,qBAAqBwM,OAAO,IAC3C,KAAKvI,iBAAiB;UAAEC,QAAQ;UAAShH,MAAM4C,KAAKC,UAAUgN,cAAc;QAAG,CAAA,CAAC;AAElF,aAAK3N,QAAQuB,KAAK,SAAS,oCAAoC6L,OAAO,EAAE;MAC1E,OAAO;AACL,aAAKpN,QAAQuB,KAAK,SAAS,SAAS6L,OAAO,mBAAmB;MAChE;aACO7O,KAAK;AACZ,WAAKyB,QAAQuB,KAAK,SAAS,gCAAgChD,GAAG,EAAE;IAClE;EACF;EAEQ,MAAMiP,uBAAuB9N,QAOpC;AACC,UAAM;MAAEyN;MAAWJ;MAAaH;MAAmBa;MAAcnG;MAAYoG;IAAW,IAAGhO;AAE3F,aAASuO,UAAU,GAAGA,WAAW3G,YAAY2G,WAAW;AACtD,UAAI;AACF,cAAMV,iBAAiB,MAAM,KAAKtF,MAAMkF,WAAW;UACjDrI,QAAQ;UACRhH,MAAM2P;UACNS,SAAS;YACP,gBAAgBnB;YAChB,yBAAyBH;YACzB,kBAAkB;UACnB;QACF,CAAA;AAED,YAAIqB,UAAU3G,cAAciG,eAAexP,WAAW,OAAOwP,eAAexP,WAAW,KAAK;AAC1F,gBAAM,IAAIJ,MAAM,6BAA6B4P,eAAexP,MAAM,EAAE;QACtE;AAEA,eAAOwP;eACAnF,GAAG;AACV,YAAI6F,YAAY3G,YAAY;AAC1B,gBAAMc;QACR;AAEA,cAAM+F,QAAQT,YAAY3M,KAAKqN,IAAI,GAAGH,OAAO;AAC7C,cAAMI,SAAStN,KAAKuN,OAAM,IAAK;AAE/B,cAAM,IAAIlD,QAASmD,aAAYC,WAAWD,SAASJ,QAAQE,MAAM,CAAC;MACpE;IACF;EACF;;;;;;;;EASA,MAAMI,aAAU;AACd,UAAMrD,QAAQC,IAAInG,OAAOwJ,OAAO,KAAK7O,8BAA8B,CAAC,EAAEsI,MAAOC,OAAK;AAChF7I,wBAAkB6I,CAAC;IACrB,CAAC;AAED,WAAO,IAAIgD,QAAQ,CAACmD,SAASI,YAAW;AACtC,UAAI;AACF,aAAK9E,MAAM,CAACtL,KAAKoH,SAAQ;AACvB,cAAIpH,KAAK;AACPgB,8BAAkBhB,GAAG;AACrBgQ,oBAAO;UACT,OAAO;AACLA,oBAAQ5I,IAAI;UACd;QACF,CAAC;eAEMyC,GAAG;AACV5I,gBAAQtB,MAAM,gDAAgDkK,CAAC;MACjE;IACF,CAAC;EACH;;EAGAyB,MAAM+E,UAA0C;AAC9C,QAAI,KAAK9E,aAAa;AACpB+E,mBAAa,KAAK/E,WAAW;AAC7B,WAAKA,cAAc;IACrB;AAEA,UAAMV,QAAQ,KAAKC,qBAA0CC,0BAA0BC,KAAK,KAAK,CAAA;AAEjG,QAAI,CAACH,MAAMQ,QAAQ;AACjB,aAAOgF,WAAQ;IACjB;AAEA,UAAME,QAAQ1F,MAAM2F,OAAO,GAAG,KAAKjO,OAAO;AAE1C,UAAM;MAAEkO;MAAgBC;QAAmB,KAAKC,kBAC9CJ,OACA1R,sBACAG,oBAAoB;AAGtB,SAAKoM,qBAA0CL,0BAA0BC,OAAO,CAAC,GAAG0F,gBAAgB,GAAG7F,KAAK,CAAC;AAE7G,UAAM+F,cAAc5L,aAAY;AAEhC,UAAM6L,OAAQ7Q,SAAmB;AAC/B,UAAIA,KAAK;AACP,aAAKyB,QAAQuB,KAAK,WAAWhD,GAAG;MAClC;AACAqQ,iBAAWrQ,KAAKuQ,KAAK;AACrB,WAAK9O,QAAQuB,KAAK,SAASuN,KAAK;;AAIlC,QAAI,KAAK3M,6BAA6B,KAAKC,WAAW;AACpD,UAAI,CAAC,KAAKrC,oBAAoBmM,IAAI,KAAK9J,SAAS,GAAG;AACjD,aAAKrC,oBAAoBoM,IAAI,KAAK/J,WAAW,CAAC,GAAG0M,KAAK,CAAC;MACzD,OAAO;AACL,aAAK/O,oBAAoBT,IAAI,KAAK8C,SAAS,GAAGoH,KAAK,GAAGsF,KAAK;MAC7D;AAEAM,WAAI;AACJ;IACF;AAEA,UAAM3O,UAAUC,KAAKC,UAAU;MAC7B0O,OAAOL;MACPtF,UAAU;QACR4F,YAAYN,eAAepF;QAC3B2F,iBAAiB,KAAKrN;QACtBsN,aAAa,KAAK9M,kBAAiB;QACnC+M,aAAa,KAAKjN,aAAY;QAC9BkN,YAAY,KAAKxP;QACjByP,UAAU;MACX;KACF;AAED,UAAMpI,MAAM,GAAG,KAAK3G,OAAO;AAE3B,UAAMgP,eAAe,KAAK/K,iBAAiB;MACzCC,QAAQ;MACRhH,MAAM2C;IACP,CAAA;AAED,UAAMoP,iBAAiB,KAAKC,eAAevI,KAAKqI,YAAY,EACzDG,KAAK,MAAMX,KAAI,CAAE,EACjBjH,MAAO5J,SAAO;AACb6Q,WAAK7Q,GAAG;IACV,CAAC;AACH,SAAKuB,yBAAyBqP,WAAW,IAAIU;AAC7CA,mBAAe9G,QAAQ,MAAK;AAC1B,aAAO,KAAKjJ,yBAAyBqP,WAAW;IAClD,CAAC;EACH;EAEOD,kBACL9F,OACA4G,cACAC,kBAAwB;AAExB,QAAIC,YAAY;AAChB,UAAMlB,iBAAsC,CAAA;AAC5C,UAAMC,iBAAsC,CAAA;AAE5C,aAASkB,IAAI,GAAGA,IAAI/G,MAAMQ,QAAQuG,KAAK;AACrC,UAAI;AACF,cAAMC,WAAW,IAAIC,KAAK,CAAC3P,KAAKC,UAAUyI,MAAM+G,CAAC,CAAC,CAAC,CAAC,EAAE3I;AAGtD,YAAI4I,WAAWJ,cAAc;AAC3BxQ,kBAAQ8Q,KAAK,kCAAkCF,QAAQ,mBAAmB;AAC1E;QACF;AAGA,YAAIF,YAAYE,YAAYH,kBAAkB;AAC5CzQ,kBAAQqD,MAAM,+BAA+BqN,YAAYE,QAAQ,GAAG;AACpEnB,yBAAezF,KAAK,GAAGJ,MAAMmH,MAAMJ,CAAC,CAAC;AACrC3Q,kBAAQuD,IAAI,oBAAoBkM,eAAerF,MAAM,EAAE;AACvDpK,kBAAQuD,IAAI,oBAAoBiM,eAAepF,MAAM,EAAE;AACvD;QACF;AAGAsG,qBAAaE;AACbpB,uBAAexF,KAAKJ,MAAM+G,CAAC,CAAC;eACrBjS,OAAO;AACd,aAAK8B,QAAQuB,KAAK,SAASrD,KAAK;AAChC+Q,uBAAezF,KAAK,GAAGJ,MAAMmH,MAAMJ,CAAC,CAAC;AACrC;MACF;IACF;AAEA,WAAO;MAAEnB;MAAgBC;;EAC3B;EAEApK,iBAAiB2L,GAIhB;AACC,UAAMZ,eAAqC;MACzC9K,QAAQ0L,EAAE1L;MACVoJ,SAAS;QACP,gBAAgB;QAChB,uBAAuB;QACvB,0BAA0B,KAAKxL,kBAAiB;QAChD,0BAA0B,KAAKF,aAAY;QAC3C,8BAA8B,KAAKN;QACnC,yBAAyB,KAAKhC;QAC9B,GAAG,KAAKP;QACR,GAAG,KAAK8Q,6BAA6B,KAAKvQ,WAAW,KAAKC,SAAS;;MAErErC,MAAM0S,EAAE1S;MACR,GAAI0S,EAAEtI,iBAAiB9G,SAAY;QAAEsP,QAAQC,YAAYC,QAAQJ,EAAEtI,YAAY;UAAM,CAAA;;AAGvF,WAAO0H;EACT;EAEUa,6BACRvQ,WACAC,WAAkB;AAIlB,QAAIA,cAAciB,QAAW;AAC3B,aAAO;QAAEyP,eAAe,YAAY3Q;;IACtC,OAAO;AACL,YAAM4Q,qBACJ,OAAOC,SAAS;;QAEZA,KAAK7Q,YAAY,MAAMC,SAAS;;;QAEhC6Q,OAAOC,KAAK/Q,YAAY,MAAMC,SAAS,EAAEqF,SAAS,QAAQ;;AAEhE,aAAO;QAAEqL,eAAe,WAAWC;;IACrC;EACF;EAEQ,MAAMhB,eACZvI,KACAhH,SACAsH,cAA+B;AAE9B8I,gBAAoBC,YAAY,SAASA,QAAQM,IAAU;AAC1D,YAAMC,OAAO,IAAIC,gBAAe;AAChC5C,iBAAW,MAAM2C,KAAKE,MAAK,GAAIH,EAAE;AACjC,aAAOC,KAAKT;;AAGd,WAAO,MAAM1I,UACX,YAAW;AACT,UAAIhC,MAAyD;AAC7D,UAAI;AACFA,cAAM,MAAM,KAAKiC,MAAMV,KAAK;UAC1BmJ,QAAQC,YAAYC,QAAQ,KAAK3O,cAAc;UAC/C,GAAG1B;QACJ,CAAA;eACM6H,GAAG;AAEV,cAAM,IAAInK,0BAA0BmK,CAAC;MACvC;AAEA,UAAIpC,IAAIjI,SAAS,OAAOiI,IAAIjI,UAAU,KAAK;AACzC,cAAMD,OAAO,MAAMkI,IAAIsC,KAAI;AAC3B,cAAM,IAAI5K,uBAAuBsI,KAAKtF,KAAKC,UAAU7C,IAAI,CAAC;MAC5D;AACA,YAAMwT,aAAa,MAAMtL,IAAIsC,KAAI;AACjC,UAAItC,IAAIjI,WAAW,OAAOuT,WAAWC,OAAO3H,SAAS,GAAG;AACtD,cAAM,IAAIlM,uBAAuBsI,KAAKtF,KAAKC,UAAU2Q,WAAWC,MAAM,CAAC;MACzE;AAEA,aAAOvL;IACT,GACA;MAAE,GAAG,KAAKrE;MAAe,GAAGkG;OAC3BE,YAAW,KAAK/H,QAAQuB,KAAK,SAASwG,SAAS,OAAOR,MAAM,OAAO7G,KAAKC,UAAUJ,OAAO,CAAC,CAAC;EAEhG;EAEQ,MAAMqE,kBAAqB2C,KAAahH,SAA6B;AAC3E,UAAMyF,MAAM,MAAM,KAAKiC,MAAMV,KAAKhH,OAAO;AAIzC,UAAMoF,OAAOK,IAAIjI,WAAW,MAAM,MAAMiI,IAAIqC,KAAI,IAAK,MAAMrC,IAAIsC,KAAI;AACnE,QAAItC,IAAIjI,SAAS,OAAOiI,IAAIjI,UAAU,KAAK;AACzCwB,wBAAkB,IAAI7B,uBAAuBsI,KAAKtF,KAAKC,UAAUgF,IAAI,CAAC,CAAC;IACzE;AAEA,WAAOA;EACT;EAEA,MAAM6L,gBAAa;AACjB3C,iBAAa,KAAK/E,WAAW;AAC7B,QAAI;AACF,YAAM,KAAK2E,WAAU;AACrB,YAAMrD,QAAQC,IACZnG,OAAOwJ,OAAO,KAAK5O,wBAAwB,EAAEwK,IAAKmH,OAChDA,EAAEtJ,MAAM,MAAK;OAEZ,CAAC,CACH;AAGH,YAAM,KAAKsG,WAAU;aACdrG,GAAG;AACV5I,cAAQtB,MAAM,qDAAqDkK,CAAC;IACtE;EACF;EAEA,MAAMsJ,mBAAmBtP,WAAiB;AACxC,QAAI,KAAKD,2BAA2B;AAClC0M,mBAAa,KAAK/E,WAAW;AAC7B,YAAM,KAAK2E,WAAU;AAErB,YAAMkD,SAAS,KAAK5R,oBAAoBT,IAAI8C,SAAS,KAAK,CAAA;AAC1D,WAAKrC,oBAAoB6R,OAAOxP,SAAS;AAEzC,aAAOuP;IACT,OAAO;AACL,WAAK3R,QAAQuB,KAAK,SAAS,wEAAwE;AACnG,aAAO,CAAA;IACT;EACF;EAEAsQ,WAAQ;AACNrS,YAAQ8Q,KACN,gHAAgH;AAElH,SAAK,KAAKkB,cAAa;EACzB;EAEU,MAAMM,mCAAgC;AAC9CjD,iBAAa,KAAK/E,WAAW;AAC7B,UAAM,KAAK2E,WAAU;AACrB,UAAMrD,QAAQC,IAAInG,OAAOwJ,OAAO,KAAK5O,wBAAwB,CAAC;EAChE;AACD;AA8BK,IAAgBiS,eAAhB,cAAqCC,sBAAqB;EAG9DC,YAAYC,QAA2B;AACrC,UAAM;MAAEC;MAAWC;MAAWC;MAASC;IAA0B,IAAKJ;AACtE,QAAIK,yBAAyBF,YAAY,QAAQ,QAAQ;AAEzD,QAAIC,4BAA4B;AAC9BC,+BAAyB;IAC3B,WAAW,CAACH,WAAW;AACrBG,+BAAyB;AAEzB,UAAIF,YAAY,OAAO;AACrBG,gBAAQC,KACN,6JAA6J;MAEjK;IACF,WAAW,CAACN,WAAW;AACrBI,+BAAyB;AAEzB,UAAIF,YAAY,OAAO;AACrBG,gBAAQC,KACN,6JAA6J;MAEjK;IACF;AAEA,UAAM;MAAE,GAAGP;MAAQG,SAASE;IAAwB,CAAA;AACpD,SAAKG,eAAe,IAAIC,oBAAmB;EAC7C;EAEAC,MAAMC,MAA8B;AAClC,UAAMC,KAAK,KAAKC,eAAeF,QAAQ,CAAA,CAAE;AACzC,UAAMG,IAAI,IAAIC,oBAAoB,MAAMH,EAAE;AAC1C,QAAII,OAAO,OAAO,KAAKL,MAAM;AAC3B,UAAI;AACF,cAAMM,eAAeD,OAAqB,gBAAgB;AAC1D,YAAIC,cAAc;AAChBA,uBAAaC,eAAe,CAC1B;YACEN;YACAO,MAAMR,KAAKQ,QAAQ;YACnBC,KAAKN,EAAEO,YAAW;UACnB,CAAA,CACF;QACH;cACM;MAAA;IACV;AACA,WAAOP;EACT;EAEAQ,KAAKX,MAA4B;AAC/B,UAAMY,UAAUZ,KAAKY,WAAW,KAAKV,eAAe;MAAEM,MAAMR,KAAKQ;IAAI,CAAE;AACvE,UAAMP,KAAK,KAAKY,cAAc;MAAE,GAAGb;MAAMY;IAAO,CAAE;AAClD,WAAO,IAAIE,mBAAmB,MAAMb,IAAIW,OAAO;EACjD;EAEAG,WACEf,MAAsF;AAEtF,UAAMY,UAAUZ,KAAKY,WAAW,KAAKV,eAAe;MAAEM,MAAMR,KAAKQ;IAAI,CAAE;AACvE,UAAMP,KAAK,KAAKe,oBAAoB;MAAE,GAAGhB;MAAMY;IAAO,CAAE;AACxD,WAAO,IAAIK,yBAAyB,MAAMhB,IAAIW,OAAO;EACvD;EAEAM,MAAMlB,MAA6B;AACjC,UAAMY,UAAUZ,KAAKY,WAAW,KAAKV,eAAe;MAAEM,MAAMR,KAAKQ;IAAI,CAAE;AACvE,UAAMP,KAAK,KAAKkB,eAAe;MAAE,GAAGnB;MAAMY;IAAO,CAAE;AACnD,WAAO,IAAIQ,oBAAoB,MAAMnB,IAAIW,OAAO;EAClD;EAEAS,MAAMrB,MAA6B;AACjC,SAAKsB,eAAetB,IAAI;AACxB,WAAO;EACT;EAEA,MAAMuB,WACJf,MACAgB,SAEC;AASD,UAAMC,UAAU,MAAM,KAAKC,YAAYlB,IAAI;AAC3C,UAAMmB,QAAiD,CAAA;AAEvD,QAAIC,OAAO;AACX,WAAO,MAAM;AACX,YAAMC,gBAAgB,MAAM,KAAKC,iBAAiB;QAChDC,aAAavB;QACbwB,OAAOR,SAASS,sBAAsB;QACtCL;MACD,CAAA;AACDD,YAAMO,KAAK,GAAGL,cAAcM,IAAI;AAChC,UAAIN,cAAcO,KAAKC,cAAcT,MAAM;AACzC;MACF;AACAA;IACF;AAEA,UAAMU,gBAAgB;MACpB,GAAGb;MACHc,aAAad,QAAQc,eAAeC;MACpCC,UAAUhB,QAAQgB,YAAYD;MAC9Bb,OAAOA,MAAMe,IAAKC,WAAU;QAC1B,GAAGA;QACHC,MAAM,OACJC,KACAC,SACAC,YAIE;AACF,gBAAM,KAAKC,iCAAgC;AAC3C,gBAAMb,OAAO,MAAM,KAAKc,qBAAqB;YAC3CH;YACAI,eAAeP,KAAK1C;YACpBkD,eAAeN,IAAIM;YACnBvC,SAASiC,IAAIjC;YACbwC,gBAAgBL,SAASR;YACzBE,UAAUM,SAASN;UACpB,CAAA;AACD,iBAAON;QACT;MACD,EAAC;;AAGJ,WAAOG;EACT;EAKA,MAAMe,aACJrD,MAAwF;AAExF,UAAMsD,SAAStD,KAAKsD,UAAU,CAAA;AAE9B,UAAMC,iBACJvD,KAAKwD,SAAS,SACV,MAAM,KAAKC,sBAAsB;MAC/B,GAAGzD;MACH0D,QAAQ1D,KAAK0D,OAAOhB,IAAKC,UAAQ;AAC/B,YAAI,UAAUA,QAAQA,KAAKa,SAASG,gBAAgBC,aAAa;AAC/D,iBAAO;YAAEJ,MAAMG,gBAAgBC;YAAapD,MAAOmC,KAA4BnC;;QACjF,OAAO;AAEL,iBAAO;YAAEgD,MAAMG,gBAAgBE;YAAa,GAAGlB;;QACjD;MACF,CAAC;MACDW,QAAQtD,KAAK8D,WAAW,CAAC,GAAG,oBAAIC,IAAI,CAAC,GAAGT,QAAQ,YAAY,CAAC,CAAC,IAAIA;;KACnE,IACD,MAAM,KAAKG,sBAAsB;MAC/B,GAAGzD;MACHwD,MAAMxD,KAAKwD,QAAQ;MACnBF,QAAQtD,KAAK8D,WAAW,CAAC,GAAG,oBAAIC,IAAI,CAAC,GAAGT,QAAQ,YAAY,CAAC,CAAC,IAAIA;;IACnE,CAAA;AAEP,QAAIC,eAAeC,SAAS,QAAQ;AAClC,aAAO,IAAIQ,iBAAiBT,cAAc;IAC5C;AAEA,WAAO,IAAIU,iBAAiBV,cAAc;EAC5C;EAEA,MAAMW,aAAalE,MAA4D;AAC7E,UAAMmE,YAAY,MAAM,KAAKC,sBAAsBpE,IAAI;AACvD,SAAKH,aAAawE,WAAWrE,KAAKQ,IAAI;AACtC,WAAO2D;EACT;EA0BA,MAAMG,UACJ9D,MACA+D,UACA/C,SAOC;AAED,UAAMgD,WAAW,KAAKC,mBAAmB;MAAEjE;MAAM+D,SAAAA;MAASG,OAAOlD,SAASkD;IAAK,CAAE;AACjF,UAAMC,eAAe,KAAK9E,aAAa+E,oBAAoBJ,QAAQ;AACnE,QAAI,CAACG,gBAAgBnD,SAASqD,oBAAoB,GAAG;AACnD,UAAI;AACF,eAAO,MAAM,KAAKC,2BAA2B;UAC3CtE;UACA+D,SAAAA;UACAG,OAAOlD,SAASkD;UAChBG,iBAAiBrD,SAASqD;UAC1BE,YAAYvD,SAASuD;UACrBC,cAAcxD,SAASyD;QACxB,CAAA;eACMC,KAAK;AACZ,YAAI1D,SAAS2D,UAAU;AACrB,gBAAMC,uBAAuB;YAC3B5E;YACA+D,SAASA,YAAW;YACpBjB,QAAQ9B,QAAQkD,QAAQ,CAAClD,QAAQkD,KAAK,IAAI,CAAA;YAC1CG,iBAAiBrD,SAASqD;YAC1BQ,QAAQ,CAAA;YACRC,MAAM,CAAA;;AAGR,cAAI9D,QAAQgC,SAAS,QAAQ;AAC3B,mBAAO,IAAIQ,iBACT;cACE,GAAGoB;cACH5B,MAAM;cACNE,QAASlC,QAAQ2D,SAA2BzC,IAAK6C,UAAS;gBACxD/B,MAAMG,gBAAgBE;gBACtB,GAAG0B;cACJ,EAAC;eAEJ,IAAI;UAER,OAAO;AACL,mBAAO,IAAItB,iBACT;cACE,GAAGmB;cACH5B,MAAM;cACNE,QAAQlC,QAAQ2D;eAElB,IAAI;UAER;QACF;AAEA,cAAMD;MACR;IACF;AAEA,QAAIP,aAAaa,WAAW;AAE1B,UAAI,CAAC,KAAK3F,aAAa4F,aAAajB,QAAQ,GAAG;AAC7C,cAAMkB,uBAAuB,KAAKZ,2BAA2B;UAC3DtE;UACA+D,SAAAA;UACAG,OAAOlD,SAASkD;UAChBG,iBAAiBrD,SAASqD;UAC1BE,YAAYvD,SAASuD;UACrBC,cAAcxD,SAASyD;QACxB,CAAA,EAAEU,MAAM,MAAK;AACZhG,kBAAQC,KACN,mCAAmC4E,QAAQ,0DAA0D;QAEzG,CAAC;AACD,aAAK3E,aAAa+F,qBAAqBpB,UAAUkB,oBAAoB;MACvE;AAEA,aAAOf,aAAakB;IACtB;AAEA,WAAOlB,aAAakB;EACtB;EAEQpB,mBAAmBpF,QAA0D;AACnF,UAAM;MAAEmB;MAAM+D,SAAAA;MAASG;IAAK,IAAKrF;AACjC,UAAMyG,QAAQ,CAACtF,IAAI;AAEnB,QAAI+D,aAAY/B,QAAW;AACzBsD,YAAM5D,KAAK,aAAaqC,SAAQwB,SAAQ,CAAE;IAC5C,WAAWrB,UAAUlC,QAAW;AAC9BsD,YAAM5D,KAAK,WAAWwC,KAAK;IAC7B,OAAO;AACLoB,YAAM5D,KAAK,kBAAkB;IAC/B;AAEA,WAAO4D,MAAME,KAAK,GAAG;EACvB;EAEQ,MAAMlB,2BAA2BzF,QAOxC;AACC,UAAMmF,WAAW,KAAKC,mBAAmBpF,MAAM;AAE/C,QAAI;AACF,YAAM;QAAEmB;QAAM+D,SAAAA;QAASM;QAAiBH;QAAOK;QAAYC;MAAc,IAAG3F;AAE5E,YAAM;QAAE8C;QAAM8D;UAAgB,MAAM,KAAKC,mBAAmB1F,MAAM+D,UAASG,OAAOK,YAAYC,YAAY;AAC1G,UAAIiB,gBAAgB,WAAW;AAC7B,cAAME,MAAMhE,KAAKiE,WAAW,sCAAsC;MACpE;AAEA,UAAI1C;AACJ,UAAIvB,KAAKqB,SAAS,QAAQ;AACxBE,QAAAA,UAAS,IAAIM,iBAAiB7B,IAAI;MACpC,OAAO;AACLuB,QAAAA,UAAS,IAAIO,iBAAiB9B,IAAI;MACpC;AAEA,WAAKtC,aAAawG,IAAI7B,UAAUd,SAAQmB,eAAe;AAEvD,aAAOnB;aACA4C,OAAO;AACd3G,cAAQ2G,MAAM,+CAA+C9B,QAAQ,MAAM8B,KAAK;AAEhF,YAAMA;IACR;EACF;EAEO,MAAMC,WAAWtG,IAAU;AAChC,WAAO,MAAM,KAAKuG,YAAYvG,EAAE;EAClC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA0CO,MAAMwG,uBACXpH,QAA4E;AAE5E,UAAM;MAAEwD;MAAK,GAAG6D;IAAM,IAAGrH;AAEzB,WAAOsH,cAAcF,uBAA0B;MAAE,GAAGC;MAAME,gBAAgB;MAAM/D;IAAG,CAAE;EACvF;EACAgE,YAAY7G,MAA4B;AACtC,SAAK8G,oBAAoB9G,IAAI;AAC7B,WAAO;EACT;EAEA+G,kBAAkB/G,MAAkC;AAClD,SAAKgH,0BAA0BhH,IAAI;AACnC,WAAO;EACT;AACD;IAEqBiH,6BAAoB;EAMxC7H,YAAY;IACV8H;IACAjH;IACAW;IACAuC;EAMD,GAAA;AACC,SAAK+D,SAASA;AACd,SAAKjH,KAAKA;AACV,SAAKW,UAAUA;AACf,SAAKuC,gBAAgBA;EACvB;EAEAjC,MAAMlB,MAAsE;AAC1E,WAAO,KAAKkH,OAAOhG,MAAM;MACvB,GAAGlB;MACHY,SAAS,KAAKA;MACduG,qBAAqB,KAAKhE;IAC3B,CAAA;EACH;EAEAxC,KAAKX,MAAqE;AACxE,WAAO,KAAKkH,OAAOvG,KAAK;MACtB,GAAGX;MACHY,SAAS,KAAKA;MACduG,qBAAqB,KAAKhE;IAC3B,CAAA;EACH;EAEApC,WACEf,MACa;AAEb,WAAO,KAAKkH,OAAOnG,WAAW;MAC5B,GAAGf;MACHY,SAAS,KAAKA;MACduG,qBAAqB,KAAKhE;IAC3B,CAAA;EACH;EAEA9B,MAAMrB,MAAsE;AAC1E,SAAKkH,OAAO7F,MAAM;MAChB,GAAGrB;MACHY,SAAS,KAAKA;MACduC,eAAe,KAAKA;IACrB,CAAA;AACD,WAAO;EACT;EAEAzC,cAAW;AACT,WAAO,GAAG,KAAKwG,OAAOE,OAAO,UAAU,KAAKxG,OAAO;EACrD;AACD;AAEK,IAAOR,sBAAP,cAAmC6G,qBAAoB;EAC3D7H,YAAY8H,QAAsBtG,SAAe;AAC/C,UAAM;MAAEsG;MAAQjH,IAAIW;MAASA;MAASuC,eAAe;IAAI,CAAE;EAC7D;EAEAkE,OAAOrH,MAAyC;AAC9C,SAAKkH,OAAOnH,MAAM;MAChB,GAAGC;MACHC,IAAI,KAAKA;IACV,CAAA;AACD,WAAO;EACT;AACD;AAED,IAAeqH,4BAAf,cAAiDL,qBAAoB;EACnE7H,YAAY8H,QAAsBjH,IAAYW,SAAe;AAC3D,UAAM;MAAEsG;MAAQjH;MAAIW;MAASuC,eAAelD;IAAE,CAAE;EAClD;AACD;AAEK,IAAOa,qBAAP,cAAkCwG,0BAAyB;EAC/DlI,YAAY8H,QAAsBjH,IAAYW,SAAe;AAC3D,UAAMsG,QAAQjH,IAAIW,OAAO;EAC3B;EAEAyG,OAAOrH,MAAoD;AACzD,SAAKkH,OAAOL,YAAY;MACtB,GAAG7G;MACHC,IAAI,KAAKA;MACTW,SAAS,KAAKA;IACf,CAAA;AACD,WAAO;EACT;EAEA2G,IAAIvH,MAAiE;AACnE,SAAKkH,OAAOL,YAAY;MACtB,GAAG7G;MACHC,IAAI,KAAKA;MACTW,SAAS,KAAKA;MACd4G,SAAS,oBAAIC,KAAI;IAClB,CAAA;AACD,WAAO;EACT;AACD;AAEK,IAAOxG,2BAAP,cAAwCqG,0BAAyB;EACrElI,YAAY8H,QAAsBjH,IAAYW,SAAe;AAC3D,UAAMsG,QAAQjH,IAAIW,OAAO;EAC3B;EAEAyG,OACErH,MAAyG;AAEzG,SAAKkH,OAAOH,kBAAkB;MAC5B,GAAG/G;MACHC,IAAI,KAAKA;MACTW,SAAS,KAAKA;IACf,CAAA;AACD,WAAO;EACT;EAEA2G,IACEvH,MACa;AAEb,SAAKkH,OAAOH,kBAAkB;MAC5B,GAAG/G;MACHC,IAAI,KAAKA;MACTW,SAAS,KAAKA;MACd4G,SAAS,oBAAIC,KAAI;IAClB,CAAA;AACD,WAAO;EACT;AACD;AAEK,IAAOrG,sBAAP,cAAmCkG,0BAAyB;EAChElI,YAAY8H,QAAsBjH,IAAYW,SAAe;AAC3D,UAAMsG,QAAQjH,IAAIW,OAAO;EAC3B;AACD;;;AC71DM,IAAM8G,cAA+B;EAC1CC,QAAQC,KAAG;AACT,QAAI;AACF,YAAMC,SAASD,MAAM;AACrB,YAAME,KAAKC,SAASC,OAAOC,MAAM,GAAG;AACpC,eAASC,IAAI,GAAGA,IAAIJ,GAAGK,QAAQD,KAAK;AAClC,YAAIE,IAAIN,GAAGI,CAAC;AACZ,eAAOE,EAAEC,OAAO,CAAC,KAAK,KAAK;AACzBD,cAAIA,EAAEE,UAAU,GAAGF,EAAED,MAAM;QAC7B;AACA,YAAIC,EAAEG,QAAQV,MAAM,MAAM,GAAG;AAC3B,iBAAOW,mBAAmBJ,EAAEE,UAAUT,OAAOM,QAAQC,EAAED,MAAM,CAAC;QAChE;MACF;IACF,SAASM,KAAK;IAAA;AACd,WAAO;;EAGTC,QAAQd,KAAae,OAAa;AAChC,QAAI;AACF,YAAMC,UAAU,IACdC,UAAU,IACVC,SAAS;AAEX,YAAMC,iBAAiBnB,MAAM,MAAMoB,mBAAmBL,KAAK,IAAIE,UAAU,aAAaD,UAAUE;AAChGf,eAASC,SAASe;aACXN,KAAK;AACZ;IACF;;EAGFQ,WAAWC,MAAI;AACb,QAAI;AACFxB,kBAAYgB,QAAQQ,MAAM,EAAE;aACrBT,KAAK;AACZ;IACF;;EAEFU,QAAK;AACHpB,aAASC,SAAS;;EAEpBoB,aAAU;AACR,UAAMtB,KAAKC,SAASC,OAAOC,MAAM,GAAG;AACpC,UAAMoB,OAAO,CAAA;AAEb,aAASnB,IAAI,GAAGA,IAAIJ,GAAGK,QAAQD,KAAK;AAClC,UAAIE,IAAIN,GAAGI,CAAC;AACZ,aAAOE,EAAEC,OAAO,CAAC,KAAK,KAAK;AACzBD,YAAIA,EAAEE,UAAU,GAAGF,EAAED,MAAM;MAC7B;AACAkB,WAAKC,KAAKlB,EAAEH,MAAM,GAAG,EAAE,CAAC,CAAC;IAC3B;AAEA,WAAOoB;EACT;;AAGF,IAAME,oBAAqBC,WAA+B;AACxD,SAAO;IACL7B,QAAQC,KAAG;AACT,aAAO4B,MAAM7B,QAAQC,GAAG;;IAG1Bc,QAAQd,KAAKe,OAAK;AAChBa,YAAMd,QAAQd,KAAKe,KAAK;;IAG1BM,WAAWrB,KAAG;AACZ4B,YAAMP,WAAWrB,GAAG;;IAEtBuB,QAAK;AACHK,YAAML,MAAK;;IAEbC,aAAU;AACR,YAAMC,OAAO,CAAA;AACb,iBAAWzB,OAAO6B,cAAc;AAC9BJ,aAAKC,KAAK1B,GAAG;MACf;AACA,aAAOyB;IACT;;AAEJ;AAEA,IAAMK,wBAAwBA,CAACC,SAA0B/B,MAAM,sBAA8B;AAC3F,MAAI,CAACgC,QAAQ;AACX,WAAO;EACT;AACA,MAAI;AACF,UAAMC,MAAM;AACZF,YAAQjB,QAAQd,KAAKiC,GAAG;AACxB,QAAIF,QAAQhC,QAAQC,GAAG,MAAMiC,KAAK;AAChC,aAAO;IACT;AACAF,YAAQV,WAAWrB,GAAG;AACtB,WAAO;WACAa,KAAK;AACZ,WAAO;EACT;AACF;AAEA,IAAIqB,aAA0CC;AAC9C,IAAIC,eAA4CD;AAEhD,IAAME,sBAAsBA,MAAsB;AAChD,QAAMC,SAA6C,CAAA;AAEnD,QAAMV,QAAyB;IAC7B7B,QAAQC,KAAG;AACT,aAAOsC,OAAOtC,GAAG;;IAGnBc,QAAQd,KAAKe,OAAK;AAChBuB,aAAOtC,GAAG,IAAIe,UAAU,OAAOA,QAAQoB;;IAGzCd,WAAWrB,KAAG;AACZ,aAAOsC,OAAOtC,GAAG;;IAEnBuB,QAAK;AACH,iBAAWvB,OAAOsC,QAAQ;AACxB,eAAOA,OAAOtC,GAAG;MACnB;;IAEFwB,aAAU;AACR,YAAMC,OAAO,CAAA;AACb,iBAAWzB,OAAOsC,QAAQ;AACxBb,aAAKC,KAAK1B,GAAG;MACf;AACA,aAAOyB;IACT;;AAEF,SAAOG;AACT;AAEO,IAAMW,aAAaA,CAACC,MAAsCR,YAA+C;AAC9G,MAAI,OAAOA,YAAWG,UAAaH,SAAQ;AACzC,QAAI,CAACH,cAAc;AACjB,YAAMY,cAAcd,kBAAkBK,QAAOH,YAAY;AACzDK,mBAAaJ,sBAAsBW,WAAW,IAAIA,cAAcN;IAClE;AAEA,QAAI,CAACC,cAAc;AACjB,YAAMM,gBAAgBf,kBAAkBK,QAAOW,cAAc;AAC7DP,qBAAeN,sBAAsBY,aAAa,IAAIA,gBAAgBP;IACxE;EACF;AAEA,UAAQK,MAAI;IACV,KAAK;AACH,aAAO1C,eAAeoC,cAAcE,gBAAgBC,oBAAmB;IACzE,KAAK;AACH,aAAOH,cAAcE,gBAAgBC,oBAAmB;IAC1D,KAAK;AACH,aAAOD,gBAAgBC,oBAAmB;IAC5C,KAAK;AACH,aAAOA,oBAAmB;IAC5B;AACE,aAAOA,oBAAmB;EAC9B;AACF;AC00DA,IAAYO;CAAZ,SAAYA,cAAW;AACrBA,EAAAA,aAAA,MAAA,IAAA;AACAA,EAAAA,aAAA,UAAA,IAAA;AACAA,EAAAA,aAAA,YAAA,IAAA;AACAA,EAAAA,aAAA,MAAA,IAAA;AACF,GALYA,gBAAAA,cAKX,CAAA,EAAA;IAEYC,mBAAU;EAcrBC,YAAYC,YAAyC,CAAA,GAAE;AAbhD,SAAOC,UAAW;AACjB,SAAYC,eAA4B;AAExC,SAAAC,mBAAmB,oBAAIC,IAAG;AAC1B,SAAAC,cAAc,IAAIC,gBAA0CC,MAAM,GAAGD,WAAW;AAEhF,SAAAE,gBAA+B;MACrCC,aAAa;MACbC,SAAS,CAAA;MACTC,UAAU;MACVC,gBAAgB;;AAOX,SAAAC,kBAAmBC,UAAiC;AACzD,WAAKZ,eAAeY;;AA8Bd,SAAAC,oBAA8D;MACpE,CAAClB,YAAYmB,IAAI,GAAIC,WACnBA,UAAU,SAAS,OAAOA,UAAU,YAAY,OAAOA,UAAU,YAAYC,KAAKC,UAAUF,KAAK,IAAIA;MACvG,CAACpB,YAAYuB,IAAI,GAAIH,WAAgBA,UAAU,QAAQ,OAAOA,UAAU,WAAWC,KAAKC,UAAUF,KAAK,IAAIA;MAC3G,CAACpB,YAAYwB,QAAQ,GAAIJ,WACvBK,OAAO5C,KAAKuC,SAAS,CAAA,CAAE,EAAEM,OAAO,CAACC,UAAUvE,QAAO;AAChD,cAAMwE,WAAWR,MAAMhE,GAAG;AAC1BuE,iBAASE,OACPzE,KACAwE,oBAAoBE,OAChBF,WACA,OAAOA,aAAa,YAAYA,aAAa,OAC3CP,KAAKC,UAAUM,QAAQ,IACvB,GAAGA,QAAQ,EAAE;AAErB,eAAOD;MACT,GAAG,IAAIH,SAAQ,CAAE;MACnB,CAACxB,YAAY+B,UAAU,GAAIX,WAAe,KAAKY,cAAcZ,KAAK;;AAgB1D,SAAAa,oBAAqBC,iBAAqD;AAClF,UAAI,KAAK5B,iBAAiB6B,IAAID,WAAW,GAAG;AAC1C,cAAME,mBAAkB,KAAK9B,iBAAiB+B,IAAIH,WAAW;AAC7D,YAAIE,kBAAiB;AACnB,iBAAOA,iBAAgBE;QACzB;AACA,eAAO;MACT;AAEA,YAAMF,kBAAkB,IAAIG,gBAAe;AAC3C,WAAKjC,iBAAiBkC,IAAIN,aAAaE,eAAe;AACtD,aAAOA,gBAAgBE;;AAGlB,SAAAG,eAAgBP,iBAA4B;AACjD,YAAME,kBAAkB,KAAK9B,iBAAiB+B,IAAIH,WAAW;AAE7D,UAAIE,iBAAiB;AACnBA,wBAAgBM,MAAK;AACrB,aAAKpC,iBAAiBqC,OAAOT,WAAW;MAC1C;;AAGK,SAAOU,UAAG,OAAyB;MACxCC;MACAvE;MACAwE;MACAlD;MACAmD;MACAC;MACA5C;MACA8B;MACA,GAAGe;IACe,MAAgB;AAClC,YAAMC,gBACF,OAAO5E,WAAW,YAAYA,SAAS,KAAKqC,cAAcrC,WAC1D,KAAK6E,kBACJ,MAAM,KAAKA,eAAe,KAAK9C,YAAY,KAC9C,CAAA;AACF,YAAM+C,gBAAgB,KAAKC,mBAAmBJ,QAAQC,YAAY;AAClE,YAAMI,cAAcP,SAAS,KAAKf,cAAce,KAAK;AACrD,YAAMQ,mBAAmB,KAAKrC,kBAAkBtB,QAAQI,YAAYmB,IAAI;AACxE,YAAMqC,iBAAiBR,UAAUI,cAAcJ;AAE/C,aAAO,KAAKxC,YAAY,GAAGJ,WAAW,KAAKA,WAAW,EAAE,GAAG0C,IAAI,GAAGQ,cAAc,IAAIA,WAAW,KAAK,EAAE,IAAI;QACxG,GAAGF;QACHvC,SAAS;UACP,GAAIuC,cAAcvC,WAAW,CAAA;UAC7B,GAAIjB,QAAQA,SAASI,YAAYwB,WAAW;YAAE,gBAAgB5B;cAAS,CAAA;;QAEzE0C,SAASJ,cAAc,KAAKD,kBAAkBC,WAAW,IAAIkB,cAAcd,WAAW;QACtFO,MAAM,OAAOA,SAAS,eAAeA,SAAS,OAAO,OAAOU,iBAAiBV,IAAI;MAClF,CAAA,EAAEY,KAAK,OAAOC,aAAY;AACzB,cAAMC,IAAID,SAASE,MAAK;AACxBD,UAAE1C,OAAO;AACT0C,UAAEE,QAAQ;AAEV,cAAM5C,OAAO,CAACuC,iBACVG,IACA,MAAMD,SAASF,cAAc,EAAC,EAC3BC,KAAMxC,CAAAA,UAAQ;AACb,cAAI0C,EAAEG,IAAI;AACRH,cAAE1C,OAAOA;UACX,OAAO;AACL0C,cAAEE,QAAQ5C;UACZ;AACA,iBAAO0C;QACT,CAAC,EACAI,MAAOC,OAAK;AACXL,YAAEE,QAAQG;AACV,iBAAOL;QACT,CAAC;AAEP,YAAIzB,aAAa;AACf,eAAK5B,iBAAiBqC,OAAOT,WAAW;QAC1C;AAEA,YAAI,CAACwB,SAASI,GAAI,OAAM7C;AACxB,eAAOA,KAAKA;MACd,CAAC;;AAlJDQ,WAAOwC,OAAO,MAAM9D,SAAS;EAC/B;EAMU+D,iBAAiB9G,KAAae,OAAU;AAChD,UAAMgG,aAAa3F,mBAAmBpB,GAAG;AACzC,WAAO,GAAG+G,UAAU,IAAI3F,mBAAmB,OAAOL,UAAU,WAAWA,QAAQ,GAAGA,KAAK,EAAE,CAAC;EAC5F;EAEUiG,cAAcrB,OAAwB3F,KAAW;AACzD,WAAO,KAAK8G,iBAAiB9G,KAAK2F,MAAM3F,GAAG,CAAC;EAC9C;EAEUiH,mBAAmBtB,OAAwB3F,KAAW;AAC9D,UAAMe,QAAQ4E,MAAM3F,GAAG;AACvB,WAAOe,MAAMmG,IAAKC,OAAW,KAAKL,iBAAiB9G,KAAKmH,CAAC,CAAC,EAAEC,KAAK,GAAG;EACtE;EAEUxC,cAAcyC,UAA0B;AAChD,UAAM1B,QAAQ0B,YAAY,CAAA;AAC1B,UAAM5F,OAAO4C,OAAO5C,KAAKkE,KAAK,EAAE2B,OAAQtH,SAAQ,gBAAgB,OAAO2F,MAAM3F,GAAG,CAAC;AACjF,WAAOyB,KACJyF,IAAKlH,SAASuH,MAAMC,QAAQ7B,MAAM3F,GAAG,CAAC,IAAI,KAAKiH,mBAAmBtB,OAAO3F,GAAG,IAAI,KAAKgH,cAAcrB,OAAO3F,GAAG,CAAE,EAC/GoH,KAAK,GAAG;EACb;EAEUK,eAAeJ,UAA0B;AACjD,UAAMnB,cAAc,KAAKtB,cAAcyC,QAAQ;AAC/C,WAAOnB,cAAc,IAAIA,WAAW,KAAK;EAC3C;EAsBUD,mBAAmByB,SAAwBC,SAAuB;AAC1E,WAAO;MACL,GAAG,KAAKpE;MACR,GAAGmE;MACH,GAAIC,WAAW,CAAA;MACflE,SAAS;QACP,GAAI,KAAKF,cAAcE,WAAW,CAAA;QAClC,GAAIiE,QAAQjE,WAAW,CAAA;QACvB,GAAKkE,WAAWA,QAAQlE,WAAY,CAAA;MACrC;;EAEL;AAmFD;AAiBK,IAAOmE,oBAAP,cAAmE/E,WAA4B;EAArGC,cAAA;;AACE,SAAA+E,MAAM;;;;;;;;;MASJC,iCAAiCA,CAC/BC,SACAlE,MACAgC,SAAwB,CAAA,MAExB,KAAKL,QAAqC;QACxCE,MAAM,iCAAiCqC,OAAO;QAC9CC,QAAQ;QACRvC,MAAM5B;QACN3C,QAAQ;QACRsB,MAAMI,YAAYmB;QAClB6B,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUHoC,iCAAiCA,CAACF,SAAiBG,QAAgBrC,SAAwB,CAAA,MACzF,KAAKL,QAAmD;QACtDE,MAAM,iCAAiCqC,OAAO,UAAUG,MAAM;QAC9DF,QAAQ;QACR9G,QAAQ;QACR0E,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUHsC,0BAA0BA,CAACJ,SAAiBlC,SAAwB,CAAA,MAClE,KAAKL,QAAiC;QACpCE,MAAM,iCAAiCqC,OAAO;QAC9CC,QAAQ;QACR9G,QAAQ;QACR0E,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUHuC,8BAA8BA,CAACL,SAAiBG,QAAgBrC,SAAwB,CAAA,MACtF,KAAKL,QAAqC;QACxCE,MAAM,iCAAiCqC,OAAO,UAAUG,MAAM;QAC9DF,QAAQ;QACR9G,QAAQ;QACR0E,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUHwC,gCAAgCA,CAC9B;QAAEN;QAAS,GAAGpC;SACdE,SAAwB,CAAA,MAExB,KAAKL,QAA+C;QAClDE,MAAM,iCAAiCqC,OAAO;QAC9CC,QAAQ;QACRrC;QACAzE,QAAQ;QACR0E,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUHyC,4BAA4BA,CAAC3C,OAA4CE,SAAwB,CAAA,MAC/F,KAAKL,QAA2C;QAC9CE,MAAM;QACNsC,QAAQ;QACRrC;QACAzE,QAAQ;QACR0E,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUH0C,iCAAiCA,CAC/BR,SACAG,QACArE,MACAgC,SAAwB,CAAA,MAExB,KAAKL,QAAqC;QACxCE,MAAM,iCAAiCqC,OAAO,UAAUG,MAAM;QAC9DF,QAAQ;QACRvC,MAAM5B;QACN3C,QAAQ;QACRsB,MAAMI,YAAYmB;QAClB6B,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUH2C,gBAAgBA,CAAC3E,MAA+BgC,SAAwB,CAAA,MACtE,KAAKL,QAAuC;QAC1CE,MAAM;QACNsC,QAAQ;QACRvC,MAAM5B;QACN3C,QAAQ;QACRsB,MAAMI,YAAYmB;QAClB6B,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUH4C,aAAaA,CAAC9C,OAA6BE,SAAwB,CAAA,MACjE,KAAKL,QAAqC;QACxCE,MAAM;QACNsC,QAAQ;QACRrC;QACAzE,QAAQ;QACR0E,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUH6C,iBAAiBA,CAACC,WAAmB9C,SAAwB,CAAA,MAC3D,KAAKL,QAAyB;QAC5BE,MAAM,wBAAwBiD,SAAS;QACvCX,QAAQ;QACR9G,QAAQ;QACR0E,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUH+C,oBAAoBA,CAAC/E,MAAmCgC,SAAwB,CAAA,MAC9E,KAAKL,QAA6B;QAChCE,MAAM;QACNsC,QAAQ;QACRvC,MAAM5B;QACN3C,QAAQ;QACRsB,MAAMI,YAAYmB;QAClB6B,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUHgD,oBAAoBA,CAACC,IAAYjD,SAAwB,CAAA,MACvD,KAAKL,QAA2C;QAC9CE,MAAM,6BAA6BoD,EAAE;QACrCd,QAAQ;QACR9G,QAAQ;QACR0E,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUHkD,iBAAiBA,CAACD,IAAYjD,SAAwB,CAAA,MACpD,KAAKL,QAA6B;QAChCE,MAAM,6BAA6BoD,EAAE;QACrCd,QAAQ;QACR9G,QAAQ;QACR0E,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUHmD,kBAAkBA,CAACrD,OAAkCE,SAAwB,CAAA,MAC3E,KAAKL,QAAuC;QAC1CE,MAAM;QACNsC,QAAQ;QACRrC;QACAzE,QAAQ;QACR0E,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUHoD,uBAAuBA,CAACpF,MAAsCgC,SAAwB,CAAA,MACpF,KAAKL,QAAgC;QACnCE,MAAM;QACNsC,QAAQ;QACRvC,MAAM5B;QACN3C,QAAQ;QACRsB,MAAMI,YAAYmB;QAClB6B,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUHqD,qBAAqBA,CAACvD,OAAqCE,SAAwB,CAAA,MACjF,KAAKL,QAAmB;QACtBE,MAAM;QACNsC,QAAQ;QACRrC;QACAzE,QAAQ;QACR,GAAG2E;OACJ;;;;;;;;;MAUHsD,gBAAgBA,CAACtF,MAA+BgC,SAAwB,CAAA,MACtE,KAAKL,QAAyB;QAC5BE,MAAM;QACNsC,QAAQ;QACRvC,MAAM5B;QACN3C,QAAQ;QACRsB,MAAMI,YAAYmB;QAClB6B,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUHuD,mBAAmBA,CAACC,aAAqBC,SAAiBzD,SAAwB,CAAA,MAChF,KAAKL,QAA0C;QAC7CE,MAAM,wBAAwB2D,WAAW,SAASC,OAAO;QACzDtB,QAAQ;QACR9G,QAAQ;QACR0E,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUH0D,aAAaA,CAACF,aAAqBxD,SAAwB,CAAA,MACzD,KAAKL,QAAyB;QAC5BE,MAAM,2BAA2B2D,WAAW;QAC5CrB,QAAQ;QACR9G,QAAQ;QACR0E,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUH2D,gBAAgBA,CAACH,aAAqBC,SAAiBzD,SAAwB,CAAA,MAC7E,KAAKL,QAAqC;QACxCE,MAAM,wBAAwB2D,WAAW,SAASC,OAAO;QACzDtB,QAAQ;QACR9G,QAAQ;QACR0E,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUH4D,iBAAiBA,CAAC;QAAEJ;QAAa,GAAG1D;SAAmCE,SAAwB,CAAA,MAC7F,KAAKL,QAAsC;QACzCE,MAAM,wBAAwB2D,WAAW;QACzCrB,QAAQ;QACRrC;QACAzE,QAAQ;QACR0E,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUH6D,cAAcA,CAAC/D,OAA8BE,SAAwB,CAAA,MACnE,KAAKL,QAAmC;QACtCE,MAAM;QACNsC,QAAQ;QACRrC;QACAzE,QAAQ;QACR0E,QAAQ;QACR,GAAGC;OACJ;;;;;;;;MASH8D,cAAcA,CAAC9D,SAAwB,CAAA,MACrC,KAAKL,QAAiC;QACpCE,MAAM;QACNsC,QAAQ;QACRpC,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUH+D,gBAAgBA,CAAC/F,MAAgCgC,SAAwB,CAAA,MACvE,KAAKL,QAAmC;QACtCE,MAAM;QACNsC,QAAQ;QACRvC,MAAM5B;QACN3C,QAAQ;QACRsB,MAAMI,YAAYmB;QAClB6B,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUHgE,UAAUA,CAACC,SAAiBjE,SAAwB,CAAA,MAClD,KAAKL,QAAkC;QACrCE,MAAM,qBAAqBoE,OAAO;QAClC9B,QAAQ;QACR9G,QAAQ;QACR0E,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUHkE,mBAAmBA,CAAClG,MAAmCgC,SAAwB,CAAA,MAC7E,KAAKL,QAA2C;QAC9CE,MAAM;QACNsC,QAAQ;QACRvC,MAAM5B;QACN3C,QAAQ;QACRsB,MAAMI,YAAYmB;QAClB6B,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUHmE,YAAYA,CAACF,SAAiBjG,MAAyBgC,SAAwB,CAAA,MAC7E,KAAKL,QAAmB;QACtBE,MAAM,qBAAqBoE,OAAO;QAClC9B,QAAQ;QACRvC,MAAM5B;QACN3C,QAAQ;QACRsB,MAAMI,YAAYmB;QAClB,GAAG8B;OACJ;;;;;;;;;MAUHoE,gBAAgBA,CAACtE,OAAgCE,SAAwB,CAAA,MACvE,KAAKL,QAAiC;QACpCE,MAAM;QACNsC,QAAQ;QACRrC;QACAzE,QAAQ;QACR0E,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUHqE,cAAcA,CAACrG,MAA6BgC,SAAwB,CAAA,MAClE,KAAKL,QAAuB;QAC1BE,MAAM;QACNsC,QAAQ;QACRvC,MAAM5B;QACN3C,QAAQ;QACRsB,MAAMI,YAAYmB;QAClB6B,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUHsE,cAAcA,CAACrB,IAAYjD,SAAwB,CAAA,MACjD,KAAKL,QAAmB;QACtBE,MAAM,sBAAsBoD,EAAE;QAC9Bd,QAAQ;QACR9G,QAAQ;QACR,GAAG2E;OACJ;;;;;;;;;MAUHuE,WAAWA,CAACtB,IAAYjD,SAAwB,CAAA,MAC9C,KAAKL,QAAuB;QAC1BE,MAAM,sBAAsBoD,EAAE;QAC9Bd,QAAQ;QACR9G,QAAQ;QACR0E,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUHwE,YAAYA,CAAC1E,OAA4BE,SAAwB,CAAA,MAC/D,KAAKL,QAAiC;QACpCE,MAAM;QACNsC,QAAQ;QACRrC;QACAzE,QAAQ;QACR0E,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUHyE,iBAAiBA,CAACC,eAAuB1E,SAAwB,CAAA,MAC/D,KAAKL,QAAkC;QACrCE,MAAM,4BAA4B6E,aAAa;QAC/CvC,QAAQ;QACR9G,QAAQ;QACR0E,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUH2E,qBAAqBA,CAAC7E,OAAqCE,SAAwB,CAAA,MACjF,KAAKL,QAAmC;QACtCE,MAAM;QACNsC,QAAQ;QACRrC;QACAzE,QAAQ;QACR0E,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUH4E,yCAAyCA,CAAC5E,SAAwB,CAAA,MAChE,KAAKL,QAAqC;QACxCE,MAAM;QACNsC,QAAQ;QACR9G,QAAQ;QACR0E,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUH6E,sCAAsCA,CAAC7E,SAAwB,CAAA,MAC7D,KAAKL,QAA8C;QACjDE,MAAM;QACNsC,QAAQ;QACR9G,QAAQ;QACR0E,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUH8E,oCAAoCA,CAACC,WAAmB/E,SAAwB,CAAA,MAC9E,KAAKL,QAAqC;QACxCE,MAAM,wBAAwBkF,SAAS;QACvC5C,QAAQ;QACR9G,QAAQ;QACR0E,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUHgF,2CAA2CA,CAAChH,MAA4BgC,SAAwB,CAAA,MAC9F,KAAKL,QAAoC;QACvCE,MAAM;QACNsC,QAAQ;QACRvC,MAAM5B;QACN3C,QAAQ;QACRsB,MAAMI,YAAYmB;QAClB6B,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUHiF,sCAAsCA,CAACF,WAAmB/G,MAA4BgC,SAAwB,CAAA,MAC5G,KAAKL,QAAoC;QACvCE,MAAM,wBAAwBkF,SAAS;QACvC5C,QAAQ;QACRvC,MAAM5B;QACN3C,QAAQ;QACRsB,MAAMI,YAAYmB;QAClB6B,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUHkF,gBAAgBA,CAAClH,MAAgCgC,SAAwB,CAAA,MACvE,KAAKL,QAAyB;QAC5BE,MAAM;QACNsC,QAAQ;QACRvC,MAAM5B;QACN3C,QAAQ;QACRsB,MAAMI,YAAYmB;QAClB6B,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUHmF,sBAAsBA,CAACJ,WAAmB/G,MAAsCgC,SAAwB,CAAA,MACtG,KAAKL,QAAgC;QACnCE,MAAM,wBAAwBkF,SAAS;QACvC5C,QAAQ;QACRvC,MAAM5B;QACN3C,QAAQ;QACRsB,MAAMI,YAAYmB;QAClB6B,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUHoF,gBAAgBA,CAACL,WAAmB/E,SAAwB,CAAA,MAC1D,KAAKL,QAAyC;QAC5CE,MAAM,wBAAwBkF,SAAS;QACvC5C,QAAQ;QACR9G,QAAQ;QACR0E,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUHqF,sBAAsBA,CAACN,WAAmBO,UAAkBtF,SAAwB,CAAA,MAClF,KAAKL,QAAwC;QAC3CE,MAAM,wBAAwBkF,SAAS,YAAYO,QAAQ;QAC3DnD,QAAQ;QACR9G,QAAQ;QACR0E,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUHuF,aAAaA,CAACvF,SAAwB,CAAA,MACpC,KAAKL,QAA0B;QAC7BE,MAAM;QACNsC,QAAQ;QACR9G,QAAQ;QACR0E,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUHwF,oBAAoBA,CAACT,WAAmB/E,SAAwB,CAAA,MAC9D,KAAKL,QAA4B;QAC/BE,MAAM,wBAAwBkF,SAAS;QACvC5C,QAAQ;QACR9G,QAAQ;QACR0E,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUHyF,gBAAgBA,CAACV,WAAmB/G,MAAgCgC,SAAwB,CAAA,MAC1F,KAAKL,QAAyB;QAC5BE,MAAM,wBAAwBkF,SAAS;QACvC5C,QAAQ;QACRvC,MAAM5B;QACN3C,QAAQ;QACRsB,MAAMI,YAAYmB;QAClB6B,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUH0F,eAAeA,CAAC1H,MAA8BgC,SAAwB,CAAA,MACpE,KAAKL,QAAwB;QAC3BE,MAAM;QACNsC,QAAQ;QACRvC,MAAM5B;QACN3C,QAAQ;QACRsB,MAAMI,YAAYmB;QAClB6B,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUH2F,YAAYA,CAAC;QAAEC;QAAY,GAAG9F;SAA8BE,SAAwB,CAAA,MAClF,KAAKL,QAAwB;QAC3BE,MAAM,0BAA0B+F,UAAU;QAC1CzD,QAAQ;QACRrC;QACAzE,QAAQ;QACR0E,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUH6F,aAAaA,CAAC/F,OAA6BE,SAAwB,CAAA,MACjE,KAAKL,QAAwC;QAC3CE,MAAM;QACNsC,QAAQ;QACRrC;QACAzE,QAAQ;QACR0E,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUH8F,qBAAqBA,CACnBrK,MACAsK,UACA/H,MACAgC,SAAwB,CAAA,MAExB,KAAKL,QAAwB;QAC3BE,MAAM,0BAA0BpE,IAAI,aAAasK,QAAO;QACxD5D,QAAQ;QACRvC,MAAM5B;QACN3C,QAAQ;QACRsB,MAAMI,YAAYmB;QAClB6B,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUHgG,gBAAgBA,CAAChI,MAAgCgC,SAAwB,CAAA,MACvE,KAAKL,QAA0B;QAC7BE,MAAM;QACNsC,QAAQ;QACRvC,MAAM5B;QACN3C,QAAQ;QACRsB,MAAMI,YAAYmB;QAClB6B,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUHiG,gBAAgBA,CAACC,QAAgBlG,SAAwB,CAAA,MACvD,KAAKL,QAA+B;QAClCE,MAAM,0BAA0BqG,MAAM;QACtC/D,QAAQ;QACR9G,QAAQ;QACR0E,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUHmG,sBAAsBA,CAACnG,SAAwB,CAAA,MAC7C,KAAKL,QAAuC;QAC1CE,MAAM;QACNsC,QAAQ;QACR9G,QAAQ;QACR0E,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUHoG,gBAAgBA,CAACpG,SAAwB,CAAA,MACvC,KAAKL,QAAiC;QACpCE,MAAM;QACNsC,QAAQ;QACR9G,QAAQ;QACR0E,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUHqG,8BAA8BA,CAACrG,SAAwB,CAAA,MACrD,KAAKL,QAAuC;QAC1CE,MAAM;QACNsC,QAAQ;QACR9G,QAAQ;QACR0E,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUHsG,aAAaA,CAACJ,QAAgBlG,SAAwB,CAAA,MACpD,KAAKL,QAA0B;QAC7BE,MAAM,0BAA0BqG,MAAM;QACtC/D,QAAQ;QACR9G,QAAQ;QACR0E,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUHuG,eAAeA,CAACzG,OAA+BE,SAAwB,CAAA,MACrE,KAAKL,QAAuC;QAC1CE,MAAM;QACNsC,QAAQ;QACRrC;QACAzE,QAAQ;QACR0E,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUHwG,oBAAoBA,CAACxI,MAAmCgC,SAAwB,CAAA,MAC9E,KAAKL,QAA6B;QAChCE,MAAM;QACNsC,QAAQ;QACRvC,MAAM5B;QACN3C,QAAQ;QACRsB,MAAMI,YAAYmB;QAClB6B,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUHyG,iBAAiBA,CAAC3G,OAAiCE,SAAwB,CAAA,MACzE,KAAKL,QAA8B;QACjCE,MAAM;QACNsC,QAAQ;QACRrC;QACAzE,QAAQ;QACR0E,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUH0G,qBAAqBA,CAACC,UAAkB3G,SAAwB,CAAA,MAC9D,KAAKL,QAA6B;QAChCE,MAAM,6BAA6B8G,QAAQ;QAC3CxE,QAAQ;QACR9G,QAAQ;QACR0E,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUH4G,aAAaA,CAAC5I,MAA6BgC,SAAwB,CAAA,MACjE,KAAKL,QAAqC;QACxCE,MAAM;QACNsC,QAAQ;QACRvC,MAAM5B;QACN3C,QAAQ;QACRsB,MAAMI,YAAYmB;QAClB6B,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUH6G,aAAaA,CAACC,SAAiB9G,SAAwB,CAAA,MACrD,KAAKL,QAAmB;QACtBE,MAAM,sBAAsBiH,OAAO;QACnC3E,QAAQ;QACR9G,QAAQ;QACR,GAAG2E;OACJ;;;;;;;;;MAUH+G,YAAYA,CAACjH,OAA4BE,SAAwB,CAAA,MAC/D,KAAKL,QAAmC;QACtCE,MAAM;QACNsC,QAAQ;QACRrC;QACAzE,QAAQ;QACR0E,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUHgH,gBAAgBA,CAACF,SAAiB9G,SAAwB,CAAA,MACxD,KAAKL,QAAuB;QAC1BE,MAAM,yBAAyBiH,OAAO;QACtC3E,QAAQ;QACR9G,QAAQ;QACR0E,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUHiH,aAAaA,CAACC,YAAmBlH,SAAwB,CAAA,MACvD,KAAKL,QAAmC;QACtCE,MAAM,wBAAwBqH,UAAS;QACvC/E,QAAQ;QACR9G,QAAQ;QACR0E,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUHmH,cAAcA,CAACrH,OAA8BE,SAAwB,CAAA,MACnE,KAAKL,QAAmC;QACtCE,MAAM;QACNsC,QAAQ;QACRrC;QACAzE,QAAQ;QACR0E,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUHoH,aAAaA,CAACC,SAAiBrH,SAAwB,CAAA,MACrD,KAAKL,QAAqC;QACxCE,MAAM,sBAAsBwH,OAAO;QACnClF,QAAQ;QACR9G,QAAQ;QACR0E,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUHsH,qBAAqBA,CAACtJ,MAAqCgC,SAAwB,CAAA,MACjF,KAAKL,QAAqC;QACxCE,MAAM;QACNsC,QAAQ;QACRvC,MAAM5B;QACN3C,QAAQ;QACRsB,MAAMI,YAAYmB;QAClB6B,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUHuH,UAAUA,CAACF,SAAiBrH,SAAwB,CAAA,MAClD,KAAKL,QAAsC;QACzCE,MAAM,sBAAsBwH,OAAO;QACnClF,QAAQ;QACR9G,QAAQ;QACR0E,QAAQ;QACR,GAAGC;OACJ;;;;;;;;;MAUHwH,WAAWA,CAAC1H,OAA2BE,SAAwB,CAAA,MAC7D,KAAKL,QAAwB;QAC3BE,MAAM;QACNsC,QAAQ;QACRrC;QACAzE,QAAQ;QACR0E,QAAQ;QACR,GAAGC;OACJ;;EAEP;AAAC;;ACj4GK,IAAOyH,WAAP,cAAwBC,aAAY;EAMxCzK,YAAY+C,QAAqE;AAC/E,UAAM2H,iBAAiBC,MAAMC,kBAAkB7H,MAAM;AACrD,UAAM2H,cAAc;AAEpB,QAAI,OAAOxL,WAAW,eAAe,UAAUA,WAAW,OAAO;AAC/D,WAAK2L,cAAc9H,QAAQ+H,mBACvB,MAAM/H,OAAO+H,gBAAgB,KAC7B,MAAMJ,eAAeK,SAAS;AAClC,WAAKC,WAAWvL,WAAWsD,QAAQkI,eAAe,gBAAgB/L,MAAM;IAC1E,OAAO;AACL,WAAK2L,cAAc,MAAMH,eAAeK,SAAS;AACjD,WAAKC,WAAWvL,WAAW,UAAUJ,MAAS;IAChD;AAEA,SAAK0F,MAAM,IAAID,kBAAkB;MAC/B5E,SAAS,KAAKA;MACdO,eAAe;QACbE,SAAS;UACP,uBAAuB;UACvB,0BAA0B,KAAKuK,kBAAiB;UAChD,0BAA0B,KAAKC,aAAY;UAC3C,8BAA8B,KAAKC;UACnC,yBAAyB,KAAKL;UAC9B,GAAG,KAAKM;UACR,GAAG,KAAKC,6BAA6B,KAAKP,WAAW,KAAKQ,SAAS;QACpE;MACF;KACF,EAAExG;EACL;EAEAyG,qBAAwBtO,KAA8B;AACpD,QAAI,CAAC,KAAKuO,eAAe;AACvB,WAAKA,gBAAgBtK,KAAKuK,MAAM,KAAKV,SAAS/N,QAAQ,KAAK4N,WAAW,KAAK,IAAI,KAAK,CAAA;IACtF;AAEA,WAAO,KAAKY,cAAcvO,GAAG;EAC/B;EAEAyO,qBAAwBzO,KAAgCe,OAAe;AACrE,QAAI,CAAC,KAAKwN,eAAe;AACvB,WAAKA,gBAAgBtK,KAAKuK,MAAM,KAAKV,SAAS/N,QAAQ,KAAK4N,WAAW,KAAK,IAAI,KAAK,CAAA;IACtF;AAEA,QAAI5M,UAAU,MAAM;AAClB,aAAO,KAAKwN,cAAcvO,GAAG;IAC/B,OAAO;AACL,WAAKuO,cAAcvO,GAAG,IAAIe;IAC5B;AAEA,SAAK+M,SAAShN,QAAQ,KAAK6M,aAAa1J,KAAKC,UAAU,KAAKqK,aAAa,CAAC;EAC5E;EAEAjL,MAAMoL,KAAaC,SAA6B;AAC9C,WAAOrL,MAAMoL,KAAKC,OAAO;EAC3B;EAEAV,eAAY;AACV,WAAO;EACT;EAEAD,oBAAiB;AACf,WAAOpC;EACT;EAEAgD,qBAAkB;AAChB;EACF;AACD;IC/FYC,0BAAAA,mBAAiB;;;;;;EAQrB,OAAOC,YAAYC,QAA2B;AACnD,QAAI,CAACF,mBAAkBG,UAAU;AAC/BH,yBAAkBG,WAAW,IAAIC,SAASF,MAAM;IAClD;AACA,WAAOF,mBAAkBG;EAC3B;;AAZeH,kBAAAG,WAA4B;;;AKJ7C,IAAM,kBAAkB;AACxB,IAAM,aAAa;AAEZ,IAAM,oBAAN,MAA6C;AAAA,EAClD,YAA6B,QAAoB;AAApB;AAAA,EAAqB;AAAA,EAElD,MAAM,YAAY,MAAoC;AACpD,QAAI,CAAC,KAAK,OAAO,aAAa,CAAC,KAAK,OAAO,UAAW;AAEtD,UAAM,SAAS,IAAI,SAAS;AAAA,MAC1B,WAAW,KAAK,OAAO;AAAA,MACvB,WAAW,KAAK,OAAO;AAAA,MACvB,SAAS,KAAK,OAAO;AAAA,MACrB,SAAS,KAAK,OAAO;AAAA,MACrB,aAAa,KAAK,OAAO;AAAA,MACzB,gBAAgB;AAAA,MAChB,SAAS;AAAA,MACT,iBAAiB;AAAA,MACjB,gBAAgB;AAAA,IAClB,CAAC;AAED,QAAI;AAEF,YAAM,aAAyB;AAAA,QAC7B,MAAM;AAAA,QACN,WAAW,KAAK;AAAA,QAChB,OAAO,KAAK;AAAA,QACZ,UAAU;AAAA,UACR,QAAQ;AAAA,UACR,QAAQ,KAAK;AAAA,UACb,WAAW,KAAK,MAAM;AAAA,QACxB;AAAA,QACA,MAAM,CAAC,SAAS,YAAY;AAAA,MAC9B;AACA,UAAI,KAAK,OAAO,OAAQ,YAAW,SAAS,KAAK,OAAO;AACxD,YAAM,QAAQ,OAAO,MAAM,UAAU;AAErC,iBAAW,QAAQ,KAAK,OAAO;AAC7B,cAAM,OAAO,MAAM,KAAK;AAAA,UACtB,MAAM,QAAQ,KAAK,IAAI;AAAA,UACvB,OAAO,KAAK;AAAA,UACZ,UAAU;AAAA,YACR,QAAQ,KAAK;AAAA,YACb,WAAW,KAAK;AAAA,YAChB,SAAS,KAAK;AAAA,UAChB;AAAA,QACF,CAAC;AACD,YAAI,KAAK,OAAO;AACd,eAAK,IAAI;AAAA,YACP,QAAQ,EAAE,OAAO,KAAK,MAAM;AAAA,YAC5B,OAAO;AAAA,YACP,eAAe,KAAK;AAAA,UACtB,CAAC;AAAA,QACH,OAAO;AACL,eAAK,IAAI,EAAE,QAAQ,KAAK,OAAO,CAAC;AAAA,QAClC;AAAA,MACF;AAEA,YAAM,aAAa,MAAM,WAAW;AAAA,QAClC,MAAM;AAAA,QACN,OAAO,KAAK;AAAA,QACZ,UAAU;AAAA,UACR,QAAQ,KAAK;AAAA,QACf;AAAA,MACF,CAAC;AACD,iBAAW,IAAI,EAAE,QAAQ,KAAK,iBAAiB,CAAC;AAEhD,YAAM,OAAO;AAAA,QACX,QAAQ,KAAK;AAAA,QACb,UAAU;AAAA,UACR,QAAQ;AAAA,UACR,QAAQ,KAAK;AAAA,UACb,WAAW,KAAK,MAAM;AAAA,QACxB;AAAA,MACF,CAAC;AAED,YAAM,OAAO,WAAW;AAAA,IAC1B,UAAE;AACA,YAAM,OAAO,cAAc;AAAA,IAC7B;AAAA,EACF;AACF;;;AxB5EA,SAASE,UAAS,OAAkD;AAClE,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAEA,SAAS,aAAa,KAA0B;AAC9C,MAAI,CAAC,IAAI,KAAK,EAAG,QAAO,CAAC;AACzB,MAAI;AACF,UAAM,SAAkB,KAAK,MAAM,GAAG;AACtC,WAAOA,UAAS,MAAM,IAAK,SAAyB,CAAC;AAAA,EACvD,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAEA,eAAe,YAA6B;AAC1C,MAAI,MAAM;AACV,UAAQ,MAAM,YAAY,MAAM;AAChC,mBAAiB,SAAS,QAAQ,MAAO,QAAO;AAChD,SAAO;AACT;AAEA,SAAS,YAAY,SAAkB,SAAuB;AAC5D,MAAI,QAAS,SAAQ,OAAO,MAAM,4BAA4B,OAAO;AAAA,CAAI;AAC3E;AAEA,eAAe,OAAsB;AACnC,QAAM,SAAS,WAAW;AAC1B,QAAM,UAAU,aAAa,MAAM,UAAU,CAAC;AAC9C,QAAM,QAAQ,UAAU,OAAO,KAAK;AACpC,QAAM,UAAU,UAAU,OAAO,KAAK;AAEtC,cAAY,OAAO,OAAO,SAAS,KAAK,YAAY,OAAO,EAAE;AAE7D,QAAM,OAAO,aAAa,MAAM,IAC5B,IAAI,kBAAkB,MAAM,IAC5B,IAAI,cAAc;AACtB,QAAM,UAAU,IAAI;AAAA,IAClB,IAAI,eAAe;AAAA,IACnB;AAAA,IACA;AAAA,IACA,EAAE,KAAK,MAAM,oBAAI,KAAK,EAAE;AAAA,IACxB,EAAE,MAAMC,YAAW;AAAA,EACrB;AAEA,MAAI;AACF,UAAM,QAAQ,OAAO,OAAO;AAAA,EAC9B,SAAS,OAAO;AACd,UAAM,OAAO,iBAAiB,QAAQ,MAAM,OAAO;AACnD,gBAAY,OAAO,OAAO,qBAAqB,IAAI,GAAG;AAAA,EACxD;AAGA,UAAQ,OAAO,MAAM,MAAM;AAC7B;AAEA,MAAM,KAAK;", + "names": ["randomUUID", "option", "homedir", "join", "sessionId", "clearCache", "parse", "render", "SimpleEventEmitter", "constructor", "events", "on", "event", "listener", "push", "filter", "x", "emit", "payload", "DEFAULT_PROMPT_CACHE_TTL_SECONDS", "LangfusePromptCacheItem", "value", "ttlSeconds", "_expiry", "Date", "now", "isExpired", "LangfusePromptCache", "_cache", "Map", "_defaultTtlSeconds", "_refreshingKeys", "getIncludingExpired", "key", "get", "set", "effectiveTtlSeconds", "addRefreshingPromise", "promise", "then", "delete", "catch", "isRefreshing", "has", "invalidate", "promptName", "console", "log", "keys", "startsWith", "LangfusePersistedProperty", "ChatMessageType", "mustache", "escape", "text", "BasePromptClient", "prompt", "isFallback", "type", "name", "version", "config", "labels", "tags", "commitMessage", "_transformToLangchainVariables", "content", "jsonEscapedContent", "escapeJsonForLangchain", "replace", "out", "stack", "i", "n", "length", "ch", "j", "test", "isJson", "pop", "join", "TextPromptClient", "promptResponse", "compile", "variables", "_placeholders", "render", "getLangchainPrompt", "_options", "toJSON", "JSON", "stringify", "ChatPromptClient", "normalizedPrompt", "normalizePrompt", "typedPrompt", "map", "item", "ChatMessage", "placeholders", "messagesWithPlaceholdersReplaced", "placeholderValues", "Placeholder", "placeholderValue", "Array", "isArray", "every", "msg", "undefined", "role", "options", "variableName", "optional", "_", "messageWithoutType", "assert", "truthyValue", "message", "Error", "removeTrailingSlash", "url", "retriable", "fn", "props", "retryCount", "retryDelay", "retryCheck", "lastError", "Promise", "resolve", "setTimeout", "res", "e", "generateUUID", "globalThis", "d", "getTime", "d2", "performance", "c", "r", "Math", "random", "floor", "toString", "currentTimestamp", "currentISOTime", "toISOString", "safeSetTimeout", "timeout", "t", "unref", "getEnv", "process", "env", "configLangfuseSDK", "params", "secretRequired", "publicKey", "secretKey", "coreOptions", "finalPublicKey", "finalSecretKey", "finalBaseUrl", "baseUrl", "finalCoreOptions", "encodeQueryParams", "queryParams", "URLSearchParams", "Object", "entries", "forEach", "append", "common_release_envs", "getCommonReleaseEnvs", "fs", "cryptoModule", "dynamicImport", "module", "Deno", "all", "importedFs", "importedCrypto", "versions", "node", "crypto", "LangfuseMedia", "obj", "base64DataUri", "contentType", "contentBytes", "filePath", "_mediaId", "contentBytesParsed", "contentTypeParsed", "parseBase64DataUri", "_contentBytes", "_contentType", "_source", "existsSync", "readFile", "error", "readFileSync", "data", "header", "actualData", "slice", "split", "headerParts", "includes", "Buffer", "from", "contentLength", "contentSha256Hash", "sha256Hash", "createHash", "update", "digest", "parseReferenceString", "referenceString", "prefix", "suffix", "endsWith", "pairs", "parsedData", "pair", "mediaId", "source", "resolveMediaReferences", "langfuseClient", "maxDepth", "traverse", "depth", "regex", "referenceStringMatches", "match", "result", "referenceStringToMediaContentMap", "parsedMediaReference", "mediaData", "fetchMedia", "mediaContent", "fetch", "method", "headers", "status", "base64MediaContent", "arrayBuffer", "warn", "replaceAll", "fromEntries", "isInSample", "input", "sampleRate", "isNaN", "simpleHash", "str", "hash", "prime", "charCodeAt", "abs", "MAX_EVENT_SIZE_BYTES", "getEnv", "Number", "MAX_BATCH_SIZE_BYTES", "ENVIRONMENT_PATTERN", "WHITELISTED_LANGFUSE_INTERNAL_ENVIRONMENTS", "LangfuseFetchHttpError", "Error", "constructor", "response", "body", "status", "name", "LangfuseFetchNetworkError", "error", "cause", "isLangfuseFetchHttpError", "isLangfuseFetchNetworkError", "isLangfuseFetchError", "err", "SUPPORT_URL", "API_DOCS_URL", "RBAC_DOCS_URL", "INSTALLATION_DOCS_URL", "RATE_LIMITS_URL", "NPM_PACKAGE_URL", "updatePromptResponse", "defaultServerErrorPrompt", "defaultErrorResponse", "errorResponseByCode", "Map", "getErrorResponseByCode", "code", "errorResponse", "get", "logIngestionError", "console", "LangfuseCoreStateless", "params", "additionalHeaders", "debugMode", "pendingEventProcessingPromises", "pendingIngestionPromises", "localEventExportMap", "_events", "SimpleEventEmitter", "publicKey", "secretKey", "enabled", "_projectId", "_isLocalEventExportEnabled", "options", "on", "payload", "JSON", "stringify", "baseUrl", "removeTrailingSlash", "flushAt", "Math", "max", "flushInterval", "release", "getCommonReleaseEnvs", "undefined", "mask", "sampleRate", "emit", "environment", "test", "includes", "_retryOptions", "retryCount", "fetchRetryCount", "retryDelay", "fetchRetryDelay", "retryCheck", "requestTimeout", "sdkIntegration", "isLocalEventExportEnabled", "projectId", "getSdkIntegration", "getCommonEventProperties", "$lib", "getLibraryId", "$lib_version", "getLibraryVersion", "event", "cb", "debug", "removeDebugCallback", "log", "traceStateless", "id", "bodyId", "timestamp", "bodyTimestamp", "bodyRelease", "rest", "generateUUID", "parsedBody", "Date", "enqueue", "eventStateless", "startTime", "bodyStartTime", "spanStateless", "generationStateless", "prompt", "promptDetails", "isFallback", "promptName", "promptVersion", "version", "scoreStateless", "updateSpanStateless", "updateGenerationStateless", "_getDataset", "encodedName", "encodeURIComponent", "fetchAndLogErrors", "_getFetchOptions", "method", "_getDatasetItems", "query", "URLSearchParams", "Object", "entries", "forEach", "key", "value", "append", "toString", "_fetchMedia", "fetchTraces", "data", "meta", "encodeQueryParams", "fetchTrace", "traceId", "res", "fetchObservations", "fetchObservation", "observationId", "fetchSessions", "getDatasetRun", "encodedDatasetName", "datasetName", "encodedRunName", "runName", "getDatasetRuns", "createDatasetRunItem", "createDataset", "dataset", "createDatasetItem", "getDatasetItem", "_parsePayload", "parse", "createPromptStateless", "updatePromptStateless", "getPromptStateless", "label", "maxRetries", "url", "size", "boundedMaxRetries", "_getBoundedMaxRetries", "defaultMaxRetries", "maxRetriesUpperBound", "retryOptions", "retryLogger", "string", "retriable", "fetch", "fetchTimeout", "catch", "e", "text", "json", "fetchResult", "min", "type", "parseTraceId", "isInSample", "promise", "processEnqueueEvent", "promiseId", "finally", "maskEventBodyInPlace", "processMediaInEvent", "finalEventBody", "truncateEventBody", "queue", "getPersistedProperty", "LangfusePersistedProperty", "Queue", "push", "currentISOTime", "metadata", "setPersistedProperty", "length", "flush", "_flushTimer", "safeSetTimeout", "maskableKeys", "maxByteSize", "bodySize", "getByteSize", "keysToCheck", "keySizes", "map", "sort", "a", "b", "result", "currentSize", "prototype", "hasOwnProperty", "call", "obj", "serialized", "TextEncoder", "encode", "replace", "Promise", "all", "field", "findAndProcessMedia", "seenObjects", "WeakMap", "maxLevels", "processRecursively", "level", "startsWith", "media", "LangfuseMedia", "base64DataUri", "processMediaItem", "has", "set", "Array", "isArray", "item", "input_audio", "audio", "fromEntries", "contentLength", "_contentType", "contentSha256Hash", "_contentBytes", "getUploadUrlBody", "contentType", "sha256Hash", "fetchResponse", "uploadUrlResponse", "uploadUrl", "mediaId", "_mediaId", "now", "uploadResponse", "uploadMediaWithBackoff", "contentBytes", "baseDelay", "patchMediaBody", "uploadedAt", "toISOString", "uploadHttpStatus", "uploadHttpError", "uploadTimeMs", "attempt", "headers", "delay", "pow", "jitter", "random", "resolve", "setTimeout", "flushAsync", "values", "_reject", "callback", "clearTimeout", "items", "splice", "processedItems", "remainingItems", "processQueueItems", "promiseUUID", "done", "batch", "batch_size", "sdk_integration", "sdk_version", "sdk_variant", "public_key", "sdk_name", "fetchOptions", "requestPromise", "fetchWithRetry", "then", "MAX_MSG_SIZE", "BATCH_SIZE_LIMIT", "totalSize", "i", "itemSize", "Blob", "warn", "slice", "p", "constructAuthorizationHeader", "signal", "AbortSignal", "timeout", "Authorization", "encodedCredentials", "btoa", "Buffer", "from", "ms", "ctrl", "AbortController", "abort", "returnBody", "errors", "shutdownAsync", "x", "_exportLocalEvents", "events", "delete", "shutdown", "awaitAllQueuedAndPendingRequests", "LangfuseCore", "LangfuseCoreStateless", "constructor", "params", "publicKey", "secretKey", "enabled", "_isLocalEventExportEnabled", "isObservabilityEnabled", "console", "warn", "_promptCache", "LangfusePromptCache", "trace", "body", "id", "traceStateless", "t", "LangfuseTraceClient", "getEnv", "deferRuntime", "langfuseTraces", "name", "url", "getTraceUrl", "span", "traceId", "spanStateless", "LangfuseSpanClient", "generation", "generationStateless", "LangfuseGenerationClient", "event", "eventStateless", "LangfuseEventClient", "score", "scoreStateless", "getDataset", "options", "dataset", "_getDataset", "items", "page", "itemsResponse", "_getDatasetItems", "datasetName", "limit", "fetchItemsPageSize", "push", "data", "meta", "totalPages", "returnDataset", "description", "undefined", "metadata", "map", "item", "link", "obj", "runName", "runArgs", "awaitAllQueuedAndPendingRequests", "createDatasetRunItem", "datasetItemId", "observationId", "runDescription", "createPrompt", "labels", "promptResponse", "type", "createPromptStateless", "prompt", "ChatMessageType", "Placeholder", "ChatMessage", "isActive", "Set", "ChatPromptClient", "TextPromptClient", "updatePrompt", "newPrompt", "updatePromptStateless", "invalidate", "getPrompt", "version", "cacheKey", "_getPromptCacheKey", "label", "cachedPrompt", "getIncludingExpired", "cacheTtlSeconds", "_fetchPromptAndUpdateCache", "maxRetries", "fetchTimeout", "fetchTimeoutMs", "err", "fallback", "sharedFallbackParams", "config", "tags", "msg", "isExpired", "isRefreshing", "refreshPromptPromise", "catch", "addRefreshingPromise", "value", "parts", "toString", "join", "fetchResult", "getPromptStateless", "Error", "message", "set", "error", "fetchMedia", "_fetchMedia", "resolveMediaReferences", "rest", "LangfuseMedia", "langfuseClient", "_updateSpan", "updateSpanStateless", "_updateGeneration", "updateGenerationStateless", "LangfuseObjectClient", "client", "parentObservationId", "baseUrl", "update", "LangfuseObservationClient", "end", "endTime", "Date", "cookieStore", "getItem", "key", "nameEQ", "ca", "document", "cookie", "split", "i", "length", "c", "charAt", "substring", "indexOf", "decodeURIComponent", "err", "setItem", "value", "cdomain", "expires", "secure", "new_cookie_val", "encodeURIComponent", "removeItem", "name", "clear", "getAllKeys", "keys", "push", "createStorageLike", "store", "localStorage", "checkStoreIsSupported", "storage", "window", "val", "localStore", "undefined", "sessionStore", "createMemoryStorage", "_cache", "getStorage", "type", "_localStore", "_sessionStore", "sessionStorage", "ContentType", "HttpClient", "constructor", "apiConfig", "baseUrl", "securityData", "abortControllers", "Map", "customFetch", "fetchParams", "fetch", "baseApiParams", "credentials", "headers", "redirect", "referrerPolicy", "setSecurityData", "data", "contentFormatters", "Json", "input", "JSON", "stringify", "Text", "FormData", "Object", "reduce", "formData", "property", "append", "Blob", "UrlEncoded", "toQueryString", "createAbortSignal", "cancelToken", "has", "abortController", "get", "signal", "AbortController", "set", "abortRequest", "abort", "delete", "request", "body", "path", "query", "format", "params", "secureParams", "securityWorker", "requestParams", "mergeRequestParams", "queryString", "payloadFormatter", "responseFormat", "then", "response", "r", "clone", "error", "ok", "catch", "e", "assign", "encodeQueryParam", "encodedKey", "addQueryParam", "addArrayQueryParam", "map", "v", "join", "rawQuery", "filter", "Array", "isArray", "addQueryParams", "params1", "params2", "LangfusePublicApi", "api", "annotationQueuesCreateQueueItem", "queueId", "method", "annotationQueuesDeleteQueueItem", "itemId", "annotationQueuesGetQueue", "annotationQueuesGetQueueItem", "annotationQueuesListQueueItems", "annotationQueuesListQueues", "annotationQueuesUpdateQueueItem", "commentsCreate", "commentsGet", "commentsGetById", "commentId", "datasetItemsCreate", "datasetItemsDelete", "id", "datasetItemsGet", "datasetItemsList", "datasetRunItemsCreate", "datasetRunItemsList", "datasetsCreate", "datasetsDeleteRun", "datasetName", "runName", "datasetsGet", "datasetsGetRun", "datasetsGetRuns", "datasetsList", "healthHealth", "ingestionBatch", "mediaGet", "mediaId", "mediaGetUploadUrl", "mediaPatch", "metricsMetrics", "modelsCreate", "modelsDelete", "modelsGet", "modelsList", "observationsGet", "observationId", "observationsGetMany", "organizationsGetOrganizationMemberships", "organizationsGetOrganizationProjects", "organizationsGetProjectMemberships", "projectId", "organizationsUpdateOrganizationMembership", "organizationsUpdateProjectMembership", "projectsCreate", "projectsCreateApiKey", "projectsDelete", "projectsDeleteApiKey", "apiKeyId", "projectsGet", "projectsGetApiKeys", "projectsUpdate", "promptsCreate", "promptsGet", "promptName", "promptsList", "promptVersionUpdate", "version", "scimCreateUser", "scimDeleteUser", "userId", "scimGetResourceTypes", "scimGetSchemas", "scimGetServiceProviderConfig", "scimGetUser", "scimListUsers", "scoreConfigsCreate", "scoreConfigsGet", "scoreConfigsGetById", "configId", "scoreCreate", "scoreDelete", "scoreId", "scoreV2Get", "scoreV2GetById", "sessionsGet", "sessionId", "sessionsList", "traceDelete", "traceId", "traceDeleteMultiple", "traceGet", "traceList", "Langfuse", "LangfuseCore", "langfuseConfig", "utils", "configLangfuseSDK", "_storageKey", "persistence_name", "publicKey", "_storage", "persistence", "getLibraryVersion", "getLibraryId", "sdkIntegration", "additionalHeaders", "constructAuthorizationHeader", "secretKey", "getPersistedProperty", "_storageCache", "parse", "setPersistedProperty", "url", "options", "getCustomUserAgent", "LangfuseSingleton", "getInstance", "params", "instance", "Langfuse", "isRecord", "randomUUID"] +} diff --git a/plugins/langfuse-observability/payload/dist/hooks/hooks.json b/plugins/langfuse-observability/payload/dist/hooks/hooks.json new file mode 100644 index 0000000..37b12fb --- /dev/null +++ b/plugins/langfuse-observability/payload/dist/hooks/hooks.json @@ -0,0 +1,91 @@ +{ + "description": "Langfuse observability hooks for ZCode.", + "hooks": { + "SessionStart": [ + { + "hooks": [ + { + "type": "process", + "command": "node", + "args": [ + "${ZCODE_PLUGIN_ROOT}/dist/hooks/entry.mjs" + ], + "timeoutMs": 5000, + "statusMessage": "Langfuse: preparing session tracing…" + } + ] + } + ], + "UserPromptSubmit": [ + { + "hooks": [ + { + "type": "process", + "command": "node", + "args": [ + "${ZCODE_PLUGIN_ROOT}/dist/hooks/entry.mjs" + ], + "timeoutMs": 5000 + } + ] + } + ], + "PreToolUse": [ + { + "hooks": [ + { + "type": "process", + "command": "node", + "args": [ + "${ZCODE_PLUGIN_ROOT}/dist/hooks/entry.mjs" + ], + "timeoutMs": 5000 + } + ] + } + ], + "PostToolUse": [ + { + "hooks": [ + { + "type": "process", + "command": "node", + "args": [ + "${ZCODE_PLUGIN_ROOT}/dist/hooks/entry.mjs" + ], + "timeoutMs": 5000 + } + ] + } + ], + "PostToolUseFailure": [ + { + "hooks": [ + { + "type": "process", + "command": "node", + "args": [ + "${ZCODE_PLUGIN_ROOT}/dist/hooks/entry.mjs" + ], + "timeoutMs": 5000 + } + ] + } + ], + "Stop": [ + { + "hooks": [ + { + "type": "process", + "command": "node", + "args": [ + "${ZCODE_PLUGIN_ROOT}/dist/hooks/entry.mjs" + ], + "timeoutMs": 20000, + "statusMessage": "Langfuse: sending trace…" + } + ] + } + ] + } +} diff --git a/plugins/langfuse-observability/payload/dist/plugin.json b/plugins/langfuse-observability/payload/dist/plugin.json new file mode 100644 index 0000000..93829e7 --- /dev/null +++ b/plugins/langfuse-observability/payload/dist/plugin.json @@ -0,0 +1,83 @@ +{ + "name": "langfuse-observability", + "description": "Fail-open Langfuse tracing for ZCode sessions, prompts, tool calls, and assistant responses.", + "description_i18n": { + "en": "Fail-open Langfuse tracing for ZCode sessions, prompts, tool calls, and assistant responses.", + "zh-CN": "为 ZCode 会话、提示词、工具调用和助手回复提供 fail-open 的 Langfuse 追踪。" + }, + "version": "0.1.0", + "author": { + "name": "Community contributor" + }, + "license": "MIT", + "keywords": [ + "langfuse", + "observability", + "tracing", + "telemetry", + "hooks" + ], + "userConfig": { + "langfuse_public_key": { + "title": "Langfuse public key", + "description": "Langfuse project public key. You can also provide LANGFUSE_PUBLIC_KEY in the ZCode process environment.", + "type": "string", + "required": false + }, + "langfuse_secret_key": { + "title": "Langfuse secret key", + "description": "Langfuse project secret key. It is used only by the local hook process and is never written to hook output.", + "type": "string", + "required": false, + "sensitive": true + }, + "langfuse_base_url": { + "title": "Langfuse base URL", + "description": "Langfuse host, for example https://cloud.langfuse.com or a self-hosted URL.", + "type": "string", + "default": "https://cloud.langfuse.com" + }, + "langfuse_user_id": { + "title": "Langfuse user ID", + "description": "Optional stable user identifier attached to traces. Leave empty to omit it.", + "type": "string", + "required": false + }, + "langfuse_environment": { + "title": "Langfuse environment", + "description": "Environment label attached to traces.", + "type": "string", + "default": "development" + }, + "capture_prompts": { + "title": "Capture prompts", + "description": "Include user prompts and assistant responses in Langfuse. Disable for metadata-only tracing.", + "type": "boolean", + "default": true + }, + "capture_tool_inputs": { + "title": "Capture tool inputs", + "description": "Include tool names and inputs in Langfuse spans.", + "type": "boolean", + "default": true + }, + "capture_tool_outputs": { + "title": "Capture tool outputs", + "description": "Include tool results and errors in Langfuse spans.", + "type": "boolean", + "default": true + }, + "max_capture_chars": { + "title": "Maximum captured characters", + "description": "Maximum characters per captured prompt, tool payload, or response. Larger values are truncated.", + "type": "number", + "default": 20000 + }, + "debug": { + "title": "Debug diagnostics", + "description": "Write non-sensitive lifecycle diagnostics to stderr. Disabled by default.", + "type": "boolean", + "default": false + } + } +} From 3b8c622d47dadb900f63e35da8f5b787ea271814 Mon Sep 17 00:00:00 2001 From: erlinerd Date: Sun, 13 Sep 2026 01:24:03 +0800 Subject: [PATCH 04/13] fix(langfuse-observability): address review feedback --- marketplace.json | 2 +- .../.zcode-plugin/plugin.json | 4 +- plugins/langfuse-observability/README.md | 31 +- plugins/langfuse-observability/README_CN.md | 27 +- .../THIRD_PARTY_NOTICES.md | 16 + .../langfuse-observability/hooks/config.mjs | 175 + .../langfuse-observability/hooks/entry.mjs | 33 + .../langfuse-observability/hooks/extract.mjs | 104 + .../langfuse-observability/hooks/hooks.json | 12 +- .../langfuse-observability/hooks/langfuse.mjs | 220 + .../langfuse-observability/hooks/state.mjs | 186 + .../langfuse-observability/hooks/tracker.mjs | 232 + plugins/langfuse-observability/package.json | 12 + .../payload/dist/hooks/entry.mjs | 4744 ----------------- .../payload/dist/hooks/entry.mjs.map | 7 - .../payload/dist/hooks/hooks.json | 91 - .../payload/dist/plugin.json | 83 - 17 files changed, 1028 insertions(+), 4951 deletions(-) create mode 100644 plugins/langfuse-observability/THIRD_PARTY_NOTICES.md create mode 100644 plugins/langfuse-observability/hooks/config.mjs create mode 100644 plugins/langfuse-observability/hooks/entry.mjs create mode 100644 plugins/langfuse-observability/hooks/extract.mjs create mode 100644 plugins/langfuse-observability/hooks/langfuse.mjs create mode 100644 plugins/langfuse-observability/hooks/state.mjs create mode 100644 plugins/langfuse-observability/hooks/tracker.mjs create mode 100644 plugins/langfuse-observability/package.json delete mode 100644 plugins/langfuse-observability/payload/dist/hooks/entry.mjs delete mode 100644 plugins/langfuse-observability/payload/dist/hooks/entry.mjs.map delete mode 100644 plugins/langfuse-observability/payload/dist/hooks/hooks.json delete mode 100644 plugins/langfuse-observability/payload/dist/plugin.json diff --git a/marketplace.json b/marketplace.json index 9836563..66c9807 100644 --- a/marketplace.json +++ b/marketplace.json @@ -537,7 +537,7 @@ "name": "langfuse-observability", "source": "./plugins/langfuse-observability", "description": "Fail-open Langfuse tracing for ZCode sessions, prompts, tool calls, and assistant responses.", - "version": "0.1.0", + "version": "0.1.1", "category": "developer-tools", "tags": [ "langfuse", diff --git a/plugins/langfuse-observability/.zcode-plugin/plugin.json b/plugins/langfuse-observability/.zcode-plugin/plugin.json index 8edc854..74c24b6 100644 --- a/plugins/langfuse-observability/.zcode-plugin/plugin.json +++ b/plugins/langfuse-observability/.zcode-plugin/plugin.json @@ -5,7 +5,7 @@ "en": "Fail-open Langfuse tracing for ZCode sessions, prompts, tool calls, and assistant responses.", "zh-CN": "为 ZCode 会话、提示词、工具调用和助手回复提供 fail-open 的 Langfuse 追踪。" }, - "version": "0.1.0", + "version": "0.1.1", "author": { "name": "erlinerd", "url": "https://github.com/erlinerd" @@ -34,7 +34,7 @@ }, "langfuse_base_url": { "title": "Langfuse base URL", - "description": "Langfuse host, for example https://cloud.langfuse.com or a self-hosted URL.", + "description": "HTTPS Langfuse host, for example https://cloud.langfuse.com or a self-hosted HTTPS URL. Plain HTTP is rejected.", "type": "string", "default": "https://cloud.langfuse.com" }, diff --git a/plugins/langfuse-observability/README.md b/plugins/langfuse-observability/README.md index 484b83a..b103727 100644 --- a/plugins/langfuse-observability/README.md +++ b/plugins/langfuse-observability/README.md @@ -15,7 +15,15 @@ files and never collects hidden chain-of-thought. Missing credentials, malformed Hook input, local-state errors, and Langfuse errors are fail-open and must not block ZCode. Session state is stored under -`ZCODE_PLUGIN_DATA` when available and is cleaned up after a completed `Stop`. +`ZCODE_PLUGIN_DATA` when available, otherwise under the ZCode plugin data +fallback, and is cleaned up after a completed `Stop`. + +Each of the six events starts a `node` process with the current user's +permissions. The process reads one JSON Hook event from stdin and writes one +empty JSON object to stdout; it does not spawn a shell or run user commands. +It reads `ZCODE_CONFIG_PATH`, or `~/.zcode/cli/config.json` when unset, to find +persisted options, writes only bounded hashed JSON session state, and sends +HTTPS requests to the configured Langfuse ingestion endpoint at `Stop`. ## Privacy and configuration @@ -46,19 +54,26 @@ LANGFUSE_MAX_CAPTURE_CHARS LANGFUSE_DEBUG ``` -`LANGFUSE_BASE_URL` defaults to `https://cloud.langfuse.com`. The plugin sends -telemetry only to that configured Langfuse endpoint and writes only its bounded -local session state. Never commit credentials or private Hook payloads. +`LANGFUSE_BASE_URL` defaults to `https://cloud.langfuse.com`. It must be an +HTTPS URL; plaintext HTTP is rejected and replaced with the default HTTPS +endpoint. The plugin sends telemetry only to that configured Langfuse endpoint +and writes only its bounded local session state. Never commit credentials or +private Hook payloads. ## Files and dependencies The process Hook is declared in [`hooks/hooks.json`](./hooks/hooks.json) and -runs the bundled runtime at `payload/dist/hooks/entry.mjs`. The runtime bundles the -official `langfuse` JavaScript SDK. No runtime installation step is required. +runs the reviewable source runtime at `hooks/entry.mjs`. When the optional +`langfuse` dependency is installed during local development, the runtime uses +the official JavaScript SDK. The published official cache is self-contained and +falls back to the same Langfuse HTTPS ingestion API through Node's built-in +`fetch`, so no install step is required. ## Source and license Source repository: -Licensed under MIT. See the source repository for architecture, tests, release -checks, and the complete development history. +Licensed under MIT. See [`THIRD_PARTY_NOTICES.md`](./THIRD_PARTY_NOTICES.md) +for the exact Langfuse SDK, Langfuse Core, and Mustache versions and their MIT +licenses. See the source repository for architecture, tests, release checks, +and the complete development history. diff --git a/plugins/langfuse-observability/README_CN.md b/plugins/langfuse-observability/README_CN.md index 5028c08..7f51a1f 100644 --- a/plugins/langfuse-observability/README_CN.md +++ b/plugins/langfuse-observability/README_CN.md @@ -13,8 +13,14 @@ generation。 ZCode Hook stdin 传入的字段,不读取 transcript 文件,也不采集隐藏完整思维链。 缺少凭据、Hook 输入损坏、本地状态错误和 Langfuse 错误都采用 fail-open, -不能阻塞 ZCode。可用时,会在 `ZCODE_PLUGIN_DATA` 下保存会话状态,并在 -完成 `Stop` 后清理。 +不能阻塞 ZCode。可用时,会在 `ZCODE_PLUGIN_DATA` 下保存会话状态,否则使用 +ZCode 插件数据回退路径,并在完成 `Stop` 后清理。 + +六个事件都会以当前用户权限启动一个 `node` process Hook。Hook 从 stdin +读取一个 JSON 事件,并向 stdout 写入一个空 JSON 对象;不会启动 shell,也不会 +执行用户命令。Hook 会读取 `ZCODE_CONFIG_PATH`;未设置时读取 +`~/.zcode/cli/config.json` 中保存的插件选项,只写入有界、哈希化的 JSON 会话 +状态,并在 `Stop` 时向配置的 Langfuse HTTPS 接口发送请求。 ## 隐私与配置 @@ -43,18 +49,21 @@ LANGFUSE_MAX_CAPTURE_CHARS LANGFUSE_DEBUG ``` -`LANGFUSE_BASE_URL` 默认是 `https://cloud.langfuse.com`。插件只向配置的 -Langfuse endpoint 发送观测数据,只写入有大小限制的本地会话状态。不要提交 -凭据或私有 Hook payload。 +`LANGFUSE_BASE_URL` 默认是 `https://cloud.langfuse.com`,必须使用 HTTPS;明文 +HTTP 会被拒绝并回退到默认 HTTPS 地址。插件只向配置的 Langfuse endpoint 发送 +观测数据,只写入有大小限制的本地会话状态。不要提交凭据或私有 Hook payload。 ## 文件与依赖 -进程 Hook 声明在 [`hooks/hooks.json`](./hooks/hooks.json),运行 -`payload/dist/hooks/entry.mjs` 中的 bundle。运行时已包含官方 `langfuse` -JavaScript SDK,不需要额外安装运行时依赖。 +进程 Hook 声明在 [`hooks/hooks.json`](./hooks/hooks.json),运行可审查的源代码 +`hooks/entry.mjs`。本地开发安装可选的 `langfuse` 依赖时会使用官方 JavaScript +SDK;官方缓存中的插件使用 Node 内置 `fetch` 调用相同的 Langfuse HTTPS ingestion +API,因此不需要运行时安装步骤。 ## 来源与许可证 源码仓库: -本插件采用 MIT 许可证。架构、测试、发布检查和完整开发历史见源码仓库。 +本插件采用 MIT 许可证。确切的 Langfuse SDK、Langfuse Core 和 Mustache 版本及 +MIT 许可证见 [`THIRD_PARTY_NOTICES.md`](./THIRD_PARTY_NOTICES.md)。架构、测试、 +发布检查和完整开发历史见源码仓库。 diff --git a/plugins/langfuse-observability/THIRD_PARTY_NOTICES.md b/plugins/langfuse-observability/THIRD_PARTY_NOTICES.md new file mode 100644 index 0000000..8fb5125 --- /dev/null +++ b/plugins/langfuse-observability/THIRD_PARTY_NOTICES.md @@ -0,0 +1,16 @@ +# Third-party notices + +The source runtime can use the following official npm packages when the plugin +is developed with dependencies installed. The self-contained official cache +fallback uses the same Langfuse public ingestion API through Node's built-in +`fetch`, so the published plugin does not require an install step. + +| Package | Version | License | Source | +| --- | --- | --- | --- | +| `langfuse` | 3.38.20 | MIT | | +| `langfuse-core` | 3.38.20 | MIT | | +| `mustache` | 4.2.0 | MIT | | + +The plugin sends telemetry to a user-configured Langfuse service over HTTPS. +That service is external to this repository and is governed by the service +operator's terms and privacy policy. No credentials are bundled or published. diff --git a/plugins/langfuse-observability/hooks/config.mjs b/plugins/langfuse-observability/hooks/config.mjs new file mode 100644 index 0000000..3bd6899 --- /dev/null +++ b/plugins/langfuse-observability/hooks/config.mjs @@ -0,0 +1,175 @@ +import { readFileSync } from "node:fs"; +import { homedir } from "node:os"; +import { join } from "node:path"; + +const DEFAULT_BASE_URL = "https://cloud.langfuse.com"; +const DEFAULT_RELEASE = "0.1.1"; +const DEFAULT_MAX_CAPTURE_CHARS = 20_000; + +function asText(value) { + if (value === undefined) return undefined; + return typeof value === "string" ? value : String(value); +} + +function firstNonEmpty(...values) { + return values + .map(asText) + .find((value) => value !== undefined && value.trim().length > 0) + ?.trim(); +} + +function userConfig(env, name) { + const normalized = name.toUpperCase(); + return firstNonEmpty( + env[`ZCODE_USER_CONFIG_${normalized}`], + env[`ZCODE_PLUGIN_CONFIG_${normalized}`], + ); +} + +function isStoredOption(value) { + return ( + typeof value === "string" || + typeof value === "number" || + typeof value === "boolean" + ); +} + +function parseStoredOptions(value) { + if (typeof value !== "object" || value === null || Array.isArray(value)) + return {}; + const options = {}; + for (const [key, option] of Object.entries(value)) { + if (isStoredOption(option)) options[key] = option; + } + return options; +} + +function readStoredOptions(env) { + const configPaths = [ + env.ZCODE_CONFIG_PATH, + join(homedir(), ".zcode", "cli", "config.json"), + ].filter(Boolean); + const configuredPluginId = env.ZCODE_PLUGIN_ID; + + if (!configuredPluginId) return {}; + + for (const configPath of configPaths) { + try { + const parsed = JSON.parse(readFileSync(configPath, "utf8")); + const plugins = parsed?.plugins; + const options = plugins?.options; + if ( + !options || + typeof options !== "object" || + Array.isArray(options) || + !Object.prototype.hasOwnProperty.call(options, configuredPluginId) + ) { + continue; + } + return parseStoredOptions(options[configuredPluginId]); + } catch { + // A missing or unreadable user config must not block a ZCode hook. + } + } + return {}; +} + +function option(env, storedOptions, name, environmentName) { + return firstNonEmpty( + userConfig(env, name), + env[environmentName], + storedOptions[name], + ); +} + +function parseBoolean(value, fallback) { + if (!value) return fallback; + if (["1", "true", "yes", "on"].includes(value.toLowerCase())) return true; + if (["0", "false", "no", "off"].includes(value.toLowerCase())) return false; + return fallback; +} + +function parsePositiveInteger(value, fallback) { + if (!value) return fallback; + const parsed = Number.parseInt(value, 10); + if (!Number.isFinite(parsed) || parsed <= 0) return fallback; + return Math.min(parsed, 1_000_000); +} + +function normalizeBaseUrl(value) { + const candidate = value ?? DEFAULT_BASE_URL; + try { + const url = new URL(candidate); + if (url.protocol !== "https:") return DEFAULT_BASE_URL; + return candidate.replace(/\/+$/, ""); + } catch { + return DEFAULT_BASE_URL; + } +} + +export function readConfig(env = process.env) { + const storedOptions = readStoredOptions(env); + const enabled = parseBoolean( + option(env, storedOptions, "enabled", "LANGFUSE_ENABLED"), + true, + ); + const publicKey = + option(env, storedOptions, "langfuse_public_key", "LANGFUSE_PUBLIC_KEY") ?? + null; + const secretKey = + option(env, storedOptions, "langfuse_secret_key", "LANGFUSE_SECRET_KEY") ?? + null; + + return { + publicKey, + secretKey, + baseUrl: normalizeBaseUrl( + option(env, storedOptions, "langfuse_base_url", "LANGFUSE_BASE_URL"), + ), + userId: + option(env, storedOptions, "langfuse_user_id", "LANGFUSE_USER_ID") ?? null, + environment: + option(env, storedOptions, "langfuse_environment", "LANGFUSE_ENVIRONMENT") ?? + "development", + release: + option(env, storedOptions, "langfuse_release", "LANGFUSE_RELEASE") ?? + DEFAULT_RELEASE, + enabled, + capturePrompts: parseBoolean( + option(env, storedOptions, "capture_prompts", "LANGFUSE_CAPTURE_PROMPTS"), + true, + ), + captureToolInputs: parseBoolean( + option( + env, + storedOptions, + "capture_tool_inputs", + "LANGFUSE_CAPTURE_TOOL_INPUTS", + ), + true, + ), + captureToolOutputs: parseBoolean( + option( + env, + storedOptions, + "capture_tool_outputs", + "LANGFUSE_CAPTURE_TOOL_OUTPUTS", + ), + true, + ), + maxCaptureChars: parsePositiveInteger( + option( + env, + storedOptions, + "max_capture_chars", + "LANGFUSE_MAX_CAPTURE_CHARS", + ), + DEFAULT_MAX_CAPTURE_CHARS, + ), + debug: parseBoolean(option(env, storedOptions, "debug", "LANGFUSE_DEBUG"), false), + }; +} + +export function isConfigured(config) { + return config.enabled && Boolean(config.publicKey && config.secretKey); +} diff --git a/plugins/langfuse-observability/hooks/entry.mjs b/plugins/langfuse-observability/hooks/entry.mjs new file mode 100644 index 0000000..3f30a11 --- /dev/null +++ b/plugins/langfuse-observability/hooks/entry.mjs @@ -0,0 +1,33 @@ +import { readConfig, isConfigured } from "./config.mjs"; +import { LangfuseTraceSink } from "./langfuse.mjs"; +import { JsonStateStore } from "./state.mjs"; +import { NoopTraceSink, TurnTracker, parsePayload } from "./tracker.mjs"; + +function diagnostics(enabled, message) { + if (enabled) process.stderr.write(`[langfuse-observability] ${message}\n`); +} + +let raw = ""; +process.stdin.setEncoding("utf8"); +for await (const chunk of process.stdin) raw += chunk; + +const config = readConfig(); +const payload = parsePayload(raw); +const event = payload.hook_event_name || payload.hookEventName || "unknown"; +const session = payload.session_id || payload.sessionId || "?"; + +diagnostics(config.debug, `event=${event} session=${session}`); + +const sink = isConfigured(config) + ? new LangfuseTraceSink(config) + : new NoopTraceSink(); +const tracker = new TurnTracker(new JsonStateStore(), sink, config); + +try { + await tracker.handle(payload); +} catch (error) { + const kind = error instanceof Error ? error.name : "unknown"; + diagnostics(config.debug, `hook failed open (${kind})`); +} + +process.stdout.write("{}\n"); diff --git a/plugins/langfuse-observability/hooks/extract.mjs b/plugins/langfuse-observability/hooks/extract.mjs new file mode 100644 index 0000000..b057f71 --- /dev/null +++ b/plugins/langfuse-observability/hooks/extract.mjs @@ -0,0 +1,104 @@ +function toJsonValue(value) { + if ( + value === null || + typeof value === "string" || + typeof value === "number" || + typeof value === "boolean" + ) { + return value; + } + if (Array.isArray(value)) { + const values = value.map(toJsonValue); + return values.every((item) => item !== undefined) ? values : undefined; + } + if (typeof value === "object") { + const result = {}; + for (const [key, item] of Object.entries(value)) { + const parsed = toJsonValue(item); + if (parsed === undefined) return undefined; + result[key] = parsed; + } + return result; + } + return undefined; +} + +function valueAt(payload, ...keys) { + for (const key of keys) { + const value = toJsonValue(payload[key]); + if (value !== undefined && value !== null) return value; + } + return undefined; +} + +function asString(value) { + if (typeof value === "string") return value; + if (typeof value === "number" || typeof value === "boolean") return String(value); + return null; +} + +export function stringifyValue(value) { + if (typeof value === "string") return value; + if (value === undefined) return ""; + const json = JSON.stringify(value); + return json ?? String(value); +} + +export function eventName(payload) { + return asString( + valueAt(payload, "hook_event_name", "hookEventName", "event", "event_name"), + ); +} + +export function sessionId(payload) { + const value = asString(valueAt(payload, "session_id", "sessionId")); + return value?.trim() || null; +} + +export function prompt(payload) { + const value = valueAt(payload, "prompt", "user_prompt", "userPrompt", "message"); + return value === undefined ? null : stringifyValue(value); +} + +export function assistantMessage(payload) { + const value = valueAt(payload, "last_assistant_message", "lastAssistantMessage"); + return value === undefined ? null : stringifyValue(value); +} + +export function toolId(payload) { + const value = asString( + valueAt(payload, "tool_use_id", "toolUseId", "tool_id", "toolId", "id"), + ); + return value?.trim() || null; +} + +export function toolName(payload) { + return ( + asString(valueAt(payload, "tool_name", "toolName", "name", "tool"))?.trim() ?? + "unknown" + ); +} + +export function toolInput(payload) { + return valueAt(payload, "tool_input", "toolInput", "input", "arguments") ?? null; +} + +export function toolOutput(payload) { + return ( + valueAt( + payload, + "tool_output", + "toolOutput", + "output", + "result", + "tool_response", + ) ?? null + ); +} + +export function toolError(payload) { + return stringifyValue( + valueAt(payload, "error", "error_message", "errorMessage", "message") ?? + "Tool failed", + ); +} diff --git a/plugins/langfuse-observability/hooks/hooks.json b/plugins/langfuse-observability/hooks/hooks.json index 1b179bb..ab20687 100644 --- a/plugins/langfuse-observability/hooks/hooks.json +++ b/plugins/langfuse-observability/hooks/hooks.json @@ -8,7 +8,7 @@ "type": "process", "command": "node", "args": [ - "${ZCODE_PLUGIN_ROOT}/payload/dist/hooks/entry.mjs" + "${ZCODE_PLUGIN_ROOT}/hooks/entry.mjs" ], "timeoutMs": 5000, "statusMessage": "Langfuse: preparing session tracing…" @@ -23,7 +23,7 @@ "type": "process", "command": "node", "args": [ - "${ZCODE_PLUGIN_ROOT}/payload/dist/hooks/entry.mjs" + "${ZCODE_PLUGIN_ROOT}/hooks/entry.mjs" ], "timeoutMs": 5000 } @@ -37,7 +37,7 @@ "type": "process", "command": "node", "args": [ - "${ZCODE_PLUGIN_ROOT}/payload/dist/hooks/entry.mjs" + "${ZCODE_PLUGIN_ROOT}/hooks/entry.mjs" ], "timeoutMs": 5000 } @@ -51,7 +51,7 @@ "type": "process", "command": "node", "args": [ - "${ZCODE_PLUGIN_ROOT}/payload/dist/hooks/entry.mjs" + "${ZCODE_PLUGIN_ROOT}/hooks/entry.mjs" ], "timeoutMs": 5000 } @@ -65,7 +65,7 @@ "type": "process", "command": "node", "args": [ - "${ZCODE_PLUGIN_ROOT}/payload/dist/hooks/entry.mjs" + "${ZCODE_PLUGIN_ROOT}/hooks/entry.mjs" ], "timeoutMs": 5000 } @@ -79,7 +79,7 @@ "type": "process", "command": "node", "args": [ - "${ZCODE_PLUGIN_ROOT}/payload/dist/hooks/entry.mjs" + "${ZCODE_PLUGIN_ROOT}/hooks/entry.mjs" ], "timeoutMs": 20000, "statusMessage": "Langfuse: sending trace…" diff --git a/plugins/langfuse-observability/hooks/langfuse.mjs b/plugins/langfuse-observability/hooks/langfuse.mjs new file mode 100644 index 0000000..0a43991 --- /dev/null +++ b/plugins/langfuse-observability/hooks/langfuse.mjs @@ -0,0 +1,220 @@ +import { randomUUID } from "node:crypto"; + +const SDK_INTEGRATION = "zcode-langfuse-observability"; +const TRACE_NAME = "ZCode Turn"; + +function isMissingSdk(error) { + return ( + error?.code === "ERR_MODULE_NOT_FOUND" || + (error instanceof Error && error.message.includes("Cannot find package 'langfuse'")) + ); +} + +async function publishWithOfficialSdk(config, turn) { + let Langfuse; + try { + ({ Langfuse } = await import("langfuse")); + } catch (error) { + if (isMissingSdk(error)) return false; + throw error; + } + + const client = new Langfuse({ + publicKey: config.publicKey, + secretKey: config.secretKey, + baseUrl: config.baseUrl, + release: config.release, + environment: config.environment, + sdkIntegration: SDK_INTEGRATION, + flushAt: 1, + fetchRetryCount: 1, + requestTimeout: 8_000, + }); + + try { + const trace = client.trace({ + name: TRACE_NAME, + sessionId: turn.sessionId, + input: turn.prompt, + metadata: { + source: "zcode", + turnId: turn.turnId, + toolCount: turn.tools.length, + }, + tags: ["zcode", "zcode-hook"], + ...(config.userId ? { userId: config.userId } : {}), + }); + + for (const tool of turn.tools) { + const span = trace.span({ + name: `tool.${tool.name}`, + input: tool.input, + metadata: { + toolId: tool.id, + startedAt: tool.startedAt, + endedAt: tool.endedAt, + }, + }); + if (tool.error) { + span.end({ + output: { error: tool.error }, + level: "ERROR", + statusMessage: tool.error, + }); + } else { + span.end({ output: tool.output }); + } + } + + const generation = trace.generation({ + name: "zcode.assistant", + input: turn.prompt, + metadata: { turnId: turn.turnId }, + }); + generation.end({ output: turn.assistantMessage }); + trace.update({ + output: turn.assistantMessage, + metadata: { + source: "zcode", + turnId: turn.turnId, + toolCount: turn.tools.length, + }, + }); + await client.flushAsync(); + } finally { + await client.shutdownAsync(); + } + return true; +} + +function queued(type, body, timestamp) { + return { + id: randomUUID(), + type, + timestamp, + body, + }; +} + +async function publishWithHttp(config, turn) { + const timestamp = new Date().toISOString(); + const traceId = randomUUID(); + const metadata = { + source: "zcode", + turnId: turn.turnId, + toolCount: turn.tools.length, + }; + const batch = [ + queued( + "trace-create", + { + id: traceId, + name: TRACE_NAME, + sessionId: turn.sessionId, + timestamp, + release: config.release, + environment: config.environment, + input: turn.prompt, + metadata, + tags: ["zcode", "zcode-hook"], + ...(config.userId ? { userId: config.userId } : {}), + }, + timestamp, + ), + ]; + + for (const tool of turn.tools) { + const startTime = tool.startedAt || timestamp; + const endTime = tool.endedAt || timestamp; + batch.push( + queued( + "span-create", + { + id: randomUUID(), + traceId, + name: `tool.${tool.name}`, + startTime, + endTime, + input: tool.input, + output: tool.error ? { error: tool.error } : tool.output, + metadata: { + toolId: tool.id, + startedAt: tool.startedAt, + endedAt: tool.endedAt, + }, + ...(tool.error + ? { level: "ERROR", statusMessage: tool.error } + : {}), + }, + timestamp, + ), + ); + } + + batch.push( + queued( + "generation-create", + { + id: randomUUID(), + traceId, + name: "zcode.assistant", + startTime: turn.startedAt || timestamp, + endTime: turn.endedAt || timestamp, + input: turn.prompt, + output: turn.assistantMessage, + environment: config.environment, + metadata: { turnId: turn.turnId }, + }, + timestamp, + ), + queued( + "trace-update", + { + id: traceId, + output: turn.assistantMessage, + metadata, + }, + timestamp, + ), + ); + + const response = await fetch(`${config.baseUrl}/api/public/ingestion`, { + method: "POST", + headers: { + Authorization: `Basic ${Buffer.from( + `${config.publicKey}:${config.secretKey}`, + ).toString("base64")}`, + "Content-Type": "application/json", + "X-Langfuse-Sdk-Name": "langfuse-js-compatible", + "X-Langfuse-Sdk-Integration": SDK_INTEGRATION, + "X-Langfuse-Public-Key": config.publicKey, + }, + body: JSON.stringify({ + batch, + metadata: { + batch_size: batch.length, + sdk_integration: SDK_INTEGRATION, + sdk_name: "langfuse-js-compatible", + public_key: config.publicKey, + }, + }), + signal: AbortSignal.timeout(8_000), + }); + + if (!response.ok) { + throw new Error(`Langfuse ingestion returned HTTP ${response.status}`); + } +} + +export class LangfuseTraceSink { + constructor(config) { + this.config = config; + } + + async publishTurn(turn) { + if (!this.config.publicKey || !this.config.secretKey) return; + if (!(await publishWithOfficialSdk(this.config, turn))) { + await publishWithHttp(this.config, turn); + } + } +} diff --git a/plugins/langfuse-observability/hooks/state.mjs b/plugins/langfuse-observability/hooks/state.mjs new file mode 100644 index 0000000..860236c --- /dev/null +++ b/plugins/langfuse-observability/hooks/state.mjs @@ -0,0 +1,186 @@ +import { createHash, randomUUID } from "node:crypto"; +import { + mkdir, + open, + readFile, + rename, + rm, + stat, + writeFile, +} from "node:fs/promises"; +import { homedir, tmpdir } from "node:os"; +import { join } from "node:path"; + +const LOCK_WAIT_MS = 25; +const LOCK_ATTEMPTS = 160; +const STALE_LOCK_MS = 60_000; + +function isRecord(value) { + return typeof value === "object" && value !== null && !Array.isArray(value); +} + +function isJsonValue(value) { + if ( + value === null || + typeof value === "string" || + typeof value === "number" || + typeof value === "boolean" + ) { + return true; + } + if (Array.isArray(value)) return value.every(isJsonValue); + return isRecord(value) && Object.values(value).every(isJsonValue); +} + +function parseToolCall(value) { + if (!isRecord(value)) return null; + if ( + typeof value.id !== "string" || + typeof value.name !== "string" || + !isJsonValue(value.input) || + (value.output !== null && !isJsonValue(value.output)) || + (value.error !== null && typeof value.error !== "string") || + typeof value.startedAt !== "string" || + (value.endedAt !== null && typeof value.endedAt !== "string") + ) { + return null; + } + return { + id: value.id, + name: value.name, + input: value.input, + output: value.output, + error: value.error, + startedAt: value.startedAt, + endedAt: value.endedAt, + }; +} + +function parseState(value) { + if (!isRecord(value)) return null; + if ( + value.version !== 1 || + typeof value.sessionId !== "string" || + typeof value.startedAt !== "string" || + typeof value.updatedAt !== "string" + ) { + return null; + } + let currentTurn = null; + if (value.currentTurn !== null) { + const turn = value.currentTurn; + if ( + !isRecord(turn) || + typeof turn.id !== "string" || + (turn.prompt !== null && typeof turn.prompt !== "string") || + typeof turn.startedAt !== "string" || + !Array.isArray(turn.tools) + ) { + return null; + } + const tools = turn.tools.map(parseToolCall); + if (tools.some((tool) => tool === null)) return null; + currentTurn = { + id: turn.id, + prompt: turn.prompt, + startedAt: turn.startedAt, + tools, + }; + } + return { + version: 1, + sessionId: value.sessionId, + startedAt: value.startedAt, + updatedAt: value.updatedAt, + currentTurn, + }; +} + +function sessionKey(sessionId) { + return createHash("sha256").update(sessionId).digest("hex"); +} + +function defaultDataDir() { + return join( + homedir() || tmpdir(), + ".zcode", + "cli", + "plugins", + "data", + "langfuse-observability", + ); +} + +export class JsonStateStore { + constructor(dataDir = process.env.ZCODE_PLUGIN_DATA || defaultDataDir()) { + this.dataDir = dataDir; + } + + async load(sessionId) { + try { + const contents = await readFile(this.statePath(sessionId), "utf8"); + return parseState(JSON.parse(contents)); + } catch { + return null; + } + } + + async save(state) { + await mkdir(this.dataDir, { recursive: true, mode: 0o700 }); + const filePath = this.statePath(state.sessionId); + const temporaryPath = `${filePath}.${randomUUID()}.tmp`; + await writeFile(temporaryPath, JSON.stringify(state), { + encoding: "utf8", + mode: 0o600, + }); + await rename(temporaryPath, filePath); + } + + async clear(sessionId) { + await rm(this.statePath(sessionId), { force: true }); + } + + async withSessionLock(sessionId, task) { + await mkdir(this.dataDir, { recursive: true, mode: 0o700 }); + const lockPath = this.lockPath(sessionId); + let acquired = false; + + for (let attempt = 0; attempt < LOCK_ATTEMPTS; attempt += 1) { + try { + const handle = await open(lockPath, "wx", 0o600); + await handle.close(); + acquired = true; + break; + } catch { + await this.removeStaleLock(lockPath); + await new Promise((resolve) => setTimeout(resolve, LOCK_WAIT_MS)); + } + } + + if (!acquired) throw new Error("Timed out acquiring the Langfuse state lock"); + + try { + return await task(); + } finally { + await rm(lockPath, { force: true }); + } + } + + statePath(sessionId) { + return join(this.dataDir, `${sessionKey(sessionId)}.json`); + } + + lockPath(sessionId) { + return join(this.dataDir, `${sessionKey(sessionId)}.lock`); + } + + async removeStaleLock(lockPath) { + try { + const details = await stat(lockPath); + if (Date.now() - details.mtimeMs > STALE_LOCK_MS) + await rm(lockPath, { force: true }); + } catch { + // The lock may disappear between open(), stat(), and rm(). + } + } +} diff --git a/plugins/langfuse-observability/hooks/tracker.mjs b/plugins/langfuse-observability/hooks/tracker.mjs new file mode 100644 index 0000000..176d74d --- /dev/null +++ b/plugins/langfuse-observability/hooks/tracker.mjs @@ -0,0 +1,232 @@ +import { randomUUID } from "node:crypto"; +import { + assistantMessage, + eventName, + prompt, + sessionId, + toolError, + toolId, + toolInput, + toolName, + toolOutput, +} from "./extract.mjs"; + +function createSession(session, now) { + return { + version: 1, + sessionId: session, + startedAt: now, + updatedAt: now, + currentTurn: null, + }; +} + +function createTurn(id, now, userPrompt) { + return { + id, + prompt: userPrompt, + startedAt: now, + tools: [], + }; +} + +const TRUNCATION_MARKER = "… [truncated]"; + +function truncate(value, maxChars) { + if (value.length <= maxChars) return value; + if (maxChars <= TRUNCATION_MARKER.length) return value.slice(0, maxChars); + return `${value.slice(0, maxChars - TRUNCATION_MARKER.length)}${TRUNCATION_MARKER}`; +} + +function boundedValue(value, maxChars) { + const serialized = JSON.stringify(value); + if (serialized.length <= maxChars) return value; + return truncate(serialized, maxChars); +} + +function sanitizeTurn(turn, config) { + return { + ...turn, + prompt: + config.capturePrompts && turn.prompt !== null + ? truncate(turn.prompt, config.maxCaptureChars) + : null, + assistantMessage: + config.capturePrompts && turn.assistantMessage !== null + ? truncate(turn.assistantMessage, config.maxCaptureChars) + : null, + tools: turn.tools.map((tool) => ({ + ...tool, + name: truncate(tool.name, 256), + input: config.captureToolInputs + ? boundedValue(tool.input, config.maxCaptureChars) + : null, + output: + config.captureToolOutputs && tool.output !== null + ? boundedValue(tool.output, config.maxCaptureChars) + : null, + error: + config.captureToolOutputs && tool.error !== null + ? truncate(tool.error, config.maxCaptureChars) + : null, + })), + }; +} + +function findTool(turn, id, name) { + if (id) { + const byId = turn.tools.find((tool) => tool.id === id); + if (byId) return byId; + } + return ( + [...turn.tools].reverse().find((tool) => tool.endedAt === null && tool.name === name) ?? + [...turn.tools].reverse().find((tool) => tool.endedAt === null) ?? + null + ); +} + +export class TurnTracker { + constructor( + stateStore, + traceSink, + config, + clock = () => new Date(), + idGenerator = randomUUID, + ) { + this.stateStore = stateStore; + this.traceSink = traceSink; + this.config = config; + this.clock = clock; + this.idGenerator = idGenerator; + } + + async handle(payload) { + const name = eventName(payload); + const session = sessionId(payload); + if (!name || !session) return; + + let completed = null; + await this.stateStore.withSessionLock(session, async () => { + const now = this.clock().toISOString(); + const existing = + (await this.stateStore.load(session)) ?? createSession(session, now); + + switch (name) { + case "SessionStart": + existing.updatedAt = now; + await this.stateStore.save(existing); + return; + case "UserPromptSubmit": { + const userPrompt = prompt(payload); + existing.currentTurn = createTurn( + this.idGenerator(), + now, + this.config.capturePrompts && userPrompt !== null + ? truncate(userPrompt, this.config.maxCaptureChars) + : null, + ); + existing.updatedAt = now; + await this.stateStore.save(existing); + return; + } + case "PreToolUse": + this.recordToolStart(existing, payload, now); + await this.stateStore.save(existing); + return; + case "PostToolUse": + this.recordToolOutput(existing, payload, now, false); + await this.stateStore.save(existing); + return; + case "PostToolUseFailure": + this.recordToolOutput(existing, payload, now, true); + await this.stateStore.save(existing); + return; + case "Stop": + completed = sanitizeTurn( + { + sessionId: session, + turnId: existing.currentTurn?.id ?? this.idGenerator(), + prompt: existing.currentTurn?.prompt ?? null, + assistantMessage: assistantMessage(payload), + startedAt: existing.currentTurn?.startedAt ?? now, + endedAt: now, + tools: existing.currentTurn?.tools ?? [], + }, + this.config, + ); + await this.stateStore.clear(session); + return; + default: + return; + } + }); + + if (completed) await this.traceSink.publishTurn(completed); + } + + recordToolStart(state, payload, now) { + const turn = + state.currentTurn ?? createTurn(this.idGenerator(), now, null); + state.currentTurn = turn; + turn.tools.push({ + id: toolId(payload) ?? this.idGenerator(), + name: toolName(payload), + input: this.config.captureToolInputs + ? boundedValue(toolInput(payload), this.config.maxCaptureChars) + : null, + output: null, + error: null, + startedAt: now, + endedAt: null, + }); + state.updatedAt = now; + } + + recordToolOutput(state, payload, now, failed) { + const turn = + state.currentTurn ?? createTurn(this.idGenerator(), now, null); + state.currentTurn = turn; + const name = toolName(payload); + const tool = findTool(turn, toolId(payload), name); + const output = + !failed && this.config.captureToolOutputs + ? boundedValue(toolOutput(payload), this.config.maxCaptureChars) + : null; + const error = + failed && this.config.captureToolOutputs + ? truncate(toolError(payload), this.config.maxCaptureChars) + : null; + if (tool) { + tool.output = output; + tool.error = error; + tool.endedAt = now; + } else { + turn.tools.push({ + id: toolId(payload) ?? this.idGenerator(), + name, + input: null, + output, + error, + startedAt: now, + endedAt: now, + }); + } + state.updatedAt = now; + } +} + +export class NoopTraceSink { + async publishTurn(_turn) {} +} + +export function parsePayload(raw) { + if (!raw.trim()) return {}; + try { + const parsed = JSON.parse(raw); + return parsed && typeof parsed === "object" && !Array.isArray(parsed) + ? parsed + : {}; + } catch { + return {}; + } +} diff --git a/plugins/langfuse-observability/package.json b/plugins/langfuse-observability/package.json new file mode 100644 index 0000000..28bb4da --- /dev/null +++ b/plugins/langfuse-observability/package.json @@ -0,0 +1,12 @@ +{ + "name": "zcode-langfuse-observability", + "version": "0.1.1", + "private": true, + "type": "module", + "engines": { + "node": ">=20" + }, + "dependencies": { + "langfuse": "3.38.20" + } +} diff --git a/plugins/langfuse-observability/payload/dist/hooks/entry.mjs b/plugins/langfuse-observability/payload/dist/hooks/entry.mjs deleted file mode 100644 index 0a495e3..0000000 --- a/plugins/langfuse-observability/payload/dist/hooks/entry.mjs +++ /dev/null @@ -1,4744 +0,0 @@ -// Generated by npm run build. Edit src/, not dist/. - -// src/hooks/entry.ts -import { randomUUID as randomUUID2 } from "node:crypto"; - -// src/application/config.ts -import { readFileSync } from "node:fs"; -import { homedir } from "node:os"; -import { join } from "node:path"; -var DEFAULT_BASE_URL = "https://cloud.langfuse.com"; -var DEFAULT_RELEASE = "0.1.0"; -var DEFAULT_MAX_CAPTURE_CHARS = 2e4; -function asText(value) { - if (value === void 0) return void 0; - return typeof value === "string" ? value : String(value); -} -function firstNonEmpty(...values) { - return values.map(asText).find((value) => value !== void 0 && value.trim().length > 0)?.trim(); -} -function userConfig(env, name) { - const normalized = name.toUpperCase(); - return firstNonEmpty( - env[`ZCODE_USER_CONFIG_${normalized}`], - env[`ZCODE_PLUGIN_CONFIG_${normalized}`] - ); -} -function isStoredOption(value) { - return typeof value === "string" || typeof value === "number" || typeof value === "boolean"; -} -function parseStoredOptions(value) { - if (typeof value !== "object" || value === null || Array.isArray(value)) - return {}; - const options = {}; - for (const [key, option2] of Object.entries(value)) { - if (isStoredOption(option2)) options[key] = option2; - } - return options; -} -function readStoredOptions(env) { - const configPaths = [ - env.ZCODE_CONFIG_PATH, - join(homedir(), ".zcode", "cli", "config.json") - ].filter((path) => Boolean(path)); - const configuredPluginId = env.ZCODE_PLUGIN_ID; - for (const configPath of configPaths) { - try { - const parsed = JSON.parse(readFileSync(configPath, "utf8")); - if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) - continue; - const plugins = parsed.plugins; - if (typeof plugins !== "object" || plugins === null || Array.isArray(plugins)) - continue; - const options = plugins.options; - if (typeof options !== "object" || options === null || Array.isArray(options)) - continue; - const optionEntries = options; - const pluginId = configuredPluginId && optionEntries[configuredPluginId] ? configuredPluginId : Object.keys(optionEntries).find( - (key) => key.startsWith("langfuse-observability@") - ); - if (pluginId) return parseStoredOptions(optionEntries[pluginId]); - } catch { - } - } - return {}; -} -function option(env, storedOptions, name, environmentName) { - return firstNonEmpty( - userConfig(env, name), - storedOptions[name], - env[environmentName] - ); -} -function parseBoolean(value, fallback) { - if (!value) return fallback; - if (["1", "true", "yes", "on"].includes(value.toLowerCase())) return true; - if (["0", "false", "no", "off"].includes(value.toLowerCase())) return false; - return fallback; -} -function parsePositiveInteger(value, fallback) { - if (!value) return fallback; - const parsed = Number.parseInt(value, 10); - if (!Number.isFinite(parsed) || parsed <= 0) return fallback; - return Math.min(parsed, 1e6); -} -function normalizeBaseUrl(value) { - const candidate = value ?? DEFAULT_BASE_URL; - try { - const url = new URL(candidate); - if (url.protocol !== "http:" && url.protocol !== "https:") - return DEFAULT_BASE_URL; - return candidate.replace(/\/+$/, ""); - } catch { - return DEFAULT_BASE_URL; - } -} -function readConfig(env = process.env, storedOptions = readStoredOptions(env)) { - const enabled = parseBoolean( - option(env, storedOptions, "enabled", "LANGFUSE_ENABLED"), - true - ); - const publicKey = option(env, storedOptions, "langfuse_public_key", "LANGFUSE_PUBLIC_KEY") ?? null; - const secretKey = option(env, storedOptions, "langfuse_secret_key", "LANGFUSE_SECRET_KEY") ?? null; - return { - publicKey, - secretKey, - baseUrl: normalizeBaseUrl( - option(env, storedOptions, "langfuse_base_url", "LANGFUSE_BASE_URL") - ), - userId: option(env, storedOptions, "langfuse_user_id", "LANGFUSE_USER_ID") ?? null, - environment: option( - env, - storedOptions, - "langfuse_environment", - "LANGFUSE_ENVIRONMENT" - ) ?? "development", - release: option(env, storedOptions, "langfuse_release", "LANGFUSE_RELEASE") ?? DEFAULT_RELEASE, - enabled, - capturePrompts: parseBoolean( - option(env, storedOptions, "capture_prompts", "LANGFUSE_CAPTURE_PROMPTS"), - true - ), - captureToolInputs: parseBoolean( - option( - env, - storedOptions, - "capture_tool_inputs", - "LANGFUSE_CAPTURE_TOOL_INPUTS" - ), - true - ), - captureToolOutputs: parseBoolean( - option( - env, - storedOptions, - "capture_tool_outputs", - "LANGFUSE_CAPTURE_TOOL_OUTPUTS" - ), - true - ), - maxCaptureChars: parsePositiveInteger( - option( - env, - storedOptions, - "max_capture_chars", - "LANGFUSE_MAX_CAPTURE_CHARS" - ), - DEFAULT_MAX_CAPTURE_CHARS - ), - debug: parseBoolean( - option(env, storedOptions, "debug", "LANGFUSE_DEBUG"), - false - ) - }; -} -function isConfigured(config) { - return config.enabled && Boolean(config.publicKey && config.secretKey); -} - -// src/domain/extract.ts -function toJsonValue(value) { - if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") { - return value; - } - if (Array.isArray(value)) { - const values = value.map(toJsonValue); - return values.every((item) => item !== void 0) ? values : void 0; - } - if (typeof value === "object") { - const entries = Object.entries(value); - const result = {}; - for (const [key, item] of entries) { - const parsed = toJsonValue(item); - if (parsed === void 0) return void 0; - result[key] = parsed; - } - return result; - } - return void 0; -} -function valueAt(payload, ...keys) { - for (const key of keys) { - const value = toJsonValue(payload[key]); - if (value !== void 0 && value !== null) return value; - } - return void 0; -} -function asString(value) { - if (typeof value === "string") return value; - if (typeof value === "number" || typeof value === "boolean") - return String(value); - return null; -} -function stringifyValue(value) { - if (typeof value === "string") return value; - if (value === void 0) return ""; - const json = JSON.stringify(value); - return json ?? String(value); -} -function eventName(payload) { - return asString( - valueAt(payload, "hook_event_name", "hookEventName", "event", "event_name") - ); -} -function sessionId(payload) { - const value = asString(valueAt(payload, "session_id", "sessionId")); - return value?.trim() || null; -} -function prompt(payload) { - const value = valueAt( - payload, - "prompt", - "user_prompt", - "userPrompt", - "message" - ); - return value === void 0 ? null : stringifyValue(value); -} -function assistantMessage(payload) { - const value = valueAt( - payload, - "last_assistant_message", - "lastAssistantMessage" - ); - return value === void 0 ? null : stringifyValue(value); -} -function toolId(payload) { - const value = asString( - valueAt(payload, "tool_use_id", "toolUseId", "tool_id", "toolId", "id") - ); - return value?.trim() || null; -} -function toolName(payload) { - return asString( - valueAt(payload, "tool_name", "toolName", "name", "tool") - )?.trim() ?? "unknown"; -} -function toolInput(payload) { - return valueAt(payload, "tool_input", "toolInput", "input", "arguments") ?? null; -} -function toolOutput(payload) { - return valueAt( - payload, - "tool_output", - "toolOutput", - "output", - "result", - "tool_response" - ) ?? null; -} -function toolError(payload) { - return stringifyValue( - valueAt(payload, "error", "error_message", "errorMessage", "message") ?? "Tool failed" - ); -} - -// src/application/turn-tracker.ts -function createSession(session, now) { - return { - version: 1, - sessionId: session, - startedAt: now, - updatedAt: now, - currentTurn: null - }; -} -function createTurn(id, now, userPrompt) { - return { - id, - prompt: userPrompt, - startedAt: now, - tools: [] - }; -} -function truncate(value, maxChars) { - if (value.length <= maxChars) return value; - return `${value.slice(0, Math.max(0, maxChars - 18))}\u2026 [truncated]`; -} -function boundedValue(value, maxChars) { - const serialized = JSON.stringify(value); - if (serialized.length <= maxChars) return value; - return truncate(serialized, maxChars); -} -function sanitizeTurn(turn, config) { - return { - ...turn, - prompt: config.capturePrompts && turn.prompt !== null ? truncate(turn.prompt, config.maxCaptureChars) : null, - assistantMessage: config.capturePrompts && turn.assistantMessage !== null ? truncate(turn.assistantMessage, config.maxCaptureChars) : null, - tools: turn.tools.map((tool) => ({ - ...tool, - name: truncate(tool.name, 256), - input: config.captureToolInputs ? boundedValue(tool.input, config.maxCaptureChars) : null, - output: config.captureToolOutputs && tool.output !== null ? boundedValue(tool.output, config.maxCaptureChars) : null, - error: config.captureToolOutputs && tool.error !== null ? truncate(tool.error, config.maxCaptureChars) : null - })) - }; -} -function findTool(turn, id, name) { - if (id) { - const byId = turn.tools.find((tool) => tool.id === id); - if (byId) return byId; - } - return [...turn.tools].reverse().find((tool) => tool.endedAt === null && tool.name === name) ?? [...turn.tools].reverse().find((tool) => tool.endedAt === null) ?? null; -} -var TurnTracker = class { - constructor(stateStore, traceSink, config, clock, idGenerator) { - this.stateStore = stateStore; - this.traceSink = traceSink; - this.config = config; - this.clock = clock; - this.idGenerator = idGenerator; - } - async handle(payload) { - const name = eventName(payload); - const session = sessionId(payload); - if (!name || !session) return; - let completed = null; - await this.stateStore.withSessionLock(session, async () => { - const now = this.clock.now().toISOString(); - const existing = await this.stateStore.load(session) ?? createSession(session, now); - switch (name) { - case "SessionStart": - existing.updatedAt = now; - await this.stateStore.save(existing); - return; - case "UserPromptSubmit": { - const userPrompt = prompt(payload); - existing.currentTurn = createTurn( - this.idGenerator.next(), - now, - this.config.capturePrompts && userPrompt !== null ? truncate(userPrompt, this.config.maxCaptureChars) : null - ); - existing.updatedAt = now; - await this.stateStore.save(existing); - return; - } - case "PreToolUse": - this.recordToolStart(existing, payload, now); - await this.stateStore.save(existing); - return; - case "PostToolUse": - this.recordToolOutput(existing, payload, now, false); - await this.stateStore.save(existing); - return; - case "PostToolUseFailure": - this.recordToolOutput(existing, payload, now, true); - await this.stateStore.save(existing); - return; - case "Stop": - completed = sanitizeTurn( - { - sessionId: session, - turnId: existing.currentTurn?.id ?? this.idGenerator.next(), - prompt: existing.currentTurn?.prompt ?? null, - assistantMessage: assistantMessage(payload), - startedAt: existing.currentTurn?.startedAt ?? now, - endedAt: now, - tools: existing.currentTurn?.tools ?? [] - }, - this.config - ); - await this.stateStore.clear(session); - return; - default: - return; - } - }); - if (completed) await this.traceSink.publishTurn(completed); - } - recordToolStart(state, payload, now) { - const turn = state.currentTurn ?? createTurn(this.idGenerator.next(), now, null); - state.currentTurn = turn; - turn.tools.push({ - id: toolId(payload) ?? this.idGenerator.next(), - name: toolName(payload), - input: this.config.captureToolInputs ? boundedValue(toolInput(payload), this.config.maxCaptureChars) : null, - output: null, - error: null, - startedAt: now, - endedAt: null - }); - state.updatedAt = now; - } - recordToolOutput(state, payload, now, failed) { - const turn = state.currentTurn ?? createTurn(this.idGenerator.next(), now, null); - state.currentTurn = turn; - const name = toolName(payload); - const tool = findTool(turn, toolId(payload), name); - const output = !failed && this.config.captureToolOutputs ? boundedValue(toolOutput(payload), this.config.maxCaptureChars) : null; - const error = failed && this.config.captureToolOutputs ? truncate(toolError(payload), this.config.maxCaptureChars) : null; - if (tool) { - tool.output = output; - tool.error = error; - tool.endedAt = now; - } else { - turn.tools.push({ - id: toolId(payload) ?? this.idGenerator.next(), - name, - input: null, - output, - error, - startedAt: now, - endedAt: now - }); - } - state.updatedAt = now; - } -}; -var NoopTraceSink = class { - async publishTurn(_turn) { - } -}; - -// src/adapters/json-state-store.ts -import { createHash, randomUUID } from "node:crypto"; -import { - mkdir, - open, - readFile, - rename, - rm, - stat, - writeFile -} from "node:fs/promises"; -import { homedir as homedir2, tmpdir } from "node:os"; -import { join as join2 } from "node:path"; -var LOCK_WAIT_MS = 25; -var LOCK_ATTEMPTS = 160; -var STALE_LOCK_MS = 6e4; -function isRecord(value) { - return typeof value === "object" && value !== null && !Array.isArray(value); -} -function isJsonValue(value) { - if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") { - return true; - } - if (Array.isArray(value)) return value.every(isJsonValue); - return isRecord(value) && Object.values(value).every(isJsonValue); -} -function parseToolCall(value) { - if (!isRecord(value)) return null; - if (typeof value.id !== "string" || typeof value.name !== "string" || !isJsonValue(value.input) || value.output !== null && !isJsonValue(value.output) || value.error !== null && typeof value.error !== "string" || typeof value.startedAt !== "string" || value.endedAt !== null && typeof value.endedAt !== "string") { - return null; - } - return { - id: value.id, - name: value.name, - input: value.input, - output: value.output, - error: value.error, - startedAt: value.startedAt, - endedAt: value.endedAt - }; -} -function parseTurn(value) { - if (!isRecord(value)) return null; - if (typeof value.id !== "string" || value.prompt !== null && typeof value.prompt !== "string" || typeof value.startedAt !== "string" || !Array.isArray(value.tools)) { - return null; - } - const tools = value.tools.map(parseToolCall); - if (tools.some((tool) => tool === null)) return null; - return { - id: value.id, - prompt: value.prompt, - startedAt: value.startedAt, - tools: tools.filter((tool) => tool !== null) - }; -} -function parseState(value) { - if (!isRecord(value)) return null; - if (value.version !== 1 || typeof value.sessionId !== "string" || typeof value.startedAt !== "string" || typeof value.updatedAt !== "string") { - return null; - } - const currentTurn = value.currentTurn === null ? null : parseTurn(value.currentTurn); - if (value.currentTurn !== null && currentTurn === null) return null; - return { - version: 1, - sessionId: value.sessionId, - startedAt: value.startedAt, - updatedAt: value.updatedAt, - currentTurn - }; -} -function sessionKey(sessionId2) { - return createHash("sha256").update(sessionId2).digest("hex"); -} -function defaultDataDir() { - return join2( - homedir2() || tmpdir(), - ".zcode", - "cli", - "plugins", - "data", - "langfuse-observability" - ); -} -var JsonStateStore = class { - dataDir; - constructor(dataDir = process.env.ZCODE_PLUGIN_DATA || defaultDataDir()) { - this.dataDir = dataDir; - } - async load(sessionId2) { - try { - const contents = await readFile(this.statePath(sessionId2), "utf8"); - return parseState(JSON.parse(contents)); - } catch { - return null; - } - } - async save(state) { - await mkdir(this.dataDir, { recursive: true, mode: 448 }); - const path = this.statePath(state.sessionId); - const temporaryPath = `${path}.${randomUUID()}.tmp`; - await writeFile(temporaryPath, JSON.stringify(state), { - encoding: "utf8", - mode: 384 - }); - await rename(temporaryPath, path); - } - async clear(sessionId2) { - await rm(this.statePath(sessionId2), { force: true }); - } - async withSessionLock(sessionId2, task) { - await mkdir(this.dataDir, { recursive: true, mode: 448 }); - const lockPath = this.lockPath(sessionId2); - let acquired = false; - for (let attempt = 0; attempt < LOCK_ATTEMPTS; attempt += 1) { - try { - const handle = await open(lockPath, "wx", 384); - await handle.close(); - acquired = true; - break; - } catch { - await this.removeStaleLock(lockPath); - await new Promise((resolve) => setTimeout(resolve, LOCK_WAIT_MS)); - } - } - if (!acquired) - throw new Error("Timed out acquiring the Langfuse state lock"); - try { - return await task(); - } finally { - await rm(lockPath, { force: true }); - } - } - statePath(sessionId2) { - return join2(this.dataDir, `${sessionKey(sessionId2)}.json`); - } - lockPath(sessionId2) { - return join2(this.dataDir, `${sessionKey(sessionId2)}.lock`); - } - async removeStaleLock(lockPath) { - try { - const details = await stat(lockPath); - if (Date.now() - details.mtimeMs > STALE_LOCK_MS) - await rm(lockPath, { force: true }); - } catch { - } - } -}; - -// node_modules/mustache/mustache.mjs -var objectToString = Object.prototype.toString; -var isArray = Array.isArray || function isArrayPolyfill(object) { - return objectToString.call(object) === "[object Array]"; -}; -function isFunction(object) { - return typeof object === "function"; -} -function typeStr(obj) { - return isArray(obj) ? "array" : typeof obj; -} -function escapeRegExp(string) { - return string.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&"); -} -function hasProperty(obj, propName) { - return obj != null && typeof obj === "object" && propName in obj; -} -function primitiveHasOwnProperty(primitive, propName) { - return primitive != null && typeof primitive !== "object" && primitive.hasOwnProperty && primitive.hasOwnProperty(propName); -} -var regExpTest = RegExp.prototype.test; -function testRegExp(re, string) { - return regExpTest.call(re, string); -} -var nonSpaceRe = /\S/; -function isWhitespace(string) { - return !testRegExp(nonSpaceRe, string); -} -var entityMap = { - "&": "&", - "<": "<", - ">": ">", - '"': """, - "'": "'", - "/": "/", - "`": "`", - "=": "=" -}; -function escapeHtml(string) { - return String(string).replace(/[&<>"'`=\/]/g, function fromEntityMap(s) { - return entityMap[s]; - }); -} -var whiteRe = /\s*/; -var spaceRe = /\s+/; -var equalsRe = /\s*=/; -var curlyRe = /\s*\}/; -var tagRe = /#|\^|\/|>|\{|&|=|!/; -function parseTemplate(template, tags) { - if (!template) - return []; - var lineHasNonSpace = false; - var sections = []; - var tokens = []; - var spaces = []; - var hasTag = false; - var nonSpace = false; - var indentation = ""; - var tagIndex = 0; - function stripSpace() { - if (hasTag && !nonSpace) { - while (spaces.length) - delete tokens[spaces.pop()]; - } else { - spaces = []; - } - hasTag = false; - nonSpace = false; - } - var openingTagRe, closingTagRe, closingCurlyRe; - function compileTags(tagsToCompile) { - if (typeof tagsToCompile === "string") - tagsToCompile = tagsToCompile.split(spaceRe, 2); - if (!isArray(tagsToCompile) || tagsToCompile.length !== 2) - throw new Error("Invalid tags: " + tagsToCompile); - openingTagRe = new RegExp(escapeRegExp(tagsToCompile[0]) + "\\s*"); - closingTagRe = new RegExp("\\s*" + escapeRegExp(tagsToCompile[1])); - closingCurlyRe = new RegExp("\\s*" + escapeRegExp("}" + tagsToCompile[1])); - } - compileTags(tags || mustache.tags); - var scanner = new Scanner(template); - var start, type, value, chr, token, openSection; - while (!scanner.eos()) { - start = scanner.pos; - value = scanner.scanUntil(openingTagRe); - if (value) { - for (var i = 0, valueLength = value.length; i < valueLength; ++i) { - chr = value.charAt(i); - if (isWhitespace(chr)) { - spaces.push(tokens.length); - indentation += chr; - } else { - nonSpace = true; - lineHasNonSpace = true; - indentation += " "; - } - tokens.push(["text", chr, start, start + 1]); - start += 1; - if (chr === "\n") { - stripSpace(); - indentation = ""; - tagIndex = 0; - lineHasNonSpace = false; - } - } - } - if (!scanner.scan(openingTagRe)) - break; - hasTag = true; - type = scanner.scan(tagRe) || "name"; - scanner.scan(whiteRe); - if (type === "=") { - value = scanner.scanUntil(equalsRe); - scanner.scan(equalsRe); - scanner.scanUntil(closingTagRe); - } else if (type === "{") { - value = scanner.scanUntil(closingCurlyRe); - scanner.scan(curlyRe); - scanner.scanUntil(closingTagRe); - type = "&"; - } else { - value = scanner.scanUntil(closingTagRe); - } - if (!scanner.scan(closingTagRe)) - throw new Error("Unclosed tag at " + scanner.pos); - if (type == ">") { - token = [type, value, start, scanner.pos, indentation, tagIndex, lineHasNonSpace]; - } else { - token = [type, value, start, scanner.pos]; - } - tagIndex++; - tokens.push(token); - if (type === "#" || type === "^") { - sections.push(token); - } else if (type === "/") { - openSection = sections.pop(); - if (!openSection) - throw new Error('Unopened section "' + value + '" at ' + start); - if (openSection[1] !== value) - throw new Error('Unclosed section "' + openSection[1] + '" at ' + start); - } else if (type === "name" || type === "{" || type === "&") { - nonSpace = true; - } else if (type === "=") { - compileTags(value); - } - } - stripSpace(); - openSection = sections.pop(); - if (openSection) - throw new Error('Unclosed section "' + openSection[1] + '" at ' + scanner.pos); - return nestTokens(squashTokens(tokens)); -} -function squashTokens(tokens) { - var squashedTokens = []; - var token, lastToken; - for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) { - token = tokens[i]; - if (token) { - if (token[0] === "text" && lastToken && lastToken[0] === "text") { - lastToken[1] += token[1]; - lastToken[3] = token[3]; - } else { - squashedTokens.push(token); - lastToken = token; - } - } - } - return squashedTokens; -} -function nestTokens(tokens) { - var nestedTokens = []; - var collector = nestedTokens; - var sections = []; - var token, section; - for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) { - token = tokens[i]; - switch (token[0]) { - case "#": - case "^": - collector.push(token); - sections.push(token); - collector = token[4] = []; - break; - case "/": - section = sections.pop(); - section[5] = token[2]; - collector = sections.length > 0 ? sections[sections.length - 1][4] : nestedTokens; - break; - default: - collector.push(token); - } - } - return nestedTokens; -} -function Scanner(string) { - this.string = string; - this.tail = string; - this.pos = 0; -} -Scanner.prototype.eos = function eos() { - return this.tail === ""; -}; -Scanner.prototype.scan = function scan(re) { - var match = this.tail.match(re); - if (!match || match.index !== 0) - return ""; - var string = match[0]; - this.tail = this.tail.substring(string.length); - this.pos += string.length; - return string; -}; -Scanner.prototype.scanUntil = function scanUntil(re) { - var index = this.tail.search(re), match; - switch (index) { - case -1: - match = this.tail; - this.tail = ""; - break; - case 0: - match = ""; - break; - default: - match = this.tail.substring(0, index); - this.tail = this.tail.substring(index); - } - this.pos += match.length; - return match; -}; -function Context(view, parentContext) { - this.view = view; - this.cache = { ".": this.view }; - this.parent = parentContext; -} -Context.prototype.push = function push(view) { - return new Context(view, this); -}; -Context.prototype.lookup = function lookup(name) { - var cache = this.cache; - var value; - if (cache.hasOwnProperty(name)) { - value = cache[name]; - } else { - var context = this, intermediateValue, names, index, lookupHit = false; - while (context) { - if (name.indexOf(".") > 0) { - intermediateValue = context.view; - names = name.split("."); - index = 0; - while (intermediateValue != null && index < names.length) { - if (index === names.length - 1) - lookupHit = hasProperty(intermediateValue, names[index]) || primitiveHasOwnProperty(intermediateValue, names[index]); - intermediateValue = intermediateValue[names[index++]]; - } - } else { - intermediateValue = context.view[name]; - lookupHit = hasProperty(context.view, name); - } - if (lookupHit) { - value = intermediateValue; - break; - } - context = context.parent; - } - cache[name] = value; - } - if (isFunction(value)) - value = value.call(this.view); - return value; -}; -function Writer() { - this.templateCache = { - _cache: {}, - set: function set(key, value) { - this._cache[key] = value; - }, - get: function get(key) { - return this._cache[key]; - }, - clear: function clear() { - this._cache = {}; - } - }; -} -Writer.prototype.clearCache = function clearCache() { - if (typeof this.templateCache !== "undefined") { - this.templateCache.clear(); - } -}; -Writer.prototype.parse = function parse(template, tags) { - var cache = this.templateCache; - var cacheKey = template + ":" + (tags || mustache.tags).join(":"); - var isCacheEnabled = typeof cache !== "undefined"; - var tokens = isCacheEnabled ? cache.get(cacheKey) : void 0; - if (tokens == void 0) { - tokens = parseTemplate(template, tags); - isCacheEnabled && cache.set(cacheKey, tokens); - } - return tokens; -}; -Writer.prototype.render = function render(template, view, partials, config) { - var tags = this.getConfigTags(config); - var tokens = this.parse(template, tags); - var context = view instanceof Context ? view : new Context(view, void 0); - return this.renderTokens(tokens, context, partials, template, config); -}; -Writer.prototype.renderTokens = function renderTokens(tokens, context, partials, originalTemplate, config) { - var buffer = ""; - var token, symbol, value; - for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) { - value = void 0; - token = tokens[i]; - symbol = token[0]; - if (symbol === "#") value = this.renderSection(token, context, partials, originalTemplate, config); - else if (symbol === "^") value = this.renderInverted(token, context, partials, originalTemplate, config); - else if (symbol === ">") value = this.renderPartial(token, context, partials, config); - else if (symbol === "&") value = this.unescapedValue(token, context); - else if (symbol === "name") value = this.escapedValue(token, context, config); - else if (symbol === "text") value = this.rawValue(token); - if (value !== void 0) - buffer += value; - } - return buffer; -}; -Writer.prototype.renderSection = function renderSection(token, context, partials, originalTemplate, config) { - var self = this; - var buffer = ""; - var value = context.lookup(token[1]); - function subRender(template) { - return self.render(template, context, partials, config); - } - if (!value) return; - if (isArray(value)) { - for (var j = 0, valueLength = value.length; j < valueLength; ++j) { - buffer += this.renderTokens(token[4], context.push(value[j]), partials, originalTemplate, config); - } - } else if (typeof value === "object" || typeof value === "string" || typeof value === "number") { - buffer += this.renderTokens(token[4], context.push(value), partials, originalTemplate, config); - } else if (isFunction(value)) { - if (typeof originalTemplate !== "string") - throw new Error("Cannot use higher-order sections without the original template"); - value = value.call(context.view, originalTemplate.slice(token[3], token[5]), subRender); - if (value != null) - buffer += value; - } else { - buffer += this.renderTokens(token[4], context, partials, originalTemplate, config); - } - return buffer; -}; -Writer.prototype.renderInverted = function renderInverted(token, context, partials, originalTemplate, config) { - var value = context.lookup(token[1]); - if (!value || isArray(value) && value.length === 0) - return this.renderTokens(token[4], context, partials, originalTemplate, config); -}; -Writer.prototype.indentPartial = function indentPartial(partial, indentation, lineHasNonSpace) { - var filteredIndentation = indentation.replace(/[^ \t]/g, ""); - var partialByNl = partial.split("\n"); - for (var i = 0; i < partialByNl.length; i++) { - if (partialByNl[i].length && (i > 0 || !lineHasNonSpace)) { - partialByNl[i] = filteredIndentation + partialByNl[i]; - } - } - return partialByNl.join("\n"); -}; -Writer.prototype.renderPartial = function renderPartial(token, context, partials, config) { - if (!partials) return; - var tags = this.getConfigTags(config); - var value = isFunction(partials) ? partials(token[1]) : partials[token[1]]; - if (value != null) { - var lineHasNonSpace = token[6]; - var tagIndex = token[5]; - var indentation = token[4]; - var indentedValue = value; - if (tagIndex == 0 && indentation) { - indentedValue = this.indentPartial(value, indentation, lineHasNonSpace); - } - var tokens = this.parse(indentedValue, tags); - return this.renderTokens(tokens, context, partials, indentedValue, config); - } -}; -Writer.prototype.unescapedValue = function unescapedValue(token, context) { - var value = context.lookup(token[1]); - if (value != null) - return value; -}; -Writer.prototype.escapedValue = function escapedValue(token, context, config) { - var escape = this.getConfigEscape(config) || mustache.escape; - var value = context.lookup(token[1]); - if (value != null) - return typeof value === "number" && escape === mustache.escape ? String(value) : escape(value); -}; -Writer.prototype.rawValue = function rawValue(token) { - return token[1]; -}; -Writer.prototype.getConfigTags = function getConfigTags(config) { - if (isArray(config)) { - return config; - } else if (config && typeof config === "object") { - return config.tags; - } else { - return void 0; - } -}; -Writer.prototype.getConfigEscape = function getConfigEscape(config) { - if (config && typeof config === "object" && !isArray(config)) { - return config.escape; - } else { - return void 0; - } -}; -var mustache = { - name: "mustache.js", - version: "4.2.0", - tags: ["{{", "}}"], - clearCache: void 0, - escape: void 0, - parse: void 0, - render: void 0, - Scanner: void 0, - Context: void 0, - Writer: void 0, - /** - * Allows a user to override the default caching strategy, by providing an - * object with set, get and clear methods. This can also be used to disable - * the cache by setting it to the literal `undefined`. - */ - set templateCache(cache) { - defaultWriter.templateCache = cache; - }, - /** - * Gets the default or overridden caching object from the default writer. - */ - get templateCache() { - return defaultWriter.templateCache; - } -}; -var defaultWriter = new Writer(); -mustache.clearCache = function clearCache2() { - return defaultWriter.clearCache(); -}; -mustache.parse = function parse2(template, tags) { - return defaultWriter.parse(template, tags); -}; -mustache.render = function render2(template, view, partials, config) { - if (typeof template !== "string") { - throw new TypeError('Invalid template! Template should be a "string" but "' + typeStr(template) + '" was given as the first argument for mustache#render(template, view, partials)'); - } - return defaultWriter.render(template, view, partials, config); -}; -mustache.escape = escapeHtml; -mustache.Scanner = Scanner; -mustache.Context = Context; -mustache.Writer = Writer; -var mustache_default = mustache; - -// node_modules/langfuse-core/lib/index.mjs -var SimpleEventEmitter = class { - constructor() { - this.events = {}; - this.events = {}; - } - on(event, listener) { - if (!this.events[event]) { - this.events[event] = []; - } - this.events[event].push(listener); - return () => { - this.events[event] = this.events[event].filter((x) => x !== listener); - }; - } - emit(event, payload) { - for (const listener of this.events[event] || []) { - listener(payload); - } - for (const listener of this.events["*"] || []) { - listener(event, payload); - } - } -}; -var DEFAULT_PROMPT_CACHE_TTL_SECONDS = 60; -var LangfusePromptCacheItem = class { - constructor(value, ttlSeconds) { - this.value = value; - this._expiry = Date.now() + ttlSeconds * 1e3; - } - get isExpired() { - return Date.now() > this._expiry; - } -}; -var LangfusePromptCache = class { - constructor() { - this._cache = /* @__PURE__ */ new Map(); - this._defaultTtlSeconds = DEFAULT_PROMPT_CACHE_TTL_SECONDS; - this._refreshingKeys = /* @__PURE__ */ new Map(); - } - getIncludingExpired(key) { - return this._cache.get(key) ?? null; - } - set(key, value, ttlSeconds) { - const effectiveTtlSeconds = ttlSeconds ?? this._defaultTtlSeconds; - this._cache.set(key, new LangfusePromptCacheItem(value, effectiveTtlSeconds)); - } - addRefreshingPromise(key, promise) { - this._refreshingKeys.set(key, promise); - promise.then(() => { - this._refreshingKeys.delete(key); - }).catch(() => { - this._refreshingKeys.delete(key); - }); - } - isRefreshing(key) { - return this._refreshingKeys.has(key); - } - invalidate(promptName) { - console.log("invalidating", promptName, this._cache.keys()); - for (const key of this._cache.keys()) { - if (key.startsWith(promptName)) { - this._cache.delete(key); - } - } - } -}; -var LangfusePersistedProperty; -(function(LangfusePersistedProperty2) { - LangfusePersistedProperty2["Props"] = "props"; - LangfusePersistedProperty2["Queue"] = "queue"; - LangfusePersistedProperty2["OptedOut"] = "opted_out"; -})(LangfusePersistedProperty || (LangfusePersistedProperty = {})); -var ChatMessageType; -(function(ChatMessageType2) { - ChatMessageType2["ChatMessage"] = "chatmessage"; - ChatMessageType2["Placeholder"] = "placeholder"; -})(ChatMessageType || (ChatMessageType = {})); -mustache_default.escape = function(text) { - return text; -}; -var BasePromptClient = class { - constructor(prompt2, isFallback = false, type) { - this.name = prompt2.name; - this.version = prompt2.version; - this.config = prompt2.config; - this.labels = prompt2.labels; - this.tags = prompt2.tags; - this.isFallback = isFallback; - this.type = type; - this.commitMessage = prompt2.commitMessage; - } - _transformToLangchainVariables(content) { - const jsonEscapedContent = this.escapeJsonForLangchain(content); - return jsonEscapedContent.replace(/\{\{(\w+)\}\}/g, "{$1}"); - } - /** - * Escapes every curly brace that is part of a JSON object by doubling it. - * - * A curly brace is considered “JSON-related” when, after skipping any immediate - * whitespace, the next non-whitespace character is a single (') or double (") quote. - * - * Braces that are already doubled (e.g. `{{variable}}` placeholders) are left untouched. - * - * @param text - Input string that may contain JSON snippets. - * @returns The string with JSON-related braces doubled. - */ - escapeJsonForLangchain(text) { - const out = []; - const stack = []; - let i = 0; - const n = text.length; - while (i < n) { - const ch = text[i]; - if (ch === "{") { - if (i + 1 < n && text[i + 1] === "{") { - out.push("{{"); - i += 2; - continue; - } - let j = i + 1; - while (j < n && /\s/.test(text[j])) { - j++; - } - const isJson = j < n && (text[j] === "'" || text[j] === '"'); - out.push(isJson ? "{{" : "{"); - stack.push(isJson); - i += 1; - continue; - } - if (ch === "}") { - if (i + 1 < n && text[i + 1] === "}") { - out.push("}}"); - i += 2; - continue; - } - const isJson = stack.pop() ?? false; - out.push(isJson ? "}}" : "}"); - i += 1; - continue; - } - out.push(ch); - i += 1; - } - return out.join(""); - } -}; -var TextPromptClient = class extends BasePromptClient { - constructor(prompt2, isFallback = false) { - super(prompt2, isFallback, "text"); - this.promptResponse = prompt2; - this.prompt = prompt2.prompt; - } - compile(variables, _placeholders) { - return mustache_default.render(this.promptResponse.prompt, variables ?? {}); - } - getLangchainPrompt(_options) { - return this._transformToLangchainVariables(this.prompt); - } - toJSON() { - return JSON.stringify({ - name: this.name, - prompt: this.prompt, - version: this.version, - isFallback: this.isFallback, - tags: this.tags, - labels: this.labels, - type: this.type, - config: this.config - }); - } -}; -var ChatPromptClient = class _ChatPromptClient extends BasePromptClient { - constructor(prompt2, isFallback = false) { - const normalizedPrompt = _ChatPromptClient.normalizePrompt(prompt2.prompt); - const typedPrompt = { - ...prompt2, - prompt: normalizedPrompt - }; - super(typedPrompt, isFallback, "chat"); - this.promptResponse = typedPrompt; - this.prompt = normalizedPrompt; - } - static normalizePrompt(prompt2) { - return prompt2.map((item) => { - if ("type" in item) { - return item; - } else { - return { - type: ChatMessageType.ChatMessage, - ...item - }; - } - }); - } - compile(variables, placeholders) { - const messagesWithPlaceholdersReplaced = []; - const placeholderValues = placeholders ?? {}; - for (const item of this.prompt) { - if ("type" in item && item.type === ChatMessageType.Placeholder) { - const placeholderValue = placeholderValues[item.name]; - if (Array.isArray(placeholderValue) && placeholderValue.length > 0 && placeholderValue.every((msg) => typeof msg === "object" && "role" in msg && "content" in msg)) { - messagesWithPlaceholdersReplaced.push(...placeholderValue); - } else if (Array.isArray(placeholderValue) && placeholderValue.length === 0) ; - else if (placeholderValue !== void 0) { - messagesWithPlaceholdersReplaced.push(JSON.stringify(placeholderValue)); - } else { - messagesWithPlaceholdersReplaced.push(item); - } - } else if ("role" in item && "content" in item && item.type === ChatMessageType.ChatMessage) { - messagesWithPlaceholdersReplaced.push({ - role: item.role, - content: item.content - }); - } - } - return messagesWithPlaceholdersReplaced.map((item) => { - if (typeof item === "object" && item !== null && "role" in item && "content" in item) { - return { - ...item, - content: mustache_default.render(item.content, variables ?? {}) - }; - } else { - return item; - } - }); - } - getLangchainPrompt(options) { - const messagesWithPlaceholdersReplaced = []; - const placeholderValues = options?.placeholders ?? {}; - for (const item of this.prompt) { - if ("type" in item && item.type === ChatMessageType.Placeholder) { - const placeholderValue = placeholderValues[item.name]; - if (Array.isArray(placeholderValue) && placeholderValue.length > 0 && placeholderValue.every((msg) => typeof msg === "object" && "role" in msg && "content" in msg)) { - messagesWithPlaceholdersReplaced.push(...placeholderValue.map((msg) => { - return { - role: msg.role, - content: this._transformToLangchainVariables(msg.content) - }; - })); - } else if (Array.isArray(placeholderValue) && placeholderValue.length === 0) ; - else if (placeholderValue !== void 0) { - messagesWithPlaceholdersReplaced.push(JSON.stringify(placeholderValue)); - } else { - messagesWithPlaceholdersReplaced.push({ - variableName: item.name, - optional: false - }); - } - } else if ("role" in item && "content" in item && item.type === ChatMessageType.ChatMessage) { - messagesWithPlaceholdersReplaced.push({ - role: item.role, - content: this._transformToLangchainVariables(item.content) - }); - } - } - return messagesWithPlaceholdersReplaced; - } - toJSON() { - return JSON.stringify({ - name: this.name, - prompt: this.promptResponse.prompt.map((item) => { - if ("type" in item && item.type === ChatMessageType.ChatMessage) { - const { - type: _, - ...messageWithoutType - } = item; - return messageWithoutType; - } - return item; - }), - version: this.version, - isFallback: this.isFallback, - tags: this.tags, - labels: this.labels, - type: this.type, - config: this.config - }); - } -}; -function assert(truthyValue, message) { - if (!truthyValue) { - throw new Error(message); - } -} -function removeTrailingSlash(url) { - return url?.replace(/\/+$/, ""); -} -async function retriable(fn, props = {}, log) { - const { - retryCount = 3, - retryDelay = 5e3, - retryCheck = () => true - } = props; - let lastError = null; - for (let i = 0; i < retryCount + 1; i++) { - if (i > 0) { - await new Promise((resolve) => setTimeout(resolve, retryDelay)); - log(`Retrying ${i + 1} of ${retryCount + 1}`); - } - try { - const res = await fn(); - return res; - } catch (e) { - lastError = e; - if (!retryCheck(e)) { - throw e; - } - log(`Retriable error: ${JSON.stringify(e)}`); - } - } - throw lastError; -} -function generateUUID(globalThis2) { - let d = (/* @__PURE__ */ new Date()).getTime(); - let d2 = globalThis2 && globalThis2.performance && globalThis2.performance.now && globalThis2.performance.now() * 1e3 || 0; - return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) { - let r = Math.random() * 16; - if (d > 0) { - r = (d + r) % 16 | 0; - d = Math.floor(d / 16); - } else { - r = (d2 + r) % 16 | 0; - d2 = Math.floor(d2 / 16); - } - return (c === "x" ? r : r & 3 | 8).toString(16); - }); -} -function currentTimestamp() { - return (/* @__PURE__ */ new Date()).getTime(); -} -function currentISOTime() { - return (/* @__PURE__ */ new Date()).toISOString(); -} -function safeSetTimeout(fn, timeout) { - const t = setTimeout(fn, timeout); - t?.unref && t?.unref(); - return t; -} -function getEnv(key) { - if (typeof process !== "undefined" && process.env[key]) { - return process.env[key]; - } else if (typeof globalThis !== "undefined") { - return globalThis[key]; - } - return; -} -function configLangfuseSDK(params, secretRequired = true) { - const { - publicKey, - secretKey, - ...coreOptions - } = params ?? {}; - const finalPublicKey = publicKey ?? getEnv("LANGFUSE_PUBLIC_KEY"); - const finalSecretKey = secretRequired ? secretKey ?? getEnv("LANGFUSE_SECRET_KEY") : void 0; - const finalBaseUrl = coreOptions.baseUrl ?? getEnv("LANGFUSE_BASEURL"); - const finalCoreOptions = { - ...coreOptions, - baseUrl: finalBaseUrl - }; - return { - publicKey: finalPublicKey, - ...secretRequired ? { - secretKey: finalSecretKey - } : void 0, - ...finalCoreOptions - }; -} -var encodeQueryParams = (params) => { - const queryParams = new URLSearchParams(); - Object.entries(params ?? {}).forEach(([key, value]) => { - if (value !== void 0 && value !== null) { - if (value instanceof Date) { - queryParams.append(key, value.toISOString()); - } else { - queryParams.append(key, value.toString()); - } - } - }); - return queryParams.toString(); -}; -var utils = /* @__PURE__ */ Object.freeze({ - __proto__: null, - assert, - configLangfuseSDK, - currentISOTime, - currentTimestamp, - encodeQueryParams, - generateUUID, - getEnv, - removeTrailingSlash, - retriable, - safeSetTimeout -}); -var common_release_envs = [ - // Vercel - "VERCEL_GIT_COMMIT_SHA", - "NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA", - // Netlify - "COMMIT_REF", - // Render - "RENDER_GIT_COMMIT", - // GitLab CI - "CI_COMMIT_SHA", - // CicleCI - "CIRCLE_SHA1", - // Cloudflare pages - "CF_PAGES_COMMIT_SHA", - // AWS Amplify - "REACT_APP_GIT_SHA", - // Heroku - "SOURCE_VERSION", - // Trigger.dev - "TRIGGER_DEPLOYMENT_ID" -]; -function getCommonReleaseEnvs() { - for (const key of common_release_envs) { - const value = getEnv(key); - if (value) { - return value; - } - } - return void 0; -} -var fs = null; -var cryptoModule = null; -var dynamicImport = (module) => { - return import( - /* webpackIgnore: true */ - module - ); -}; -if (typeof globalThis.Deno !== "undefined") { - Promise.all([dynamicImport("node:fs"), dynamicImport("node:crypto")]).then(([importedFs, importedCrypto]) => { - fs = importedFs; - cryptoModule = importedCrypto; - }).catch(); -} else if (typeof process !== "undefined" && process.versions?.node) { - Promise.all([dynamicImport("fs"), dynamicImport("crypto")]).then(([importedFs, importedCrypto]) => { - fs = importedFs; - cryptoModule = importedCrypto; - }).catch(); -} else if (typeof crypto !== "undefined") { - cryptoModule = crypto; -} -var LangfuseMedia = class _LangfuseMedia { - constructor(params) { - const { - obj, - base64DataUri, - contentType, - contentBytes, - filePath - } = params; - this.obj = obj; - this._mediaId = void 0; - if (base64DataUri) { - const [contentBytesParsed, contentTypeParsed] = this.parseBase64DataUri(base64DataUri); - this._contentBytes = contentBytesParsed; - this._contentType = contentTypeParsed; - this._source = "base64_data_uri"; - } else if (contentBytes && contentType) { - this._contentBytes = contentBytes; - this._contentType = contentType; - this._source = "bytes"; - } else if (filePath && contentType) { - if (!fs) { - throw new Error("File system support is not available in this environment"); - } - if (!fs.existsSync(filePath)) { - throw new Error(`File at path ${filePath} does not exist`); - } - this._contentBytes = this.readFile(filePath); - this._contentType = this._contentBytes ? contentType : void 0; - this._source = this._contentBytes ? "file" : void 0; - } else { - console.error("base64DataUri, or contentBytes and contentType, or filePath must be provided to LangfuseMedia"); - } - } - readFile(filePath) { - try { - if (!fs) { - throw new Error("File system support is not available in this environment"); - } - return fs.readFileSync(filePath); - } catch (error) { - console.error(`Error reading file at path ${filePath}`, error); - return void 0; - } - } - parseBase64DataUri(data) { - try { - if (!data || typeof data !== "string") { - throw new Error("Data URI is not a string"); - } - if (!data.startsWith("data:")) { - throw new Error("Data URI does not start with 'data:'"); - } - const [header, actualData] = data.slice(5).split(",", 2); - if (!header || !actualData) { - throw new Error("Invalid URI"); - } - const headerParts = header.split(";"); - if (!headerParts.includes("base64")) { - throw new Error("Data is not base64 encoded"); - } - const contentType = headerParts[0]; - if (!contentType) { - throw new Error("Content type is empty"); - } - return [Buffer.from(actualData, "base64"), contentType]; - } catch (error) { - console.error("Error parsing base64 data URI", error); - return [void 0, void 0]; - } - } - get contentLength() { - return this._contentBytes?.length; - } - get contentSha256Hash() { - if (!this._contentBytes) { - return void 0; - } - if (!cryptoModule) { - console.error("Crypto support is not available in this environment"); - return void 0; - } - const sha256Hash = cryptoModule.createHash("sha256").update(this._contentBytes).digest("base64"); - return sha256Hash; - } - toJSON() { - if (!this._contentType || !this._source || !this._mediaId) { - return ``; - } - return `@@@langfuseMedia:type=${this._contentType}|id=${this._mediaId}|source=${this._source}@@@`; - } - /** - * Parses a media reference string into a ParsedMediaReference. - * - * Example reference string: - * "@@@langfuseMedia:type=image/jpeg|id=some-uuid|source=base64DataUri@@@" - * - * @param referenceString - The reference string to parse. - * @returns An object with the mediaId, source, and contentType. - * - * @throws Error if the reference string is invalid or missing required fields. - */ - static parseReferenceString(referenceString) { - const prefix = "@@@langfuseMedia:"; - const suffix = "@@@"; - if (!referenceString.startsWith(prefix)) { - throw new Error("Reference string does not start with '@@@langfuseMedia:type='"); - } - if (!referenceString.endsWith(suffix)) { - throw new Error("Reference string does not end with '@@@'"); - } - const content = referenceString.slice(prefix.length, -suffix.length); - const pairs = content.split("|"); - const parsedData = {}; - for (const pair of pairs) { - const [key, value] = pair.split("=", 2); - parsedData[key] = value; - } - if (!("type" in parsedData && "id" in parsedData && "source" in parsedData)) { - throw new Error("Missing required fields in reference string"); - } - return { - mediaId: parsedData["id"], - source: parsedData["source"], - contentType: parsedData["type"] - }; - } - /** - * Replaces the media reference strings in an object with base64 data URIs for the media content. - * - * This method recursively traverses an object (up to a maximum depth of 10) looking for media reference strings - * in the format "@@@langfuseMedia:...@@@". When found, it fetches the actual media content using the provided - * Langfuse client and replaces the reference string with a base64 data URI. - * - * If fetching media content fails for a reference string, a warning is logged and the reference string is left unchanged. - * - * @param params - Configuration object - * @param params.obj - The object to process. Can be a primitive value, array, or nested object - * @param params.langfuseClient - Langfuse client instance used to fetch media content - * @param params.resolveWith - The representation of the media content to replace the media reference string with. Currently only "base64DataUri" is supported. - * @param params.maxDepth - Optional. Default is 10. The maximum depth to traverse the object. - * - * @returns A deep copy of the input object with all media references replaced with base64 data URIs where possible - * - * @example - * ```typescript - * const obj = { - * image: "@@@langfuseMedia:type=image/jpeg|id=123|source=bytes@@@", - * nested: { - * pdf: "@@@langfuseMedia:type=application/pdf|id=456|source=bytes@@@" - * } - * }; - * - * const result = await LangfuseMedia.resolveMediaReferences({ - * obj, - * langfuseClient - * }); - * - * // Result: - * // { - * // image: "data:image/jpeg;base64,/9j/4AAQSkZJRg...", - * // nested: { - * // pdf: "data:application/pdf;base64,JVBERi0xLjcK..." - * // } - * // } - * ``` - */ - static async resolveMediaReferences(params) { - const { - obj, - langfuseClient, - maxDepth = 10 - } = params; - async function traverse(obj2, depth) { - if (depth > maxDepth) { - return obj2; - } - if (typeof obj2 === "string") { - const regex = /@@@langfuseMedia:.+?@@@/g; - const referenceStringMatches = obj2.match(regex); - if (!referenceStringMatches) { - return obj2; - } - let result = obj2; - const referenceStringToMediaContentMap = /* @__PURE__ */ new Map(); - await Promise.all(referenceStringMatches.map(async (referenceString) => { - try { - const parsedMediaReference = _LangfuseMedia.parseReferenceString(referenceString); - const mediaData = await langfuseClient.fetchMedia(parsedMediaReference.mediaId); - const mediaContent = await langfuseClient.fetch(mediaData.url, { - method: "GET", - headers: {} - }); - if (mediaContent.status !== 200) { - throw new Error("Failed to fetch media content"); - } - const base64MediaContent = Buffer.from(await mediaContent.arrayBuffer()).toString("base64"); - const base64DataUri = `data:${mediaData.contentType};base64,${base64MediaContent}`; - referenceStringToMediaContentMap.set(referenceString, base64DataUri); - } catch (error) { - console.warn("Error fetching media content for reference string", referenceString, error); - } - })); - for (const [referenceString, base64MediaContent] of referenceStringToMediaContentMap.entries()) { - result = result.replaceAll(referenceString, base64MediaContent); - } - return result; - } - if (Array.isArray(obj2)) { - return Promise.all(obj2.map(async (item) => await traverse(item, depth + 1))); - } - if (typeof obj2 === "object" && obj2 !== null) { - return Object.fromEntries(await Promise.all(Object.entries(obj2).map(async ([key, value]) => [key, await traverse(value, depth + 1)]))); - } - return obj2; - } - return traverse(obj, 0); - } -}; -function isInSample(input, sampleRate) { - if (sampleRate === void 0) { - return true; - } else if (sampleRate === 0) { - return false; - } - if (sampleRate < 0 || sampleRate > 1 || isNaN(sampleRate)) { - console.warn("Sample rate must be between 0 and 1. Ignoring setting."); - return true; - } - return simpleHash(input) < sampleRate; -} -function simpleHash(str) { - let hash = 0; - const prime = 31; - for (let i = 0; i < str.length; i++) { - hash = hash * prime + str.charCodeAt(i) >>> 0; - } - hash = (hash >>> 16 ^ hash) * 73244475; - hash = (hash >>> 16 ^ hash) * 73244475; - hash = hash >>> 16 ^ hash; - return Math.abs(hash) / 2147483647; -} -var MAX_EVENT_SIZE_BYTES = getEnv("LANGFUSE_MAX_EVENT_SIZE_BYTES") ? Number(getEnv("LANGFUSE_MAX_EVENT_SIZE_BYTES")) : 1e6; -var MAX_BATCH_SIZE_BYTES = getEnv("LANGFUSE_MAX_BATCH_SIZE_BYTES") ? Number(getEnv("LANGFUSE_MAX_BATCH_SIZE_BYTES")) : 25e5; -var ENVIRONMENT_PATTERN = /^(?!langfuse)[a-z0-9_-]+$/; -var WHITELISTED_LANGFUSE_INTERNAL_ENVIRONMENTS = ["langfuse-prompt-experiment"]; -var LangfuseFetchHttpError = class extends Error { - constructor(response, body) { - super("HTTP error while fetching Langfuse: " + response.status + " and body: " + body); - this.response = response; - this.name = "LangfuseFetchHttpError"; - } -}; -var LangfuseFetchNetworkError = class extends Error { - constructor(error) { - super("Network error while fetching Langfuse", error instanceof Error ? { - cause: error - } : {}); - this.error = error; - this.name = "LangfuseFetchNetworkError"; - } -}; -function isLangfuseFetchHttpError(error) { - return typeof error === "object" && error.name === "LangfuseFetchHttpError"; -} -function isLangfuseFetchNetworkError(error) { - return typeof error === "object" && error.name === "LangfuseFetchNetworkError"; -} -function isLangfuseFetchError(err) { - return isLangfuseFetchHttpError(err) || isLangfuseFetchNetworkError(err); -} -var SUPPORT_URL = "https://langfuse.com/support"; -var API_DOCS_URL = "https://api.reference.langfuse.com"; -var RBAC_DOCS_URL = "https://langfuse.com/docs/rbac"; -var INSTALLATION_DOCS_URL = "https://langfuse.com/docs/sdk/typescript/guide"; -var RATE_LIMITS_URL = "https://langfuse.com/faq/all/api-limits"; -var NPM_PACKAGE_URL = "https://www.npmjs.com/package/langfuse"; -var updatePromptResponse = `Make sure to keep your SDK updated, refer to ${NPM_PACKAGE_URL} for details.`; -var defaultServerErrorPrompt = `This is an unusual occurrence and we are monitoring it closely. For help, please contact support: ${SUPPORT_URL}.`; -var defaultErrorResponse = `Unexpected error occurred. Please check your request and contact support: ${SUPPORT_URL}.`; -var errorResponseByCode = /* @__PURE__ */ new Map([ - // Internal error category: 5xx errors, 404 error - [500, `Internal server error occurred. For help, please contact support: ${SUPPORT_URL}`], - [501, `Not implemented. Please check your request and contact support for help: ${SUPPORT_URL}.`], - [502, `Bad gateway. ${defaultServerErrorPrompt}`], - [503, `Service unavailable. ${defaultServerErrorPrompt}`], - [504, `Gateway timeout. ${defaultServerErrorPrompt}`], - [404, `Internal error occurred. ${defaultServerErrorPrompt}`], - // Client error category: 4xx errors, excluding 404 - [400, `Bad request. Please check your request for any missing or incorrect parameters. Refer to our API docs: ${API_DOCS_URL} for details.`], - [401, `Unauthorized. Please check your public/private host settings. Refer to our installation and setup guide: ${INSTALLATION_DOCS_URL} for details on SDK configuration.`], - [403, `Forbidden. Please check your access control settings. Refer to our RBAC docs: ${RBAC_DOCS_URL} for details.`], - [429, `Rate limit exceeded. For more information on rate limits please see: ${RATE_LIMITS_URL}`] -]); -function getErrorResponseByCode(code) { - if (!code) { - return `${defaultErrorResponse} ${updatePromptResponse}`; - } - const errorResponse = errorResponseByCode.get(code) || defaultErrorResponse; - return `${code}: ${errorResponse} ${updatePromptResponse}`; -} -function logIngestionError(error) { - if (isLangfuseFetchHttpError(error)) { - const code = error.response.status; - const errorResponse = getErrorResponseByCode(code); - console.error("[Langfuse SDK]", errorResponse, `Error details: ${error}`); - } else if (isLangfuseFetchNetworkError(error)) { - console.error("[Langfuse SDK] Network error: ", error); - } else { - console.error("[Langfuse SDK] Unknown error:", error); - } -} -var LangfuseCoreStateless = class { - constructor(params) { - this.additionalHeaders = {}; - this.debugMode = false; - this.pendingEventProcessingPromises = {}; - this.pendingIngestionPromises = {}; - this.localEventExportMap = /* @__PURE__ */ new Map(); - this._events = new SimpleEventEmitter(); - const { - publicKey, - secretKey, - enabled, - _projectId, - _isLocalEventExportEnabled, - ...options - } = params; - this._events.on("error", (payload) => { - console.error(`[Langfuse SDK] ${typeof payload === "string" ? payload : JSON.stringify(payload)}`); - }); - this.enabled = enabled === false ? false : true; - this.publicKey = publicKey ?? ""; - this.secretKey = secretKey; - this.baseUrl = removeTrailingSlash(options?.baseUrl || "https://cloud.langfuse.com"); - this.additionalHeaders = options?.additionalHeaders || {}; - this.flushAt = options?.flushAt ? Math.max(options?.flushAt, 1) : 15; - this.flushInterval = options?.flushInterval ?? 1e4; - this.release = options?.release ?? getEnv("LANGFUSE_RELEASE") ?? getCommonReleaseEnvs() ?? void 0; - this.mask = options?.mask; - this.sampleRate = options?.sampleRate ?? (getEnv("LANGFUSE_SAMPLE_RATE") ? Number(getEnv("LANGFUSE_SAMPLE_RATE")) : void 0); - if (this.sampleRate) { - this._events.emit("debug", `Langfuse trace sampling enabled with sampleRate ${this.sampleRate}.`); - } - this.environment = options?.environment ?? getEnv("LANGFUSE_TRACING_ENVIRONMENT"); - if (this.environment && !(ENVIRONMENT_PATTERN.test(this.environment) || WHITELISTED_LANGFUSE_INTERNAL_ENVIRONMENTS.includes(this.environment)) && !_isLocalEventExportEnabled) { - this._events.emit("error", `Invalid tracing environment set: ${this.environment} . Environment must match regex ${ENVIRONMENT_PATTERN}. Events will be rejected by Langfuse server.`); - } - this._retryOptions = { - retryCount: options?.fetchRetryCount ?? 3, - retryDelay: options?.fetchRetryDelay ?? 3e3, - retryCheck: isLangfuseFetchError - }; - this.requestTimeout = options?.requestTimeout ?? 5e3; - this.sdkIntegration = options?.sdkIntegration ?? "DEFAULT"; - this.isLocalEventExportEnabled = _isLocalEventExportEnabled ?? false; - if (this.isLocalEventExportEnabled && !_projectId) { - this._events.emit("error", "Local event export is enabled, but no project ID was provided. Disabling local export."); - this.isLocalEventExportEnabled = false; - return; - } else if (!this.isLocalEventExportEnabled && _projectId) { - this._events.emit("error", "Local event export is disabled, but a project ID was provided. Disabling local export."); - this.isLocalEventExportEnabled = false; - return; - } else { - this.projectId = _projectId; - } - } - getSdkIntegration() { - return this.sdkIntegration; - } - getCommonEventProperties() { - return { - $lib: this.getLibraryId(), - $lib_version: this.getLibraryVersion() - }; - } - on(event, cb) { - return this._events.on(event, cb); - } - debug(enabled = true) { - this.removeDebugCallback?.(); - this.debugMode = enabled; - if (enabled) { - this.removeDebugCallback = this.on("*", (event, payload) => { - if (event === "error") { - return; - } - console.log("[Langfuse Debug]", event, JSON.stringify(payload)); - }); - } - } - /*** - *** Handlers for each object type - ***/ - traceStateless(body) { - const { - id: bodyId, - timestamp: bodyTimestamp, - release: bodyRelease, - ...rest - } = body; - const id = bodyId ?? generateUUID(); - const release = bodyRelease ?? this.release; - const parsedBody = { - id, - release, - timestamp: bodyTimestamp ?? /* @__PURE__ */ new Date(), - environment: this.environment, - ...rest - }; - this.enqueue("trace-create", parsedBody); - return id; - } - eventStateless(body) { - const { - id: bodyId, - startTime: bodyStartTime, - ...rest - } = body; - const id = bodyId ?? generateUUID(); - const parsedBody = { - id, - startTime: bodyStartTime ?? /* @__PURE__ */ new Date(), - environment: this.environment, - ...rest - }; - this.enqueue("event-create", parsedBody); - return id; - } - spanStateless(body) { - const { - id: bodyId, - startTime: bodyStartTime, - ...rest - } = body; - const id = bodyId || generateUUID(); - const parsedBody = { - id, - startTime: bodyStartTime ?? /* @__PURE__ */ new Date(), - environment: this.environment, - ...rest - }; - this.enqueue("span-create", parsedBody); - return id; - } - generationStateless(body) { - const { - id: bodyId, - startTime: bodyStartTime, - prompt: prompt2, - ...rest - } = body; - const promptDetails = prompt2 && !prompt2.isFallback ? { - promptName: prompt2.name, - promptVersion: prompt2.version - } : {}; - const id = bodyId || generateUUID(); - const parsedBody = { - id, - startTime: bodyStartTime ?? /* @__PURE__ */ new Date(), - environment: this.environment, - ...promptDetails, - ...rest - }; - this.enqueue("generation-create", parsedBody); - return id; - } - scoreStateless(body) { - const { - id: bodyId, - ...rest - } = body; - const id = bodyId || generateUUID(); - const parsedBody = { - id, - environment: this.environment, - ...rest - }; - this.enqueue("score-create", parsedBody); - return id; - } - updateSpanStateless(body) { - this.enqueue("span-update", body); - return body.id; - } - updateGenerationStateless(body) { - const { - prompt: prompt2, - ...rest - } = body; - const promptDetails = prompt2 && !prompt2.isFallback ? { - promptName: prompt2.name, - promptVersion: prompt2.version - } : {}; - const parsedBody = { - ...promptDetails, - ...rest - }; - this.enqueue("generation-update", parsedBody); - return body.id; - } - async _getDataset(name) { - const encodedName = encodeURIComponent(name); - return this.fetchAndLogErrors(`${this.baseUrl}/api/public/v2/datasets/${encodedName}`, this._getFetchOptions({ - method: "GET" - })); - } - async _getDatasetItems(query) { - const params = new URLSearchParams(); - Object.entries(query ?? {}).forEach(([key, value]) => { - if (value !== void 0 && value !== null) { - params.append(key, value.toString()); - } - }); - return this.fetchAndLogErrors(`${this.baseUrl}/api/public/dataset-items?${params}`, this._getFetchOptions({ - method: "GET" - })); - } - async _fetchMedia(id) { - return this.fetchAndLogErrors(`${this.baseUrl}/api/public/media/${id}`, this._getFetchOptions({ - method: "GET" - })); - } - async fetchTraces(query) { - const { - data, - meta - } = await this.fetchAndLogErrors(`${this.baseUrl}/api/public/traces?${encodeQueryParams(query)}`, this._getFetchOptions({ - method: "GET" - })); - return { - data, - meta - }; - } - async fetchTrace(traceId) { - const res = await this.fetchAndLogErrors(`${this.baseUrl}/api/public/traces/${traceId}`, this._getFetchOptions({ - method: "GET" - })); - return { - data: res - }; - } - async fetchObservations(query) { - const { - data, - meta - } = await this.fetchAndLogErrors(`${this.baseUrl}/api/public/observations?${encodeQueryParams(query)}`, this._getFetchOptions({ - method: "GET" - })); - return { - data, - meta - }; - } - async fetchObservation(observationId) { - const res = await this.fetchAndLogErrors(`${this.baseUrl}/api/public/observations/${observationId}`, this._getFetchOptions({ - method: "GET" - })); - return { - data: res - }; - } - async fetchSessions(query) { - const { - data, - meta - } = await this.fetchAndLogErrors(`${this.baseUrl}/api/public/sessions?${encodeQueryParams(query)}`, this._getFetchOptions({ - method: "GET" - })); - return { - data, - meta - }; - } - async getDatasetRun(params) { - const encodedDatasetName = encodeURIComponent(params.datasetName); - const encodedRunName = encodeURIComponent(params.runName); - return this.fetchAndLogErrors(`${this.baseUrl}/api/public/datasets/${encodedDatasetName}/runs/${encodedRunName}`, this._getFetchOptions({ - method: "GET" - })); - } - async getDatasetRuns(datasetName, query) { - return this.fetchAndLogErrors(`${this.baseUrl}/api/public/datasets/${encodeURIComponent(datasetName)}/runs?${encodeQueryParams(query)}`, this._getFetchOptions({ - method: "GET" - })); - } - async createDatasetRunItem(body) { - return this.fetchAndLogErrors(`${this.baseUrl}/api/public/dataset-run-items`, this._getFetchOptions({ - method: "POST", - body: JSON.stringify(body) - })); - } - /** - * Creates a dataset. Upserts the dataset if it already exists. - * - * @param dataset Can be either a string (name) or an object with name, description and metadata - * @returns A promise that resolves to the response of the create operation. - */ - async createDataset(dataset) { - const body = typeof dataset === "string" ? { - name: dataset - } : dataset; - return this.fetchAndLogErrors(`${this.baseUrl}/api/public/datasets`, this._getFetchOptions({ - method: "POST", - body: JSON.stringify(body) - })); - } - /** - * Creates a dataset item. Upserts the item if it already exists. - * @param body The body of the dataset item to be created. - * @returns A promise that resolves to the response of the create operation. - */ - async createDatasetItem(body) { - return this.fetchAndLogErrors(`${this.baseUrl}/api/public/dataset-items`, this._getFetchOptions({ - method: "POST", - body: JSON.stringify(body) - })); - } - async getDatasetItem(id) { - return this.fetchAndLogErrors(`${this.baseUrl}/api/public/dataset-items/${id}`, this._getFetchOptions({ - method: "GET" - })); - } - _parsePayload(response) { - try { - return JSON.parse(response); - } catch { - return response; - } - } - async createPromptStateless(body) { - return this.fetchAndLogErrors(`${this.baseUrl}/api/public/v2/prompts`, this._getFetchOptions({ - method: "POST", - body: JSON.stringify(body) - })); - } - async updatePromptStateless(body) { - return this.fetchAndLogErrors(`${this.baseUrl}/api/public/v2/prompts/${encodeURIComponent(body.name)}/versions/${encodeURIComponent(body.version)}`, this._getFetchOptions({ - method: "PATCH", - body: JSON.stringify(body) - })); - } - async getPromptStateless(name, version2, label, maxRetries, requestTimeout) { - const encodedName = encodeURIComponent(name); - const params = new URLSearchParams(); - if (version2 && label) { - throw new Error("Provide either version or label, not both."); - } - if (version2) { - params.append("version", version2.toString()); - } - if (label) { - params.append("label", label); - } - const url = `${this.baseUrl}/api/public/v2/prompts/${encodedName}${params.size ? "?" + params : ""}`; - const boundedMaxRetries = this._getBoundedMaxRetries({ - maxRetries, - defaultMaxRetries: 2, - maxRetriesUpperBound: 4 - }); - const retryOptions = { - ...this._retryOptions, - retryCount: boundedMaxRetries, - retryDelay: 500 - }; - const retryLogger = (string) => this._events.emit("retry", string + ", " + url + ", " + JSON.stringify(retryOptions)); - return retriable(async () => { - const res = await this.fetch(url, this._getFetchOptions({ - method: "GET", - fetchTimeout: requestTimeout ?? this.requestTimeout - })).catch((e) => { - if (e.name === "AbortError") { - throw new LangfuseFetchNetworkError("Fetch request timed out"); - } - throw new LangfuseFetchNetworkError(e); - }); - if (res.status >= 500) { - throw new LangfuseFetchHttpError(res, await res.text()); - } - const data = await res.json(); - return { - fetchResult: res.status === 200 ? "success" : "failure", - data - }; - }, retryOptions, retryLogger); - } - _getBoundedMaxRetries(params) { - const defaultMaxRetries = Math.max(params.defaultMaxRetries ?? 2, 0); - const maxRetriesUpperBound = Math.max(params.maxRetriesUpperBound ?? 4, 0); - if (params.maxRetries === void 0) { - return defaultMaxRetries; - } - return Math.min(Math.max(params.maxRetries, 0), maxRetriesUpperBound); - } - /*** - *** QUEUEING AND FLUSHING - ***/ - enqueue(type, body) { - if (!this.enabled) { - return; - } - const traceId = this.parseTraceId(type, body); - if (!traceId) { - this._events.emit("warning", "Failed to parse traceID for sampling. Please open a Github issue in https://github.com/langfuse/langfuse/issues/new/choose"); - } else if (!isInSample(traceId, this.sampleRate)) { - this._events.emit("debug", `Event with trace ID ${traceId} is out of sample. Skipping.`); - return; - } - const promise = this.processEnqueueEvent(type, body); - const promiseId = generateUUID(); - this.pendingEventProcessingPromises[promiseId] = promise; - promise.catch((e) => { - this._events.emit("error", e); - }).finally(() => { - delete this.pendingEventProcessingPromises[promiseId]; - }); - } - async processEnqueueEvent(type, body) { - this.maskEventBodyInPlace(body); - await this.processMediaInEvent(type, body); - const finalEventBody = this.truncateEventBody(body, MAX_EVENT_SIZE_BYTES); - try { - JSON.stringify(finalEventBody); - } catch (e) { - this._events.emit("error", `Event Body for ${type} is not JSON-serializable: ${e}`); - return; - } - const queue = this.getPersistedProperty(LangfusePersistedProperty.Queue) || []; - queue.push({ - id: generateUUID(), - type, - timestamp: currentISOTime(), - body: finalEventBody, - // TODO: fix typecast. EventBody is not correctly narrowed to the correct type dictated by the 'type' property. This should be part of a larger type cleanup. - metadata: void 0 - }); - this.setPersistedProperty(LangfusePersistedProperty.Queue, queue); - this._events.emit(type, finalEventBody); - if (queue.length >= this.flushAt) { - this.flush(); - } - if (this.flushInterval && !this._flushTimer) { - this._flushTimer = safeSetTimeout(() => this.flush(), this.flushInterval); - } - } - maskEventBodyInPlace(body) { - if (!this.mask) { - return; - } - const maskableKeys = ["input", "output"]; - for (const key of maskableKeys) { - if (key in body) { - try { - body[key] = this.mask({ - data: body[key] - }); - } catch (e) { - this._events.emit("error", `Error masking ${key}: ${e}`); - body[key] = ""; - } - } - } - } - /** - * Truncates the event body if its byte size exceeds the specified maximum byte size. - * Emits a warning event if truncation occurs. - * The fields that may be truncated are: "input", "output", and "metadata". - * The fields are truncated in the order of their size, from largest to smallest until the total byte size is within the limit. - */ - truncateEventBody(body, maxByteSize) { - const bodySize = this.getByteSize(body); - if (bodySize <= maxByteSize) { - return body; - } - this._events.emit("warning", `Event Body is too large (${bodySize} bytes) and will be truncated`); - const keysToCheck = ["input", "output", "metadata"]; - const keySizes = keysToCheck.map((key) => ({ - key, - size: key in body ? this.getByteSize(body[key]) : 0 - })).sort((a, b) => b.size - a.size); - let result = { - ...body - }; - let currentSize = bodySize; - for (const { - key, - size - } of keySizes) { - if (currentSize > maxByteSize && Object.prototype.hasOwnProperty.call(result, key)) { - result = { - ...result, - [key]: "" - }; - this._events.emit("warning", `Truncated ${key} due to total size exceeding limit`); - currentSize -= size; - } - } - return result; - } - getByteSize(obj) { - const serialized = JSON.stringify(obj); - if (typeof TextEncoder !== "undefined") { - return new TextEncoder().encode(serialized).length; - } else { - return encodeURIComponent(serialized).replace(/%[A-F\d]{2}/g, "U").length; - } - } - async processMediaInEvent(type, body) { - if (!body) { - return; - } - const traceId = this.parseTraceId(type, body); - if (!traceId) { - this._events.emit("warning", "traceId is required for media upload"); - return; - } - const observationId = (type.includes("generation") || type.includes("span")) && body.id ? body.id : void 0; - await Promise.all(["input", "output", "metadata"].map(async (field) => { - if (body[field]) { - body[field] = await this.findAndProcessMedia({ - data: body[field], - traceId, - observationId, - field - }).catch((e) => { - this._events.emit("error", `Error processing multimodal event: ${e}`); - }) ?? body[field]; - } - })); - } - parseTraceId(type, body) { - return "traceId" in body ? body.traceId : type.includes("trace") ? body.id : void 0; - } - async findAndProcessMedia({ - data, - traceId, - observationId, - field - }) { - const seenObjects = /* @__PURE__ */ new WeakMap(); - const maxLevels = 10; - const processRecursively = async (data2, level) => { - if (typeof data2 === "string" && data2.startsWith("data:")) { - const media = new LangfuseMedia({ - base64DataUri: data2 - }); - await this.processMediaItem({ - media, - traceId, - observationId, - field - }); - return media; - } - if (typeof data2 !== "object" || data2 === null) { - return data2; - } - if (seenObjects.has(data2) || level > maxLevels) { - return data2; - } - seenObjects.set(data2, true); - if (data2 instanceof LangfuseMedia || Object.prototype.toString.call(data2) === "[object LangfuseMedia]") { - await this.processMediaItem({ - media: data2, - traceId, - observationId, - field - }); - return data2; - } - if (Array.isArray(data2)) { - return await Promise.all(data2.map((item) => processRecursively(item, level + 1))); - } - if (typeof data2 === "object" && data2 !== null) { - if ("input_audio" in data2 && typeof data2["input_audio"] === "object" && "data" in data2.input_audio) { - const media = new LangfuseMedia({ - base64DataUri: `data:audio/${data2.input_audio["format"] || "wav"};base64,${data2.input_audio.data}` - }); - await this.processMediaItem({ - media, - traceId, - observationId, - field - }); - return { - ...data2, - input_audio: { - ...data2.input_audio, - data: media - } - }; - } - if ("audio" in data2 && typeof data2["audio"] === "object" && "data" in data2.audio) { - const media = new LangfuseMedia({ - base64DataUri: `data:audio/${data2.audio["format"] || "wav"};base64,${data2.audio.data}` - }); - await this.processMediaItem({ - media, - traceId, - observationId, - field - }); - return { - ...data2, - audio: { - ...data2.audio, - data: media - } - }; - } - return Object.fromEntries(await Promise.all(Object.entries(data2).map(async ([key, value]) => [key, await processRecursively(value, level + 1)]))); - } - return data2; - }; - return await processRecursively(data, 1); - } - async processMediaItem({ - media, - traceId, - observationId, - field - }) { - try { - if (!media.contentLength || !media._contentType || !media.contentSha256Hash || !media._contentBytes) { - return; - } - const getUploadUrlBody = { - contentLength: media.contentLength, - traceId, - observationId, - field, - contentType: media._contentType, - sha256Hash: media.contentSha256Hash - }; - const fetchResponse = await this.fetch(`${this.baseUrl}/api/public/media`, this._getFetchOptions({ - method: "POST", - body: JSON.stringify(getUploadUrlBody) - })); - const uploadUrlResponse = await fetchResponse.json(); - const { - uploadUrl, - mediaId - } = uploadUrlResponse; - media._mediaId = mediaId; - if (uploadUrl) { - this._events.emit("debug", `Uploading media ${mediaId}`); - const startTime = Date.now(); - const uploadResponse = await this.uploadMediaWithBackoff({ - uploadUrl, - contentBytes: media._contentBytes, - contentType: media._contentType, - contentSha256Hash: media.contentSha256Hash, - maxRetries: 3, - baseDelay: 1e3 - }); - if (!uploadResponse) { - throw Error("Media upload process failed"); - } - const patchMediaBody = { - uploadedAt: (/* @__PURE__ */ new Date()).toISOString(), - uploadHttpStatus: uploadResponse.status, - uploadHttpError: await uploadResponse.text(), - uploadTimeMs: Date.now() - startTime - }; - await this.fetch(`${this.baseUrl}/api/public/media/${mediaId}`, this._getFetchOptions({ - method: "PATCH", - body: JSON.stringify(patchMediaBody) - })); - this._events.emit("debug", `Media upload status reported for ${mediaId}`); - } else { - this._events.emit("debug", `Media ${mediaId} already uploaded`); - } - } catch (err) { - this._events.emit("error", `Error processing media item: ${err}`); - } - } - async uploadMediaWithBackoff(params) { - const { - uploadUrl, - contentType, - contentSha256Hash, - contentBytes, - maxRetries, - baseDelay - } = params; - for (let attempt = 0; attempt <= maxRetries; attempt++) { - try { - const uploadResponse = await this.fetch(uploadUrl, { - method: "PUT", - body: contentBytes, - headers: { - "Content-Type": contentType, - "x-amz-checksum-sha256": contentSha256Hash, - "x-ms-blob-type": "BlockBlob" - } - }); - if (attempt < maxRetries && uploadResponse.status !== 200 && uploadResponse.status !== 201) { - throw new Error(`Upload failed with status ${uploadResponse.status}`); - } - return uploadResponse; - } catch (e) { - if (attempt === maxRetries) { - throw e; - } - const delay = baseDelay * Math.pow(2, attempt); - const jitter = Math.random() * 1e3; - await new Promise((resolve) => setTimeout(resolve, delay + jitter)); - } - } - } - /** - * Asynchronously flushes all events that are not yet sent to the server. - * This function always resolves, even if there were errors when flushing. - * Errors are emitted as "error" events and the promise resolves. - * - * @returns {Promise} A promise that resolves when the flushing is completed. - */ - async flushAsync() { - await Promise.all(Object.values(this.pendingEventProcessingPromises)).catch((e) => { - logIngestionError(e); - }); - return new Promise((resolve, _reject) => { - try { - this.flush((err, data) => { - if (err) { - logIngestionError(err); - resolve(); - } else { - resolve(data); - } - }); - } catch (e) { - console.error("[Langfuse SDK] Error while flushing Langfuse", e); - } - }); - } - // Flushes all events that are not yet sent to the server - flush(callback) { - if (this._flushTimer) { - clearTimeout(this._flushTimer); - this._flushTimer = null; - } - const queue = this.getPersistedProperty(LangfusePersistedProperty.Queue) || []; - if (!queue.length) { - return callback?.(); - } - const items = queue.splice(0, this.flushAt); - const { - processedItems, - remainingItems - } = this.processQueueItems(items, MAX_EVENT_SIZE_BYTES, MAX_BATCH_SIZE_BYTES); - this.setPersistedProperty(LangfusePersistedProperty.Queue, [...remainingItems, ...queue]); - const promiseUUID = generateUUID(); - const done = (err) => { - if (err) { - this._events.emit("warning", err); - } - callback?.(err, items); - this._events.emit("flush", items); - }; - if (this.isLocalEventExportEnabled && this.projectId) { - if (!this.localEventExportMap.has(this.projectId)) { - this.localEventExportMap.set(this.projectId, [...items]); - } else { - this.localEventExportMap.get(this.projectId)?.push(...items); - } - done(); - return; - } - const payload = JSON.stringify({ - batch: processedItems, - metadata: { - batch_size: processedItems.length, - sdk_integration: this.sdkIntegration, - sdk_version: this.getLibraryVersion(), - sdk_variant: this.getLibraryId(), - public_key: this.publicKey, - sdk_name: "langfuse-js" - } - }); - const url = `${this.baseUrl}/api/public/ingestion`; - const fetchOptions = this._getFetchOptions({ - method: "POST", - body: payload - }); - const requestPromise = this.fetchWithRetry(url, fetchOptions).then(() => done()).catch((err) => { - done(err); - }); - this.pendingIngestionPromises[promiseUUID] = requestPromise; - requestPromise.finally(() => { - delete this.pendingIngestionPromises[promiseUUID]; - }); - } - processQueueItems(queue, MAX_MSG_SIZE, BATCH_SIZE_LIMIT) { - let totalSize = 0; - const processedItems = []; - const remainingItems = []; - for (let i = 0; i < queue.length; i++) { - try { - const itemSize = new Blob([JSON.stringify(queue[i])]).size; - if (itemSize > MAX_MSG_SIZE) { - console.warn(`Item exceeds size limit (size: ${itemSize}), dropping item.`); - continue; - } - if (totalSize + itemSize >= BATCH_SIZE_LIMIT) { - console.debug(`hit batch size limit (size: ${totalSize + itemSize})`); - remainingItems.push(...queue.slice(i)); - console.log(`Remaining items: ${remainingItems.length}`); - console.log(`processes items: ${processedItems.length}`); - break; - } - totalSize += itemSize; - processedItems.push(queue[i]); - } catch (error) { - this._events.emit("error", error); - remainingItems.push(...queue.slice(i)); - break; - } - } - return { - processedItems, - remainingItems - }; - } - _getFetchOptions(p) { - const fetchOptions = { - method: p.method, - headers: { - "Content-Type": "application/json", - "X-Langfuse-Sdk-Name": "langfuse-js", - "X-Langfuse-Sdk-Version": this.getLibraryVersion(), - "X-Langfuse-Sdk-Variant": this.getLibraryId(), - "X-Langfuse-Sdk-Integration": this.sdkIntegration, - "X-Langfuse-Public-Key": this.publicKey, - ...this.additionalHeaders, - ...this.constructAuthorizationHeader(this.publicKey, this.secretKey) - }, - body: p.body, - ...p.fetchTimeout !== void 0 ? { - signal: AbortSignal.timeout(p.fetchTimeout) - } : {} - }; - return fetchOptions; - } - constructAuthorizationHeader(publicKey, secretKey) { - if (secretKey === void 0) { - return { - Authorization: "Bearer " + publicKey - }; - } else { - const encodedCredentials = typeof btoa === "function" ? ( - // btoa() is available, the code is running in a browser or edge environment - btoa(publicKey + ":" + secretKey) - ) : ( - // btoa() is not available, the code is running in Node.js - Buffer.from(publicKey + ":" + secretKey).toString("base64") - ); - return { - Authorization: "Basic " + encodedCredentials - }; - } - } - async fetchWithRetry(url, options, retryOptions) { - AbortSignal.timeout ??= function timeout(ms) { - const ctrl = new AbortController(); - setTimeout(() => ctrl.abort(), ms); - return ctrl.signal; - }; - return await retriable(async () => { - let res = null; - try { - res = await this.fetch(url, { - signal: AbortSignal.timeout(this.requestTimeout), - ...options - }); - } catch (e) { - throw new LangfuseFetchNetworkError(e); - } - if (res.status < 200 || res.status >= 400) { - const body = await res.json(); - throw new LangfuseFetchHttpError(res, JSON.stringify(body)); - } - const returnBody = await res.json(); - if (res.status === 207 && returnBody.errors.length > 0) { - throw new LangfuseFetchHttpError(res, JSON.stringify(returnBody.errors)); - } - return res; - }, { - ...this._retryOptions, - ...retryOptions - }, (string) => this._events.emit("retry", string + ", " + url + ", " + JSON.stringify(options))); - } - async fetchAndLogErrors(url, options) { - const res = await this.fetch(url, options); - const data = res.status === 429 ? await res.text() : await res.json(); - if (res.status < 200 || res.status >= 400) { - logIngestionError(new LangfuseFetchHttpError(res, JSON.stringify(data))); - } - return data; - } - async shutdownAsync() { - clearTimeout(this._flushTimer); - try { - await this.flushAsync(); - await Promise.all(Object.values(this.pendingIngestionPromises).map((x) => x.catch(() => { - }))); - await this.flushAsync(); - } catch (e) { - console.error("[Langfuse SDK] Error while shutting down Langfuse", e); - } - } - async _exportLocalEvents(projectId) { - if (this.isLocalEventExportEnabled) { - clearTimeout(this._flushTimer); - await this.flushAsync(); - const events = this.localEventExportMap.get(projectId) ?? []; - this.localEventExportMap.delete(projectId); - return events; - } else { - this._events.emit("error", "Local event exports are disabled, but _exportLocalEvents() was called."); - return []; - } - } - shutdown() { - console.warn("shutdown() is deprecated. It does not wait for all events to be processed. Please use shutdownAsync() instead."); - void this.shutdownAsync(); - } - async awaitAllQueuedAndPendingRequests() { - clearTimeout(this._flushTimer); - await this.flushAsync(); - await Promise.all(Object.values(this.pendingIngestionPromises)); - } -}; -var LangfuseCore = class extends LangfuseCoreStateless { - constructor(params) { - const { - publicKey, - secretKey, - enabled, - _isLocalEventExportEnabled - } = params; - let isObservabilityEnabled = enabled === false ? false : true; - if (_isLocalEventExportEnabled) { - isObservabilityEnabled = true; - } else if (!secretKey) { - isObservabilityEnabled = false; - if (enabled !== false) { - console.warn("Langfuse secret key was not passed to constructor or not set as 'LANGFUSE_SECRET_KEY' environment variable. No observability data will be sent to Langfuse."); - } - } else if (!publicKey) { - isObservabilityEnabled = false; - if (enabled !== false) { - console.warn("Langfuse public key was not passed to constructor or not set as 'LANGFUSE_PUBLIC_KEY' environment variable. No observability data will be sent to Langfuse."); - } - } - super({ - ...params, - enabled: isObservabilityEnabled - }); - this._promptCache = new LangfusePromptCache(); - } - trace(body) { - const id = this.traceStateless(body ?? {}); - const t = new LangfuseTraceClient(this, id); - if (getEnv("DEFER") && body) { - try { - const deferRuntime = getEnv("__deferRuntime"); - if (deferRuntime) { - deferRuntime.langfuseTraces([{ - id, - name: body.name || "", - url: t.getTraceUrl() - }]); - } - } catch { - } - } - return t; - } - span(body) { - const traceId = body.traceId || this.traceStateless({ - name: body.name - }); - const id = this.spanStateless({ - ...body, - traceId - }); - return new LangfuseSpanClient(this, id, traceId); - } - generation(body) { - const traceId = body.traceId || this.traceStateless({ - name: body.name - }); - const id = this.generationStateless({ - ...body, - traceId - }); - return new LangfuseGenerationClient(this, id, traceId); - } - event(body) { - const traceId = body.traceId || this.traceStateless({ - name: body.name - }); - const id = this.eventStateless({ - ...body, - traceId - }); - return new LangfuseEventClient(this, id, traceId); - } - score(body) { - this.scoreStateless(body); - return this; - } - async getDataset(name, options) { - const dataset = await this._getDataset(name); - const items = []; - let page = 1; - while (true) { - const itemsResponse = await this._getDatasetItems({ - datasetName: name, - limit: options?.fetchItemsPageSize ?? 50, - page - }); - items.push(...itemsResponse.data); - if (itemsResponse.meta.totalPages <= page) { - break; - } - page++; - } - const returnDataset = { - ...dataset, - description: dataset.description ?? void 0, - metadata: dataset.metadata ?? void 0, - items: items.map((item) => ({ - ...item, - link: async (obj, runName, runArgs) => { - await this.awaitAllQueuedAndPendingRequests(); - const data = await this.createDatasetRunItem({ - runName, - datasetItemId: item.id, - observationId: obj.observationId, - traceId: obj.traceId, - runDescription: runArgs?.description, - metadata: runArgs?.metadata - }); - return data; - } - })) - }; - return returnDataset; - } - async createPrompt(body) { - const labels = body.labels ?? []; - const promptResponse = body.type === "chat" ? await this.createPromptStateless({ - ...body, - prompt: body.prompt.map((item) => { - if ("type" in item && item.type === ChatMessageType.Placeholder) { - return { - type: ChatMessageType.Placeholder, - name: item.name - }; - } else { - return { - type: ChatMessageType.ChatMessage, - ...item - }; - } - }), - labels: body.isActive ? [.../* @__PURE__ */ new Set([...labels, "production"])] : labels - // backward compatibility for isActive - }) : await this.createPromptStateless({ - ...body, - type: body.type ?? "text", - labels: body.isActive ? [.../* @__PURE__ */ new Set([...labels, "production"])] : labels - // backward compatibility for isActive - }); - if (promptResponse.type === "chat") { - return new ChatPromptClient(promptResponse); - } - return new TextPromptClient(promptResponse); - } - async updatePrompt(body) { - const newPrompt = await this.updatePromptStateless(body); - this._promptCache.invalidate(body.name); - return newPrompt; - } - async getPrompt(name, version2, options) { - const cacheKey = this._getPromptCacheKey({ - name, - version: version2, - label: options?.label - }); - const cachedPrompt = this._promptCache.getIncludingExpired(cacheKey); - if (!cachedPrompt || options?.cacheTtlSeconds === 0) { - try { - return await this._fetchPromptAndUpdateCache({ - name, - version: version2, - label: options?.label, - cacheTtlSeconds: options?.cacheTtlSeconds, - maxRetries: options?.maxRetries, - fetchTimeout: options?.fetchTimeoutMs - }); - } catch (err) { - if (options?.fallback) { - const sharedFallbackParams = { - name, - version: version2 ?? 0, - labels: options.label ? [options.label] : [], - cacheTtlSeconds: options?.cacheTtlSeconds, - config: {}, - tags: [] - }; - if (options.type === "chat") { - return new ChatPromptClient({ - ...sharedFallbackParams, - type: "chat", - prompt: options.fallback.map((msg) => ({ - type: ChatMessageType.ChatMessage, - ...msg - })) - }, true); - } else { - return new TextPromptClient({ - ...sharedFallbackParams, - type: "text", - prompt: options.fallback - }, true); - } - } - throw err; - } - } - if (cachedPrompt.isExpired) { - if (!this._promptCache.isRefreshing(cacheKey)) { - const refreshPromptPromise = this._fetchPromptAndUpdateCache({ - name, - version: version2, - label: options?.label, - cacheTtlSeconds: options?.cacheTtlSeconds, - maxRetries: options?.maxRetries, - fetchTimeout: options?.fetchTimeoutMs - }).catch(() => { - console.warn(`Failed to refresh prompt cache '${cacheKey}', stale cache will be used until next refresh succeeds.`); - }); - this._promptCache.addRefreshingPromise(cacheKey, refreshPromptPromise); - } - return cachedPrompt.value; - } - return cachedPrompt.value; - } - _getPromptCacheKey(params) { - const { - name, - version: version2, - label - } = params; - const parts = [name]; - if (version2 !== void 0) { - parts.push("version:" + version2.toString()); - } else if (label !== void 0) { - parts.push("label:" + label); - } else { - parts.push("label:production"); - } - return parts.join("-"); - } - async _fetchPromptAndUpdateCache(params) { - const cacheKey = this._getPromptCacheKey(params); - try { - const { - name, - version: version2, - cacheTtlSeconds, - label, - maxRetries, - fetchTimeout - } = params; - const { - data, - fetchResult - } = await this.getPromptStateless(name, version2, label, maxRetries, fetchTimeout); - if (fetchResult === "failure") { - throw Error(data.message ?? "Internal error while fetching prompt"); - } - let prompt2; - if (data.type === "chat") { - prompt2 = new ChatPromptClient(data); - } else { - prompt2 = new TextPromptClient(data); - } - this._promptCache.set(cacheKey, prompt2, cacheTtlSeconds); - return prompt2; - } catch (error) { - console.error(`[Langfuse SDK] Error while fetching prompt '${cacheKey}':`, error); - throw error; - } - } - async fetchMedia(id) { - return await this._fetchMedia(id); - } - /** - * Replaces the media reference strings in an object with base64 data URIs for the media content. - * - * This method recursively traverses an object (up to a maximum depth of 10) looking for media reference strings - * in the format "@@@langfuseMedia:...@@@". When found, it fetches the actual media content using the provided - * Langfuse client and replaces the reference string with a base64 data URI. - * - * If fetching media content fails for a reference string, a warning is logged and the reference string is left unchanged. - * - * @param params - Configuration object - * @param params.obj - The object to process. Can be a primitive value, array, or nested object - * @param params.langfuseClient - Langfuse client instance used to fetch media content - * @param params.resolveWith - The representation of the media content to replace the media reference string with. Currently only "base64DataUri" is supported. - * @param params.maxDepth - Optional. Default is 10. The maximum depth to traverse the object. - * - * @returns A deep copy of the input object with all media references replaced with base64 data URIs where possible - * - * @example - * ```typescript - * const obj = { - * image: "@@@langfuseMedia:type=image/jpeg|id=123|source=bytes@@@", - * nested: { - * pdf: "@@@langfuseMedia:type=application/pdf|id=456|source=bytes@@@" - * } - * }; - * - * const result = await LangfuseMedia.resolveMediaReferences({ - * obj, - * langfuseClient - * }); - * - * // Result: - * // { - * // image: "data:image/jpeg;base64,/9j/4AAQSkZJRg...", - * // nested: { - * // pdf: "data:application/pdf;base64,JVBERi0xLjcK..." - * // } - * // } - * ``` - */ - async resolveMediaReferences(params) { - const { - obj, - ...rest - } = params; - return LangfuseMedia.resolveMediaReferences({ - ...rest, - langfuseClient: this, - obj - }); - } - _updateSpan(body) { - this.updateSpanStateless(body); - return this; - } - _updateGeneration(body) { - this.updateGenerationStateless(body); - return this; - } -}; -var LangfuseObjectClient = class { - constructor({ - client, - id, - traceId, - observationId - }) { - this.client = client; - this.id = id; - this.traceId = traceId; - this.observationId = observationId; - } - event(body) { - return this.client.event({ - ...body, - traceId: this.traceId, - parentObservationId: this.observationId - }); - } - span(body) { - return this.client.span({ - ...body, - traceId: this.traceId, - parentObservationId: this.observationId - }); - } - generation(body) { - return this.client.generation({ - ...body, - traceId: this.traceId, - parentObservationId: this.observationId - }); - } - score(body) { - this.client.score({ - ...body, - traceId: this.traceId, - observationId: this.observationId - }); - return this; - } - getTraceUrl() { - return `${this.client.baseUrl}/trace/${this.traceId}`; - } -}; -var LangfuseTraceClient = class extends LangfuseObjectClient { - constructor(client, traceId) { - super({ - client, - id: traceId, - traceId, - observationId: null - }); - } - update(body) { - this.client.trace({ - ...body, - id: this.id - }); - return this; - } -}; -var LangfuseObservationClient = class extends LangfuseObjectClient { - constructor(client, id, traceId) { - super({ - client, - id, - traceId, - observationId: id - }); - } -}; -var LangfuseSpanClient = class extends LangfuseObservationClient { - constructor(client, id, traceId) { - super(client, id, traceId); - } - update(body) { - this.client._updateSpan({ - ...body, - id: this.id, - traceId: this.traceId - }); - return this; - } - end(body) { - this.client._updateSpan({ - ...body, - id: this.id, - traceId: this.traceId, - endTime: /* @__PURE__ */ new Date() - }); - return this; - } -}; -var LangfuseGenerationClient = class extends LangfuseObservationClient { - constructor(client, id, traceId) { - super(client, id, traceId); - } - update(body) { - this.client._updateGeneration({ - ...body, - id: this.id, - traceId: this.traceId - }); - return this; - } - end(body) { - this.client._updateGeneration({ - ...body, - id: this.id, - traceId: this.traceId, - endTime: /* @__PURE__ */ new Date() - }); - return this; - } -}; -var LangfuseEventClient = class extends LangfuseObservationClient { - constructor(client, id, traceId) { - super(client, id, traceId); - } -}; - -// node_modules/langfuse/lib/index.mjs -var cookieStore = { - getItem(key) { - try { - const nameEQ = key + "="; - const ca = document.cookie.split(";"); - for (let i = 0; i < ca.length; i++) { - let c = ca[i]; - while (c.charAt(0) == " ") { - c = c.substring(1, c.length); - } - if (c.indexOf(nameEQ) === 0) { - return decodeURIComponent(c.substring(nameEQ.length, c.length)); - } - } - } catch (err) { - } - return null; - }, - setItem(key, value) { - try { - const cdomain = "", expires = "", secure = ""; - const new_cookie_val = key + "=" + encodeURIComponent(value) + expires + "; path=/" + cdomain + secure; - document.cookie = new_cookie_val; - } catch (err) { - return; - } - }, - removeItem(name) { - try { - cookieStore.setItem(name, ""); - } catch (err) { - return; - } - }, - clear() { - document.cookie = ""; - }, - getAllKeys() { - const ca = document.cookie.split(";"); - const keys = []; - for (let i = 0; i < ca.length; i++) { - let c = ca[i]; - while (c.charAt(0) == " ") { - c = c.substring(1, c.length); - } - keys.push(c.split("=")[0]); - } - return keys; - } -}; -var createStorageLike = (store) => { - return { - getItem(key) { - return store.getItem(key); - }, - setItem(key, value) { - store.setItem(key, value); - }, - removeItem(key) { - store.removeItem(key); - }, - clear() { - store.clear(); - }, - getAllKeys() { - const keys = []; - for (const key in localStorage) { - keys.push(key); - } - return keys; - } - }; -}; -var checkStoreIsSupported = (storage, key = "__mplssupport__") => { - if (!window) { - return false; - } - try { - const val = "xyz"; - storage.setItem(key, val); - if (storage.getItem(key) !== val) { - return false; - } - storage.removeItem(key); - return true; - } catch (err) { - return false; - } -}; -var localStore = void 0; -var sessionStore = void 0; -var createMemoryStorage = () => { - const _cache = {}; - const store = { - getItem(key) { - return _cache[key]; - }, - setItem(key, value) { - _cache[key] = value !== null ? value : void 0; - }, - removeItem(key) { - delete _cache[key]; - }, - clear() { - for (const key in _cache) { - delete _cache[key]; - } - }, - getAllKeys() { - const keys = []; - for (const key in _cache) { - keys.push(key); - } - return keys; - } - }; - return store; -}; -var getStorage = (type, window2) => { - if (typeof window2 !== void 0 && window2) { - if (!localStorage) { - const _localStore = createStorageLike(window2.localStorage); - localStore = checkStoreIsSupported(_localStore) ? _localStore : void 0; - } - if (!sessionStore) { - const _sessionStore = createStorageLike(window2.sessionStorage); - sessionStore = checkStoreIsSupported(_sessionStore) ? _sessionStore : void 0; - } - } - switch (type) { - case "cookie": - return cookieStore || localStore || sessionStore || createMemoryStorage(); - case "localStorage": - return localStore || sessionStore || createMemoryStorage(); - case "sessionStorage": - return sessionStore || createMemoryStorage(); - case "memory": - return createMemoryStorage(); - default: - return createMemoryStorage(); - } -}; -var ContentType; -(function(ContentType2) { - ContentType2["Json"] = "application/json"; - ContentType2["FormData"] = "multipart/form-data"; - ContentType2["UrlEncoded"] = "application/x-www-form-urlencoded"; - ContentType2["Text"] = "text/plain"; -})(ContentType || (ContentType = {})); -var HttpClient = class { - constructor(apiConfig = {}) { - this.baseUrl = ""; - this.securityData = null; - this.abortControllers = /* @__PURE__ */ new Map(); - this.customFetch = (...fetchParams) => fetch(...fetchParams); - this.baseApiParams = { - credentials: "same-origin", - headers: {}, - redirect: "follow", - referrerPolicy: "no-referrer" - }; - this.setSecurityData = (data) => { - this.securityData = data; - }; - this.contentFormatters = { - [ContentType.Json]: (input) => input !== null && (typeof input === "object" || typeof input === "string") ? JSON.stringify(input) : input, - [ContentType.Text]: (input) => input !== null && typeof input !== "string" ? JSON.stringify(input) : input, - [ContentType.FormData]: (input) => Object.keys(input || {}).reduce((formData, key) => { - const property = input[key]; - formData.append(key, property instanceof Blob ? property : typeof property === "object" && property !== null ? JSON.stringify(property) : `${property}`); - return formData; - }, new FormData()), - [ContentType.UrlEncoded]: (input) => this.toQueryString(input) - }; - this.createAbortSignal = (cancelToken) => { - if (this.abortControllers.has(cancelToken)) { - const abortController2 = this.abortControllers.get(cancelToken); - if (abortController2) { - return abortController2.signal; - } - return void 0; - } - const abortController = new AbortController(); - this.abortControllers.set(cancelToken, abortController); - return abortController.signal; - }; - this.abortRequest = (cancelToken) => { - const abortController = this.abortControllers.get(cancelToken); - if (abortController) { - abortController.abort(); - this.abortControllers.delete(cancelToken); - } - }; - this.request = async ({ - body, - secure, - path, - type, - query, - format, - baseUrl, - cancelToken, - ...params - }) => { - const secureParams = (typeof secure === "boolean" ? secure : this.baseApiParams.secure) && this.securityWorker && await this.securityWorker(this.securityData) || {}; - const requestParams = this.mergeRequestParams(params, secureParams); - const queryString = query && this.toQueryString(query); - const payloadFormatter = this.contentFormatters[type || ContentType.Json]; - const responseFormat = format || requestParams.format; - return this.customFetch(`${baseUrl || this.baseUrl || ""}${path}${queryString ? `?${queryString}` : ""}`, { - ...requestParams, - headers: { - ...requestParams.headers || {}, - ...type && type !== ContentType.FormData ? { - "Content-Type": type - } : {} - }, - signal: (cancelToken ? this.createAbortSignal(cancelToken) : requestParams.signal) || null, - body: typeof body === "undefined" || body === null ? null : payloadFormatter(body) - }).then(async (response) => { - const r = response.clone(); - r.data = null; - r.error = null; - const data = !responseFormat ? r : await response[responseFormat]().then((data2) => { - if (r.ok) { - r.data = data2; - } else { - r.error = data2; - } - return r; - }).catch((e) => { - r.error = e; - return r; - }); - if (cancelToken) { - this.abortControllers.delete(cancelToken); - } - if (!response.ok) throw data; - return data.data; - }); - }; - Object.assign(this, apiConfig); - } - encodeQueryParam(key, value) { - const encodedKey = encodeURIComponent(key); - return `${encodedKey}=${encodeURIComponent(typeof value === "number" ? value : `${value}`)}`; - } - addQueryParam(query, key) { - return this.encodeQueryParam(key, query[key]); - } - addArrayQueryParam(query, key) { - const value = query[key]; - return value.map((v) => this.encodeQueryParam(key, v)).join("&"); - } - toQueryString(rawQuery) { - const query = rawQuery || {}; - const keys = Object.keys(query).filter((key) => "undefined" !== typeof query[key]); - return keys.map((key) => Array.isArray(query[key]) ? this.addArrayQueryParam(query, key) : this.addQueryParam(query, key)).join("&"); - } - addQueryParams(rawQuery) { - const queryString = this.toQueryString(rawQuery); - return queryString ? `?${queryString}` : ""; - } - mergeRequestParams(params1, params2) { - return { - ...this.baseApiParams, - ...params1, - ...params2 || {}, - headers: { - ...this.baseApiParams.headers || {}, - ...params1.headers || {}, - ...params2 && params2.headers || {} - } - }; - } -}; -var LangfusePublicApi = class extends HttpClient { - constructor() { - super(...arguments); - this.api = { - /** - * @description Add an item to an annotation queue - * - * @tags AnnotationQueues - * @name AnnotationQueuesCreateQueueItem - * @request POST:/api/public/annotation-queues/{queueId}/items - * @secure - */ - annotationQueuesCreateQueueItem: (queueId, data, params = {}) => this.request({ - path: `/api/public/annotation-queues/${queueId}/items`, - method: "POST", - body: data, - secure: true, - type: ContentType.Json, - format: "json", - ...params - }), - /** - * @description Remove an item from an annotation queue - * - * @tags AnnotationQueues - * @name AnnotationQueuesDeleteQueueItem - * @request DELETE:/api/public/annotation-queues/{queueId}/items/{itemId} - * @secure - */ - annotationQueuesDeleteQueueItem: (queueId, itemId, params = {}) => this.request({ - path: `/api/public/annotation-queues/${queueId}/items/${itemId}`, - method: "DELETE", - secure: true, - format: "json", - ...params - }), - /** - * @description Get an annotation queue by ID - * - * @tags AnnotationQueues - * @name AnnotationQueuesGetQueue - * @request GET:/api/public/annotation-queues/{queueId} - * @secure - */ - annotationQueuesGetQueue: (queueId, params = {}) => this.request({ - path: `/api/public/annotation-queues/${queueId}`, - method: "GET", - secure: true, - format: "json", - ...params - }), - /** - * @description Get a specific item from an annotation queue - * - * @tags AnnotationQueues - * @name AnnotationQueuesGetQueueItem - * @request GET:/api/public/annotation-queues/{queueId}/items/{itemId} - * @secure - */ - annotationQueuesGetQueueItem: (queueId, itemId, params = {}) => this.request({ - path: `/api/public/annotation-queues/${queueId}/items/${itemId}`, - method: "GET", - secure: true, - format: "json", - ...params - }), - /** - * @description Get items for a specific annotation queue - * - * @tags AnnotationQueues - * @name AnnotationQueuesListQueueItems - * @request GET:/api/public/annotation-queues/{queueId}/items - * @secure - */ - annotationQueuesListQueueItems: ({ - queueId, - ...query - }, params = {}) => this.request({ - path: `/api/public/annotation-queues/${queueId}/items`, - method: "GET", - query, - secure: true, - format: "json", - ...params - }), - /** - * @description Get all annotation queues - * - * @tags AnnotationQueues - * @name AnnotationQueuesListQueues - * @request GET:/api/public/annotation-queues - * @secure - */ - annotationQueuesListQueues: (query, params = {}) => this.request({ - path: `/api/public/annotation-queues`, - method: "GET", - query, - secure: true, - format: "json", - ...params - }), - /** - * @description Update an annotation queue item - * - * @tags AnnotationQueues - * @name AnnotationQueuesUpdateQueueItem - * @request PATCH:/api/public/annotation-queues/{queueId}/items/{itemId} - * @secure - */ - annotationQueuesUpdateQueueItem: (queueId, itemId, data, params = {}) => this.request({ - path: `/api/public/annotation-queues/${queueId}/items/${itemId}`, - method: "PATCH", - body: data, - secure: true, - type: ContentType.Json, - format: "json", - ...params - }), - /** - * @description Create a comment. Comments may be attached to different object types (trace, observation, session, prompt). - * - * @tags Comments - * @name CommentsCreate - * @request POST:/api/public/comments - * @secure - */ - commentsCreate: (data, params = {}) => this.request({ - path: `/api/public/comments`, - method: "POST", - body: data, - secure: true, - type: ContentType.Json, - format: "json", - ...params - }), - /** - * @description Get all comments - * - * @tags Comments - * @name CommentsGet - * @request GET:/api/public/comments - * @secure - */ - commentsGet: (query, params = {}) => this.request({ - path: `/api/public/comments`, - method: "GET", - query, - secure: true, - format: "json", - ...params - }), - /** - * @description Get a comment by id - * - * @tags Comments - * @name CommentsGetById - * @request GET:/api/public/comments/{commentId} - * @secure - */ - commentsGetById: (commentId, params = {}) => this.request({ - path: `/api/public/comments/${commentId}`, - method: "GET", - secure: true, - format: "json", - ...params - }), - /** - * @description Create a dataset item - * - * @tags DatasetItems - * @name DatasetItemsCreate - * @request POST:/api/public/dataset-items - * @secure - */ - datasetItemsCreate: (data, params = {}) => this.request({ - path: `/api/public/dataset-items`, - method: "POST", - body: data, - secure: true, - type: ContentType.Json, - format: "json", - ...params - }), - /** - * @description Delete a dataset item and all its run items. This action is irreversible. - * - * @tags DatasetItems - * @name DatasetItemsDelete - * @request DELETE:/api/public/dataset-items/{id} - * @secure - */ - datasetItemsDelete: (id, params = {}) => this.request({ - path: `/api/public/dataset-items/${id}`, - method: "DELETE", - secure: true, - format: "json", - ...params - }), - /** - * @description Get a dataset item - * - * @tags DatasetItems - * @name DatasetItemsGet - * @request GET:/api/public/dataset-items/{id} - * @secure - */ - datasetItemsGet: (id, params = {}) => this.request({ - path: `/api/public/dataset-items/${id}`, - method: "GET", - secure: true, - format: "json", - ...params - }), - /** - * @description Get dataset items - * - * @tags DatasetItems - * @name DatasetItemsList - * @request GET:/api/public/dataset-items - * @secure - */ - datasetItemsList: (query, params = {}) => this.request({ - path: `/api/public/dataset-items`, - method: "GET", - query, - secure: true, - format: "json", - ...params - }), - /** - * @description Create a dataset run item - * - * @tags DatasetRunItems - * @name DatasetRunItemsCreate - * @request POST:/api/public/dataset-run-items - * @secure - */ - datasetRunItemsCreate: (data, params = {}) => this.request({ - path: `/api/public/dataset-run-items`, - method: "POST", - body: data, - secure: true, - type: ContentType.Json, - format: "json", - ...params - }), - /** - * @description List dataset run items - * - * @tags DatasetRunItems - * @name DatasetRunItemsList - * @request GET:/api/public/dataset-run-items - * @secure - */ - datasetRunItemsList: (query, params = {}) => this.request({ - path: `/api/public/dataset-run-items`, - method: "GET", - query, - secure: true, - ...params - }), - /** - * @description Create a dataset - * - * @tags Datasets - * @name DatasetsCreate - * @request POST:/api/public/v2/datasets - * @secure - */ - datasetsCreate: (data, params = {}) => this.request({ - path: `/api/public/v2/datasets`, - method: "POST", - body: data, - secure: true, - type: ContentType.Json, - format: "json", - ...params - }), - /** - * @description Delete a dataset run and all its run items. This action is irreversible. - * - * @tags Datasets - * @name DatasetsDeleteRun - * @request DELETE:/api/public/datasets/{datasetName}/runs/{runName} - * @secure - */ - datasetsDeleteRun: (datasetName, runName, params = {}) => this.request({ - path: `/api/public/datasets/${datasetName}/runs/${runName}`, - method: "DELETE", - secure: true, - format: "json", - ...params - }), - /** - * @description Get a dataset - * - * @tags Datasets - * @name DatasetsGet - * @request GET:/api/public/v2/datasets/{datasetName} - * @secure - */ - datasetsGet: (datasetName, params = {}) => this.request({ - path: `/api/public/v2/datasets/${datasetName}`, - method: "GET", - secure: true, - format: "json", - ...params - }), - /** - * @description Get a dataset run and its items - * - * @tags Datasets - * @name DatasetsGetRun - * @request GET:/api/public/datasets/{datasetName}/runs/{runName} - * @secure - */ - datasetsGetRun: (datasetName, runName, params = {}) => this.request({ - path: `/api/public/datasets/${datasetName}/runs/${runName}`, - method: "GET", - secure: true, - format: "json", - ...params - }), - /** - * @description Get dataset runs - * - * @tags Datasets - * @name DatasetsGetRuns - * @request GET:/api/public/datasets/{datasetName}/runs - * @secure - */ - datasetsGetRuns: ({ - datasetName, - ...query - }, params = {}) => this.request({ - path: `/api/public/datasets/${datasetName}/runs`, - method: "GET", - query, - secure: true, - format: "json", - ...params - }), - /** - * @description Get all datasets - * - * @tags Datasets - * @name DatasetsList - * @request GET:/api/public/v2/datasets - * @secure - */ - datasetsList: (query, params = {}) => this.request({ - path: `/api/public/v2/datasets`, - method: "GET", - query, - secure: true, - format: "json", - ...params - }), - /** - * @description Check health of API and database - * - * @tags Health - * @name HealthHealth - * @request GET:/api/public/health - */ - healthHealth: (params = {}) => this.request({ - path: `/api/public/health`, - method: "GET", - format: "json", - ...params - }), - /** - * @description Batched ingestion for Langfuse Tracing. If you want to use tracing via the API, such as to build your own Langfuse client implementation, this is the only API route you need to implement. Within each batch, there can be multiple events. Each event has a type, an id, a timestamp, metadata and a body. Internally, we refer to this as the "event envelope" as it tells us something about the event but not the trace. We use the event id within this envelope to deduplicate messages to avoid processing the same event twice, i.e. the event id should be unique per request. The event.body.id is the ID of the actual trace and will be used for updates and will be visible within the Langfuse App. I.e. if you want to update a trace, you'd use the same body id, but separate event IDs. Notes: - Introduction to data model: https://langfuse.com/docs/tracing-data-model - Batch sizes are limited to 3.5 MB in total. You need to adjust the number of events per batch accordingly. - The API does not return a 4xx status code for input errors. Instead, it responds with a 207 status code, which includes a list of the encountered errors. - * - * @tags Ingestion - * @name IngestionBatch - * @request POST:/api/public/ingestion - * @secure - */ - ingestionBatch: (data, params = {}) => this.request({ - path: `/api/public/ingestion`, - method: "POST", - body: data, - secure: true, - type: ContentType.Json, - format: "json", - ...params - }), - /** - * @description Get a media record - * - * @tags Media - * @name MediaGet - * @request GET:/api/public/media/{mediaId} - * @secure - */ - mediaGet: (mediaId, params = {}) => this.request({ - path: `/api/public/media/${mediaId}`, - method: "GET", - secure: true, - format: "json", - ...params - }), - /** - * @description Get a presigned upload URL for a media record - * - * @tags Media - * @name MediaGetUploadUrl - * @request POST:/api/public/media - * @secure - */ - mediaGetUploadUrl: (data, params = {}) => this.request({ - path: `/api/public/media`, - method: "POST", - body: data, - secure: true, - type: ContentType.Json, - format: "json", - ...params - }), - /** - * @description Patch a media record - * - * @tags Media - * @name MediaPatch - * @request PATCH:/api/public/media/{mediaId} - * @secure - */ - mediaPatch: (mediaId, data, params = {}) => this.request({ - path: `/api/public/media/${mediaId}`, - method: "PATCH", - body: data, - secure: true, - type: ContentType.Json, - ...params - }), - /** - * @description Get metrics from the Langfuse project using a query object - * - * @tags Metrics - * @name MetricsMetrics - * @request GET:/api/public/metrics - * @secure - */ - metricsMetrics: (query, params = {}) => this.request({ - path: `/api/public/metrics`, - method: "GET", - query, - secure: true, - format: "json", - ...params - }), - /** - * @description Create a model - * - * @tags Models - * @name ModelsCreate - * @request POST:/api/public/models - * @secure - */ - modelsCreate: (data, params = {}) => this.request({ - path: `/api/public/models`, - method: "POST", - body: data, - secure: true, - type: ContentType.Json, - format: "json", - ...params - }), - /** - * @description Delete a model. Cannot delete models managed by Langfuse. You can create your own definition with the same modelName to override the definition though. - * - * @tags Models - * @name ModelsDelete - * @request DELETE:/api/public/models/{id} - * @secure - */ - modelsDelete: (id, params = {}) => this.request({ - path: `/api/public/models/${id}`, - method: "DELETE", - secure: true, - ...params - }), - /** - * @description Get a model - * - * @tags Models - * @name ModelsGet - * @request GET:/api/public/models/{id} - * @secure - */ - modelsGet: (id, params = {}) => this.request({ - path: `/api/public/models/${id}`, - method: "GET", - secure: true, - format: "json", - ...params - }), - /** - * @description Get all models - * - * @tags Models - * @name ModelsList - * @request GET:/api/public/models - * @secure - */ - modelsList: (query, params = {}) => this.request({ - path: `/api/public/models`, - method: "GET", - query, - secure: true, - format: "json", - ...params - }), - /** - * @description Get a observation - * - * @tags Observations - * @name ObservationsGet - * @request GET:/api/public/observations/{observationId} - * @secure - */ - observationsGet: (observationId, params = {}) => this.request({ - path: `/api/public/observations/${observationId}`, - method: "GET", - secure: true, - format: "json", - ...params - }), - /** - * @description Get a list of observations - * - * @tags Observations - * @name ObservationsGetMany - * @request GET:/api/public/observations - * @secure - */ - observationsGetMany: (query, params = {}) => this.request({ - path: `/api/public/observations`, - method: "GET", - query, - secure: true, - format: "json", - ...params - }), - /** - * @description Get all memberships for the organization associated with the API key (requires organization-scoped API key) - * - * @tags Organizations - * @name OrganizationsGetOrganizationMemberships - * @request GET:/api/public/organizations/memberships - * @secure - */ - organizationsGetOrganizationMemberships: (params = {}) => this.request({ - path: `/api/public/organizations/memberships`, - method: "GET", - secure: true, - format: "json", - ...params - }), - /** - * @description Get all projects for the organization associated with the API key (requires organization-scoped API key) - * - * @tags Organizations - * @name OrganizationsGetOrganizationProjects - * @request GET:/api/public/organizations/projects - * @secure - */ - organizationsGetOrganizationProjects: (params = {}) => this.request({ - path: `/api/public/organizations/projects`, - method: "GET", - secure: true, - format: "json", - ...params - }), - /** - * @description Get all memberships for a specific project (requires organization-scoped API key) - * - * @tags Organizations - * @name OrganizationsGetProjectMemberships - * @request GET:/api/public/projects/{projectId}/memberships - * @secure - */ - organizationsGetProjectMemberships: (projectId, params = {}) => this.request({ - path: `/api/public/projects/${projectId}/memberships`, - method: "GET", - secure: true, - format: "json", - ...params - }), - /** - * @description Create or update a membership for the organization associated with the API key (requires organization-scoped API key) - * - * @tags Organizations - * @name OrganizationsUpdateOrganizationMembership - * @request PUT:/api/public/organizations/memberships - * @secure - */ - organizationsUpdateOrganizationMembership: (data, params = {}) => this.request({ - path: `/api/public/organizations/memberships`, - method: "PUT", - body: data, - secure: true, - type: ContentType.Json, - format: "json", - ...params - }), - /** - * @description Create or update a membership for a specific project (requires organization-scoped API key). The user must already be a member of the organization. - * - * @tags Organizations - * @name OrganizationsUpdateProjectMembership - * @request PUT:/api/public/projects/{projectId}/memberships - * @secure - */ - organizationsUpdateProjectMembership: (projectId, data, params = {}) => this.request({ - path: `/api/public/projects/${projectId}/memberships`, - method: "PUT", - body: data, - secure: true, - type: ContentType.Json, - format: "json", - ...params - }), - /** - * @description Create a new project (requires organization-scoped API key) - * - * @tags Projects - * @name ProjectsCreate - * @request POST:/api/public/projects - * @secure - */ - projectsCreate: (data, params = {}) => this.request({ - path: `/api/public/projects`, - method: "POST", - body: data, - secure: true, - type: ContentType.Json, - format: "json", - ...params - }), - /** - * @description Create a new API key for a project (requires organization-scoped API key) - * - * @tags Projects - * @name ProjectsCreateApiKey - * @request POST:/api/public/projects/{projectId}/apiKeys - * @secure - */ - projectsCreateApiKey: (projectId, data, params = {}) => this.request({ - path: `/api/public/projects/${projectId}/apiKeys`, - method: "POST", - body: data, - secure: true, - type: ContentType.Json, - format: "json", - ...params - }), - /** - * @description Delete a project by ID (requires organization-scoped API key). Project deletion is processed asynchronously. - * - * @tags Projects - * @name ProjectsDelete - * @request DELETE:/api/public/projects/{projectId} - * @secure - */ - projectsDelete: (projectId, params = {}) => this.request({ - path: `/api/public/projects/${projectId}`, - method: "DELETE", - secure: true, - format: "json", - ...params - }), - /** - * @description Delete an API key for a project (requires organization-scoped API key) - * - * @tags Projects - * @name ProjectsDeleteApiKey - * @request DELETE:/api/public/projects/{projectId}/apiKeys/{apiKeyId} - * @secure - */ - projectsDeleteApiKey: (projectId, apiKeyId, params = {}) => this.request({ - path: `/api/public/projects/${projectId}/apiKeys/${apiKeyId}`, - method: "DELETE", - secure: true, - format: "json", - ...params - }), - /** - * @description Get Project associated with API key - * - * @tags Projects - * @name ProjectsGet - * @request GET:/api/public/projects - * @secure - */ - projectsGet: (params = {}) => this.request({ - path: `/api/public/projects`, - method: "GET", - secure: true, - format: "json", - ...params - }), - /** - * @description Get all API keys for a project (requires organization-scoped API key) - * - * @tags Projects - * @name ProjectsGetApiKeys - * @request GET:/api/public/projects/{projectId}/apiKeys - * @secure - */ - projectsGetApiKeys: (projectId, params = {}) => this.request({ - path: `/api/public/projects/${projectId}/apiKeys`, - method: "GET", - secure: true, - format: "json", - ...params - }), - /** - * @description Update a project by ID (requires organization-scoped API key). - * - * @tags Projects - * @name ProjectsUpdate - * @request PUT:/api/public/projects/{projectId} - * @secure - */ - projectsUpdate: (projectId, data, params = {}) => this.request({ - path: `/api/public/projects/${projectId}`, - method: "PUT", - body: data, - secure: true, - type: ContentType.Json, - format: "json", - ...params - }), - /** - * @description Create a new version for the prompt with the given `name` - * - * @tags Prompts - * @name PromptsCreate - * @request POST:/api/public/v2/prompts - * @secure - */ - promptsCreate: (data, params = {}) => this.request({ - path: `/api/public/v2/prompts`, - method: "POST", - body: data, - secure: true, - type: ContentType.Json, - format: "json", - ...params - }), - /** - * @description Get a prompt - * - * @tags Prompts - * @name PromptsGet - * @request GET:/api/public/v2/prompts/{promptName} - * @secure - */ - promptsGet: ({ - promptName, - ...query - }, params = {}) => this.request({ - path: `/api/public/v2/prompts/${promptName}`, - method: "GET", - query, - secure: true, - format: "json", - ...params - }), - /** - * @description Get a list of prompt names with versions and labels - * - * @tags Prompts - * @name PromptsList - * @request GET:/api/public/v2/prompts - * @secure - */ - promptsList: (query, params = {}) => this.request({ - path: `/api/public/v2/prompts`, - method: "GET", - query, - secure: true, - format: "json", - ...params - }), - /** - * @description Update labels for a specific prompt version - * - * @tags PromptVersion - * @name PromptVersionUpdate - * @request PATCH:/api/public/v2/prompts/{name}/versions/{version} - * @secure - */ - promptVersionUpdate: (name, version2, data, params = {}) => this.request({ - path: `/api/public/v2/prompts/${name}/versions/${version2}`, - method: "PATCH", - body: data, - secure: true, - type: ContentType.Json, - format: "json", - ...params - }), - /** - * @description Create a new user in the organization (requires organization-scoped API key) - * - * @tags Scim - * @name ScimCreateUser - * @request POST:/api/public/scim/Users - * @secure - */ - scimCreateUser: (data, params = {}) => this.request({ - path: `/api/public/scim/Users`, - method: "POST", - body: data, - secure: true, - type: ContentType.Json, - format: "json", - ...params - }), - /** - * @description Remove a user from the organization (requires organization-scoped API key). Note that this only removes the user from the organization but does not delete the user entity itself. - * - * @tags Scim - * @name ScimDeleteUser - * @request DELETE:/api/public/scim/Users/{userId} - * @secure - */ - scimDeleteUser: (userId, params = {}) => this.request({ - path: `/api/public/scim/Users/${userId}`, - method: "DELETE", - secure: true, - format: "json", - ...params - }), - /** - * @description Get SCIM Resource Types (requires organization-scoped API key) - * - * @tags Scim - * @name ScimGetResourceTypes - * @request GET:/api/public/scim/ResourceTypes - * @secure - */ - scimGetResourceTypes: (params = {}) => this.request({ - path: `/api/public/scim/ResourceTypes`, - method: "GET", - secure: true, - format: "json", - ...params - }), - /** - * @description Get SCIM Schemas (requires organization-scoped API key) - * - * @tags Scim - * @name ScimGetSchemas - * @request GET:/api/public/scim/Schemas - * @secure - */ - scimGetSchemas: (params = {}) => this.request({ - path: `/api/public/scim/Schemas`, - method: "GET", - secure: true, - format: "json", - ...params - }), - /** - * @description Get SCIM Service Provider Configuration (requires organization-scoped API key) - * - * @tags Scim - * @name ScimGetServiceProviderConfig - * @request GET:/api/public/scim/ServiceProviderConfig - * @secure - */ - scimGetServiceProviderConfig: (params = {}) => this.request({ - path: `/api/public/scim/ServiceProviderConfig`, - method: "GET", - secure: true, - format: "json", - ...params - }), - /** - * @description Get a specific user by ID (requires organization-scoped API key) - * - * @tags Scim - * @name ScimGetUser - * @request GET:/api/public/scim/Users/{userId} - * @secure - */ - scimGetUser: (userId, params = {}) => this.request({ - path: `/api/public/scim/Users/${userId}`, - method: "GET", - secure: true, - format: "json", - ...params - }), - /** - * @description List users in the organization (requires organization-scoped API key) - * - * @tags Scim - * @name ScimListUsers - * @request GET:/api/public/scim/Users - * @secure - */ - scimListUsers: (query, params = {}) => this.request({ - path: `/api/public/scim/Users`, - method: "GET", - query, - secure: true, - format: "json", - ...params - }), - /** - * @description Create a score configuration (config). Score configs are used to define the structure of scores - * - * @tags ScoreConfigs - * @name ScoreConfigsCreate - * @request POST:/api/public/score-configs - * @secure - */ - scoreConfigsCreate: (data, params = {}) => this.request({ - path: `/api/public/score-configs`, - method: "POST", - body: data, - secure: true, - type: ContentType.Json, - format: "json", - ...params - }), - /** - * @description Get all score configs - * - * @tags ScoreConfigs - * @name ScoreConfigsGet - * @request GET:/api/public/score-configs - * @secure - */ - scoreConfigsGet: (query, params = {}) => this.request({ - path: `/api/public/score-configs`, - method: "GET", - query, - secure: true, - format: "json", - ...params - }), - /** - * @description Get a score config - * - * @tags ScoreConfigs - * @name ScoreConfigsGetById - * @request GET:/api/public/score-configs/{configId} - * @secure - */ - scoreConfigsGetById: (configId, params = {}) => this.request({ - path: `/api/public/score-configs/${configId}`, - method: "GET", - secure: true, - format: "json", - ...params - }), - /** - * @description Create a score (supports both trace and session scores) - * - * @tags Score - * @name ScoreCreate - * @request POST:/api/public/scores - * @secure - */ - scoreCreate: (data, params = {}) => this.request({ - path: `/api/public/scores`, - method: "POST", - body: data, - secure: true, - type: ContentType.Json, - format: "json", - ...params - }), - /** - * @description Delete a score (supports both trace and session scores) - * - * @tags Score - * @name ScoreDelete - * @request DELETE:/api/public/scores/{scoreId} - * @secure - */ - scoreDelete: (scoreId, params = {}) => this.request({ - path: `/api/public/scores/${scoreId}`, - method: "DELETE", - secure: true, - ...params - }), - /** - * @description Get a list of scores (supports both trace and session scores) - * - * @tags ScoreV2 - * @name ScoreV2Get - * @request GET:/api/public/v2/scores - * @secure - */ - scoreV2Get: (query, params = {}) => this.request({ - path: `/api/public/v2/scores`, - method: "GET", - query, - secure: true, - format: "json", - ...params - }), - /** - * @description Get a score (supports both trace and session scores) - * - * @tags ScoreV2 - * @name ScoreV2GetById - * @request GET:/api/public/v2/scores/{scoreId} - * @secure - */ - scoreV2GetById: (scoreId, params = {}) => this.request({ - path: `/api/public/v2/scores/${scoreId}`, - method: "GET", - secure: true, - format: "json", - ...params - }), - /** - * @description Get a session. Please note that `traces` on this endpoint are not paginated, if you plan to fetch large sessions, consider `GET /api/public/traces?sessionId=` - * - * @tags Sessions - * @name SessionsGet - * @request GET:/api/public/sessions/{sessionId} - * @secure - */ - sessionsGet: (sessionId2, params = {}) => this.request({ - path: `/api/public/sessions/${sessionId2}`, - method: "GET", - secure: true, - format: "json", - ...params - }), - /** - * @description Get sessions - * - * @tags Sessions - * @name SessionsList - * @request GET:/api/public/sessions - * @secure - */ - sessionsList: (query, params = {}) => this.request({ - path: `/api/public/sessions`, - method: "GET", - query, - secure: true, - format: "json", - ...params - }), - /** - * @description Delete a specific trace - * - * @tags Trace - * @name TraceDelete - * @request DELETE:/api/public/traces/{traceId} - * @secure - */ - traceDelete: (traceId, params = {}) => this.request({ - path: `/api/public/traces/${traceId}`, - method: "DELETE", - secure: true, - format: "json", - ...params - }), - /** - * @description Delete multiple traces - * - * @tags Trace - * @name TraceDeleteMultiple - * @request DELETE:/api/public/traces - * @secure - */ - traceDeleteMultiple: (data, params = {}) => this.request({ - path: `/api/public/traces`, - method: "DELETE", - body: data, - secure: true, - type: ContentType.Json, - format: "json", - ...params - }), - /** - * @description Get a specific trace - * - * @tags Trace - * @name TraceGet - * @request GET:/api/public/traces/{traceId} - * @secure - */ - traceGet: (traceId, params = {}) => this.request({ - path: `/api/public/traces/${traceId}`, - method: "GET", - secure: true, - format: "json", - ...params - }), - /** - * @description Get list of traces - * - * @tags Trace - * @name TraceList - * @request GET:/api/public/traces - * @secure - */ - traceList: (query, params = {}) => this.request({ - path: `/api/public/traces`, - method: "GET", - query, - secure: true, - format: "json", - ...params - }) - }; - } -}; -var version = "3.38.20"; -var Langfuse = class extends LangfuseCore { - constructor(params) { - const langfuseConfig = utils.configLangfuseSDK(params); - super(langfuseConfig); - if (typeof window !== "undefined" && "Deno" in window === false) { - this._storageKey = params?.persistence_name ? `lf_${params.persistence_name}` : `lf_${langfuseConfig.publicKey}_langfuse`; - this._storage = getStorage(params?.persistence || "localStorage", window); - } else { - this._storageKey = `lf_${langfuseConfig.publicKey}_langfuse`; - this._storage = getStorage("memory", void 0); - } - this.api = new LangfusePublicApi({ - baseUrl: this.baseUrl, - baseApiParams: { - headers: { - "X-Langfuse-Sdk-Name": "langfuse-js", - "X-Langfuse-Sdk-Version": this.getLibraryVersion(), - "X-Langfuse-Sdk-Variant": this.getLibraryId(), - "X-Langfuse-Sdk-Integration": this.sdkIntegration, - "X-Langfuse-Public-Key": this.publicKey, - ...this.additionalHeaders, - ...this.constructAuthorizationHeader(this.publicKey, this.secretKey) - } - } - }).api; - } - getPersistedProperty(key) { - if (!this._storageCache) { - this._storageCache = JSON.parse(this._storage.getItem(this._storageKey) || "{}") || {}; - } - return this._storageCache[key]; - } - setPersistedProperty(key, value) { - if (!this._storageCache) { - this._storageCache = JSON.parse(this._storage.getItem(this._storageKey) || "{}") || {}; - } - if (value === null) { - delete this._storageCache[key]; - } else { - this._storageCache[key] = value; - } - this._storage.setItem(this._storageKey, JSON.stringify(this._storageCache)); - } - fetch(url, options) { - return fetch(url, options); - } - getLibraryId() { - return "langfuse"; - } - getLibraryVersion() { - return version; - } - getCustomUserAgent() { - return; - } -}; -var LangfuseSingleton = class _LangfuseSingleton { - /** - * Returns the singleton instance of the Langfuse client. - * @param params Optional parameters for initializing the Langfuse instance. Only used for the first call. - * @returns The singleton instance of the Langfuse client. - */ - static getInstance(params) { - if (!_LangfuseSingleton.instance) { - _LangfuseSingleton.instance = new Langfuse(params); - } - return _LangfuseSingleton.instance; - } -}; -LangfuseSingleton.instance = null; - -// src/adapters/langfuse-sink.ts -var SDK_INTEGRATION = "zcode-langfuse-observability"; -var TRACE_NAME = "ZCode Turn"; -var LangfuseTraceSink = class { - constructor(config) { - this.config = config; - } - async publishTurn(turn) { - if (!this.config.publicKey || !this.config.secretKey) return; - const client = new Langfuse({ - publicKey: this.config.publicKey, - secretKey: this.config.secretKey, - baseUrl: this.config.baseUrl, - release: this.config.release, - environment: this.config.environment, - sdkIntegration: SDK_INTEGRATION, - flushAt: 1, - fetchRetryCount: 1, - requestTimeout: 8e3 - }); - try { - const traceInput = { - name: TRACE_NAME, - sessionId: turn.sessionId, - input: turn.prompt, - metadata: { - source: "zcode", - turnId: turn.turnId, - toolCount: turn.tools.length - }, - tags: ["zcode", "zcode-hook"] - }; - if (this.config.userId) traceInput.userId = this.config.userId; - const trace = client.trace(traceInput); - for (const tool of turn.tools) { - const span = trace.span({ - name: `tool.${tool.name}`, - input: tool.input, - metadata: { - toolId: tool.id, - startedAt: tool.startedAt, - endedAt: tool.endedAt - } - }); - if (tool.error) { - span.end({ - output: { error: tool.error }, - level: "ERROR", - statusMessage: tool.error - }); - } else { - span.end({ output: tool.output }); - } - } - const generation = trace.generation({ - name: "zcode.assistant", - input: turn.prompt, - metadata: { - turnId: turn.turnId - } - }); - generation.end({ output: turn.assistantMessage }); - trace.update({ - output: turn.assistantMessage, - metadata: { - source: "zcode", - turnId: turn.turnId, - toolCount: turn.tools.length - } - }); - await client.flushAsync(); - } finally { - await client.shutdownAsync(); - } - } -}; - -// src/hooks/entry.ts -function isRecord2(value) { - return typeof value === "object" && value !== null && !Array.isArray(value); -} -function parsePayload(raw) { - if (!raw.trim()) return {}; - try { - const parsed = JSON.parse(raw); - return isRecord2(parsed) ? parsed : {}; - } catch { - return {}; - } -} -async function readStdin() { - let raw = ""; - process.stdin.setEncoding("utf8"); - for await (const chunk of process.stdin) raw += chunk; - return raw; -} -function diagnostics(enabled, message) { - if (enabled) process.stderr.write(`[langfuse-observability] ${message} -`); -} -async function main() { - const config = readConfig(); - const payload = parsePayload(await readStdin()); - const event = eventName(payload) ?? "unknown"; - const session = sessionId(payload) ?? "?"; - diagnostics(config.debug, `event=${event} session=${session}`); - const sink = isConfigured(config) ? new LangfuseTraceSink(config) : new NoopTraceSink(); - const tracker = new TurnTracker( - new JsonStateStore(), - sink, - config, - { now: () => /* @__PURE__ */ new Date() }, - { next: randomUUID2 } - ); - try { - await tracker.handle(payload); - } catch (error) { - const kind = error instanceof Error ? error.name : "unknown"; - diagnostics(config.debug, `hook failed open (${kind})`); - } - process.stdout.write("{}\n"); -} -await main(); -/*! Bundled license information: - -mustache/mustache.mjs: - (*! - * mustache.js - Logic-less {{mustache}} templates with JavaScript - * http://github.com/janl/mustache.js - *) -*/ -//# sourceMappingURL=entry.mjs.map diff --git a/plugins/langfuse-observability/payload/dist/hooks/entry.mjs.map b/plugins/langfuse-observability/payload/dist/hooks/entry.mjs.map deleted file mode 100644 index e7e73d5..0000000 --- a/plugins/langfuse-observability/payload/dist/hooks/entry.mjs.map +++ /dev/null @@ -1,7 +0,0 @@ -{ - "version": 3, - "sources": ["../../src/hooks/entry.ts", "../../src/application/config.ts", "../../src/domain/extract.ts", "../../src/application/turn-tracker.ts", "../../src/adapters/json-state-store.ts", "../../node_modules/mustache/mustache.mjs", "../../node_modules/langfuse-core/src/eventemitter.ts", "../../node_modules/langfuse-core/src/prompts/promptCache.ts", "../../node_modules/langfuse-core/src/types.ts", "../../node_modules/langfuse-core/src/prompts/promptClients.ts", "../../node_modules/langfuse-core/src/utils.ts", "../../node_modules/langfuse-core/src/release-env.ts", "../../node_modules/langfuse-core/src/media/LangfuseMedia.ts", "../../node_modules/langfuse-core/src/sampling.ts", "../../node_modules/langfuse-core/src/storage-memory.ts", "../../node_modules/langfuse-core/src/index.ts", "../../node_modules/langfuse/src/storage.ts", "../../node_modules/langfuse/src/publicApi.ts", "../../node_modules/langfuse/src/langfuse.ts", "../../node_modules/langfuse/src/openai/LangfuseSingleton.ts", "../../node_modules/langfuse/src/openai/parseOpenAI.ts", "../../node_modules/langfuse/src/openai/utils.ts", "../../node_modules/langfuse/src/openai/traceMethod.ts", "../../node_modules/langfuse/src/openai/observeOpenAI.ts", "../../src/adapters/langfuse-sink.ts"], - "sourcesContent": ["import { randomUUID } from \"node:crypto\";\nimport { readConfig, isConfigured } from \"../application/config.js\";\nimport { NoopTraceSink, TurnTracker } from \"../application/turn-tracker.js\";\nimport { JsonStateStore } from \"../adapters/json-state-store.js\";\nimport { LangfuseTraceSink } from \"../adapters/langfuse-sink.js\";\nimport { eventName, sessionId } from \"../domain/extract.js\";\nimport type { Clock, HookPayload, IdGenerator } from \"../domain/types.js\";\n\nfunction isRecord(value: unknown): value is Record {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction parsePayload(raw: string): HookPayload {\n if (!raw.trim()) return {};\n try {\n const parsed: unknown = JSON.parse(raw);\n return isRecord(parsed) ? (parsed as HookPayload) : {};\n } catch {\n return {};\n }\n}\n\nasync function readStdin(): Promise {\n let raw = \"\";\n process.stdin.setEncoding(\"utf8\");\n for await (const chunk of process.stdin) raw += chunk;\n return raw;\n}\n\nfunction diagnostics(enabled: boolean, message: string): void {\n if (enabled) process.stderr.write(`[langfuse-observability] ${message}\\n`);\n}\n\nasync function main(): Promise {\n const config = readConfig();\n const payload = parsePayload(await readStdin());\n const event = eventName(payload) ?? \"unknown\";\n const session = sessionId(payload) ?? \"?\";\n\n diagnostics(config.debug, `event=${event} session=${session}`);\n\n const sink = isConfigured(config)\n ? new LangfuseTraceSink(config)\n : new NoopTraceSink();\n const tracker = new TurnTracker(\n new JsonStateStore(),\n sink,\n config,\n { now: () => new Date() } satisfies Clock,\n { next: randomUUID } satisfies IdGenerator,\n );\n\n try {\n await tracker.handle(payload);\n } catch (error) {\n const kind = error instanceof Error ? error.name : \"unknown\";\n diagnostics(config.debug, `hook failed open (${kind})`);\n }\n\n // Hooks must always emit one JSON object. Observability has no context to add.\n process.stdout.write(\"{}\\n\");\n}\n\nawait main();\n", "import { readFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { HookConfig } from \"../domain/types.js\";\n\nconst DEFAULT_BASE_URL = \"https://cloud.langfuse.com\";\nconst DEFAULT_RELEASE = \"0.1.0\";\nconst DEFAULT_MAX_CAPTURE_CHARS = 20_000;\n\nexport type StoredOption = string | number | boolean;\nexport type StoredOptions = Record;\ntype ConfigValue = StoredOption | undefined;\n\nfunction asText(value: ConfigValue): string | undefined {\n if (value === undefined) return undefined;\n return typeof value === \"string\" ? value : String(value);\n}\n\nfunction firstNonEmpty(...values: ConfigValue[]): string | undefined {\n return values\n .map(asText)\n .find((value) => value !== undefined && value.trim().length > 0)\n ?.trim();\n}\n\nfunction userConfig(env: NodeJS.ProcessEnv, name: string): string | undefined {\n const normalized = name.toUpperCase();\n return firstNonEmpty(\n env[`ZCODE_USER_CONFIG_${normalized}`],\n env[`ZCODE_PLUGIN_CONFIG_${normalized}`],\n );\n}\n\nfunction isStoredOption(value: unknown): value is StoredOption {\n return (\n typeof value === \"string\" ||\n typeof value === \"number\" ||\n typeof value === \"boolean\"\n );\n}\n\nfunction parseStoredOptions(value: unknown): StoredOptions {\n if (typeof value !== \"object\" || value === null || Array.isArray(value))\n return {};\n const options: StoredOptions = {};\n for (const [key, option] of Object.entries(value)) {\n if (isStoredOption(option)) options[key] = option;\n }\n return options;\n}\n\nfunction readStoredOptions(env: NodeJS.ProcessEnv): StoredOptions {\n const configPaths = [\n env.ZCODE_CONFIG_PATH,\n join(homedir(), \".zcode\", \"cli\", \"config.json\"),\n ].filter((path): path is string => Boolean(path));\n const configuredPluginId = env.ZCODE_PLUGIN_ID;\n\n for (const configPath of configPaths) {\n try {\n const parsed: unknown = JSON.parse(readFileSync(configPath, \"utf8\"));\n if (\n typeof parsed !== \"object\" ||\n parsed === null ||\n Array.isArray(parsed)\n )\n continue;\n const plugins = (parsed as Record).plugins;\n if (\n typeof plugins !== \"object\" ||\n plugins === null ||\n Array.isArray(plugins)\n )\n continue;\n const options = (plugins as Record).options;\n if (\n typeof options !== \"object\" ||\n options === null ||\n Array.isArray(options)\n )\n continue;\n const optionEntries = options as Record;\n const pluginId =\n configuredPluginId && optionEntries[configuredPluginId]\n ? configuredPluginId\n : Object.keys(optionEntries).find((key) =>\n key.startsWith(\"langfuse-observability@\"),\n );\n if (pluginId) return parseStoredOptions(optionEntries[pluginId]);\n } catch {\n // A missing or unreadable user config must not block a ZCode hook.\n }\n }\n return {};\n}\n\nfunction option(\n env: NodeJS.ProcessEnv,\n storedOptions: StoredOptions,\n name: string,\n environmentName: string,\n): string | undefined {\n return firstNonEmpty(\n userConfig(env, name),\n storedOptions[name],\n env[environmentName],\n );\n}\n\nfunction parseBoolean(value: string | undefined, fallback: boolean): boolean {\n if (!value) return fallback;\n if ([\"1\", \"true\", \"yes\", \"on\"].includes(value.toLowerCase())) return true;\n if ([\"0\", \"false\", \"no\", \"off\"].includes(value.toLowerCase())) return false;\n return fallback;\n}\n\nfunction parsePositiveInteger(\n value: string | undefined,\n fallback: number,\n): number {\n if (!value) return fallback;\n const parsed = Number.parseInt(value, 10);\n if (!Number.isFinite(parsed) || parsed <= 0) return fallback;\n return Math.min(parsed, 1_000_000);\n}\n\nfunction normalizeBaseUrl(value: string | undefined): string {\n const candidate = value ?? DEFAULT_BASE_URL;\n try {\n const url = new URL(candidate);\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\")\n return DEFAULT_BASE_URL;\n return candidate.replace(/\\/+$/, \"\");\n } catch {\n return DEFAULT_BASE_URL;\n }\n}\n\nexport function readConfig(\n env: NodeJS.ProcessEnv = process.env,\n storedOptions: StoredOptions = readStoredOptions(env),\n): HookConfig {\n const enabled = parseBoolean(\n option(env, storedOptions, \"enabled\", \"LANGFUSE_ENABLED\"),\n true,\n );\n const publicKey =\n option(env, storedOptions, \"langfuse_public_key\", \"LANGFUSE_PUBLIC_KEY\") ??\n null;\n const secretKey =\n option(env, storedOptions, \"langfuse_secret_key\", \"LANGFUSE_SECRET_KEY\") ??\n null;\n\n return {\n publicKey,\n secretKey,\n baseUrl: normalizeBaseUrl(\n option(env, storedOptions, \"langfuse_base_url\", \"LANGFUSE_BASE_URL\"),\n ),\n userId:\n option(env, storedOptions, \"langfuse_user_id\", \"LANGFUSE_USER_ID\") ??\n null,\n environment:\n option(\n env,\n storedOptions,\n \"langfuse_environment\",\n \"LANGFUSE_ENVIRONMENT\",\n ) ?? \"development\",\n release:\n option(env, storedOptions, \"langfuse_release\", \"LANGFUSE_RELEASE\") ??\n DEFAULT_RELEASE,\n enabled,\n capturePrompts: parseBoolean(\n option(env, storedOptions, \"capture_prompts\", \"LANGFUSE_CAPTURE_PROMPTS\"),\n true,\n ),\n captureToolInputs: parseBoolean(\n option(\n env,\n storedOptions,\n \"capture_tool_inputs\",\n \"LANGFUSE_CAPTURE_TOOL_INPUTS\",\n ),\n true,\n ),\n captureToolOutputs: parseBoolean(\n option(\n env,\n storedOptions,\n \"capture_tool_outputs\",\n \"LANGFUSE_CAPTURE_TOOL_OUTPUTS\",\n ),\n true,\n ),\n maxCaptureChars: parsePositiveInteger(\n option(\n env,\n storedOptions,\n \"max_capture_chars\",\n \"LANGFUSE_MAX_CAPTURE_CHARS\",\n ),\n DEFAULT_MAX_CAPTURE_CHARS,\n ),\n debug: parseBoolean(\n option(env, storedOptions, \"debug\", \"LANGFUSE_DEBUG\"),\n false,\n ),\n };\n}\n\nexport function isConfigured(config: HookConfig): boolean {\n return config.enabled && Boolean(config.publicKey && config.secretKey);\n}\n", "import type { HookEventName, HookPayload, JsonValue } from \"./types.js\";\n\nfunction toJsonValue(value: unknown): JsonValue | undefined {\n if (\n value === null ||\n typeof value === \"string\" ||\n typeof value === \"number\" ||\n typeof value === \"boolean\"\n ) {\n return value;\n }\n if (Array.isArray(value)) {\n const values = value.map(toJsonValue);\n return values.every((item): item is JsonValue => item !== undefined)\n ? values\n : undefined;\n }\n if (typeof value === \"object\") {\n const entries = Object.entries(value as Record);\n const result: { [key: string]: JsonValue } = {};\n for (const [key, item] of entries) {\n const parsed = toJsonValue(item);\n if (parsed === undefined) return undefined;\n result[key] = parsed;\n }\n return result;\n }\n return undefined;\n}\n\nfunction valueAt(\n payload: HookPayload,\n ...keys: string[]\n): JsonValue | undefined {\n for (const key of keys) {\n const value = toJsonValue(payload[key]);\n if (value !== undefined && value !== null) return value;\n }\n return undefined;\n}\n\nexport function asString(value: JsonValue | undefined): string | null {\n if (typeof value === \"string\") return value;\n if (typeof value === \"number\" || typeof value === \"boolean\")\n return String(value);\n return null;\n}\n\nexport function stringifyValue(value: JsonValue | undefined): string {\n if (typeof value === \"string\") return value;\n if (value === undefined) return \"\";\n const json = JSON.stringify(value);\n return json ?? String(value);\n}\n\nexport function eventName(payload: HookPayload): HookEventName | null {\n return asString(\n valueAt(payload, \"hook_event_name\", \"hookEventName\", \"event\", \"event_name\"),\n ) as HookEventName | null;\n}\n\nexport function sessionId(payload: HookPayload): string | null {\n const value = asString(valueAt(payload, \"session_id\", \"sessionId\"));\n return value?.trim() || null;\n}\n\nexport function prompt(payload: HookPayload): string | null {\n const value = valueAt(\n payload,\n \"prompt\",\n \"user_prompt\",\n \"userPrompt\",\n \"message\",\n );\n return value === undefined ? null : stringifyValue(value);\n}\n\nexport function assistantMessage(payload: HookPayload): string | null {\n const value = valueAt(\n payload,\n \"last_assistant_message\",\n \"lastAssistantMessage\",\n );\n return value === undefined ? null : stringifyValue(value);\n}\n\nexport function toolId(payload: HookPayload): string | null {\n const value = asString(\n valueAt(payload, \"tool_use_id\", \"toolUseId\", \"tool_id\", \"toolId\", \"id\"),\n );\n return value?.trim() || null;\n}\n\nexport function toolName(payload: HookPayload): string {\n return (\n asString(\n valueAt(payload, \"tool_name\", \"toolName\", \"name\", \"tool\"),\n )?.trim() ?? \"unknown\"\n );\n}\n\nexport function toolInput(payload: HookPayload): JsonValue {\n return (\n valueAt(payload, \"tool_input\", \"toolInput\", \"input\", \"arguments\") ?? null\n );\n}\n\nexport function toolOutput(payload: HookPayload): JsonValue {\n return (\n valueAt(\n payload,\n \"tool_output\",\n \"toolOutput\",\n \"output\",\n \"result\",\n \"tool_response\",\n ) ?? null\n );\n}\n\nexport function toolError(payload: HookPayload): string {\n return stringifyValue(\n valueAt(payload, \"error\", \"error_message\", \"errorMessage\", \"message\") ??\n \"Tool failed\",\n );\n}\n", "import {\n assistantMessage,\n eventName,\n prompt,\n sessionId,\n toolError,\n toolId,\n toolInput,\n toolName,\n toolOutput,\n} from \"../domain/extract.js\";\nimport type {\n Clock,\n CompletedTurn,\n HookConfig,\n HookPayload,\n IdGenerator,\n JsonValue,\n SessionState,\n StateStore,\n ToolCall,\n TraceSink,\n TurnState,\n} from \"../domain/types.js\";\n\nfunction createSession(session: string, now: string): SessionState {\n return {\n version: 1,\n sessionId: session,\n startedAt: now,\n updatedAt: now,\n currentTurn: null,\n };\n}\n\nfunction createTurn(\n id: string,\n now: string,\n userPrompt: string | null,\n): TurnState {\n return {\n id,\n prompt: userPrompt,\n startedAt: now,\n tools: [],\n };\n}\n\nfunction truncate(value: string, maxChars: number): string {\n if (value.length <= maxChars) return value;\n return `${value.slice(0, Math.max(0, maxChars - 18))}\u2026 [truncated]`;\n}\n\nfunction boundedValue(value: JsonValue, maxChars: number): JsonValue {\n const serialized = JSON.stringify(value);\n if (serialized.length <= maxChars) return value;\n return truncate(serialized, maxChars);\n}\n\nfunction sanitizeTurn(turn: CompletedTurn, config: HookConfig): CompletedTurn {\n return {\n ...turn,\n prompt:\n config.capturePrompts && turn.prompt !== null\n ? truncate(turn.prompt, config.maxCaptureChars)\n : null,\n assistantMessage:\n config.capturePrompts && turn.assistantMessage !== null\n ? truncate(turn.assistantMessage, config.maxCaptureChars)\n : null,\n tools: turn.tools.map((tool) => ({\n ...tool,\n name: truncate(tool.name, 256),\n input: config.captureToolInputs\n ? boundedValue(tool.input, config.maxCaptureChars)\n : null,\n output:\n config.captureToolOutputs && tool.output !== null\n ? boundedValue(tool.output, config.maxCaptureChars)\n : null,\n error:\n config.captureToolOutputs && tool.error !== null\n ? truncate(tool.error, config.maxCaptureChars)\n : null,\n })),\n };\n}\n\nfunction findTool(\n turn: TurnState,\n id: string | null,\n name: string,\n): ToolCall | null {\n if (id) {\n const byId = turn.tools.find((tool) => tool.id === id);\n if (byId) return byId;\n }\n return (\n [...turn.tools]\n .reverse()\n .find((tool) => tool.endedAt === null && tool.name === name) ??\n [...turn.tools].reverse().find((tool) => tool.endedAt === null) ??\n null\n );\n}\n\nexport class TurnTracker {\n constructor(\n private readonly stateStore: StateStore,\n private readonly traceSink: TraceSink,\n private readonly config: HookConfig,\n private readonly clock: Clock,\n private readonly idGenerator: IdGenerator,\n ) {}\n\n async handle(payload: HookPayload): Promise {\n const name = eventName(payload);\n const session = sessionId(payload);\n if (!name || !session) return;\n\n let completed: CompletedTurn | null = null;\n await this.stateStore.withSessionLock(session, async () => {\n const now = this.clock.now().toISOString();\n const existing =\n (await this.stateStore.load(session)) ?? createSession(session, now);\n\n switch (name) {\n case \"SessionStart\":\n existing.updatedAt = now;\n await this.stateStore.save(existing);\n return;\n case \"UserPromptSubmit\": {\n const userPrompt = prompt(payload);\n existing.currentTurn = createTurn(\n this.idGenerator.next(),\n now,\n this.config.capturePrompts && userPrompt !== null\n ? truncate(userPrompt, this.config.maxCaptureChars)\n : null,\n );\n existing.updatedAt = now;\n await this.stateStore.save(existing);\n return;\n }\n case \"PreToolUse\":\n this.recordToolStart(existing, payload, now);\n await this.stateStore.save(existing);\n return;\n case \"PostToolUse\":\n this.recordToolOutput(existing, payload, now, false);\n await this.stateStore.save(existing);\n return;\n case \"PostToolUseFailure\":\n this.recordToolOutput(existing, payload, now, true);\n await this.stateStore.save(existing);\n return;\n case \"Stop\":\n completed = sanitizeTurn(\n {\n sessionId: session,\n turnId: existing.currentTurn?.id ?? this.idGenerator.next(),\n prompt: existing.currentTurn?.prompt ?? null,\n assistantMessage: assistantMessage(payload),\n startedAt: existing.currentTurn?.startedAt ?? now,\n endedAt: now,\n tools: existing.currentTurn?.tools ?? [],\n },\n this.config,\n );\n await this.stateStore.clear(session);\n return;\n default:\n return;\n }\n });\n\n if (completed) await this.traceSink.publishTurn(completed);\n }\n\n private recordToolStart(\n state: SessionState,\n payload: HookPayload,\n now: string,\n ): void {\n const turn =\n state.currentTurn ?? createTurn(this.idGenerator.next(), now, null);\n state.currentTurn = turn;\n turn.tools.push({\n id: toolId(payload) ?? this.idGenerator.next(),\n name: toolName(payload),\n input: this.config.captureToolInputs\n ? boundedValue(toolInput(payload), this.config.maxCaptureChars)\n : null,\n output: null,\n error: null,\n startedAt: now,\n endedAt: null,\n });\n state.updatedAt = now;\n }\n\n private recordToolOutput(\n state: SessionState,\n payload: HookPayload,\n now: string,\n failed: boolean,\n ): void {\n const turn =\n state.currentTurn ?? createTurn(this.idGenerator.next(), now, null);\n state.currentTurn = turn;\n const name = toolName(payload);\n const tool = findTool(turn, toolId(payload), name);\n const output =\n !failed && this.config.captureToolOutputs\n ? boundedValue(toolOutput(payload), this.config.maxCaptureChars)\n : null;\n const error =\n failed && this.config.captureToolOutputs\n ? truncate(toolError(payload), this.config.maxCaptureChars)\n : null;\n if (tool) {\n tool.output = output;\n tool.error = error;\n tool.endedAt = now;\n } else {\n turn.tools.push({\n id: toolId(payload) ?? this.idGenerator.next(),\n name,\n input: null,\n output,\n error,\n startedAt: now,\n endedAt: now,\n });\n }\n state.updatedAt = now;\n }\n}\n\nexport class NoopTraceSink implements TraceSink {\n async publishTurn(_turn: CompletedTurn): Promise {\n // Missing credentials must never make ZCode hooks fail.\n }\n}\n", "import { createHash, randomUUID } from \"node:crypto\";\nimport {\n mkdir,\n open,\n readFile,\n rename,\n rm,\n stat,\n writeFile,\n} from \"node:fs/promises\";\nimport { homedir, tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type {\n JsonValue,\n SessionState,\n StateStore,\n ToolCall,\n TurnState,\n} from \"../domain/types.js\";\n\nconst LOCK_WAIT_MS = 25;\nconst LOCK_ATTEMPTS = 160;\nconst STALE_LOCK_MS = 60_000;\n\nfunction isRecord(value: unknown): value is Record {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction isJsonValue(value: unknown): value is JsonValue {\n if (\n value === null ||\n typeof value === \"string\" ||\n typeof value === \"number\" ||\n typeof value === \"boolean\"\n ) {\n return true;\n }\n if (Array.isArray(value)) return value.every(isJsonValue);\n return isRecord(value) && Object.values(value).every(isJsonValue);\n}\n\nfunction parseToolCall(value: unknown): ToolCall | null {\n if (!isRecord(value)) return null;\n if (\n typeof value.id !== \"string\" ||\n typeof value.name !== \"string\" ||\n !isJsonValue(value.input) ||\n (value.output !== null && !isJsonValue(value.output)) ||\n (value.error !== null && typeof value.error !== \"string\") ||\n typeof value.startedAt !== \"string\" ||\n (value.endedAt !== null && typeof value.endedAt !== \"string\")\n ) {\n return null;\n }\n return {\n id: value.id,\n name: value.name,\n input: value.input,\n output: value.output,\n error: value.error,\n startedAt: value.startedAt,\n endedAt: value.endedAt,\n };\n}\n\nfunction parseTurn(value: unknown): TurnState | null {\n if (!isRecord(value)) return null;\n if (\n typeof value.id !== \"string\" ||\n (value.prompt !== null && typeof value.prompt !== \"string\") ||\n typeof value.startedAt !== \"string\" ||\n !Array.isArray(value.tools)\n ) {\n return null;\n }\n const tools = value.tools.map(parseToolCall);\n if (tools.some((tool): tool is null => tool === null)) return null;\n return {\n id: value.id,\n prompt: value.prompt,\n startedAt: value.startedAt,\n tools: tools.filter((tool): tool is ToolCall => tool !== null),\n };\n}\n\nfunction parseState(value: unknown): SessionState | null {\n if (!isRecord(value)) return null;\n if (\n value.version !== 1 ||\n typeof value.sessionId !== \"string\" ||\n typeof value.startedAt !== \"string\" ||\n typeof value.updatedAt !== \"string\"\n ) {\n return null;\n }\n const currentTurn =\n value.currentTurn === null ? null : parseTurn(value.currentTurn);\n if (value.currentTurn !== null && currentTurn === null) return null;\n return {\n version: 1,\n sessionId: value.sessionId,\n startedAt: value.startedAt,\n updatedAt: value.updatedAt,\n currentTurn,\n };\n}\n\nfunction sessionKey(sessionId: string): string {\n return createHash(\"sha256\").update(sessionId).digest(\"hex\");\n}\n\nfunction defaultDataDir(): string {\n return join(\n homedir() || tmpdir(),\n \".zcode\",\n \"cli\",\n \"plugins\",\n \"data\",\n \"langfuse-observability\",\n );\n}\n\nexport class JsonStateStore implements StateStore {\n private readonly dataDir: string;\n\n constructor(dataDir = process.env.ZCODE_PLUGIN_DATA || defaultDataDir()) {\n this.dataDir = dataDir;\n }\n\n async load(sessionId: string): Promise {\n try {\n const contents = await readFile(this.statePath(sessionId), \"utf8\");\n return parseState(JSON.parse(contents));\n } catch {\n return null;\n }\n }\n\n async save(state: SessionState): Promise {\n await mkdir(this.dataDir, { recursive: true, mode: 0o700 });\n const path = this.statePath(state.sessionId);\n const temporaryPath = `${path}.${randomUUID()}.tmp`;\n await writeFile(temporaryPath, JSON.stringify(state), {\n encoding: \"utf8\",\n mode: 0o600,\n });\n await rename(temporaryPath, path);\n }\n\n async clear(sessionId: string): Promise {\n await rm(this.statePath(sessionId), { force: true });\n }\n\n async withSessionLock(\n sessionId: string,\n task: () => Promise,\n ): Promise {\n await mkdir(this.dataDir, { recursive: true, mode: 0o700 });\n const lockPath = this.lockPath(sessionId);\n let acquired = false;\n\n for (let attempt = 0; attempt < LOCK_ATTEMPTS; attempt += 1) {\n try {\n const handle = await open(lockPath, \"wx\", 0o600);\n await handle.close();\n acquired = true;\n break;\n } catch {\n await this.removeStaleLock(lockPath);\n await new Promise((resolve) => setTimeout(resolve, LOCK_WAIT_MS));\n }\n }\n\n if (!acquired)\n throw new Error(\"Timed out acquiring the Langfuse state lock\");\n\n try {\n return await task();\n } finally {\n await rm(lockPath, { force: true });\n }\n }\n\n private statePath(sessionId: string): string {\n return join(this.dataDir, `${sessionKey(sessionId)}.json`);\n }\n\n private lockPath(sessionId: string): string {\n return join(this.dataDir, `${sessionKey(sessionId)}.lock`);\n }\n\n private async removeStaleLock(lockPath: string): Promise {\n try {\n const details = await stat(lockPath);\n if (Date.now() - details.mtimeMs > STALE_LOCK_MS)\n await rm(lockPath, { force: true });\n } catch {\n // The lock may disappear between open(), stat(), and rm().\n }\n }\n}\n", "/*!\n * mustache.js - Logic-less {{mustache}} templates with JavaScript\n * http://github.com/janl/mustache.js\n */\n\nvar objectToString = Object.prototype.toString;\nvar isArray = Array.isArray || function isArrayPolyfill (object) {\n return objectToString.call(object) === '[object Array]';\n};\n\nfunction isFunction (object) {\n return typeof object === 'function';\n}\n\n/**\n * More correct typeof string handling array\n * which normally returns typeof 'object'\n */\nfunction typeStr (obj) {\n return isArray(obj) ? 'array' : typeof obj;\n}\n\nfunction escapeRegExp (string) {\n return string.replace(/[\\-\\[\\]{}()*+?.,\\\\\\^$|#\\s]/g, '\\\\$&');\n}\n\n/**\n * Null safe way of checking whether or not an object,\n * including its prototype, has a given property\n */\nfunction hasProperty (obj, propName) {\n return obj != null && typeof obj === 'object' && (propName in obj);\n}\n\n/**\n * Safe way of detecting whether or not the given thing is a primitive and\n * whether it has the given property\n */\nfunction primitiveHasOwnProperty (primitive, propName) {\n return (\n primitive != null\n && typeof primitive !== 'object'\n && primitive.hasOwnProperty\n && primitive.hasOwnProperty(propName)\n );\n}\n\n// Workaround for https://issues.apache.org/jira/browse/COUCHDB-577\n// See https://github.com/janl/mustache.js/issues/189\nvar regExpTest = RegExp.prototype.test;\nfunction testRegExp (re, string) {\n return regExpTest.call(re, string);\n}\n\nvar nonSpaceRe = /\\S/;\nfunction isWhitespace (string) {\n return !testRegExp(nonSpaceRe, string);\n}\n\nvar entityMap = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": ''',\n '/': '/',\n '`': '`',\n '=': '='\n};\n\nfunction escapeHtml (string) {\n return String(string).replace(/[&<>\"'`=\\/]/g, function fromEntityMap (s) {\n return entityMap[s];\n });\n}\n\nvar whiteRe = /\\s*/;\nvar spaceRe = /\\s+/;\nvar equalsRe = /\\s*=/;\nvar curlyRe = /\\s*\\}/;\nvar tagRe = /#|\\^|\\/|>|\\{|&|=|!/;\n\n/**\n * Breaks up the given `template` string into a tree of tokens. If the `tags`\n * argument is given here it must be an array with two string values: the\n * opening and closing tags used in the template (e.g. [ \"<%\", \"%>\" ]). Of\n * course, the default is to use mustaches (i.e. mustache.tags).\n *\n * A token is an array with at least 4 elements. The first element is the\n * mustache symbol that was used inside the tag, e.g. \"#\" or \"&\". If the tag\n * did not contain a symbol (i.e. {{myValue}}) this element is \"name\". For\n * all text that appears outside a symbol this element is \"text\".\n *\n * The second element of a token is its \"value\". For mustache tags this is\n * whatever else was inside the tag besides the opening symbol. For text tokens\n * this is the text itself.\n *\n * The third and fourth elements of the token are the start and end indices,\n * respectively, of the token in the original template.\n *\n * Tokens that are the root node of a subtree contain two more elements: 1) an\n * array of tokens in the subtree and 2) the index in the original template at\n * which the closing tag for that section begins.\n *\n * Tokens for partials also contain two more elements: 1) a string value of\n * indendation prior to that tag and 2) the index of that tag on that line -\n * eg a value of 2 indicates the partial is the third tag on this line.\n */\nfunction parseTemplate (template, tags) {\n if (!template)\n return [];\n var lineHasNonSpace = false;\n var sections = []; // Stack to hold section tokens\n var tokens = []; // Buffer to hold the tokens\n var spaces = []; // Indices of whitespace tokens on the current line\n var hasTag = false; // Is there a {{tag}} on the current line?\n var nonSpace = false; // Is there a non-space char on the current line?\n var indentation = ''; // Tracks indentation for tags that use it\n var tagIndex = 0; // Stores a count of number of tags encountered on a line\n\n // Strips all whitespace tokens array for the current line\n // if there was a {{#tag}} on it and otherwise only space.\n function stripSpace () {\n if (hasTag && !nonSpace) {\n while (spaces.length)\n delete tokens[spaces.pop()];\n } else {\n spaces = [];\n }\n\n hasTag = false;\n nonSpace = false;\n }\n\n var openingTagRe, closingTagRe, closingCurlyRe;\n function compileTags (tagsToCompile) {\n if (typeof tagsToCompile === 'string')\n tagsToCompile = tagsToCompile.split(spaceRe, 2);\n\n if (!isArray(tagsToCompile) || tagsToCompile.length !== 2)\n throw new Error('Invalid tags: ' + tagsToCompile);\n\n openingTagRe = new RegExp(escapeRegExp(tagsToCompile[0]) + '\\\\s*');\n closingTagRe = new RegExp('\\\\s*' + escapeRegExp(tagsToCompile[1]));\n closingCurlyRe = new RegExp('\\\\s*' + escapeRegExp('}' + tagsToCompile[1]));\n }\n\n compileTags(tags || mustache.tags);\n\n var scanner = new Scanner(template);\n\n var start, type, value, chr, token, openSection;\n while (!scanner.eos()) {\n start = scanner.pos;\n\n // Match any text between tags.\n value = scanner.scanUntil(openingTagRe);\n\n if (value) {\n for (var i = 0, valueLength = value.length; i < valueLength; ++i) {\n chr = value.charAt(i);\n\n if (isWhitespace(chr)) {\n spaces.push(tokens.length);\n indentation += chr;\n } else {\n nonSpace = true;\n lineHasNonSpace = true;\n indentation += ' ';\n }\n\n tokens.push([ 'text', chr, start, start + 1 ]);\n start += 1;\n\n // Check for whitespace on the current line.\n if (chr === '\\n') {\n stripSpace();\n indentation = '';\n tagIndex = 0;\n lineHasNonSpace = false;\n }\n }\n }\n\n // Match the opening tag.\n if (!scanner.scan(openingTagRe))\n break;\n\n hasTag = true;\n\n // Get the tag type.\n type = scanner.scan(tagRe) || 'name';\n scanner.scan(whiteRe);\n\n // Get the tag value.\n if (type === '=') {\n value = scanner.scanUntil(equalsRe);\n scanner.scan(equalsRe);\n scanner.scanUntil(closingTagRe);\n } else if (type === '{') {\n value = scanner.scanUntil(closingCurlyRe);\n scanner.scan(curlyRe);\n scanner.scanUntil(closingTagRe);\n type = '&';\n } else {\n value = scanner.scanUntil(closingTagRe);\n }\n\n // Match the closing tag.\n if (!scanner.scan(closingTagRe))\n throw new Error('Unclosed tag at ' + scanner.pos);\n\n if (type == '>') {\n token = [ type, value, start, scanner.pos, indentation, tagIndex, lineHasNonSpace ];\n } else {\n token = [ type, value, start, scanner.pos ];\n }\n tagIndex++;\n tokens.push(token);\n\n if (type === '#' || type === '^') {\n sections.push(token);\n } else if (type === '/') {\n // Check section nesting.\n openSection = sections.pop();\n\n if (!openSection)\n throw new Error('Unopened section \"' + value + '\" at ' + start);\n\n if (openSection[1] !== value)\n throw new Error('Unclosed section \"' + openSection[1] + '\" at ' + start);\n } else if (type === 'name' || type === '{' || type === '&') {\n nonSpace = true;\n } else if (type === '=') {\n // Set the tags for the next time around.\n compileTags(value);\n }\n }\n\n stripSpace();\n\n // Make sure there are no open sections when we're done.\n openSection = sections.pop();\n\n if (openSection)\n throw new Error('Unclosed section \"' + openSection[1] + '\" at ' + scanner.pos);\n\n return nestTokens(squashTokens(tokens));\n}\n\n/**\n * Combines the values of consecutive text tokens in the given `tokens` array\n * to a single token.\n */\nfunction squashTokens (tokens) {\n var squashedTokens = [];\n\n var token, lastToken;\n for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) {\n token = tokens[i];\n\n if (token) {\n if (token[0] === 'text' && lastToken && lastToken[0] === 'text') {\n lastToken[1] += token[1];\n lastToken[3] = token[3];\n } else {\n squashedTokens.push(token);\n lastToken = token;\n }\n }\n }\n\n return squashedTokens;\n}\n\n/**\n * Forms the given array of `tokens` into a nested tree structure where\n * tokens that represent a section have two additional items: 1) an array of\n * all tokens that appear in that section and 2) the index in the original\n * template that represents the end of that section.\n */\nfunction nestTokens (tokens) {\n var nestedTokens = [];\n var collector = nestedTokens;\n var sections = [];\n\n var token, section;\n for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) {\n token = tokens[i];\n\n switch (token[0]) {\n case '#':\n case '^':\n collector.push(token);\n sections.push(token);\n collector = token[4] = [];\n break;\n case '/':\n section = sections.pop();\n section[5] = token[2];\n collector = sections.length > 0 ? sections[sections.length - 1][4] : nestedTokens;\n break;\n default:\n collector.push(token);\n }\n }\n\n return nestedTokens;\n}\n\n/**\n * A simple string scanner that is used by the template parser to find\n * tokens in template strings.\n */\nfunction Scanner (string) {\n this.string = string;\n this.tail = string;\n this.pos = 0;\n}\n\n/**\n * Returns `true` if the tail is empty (end of string).\n */\nScanner.prototype.eos = function eos () {\n return this.tail === '';\n};\n\n/**\n * Tries to match the given regular expression at the current position.\n * Returns the matched text if it can match, the empty string otherwise.\n */\nScanner.prototype.scan = function scan (re) {\n var match = this.tail.match(re);\n\n if (!match || match.index !== 0)\n return '';\n\n var string = match[0];\n\n this.tail = this.tail.substring(string.length);\n this.pos += string.length;\n\n return string;\n};\n\n/**\n * Skips all text until the given regular expression can be matched. Returns\n * the skipped string, which is the entire tail if no match can be made.\n */\nScanner.prototype.scanUntil = function scanUntil (re) {\n var index = this.tail.search(re), match;\n\n switch (index) {\n case -1:\n match = this.tail;\n this.tail = '';\n break;\n case 0:\n match = '';\n break;\n default:\n match = this.tail.substring(0, index);\n this.tail = this.tail.substring(index);\n }\n\n this.pos += match.length;\n\n return match;\n};\n\n/**\n * Represents a rendering context by wrapping a view object and\n * maintaining a reference to the parent context.\n */\nfunction Context (view, parentContext) {\n this.view = view;\n this.cache = { '.': this.view };\n this.parent = parentContext;\n}\n\n/**\n * Creates a new context using the given view with this context\n * as the parent.\n */\nContext.prototype.push = function push (view) {\n return new Context(view, this);\n};\n\n/**\n * Returns the value of the given name in this context, traversing\n * up the context hierarchy if the value is absent in this context's view.\n */\nContext.prototype.lookup = function lookup (name) {\n var cache = this.cache;\n\n var value;\n if (cache.hasOwnProperty(name)) {\n value = cache[name];\n } else {\n var context = this, intermediateValue, names, index, lookupHit = false;\n\n while (context) {\n if (name.indexOf('.') > 0) {\n intermediateValue = context.view;\n names = name.split('.');\n index = 0;\n\n /**\n * Using the dot notion path in `name`, we descend through the\n * nested objects.\n *\n * To be certain that the lookup has been successful, we have to\n * check if the last object in the path actually has the property\n * we are looking for. We store the result in `lookupHit`.\n *\n * This is specially necessary for when the value has been set to\n * `undefined` and we want to avoid looking up parent contexts.\n *\n * In the case where dot notation is used, we consider the lookup\n * to be successful even if the last \"object\" in the path is\n * not actually an object but a primitive (e.g., a string, or an\n * integer), because it is sometimes useful to access a property\n * of an autoboxed primitive, such as the length of a string.\n **/\n while (intermediateValue != null && index < names.length) {\n if (index === names.length - 1)\n lookupHit = (\n hasProperty(intermediateValue, names[index])\n || primitiveHasOwnProperty(intermediateValue, names[index])\n );\n\n intermediateValue = intermediateValue[names[index++]];\n }\n } else {\n intermediateValue = context.view[name];\n\n /**\n * Only checking against `hasProperty`, which always returns `false` if\n * `context.view` is not an object. Deliberately omitting the check\n * against `primitiveHasOwnProperty` if dot notation is not used.\n *\n * Consider this example:\n * ```\n * Mustache.render(\"The length of a football field is {{#length}}{{length}}{{/length}}.\", {length: \"100 yards\"})\n * ```\n *\n * If we were to check also against `primitiveHasOwnProperty`, as we do\n * in the dot notation case, then render call would return:\n *\n * \"The length of a football field is 9.\"\n *\n * rather than the expected:\n *\n * \"The length of a football field is 100 yards.\"\n **/\n lookupHit = hasProperty(context.view, name);\n }\n\n if (lookupHit) {\n value = intermediateValue;\n break;\n }\n\n context = context.parent;\n }\n\n cache[name] = value;\n }\n\n if (isFunction(value))\n value = value.call(this.view);\n\n return value;\n};\n\n/**\n * A Writer knows how to take a stream of tokens and render them to a\n * string, given a context. It also maintains a cache of templates to\n * avoid the need to parse the same template twice.\n */\nfunction Writer () {\n this.templateCache = {\n _cache: {},\n set: function set (key, value) {\n this._cache[key] = value;\n },\n get: function get (key) {\n return this._cache[key];\n },\n clear: function clear () {\n this._cache = {};\n }\n };\n}\n\n/**\n * Clears all cached templates in this writer.\n */\nWriter.prototype.clearCache = function clearCache () {\n if (typeof this.templateCache !== 'undefined') {\n this.templateCache.clear();\n }\n};\n\n/**\n * Parses and caches the given `template` according to the given `tags` or\n * `mustache.tags` if `tags` is omitted, and returns the array of tokens\n * that is generated from the parse.\n */\nWriter.prototype.parse = function parse (template, tags) {\n var cache = this.templateCache;\n var cacheKey = template + ':' + (tags || mustache.tags).join(':');\n var isCacheEnabled = typeof cache !== 'undefined';\n var tokens = isCacheEnabled ? cache.get(cacheKey) : undefined;\n\n if (tokens == undefined) {\n tokens = parseTemplate(template, tags);\n isCacheEnabled && cache.set(cacheKey, tokens);\n }\n return tokens;\n};\n\n/**\n * High-level method that is used to render the given `template` with\n * the given `view`.\n *\n * The optional `partials` argument may be an object that contains the\n * names and templates of partials that are used in the template. It may\n * also be a function that is used to load partial templates on the fly\n * that takes a single argument: the name of the partial.\n *\n * If the optional `config` argument is given here, then it should be an\n * object with a `tags` attribute or an `escape` attribute or both.\n * If an array is passed, then it will be interpreted the same way as\n * a `tags` attribute on a `config` object.\n *\n * The `tags` attribute of a `config` object must be an array with two\n * string values: the opening and closing tags used in the template (e.g.\n * [ \"<%\", \"%>\" ]). The default is to mustache.tags.\n *\n * The `escape` attribute of a `config` object must be a function which\n * accepts a string as input and outputs a safely escaped string.\n * If an `escape` function is not provided, then an HTML-safe string\n * escaping function is used as the default.\n */\nWriter.prototype.render = function render (template, view, partials, config) {\n var tags = this.getConfigTags(config);\n var tokens = this.parse(template, tags);\n var context = (view instanceof Context) ? view : new Context(view, undefined);\n return this.renderTokens(tokens, context, partials, template, config);\n};\n\n/**\n * Low-level method that renders the given array of `tokens` using\n * the given `context` and `partials`.\n *\n * Note: The `originalTemplate` is only ever used to extract the portion\n * of the original template that was contained in a higher-order section.\n * If the template doesn't use higher-order sections, this argument may\n * be omitted.\n */\nWriter.prototype.renderTokens = function renderTokens (tokens, context, partials, originalTemplate, config) {\n var buffer = '';\n\n var token, symbol, value;\n for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) {\n value = undefined;\n token = tokens[i];\n symbol = token[0];\n\n if (symbol === '#') value = this.renderSection(token, context, partials, originalTemplate, config);\n else if (symbol === '^') value = this.renderInverted(token, context, partials, originalTemplate, config);\n else if (symbol === '>') value = this.renderPartial(token, context, partials, config);\n else if (symbol === '&') value = this.unescapedValue(token, context);\n else if (symbol === 'name') value = this.escapedValue(token, context, config);\n else if (symbol === 'text') value = this.rawValue(token);\n\n if (value !== undefined)\n buffer += value;\n }\n\n return buffer;\n};\n\nWriter.prototype.renderSection = function renderSection (token, context, partials, originalTemplate, config) {\n var self = this;\n var buffer = '';\n var value = context.lookup(token[1]);\n\n // This function is used to render an arbitrary template\n // in the current context by higher-order sections.\n function subRender (template) {\n return self.render(template, context, partials, config);\n }\n\n if (!value) return;\n\n if (isArray(value)) {\n for (var j = 0, valueLength = value.length; j < valueLength; ++j) {\n buffer += this.renderTokens(token[4], context.push(value[j]), partials, originalTemplate, config);\n }\n } else if (typeof value === 'object' || typeof value === 'string' || typeof value === 'number') {\n buffer += this.renderTokens(token[4], context.push(value), partials, originalTemplate, config);\n } else if (isFunction(value)) {\n if (typeof originalTemplate !== 'string')\n throw new Error('Cannot use higher-order sections without the original template');\n\n // Extract the portion of the original template that the section contains.\n value = value.call(context.view, originalTemplate.slice(token[3], token[5]), subRender);\n\n if (value != null)\n buffer += value;\n } else {\n buffer += this.renderTokens(token[4], context, partials, originalTemplate, config);\n }\n return buffer;\n};\n\nWriter.prototype.renderInverted = function renderInverted (token, context, partials, originalTemplate, config) {\n var value = context.lookup(token[1]);\n\n // Use JavaScript's definition of falsy. Include empty arrays.\n // See https://github.com/janl/mustache.js/issues/186\n if (!value || (isArray(value) && value.length === 0))\n return this.renderTokens(token[4], context, partials, originalTemplate, config);\n};\n\nWriter.prototype.indentPartial = function indentPartial (partial, indentation, lineHasNonSpace) {\n var filteredIndentation = indentation.replace(/[^ \\t]/g, '');\n var partialByNl = partial.split('\\n');\n for (var i = 0; i < partialByNl.length; i++) {\n if (partialByNl[i].length && (i > 0 || !lineHasNonSpace)) {\n partialByNl[i] = filteredIndentation + partialByNl[i];\n }\n }\n return partialByNl.join('\\n');\n};\n\nWriter.prototype.renderPartial = function renderPartial (token, context, partials, config) {\n if (!partials) return;\n var tags = this.getConfigTags(config);\n\n var value = isFunction(partials) ? partials(token[1]) : partials[token[1]];\n if (value != null) {\n var lineHasNonSpace = token[6];\n var tagIndex = token[5];\n var indentation = token[4];\n var indentedValue = value;\n if (tagIndex == 0 && indentation) {\n indentedValue = this.indentPartial(value, indentation, lineHasNonSpace);\n }\n var tokens = this.parse(indentedValue, tags);\n return this.renderTokens(tokens, context, partials, indentedValue, config);\n }\n};\n\nWriter.prototype.unescapedValue = function unescapedValue (token, context) {\n var value = context.lookup(token[1]);\n if (value != null)\n return value;\n};\n\nWriter.prototype.escapedValue = function escapedValue (token, context, config) {\n var escape = this.getConfigEscape(config) || mustache.escape;\n var value = context.lookup(token[1]);\n if (value != null)\n return (typeof value === 'number' && escape === mustache.escape) ? String(value) : escape(value);\n};\n\nWriter.prototype.rawValue = function rawValue (token) {\n return token[1];\n};\n\nWriter.prototype.getConfigTags = function getConfigTags (config) {\n if (isArray(config)) {\n return config;\n }\n else if (config && typeof config === 'object') {\n return config.tags;\n }\n else {\n return undefined;\n }\n};\n\nWriter.prototype.getConfigEscape = function getConfigEscape (config) {\n if (config && typeof config === 'object' && !isArray(config)) {\n return config.escape;\n }\n else {\n return undefined;\n }\n};\n\nvar mustache = {\n name: 'mustache.js',\n version: '4.2.0',\n tags: [ '{{', '}}' ],\n clearCache: undefined,\n escape: undefined,\n parse: undefined,\n render: undefined,\n Scanner: undefined,\n Context: undefined,\n Writer: undefined,\n /**\n * Allows a user to override the default caching strategy, by providing an\n * object with set, get and clear methods. This can also be used to disable\n * the cache by setting it to the literal `undefined`.\n */\n set templateCache (cache) {\n defaultWriter.templateCache = cache;\n },\n /**\n * Gets the default or overridden caching object from the default writer.\n */\n get templateCache () {\n return defaultWriter.templateCache;\n }\n};\n\n// All high-level mustache.* functions use this writer.\nvar defaultWriter = new Writer();\n\n/**\n * Clears all cached templates in the default writer.\n */\nmustache.clearCache = function clearCache () {\n return defaultWriter.clearCache();\n};\n\n/**\n * Parses and caches the given template in the default writer and returns the\n * array of tokens it contains. Doing this ahead of time avoids the need to\n * parse templates on the fly as they are rendered.\n */\nmustache.parse = function parse (template, tags) {\n return defaultWriter.parse(template, tags);\n};\n\n/**\n * Renders the `template` with the given `view`, `partials`, and `config`\n * using the default writer.\n */\nmustache.render = function render (template, view, partials, config) {\n if (typeof template !== 'string') {\n throw new TypeError('Invalid template! Template should be a \"string\" ' +\n 'but \"' + typeStr(template) + '\" was given as the first ' +\n 'argument for mustache#render(template, view, partials)');\n }\n\n return defaultWriter.render(template, view, partials, config);\n};\n\n// Export the escaping function so that the user may override it.\n// See https://github.com/janl/mustache.js/issues/244\nmustache.escape = escapeHtml;\n\n// Export these mainly for testing, but also for advanced usage.\nmustache.Scanner = Scanner;\nmustache.Context = Context;\nmustache.Writer = Writer;\n\nexport default mustache;\n", "export class SimpleEventEmitter {\n events: { [key: string]: ((...args: any[]) => void)[] } = {};\n\n constructor() {\n this.events = {};\n }\n\n on(event: string, listener: (...args: any[]) => void): () => void {\n if (!this.events[event]) {\n this.events[event] = [];\n }\n this.events[event].push(listener);\n\n return () => {\n this.events[event] = this.events[event].filter((x) => x !== listener);\n };\n }\n\n emit(event: string, payload: any): void {\n for (const listener of this.events[event] || []) {\n listener(payload);\n }\n for (const listener of this.events[\"*\"] || []) {\n listener(event, payload);\n }\n }\n}\n", "import type { LangfusePromptClient } from \"./promptClients\";\n\nexport const DEFAULT_PROMPT_CACHE_TTL_SECONDS = 60;\n\nclass LangfusePromptCacheItem {\n private _expiry: number;\n\n constructor(\n public value: LangfusePromptClient,\n ttlSeconds: number\n ) {\n this._expiry = Date.now() + ttlSeconds * 1000;\n }\n\n get isExpired(): boolean {\n return Date.now() > this._expiry;\n }\n}\nexport class LangfusePromptCache {\n private _cache: Map;\n private _defaultTtlSeconds: number;\n private _refreshingKeys: Map>;\n\n constructor() {\n this._cache = new Map();\n this._defaultTtlSeconds = DEFAULT_PROMPT_CACHE_TTL_SECONDS;\n this._refreshingKeys = new Map>();\n }\n\n public getIncludingExpired(key: string): LangfusePromptCacheItem | null {\n return this._cache.get(key) ?? null;\n }\n\n public set(key: string, value: LangfusePromptClient, ttlSeconds?: number): void {\n const effectiveTtlSeconds = ttlSeconds ?? this._defaultTtlSeconds;\n this._cache.set(key, new LangfusePromptCacheItem(value, effectiveTtlSeconds));\n }\n\n public addRefreshingPromise(key: string, promise: Promise): void {\n this._refreshingKeys.set(key, promise);\n promise\n .then(() => {\n this._refreshingKeys.delete(key);\n })\n .catch(() => {\n this._refreshingKeys.delete(key);\n });\n }\n\n public isRefreshing(key: string): boolean {\n return this._refreshingKeys.has(key);\n }\n\n public invalidate(promptName: string): void {\n console.log(\"invalidating\", promptName, this._cache.keys());\n for (const key of this._cache.keys()) {\n if (key.startsWith(promptName)) {\n this._cache.delete(key);\n }\n }\n }\n}\n", "import { type LangfuseObjectClient } from \"./index\";\nimport { type LangfusePromptClient } from \"./prompts/promptClients\";\nimport { type components, type paths } from \"./openapi/server\";\n\nexport type LangfuseCoreOptions = {\n // Langfuse API publicKey obtained from the Langfuse UI project settings\n publicKey?: string;\n // Langfuse API secretKey obtained from the Langfuse UI project settings\n secretKey?: string;\n // Langfuse API baseUrl (https://cloud.langfuse.com by default)\n baseUrl?: string;\n // Additional HTTP headers to send with each request\n additionalHeaders?: Record;\n // The number of events to queue before sending to Langfuse (flushing)\n flushAt?: number;\n // The interval in milliseconds between periodic flushes\n flushInterval?: number;\n // How many times we will retry HTTP requests\n fetchRetryCount?: number;\n // The delay between HTTP request retries\n fetchRetryDelay?: number;\n // Timeout in milliseconds for any calls. Defaults to 10 seconds.\n requestTimeout?: number;\n // release (version) of the application, defaults to env LANGFUSE_RELEASE\n release?: string;\n // integration type of the SDK.\n sdkIntegration?: string; // DEFAULT, LANGCHAIN, or any other custom value\n // Enabled switch for the SDK. If disabled, no observability data will be sent to Langfuse. Defaults to true.\n enabled?: boolean;\n // Mask function to mask data in the event body\n mask?: MaskFunction;\n // Trace sampling rate. Approx. sampleRate % traces will be sent to LF servers\n sampleRate?: number;\n // Environment from which traces originate\n environment?: string;\n // Project ID to use for the SDK in admin mode. This should never be set by users.\n _projectId?: string;\n // Whether to enable local event export. Defaults to false.\n _isLocalEventExportEnabled?: boolean;\n};\n\nexport enum LangfusePersistedProperty {\n Props = \"props\",\n Queue = \"queue\",\n OptedOut = \"opted_out\",\n}\n\nexport type LangfuseFetchOptions = {\n method: \"GET\" | \"POST\" | \"PUT\" | \"PATCH\";\n headers: { [key: string]: string };\n body?: string | Buffer;\n signal?: AbortSignal;\n};\n\nexport type LangfuseFetchResponse = {\n status: number;\n text: () => Promise;\n json: () => Promise;\n arrayBuffer: () => Promise;\n};\n\nexport type LangfuseObject = SingleIngestionEvent[\"type\"];\n\nexport type LangfuseQueueItem = SingleIngestionEvent & {\n callback?: (err: any) => void;\n};\n\nexport type SingleIngestionEvent =\n paths[\"/api/public/ingestion\"][\"post\"][\"requestBody\"][\"content\"][\"application/json\"][\"batch\"][number];\n\n// return type of ingestion endpoint defined on 200 status error in fern as 207 is not possible\nexport type IngestionReturnType =\n paths[\"/api/public/ingestion\"][\"post\"][\"responses\"][207][\"content\"][\"application/json\"];\n\nexport type LangfuseEventProperties = {\n [key: string]: any;\n};\n\nexport type LangfuseMetadataProperties = {\n [key: string]: any;\n};\n\n// ASYNC\nexport type CreateLangfuseTraceBody = FixTypes;\n\nexport type CreateLangfuseEventBody = FixTypes;\n\nexport type CreateLangfuseSpanBody = FixTypes;\nexport type UpdateLangfuseSpanBody = FixTypes;\nexport type EventBody =\n | CreateLangfuseTraceBody\n | CreateLangfuseEventBody\n | CreateLangfuseSpanBody\n | CreateLangfuseGenerationBody\n | CreateLangfuseScoreBody\n | UpdateLangfuseSpanBody\n | UpdateLangfuseGenerationBody;\n\nexport type Usage = FixTypes;\nexport type UsageDetails = FixTypes;\nexport type CreateLangfuseGenerationBody = FixTypes;\nexport type UpdateLangfuseGenerationBody = FixTypes;\n\nexport type CreateLangfuseScoreBody = FixTypes;\n\n// SYNC\nexport type GetLangfuseTracesQuery = FixTypes;\nexport type GetLangfuseTracesResponse = FixTypes<\n paths[\"/api/public/traces\"][\"get\"][\"responses\"][\"200\"][\"content\"][\"application/json\"]\n>;\nexport type GetLangfuseTraceResponse = FixTypes<\n paths[\"/api/public/traces/{traceId}\"][\"get\"][\"responses\"][\"200\"][\"content\"][\"application/json\"]\n>;\nexport type GetLangfuseObservationsQuery = FixTypes;\nexport type GetLangfuseObservationsResponse = FixTypes<\n paths[\"/api/public/observations\"][\"get\"][\"responses\"][\"200\"][\"content\"][\"application/json\"]\n>;\nexport type GetLangfuseObservationResponse = FixTypes<\n paths[\"/api/public/observations/{observationId}\"][\"get\"][\"responses\"][\"200\"][\"content\"][\"application/json\"]\n>;\nexport type GetLangfuseSessionsQuery = FixTypes;\nexport type GetLangfuseSessionsResponse = FixTypes<\n paths[\"/api/public/sessions\"][\"get\"][\"responses\"][\"200\"][\"content\"][\"application/json\"]\n>;\nexport type GetLangfuseDatasetParams = FixTypes<\n paths[\"/api/public/v2/datasets/{datasetName}\"][\"get\"][\"parameters\"][\"path\"]\n>;\nexport type GetLangfuseDatasetResponse = FixTypes<\n paths[\"/api/public/v2/datasets/{datasetName}\"][\"get\"][\"responses\"][\"200\"][\"content\"][\"application/json\"]\n>;\nexport type GetLangfuseDatasetItemsQuery = paths[\"/api/public/dataset-items\"][\"get\"][\"parameters\"][\"query\"];\nexport type GetLangfuseDatasetItemsResponse = FixTypes<\n paths[\"/api/public/dataset-items\"][\"get\"][\"responses\"][\"200\"][\"content\"][\"application/json\"]\n>;\nexport type CreateLangfuseDatasetRunItemBody = FixTypes<\n paths[\"/api/public/dataset-run-items\"][\"post\"][\"requestBody\"][\"content\"][\"application/json\"]\n>;\nexport type CreateLangfuseDatasetRunItemResponse = FixTypes<\n paths[\"/api/public/dataset-run-items\"][\"post\"][\"responses\"][\"200\"][\"content\"][\"application/json\"]\n>;\nexport type CreateLangfuseDatasetBody =\n paths[\"/api/public/v2/datasets\"][\"post\"][\"requestBody\"][\"content\"][\"application/json\"];\nexport type CreateLangfuseDatasetResponse = FixTypes<\n paths[\"/api/public/v2/datasets\"][\"post\"][\"responses\"][\"200\"][\"content\"][\"application/json\"]\n>;\nexport type CreateLangfuseDatasetItemBody = FixTypes<\n paths[\"/api/public/dataset-items\"][\"post\"][\"requestBody\"][\"content\"][\"application/json\"]\n>;\nexport type CreateLangfuseDatasetItemResponse = FixTypes<\n paths[\"/api/public/dataset-items\"][\"post\"][\"responses\"][\"200\"][\"content\"][\"application/json\"]\n>;\nexport type GetLangfuseDatasetRunParams = FixTypes<\n paths[\"/api/public/datasets/{datasetName}/runs/{runName}\"][\"get\"][\"parameters\"][\"path\"]\n>;\nexport type GetLangfuseDatasetRunResponse = FixTypes<\n paths[\"/api/public/datasets/{datasetName}/runs/{runName}\"][\"get\"][\"responses\"][\"200\"][\"content\"][\"application/json\"]\n>;\nexport type GetLangfuseDatasetRunsQuery =\n paths[\"/api/public/datasets/{datasetName}/runs\"][\"get\"][\"parameters\"][\"query\"];\nexport type GetLangfuseDatasetRunsPath = paths[\"/api/public/datasets/{datasetName}/runs\"][\"get\"][\"parameters\"][\"path\"];\n\nexport type GetLangfuseDatasetRunsResponse = FixTypes<\n paths[\"/api/public/datasets/{datasetName}/runs\"][\"get\"][\"responses\"][\"200\"][\"content\"][\"application/json\"]\n>;\nexport type CreateLangfusePromptBody = FixTypes<\n paths[\"/api/public/v2/prompts\"][\"post\"][\"requestBody\"][\"content\"][\"application/json\"]\n>;\nexport type UpdatePromptBody = FixTypes<\n paths[\"/api/public/v2/prompts/{name}/versions/{version}\"][\"patch\"][\"requestBody\"][\"content\"][\"application/json\"]\n>;\nexport type CreateLangfusePromptResponse =\n paths[\"/api/public/v2/prompts\"][\"post\"][\"responses\"][\"200\"][\"content\"][\"application/json\"];\n\nexport type GetLangfusePromptSuccessData =\n paths[\"/api/public/v2/prompts/{promptName}\"][\"get\"][\"responses\"][\"200\"][\"content\"][\"application/json\"];\n\nexport type GetLangfusePromptFailureData = { message?: string };\nexport type GetLangfusePromptResponse =\n | {\n fetchResult: \"success\";\n data: GetLangfusePromptSuccessData;\n }\n | { fetchResult: \"failure\"; data: GetLangfusePromptFailureData };\n\nexport type ChatMessage = FixTypes;\nexport type PlaceholderMessage = FixTypes;\nexport type ChatMessageWithPlaceholders = FixTypes;\nexport type ChatPrompt = FixTypes & { type: \"chat\" };\nexport type TextPrompt = FixTypes & { type: \"text\" };\n\n// Make ChatPromptClient constructor backwards compatible with normal chat messages (probably no one uses it that way, but still)\nexport type ChatPromptCompat = Omit & {\n prompt: ChatMessage[] | ChatMessageWithPlaceholders[];\n};\n\nexport enum ChatMessageType {\n ChatMessage = \"chatmessage\",\n Placeholder = \"placeholder\",\n}\n\nexport type ChatMessageOrPlaceholder = ChatMessage | ({ type: ChatMessageType.Placeholder } & PlaceholderMessage);\n\nexport type LangchainMessagesPlaceholder = {\n variableName: string;\n optional?: boolean;\n};\n\n// Media\nexport type GetMediaUploadUrlRequest = FixTypes;\nexport type GetMediaUploadUrlResponse = FixTypes;\nexport type MediaContentType = components[\"schemas\"][\"MediaContentType\"];\nexport type PatchMediaBody = FixTypes;\nexport type GetMediaResponse = FixTypes;\n\ntype CreateTextPromptRequest = FixTypes;\ntype CreateChatPromptRequest = FixTypes;\nexport type CreateTextPromptBody = { type?: \"text\" } & Omit & { isActive?: boolean }; // isActive is optional for backward compatibility\nexport type CreateChatPromptBody = { type: \"chat\" } & Omit & { isActive?: boolean }; // isActive is optional for backward compatibility\n\nexport type CreateChatPromptBodyWithPlaceholders = {\n type: \"chat\";\n} & Omit & {\n prompt: (ChatMessage | ChatMessageWithPlaceholders)[];\n isActive?: boolean;\n };\n\nexport type CreatePromptBody = CreateTextPromptBody | CreateChatPromptBody;\n\nexport type PromptInput = {\n prompt?: LangfusePromptRecord | LangfusePromptClient;\n};\n\nexport type JsonType = string | number | boolean | null | { [key: string]: JsonType } | Array;\n\ntype OptionalTypes = T extends null | undefined ? T : never;\n\ntype FixTypes = T extends undefined\n ? undefined\n : Omit<\n {\n [P in keyof T]: P extends\n | \"startTime\"\n | \"endTime\"\n | \"timestamp\"\n | \"completionStartTime\"\n | \"createdAt\"\n | \"updatedAt\"\n | \"fromTimestamp\"\n | \"toTimestamp\"\n | \"fromStartTime\"\n | \"toStartTime\"\n ? // Dates instead of strings\n Date | OptionalTypes\n : P extends \"metadata\" | \"input\" | \"output\" | \"completion\" | \"expectedOutput\"\n ? // JSON instead of strings\n any | OptionalTypes\n : T[P];\n },\n \"externalId\" | \"traceIdType\"\n >;\n\nexport type DeferRuntime = {\n langfuseTraces: (\n traces: {\n id: string;\n name: string;\n url: string;\n }[]\n ) => void;\n};\n\n// Datasets\nexport type DatasetItemData = GetLangfuseDatasetItemsResponse[\"data\"][number];\nexport type LinkDatasetItem = (\n obj: LangfuseObjectClient,\n runName: string,\n runArgs?: {\n description?: string;\n metadata?: any;\n }\n) => Promise<{ id: string }>;\nexport type DatasetItem = DatasetItemData & { link: LinkDatasetItem };\n\nexport type MaskFunction = (params: { data: any }) => any;\n\nexport type LangfusePromptRecord = (TextPrompt | ChatPrompt) & { isFallback: boolean };\n", "import mustache from \"mustache\";\n\nimport {\n type ChatMessage,\n type ChatMessageOrPlaceholder,\n ChatMessageType,\n type ChatPrompt,\n type ChatPromptCompat,\n type CreateLangfusePromptResponse,\n type LangchainMessagesPlaceholder,\n type TextPrompt,\n type ChatMessageWithPlaceholders,\n} from \"../types\";\n\nmustache.escape = function (text) {\n return text;\n};\n\nabstract class BasePromptClient {\n public readonly name: string;\n public readonly version: number;\n public readonly config: unknown;\n public readonly labels: string[];\n public readonly tags: string[];\n public readonly isFallback: boolean;\n public readonly type: \"text\" | \"chat\";\n public readonly commitMessage: string | null | undefined;\n\n constructor(prompt: CreateLangfusePromptResponse, isFallback = false, type: \"text\" | \"chat\") {\n this.name = prompt.name;\n this.version = prompt.version;\n this.config = prompt.config;\n this.labels = prompt.labels;\n this.tags = prompt.tags;\n this.isFallback = isFallback;\n this.type = type;\n this.commitMessage = prompt.commitMessage;\n }\n\n abstract get prompt(): string | ChatMessageWithPlaceholders[];\n abstract set prompt(value: string | ChatMessageWithPlaceholders[]);\n\n abstract compile(\n variables?: Record,\n placeholders?: Record\n ): string | ChatMessage[] | (ChatMessageOrPlaceholder | any)[];\n\n public abstract getLangchainPrompt(options?: {\n placeholders?: Record;\n }): string | ChatMessage[] | ChatMessageOrPlaceholder[] | (ChatMessage | LangchainMessagesPlaceholder | any)[];\n\n protected _transformToLangchainVariables(content: string): string {\n const jsonEscapedContent = this.escapeJsonForLangchain(content);\n\n return jsonEscapedContent.replace(/\\{\\{(\\w+)\\}\\}/g, \"{$1}\");\n }\n\n /**\n * Escapes every curly brace that is part of a JSON object by doubling it.\n *\n * A curly brace is considered “JSON-related” when, after skipping any immediate\n * whitespace, the next non-whitespace character is a single (') or double (\") quote.\n *\n * Braces that are already doubled (e.g. `{{variable}}` placeholders) are left untouched.\n *\n * @param text - Input string that may contain JSON snippets.\n * @returns The string with JSON-related braces doubled.\n */\n protected escapeJsonForLangchain(text: string): string {\n const out: string[] = []; // collected characters\n const stack: boolean[] = []; // true = “this { belongs to JSON”, false = normal “{”\n let i = 0;\n const n = text.length;\n\n while (i < n) {\n const ch = text[i];\n\n // ---------- opening brace ----------\n if (ch === \"{\") {\n // leave existing “{{ …” untouched\n if (i + 1 < n && text[i + 1] === \"{\") {\n out.push(\"{{\");\n i += 2;\n continue;\n }\n\n // look ahead to find the next non-space character\n let j = i + 1;\n while (j < n && /\\s/.test(text[j])) {\n j++;\n }\n\n const isJson = j < n && (text[j] === \"'\" || text[j] === '\"');\n out.push(isJson ? \"{{\" : \"{\");\n stack.push(isJson); // remember how this “{” was treated\n i += 1;\n continue;\n }\n\n // ---------- closing brace ----------\n if (ch === \"}\") {\n // leave existing “… }}” untouched\n if (i + 1 < n && text[i + 1] === \"}\") {\n out.push(\"}}\");\n i += 2;\n continue;\n }\n\n const isJson = stack.pop() ?? false;\n out.push(isJson ? \"}}\" : \"}\");\n i += 1;\n continue;\n }\n\n // ---------- any other character ----------\n out.push(ch);\n i += 1;\n }\n\n return out.join(\"\");\n }\n\n public abstract toJSON(): string;\n}\n\nexport class TextPromptClient extends BasePromptClient {\n public readonly promptResponse: TextPrompt;\n public readonly prompt: string;\n\n constructor(prompt: TextPrompt, isFallback = false) {\n super(prompt, isFallback, \"text\");\n this.promptResponse = prompt;\n this.prompt = prompt.prompt;\n }\n\n compile(variables?: Record, _placeholders?: Record): string {\n return mustache.render(this.promptResponse.prompt, variables ?? {});\n }\n\n public getLangchainPrompt(_options?: { placeholders?: Record }): string {\n /**\n * Converts Langfuse prompt into string compatible with Langchain PromptTemplate.\n *\n * It specifically adapts the mustache-style double curly braces {{variable}} used in Langfuse\n * to the single curly brace {variable} format expected by Langchain.\n *\n * @returns {string} The string that can be plugged into Langchain's PromptTemplate.\n */\n return this._transformToLangchainVariables(this.prompt);\n }\n\n public toJSON(): string {\n return JSON.stringify({\n name: this.name,\n prompt: this.prompt,\n version: this.version,\n isFallback: this.isFallback,\n tags: this.tags,\n labels: this.labels,\n type: this.type,\n config: this.config,\n });\n }\n}\n\nexport class ChatPromptClient extends BasePromptClient {\n public readonly promptResponse: ChatPrompt;\n public readonly prompt: ChatMessageWithPlaceholders[];\n\n constructor(prompt: ChatPromptCompat, isFallback = false) {\n const normalizedPrompt = ChatPromptClient.normalizePrompt(prompt.prompt);\n const typedPrompt: ChatPrompt = {\n ...prompt,\n prompt: normalizedPrompt,\n };\n\n super(typedPrompt, isFallback, \"chat\");\n this.promptResponse = typedPrompt;\n this.prompt = normalizedPrompt;\n }\n\n private static normalizePrompt(prompt: ChatMessage[] | ChatMessageWithPlaceholders[]): ChatMessageWithPlaceholders[] {\n // Convert ChatMessages to ChatMessageWithPlaceholders for backward compatibility\n return prompt.map((item): ChatMessageWithPlaceholders => {\n if (\"type\" in item) {\n // Already has type field (new format)\n return item as ChatMessageWithPlaceholders;\n } else {\n // Plain ChatMessage (legacy format) - add type field\n return { type: ChatMessageType.ChatMessage, ...item } as ChatMessageWithPlaceholders;\n }\n });\n }\n\n compile(variables?: Record, placeholders?: Record): (ChatMessageOrPlaceholder | any)[] {\n /**\n * Compiles the chat prompt by replacing placeholders and variables with provided values.\n *\n * First fills-in placeholders by from the provided placeholder parameter.\n * Then compiles variables into the message content.\n * Unresolved placeholders are included in the output as placeholder objects.\n * If you only want to fill-in placeholders, pass an empty object for variables.\n *\n * @param variables - Key-value pairs for Mustache variable substitution in message content\n * @param placeholders - Key-value pairs where keys are placeholder names and values can be ChatMessage arrays\n * @returns Array of ChatMessage objects and placeholder objects with placeholders replaced and variables rendered\n */\n const messagesWithPlaceholdersReplaced: (ChatMessageOrPlaceholder | any)[] = [];\n const placeholderValues = placeholders ?? {};\n\n for (const item of this.prompt) {\n if (\"type\" in item && item.type === ChatMessageType.Placeholder) {\n const placeholderValue = placeholderValues[item.name];\n if (\n Array.isArray(placeholderValue) &&\n placeholderValue.length > 0 &&\n placeholderValue.every((msg) => typeof msg === \"object\" && \"role\" in msg && \"content\" in msg)\n ) {\n messagesWithPlaceholdersReplaced.push(...(placeholderValue as ChatMessage[]));\n } else if (Array.isArray(placeholderValue) && placeholderValue.length === 0) {\n // Empty array provided - skip placeholder (don't include it)\n } else if (placeholderValue !== undefined) {\n // Non-standard placeholder value format, just stringfiy\n messagesWithPlaceholdersReplaced.push(JSON.stringify(placeholderValue));\n } else {\n // Keep unresolved placeholder in the output\n messagesWithPlaceholdersReplaced.push(item as { type: ChatMessageType.Placeholder } & typeof item);\n }\n } else if (\"role\" in item && \"content\" in item && item.type === ChatMessageType.ChatMessage) {\n messagesWithPlaceholdersReplaced.push({\n role: item.role,\n content: item.content,\n });\n }\n }\n\n return messagesWithPlaceholdersReplaced.map((item) => {\n if (typeof item === \"object\" && item !== null && \"role\" in item && \"content\" in item) {\n return {\n ...item,\n content: mustache.render(item.content, variables ?? {}),\n };\n } else {\n // Return placeholder or stringified value as-is\n return item;\n }\n });\n }\n\n public getLangchainPrompt(options?: {\n placeholders?: Record;\n }): (ChatMessage | LangchainMessagesPlaceholder | any)[] {\n /*\n * Converts Langfuse prompt into format compatible with Langchain PromptTemplate.\n *\n * Fills-in placeholders from provided values and converts unresolved ones to Langchain MessagesPlaceholder objects.\n * Transforms variables from {{var}} to {var} format for Langchain without rendering them.\n *\n * @param options - Configuration object\n * @param options.placeholders - Key-value pairs where keys are placeholder names and values can be ChatMessage arrays\n * @returns Array of ChatMessage objects and Langchain MessagesPlaceholder objects with variables transformed for Langchain compatibility.\n *\n * @example\n * ```typescript\n * const client = new ChatPromptClient(prompt);\n * client.getLangchainPrompt({ placeholders: { examples: [{ role: \"user\", content: \"Hello\" }] } });\n * ```\n */\n const messagesWithPlaceholdersReplaced: (ChatMessage | LangchainMessagesPlaceholder | any)[] = [];\n const placeholderValues = options?.placeholders ?? {};\n\n for (const item of this.prompt) {\n if (\"type\" in item && item.type === ChatMessageType.Placeholder) {\n const placeholderValue = placeholderValues[item.name];\n if (\n Array.isArray(placeholderValue) &&\n placeholderValue.length > 0 &&\n placeholderValue.every((msg) => typeof msg === \"object\" && \"role\" in msg && \"content\" in msg)\n ) {\n // Complete placeholder fill-in, replace with it\n messagesWithPlaceholdersReplaced.push(\n ...(placeholderValue as ChatMessage[]).map((msg) => {\n return {\n role: msg.role,\n content: this._transformToLangchainVariables(msg.content),\n };\n })\n );\n } else if (Array.isArray(placeholderValue) && placeholderValue.length === 0) {\n // Skip empty array placeholder\n } else if (placeholderValue !== undefined) {\n // Non-standard placeholder value, just stringify and add directly\n messagesWithPlaceholdersReplaced.push(JSON.stringify(placeholderValue));\n } else {\n // Convert unresolved placeholder to Langchain MessagesPlaceholder\n messagesWithPlaceholdersReplaced.push({\n variableName: item.name,\n optional: false,\n });\n }\n } else if (\"role\" in item && \"content\" in item && item.type === ChatMessageType.ChatMessage) {\n messagesWithPlaceholdersReplaced.push({\n role: item.role,\n content: this._transformToLangchainVariables(item.content),\n });\n }\n }\n\n return messagesWithPlaceholdersReplaced;\n }\n\n public toJSON(): string {\n return JSON.stringify({\n name: this.name,\n prompt: this.promptResponse.prompt.map((item) => {\n if (\"type\" in item && item.type === ChatMessageType.ChatMessage) {\n const { type: _, ...messageWithoutType } = item;\n return messageWithoutType;\n }\n return item;\n }),\n version: this.version,\n isFallback: this.isFallback,\n tags: this.tags,\n labels: this.labels,\n type: this.type,\n config: this.config,\n });\n }\n}\n\nexport type LangfusePromptClient = TextPromptClient | ChatPromptClient;\n", "import { type LangfuseCoreOptions } from \"./types\";\n\nexport function assert(truthyValue: any, message: string): void {\n if (!truthyValue) {\n throw new Error(message);\n }\n}\n\nexport function removeTrailingSlash(url: string): string {\n return url?.replace(/\\/+$/, \"\");\n}\n\nexport interface RetriableOptions {\n retryCount?: number;\n retryDelay?: number;\n retryCheck?: (err: any) => boolean;\n}\n\nexport async function retriable(\n fn: () => Promise,\n props: RetriableOptions = {},\n log: (msg: string) => void\n): Promise {\n const { retryCount = 3, retryDelay = 5000, retryCheck = () => true } = props;\n let lastError = null;\n\n for (let i = 0; i < retryCount + 1; i++) {\n if (i > 0) {\n // don't wait when it's the first try\n await new Promise((resolve) => setTimeout(resolve, retryDelay));\n log(`Retrying ${i + 1} of ${retryCount + 1}`);\n }\n\n try {\n const res = await fn();\n return res;\n } catch (e) {\n lastError = e;\n if (!retryCheck(e)) {\n throw e;\n }\n log(`Retriable error: ${JSON.stringify(e)}`);\n }\n }\n\n throw lastError;\n}\n\n// https://stackoverflow.com/a/8809472\nexport function generateUUID(globalThis?: any): string {\n // Public Domain/MIT\n let d = new Date().getTime(); //Timestamp\n let d2 =\n (globalThis && globalThis.performance && globalThis.performance.now && globalThis.performance.now() * 1000) || 0; //Time in microseconds since page-load or 0 if unsupported\n return \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, function (c) {\n let r = Math.random() * 16; //random number between 0 and 16\n if (d > 0) {\n //Use timestamp until depleted\n r = (d + r) % 16 | 0;\n d = Math.floor(d / 16);\n } else {\n //Use microseconds since page-load if supported\n r = (d2 + r) % 16 | 0;\n d2 = Math.floor(d2 / 16);\n }\n return (c === \"x\" ? r : (r & 0x3) | 0x8).toString(16);\n });\n}\n\nexport function currentTimestamp(): number {\n return new Date().getTime();\n}\n\nexport function currentISOTime(): string {\n return new Date().toISOString();\n}\n\nexport function safeSetTimeout(fn: () => void, timeout: number): any {\n // NOTE: we use this so rarely that it is totally fine to do `safeSetTimeout(fn, 0)``\n // rather than setImmediate.\n const t = setTimeout(fn, timeout) as any;\n // We unref if available to prevent Node.js hanging on exit\n t?.unref && t?.unref();\n return t;\n}\n\nexport function getEnv(key: string): T | undefined {\n if (typeof process !== \"undefined\" && process.env[key]) {\n return process.env[key] as T;\n } else if (typeof globalThis !== \"undefined\") {\n return (globalThis as any)[key];\n }\n return;\n}\n\nexport function configLangfuseSDK(params?: LangfuseCoreOptions, secretRequired: boolean = true): LangfuseCoreOptions {\n const { publicKey, secretKey, ...coreOptions } = params ?? {};\n\n // check environment variables if values not provided\n const finalPublicKey = publicKey ?? getEnv(\"LANGFUSE_PUBLIC_KEY\");\n const finalSecretKey = secretRequired ? secretKey ?? getEnv(\"LANGFUSE_SECRET_KEY\") : undefined;\n const finalBaseUrl = coreOptions.baseUrl ?? getEnv(\"LANGFUSE_BASEURL\");\n\n const finalCoreOptions = {\n ...coreOptions,\n baseUrl: finalBaseUrl,\n };\n\n return {\n publicKey: finalPublicKey,\n ...(secretRequired ? { secretKey: finalSecretKey } : undefined),\n ...finalCoreOptions,\n };\n}\n\nexport const encodeQueryParams = (params?: { [key: string]: any }): string => {\n const queryParams = new URLSearchParams();\n Object.entries(params ?? {}).forEach(([key, value]) => {\n if (value !== undefined && value !== null) {\n // check for date\n if (value instanceof Date) {\n queryParams.append(key, value.toISOString());\n } else {\n queryParams.append(key, value.toString());\n }\n }\n });\n return queryParams.toString();\n};\n", "import { getEnv } from \"./utils\";\n\nconst common_release_envs = [\n // Vercel\n \"VERCEL_GIT_COMMIT_SHA\",\n \"NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA\",\n // Netlify\n \"COMMIT_REF\",\n // Render\n \"RENDER_GIT_COMMIT\",\n // GitLab CI\n \"CI_COMMIT_SHA\",\n // CicleCI\n \"CIRCLE_SHA1\",\n // Cloudflare pages\n \"CF_PAGES_COMMIT_SHA\",\n // AWS Amplify\n \"REACT_APP_GIT_SHA\",\n // Heroku\n \"SOURCE_VERSION\",\n // Trigger.dev\n \"TRIGGER_DEPLOYMENT_ID\",\n] as const;\n\nexport function getCommonReleaseEnvs(): string | undefined {\n for (const key of common_release_envs) {\n const value = getEnv(key);\n if (value) {\n return value;\n }\n }\n return undefined;\n}\n", "import { type LangfuseCore } from \"../index\";\n\nlet fs: any = null;\nlet cryptoModule: any = null;\n\n// Use wrapper to prevent bundlers from trying to resolve the dynamic import\n// Otherwise, the import will be incorrectly resolved as a static import even though it's dynamic\n// Test for browser environment would fail because the import will be incorrectly resolved as a static import and fs and crypto will be unavailable\nconst dynamicImport = (module: string): Promise => {\n return import(/* webpackIgnore: true */ module);\n};\n\nif (typeof (globalThis as any).Deno !== \"undefined\") {\n // Deno\n Promise.all([dynamicImport(\"node:fs\"), dynamicImport(\"node:crypto\")])\n .then(([importedFs, importedCrypto]) => {\n fs = importedFs;\n cryptoModule = importedCrypto;\n })\n .catch(); // Errors are handled on runtime\n} else if (typeof process !== \"undefined\" && process.versions?.node) {\n // Node\n Promise.all([dynamicImport(\"fs\"), dynamicImport(\"crypto\")])\n .then(([importedFs, importedCrypto]) => {\n fs = importedFs;\n cryptoModule = importedCrypto;\n })\n .catch(); // Errors are handled on runtime\n} else if (typeof crypto !== \"undefined\") {\n // Edge runtime (Cloudflare Workers, Vercel Cloud Function)\n cryptoModule = crypto;\n}\n\nimport { type MediaContentType } from \"../types\";\n\ninterface ParsedMediaReference {\n mediaId: string;\n source: string;\n contentType: MediaContentType;\n}\n\nexport type LangfuseMediaResolveMediaReferencesParams = {\n obj: T;\n langfuseClient: LangfuseCore;\n resolveWith: \"base64DataUri\";\n maxDepth?: number;\n};\n\n/**\n * A class for wrapping media objects for upload to Langfuse.\n *\n * This class handles the preparation and formatting of media content for Langfuse,\n * supporting both base64 data URIs and raw content bytes.\n */\nclass LangfuseMedia {\n obj?: object;\n\n _contentBytes?: Buffer;\n _contentType?: MediaContentType;\n _source?: string;\n _mediaId?: string;\n\n constructor(params: {\n obj?: object;\n base64DataUri?: string;\n contentType?: MediaContentType;\n contentBytes?: Buffer;\n filePath?: string;\n }) {\n const { obj, base64DataUri, contentType, contentBytes, filePath } = params;\n\n this.obj = obj;\n this._mediaId = undefined;\n\n if (base64DataUri) {\n const [contentBytesParsed, contentTypeParsed] = this.parseBase64DataUri(base64DataUri);\n this._contentBytes = contentBytesParsed;\n this._contentType = contentTypeParsed;\n this._source = \"base64_data_uri\";\n } else if (contentBytes && contentType) {\n this._contentBytes = contentBytes;\n this._contentType = contentType;\n this._source = \"bytes\";\n } else if (filePath && contentType) {\n if (!fs) {\n throw new Error(\"File system support is not available in this environment\");\n }\n\n if (!fs.existsSync(filePath)) {\n throw new Error(`File at path ${filePath} does not exist`);\n }\n\n this._contentBytes = this.readFile(filePath);\n this._contentType = this._contentBytes ? contentType : undefined;\n this._source = this._contentBytes ? \"file\" : undefined;\n } else {\n console.error(\"base64DataUri, or contentBytes and contentType, or filePath must be provided to LangfuseMedia\");\n }\n }\n\n private readFile(filePath: string): Buffer | undefined {\n try {\n if (!fs) {\n throw new Error(\"File system support is not available in this environment\");\n }\n\n return fs.readFileSync(filePath);\n } catch (error) {\n console.error(`Error reading file at path ${filePath}`, error);\n return undefined;\n }\n }\n\n private parseBase64DataUri(data: string): [Buffer | undefined, MediaContentType | undefined] {\n try {\n if (!data || typeof data !== \"string\") {\n throw new Error(\"Data URI is not a string\");\n }\n\n if (!data.startsWith(\"data:\")) {\n throw new Error(\"Data URI does not start with 'data:'\");\n }\n\n const [header, actualData] = data.slice(5).split(\",\", 2);\n if (!header || !actualData) {\n throw new Error(\"Invalid URI\");\n }\n\n const headerParts = header.split(\";\");\n if (!headerParts.includes(\"base64\")) {\n throw new Error(\"Data is not base64 encoded\");\n }\n\n const contentType = headerParts[0];\n if (!contentType) {\n throw new Error(\"Content type is empty\");\n }\n\n return [Buffer.from(actualData, \"base64\"), contentType as MediaContentType];\n } catch (error) {\n console.error(\"Error parsing base64 data URI\", error);\n return [undefined, undefined];\n }\n }\n\n get contentLength(): number | undefined {\n return this._contentBytes?.length;\n }\n\n get contentSha256Hash(): string | undefined {\n if (!this._contentBytes) {\n return undefined;\n }\n\n if (!cryptoModule) {\n console.error(\"Crypto support is not available in this environment\");\n return undefined;\n }\n\n const sha256Hash = cryptoModule.createHash(\"sha256\").update(this._contentBytes).digest(\"base64\");\n return sha256Hash;\n }\n\n toJSON(): string | undefined {\n if (!this._contentType || !this._source || !this._mediaId) {\n return ``;\n }\n\n return `@@@langfuseMedia:type=${this._contentType}|id=${this._mediaId}|source=${this._source}@@@`;\n }\n\n /**\n * Parses a media reference string into a ParsedMediaReference.\n *\n * Example reference string:\n * \"@@@langfuseMedia:type=image/jpeg|id=some-uuid|source=base64DataUri@@@\"\n *\n * @param referenceString - The reference string to parse.\n * @returns An object with the mediaId, source, and contentType.\n *\n * @throws Error if the reference string is invalid or missing required fields.\n */\n public static parseReferenceString(referenceString: string): ParsedMediaReference {\n const prefix = \"@@@langfuseMedia:\";\n const suffix = \"@@@\";\n\n if (!referenceString.startsWith(prefix)) {\n throw new Error(\"Reference string does not start with '@@@langfuseMedia:type='\");\n }\n\n if (!referenceString.endsWith(suffix)) {\n throw new Error(\"Reference string does not end with '@@@'\");\n }\n\n const content = referenceString.slice(prefix.length, -suffix.length);\n\n const pairs = content.split(\"|\");\n const parsedData: { [key: string]: string } = {};\n\n for (const pair of pairs) {\n const [key, value] = pair.split(\"=\", 2);\n parsedData[key] = value;\n }\n\n if (!(\"type\" in parsedData && \"id\" in parsedData && \"source\" in parsedData)) {\n throw new Error(\"Missing required fields in reference string\");\n }\n\n return {\n mediaId: parsedData[\"id\"],\n source: parsedData[\"source\"],\n contentType: parsedData[\"type\"] as MediaContentType,\n };\n }\n\n /**\n * Replaces the media reference strings in an object with base64 data URIs for the media content.\n *\n * This method recursively traverses an object (up to a maximum depth of 10) looking for media reference strings\n * in the format \"@@@langfuseMedia:...@@@\". When found, it fetches the actual media content using the provided\n * Langfuse client and replaces the reference string with a base64 data URI.\n *\n * If fetching media content fails for a reference string, a warning is logged and the reference string is left unchanged.\n *\n * @param params - Configuration object\n * @param params.obj - The object to process. Can be a primitive value, array, or nested object\n * @param params.langfuseClient - Langfuse client instance used to fetch media content\n * @param params.resolveWith - The representation of the media content to replace the media reference string with. Currently only \"base64DataUri\" is supported.\n * @param params.maxDepth - Optional. Default is 10. The maximum depth to traverse the object.\n *\n * @returns A deep copy of the input object with all media references replaced with base64 data URIs where possible\n *\n * @example\n * ```typescript\n * const obj = {\n * image: \"@@@langfuseMedia:type=image/jpeg|id=123|source=bytes@@@\",\n * nested: {\n * pdf: \"@@@langfuseMedia:type=application/pdf|id=456|source=bytes@@@\"\n * }\n * };\n *\n * const result = await LangfuseMedia.resolveMediaReferences({\n * obj,\n * langfuseClient\n * });\n *\n * // Result:\n * // {\n * // image: \"data:image/jpeg;base64,/9j/4AAQSkZJRg...\",\n * // nested: {\n * // pdf: \"data:application/pdf;base64,JVBERi0xLjcK...\"\n * // }\n * // }\n * ```\n */\n public static async resolveMediaReferences(params: LangfuseMediaResolveMediaReferencesParams): Promise {\n const { obj, langfuseClient, maxDepth = 10 } = params;\n\n async function traverse(obj: T, depth: number): Promise {\n if (depth > maxDepth) {\n return obj;\n }\n\n // Handle string with potential media references\n if (typeof obj === \"string\") {\n const regex = /@@@langfuseMedia:.+?@@@/g;\n const referenceStringMatches = obj.match(regex);\n if (!referenceStringMatches) {\n return obj;\n }\n\n let result = obj;\n const referenceStringToMediaContentMap = new Map();\n\n await Promise.all(\n referenceStringMatches.map(async (referenceString) => {\n try {\n const parsedMediaReference = LangfuseMedia.parseReferenceString(referenceString);\n const mediaData = await langfuseClient.fetchMedia(parsedMediaReference.mediaId);\n const mediaContent = await langfuseClient.fetch(mediaData.url, { method: \"GET\", headers: {} });\n if (mediaContent.status !== 200) {\n throw new Error(\"Failed to fetch media content\");\n }\n\n const base64MediaContent = Buffer.from(await mediaContent.arrayBuffer()).toString(\"base64\");\n const base64DataUri = `data:${mediaData.contentType};base64,${base64MediaContent}`;\n\n referenceStringToMediaContentMap.set(referenceString, base64DataUri);\n } catch (error) {\n console.warn(\"Error fetching media content for reference string\", referenceString, error);\n // Do not replace the reference string if there's an error\n }\n })\n );\n\n for (const [referenceString, base64MediaContent] of referenceStringToMediaContentMap.entries()) {\n result = result.replaceAll(referenceString, base64MediaContent) as T & string;\n }\n\n return result;\n }\n\n // Handle arrays\n if (Array.isArray(obj)) {\n return Promise.all(obj.map(async (item) => await traverse(item, depth + 1))) as Promise;\n }\n\n // Handle objects\n if (typeof obj === \"object\" && obj !== null) {\n return Object.fromEntries(\n await Promise.all(Object.entries(obj).map(async ([key, value]) => [key, await traverse(value, depth + 1)]))\n );\n }\n\n return obj;\n }\n\n return traverse(obj, 0);\n }\n}\n\nexport { LangfuseMedia, type MediaContentType, type ParsedMediaReference };\n", "/**\n * A consistent sampling function that works for arbitrary strings across all JavaScript runtimes.\n */\nexport function isInSample(input: string, sampleRate: number | undefined): boolean {\n if (sampleRate === undefined) {\n return true;\n } else if (sampleRate === 0) {\n return false;\n }\n\n if (sampleRate < 0 || sampleRate > 1 || isNaN(sampleRate)) {\n console.warn(\"Sample rate must be between 0 and 1. Ignoring setting.\");\n\n return true;\n }\n\n return simpleHash(input) < sampleRate;\n}\n\n/**\n * Simple and consistent string hashing function.\n * Uses character codes and prime numbers for good distribution.\n */\nfunction simpleHash(str: string): number {\n let hash = 0;\n const prime = 31;\n\n for (let i = 0; i < str.length; i++) {\n // Use rolling multiplication instead of Math.pow\n hash = (hash * prime + str.charCodeAt(i)) >>> 0;\n }\n\n // Use bit operations for better distribution\n hash = ((hash >>> 16) ^ hash) * 0x45d9f3b;\n hash = ((hash >>> 16) ^ hash) * 0x45d9f3b;\n hash = (hash >>> 16) ^ hash;\n\n return Math.abs(hash) / 0x7fffffff;\n}\n", "import { type LangfusePersistedProperty } from \"./types\";\n\nexport class LangfuseMemoryStorage {\n private _memoryStorage: { [key: string]: any | undefined } = {};\n\n getProperty(key: LangfusePersistedProperty): any | undefined {\n return this._memoryStorage[key];\n }\n\n setProperty(key: LangfusePersistedProperty, value: any | null): void {\n this._memoryStorage[key] = value !== null ? value : undefined;\n }\n}\n", "import { SimpleEventEmitter } from \"./eventemitter\";\nimport { LangfusePromptCache } from \"./prompts/promptCache\";\nimport { ChatPromptClient, TextPromptClient, type LangfusePromptClient } from \"./prompts/promptClients\";\nimport { getCommonReleaseEnvs } from \"./release-env\";\nimport {\n ChatMessageType,\n LangfusePersistedProperty,\n type ChatMessage,\n type CreateLangfuseDatasetBody,\n type CreateLangfuseDatasetItemBody,\n type CreateLangfuseDatasetItemResponse,\n type CreateLangfuseDatasetResponse,\n type CreateLangfuseDatasetRunItemBody,\n type CreateLangfuseDatasetRunItemResponse,\n type CreateLangfuseEventBody,\n type CreateLangfuseGenerationBody,\n type CreateLangfusePromptBody,\n type CreateLangfusePromptResponse,\n type GetMediaUploadUrlRequest,\n type GetMediaUploadUrlResponse,\n type PatchMediaBody,\n type CreateLangfuseScoreBody,\n type CreateLangfuseSpanBody,\n type CreateLangfuseTraceBody,\n type CreateChatPromptBody,\n type CreateChatPromptBodyWithPlaceholders,\n type CreateTextPromptBody,\n type DatasetItem,\n type DeferRuntime,\n type EventBody,\n type GetLangfuseDatasetItemsQuery,\n type GetLangfuseDatasetItemsResponse,\n type GetLangfuseDatasetParams,\n type GetLangfuseDatasetResponse,\n type GetLangfuseDatasetRunParams,\n type GetLangfuseDatasetRunResponse,\n type GetLangfuseDatasetRunsQuery,\n type GetLangfuseDatasetRunsResponse,\n type GetLangfuseObservationResponse,\n type GetLangfuseObservationsQuery,\n type GetLangfuseObservationsResponse,\n type GetLangfusePromptResponse,\n type GetLangfuseSessionsQuery,\n type GetLangfuseSessionsResponse,\n type GetLangfuseTraceResponse,\n type GetLangfuseTracesQuery,\n type GetLangfuseTracesResponse,\n type GetMediaResponse,\n type IngestionReturnType,\n type LangfuseCoreOptions,\n type LangfuseFetchOptions,\n type LangfuseFetchResponse,\n type LangfuseObject,\n type LangfuseQueueItem,\n type MaskFunction,\n type PlaceholderMessage,\n type PromptInput,\n type SingleIngestionEvent,\n type UpdateLangfuseGenerationBody,\n type UpdateLangfuseSpanBody,\n type UpdatePromptBody,\n} from \"./types\";\nimport { LangfuseMedia, type LangfuseMediaResolveMediaReferencesParams } from \"./media/LangfuseMedia\";\nimport {\n currentISOTime,\n encodeQueryParams,\n generateUUID,\n getEnv,\n removeTrailingSlash,\n retriable,\n safeSetTimeout,\n type RetriableOptions,\n} from \"./utils\";\nimport { isInSample } from \"./sampling\";\n\nexport * from \"./prompts/promptClients\";\nexport * from \"./media/LangfuseMedia\";\n\nexport { LangfuseMemoryStorage } from \"./storage-memory\";\nexport type { LangfusePromptRecord } from \"./types\";\nexport * as utils from \"./utils\";\nexport type IngestionBody = SingleIngestionEvent[\"body\"];\n\nconst MAX_EVENT_SIZE_BYTES = getEnv(\"LANGFUSE_MAX_EVENT_SIZE_BYTES\")\n ? Number(getEnv(\"LANGFUSE_MAX_EVENT_SIZE_BYTES\"))\n : 1_000_000;\nconst MAX_BATCH_SIZE_BYTES = getEnv(\"LANGFUSE_MAX_BATCH_SIZE_BYTES\")\n ? Number(getEnv(\"LANGFUSE_MAX_BATCH_SIZE_BYTES\"))\n : 2_500_000;\nconst ENVIRONMENT_PATTERN = /^(?!langfuse)[a-z0-9_-]+$/;\n// NOTE: This list whitelists environments that are used for traces ingested by Langfuse. Please mirror edits to this list in the Langfuse ingestion schema.\nconst WHITELISTED_LANGFUSE_INTERNAL_ENVIRONMENTS = [\"langfuse-prompt-experiment\"];\n\nclass LangfuseFetchHttpError extends Error {\n name = \"LangfuseFetchHttpError\";\n body: string | undefined;\n\n constructor(\n public response: LangfuseFetchResponse,\n body: string\n ) {\n super(\"HTTP error while fetching Langfuse: \" + response.status + \" and body: \" + body);\n }\n}\n\nclass LangfuseFetchNetworkError extends Error {\n name = \"LangfuseFetchNetworkError\";\n\n constructor(public error: unknown) {\n super(\"Network error while fetching Langfuse\", error instanceof Error ? { cause: error } : {});\n }\n}\n\nfunction isLangfuseFetchHttpError(error: any): error is LangfuseFetchHttpError {\n return typeof error === \"object\" && error.name === \"LangfuseFetchHttpError\";\n}\n\nfunction isLangfuseFetchNetworkError(error: any): error is LangfuseFetchNetworkError {\n return typeof error === \"object\" && error.name === \"LangfuseFetchNetworkError\";\n}\n\nfunction isLangfuseFetchError(err: any): boolean {\n return isLangfuseFetchHttpError(err) || isLangfuseFetchNetworkError(err);\n}\n\n// Constants for URLs\nconst SUPPORT_URL = \"https://langfuse.com/support\";\nconst API_DOCS_URL = \"https://api.reference.langfuse.com\";\nconst RBAC_DOCS_URL = \"https://langfuse.com/docs/rbac\";\nconst INSTALLATION_DOCS_URL = \"https://langfuse.com/docs/sdk/typescript/guide\";\nconst RATE_LIMITS_URL = \"https://langfuse.com/faq/all/api-limits\";\nconst NPM_PACKAGE_URL = \"https://www.npmjs.com/package/langfuse\";\n\n// Error messages\nconst updatePromptResponse = `Make sure to keep your SDK updated, refer to ${NPM_PACKAGE_URL} for details.`;\nconst defaultServerErrorPrompt = `This is an unusual occurrence and we are monitoring it closely. For help, please contact support: ${SUPPORT_URL}.`;\nconst defaultErrorResponse = `Unexpected error occurred. Please check your request and contact support: ${SUPPORT_URL}.`;\n\n// Error response map\nconst errorResponseByCode = new Map([\n // Internal error category: 5xx errors, 404 error\n [500, `Internal server error occurred. For help, please contact support: ${SUPPORT_URL}`],\n [501, `Not implemented. Please check your request and contact support for help: ${SUPPORT_URL}.`],\n [502, `Bad gateway. ${defaultServerErrorPrompt}`],\n [503, `Service unavailable. ${defaultServerErrorPrompt}`],\n [504, `Gateway timeout. ${defaultServerErrorPrompt}`],\n [404, `Internal error occurred. ${defaultServerErrorPrompt}`],\n\n // Client error category: 4xx errors, excluding 404\n [\n 400,\n `Bad request. Please check your request for any missing or incorrect parameters. Refer to our API docs: ${API_DOCS_URL} for details.`,\n ],\n [\n 401,\n `Unauthorized. Please check your public/private host settings. Refer to our installation and setup guide: ${INSTALLATION_DOCS_URL} for details on SDK configuration.`,\n ],\n [403, `Forbidden. Please check your access control settings. Refer to our RBAC docs: ${RBAC_DOCS_URL} for details.`],\n [429, `Rate limit exceeded. For more information on rate limits please see: ${RATE_LIMITS_URL}`],\n]);\n\n// Returns a user-friendly error message based on the HTTP status code\nfunction getErrorResponseByCode(code: number | undefined): string {\n if (!code) {\n return `${defaultErrorResponse} ${updatePromptResponse}`;\n }\n\n const errorResponse = errorResponseByCode.get(code) || defaultErrorResponse;\n return `${code}: ${errorResponse} ${updatePromptResponse}`;\n}\n\nfunction logIngestionError(error: any): void {\n if (isLangfuseFetchHttpError(error)) {\n const code = error.response.status;\n const errorResponse = getErrorResponseByCode(code);\n console.error(\"[Langfuse SDK]\", errorResponse, `Error details: ${error}`);\n } else if (isLangfuseFetchNetworkError(error)) {\n console.error(\"[Langfuse SDK] Network error: \", error);\n } else {\n console.error(\"[Langfuse SDK] Unknown error:\", error);\n }\n}\n\nabstract class LangfuseCoreStateless {\n // options\n protected secretKey: string | undefined;\n protected publicKey: string;\n baseUrl: string;\n additionalHeaders: Record = {};\n private flushAt: number;\n private flushInterval: number;\n private requestTimeout: number;\n private removeDebugCallback?: () => void;\n private debugMode: boolean = false;\n private pendingEventProcessingPromises: Record> = {};\n private pendingIngestionPromises: Record> = {};\n private release: string | undefined;\n protected sdkIntegration: string;\n private enabled: boolean;\n protected isLocalEventExportEnabled: boolean;\n private localEventExportMap: Map = new Map();\n private projectId: string | undefined;\n private mask: MaskFunction | undefined;\n private sampleRate: number | undefined;\n private environment: string | undefined;\n\n // internal\n protected _events = new SimpleEventEmitter();\n protected _flushTimer?: any;\n protected _retryOptions: RetriableOptions;\n\n // Abstract methods to be overridden by implementations\n abstract fetch(url: string, options: LangfuseFetchOptions): Promise;\n abstract getLibraryId(): string;\n abstract getLibraryVersion(): string;\n\n // This is our abstracted storage. Each implementation should handle its own\n abstract getPersistedProperty(key: LangfusePersistedProperty): T | undefined;\n abstract setPersistedProperty(key: LangfusePersistedProperty, value: T | null): void;\n\n constructor(params: LangfuseCoreOptions) {\n const { publicKey, secretKey, enabled, _projectId, _isLocalEventExportEnabled, ...options } = params;\n\n this._events.on(\"error\", (payload) => {\n console.error(`[Langfuse SDK] ${typeof payload === \"string\" ? payload : JSON.stringify(payload)}`);\n });\n\n this.enabled = enabled === false ? false : true;\n this.publicKey = publicKey ?? \"\";\n this.secretKey = secretKey;\n this.baseUrl = removeTrailingSlash(options?.baseUrl || \"https://cloud.langfuse.com\");\n this.additionalHeaders = options?.additionalHeaders || {};\n this.flushAt = options?.flushAt ? Math.max(options?.flushAt, 1) : 15;\n this.flushInterval = options?.flushInterval ?? 10000;\n this.release = options?.release ?? getEnv(\"LANGFUSE_RELEASE\") ?? getCommonReleaseEnvs() ?? undefined;\n this.mask = options?.mask;\n this.sampleRate =\n options?.sampleRate ?? (getEnv(\"LANGFUSE_SAMPLE_RATE\") ? Number(getEnv(\"LANGFUSE_SAMPLE_RATE\")) : undefined);\n\n if (this.sampleRate) {\n this._events.emit(\"debug\", `Langfuse trace sampling enabled with sampleRate ${this.sampleRate}.`);\n }\n\n this.environment = options?.environment ?? getEnv(\"LANGFUSE_TRACING_ENVIRONMENT\");\n if (\n this.environment &&\n !(\n ENVIRONMENT_PATTERN.test(this.environment) ||\n WHITELISTED_LANGFUSE_INTERNAL_ENVIRONMENTS.includes(this.environment)\n ) &&\n !_isLocalEventExportEnabled\n ) {\n this._events.emit(\n \"error\",\n `Invalid tracing environment set: ${this.environment} . Environment must match regex ${ENVIRONMENT_PATTERN}. Events will be rejected by Langfuse server.`\n );\n }\n\n this._retryOptions = {\n retryCount: options?.fetchRetryCount ?? 3,\n retryDelay: options?.fetchRetryDelay ?? 3000,\n retryCheck: isLangfuseFetchError,\n };\n this.requestTimeout = options?.requestTimeout ?? 5000; // 5 seconds\n\n this.sdkIntegration = options?.sdkIntegration ?? \"DEFAULT\";\n\n this.isLocalEventExportEnabled = _isLocalEventExportEnabled ?? false;\n\n if (this.isLocalEventExportEnabled && !_projectId) {\n this._events.emit(\n \"error\",\n \"Local event export is enabled, but no project ID was provided. Disabling local export.\"\n );\n this.isLocalEventExportEnabled = false;\n return;\n } else if (!this.isLocalEventExportEnabled && _projectId) {\n this._events.emit(\n \"error\",\n \"Local event export is disabled, but a project ID was provided. Disabling local export.\"\n );\n this.isLocalEventExportEnabled = false;\n return;\n } else {\n this.projectId = _projectId;\n }\n }\n\n getSdkIntegration(): string {\n return this.sdkIntegration;\n }\n\n protected getCommonEventProperties(): any {\n return {\n $lib: this.getLibraryId(),\n $lib_version: this.getLibraryVersion(),\n };\n }\n\n on(event: string, cb: (...args: any[]) => void): () => void {\n return this._events.on(event, cb);\n }\n\n debug(enabled: boolean = true): void {\n this.removeDebugCallback?.();\n\n this.debugMode = enabled;\n\n if (enabled) {\n this.removeDebugCallback = this.on(\"*\", (event, payload) => {\n // we already have a logger attached to error events\n if (event === \"error\") {\n return;\n }\n\n console.log(\"[Langfuse Debug]\", event, JSON.stringify(payload));\n });\n }\n }\n\n /***\n *** Handlers for each object type\n ***/\n protected traceStateless(body: CreateLangfuseTraceBody): string {\n const { id: bodyId, timestamp: bodyTimestamp, release: bodyRelease, ...rest } = body;\n\n const id = bodyId ?? generateUUID();\n const release = bodyRelease ?? this.release;\n\n const parsedBody: CreateLangfuseTraceBody = {\n id,\n release,\n timestamp: bodyTimestamp ?? new Date(),\n environment: this.environment,\n ...rest,\n };\n this.enqueue(\"trace-create\", parsedBody);\n return id;\n }\n\n protected eventStateless(body: CreateLangfuseEventBody): string {\n const { id: bodyId, startTime: bodyStartTime, ...rest } = body;\n\n const id = bodyId ?? generateUUID();\n\n const parsedBody: CreateLangfuseEventBody = {\n id,\n startTime: bodyStartTime ?? new Date(),\n environment: this.environment,\n ...rest,\n };\n this.enqueue(\"event-create\", parsedBody);\n return id;\n }\n\n protected spanStateless(body: CreateLangfuseSpanBody): string {\n const { id: bodyId, startTime: bodyStartTime, ...rest } = body;\n\n const id = bodyId || generateUUID();\n\n const parsedBody: CreateLangfuseSpanBody = {\n id,\n startTime: bodyStartTime ?? new Date(),\n environment: this.environment,\n ...rest,\n };\n this.enqueue(\"span-create\", parsedBody);\n return id;\n }\n\n protected generationStateless(\n body: Omit & PromptInput\n ): string {\n const { id: bodyId, startTime: bodyStartTime, prompt, ...rest } = body;\n const promptDetails =\n prompt && !prompt.isFallback ? { promptName: prompt.name, promptVersion: prompt.version } : {};\n\n const id = bodyId || generateUUID();\n\n const parsedBody: CreateLangfuseGenerationBody = {\n id,\n startTime: bodyStartTime ?? new Date(),\n environment: this.environment,\n ...promptDetails,\n ...rest,\n };\n\n this.enqueue(\"generation-create\", parsedBody);\n return id;\n }\n\n protected scoreStateless(body: CreateLangfuseScoreBody): string {\n const { id: bodyId, ...rest } = body;\n\n const id = bodyId || generateUUID();\n\n const parsedBody: CreateLangfuseScoreBody = {\n id,\n environment: this.environment,\n ...rest,\n };\n this.enqueue(\"score-create\", parsedBody);\n return id;\n }\n\n protected updateSpanStateless(body: UpdateLangfuseSpanBody): string {\n this.enqueue(\"span-update\", body);\n return body.id;\n }\n\n protected updateGenerationStateless(\n body: Omit & PromptInput\n ): string {\n const { prompt, ...rest } = body;\n const promptDetails =\n prompt && !prompt.isFallback ? { promptName: prompt.name, promptVersion: prompt.version } : {};\n\n const parsedBody: UpdateLangfuseGenerationBody = {\n ...promptDetails,\n ...rest,\n };\n this.enqueue(\"generation-update\", parsedBody);\n return body.id;\n }\n\n protected async _getDataset(name: GetLangfuseDatasetParams[\"datasetName\"]): Promise {\n const encodedName = encodeURIComponent(name);\n return this.fetchAndLogErrors(\n `${this.baseUrl}/api/public/v2/datasets/${encodedName}`,\n this._getFetchOptions({ method: \"GET\" })\n );\n }\n\n protected async _getDatasetItems(query: GetLangfuseDatasetItemsQuery): Promise {\n const params = new URLSearchParams();\n Object.entries(query ?? {}).forEach(([key, value]) => {\n if (value !== undefined && value !== null) {\n params.append(key, value.toString());\n }\n });\n\n return this.fetchAndLogErrors(\n `${this.baseUrl}/api/public/dataset-items?${params}`,\n this._getFetchOptions({ method: \"GET\" })\n );\n }\n\n protected async _fetchMedia(id: string): Promise {\n return this.fetchAndLogErrors(`${this.baseUrl}/api/public/media/${id}`, this._getFetchOptions({ method: \"GET\" }));\n }\n\n async fetchTraces(query?: GetLangfuseTracesQuery): Promise {\n // destructure the response into data and meta to be explicit about the shape of the response and add type-warnings in case the API changes\n const { data, meta } = await this.fetchAndLogErrors(\n `${this.baseUrl}/api/public/traces?${encodeQueryParams(query)}`,\n this._getFetchOptions({ method: \"GET\" })\n );\n return { data, meta };\n }\n\n async fetchTrace(traceId: string): Promise<{ data: GetLangfuseTraceResponse }> {\n const res = await this.fetchAndLogErrors(\n `${this.baseUrl}/api/public/traces/${traceId}`,\n this._getFetchOptions({ method: \"GET\" })\n );\n return { data: res };\n }\n\n async fetchObservations(query?: GetLangfuseObservationsQuery): Promise {\n // destructure the response into data and meta to be explicit about the shape of the response and add type-warnings in case the API changes\n const { data, meta } = await this.fetchAndLogErrors(\n `${this.baseUrl}/api/public/observations?${encodeQueryParams(query)}`,\n this._getFetchOptions({ method: \"GET\" })\n );\n\n return { data, meta };\n }\n\n async fetchObservation(observationId: string): Promise<{ data: GetLangfuseObservationResponse }> {\n const res = await this.fetchAndLogErrors(\n `${this.baseUrl}/api/public/observations/${observationId}`,\n this._getFetchOptions({ method: \"GET\" })\n );\n\n return { data: res };\n }\n\n async fetchSessions(query?: GetLangfuseSessionsQuery): Promise {\n // destructure the response into data and meta to be explicit about the shape of the response and add type-warnings in case the API changes\n const { data, meta } = await this.fetchAndLogErrors(\n `${this.baseUrl}/api/public/sessions?${encodeQueryParams(query)}`,\n this._getFetchOptions({ method: \"GET\" })\n );\n\n return { data, meta };\n }\n\n async getDatasetRun(params: GetLangfuseDatasetRunParams): Promise {\n const encodedDatasetName = encodeURIComponent(params.datasetName);\n const encodedRunName = encodeURIComponent(params.runName);\n return this.fetchAndLogErrors(\n `${this.baseUrl}/api/public/datasets/${encodedDatasetName}/runs/${encodedRunName}`,\n this._getFetchOptions({ method: \"GET\" })\n );\n }\n\n async getDatasetRuns(\n datasetName: string,\n query?: GetLangfuseDatasetRunsQuery\n ): Promise {\n return this.fetchAndLogErrors(\n `${this.baseUrl}/api/public/datasets/${encodeURIComponent(datasetName)}/runs?${encodeQueryParams(query)}`,\n this._getFetchOptions({ method: \"GET\" })\n );\n }\n\n async createDatasetRunItem(body: CreateLangfuseDatasetRunItemBody): Promise {\n return this.fetchAndLogErrors(\n `${this.baseUrl}/api/public/dataset-run-items`,\n this._getFetchOptions({ method: \"POST\", body: JSON.stringify(body) })\n );\n }\n\n /**\n * Creates a dataset. Upserts the dataset if it already exists.\n *\n * @param dataset Can be either a string (name) or an object with name, description and metadata\n * @returns A promise that resolves to the response of the create operation.\n */\n async createDataset(\n dataset:\n | string // name\n | {\n name: string;\n description?: string;\n metadata?: any;\n }\n ): Promise {\n const body: CreateLangfuseDatasetBody = typeof dataset === \"string\" ? { name: dataset } : dataset;\n return this.fetchAndLogErrors(\n `${this.baseUrl}/api/public/datasets`,\n this._getFetchOptions({ method: \"POST\", body: JSON.stringify(body) })\n );\n }\n\n /**\n * Creates a dataset item. Upserts the item if it already exists.\n * @param body The body of the dataset item to be created.\n * @returns A promise that resolves to the response of the create operation.\n */\n async createDatasetItem(body: CreateLangfuseDatasetItemBody): Promise {\n return this.fetchAndLogErrors(\n `${this.baseUrl}/api/public/dataset-items`,\n this._getFetchOptions({ method: \"POST\", body: JSON.stringify(body) })\n );\n }\n\n async getDatasetItem(id: string): Promise {\n return this.fetchAndLogErrors(\n `${this.baseUrl}/api/public/dataset-items/${id}`,\n this._getFetchOptions({ method: \"GET\" })\n );\n }\n\n protected _parsePayload(response: any): any {\n try {\n return JSON.parse(response);\n } catch {\n return response;\n }\n }\n\n async createPromptStateless(body: CreateLangfusePromptBody): Promise {\n return this.fetchAndLogErrors(\n `${this.baseUrl}/api/public/v2/prompts`,\n this._getFetchOptions({ method: \"POST\", body: JSON.stringify(body) })\n );\n }\n\n async updatePromptStateless(\n body: UpdatePromptBody & { name: string; version: number }\n ): Promise {\n return this.fetchAndLogErrors(\n `${this.baseUrl}/api/public/v2/prompts/${encodeURIComponent(body.name)}/versions/${encodeURIComponent(body.version)}`,\n this._getFetchOptions({ method: \"PATCH\", body: JSON.stringify(body) })\n );\n }\n\n async getPromptStateless(\n name: string,\n version?: number,\n label?: string,\n maxRetries?: number,\n requestTimeout?: number // this will override the default requestTimeout for fetching prompts. Together with maxRetries, it can be used to fetch prompts fast when the first fetch is slow.\n ): Promise {\n const encodedName = encodeURIComponent(name);\n const params = new URLSearchParams();\n\n // Add parameters only if they are provided\n if (version && label) {\n throw new Error(\"Provide either version or label, not both.\");\n }\n\n if (version) {\n params.append(\"version\", version.toString());\n }\n\n if (label) {\n params.append(\"label\", label);\n }\n\n const url = `${this.baseUrl}/api/public/v2/prompts/${encodedName}${params.size ? \"?\" + params : \"\"}`;\n\n const boundedMaxRetries = this._getBoundedMaxRetries({ maxRetries, defaultMaxRetries: 2, maxRetriesUpperBound: 4 });\n const retryOptions = { ...this._retryOptions, retryCount: boundedMaxRetries, retryDelay: 500 };\n const retryLogger = (string: string): void =>\n this._events.emit(\"retry\", string + \", \" + url + \", \" + JSON.stringify(retryOptions));\n\n return retriable(\n async () => {\n const res = await this.fetch(\n url,\n this._getFetchOptions({ method: \"GET\", fetchTimeout: requestTimeout ?? this.requestTimeout })\n ).catch((e) => {\n if (e.name === \"AbortError\") {\n throw new LangfuseFetchNetworkError(\"Fetch request timed out\");\n }\n throw new LangfuseFetchNetworkError(e);\n });\n\n if (res.status >= 500) {\n throw new LangfuseFetchHttpError(res, await res.text());\n }\n\n const data = await res.json();\n\n return { fetchResult: res.status === 200 ? \"success\" : \"failure\", data };\n },\n retryOptions,\n retryLogger\n );\n }\n\n private _getBoundedMaxRetries(params: {\n maxRetries?: number;\n defaultMaxRetries?: number;\n maxRetriesUpperBound?: number;\n }): number {\n const defaultMaxRetries = Math.max(params.defaultMaxRetries ?? 2, 0);\n const maxRetriesUpperBound = Math.max(params.maxRetriesUpperBound ?? 4, 0);\n\n if (params.maxRetries === undefined) {\n return defaultMaxRetries;\n }\n\n return Math.min(Math.max(params.maxRetries, 0), maxRetriesUpperBound);\n }\n\n /***\n *** QUEUEING AND FLUSHING\n ***/\n protected enqueue(type: LangfuseObject, body: EventBody): void {\n if (!this.enabled) {\n return;\n }\n\n // Sampling\n const traceId = this.parseTraceId(type, body);\n if (!traceId) {\n this._events.emit(\n \"warning\",\n \"Failed to parse traceID for sampling. Please open a Github issue in https://github.com/langfuse/langfuse/issues/new/choose\"\n );\n } else if (!isInSample(traceId, this.sampleRate)) {\n this._events.emit(\"debug\", `Event with trace ID ${traceId} is out of sample. Skipping.`);\n\n return;\n }\n\n const promise = this.processEnqueueEvent(type, body);\n const promiseId = generateUUID();\n this.pendingEventProcessingPromises[promiseId] = promise;\n\n promise\n .catch((e) => {\n this._events.emit(\"error\", e);\n })\n .finally(() => {\n delete this.pendingEventProcessingPromises[promiseId];\n });\n }\n\n protected async processEnqueueEvent(type: LangfuseObject, body: EventBody): Promise {\n this.maskEventBodyInPlace(body);\n await this.processMediaInEvent(type, body);\n const finalEventBody = this.truncateEventBody(body, MAX_EVENT_SIZE_BYTES);\n\n try {\n JSON.stringify(finalEventBody);\n } catch (e) {\n this._events.emit(\"error\", `Event Body for ${type} is not JSON-serializable: ${e}`);\n return;\n }\n\n const queue = this.getPersistedProperty(LangfusePersistedProperty.Queue) || [];\n\n queue.push({\n id: generateUUID(),\n type,\n timestamp: currentISOTime(),\n body: finalEventBody as any, // TODO: fix typecast. EventBody is not correctly narrowed to the correct type dictated by the 'type' property. This should be part of a larger type cleanup.\n metadata: undefined,\n });\n this.setPersistedProperty(LangfusePersistedProperty.Queue, queue);\n\n this._events.emit(type, finalEventBody);\n\n // Flush queued events if we meet the flushAt length\n if (queue.length >= this.flushAt) {\n this.flush();\n }\n\n if (this.flushInterval && !this._flushTimer) {\n this._flushTimer = safeSetTimeout(() => this.flush(), this.flushInterval);\n }\n }\n\n private maskEventBodyInPlace(body: EventBody): void {\n if (!this.mask) {\n return;\n }\n\n const maskableKeys = [\"input\", \"output\"] as const;\n\n for (const key of maskableKeys) {\n if (key in body) {\n try {\n body[key as keyof EventBody] = this.mask({ data: body[key as keyof EventBody] });\n } catch (e) {\n this._events.emit(\"error\", `Error masking ${key}: ${e}`);\n body[key as keyof EventBody] = \"\";\n }\n }\n }\n }\n\n /**\n * Truncates the event body if its byte size exceeds the specified maximum byte size.\n * Emits a warning event if truncation occurs.\n * The fields that may be truncated are: \"input\", \"output\", and \"metadata\".\n * The fields are truncated in the order of their size, from largest to smallest until the total byte size is within the limit.\n */\n protected truncateEventBody(body: EventBody, maxByteSize: number): EventBody {\n const bodySize = this.getByteSize(body);\n\n if (bodySize <= maxByteSize) {\n return body;\n }\n\n this._events.emit(\"warning\", `Event Body is too large (${bodySize} bytes) and will be truncated`);\n\n // Sort keys by size and truncate the largest keys first\n const keysToCheck = [\"input\", \"output\", \"metadata\"] as const;\n const keySizes = keysToCheck\n .map((key) => ({ key, size: key in body ? this.getByteSize(body[key as keyof typeof body]) : 0 }))\n .sort((a, b) => b.size - a.size);\n\n let result = { ...body };\n let currentSize = bodySize;\n\n for (const { key, size } of keySizes) {\n if (currentSize > maxByteSize && Object.prototype.hasOwnProperty.call(result, key)) {\n result = { ...result, [key]: \"\" };\n\n this._events.emit(\"warning\", `Truncated ${key} due to total size exceeding limit`);\n\n currentSize -= size;\n }\n }\n\n return result;\n }\n\n private getByteSize(obj: any): number {\n const serialized = JSON.stringify(obj);\n\n // Use TextEncoder if available, otherwise fallback to encodeURIComponent\n if (typeof TextEncoder !== \"undefined\") {\n return new TextEncoder().encode(serialized).length;\n } else {\n return encodeURIComponent(serialized).replace(/%[A-F\\d]{2}/g, \"U\").length;\n }\n }\n\n protected async processMediaInEvent(type: LangfuseObject, body: EventBody): Promise {\n if (!body) {\n return;\n }\n\n const traceId = this.parseTraceId(type, body);\n\n if (!traceId) {\n this._events.emit(\"warning\", \"traceId is required for media upload\");\n return;\n }\n\n const observationId = (type.includes(\"generation\") || type.includes(\"span\")) && body.id ? body.id : undefined;\n\n await Promise.all(\n ([\"input\", \"output\", \"metadata\"] as const).map(async (field) => {\n if (body[field as keyof EventBody]) {\n body[field as keyof EventBody] =\n (await this.findAndProcessMedia({\n data: body[field as keyof EventBody],\n traceId,\n observationId,\n field,\n }).catch((e) => {\n this._events.emit(\"error\", `Error processing multimodal event: ${e}`);\n })) ?? body[field as keyof EventBody];\n }\n })\n );\n }\n\n protected parseTraceId(type: LangfuseObject, body: EventBody): string | null | undefined {\n return \"traceId\" in body ? body.traceId : type.includes(\"trace\") ? body.id : undefined;\n }\n\n protected async findAndProcessMedia({\n data,\n traceId,\n observationId,\n field,\n }: {\n data: any;\n traceId: string;\n observationId?: string;\n field: \"input\" | \"output\" | \"metadata\";\n }): Promise {\n const seenObjects = new WeakMap();\n const maxLevels = 10;\n\n const processRecursively = async (data: any, level: number): Promise => {\n if (typeof data === \"string\" && data.startsWith(\"data:\")) {\n const media = new LangfuseMedia({ base64DataUri: data });\n await this.processMediaItem({ media, traceId, observationId, field });\n\n return media;\n }\n if (typeof data !== \"object\" || data === null) {\n return data;\n }\n\n // Use WeakMap to detect cycles\n if (seenObjects.has(data) || level > maxLevels) {\n return data;\n }\n\n seenObjects.set(data, true);\n\n if (data instanceof LangfuseMedia || Object.prototype.toString.call(data) === \"[object LangfuseMedia]\") {\n await this.processMediaItem({ media: data, traceId, observationId, field });\n\n return data;\n }\n\n if (Array.isArray(data)) {\n return await Promise.all(data.map((item) => processRecursively(item, level + 1)));\n }\n\n // Parse OpenAI input audio data which is passed as base64 string NOT in the data uri format\n if (typeof data === \"object\" && data !== null) {\n if (\"input_audio\" in data && typeof data[\"input_audio\"] === \"object\" && \"data\" in data.input_audio) {\n const media = new LangfuseMedia({\n base64DataUri: `data:audio/${data.input_audio[\"format\"] || \"wav\"};base64,${data.input_audio.data}`,\n });\n\n await this.processMediaItem({ media, traceId, observationId, field });\n\n return {\n ...data,\n input_audio: {\n ...data.input_audio,\n data: media,\n },\n };\n }\n\n // OpenAI output audio data is passed as base64 string NOT in the data uri format\n if (\"audio\" in data && typeof data[\"audio\"] === \"object\" && \"data\" in data.audio) {\n const media = new LangfuseMedia({\n base64DataUri: `data:audio/${data.audio[\"format\"] || \"wav\"};base64,${data.audio.data}`,\n });\n\n await this.processMediaItem({ media, traceId, observationId, field });\n\n return {\n ...data,\n audio: {\n ...data.audio,\n data: media,\n },\n };\n }\n\n // Recursively process nested objects\n return Object.fromEntries(\n await Promise.all(\n Object.entries(data).map(async ([key, value]) => [key, await processRecursively(value, level + 1)])\n )\n );\n }\n\n return data;\n };\n\n return await processRecursively(data, 1);\n }\n\n private async processMediaItem({\n media,\n traceId,\n observationId,\n field,\n }: {\n media: LangfuseMedia;\n traceId: string;\n observationId?: string;\n field: string;\n }): Promise {\n try {\n if (!media.contentLength || !media._contentType || !media.contentSha256Hash || !media._contentBytes) {\n return;\n }\n\n const getUploadUrlBody: GetMediaUploadUrlRequest = {\n contentLength: media.contentLength,\n traceId,\n observationId,\n field,\n contentType: media._contentType,\n sha256Hash: media.contentSha256Hash,\n };\n\n const fetchResponse = await this.fetch(\n `${this.baseUrl}/api/public/media`,\n this._getFetchOptions({\n method: \"POST\",\n body: JSON.stringify(getUploadUrlBody),\n })\n );\n\n const uploadUrlResponse = (await fetchResponse.json()) as GetMediaUploadUrlResponse;\n\n const { uploadUrl, mediaId } = uploadUrlResponse;\n media._mediaId = mediaId;\n\n if (uploadUrl) {\n this._events.emit(\"debug\", `Uploading media ${mediaId}`);\n const startTime = Date.now();\n\n const uploadResponse = await this.uploadMediaWithBackoff({\n uploadUrl,\n contentBytes: media._contentBytes,\n contentType: media._contentType,\n contentSha256Hash: media.contentSha256Hash,\n maxRetries: 3,\n baseDelay: 1000,\n });\n\n if (!uploadResponse) {\n throw Error(\"Media upload process failed\");\n }\n\n const patchMediaBody: PatchMediaBody = {\n uploadedAt: new Date().toISOString(),\n uploadHttpStatus: uploadResponse.status,\n uploadHttpError: await uploadResponse.text(),\n uploadTimeMs: Date.now() - startTime,\n };\n\n await this.fetch(\n `${this.baseUrl}/api/public/media/${mediaId}`,\n this._getFetchOptions({ method: \"PATCH\", body: JSON.stringify(patchMediaBody) })\n );\n this._events.emit(\"debug\", `Media upload status reported for ${mediaId}`);\n } else {\n this._events.emit(\"debug\", `Media ${mediaId} already uploaded`);\n }\n } catch (err) {\n this._events.emit(\"error\", `Error processing media item: ${err}`);\n }\n }\n\n private async uploadMediaWithBackoff(params: {\n uploadUrl: string;\n contentType: string;\n contentSha256Hash: string;\n contentBytes: Buffer;\n maxRetries: number;\n baseDelay: number;\n }): Promise {\n const { uploadUrl, contentType, contentSha256Hash, contentBytes, maxRetries, baseDelay } = params;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n const uploadResponse = await this.fetch(uploadUrl, {\n method: \"PUT\",\n body: contentBytes,\n headers: {\n \"Content-Type\": contentType,\n \"x-amz-checksum-sha256\": contentSha256Hash,\n \"x-ms-blob-type\": \"BlockBlob\",\n },\n });\n\n if (attempt < maxRetries && uploadResponse.status !== 200 && uploadResponse.status !== 201) {\n throw new Error(`Upload failed with status ${uploadResponse.status}`);\n }\n\n return uploadResponse;\n } catch (e) {\n if (attempt === maxRetries) {\n throw e;\n }\n\n const delay = baseDelay * Math.pow(2, attempt);\n const jitter = Math.random() * 1000;\n\n await new Promise((resolve) => setTimeout(resolve, delay + jitter));\n }\n }\n }\n\n /**\n * Asynchronously flushes all events that are not yet sent to the server.\n * This function always resolves, even if there were errors when flushing.\n * Errors are emitted as \"error\" events and the promise resolves.\n *\n * @returns {Promise} A promise that resolves when the flushing is completed.\n */\n async flushAsync(): Promise {\n await Promise.all(Object.values(this.pendingEventProcessingPromises)).catch((e) => {\n logIngestionError(e);\n });\n\n return new Promise((resolve, _reject) => {\n try {\n this.flush((err, data) => {\n if (err) {\n logIngestionError(err);\n resolve();\n } else {\n resolve(data);\n }\n });\n // safeguard against unexpected synchronous errors\n } catch (e) {\n console.error(\"[Langfuse SDK] Error while flushing Langfuse\", e);\n }\n });\n }\n\n // Flushes all events that are not yet sent to the server\n flush(callback?: (err?: any, data?: any) => void): void {\n if (this._flushTimer) {\n clearTimeout(this._flushTimer);\n this._flushTimer = null;\n }\n\n const queue = this.getPersistedProperty(LangfusePersistedProperty.Queue) || [];\n\n if (!queue.length) {\n return callback?.();\n }\n\n const items = queue.splice(0, this.flushAt);\n\n const { processedItems, remainingItems } = this.processQueueItems(\n items,\n MAX_EVENT_SIZE_BYTES,\n MAX_BATCH_SIZE_BYTES\n );\n\n this.setPersistedProperty(LangfusePersistedProperty.Queue, [...remainingItems, ...queue]);\n\n const promiseUUID = generateUUID();\n\n const done = (err?: any): void => {\n if (err) {\n this._events.emit(\"warning\", err);\n }\n callback?.(err, items);\n this._events.emit(\"flush\", items);\n };\n\n // If local event export is enabled, we don't send the events to the server, but instead store them in the localEventExportMap\n if (this.isLocalEventExportEnabled && this.projectId) {\n if (!this.localEventExportMap.has(this.projectId)) {\n this.localEventExportMap.set(this.projectId, [...items]);\n } else {\n this.localEventExportMap.get(this.projectId)?.push(...items);\n }\n\n done();\n return;\n }\n\n const payload = JSON.stringify({\n batch: processedItems,\n metadata: {\n batch_size: processedItems.length,\n sdk_integration: this.sdkIntegration,\n sdk_version: this.getLibraryVersion(),\n sdk_variant: this.getLibraryId(),\n public_key: this.publicKey,\n sdk_name: \"langfuse-js\",\n },\n }); // implicit conversion also of dates to strings\n\n const url = `${this.baseUrl}/api/public/ingestion`;\n\n const fetchOptions = this._getFetchOptions({\n method: \"POST\",\n body: payload,\n });\n\n const requestPromise = this.fetchWithRetry(url, fetchOptions)\n .then(() => done())\n .catch((err) => {\n done(err);\n });\n this.pendingIngestionPromises[promiseUUID] = requestPromise;\n requestPromise.finally(() => {\n delete this.pendingIngestionPromises[promiseUUID];\n });\n }\n\n public processQueueItems(\n queue: LangfuseQueueItem[],\n MAX_MSG_SIZE: number,\n BATCH_SIZE_LIMIT: number\n ): { processedItems: LangfuseQueueItem[]; remainingItems: LangfuseQueueItem[] } {\n let totalSize = 0;\n const processedItems: LangfuseQueueItem[] = [];\n const remainingItems: LangfuseQueueItem[] = [];\n\n for (let i = 0; i < queue.length; i++) {\n try {\n const itemSize = new Blob([JSON.stringify(queue[i])]).size;\n\n // discard item if it exceeds the maximum size per event\n if (itemSize > MAX_MSG_SIZE) {\n console.warn(`Item exceeds size limit (size: ${itemSize}), dropping item.`);\n continue;\n }\n\n // if adding the next item would exceed the batch size limit, stop processing\n if (totalSize + itemSize >= BATCH_SIZE_LIMIT) {\n console.debug(`hit batch size limit (size: ${totalSize + itemSize})`);\n remainingItems.push(...queue.slice(i));\n console.log(`Remaining items: ${remainingItems.length}`);\n console.log(`processes items: ${processedItems.length}`);\n break;\n }\n\n // only add the item if it passes both requirements\n totalSize += itemSize;\n processedItems.push(queue[i]);\n } catch (error) {\n this._events.emit(\"error\", error);\n remainingItems.push(...queue.slice(i));\n break;\n }\n }\n\n return { processedItems, remainingItems };\n }\n\n _getFetchOptions(p: {\n method: LangfuseFetchOptions[\"method\"];\n body?: LangfuseFetchOptions[\"body\"];\n fetchTimeout?: number;\n }): LangfuseFetchOptions {\n const fetchOptions: LangfuseFetchOptions = {\n method: p.method,\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-Langfuse-Sdk-Name\": \"langfuse-js\",\n \"X-Langfuse-Sdk-Version\": this.getLibraryVersion(),\n \"X-Langfuse-Sdk-Variant\": this.getLibraryId(),\n \"X-Langfuse-Sdk-Integration\": this.sdkIntegration,\n \"X-Langfuse-Public-Key\": this.publicKey,\n ...this.additionalHeaders,\n ...this.constructAuthorizationHeader(this.publicKey, this.secretKey),\n },\n body: p.body,\n ...(p.fetchTimeout !== undefined ? { signal: AbortSignal.timeout(p.fetchTimeout) } : {}),\n };\n\n return fetchOptions;\n }\n\n protected constructAuthorizationHeader(\n publicKey: string,\n secretKey?: string\n ): {\n Authorization: string;\n } {\n if (secretKey === undefined) {\n return { Authorization: \"Bearer \" + publicKey };\n } else {\n const encodedCredentials =\n typeof btoa === \"function\"\n ? // btoa() is available, the code is running in a browser or edge environment\n btoa(publicKey + \":\" + secretKey)\n : // btoa() is not available, the code is running in Node.js\n Buffer.from(publicKey + \":\" + secretKey).toString(\"base64\");\n\n return { Authorization: \"Basic \" + encodedCredentials };\n }\n }\n\n private async fetchWithRetry(\n url: string,\n options: LangfuseFetchOptions,\n retryOptions?: RetriableOptions\n ): Promise {\n (AbortSignal as any).timeout ??= function timeout(ms: number) {\n const ctrl = new AbortController();\n setTimeout(() => ctrl.abort(), ms);\n return ctrl.signal;\n };\n\n return await retriable(\n async () => {\n let res: LangfuseFetchResponse | null = null;\n try {\n res = await this.fetch(url, {\n signal: AbortSignal.timeout(this.requestTimeout),\n ...options,\n });\n } catch (e) {\n // fetch will only throw on network errors or on timeouts\n throw new LangfuseFetchNetworkError(e);\n }\n\n if (res.status < 200 || res.status >= 400) {\n const body = await res.json();\n throw new LangfuseFetchHttpError(res, JSON.stringify(body));\n }\n const returnBody = await res.json();\n if (res.status === 207 && returnBody.errors.length > 0) {\n throw new LangfuseFetchHttpError(res, JSON.stringify(returnBody.errors));\n }\n\n return res;\n },\n { ...this._retryOptions, ...retryOptions },\n (string) => this._events.emit(\"retry\", string + \", \" + url + \", \" + JSON.stringify(options))\n );\n }\n\n private async fetchAndLogErrors(url: string, options: LangfuseFetchOptions): Promise {\n const res = await this.fetch(url, options);\n\n // 429 responses do not have a JSON body, so attempting to execute `json()`\n // will throw and error before the 429 is logged.\n const data = res.status === 429 ? await res.text() : await res.json();\n if (res.status < 200 || res.status >= 400) {\n logIngestionError(new LangfuseFetchHttpError(res, JSON.stringify(data)));\n }\n\n return data;\n }\n\n async shutdownAsync(): Promise {\n clearTimeout(this._flushTimer);\n try {\n await this.flushAsync();\n await Promise.all(\n Object.values(this.pendingIngestionPromises).map((x) =>\n x.catch(() => {\n // ignore errors as we are shutting down and can't deal with them anyways.\n })\n )\n );\n // flush again in case there are new events that were added while we were waiting for the pending promises to resolve\n await this.flushAsync();\n } catch (e) {\n console.error(\"[Langfuse SDK] Error while shutting down Langfuse\", e);\n }\n }\n\n async _exportLocalEvents(projectId: string): Promise {\n if (this.isLocalEventExportEnabled) {\n clearTimeout(this._flushTimer);\n await this.flushAsync();\n\n const events = this.localEventExportMap.get(projectId) ?? [];\n this.localEventExportMap.delete(projectId);\n\n return events;\n } else {\n this._events.emit(\"error\", \"Local event exports are disabled, but _exportLocalEvents() was called.\");\n return [];\n }\n }\n\n shutdown(): void {\n console.warn(\n \"shutdown() is deprecated. It does not wait for all events to be processed. Please use shutdownAsync() instead.\"\n );\n void this.shutdownAsync();\n }\n\n protected async awaitAllQueuedAndPendingRequests(): Promise {\n clearTimeout(this._flushTimer);\n await this.flushAsync();\n await Promise.all(Object.values(this.pendingIngestionPromises));\n }\n}\n\nexport abstract class LangfuseWebStateless extends LangfuseCoreStateless {\n constructor(params: Omit) {\n const { flushAt, flushInterval, publicKey, enabled, ...rest } = params;\n let isObservabilityEnabled = enabled === false ? false : true;\n\n if (isObservabilityEnabled && !publicKey) {\n isObservabilityEnabled = false;\n console.warn(\n \"Langfuse public key not passed to constructor and not set as 'LANGFUSE_PUBLIC_KEY' environment variable. No observability data will be sent to Langfuse.\"\n );\n }\n\n super({\n ...rest,\n publicKey,\n flushAt: flushAt ?? 1,\n flushInterval: flushInterval ?? 0,\n enabled: isObservabilityEnabled,\n });\n }\n\n async score(body: CreateLangfuseScoreBody): Promise {\n this.scoreStateless(body);\n await this.awaitAllQueuedAndPendingRequests();\n return this;\n }\n}\n\nexport abstract class LangfuseCore extends LangfuseCoreStateless {\n private _promptCache: LangfusePromptCache;\n\n constructor(params: LangfuseCoreOptions) {\n const { publicKey, secretKey, enabled, _isLocalEventExportEnabled } = params;\n let isObservabilityEnabled = enabled === false ? false : true;\n\n if (_isLocalEventExportEnabled) {\n isObservabilityEnabled = true;\n } else if (!secretKey) {\n isObservabilityEnabled = false;\n\n if (enabled !== false) {\n console.warn(\n \"Langfuse secret key was not passed to constructor or not set as 'LANGFUSE_SECRET_KEY' environment variable. No observability data will be sent to Langfuse.\"\n );\n }\n } else if (!publicKey) {\n isObservabilityEnabled = false;\n\n if (enabled !== false) {\n console.warn(\n \"Langfuse public key was not passed to constructor or not set as 'LANGFUSE_PUBLIC_KEY' environment variable. No observability data will be sent to Langfuse.\"\n );\n }\n }\n\n super({ ...params, enabled: isObservabilityEnabled });\n this._promptCache = new LangfusePromptCache();\n }\n\n trace(body?: CreateLangfuseTraceBody): LangfuseTraceClient {\n const id = this.traceStateless(body ?? {});\n const t = new LangfuseTraceClient(this, id);\n if (getEnv(\"DEFER\") && body) {\n try {\n const deferRuntime = getEnv(\"__deferRuntime\");\n if (deferRuntime) {\n deferRuntime.langfuseTraces([\n {\n id: id,\n name: body.name || \"\",\n url: t.getTraceUrl(),\n },\n ]);\n }\n } catch {}\n }\n return t;\n }\n\n span(body: CreateLangfuseSpanBody): LangfuseSpanClient {\n const traceId = body.traceId || this.traceStateless({ name: body.name });\n const id = this.spanStateless({ ...body, traceId });\n return new LangfuseSpanClient(this, id, traceId);\n }\n\n generation(\n body: Omit & PromptInput\n ): LangfuseGenerationClient {\n const traceId = body.traceId || this.traceStateless({ name: body.name });\n const id = this.generationStateless({ ...body, traceId });\n return new LangfuseGenerationClient(this, id, traceId);\n }\n\n event(body: CreateLangfuseEventBody): LangfuseEventClient {\n const traceId = body.traceId || this.traceStateless({ name: body.name });\n const id = this.eventStateless({ ...body, traceId });\n return new LangfuseEventClient(this, id, traceId);\n }\n\n score(body: CreateLangfuseScoreBody): this {\n this.scoreStateless(body);\n return this;\n }\n\n async getDataset(\n name: string,\n options?: {\n fetchItemsPageSize: number;\n }\n ): Promise<{\n id: string;\n name: string;\n description?: string;\n metadata?: any;\n projectId: string;\n items: DatasetItem[];\n }> {\n const dataset = await this._getDataset(name);\n const items: GetLangfuseDatasetItemsResponse[\"data\"] = [];\n\n let page = 1;\n while (true) {\n const itemsResponse = await this._getDatasetItems({\n datasetName: name,\n limit: options?.fetchItemsPageSize ?? 50,\n page,\n });\n items.push(...itemsResponse.data);\n if (itemsResponse.meta.totalPages <= page) {\n break;\n }\n page++;\n }\n\n const returnDataset = {\n ...dataset,\n description: dataset.description ?? undefined,\n metadata: dataset.metadata ?? undefined,\n items: items.map((item) => ({\n ...item,\n link: async (\n obj: LangfuseObjectClient,\n runName: string,\n runArgs?: {\n description?: string;\n metadata?: any;\n }\n ) => {\n await this.awaitAllQueuedAndPendingRequests();\n const data = await this.createDatasetRunItem({\n runName,\n datasetItemId: item.id,\n observationId: obj.observationId,\n traceId: obj.traceId,\n runDescription: runArgs?.description,\n metadata: runArgs?.metadata,\n });\n return data;\n },\n })),\n };\n\n return returnDataset;\n }\n\n async createPrompt(body: CreateChatPromptBodyWithPlaceholders): Promise;\n async createPrompt(body: CreateTextPromptBody): Promise;\n async createPrompt(body: CreateChatPromptBody): Promise;\n async createPrompt(\n body: CreateTextPromptBody | CreateChatPromptBody | CreateChatPromptBodyWithPlaceholders\n ): Promise {\n const labels = body.labels ?? [];\n\n const promptResponse =\n body.type === \"chat\" // necessary to get types right here\n ? await this.createPromptStateless({\n ...body,\n prompt: body.prompt.map((item) => {\n if (\"type\" in item && item.type === ChatMessageType.Placeholder) {\n return { type: ChatMessageType.Placeholder, name: (item as PlaceholderMessage).name };\n } else {\n // Handle regular ChatMessage (without type field) from API\n return { type: ChatMessageType.ChatMessage, ...item };\n }\n }),\n labels: body.isActive ? [...new Set([...labels, \"production\"])] : labels, // backward compatibility for isActive\n })\n : await this.createPromptStateless({\n ...body,\n type: body.type ?? \"text\",\n labels: body.isActive ? [...new Set([...labels, \"production\"])] : labels, // backward compatibility for isActive\n });\n\n if (promptResponse.type === \"chat\") {\n return new ChatPromptClient(promptResponse);\n }\n\n return new TextPromptClient(promptResponse);\n }\n\n async updatePrompt(body: { name: string; version: number; newLabels: string[] }): Promise {\n const newPrompt = await this.updatePromptStateless(body);\n this._promptCache.invalidate(body.name);\n return newPrompt;\n }\n\n async getPrompt(\n name: string,\n version?: number,\n options?: {\n label?: string;\n cacheTtlSeconds?: number;\n fallback?: string;\n maxRetries?: number;\n type?: \"text\";\n fetchTimeoutMs?: number;\n }\n ): Promise;\n async getPrompt(\n name: string,\n version?: number,\n options?: {\n label?: string;\n cacheTtlSeconds?: number;\n fallback?: ChatMessage[];\n maxRetries?: number;\n type: \"chat\";\n fetchTimeoutMs?: number;\n }\n ): Promise;\n async getPrompt(\n name: string,\n version?: number,\n options?: {\n label?: string;\n cacheTtlSeconds?: number;\n fallback?: ChatMessage[] | string;\n maxRetries?: number;\n type?: \"chat\" | \"text\";\n fetchTimeoutMs?: number;\n }\n ): Promise {\n const cacheKey = this._getPromptCacheKey({ name, version, label: options?.label });\n const cachedPrompt = this._promptCache.getIncludingExpired(cacheKey);\n if (!cachedPrompt || options?.cacheTtlSeconds === 0) {\n try {\n return await this._fetchPromptAndUpdateCache({\n name,\n version,\n label: options?.label,\n cacheTtlSeconds: options?.cacheTtlSeconds,\n maxRetries: options?.maxRetries,\n fetchTimeout: options?.fetchTimeoutMs,\n });\n } catch (err) {\n if (options?.fallback) {\n const sharedFallbackParams = {\n name,\n version: version ?? 0,\n labels: options.label ? [options.label] : [],\n cacheTtlSeconds: options?.cacheTtlSeconds,\n config: {},\n tags: [],\n };\n\n if (options.type === \"chat\") {\n return new ChatPromptClient(\n {\n ...sharedFallbackParams,\n type: \"chat\",\n prompt: (options.fallback as ChatMessage[]).map((msg) => ({\n type: ChatMessageType.ChatMessage,\n ...msg,\n })),\n },\n true\n );\n } else {\n return new TextPromptClient(\n {\n ...sharedFallbackParams,\n type: \"text\",\n prompt: options.fallback as string,\n },\n true\n );\n }\n }\n\n throw err;\n }\n }\n\n if (cachedPrompt.isExpired) {\n // If the cache is not currently being refreshed, start refreshing it and register the promise in the cache\n if (!this._promptCache.isRefreshing(cacheKey)) {\n const refreshPromptPromise = this._fetchPromptAndUpdateCache({\n name,\n version,\n label: options?.label,\n cacheTtlSeconds: options?.cacheTtlSeconds,\n maxRetries: options?.maxRetries,\n fetchTimeout: options?.fetchTimeoutMs,\n }).catch(() => {\n console.warn(\n `Failed to refresh prompt cache '${cacheKey}', stale cache will be used until next refresh succeeds.`\n );\n });\n this._promptCache.addRefreshingPromise(cacheKey, refreshPromptPromise);\n }\n\n return cachedPrompt.value;\n }\n\n return cachedPrompt.value;\n }\n\n private _getPromptCacheKey(params: { name: string; version?: number; label?: string }): string {\n const { name, version, label } = params;\n const parts = [name];\n\n if (version !== undefined) {\n parts.push(\"version:\" + version.toString());\n } else if (label !== undefined) {\n parts.push(\"label:\" + label);\n } else {\n parts.push(\"label:production\");\n }\n\n return parts.join(\"-\");\n }\n\n private async _fetchPromptAndUpdateCache(params: {\n name: string;\n version?: number;\n cacheTtlSeconds?: number;\n label?: string;\n maxRetries?: number;\n fetchTimeout?: number;\n }): Promise {\n const cacheKey = this._getPromptCacheKey(params);\n\n try {\n const { name, version, cacheTtlSeconds, label, maxRetries, fetchTimeout } = params;\n\n const { data, fetchResult } = await this.getPromptStateless(name, version, label, maxRetries, fetchTimeout);\n if (fetchResult === \"failure\") {\n throw Error(data.message ?? \"Internal error while fetching prompt\");\n }\n\n let prompt: LangfusePromptClient;\n if (data.type === \"chat\") {\n prompt = new ChatPromptClient(data);\n } else {\n prompt = new TextPromptClient(data);\n }\n\n this._promptCache.set(cacheKey, prompt, cacheTtlSeconds);\n\n return prompt;\n } catch (error) {\n console.error(`[Langfuse SDK] Error while fetching prompt '${cacheKey}':`, error);\n\n throw error;\n }\n }\n\n public async fetchMedia(id: string): Promise {\n return await this._fetchMedia(id);\n }\n\n /**\n * Replaces the media reference strings in an object with base64 data URIs for the media content.\n *\n * This method recursively traverses an object (up to a maximum depth of 10) looking for media reference strings\n * in the format \"@@@langfuseMedia:...@@@\". When found, it fetches the actual media content using the provided\n * Langfuse client and replaces the reference string with a base64 data URI.\n *\n * If fetching media content fails for a reference string, a warning is logged and the reference string is left unchanged.\n *\n * @param params - Configuration object\n * @param params.obj - The object to process. Can be a primitive value, array, or nested object\n * @param params.langfuseClient - Langfuse client instance used to fetch media content\n * @param params.resolveWith - The representation of the media content to replace the media reference string with. Currently only \"base64DataUri\" is supported.\n * @param params.maxDepth - Optional. Default is 10. The maximum depth to traverse the object.\n *\n * @returns A deep copy of the input object with all media references replaced with base64 data URIs where possible\n *\n * @example\n * ```typescript\n * const obj = {\n * image: \"@@@langfuseMedia:type=image/jpeg|id=123|source=bytes@@@\",\n * nested: {\n * pdf: \"@@@langfuseMedia:type=application/pdf|id=456|source=bytes@@@\"\n * }\n * };\n *\n * const result = await LangfuseMedia.resolveMediaReferences({\n * obj,\n * langfuseClient\n * });\n *\n * // Result:\n * // {\n * // image: \"data:image/jpeg;base64,/9j/4AAQSkZJRg...\",\n * // nested: {\n * // pdf: \"data:application/pdf;base64,JVBERi0xLjcK...\"\n * // }\n * // }\n * ```\n */\n public async resolveMediaReferences(\n params: Omit, \"langfuseClient\">\n ): Promise {\n const { obj, ...rest } = params;\n\n return LangfuseMedia.resolveMediaReferences({ ...rest, langfuseClient: this, obj });\n }\n _updateSpan(body: UpdateLangfuseSpanBody): this {\n this.updateSpanStateless(body);\n return this;\n }\n\n _updateGeneration(body: UpdateLangfuseGenerationBody): this {\n this.updateGenerationStateless(body);\n return this;\n }\n}\n\nexport abstract class LangfuseObjectClient {\n public readonly client: LangfuseCore;\n public readonly id: string; // id of item itself\n public readonly traceId: string; // id of trace, if traceClient this is the same as id\n public readonly observationId: string | null; // id of observation, if observationClient this is the same as id, if traceClient this is null\n\n constructor({\n client,\n id,\n traceId,\n observationId,\n }: {\n client: LangfuseCore;\n id: string;\n traceId: string;\n observationId: string | null;\n }) {\n this.client = client;\n this.id = id;\n this.traceId = traceId;\n this.observationId = observationId;\n }\n\n event(body: Omit): LangfuseEventClient {\n return this.client.event({\n ...body,\n traceId: this.traceId,\n parentObservationId: this.observationId,\n });\n }\n\n span(body: Omit): LangfuseSpanClient {\n return this.client.span({\n ...body,\n traceId: this.traceId,\n parentObservationId: this.observationId,\n });\n }\n\n generation(\n body: Omit &\n PromptInput\n ): LangfuseGenerationClient {\n return this.client.generation({\n ...body,\n traceId: this.traceId,\n parentObservationId: this.observationId,\n });\n }\n\n score(body: Omit): this {\n this.client.score({\n ...body,\n traceId: this.traceId,\n observationId: this.observationId,\n });\n return this;\n }\n\n getTraceUrl(): string {\n return `${this.client.baseUrl}/trace/${this.traceId}`;\n }\n}\n\nexport class LangfuseTraceClient extends LangfuseObjectClient {\n constructor(client: LangfuseCore, traceId: string) {\n super({ client, id: traceId, traceId, observationId: null });\n }\n\n update(body: Omit): this {\n this.client.trace({\n ...body,\n id: this.id,\n });\n return this;\n }\n}\n\nabstract class LangfuseObservationClient extends LangfuseObjectClient {\n constructor(client: LangfuseCore, id: string, traceId: string) {\n super({ client, id, traceId, observationId: id });\n }\n}\n\nexport class LangfuseSpanClient extends LangfuseObservationClient {\n constructor(client: LangfuseCore, id: string, traceId: string) {\n super(client, id, traceId);\n }\n\n update(body: Omit): this {\n this.client._updateSpan({\n ...body,\n id: this.id,\n traceId: this.traceId,\n });\n return this;\n }\n\n end(body?: Omit): this {\n this.client._updateSpan({\n ...body,\n id: this.id,\n traceId: this.traceId,\n endTime: new Date(),\n });\n return this;\n }\n}\n\nexport class LangfuseGenerationClient extends LangfuseObservationClient {\n constructor(client: LangfuseCore, id: string, traceId: string) {\n super(client, id, traceId);\n }\n\n update(\n body: Omit & PromptInput\n ): this {\n this.client._updateGeneration({\n ...body,\n id: this.id,\n traceId: this.traceId,\n });\n return this;\n }\n\n end(\n body?: Omit &\n PromptInput\n ): this {\n this.client._updateGeneration({\n ...body,\n id: this.id,\n traceId: this.traceId,\n endTime: new Date(),\n });\n return this;\n }\n}\n\nexport class LangfuseEventClient extends LangfuseObservationClient {\n constructor(client: LangfuseCore, id: string, traceId: string) {\n super(client, id, traceId);\n }\n}\n\nexport * from \"./types\";\nexport * from \"./openapi/server\";\n", "import { type LangfuseOptions } from \"./types\";\n\nexport type LangfuseStorage = {\n getItem: (key: string) => string | null | undefined;\n setItem: (key: string, value: string) => void;\n removeItem: (key: string) => void;\n clear: () => void;\n getAllKeys: () => readonly string[];\n};\n\n// Methods partially borrowed from quirksmode.org/js/cookies.html\nexport const cookieStore: LangfuseStorage = {\n getItem(key) {\n try {\n const nameEQ = key + \"=\";\n const ca = document.cookie.split(\";\");\n for (let i = 0; i < ca.length; i++) {\n let c = ca[i];\n while (c.charAt(0) == \" \") {\n c = c.substring(1, c.length);\n }\n if (c.indexOf(nameEQ) === 0) {\n return decodeURIComponent(c.substring(nameEQ.length, c.length));\n }\n }\n } catch (err) {}\n return null;\n },\n\n setItem(key: string, value: string) {\n try {\n const cdomain = \"\",\n expires = \"\",\n secure = \"\";\n\n const new_cookie_val = key + \"=\" + encodeURIComponent(value) + expires + \"; path=/\" + cdomain + secure;\n document.cookie = new_cookie_val;\n } catch (err) {\n return;\n }\n },\n\n removeItem(name) {\n try {\n cookieStore.setItem(name, \"\");\n } catch (err) {\n return;\n }\n },\n clear() {\n document.cookie = \"\";\n },\n getAllKeys() {\n const ca = document.cookie.split(\";\");\n const keys = [];\n\n for (let i = 0; i < ca.length; i++) {\n let c = ca[i];\n while (c.charAt(0) == \" \") {\n c = c.substring(1, c.length);\n }\n keys.push(c.split(\"=\")[0]);\n }\n\n return keys;\n },\n};\n\nconst createStorageLike = (store: any): LangfuseStorage => {\n return {\n getItem(key) {\n return store.getItem(key);\n },\n\n setItem(key, value) {\n store.setItem(key, value);\n },\n\n removeItem(key) {\n store.removeItem(key);\n },\n clear() {\n store.clear();\n },\n getAllKeys() {\n const keys = [];\n for (const key in localStorage) {\n keys.push(key);\n }\n return keys;\n },\n };\n};\n\nconst checkStoreIsSupported = (storage: LangfuseStorage, key = \"__mplssupport__\"): boolean => {\n if (!window) {\n return false;\n }\n try {\n const val = \"xyz\";\n storage.setItem(key, val);\n if (storage.getItem(key) !== val) {\n return false;\n }\n storage.removeItem(key);\n return true;\n } catch (err) {\n return false;\n }\n};\n\nlet localStore: LangfuseStorage | undefined = undefined;\nlet sessionStore: LangfuseStorage | undefined = undefined;\n\nconst createMemoryStorage = (): LangfuseStorage => {\n const _cache: { [key: string]: any | undefined } = {};\n\n const store: LangfuseStorage = {\n getItem(key) {\n return _cache[key];\n },\n\n setItem(key, value) {\n _cache[key] = value !== null ? value : undefined;\n },\n\n removeItem(key) {\n delete _cache[key];\n },\n clear() {\n for (const key in _cache) {\n delete _cache[key];\n }\n },\n getAllKeys() {\n const keys = [];\n for (const key in _cache) {\n keys.push(key);\n }\n return keys;\n },\n };\n return store;\n};\n\nexport const getStorage = (type: LangfuseOptions[\"persistence\"], window: Window | undefined): LangfuseStorage => {\n if (typeof window !== undefined && window) {\n if (!localStorage) {\n const _localStore = createStorageLike(window.localStorage);\n localStore = checkStoreIsSupported(_localStore) ? _localStore : undefined;\n }\n\n if (!sessionStore) {\n const _sessionStore = createStorageLike(window.sessionStorage);\n sessionStore = checkStoreIsSupported(_sessionStore) ? _sessionStore : undefined;\n }\n }\n\n switch (type) {\n case \"cookie\":\n return cookieStore || localStore || sessionStore || createMemoryStorage();\n case \"localStorage\":\n return localStore || sessionStore || createMemoryStorage();\n case \"sessionStorage\":\n return sessionStore || createMemoryStorage();\n case \"memory\":\n return createMemoryStorage();\n default:\n return createMemoryStorage();\n }\n};\n", "/* eslint-disable */\n/* tslint:disable */\n/*\n * ---------------------------------------------------------------\n * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ##\n * ## ##\n * ## AUTHOR: acacode ##\n * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##\n * ---------------------------------------------------------------\n */\n\n/** AnnotationQueueStatus */\nexport type ApiAnnotationQueueStatus = \"PENDING\" | \"COMPLETED\";\n\n/** AnnotationQueueObjectType */\nexport type ApiAnnotationQueueObjectType = \"TRACE\" | \"OBSERVATION\";\n\n/** AnnotationQueue */\nexport interface ApiAnnotationQueue {\n id: string;\n name: string;\n description?: string | null;\n scoreConfigIds: string[];\n /** @format date-time */\n createdAt: string;\n /** @format date-time */\n updatedAt: string;\n}\n\n/** AnnotationQueueItem */\nexport interface ApiAnnotationQueueItem {\n id: string;\n queueId: string;\n objectId: string;\n objectType: ApiAnnotationQueueObjectType;\n status: ApiAnnotationQueueStatus;\n /** @format date-time */\n completedAt?: string | null;\n /** @format date-time */\n createdAt: string;\n /** @format date-time */\n updatedAt: string;\n}\n\n/** PaginatedAnnotationQueues */\nexport interface ApiPaginatedAnnotationQueues {\n data: ApiAnnotationQueue[];\n meta: ApiUtilsMetaResponse;\n}\n\n/** PaginatedAnnotationQueueItems */\nexport interface ApiPaginatedAnnotationQueueItems {\n data: ApiAnnotationQueueItem[];\n meta: ApiUtilsMetaResponse;\n}\n\n/** CreateAnnotationQueueItemRequest */\nexport interface ApiCreateAnnotationQueueItemRequest {\n objectId: string;\n objectType: ApiAnnotationQueueObjectType;\n /** Defaults to PENDING for new queue items */\n status?: ApiAnnotationQueueStatus | null;\n}\n\n/** UpdateAnnotationQueueItemRequest */\nexport interface ApiUpdateAnnotationQueueItemRequest {\n status?: ApiAnnotationQueueStatus | null;\n}\n\n/** DeleteAnnotationQueueItemResponse */\nexport interface ApiDeleteAnnotationQueueItemResponse {\n success: boolean;\n message: string;\n}\n\n/** CreateCommentRequest */\nexport interface ApiCreateCommentRequest {\n /** The id of the project to attach the comment to. */\n projectId: string;\n /** The type of the object to attach the comment to (trace, observation, session, prompt). */\n objectType: string;\n /** The id of the object to attach the comment to. If this does not reference a valid existing object, an error will be thrown. */\n objectId: string;\n /** The content of the comment. May include markdown. Currently limited to 3000 characters. */\n content: string;\n /** The id of the user who created the comment. */\n authorUserId?: string | null;\n}\n\n/** CreateCommentResponse */\nexport interface ApiCreateCommentResponse {\n /** The id of the created object in Langfuse */\n id: string;\n}\n\n/** GetCommentsResponse */\nexport interface ApiGetCommentsResponse {\n data: ApiComment[];\n meta: ApiUtilsMetaResponse;\n}\n\n/** Trace */\nexport interface ApiTrace {\n /** The unique identifier of a trace */\n id: string;\n /**\n * The timestamp when the trace was created\n * @format date-time\n */\n timestamp: string;\n /** The name of the trace */\n name?: string | null;\n /** The input data of the trace. Can be any JSON. */\n input?: any;\n /** The output data of the trace. Can be any JSON. */\n output?: any;\n /** The session identifier associated with the trace */\n sessionId?: string | null;\n /** The release version of the application when the trace was created */\n release?: string | null;\n /** The version of the trace */\n version?: string | null;\n /** The user identifier associated with the trace */\n userId?: string | null;\n /** The metadata associated with the trace. Can be any JSON. */\n metadata?: any;\n /** The tags associated with the trace. Can be an array of strings or null. */\n tags?: string[] | null;\n /** Public traces are accessible via url without login */\n public?: boolean | null;\n /** The environment from which this trace originated. Can be any lowercase alphanumeric string with hyphens and underscores that does not start with 'langfuse'. */\n environment?: string | null;\n}\n\n/** TraceWithDetails */\nexport type ApiTraceWithDetails = ApiTrace & {\n /** Path of trace in Langfuse UI */\n htmlPath: string;\n /**\n * Latency of trace in seconds\n * @format double\n */\n latency: number;\n /**\n * Cost of trace in USD\n * @format double\n */\n totalCost: number;\n /** List of observation ids */\n observations: string[];\n /** List of score ids */\n scores: string[];\n};\n\n/** TraceWithFullDetails */\nexport type ApiTraceWithFullDetails = ApiTrace & {\n /** Path of trace in Langfuse UI */\n htmlPath: string;\n /**\n * Latency of trace in seconds\n * @format double\n */\n latency: number;\n /**\n * Cost of trace in USD\n * @format double\n */\n totalCost: number;\n /** List of observations */\n observations: ApiObservationsView[];\n /** List of scores */\n scores: ApiScoreV1[];\n};\n\n/** Session */\nexport interface ApiSession {\n id: string;\n /** @format date-time */\n createdAt: string;\n projectId: string;\n /** The environment from which this session originated. */\n environment?: string | null;\n}\n\n/** SessionWithTraces */\nexport type ApiSessionWithTraces = ApiSession & {\n traces: ApiTrace[];\n};\n\n/** Observation */\nexport interface ApiObservation {\n /** The unique identifier of the observation */\n id: string;\n /** The trace ID associated with the observation */\n traceId?: string | null;\n /** The type of the observation */\n type: string;\n /** The name of the observation */\n name?: string | null;\n /**\n * The start time of the observation\n * @format date-time\n */\n startTime: string;\n /**\n * The end time of the observation.\n * @format date-time\n */\n endTime?: string | null;\n /**\n * The completion start time of the observation\n * @format date-time\n */\n completionStartTime?: string | null;\n /** The model used for the observation */\n model?: string | null;\n /** The parameters of the model used for the observation */\n modelParameters?: Record;\n /** The input data of the observation */\n input?: any;\n /** The version of the observation */\n version?: string | null;\n /** Additional metadata of the observation */\n metadata?: any;\n /** The output data of the observation */\n output?: any;\n /** (Deprecated. Use usageDetails and costDetails instead.) The usage data of the observation */\n usage?: ApiUsage | null;\n /** The level of the observation */\n level: ApiObservationLevel;\n /** The status message of the observation */\n statusMessage?: string | null;\n /** The parent observation ID */\n parentObservationId?: string | null;\n /** The prompt ID associated with the observation */\n promptId?: string | null;\n /** The usage details of the observation. Key is the name of the usage metric, value is the number of units consumed. The total key is the sum of all (non-total) usage metrics or the total value ingested. */\n usageDetails?: Record;\n /** The cost details of the observation. Key is the name of the cost metric, value is the cost in USD. The total key is the sum of all (non-total) cost metrics or the total value ingested. */\n costDetails?: Record;\n /** The environment from which this observation originated. Can be any lowercase alphanumeric string with hyphens and underscores that does not start with 'langfuse'. */\n environment?: string | null;\n}\n\n/** ObservationsView */\nexport type ApiObservationsView = ApiObservation & {\n /** The name of the prompt associated with the observation */\n promptName?: string | null;\n /** The version of the prompt associated with the observation */\n promptVersion?: number | null;\n /** The unique identifier of the model */\n modelId?: string | null;\n /**\n * The price of the input in USD\n * @format double\n */\n inputPrice?: number | null;\n /**\n * The price of the output in USD.\n * @format double\n */\n outputPrice?: number | null;\n /**\n * The total price in USD.\n * @format double\n */\n totalPrice?: number | null;\n /**\n * (Deprecated. Use usageDetails and costDetails instead.) The calculated cost of the input in USD\n * @format double\n */\n calculatedInputCost?: number | null;\n /**\n * (Deprecated. Use usageDetails and costDetails instead.) The calculated cost of the output in USD\n * @format double\n */\n calculatedOutputCost?: number | null;\n /**\n * (Deprecated. Use usageDetails and costDetails instead.) The calculated total cost in USD\n * @format double\n */\n calculatedTotalCost?: number | null;\n /**\n * The latency in seconds.\n * @format double\n */\n latency?: number | null;\n /**\n * The time to the first token in seconds\n * @format double\n */\n timeToFirstToken?: number | null;\n};\n\n/**\n * Usage\n * (Deprecated. Use usageDetails and costDetails instead.) Standard interface for usage and cost\n */\nexport interface ApiUsage {\n /** Number of input units (e.g. tokens) */\n input?: number | null;\n /** Number of output units (e.g. tokens) */\n output?: number | null;\n /** Defaults to input+output if not set */\n total?: number | null;\n /** Unit of usage in Langfuse */\n unit?: ApiModelUsageUnit | null;\n /**\n * USD input cost\n * @format double\n */\n inputCost?: number | null;\n /**\n * USD output cost\n * @format double\n */\n outputCost?: number | null;\n /**\n * USD total cost, defaults to input+output\n * @format double\n */\n totalCost?: number | null;\n}\n\n/**\n * ScoreConfig\n * Configuration for a score\n */\nexport interface ApiScoreConfig {\n id: string;\n name: string;\n /** @format date-time */\n createdAt: string;\n /** @format date-time */\n updatedAt: string;\n projectId: string;\n dataType: ApiScoreDataType;\n /** Whether the score config is archived. Defaults to false */\n isArchived: boolean;\n /**\n * Sets minimum value for numerical scores. If not set, the minimum value defaults to -∞\n * @format double\n */\n minValue?: number | null;\n /**\n * Sets maximum value for numerical scores. If not set, the maximum value defaults to +∞\n * @format double\n */\n maxValue?: number | null;\n /** Configures custom categories for categorical scores */\n categories?: ApiConfigCategory[] | null;\n description?: string | null;\n}\n\n/** ConfigCategory */\nexport interface ApiConfigCategory {\n /** @format double */\n value: number;\n label: string;\n}\n\n/** BaseScoreV1 */\nexport interface ApiBaseScoreV1 {\n id: string;\n traceId: string;\n name: string;\n source: ApiScoreSource;\n observationId?: string | null;\n /** @format date-time */\n timestamp: string;\n /** @format date-time */\n createdAt: string;\n /** @format date-time */\n updatedAt: string;\n authorUserId?: string | null;\n comment?: string | null;\n metadata?: any;\n /** Reference a score config on a score. When set, config and score name must be equal and value must comply to optionally defined numerical range */\n configId?: string | null;\n /** Reference an annotation queue on a score. Populated if the score was initially created in an annotation queue. */\n queueId?: string | null;\n /** The environment from which this score originated. Can be any lowercase alphanumeric string with hyphens and underscores that does not start with 'langfuse'. */\n environment?: string | null;\n}\n\n/** NumericScoreV1 */\nexport type ApiNumericScoreV1 = ApiBaseScoreV1 & {\n /**\n * The numeric value of the score\n * @format double\n */\n value: number;\n};\n\n/** BooleanScoreV1 */\nexport type ApiBooleanScoreV1 = ApiBaseScoreV1 & {\n /**\n * The numeric value of the score. Equals 1 for \"True\" and 0 for \"False\"\n * @format double\n */\n value: number;\n /** The string representation of the score value. Is inferred from the numeric value and equals \"True\" or \"False\" */\n stringValue: string;\n};\n\n/** CategoricalScoreV1 */\nexport type ApiCategoricalScoreV1 = ApiBaseScoreV1 & {\n /**\n * Only defined if a config is linked. Represents the numeric category mapping of the stringValue\n * @format double\n */\n value?: number | null;\n /** The string representation of the score value. If no config is linked, can be any string. Otherwise, must map to a config category */\n stringValue: string;\n};\n\n/** ScoreV1 */\nexport type ApiScoreV1 =\n | ({\n dataType: \"NUMERIC\";\n } & ApiNumericScoreV1)\n | ({\n dataType: \"CATEGORICAL\";\n } & ApiCategoricalScoreV1)\n | ({\n dataType: \"BOOLEAN\";\n } & ApiBooleanScoreV1);\n\n/** BaseScore */\nexport interface ApiBaseScore {\n id: string;\n traceId?: string | null;\n sessionId?: string | null;\n observationId?: string | null;\n datasetRunId?: string | null;\n name: string;\n source: ApiScoreSource;\n /** @format date-time */\n timestamp: string;\n /** @format date-time */\n createdAt: string;\n /** @format date-time */\n updatedAt: string;\n authorUserId?: string | null;\n comment?: string | null;\n metadata?: any;\n /** Reference a score config on a score. When set, config and score name must be equal and value must comply to optionally defined numerical range */\n configId?: string | null;\n /** Reference an annotation queue on a score. Populated if the score was initially created in an annotation queue. */\n queueId?: string | null;\n /** The environment from which this score originated. Can be any lowercase alphanumeric string with hyphens and underscores that does not start with 'langfuse'. */\n environment?: string | null;\n}\n\n/** NumericScore */\nexport type ApiNumericScore = ApiBaseScore & {\n /**\n * The numeric value of the score\n * @format double\n */\n value: number;\n};\n\n/** BooleanScore */\nexport type ApiBooleanScore = ApiBaseScore & {\n /**\n * The numeric value of the score. Equals 1 for \"True\" and 0 for \"False\"\n * @format double\n */\n value: number;\n /** The string representation of the score value. Is inferred from the numeric value and equals \"True\" or \"False\" */\n stringValue: string;\n};\n\n/** CategoricalScore */\nexport type ApiCategoricalScore = ApiBaseScore & {\n /**\n * Only defined if a config is linked. Represents the numeric category mapping of the stringValue\n * @format double\n */\n value?: number | null;\n /** The string representation of the score value. If no config is linked, can be any string. Otherwise, must map to a config category */\n stringValue: string;\n};\n\n/** Score */\nexport type ApiScore =\n | ({\n dataType: \"NUMERIC\";\n } & ApiNumericScore)\n | ({\n dataType: \"CATEGORICAL\";\n } & ApiCategoricalScore)\n | ({\n dataType: \"BOOLEAN\";\n } & ApiBooleanScore);\n\n/**\n * CreateScoreValue\n * The value of the score. Must be passed as string for categorical scores, and numeric for boolean and numeric scores\n */\nexport type ApiCreateScoreValue = number | string;\n\n/** Comment */\nexport interface ApiComment {\n id: string;\n projectId: string;\n /** @format date-time */\n createdAt: string;\n /** @format date-time */\n updatedAt: string;\n objectType: ApiCommentObjectType;\n objectId: string;\n content: string;\n authorUserId?: string | null;\n}\n\n/** Dataset */\nexport interface ApiDataset {\n id: string;\n name: string;\n description?: string | null;\n metadata?: any;\n projectId: string;\n /** @format date-time */\n createdAt: string;\n /** @format date-time */\n updatedAt: string;\n}\n\n/** DatasetItem */\nexport interface ApiDatasetItem {\n id: string;\n status: ApiDatasetStatus;\n input?: any;\n expectedOutput?: any;\n metadata?: any;\n sourceTraceId?: string | null;\n sourceObservationId?: string | null;\n datasetId: string;\n datasetName: string;\n /** @format date-time */\n createdAt: string;\n /** @format date-time */\n updatedAt: string;\n}\n\n/** DatasetRunItem */\nexport interface ApiDatasetRunItem {\n id: string;\n datasetRunId: string;\n datasetRunName: string;\n datasetItemId: string;\n traceId: string;\n observationId?: string | null;\n /** @format date-time */\n createdAt: string;\n /** @format date-time */\n updatedAt: string;\n}\n\n/** DatasetRun */\nexport interface ApiDatasetRun {\n /** Unique identifier of the dataset run */\n id: string;\n /** Name of the dataset run */\n name: string;\n /** Description of the run */\n description?: string | null;\n /** Metadata of the dataset run */\n metadata?: any;\n /** Id of the associated dataset */\n datasetId: string;\n /** Name of the associated dataset */\n datasetName: string;\n /**\n * The date and time when the dataset run was created\n * @format date-time\n */\n createdAt: string;\n /**\n * The date and time when the dataset run was last updated\n * @format date-time\n */\n updatedAt: string;\n}\n\n/** DatasetRunWithItems */\nexport type ApiDatasetRunWithItems = ApiDatasetRun & {\n datasetRunItems: ApiDatasetRunItem[];\n};\n\n/**\n * Model\n * Model definition used for transforming usage into USD cost and/or tokenization.\n */\nexport interface ApiModel {\n id: string;\n /** Name of the model definition. If multiple with the same name exist, they are applied in the following order: (1) custom over built-in, (2) newest according to startTime where model.startTime;\n}\n\n/** ModelPrice */\nexport interface ApiModelPrice {\n /** @format double */\n price: number;\n}\n\n/**\n * ModelUsageUnit\n * Unit of usage in Langfuse\n */\nexport type ApiModelUsageUnit = \"CHARACTERS\" | \"TOKENS\" | \"MILLISECONDS\" | \"SECONDS\" | \"IMAGES\" | \"REQUESTS\";\n\n/** ObservationLevel */\nexport type ApiObservationLevel = \"DEBUG\" | \"DEFAULT\" | \"WARNING\" | \"ERROR\";\n\n/** MapValue */\nexport type ApiMapValue = string | null | number | null | boolean | null | string[] | null;\n\n/** CommentObjectType */\nexport type ApiCommentObjectType = \"TRACE\" | \"OBSERVATION\" | \"SESSION\" | \"PROMPT\";\n\n/** DatasetStatus */\nexport type ApiDatasetStatus = \"ACTIVE\" | \"ARCHIVED\";\n\n/** ScoreSource */\nexport type ApiScoreSource = \"ANNOTATION\" | \"API\" | \"EVAL\";\n\n/** ScoreDataType */\nexport type ApiScoreDataType = \"NUMERIC\" | \"BOOLEAN\" | \"CATEGORICAL\";\n\n/** DeleteDatasetItemResponse */\nexport interface ApiDeleteDatasetItemResponse {\n /** Success message after deletion */\n message: string;\n}\n\n/** CreateDatasetItemRequest */\nexport interface ApiCreateDatasetItemRequest {\n datasetName: string;\n input?: any;\n expectedOutput?: any;\n metadata?: any;\n sourceTraceId?: string | null;\n sourceObservationId?: string | null;\n /** Dataset items are upserted on their id. Id needs to be unique (project-level) and cannot be reused across datasets. */\n id?: string | null;\n /** Defaults to ACTIVE for newly created items */\n status?: ApiDatasetStatus | null;\n}\n\n/** PaginatedDatasetItems */\nexport interface ApiPaginatedDatasetItems {\n data: ApiDatasetItem[];\n meta: ApiUtilsMetaResponse;\n}\n\n/** CreateDatasetRunItemRequest */\nexport interface ApiCreateDatasetRunItemRequest {\n runName: string;\n /** Description of the run. If run exists, description will be updated. */\n runDescription?: string | null;\n /** Metadata of the dataset run, updates run if run already exists */\n metadata?: any;\n datasetItemId: string;\n observationId?: string | null;\n /** traceId should always be provided. For compatibility with older SDK versions it can also be inferred from the provided observationId. */\n traceId?: string | null;\n}\n\n/** PaginatedDatasetRunItems */\nexport interface ApiPaginatedDatasetRunItems {\n data: ApiDatasetRunItem[];\n meta: ApiUtilsMetaResponse;\n}\n\n/** PaginatedDatasets */\nexport interface ApiPaginatedDatasets {\n data: ApiDataset[];\n meta: ApiUtilsMetaResponse;\n}\n\n/** CreateDatasetRequest */\nexport interface ApiCreateDatasetRequest {\n name: string;\n description?: string | null;\n metadata?: any;\n}\n\n/** PaginatedDatasetRuns */\nexport interface ApiPaginatedDatasetRuns {\n data: ApiDatasetRun[];\n meta: ApiUtilsMetaResponse;\n}\n\n/** DeleteDatasetRunResponse */\nexport interface ApiDeleteDatasetRunResponse {\n message: string;\n}\n\n/** HealthResponse */\nexport interface ApiHealthResponse {\n /**\n * Langfuse server version\n * @example \"1.25.0\"\n */\n version: string;\n /** @example \"OK\" */\n status: string;\n}\n\n/** IngestionEvent */\nexport type ApiIngestionEvent =\n | ({\n type: \"trace-create\";\n } & ApiTraceEvent)\n | ({\n type: \"score-create\";\n } & ApiScoreEvent)\n | ({\n type: \"span-create\";\n } & ApiCreateSpanEvent)\n | ({\n type: \"span-update\";\n } & ApiUpdateSpanEvent)\n | ({\n type: \"generation-create\";\n } & ApiCreateGenerationEvent)\n | ({\n type: \"generation-update\";\n } & ApiUpdateGenerationEvent)\n | ({\n type: \"event-create\";\n } & ApiCreateEventEvent)\n | ({\n type: \"sdk-log\";\n } & ApiSDKLogEvent)\n | ({\n type: \"observation-create\";\n } & ApiCreateObservationEvent)\n | ({\n type: \"observation-update\";\n } & ApiUpdateObservationEvent);\n\n/** ObservationType */\nexport type ApiObservationType = \"SPAN\" | \"GENERATION\" | \"EVENT\";\n\n/** IngestionUsage */\nexport type ApiIngestionUsage = ApiUsage | ApiOpenAIUsage;\n\n/**\n * OpenAIUsage\n * Usage interface of OpenAI for improved compatibility.\n */\nexport interface ApiOpenAIUsage {\n promptTokens?: number | null;\n completionTokens?: number | null;\n totalTokens?: number | null;\n}\n\n/** OptionalObservationBody */\nexport interface ApiOptionalObservationBody {\n traceId?: string | null;\n name?: string | null;\n /** @format date-time */\n startTime?: string | null;\n metadata?: any;\n input?: any;\n output?: any;\n level?: ApiObservationLevel | null;\n statusMessage?: string | null;\n parentObservationId?: string | null;\n version?: string | null;\n environment?: string | null;\n}\n\n/** CreateEventBody */\nexport type ApiCreateEventBody = ApiOptionalObservationBody & {\n id?: string | null;\n};\n\n/** UpdateEventBody */\nexport type ApiUpdateEventBody = ApiOptionalObservationBody & {\n id: string;\n};\n\n/** CreateSpanBody */\nexport type ApiCreateSpanBody = ApiCreateEventBody & {\n /** @format date-time */\n endTime?: string | null;\n};\n\n/** UpdateSpanBody */\nexport type ApiUpdateSpanBody = ApiUpdateEventBody & {\n /** @format date-time */\n endTime?: string | null;\n};\n\n/** CreateGenerationBody */\nexport type ApiCreateGenerationBody = ApiCreateSpanBody & {\n /** @format date-time */\n completionStartTime?: string | null;\n model?: string | null;\n modelParameters?: Record;\n usage?: ApiIngestionUsage | null;\n usageDetails?: ApiUsageDetails | null;\n costDetails?: Record;\n promptName?: string | null;\n promptVersion?: number | null;\n};\n\n/** UpdateGenerationBody */\nexport type ApiUpdateGenerationBody = ApiUpdateSpanBody & {\n /** @format date-time */\n completionStartTime?: string | null;\n model?: string | null;\n modelParameters?: Record;\n usage?: ApiIngestionUsage | null;\n promptName?: string | null;\n usageDetails?: ApiUsageDetails | null;\n costDetails?: Record;\n promptVersion?: number | null;\n};\n\n/** ObservationBody */\nexport interface ApiObservationBody {\n id?: string | null;\n traceId?: string | null;\n type: ApiObservationType;\n name?: string | null;\n /** @format date-time */\n startTime?: string | null;\n /** @format date-time */\n endTime?: string | null;\n /** @format date-time */\n completionStartTime?: string | null;\n model?: string | null;\n modelParameters?: Record;\n input?: any;\n version?: string | null;\n metadata?: any;\n output?: any;\n /** (Deprecated. Use usageDetails and costDetails instead.) Standard interface for usage and cost */\n usage?: ApiUsage | null;\n level?: ApiObservationLevel | null;\n statusMessage?: string | null;\n parentObservationId?: string | null;\n environment?: string | null;\n}\n\n/** TraceBody */\nexport interface ApiTraceBody {\n id?: string | null;\n /** @format date-time */\n timestamp?: string | null;\n name?: string | null;\n userId?: string | null;\n input?: any;\n output?: any;\n sessionId?: string | null;\n release?: string | null;\n version?: string | null;\n metadata?: any;\n tags?: string[] | null;\n environment?: string | null;\n /** Make trace publicly accessible via url */\n public?: boolean | null;\n}\n\n/** SDKLogBody */\nexport interface ApiSDKLogBody {\n log: any;\n}\n\n/** ScoreBody */\nexport interface ApiScoreBody {\n id?: string | null;\n traceId?: string | null;\n sessionId?: string | null;\n observationId?: string | null;\n datasetRunId?: string | null;\n /** @example \"novelty\" */\n name: string;\n environment?: string | null;\n /** The value of the score. Must be passed as string for categorical scores, and numeric for boolean and numeric scores. Boolean score values must equal either 1 or 0 (true or false) */\n value: ApiCreateScoreValue;\n comment?: string | null;\n metadata?: any;\n /** When set, must match the score value's type. If not set, will be inferred from the score value or config */\n dataType?: ApiScoreDataType | null;\n /** Reference a score config on a score. When set, the score name must equal the config name and scores must comply with the config's range and data type. For categorical scores, the value must map to a config category. Numeric scores might be constrained by the score config's max and min values */\n configId?: string | null;\n}\n\n/** BaseEvent */\nexport interface ApiBaseEvent {\n /** UUID v4 that identifies the event */\n id: string;\n /** Datetime (ISO 8601) of event creation in client. Should be as close to actual event creation in client as possible, this timestamp will be used for ordering of events in future release. Resolution: milliseconds (required), microseconds (optimal). */\n timestamp: string;\n /** Optional. Metadata field used by the Langfuse SDKs for debugging. */\n metadata?: any;\n}\n\n/** TraceEvent */\nexport type ApiTraceEvent = ApiBaseEvent & {\n body: ApiTraceBody;\n};\n\n/** CreateObservationEvent */\nexport type ApiCreateObservationEvent = ApiBaseEvent & {\n body: ApiObservationBody;\n};\n\n/** UpdateObservationEvent */\nexport type ApiUpdateObservationEvent = ApiBaseEvent & {\n body: ApiObservationBody;\n};\n\n/** ScoreEvent */\nexport type ApiScoreEvent = ApiBaseEvent & {\n body: ApiScoreBody;\n};\n\n/** SDKLogEvent */\nexport type ApiSDKLogEvent = ApiBaseEvent & {\n body: ApiSDKLogBody;\n};\n\n/** CreateGenerationEvent */\nexport type ApiCreateGenerationEvent = ApiBaseEvent & {\n body: ApiCreateGenerationBody;\n};\n\n/** UpdateGenerationEvent */\nexport type ApiUpdateGenerationEvent = ApiBaseEvent & {\n body: ApiUpdateGenerationBody;\n};\n\n/** CreateSpanEvent */\nexport type ApiCreateSpanEvent = ApiBaseEvent & {\n body: ApiCreateSpanBody;\n};\n\n/** UpdateSpanEvent */\nexport type ApiUpdateSpanEvent = ApiBaseEvent & {\n body: ApiUpdateSpanBody;\n};\n\n/** CreateEventEvent */\nexport type ApiCreateEventEvent = ApiBaseEvent & {\n body: ApiCreateEventBody;\n};\n\n/** IngestionSuccess */\nexport interface ApiIngestionSuccess {\n id: string;\n status: number;\n}\n\n/** IngestionError */\nexport interface ApiIngestionError {\n id: string;\n status: number;\n message?: string | null;\n error?: any;\n}\n\n/** IngestionResponse */\nexport interface ApiIngestionResponse {\n successes: ApiIngestionSuccess[];\n errors: ApiIngestionError[];\n}\n\n/**\n * OpenAICompletionUsageSchema\n * OpenAI Usage schema from (Chat-)Completion APIs\n */\nexport interface ApiOpenAICompletionUsageSchema {\n prompt_tokens: number;\n completion_tokens: number;\n total_tokens: number;\n prompt_tokens_details?: Record;\n completion_tokens_details?: Record;\n}\n\n/**\n * OpenAIResponseUsageSchema\n * OpenAI Usage schema from Response API\n */\nexport interface ApiOpenAIResponseUsageSchema {\n input_tokens: number;\n output_tokens: number;\n total_tokens: number;\n input_tokens_details?: Record;\n output_tokens_details?: Record;\n}\n\n/** UsageDetails */\nexport type ApiUsageDetails = Record | ApiOpenAICompletionUsageSchema | ApiOpenAIResponseUsageSchema;\n\n/** GetMediaResponse */\nexport interface ApiGetMediaResponse {\n /** The unique langfuse identifier of a media record */\n mediaId: string;\n /** The MIME type of the media record */\n contentType: string;\n /** The size of the media record in bytes */\n contentLength: number;\n /**\n * The date and time when the media record was uploaded\n * @format date-time\n */\n uploadedAt: string;\n /** The download URL of the media record */\n url: string;\n /** The expiry date and time of the media record download URL */\n urlExpiry: string;\n}\n\n/** PatchMediaBody */\nexport interface ApiPatchMediaBody {\n /**\n * The date and time when the media record was uploaded\n * @format date-time\n */\n uploadedAt: string;\n /** The HTTP status code of the upload */\n uploadHttpStatus: number;\n /** The HTTP error message of the upload */\n uploadHttpError?: string | null;\n /** The time in milliseconds it took to upload the media record */\n uploadTimeMs?: number | null;\n}\n\n/** GetMediaUploadUrlRequest */\nexport interface ApiGetMediaUploadUrlRequest {\n /** The trace ID associated with the media record */\n traceId: string;\n /** The observation ID associated with the media record. If the media record is associated directly with a trace, this will be null. */\n observationId?: string | null;\n /** The MIME type of the media record */\n contentType: ApiMediaContentType;\n /** The size of the media record in bytes */\n contentLength: number;\n /** The SHA-256 hash of the media record */\n sha256Hash: string;\n /** The trace / observation field the media record is associated with. This can be one of `input`, `output`, `metadata` */\n field: string;\n}\n\n/** GetMediaUploadUrlResponse */\nexport interface ApiGetMediaUploadUrlResponse {\n /** The presigned upload URL. If the asset is already uploaded, this will be null */\n uploadUrl?: string | null;\n /** The unique langfuse identifier of a media record */\n mediaId: string;\n}\n\n/**\n * MediaContentType\n * The MIME type of the media record\n */\nexport type ApiMediaContentType =\n | \"image/png\"\n | \"image/jpeg\"\n | \"image/jpg\"\n | \"image/webp\"\n | \"image/gif\"\n | \"image/svg+xml\"\n | \"image/tiff\"\n | \"image/bmp\"\n | \"audio/mpeg\"\n | \"audio/mp3\"\n | \"audio/wav\"\n | \"audio/ogg\"\n | \"audio/oga\"\n | \"audio/aac\"\n | \"audio/mp4\"\n | \"audio/flac\"\n | \"video/mp4\"\n | \"video/webm\"\n | \"text/plain\"\n | \"text/html\"\n | \"text/css\"\n | \"text/csv\"\n | \"application/pdf\"\n | \"application/msword\"\n | \"application/vnd.ms-excel\"\n | \"application/zip\"\n | \"application/json\"\n | \"application/xml\"\n | \"application/octet-stream\";\n\n/** MetricsResponse */\nexport interface ApiMetricsResponse {\n /**\n * The metrics data. Each item in the list contains the metric values and dimensions requested in the query.\n * Format varies based on the query parameters.\n * Histograms will return an array with [lower, upper, height] tuples.\n */\n data: Record[];\n}\n\n/** PaginatedModels */\nexport interface ApiPaginatedModels {\n data: ApiModel[];\n meta: ApiUtilsMetaResponse;\n}\n\n/** CreateModelRequest */\nexport interface ApiCreateModelRequest {\n /** Name of the model definition. If multiple with the same name exist, they are applied in the following order: (1) custom over built-in, (2) newest according to startTime where model.startTime;\n /** @format date-time */\n createdAt: string;\n /** @format date-time */\n updatedAt: string;\n}\n\n/** OrganizationProjectsResponse */\nexport interface ApiOrganizationProjectsResponse {\n projects: ApiOrganizationProject[];\n}\n\n/** Projects */\nexport interface ApiProjects {\n data: ApiProject[];\n}\n\n/** Project */\nexport interface ApiProject {\n id: string;\n name: string;\n /** Metadata for the project */\n metadata: Record;\n /** Number of days to retain data. Null or 0 means no retention. Omitted if no retention is configured. */\n retentionDays?: number | null;\n}\n\n/** ProjectDeletionResponse */\nexport interface ApiProjectDeletionResponse {\n success: boolean;\n message: string;\n}\n\n/**\n * ApiKeyList\n * List of API keys for a project\n */\nexport interface ApiApiKeyList {\n apiKeys: ApiApiKeySummary[];\n}\n\n/**\n * ApiKeySummary\n * Summary of an API key\n */\nexport interface ApiApiKeySummary {\n id: string;\n /** @format date-time */\n createdAt: string;\n /** @format date-time */\n expiresAt?: string | null;\n /** @format date-time */\n lastUsedAt?: string | null;\n note?: string | null;\n publicKey: string;\n displaySecretKey: string;\n}\n\n/**\n * ApiKeyResponse\n * Response for API key creation\n */\nexport interface ApiApiKeyResponse {\n id: string;\n /** @format date-time */\n createdAt: string;\n publicKey: string;\n secretKey: string;\n displaySecretKey: string;\n note?: string | null;\n}\n\n/**\n * ApiKeyDeletionResponse\n * Response for API key deletion\n */\nexport interface ApiApiKeyDeletionResponse {\n success: boolean;\n}\n\n/** PromptMetaListResponse */\nexport interface ApiPromptMetaListResponse {\n data: ApiPromptMeta[];\n meta: ApiUtilsMetaResponse;\n}\n\n/** PromptMeta */\nexport interface ApiPromptMeta {\n name: string;\n versions: number[];\n labels: string[];\n tags: string[];\n /** @format date-time */\n lastUpdatedAt: string;\n /** Config object of the most recent prompt version that matches the filters (if any are provided) */\n lastConfig: any;\n}\n\n/** CreatePromptRequest */\nexport type ApiCreatePromptRequest =\n | ({\n type: \"chat\";\n } & ApiCreateChatPromptRequest)\n | ({\n type: \"text\";\n } & ApiCreateTextPromptRequest);\n\n/** CreateChatPromptRequest */\nexport interface ApiCreateChatPromptRequest {\n name: string;\n prompt: ApiChatMessageWithPlaceholders[];\n config?: any;\n /** List of deployment labels of this prompt version. */\n labels?: string[] | null;\n /** List of tags to apply to all versions of this prompt. */\n tags?: string[] | null;\n /** Commit message for this prompt version. */\n commitMessage?: string | null;\n}\n\n/** CreateTextPromptRequest */\nexport interface ApiCreateTextPromptRequest {\n name: string;\n prompt: string;\n config?: any;\n /** List of deployment labels of this prompt version. */\n labels?: string[] | null;\n /** List of tags to apply to all versions of this prompt. */\n tags?: string[] | null;\n /** Commit message for this prompt version. */\n commitMessage?: string | null;\n}\n\n/** Prompt */\nexport type ApiPrompt =\n | ({\n type: \"chat\";\n } & ApiChatPrompt)\n | ({\n type: \"text\";\n } & ApiTextPrompt);\n\n/** BasePrompt */\nexport interface ApiBasePrompt {\n name: string;\n version: number;\n config: any;\n /** List of deployment labels of this prompt version. */\n labels: string[];\n /** List of tags. Used to filter via UI and API. The same across versions of a prompt. */\n tags: string[];\n /** Commit message for this prompt version. */\n commitMessage?: string | null;\n /** The dependency resolution graph for the current prompt. Null if prompt has no dependencies. */\n resolutionGraph?: Record;\n}\n\n/** ChatMessageWithPlaceholders */\nexport type ApiChatMessageWithPlaceholders =\n | ({\n type: \"chatmessage\";\n } & ApiChatMessage)\n | ({\n type: \"placeholder\";\n } & ApiPlaceholderMessage);\n\n/** ChatMessage */\nexport interface ApiChatMessage {\n role: string;\n content: string;\n}\n\n/** PlaceholderMessage */\nexport interface ApiPlaceholderMessage {\n name: string;\n}\n\n/** TextPrompt */\nexport type ApiTextPrompt = ApiBasePrompt & {\n prompt: string;\n};\n\n/** ChatPrompt */\nexport type ApiChatPrompt = ApiBasePrompt & {\n prompt: ApiChatMessageWithPlaceholders[];\n};\n\n/** ServiceProviderConfig */\nexport interface ApiServiceProviderConfig {\n schemas: string[];\n documentationUri: string;\n patch: ApiScimFeatureSupport;\n bulk: ApiBulkConfig;\n filter: ApiFilterConfig;\n changePassword: ApiScimFeatureSupport;\n sort: ApiScimFeatureSupport;\n etag: ApiScimFeatureSupport;\n authenticationSchemes: ApiAuthenticationScheme[];\n meta: ApiResourceMeta;\n}\n\n/** ScimFeatureSupport */\nexport interface ApiScimFeatureSupport {\n supported: boolean;\n}\n\n/** BulkConfig */\nexport interface ApiBulkConfig {\n supported: boolean;\n maxOperations: number;\n maxPayloadSize: number;\n}\n\n/** FilterConfig */\nexport interface ApiFilterConfig {\n supported: boolean;\n maxResults: number;\n}\n\n/** ResourceMeta */\nexport interface ApiResourceMeta {\n resourceType: string;\n location: string;\n}\n\n/** AuthenticationScheme */\nexport interface ApiAuthenticationScheme {\n name: string;\n description: string;\n specUri: string;\n type: string;\n primary: boolean;\n}\n\n/** ResourceTypesResponse */\nexport interface ApiResourceTypesResponse {\n schemas: string[];\n totalResults: number;\n Resources: ApiResourceType[];\n}\n\n/** ResourceType */\nexport interface ApiResourceType {\n schemas?: string[] | null;\n id: string;\n name: string;\n endpoint: string;\n description: string;\n schema: string;\n schemaExtensions: ApiSchemaExtension[];\n meta: ApiResourceMeta;\n}\n\n/** SchemaExtension */\nexport interface ApiSchemaExtension {\n schema: string;\n required: boolean;\n}\n\n/** SchemasResponse */\nexport interface ApiSchemasResponse {\n schemas: string[];\n totalResults: number;\n Resources: ApiSchemaResource[];\n}\n\n/** SchemaResource */\nexport interface ApiSchemaResource {\n id: string;\n name: string;\n description: string;\n attributes: any[];\n meta: ApiResourceMeta;\n}\n\n/** ScimUsersListResponse */\nexport interface ApiScimUsersListResponse {\n schemas: string[];\n totalResults: number;\n startIndex: number;\n itemsPerPage: number;\n Resources: ApiScimUser[];\n}\n\n/** ScimUser */\nexport interface ApiScimUser {\n schemas: string[];\n id: string;\n userName: string;\n name: ApiScimName;\n emails: ApiScimEmail[];\n meta: ApiUserMeta;\n}\n\n/** UserMeta */\nexport interface ApiUserMeta {\n resourceType: string;\n created?: string | null;\n lastModified?: string | null;\n}\n\n/** ScimName */\nexport interface ApiScimName {\n formatted?: string | null;\n}\n\n/** ScimEmail */\nexport interface ApiScimEmail {\n primary: boolean;\n value: string;\n type: string;\n}\n\n/**\n * EmptyResponse\n * Empty response for 204 No Content responses\n */\nexport type ApiEmptyResponse = object;\n\n/** ScoreConfigs */\nexport interface ApiScoreConfigs {\n data: ApiScoreConfig[];\n meta: ApiUtilsMetaResponse;\n}\n\n/** CreateScoreConfigRequest */\nexport interface ApiCreateScoreConfigRequest {\n name: string;\n dataType: ApiScoreDataType;\n /** Configure custom categories for categorical scores. Pass a list of objects with `label` and `value` properties. Categories are autogenerated for boolean configs and cannot be passed */\n categories?: ApiConfigCategory[] | null;\n /**\n * Configure a minimum value for numerical scores. If not set, the minimum value defaults to -∞\n * @format double\n */\n minValue?: number | null;\n /**\n * Configure a maximum value for numerical scores. If not set, the maximum value defaults to +∞\n * @format double\n */\n maxValue?: number | null;\n /** Description is shown across the Langfuse UI and can be used to e.g. explain the config categories in detail, why a numeric range was set, or provide additional context on config name or usage */\n description?: string | null;\n}\n\n/** GetScoresResponseTraceData */\nexport interface ApiGetScoresResponseTraceData {\n /** The user ID associated with the trace referenced by score */\n userId?: string | null;\n /** A list of tags associated with the trace referenced by score */\n tags?: string[] | null;\n /** The environment of the trace referenced by score */\n environment?: string | null;\n}\n\n/** GetScoresResponseDataNumeric */\nexport type ApiGetScoresResponseDataNumeric = ApiNumericScore & {\n trace?: ApiGetScoresResponseTraceData | null;\n};\n\n/** GetScoresResponseDataCategorical */\nexport type ApiGetScoresResponseDataCategorical = ApiCategoricalScore & {\n trace?: ApiGetScoresResponseTraceData | null;\n};\n\n/** GetScoresResponseDataBoolean */\nexport type ApiGetScoresResponseDataBoolean = ApiBooleanScore & {\n trace?: ApiGetScoresResponseTraceData | null;\n};\n\n/** GetScoresResponseData */\nexport type ApiGetScoresResponseData =\n | ({\n dataType: \"NUMERIC\";\n } & ApiGetScoresResponseDataNumeric)\n | ({\n dataType: \"CATEGORICAL\";\n } & ApiGetScoresResponseDataCategorical)\n | ({\n dataType: \"BOOLEAN\";\n } & ApiGetScoresResponseDataBoolean);\n\n/** GetScoresResponse */\nexport interface ApiGetScoresResponse {\n data: ApiGetScoresResponseData[];\n meta: ApiUtilsMetaResponse;\n}\n\n/** CreateScoreRequest */\nexport interface ApiCreateScoreRequest {\n id?: string | null;\n traceId?: string | null;\n sessionId?: string | null;\n observationId?: string | null;\n datasetRunId?: string | null;\n /** @example \"novelty\" */\n name: string;\n /** The value of the score. Must be passed as string for categorical scores, and numeric for boolean and numeric scores. Boolean score values must equal either 1 or 0 (true or false) */\n value: ApiCreateScoreValue;\n comment?: string | null;\n metadata?: any;\n /** The environment of the score. Can be any lowercase alphanumeric string with hyphens and underscores that does not start with 'langfuse'. */\n environment?: string | null;\n /** The data type of the score. When passing a configId this field is inferred. Otherwise, this field must be passed or will default to numeric. */\n dataType?: ApiScoreDataType | null;\n /** Reference a score config on a score. The unique langfuse identifier of a score config. When passing this field, the dataType and stringValue fields are automatically populated. */\n configId?: string | null;\n}\n\n/** CreateScoreResponse */\nexport interface ApiCreateScoreResponse {\n /** The id of the created object in Langfuse */\n id: string;\n}\n\n/** PaginatedSessions */\nexport interface ApiPaginatedSessions {\n data: ApiSession[];\n meta: ApiUtilsMetaResponse;\n}\n\n/** Traces */\nexport interface ApiTraces {\n data: ApiTraceWithDetails[];\n meta: ApiUtilsMetaResponse;\n}\n\n/** DeleteTraceResponse */\nexport interface ApiDeleteTraceResponse {\n message: string;\n}\n\n/** Sort */\nexport interface ApiSort {\n id: string;\n}\n\n/** utilsMetaResponse */\nexport interface ApiUtilsMetaResponse {\n /** current page number */\n page: number;\n /** number of items per page */\n limit: number;\n /** number of total items given the current filters/selection (if any) */\n totalItems: number;\n /** number of total pages given the current limit */\n totalPages: number;\n}\n\nexport interface ApiAnnotationQueuesListQueuesParams {\n /** page number, starts at 1 */\n page?: number | null;\n /** limit of items per page */\n limit?: number | null;\n}\n\nexport interface ApiAnnotationQueuesListQueueItemsParams {\n /** Filter by status */\n status?: ApiAnnotationQueueStatus | null;\n /** page number, starts at 1 */\n page?: number | null;\n /** limit of items per page */\n limit?: number | null;\n /** The unique identifier of the annotation queue */\n queueId: string;\n}\n\nexport interface ApiCommentsGetParams {\n /** Page number, starts at 1. */\n page?: number | null;\n /** Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit */\n limit?: number | null;\n /** Filter comments by object type (trace, observation, session, prompt). */\n objectType?: string | null;\n /** Filter comments by object id. If objectType is not provided, an error will be thrown. */\n objectId?: string | null;\n /** Filter comments by author user id. */\n authorUserId?: string | null;\n}\n\nexport interface ApiDatasetItemsListParams {\n datasetName?: string | null;\n sourceTraceId?: string | null;\n sourceObservationId?: string | null;\n /** page number, starts at 1 */\n page?: number | null;\n /** limit of items per page */\n limit?: number | null;\n}\n\nexport interface ApiDatasetRunItemsListParams {\n datasetId: string;\n runName: string;\n /** page number, starts at 1 */\n page?: number | null;\n /** limit of items per page */\n limit?: number | null;\n response: ApiPaginatedDatasetRunItems;\n}\n\nexport interface ApiDatasetsListParams {\n /** page number, starts at 1 */\n page?: number | null;\n /** limit of items per page */\n limit?: number | null;\n}\n\nexport interface ApiDatasetsGetRunsParams {\n /** page number, starts at 1 */\n page?: number | null;\n /** limit of items per page */\n limit?: number | null;\n datasetName: string;\n}\n\nexport interface ApiIngestionBatchPayload {\n /** Batch of tracing events to be ingested. Discriminated by attribute `type`. */\n batch: ApiIngestionEvent[];\n /** Optional. Metadata field used by the Langfuse SDKs for debugging. */\n metadata?: any;\n}\n\nexport interface ApiMetricsMetricsParams {\n /**\n * JSON string containing the query parameters with the following structure:\n * ```json\n * {\n * \"view\": string, // Required. One of \"traces\", \"observations\", \"scores-numeric\", \"scores-categorical\"\n * \"dimensions\": [ // Optional. Default: []\n * {\n * \"field\": string // Field to group by, e.g. \"name\", \"userId\", \"sessionId\"\n * }\n * ],\n * \"metrics\": [ // Required. At least one metric must be provided\n * {\n * \"measure\": string, // What to measure, e.g. \"count\", \"latency\", \"value\"\n * \"aggregation\": string // How to aggregate, e.g. \"count\", \"sum\", \"avg\", \"p95\", \"histogram\"\n * }\n * ],\n * \"filters\": [ // Optional. Default: []\n * {\n * \"column\": string, // Column to filter on\n * \"operator\": string, // Operator, e.g. \"=\", \">\", \"<\", \"contains\"\n * \"value\": any, // Value to compare against\n * \"type\": string, // Data type, e.g. \"string\", \"number\", \"stringObject\"\n * \"key\": string // Required only when filtering on metadata\n * }\n * ],\n * \"timeDimension\": { // Optional. Default: null. If provided, results will be grouped by time\n * \"granularity\": string // One of \"minute\", \"hour\", \"day\", \"week\", \"month\", \"auto\"\n * },\n * \"fromTimestamp\": string, // Required. ISO datetime string for start of time range\n * \"toTimestamp\": string, // Required. ISO datetime string for end of time range\n * \"orderBy\": [ // Optional. Default: null\n * {\n * \"field\": string, // Field to order by\n * \"direction\": string // \"asc\" or \"desc\"\n * }\n * ],\n * \"config\": { // Optional. Query-specific configuration\n * \"bins\": number, // Optional. Number of bins for histogram (1-100), default: 10\n * \"row_limit\": number // Optional. Row limit for results (1-1000)\n * }\n * }\n * ```\n */\n query: string;\n}\n\nexport interface ApiModelsListParams {\n /** page number, starts at 1 */\n page?: number | null;\n /** limit of items per page */\n limit?: number | null;\n}\n\nexport interface ApiObservationsGetManyParams {\n /** Page number, starts at 1. */\n page?: number | null;\n /** Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit. */\n limit?: number | null;\n name?: string | null;\n userId?: string | null;\n type?: string | null;\n traceId?: string | null;\n parentObservationId?: string | null;\n /** Optional filter for observations where the environment is one of the provided values. */\n environment?: (string | null)[];\n /**\n * Retrieve only observations with a start_time on or after this datetime (ISO 8601).\n * @format date-time\n */\n fromStartTime?: string | null;\n /**\n * Retrieve only observations with a start_time before this datetime (ISO 8601).\n * @format date-time\n */\n toStartTime?: string | null;\n /** Optional filter to only include observations with a certain version. */\n version?: string | null;\n}\n\nexport interface ApiProjectsCreatePayload {\n name: string;\n /** Optional metadata for the project */\n metadata?: Record;\n /** Number of days to retain data. Must be 0 or at least 3 days. Requires data-retention entitlement for non-zero values. Optional. */\n retention: number;\n}\n\nexport interface ApiProjectsUpdatePayload {\n name: string;\n /** Optional metadata for the project */\n metadata?: Record;\n /** Number of days to retain data. Must be 0 or at least 3 days. Requires data-retention entitlement for non-zero values. Optional. */\n retention: number;\n}\n\nexport interface ApiProjectsCreateApiKeyPayload {\n /** Optional note for the API key */\n note?: string | null;\n}\n\nexport interface ApiPromptVersionUpdatePayload {\n /** New labels for the prompt version. Labels are unique across versions. The \"latest\" label is reserved and managed by Langfuse. */\n newLabels: string[];\n}\n\nexport interface ApiPromptsGetParams {\n /** Version of the prompt to be retrieved. */\n version?: number | null;\n /** Label of the prompt to be retrieved. Defaults to \"production\" if no label or version is set. */\n label?: string | null;\n /** The name of the prompt */\n promptName: string;\n}\n\nexport interface ApiPromptsListParams {\n name?: string | null;\n label?: string | null;\n tag?: string | null;\n /** page number, starts at 1 */\n page?: number | null;\n /** limit of items per page */\n limit?: number | null;\n /**\n * Optional filter to only include prompt versions created/updated on or after a certain datetime (ISO 8601)\n * @format date-time\n */\n fromUpdatedAt?: string | null;\n /**\n * Optional filter to only include prompt versions created/updated before a certain datetime (ISO 8601)\n * @format date-time\n */\n toUpdatedAt?: string | null;\n}\n\nexport interface ApiScimListUsersParams {\n /** Filter expression (e.g. userName eq \"value\") */\n filter?: string | null;\n /** 1-based index of the first result to return (default 1) */\n startIndex?: number | null;\n /** Maximum number of results to return (default 100) */\n count?: number | null;\n}\n\nexport interface ApiScimCreateUserPayload {\n /** User's email address (required) */\n userName: string;\n /** User's name information */\n name: ApiScimName;\n /** User's email addresses */\n emails?: ApiScimEmail[] | null;\n /** Whether the user is active */\n active?: boolean | null;\n /** Initial password for the user */\n password?: string | null;\n}\n\nexport interface ApiScoreConfigsGetParams {\n /** Page number, starts at 1. */\n page?: number | null;\n /** Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit */\n limit?: number | null;\n}\n\nexport interface ApiScoreV2GetParams {\n /** Page number, starts at 1. */\n page?: number | null;\n /** Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit. */\n limit?: number | null;\n /** Retrieve only scores with this userId associated to the trace. */\n userId?: string | null;\n /** Retrieve only scores with this name. */\n name?: string | null;\n /**\n * Optional filter to only include scores created on or after a certain datetime (ISO 8601)\n * @format date-time\n */\n fromTimestamp?: string | null;\n /**\n * Optional filter to only include scores created before a certain datetime (ISO 8601)\n * @format date-time\n */\n toTimestamp?: string | null;\n /** Optional filter for scores where the environment is one of the provided values. */\n environment?: (string | null)[];\n /** Retrieve only scores from a specific source. */\n source?: ApiScoreSource | null;\n /** Retrieve only scores with value. */\n operator?: string | null;\n /**\n * Retrieve only scores with value.\n * @format double\n */\n value?: number | null;\n /** Comma-separated list of score IDs to limit the results to. */\n scoreIds?: string | null;\n /** Retrieve only scores with a specific configId. */\n configId?: string | null;\n /** Retrieve only scores with a specific annotation queueId. */\n queueId?: string | null;\n /** Retrieve only scores with a specific dataType. */\n dataType?: ApiScoreDataType | null;\n /** Only scores linked to traces that include all of these tags will be returned. */\n traceTags?: (string | null)[];\n}\n\nexport interface ApiSessionsListParams {\n /** Page number, starts at 1 */\n page?: number | null;\n /** Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit. */\n limit?: number | null;\n /**\n * Optional filter to only include sessions created on or after a certain datetime (ISO 8601)\n * @format date-time\n */\n fromTimestamp?: string | null;\n /**\n * Optional filter to only include sessions created before a certain datetime (ISO 8601)\n * @format date-time\n */\n toTimestamp?: string | null;\n /** Optional filter for sessions where the environment is one of the provided values. */\n environment?: (string | null)[];\n}\n\nexport interface ApiTraceListParams {\n /** Page number, starts at 1 */\n page?: number | null;\n /** Limit of items per page. If you encounter api issues due to too large page sizes, try to reduce the limit. */\n limit?: number | null;\n userId?: string | null;\n name?: string | null;\n sessionId?: string | null;\n /**\n * Optional filter to only include traces with a trace.timestamp on or after a certain datetime (ISO 8601)\n * @format date-time\n */\n fromTimestamp?: string | null;\n /**\n * Optional filter to only include traces with a trace.timestamp before a certain datetime (ISO 8601)\n * @format date-time\n */\n toTimestamp?: string | null;\n /** Format of the string [field].[asc/desc]. Fields: id, timestamp, name, userId, release, version, public, bookmarked, sessionId. Example: timestamp.asc */\n orderBy?: string | null;\n /** Only traces that include all of these tags will be returned. */\n tags?: (string | null)[];\n /** Optional filter to only include traces with a certain version. */\n version?: string | null;\n /** Optional filter to only include traces with a certain release. */\n release?: string | null;\n /** Optional filter for traces where the environment is one of the provided values. */\n environment?: (string | null)[];\n /** Comma-separated list of fields to include in the response. Available field groups are 'core' (always included), 'io' (input, output, metadata), 'scores', 'observations', 'metrics'. If not provided, all fields are included. Example: 'core,scores,metrics' */\n fields?: string | null;\n}\n\nexport interface ApiTraceDeleteMultiplePayload {\n /** List of trace IDs to delete */\n traceIds: string[];\n}\n\nexport type QueryParamsType = Record;\nexport type ResponseFormat = keyof Omit;\n\nexport interface FullRequestParams extends Omit {\n /** set parameter to `true` for call `securityWorker` for this request */\n secure?: boolean;\n /** request path */\n path: string;\n /** content type of request body */\n type?: ContentType;\n /** query params */\n query?: QueryParamsType;\n /** format of response (i.e. response.json() -> format: \"json\") */\n format?: ResponseFormat;\n /** request body */\n body?: unknown;\n /** base url */\n baseUrl?: string;\n /** request cancellation token */\n cancelToken?: CancelToken;\n}\n\nexport type RequestParams = Omit;\n\nexport interface ApiConfig {\n baseUrl?: string;\n baseApiParams?: Omit;\n securityWorker?: (securityData: SecurityDataType | null) => Promise | RequestParams | void;\n customFetch?: typeof fetch;\n}\n\nexport interface HttpResponse extends Response {\n data: D;\n error: E;\n}\n\ntype CancelToken = Symbol | string | number;\n\nexport enum ContentType {\n Json = \"application/json\",\n FormData = \"multipart/form-data\",\n UrlEncoded = \"application/x-www-form-urlencoded\",\n Text = \"text/plain\",\n}\n\nexport class HttpClient {\n public baseUrl: string = \"\";\n private securityData: SecurityDataType | null = null;\n private securityWorker?: ApiConfig[\"securityWorker\"];\n private abortControllers = new Map();\n private customFetch = (...fetchParams: Parameters) => fetch(...fetchParams);\n\n private baseApiParams: RequestParams = {\n credentials: \"same-origin\",\n headers: {},\n redirect: \"follow\",\n referrerPolicy: \"no-referrer\",\n };\n\n constructor(apiConfig: ApiConfig = {}) {\n Object.assign(this, apiConfig);\n }\n\n public setSecurityData = (data: SecurityDataType | null) => {\n this.securityData = data;\n };\n\n protected encodeQueryParam(key: string, value: any) {\n const encodedKey = encodeURIComponent(key);\n return `${encodedKey}=${encodeURIComponent(typeof value === \"number\" ? value : `${value}`)}`;\n }\n\n protected addQueryParam(query: QueryParamsType, key: string) {\n return this.encodeQueryParam(key, query[key]);\n }\n\n protected addArrayQueryParam(query: QueryParamsType, key: string) {\n const value = query[key];\n return value.map((v: any) => this.encodeQueryParam(key, v)).join(\"&\");\n }\n\n protected toQueryString(rawQuery?: QueryParamsType): string {\n const query = rawQuery || {};\n const keys = Object.keys(query).filter((key) => \"undefined\" !== typeof query[key]);\n return keys\n .map((key) => (Array.isArray(query[key]) ? this.addArrayQueryParam(query, key) : this.addQueryParam(query, key)))\n .join(\"&\");\n }\n\n protected addQueryParams(rawQuery?: QueryParamsType): string {\n const queryString = this.toQueryString(rawQuery);\n return queryString ? `?${queryString}` : \"\";\n }\n\n private contentFormatters: Record any> = {\n [ContentType.Json]: (input: any) =>\n input !== null && (typeof input === \"object\" || typeof input === \"string\") ? JSON.stringify(input) : input,\n [ContentType.Text]: (input: any) => (input !== null && typeof input !== \"string\" ? JSON.stringify(input) : input),\n [ContentType.FormData]: (input: any) =>\n Object.keys(input || {}).reduce((formData, key) => {\n const property = input[key];\n formData.append(\n key,\n property instanceof Blob\n ? property\n : typeof property === \"object\" && property !== null\n ? JSON.stringify(property)\n : `${property}`\n );\n return formData;\n }, new FormData()),\n [ContentType.UrlEncoded]: (input: any) => this.toQueryString(input),\n };\n\n protected mergeRequestParams(params1: RequestParams, params2?: RequestParams): RequestParams {\n return {\n ...this.baseApiParams,\n ...params1,\n ...(params2 || {}),\n headers: {\n ...(this.baseApiParams.headers || {}),\n ...(params1.headers || {}),\n ...((params2 && params2.headers) || {}),\n },\n };\n }\n\n protected createAbortSignal = (cancelToken: CancelToken): AbortSignal | undefined => {\n if (this.abortControllers.has(cancelToken)) {\n const abortController = this.abortControllers.get(cancelToken);\n if (abortController) {\n return abortController.signal;\n }\n return void 0;\n }\n\n const abortController = new AbortController();\n this.abortControllers.set(cancelToken, abortController);\n return abortController.signal;\n };\n\n public abortRequest = (cancelToken: CancelToken) => {\n const abortController = this.abortControllers.get(cancelToken);\n\n if (abortController) {\n abortController.abort();\n this.abortControllers.delete(cancelToken);\n }\n };\n\n public request = async ({\n body,\n secure,\n path,\n type,\n query,\n format,\n baseUrl,\n cancelToken,\n ...params\n }: FullRequestParams): Promise => {\n const secureParams =\n ((typeof secure === \"boolean\" ? secure : this.baseApiParams.secure) &&\n this.securityWorker &&\n (await this.securityWorker(this.securityData))) ||\n {};\n const requestParams = this.mergeRequestParams(params, secureParams);\n const queryString = query && this.toQueryString(query);\n const payloadFormatter = this.contentFormatters[type || ContentType.Json];\n const responseFormat = format || requestParams.format;\n\n return this.customFetch(`${baseUrl || this.baseUrl || \"\"}${path}${queryString ? `?${queryString}` : \"\"}`, {\n ...requestParams,\n headers: {\n ...(requestParams.headers || {}),\n ...(type && type !== ContentType.FormData ? { \"Content-Type\": type } : {}),\n },\n signal: (cancelToken ? this.createAbortSignal(cancelToken) : requestParams.signal) || null,\n body: typeof body === \"undefined\" || body === null ? null : payloadFormatter(body),\n }).then(async (response) => {\n const r = response.clone() as HttpResponse;\n r.data = null as unknown as T;\n r.error = null as unknown as E;\n\n const data = !responseFormat\n ? r\n : await response[responseFormat]()\n .then((data) => {\n if (r.ok) {\n r.data = data;\n } else {\n r.error = data;\n }\n return r;\n })\n .catch((e) => {\n r.error = e;\n return r;\n });\n\n if (cancelToken) {\n this.abortControllers.delete(cancelToken);\n }\n\n if (!response.ok) throw data;\n return data.data;\n });\n };\n}\n\n/**\n * @title langfuse\n *\n * ## Authentication\n *\n * Authenticate with the API using [Basic Auth](https://en.wikipedia.org/wiki/Basic_access_authentication), get API keys in the project settings:\n *\n * - username: Langfuse Public Key\n * - password: Langfuse Secret Key\n *\n * ## Exports\n *\n * - OpenAPI spec: https://cloud.langfuse.com/generated/api/openapi.yml\n * - Postman collection: https://cloud.langfuse.com/generated/postman/collection.json\n */\nexport class LangfusePublicApi extends HttpClient {\n api = {\n /**\n * @description Add an item to an annotation queue\n *\n * @tags AnnotationQueues\n * @name AnnotationQueuesCreateQueueItem\n * @request POST:/api/public/annotation-queues/{queueId}/items\n * @secure\n */\n annotationQueuesCreateQueueItem: (\n queueId: string,\n data: ApiCreateAnnotationQueueItemRequest,\n params: RequestParams = {}\n ) =>\n this.request({\n path: `/api/public/annotation-queues/${queueId}/items`,\n method: \"POST\",\n body: data,\n secure: true,\n type: ContentType.Json,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Remove an item from an annotation queue\n *\n * @tags AnnotationQueues\n * @name AnnotationQueuesDeleteQueueItem\n * @request DELETE:/api/public/annotation-queues/{queueId}/items/{itemId}\n * @secure\n */\n annotationQueuesDeleteQueueItem: (queueId: string, itemId: string, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/annotation-queues/${queueId}/items/${itemId}`,\n method: \"DELETE\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get an annotation queue by ID\n *\n * @tags AnnotationQueues\n * @name AnnotationQueuesGetQueue\n * @request GET:/api/public/annotation-queues/{queueId}\n * @secure\n */\n annotationQueuesGetQueue: (queueId: string, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/annotation-queues/${queueId}`,\n method: \"GET\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get a specific item from an annotation queue\n *\n * @tags AnnotationQueues\n * @name AnnotationQueuesGetQueueItem\n * @request GET:/api/public/annotation-queues/{queueId}/items/{itemId}\n * @secure\n */\n annotationQueuesGetQueueItem: (queueId: string, itemId: string, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/annotation-queues/${queueId}/items/${itemId}`,\n method: \"GET\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get items for a specific annotation queue\n *\n * @tags AnnotationQueues\n * @name AnnotationQueuesListQueueItems\n * @request GET:/api/public/annotation-queues/{queueId}/items\n * @secure\n */\n annotationQueuesListQueueItems: (\n { queueId, ...query }: ApiAnnotationQueuesListQueueItemsParams,\n params: RequestParams = {}\n ) =>\n this.request({\n path: `/api/public/annotation-queues/${queueId}/items`,\n method: \"GET\",\n query: query,\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get all annotation queues\n *\n * @tags AnnotationQueues\n * @name AnnotationQueuesListQueues\n * @request GET:/api/public/annotation-queues\n * @secure\n */\n annotationQueuesListQueues: (query: ApiAnnotationQueuesListQueuesParams, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/annotation-queues`,\n method: \"GET\",\n query: query,\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Update an annotation queue item\n *\n * @tags AnnotationQueues\n * @name AnnotationQueuesUpdateQueueItem\n * @request PATCH:/api/public/annotation-queues/{queueId}/items/{itemId}\n * @secure\n */\n annotationQueuesUpdateQueueItem: (\n queueId: string,\n itemId: string,\n data: ApiUpdateAnnotationQueueItemRequest,\n params: RequestParams = {}\n ) =>\n this.request({\n path: `/api/public/annotation-queues/${queueId}/items/${itemId}`,\n method: \"PATCH\",\n body: data,\n secure: true,\n type: ContentType.Json,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Create a comment. Comments may be attached to different object types (trace, observation, session, prompt).\n *\n * @tags Comments\n * @name CommentsCreate\n * @request POST:/api/public/comments\n * @secure\n */\n commentsCreate: (data: ApiCreateCommentRequest, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/comments`,\n method: \"POST\",\n body: data,\n secure: true,\n type: ContentType.Json,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get all comments\n *\n * @tags Comments\n * @name CommentsGet\n * @request GET:/api/public/comments\n * @secure\n */\n commentsGet: (query: ApiCommentsGetParams, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/comments`,\n method: \"GET\",\n query: query,\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get a comment by id\n *\n * @tags Comments\n * @name CommentsGetById\n * @request GET:/api/public/comments/{commentId}\n * @secure\n */\n commentsGetById: (commentId: string, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/comments/${commentId}`,\n method: \"GET\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Create a dataset item\n *\n * @tags DatasetItems\n * @name DatasetItemsCreate\n * @request POST:/api/public/dataset-items\n * @secure\n */\n datasetItemsCreate: (data: ApiCreateDatasetItemRequest, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/dataset-items`,\n method: \"POST\",\n body: data,\n secure: true,\n type: ContentType.Json,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Delete a dataset item and all its run items. This action is irreversible.\n *\n * @tags DatasetItems\n * @name DatasetItemsDelete\n * @request DELETE:/api/public/dataset-items/{id}\n * @secure\n */\n datasetItemsDelete: (id: string, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/dataset-items/${id}`,\n method: \"DELETE\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get a dataset item\n *\n * @tags DatasetItems\n * @name DatasetItemsGet\n * @request GET:/api/public/dataset-items/{id}\n * @secure\n */\n datasetItemsGet: (id: string, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/dataset-items/${id}`,\n method: \"GET\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get dataset items\n *\n * @tags DatasetItems\n * @name DatasetItemsList\n * @request GET:/api/public/dataset-items\n * @secure\n */\n datasetItemsList: (query: ApiDatasetItemsListParams, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/dataset-items`,\n method: \"GET\",\n query: query,\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Create a dataset run item\n *\n * @tags DatasetRunItems\n * @name DatasetRunItemsCreate\n * @request POST:/api/public/dataset-run-items\n * @secure\n */\n datasetRunItemsCreate: (data: ApiCreateDatasetRunItemRequest, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/dataset-run-items`,\n method: \"POST\",\n body: data,\n secure: true,\n type: ContentType.Json,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description List dataset run items\n *\n * @tags DatasetRunItems\n * @name DatasetRunItemsList\n * @request GET:/api/public/dataset-run-items\n * @secure\n */\n datasetRunItemsList: (query: ApiDatasetRunItemsListParams, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/dataset-run-items`,\n method: \"GET\",\n query: query,\n secure: true,\n ...params,\n }),\n\n /**\n * @description Create a dataset\n *\n * @tags Datasets\n * @name DatasetsCreate\n * @request POST:/api/public/v2/datasets\n * @secure\n */\n datasetsCreate: (data: ApiCreateDatasetRequest, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/v2/datasets`,\n method: \"POST\",\n body: data,\n secure: true,\n type: ContentType.Json,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Delete a dataset run and all its run items. This action is irreversible.\n *\n * @tags Datasets\n * @name DatasetsDeleteRun\n * @request DELETE:/api/public/datasets/{datasetName}/runs/{runName}\n * @secure\n */\n datasetsDeleteRun: (datasetName: string, runName: string, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/datasets/${datasetName}/runs/${runName}`,\n method: \"DELETE\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get a dataset\n *\n * @tags Datasets\n * @name DatasetsGet\n * @request GET:/api/public/v2/datasets/{datasetName}\n * @secure\n */\n datasetsGet: (datasetName: string, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/v2/datasets/${datasetName}`,\n method: \"GET\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get a dataset run and its items\n *\n * @tags Datasets\n * @name DatasetsGetRun\n * @request GET:/api/public/datasets/{datasetName}/runs/{runName}\n * @secure\n */\n datasetsGetRun: (datasetName: string, runName: string, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/datasets/${datasetName}/runs/${runName}`,\n method: \"GET\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get dataset runs\n *\n * @tags Datasets\n * @name DatasetsGetRuns\n * @request GET:/api/public/datasets/{datasetName}/runs\n * @secure\n */\n datasetsGetRuns: ({ datasetName, ...query }: ApiDatasetsGetRunsParams, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/datasets/${datasetName}/runs`,\n method: \"GET\",\n query: query,\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get all datasets\n *\n * @tags Datasets\n * @name DatasetsList\n * @request GET:/api/public/v2/datasets\n * @secure\n */\n datasetsList: (query: ApiDatasetsListParams, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/v2/datasets`,\n method: \"GET\",\n query: query,\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Check health of API and database\n *\n * @tags Health\n * @name HealthHealth\n * @request GET:/api/public/health\n */\n healthHealth: (params: RequestParams = {}) =>\n this.request({\n path: `/api/public/health`,\n method: \"GET\",\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Batched ingestion for Langfuse Tracing. If you want to use tracing via the API, such as to build your own Langfuse client implementation, this is the only API route you need to implement. Within each batch, there can be multiple events. Each event has a type, an id, a timestamp, metadata and a body. Internally, we refer to this as the \"event envelope\" as it tells us something about the event but not the trace. We use the event id within this envelope to deduplicate messages to avoid processing the same event twice, i.e. the event id should be unique per request. The event.body.id is the ID of the actual trace and will be used for updates and will be visible within the Langfuse App. I.e. if you want to update a trace, you'd use the same body id, but separate event IDs. Notes: - Introduction to data model: https://langfuse.com/docs/tracing-data-model - Batch sizes are limited to 3.5 MB in total. You need to adjust the number of events per batch accordingly. - The API does not return a 4xx status code for input errors. Instead, it responds with a 207 status code, which includes a list of the encountered errors.\n *\n * @tags Ingestion\n * @name IngestionBatch\n * @request POST:/api/public/ingestion\n * @secure\n */\n ingestionBatch: (data: ApiIngestionBatchPayload, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/ingestion`,\n method: \"POST\",\n body: data,\n secure: true,\n type: ContentType.Json,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get a media record\n *\n * @tags Media\n * @name MediaGet\n * @request GET:/api/public/media/{mediaId}\n * @secure\n */\n mediaGet: (mediaId: string, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/media/${mediaId}`,\n method: \"GET\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get a presigned upload URL for a media record\n *\n * @tags Media\n * @name MediaGetUploadUrl\n * @request POST:/api/public/media\n * @secure\n */\n mediaGetUploadUrl: (data: ApiGetMediaUploadUrlRequest, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/media`,\n method: \"POST\",\n body: data,\n secure: true,\n type: ContentType.Json,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Patch a media record\n *\n * @tags Media\n * @name MediaPatch\n * @request PATCH:/api/public/media/{mediaId}\n * @secure\n */\n mediaPatch: (mediaId: string, data: ApiPatchMediaBody, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/media/${mediaId}`,\n method: \"PATCH\",\n body: data,\n secure: true,\n type: ContentType.Json,\n ...params,\n }),\n\n /**\n * @description Get metrics from the Langfuse project using a query object\n *\n * @tags Metrics\n * @name MetricsMetrics\n * @request GET:/api/public/metrics\n * @secure\n */\n metricsMetrics: (query: ApiMetricsMetricsParams, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/metrics`,\n method: \"GET\",\n query: query,\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Create a model\n *\n * @tags Models\n * @name ModelsCreate\n * @request POST:/api/public/models\n * @secure\n */\n modelsCreate: (data: ApiCreateModelRequest, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/models`,\n method: \"POST\",\n body: data,\n secure: true,\n type: ContentType.Json,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Delete a model. Cannot delete models managed by Langfuse. You can create your own definition with the same modelName to override the definition though.\n *\n * @tags Models\n * @name ModelsDelete\n * @request DELETE:/api/public/models/{id}\n * @secure\n */\n modelsDelete: (id: string, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/models/${id}`,\n method: \"DELETE\",\n secure: true,\n ...params,\n }),\n\n /**\n * @description Get a model\n *\n * @tags Models\n * @name ModelsGet\n * @request GET:/api/public/models/{id}\n * @secure\n */\n modelsGet: (id: string, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/models/${id}`,\n method: \"GET\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get all models\n *\n * @tags Models\n * @name ModelsList\n * @request GET:/api/public/models\n * @secure\n */\n modelsList: (query: ApiModelsListParams, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/models`,\n method: \"GET\",\n query: query,\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get a observation\n *\n * @tags Observations\n * @name ObservationsGet\n * @request GET:/api/public/observations/{observationId}\n * @secure\n */\n observationsGet: (observationId: string, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/observations/${observationId}`,\n method: \"GET\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get a list of observations\n *\n * @tags Observations\n * @name ObservationsGetMany\n * @request GET:/api/public/observations\n * @secure\n */\n observationsGetMany: (query: ApiObservationsGetManyParams, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/observations`,\n method: \"GET\",\n query: query,\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get all memberships for the organization associated with the API key (requires organization-scoped API key)\n *\n * @tags Organizations\n * @name OrganizationsGetOrganizationMemberships\n * @request GET:/api/public/organizations/memberships\n * @secure\n */\n organizationsGetOrganizationMemberships: (params: RequestParams = {}) =>\n this.request({\n path: `/api/public/organizations/memberships`,\n method: \"GET\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get all projects for the organization associated with the API key (requires organization-scoped API key)\n *\n * @tags Organizations\n * @name OrganizationsGetOrganizationProjects\n * @request GET:/api/public/organizations/projects\n * @secure\n */\n organizationsGetOrganizationProjects: (params: RequestParams = {}) =>\n this.request({\n path: `/api/public/organizations/projects`,\n method: \"GET\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get all memberships for a specific project (requires organization-scoped API key)\n *\n * @tags Organizations\n * @name OrganizationsGetProjectMemberships\n * @request GET:/api/public/projects/{projectId}/memberships\n * @secure\n */\n organizationsGetProjectMemberships: (projectId: string, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/projects/${projectId}/memberships`,\n method: \"GET\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Create or update a membership for the organization associated with the API key (requires organization-scoped API key)\n *\n * @tags Organizations\n * @name OrganizationsUpdateOrganizationMembership\n * @request PUT:/api/public/organizations/memberships\n * @secure\n */\n organizationsUpdateOrganizationMembership: (data: ApiMembershipRequest, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/organizations/memberships`,\n method: \"PUT\",\n body: data,\n secure: true,\n type: ContentType.Json,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Create or update a membership for a specific project (requires organization-scoped API key). The user must already be a member of the organization.\n *\n * @tags Organizations\n * @name OrganizationsUpdateProjectMembership\n * @request PUT:/api/public/projects/{projectId}/memberships\n * @secure\n */\n organizationsUpdateProjectMembership: (projectId: string, data: ApiMembershipRequest, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/projects/${projectId}/memberships`,\n method: \"PUT\",\n body: data,\n secure: true,\n type: ContentType.Json,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Create a new project (requires organization-scoped API key)\n *\n * @tags Projects\n * @name ProjectsCreate\n * @request POST:/api/public/projects\n * @secure\n */\n projectsCreate: (data: ApiProjectsCreatePayload, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/projects`,\n method: \"POST\",\n body: data,\n secure: true,\n type: ContentType.Json,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Create a new API key for a project (requires organization-scoped API key)\n *\n * @tags Projects\n * @name ProjectsCreateApiKey\n * @request POST:/api/public/projects/{projectId}/apiKeys\n * @secure\n */\n projectsCreateApiKey: (projectId: string, data: ApiProjectsCreateApiKeyPayload, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/projects/${projectId}/apiKeys`,\n method: \"POST\",\n body: data,\n secure: true,\n type: ContentType.Json,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Delete a project by ID (requires organization-scoped API key). Project deletion is processed asynchronously.\n *\n * @tags Projects\n * @name ProjectsDelete\n * @request DELETE:/api/public/projects/{projectId}\n * @secure\n */\n projectsDelete: (projectId: string, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/projects/${projectId}`,\n method: \"DELETE\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Delete an API key for a project (requires organization-scoped API key)\n *\n * @tags Projects\n * @name ProjectsDeleteApiKey\n * @request DELETE:/api/public/projects/{projectId}/apiKeys/{apiKeyId}\n * @secure\n */\n projectsDeleteApiKey: (projectId: string, apiKeyId: string, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/projects/${projectId}/apiKeys/${apiKeyId}`,\n method: \"DELETE\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get Project associated with API key\n *\n * @tags Projects\n * @name ProjectsGet\n * @request GET:/api/public/projects\n * @secure\n */\n projectsGet: (params: RequestParams = {}) =>\n this.request({\n path: `/api/public/projects`,\n method: \"GET\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get all API keys for a project (requires organization-scoped API key)\n *\n * @tags Projects\n * @name ProjectsGetApiKeys\n * @request GET:/api/public/projects/{projectId}/apiKeys\n * @secure\n */\n projectsGetApiKeys: (projectId: string, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/projects/${projectId}/apiKeys`,\n method: \"GET\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Update a project by ID (requires organization-scoped API key).\n *\n * @tags Projects\n * @name ProjectsUpdate\n * @request PUT:/api/public/projects/{projectId}\n * @secure\n */\n projectsUpdate: (projectId: string, data: ApiProjectsUpdatePayload, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/projects/${projectId}`,\n method: \"PUT\",\n body: data,\n secure: true,\n type: ContentType.Json,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Create a new version for the prompt with the given `name`\n *\n * @tags Prompts\n * @name PromptsCreate\n * @request POST:/api/public/v2/prompts\n * @secure\n */\n promptsCreate: (data: ApiCreatePromptRequest, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/v2/prompts`,\n method: \"POST\",\n body: data,\n secure: true,\n type: ContentType.Json,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get a prompt\n *\n * @tags Prompts\n * @name PromptsGet\n * @request GET:/api/public/v2/prompts/{promptName}\n * @secure\n */\n promptsGet: ({ promptName, ...query }: ApiPromptsGetParams, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/v2/prompts/${promptName}`,\n method: \"GET\",\n query: query,\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get a list of prompt names with versions and labels\n *\n * @tags Prompts\n * @name PromptsList\n * @request GET:/api/public/v2/prompts\n * @secure\n */\n promptsList: (query: ApiPromptsListParams, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/v2/prompts`,\n method: \"GET\",\n query: query,\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Update labels for a specific prompt version\n *\n * @tags PromptVersion\n * @name PromptVersionUpdate\n * @request PATCH:/api/public/v2/prompts/{name}/versions/{version}\n * @secure\n */\n promptVersionUpdate: (\n name: string,\n version: number,\n data: ApiPromptVersionUpdatePayload,\n params: RequestParams = {}\n ) =>\n this.request({\n path: `/api/public/v2/prompts/${name}/versions/${version}`,\n method: \"PATCH\",\n body: data,\n secure: true,\n type: ContentType.Json,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Create a new user in the organization (requires organization-scoped API key)\n *\n * @tags Scim\n * @name ScimCreateUser\n * @request POST:/api/public/scim/Users\n * @secure\n */\n scimCreateUser: (data: ApiScimCreateUserPayload, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/scim/Users`,\n method: \"POST\",\n body: data,\n secure: true,\n type: ContentType.Json,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Remove a user from the organization (requires organization-scoped API key). Note that this only removes the user from the organization but does not delete the user entity itself.\n *\n * @tags Scim\n * @name ScimDeleteUser\n * @request DELETE:/api/public/scim/Users/{userId}\n * @secure\n */\n scimDeleteUser: (userId: string, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/scim/Users/${userId}`,\n method: \"DELETE\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get SCIM Resource Types (requires organization-scoped API key)\n *\n * @tags Scim\n * @name ScimGetResourceTypes\n * @request GET:/api/public/scim/ResourceTypes\n * @secure\n */\n scimGetResourceTypes: (params: RequestParams = {}) =>\n this.request({\n path: `/api/public/scim/ResourceTypes`,\n method: \"GET\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get SCIM Schemas (requires organization-scoped API key)\n *\n * @tags Scim\n * @name ScimGetSchemas\n * @request GET:/api/public/scim/Schemas\n * @secure\n */\n scimGetSchemas: (params: RequestParams = {}) =>\n this.request({\n path: `/api/public/scim/Schemas`,\n method: \"GET\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get SCIM Service Provider Configuration (requires organization-scoped API key)\n *\n * @tags Scim\n * @name ScimGetServiceProviderConfig\n * @request GET:/api/public/scim/ServiceProviderConfig\n * @secure\n */\n scimGetServiceProviderConfig: (params: RequestParams = {}) =>\n this.request({\n path: `/api/public/scim/ServiceProviderConfig`,\n method: \"GET\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get a specific user by ID (requires organization-scoped API key)\n *\n * @tags Scim\n * @name ScimGetUser\n * @request GET:/api/public/scim/Users/{userId}\n * @secure\n */\n scimGetUser: (userId: string, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/scim/Users/${userId}`,\n method: \"GET\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description List users in the organization (requires organization-scoped API key)\n *\n * @tags Scim\n * @name ScimListUsers\n * @request GET:/api/public/scim/Users\n * @secure\n */\n scimListUsers: (query: ApiScimListUsersParams, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/scim/Users`,\n method: \"GET\",\n query: query,\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Create a score configuration (config). Score configs are used to define the structure of scores\n *\n * @tags ScoreConfigs\n * @name ScoreConfigsCreate\n * @request POST:/api/public/score-configs\n * @secure\n */\n scoreConfigsCreate: (data: ApiCreateScoreConfigRequest, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/score-configs`,\n method: \"POST\",\n body: data,\n secure: true,\n type: ContentType.Json,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get all score configs\n *\n * @tags ScoreConfigs\n * @name ScoreConfigsGet\n * @request GET:/api/public/score-configs\n * @secure\n */\n scoreConfigsGet: (query: ApiScoreConfigsGetParams, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/score-configs`,\n method: \"GET\",\n query: query,\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get a score config\n *\n * @tags ScoreConfigs\n * @name ScoreConfigsGetById\n * @request GET:/api/public/score-configs/{configId}\n * @secure\n */\n scoreConfigsGetById: (configId: string, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/score-configs/${configId}`,\n method: \"GET\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Create a score (supports both trace and session scores)\n *\n * @tags Score\n * @name ScoreCreate\n * @request POST:/api/public/scores\n * @secure\n */\n scoreCreate: (data: ApiCreateScoreRequest, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/scores`,\n method: \"POST\",\n body: data,\n secure: true,\n type: ContentType.Json,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Delete a score (supports both trace and session scores)\n *\n * @tags Score\n * @name ScoreDelete\n * @request DELETE:/api/public/scores/{scoreId}\n * @secure\n */\n scoreDelete: (scoreId: string, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/scores/${scoreId}`,\n method: \"DELETE\",\n secure: true,\n ...params,\n }),\n\n /**\n * @description Get a list of scores (supports both trace and session scores)\n *\n * @tags ScoreV2\n * @name ScoreV2Get\n * @request GET:/api/public/v2/scores\n * @secure\n */\n scoreV2Get: (query: ApiScoreV2GetParams, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/v2/scores`,\n method: \"GET\",\n query: query,\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get a score (supports both trace and session scores)\n *\n * @tags ScoreV2\n * @name ScoreV2GetById\n * @request GET:/api/public/v2/scores/{scoreId}\n * @secure\n */\n scoreV2GetById: (scoreId: string, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/v2/scores/${scoreId}`,\n method: \"GET\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get a session. Please note that `traces` on this endpoint are not paginated, if you plan to fetch large sessions, consider `GET /api/public/traces?sessionId=`\n *\n * @tags Sessions\n * @name SessionsGet\n * @request GET:/api/public/sessions/{sessionId}\n * @secure\n */\n sessionsGet: (sessionId: string, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/sessions/${sessionId}`,\n method: \"GET\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get sessions\n *\n * @tags Sessions\n * @name SessionsList\n * @request GET:/api/public/sessions\n * @secure\n */\n sessionsList: (query: ApiSessionsListParams, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/sessions`,\n method: \"GET\",\n query: query,\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Delete a specific trace\n *\n * @tags Trace\n * @name TraceDelete\n * @request DELETE:/api/public/traces/{traceId}\n * @secure\n */\n traceDelete: (traceId: string, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/traces/${traceId}`,\n method: \"DELETE\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Delete multiple traces\n *\n * @tags Trace\n * @name TraceDeleteMultiple\n * @request DELETE:/api/public/traces\n * @secure\n */\n traceDeleteMultiple: (data: ApiTraceDeleteMultiplePayload, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/traces`,\n method: \"DELETE\",\n body: data,\n secure: true,\n type: ContentType.Json,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get a specific trace\n *\n * @tags Trace\n * @name TraceGet\n * @request GET:/api/public/traces/{traceId}\n * @secure\n */\n traceGet: (traceId: string, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/traces/${traceId}`,\n method: \"GET\",\n secure: true,\n format: \"json\",\n ...params,\n }),\n\n /**\n * @description Get list of traces\n *\n * @tags Trace\n * @name TraceList\n * @request GET:/api/public/traces\n * @secure\n */\n traceList: (query: ApiTraceListParams, params: RequestParams = {}) =>\n this.request({\n path: `/api/public/traces`,\n method: \"GET\",\n query: query,\n secure: true,\n format: \"json\",\n ...params,\n }),\n };\n}\n", "import {\n LangfuseCore,\n LangfuseWebStateless,\n type LangfuseFetchOptions,\n type LangfuseFetchResponse,\n type LangfusePersistedProperty,\n utils,\n} from \"langfuse-core\";\nimport { type LangfuseStorage, getStorage } from \"./storage\";\nimport { LangfusePublicApi } from \"./publicApi\";\nimport { version } from \"../package.json\";\nimport { type LangfuseOptions } from \"./types\";\n\nexport type * from \"./publicApi\";\nexport type {\n LangfusePromptClient,\n ChatPromptClient,\n TextPromptClient,\n LangfusePromptRecord,\n LangfuseTraceClient,\n LangfuseSpanClient,\n LangfuseEventClient,\n LangfuseGenerationClient,\n} from \"langfuse-core\";\n\n// Required when users pass these as typed arguments\nexport { LangfuseMedia } from \"langfuse-core\";\n\nexport class Langfuse extends LangfuseCore {\n private _storage: LangfuseStorage;\n private _storageCache: any;\n private _storageKey: string;\n public api: LangfusePublicApi[\"api\"];\n\n constructor(params?: { publicKey?: string; secretKey?: string } & LangfuseOptions) {\n const langfuseConfig = utils.configLangfuseSDK(params);\n super(langfuseConfig);\n\n if (typeof window !== \"undefined\" && \"Deno\" in window === false) {\n this._storageKey = params?.persistence_name\n ? `lf_${params.persistence_name}`\n : `lf_${langfuseConfig.publicKey}_langfuse`;\n this._storage = getStorage(params?.persistence || \"localStorage\", window);\n } else {\n this._storageKey = `lf_${langfuseConfig.publicKey}_langfuse`;\n this._storage = getStorage(\"memory\", undefined);\n }\n\n this.api = new LangfusePublicApi({\n baseUrl: this.baseUrl,\n baseApiParams: {\n headers: {\n \"X-Langfuse-Sdk-Name\": \"langfuse-js\",\n \"X-Langfuse-Sdk-Version\": this.getLibraryVersion(),\n \"X-Langfuse-Sdk-Variant\": this.getLibraryId(),\n \"X-Langfuse-Sdk-Integration\": this.sdkIntegration,\n \"X-Langfuse-Public-Key\": this.publicKey,\n ...this.additionalHeaders,\n ...this.constructAuthorizationHeader(this.publicKey, this.secretKey),\n },\n },\n }).api;\n }\n\n getPersistedProperty(key: LangfusePersistedProperty): T | undefined {\n if (!this._storageCache) {\n this._storageCache = JSON.parse(this._storage.getItem(this._storageKey) || \"{}\") || {};\n }\n\n return this._storageCache[key];\n }\n\n setPersistedProperty(key: LangfusePersistedProperty, value: T | null): void {\n if (!this._storageCache) {\n this._storageCache = JSON.parse(this._storage.getItem(this._storageKey) || \"{}\") || {};\n }\n\n if (value === null) {\n delete this._storageCache[key];\n } else {\n this._storageCache[key] = value;\n }\n\n this._storage.setItem(this._storageKey, JSON.stringify(this._storageCache));\n }\n\n fetch(url: string, options: LangfuseFetchOptions): Promise {\n return fetch(url, options);\n }\n\n getLibraryId(): string {\n return \"langfuse\";\n }\n\n getLibraryVersion(): string {\n return version;\n }\n\n getCustomUserAgent(): void {\n return;\n }\n}\n\nexport class LangfuseWeb extends LangfuseWebStateless {\n private _storage: LangfuseStorage;\n private _storageCache: any;\n private _storageKey: string;\n\n constructor(params?: Omit) {\n const langfuseConfig = utils.configLangfuseSDK(params, false);\n super(langfuseConfig);\n\n if (typeof window !== \"undefined\") {\n this._storageKey = params?.persistence_name\n ? `lf_${params.persistence_name}`\n : `lf_${langfuseConfig.publicKey}_langfuse`;\n this._storage = getStorage(params?.persistence || \"localStorage\", window);\n } else {\n this._storageKey = `lf_${langfuseConfig.publicKey}_langfuse`;\n this._storage = getStorage(\"memory\", undefined);\n }\n }\n\n getPersistedProperty(key: LangfusePersistedProperty): T | undefined {\n if (!this._storageCache) {\n this._storageCache = JSON.parse(this._storage.getItem(this._storageKey) || \"{}\") || {};\n }\n\n return this._storageCache[key];\n }\n\n setPersistedProperty(key: LangfusePersistedProperty, value: T | null): void {\n if (!this._storageCache) {\n this._storageCache = JSON.parse(this._storage.getItem(this._storageKey) || \"{}\") || {};\n }\n\n if (value === null) {\n delete this._storageCache[key];\n } else {\n this._storageCache[key] = value;\n }\n\n this._storage.setItem(this._storageKey, JSON.stringify(this._storageCache));\n }\n\n fetch(url: string, options: LangfuseFetchOptions): Promise {\n return fetch(url, options);\n }\n\n getLibraryId(): string {\n return \"langfuse-frontend\";\n }\n\n getLibraryVersion(): string {\n return version;\n }\n\n getCustomUserAgent(): void {\n return;\n }\n}\n", "import { Langfuse } from \"../langfuse\";\nimport type { LangfuseInitParams } from \"./types\";\n\n/**\n * Represents a singleton instance of the Langfuse client.\n */\nexport class LangfuseSingleton {\n private static instance: Langfuse | null = null; // Lazy initialization\n\n /**\n * Returns the singleton instance of the Langfuse client.\n * @param params Optional parameters for initializing the Langfuse instance. Only used for the first call.\n * @returns The singleton instance of the Langfuse client.\n */\n public static getInstance(params?: LangfuseInitParams): Langfuse {\n if (!LangfuseSingleton.instance) {\n LangfuseSingleton.instance = new Langfuse(params);\n }\n return LangfuseSingleton.instance;\n }\n}\n", "import type OpenAI from \"openai\";\nimport type { CreateLangfuseGenerationBody, Usage, UsageDetails } from \"langfuse-core\";\n\ntype ParsedOpenAIArguments = {\n model: string;\n input: Record | string;\n modelParameters: Record;\n};\n\nexport const parseInputArgs = (args: Record): ParsedOpenAIArguments => {\n let params: Record = {};\n params = {\n frequency_penalty: args.frequency_penalty,\n logit_bias: args.logit_bias,\n logprobs: args.logprobs,\n max_tokens: args.max_tokens,\n n: args.n,\n presence_penalty: args.presence_penalty,\n seed: args.seed,\n stop: args.stop,\n stream: args.stream,\n temperature: args.temperature,\n top_p: args.top_p,\n user: args.user,\n response_format: args.response_format,\n top_logprobs: args.top_logprobs,\n };\n\n let input: Record | string = args.input;\n\n if (args && typeof args === \"object\" && !Array.isArray(args) && \"messages\" in args) {\n input = {};\n input.messages = args.messages;\n if (\"function_call\" in args) {\n input.function_call = args.function_call;\n }\n if (\"functions\" in args) {\n input.functions = args.functions;\n }\n if (\"tools\" in args) {\n input.tools = args.tools;\n }\n\n if (\"tool_choice\" in args) {\n input.tool_choice = args.tool_choice;\n }\n } else if (!input) {\n input = args.prompt;\n }\n\n return {\n model: args.model,\n input: input,\n modelParameters: params,\n };\n};\n\nexport const parseCompletionOutput = (res: unknown): CreateLangfuseGenerationBody[\"output\"] => {\n if (res instanceof Object && \"output_text\" in res && res[\"output_text\"] !== \"\") {\n return res[\"output_text\"] as string;\n }\n\n if (typeof res === \"object\" && res && \"output\" in res && Array.isArray(res[\"output\"])) {\n const output = res[\"output\"];\n\n if (output.length > 1) {\n return output;\n }\n if (output.length === 1) {\n return output[0] as Record;\n }\n\n return null;\n }\n\n if (!(res instanceof Object && \"choices\" in res && Array.isArray(res.choices))) {\n return \"\";\n }\n\n return \"message\" in res.choices[0] ? res.choices[0].message : res.choices[0].text ?? \"\";\n};\n\nexport const parseUsage = (res: unknown): Usage | undefined => {\n if (hasCompletionUsage(res)) {\n const { prompt_tokens, completion_tokens, total_tokens } = res.usage;\n\n return {\n input: prompt_tokens,\n output: completion_tokens,\n total: total_tokens,\n };\n }\n};\n\nexport const parseUsageDetails = (completionUsage: OpenAI.CompletionUsage): UsageDetails | undefined => {\n if (\"prompt_tokens\" in completionUsage) {\n const { prompt_tokens, completion_tokens, total_tokens, completion_tokens_details, prompt_tokens_details } =\n completionUsage;\n\n return {\n input: prompt_tokens,\n output: completion_tokens,\n total: total_tokens,\n ...Object.fromEntries(\n Object.entries(prompt_tokens_details ?? {}).map(([key, value]) => [`input_${key}`, value as number])\n ),\n ...Object.fromEntries(\n Object.entries(completion_tokens_details ?? {}).map(([key, value]) => [`output_${key}`, value as number])\n ),\n };\n } else if (\"input_tokens\" in completionUsage) {\n const { input_tokens, output_tokens, total_tokens, input_tokens_details, output_tokens_details } = completionUsage;\n\n return {\n input: input_tokens,\n output: output_tokens,\n total: total_tokens,\n ...Object.fromEntries(\n Object.entries(input_tokens_details ?? {}).map(([key, value]) => [`input_${key}`, value as number])\n ),\n ...Object.fromEntries(\n Object.entries(output_tokens_details ?? {}).map(([key, value]) => [`output_${key}`, value as number])\n ),\n };\n }\n};\n\nexport const parseUsageDetailsFromResponse = (res: unknown): UsageDetails | undefined => {\n if (hasCompletionUsage(res)) {\n return parseUsageDetails(res.usage);\n }\n};\n\nexport const parseChunk = (\n rawChunk: unknown\n):\n | { isToolCall: false; data: string }\n | { isToolCall: true; data: OpenAI.Chat.Completions.ChatCompletionChunk.Choice.Delta.ToolCall } => {\n let isToolCall = false;\n const _chunk = rawChunk as OpenAI.ChatCompletionChunk | OpenAI.Completions.Completion;\n const chunkData = _chunk?.choices?.[0];\n\n try {\n if (\"delta\" in chunkData && \"tool_calls\" in chunkData.delta && Array.isArray(chunkData.delta.tool_calls)) {\n isToolCall = true;\n\n return { isToolCall, data: chunkData.delta.tool_calls[0] };\n }\n if (\"delta\" in chunkData) {\n return { isToolCall, data: chunkData.delta?.content || \"\" };\n }\n\n if (\"text\" in chunkData) {\n return { isToolCall, data: chunkData.text || \"\" };\n }\n } catch (e) {}\n\n return { isToolCall: false, data: \"\" };\n};\n\n// Type guard to check if an unknown object is a UsageResponse\nfunction hasCompletionUsage(obj: any): obj is { usage: OpenAI.CompletionUsage } {\n return (\n obj instanceof Object &&\n \"usage\" in obj &&\n obj.usage instanceof Object &&\n // Completion API Usage format\n ((typeof obj.usage.prompt_tokens === \"number\" &&\n typeof obj.usage.completion_tokens === \"number\" &&\n typeof obj.usage.total_tokens === \"number\") ||\n // Response API Usage format\n (typeof obj.usage.input_tokens === \"number\" &&\n typeof obj.usage.output_tokens === \"number\" &&\n typeof obj.usage.total_tokens === \"number\"))\n );\n}\n\nexport const getToolCallOutput = (\n toolCallChunks: OpenAI.Chat.Completions.ChatCompletionChunk.Choice.Delta.ToolCall[]\n): {\n tool_calls: {\n function: {\n name: string;\n arguments: string;\n };\n }[];\n} => {\n let name = \"\";\n let toolArguments = \"\";\n\n for (const toolCall of toolCallChunks) {\n name = toolCall.function?.name || name;\n toolArguments += toolCall.function?.arguments || \"\";\n }\n\n return {\n tool_calls: [\n {\n function: {\n name,\n arguments: toolArguments,\n },\n },\n ],\n };\n};\n\nexport const parseModelDataFromResponse = (\n res: unknown\n): {\n model: string | undefined;\n modelParameters: Record | undefined;\n metadata: Record | undefined;\n} => {\n if (typeof res !== \"object\" || res === null) {\n return {\n model: undefined,\n modelParameters: undefined,\n metadata: undefined,\n };\n }\n\n const model = \"model\" in res ? (res[\"model\"] as string) : undefined;\n const modelParameters: Record = {};\n const modelParamKeys = [\n \"max_output_tokens\",\n \"parallel_tool_calls\",\n \"store\",\n \"temperature\",\n \"tool_choice\",\n \"top_p\",\n \"truncation\",\n \"user\",\n ];\n\n const metadata: Record = {};\n const metadataKeys = [\n \"reasoning\",\n \"incomplete_details\",\n \"instructions\",\n \"previous_response_id\",\n \"tools\",\n \"metadata\",\n \"status\",\n \"error\",\n ];\n\n for (const key of modelParamKeys) {\n const val = key in res ? (res[key as keyof typeof res] as string | number) : null;\n if (val !== null && val !== undefined) {\n modelParameters[key as keyof typeof modelParameters] = val;\n }\n }\n\n for (const key of metadataKeys) {\n const val = key in res ? (res[key as keyof typeof res] as string | number) : null;\n if (val) {\n metadata[key as keyof typeof metadata] = val;\n }\n }\n\n return {\n model,\n modelParameters: Object.keys(modelParameters).length > 0 ? modelParameters : undefined,\n metadata: Object.keys(metadata).length > 0 ? metadata : undefined,\n };\n};\n", "export const isAsyncIterable = (x: unknown): x is AsyncIterable =>\n x != null && typeof x === \"object\" && typeof (x as any)[Symbol.asyncIterator] === \"function\";\n", "import type OpenAI from \"openai\";\n\nimport { type CreateLangfuseGenerationBody } from \"langfuse-core\";\n\nimport { LangfuseSingleton } from \"./LangfuseSingleton\";\nimport {\n getToolCallOutput,\n parseChunk,\n parseCompletionOutput,\n parseInputArgs,\n parseUsage,\n parseUsageDetails,\n parseModelDataFromResponse,\n parseUsageDetailsFromResponse,\n} from \"./parseOpenAI\";\nimport { isAsyncIterable } from \"./utils\";\nimport type { LangfuseConfig, LangfuseParent } from \"./types\";\n\ntype GenericMethod = (...args: unknown[]) => unknown;\n\nexport const withTracing = (\n tracedMethod: T,\n config?: LangfuseConfig & Required<{ generationName: string }>\n): ((...args: Parameters) => Promise>) => {\n return (...args) => wrapMethod(tracedMethod, config, ...args);\n};\n\nconst wrapMethod = (\n tracedMethod: T,\n config?: LangfuseConfig,\n ...args: Parameters\n): ReturnType | any => {\n const { model, input, modelParameters } = parseInputArgs(args[0] ?? {});\n\n const finalModelParams = { ...modelParameters, response_format: null };\n const finalMetadata = {\n ...config?.metadata,\n response_format: \"response_format\" in modelParameters ? modelParameters.response_format : undefined,\n };\n\n let observationData = {\n model,\n input,\n modelParameters: finalModelParams,\n name: config?.generationName,\n startTime: new Date(),\n promptName: config?.langfusePrompt?.name,\n promptVersion: config?.langfusePrompt?.version,\n metadata: finalMetadata,\n };\n\n let langfuseParent: LangfuseParent;\n const hasUserProvidedParent = config && \"parent\" in config;\n\n if (hasUserProvidedParent) {\n langfuseParent = config.parent;\n\n // Remove the parent from the config to avoid circular references in the generation body\n const filteredConfig = { ...config, parent: undefined };\n\n observationData = {\n ...filteredConfig,\n ...observationData,\n promptName: config?.promptName ?? config?.langfusePrompt?.name, // Maintain backward compatibility for users who use promptName\n promptVersion: config?.promptVersion ?? config?.langfusePrompt?.version, // Maintain backward compatibility for users who use promptVersion\n };\n } else {\n const langfuse = LangfuseSingleton.getInstance(config?.clientInitParams);\n langfuseParent = langfuse.trace({\n ...config,\n ...observationData,\n id: config?.traceId,\n name: config?.traceName,\n timestamp: observationData.startTime,\n });\n }\n\n try {\n const res = tracedMethod(...args);\n\n // Handle stream responses\n if (isAsyncIterable(res)) {\n return wrapAsyncIterable(res, langfuseParent, hasUserProvidedParent, observationData);\n }\n\n if (res instanceof Promise) {\n const wrappedPromise = res\n .then((result) => {\n if (isAsyncIterable(result)) {\n return wrapAsyncIterable(result, langfuseParent, hasUserProvidedParent, observationData);\n }\n\n const output = parseCompletionOutput(result);\n const usage = parseUsage(result);\n const usageDetails = parseUsageDetailsFromResponse(result);\n const {\n model: modelFromResponse,\n modelParameters: modelParametersFromResponse,\n metadata: metadataFromResponse,\n } = parseModelDataFromResponse(result);\n\n langfuseParent.generation({\n ...observationData,\n output,\n endTime: new Date(),\n usage,\n usageDetails,\n model: modelFromResponse || observationData.model,\n modelParameters: { ...observationData.modelParameters, ...modelParametersFromResponse },\n metadata: { ...observationData.metadata, ...metadataFromResponse },\n });\n\n if (!hasUserProvidedParent) {\n langfuseParent.update({ output });\n }\n\n return result;\n })\n .catch((err) => {\n langfuseParent.generation({\n ...observationData,\n endTime: new Date(),\n statusMessage: String(err),\n level: \"ERROR\",\n usage: {\n inputCost: 0,\n outputCost: 0,\n totalCost: 0,\n },\n costDetails: {\n input: 0,\n output: 0,\n total: 0,\n },\n });\n\n throw err;\n });\n\n return wrappedPromise;\n }\n\n return res;\n } catch (error) {\n langfuseParent.generation({\n ...observationData,\n endTime: new Date(),\n statusMessage: String(error),\n level: \"ERROR\",\n usage: {\n inputCost: 0,\n outputCost: 0,\n totalCost: 0,\n },\n costDetails: {\n input: 0,\n output: 0,\n total: 0,\n },\n });\n\n throw error;\n }\n};\n\nfunction wrapAsyncIterable(\n iterable: AsyncIterable,\n langfuseParent: LangfuseParent,\n hasUserProvidedParent: boolean | undefined,\n observationData: Record\n): R {\n async function* tracedOutputGenerator(): AsyncGenerator {\n const response = iterable;\n const textChunks: string[] = [];\n const toolCallChunks: OpenAI.Chat.Completions.ChatCompletionChunk.Choice.Delta.ToolCall[] = [];\n let completionStartTime: Date | null = null;\n let usage: OpenAI.CompletionUsage | null = null;\n let usageDetails: CreateLangfuseGenerationBody[\"usageDetails\"] = undefined;\n let output: CreateLangfuseGenerationBody[\"output\"] = null;\n\n for await (const rawChunk of response as AsyncIterable) {\n completionStartTime = completionStartTime ?? new Date();\n\n // Handle Response API chunks\n if (typeof rawChunk === \"object\" && rawChunk && \"response\" in rawChunk) {\n const result = rawChunk[\"response\"];\n output = parseCompletionOutput(result);\n usageDetails = parseUsageDetailsFromResponse(result);\n\n const {\n model: modelFromResponse,\n modelParameters: modelParametersFromResponse,\n metadata: metadataFromResponse,\n } = parseModelDataFromResponse(result);\n\n observationData[\"model\"] = modelFromResponse ?? observationData[\"model\"];\n observationData[\"modelParameters\"] = { ...observationData.modelParameters, ...modelParametersFromResponse };\n observationData[\"metadata\"] = { ...observationData.metadata, ...metadataFromResponse };\n }\n\n if (typeof rawChunk === \"object\" && rawChunk != null && \"usage\" in rawChunk) {\n usage = rawChunk.usage as OpenAI.CompletionUsage | null;\n }\n\n const processedChunk = parseChunk(rawChunk);\n\n if (!processedChunk.isToolCall) {\n textChunks.push(processedChunk.data);\n } else {\n toolCallChunks.push(processedChunk.data);\n }\n\n yield rawChunk;\n }\n\n output = output ?? (toolCallChunks.length > 0 ? getToolCallOutput(toolCallChunks) : textChunks.join(\"\"));\n\n langfuseParent.generation({\n ...observationData,\n output,\n endTime: new Date(),\n completionStartTime,\n usage: usage\n ? {\n input: \"prompt_tokens\" in usage ? usage.prompt_tokens : undefined,\n output: \"completion_tokens\" in usage ? usage.completion_tokens : undefined,\n total: \"total_tokens\" in usage ? usage.total_tokens : undefined,\n }\n : undefined,\n usageDetails: usageDetails ?? (usage ? parseUsageDetails(usage) : undefined),\n });\n\n if (!hasUserProvidedParent) {\n langfuseParent.update({ output });\n }\n }\n\n return tracedOutputGenerator() as R;\n}\n", "import type { LangfuseCore } from \"langfuse-core\";\n\nimport { LangfuseSingleton } from \"./LangfuseSingleton\";\nimport { withTracing } from \"./traceMethod\";\nimport type { LangfuseConfig, LangfuseExtension } from \"./types\";\n\n/**\n * Wraps an OpenAI SDK object with Langfuse tracing. Function calls are extended with a tracer that logs detailed information about the call, including the method name,\n * input parameters, and output.\n * \n * @param {T} sdk - The OpenAI SDK object to be wrapped.\n * @param {LangfuseConfig} [langfuseConfig] - Optional configuration object for the wrapper.\n * @param {string} [langfuseConfig.traceName] - The name to use for tracing. If not provided, a default name based on the SDK's constructor name and the method name will be used.\n * @param {string} [langfuseConfig.sessionId] - Optional session ID for tracing.\n * @param {string} [langfuseConfig.userId] - Optional user ID for tracing.\n * @param {string} [langfuseConfig.release] - Optional release version for tracing.\n * @param {string} [langfuseConfig.version] - Optional version for tracing.\n * @param {string} [langfuseConfig.metadata] - Optional metadata for tracing.\n * @param {string} [langfuseConfig.tags] - Optional tags for tracing.\n * @returns {T} - A proxy of the original SDK object with methods wrapped for tracing.\n *\n * @example\n * const client = new OpenAI();\n * const res = observeOpenAI(client, { traceName: \"My.OpenAI.Chat.Trace\" }).chat.completions.create({\n * messages: [{ role: \"system\", content: \"Say this is a test!\" }],\n model: \"gpt-3.5-turbo\",\n user: \"langfuse\",\n max_tokens: 300\n * });\n * */\nexport const observeOpenAI = (\n sdk: SDKType,\n langfuseConfig?: LangfuseConfig\n): SDKType & LangfuseExtension => {\n return new Proxy(sdk, {\n get(wrappedSdk, propKey, proxy) {\n const originalProperty = wrappedSdk[propKey as keyof SDKType];\n\n const defaultGenerationName = `${sdk.constructor?.name}.${propKey.toString()}`;\n const generationName = langfuseConfig?.generationName ?? defaultGenerationName;\n const traceName = langfuseConfig && \"traceName\" in langfuseConfig ? langfuseConfig.traceName : generationName;\n const config = { ...langfuseConfig, generationName, traceName };\n\n // Add a flushAsync method to the OpenAI SDK that flushes the Langfuse client\n if (propKey === \"flushAsync\") {\n let langfuseClient: LangfuseCore;\n\n // Flush the correct client depending on whether a parent client is provided\n if (langfuseConfig && \"parent\" in langfuseConfig) {\n langfuseClient = langfuseConfig.parent.client;\n } else {\n langfuseClient = LangfuseSingleton.getInstance();\n }\n\n return langfuseClient.flushAsync.bind(langfuseClient);\n }\n\n // Add a shutdownAsync method to the OpenAI SDK that flushes the Langfuse client\n if (propKey === \"shutdownAsync\") {\n let langfuseClient: LangfuseCore;\n\n // Flush the correct client depending on whether a parent client is provided\n if (langfuseConfig && \"parent\" in langfuseConfig) {\n langfuseClient = langfuseConfig.parent.client;\n } else {\n langfuseClient = LangfuseSingleton.getInstance();\n }\n\n return langfuseClient.shutdownAsync.bind(langfuseClient);\n }\n\n // Trace methods of the OpenAI SDK\n if (typeof originalProperty === \"function\") {\n return withTracing(originalProperty.bind(wrappedSdk), config);\n }\n\n const isNestedOpenAIObject =\n originalProperty &&\n !Array.isArray(originalProperty) &&\n !(originalProperty instanceof Date) &&\n typeof originalProperty === \"object\";\n\n // Recursively wrap nested objects to ensure all nested properties or methods are also traced\n if (isNestedOpenAIObject) {\n return observeOpenAI(originalProperty, config);\n }\n\n // Fallback to returning the original value\n return Reflect.get(wrappedSdk, propKey, proxy);\n },\n }) as SDKType & LangfuseExtension;\n};\n", "import { Langfuse } from \"langfuse\";\nimport type { CompletedTurn, HookConfig, TraceSink } from \"../domain/types.js\";\n\nconst SDK_INTEGRATION = \"zcode-langfuse-observability\";\nconst TRACE_NAME = \"ZCode Turn\";\n\nexport class LangfuseTraceSink implements TraceSink {\n constructor(private readonly config: HookConfig) {}\n\n async publishTurn(turn: CompletedTurn): Promise {\n if (!this.config.publicKey || !this.config.secretKey) return;\n\n const client = new Langfuse({\n publicKey: this.config.publicKey,\n secretKey: this.config.secretKey,\n baseUrl: this.config.baseUrl,\n release: this.config.release,\n environment: this.config.environment,\n sdkIntegration: SDK_INTEGRATION,\n flushAt: 1,\n fetchRetryCount: 1,\n requestTimeout: 8_000,\n });\n\n try {\n type TraceInput = NonNullable[0]>;\n const traceInput: TraceInput = {\n name: TRACE_NAME,\n sessionId: turn.sessionId,\n input: turn.prompt,\n metadata: {\n source: \"zcode\",\n turnId: turn.turnId,\n toolCount: turn.tools.length,\n },\n tags: [\"zcode\", \"zcode-hook\"],\n };\n if (this.config.userId) traceInput.userId = this.config.userId;\n const trace = client.trace(traceInput);\n\n for (const tool of turn.tools) {\n const span = trace.span({\n name: `tool.${tool.name}`,\n input: tool.input,\n metadata: {\n toolId: tool.id,\n startedAt: tool.startedAt,\n endedAt: tool.endedAt,\n },\n });\n if (tool.error) {\n span.end({\n output: { error: tool.error },\n level: \"ERROR\",\n statusMessage: tool.error,\n });\n } else {\n span.end({ output: tool.output });\n }\n }\n\n const generation = trace.generation({\n name: \"zcode.assistant\",\n input: turn.prompt,\n metadata: {\n turnId: turn.turnId,\n },\n });\n generation.end({ output: turn.assistantMessage });\n\n trace.update({\n output: turn.assistantMessage,\n metadata: {\n source: \"zcode\",\n turnId: turn.turnId,\n toolCount: turn.tools.length,\n },\n });\n\n await client.flushAsync();\n } finally {\n await client.shutdownAsync();\n }\n }\n}\n"], - "mappings": "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", - "names": ["randomUUID", "option", "homedir", "join", "sessionId", "clearCache", "parse", "render", "SimpleEventEmitter", "constructor", "events", "on", "event", "listener", "push", "filter", "x", "emit", "payload", "DEFAULT_PROMPT_CACHE_TTL_SECONDS", "LangfusePromptCacheItem", "value", "ttlSeconds", "_expiry", "Date", "now", "isExpired", "LangfusePromptCache", "_cache", "Map", "_defaultTtlSeconds", "_refreshingKeys", "getIncludingExpired", "key", "get", "set", "effectiveTtlSeconds", "addRefreshingPromise", "promise", "then", "delete", "catch", "isRefreshing", "has", "invalidate", "promptName", "console", "log", "keys", "startsWith", "LangfusePersistedProperty", "ChatMessageType", "mustache", "escape", "text", "BasePromptClient", "prompt", "isFallback", "type", "name", "version", "config", "labels", "tags", "commitMessage", "_transformToLangchainVariables", "content", "jsonEscapedContent", "escapeJsonForLangchain", "replace", "out", "stack", "i", "n", "length", "ch", "j", "test", "isJson", "pop", "join", "TextPromptClient", "promptResponse", "compile", "variables", "_placeholders", "render", "getLangchainPrompt", "_options", "toJSON", "JSON", "stringify", "ChatPromptClient", "normalizedPrompt", "normalizePrompt", "typedPrompt", "map", "item", "ChatMessage", "placeholders", "messagesWithPlaceholdersReplaced", "placeholderValues", "Placeholder", "placeholderValue", "Array", "isArray", "every", "msg", "undefined", "role", "options", "variableName", "optional", "_", "messageWithoutType", "assert", "truthyValue", "message", "Error", "removeTrailingSlash", "url", "retriable", "fn", "props", "retryCount", "retryDelay", "retryCheck", "lastError", "Promise", "resolve", "setTimeout", "res", "e", "generateUUID", "globalThis", "d", "getTime", "d2", "performance", "c", "r", "Math", "random", "floor", "toString", "currentTimestamp", "currentISOTime", "toISOString", "safeSetTimeout", "timeout", "t", "unref", "getEnv", "process", "env", "configLangfuseSDK", "params", "secretRequired", "publicKey", "secretKey", "coreOptions", "finalPublicKey", "finalSecretKey", "finalBaseUrl", "baseUrl", "finalCoreOptions", "encodeQueryParams", "queryParams", "URLSearchParams", "Object", "entries", "forEach", "append", "common_release_envs", "getCommonReleaseEnvs", "fs", "cryptoModule", "dynamicImport", "module", "Deno", "all", "importedFs", "importedCrypto", "versions", "node", "crypto", "LangfuseMedia", "obj", "base64DataUri", "contentType", "contentBytes", "filePath", "_mediaId", "contentBytesParsed", "contentTypeParsed", "parseBase64DataUri", "_contentBytes", "_contentType", "_source", "existsSync", "readFile", "error", "readFileSync", "data", "header", "actualData", "slice", "split", "headerParts", "includes", "Buffer", "from", "contentLength", "contentSha256Hash", "sha256Hash", "createHash", "update", "digest", "parseReferenceString", "referenceString", "prefix", "suffix", "endsWith", "pairs", "parsedData", "pair", "mediaId", "source", "resolveMediaReferences", "langfuseClient", "maxDepth", "traverse", "depth", "regex", "referenceStringMatches", "match", "result", "referenceStringToMediaContentMap", "parsedMediaReference", "mediaData", "fetchMedia", "mediaContent", "fetch", "method", "headers", "status", "base64MediaContent", "arrayBuffer", "warn", "replaceAll", "fromEntries", "isInSample", "input", "sampleRate", "isNaN", "simpleHash", "str", "hash", "prime", "charCodeAt", "abs", "MAX_EVENT_SIZE_BYTES", "getEnv", "Number", "MAX_BATCH_SIZE_BYTES", "ENVIRONMENT_PATTERN", "WHITELISTED_LANGFUSE_INTERNAL_ENVIRONMENTS", "LangfuseFetchHttpError", "Error", "constructor", "response", "body", "status", "name", "LangfuseFetchNetworkError", "error", "cause", "isLangfuseFetchHttpError", "isLangfuseFetchNetworkError", "isLangfuseFetchError", "err", "SUPPORT_URL", "API_DOCS_URL", "RBAC_DOCS_URL", "INSTALLATION_DOCS_URL", "RATE_LIMITS_URL", "NPM_PACKAGE_URL", "updatePromptResponse", "defaultServerErrorPrompt", "defaultErrorResponse", "errorResponseByCode", "Map", "getErrorResponseByCode", "code", "errorResponse", "get", "logIngestionError", "console", "LangfuseCoreStateless", "params", "additionalHeaders", "debugMode", "pendingEventProcessingPromises", "pendingIngestionPromises", "localEventExportMap", "_events", "SimpleEventEmitter", "publicKey", "secretKey", "enabled", "_projectId", "_isLocalEventExportEnabled", "options", "on", "payload", "JSON", "stringify", "baseUrl", "removeTrailingSlash", "flushAt", "Math", "max", "flushInterval", "release", "getCommonReleaseEnvs", "undefined", "mask", "sampleRate", "emit", "environment", "test", "includes", "_retryOptions", "retryCount", "fetchRetryCount", "retryDelay", "fetchRetryDelay", "retryCheck", "requestTimeout", "sdkIntegration", "isLocalEventExportEnabled", "projectId", "getSdkIntegration", "getCommonEventProperties", "$lib", "getLibraryId", "$lib_version", "getLibraryVersion", "event", "cb", "debug", "removeDebugCallback", "log", "traceStateless", "id", "bodyId", "timestamp", "bodyTimestamp", "bodyRelease", "rest", "generateUUID", "parsedBody", "Date", "enqueue", "eventStateless", "startTime", "bodyStartTime", "spanStateless", "generationStateless", "prompt", "promptDetails", "isFallback", "promptName", "promptVersion", "version", "scoreStateless", "updateSpanStateless", "updateGenerationStateless", "_getDataset", "encodedName", "encodeURIComponent", "fetchAndLogErrors", "_getFetchOptions", "method", "_getDatasetItems", "query", "URLSearchParams", "Object", "entries", "forEach", "key", "value", "append", "toString", "_fetchMedia", "fetchTraces", "data", "meta", "encodeQueryParams", "fetchTrace", "traceId", "res", "fetchObservations", "fetchObservation", "observationId", "fetchSessions", "getDatasetRun", "encodedDatasetName", "datasetName", "encodedRunName", "runName", "getDatasetRuns", "createDatasetRunItem", "createDataset", "dataset", "createDatasetItem", "getDatasetItem", "_parsePayload", "parse", "createPromptStateless", "updatePromptStateless", "getPromptStateless", "label", "maxRetries", "url", "size", "boundedMaxRetries", "_getBoundedMaxRetries", "defaultMaxRetries", "maxRetriesUpperBound", "retryOptions", "retryLogger", "string", "retriable", "fetch", "fetchTimeout", "catch", "e", "text", "json", "fetchResult", "min", "type", "parseTraceId", "isInSample", "promise", "processEnqueueEvent", "promiseId", "finally", "maskEventBodyInPlace", "processMediaInEvent", "finalEventBody", "truncateEventBody", "queue", "getPersistedProperty", "LangfusePersistedProperty", "Queue", "push", "currentISOTime", "metadata", "setPersistedProperty", "length", "flush", "_flushTimer", "safeSetTimeout", "maskableKeys", "maxByteSize", "bodySize", "getByteSize", "keysToCheck", "keySizes", "map", "sort", "a", "b", "result", "currentSize", "prototype", "hasOwnProperty", "call", "obj", "serialized", "TextEncoder", "encode", "replace", "Promise", "all", "field", "findAndProcessMedia", "seenObjects", "WeakMap", "maxLevels", "processRecursively", "level", "startsWith", "media", "LangfuseMedia", "base64DataUri", "processMediaItem", "has", "set", "Array", "isArray", "item", "input_audio", "audio", "fromEntries", "contentLength", "_contentType", "contentSha256Hash", "_contentBytes", "getUploadUrlBody", "contentType", "sha256Hash", "fetchResponse", "uploadUrlResponse", "uploadUrl", "mediaId", "_mediaId", "now", "uploadResponse", "uploadMediaWithBackoff", "contentBytes", "baseDelay", "patchMediaBody", "uploadedAt", "toISOString", "uploadHttpStatus", "uploadHttpError", "uploadTimeMs", "attempt", "headers", "delay", "pow", "jitter", "random", "resolve", "setTimeout", "flushAsync", "values", "_reject", "callback", "clearTimeout", "items", "splice", "processedItems", "remainingItems", "processQueueItems", "promiseUUID", "done", "batch", "batch_size", "sdk_integration", "sdk_version", "sdk_variant", "public_key", "sdk_name", "fetchOptions", "requestPromise", "fetchWithRetry", "then", "MAX_MSG_SIZE", "BATCH_SIZE_LIMIT", "totalSize", "i", "itemSize", "Blob", "warn", "slice", "p", "constructAuthorizationHeader", "signal", "AbortSignal", "timeout", "Authorization", "encodedCredentials", "btoa", "Buffer", "from", "ms", "ctrl", "AbortController", "abort", "returnBody", "errors", "shutdownAsync", "x", "_exportLocalEvents", "events", "delete", "shutdown", "awaitAllQueuedAndPendingRequests", "LangfuseCore", "LangfuseCoreStateless", "constructor", "params", "publicKey", "secretKey", "enabled", "_isLocalEventExportEnabled", "isObservabilityEnabled", "console", "warn", "_promptCache", "LangfusePromptCache", "trace", "body", "id", "traceStateless", "t", "LangfuseTraceClient", "getEnv", "deferRuntime", "langfuseTraces", "name", "url", "getTraceUrl", "span", "traceId", "spanStateless", "LangfuseSpanClient", "generation", "generationStateless", "LangfuseGenerationClient", "event", "eventStateless", "LangfuseEventClient", "score", "scoreStateless", "getDataset", "options", "dataset", "_getDataset", "items", "page", "itemsResponse", "_getDatasetItems", "datasetName", "limit", "fetchItemsPageSize", "push", "data", "meta", "totalPages", "returnDataset", "description", "undefined", "metadata", "map", "item", "link", "obj", "runName", "runArgs", "awaitAllQueuedAndPendingRequests", "createDatasetRunItem", "datasetItemId", "observationId", "runDescription", "createPrompt", "labels", "promptResponse", "type", "createPromptStateless", "prompt", "ChatMessageType", "Placeholder", "ChatMessage", "isActive", "Set", "ChatPromptClient", "TextPromptClient", "updatePrompt", "newPrompt", "updatePromptStateless", "invalidate", "getPrompt", "version", "cacheKey", "_getPromptCacheKey", "label", "cachedPrompt", "getIncludingExpired", "cacheTtlSeconds", "_fetchPromptAndUpdateCache", "maxRetries", "fetchTimeout", "fetchTimeoutMs", "err", "fallback", "sharedFallbackParams", "config", "tags", "msg", "isExpired", "isRefreshing", "refreshPromptPromise", "catch", "addRefreshingPromise", "value", "parts", "toString", "join", "fetchResult", "getPromptStateless", "Error", "message", "set", "error", "fetchMedia", "_fetchMedia", "resolveMediaReferences", "rest", "LangfuseMedia", "langfuseClient", "_updateSpan", "updateSpanStateless", "_updateGeneration", "updateGenerationStateless", "LangfuseObjectClient", "client", "parentObservationId", "baseUrl", "update", "LangfuseObservationClient", "end", "endTime", "Date", "cookieStore", "getItem", "key", "nameEQ", "ca", "document", "cookie", "split", "i", "length", "c", "charAt", "substring", "indexOf", "decodeURIComponent", "err", "setItem", "value", "cdomain", "expires", "secure", "new_cookie_val", "encodeURIComponent", "removeItem", "name", "clear", "getAllKeys", "keys", "push", "createStorageLike", "store", "localStorage", "checkStoreIsSupported", "storage", "window", "val", "localStore", "undefined", "sessionStore", "createMemoryStorage", "_cache", "getStorage", "type", "_localStore", "_sessionStore", "sessionStorage", "ContentType", "HttpClient", "constructor", "apiConfig", "baseUrl", "securityData", "abortControllers", "Map", "customFetch", "fetchParams", "fetch", "baseApiParams", "credentials", "headers", "redirect", "referrerPolicy", "setSecurityData", "data", "contentFormatters", "Json", "input", "JSON", "stringify", "Text", "FormData", "Object", "reduce", "formData", "property", "append", "Blob", "UrlEncoded", "toQueryString", "createAbortSignal", "cancelToken", "has", "abortController", "get", "signal", "AbortController", "set", "abortRequest", "abort", "delete", "request", "body", "path", "query", "format", "params", "secureParams", "securityWorker", "requestParams", "mergeRequestParams", "queryString", "payloadFormatter", "responseFormat", "then", "response", "r", "clone", "error", "ok", "catch", "e", "assign", "encodeQueryParam", "encodedKey", "addQueryParam", "addArrayQueryParam", "map", "v", "join", "rawQuery", "filter", "Array", "isArray", "addQueryParams", "params1", "params2", "LangfusePublicApi", "api", "annotationQueuesCreateQueueItem", "queueId", "method", "annotationQueuesDeleteQueueItem", "itemId", "annotationQueuesGetQueue", "annotationQueuesGetQueueItem", "annotationQueuesListQueueItems", "annotationQueuesListQueues", "annotationQueuesUpdateQueueItem", "commentsCreate", "commentsGet", "commentsGetById", "commentId", "datasetItemsCreate", "datasetItemsDelete", "id", "datasetItemsGet", "datasetItemsList", "datasetRunItemsCreate", "datasetRunItemsList", "datasetsCreate", "datasetsDeleteRun", "datasetName", "runName", "datasetsGet", "datasetsGetRun", "datasetsGetRuns", "datasetsList", "healthHealth", "ingestionBatch", "mediaGet", "mediaId", "mediaGetUploadUrl", "mediaPatch", "metricsMetrics", "modelsCreate", "modelsDelete", "modelsGet", "modelsList", "observationsGet", "observationId", "observationsGetMany", "organizationsGetOrganizationMemberships", "organizationsGetOrganizationProjects", "organizationsGetProjectMemberships", "projectId", "organizationsUpdateOrganizationMembership", "organizationsUpdateProjectMembership", "projectsCreate", "projectsCreateApiKey", "projectsDelete", "projectsDeleteApiKey", "apiKeyId", "projectsGet", "projectsGetApiKeys", "projectsUpdate", "promptsCreate", "promptsGet", "promptName", "promptsList", "promptVersionUpdate", "version", "scimCreateUser", "scimDeleteUser", "userId", "scimGetResourceTypes", "scimGetSchemas", "scimGetServiceProviderConfig", "scimGetUser", "scimListUsers", "scoreConfigsCreate", "scoreConfigsGet", "scoreConfigsGetById", "configId", "scoreCreate", "scoreDelete", "scoreId", "scoreV2Get", "scoreV2GetById", "sessionsGet", "sessionId", "sessionsList", "traceDelete", "traceId", "traceDeleteMultiple", "traceGet", "traceList", "Langfuse", "LangfuseCore", "langfuseConfig", "utils", "configLangfuseSDK", "_storageKey", "persistence_name", "publicKey", "_storage", "persistence", "getLibraryVersion", "getLibraryId", "sdkIntegration", "additionalHeaders", "constructAuthorizationHeader", "secretKey", "getPersistedProperty", "_storageCache", "parse", "setPersistedProperty", "url", "options", "getCustomUserAgent", "LangfuseSingleton", "getInstance", "params", "instance", "Langfuse", "isRecord", "randomUUID"] -} diff --git a/plugins/langfuse-observability/payload/dist/hooks/hooks.json b/plugins/langfuse-observability/payload/dist/hooks/hooks.json deleted file mode 100644 index 37b12fb..0000000 --- a/plugins/langfuse-observability/payload/dist/hooks/hooks.json +++ /dev/null @@ -1,91 +0,0 @@ -{ - "description": "Langfuse observability hooks for ZCode.", - "hooks": { - "SessionStart": [ - { - "hooks": [ - { - "type": "process", - "command": "node", - "args": [ - "${ZCODE_PLUGIN_ROOT}/dist/hooks/entry.mjs" - ], - "timeoutMs": 5000, - "statusMessage": "Langfuse: preparing session tracing…" - } - ] - } - ], - "UserPromptSubmit": [ - { - "hooks": [ - { - "type": "process", - "command": "node", - "args": [ - "${ZCODE_PLUGIN_ROOT}/dist/hooks/entry.mjs" - ], - "timeoutMs": 5000 - } - ] - } - ], - "PreToolUse": [ - { - "hooks": [ - { - "type": "process", - "command": "node", - "args": [ - "${ZCODE_PLUGIN_ROOT}/dist/hooks/entry.mjs" - ], - "timeoutMs": 5000 - } - ] - } - ], - "PostToolUse": [ - { - "hooks": [ - { - "type": "process", - "command": "node", - "args": [ - "${ZCODE_PLUGIN_ROOT}/dist/hooks/entry.mjs" - ], - "timeoutMs": 5000 - } - ] - } - ], - "PostToolUseFailure": [ - { - "hooks": [ - { - "type": "process", - "command": "node", - "args": [ - "${ZCODE_PLUGIN_ROOT}/dist/hooks/entry.mjs" - ], - "timeoutMs": 5000 - } - ] - } - ], - "Stop": [ - { - "hooks": [ - { - "type": "process", - "command": "node", - "args": [ - "${ZCODE_PLUGIN_ROOT}/dist/hooks/entry.mjs" - ], - "timeoutMs": 20000, - "statusMessage": "Langfuse: sending trace…" - } - ] - } - ] - } -} diff --git a/plugins/langfuse-observability/payload/dist/plugin.json b/plugins/langfuse-observability/payload/dist/plugin.json deleted file mode 100644 index 93829e7..0000000 --- a/plugins/langfuse-observability/payload/dist/plugin.json +++ /dev/null @@ -1,83 +0,0 @@ -{ - "name": "langfuse-observability", - "description": "Fail-open Langfuse tracing for ZCode sessions, prompts, tool calls, and assistant responses.", - "description_i18n": { - "en": "Fail-open Langfuse tracing for ZCode sessions, prompts, tool calls, and assistant responses.", - "zh-CN": "为 ZCode 会话、提示词、工具调用和助手回复提供 fail-open 的 Langfuse 追踪。" - }, - "version": "0.1.0", - "author": { - "name": "Community contributor" - }, - "license": "MIT", - "keywords": [ - "langfuse", - "observability", - "tracing", - "telemetry", - "hooks" - ], - "userConfig": { - "langfuse_public_key": { - "title": "Langfuse public key", - "description": "Langfuse project public key. You can also provide LANGFUSE_PUBLIC_KEY in the ZCode process environment.", - "type": "string", - "required": false - }, - "langfuse_secret_key": { - "title": "Langfuse secret key", - "description": "Langfuse project secret key. It is used only by the local hook process and is never written to hook output.", - "type": "string", - "required": false, - "sensitive": true - }, - "langfuse_base_url": { - "title": "Langfuse base URL", - "description": "Langfuse host, for example https://cloud.langfuse.com or a self-hosted URL.", - "type": "string", - "default": "https://cloud.langfuse.com" - }, - "langfuse_user_id": { - "title": "Langfuse user ID", - "description": "Optional stable user identifier attached to traces. Leave empty to omit it.", - "type": "string", - "required": false - }, - "langfuse_environment": { - "title": "Langfuse environment", - "description": "Environment label attached to traces.", - "type": "string", - "default": "development" - }, - "capture_prompts": { - "title": "Capture prompts", - "description": "Include user prompts and assistant responses in Langfuse. Disable for metadata-only tracing.", - "type": "boolean", - "default": true - }, - "capture_tool_inputs": { - "title": "Capture tool inputs", - "description": "Include tool names and inputs in Langfuse spans.", - "type": "boolean", - "default": true - }, - "capture_tool_outputs": { - "title": "Capture tool outputs", - "description": "Include tool results and errors in Langfuse spans.", - "type": "boolean", - "default": true - }, - "max_capture_chars": { - "title": "Maximum captured characters", - "description": "Maximum characters per captured prompt, tool payload, or response. Larger values are truncated.", - "type": "number", - "default": 20000 - }, - "debug": { - "title": "Debug diagnostics", - "description": "Write non-sensitive lifecycle diagnostics to stderr. Disabled by default.", - "type": "boolean", - "default": false - } - } -} From 60b250d35d4ae0bf7a2f06f451e72d287201cef5 Mon Sep 17 00:00:00 2001 From: erlinerd Date: Sun, 13 Sep 2026 14:11:46 +0800 Subject: [PATCH 05/13] feat: rename plugin to zcode-plugin-langfuse (v0.2.0) Align with the source repository rename (erlinerd/zcode-plugin-langfuse): - plugins/langfuse-observability -> plugins/zcode-plugin-langfuse - plugin name, marketplace entry, and in-code identifiers - version 0.1.1 -> 0.2.0 (plugin id change is breaking for installed copies) --- marketplace.json | 6 +++--- .../.zcode-plugin/plugin.json | 8 ++++---- .../LICENSE | 0 .../README.md | 2 +- .../README_CN.md | 2 +- .../THIRD_PARTY_NOTICES.md | 0 .../hooks/config.mjs | 0 .../hooks/entry.mjs | 2 +- .../hooks/extract.mjs | 0 .../hooks/hooks.json | 0 .../hooks/langfuse.mjs | 2 +- .../hooks/state.mjs | 2 +- .../hooks/tracker.mjs | 0 .../package.json | 4 ++-- 14 files changed, 14 insertions(+), 14 deletions(-) rename plugins/{langfuse-observability => zcode-plugin-langfuse}/.zcode-plugin/plugin.json (93%) rename plugins/{langfuse-observability => zcode-plugin-langfuse}/LICENSE (100%) rename plugins/{langfuse-observability => zcode-plugin-langfuse}/README.md (97%) rename plugins/{langfuse-observability => zcode-plugin-langfuse}/README_CN.md (97%) rename plugins/{langfuse-observability => zcode-plugin-langfuse}/THIRD_PARTY_NOTICES.md (100%) rename plugins/{langfuse-observability => zcode-plugin-langfuse}/hooks/config.mjs (100%) rename plugins/{langfuse-observability => zcode-plugin-langfuse}/hooks/entry.mjs (92%) rename plugins/{langfuse-observability => zcode-plugin-langfuse}/hooks/extract.mjs (100%) rename plugins/{langfuse-observability => zcode-plugin-langfuse}/hooks/hooks.json (100%) rename plugins/{langfuse-observability => zcode-plugin-langfuse}/hooks/langfuse.mjs (98%) rename plugins/{langfuse-observability => zcode-plugin-langfuse}/hooks/state.mjs (99%) rename plugins/{langfuse-observability => zcode-plugin-langfuse}/hooks/tracker.mjs (100%) rename plugins/{langfuse-observability => zcode-plugin-langfuse}/package.json (67%) diff --git a/marketplace.json b/marketplace.json index 66c9807..80ba156 100644 --- a/marketplace.json +++ b/marketplace.json @@ -534,10 +534,10 @@ ] }, { - "name": "langfuse-observability", - "source": "./plugins/langfuse-observability", + "name": "zcode-plugin-langfuse", + "source": "./plugins/zcode-plugin-langfuse", "description": "Fail-open Langfuse tracing for ZCode sessions, prompts, tool calls, and assistant responses.", - "version": "0.1.1", + "version": "0.2.0", "category": "developer-tools", "tags": [ "langfuse", diff --git a/plugins/langfuse-observability/.zcode-plugin/plugin.json b/plugins/zcode-plugin-langfuse/.zcode-plugin/plugin.json similarity index 93% rename from plugins/langfuse-observability/.zcode-plugin/plugin.json rename to plugins/zcode-plugin-langfuse/.zcode-plugin/plugin.json index 74c24b6..1bf4743 100644 --- a/plugins/langfuse-observability/.zcode-plugin/plugin.json +++ b/plugins/zcode-plugin-langfuse/.zcode-plugin/plugin.json @@ -1,11 +1,11 @@ { - "name": "langfuse-observability", + "name": "zcode-plugin-langfuse", "description": "Fail-open Langfuse tracing for ZCode sessions, prompts, tool calls, and assistant responses.", "description_i18n": { "en": "Fail-open Langfuse tracing for ZCode sessions, prompts, tool calls, and assistant responses.", "zh-CN": "为 ZCode 会话、提示词、工具调用和助手回复提供 fail-open 的 Langfuse 追踪。" }, - "version": "0.1.1", + "version": "0.2.0", "author": { "name": "erlinerd", "url": "https://github.com/erlinerd" @@ -81,6 +81,6 @@ "default": false } }, - "homepage": "https://github.com/erlinerd/zcode-langfuse-plugin", - "repository": "https://github.com/erlinerd/zcode-langfuse-plugin" + "homepage": "https://github.com/erlinerd/zcode-plugin-langfuse", + "repository": "https://github.com/erlinerd/zcode-plugin-langfuse" } diff --git a/plugins/langfuse-observability/LICENSE b/plugins/zcode-plugin-langfuse/LICENSE similarity index 100% rename from plugins/langfuse-observability/LICENSE rename to plugins/zcode-plugin-langfuse/LICENSE diff --git a/plugins/langfuse-observability/README.md b/plugins/zcode-plugin-langfuse/README.md similarity index 97% rename from plugins/langfuse-observability/README.md rename to plugins/zcode-plugin-langfuse/README.md index b103727..692da54 100644 --- a/plugins/langfuse-observability/README.md +++ b/plugins/zcode-plugin-langfuse/README.md @@ -71,7 +71,7 @@ falls back to the same Langfuse HTTPS ingestion API through Node's built-in ## Source and license -Source repository: +Source repository: Licensed under MIT. See [`THIRD_PARTY_NOTICES.md`](./THIRD_PARTY_NOTICES.md) for the exact Langfuse SDK, Langfuse Core, and Mustache versions and their MIT diff --git a/plugins/langfuse-observability/README_CN.md b/plugins/zcode-plugin-langfuse/README_CN.md similarity index 97% rename from plugins/langfuse-observability/README_CN.md rename to plugins/zcode-plugin-langfuse/README_CN.md index 7f51a1f..eab6818 100644 --- a/plugins/langfuse-observability/README_CN.md +++ b/plugins/zcode-plugin-langfuse/README_CN.md @@ -62,7 +62,7 @@ API,因此不需要运行时安装步骤。 ## 来源与许可证 -源码仓库: +源码仓库: 本插件采用 MIT 许可证。确切的 Langfuse SDK、Langfuse Core 和 Mustache 版本及 MIT 许可证见 [`THIRD_PARTY_NOTICES.md`](./THIRD_PARTY_NOTICES.md)。架构、测试、 diff --git a/plugins/langfuse-observability/THIRD_PARTY_NOTICES.md b/plugins/zcode-plugin-langfuse/THIRD_PARTY_NOTICES.md similarity index 100% rename from plugins/langfuse-observability/THIRD_PARTY_NOTICES.md rename to plugins/zcode-plugin-langfuse/THIRD_PARTY_NOTICES.md diff --git a/plugins/langfuse-observability/hooks/config.mjs b/plugins/zcode-plugin-langfuse/hooks/config.mjs similarity index 100% rename from plugins/langfuse-observability/hooks/config.mjs rename to plugins/zcode-plugin-langfuse/hooks/config.mjs diff --git a/plugins/langfuse-observability/hooks/entry.mjs b/plugins/zcode-plugin-langfuse/hooks/entry.mjs similarity index 92% rename from plugins/langfuse-observability/hooks/entry.mjs rename to plugins/zcode-plugin-langfuse/hooks/entry.mjs index 3f30a11..7e7c338 100644 --- a/plugins/langfuse-observability/hooks/entry.mjs +++ b/plugins/zcode-plugin-langfuse/hooks/entry.mjs @@ -4,7 +4,7 @@ import { JsonStateStore } from "./state.mjs"; import { NoopTraceSink, TurnTracker, parsePayload } from "./tracker.mjs"; function diagnostics(enabled, message) { - if (enabled) process.stderr.write(`[langfuse-observability] ${message}\n`); + if (enabled) process.stderr.write(`[zcode-plugin-langfuse] ${message}\n`); } let raw = ""; diff --git a/plugins/langfuse-observability/hooks/extract.mjs b/plugins/zcode-plugin-langfuse/hooks/extract.mjs similarity index 100% rename from plugins/langfuse-observability/hooks/extract.mjs rename to plugins/zcode-plugin-langfuse/hooks/extract.mjs diff --git a/plugins/langfuse-observability/hooks/hooks.json b/plugins/zcode-plugin-langfuse/hooks/hooks.json similarity index 100% rename from plugins/langfuse-observability/hooks/hooks.json rename to plugins/zcode-plugin-langfuse/hooks/hooks.json diff --git a/plugins/langfuse-observability/hooks/langfuse.mjs b/plugins/zcode-plugin-langfuse/hooks/langfuse.mjs similarity index 98% rename from plugins/langfuse-observability/hooks/langfuse.mjs rename to plugins/zcode-plugin-langfuse/hooks/langfuse.mjs index 0a43991..8be519e 100644 --- a/plugins/langfuse-observability/hooks/langfuse.mjs +++ b/plugins/zcode-plugin-langfuse/hooks/langfuse.mjs @@ -1,6 +1,6 @@ import { randomUUID } from "node:crypto"; -const SDK_INTEGRATION = "zcode-langfuse-observability"; +const SDK_INTEGRATION = "zcode-plugin-langfuse"; const TRACE_NAME = "ZCode Turn"; function isMissingSdk(error) { diff --git a/plugins/langfuse-observability/hooks/state.mjs b/plugins/zcode-plugin-langfuse/hooks/state.mjs similarity index 99% rename from plugins/langfuse-observability/hooks/state.mjs rename to plugins/zcode-plugin-langfuse/hooks/state.mjs index 860236c..ea3ad4f 100644 --- a/plugins/langfuse-observability/hooks/state.mjs +++ b/plugins/zcode-plugin-langfuse/hooks/state.mjs @@ -107,7 +107,7 @@ function defaultDataDir() { "cli", "plugins", "data", - "langfuse-observability", + "zcode-plugin-langfuse", ); } diff --git a/plugins/langfuse-observability/hooks/tracker.mjs b/plugins/zcode-plugin-langfuse/hooks/tracker.mjs similarity index 100% rename from plugins/langfuse-observability/hooks/tracker.mjs rename to plugins/zcode-plugin-langfuse/hooks/tracker.mjs diff --git a/plugins/langfuse-observability/package.json b/plugins/zcode-plugin-langfuse/package.json similarity index 67% rename from plugins/langfuse-observability/package.json rename to plugins/zcode-plugin-langfuse/package.json index 28bb4da..bad2c38 100644 --- a/plugins/langfuse-observability/package.json +++ b/plugins/zcode-plugin-langfuse/package.json @@ -1,6 +1,6 @@ { - "name": "zcode-langfuse-observability", - "version": "0.1.1", + "name": "zcode-plugin-langfuse", + "version": "0.2.0", "private": true, "type": "module", "engines": { From be4555945b3cfb447b4fdaa23753171376670494 Mon Sep 17 00:00:00 2001 From: erlinerd Date: Sun, 13 Sep 2026 19:08:08 +0800 Subject: [PATCH 06/13] chore(catalog): sync zcode-plugin-langfuse v0.2.0 --- marketplace.json | 1 + .../.zcode-plugin/plugin.json | 7 +- plugins/zcode-plugin-langfuse/README.md | 233 ++++++++++++++---- plugins/zcode-plugin-langfuse/README_CN.md | 154 +++++++++--- .../THIRD_PARTY_NOTICES.md | 6 +- .../zcode-plugin-langfuse/hooks/config.mjs | 175 ------------- plugins/zcode-plugin-langfuse/hooks/entry.mjs | 33 --- .../zcode-plugin-langfuse/hooks/extract.mjs | 104 -------- .../zcode-plugin-langfuse/hooks/hooks.json | 12 +- .../zcode-plugin-langfuse/hooks/langfuse.mjs | 220 ----------------- plugins/zcode-plugin-langfuse/hooks/state.mjs | 186 -------------- .../zcode-plugin-langfuse/hooks/tracker.mjs | 232 ----------------- plugins/zcode-plugin-langfuse/package.json | 7 +- 13 files changed, 325 insertions(+), 1045 deletions(-) delete mode 100644 plugins/zcode-plugin-langfuse/hooks/config.mjs delete mode 100644 plugins/zcode-plugin-langfuse/hooks/entry.mjs delete mode 100644 plugins/zcode-plugin-langfuse/hooks/extract.mjs delete mode 100644 plugins/zcode-plugin-langfuse/hooks/langfuse.mjs delete mode 100644 plugins/zcode-plugin-langfuse/hooks/state.mjs delete mode 100644 plugins/zcode-plugin-langfuse/hooks/tracker.mjs diff --git a/marketplace.json b/marketplace.json index 80ba156..07c01b1 100644 --- a/marketplace.json +++ b/marketplace.json @@ -541,6 +541,7 @@ "category": "developer-tools", "tags": [ "langfuse", + "observability", "tracing", "telemetry", "hooks" diff --git a/plugins/zcode-plugin-langfuse/.zcode-plugin/plugin.json b/plugins/zcode-plugin-langfuse/.zcode-plugin/plugin.json index 1bf4743..2f565a3 100644 --- a/plugins/zcode-plugin-langfuse/.zcode-plugin/plugin.json +++ b/plugins/zcode-plugin-langfuse/.zcode-plugin/plugin.json @@ -7,8 +7,7 @@ }, "version": "0.2.0", "author": { - "name": "erlinerd", - "url": "https://github.com/erlinerd" + "name": "Community contributor" }, "license": "MIT", "keywords": [ @@ -80,7 +79,5 @@ "type": "boolean", "default": false } - }, - "homepage": "https://github.com/erlinerd/zcode-plugin-langfuse", - "repository": "https://github.com/erlinerd/zcode-plugin-langfuse" + } } diff --git a/plugins/zcode-plugin-langfuse/README.md b/plugins/zcode-plugin-langfuse/README.md index 692da54..e07c172 100644 --- a/plugins/zcode-plugin-langfuse/README.md +++ b/plugins/zcode-plugin-langfuse/README.md @@ -1,43 +1,87 @@ # Langfuse Observability for ZCode -[中文文档](./README_CN.md) +[English](README.md) · [简体中文](README.zh-CN.md) -A fail-open ZCode plugin that sends one Langfuse trace named `ZCode Turn` for -each completed turn. Tool calls are spans and the assistant response is a -generation. +A community ZCode plugin that sends one Langfuse trace per completed ZCode turn. +It is designed for review and possible inclusion in the ZCode official plugin +marketplace. -## Behavior +> **Status:** early community contribution (`0.1.1`). The plugin is fail-open: +> a missing credential, malformed hook payload, local state error, or Langfuse +> request error must never block a ZCode session. -The plugin listens to `SessionStart`, `UserPromptSubmit`, `PreToolUse`, -`PostToolUse`, `PostToolUseFailure`, and `Stop`. It publishes only at `Stop`. -It reads only fields delivered on ZCode Hook stdin. It never reads transcript -files and never collects hidden chain-of-thought. +## What it records -Missing credentials, malformed Hook input, local-state errors, and Langfuse -errors are fail-open and must not block ZCode. Session state is stored under -`ZCODE_PLUGIN_DATA` when available, otherwise under the ZCode plugin data -fallback, and is cleaned up after a completed `Stop`. +The plugin listens to ZCode's process-hook events: + +- `SessionStart` +- `UserPromptSubmit` +- `PreToolUse` +- `PostToolUse` +- `PostToolUseFailure` +- `Stop` + +At `Stop`, it emits a trace named `ZCode Turn` containing: + +- the ZCode session ID; +- the user prompt and final assistant message, when prompt capture is enabled; +- tool calls as Langfuse spans, including names and optional input/output; +- an assistant response generation; +- release, environment, turn ID, and tool-count metadata. + +It does **not** read the transcript file or collect hidden chain-of-thought. It +only uses fields delivered in the ZCode hook payload. + +## Permissions and side effects Each of the six events starts a `node` process with the current user's permissions. The process reads one JSON Hook event from stdin and writes one empty JSON object to stdout; it does not spawn a shell or run user commands. -It reads `ZCODE_CONFIG_PATH`, or `~/.zcode/cli/config.json` when unset, to find -persisted options, writes only bounded hashed JSON session state, and sends -HTTPS requests to the configured Langfuse ingestion endpoint at `Stop`. -## Privacy and configuration +The hook reads `ZCODE_CONFIG_PATH`, or +`~/.zcode/cli/config.json` when that variable is unset, to find persisted plugin +options. It writes only bounded, hashed JSON session state under +`ZCODE_PLUGIN_DATA`, or the ZCode plugin data directory fallback. At `Stop`, +it sends HTTPS requests to the configured Langfuse ingestion endpoint using the +local credentials. No transcript files or hidden reasoning are read. -Content capture is bounded and controlled independently for prompts, tool input, -and tool output. Set these environment variables to disable content capture: +## Architecture ```text -LANGFUSE_CAPTURE_PROMPTS=false -LANGFUSE_CAPTURE_TOOL_INPUTS=false -LANGFUSE_CAPTURE_TOOL_OUTPUTS=false +ZCode hook stdin + │ one JSON object + ▼ + hooks/entry.mjs ──► TurnTracker ──► JsonStateStore + │ │ + │ └─ per-session, atomic, hashed filename + ▼ + LangfuseTraceSink ──► bundled official `langfuse` SDK + │ + ▼ + Langfuse ingestion API +``` + +The source is deliberately split into deep modules: + +- `src/domain/` — hook and trace data types plus payload extraction; +- `src/application/` — configuration and the turn state machine; +- `src/adapters/` — the filesystem state adapter and Langfuse SDK adapter; +- `src/hooks/` — the small process entry point and fail-open policy. + +The distributable `dist/hooks/entry.mjs` bundles the official JavaScript SDK, so +an installed plugin does not need a separate `npm install` at runtime. + +## Configuration + +The plugin reads ZCode `userConfig` values using the standard ZCode environment +mapping. For example, the manifest key `langfuse_public_key` becomes: + +```text +ZCODE_USER_CONFIG_LANGFUSE_PUBLIC_KEY ``` -Configuration precedence is process environment, persisted ZCode plugin options, -then defaults. Supported configuration includes: +The following ordinary environment variables are also accepted, which is useful +for local smoke tests and managed deployments: ```text LANGFUSE_PUBLIC_KEY @@ -54,26 +98,131 @@ LANGFUSE_MAX_CAPTURE_CHARS LANGFUSE_DEBUG ``` -`LANGFUSE_BASE_URL` defaults to `https://cloud.langfuse.com`. It must be an -HTTPS URL; plaintext HTTP is rejected and replaced with the default HTTPS -endpoint. The plugin sends telemetry only to that configured Langfuse endpoint -and writes only its bounded local session state. Never commit credentials or -private Hook payloads. +`LANGFUSE_BASE_URL` defaults to `https://cloud.langfuse.com`. Set it to your +self-hosted HTTPS URL, for example `https://langfuse.example.com`. Plain HTTP +URLs are rejected and replaced with the default HTTPS endpoint. + +For a self-hosted project, configure the public and secret keys in the plugin +configuration. The hook reads its own persisted `plugins.options` entry using +`ZCODE_PLUGIN_ID`; this is needed because current ZCode runtimes do not inject +all `userConfig` values into process-hook environment variables. Standard +`LANGFUSE_*` environment variables override stored options. Never commit +credentials or put them in `hooks/hooks.json`. + +### Privacy controls + +All capture controls default to `true` for useful traces. Set any of these to +`false` to keep the corresponding content out of both the Langfuse request and +local per-session state: + +```text +LANGFUSE_CAPTURE_PROMPTS=false +LANGFUSE_CAPTURE_TOOL_INPUTS=false +LANGFUSE_CAPTURE_TOOL_OUTPUTS=false +``` + +Every captured field is bounded by `LANGFUSE_MAX_CAPTURE_CHARS` (default +`20000`). Metadata-only mode still reports timing/session/tool-count structure, +but not prompt, response, tool input, tool output, or error text. + +## Development + +Requirements: Node.js 20 or newer. + +```bash +npm ci +npm run check +npm run package:plugin +``` + +`npm run build` creates the local `dist/` bundle. `npm run package:plugin` is the +default distribution build: it runs the bundle build once, validates it, and +creates both the ignored release files and the catalog-ready plugin layout: + +```text +artifacts/plugin.zip +artifacts/plugin.zip.sha256 +artifacts/plugin-layout/plugins/zcode-plugin-langfuse/ +artifacts/plugin-layout/marketplace-entry.json +``` + +The ZIP and plugin layout use the same generated bundle. The ZIP contains the +ZCode manifest, Hook declaration, bundled SDK, and third-party notices. Neither +`dist/` nor `artifacts/` is committed. + +The hook can be smoke-tested without credentials: + +```bash +printf '%s\n' '{"hook_event_name":"Stop","session_id":"smoke","last_assistant_message":"ok"}' \ + | ZCODE_PLUGIN_DATA="$(mktemp -d)" \ + LANGFUSE_DEBUG=true \ + node dist/hooks/entry.mjs +``` + +Expected stdout is one empty hook result object: + +```json +{} +``` + +Diagnostics, when enabled, go to stderr. They never contain keys or full +prompts. + +## Third-party software + +The runtime uses `langfuse` 3.38.20, `langfuse-core` 3.38.20, and `mustache` +4.2.0. Each dependency is MIT-licensed; see +[THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md) for versions, provenance, and +source links. Langfuse is an external service selected by the user and is not +bundled with credentials. + +## Local installation for testing + +ZCode treats a selected directory as a **plugin marketplace**, so this repository +includes `marketplace.json` at its root. Build first, then use **Settings → +Plugins → Add marketplace → Select directory** and choose this repository. +Install `zcode-plugin-langfuse` from the resulting personal marketplace, enable +it, and configure its `userConfig` values. + +The plugin manifest is at `.zcode-plugin/plugin.json`; the hook declaration is +at `hooks/hooks.json`. + +## Online ZCode marketplace + +Add this repository in **Settings → Plugins → Create → Add marketplace** using +its GitHub repository URL. ZCode accepts a GitHub repository or its URL as a +marketplace source. + +- Community marketplace repository: +- Marketplace manifest: +- Plugin manifest: +- Latest plugin ZIP: +- Latest ZIP checksum: +- Release page: +- Official ZCode plugin documentation: +- Official ZCode plugin marketplace: + +The repository marketplace is named `zcode-plugin-langfuse`, and its +`marketplace.json` resolves the plugin from `source: "."`. Versioned release +assets are generated by the tagged GitHub Actions workflow. + +Before submitting a marketplace change, verify: -## Files and dependencies +1. `npm run check` passes; +2. `npm run package:plugin` creates and validates the release ZIP; +3. `hooks/hooks.json` contains no unsupported hook events; +4. no credentials are present in the repository, package, or logs; +5. disabling prompt/tool capture behaves as documented. -The process Hook is declared in [`hooks/hooks.json`](./hooks/hooks.json) and -runs the reviewable source runtime at `hooks/entry.mjs`. When the optional -`langfuse` dependency is installed during local development, the runtime uses -the official JavaScript SDK. The published official cache is self-contained and -falls back to the same Langfuse HTTPS ingestion API through Node's built-in -`fetch`, so no install step is required. +## Project policies -## Source and license +- [Agent instructions](AGENTS.md) +- [Contributing](CONTRIBUTING.md) +- [Code of Conduct](CODE_OF_CONDUCT.md) +- [Security policy](SECURITY.md) +- [Release checklist](docs/releasing.md) +- [Design notes](DESIGN.md) -Source repository: +## License -Licensed under MIT. See [`THIRD_PARTY_NOTICES.md`](./THIRD_PARTY_NOTICES.md) -for the exact Langfuse SDK, Langfuse Core, and Mustache versions and their MIT -licenses. See the source repository for architecture, tests, release checks, -and the complete development history. +MIT. See [LICENSE](LICENSE). diff --git a/plugins/zcode-plugin-langfuse/README_CN.md b/plugins/zcode-plugin-langfuse/README_CN.md index eab6818..eaad77b 100644 --- a/plugins/zcode-plugin-langfuse/README_CN.md +++ b/plugins/zcode-plugin-langfuse/README_CN.md @@ -1,38 +1,50 @@ -# ZCode Langfuse 观测插件 +# ZCode Langfuse Observability -[English](./README.md) +[English](README.md) · [简体中文](README.zh-CN.md) -这是一个 fail-open 的 ZCode 插件:每个完成的 turn 发送一条名为 -`ZCode Turn` 的 Langfuse trace。工具调用记录为 span,助手回复记录为 -generation。 +面向 ZCode 的社区 Langfuse 观测插件,按每个完成的 ZCode turn 生成一条 +Langfuse trace,目标是提交给 ZCode 官方插件市场。 -## 行为 +> 当前版本:`0.1.1`。插件遵循 **fail-open**:缺少凭据、Hook 输入损坏、本地 +> 状态错误或 Langfuse 请求失败,都不能阻塞 ZCode 会话。 -插件监听 `SessionStart`、`UserPromptSubmit`、`PreToolUse`、`PostToolUse`、 -`PostToolUseFailure` 和 `Stop`,只在 `Stop` 时发布 trace。插件只读取 -ZCode Hook stdin 传入的字段,不读取 transcript 文件,也不采集隐藏完整思维链。 +## 采集范围 -缺少凭据、Hook 输入损坏、本地状态错误和 Langfuse 错误都采用 fail-open, -不能阻塞 ZCode。可用时,会在 `ZCODE_PLUGIN_DATA` 下保存会话状态,否则使用 -ZCode 插件数据回退路径,并在完成 `Stop` 后清理。 +监听 `SessionStart`、`UserPromptSubmit`、`PreToolUse`、`PostToolUse`、 +`PostToolUseFailure` 和 `Stop`。在 `Stop` 时发送: + +- session ID; +- 用户 prompt 与最后一条 assistant 回复(可关闭); +- 工具调用 Langfuse span(名称、输入、输出可分别关闭); +- assistant generation; +- release、environment、turn ID 和工具数量元数据。 + +插件**不读取 transcript 文件,也不采集隐藏完整思维链**,只使用 ZCode +Hook stdin 传入的字段。 + +## 权限与副作用 六个事件都会以当前用户权限启动一个 `node` process Hook。Hook 从 stdin 读取一个 JSON 事件,并向 stdout 写入一个空 JSON 对象;不会启动 shell,也不会 -执行用户命令。Hook 会读取 `ZCODE_CONFIG_PATH`;未设置时读取 -`~/.zcode/cli/config.json` 中保存的插件选项,只写入有界、哈希化的 JSON 会话 -状态,并在 `Stop` 时向配置的 Langfuse HTTPS 接口发送请求。 +执行用户命令。 -## 隐私与配置 +Hook 会读取 `ZCODE_CONFIG_PATH` 指定的配置;未设置时读取 +`~/.zcode/cli/config.json` 中保存的插件选项。它只会在 +`ZCODE_PLUGIN_DATA` 或 ZCode 插件数据目录回退路径下写入有界、哈希化的 JSON +会话状态。`Stop` 时使用本地凭据向用户配置的 Langfuse HTTPS 接口发送请求。 +不会读取 transcript 文件或隐藏推理内容。 -内容采集有大小限制,并分别控制 prompt、工具输入和工具输出。关闭内容采集: +## 配置 + +Manifest 中的 `userConfig` 会映射成以下环境变量,例如: ```text -LANGFUSE_CAPTURE_PROMPTS=false -LANGFUSE_CAPTURE_TOOL_INPUTS=false -LANGFUSE_CAPTURE_TOOL_OUTPUTS=false +ZCODE_USER_CONFIG_LANGFUSE_PUBLIC_KEY +ZCODE_USER_CONFIG_LANGFUSE_SECRET_KEY +ZCODE_USER_CONFIG_LANGFUSE_BASE_URL ``` -配置优先级为进程环境变量、持久化的 ZCode 插件选项、默认值。支持的配置包括: +同时支持标准变量: ```text LANGFUSE_PUBLIC_KEY @@ -49,21 +61,95 @@ LANGFUSE_MAX_CAPTURE_CHARS LANGFUSE_DEBUG ``` -`LANGFUSE_BASE_URL` 默认是 `https://cloud.langfuse.com`,必须使用 HTTPS;明文 -HTTP 会被拒绝并回退到默认 HTTPS 地址。插件只向配置的 Langfuse endpoint 发送 -观测数据,只写入有大小限制的本地会话状态。不要提交凭据或私有 Hook payload。 +`LANGFUSE_BASE_URL` 默认 `https://cloud.langfuse.com`;自建 Langfuse 请填写 +HTTPS 地址,例如 `https://langfuse.example.com`。明文 HTTP 地址会被拒绝并回退到 +默认 HTTPS 地址。 + +敏感 secret 不要写入仓库或 `hooks/hooks.json`。Hook 会根据运行时提供的 +`ZCODE_PLUGIN_ID` 从 ZCode 的 `plugins.options` 读取本插件配置;这是因为当前 +ZCode 不会把全部 `userConfig` 自动注入 process Hook 环境。标准 +`LANGFUSE_*` 环境变量优先级更高,可覆盖保存的配置。 + +关闭内容采集: + +```text +LANGFUSE_CAPTURE_PROMPTS=false +LANGFUSE_CAPTURE_TOOL_INPUTS=false +LANGFUSE_CAPTURE_TOOL_OUTPUTS=false +``` + +关闭后,内容不会写入 Langfuse,也不会写入本地会话状态;仍保留 session、 +工具数量等结构化元数据。单字段默认最多采集 20000 个字符,可用 +`LANGFUSE_MAX_CAPTURE_CHARS` 调整。 + +## 第三方软件 + +运行时使用 `langfuse` 3.38.20、`langfuse-core` 3.38.20 和 `mustache` 4.2.0, +均为 MIT 许可证。版本、来源和许可证见 +[THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md)。Langfuse 是用户选择的外部服务, +仓库不会打包凭据。 + +## 在 ZCode 中本地测试 + +ZCode 选择本地目录时会按“插件市场”读取,因此仓库根目录包含 +`marketplace.json`。先运行 `npm run build`,再在 **设置 → 插件 → 添加插件市场 → +选择目录** 中选择本仓库,从“个人”市场安装并启用 +`zcode-plugin-langfuse`。正式市场使用版本化 ZIP 和 SHA-256。 + +## 在线 ZCode 插件市场 + +在 **设置 → 插件 → 创建 → 添加插件市场** 中,使用 GitHub 仓库地址添加: + +- 社区市场仓库: +- 市场清单: +- 插件清单: +- 最新插件 ZIP: +- 最新 ZIP 校验文件: +- Release 页面: +- ZCode 官方插件文档: +- ZCode 官方插件市场: + +本仓库市场名为 `zcode-plugin-langfuse`,`marketplace.json` 使用 +`source: "."` 从仓库根目录解析插件。版本化发行文件由 GitHub Actions 的 tag +workflow 生成。 + +## 开发与构建 + +需要 Node.js 20+: + +```bash +npm ci +npm run check +npm run package:plugin +``` + +`npm run build` 会把官方 `langfuse` JavaScript SDK 打包进本地 `dist/`。 +`npm run package:plugin` 是默认的分发构建:只执行一次 bundle 构建,完成校验, +并同时生成发行文件和可同步到插件市场的布局: + +```text +artifacts/plugin.zip +artifacts/plugin.zip.sha256 +artifacts/plugin-layout/plugins/zcode-plugin-langfuse/ +artifacts/plugin-layout/marketplace-entry.json +``` + +ZIP 和 plugin layout 使用同一个生成 bundle。ZIP 包含 ZCode manifest、Hook 声明、 +SDK bundle 和第三方声明。`dist/` 与 `artifacts/` 都是生成目录,不提交到源码仓库。 -## 文件与依赖 +目录分层: -进程 Hook 声明在 [`hooks/hooks.json`](./hooks/hooks.json),运行可审查的源代码 -`hooks/entry.mjs`。本地开发安装可选的 `langfuse` 依赖时会使用官方 JavaScript -SDK;官方缓存中的插件使用 Node 内置 `fetch` 调用相同的 Langfuse HTTPS ingestion -API,因此不需要运行时安装步骤。 +- `src/domain/`:Hook 与 trace 数据类型、输入解析; +- `src/application/`:配置与 turn 状态机; +- `src/adapters/`:本地状态和 Langfuse SDK 适配器; +- `src/hooks/`:很薄的进程入口和 fail-open 策略。 -## 来源与许可证 +更多设计约束见 [DESIGN.md](DESIGN.md)。 -源码仓库: +项目规范: -本插件采用 MIT 许可证。确切的 Langfuse SDK、Langfuse Core 和 Mustache 版本及 -MIT 许可证见 [`THIRD_PARTY_NOTICES.md`](./THIRD_PARTY_NOTICES.md)。架构、测试、 -发布检查和完整开发历史见源码仓库。 +- [Agent 指令](AGENTS.md) +- [贡献指南](CONTRIBUTING.md) +- [行为准则](CODE_OF_CONDUCT.md) +- [安全策略](SECURITY.md) +- [发布清单](docs/releasing.md) diff --git a/plugins/zcode-plugin-langfuse/THIRD_PARTY_NOTICES.md b/plugins/zcode-plugin-langfuse/THIRD_PARTY_NOTICES.md index 8fb5125..e228fc0 100644 --- a/plugins/zcode-plugin-langfuse/THIRD_PARTY_NOTICES.md +++ b/plugins/zcode-plugin-langfuse/THIRD_PARTY_NOTICES.md @@ -1,9 +1,7 @@ # Third-party notices -The source runtime can use the following official npm packages when the plugin -is developed with dependencies installed. The self-contained official cache -fallback uses the same Langfuse public ingestion API through Node's built-in -`fetch`, so the published plugin does not require an install step. +The runtime uses the following npm packages. Versions are pinned by +`package-lock.json` and their licenses remain with the respective projects: | Package | Version | License | Source | | --- | --- | --- | --- | diff --git a/plugins/zcode-plugin-langfuse/hooks/config.mjs b/plugins/zcode-plugin-langfuse/hooks/config.mjs deleted file mode 100644 index 3bd6899..0000000 --- a/plugins/zcode-plugin-langfuse/hooks/config.mjs +++ /dev/null @@ -1,175 +0,0 @@ -import { readFileSync } from "node:fs"; -import { homedir } from "node:os"; -import { join } from "node:path"; - -const DEFAULT_BASE_URL = "https://cloud.langfuse.com"; -const DEFAULT_RELEASE = "0.1.1"; -const DEFAULT_MAX_CAPTURE_CHARS = 20_000; - -function asText(value) { - if (value === undefined) return undefined; - return typeof value === "string" ? value : String(value); -} - -function firstNonEmpty(...values) { - return values - .map(asText) - .find((value) => value !== undefined && value.trim().length > 0) - ?.trim(); -} - -function userConfig(env, name) { - const normalized = name.toUpperCase(); - return firstNonEmpty( - env[`ZCODE_USER_CONFIG_${normalized}`], - env[`ZCODE_PLUGIN_CONFIG_${normalized}`], - ); -} - -function isStoredOption(value) { - return ( - typeof value === "string" || - typeof value === "number" || - typeof value === "boolean" - ); -} - -function parseStoredOptions(value) { - if (typeof value !== "object" || value === null || Array.isArray(value)) - return {}; - const options = {}; - for (const [key, option] of Object.entries(value)) { - if (isStoredOption(option)) options[key] = option; - } - return options; -} - -function readStoredOptions(env) { - const configPaths = [ - env.ZCODE_CONFIG_PATH, - join(homedir(), ".zcode", "cli", "config.json"), - ].filter(Boolean); - const configuredPluginId = env.ZCODE_PLUGIN_ID; - - if (!configuredPluginId) return {}; - - for (const configPath of configPaths) { - try { - const parsed = JSON.parse(readFileSync(configPath, "utf8")); - const plugins = parsed?.plugins; - const options = plugins?.options; - if ( - !options || - typeof options !== "object" || - Array.isArray(options) || - !Object.prototype.hasOwnProperty.call(options, configuredPluginId) - ) { - continue; - } - return parseStoredOptions(options[configuredPluginId]); - } catch { - // A missing or unreadable user config must not block a ZCode hook. - } - } - return {}; -} - -function option(env, storedOptions, name, environmentName) { - return firstNonEmpty( - userConfig(env, name), - env[environmentName], - storedOptions[name], - ); -} - -function parseBoolean(value, fallback) { - if (!value) return fallback; - if (["1", "true", "yes", "on"].includes(value.toLowerCase())) return true; - if (["0", "false", "no", "off"].includes(value.toLowerCase())) return false; - return fallback; -} - -function parsePositiveInteger(value, fallback) { - if (!value) return fallback; - const parsed = Number.parseInt(value, 10); - if (!Number.isFinite(parsed) || parsed <= 0) return fallback; - return Math.min(parsed, 1_000_000); -} - -function normalizeBaseUrl(value) { - const candidate = value ?? DEFAULT_BASE_URL; - try { - const url = new URL(candidate); - if (url.protocol !== "https:") return DEFAULT_BASE_URL; - return candidate.replace(/\/+$/, ""); - } catch { - return DEFAULT_BASE_URL; - } -} - -export function readConfig(env = process.env) { - const storedOptions = readStoredOptions(env); - const enabled = parseBoolean( - option(env, storedOptions, "enabled", "LANGFUSE_ENABLED"), - true, - ); - const publicKey = - option(env, storedOptions, "langfuse_public_key", "LANGFUSE_PUBLIC_KEY") ?? - null; - const secretKey = - option(env, storedOptions, "langfuse_secret_key", "LANGFUSE_SECRET_KEY") ?? - null; - - return { - publicKey, - secretKey, - baseUrl: normalizeBaseUrl( - option(env, storedOptions, "langfuse_base_url", "LANGFUSE_BASE_URL"), - ), - userId: - option(env, storedOptions, "langfuse_user_id", "LANGFUSE_USER_ID") ?? null, - environment: - option(env, storedOptions, "langfuse_environment", "LANGFUSE_ENVIRONMENT") ?? - "development", - release: - option(env, storedOptions, "langfuse_release", "LANGFUSE_RELEASE") ?? - DEFAULT_RELEASE, - enabled, - capturePrompts: parseBoolean( - option(env, storedOptions, "capture_prompts", "LANGFUSE_CAPTURE_PROMPTS"), - true, - ), - captureToolInputs: parseBoolean( - option( - env, - storedOptions, - "capture_tool_inputs", - "LANGFUSE_CAPTURE_TOOL_INPUTS", - ), - true, - ), - captureToolOutputs: parseBoolean( - option( - env, - storedOptions, - "capture_tool_outputs", - "LANGFUSE_CAPTURE_TOOL_OUTPUTS", - ), - true, - ), - maxCaptureChars: parsePositiveInteger( - option( - env, - storedOptions, - "max_capture_chars", - "LANGFUSE_MAX_CAPTURE_CHARS", - ), - DEFAULT_MAX_CAPTURE_CHARS, - ), - debug: parseBoolean(option(env, storedOptions, "debug", "LANGFUSE_DEBUG"), false), - }; -} - -export function isConfigured(config) { - return config.enabled && Boolean(config.publicKey && config.secretKey); -} diff --git a/plugins/zcode-plugin-langfuse/hooks/entry.mjs b/plugins/zcode-plugin-langfuse/hooks/entry.mjs deleted file mode 100644 index 7e7c338..0000000 --- a/plugins/zcode-plugin-langfuse/hooks/entry.mjs +++ /dev/null @@ -1,33 +0,0 @@ -import { readConfig, isConfigured } from "./config.mjs"; -import { LangfuseTraceSink } from "./langfuse.mjs"; -import { JsonStateStore } from "./state.mjs"; -import { NoopTraceSink, TurnTracker, parsePayload } from "./tracker.mjs"; - -function diagnostics(enabled, message) { - if (enabled) process.stderr.write(`[zcode-plugin-langfuse] ${message}\n`); -} - -let raw = ""; -process.stdin.setEncoding("utf8"); -for await (const chunk of process.stdin) raw += chunk; - -const config = readConfig(); -const payload = parsePayload(raw); -const event = payload.hook_event_name || payload.hookEventName || "unknown"; -const session = payload.session_id || payload.sessionId || "?"; - -diagnostics(config.debug, `event=${event} session=${session}`); - -const sink = isConfigured(config) - ? new LangfuseTraceSink(config) - : new NoopTraceSink(); -const tracker = new TurnTracker(new JsonStateStore(), sink, config); - -try { - await tracker.handle(payload); -} catch (error) { - const kind = error instanceof Error ? error.name : "unknown"; - diagnostics(config.debug, `hook failed open (${kind})`); -} - -process.stdout.write("{}\n"); diff --git a/plugins/zcode-plugin-langfuse/hooks/extract.mjs b/plugins/zcode-plugin-langfuse/hooks/extract.mjs deleted file mode 100644 index b057f71..0000000 --- a/plugins/zcode-plugin-langfuse/hooks/extract.mjs +++ /dev/null @@ -1,104 +0,0 @@ -function toJsonValue(value) { - if ( - value === null || - typeof value === "string" || - typeof value === "number" || - typeof value === "boolean" - ) { - return value; - } - if (Array.isArray(value)) { - const values = value.map(toJsonValue); - return values.every((item) => item !== undefined) ? values : undefined; - } - if (typeof value === "object") { - const result = {}; - for (const [key, item] of Object.entries(value)) { - const parsed = toJsonValue(item); - if (parsed === undefined) return undefined; - result[key] = parsed; - } - return result; - } - return undefined; -} - -function valueAt(payload, ...keys) { - for (const key of keys) { - const value = toJsonValue(payload[key]); - if (value !== undefined && value !== null) return value; - } - return undefined; -} - -function asString(value) { - if (typeof value === "string") return value; - if (typeof value === "number" || typeof value === "boolean") return String(value); - return null; -} - -export function stringifyValue(value) { - if (typeof value === "string") return value; - if (value === undefined) return ""; - const json = JSON.stringify(value); - return json ?? String(value); -} - -export function eventName(payload) { - return asString( - valueAt(payload, "hook_event_name", "hookEventName", "event", "event_name"), - ); -} - -export function sessionId(payload) { - const value = asString(valueAt(payload, "session_id", "sessionId")); - return value?.trim() || null; -} - -export function prompt(payload) { - const value = valueAt(payload, "prompt", "user_prompt", "userPrompt", "message"); - return value === undefined ? null : stringifyValue(value); -} - -export function assistantMessage(payload) { - const value = valueAt(payload, "last_assistant_message", "lastAssistantMessage"); - return value === undefined ? null : stringifyValue(value); -} - -export function toolId(payload) { - const value = asString( - valueAt(payload, "tool_use_id", "toolUseId", "tool_id", "toolId", "id"), - ); - return value?.trim() || null; -} - -export function toolName(payload) { - return ( - asString(valueAt(payload, "tool_name", "toolName", "name", "tool"))?.trim() ?? - "unknown" - ); -} - -export function toolInput(payload) { - return valueAt(payload, "tool_input", "toolInput", "input", "arguments") ?? null; -} - -export function toolOutput(payload) { - return ( - valueAt( - payload, - "tool_output", - "toolOutput", - "output", - "result", - "tool_response", - ) ?? null - ); -} - -export function toolError(payload) { - return stringifyValue( - valueAt(payload, "error", "error_message", "errorMessage", "message") ?? - "Tool failed", - ); -} diff --git a/plugins/zcode-plugin-langfuse/hooks/hooks.json b/plugins/zcode-plugin-langfuse/hooks/hooks.json index ab20687..37b12fb 100644 --- a/plugins/zcode-plugin-langfuse/hooks/hooks.json +++ b/plugins/zcode-plugin-langfuse/hooks/hooks.json @@ -8,7 +8,7 @@ "type": "process", "command": "node", "args": [ - "${ZCODE_PLUGIN_ROOT}/hooks/entry.mjs" + "${ZCODE_PLUGIN_ROOT}/dist/hooks/entry.mjs" ], "timeoutMs": 5000, "statusMessage": "Langfuse: preparing session tracing…" @@ -23,7 +23,7 @@ "type": "process", "command": "node", "args": [ - "${ZCODE_PLUGIN_ROOT}/hooks/entry.mjs" + "${ZCODE_PLUGIN_ROOT}/dist/hooks/entry.mjs" ], "timeoutMs": 5000 } @@ -37,7 +37,7 @@ "type": "process", "command": "node", "args": [ - "${ZCODE_PLUGIN_ROOT}/hooks/entry.mjs" + "${ZCODE_PLUGIN_ROOT}/dist/hooks/entry.mjs" ], "timeoutMs": 5000 } @@ -51,7 +51,7 @@ "type": "process", "command": "node", "args": [ - "${ZCODE_PLUGIN_ROOT}/hooks/entry.mjs" + "${ZCODE_PLUGIN_ROOT}/dist/hooks/entry.mjs" ], "timeoutMs": 5000 } @@ -65,7 +65,7 @@ "type": "process", "command": "node", "args": [ - "${ZCODE_PLUGIN_ROOT}/hooks/entry.mjs" + "${ZCODE_PLUGIN_ROOT}/dist/hooks/entry.mjs" ], "timeoutMs": 5000 } @@ -79,7 +79,7 @@ "type": "process", "command": "node", "args": [ - "${ZCODE_PLUGIN_ROOT}/hooks/entry.mjs" + "${ZCODE_PLUGIN_ROOT}/dist/hooks/entry.mjs" ], "timeoutMs": 20000, "statusMessage": "Langfuse: sending trace…" diff --git a/plugins/zcode-plugin-langfuse/hooks/langfuse.mjs b/plugins/zcode-plugin-langfuse/hooks/langfuse.mjs deleted file mode 100644 index 8be519e..0000000 --- a/plugins/zcode-plugin-langfuse/hooks/langfuse.mjs +++ /dev/null @@ -1,220 +0,0 @@ -import { randomUUID } from "node:crypto"; - -const SDK_INTEGRATION = "zcode-plugin-langfuse"; -const TRACE_NAME = "ZCode Turn"; - -function isMissingSdk(error) { - return ( - error?.code === "ERR_MODULE_NOT_FOUND" || - (error instanceof Error && error.message.includes("Cannot find package 'langfuse'")) - ); -} - -async function publishWithOfficialSdk(config, turn) { - let Langfuse; - try { - ({ Langfuse } = await import("langfuse")); - } catch (error) { - if (isMissingSdk(error)) return false; - throw error; - } - - const client = new Langfuse({ - publicKey: config.publicKey, - secretKey: config.secretKey, - baseUrl: config.baseUrl, - release: config.release, - environment: config.environment, - sdkIntegration: SDK_INTEGRATION, - flushAt: 1, - fetchRetryCount: 1, - requestTimeout: 8_000, - }); - - try { - const trace = client.trace({ - name: TRACE_NAME, - sessionId: turn.sessionId, - input: turn.prompt, - metadata: { - source: "zcode", - turnId: turn.turnId, - toolCount: turn.tools.length, - }, - tags: ["zcode", "zcode-hook"], - ...(config.userId ? { userId: config.userId } : {}), - }); - - for (const tool of turn.tools) { - const span = trace.span({ - name: `tool.${tool.name}`, - input: tool.input, - metadata: { - toolId: tool.id, - startedAt: tool.startedAt, - endedAt: tool.endedAt, - }, - }); - if (tool.error) { - span.end({ - output: { error: tool.error }, - level: "ERROR", - statusMessage: tool.error, - }); - } else { - span.end({ output: tool.output }); - } - } - - const generation = trace.generation({ - name: "zcode.assistant", - input: turn.prompt, - metadata: { turnId: turn.turnId }, - }); - generation.end({ output: turn.assistantMessage }); - trace.update({ - output: turn.assistantMessage, - metadata: { - source: "zcode", - turnId: turn.turnId, - toolCount: turn.tools.length, - }, - }); - await client.flushAsync(); - } finally { - await client.shutdownAsync(); - } - return true; -} - -function queued(type, body, timestamp) { - return { - id: randomUUID(), - type, - timestamp, - body, - }; -} - -async function publishWithHttp(config, turn) { - const timestamp = new Date().toISOString(); - const traceId = randomUUID(); - const metadata = { - source: "zcode", - turnId: turn.turnId, - toolCount: turn.tools.length, - }; - const batch = [ - queued( - "trace-create", - { - id: traceId, - name: TRACE_NAME, - sessionId: turn.sessionId, - timestamp, - release: config.release, - environment: config.environment, - input: turn.prompt, - metadata, - tags: ["zcode", "zcode-hook"], - ...(config.userId ? { userId: config.userId } : {}), - }, - timestamp, - ), - ]; - - for (const tool of turn.tools) { - const startTime = tool.startedAt || timestamp; - const endTime = tool.endedAt || timestamp; - batch.push( - queued( - "span-create", - { - id: randomUUID(), - traceId, - name: `tool.${tool.name}`, - startTime, - endTime, - input: tool.input, - output: tool.error ? { error: tool.error } : tool.output, - metadata: { - toolId: tool.id, - startedAt: tool.startedAt, - endedAt: tool.endedAt, - }, - ...(tool.error - ? { level: "ERROR", statusMessage: tool.error } - : {}), - }, - timestamp, - ), - ); - } - - batch.push( - queued( - "generation-create", - { - id: randomUUID(), - traceId, - name: "zcode.assistant", - startTime: turn.startedAt || timestamp, - endTime: turn.endedAt || timestamp, - input: turn.prompt, - output: turn.assistantMessage, - environment: config.environment, - metadata: { turnId: turn.turnId }, - }, - timestamp, - ), - queued( - "trace-update", - { - id: traceId, - output: turn.assistantMessage, - metadata, - }, - timestamp, - ), - ); - - const response = await fetch(`${config.baseUrl}/api/public/ingestion`, { - method: "POST", - headers: { - Authorization: `Basic ${Buffer.from( - `${config.publicKey}:${config.secretKey}`, - ).toString("base64")}`, - "Content-Type": "application/json", - "X-Langfuse-Sdk-Name": "langfuse-js-compatible", - "X-Langfuse-Sdk-Integration": SDK_INTEGRATION, - "X-Langfuse-Public-Key": config.publicKey, - }, - body: JSON.stringify({ - batch, - metadata: { - batch_size: batch.length, - sdk_integration: SDK_INTEGRATION, - sdk_name: "langfuse-js-compatible", - public_key: config.publicKey, - }, - }), - signal: AbortSignal.timeout(8_000), - }); - - if (!response.ok) { - throw new Error(`Langfuse ingestion returned HTTP ${response.status}`); - } -} - -export class LangfuseTraceSink { - constructor(config) { - this.config = config; - } - - async publishTurn(turn) { - if (!this.config.publicKey || !this.config.secretKey) return; - if (!(await publishWithOfficialSdk(this.config, turn))) { - await publishWithHttp(this.config, turn); - } - } -} diff --git a/plugins/zcode-plugin-langfuse/hooks/state.mjs b/plugins/zcode-plugin-langfuse/hooks/state.mjs deleted file mode 100644 index ea3ad4f..0000000 --- a/plugins/zcode-plugin-langfuse/hooks/state.mjs +++ /dev/null @@ -1,186 +0,0 @@ -import { createHash, randomUUID } from "node:crypto"; -import { - mkdir, - open, - readFile, - rename, - rm, - stat, - writeFile, -} from "node:fs/promises"; -import { homedir, tmpdir } from "node:os"; -import { join } from "node:path"; - -const LOCK_WAIT_MS = 25; -const LOCK_ATTEMPTS = 160; -const STALE_LOCK_MS = 60_000; - -function isRecord(value) { - return typeof value === "object" && value !== null && !Array.isArray(value); -} - -function isJsonValue(value) { - if ( - value === null || - typeof value === "string" || - typeof value === "number" || - typeof value === "boolean" - ) { - return true; - } - if (Array.isArray(value)) return value.every(isJsonValue); - return isRecord(value) && Object.values(value).every(isJsonValue); -} - -function parseToolCall(value) { - if (!isRecord(value)) return null; - if ( - typeof value.id !== "string" || - typeof value.name !== "string" || - !isJsonValue(value.input) || - (value.output !== null && !isJsonValue(value.output)) || - (value.error !== null && typeof value.error !== "string") || - typeof value.startedAt !== "string" || - (value.endedAt !== null && typeof value.endedAt !== "string") - ) { - return null; - } - return { - id: value.id, - name: value.name, - input: value.input, - output: value.output, - error: value.error, - startedAt: value.startedAt, - endedAt: value.endedAt, - }; -} - -function parseState(value) { - if (!isRecord(value)) return null; - if ( - value.version !== 1 || - typeof value.sessionId !== "string" || - typeof value.startedAt !== "string" || - typeof value.updatedAt !== "string" - ) { - return null; - } - let currentTurn = null; - if (value.currentTurn !== null) { - const turn = value.currentTurn; - if ( - !isRecord(turn) || - typeof turn.id !== "string" || - (turn.prompt !== null && typeof turn.prompt !== "string") || - typeof turn.startedAt !== "string" || - !Array.isArray(turn.tools) - ) { - return null; - } - const tools = turn.tools.map(parseToolCall); - if (tools.some((tool) => tool === null)) return null; - currentTurn = { - id: turn.id, - prompt: turn.prompt, - startedAt: turn.startedAt, - tools, - }; - } - return { - version: 1, - sessionId: value.sessionId, - startedAt: value.startedAt, - updatedAt: value.updatedAt, - currentTurn, - }; -} - -function sessionKey(sessionId) { - return createHash("sha256").update(sessionId).digest("hex"); -} - -function defaultDataDir() { - return join( - homedir() || tmpdir(), - ".zcode", - "cli", - "plugins", - "data", - "zcode-plugin-langfuse", - ); -} - -export class JsonStateStore { - constructor(dataDir = process.env.ZCODE_PLUGIN_DATA || defaultDataDir()) { - this.dataDir = dataDir; - } - - async load(sessionId) { - try { - const contents = await readFile(this.statePath(sessionId), "utf8"); - return parseState(JSON.parse(contents)); - } catch { - return null; - } - } - - async save(state) { - await mkdir(this.dataDir, { recursive: true, mode: 0o700 }); - const filePath = this.statePath(state.sessionId); - const temporaryPath = `${filePath}.${randomUUID()}.tmp`; - await writeFile(temporaryPath, JSON.stringify(state), { - encoding: "utf8", - mode: 0o600, - }); - await rename(temporaryPath, filePath); - } - - async clear(sessionId) { - await rm(this.statePath(sessionId), { force: true }); - } - - async withSessionLock(sessionId, task) { - await mkdir(this.dataDir, { recursive: true, mode: 0o700 }); - const lockPath = this.lockPath(sessionId); - let acquired = false; - - for (let attempt = 0; attempt < LOCK_ATTEMPTS; attempt += 1) { - try { - const handle = await open(lockPath, "wx", 0o600); - await handle.close(); - acquired = true; - break; - } catch { - await this.removeStaleLock(lockPath); - await new Promise((resolve) => setTimeout(resolve, LOCK_WAIT_MS)); - } - } - - if (!acquired) throw new Error("Timed out acquiring the Langfuse state lock"); - - try { - return await task(); - } finally { - await rm(lockPath, { force: true }); - } - } - - statePath(sessionId) { - return join(this.dataDir, `${sessionKey(sessionId)}.json`); - } - - lockPath(sessionId) { - return join(this.dataDir, `${sessionKey(sessionId)}.lock`); - } - - async removeStaleLock(lockPath) { - try { - const details = await stat(lockPath); - if (Date.now() - details.mtimeMs > STALE_LOCK_MS) - await rm(lockPath, { force: true }); - } catch { - // The lock may disappear between open(), stat(), and rm(). - } - } -} diff --git a/plugins/zcode-plugin-langfuse/hooks/tracker.mjs b/plugins/zcode-plugin-langfuse/hooks/tracker.mjs deleted file mode 100644 index 176d74d..0000000 --- a/plugins/zcode-plugin-langfuse/hooks/tracker.mjs +++ /dev/null @@ -1,232 +0,0 @@ -import { randomUUID } from "node:crypto"; -import { - assistantMessage, - eventName, - prompt, - sessionId, - toolError, - toolId, - toolInput, - toolName, - toolOutput, -} from "./extract.mjs"; - -function createSession(session, now) { - return { - version: 1, - sessionId: session, - startedAt: now, - updatedAt: now, - currentTurn: null, - }; -} - -function createTurn(id, now, userPrompt) { - return { - id, - prompt: userPrompt, - startedAt: now, - tools: [], - }; -} - -const TRUNCATION_MARKER = "… [truncated]"; - -function truncate(value, maxChars) { - if (value.length <= maxChars) return value; - if (maxChars <= TRUNCATION_MARKER.length) return value.slice(0, maxChars); - return `${value.slice(0, maxChars - TRUNCATION_MARKER.length)}${TRUNCATION_MARKER}`; -} - -function boundedValue(value, maxChars) { - const serialized = JSON.stringify(value); - if (serialized.length <= maxChars) return value; - return truncate(serialized, maxChars); -} - -function sanitizeTurn(turn, config) { - return { - ...turn, - prompt: - config.capturePrompts && turn.prompt !== null - ? truncate(turn.prompt, config.maxCaptureChars) - : null, - assistantMessage: - config.capturePrompts && turn.assistantMessage !== null - ? truncate(turn.assistantMessage, config.maxCaptureChars) - : null, - tools: turn.tools.map((tool) => ({ - ...tool, - name: truncate(tool.name, 256), - input: config.captureToolInputs - ? boundedValue(tool.input, config.maxCaptureChars) - : null, - output: - config.captureToolOutputs && tool.output !== null - ? boundedValue(tool.output, config.maxCaptureChars) - : null, - error: - config.captureToolOutputs && tool.error !== null - ? truncate(tool.error, config.maxCaptureChars) - : null, - })), - }; -} - -function findTool(turn, id, name) { - if (id) { - const byId = turn.tools.find((tool) => tool.id === id); - if (byId) return byId; - } - return ( - [...turn.tools].reverse().find((tool) => tool.endedAt === null && tool.name === name) ?? - [...turn.tools].reverse().find((tool) => tool.endedAt === null) ?? - null - ); -} - -export class TurnTracker { - constructor( - stateStore, - traceSink, - config, - clock = () => new Date(), - idGenerator = randomUUID, - ) { - this.stateStore = stateStore; - this.traceSink = traceSink; - this.config = config; - this.clock = clock; - this.idGenerator = idGenerator; - } - - async handle(payload) { - const name = eventName(payload); - const session = sessionId(payload); - if (!name || !session) return; - - let completed = null; - await this.stateStore.withSessionLock(session, async () => { - const now = this.clock().toISOString(); - const existing = - (await this.stateStore.load(session)) ?? createSession(session, now); - - switch (name) { - case "SessionStart": - existing.updatedAt = now; - await this.stateStore.save(existing); - return; - case "UserPromptSubmit": { - const userPrompt = prompt(payload); - existing.currentTurn = createTurn( - this.idGenerator(), - now, - this.config.capturePrompts && userPrompt !== null - ? truncate(userPrompt, this.config.maxCaptureChars) - : null, - ); - existing.updatedAt = now; - await this.stateStore.save(existing); - return; - } - case "PreToolUse": - this.recordToolStart(existing, payload, now); - await this.stateStore.save(existing); - return; - case "PostToolUse": - this.recordToolOutput(existing, payload, now, false); - await this.stateStore.save(existing); - return; - case "PostToolUseFailure": - this.recordToolOutput(existing, payload, now, true); - await this.stateStore.save(existing); - return; - case "Stop": - completed = sanitizeTurn( - { - sessionId: session, - turnId: existing.currentTurn?.id ?? this.idGenerator(), - prompt: existing.currentTurn?.prompt ?? null, - assistantMessage: assistantMessage(payload), - startedAt: existing.currentTurn?.startedAt ?? now, - endedAt: now, - tools: existing.currentTurn?.tools ?? [], - }, - this.config, - ); - await this.stateStore.clear(session); - return; - default: - return; - } - }); - - if (completed) await this.traceSink.publishTurn(completed); - } - - recordToolStart(state, payload, now) { - const turn = - state.currentTurn ?? createTurn(this.idGenerator(), now, null); - state.currentTurn = turn; - turn.tools.push({ - id: toolId(payload) ?? this.idGenerator(), - name: toolName(payload), - input: this.config.captureToolInputs - ? boundedValue(toolInput(payload), this.config.maxCaptureChars) - : null, - output: null, - error: null, - startedAt: now, - endedAt: null, - }); - state.updatedAt = now; - } - - recordToolOutput(state, payload, now, failed) { - const turn = - state.currentTurn ?? createTurn(this.idGenerator(), now, null); - state.currentTurn = turn; - const name = toolName(payload); - const tool = findTool(turn, toolId(payload), name); - const output = - !failed && this.config.captureToolOutputs - ? boundedValue(toolOutput(payload), this.config.maxCaptureChars) - : null; - const error = - failed && this.config.captureToolOutputs - ? truncate(toolError(payload), this.config.maxCaptureChars) - : null; - if (tool) { - tool.output = output; - tool.error = error; - tool.endedAt = now; - } else { - turn.tools.push({ - id: toolId(payload) ?? this.idGenerator(), - name, - input: null, - output, - error, - startedAt: now, - endedAt: now, - }); - } - state.updatedAt = now; - } -} - -export class NoopTraceSink { - async publishTurn(_turn) {} -} - -export function parsePayload(raw) { - if (!raw.trim()) return {}; - try { - const parsed = JSON.parse(raw); - return parsed && typeof parsed === "object" && !Array.isArray(parsed) - ? parsed - : {}; - } catch { - return {}; - } -} diff --git a/plugins/zcode-plugin-langfuse/package.json b/plugins/zcode-plugin-langfuse/package.json index bad2c38..48e0d45 100644 --- a/plugins/zcode-plugin-langfuse/package.json +++ b/plugins/zcode-plugin-langfuse/package.json @@ -1,12 +1,11 @@ { "name": "zcode-plugin-langfuse", "version": "0.2.0", - "private": true, + "description": "Fail-open Langfuse tracing for ZCode sessions, prompts, tool calls, and assistant responses.", + "license": "MIT", "type": "module", + "private": true, "engines": { "node": ">=20" - }, - "dependencies": { - "langfuse": "3.38.20" } } From 7d139b6b360a78414c295978e56fdb5fbaea90f1 Mon Sep 17 00:00:00 2001 From: erlinerd Date: Sun, 13 Sep 2026 19:14:40 +0800 Subject: [PATCH 07/13] chore(catalog): sync zcode-plugin-langfuse v0.2.0 --- .../dist/hooks/entry.mjs | 4754 +++++++++++++++++ 1 file changed, 4754 insertions(+) create mode 100644 plugins/zcode-plugin-langfuse/dist/hooks/entry.mjs diff --git a/plugins/zcode-plugin-langfuse/dist/hooks/entry.mjs b/plugins/zcode-plugin-langfuse/dist/hooks/entry.mjs new file mode 100644 index 0000000..f10d506 --- /dev/null +++ b/plugins/zcode-plugin-langfuse/dist/hooks/entry.mjs @@ -0,0 +1,4754 @@ +// Generated by npm run build. Edit src/, not dist/. + +// src/hooks/entry.ts +import { randomUUID as randomUUID2 } from "node:crypto"; + +// src/application/config.ts +import { readFileSync } from "node:fs"; +import { homedir } from "node:os"; +import { join } from "node:path"; +var DEFAULT_BASE_URL = "https://cloud.langfuse.com"; +var DEFAULT_RELEASE = "0.1.1"; +var DEFAULT_MAX_CAPTURE_CHARS = 2e4; +function asText(value) { + if (value === void 0) return void 0; + return typeof value === "string" ? value : String(value); +} +function firstNonEmpty(...values) { + return values.map(asText).find((value) => value !== void 0 && value.trim().length > 0)?.trim(); +} +function userConfig(env, name) { + const normalized = name.toUpperCase(); + return firstNonEmpty( + env[`ZCODE_USER_CONFIG_${normalized}`], + env[`ZCODE_PLUGIN_CONFIG_${normalized}`] + ); +} +function isStoredOption(value) { + return typeof value === "string" || typeof value === "number" || typeof value === "boolean"; +} +function parseStoredOptions(value) { + if (typeof value !== "object" || value === null || Array.isArray(value)) + return {}; + const options = {}; + for (const [key, option2] of Object.entries(value)) { + if (isStoredOption(option2)) options[key] = option2; + } + return options; +} +function readStoredOptions(env) { + const configPaths = [ + env.ZCODE_CONFIG_PATH, + join(homedir(), ".zcode", "cli", "config.json") + ].filter((path) => Boolean(path)); + const configuredPluginId = env.ZCODE_PLUGIN_ID; + for (const configPath of configPaths) { + try { + const parsed = JSON.parse(readFileSync(configPath, "utf8")); + if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) + continue; + const plugins = parsed.plugins; + if (typeof plugins !== "object" || plugins === null || Array.isArray(plugins)) + continue; + const options = plugins.options; + if (typeof options !== "object" || options === null || Array.isArray(options)) + continue; + const optionEntries = options; + if (configuredPluginId && optionEntries[configuredPluginId]) { + return parseStoredOptions(optionEntries[configuredPluginId]); + } + } catch { + } + } + return {}; +} +function option(env, storedOptions, name, environmentName) { + return firstNonEmpty( + userConfig(env, name), + env[environmentName], + storedOptions[name] + ); +} +function parseBoolean(value, fallback) { + if (!value) return fallback; + if (["1", "true", "yes", "on"].includes(value.toLowerCase())) return true; + if (["0", "false", "no", "off"].includes(value.toLowerCase())) return false; + return fallback; +} +function parsePositiveInteger(value, fallback) { + if (!value) return fallback; + const parsed = Number.parseInt(value, 10); + if (!Number.isFinite(parsed) || parsed <= 0) return fallback; + return Math.min(parsed, 1e6); +} +function normalizeBaseUrl(value) { + const candidate = value ?? DEFAULT_BASE_URL; + try { + const url = new URL(candidate); + if (url.protocol !== "https:") return DEFAULT_BASE_URL; + return candidate.replace(/\/+$/, ""); + } catch { + return DEFAULT_BASE_URL; + } +} +function readConfig(env = process.env, storedOptions = readStoredOptions(env)) { + const enabled = parseBoolean( + option(env, storedOptions, "enabled", "LANGFUSE_ENABLED"), + true + ); + const publicKey = option(env, storedOptions, "langfuse_public_key", "LANGFUSE_PUBLIC_KEY") ?? null; + const secretKey = option(env, storedOptions, "langfuse_secret_key", "LANGFUSE_SECRET_KEY") ?? null; + return { + publicKey, + secretKey, + baseUrl: normalizeBaseUrl( + option(env, storedOptions, "langfuse_base_url", "LANGFUSE_BASE_URL") + ), + userId: option(env, storedOptions, "langfuse_user_id", "LANGFUSE_USER_ID") ?? null, + environment: option( + env, + storedOptions, + "langfuse_environment", + "LANGFUSE_ENVIRONMENT" + ) ?? "development", + release: option(env, storedOptions, "langfuse_release", "LANGFUSE_RELEASE") ?? DEFAULT_RELEASE, + enabled, + capturePrompts: parseBoolean( + option(env, storedOptions, "capture_prompts", "LANGFUSE_CAPTURE_PROMPTS"), + true + ), + captureToolInputs: parseBoolean( + option( + env, + storedOptions, + "capture_tool_inputs", + "LANGFUSE_CAPTURE_TOOL_INPUTS" + ), + true + ), + captureToolOutputs: parseBoolean( + option( + env, + storedOptions, + "capture_tool_outputs", + "LANGFUSE_CAPTURE_TOOL_OUTPUTS" + ), + true + ), + maxCaptureChars: parsePositiveInteger( + option( + env, + storedOptions, + "max_capture_chars", + "LANGFUSE_MAX_CAPTURE_CHARS" + ), + DEFAULT_MAX_CAPTURE_CHARS + ), + debug: parseBoolean( + option(env, storedOptions, "debug", "LANGFUSE_DEBUG"), + false + ) + }; +} +function isConfigured(config) { + return config.enabled && Boolean(config.publicKey && config.secretKey); +} + +// src/domain/extract.ts +function toJsonValue(value) { + if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") { + return value; + } + if (Array.isArray(value)) { + const values = value.map(toJsonValue); + return values.every((item) => item !== void 0) ? values : void 0; + } + if (typeof value === "object") { + const entries = Object.entries(value); + const result = {}; + for (const [key, item] of entries) { + const parsed = toJsonValue(item); + if (parsed === void 0) return void 0; + result[key] = parsed; + } + return result; + } + return void 0; +} +function valueAt(payload, ...keys) { + for (const key of keys) { + const value = toJsonValue(payload[key]); + if (value !== void 0 && value !== null) return value; + } + return void 0; +} +function asString(value) { + if (typeof value === "string") return value; + if (typeof value === "number" || typeof value === "boolean") + return String(value); + return null; +} +function stringifyValue(value) { + if (typeof value === "string") return value; + if (value === void 0) return ""; + const json = JSON.stringify(value); + return json ?? String(value); +} +function eventName(payload) { + return asString( + valueAt(payload, "hook_event_name", "hookEventName", "event", "event_name") + ); +} +function sessionId(payload) { + const value = asString(valueAt(payload, "session_id", "sessionId")); + return value?.trim() || null; +} +function prompt(payload) { + const value = valueAt( + payload, + "prompt", + "user_prompt", + "userPrompt", + "message" + ); + return value === void 0 ? null : stringifyValue(value); +} +function assistantMessage(payload) { + const value = valueAt( + payload, + "last_assistant_message", + "lastAssistantMessage" + ); + return value === void 0 ? null : stringifyValue(value); +} +function toolId(payload) { + const value = asString( + valueAt(payload, "tool_use_id", "toolUseId", "tool_id", "toolId", "id") + ); + return value?.trim() || null; +} +function toolName(payload) { + return asString( + valueAt(payload, "tool_name", "toolName", "name", "tool") + )?.trim() ?? "unknown"; +} +function toolInput(payload) { + return valueAt(payload, "tool_input", "toolInput", "input", "arguments") ?? null; +} +function toolOutput(payload) { + return valueAt( + payload, + "tool_output", + "toolOutput", + "output", + "result", + "tool_response" + ) ?? null; +} +function toolError(payload) { + return stringifyValue( + valueAt(payload, "error", "error_message", "errorMessage", "message") ?? "Tool failed" + ); +} + +// src/application/turn-tracker.ts +function createSession(session, now) { + return { + version: 1, + sessionId: session, + startedAt: now, + updatedAt: now, + currentTurn: null + }; +} +function createTurn(id, now, userPrompt) { + return { + id, + prompt: userPrompt, + startedAt: now, + tools: [] + }; +} +var TRUNCATION_MARKER = "\u2026 [truncated]"; +function truncate(value, maxChars) { + if (value.length <= maxChars) return value; + if (maxChars <= TRUNCATION_MARKER.length) return value.slice(0, maxChars); + return `${value.slice(0, maxChars - TRUNCATION_MARKER.length)}${TRUNCATION_MARKER}`; +} +function boundedValue(value, maxChars) { + const serialized = JSON.stringify(value); + if (serialized.length <= maxChars) return value; + return truncate(serialized, maxChars); +} +function sanitizeTurn(turn, config) { + return { + ...turn, + prompt: config.capturePrompts && turn.prompt !== null ? truncate(turn.prompt, config.maxCaptureChars) : null, + assistantMessage: config.capturePrompts && turn.assistantMessage !== null ? truncate(turn.assistantMessage, config.maxCaptureChars) : null, + tools: turn.tools.map((tool) => ({ + ...tool, + name: truncate(tool.name, 256), + input: config.captureToolInputs ? boundedValue(tool.input, config.maxCaptureChars) : null, + output: config.captureToolOutputs && tool.output !== null ? boundedValue(tool.output, config.maxCaptureChars) : null, + error: config.captureToolOutputs && tool.error !== null ? truncate(tool.error, config.maxCaptureChars) : null + })) + }; +} +function findTool(turn, id, name) { + if (id) { + const byId = turn.tools.find((tool) => tool.id === id); + if (byId) return byId; + } + return [...turn.tools].reverse().find((tool) => tool.endedAt === null && tool.name === name) ?? [...turn.tools].reverse().find((tool) => tool.endedAt === null) ?? null; +} +var TurnTracker = class { + constructor(stateStore, traceSink, config, clock, idGenerator) { + this.stateStore = stateStore; + this.traceSink = traceSink; + this.config = config; + this.clock = clock; + this.idGenerator = idGenerator; + } + stateStore; + traceSink; + config; + clock; + idGenerator; + async handle(payload) { + const name = eventName(payload); + const session = sessionId(payload); + if (!name || !session) return; + let completed = null; + await this.stateStore.withSessionLock(session, async () => { + const now = this.clock.now().toISOString(); + const existing = await this.stateStore.load(session) ?? createSession(session, now); + switch (name) { + case "SessionStart": + existing.updatedAt = now; + await this.stateStore.save(existing); + return; + case "UserPromptSubmit": { + const userPrompt = prompt(payload); + existing.currentTurn = createTurn( + this.idGenerator.next(), + now, + this.config.capturePrompts && userPrompt !== null ? truncate(userPrompt, this.config.maxCaptureChars) : null + ); + existing.updatedAt = now; + await this.stateStore.save(existing); + return; + } + case "PreToolUse": + this.recordToolStart(existing, payload, now); + await this.stateStore.save(existing); + return; + case "PostToolUse": + this.recordToolOutput(existing, payload, now, false); + await this.stateStore.save(existing); + return; + case "PostToolUseFailure": + this.recordToolOutput(existing, payload, now, true); + await this.stateStore.save(existing); + return; + case "Stop": + completed = sanitizeTurn( + { + sessionId: session, + turnId: existing.currentTurn?.id ?? this.idGenerator.next(), + prompt: existing.currentTurn?.prompt ?? null, + assistantMessage: assistantMessage(payload), + startedAt: existing.currentTurn?.startedAt ?? now, + endedAt: now, + tools: existing.currentTurn?.tools ?? [] + }, + this.config + ); + await this.stateStore.clear(session); + return; + default: + return; + } + }); + if (completed) await this.traceSink.publishTurn(completed); + } + recordToolStart(state, payload, now) { + const turn = state.currentTurn ?? createTurn(this.idGenerator.next(), now, null); + state.currentTurn = turn; + turn.tools.push({ + id: toolId(payload) ?? this.idGenerator.next(), + name: toolName(payload), + input: this.config.captureToolInputs ? boundedValue(toolInput(payload), this.config.maxCaptureChars) : null, + output: null, + error: null, + startedAt: now, + endedAt: null + }); + state.updatedAt = now; + } + recordToolOutput(state, payload, now, failed) { + const turn = state.currentTurn ?? createTurn(this.idGenerator.next(), now, null); + state.currentTurn = turn; + const name = toolName(payload); + const tool = findTool(turn, toolId(payload), name); + const output = !failed && this.config.captureToolOutputs ? boundedValue(toolOutput(payload), this.config.maxCaptureChars) : null; + const error = failed && this.config.captureToolOutputs ? truncate(toolError(payload), this.config.maxCaptureChars) : null; + if (tool) { + tool.output = output; + tool.error = error; + tool.endedAt = now; + } else { + turn.tools.push({ + id: toolId(payload) ?? this.idGenerator.next(), + name, + input: null, + output, + error, + startedAt: now, + endedAt: now + }); + } + state.updatedAt = now; + } +}; +var NoopTraceSink = class { + async publishTurn(_turn) { + } +}; + +// src/adapters/json-state-store.ts +import { createHash, randomUUID } from "node:crypto"; +import { + mkdir, + open, + readFile, + rename, + rm, + stat, + writeFile +} from "node:fs/promises"; +import { homedir as homedir2, tmpdir } from "node:os"; +import { join as join2 } from "node:path"; + +// src/domain/identity.ts +var PLUGIN_ID = "zcode-plugin-langfuse"; + +// src/adapters/json-state-store.ts +var LOCK_WAIT_MS = 25; +var LOCK_ATTEMPTS = 160; +var STALE_LOCK_MS = 6e4; +function isRecord(value) { + return typeof value === "object" && value !== null && !Array.isArray(value); +} +function isJsonValue(value) { + if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") { + return true; + } + if (Array.isArray(value)) return value.every(isJsonValue); + return isRecord(value) && Object.values(value).every(isJsonValue); +} +function parseToolCall(value) { + if (!isRecord(value)) return null; + if (typeof value.id !== "string" || typeof value.name !== "string" || !isJsonValue(value.input) || value.output !== null && !isJsonValue(value.output) || value.error !== null && typeof value.error !== "string" || typeof value.startedAt !== "string" || value.endedAt !== null && typeof value.endedAt !== "string") { + return null; + } + return { + id: value.id, + name: value.name, + input: value.input, + output: value.output, + error: value.error, + startedAt: value.startedAt, + endedAt: value.endedAt + }; +} +function parseTurn(value) { + if (!isRecord(value)) return null; + if (typeof value.id !== "string" || value.prompt !== null && typeof value.prompt !== "string" || typeof value.startedAt !== "string" || !Array.isArray(value.tools)) { + return null; + } + const tools = value.tools.map(parseToolCall); + if (tools.some((tool) => tool === null)) return null; + return { + id: value.id, + prompt: value.prompt, + startedAt: value.startedAt, + tools: tools.filter((tool) => tool !== null) + }; +} +function parseState(value) { + if (!isRecord(value)) return null; + if (value.version !== 1 || typeof value.sessionId !== "string" || typeof value.startedAt !== "string" || typeof value.updatedAt !== "string") { + return null; + } + const currentTurn = value.currentTurn === null ? null : parseTurn(value.currentTurn); + if (value.currentTurn !== null && currentTurn === null) return null; + return { + version: 1, + sessionId: value.sessionId, + startedAt: value.startedAt, + updatedAt: value.updatedAt, + currentTurn + }; +} +function sessionKey(sessionId2) { + return createHash("sha256").update(sessionId2).digest("hex"); +} +function defaultDataDir() { + return join2( + homedir2() || tmpdir(), + ".zcode", + "cli", + "plugins", + "data", + PLUGIN_ID + ); +} +var JsonStateStore = class { + dataDir; + constructor(dataDir = process.env.ZCODE_PLUGIN_DATA || defaultDataDir()) { + this.dataDir = dataDir; + } + async load(sessionId2) { + try { + const contents = await readFile(this.statePath(sessionId2), "utf8"); + return parseState(JSON.parse(contents)); + } catch { + return null; + } + } + async save(state) { + await mkdir(this.dataDir, { recursive: true, mode: 448 }); + const path = this.statePath(state.sessionId); + const temporaryPath = `${path}.${randomUUID()}.tmp`; + await writeFile(temporaryPath, JSON.stringify(state), { + encoding: "utf8", + mode: 384 + }); + await rename(temporaryPath, path); + } + async clear(sessionId2) { + await rm(this.statePath(sessionId2), { force: true }); + } + async withSessionLock(sessionId2, task) { + await mkdir(this.dataDir, { recursive: true, mode: 448 }); + const lockPath = this.lockPath(sessionId2); + let acquired = false; + for (let attempt = 0; attempt < LOCK_ATTEMPTS; attempt += 1) { + try { + const handle = await open(lockPath, "wx", 384); + await handle.close(); + acquired = true; + break; + } catch { + await this.removeStaleLock(lockPath); + await new Promise((resolve) => setTimeout(resolve, LOCK_WAIT_MS)); + } + } + if (!acquired) + throw new Error("Timed out acquiring the Langfuse state lock"); + try { + return await task(); + } finally { + await rm(lockPath, { force: true }); + } + } + statePath(sessionId2) { + return join2(this.dataDir, `${sessionKey(sessionId2)}.json`); + } + lockPath(sessionId2) { + return join2(this.dataDir, `${sessionKey(sessionId2)}.lock`); + } + async removeStaleLock(lockPath) { + try { + const details = await stat(lockPath); + if (Date.now() - details.mtimeMs > STALE_LOCK_MS) + await rm(lockPath, { force: true }); + } catch { + } + } +}; + +// node_modules/mustache/mustache.mjs +var objectToString = Object.prototype.toString; +var isArray = Array.isArray || function isArrayPolyfill(object) { + return objectToString.call(object) === "[object Array]"; +}; +function isFunction(object) { + return typeof object === "function"; +} +function typeStr(obj) { + return isArray(obj) ? "array" : typeof obj; +} +function escapeRegExp(string) { + return string.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&"); +} +function hasProperty(obj, propName) { + return obj != null && typeof obj === "object" && propName in obj; +} +function primitiveHasOwnProperty(primitive, propName) { + return primitive != null && typeof primitive !== "object" && primitive.hasOwnProperty && primitive.hasOwnProperty(propName); +} +var regExpTest = RegExp.prototype.test; +function testRegExp(re, string) { + return regExpTest.call(re, string); +} +var nonSpaceRe = /\S/; +function isWhitespace(string) { + return !testRegExp(nonSpaceRe, string); +} +var entityMap = { + "&": "&", + "<": "<", + ">": ">", + '"': """, + "'": "'", + "/": "/", + "`": "`", + "=": "=" +}; +function escapeHtml(string) { + return String(string).replace(/[&<>"'`=\/]/g, function fromEntityMap(s) { + return entityMap[s]; + }); +} +var whiteRe = /\s*/; +var spaceRe = /\s+/; +var equalsRe = /\s*=/; +var curlyRe = /\s*\}/; +var tagRe = /#|\^|\/|>|\{|&|=|!/; +function parseTemplate(template, tags) { + if (!template) + return []; + var lineHasNonSpace = false; + var sections = []; + var tokens = []; + var spaces = []; + var hasTag = false; + var nonSpace = false; + var indentation = ""; + var tagIndex = 0; + function stripSpace() { + if (hasTag && !nonSpace) { + while (spaces.length) + delete tokens[spaces.pop()]; + } else { + spaces = []; + } + hasTag = false; + nonSpace = false; + } + var openingTagRe, closingTagRe, closingCurlyRe; + function compileTags(tagsToCompile) { + if (typeof tagsToCompile === "string") + tagsToCompile = tagsToCompile.split(spaceRe, 2); + if (!isArray(tagsToCompile) || tagsToCompile.length !== 2) + throw new Error("Invalid tags: " + tagsToCompile); + openingTagRe = new RegExp(escapeRegExp(tagsToCompile[0]) + "\\s*"); + closingTagRe = new RegExp("\\s*" + escapeRegExp(tagsToCompile[1])); + closingCurlyRe = new RegExp("\\s*" + escapeRegExp("}" + tagsToCompile[1])); + } + compileTags(tags || mustache.tags); + var scanner = new Scanner(template); + var start, type, value, chr, token, openSection; + while (!scanner.eos()) { + start = scanner.pos; + value = scanner.scanUntil(openingTagRe); + if (value) { + for (var i = 0, valueLength = value.length; i < valueLength; ++i) { + chr = value.charAt(i); + if (isWhitespace(chr)) { + spaces.push(tokens.length); + indentation += chr; + } else { + nonSpace = true; + lineHasNonSpace = true; + indentation += " "; + } + tokens.push(["text", chr, start, start + 1]); + start += 1; + if (chr === "\n") { + stripSpace(); + indentation = ""; + tagIndex = 0; + lineHasNonSpace = false; + } + } + } + if (!scanner.scan(openingTagRe)) + break; + hasTag = true; + type = scanner.scan(tagRe) || "name"; + scanner.scan(whiteRe); + if (type === "=") { + value = scanner.scanUntil(equalsRe); + scanner.scan(equalsRe); + scanner.scanUntil(closingTagRe); + } else if (type === "{") { + value = scanner.scanUntil(closingCurlyRe); + scanner.scan(curlyRe); + scanner.scanUntil(closingTagRe); + type = "&"; + } else { + value = scanner.scanUntil(closingTagRe); + } + if (!scanner.scan(closingTagRe)) + throw new Error("Unclosed tag at " + scanner.pos); + if (type == ">") { + token = [type, value, start, scanner.pos, indentation, tagIndex, lineHasNonSpace]; + } else { + token = [type, value, start, scanner.pos]; + } + tagIndex++; + tokens.push(token); + if (type === "#" || type === "^") { + sections.push(token); + } else if (type === "/") { + openSection = sections.pop(); + if (!openSection) + throw new Error('Unopened section "' + value + '" at ' + start); + if (openSection[1] !== value) + throw new Error('Unclosed section "' + openSection[1] + '" at ' + start); + } else if (type === "name" || type === "{" || type === "&") { + nonSpace = true; + } else if (type === "=") { + compileTags(value); + } + } + stripSpace(); + openSection = sections.pop(); + if (openSection) + throw new Error('Unclosed section "' + openSection[1] + '" at ' + scanner.pos); + return nestTokens(squashTokens(tokens)); +} +function squashTokens(tokens) { + var squashedTokens = []; + var token, lastToken; + for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) { + token = tokens[i]; + if (token) { + if (token[0] === "text" && lastToken && lastToken[0] === "text") { + lastToken[1] += token[1]; + lastToken[3] = token[3]; + } else { + squashedTokens.push(token); + lastToken = token; + } + } + } + return squashedTokens; +} +function nestTokens(tokens) { + var nestedTokens = []; + var collector = nestedTokens; + var sections = []; + var token, section; + for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) { + token = tokens[i]; + switch (token[0]) { + case "#": + case "^": + collector.push(token); + sections.push(token); + collector = token[4] = []; + break; + case "/": + section = sections.pop(); + section[5] = token[2]; + collector = sections.length > 0 ? sections[sections.length - 1][4] : nestedTokens; + break; + default: + collector.push(token); + } + } + return nestedTokens; +} +function Scanner(string) { + this.string = string; + this.tail = string; + this.pos = 0; +} +Scanner.prototype.eos = function eos() { + return this.tail === ""; +}; +Scanner.prototype.scan = function scan(re) { + var match = this.tail.match(re); + if (!match || match.index !== 0) + return ""; + var string = match[0]; + this.tail = this.tail.substring(string.length); + this.pos += string.length; + return string; +}; +Scanner.prototype.scanUntil = function scanUntil(re) { + var index = this.tail.search(re), match; + switch (index) { + case -1: + match = this.tail; + this.tail = ""; + break; + case 0: + match = ""; + break; + default: + match = this.tail.substring(0, index); + this.tail = this.tail.substring(index); + } + this.pos += match.length; + return match; +}; +function Context(view, parentContext) { + this.view = view; + this.cache = { ".": this.view }; + this.parent = parentContext; +} +Context.prototype.push = function push(view) { + return new Context(view, this); +}; +Context.prototype.lookup = function lookup(name) { + var cache = this.cache; + var value; + if (cache.hasOwnProperty(name)) { + value = cache[name]; + } else { + var context = this, intermediateValue, names, index, lookupHit = false; + while (context) { + if (name.indexOf(".") > 0) { + intermediateValue = context.view; + names = name.split("."); + index = 0; + while (intermediateValue != null && index < names.length) { + if (index === names.length - 1) + lookupHit = hasProperty(intermediateValue, names[index]) || primitiveHasOwnProperty(intermediateValue, names[index]); + intermediateValue = intermediateValue[names[index++]]; + } + } else { + intermediateValue = context.view[name]; + lookupHit = hasProperty(context.view, name); + } + if (lookupHit) { + value = intermediateValue; + break; + } + context = context.parent; + } + cache[name] = value; + } + if (isFunction(value)) + value = value.call(this.view); + return value; +}; +function Writer() { + this.templateCache = { + _cache: {}, + set: function set(key, value) { + this._cache[key] = value; + }, + get: function get(key) { + return this._cache[key]; + }, + clear: function clear() { + this._cache = {}; + } + }; +} +Writer.prototype.clearCache = function clearCache() { + if (typeof this.templateCache !== "undefined") { + this.templateCache.clear(); + } +}; +Writer.prototype.parse = function parse(template, tags) { + var cache = this.templateCache; + var cacheKey = template + ":" + (tags || mustache.tags).join(":"); + var isCacheEnabled = typeof cache !== "undefined"; + var tokens = isCacheEnabled ? cache.get(cacheKey) : void 0; + if (tokens == void 0) { + tokens = parseTemplate(template, tags); + isCacheEnabled && cache.set(cacheKey, tokens); + } + return tokens; +}; +Writer.prototype.render = function render(template, view, partials, config) { + var tags = this.getConfigTags(config); + var tokens = this.parse(template, tags); + var context = view instanceof Context ? view : new Context(view, void 0); + return this.renderTokens(tokens, context, partials, template, config); +}; +Writer.prototype.renderTokens = function renderTokens(tokens, context, partials, originalTemplate, config) { + var buffer = ""; + var token, symbol, value; + for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) { + value = void 0; + token = tokens[i]; + symbol = token[0]; + if (symbol === "#") value = this.renderSection(token, context, partials, originalTemplate, config); + else if (symbol === "^") value = this.renderInverted(token, context, partials, originalTemplate, config); + else if (symbol === ">") value = this.renderPartial(token, context, partials, config); + else if (symbol === "&") value = this.unescapedValue(token, context); + else if (symbol === "name") value = this.escapedValue(token, context, config); + else if (symbol === "text") value = this.rawValue(token); + if (value !== void 0) + buffer += value; + } + return buffer; +}; +Writer.prototype.renderSection = function renderSection(token, context, partials, originalTemplate, config) { + var self = this; + var buffer = ""; + var value = context.lookup(token[1]); + function subRender(template) { + return self.render(template, context, partials, config); + } + if (!value) return; + if (isArray(value)) { + for (var j = 0, valueLength = value.length; j < valueLength; ++j) { + buffer += this.renderTokens(token[4], context.push(value[j]), partials, originalTemplate, config); + } + } else if (typeof value === "object" || typeof value === "string" || typeof value === "number") { + buffer += this.renderTokens(token[4], context.push(value), partials, originalTemplate, config); + } else if (isFunction(value)) { + if (typeof originalTemplate !== "string") + throw new Error("Cannot use higher-order sections without the original template"); + value = value.call(context.view, originalTemplate.slice(token[3], token[5]), subRender); + if (value != null) + buffer += value; + } else { + buffer += this.renderTokens(token[4], context, partials, originalTemplate, config); + } + return buffer; +}; +Writer.prototype.renderInverted = function renderInverted(token, context, partials, originalTemplate, config) { + var value = context.lookup(token[1]); + if (!value || isArray(value) && value.length === 0) + return this.renderTokens(token[4], context, partials, originalTemplate, config); +}; +Writer.prototype.indentPartial = function indentPartial(partial, indentation, lineHasNonSpace) { + var filteredIndentation = indentation.replace(/[^ \t]/g, ""); + var partialByNl = partial.split("\n"); + for (var i = 0; i < partialByNl.length; i++) { + if (partialByNl[i].length && (i > 0 || !lineHasNonSpace)) { + partialByNl[i] = filteredIndentation + partialByNl[i]; + } + } + return partialByNl.join("\n"); +}; +Writer.prototype.renderPartial = function renderPartial(token, context, partials, config) { + if (!partials) return; + var tags = this.getConfigTags(config); + var value = isFunction(partials) ? partials(token[1]) : partials[token[1]]; + if (value != null) { + var lineHasNonSpace = token[6]; + var tagIndex = token[5]; + var indentation = token[4]; + var indentedValue = value; + if (tagIndex == 0 && indentation) { + indentedValue = this.indentPartial(value, indentation, lineHasNonSpace); + } + var tokens = this.parse(indentedValue, tags); + return this.renderTokens(tokens, context, partials, indentedValue, config); + } +}; +Writer.prototype.unescapedValue = function unescapedValue(token, context) { + var value = context.lookup(token[1]); + if (value != null) + return value; +}; +Writer.prototype.escapedValue = function escapedValue(token, context, config) { + var escape = this.getConfigEscape(config) || mustache.escape; + var value = context.lookup(token[1]); + if (value != null) + return typeof value === "number" && escape === mustache.escape ? String(value) : escape(value); +}; +Writer.prototype.rawValue = function rawValue(token) { + return token[1]; +}; +Writer.prototype.getConfigTags = function getConfigTags(config) { + if (isArray(config)) { + return config; + } else if (config && typeof config === "object") { + return config.tags; + } else { + return void 0; + } +}; +Writer.prototype.getConfigEscape = function getConfigEscape(config) { + if (config && typeof config === "object" && !isArray(config)) { + return config.escape; + } else { + return void 0; + } +}; +var mustache = { + name: "mustache.js", + version: "4.2.0", + tags: ["{{", "}}"], + clearCache: void 0, + escape: void 0, + parse: void 0, + render: void 0, + Scanner: void 0, + Context: void 0, + Writer: void 0, + /** + * Allows a user to override the default caching strategy, by providing an + * object with set, get and clear methods. This can also be used to disable + * the cache by setting it to the literal `undefined`. + */ + set templateCache(cache) { + defaultWriter.templateCache = cache; + }, + /** + * Gets the default or overridden caching object from the default writer. + */ + get templateCache() { + return defaultWriter.templateCache; + } +}; +var defaultWriter = new Writer(); +mustache.clearCache = function clearCache2() { + return defaultWriter.clearCache(); +}; +mustache.parse = function parse2(template, tags) { + return defaultWriter.parse(template, tags); +}; +mustache.render = function render2(template, view, partials, config) { + if (typeof template !== "string") { + throw new TypeError('Invalid template! Template should be a "string" but "' + typeStr(template) + '" was given as the first argument for mustache#render(template, view, partials)'); + } + return defaultWriter.render(template, view, partials, config); +}; +mustache.escape = escapeHtml; +mustache.Scanner = Scanner; +mustache.Context = Context; +mustache.Writer = Writer; +var mustache_default = mustache; + +// node_modules/langfuse-core/lib/index.mjs +var SimpleEventEmitter = class { + constructor() { + this.events = {}; + this.events = {}; + } + on(event, listener) { + if (!this.events[event]) { + this.events[event] = []; + } + this.events[event].push(listener); + return () => { + this.events[event] = this.events[event].filter((x) => x !== listener); + }; + } + emit(event, payload) { + for (const listener of this.events[event] || []) { + listener(payload); + } + for (const listener of this.events["*"] || []) { + listener(event, payload); + } + } +}; +var DEFAULT_PROMPT_CACHE_TTL_SECONDS = 60; +var LangfusePromptCacheItem = class { + constructor(value, ttlSeconds) { + this.value = value; + this._expiry = Date.now() + ttlSeconds * 1e3; + } + get isExpired() { + return Date.now() > this._expiry; + } +}; +var LangfusePromptCache = class { + constructor() { + this._cache = /* @__PURE__ */ new Map(); + this._defaultTtlSeconds = DEFAULT_PROMPT_CACHE_TTL_SECONDS; + this._refreshingKeys = /* @__PURE__ */ new Map(); + } + getIncludingExpired(key) { + return this._cache.get(key) ?? null; + } + set(key, value, ttlSeconds) { + const effectiveTtlSeconds = ttlSeconds ?? this._defaultTtlSeconds; + this._cache.set(key, new LangfusePromptCacheItem(value, effectiveTtlSeconds)); + } + addRefreshingPromise(key, promise) { + this._refreshingKeys.set(key, promise); + promise.then(() => { + this._refreshingKeys.delete(key); + }).catch(() => { + this._refreshingKeys.delete(key); + }); + } + isRefreshing(key) { + return this._refreshingKeys.has(key); + } + invalidate(promptName) { + console.log("invalidating", promptName, this._cache.keys()); + for (const key of this._cache.keys()) { + if (key.startsWith(promptName)) { + this._cache.delete(key); + } + } + } +}; +var LangfusePersistedProperty; +(function(LangfusePersistedProperty2) { + LangfusePersistedProperty2["Props"] = "props"; + LangfusePersistedProperty2["Queue"] = "queue"; + LangfusePersistedProperty2["OptedOut"] = "opted_out"; +})(LangfusePersistedProperty || (LangfusePersistedProperty = {})); +var ChatMessageType; +(function(ChatMessageType2) { + ChatMessageType2["ChatMessage"] = "chatmessage"; + ChatMessageType2["Placeholder"] = "placeholder"; +})(ChatMessageType || (ChatMessageType = {})); +mustache_default.escape = function(text) { + return text; +}; +var BasePromptClient = class { + constructor(prompt2, isFallback = false, type) { + this.name = prompt2.name; + this.version = prompt2.version; + this.config = prompt2.config; + this.labels = prompt2.labels; + this.tags = prompt2.tags; + this.isFallback = isFallback; + this.type = type; + this.commitMessage = prompt2.commitMessage; + } + _transformToLangchainVariables(content) { + const jsonEscapedContent = this.escapeJsonForLangchain(content); + return jsonEscapedContent.replace(/\{\{(\w+)\}\}/g, "{$1}"); + } + /** + * Escapes every curly brace that is part of a JSON object by doubling it. + * + * A curly brace is considered “JSON-related” when, after skipping any immediate + * whitespace, the next non-whitespace character is a single (') or double (") quote. + * + * Braces that are already doubled (e.g. `{{variable}}` placeholders) are left untouched. + * + * @param text - Input string that may contain JSON snippets. + * @returns The string with JSON-related braces doubled. + */ + escapeJsonForLangchain(text) { + const out = []; + const stack = []; + let i = 0; + const n = text.length; + while (i < n) { + const ch = text[i]; + if (ch === "{") { + if (i + 1 < n && text[i + 1] === "{") { + out.push("{{"); + i += 2; + continue; + } + let j = i + 1; + while (j < n && /\s/.test(text[j])) { + j++; + } + const isJson = j < n && (text[j] === "'" || text[j] === '"'); + out.push(isJson ? "{{" : "{"); + stack.push(isJson); + i += 1; + continue; + } + if (ch === "}") { + if (i + 1 < n && text[i + 1] === "}") { + out.push("}}"); + i += 2; + continue; + } + const isJson = stack.pop() ?? false; + out.push(isJson ? "}}" : "}"); + i += 1; + continue; + } + out.push(ch); + i += 1; + } + return out.join(""); + } +}; +var TextPromptClient = class extends BasePromptClient { + constructor(prompt2, isFallback = false) { + super(prompt2, isFallback, "text"); + this.promptResponse = prompt2; + this.prompt = prompt2.prompt; + } + compile(variables, _placeholders) { + return mustache_default.render(this.promptResponse.prompt, variables ?? {}); + } + getLangchainPrompt(_options) { + return this._transformToLangchainVariables(this.prompt); + } + toJSON() { + return JSON.stringify({ + name: this.name, + prompt: this.prompt, + version: this.version, + isFallback: this.isFallback, + tags: this.tags, + labels: this.labels, + type: this.type, + config: this.config + }); + } +}; +var ChatPromptClient = class _ChatPromptClient extends BasePromptClient { + constructor(prompt2, isFallback = false) { + const normalizedPrompt = _ChatPromptClient.normalizePrompt(prompt2.prompt); + const typedPrompt = { + ...prompt2, + prompt: normalizedPrompt + }; + super(typedPrompt, isFallback, "chat"); + this.promptResponse = typedPrompt; + this.prompt = normalizedPrompt; + } + static normalizePrompt(prompt2) { + return prompt2.map((item) => { + if ("type" in item) { + return item; + } else { + return { + type: ChatMessageType.ChatMessage, + ...item + }; + } + }); + } + compile(variables, placeholders) { + const messagesWithPlaceholdersReplaced = []; + const placeholderValues = placeholders ?? {}; + for (const item of this.prompt) { + if ("type" in item && item.type === ChatMessageType.Placeholder) { + const placeholderValue = placeholderValues[item.name]; + if (Array.isArray(placeholderValue) && placeholderValue.length > 0 && placeholderValue.every((msg) => typeof msg === "object" && "role" in msg && "content" in msg)) { + messagesWithPlaceholdersReplaced.push(...placeholderValue); + } else if (Array.isArray(placeholderValue) && placeholderValue.length === 0) ; + else if (placeholderValue !== void 0) { + messagesWithPlaceholdersReplaced.push(JSON.stringify(placeholderValue)); + } else { + messagesWithPlaceholdersReplaced.push(item); + } + } else if ("role" in item && "content" in item && item.type === ChatMessageType.ChatMessage) { + messagesWithPlaceholdersReplaced.push({ + role: item.role, + content: item.content + }); + } + } + return messagesWithPlaceholdersReplaced.map((item) => { + if (typeof item === "object" && item !== null && "role" in item && "content" in item) { + return { + ...item, + content: mustache_default.render(item.content, variables ?? {}) + }; + } else { + return item; + } + }); + } + getLangchainPrompt(options) { + const messagesWithPlaceholdersReplaced = []; + const placeholderValues = options?.placeholders ?? {}; + for (const item of this.prompt) { + if ("type" in item && item.type === ChatMessageType.Placeholder) { + const placeholderValue = placeholderValues[item.name]; + if (Array.isArray(placeholderValue) && placeholderValue.length > 0 && placeholderValue.every((msg) => typeof msg === "object" && "role" in msg && "content" in msg)) { + messagesWithPlaceholdersReplaced.push(...placeholderValue.map((msg) => { + return { + role: msg.role, + content: this._transformToLangchainVariables(msg.content) + }; + })); + } else if (Array.isArray(placeholderValue) && placeholderValue.length === 0) ; + else if (placeholderValue !== void 0) { + messagesWithPlaceholdersReplaced.push(JSON.stringify(placeholderValue)); + } else { + messagesWithPlaceholdersReplaced.push({ + variableName: item.name, + optional: false + }); + } + } else if ("role" in item && "content" in item && item.type === ChatMessageType.ChatMessage) { + messagesWithPlaceholdersReplaced.push({ + role: item.role, + content: this._transformToLangchainVariables(item.content) + }); + } + } + return messagesWithPlaceholdersReplaced; + } + toJSON() { + return JSON.stringify({ + name: this.name, + prompt: this.promptResponse.prompt.map((item) => { + if ("type" in item && item.type === ChatMessageType.ChatMessage) { + const { + type: _, + ...messageWithoutType + } = item; + return messageWithoutType; + } + return item; + }), + version: this.version, + isFallback: this.isFallback, + tags: this.tags, + labels: this.labels, + type: this.type, + config: this.config + }); + } +}; +function assert(truthyValue, message) { + if (!truthyValue) { + throw new Error(message); + } +} +function removeTrailingSlash(url) { + return url?.replace(/\/+$/, ""); +} +async function retriable(fn, props = {}, log) { + const { + retryCount = 3, + retryDelay = 5e3, + retryCheck = () => true + } = props; + let lastError = null; + for (let i = 0; i < retryCount + 1; i++) { + if (i > 0) { + await new Promise((resolve) => setTimeout(resolve, retryDelay)); + log(`Retrying ${i + 1} of ${retryCount + 1}`); + } + try { + const res = await fn(); + return res; + } catch (e) { + lastError = e; + if (!retryCheck(e)) { + throw e; + } + log(`Retriable error: ${JSON.stringify(e)}`); + } + } + throw lastError; +} +function generateUUID(globalThis2) { + let d = (/* @__PURE__ */ new Date()).getTime(); + let d2 = globalThis2 && globalThis2.performance && globalThis2.performance.now && globalThis2.performance.now() * 1e3 || 0; + return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) { + let r = Math.random() * 16; + if (d > 0) { + r = (d + r) % 16 | 0; + d = Math.floor(d / 16); + } else { + r = (d2 + r) % 16 | 0; + d2 = Math.floor(d2 / 16); + } + return (c === "x" ? r : r & 3 | 8).toString(16); + }); +} +function currentTimestamp() { + return (/* @__PURE__ */ new Date()).getTime(); +} +function currentISOTime() { + return (/* @__PURE__ */ new Date()).toISOString(); +} +function safeSetTimeout(fn, timeout) { + const t = setTimeout(fn, timeout); + t?.unref && t?.unref(); + return t; +} +function getEnv(key) { + if (typeof process !== "undefined" && process.env[key]) { + return process.env[key]; + } else if (typeof globalThis !== "undefined") { + return globalThis[key]; + } + return; +} +function configLangfuseSDK(params, secretRequired = true) { + const { + publicKey, + secretKey, + ...coreOptions + } = params ?? {}; + const finalPublicKey = publicKey ?? getEnv("LANGFUSE_PUBLIC_KEY"); + const finalSecretKey = secretRequired ? secretKey ?? getEnv("LANGFUSE_SECRET_KEY") : void 0; + const finalBaseUrl = coreOptions.baseUrl ?? getEnv("LANGFUSE_BASEURL"); + const finalCoreOptions = { + ...coreOptions, + baseUrl: finalBaseUrl + }; + return { + publicKey: finalPublicKey, + ...secretRequired ? { + secretKey: finalSecretKey + } : void 0, + ...finalCoreOptions + }; +} +var encodeQueryParams = (params) => { + const queryParams = new URLSearchParams(); + Object.entries(params ?? {}).forEach(([key, value]) => { + if (value !== void 0 && value !== null) { + if (value instanceof Date) { + queryParams.append(key, value.toISOString()); + } else { + queryParams.append(key, value.toString()); + } + } + }); + return queryParams.toString(); +}; +var utils = /* @__PURE__ */ Object.freeze({ + __proto__: null, + assert, + configLangfuseSDK, + currentISOTime, + currentTimestamp, + encodeQueryParams, + generateUUID, + getEnv, + removeTrailingSlash, + retriable, + safeSetTimeout +}); +var common_release_envs = [ + // Vercel + "VERCEL_GIT_COMMIT_SHA", + "NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA", + // Netlify + "COMMIT_REF", + // Render + "RENDER_GIT_COMMIT", + // GitLab CI + "CI_COMMIT_SHA", + // CicleCI + "CIRCLE_SHA1", + // Cloudflare pages + "CF_PAGES_COMMIT_SHA", + // AWS Amplify + "REACT_APP_GIT_SHA", + // Heroku + "SOURCE_VERSION", + // Trigger.dev + "TRIGGER_DEPLOYMENT_ID" +]; +function getCommonReleaseEnvs() { + for (const key of common_release_envs) { + const value = getEnv(key); + if (value) { + return value; + } + } + return void 0; +} +var fs = null; +var cryptoModule = null; +var dynamicImport = (module) => { + return import( + /* webpackIgnore: true */ + module + ); +}; +if (typeof globalThis.Deno !== "undefined") { + Promise.all([dynamicImport("node:fs"), dynamicImport("node:crypto")]).then(([importedFs, importedCrypto]) => { + fs = importedFs; + cryptoModule = importedCrypto; + }).catch(); +} else if (typeof process !== "undefined" && process.versions?.node) { + Promise.all([dynamicImport("fs"), dynamicImport("crypto")]).then(([importedFs, importedCrypto]) => { + fs = importedFs; + cryptoModule = importedCrypto; + }).catch(); +} else if (typeof crypto !== "undefined") { + cryptoModule = crypto; +} +var LangfuseMedia = class _LangfuseMedia { + constructor(params) { + const { + obj, + base64DataUri, + contentType, + contentBytes, + filePath + } = params; + this.obj = obj; + this._mediaId = void 0; + if (base64DataUri) { + const [contentBytesParsed, contentTypeParsed] = this.parseBase64DataUri(base64DataUri); + this._contentBytes = contentBytesParsed; + this._contentType = contentTypeParsed; + this._source = "base64_data_uri"; + } else if (contentBytes && contentType) { + this._contentBytes = contentBytes; + this._contentType = contentType; + this._source = "bytes"; + } else if (filePath && contentType) { + if (!fs) { + throw new Error("File system support is not available in this environment"); + } + if (!fs.existsSync(filePath)) { + throw new Error(`File at path ${filePath} does not exist`); + } + this._contentBytes = this.readFile(filePath); + this._contentType = this._contentBytes ? contentType : void 0; + this._source = this._contentBytes ? "file" : void 0; + } else { + console.error("base64DataUri, or contentBytes and contentType, or filePath must be provided to LangfuseMedia"); + } + } + readFile(filePath) { + try { + if (!fs) { + throw new Error("File system support is not available in this environment"); + } + return fs.readFileSync(filePath); + } catch (error) { + console.error(`Error reading file at path ${filePath}`, error); + return void 0; + } + } + parseBase64DataUri(data) { + try { + if (!data || typeof data !== "string") { + throw new Error("Data URI is not a string"); + } + if (!data.startsWith("data:")) { + throw new Error("Data URI does not start with 'data:'"); + } + const [header, actualData] = data.slice(5).split(",", 2); + if (!header || !actualData) { + throw new Error("Invalid URI"); + } + const headerParts = header.split(";"); + if (!headerParts.includes("base64")) { + throw new Error("Data is not base64 encoded"); + } + const contentType = headerParts[0]; + if (!contentType) { + throw new Error("Content type is empty"); + } + return [Buffer.from(actualData, "base64"), contentType]; + } catch (error) { + console.error("Error parsing base64 data URI", error); + return [void 0, void 0]; + } + } + get contentLength() { + return this._contentBytes?.length; + } + get contentSha256Hash() { + if (!this._contentBytes) { + return void 0; + } + if (!cryptoModule) { + console.error("Crypto support is not available in this environment"); + return void 0; + } + const sha256Hash = cryptoModule.createHash("sha256").update(this._contentBytes).digest("base64"); + return sha256Hash; + } + toJSON() { + if (!this._contentType || !this._source || !this._mediaId) { + return ``; + } + return `@@@langfuseMedia:type=${this._contentType}|id=${this._mediaId}|source=${this._source}@@@`; + } + /** + * Parses a media reference string into a ParsedMediaReference. + * + * Example reference string: + * "@@@langfuseMedia:type=image/jpeg|id=some-uuid|source=base64DataUri@@@" + * + * @param referenceString - The reference string to parse. + * @returns An object with the mediaId, source, and contentType. + * + * @throws Error if the reference string is invalid or missing required fields. + */ + static parseReferenceString(referenceString) { + const prefix = "@@@langfuseMedia:"; + const suffix = "@@@"; + if (!referenceString.startsWith(prefix)) { + throw new Error("Reference string does not start with '@@@langfuseMedia:type='"); + } + if (!referenceString.endsWith(suffix)) { + throw new Error("Reference string does not end with '@@@'"); + } + const content = referenceString.slice(prefix.length, -suffix.length); + const pairs = content.split("|"); + const parsedData = {}; + for (const pair of pairs) { + const [key, value] = pair.split("=", 2); + parsedData[key] = value; + } + if (!("type" in parsedData && "id" in parsedData && "source" in parsedData)) { + throw new Error("Missing required fields in reference string"); + } + return { + mediaId: parsedData["id"], + source: parsedData["source"], + contentType: parsedData["type"] + }; + } + /** + * Replaces the media reference strings in an object with base64 data URIs for the media content. + * + * This method recursively traverses an object (up to a maximum depth of 10) looking for media reference strings + * in the format "@@@langfuseMedia:...@@@". When found, it fetches the actual media content using the provided + * Langfuse client and replaces the reference string with a base64 data URI. + * + * If fetching media content fails for a reference string, a warning is logged and the reference string is left unchanged. + * + * @param params - Configuration object + * @param params.obj - The object to process. Can be a primitive value, array, or nested object + * @param params.langfuseClient - Langfuse client instance used to fetch media content + * @param params.resolveWith - The representation of the media content to replace the media reference string with. Currently only "base64DataUri" is supported. + * @param params.maxDepth - Optional. Default is 10. The maximum depth to traverse the object. + * + * @returns A deep copy of the input object with all media references replaced with base64 data URIs where possible + * + * @example + * ```typescript + * const obj = { + * image: "@@@langfuseMedia:type=image/jpeg|id=123|source=bytes@@@", + * nested: { + * pdf: "@@@langfuseMedia:type=application/pdf|id=456|source=bytes@@@" + * } + * }; + * + * const result = await LangfuseMedia.resolveMediaReferences({ + * obj, + * langfuseClient + * }); + * + * // Result: + * // { + * // image: "data:image/jpeg;base64,/9j/4AAQSkZJRg...", + * // nested: { + * // pdf: "data:application/pdf;base64,JVBERi0xLjcK..." + * // } + * // } + * ``` + */ + static async resolveMediaReferences(params) { + const { + obj, + langfuseClient, + maxDepth = 10 + } = params; + async function traverse(obj2, depth) { + if (depth > maxDepth) { + return obj2; + } + if (typeof obj2 === "string") { + const regex = /@@@langfuseMedia:.+?@@@/g; + const referenceStringMatches = obj2.match(regex); + if (!referenceStringMatches) { + return obj2; + } + let result = obj2; + const referenceStringToMediaContentMap = /* @__PURE__ */ new Map(); + await Promise.all(referenceStringMatches.map(async (referenceString) => { + try { + const parsedMediaReference = _LangfuseMedia.parseReferenceString(referenceString); + const mediaData = await langfuseClient.fetchMedia(parsedMediaReference.mediaId); + const mediaContent = await langfuseClient.fetch(mediaData.url, { + method: "GET", + headers: {} + }); + if (mediaContent.status !== 200) { + throw new Error("Failed to fetch media content"); + } + const base64MediaContent = Buffer.from(await mediaContent.arrayBuffer()).toString("base64"); + const base64DataUri = `data:${mediaData.contentType};base64,${base64MediaContent}`; + referenceStringToMediaContentMap.set(referenceString, base64DataUri); + } catch (error) { + console.warn("Error fetching media content for reference string", referenceString, error); + } + })); + for (const [referenceString, base64MediaContent] of referenceStringToMediaContentMap.entries()) { + result = result.replaceAll(referenceString, base64MediaContent); + } + return result; + } + if (Array.isArray(obj2)) { + return Promise.all(obj2.map(async (item) => await traverse(item, depth + 1))); + } + if (typeof obj2 === "object" && obj2 !== null) { + return Object.fromEntries(await Promise.all(Object.entries(obj2).map(async ([key, value]) => [key, await traverse(value, depth + 1)]))); + } + return obj2; + } + return traverse(obj, 0); + } +}; +function isInSample(input, sampleRate) { + if (sampleRate === void 0) { + return true; + } else if (sampleRate === 0) { + return false; + } + if (sampleRate < 0 || sampleRate > 1 || isNaN(sampleRate)) { + console.warn("Sample rate must be between 0 and 1. Ignoring setting."); + return true; + } + return simpleHash(input) < sampleRate; +} +function simpleHash(str) { + let hash = 0; + const prime = 31; + for (let i = 0; i < str.length; i++) { + hash = hash * prime + str.charCodeAt(i) >>> 0; + } + hash = (hash >>> 16 ^ hash) * 73244475; + hash = (hash >>> 16 ^ hash) * 73244475; + hash = hash >>> 16 ^ hash; + return Math.abs(hash) / 2147483647; +} +var MAX_EVENT_SIZE_BYTES = getEnv("LANGFUSE_MAX_EVENT_SIZE_BYTES") ? Number(getEnv("LANGFUSE_MAX_EVENT_SIZE_BYTES")) : 1e6; +var MAX_BATCH_SIZE_BYTES = getEnv("LANGFUSE_MAX_BATCH_SIZE_BYTES") ? Number(getEnv("LANGFUSE_MAX_BATCH_SIZE_BYTES")) : 25e5; +var ENVIRONMENT_PATTERN = /^(?!langfuse)[a-z0-9_-]+$/; +var WHITELISTED_LANGFUSE_INTERNAL_ENVIRONMENTS = ["langfuse-prompt-experiment"]; +var LangfuseFetchHttpError = class extends Error { + constructor(response, body) { + super("HTTP error while fetching Langfuse: " + response.status + " and body: " + body); + this.response = response; + this.name = "LangfuseFetchHttpError"; + } +}; +var LangfuseFetchNetworkError = class extends Error { + constructor(error) { + super("Network error while fetching Langfuse", error instanceof Error ? { + cause: error + } : {}); + this.error = error; + this.name = "LangfuseFetchNetworkError"; + } +}; +function isLangfuseFetchHttpError(error) { + return typeof error === "object" && error.name === "LangfuseFetchHttpError"; +} +function isLangfuseFetchNetworkError(error) { + return typeof error === "object" && error.name === "LangfuseFetchNetworkError"; +} +function isLangfuseFetchError(err) { + return isLangfuseFetchHttpError(err) || isLangfuseFetchNetworkError(err); +} +var SUPPORT_URL = "https://langfuse.com/support"; +var API_DOCS_URL = "https://api.reference.langfuse.com"; +var RBAC_DOCS_URL = "https://langfuse.com/docs/rbac"; +var INSTALLATION_DOCS_URL = "https://langfuse.com/docs/sdk/typescript/guide"; +var RATE_LIMITS_URL = "https://langfuse.com/faq/all/api-limits"; +var NPM_PACKAGE_URL = "https://www.npmjs.com/package/langfuse"; +var updatePromptResponse = `Make sure to keep your SDK updated, refer to ${NPM_PACKAGE_URL} for details.`; +var defaultServerErrorPrompt = `This is an unusual occurrence and we are monitoring it closely. For help, please contact support: ${SUPPORT_URL}.`; +var defaultErrorResponse = `Unexpected error occurred. Please check your request and contact support: ${SUPPORT_URL}.`; +var errorResponseByCode = /* @__PURE__ */ new Map([ + // Internal error category: 5xx errors, 404 error + [500, `Internal server error occurred. For help, please contact support: ${SUPPORT_URL}`], + [501, `Not implemented. Please check your request and contact support for help: ${SUPPORT_URL}.`], + [502, `Bad gateway. ${defaultServerErrorPrompt}`], + [503, `Service unavailable. ${defaultServerErrorPrompt}`], + [504, `Gateway timeout. ${defaultServerErrorPrompt}`], + [404, `Internal error occurred. ${defaultServerErrorPrompt}`], + // Client error category: 4xx errors, excluding 404 + [400, `Bad request. Please check your request for any missing or incorrect parameters. Refer to our API docs: ${API_DOCS_URL} for details.`], + [401, `Unauthorized. Please check your public/private host settings. Refer to our installation and setup guide: ${INSTALLATION_DOCS_URL} for details on SDK configuration.`], + [403, `Forbidden. Please check your access control settings. Refer to our RBAC docs: ${RBAC_DOCS_URL} for details.`], + [429, `Rate limit exceeded. For more information on rate limits please see: ${RATE_LIMITS_URL}`] +]); +function getErrorResponseByCode(code) { + if (!code) { + return `${defaultErrorResponse} ${updatePromptResponse}`; + } + const errorResponse = errorResponseByCode.get(code) || defaultErrorResponse; + return `${code}: ${errorResponse} ${updatePromptResponse}`; +} +function logIngestionError(error) { + if (isLangfuseFetchHttpError(error)) { + const code = error.response.status; + const errorResponse = getErrorResponseByCode(code); + console.error("[Langfuse SDK]", errorResponse, `Error details: ${error}`); + } else if (isLangfuseFetchNetworkError(error)) { + console.error("[Langfuse SDK] Network error: ", error); + } else { + console.error("[Langfuse SDK] Unknown error:", error); + } +} +var LangfuseCoreStateless = class { + constructor(params) { + this.additionalHeaders = {}; + this.debugMode = false; + this.pendingEventProcessingPromises = {}; + this.pendingIngestionPromises = {}; + this.localEventExportMap = /* @__PURE__ */ new Map(); + this._events = new SimpleEventEmitter(); + const { + publicKey, + secretKey, + enabled, + _projectId, + _isLocalEventExportEnabled, + ...options + } = params; + this._events.on("error", (payload) => { + console.error(`[Langfuse SDK] ${typeof payload === "string" ? payload : JSON.stringify(payload)}`); + }); + this.enabled = enabled === false ? false : true; + this.publicKey = publicKey ?? ""; + this.secretKey = secretKey; + this.baseUrl = removeTrailingSlash(options?.baseUrl || "https://cloud.langfuse.com"); + this.additionalHeaders = options?.additionalHeaders || {}; + this.flushAt = options?.flushAt ? Math.max(options?.flushAt, 1) : 15; + this.flushInterval = options?.flushInterval ?? 1e4; + this.release = options?.release ?? getEnv("LANGFUSE_RELEASE") ?? getCommonReleaseEnvs() ?? void 0; + this.mask = options?.mask; + this.sampleRate = options?.sampleRate ?? (getEnv("LANGFUSE_SAMPLE_RATE") ? Number(getEnv("LANGFUSE_SAMPLE_RATE")) : void 0); + if (this.sampleRate) { + this._events.emit("debug", `Langfuse trace sampling enabled with sampleRate ${this.sampleRate}.`); + } + this.environment = options?.environment ?? getEnv("LANGFUSE_TRACING_ENVIRONMENT"); + if (this.environment && !(ENVIRONMENT_PATTERN.test(this.environment) || WHITELISTED_LANGFUSE_INTERNAL_ENVIRONMENTS.includes(this.environment)) && !_isLocalEventExportEnabled) { + this._events.emit("error", `Invalid tracing environment set: ${this.environment} . Environment must match regex ${ENVIRONMENT_PATTERN}. Events will be rejected by Langfuse server.`); + } + this._retryOptions = { + retryCount: options?.fetchRetryCount ?? 3, + retryDelay: options?.fetchRetryDelay ?? 3e3, + retryCheck: isLangfuseFetchError + }; + this.requestTimeout = options?.requestTimeout ?? 5e3; + this.sdkIntegration = options?.sdkIntegration ?? "DEFAULT"; + this.isLocalEventExportEnabled = _isLocalEventExportEnabled ?? false; + if (this.isLocalEventExportEnabled && !_projectId) { + this._events.emit("error", "Local event export is enabled, but no project ID was provided. Disabling local export."); + this.isLocalEventExportEnabled = false; + return; + } else if (!this.isLocalEventExportEnabled && _projectId) { + this._events.emit("error", "Local event export is disabled, but a project ID was provided. Disabling local export."); + this.isLocalEventExportEnabled = false; + return; + } else { + this.projectId = _projectId; + } + } + getSdkIntegration() { + return this.sdkIntegration; + } + getCommonEventProperties() { + return { + $lib: this.getLibraryId(), + $lib_version: this.getLibraryVersion() + }; + } + on(event, cb) { + return this._events.on(event, cb); + } + debug(enabled = true) { + this.removeDebugCallback?.(); + this.debugMode = enabled; + if (enabled) { + this.removeDebugCallback = this.on("*", (event, payload) => { + if (event === "error") { + return; + } + console.log("[Langfuse Debug]", event, JSON.stringify(payload)); + }); + } + } + /*** + *** Handlers for each object type + ***/ + traceStateless(body) { + const { + id: bodyId, + timestamp: bodyTimestamp, + release: bodyRelease, + ...rest + } = body; + const id = bodyId ?? generateUUID(); + const release = bodyRelease ?? this.release; + const parsedBody = { + id, + release, + timestamp: bodyTimestamp ?? /* @__PURE__ */ new Date(), + environment: this.environment, + ...rest + }; + this.enqueue("trace-create", parsedBody); + return id; + } + eventStateless(body) { + const { + id: bodyId, + startTime: bodyStartTime, + ...rest + } = body; + const id = bodyId ?? generateUUID(); + const parsedBody = { + id, + startTime: bodyStartTime ?? /* @__PURE__ */ new Date(), + environment: this.environment, + ...rest + }; + this.enqueue("event-create", parsedBody); + return id; + } + spanStateless(body) { + const { + id: bodyId, + startTime: bodyStartTime, + ...rest + } = body; + const id = bodyId || generateUUID(); + const parsedBody = { + id, + startTime: bodyStartTime ?? /* @__PURE__ */ new Date(), + environment: this.environment, + ...rest + }; + this.enqueue("span-create", parsedBody); + return id; + } + generationStateless(body) { + const { + id: bodyId, + startTime: bodyStartTime, + prompt: prompt2, + ...rest + } = body; + const promptDetails = prompt2 && !prompt2.isFallback ? { + promptName: prompt2.name, + promptVersion: prompt2.version + } : {}; + const id = bodyId || generateUUID(); + const parsedBody = { + id, + startTime: bodyStartTime ?? /* @__PURE__ */ new Date(), + environment: this.environment, + ...promptDetails, + ...rest + }; + this.enqueue("generation-create", parsedBody); + return id; + } + scoreStateless(body) { + const { + id: bodyId, + ...rest + } = body; + const id = bodyId || generateUUID(); + const parsedBody = { + id, + environment: this.environment, + ...rest + }; + this.enqueue("score-create", parsedBody); + return id; + } + updateSpanStateless(body) { + this.enqueue("span-update", body); + return body.id; + } + updateGenerationStateless(body) { + const { + prompt: prompt2, + ...rest + } = body; + const promptDetails = prompt2 && !prompt2.isFallback ? { + promptName: prompt2.name, + promptVersion: prompt2.version + } : {}; + const parsedBody = { + ...promptDetails, + ...rest + }; + this.enqueue("generation-update", parsedBody); + return body.id; + } + async _getDataset(name) { + const encodedName = encodeURIComponent(name); + return this.fetchAndLogErrors(`${this.baseUrl}/api/public/v2/datasets/${encodedName}`, this._getFetchOptions({ + method: "GET" + })); + } + async _getDatasetItems(query) { + const params = new URLSearchParams(); + Object.entries(query ?? {}).forEach(([key, value]) => { + if (value !== void 0 && value !== null) { + params.append(key, value.toString()); + } + }); + return this.fetchAndLogErrors(`${this.baseUrl}/api/public/dataset-items?${params}`, this._getFetchOptions({ + method: "GET" + })); + } + async _fetchMedia(id) { + return this.fetchAndLogErrors(`${this.baseUrl}/api/public/media/${id}`, this._getFetchOptions({ + method: "GET" + })); + } + async fetchTraces(query) { + const { + data, + meta + } = await this.fetchAndLogErrors(`${this.baseUrl}/api/public/traces?${encodeQueryParams(query)}`, this._getFetchOptions({ + method: "GET" + })); + return { + data, + meta + }; + } + async fetchTrace(traceId) { + const res = await this.fetchAndLogErrors(`${this.baseUrl}/api/public/traces/${traceId}`, this._getFetchOptions({ + method: "GET" + })); + return { + data: res + }; + } + async fetchObservations(query) { + const { + data, + meta + } = await this.fetchAndLogErrors(`${this.baseUrl}/api/public/observations?${encodeQueryParams(query)}`, this._getFetchOptions({ + method: "GET" + })); + return { + data, + meta + }; + } + async fetchObservation(observationId) { + const res = await this.fetchAndLogErrors(`${this.baseUrl}/api/public/observations/${observationId}`, this._getFetchOptions({ + method: "GET" + })); + return { + data: res + }; + } + async fetchSessions(query) { + const { + data, + meta + } = await this.fetchAndLogErrors(`${this.baseUrl}/api/public/sessions?${encodeQueryParams(query)}`, this._getFetchOptions({ + method: "GET" + })); + return { + data, + meta + }; + } + async getDatasetRun(params) { + const encodedDatasetName = encodeURIComponent(params.datasetName); + const encodedRunName = encodeURIComponent(params.runName); + return this.fetchAndLogErrors(`${this.baseUrl}/api/public/datasets/${encodedDatasetName}/runs/${encodedRunName}`, this._getFetchOptions({ + method: "GET" + })); + } + async getDatasetRuns(datasetName, query) { + return this.fetchAndLogErrors(`${this.baseUrl}/api/public/datasets/${encodeURIComponent(datasetName)}/runs?${encodeQueryParams(query)}`, this._getFetchOptions({ + method: "GET" + })); + } + async createDatasetRunItem(body) { + return this.fetchAndLogErrors(`${this.baseUrl}/api/public/dataset-run-items`, this._getFetchOptions({ + method: "POST", + body: JSON.stringify(body) + })); + } + /** + * Creates a dataset. Upserts the dataset if it already exists. + * + * @param dataset Can be either a string (name) or an object with name, description and metadata + * @returns A promise that resolves to the response of the create operation. + */ + async createDataset(dataset) { + const body = typeof dataset === "string" ? { + name: dataset + } : dataset; + return this.fetchAndLogErrors(`${this.baseUrl}/api/public/datasets`, this._getFetchOptions({ + method: "POST", + body: JSON.stringify(body) + })); + } + /** + * Creates a dataset item. Upserts the item if it already exists. + * @param body The body of the dataset item to be created. + * @returns A promise that resolves to the response of the create operation. + */ + async createDatasetItem(body) { + return this.fetchAndLogErrors(`${this.baseUrl}/api/public/dataset-items`, this._getFetchOptions({ + method: "POST", + body: JSON.stringify(body) + })); + } + async getDatasetItem(id) { + return this.fetchAndLogErrors(`${this.baseUrl}/api/public/dataset-items/${id}`, this._getFetchOptions({ + method: "GET" + })); + } + _parsePayload(response) { + try { + return JSON.parse(response); + } catch { + return response; + } + } + async createPromptStateless(body) { + return this.fetchAndLogErrors(`${this.baseUrl}/api/public/v2/prompts`, this._getFetchOptions({ + method: "POST", + body: JSON.stringify(body) + })); + } + async updatePromptStateless(body) { + return this.fetchAndLogErrors(`${this.baseUrl}/api/public/v2/prompts/${encodeURIComponent(body.name)}/versions/${encodeURIComponent(body.version)}`, this._getFetchOptions({ + method: "PATCH", + body: JSON.stringify(body) + })); + } + async getPromptStateless(name, version2, label, maxRetries, requestTimeout) { + const encodedName = encodeURIComponent(name); + const params = new URLSearchParams(); + if (version2 && label) { + throw new Error("Provide either version or label, not both."); + } + if (version2) { + params.append("version", version2.toString()); + } + if (label) { + params.append("label", label); + } + const url = `${this.baseUrl}/api/public/v2/prompts/${encodedName}${params.size ? "?" + params : ""}`; + const boundedMaxRetries = this._getBoundedMaxRetries({ + maxRetries, + defaultMaxRetries: 2, + maxRetriesUpperBound: 4 + }); + const retryOptions = { + ...this._retryOptions, + retryCount: boundedMaxRetries, + retryDelay: 500 + }; + const retryLogger = (string) => this._events.emit("retry", string + ", " + url + ", " + JSON.stringify(retryOptions)); + return retriable(async () => { + const res = await this.fetch(url, this._getFetchOptions({ + method: "GET", + fetchTimeout: requestTimeout ?? this.requestTimeout + })).catch((e) => { + if (e.name === "AbortError") { + throw new LangfuseFetchNetworkError("Fetch request timed out"); + } + throw new LangfuseFetchNetworkError(e); + }); + if (res.status >= 500) { + throw new LangfuseFetchHttpError(res, await res.text()); + } + const data = await res.json(); + return { + fetchResult: res.status === 200 ? "success" : "failure", + data + }; + }, retryOptions, retryLogger); + } + _getBoundedMaxRetries(params) { + const defaultMaxRetries = Math.max(params.defaultMaxRetries ?? 2, 0); + const maxRetriesUpperBound = Math.max(params.maxRetriesUpperBound ?? 4, 0); + if (params.maxRetries === void 0) { + return defaultMaxRetries; + } + return Math.min(Math.max(params.maxRetries, 0), maxRetriesUpperBound); + } + /*** + *** QUEUEING AND FLUSHING + ***/ + enqueue(type, body) { + if (!this.enabled) { + return; + } + const traceId = this.parseTraceId(type, body); + if (!traceId) { + this._events.emit("warning", "Failed to parse traceID for sampling. Please open a Github issue in https://github.com/langfuse/langfuse/issues/new/choose"); + } else if (!isInSample(traceId, this.sampleRate)) { + this._events.emit("debug", `Event with trace ID ${traceId} is out of sample. Skipping.`); + return; + } + const promise = this.processEnqueueEvent(type, body); + const promiseId = generateUUID(); + this.pendingEventProcessingPromises[promiseId] = promise; + promise.catch((e) => { + this._events.emit("error", e); + }).finally(() => { + delete this.pendingEventProcessingPromises[promiseId]; + }); + } + async processEnqueueEvent(type, body) { + this.maskEventBodyInPlace(body); + await this.processMediaInEvent(type, body); + const finalEventBody = this.truncateEventBody(body, MAX_EVENT_SIZE_BYTES); + try { + JSON.stringify(finalEventBody); + } catch (e) { + this._events.emit("error", `Event Body for ${type} is not JSON-serializable: ${e}`); + return; + } + const queue = this.getPersistedProperty(LangfusePersistedProperty.Queue) || []; + queue.push({ + id: generateUUID(), + type, + timestamp: currentISOTime(), + body: finalEventBody, + // TODO: fix typecast. EventBody is not correctly narrowed to the correct type dictated by the 'type' property. This should be part of a larger type cleanup. + metadata: void 0 + }); + this.setPersistedProperty(LangfusePersistedProperty.Queue, queue); + this._events.emit(type, finalEventBody); + if (queue.length >= this.flushAt) { + this.flush(); + } + if (this.flushInterval && !this._flushTimer) { + this._flushTimer = safeSetTimeout(() => this.flush(), this.flushInterval); + } + } + maskEventBodyInPlace(body) { + if (!this.mask) { + return; + } + const maskableKeys = ["input", "output"]; + for (const key of maskableKeys) { + if (key in body) { + try { + body[key] = this.mask({ + data: body[key] + }); + } catch (e) { + this._events.emit("error", `Error masking ${key}: ${e}`); + body[key] = ""; + } + } + } + } + /** + * Truncates the event body if its byte size exceeds the specified maximum byte size. + * Emits a warning event if truncation occurs. + * The fields that may be truncated are: "input", "output", and "metadata". + * The fields are truncated in the order of their size, from largest to smallest until the total byte size is within the limit. + */ + truncateEventBody(body, maxByteSize) { + const bodySize = this.getByteSize(body); + if (bodySize <= maxByteSize) { + return body; + } + this._events.emit("warning", `Event Body is too large (${bodySize} bytes) and will be truncated`); + const keysToCheck = ["input", "output", "metadata"]; + const keySizes = keysToCheck.map((key) => ({ + key, + size: key in body ? this.getByteSize(body[key]) : 0 + })).sort((a, b) => b.size - a.size); + let result = { + ...body + }; + let currentSize = bodySize; + for (const { + key, + size + } of keySizes) { + if (currentSize > maxByteSize && Object.prototype.hasOwnProperty.call(result, key)) { + result = { + ...result, + [key]: "" + }; + this._events.emit("warning", `Truncated ${key} due to total size exceeding limit`); + currentSize -= size; + } + } + return result; + } + getByteSize(obj) { + const serialized = JSON.stringify(obj); + if (typeof TextEncoder !== "undefined") { + return new TextEncoder().encode(serialized).length; + } else { + return encodeURIComponent(serialized).replace(/%[A-F\d]{2}/g, "U").length; + } + } + async processMediaInEvent(type, body) { + if (!body) { + return; + } + const traceId = this.parseTraceId(type, body); + if (!traceId) { + this._events.emit("warning", "traceId is required for media upload"); + return; + } + const observationId = (type.includes("generation") || type.includes("span")) && body.id ? body.id : void 0; + await Promise.all(["input", "output", "metadata"].map(async (field) => { + if (body[field]) { + body[field] = await this.findAndProcessMedia({ + data: body[field], + traceId, + observationId, + field + }).catch((e) => { + this._events.emit("error", `Error processing multimodal event: ${e}`); + }) ?? body[field]; + } + })); + } + parseTraceId(type, body) { + return "traceId" in body ? body.traceId : type.includes("trace") ? body.id : void 0; + } + async findAndProcessMedia({ + data, + traceId, + observationId, + field + }) { + const seenObjects = /* @__PURE__ */ new WeakMap(); + const maxLevels = 10; + const processRecursively = async (data2, level) => { + if (typeof data2 === "string" && data2.startsWith("data:")) { + const media = new LangfuseMedia({ + base64DataUri: data2 + }); + await this.processMediaItem({ + media, + traceId, + observationId, + field + }); + return media; + } + if (typeof data2 !== "object" || data2 === null) { + return data2; + } + if (seenObjects.has(data2) || level > maxLevels) { + return data2; + } + seenObjects.set(data2, true); + if (data2 instanceof LangfuseMedia || Object.prototype.toString.call(data2) === "[object LangfuseMedia]") { + await this.processMediaItem({ + media: data2, + traceId, + observationId, + field + }); + return data2; + } + if (Array.isArray(data2)) { + return await Promise.all(data2.map((item) => processRecursively(item, level + 1))); + } + if (typeof data2 === "object" && data2 !== null) { + if ("input_audio" in data2 && typeof data2["input_audio"] === "object" && "data" in data2.input_audio) { + const media = new LangfuseMedia({ + base64DataUri: `data:audio/${data2.input_audio["format"] || "wav"};base64,${data2.input_audio.data}` + }); + await this.processMediaItem({ + media, + traceId, + observationId, + field + }); + return { + ...data2, + input_audio: { + ...data2.input_audio, + data: media + } + }; + } + if ("audio" in data2 && typeof data2["audio"] === "object" && "data" in data2.audio) { + const media = new LangfuseMedia({ + base64DataUri: `data:audio/${data2.audio["format"] || "wav"};base64,${data2.audio.data}` + }); + await this.processMediaItem({ + media, + traceId, + observationId, + field + }); + return { + ...data2, + audio: { + ...data2.audio, + data: media + } + }; + } + return Object.fromEntries(await Promise.all(Object.entries(data2).map(async ([key, value]) => [key, await processRecursively(value, level + 1)]))); + } + return data2; + }; + return await processRecursively(data, 1); + } + async processMediaItem({ + media, + traceId, + observationId, + field + }) { + try { + if (!media.contentLength || !media._contentType || !media.contentSha256Hash || !media._contentBytes) { + return; + } + const getUploadUrlBody = { + contentLength: media.contentLength, + traceId, + observationId, + field, + contentType: media._contentType, + sha256Hash: media.contentSha256Hash + }; + const fetchResponse = await this.fetch(`${this.baseUrl}/api/public/media`, this._getFetchOptions({ + method: "POST", + body: JSON.stringify(getUploadUrlBody) + })); + const uploadUrlResponse = await fetchResponse.json(); + const { + uploadUrl, + mediaId + } = uploadUrlResponse; + media._mediaId = mediaId; + if (uploadUrl) { + this._events.emit("debug", `Uploading media ${mediaId}`); + const startTime = Date.now(); + const uploadResponse = await this.uploadMediaWithBackoff({ + uploadUrl, + contentBytes: media._contentBytes, + contentType: media._contentType, + contentSha256Hash: media.contentSha256Hash, + maxRetries: 3, + baseDelay: 1e3 + }); + if (!uploadResponse) { + throw Error("Media upload process failed"); + } + const patchMediaBody = { + uploadedAt: (/* @__PURE__ */ new Date()).toISOString(), + uploadHttpStatus: uploadResponse.status, + uploadHttpError: await uploadResponse.text(), + uploadTimeMs: Date.now() - startTime + }; + await this.fetch(`${this.baseUrl}/api/public/media/${mediaId}`, this._getFetchOptions({ + method: "PATCH", + body: JSON.stringify(patchMediaBody) + })); + this._events.emit("debug", `Media upload status reported for ${mediaId}`); + } else { + this._events.emit("debug", `Media ${mediaId} already uploaded`); + } + } catch (err) { + this._events.emit("error", `Error processing media item: ${err}`); + } + } + async uploadMediaWithBackoff(params) { + const { + uploadUrl, + contentType, + contentSha256Hash, + contentBytes, + maxRetries, + baseDelay + } = params; + for (let attempt = 0; attempt <= maxRetries; attempt++) { + try { + const uploadResponse = await this.fetch(uploadUrl, { + method: "PUT", + body: contentBytes, + headers: { + "Content-Type": contentType, + "x-amz-checksum-sha256": contentSha256Hash, + "x-ms-blob-type": "BlockBlob" + } + }); + if (attempt < maxRetries && uploadResponse.status !== 200 && uploadResponse.status !== 201) { + throw new Error(`Upload failed with status ${uploadResponse.status}`); + } + return uploadResponse; + } catch (e) { + if (attempt === maxRetries) { + throw e; + } + const delay = baseDelay * Math.pow(2, attempt); + const jitter = Math.random() * 1e3; + await new Promise((resolve) => setTimeout(resolve, delay + jitter)); + } + } + } + /** + * Asynchronously flushes all events that are not yet sent to the server. + * This function always resolves, even if there were errors when flushing. + * Errors are emitted as "error" events and the promise resolves. + * + * @returns {Promise} A promise that resolves when the flushing is completed. + */ + async flushAsync() { + await Promise.all(Object.values(this.pendingEventProcessingPromises)).catch((e) => { + logIngestionError(e); + }); + return new Promise((resolve, _reject) => { + try { + this.flush((err, data) => { + if (err) { + logIngestionError(err); + resolve(); + } else { + resolve(data); + } + }); + } catch (e) { + console.error("[Langfuse SDK] Error while flushing Langfuse", e); + } + }); + } + // Flushes all events that are not yet sent to the server + flush(callback) { + if (this._flushTimer) { + clearTimeout(this._flushTimer); + this._flushTimer = null; + } + const queue = this.getPersistedProperty(LangfusePersistedProperty.Queue) || []; + if (!queue.length) { + return callback?.(); + } + const items = queue.splice(0, this.flushAt); + const { + processedItems, + remainingItems + } = this.processQueueItems(items, MAX_EVENT_SIZE_BYTES, MAX_BATCH_SIZE_BYTES); + this.setPersistedProperty(LangfusePersistedProperty.Queue, [...remainingItems, ...queue]); + const promiseUUID = generateUUID(); + const done = (err) => { + if (err) { + this._events.emit("warning", err); + } + callback?.(err, items); + this._events.emit("flush", items); + }; + if (this.isLocalEventExportEnabled && this.projectId) { + if (!this.localEventExportMap.has(this.projectId)) { + this.localEventExportMap.set(this.projectId, [...items]); + } else { + this.localEventExportMap.get(this.projectId)?.push(...items); + } + done(); + return; + } + const payload = JSON.stringify({ + batch: processedItems, + metadata: { + batch_size: processedItems.length, + sdk_integration: this.sdkIntegration, + sdk_version: this.getLibraryVersion(), + sdk_variant: this.getLibraryId(), + public_key: this.publicKey, + sdk_name: "langfuse-js" + } + }); + const url = `${this.baseUrl}/api/public/ingestion`; + const fetchOptions = this._getFetchOptions({ + method: "POST", + body: payload + }); + const requestPromise = this.fetchWithRetry(url, fetchOptions).then(() => done()).catch((err) => { + done(err); + }); + this.pendingIngestionPromises[promiseUUID] = requestPromise; + requestPromise.finally(() => { + delete this.pendingIngestionPromises[promiseUUID]; + }); + } + processQueueItems(queue, MAX_MSG_SIZE, BATCH_SIZE_LIMIT) { + let totalSize = 0; + const processedItems = []; + const remainingItems = []; + for (let i = 0; i < queue.length; i++) { + try { + const itemSize = new Blob([JSON.stringify(queue[i])]).size; + if (itemSize > MAX_MSG_SIZE) { + console.warn(`Item exceeds size limit (size: ${itemSize}), dropping item.`); + continue; + } + if (totalSize + itemSize >= BATCH_SIZE_LIMIT) { + console.debug(`hit batch size limit (size: ${totalSize + itemSize})`); + remainingItems.push(...queue.slice(i)); + console.log(`Remaining items: ${remainingItems.length}`); + console.log(`processes items: ${processedItems.length}`); + break; + } + totalSize += itemSize; + processedItems.push(queue[i]); + } catch (error) { + this._events.emit("error", error); + remainingItems.push(...queue.slice(i)); + break; + } + } + return { + processedItems, + remainingItems + }; + } + _getFetchOptions(p) { + const fetchOptions = { + method: p.method, + headers: { + "Content-Type": "application/json", + "X-Langfuse-Sdk-Name": "langfuse-js", + "X-Langfuse-Sdk-Version": this.getLibraryVersion(), + "X-Langfuse-Sdk-Variant": this.getLibraryId(), + "X-Langfuse-Sdk-Integration": this.sdkIntegration, + "X-Langfuse-Public-Key": this.publicKey, + ...this.additionalHeaders, + ...this.constructAuthorizationHeader(this.publicKey, this.secretKey) + }, + body: p.body, + ...p.fetchTimeout !== void 0 ? { + signal: AbortSignal.timeout(p.fetchTimeout) + } : {} + }; + return fetchOptions; + } + constructAuthorizationHeader(publicKey, secretKey) { + if (secretKey === void 0) { + return { + Authorization: "Bearer " + publicKey + }; + } else { + const encodedCredentials = typeof btoa === "function" ? ( + // btoa() is available, the code is running in a browser or edge environment + btoa(publicKey + ":" + secretKey) + ) : ( + // btoa() is not available, the code is running in Node.js + Buffer.from(publicKey + ":" + secretKey).toString("base64") + ); + return { + Authorization: "Basic " + encodedCredentials + }; + } + } + async fetchWithRetry(url, options, retryOptions) { + AbortSignal.timeout ??= function timeout(ms) { + const ctrl = new AbortController(); + setTimeout(() => ctrl.abort(), ms); + return ctrl.signal; + }; + return await retriable(async () => { + let res = null; + try { + res = await this.fetch(url, { + signal: AbortSignal.timeout(this.requestTimeout), + ...options + }); + } catch (e) { + throw new LangfuseFetchNetworkError(e); + } + if (res.status < 200 || res.status >= 400) { + const body = await res.json(); + throw new LangfuseFetchHttpError(res, JSON.stringify(body)); + } + const returnBody = await res.json(); + if (res.status === 207 && returnBody.errors.length > 0) { + throw new LangfuseFetchHttpError(res, JSON.stringify(returnBody.errors)); + } + return res; + }, { + ...this._retryOptions, + ...retryOptions + }, (string) => this._events.emit("retry", string + ", " + url + ", " + JSON.stringify(options))); + } + async fetchAndLogErrors(url, options) { + const res = await this.fetch(url, options); + const data = res.status === 429 ? await res.text() : await res.json(); + if (res.status < 200 || res.status >= 400) { + logIngestionError(new LangfuseFetchHttpError(res, JSON.stringify(data))); + } + return data; + } + async shutdownAsync() { + clearTimeout(this._flushTimer); + try { + await this.flushAsync(); + await Promise.all(Object.values(this.pendingIngestionPromises).map((x) => x.catch(() => { + }))); + await this.flushAsync(); + } catch (e) { + console.error("[Langfuse SDK] Error while shutting down Langfuse", e); + } + } + async _exportLocalEvents(projectId) { + if (this.isLocalEventExportEnabled) { + clearTimeout(this._flushTimer); + await this.flushAsync(); + const events = this.localEventExportMap.get(projectId) ?? []; + this.localEventExportMap.delete(projectId); + return events; + } else { + this._events.emit("error", "Local event exports are disabled, but _exportLocalEvents() was called."); + return []; + } + } + shutdown() { + console.warn("shutdown() is deprecated. It does not wait for all events to be processed. Please use shutdownAsync() instead."); + void this.shutdownAsync(); + } + async awaitAllQueuedAndPendingRequests() { + clearTimeout(this._flushTimer); + await this.flushAsync(); + await Promise.all(Object.values(this.pendingIngestionPromises)); + } +}; +var LangfuseCore = class extends LangfuseCoreStateless { + constructor(params) { + const { + publicKey, + secretKey, + enabled, + _isLocalEventExportEnabled + } = params; + let isObservabilityEnabled = enabled === false ? false : true; + if (_isLocalEventExportEnabled) { + isObservabilityEnabled = true; + } else if (!secretKey) { + isObservabilityEnabled = false; + if (enabled !== false) { + console.warn("Langfuse secret key was not passed to constructor or not set as 'LANGFUSE_SECRET_KEY' environment variable. No observability data will be sent to Langfuse."); + } + } else if (!publicKey) { + isObservabilityEnabled = false; + if (enabled !== false) { + console.warn("Langfuse public key was not passed to constructor or not set as 'LANGFUSE_PUBLIC_KEY' environment variable. No observability data will be sent to Langfuse."); + } + } + super({ + ...params, + enabled: isObservabilityEnabled + }); + this._promptCache = new LangfusePromptCache(); + } + trace(body) { + const id = this.traceStateless(body ?? {}); + const t = new LangfuseTraceClient(this, id); + if (getEnv("DEFER") && body) { + try { + const deferRuntime = getEnv("__deferRuntime"); + if (deferRuntime) { + deferRuntime.langfuseTraces([{ + id, + name: body.name || "", + url: t.getTraceUrl() + }]); + } + } catch { + } + } + return t; + } + span(body) { + const traceId = body.traceId || this.traceStateless({ + name: body.name + }); + const id = this.spanStateless({ + ...body, + traceId + }); + return new LangfuseSpanClient(this, id, traceId); + } + generation(body) { + const traceId = body.traceId || this.traceStateless({ + name: body.name + }); + const id = this.generationStateless({ + ...body, + traceId + }); + return new LangfuseGenerationClient(this, id, traceId); + } + event(body) { + const traceId = body.traceId || this.traceStateless({ + name: body.name + }); + const id = this.eventStateless({ + ...body, + traceId + }); + return new LangfuseEventClient(this, id, traceId); + } + score(body) { + this.scoreStateless(body); + return this; + } + async getDataset(name, options) { + const dataset = await this._getDataset(name); + const items = []; + let page = 1; + while (true) { + const itemsResponse = await this._getDatasetItems({ + datasetName: name, + limit: options?.fetchItemsPageSize ?? 50, + page + }); + items.push(...itemsResponse.data); + if (itemsResponse.meta.totalPages <= page) { + break; + } + page++; + } + const returnDataset = { + ...dataset, + description: dataset.description ?? void 0, + metadata: dataset.metadata ?? void 0, + items: items.map((item) => ({ + ...item, + link: async (obj, runName, runArgs) => { + await this.awaitAllQueuedAndPendingRequests(); + const data = await this.createDatasetRunItem({ + runName, + datasetItemId: item.id, + observationId: obj.observationId, + traceId: obj.traceId, + runDescription: runArgs?.description, + metadata: runArgs?.metadata + }); + return data; + } + })) + }; + return returnDataset; + } + async createPrompt(body) { + const labels = body.labels ?? []; + const promptResponse = body.type === "chat" ? await this.createPromptStateless({ + ...body, + prompt: body.prompt.map((item) => { + if ("type" in item && item.type === ChatMessageType.Placeholder) { + return { + type: ChatMessageType.Placeholder, + name: item.name + }; + } else { + return { + type: ChatMessageType.ChatMessage, + ...item + }; + } + }), + labels: body.isActive ? [.../* @__PURE__ */ new Set([...labels, "production"])] : labels + // backward compatibility for isActive + }) : await this.createPromptStateless({ + ...body, + type: body.type ?? "text", + labels: body.isActive ? [.../* @__PURE__ */ new Set([...labels, "production"])] : labels + // backward compatibility for isActive + }); + if (promptResponse.type === "chat") { + return new ChatPromptClient(promptResponse); + } + return new TextPromptClient(promptResponse); + } + async updatePrompt(body) { + const newPrompt = await this.updatePromptStateless(body); + this._promptCache.invalidate(body.name); + return newPrompt; + } + async getPrompt(name, version2, options) { + const cacheKey = this._getPromptCacheKey({ + name, + version: version2, + label: options?.label + }); + const cachedPrompt = this._promptCache.getIncludingExpired(cacheKey); + if (!cachedPrompt || options?.cacheTtlSeconds === 0) { + try { + return await this._fetchPromptAndUpdateCache({ + name, + version: version2, + label: options?.label, + cacheTtlSeconds: options?.cacheTtlSeconds, + maxRetries: options?.maxRetries, + fetchTimeout: options?.fetchTimeoutMs + }); + } catch (err) { + if (options?.fallback) { + const sharedFallbackParams = { + name, + version: version2 ?? 0, + labels: options.label ? [options.label] : [], + cacheTtlSeconds: options?.cacheTtlSeconds, + config: {}, + tags: [] + }; + if (options.type === "chat") { + return new ChatPromptClient({ + ...sharedFallbackParams, + type: "chat", + prompt: options.fallback.map((msg) => ({ + type: ChatMessageType.ChatMessage, + ...msg + })) + }, true); + } else { + return new TextPromptClient({ + ...sharedFallbackParams, + type: "text", + prompt: options.fallback + }, true); + } + } + throw err; + } + } + if (cachedPrompt.isExpired) { + if (!this._promptCache.isRefreshing(cacheKey)) { + const refreshPromptPromise = this._fetchPromptAndUpdateCache({ + name, + version: version2, + label: options?.label, + cacheTtlSeconds: options?.cacheTtlSeconds, + maxRetries: options?.maxRetries, + fetchTimeout: options?.fetchTimeoutMs + }).catch(() => { + console.warn(`Failed to refresh prompt cache '${cacheKey}', stale cache will be used until next refresh succeeds.`); + }); + this._promptCache.addRefreshingPromise(cacheKey, refreshPromptPromise); + } + return cachedPrompt.value; + } + return cachedPrompt.value; + } + _getPromptCacheKey(params) { + const { + name, + version: version2, + label + } = params; + const parts = [name]; + if (version2 !== void 0) { + parts.push("version:" + version2.toString()); + } else if (label !== void 0) { + parts.push("label:" + label); + } else { + parts.push("label:production"); + } + return parts.join("-"); + } + async _fetchPromptAndUpdateCache(params) { + const cacheKey = this._getPromptCacheKey(params); + try { + const { + name, + version: version2, + cacheTtlSeconds, + label, + maxRetries, + fetchTimeout + } = params; + const { + data, + fetchResult + } = await this.getPromptStateless(name, version2, label, maxRetries, fetchTimeout); + if (fetchResult === "failure") { + throw Error(data.message ?? "Internal error while fetching prompt"); + } + let prompt2; + if (data.type === "chat") { + prompt2 = new ChatPromptClient(data); + } else { + prompt2 = new TextPromptClient(data); + } + this._promptCache.set(cacheKey, prompt2, cacheTtlSeconds); + return prompt2; + } catch (error) { + console.error(`[Langfuse SDK] Error while fetching prompt '${cacheKey}':`, error); + throw error; + } + } + async fetchMedia(id) { + return await this._fetchMedia(id); + } + /** + * Replaces the media reference strings in an object with base64 data URIs for the media content. + * + * This method recursively traverses an object (up to a maximum depth of 10) looking for media reference strings + * in the format "@@@langfuseMedia:...@@@". When found, it fetches the actual media content using the provided + * Langfuse client and replaces the reference string with a base64 data URI. + * + * If fetching media content fails for a reference string, a warning is logged and the reference string is left unchanged. + * + * @param params - Configuration object + * @param params.obj - The object to process. Can be a primitive value, array, or nested object + * @param params.langfuseClient - Langfuse client instance used to fetch media content + * @param params.resolveWith - The representation of the media content to replace the media reference string with. Currently only "base64DataUri" is supported. + * @param params.maxDepth - Optional. Default is 10. The maximum depth to traverse the object. + * + * @returns A deep copy of the input object with all media references replaced with base64 data URIs where possible + * + * @example + * ```typescript + * const obj = { + * image: "@@@langfuseMedia:type=image/jpeg|id=123|source=bytes@@@", + * nested: { + * pdf: "@@@langfuseMedia:type=application/pdf|id=456|source=bytes@@@" + * } + * }; + * + * const result = await LangfuseMedia.resolveMediaReferences({ + * obj, + * langfuseClient + * }); + * + * // Result: + * // { + * // image: "data:image/jpeg;base64,/9j/4AAQSkZJRg...", + * // nested: { + * // pdf: "data:application/pdf;base64,JVBERi0xLjcK..." + * // } + * // } + * ``` + */ + async resolveMediaReferences(params) { + const { + obj, + ...rest + } = params; + return LangfuseMedia.resolveMediaReferences({ + ...rest, + langfuseClient: this, + obj + }); + } + _updateSpan(body) { + this.updateSpanStateless(body); + return this; + } + _updateGeneration(body) { + this.updateGenerationStateless(body); + return this; + } +}; +var LangfuseObjectClient = class { + constructor({ + client, + id, + traceId, + observationId + }) { + this.client = client; + this.id = id; + this.traceId = traceId; + this.observationId = observationId; + } + event(body) { + return this.client.event({ + ...body, + traceId: this.traceId, + parentObservationId: this.observationId + }); + } + span(body) { + return this.client.span({ + ...body, + traceId: this.traceId, + parentObservationId: this.observationId + }); + } + generation(body) { + return this.client.generation({ + ...body, + traceId: this.traceId, + parentObservationId: this.observationId + }); + } + score(body) { + this.client.score({ + ...body, + traceId: this.traceId, + observationId: this.observationId + }); + return this; + } + getTraceUrl() { + return `${this.client.baseUrl}/trace/${this.traceId}`; + } +}; +var LangfuseTraceClient = class extends LangfuseObjectClient { + constructor(client, traceId) { + super({ + client, + id: traceId, + traceId, + observationId: null + }); + } + update(body) { + this.client.trace({ + ...body, + id: this.id + }); + return this; + } +}; +var LangfuseObservationClient = class extends LangfuseObjectClient { + constructor(client, id, traceId) { + super({ + client, + id, + traceId, + observationId: id + }); + } +}; +var LangfuseSpanClient = class extends LangfuseObservationClient { + constructor(client, id, traceId) { + super(client, id, traceId); + } + update(body) { + this.client._updateSpan({ + ...body, + id: this.id, + traceId: this.traceId + }); + return this; + } + end(body) { + this.client._updateSpan({ + ...body, + id: this.id, + traceId: this.traceId, + endTime: /* @__PURE__ */ new Date() + }); + return this; + } +}; +var LangfuseGenerationClient = class extends LangfuseObservationClient { + constructor(client, id, traceId) { + super(client, id, traceId); + } + update(body) { + this.client._updateGeneration({ + ...body, + id: this.id, + traceId: this.traceId + }); + return this; + } + end(body) { + this.client._updateGeneration({ + ...body, + id: this.id, + traceId: this.traceId, + endTime: /* @__PURE__ */ new Date() + }); + return this; + } +}; +var LangfuseEventClient = class extends LangfuseObservationClient { + constructor(client, id, traceId) { + super(client, id, traceId); + } +}; + +// node_modules/langfuse/lib/index.mjs +var cookieStore = { + getItem(key) { + try { + const nameEQ = key + "="; + const ca = document.cookie.split(";"); + for (let i = 0; i < ca.length; i++) { + let c = ca[i]; + while (c.charAt(0) == " ") { + c = c.substring(1, c.length); + } + if (c.indexOf(nameEQ) === 0) { + return decodeURIComponent(c.substring(nameEQ.length, c.length)); + } + } + } catch (err) { + } + return null; + }, + setItem(key, value) { + try { + const cdomain = "", expires = "", secure = ""; + const new_cookie_val = key + "=" + encodeURIComponent(value) + expires + "; path=/" + cdomain + secure; + document.cookie = new_cookie_val; + } catch (err) { + return; + } + }, + removeItem(name) { + try { + cookieStore.setItem(name, ""); + } catch (err) { + return; + } + }, + clear() { + document.cookie = ""; + }, + getAllKeys() { + const ca = document.cookie.split(";"); + const keys = []; + for (let i = 0; i < ca.length; i++) { + let c = ca[i]; + while (c.charAt(0) == " ") { + c = c.substring(1, c.length); + } + keys.push(c.split("=")[0]); + } + return keys; + } +}; +var createStorageLike = (store) => { + return { + getItem(key) { + return store.getItem(key); + }, + setItem(key, value) { + store.setItem(key, value); + }, + removeItem(key) { + store.removeItem(key); + }, + clear() { + store.clear(); + }, + getAllKeys() { + const keys = []; + for (const key in localStorage) { + keys.push(key); + } + return keys; + } + }; +}; +var checkStoreIsSupported = (storage, key = "__mplssupport__") => { + if (!window) { + return false; + } + try { + const val = "xyz"; + storage.setItem(key, val); + if (storage.getItem(key) !== val) { + return false; + } + storage.removeItem(key); + return true; + } catch (err) { + return false; + } +}; +var localStore = void 0; +var sessionStore = void 0; +var createMemoryStorage = () => { + const _cache = {}; + const store = { + getItem(key) { + return _cache[key]; + }, + setItem(key, value) { + _cache[key] = value !== null ? value : void 0; + }, + removeItem(key) { + delete _cache[key]; + }, + clear() { + for (const key in _cache) { + delete _cache[key]; + } + }, + getAllKeys() { + const keys = []; + for (const key in _cache) { + keys.push(key); + } + return keys; + } + }; + return store; +}; +var getStorage = (type, window2) => { + if (typeof window2 !== void 0 && window2) { + if (!localStorage) { + const _localStore = createStorageLike(window2.localStorage); + localStore = checkStoreIsSupported(_localStore) ? _localStore : void 0; + } + if (!sessionStore) { + const _sessionStore = createStorageLike(window2.sessionStorage); + sessionStore = checkStoreIsSupported(_sessionStore) ? _sessionStore : void 0; + } + } + switch (type) { + case "cookie": + return cookieStore || localStore || sessionStore || createMemoryStorage(); + case "localStorage": + return localStore || sessionStore || createMemoryStorage(); + case "sessionStorage": + return sessionStore || createMemoryStorage(); + case "memory": + return createMemoryStorage(); + default: + return createMemoryStorage(); + } +}; +var ContentType; +(function(ContentType2) { + ContentType2["Json"] = "application/json"; + ContentType2["FormData"] = "multipart/form-data"; + ContentType2["UrlEncoded"] = "application/x-www-form-urlencoded"; + ContentType2["Text"] = "text/plain"; +})(ContentType || (ContentType = {})); +var HttpClient = class { + constructor(apiConfig = {}) { + this.baseUrl = ""; + this.securityData = null; + this.abortControllers = /* @__PURE__ */ new Map(); + this.customFetch = (...fetchParams) => fetch(...fetchParams); + this.baseApiParams = { + credentials: "same-origin", + headers: {}, + redirect: "follow", + referrerPolicy: "no-referrer" + }; + this.setSecurityData = (data) => { + this.securityData = data; + }; + this.contentFormatters = { + [ContentType.Json]: (input) => input !== null && (typeof input === "object" || typeof input === "string") ? JSON.stringify(input) : input, + [ContentType.Text]: (input) => input !== null && typeof input !== "string" ? JSON.stringify(input) : input, + [ContentType.FormData]: (input) => Object.keys(input || {}).reduce((formData, key) => { + const property = input[key]; + formData.append(key, property instanceof Blob ? property : typeof property === "object" && property !== null ? JSON.stringify(property) : `${property}`); + return formData; + }, new FormData()), + [ContentType.UrlEncoded]: (input) => this.toQueryString(input) + }; + this.createAbortSignal = (cancelToken) => { + if (this.abortControllers.has(cancelToken)) { + const abortController2 = this.abortControllers.get(cancelToken); + if (abortController2) { + return abortController2.signal; + } + return void 0; + } + const abortController = new AbortController(); + this.abortControllers.set(cancelToken, abortController); + return abortController.signal; + }; + this.abortRequest = (cancelToken) => { + const abortController = this.abortControllers.get(cancelToken); + if (abortController) { + abortController.abort(); + this.abortControllers.delete(cancelToken); + } + }; + this.request = async ({ + body, + secure, + path, + type, + query, + format, + baseUrl, + cancelToken, + ...params + }) => { + const secureParams = (typeof secure === "boolean" ? secure : this.baseApiParams.secure) && this.securityWorker && await this.securityWorker(this.securityData) || {}; + const requestParams = this.mergeRequestParams(params, secureParams); + const queryString = query && this.toQueryString(query); + const payloadFormatter = this.contentFormatters[type || ContentType.Json]; + const responseFormat = format || requestParams.format; + return this.customFetch(`${baseUrl || this.baseUrl || ""}${path}${queryString ? `?${queryString}` : ""}`, { + ...requestParams, + headers: { + ...requestParams.headers || {}, + ...type && type !== ContentType.FormData ? { + "Content-Type": type + } : {} + }, + signal: (cancelToken ? this.createAbortSignal(cancelToken) : requestParams.signal) || null, + body: typeof body === "undefined" || body === null ? null : payloadFormatter(body) + }).then(async (response) => { + const r = response.clone(); + r.data = null; + r.error = null; + const data = !responseFormat ? r : await response[responseFormat]().then((data2) => { + if (r.ok) { + r.data = data2; + } else { + r.error = data2; + } + return r; + }).catch((e) => { + r.error = e; + return r; + }); + if (cancelToken) { + this.abortControllers.delete(cancelToken); + } + if (!response.ok) throw data; + return data.data; + }); + }; + Object.assign(this, apiConfig); + } + encodeQueryParam(key, value) { + const encodedKey = encodeURIComponent(key); + return `${encodedKey}=${encodeURIComponent(typeof value === "number" ? value : `${value}`)}`; + } + addQueryParam(query, key) { + return this.encodeQueryParam(key, query[key]); + } + addArrayQueryParam(query, key) { + const value = query[key]; + return value.map((v) => this.encodeQueryParam(key, v)).join("&"); + } + toQueryString(rawQuery) { + const query = rawQuery || {}; + const keys = Object.keys(query).filter((key) => "undefined" !== typeof query[key]); + return keys.map((key) => Array.isArray(query[key]) ? this.addArrayQueryParam(query, key) : this.addQueryParam(query, key)).join("&"); + } + addQueryParams(rawQuery) { + const queryString = this.toQueryString(rawQuery); + return queryString ? `?${queryString}` : ""; + } + mergeRequestParams(params1, params2) { + return { + ...this.baseApiParams, + ...params1, + ...params2 || {}, + headers: { + ...this.baseApiParams.headers || {}, + ...params1.headers || {}, + ...params2 && params2.headers || {} + } + }; + } +}; +var LangfusePublicApi = class extends HttpClient { + constructor() { + super(...arguments); + this.api = { + /** + * @description Add an item to an annotation queue + * + * @tags AnnotationQueues + * @name AnnotationQueuesCreateQueueItem + * @request POST:/api/public/annotation-queues/{queueId}/items + * @secure + */ + annotationQueuesCreateQueueItem: (queueId, data, params = {}) => this.request({ + path: `/api/public/annotation-queues/${queueId}/items`, + method: "POST", + body: data, + secure: true, + type: ContentType.Json, + format: "json", + ...params + }), + /** + * @description Remove an item from an annotation queue + * + * @tags AnnotationQueues + * @name AnnotationQueuesDeleteQueueItem + * @request DELETE:/api/public/annotation-queues/{queueId}/items/{itemId} + * @secure + */ + annotationQueuesDeleteQueueItem: (queueId, itemId, params = {}) => this.request({ + path: `/api/public/annotation-queues/${queueId}/items/${itemId}`, + method: "DELETE", + secure: true, + format: "json", + ...params + }), + /** + * @description Get an annotation queue by ID + * + * @tags AnnotationQueues + * @name AnnotationQueuesGetQueue + * @request GET:/api/public/annotation-queues/{queueId} + * @secure + */ + annotationQueuesGetQueue: (queueId, params = {}) => this.request({ + path: `/api/public/annotation-queues/${queueId}`, + method: "GET", + secure: true, + format: "json", + ...params + }), + /** + * @description Get a specific item from an annotation queue + * + * @tags AnnotationQueues + * @name AnnotationQueuesGetQueueItem + * @request GET:/api/public/annotation-queues/{queueId}/items/{itemId} + * @secure + */ + annotationQueuesGetQueueItem: (queueId, itemId, params = {}) => this.request({ + path: `/api/public/annotation-queues/${queueId}/items/${itemId}`, + method: "GET", + secure: true, + format: "json", + ...params + }), + /** + * @description Get items for a specific annotation queue + * + * @tags AnnotationQueues + * @name AnnotationQueuesListQueueItems + * @request GET:/api/public/annotation-queues/{queueId}/items + * @secure + */ + annotationQueuesListQueueItems: ({ + queueId, + ...query + }, params = {}) => this.request({ + path: `/api/public/annotation-queues/${queueId}/items`, + method: "GET", + query, + secure: true, + format: "json", + ...params + }), + /** + * @description Get all annotation queues + * + * @tags AnnotationQueues + * @name AnnotationQueuesListQueues + * @request GET:/api/public/annotation-queues + * @secure + */ + annotationQueuesListQueues: (query, params = {}) => this.request({ + path: `/api/public/annotation-queues`, + method: "GET", + query, + secure: true, + format: "json", + ...params + }), + /** + * @description Update an annotation queue item + * + * @tags AnnotationQueues + * @name AnnotationQueuesUpdateQueueItem + * @request PATCH:/api/public/annotation-queues/{queueId}/items/{itemId} + * @secure + */ + annotationQueuesUpdateQueueItem: (queueId, itemId, data, params = {}) => this.request({ + path: `/api/public/annotation-queues/${queueId}/items/${itemId}`, + method: "PATCH", + body: data, + secure: true, + type: ContentType.Json, + format: "json", + ...params + }), + /** + * @description Create a comment. Comments may be attached to different object types (trace, observation, session, prompt). + * + * @tags Comments + * @name CommentsCreate + * @request POST:/api/public/comments + * @secure + */ + commentsCreate: (data, params = {}) => this.request({ + path: `/api/public/comments`, + method: "POST", + body: data, + secure: true, + type: ContentType.Json, + format: "json", + ...params + }), + /** + * @description Get all comments + * + * @tags Comments + * @name CommentsGet + * @request GET:/api/public/comments + * @secure + */ + commentsGet: (query, params = {}) => this.request({ + path: `/api/public/comments`, + method: "GET", + query, + secure: true, + format: "json", + ...params + }), + /** + * @description Get a comment by id + * + * @tags Comments + * @name CommentsGetById + * @request GET:/api/public/comments/{commentId} + * @secure + */ + commentsGetById: (commentId, params = {}) => this.request({ + path: `/api/public/comments/${commentId}`, + method: "GET", + secure: true, + format: "json", + ...params + }), + /** + * @description Create a dataset item + * + * @tags DatasetItems + * @name DatasetItemsCreate + * @request POST:/api/public/dataset-items + * @secure + */ + datasetItemsCreate: (data, params = {}) => this.request({ + path: `/api/public/dataset-items`, + method: "POST", + body: data, + secure: true, + type: ContentType.Json, + format: "json", + ...params + }), + /** + * @description Delete a dataset item and all its run items. This action is irreversible. + * + * @tags DatasetItems + * @name DatasetItemsDelete + * @request DELETE:/api/public/dataset-items/{id} + * @secure + */ + datasetItemsDelete: (id, params = {}) => this.request({ + path: `/api/public/dataset-items/${id}`, + method: "DELETE", + secure: true, + format: "json", + ...params + }), + /** + * @description Get a dataset item + * + * @tags DatasetItems + * @name DatasetItemsGet + * @request GET:/api/public/dataset-items/{id} + * @secure + */ + datasetItemsGet: (id, params = {}) => this.request({ + path: `/api/public/dataset-items/${id}`, + method: "GET", + secure: true, + format: "json", + ...params + }), + /** + * @description Get dataset items + * + * @tags DatasetItems + * @name DatasetItemsList + * @request GET:/api/public/dataset-items + * @secure + */ + datasetItemsList: (query, params = {}) => this.request({ + path: `/api/public/dataset-items`, + method: "GET", + query, + secure: true, + format: "json", + ...params + }), + /** + * @description Create a dataset run item + * + * @tags DatasetRunItems + * @name DatasetRunItemsCreate + * @request POST:/api/public/dataset-run-items + * @secure + */ + datasetRunItemsCreate: (data, params = {}) => this.request({ + path: `/api/public/dataset-run-items`, + method: "POST", + body: data, + secure: true, + type: ContentType.Json, + format: "json", + ...params + }), + /** + * @description List dataset run items + * + * @tags DatasetRunItems + * @name DatasetRunItemsList + * @request GET:/api/public/dataset-run-items + * @secure + */ + datasetRunItemsList: (query, params = {}) => this.request({ + path: `/api/public/dataset-run-items`, + method: "GET", + query, + secure: true, + ...params + }), + /** + * @description Create a dataset + * + * @tags Datasets + * @name DatasetsCreate + * @request POST:/api/public/v2/datasets + * @secure + */ + datasetsCreate: (data, params = {}) => this.request({ + path: `/api/public/v2/datasets`, + method: "POST", + body: data, + secure: true, + type: ContentType.Json, + format: "json", + ...params + }), + /** + * @description Delete a dataset run and all its run items. This action is irreversible. + * + * @tags Datasets + * @name DatasetsDeleteRun + * @request DELETE:/api/public/datasets/{datasetName}/runs/{runName} + * @secure + */ + datasetsDeleteRun: (datasetName, runName, params = {}) => this.request({ + path: `/api/public/datasets/${datasetName}/runs/${runName}`, + method: "DELETE", + secure: true, + format: "json", + ...params + }), + /** + * @description Get a dataset + * + * @tags Datasets + * @name DatasetsGet + * @request GET:/api/public/v2/datasets/{datasetName} + * @secure + */ + datasetsGet: (datasetName, params = {}) => this.request({ + path: `/api/public/v2/datasets/${datasetName}`, + method: "GET", + secure: true, + format: "json", + ...params + }), + /** + * @description Get a dataset run and its items + * + * @tags Datasets + * @name DatasetsGetRun + * @request GET:/api/public/datasets/{datasetName}/runs/{runName} + * @secure + */ + datasetsGetRun: (datasetName, runName, params = {}) => this.request({ + path: `/api/public/datasets/${datasetName}/runs/${runName}`, + method: "GET", + secure: true, + format: "json", + ...params + }), + /** + * @description Get dataset runs + * + * @tags Datasets + * @name DatasetsGetRuns + * @request GET:/api/public/datasets/{datasetName}/runs + * @secure + */ + datasetsGetRuns: ({ + datasetName, + ...query + }, params = {}) => this.request({ + path: `/api/public/datasets/${datasetName}/runs`, + method: "GET", + query, + secure: true, + format: "json", + ...params + }), + /** + * @description Get all datasets + * + * @tags Datasets + * @name DatasetsList + * @request GET:/api/public/v2/datasets + * @secure + */ + datasetsList: (query, params = {}) => this.request({ + path: `/api/public/v2/datasets`, + method: "GET", + query, + secure: true, + format: "json", + ...params + }), + /** + * @description Check health of API and database + * + * @tags Health + * @name HealthHealth + * @request GET:/api/public/health + */ + healthHealth: (params = {}) => this.request({ + path: `/api/public/health`, + method: "GET", + format: "json", + ...params + }), + /** + * @description Batched ingestion for Langfuse Tracing. If you want to use tracing via the API, such as to build your own Langfuse client implementation, this is the only API route you need to implement. Within each batch, there can be multiple events. Each event has a type, an id, a timestamp, metadata and a body. Internally, we refer to this as the "event envelope" as it tells us something about the event but not the trace. We use the event id within this envelope to deduplicate messages to avoid processing the same event twice, i.e. the event id should be unique per request. The event.body.id is the ID of the actual trace and will be used for updates and will be visible within the Langfuse App. I.e. if you want to update a trace, you'd use the same body id, but separate event IDs. Notes: - Introduction to data model: https://langfuse.com/docs/tracing-data-model - Batch sizes are limited to 3.5 MB in total. You need to adjust the number of events per batch accordingly. - The API does not return a 4xx status code for input errors. Instead, it responds with a 207 status code, which includes a list of the encountered errors. + * + * @tags Ingestion + * @name IngestionBatch + * @request POST:/api/public/ingestion + * @secure + */ + ingestionBatch: (data, params = {}) => this.request({ + path: `/api/public/ingestion`, + method: "POST", + body: data, + secure: true, + type: ContentType.Json, + format: "json", + ...params + }), + /** + * @description Get a media record + * + * @tags Media + * @name MediaGet + * @request GET:/api/public/media/{mediaId} + * @secure + */ + mediaGet: (mediaId, params = {}) => this.request({ + path: `/api/public/media/${mediaId}`, + method: "GET", + secure: true, + format: "json", + ...params + }), + /** + * @description Get a presigned upload URL for a media record + * + * @tags Media + * @name MediaGetUploadUrl + * @request POST:/api/public/media + * @secure + */ + mediaGetUploadUrl: (data, params = {}) => this.request({ + path: `/api/public/media`, + method: "POST", + body: data, + secure: true, + type: ContentType.Json, + format: "json", + ...params + }), + /** + * @description Patch a media record + * + * @tags Media + * @name MediaPatch + * @request PATCH:/api/public/media/{mediaId} + * @secure + */ + mediaPatch: (mediaId, data, params = {}) => this.request({ + path: `/api/public/media/${mediaId}`, + method: "PATCH", + body: data, + secure: true, + type: ContentType.Json, + ...params + }), + /** + * @description Get metrics from the Langfuse project using a query object + * + * @tags Metrics + * @name MetricsMetrics + * @request GET:/api/public/metrics + * @secure + */ + metricsMetrics: (query, params = {}) => this.request({ + path: `/api/public/metrics`, + method: "GET", + query, + secure: true, + format: "json", + ...params + }), + /** + * @description Create a model + * + * @tags Models + * @name ModelsCreate + * @request POST:/api/public/models + * @secure + */ + modelsCreate: (data, params = {}) => this.request({ + path: `/api/public/models`, + method: "POST", + body: data, + secure: true, + type: ContentType.Json, + format: "json", + ...params + }), + /** + * @description Delete a model. Cannot delete models managed by Langfuse. You can create your own definition with the same modelName to override the definition though. + * + * @tags Models + * @name ModelsDelete + * @request DELETE:/api/public/models/{id} + * @secure + */ + modelsDelete: (id, params = {}) => this.request({ + path: `/api/public/models/${id}`, + method: "DELETE", + secure: true, + ...params + }), + /** + * @description Get a model + * + * @tags Models + * @name ModelsGet + * @request GET:/api/public/models/{id} + * @secure + */ + modelsGet: (id, params = {}) => this.request({ + path: `/api/public/models/${id}`, + method: "GET", + secure: true, + format: "json", + ...params + }), + /** + * @description Get all models + * + * @tags Models + * @name ModelsList + * @request GET:/api/public/models + * @secure + */ + modelsList: (query, params = {}) => this.request({ + path: `/api/public/models`, + method: "GET", + query, + secure: true, + format: "json", + ...params + }), + /** + * @description Get a observation + * + * @tags Observations + * @name ObservationsGet + * @request GET:/api/public/observations/{observationId} + * @secure + */ + observationsGet: (observationId, params = {}) => this.request({ + path: `/api/public/observations/${observationId}`, + method: "GET", + secure: true, + format: "json", + ...params + }), + /** + * @description Get a list of observations + * + * @tags Observations + * @name ObservationsGetMany + * @request GET:/api/public/observations + * @secure + */ + observationsGetMany: (query, params = {}) => this.request({ + path: `/api/public/observations`, + method: "GET", + query, + secure: true, + format: "json", + ...params + }), + /** + * @description Get all memberships for the organization associated with the API key (requires organization-scoped API key) + * + * @tags Organizations + * @name OrganizationsGetOrganizationMemberships + * @request GET:/api/public/organizations/memberships + * @secure + */ + organizationsGetOrganizationMemberships: (params = {}) => this.request({ + path: `/api/public/organizations/memberships`, + method: "GET", + secure: true, + format: "json", + ...params + }), + /** + * @description Get all projects for the organization associated with the API key (requires organization-scoped API key) + * + * @tags Organizations + * @name OrganizationsGetOrganizationProjects + * @request GET:/api/public/organizations/projects + * @secure + */ + organizationsGetOrganizationProjects: (params = {}) => this.request({ + path: `/api/public/organizations/projects`, + method: "GET", + secure: true, + format: "json", + ...params + }), + /** + * @description Get all memberships for a specific project (requires organization-scoped API key) + * + * @tags Organizations + * @name OrganizationsGetProjectMemberships + * @request GET:/api/public/projects/{projectId}/memberships + * @secure + */ + organizationsGetProjectMemberships: (projectId, params = {}) => this.request({ + path: `/api/public/projects/${projectId}/memberships`, + method: "GET", + secure: true, + format: "json", + ...params + }), + /** + * @description Create or update a membership for the organization associated with the API key (requires organization-scoped API key) + * + * @tags Organizations + * @name OrganizationsUpdateOrganizationMembership + * @request PUT:/api/public/organizations/memberships + * @secure + */ + organizationsUpdateOrganizationMembership: (data, params = {}) => this.request({ + path: `/api/public/organizations/memberships`, + method: "PUT", + body: data, + secure: true, + type: ContentType.Json, + format: "json", + ...params + }), + /** + * @description Create or update a membership for a specific project (requires organization-scoped API key). The user must already be a member of the organization. + * + * @tags Organizations + * @name OrganizationsUpdateProjectMembership + * @request PUT:/api/public/projects/{projectId}/memberships + * @secure + */ + organizationsUpdateProjectMembership: (projectId, data, params = {}) => this.request({ + path: `/api/public/projects/${projectId}/memberships`, + method: "PUT", + body: data, + secure: true, + type: ContentType.Json, + format: "json", + ...params + }), + /** + * @description Create a new project (requires organization-scoped API key) + * + * @tags Projects + * @name ProjectsCreate + * @request POST:/api/public/projects + * @secure + */ + projectsCreate: (data, params = {}) => this.request({ + path: `/api/public/projects`, + method: "POST", + body: data, + secure: true, + type: ContentType.Json, + format: "json", + ...params + }), + /** + * @description Create a new API key for a project (requires organization-scoped API key) + * + * @tags Projects + * @name ProjectsCreateApiKey + * @request POST:/api/public/projects/{projectId}/apiKeys + * @secure + */ + projectsCreateApiKey: (projectId, data, params = {}) => this.request({ + path: `/api/public/projects/${projectId}/apiKeys`, + method: "POST", + body: data, + secure: true, + type: ContentType.Json, + format: "json", + ...params + }), + /** + * @description Delete a project by ID (requires organization-scoped API key). Project deletion is processed asynchronously. + * + * @tags Projects + * @name ProjectsDelete + * @request DELETE:/api/public/projects/{projectId} + * @secure + */ + projectsDelete: (projectId, params = {}) => this.request({ + path: `/api/public/projects/${projectId}`, + method: "DELETE", + secure: true, + format: "json", + ...params + }), + /** + * @description Delete an API key for a project (requires organization-scoped API key) + * + * @tags Projects + * @name ProjectsDeleteApiKey + * @request DELETE:/api/public/projects/{projectId}/apiKeys/{apiKeyId} + * @secure + */ + projectsDeleteApiKey: (projectId, apiKeyId, params = {}) => this.request({ + path: `/api/public/projects/${projectId}/apiKeys/${apiKeyId}`, + method: "DELETE", + secure: true, + format: "json", + ...params + }), + /** + * @description Get Project associated with API key + * + * @tags Projects + * @name ProjectsGet + * @request GET:/api/public/projects + * @secure + */ + projectsGet: (params = {}) => this.request({ + path: `/api/public/projects`, + method: "GET", + secure: true, + format: "json", + ...params + }), + /** + * @description Get all API keys for a project (requires organization-scoped API key) + * + * @tags Projects + * @name ProjectsGetApiKeys + * @request GET:/api/public/projects/{projectId}/apiKeys + * @secure + */ + projectsGetApiKeys: (projectId, params = {}) => this.request({ + path: `/api/public/projects/${projectId}/apiKeys`, + method: "GET", + secure: true, + format: "json", + ...params + }), + /** + * @description Update a project by ID (requires organization-scoped API key). + * + * @tags Projects + * @name ProjectsUpdate + * @request PUT:/api/public/projects/{projectId} + * @secure + */ + projectsUpdate: (projectId, data, params = {}) => this.request({ + path: `/api/public/projects/${projectId}`, + method: "PUT", + body: data, + secure: true, + type: ContentType.Json, + format: "json", + ...params + }), + /** + * @description Create a new version for the prompt with the given `name` + * + * @tags Prompts + * @name PromptsCreate + * @request POST:/api/public/v2/prompts + * @secure + */ + promptsCreate: (data, params = {}) => this.request({ + path: `/api/public/v2/prompts`, + method: "POST", + body: data, + secure: true, + type: ContentType.Json, + format: "json", + ...params + }), + /** + * @description Get a prompt + * + * @tags Prompts + * @name PromptsGet + * @request GET:/api/public/v2/prompts/{promptName} + * @secure + */ + promptsGet: ({ + promptName, + ...query + }, params = {}) => this.request({ + path: `/api/public/v2/prompts/${promptName}`, + method: "GET", + query, + secure: true, + format: "json", + ...params + }), + /** + * @description Get a list of prompt names with versions and labels + * + * @tags Prompts + * @name PromptsList + * @request GET:/api/public/v2/prompts + * @secure + */ + promptsList: (query, params = {}) => this.request({ + path: `/api/public/v2/prompts`, + method: "GET", + query, + secure: true, + format: "json", + ...params + }), + /** + * @description Update labels for a specific prompt version + * + * @tags PromptVersion + * @name PromptVersionUpdate + * @request PATCH:/api/public/v2/prompts/{name}/versions/{version} + * @secure + */ + promptVersionUpdate: (name, version2, data, params = {}) => this.request({ + path: `/api/public/v2/prompts/${name}/versions/${version2}`, + method: "PATCH", + body: data, + secure: true, + type: ContentType.Json, + format: "json", + ...params + }), + /** + * @description Create a new user in the organization (requires organization-scoped API key) + * + * @tags Scim + * @name ScimCreateUser + * @request POST:/api/public/scim/Users + * @secure + */ + scimCreateUser: (data, params = {}) => this.request({ + path: `/api/public/scim/Users`, + method: "POST", + body: data, + secure: true, + type: ContentType.Json, + format: "json", + ...params + }), + /** + * @description Remove a user from the organization (requires organization-scoped API key). Note that this only removes the user from the organization but does not delete the user entity itself. + * + * @tags Scim + * @name ScimDeleteUser + * @request DELETE:/api/public/scim/Users/{userId} + * @secure + */ + scimDeleteUser: (userId, params = {}) => this.request({ + path: `/api/public/scim/Users/${userId}`, + method: "DELETE", + secure: true, + format: "json", + ...params + }), + /** + * @description Get SCIM Resource Types (requires organization-scoped API key) + * + * @tags Scim + * @name ScimGetResourceTypes + * @request GET:/api/public/scim/ResourceTypes + * @secure + */ + scimGetResourceTypes: (params = {}) => this.request({ + path: `/api/public/scim/ResourceTypes`, + method: "GET", + secure: true, + format: "json", + ...params + }), + /** + * @description Get SCIM Schemas (requires organization-scoped API key) + * + * @tags Scim + * @name ScimGetSchemas + * @request GET:/api/public/scim/Schemas + * @secure + */ + scimGetSchemas: (params = {}) => this.request({ + path: `/api/public/scim/Schemas`, + method: "GET", + secure: true, + format: "json", + ...params + }), + /** + * @description Get SCIM Service Provider Configuration (requires organization-scoped API key) + * + * @tags Scim + * @name ScimGetServiceProviderConfig + * @request GET:/api/public/scim/ServiceProviderConfig + * @secure + */ + scimGetServiceProviderConfig: (params = {}) => this.request({ + path: `/api/public/scim/ServiceProviderConfig`, + method: "GET", + secure: true, + format: "json", + ...params + }), + /** + * @description Get a specific user by ID (requires organization-scoped API key) + * + * @tags Scim + * @name ScimGetUser + * @request GET:/api/public/scim/Users/{userId} + * @secure + */ + scimGetUser: (userId, params = {}) => this.request({ + path: `/api/public/scim/Users/${userId}`, + method: "GET", + secure: true, + format: "json", + ...params + }), + /** + * @description List users in the organization (requires organization-scoped API key) + * + * @tags Scim + * @name ScimListUsers + * @request GET:/api/public/scim/Users + * @secure + */ + scimListUsers: (query, params = {}) => this.request({ + path: `/api/public/scim/Users`, + method: "GET", + query, + secure: true, + format: "json", + ...params + }), + /** + * @description Create a score configuration (config). Score configs are used to define the structure of scores + * + * @tags ScoreConfigs + * @name ScoreConfigsCreate + * @request POST:/api/public/score-configs + * @secure + */ + scoreConfigsCreate: (data, params = {}) => this.request({ + path: `/api/public/score-configs`, + method: "POST", + body: data, + secure: true, + type: ContentType.Json, + format: "json", + ...params + }), + /** + * @description Get all score configs + * + * @tags ScoreConfigs + * @name ScoreConfigsGet + * @request GET:/api/public/score-configs + * @secure + */ + scoreConfigsGet: (query, params = {}) => this.request({ + path: `/api/public/score-configs`, + method: "GET", + query, + secure: true, + format: "json", + ...params + }), + /** + * @description Get a score config + * + * @tags ScoreConfigs + * @name ScoreConfigsGetById + * @request GET:/api/public/score-configs/{configId} + * @secure + */ + scoreConfigsGetById: (configId, params = {}) => this.request({ + path: `/api/public/score-configs/${configId}`, + method: "GET", + secure: true, + format: "json", + ...params + }), + /** + * @description Create a score (supports both trace and session scores) + * + * @tags Score + * @name ScoreCreate + * @request POST:/api/public/scores + * @secure + */ + scoreCreate: (data, params = {}) => this.request({ + path: `/api/public/scores`, + method: "POST", + body: data, + secure: true, + type: ContentType.Json, + format: "json", + ...params + }), + /** + * @description Delete a score (supports both trace and session scores) + * + * @tags Score + * @name ScoreDelete + * @request DELETE:/api/public/scores/{scoreId} + * @secure + */ + scoreDelete: (scoreId, params = {}) => this.request({ + path: `/api/public/scores/${scoreId}`, + method: "DELETE", + secure: true, + ...params + }), + /** + * @description Get a list of scores (supports both trace and session scores) + * + * @tags ScoreV2 + * @name ScoreV2Get + * @request GET:/api/public/v2/scores + * @secure + */ + scoreV2Get: (query, params = {}) => this.request({ + path: `/api/public/v2/scores`, + method: "GET", + query, + secure: true, + format: "json", + ...params + }), + /** + * @description Get a score (supports both trace and session scores) + * + * @tags ScoreV2 + * @name ScoreV2GetById + * @request GET:/api/public/v2/scores/{scoreId} + * @secure + */ + scoreV2GetById: (scoreId, params = {}) => this.request({ + path: `/api/public/v2/scores/${scoreId}`, + method: "GET", + secure: true, + format: "json", + ...params + }), + /** + * @description Get a session. Please note that `traces` on this endpoint are not paginated, if you plan to fetch large sessions, consider `GET /api/public/traces?sessionId=` + * + * @tags Sessions + * @name SessionsGet + * @request GET:/api/public/sessions/{sessionId} + * @secure + */ + sessionsGet: (sessionId2, params = {}) => this.request({ + path: `/api/public/sessions/${sessionId2}`, + method: "GET", + secure: true, + format: "json", + ...params + }), + /** + * @description Get sessions + * + * @tags Sessions + * @name SessionsList + * @request GET:/api/public/sessions + * @secure + */ + sessionsList: (query, params = {}) => this.request({ + path: `/api/public/sessions`, + method: "GET", + query, + secure: true, + format: "json", + ...params + }), + /** + * @description Delete a specific trace + * + * @tags Trace + * @name TraceDelete + * @request DELETE:/api/public/traces/{traceId} + * @secure + */ + traceDelete: (traceId, params = {}) => this.request({ + path: `/api/public/traces/${traceId}`, + method: "DELETE", + secure: true, + format: "json", + ...params + }), + /** + * @description Delete multiple traces + * + * @tags Trace + * @name TraceDeleteMultiple + * @request DELETE:/api/public/traces + * @secure + */ + traceDeleteMultiple: (data, params = {}) => this.request({ + path: `/api/public/traces`, + method: "DELETE", + body: data, + secure: true, + type: ContentType.Json, + format: "json", + ...params + }), + /** + * @description Get a specific trace + * + * @tags Trace + * @name TraceGet + * @request GET:/api/public/traces/{traceId} + * @secure + */ + traceGet: (traceId, params = {}) => this.request({ + path: `/api/public/traces/${traceId}`, + method: "GET", + secure: true, + format: "json", + ...params + }), + /** + * @description Get list of traces + * + * @tags Trace + * @name TraceList + * @request GET:/api/public/traces + * @secure + */ + traceList: (query, params = {}) => this.request({ + path: `/api/public/traces`, + method: "GET", + query, + secure: true, + format: "json", + ...params + }) + }; + } +}; +var version = "3.38.20"; +var Langfuse = class extends LangfuseCore { + constructor(params) { + const langfuseConfig = utils.configLangfuseSDK(params); + super(langfuseConfig); + if (typeof window !== "undefined" && "Deno" in window === false) { + this._storageKey = params?.persistence_name ? `lf_${params.persistence_name}` : `lf_${langfuseConfig.publicKey}_langfuse`; + this._storage = getStorage(params?.persistence || "localStorage", window); + } else { + this._storageKey = `lf_${langfuseConfig.publicKey}_langfuse`; + this._storage = getStorage("memory", void 0); + } + this.api = new LangfusePublicApi({ + baseUrl: this.baseUrl, + baseApiParams: { + headers: { + "X-Langfuse-Sdk-Name": "langfuse-js", + "X-Langfuse-Sdk-Version": this.getLibraryVersion(), + "X-Langfuse-Sdk-Variant": this.getLibraryId(), + "X-Langfuse-Sdk-Integration": this.sdkIntegration, + "X-Langfuse-Public-Key": this.publicKey, + ...this.additionalHeaders, + ...this.constructAuthorizationHeader(this.publicKey, this.secretKey) + } + } + }).api; + } + getPersistedProperty(key) { + if (!this._storageCache) { + this._storageCache = JSON.parse(this._storage.getItem(this._storageKey) || "{}") || {}; + } + return this._storageCache[key]; + } + setPersistedProperty(key, value) { + if (!this._storageCache) { + this._storageCache = JSON.parse(this._storage.getItem(this._storageKey) || "{}") || {}; + } + if (value === null) { + delete this._storageCache[key]; + } else { + this._storageCache[key] = value; + } + this._storage.setItem(this._storageKey, JSON.stringify(this._storageCache)); + } + fetch(url, options) { + return fetch(url, options); + } + getLibraryId() { + return "langfuse"; + } + getLibraryVersion() { + return version; + } + getCustomUserAgent() { + return; + } +}; +var LangfuseSingleton = class _LangfuseSingleton { + /** + * Returns the singleton instance of the Langfuse client. + * @param params Optional parameters for initializing the Langfuse instance. Only used for the first call. + * @returns The singleton instance of the Langfuse client. + */ + static getInstance(params) { + if (!_LangfuseSingleton.instance) { + _LangfuseSingleton.instance = new Langfuse(params); + } + return _LangfuseSingleton.instance; + } +}; +LangfuseSingleton.instance = null; + +// src/adapters/langfuse-sink.ts +var SDK_INTEGRATION = PLUGIN_ID; +var TRACE_NAME = "ZCode Turn"; +var LangfuseTraceSink = class { + constructor(config) { + this.config = config; + } + config; + async publishTurn(turn) { + if (!this.config.publicKey || !this.config.secretKey) return; + const client = new Langfuse({ + publicKey: this.config.publicKey, + secretKey: this.config.secretKey, + baseUrl: this.config.baseUrl, + release: this.config.release, + environment: this.config.environment, + sdkIntegration: SDK_INTEGRATION, + flushAt: 1, + fetchRetryCount: 1, + requestTimeout: 8e3 + }); + try { + const traceInput = { + name: TRACE_NAME, + sessionId: turn.sessionId, + input: turn.prompt, + metadata: { + source: "zcode", + turnId: turn.turnId, + toolCount: turn.tools.length + }, + tags: ["zcode", "zcode-hook"] + }; + if (this.config.userId) traceInput.userId = this.config.userId; + const trace = client.trace(traceInput); + for (const tool of turn.tools) { + const span = trace.span({ + name: `tool.${tool.name}`, + input: tool.input, + metadata: { + toolId: tool.id, + startedAt: tool.startedAt, + endedAt: tool.endedAt + } + }); + if (tool.error) { + span.end({ + output: { error: tool.error }, + level: "ERROR", + statusMessage: tool.error + }); + } else { + span.end({ output: tool.output }); + } + } + const generation = trace.generation({ + name: "zcode.assistant", + input: turn.prompt, + metadata: { + turnId: turn.turnId + } + }); + generation.end({ output: turn.assistantMessage }); + trace.update({ + output: turn.assistantMessage, + metadata: { + source: "zcode", + turnId: turn.turnId, + toolCount: turn.tools.length + } + }); + await client.flushAsync(); + } finally { + await client.shutdownAsync(); + } + } +}; + +// src/hooks/entry.ts +function isRecord2(value) { + return typeof value === "object" && value !== null && !Array.isArray(value); +} +function parsePayload(raw) { + if (!raw.trim()) return {}; + try { + const parsed = JSON.parse(raw); + return isRecord2(parsed) ? parsed : {}; + } catch { + return {}; + } +} +async function readStdin() { + let raw = ""; + process.stdin.setEncoding("utf8"); + for await (const chunk of process.stdin) raw += chunk; + return raw; +} +function diagnostics(enabled, message) { + if (enabled) process.stderr.write(`[${PLUGIN_ID}] ${message} +`); +} +async function main() { + const config = readConfig(); + const payload = parsePayload(await readStdin()); + const event = eventName(payload) ?? "unknown"; + const session = sessionId(payload) ?? "?"; + diagnostics(config.debug, `event=${event} session=${session}`); + const sink = isConfigured(config) ? new LangfuseTraceSink(config) : new NoopTraceSink(); + const tracker = new TurnTracker( + new JsonStateStore(), + sink, + config, + { now: () => /* @__PURE__ */ new Date() }, + { next: randomUUID2 } + ); + try { + await tracker.handle(payload); + } catch (error) { + const kind = error instanceof Error ? error.name : "unknown"; + diagnostics(config.debug, `hook failed open (${kind})`); + } + process.stdout.write("{}\n"); +} +await main(); +/*! Bundled license information: + +mustache/mustache.mjs: + (*! + * mustache.js - Logic-less {{mustache}} templates with JavaScript + * http://github.com/janl/mustache.js + *) +*/ From b5a2ea39c7ffea1f2be4f9d9616c35e3e77f4016 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 13 Sep 2026 11:42:23 +0000 Subject: [PATCH 08/13] chore(catalog): sync zcode-plugin-langfuse v0.2.1 --- marketplace.json | 2 +- plugins/zcode-plugin-langfuse/.zcode-plugin/plugin.json | 2 +- plugins/zcode-plugin-langfuse/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/marketplace.json b/marketplace.json index 07c01b1..c58dd17 100644 --- a/marketplace.json +++ b/marketplace.json @@ -537,7 +537,7 @@ "name": "zcode-plugin-langfuse", "source": "./plugins/zcode-plugin-langfuse", "description": "Fail-open Langfuse tracing for ZCode sessions, prompts, tool calls, and assistant responses.", - "version": "0.2.0", + "version": "0.2.1", "category": "developer-tools", "tags": [ "langfuse", diff --git a/plugins/zcode-plugin-langfuse/.zcode-plugin/plugin.json b/plugins/zcode-plugin-langfuse/.zcode-plugin/plugin.json index 2f565a3..3c5318c 100644 --- a/plugins/zcode-plugin-langfuse/.zcode-plugin/plugin.json +++ b/plugins/zcode-plugin-langfuse/.zcode-plugin/plugin.json @@ -5,7 +5,7 @@ "en": "Fail-open Langfuse tracing for ZCode sessions, prompts, tool calls, and assistant responses.", "zh-CN": "为 ZCode 会话、提示词、工具调用和助手回复提供 fail-open 的 Langfuse 追踪。" }, - "version": "0.2.0", + "version": "0.2.1", "author": { "name": "Community contributor" }, diff --git a/plugins/zcode-plugin-langfuse/package.json b/plugins/zcode-plugin-langfuse/package.json index 48e0d45..946b8eb 100644 --- a/plugins/zcode-plugin-langfuse/package.json +++ b/plugins/zcode-plugin-langfuse/package.json @@ -1,6 +1,6 @@ { "name": "zcode-plugin-langfuse", - "version": "0.2.0", + "version": "0.2.1", "description": "Fail-open Langfuse tracing for ZCode sessions, prompts, tool calls, and assistant responses.", "license": "MIT", "type": "module", From 37f215ee5771dbfeab4e268098051266172c78d4 Mon Sep 17 00:00:00 2001 From: erlinerd Date: Sun, 13 Sep 2026 22:34:35 +0800 Subject: [PATCH 09/13] chore(catalog): sync zcode-plugin-langfuse v0.2.1 --- .gitignore | 2 ++ marketplace.json | 13 +++---- plugins/zcode-plugin-langfuse/README.md | 34 +++++++++---------- plugins/zcode-plugin-langfuse/README_CN.md | 24 ++++++------- .../zcode-plugin-langfuse/hooks/hooks.json | 12 +++---- plugins/zcode-plugin-langfuse/package.json | 30 ++++++++++++++-- .../{ => payload}/dist/hooks/entry.mjs | 6 ++-- 7 files changed, 72 insertions(+), 49 deletions(-) rename plugins/zcode-plugin-langfuse/{ => payload}/dist/hooks/entry.mjs (99%) diff --git a/.gitignore b/.gitignore index ab23f78..c638c91 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ __pycache__/ mirror-failure.txt mirror-result.txt publish-failure.txt +!plugins/zcode-plugin-langfuse/payload/dist/ +!plugins/zcode-plugin-langfuse/payload/dist/** diff --git a/marketplace.json b/marketplace.json index c58dd17..b5bd69a 100644 --- a/marketplace.json +++ b/marketplace.json @@ -535,22 +535,17 @@ }, { "name": "zcode-plugin-langfuse", - "source": "./plugins/zcode-plugin-langfuse", - "description": "Fail-open Langfuse tracing for ZCode sessions, prompts, tool calls, and assistant responses.", + "source": ".", + "description": "Fail-open Langfuse tracing for ZCode sessions and tool calls.", "version": "0.2.1", - "category": "developer-tools", + "category": "observability", "tags": [ "langfuse", - "observability", "tracing", "telemetry", "hooks" ], - "strict": true, - "description_i18n": { - "en": "Fail-open Langfuse tracing for ZCode sessions, prompts, tool calls, and assistant responses.", - "zh-CN": "为 ZCode 会话、提示词、工具调用和助手回复提供 fail-open 的 Langfuse 追踪。" - } + "strict": true } ] } diff --git a/plugins/zcode-plugin-langfuse/README.md b/plugins/zcode-plugin-langfuse/README.md index e07c172..b7159b2 100644 --- a/plugins/zcode-plugin-langfuse/README.md +++ b/plugins/zcode-plugin-langfuse/README.md @@ -135,20 +135,19 @@ npm run check npm run package:plugin ``` -`npm run build` creates the local `dist/` bundle. `npm run package:plugin` is the -default distribution build: it runs the bundle build once, validates it, and -creates both the ignored release files and the catalog-ready plugin layout: +`npm run build` builds the complete `dist/` output, structured as a ZCode +marketplace root (per the official catalog layout): ```text -artifacts/plugin.zip -artifacts/plugin.zip.sha256 -artifacts/plugin-layout/plugins/zcode-plugin-langfuse/ -artifacts/plugin-layout/marketplace-entry.json +dist/marketplace.json +dist/plugins/zcode-plugin-langfuse/ ``` -The ZIP and plugin layout use the same generated bundle. The ZIP contains the -ZCode manifest, Hook declaration, bundled SDK, and third-party notices. Neither -`dist/` nor `artifacts/` is committed. +`dist/plugins/zcode-plugin-langfuse/` is the installable plugin: its +`dist/hooks/entry.mjs` runtime, manifest, hooks, package metadata, dual-language +README, license, and third-party notices. The release workflow compresses the +tree into the versioned ZIP; the official catalog and local directory installs +consume the tree as-is. Everything under `dist/` is generated and not committed. The hook can be smoke-tested without credentials: @@ -187,17 +186,18 @@ it, and configure its `userConfig` values. The plugin manifest is at `.zcode-plugin/plugin.json`; the hook declaration is at `hooks/hooks.json`. -## Online ZCode marketplace +## Install a release -Add this repository in **Settings → Plugins → Create → Add marketplace** using -its GitHub repository URL. ZCode accepts a GitHub repository or its URL as a -marketplace source. +Download `zcode-plugin-langfuse-v.zip` from the release page, extract it, and add +the extracted plugin directory as a local marketplace via **Settings → +Plugins → Add marketplace**. Do not add a fresh checkout of this repository as +a marketplace: it has no `dist/` bundle (generated, not committed), so hooks +cannot start. For a development install from a checkout, run `npm install` +first — the `prepare` hook builds `dist/`. -- Community marketplace repository: - Marketplace manifest: - Plugin manifest: -- Latest plugin ZIP: -- Latest ZIP checksum: +- Latest release (asset: `zcode-plugin-langfuse-v.zip`): - Release page: - Official ZCode plugin documentation: - Official ZCode plugin marketplace: diff --git a/plugins/zcode-plugin-langfuse/README_CN.md b/plugins/zcode-plugin-langfuse/README_CN.md index eaad77b..ad90a72 100644 --- a/plugins/zcode-plugin-langfuse/README_CN.md +++ b/plugins/zcode-plugin-langfuse/README_CN.md @@ -98,13 +98,14 @@ ZCode 选择本地目录时会按“插件市场”读取,因此仓库根目 ## 在线 ZCode 插件市场 -在 **设置 → 插件 → 创建 → 添加插件市场** 中,使用 GitHub 仓库地址添加: +从 Release 页下载 `zcode-plugin-langfuse-v<版本>.zip` 并解压,在 **设置 → 插件 → 添加插件市场 → +选择目录** 中选择解压后的插件目录安装。不要把全新检出的源码仓库直接添加为市场: +其中没有 `dist/` bundle(生成目录不入库),Hook 无法启动。开发安装请先在检出目录 +运行 `npm install`(`prepare` 钩子会构建 `dist/`),再选择该目录。 -- 社区市场仓库: - 市场清单: - 插件清单: -- 最新插件 ZIP: -- 最新 ZIP 校验文件: +- 最新 Release(资产:`zcode-plugin-langfuse-v<版本>.zip`): - Release 页面: - ZCode 官方插件文档: - ZCode 官方插件市场: @@ -124,18 +125,17 @@ npm run package:plugin ``` `npm run build` 会把官方 `langfuse` JavaScript SDK 打包进本地 `dist/`。 -`npm run package:plugin` 是默认的分发构建:只执行一次 bundle 构建,完成校验, -并同时生成发行文件和可同步到插件市场的布局: +`npm run build` 会构建完整的 `dist/` 输出,结构遵循 ZCode 官方市场布局: ```text -artifacts/plugin.zip -artifacts/plugin.zip.sha256 -artifacts/plugin-layout/plugins/zcode-plugin-langfuse/ -artifacts/plugin-layout/marketplace-entry.json +dist/marketplace.json +dist/plugins/zcode-plugin-langfuse/ ``` -ZIP 和 plugin layout 使用同一个生成 bundle。ZIP 包含 ZCode manifest、Hook 声明、 -SDK bundle 和第三方声明。`dist/` 与 `artifacts/` 都是生成目录,不提交到源码仓库。 +`dist/plugins/zcode-plugin-langfuse/` 即可安装的插件:包含 `dist/hooks/entry.mjs` +运行时、manifest、hooks、包元数据、双语 README、许可证与第三方声明。Release +工作流负责把该目录压缩为版本化 ZIP;官方目录与本地目录安装直接使用这棵树。 +整个 `dist/` 均为生成目录,不入库。 目录分层: diff --git a/plugins/zcode-plugin-langfuse/hooks/hooks.json b/plugins/zcode-plugin-langfuse/hooks/hooks.json index 37b12fb..1b179bb 100644 --- a/plugins/zcode-plugin-langfuse/hooks/hooks.json +++ b/plugins/zcode-plugin-langfuse/hooks/hooks.json @@ -8,7 +8,7 @@ "type": "process", "command": "node", "args": [ - "${ZCODE_PLUGIN_ROOT}/dist/hooks/entry.mjs" + "${ZCODE_PLUGIN_ROOT}/payload/dist/hooks/entry.mjs" ], "timeoutMs": 5000, "statusMessage": "Langfuse: preparing session tracing…" @@ -23,7 +23,7 @@ "type": "process", "command": "node", "args": [ - "${ZCODE_PLUGIN_ROOT}/dist/hooks/entry.mjs" + "${ZCODE_PLUGIN_ROOT}/payload/dist/hooks/entry.mjs" ], "timeoutMs": 5000 } @@ -37,7 +37,7 @@ "type": "process", "command": "node", "args": [ - "${ZCODE_PLUGIN_ROOT}/dist/hooks/entry.mjs" + "${ZCODE_PLUGIN_ROOT}/payload/dist/hooks/entry.mjs" ], "timeoutMs": 5000 } @@ -51,7 +51,7 @@ "type": "process", "command": "node", "args": [ - "${ZCODE_PLUGIN_ROOT}/dist/hooks/entry.mjs" + "${ZCODE_PLUGIN_ROOT}/payload/dist/hooks/entry.mjs" ], "timeoutMs": 5000 } @@ -65,7 +65,7 @@ "type": "process", "command": "node", "args": [ - "${ZCODE_PLUGIN_ROOT}/dist/hooks/entry.mjs" + "${ZCODE_PLUGIN_ROOT}/payload/dist/hooks/entry.mjs" ], "timeoutMs": 5000 } @@ -79,7 +79,7 @@ "type": "process", "command": "node", "args": [ - "${ZCODE_PLUGIN_ROOT}/dist/hooks/entry.mjs" + "${ZCODE_PLUGIN_ROOT}/payload/dist/hooks/entry.mjs" ], "timeoutMs": 20000, "statusMessage": "Langfuse: sending trace…" diff --git a/plugins/zcode-plugin-langfuse/package.json b/plugins/zcode-plugin-langfuse/package.json index 946b8eb..64cf337 100644 --- a/plugins/zcode-plugin-langfuse/package.json +++ b/plugins/zcode-plugin-langfuse/package.json @@ -1,11 +1,37 @@ { "name": "zcode-plugin-langfuse", "version": "0.2.1", - "description": "Fail-open Langfuse tracing for ZCode sessions, prompts, tool calls, and assistant responses.", + "private": true, + "description": "A fail-open Langfuse observability plugin for ZCode.", "license": "MIT", "type": "module", - "private": true, "engines": { "node": ">=20" + }, + "scripts": { + "build": "tsc --noEmit && node scripts/build.mjs", + "clean": "node scripts/clean.mjs", + "test": "vitest run", + "lint": "oxlint src test scripts", + "lint:fix": "oxlint --fix src test scripts", + "typecheck": "tsc --noEmit", + "validate": "node scripts/validate.mjs", + "validate:artifact": "node scripts/validate.mjs --artifact", + "sync:catalog": "node scripts/sync-catalog.mjs", + "package:plugin": "npm run build && npm run validate && npm run validate:artifact", + "check": "npm run lint && npm run typecheck && npm test && npm run validate", + "prepare": "husky && npm run build" + }, + "dependencies": { + "langfuse": "^3.38.20" + }, + "devDependencies": { + "@types/node": "^26.4.1", + "esbuild": "^0.28.2", + "husky": "^9.1.7", + "lint-staged": "^17.5.1", + "oxlint": "^1.82.0", + "typescript": "^7.0.2", + "vitest": "^5.0.0" } } diff --git a/plugins/zcode-plugin-langfuse/dist/hooks/entry.mjs b/plugins/zcode-plugin-langfuse/payload/dist/hooks/entry.mjs similarity index 99% rename from plugins/zcode-plugin-langfuse/dist/hooks/entry.mjs rename to plugins/zcode-plugin-langfuse/payload/dist/hooks/entry.mjs index f10d506..3296bdf 100644 --- a/plugins/zcode-plugin-langfuse/dist/hooks/entry.mjs +++ b/plugins/zcode-plugin-langfuse/payload/dist/hooks/entry.mjs @@ -568,7 +568,7 @@ var JsonStateStore = class { } }; -// node_modules/mustache/mustache.mjs +// node_modules/.pnpm/mustache@4.2.0/node_modules/mustache/mustache.mjs var objectToString = Object.prototype.toString; var isArray = Array.isArray || function isArrayPolyfill(object) { return objectToString.call(object) === "[object Array]"; @@ -1022,7 +1022,7 @@ mustache.Context = Context; mustache.Writer = Writer; var mustache_default = mustache; -// node_modules/langfuse-core/lib/index.mjs +// node_modules/.pnpm/langfuse-core@3.38.20/node_modules/langfuse-core/lib/index.mjs var SimpleEventEmitter = class { constructor() { this.events = {}; @@ -3152,7 +3152,7 @@ var LangfuseEventClient = class extends LangfuseObservationClient { } }; -// node_modules/langfuse/lib/index.mjs +// node_modules/.pnpm/langfuse@3.38.20/node_modules/langfuse/lib/index.mjs var cookieStore = { getItem(key) { try { From 5af0b758cf9dc3dce5b8ae17b7fdd61d71c4b36c Mon Sep 17 00:00:00 2001 From: erlinerd Date: Sun, 13 Sep 2026 23:24:01 +0800 Subject: [PATCH 10/13] chore(catalog): sync zcode-plugin-langfuse v0.2.1 --- marketplace.json | 2 +- .../.claude-plugin/plugin.json | 83 +++++++++++++++++++ plugins/zcode-plugin-langfuse/README.md | 27 ++++-- plugins/zcode-plugin-langfuse/README_CN.md | 22 +++-- .../{payload/dist => }/hooks/entry.mjs | 0 .../zcode-plugin-langfuse/hooks/hooks.json | 12 +-- plugins/zcode-plugin-langfuse/package.json | 37 --------- 7 files changed, 125 insertions(+), 58 deletions(-) create mode 100644 plugins/zcode-plugin-langfuse/.claude-plugin/plugin.json rename plugins/zcode-plugin-langfuse/{payload/dist => }/hooks/entry.mjs (100%) delete mode 100644 plugins/zcode-plugin-langfuse/package.json diff --git a/marketplace.json b/marketplace.json index b5bd69a..218ebd3 100644 --- a/marketplace.json +++ b/marketplace.json @@ -535,7 +535,7 @@ }, { "name": "zcode-plugin-langfuse", - "source": ".", + "source": "./plugins/zcode-plugin-langfuse", "description": "Fail-open Langfuse tracing for ZCode sessions and tool calls.", "version": "0.2.1", "category": "observability", diff --git a/plugins/zcode-plugin-langfuse/.claude-plugin/plugin.json b/plugins/zcode-plugin-langfuse/.claude-plugin/plugin.json new file mode 100644 index 0000000..3c5318c --- /dev/null +++ b/plugins/zcode-plugin-langfuse/.claude-plugin/plugin.json @@ -0,0 +1,83 @@ +{ + "name": "zcode-plugin-langfuse", + "description": "Fail-open Langfuse tracing for ZCode sessions, prompts, tool calls, and assistant responses.", + "description_i18n": { + "en": "Fail-open Langfuse tracing for ZCode sessions, prompts, tool calls, and assistant responses.", + "zh-CN": "为 ZCode 会话、提示词、工具调用和助手回复提供 fail-open 的 Langfuse 追踪。" + }, + "version": "0.2.1", + "author": { + "name": "Community contributor" + }, + "license": "MIT", + "keywords": [ + "langfuse", + "observability", + "tracing", + "telemetry", + "hooks" + ], + "userConfig": { + "langfuse_public_key": { + "title": "Langfuse public key", + "description": "Langfuse project public key. You can also provide LANGFUSE_PUBLIC_KEY in the ZCode process environment.", + "type": "string", + "required": false + }, + "langfuse_secret_key": { + "title": "Langfuse secret key", + "description": "Langfuse project secret key. It is used only by the local hook process and is never written to hook output.", + "type": "string", + "required": false, + "sensitive": true + }, + "langfuse_base_url": { + "title": "Langfuse base URL", + "description": "HTTPS Langfuse host, for example https://cloud.langfuse.com or a self-hosted HTTPS URL. Plain HTTP is rejected.", + "type": "string", + "default": "https://cloud.langfuse.com" + }, + "langfuse_user_id": { + "title": "Langfuse user ID", + "description": "Optional stable user identifier attached to traces. Leave empty to omit it.", + "type": "string", + "required": false + }, + "langfuse_environment": { + "title": "Langfuse environment", + "description": "Environment label attached to traces.", + "type": "string", + "default": "development" + }, + "capture_prompts": { + "title": "Capture prompts", + "description": "Include user prompts and assistant responses in Langfuse. Disable for metadata-only tracing.", + "type": "boolean", + "default": true + }, + "capture_tool_inputs": { + "title": "Capture tool inputs", + "description": "Include tool names and inputs in Langfuse spans.", + "type": "boolean", + "default": true + }, + "capture_tool_outputs": { + "title": "Capture tool outputs", + "description": "Include tool results and errors in Langfuse spans.", + "type": "boolean", + "default": true + }, + "max_capture_chars": { + "title": "Maximum captured characters", + "description": "Maximum characters per captured prompt, tool payload, or response. Larger values are truncated.", + "type": "number", + "default": 20000 + }, + "debug": { + "title": "Debug diagnostics", + "description": "Write non-sensitive lifecycle diagnostics to stderr. Disabled by default.", + "type": "boolean", + "default": false + } + } +} diff --git a/plugins/zcode-plugin-langfuse/README.md b/plugins/zcode-plugin-langfuse/README.md index b7159b2..aaf0607 100644 --- a/plugins/zcode-plugin-langfuse/README.md +++ b/plugins/zcode-plugin-langfuse/README.md @@ -6,7 +6,7 @@ A community ZCode plugin that sends one Langfuse trace per completed ZCode turn. It is designed for review and possible inclusion in the ZCode official plugin marketplace. -> **Status:** early community contribution (`0.1.1`). The plugin is fail-open: +> **Status:** early community contribution (`0.2.1`). The plugin is fail-open: > a missing credential, malformed hook payload, local state error, or Langfuse > request error must never block a ZCode session. @@ -136,18 +136,28 @@ npm run package:plugin ``` `npm run build` builds the complete `dist/` output, structured as a ZCode -marketplace root (per the official catalog layout): +marketplace shell whose single entry uses the official plugin layout (same +shape as the `zcode-plugins-official` template): ```text dist/marketplace.json dist/plugins/zcode-plugin-langfuse/ +├── .zcode-plugin/plugin.json +├── .claude-plugin/plugin.json (identical copy for Claude compatibility) +├── hooks/hooks.json (points at hooks/entry.mjs) +├── hooks/entry.mjs (sealed runtime bundle) +├── README.md +├── README_CN.md +├── LICENSE +└── THIRD_PARTY_NOTICES.md ``` `dist/plugins/zcode-plugin-langfuse/` is the installable plugin: its -`dist/hooks/entry.mjs` runtime, manifest, hooks, package metadata, dual-language -README, license, and third-party notices. The release workflow compresses the -tree into the versioned ZIP; the official catalog and local directory installs -consume the tree as-is. Everything under `dist/` is generated and not committed. +`hooks/entry.mjs` runtime, manifest, hooks, dual-language README, license, and +third-party notices. The release workflow compresses the tree into the +versioned ZIP; the official catalog and local directory installs consume the +plugin directory as-is. Everything under `dist/` is generated and not +committed. The hook can be smoke-tested without credentials: @@ -155,7 +165,7 @@ The hook can be smoke-tested without credentials: printf '%s\n' '{"hook_event_name":"Stop","session_id":"smoke","last_assistant_message":"ok"}' \ | ZCODE_PLUGIN_DATA="$(mktemp -d)" \ LANGFUSE_DEBUG=true \ - node dist/hooks/entry.mjs + node dist/plugins/zcode-plugin-langfuse/hooks/entry.mjs ``` Expected stdout is one empty hook result object: @@ -178,7 +188,8 @@ bundled with credentials. ## Local installation for testing ZCode treats a selected directory as a **plugin marketplace**, so this repository -includes `marketplace.json` at its root. Build first, then use **Settings → +includes `marketplace.json` at its root; its entry points at +`./dist/plugins/zcode-plugin-langfuse`. Build first, then use **Settings → Plugins → Add marketplace → Select directory** and choose this repository. Install `zcode-plugin-langfuse` from the resulting personal marketplace, enable it, and configure its `userConfig` values. diff --git a/plugins/zcode-plugin-langfuse/README_CN.md b/plugins/zcode-plugin-langfuse/README_CN.md index ad90a72..52f1eb9 100644 --- a/plugins/zcode-plugin-langfuse/README_CN.md +++ b/plugins/zcode-plugin-langfuse/README_CN.md @@ -5,7 +5,7 @@ 面向 ZCode 的社区 Langfuse 观测插件,按每个完成的 ZCode turn 生成一条 Langfuse trace,目标是提交给 ZCode 官方插件市场。 -> 当前版本:`0.1.1`。插件遵循 **fail-open**:缺少凭据、Hook 输入损坏、本地 +> 当前版本:`0.2.1`。插件遵循 **fail-open**:缺少凭据、Hook 输入损坏、本地 > 状态错误或 Langfuse 请求失败,都不能阻塞 ZCode 会话。 ## 采集范围 @@ -92,7 +92,8 @@ LANGFUSE_CAPTURE_TOOL_OUTPUTS=false ## 在 ZCode 中本地测试 ZCode 选择本地目录时会按“插件市场”读取,因此仓库根目录包含 -`marketplace.json`。先运行 `npm run build`,再在 **设置 → 插件 → 添加插件市场 → +`marketplace.json`,其条目指向 `./dist/plugins/zcode-plugin-langfuse`。先运行 +`npm run build`,再在 **设置 → 插件 → 添加插件市场 → 选择目录** 中选择本仓库,从“个人”市场安装并启用 `zcode-plugin-langfuse`。正式市场使用版本化 ZIP 和 SHA-256。 @@ -125,16 +126,25 @@ npm run package:plugin ``` `npm run build` 会把官方 `langfuse` JavaScript SDK 打包进本地 `dist/`。 -`npm run build` 会构建完整的 `dist/` 输出,结构遵循 ZCode 官方市场布局: +`npm run build` 会构建完整的 `dist/` 输出:外层是插件市场壳,内层插件目录与 +ZCode 官方模板(`zcode-plugins-official`)布局一致: ```text dist/marketplace.json dist/plugins/zcode-plugin-langfuse/ +├── .zcode-plugin/plugin.json +├── .claude-plugin/plugin.json (同内容副本,兼容 Claude) +├── hooks/hooks.json (指向 hooks/entry.mjs) +├── hooks/entry.mjs (密封运行时 bundle) +├── README.md +├── README_CN.md +├── LICENSE +└── THIRD_PARTY_NOTICES.md ``` -`dist/plugins/zcode-plugin-langfuse/` 即可安装的插件:包含 `dist/hooks/entry.mjs` -运行时、manifest、hooks、包元数据、双语 README、许可证与第三方声明。Release -工作流负责把该目录压缩为版本化 ZIP;官方目录与本地目录安装直接使用这棵树。 +`dist/plugins/zcode-plugin-langfuse/` 即可安装的插件:包含 `hooks/entry.mjs` +运行时、manifest、hooks、双语 README、许可证与第三方声明。Release +工作流负责把该目录压缩为版本化 ZIP;官方目录与本地目录安装直接使用该插件目录。 整个 `dist/` 均为生成目录,不入库。 目录分层: diff --git a/plugins/zcode-plugin-langfuse/payload/dist/hooks/entry.mjs b/plugins/zcode-plugin-langfuse/hooks/entry.mjs similarity index 100% rename from plugins/zcode-plugin-langfuse/payload/dist/hooks/entry.mjs rename to plugins/zcode-plugin-langfuse/hooks/entry.mjs diff --git a/plugins/zcode-plugin-langfuse/hooks/hooks.json b/plugins/zcode-plugin-langfuse/hooks/hooks.json index 1b179bb..ab20687 100644 --- a/plugins/zcode-plugin-langfuse/hooks/hooks.json +++ b/plugins/zcode-plugin-langfuse/hooks/hooks.json @@ -8,7 +8,7 @@ "type": "process", "command": "node", "args": [ - "${ZCODE_PLUGIN_ROOT}/payload/dist/hooks/entry.mjs" + "${ZCODE_PLUGIN_ROOT}/hooks/entry.mjs" ], "timeoutMs": 5000, "statusMessage": "Langfuse: preparing session tracing…" @@ -23,7 +23,7 @@ "type": "process", "command": "node", "args": [ - "${ZCODE_PLUGIN_ROOT}/payload/dist/hooks/entry.mjs" + "${ZCODE_PLUGIN_ROOT}/hooks/entry.mjs" ], "timeoutMs": 5000 } @@ -37,7 +37,7 @@ "type": "process", "command": "node", "args": [ - "${ZCODE_PLUGIN_ROOT}/payload/dist/hooks/entry.mjs" + "${ZCODE_PLUGIN_ROOT}/hooks/entry.mjs" ], "timeoutMs": 5000 } @@ -51,7 +51,7 @@ "type": "process", "command": "node", "args": [ - "${ZCODE_PLUGIN_ROOT}/payload/dist/hooks/entry.mjs" + "${ZCODE_PLUGIN_ROOT}/hooks/entry.mjs" ], "timeoutMs": 5000 } @@ -65,7 +65,7 @@ "type": "process", "command": "node", "args": [ - "${ZCODE_PLUGIN_ROOT}/payload/dist/hooks/entry.mjs" + "${ZCODE_PLUGIN_ROOT}/hooks/entry.mjs" ], "timeoutMs": 5000 } @@ -79,7 +79,7 @@ "type": "process", "command": "node", "args": [ - "${ZCODE_PLUGIN_ROOT}/payload/dist/hooks/entry.mjs" + "${ZCODE_PLUGIN_ROOT}/hooks/entry.mjs" ], "timeoutMs": 20000, "statusMessage": "Langfuse: sending trace…" diff --git a/plugins/zcode-plugin-langfuse/package.json b/plugins/zcode-plugin-langfuse/package.json deleted file mode 100644 index 64cf337..0000000 --- a/plugins/zcode-plugin-langfuse/package.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "zcode-plugin-langfuse", - "version": "0.2.1", - "private": true, - "description": "A fail-open Langfuse observability plugin for ZCode.", - "license": "MIT", - "type": "module", - "engines": { - "node": ">=20" - }, - "scripts": { - "build": "tsc --noEmit && node scripts/build.mjs", - "clean": "node scripts/clean.mjs", - "test": "vitest run", - "lint": "oxlint src test scripts", - "lint:fix": "oxlint --fix src test scripts", - "typecheck": "tsc --noEmit", - "validate": "node scripts/validate.mjs", - "validate:artifact": "node scripts/validate.mjs --artifact", - "sync:catalog": "node scripts/sync-catalog.mjs", - "package:plugin": "npm run build && npm run validate && npm run validate:artifact", - "check": "npm run lint && npm run typecheck && npm test && npm run validate", - "prepare": "husky && npm run build" - }, - "dependencies": { - "langfuse": "^3.38.20" - }, - "devDependencies": { - "@types/node": "^26.4.1", - "esbuild": "^0.28.2", - "husky": "^9.1.7", - "lint-staged": "^17.5.1", - "oxlint": "^1.82.0", - "typescript": "^7.0.2", - "vitest": "^5.0.0" - } -} From fff8881c739e671cf834060373dee4c1590abf72 Mon Sep 17 00:00:00 2001 From: erlinerd Date: Mon, 14 Sep 2026 14:28:42 +0800 Subject: [PATCH 11/13] chore(catalog): sync zcode-plugin-langfuse v0.2.2 --- marketplace.json | 2 +- plugins/zcode-plugin-langfuse/.claude-plugin/plugin.json | 2 +- plugins/zcode-plugin-langfuse/.zcode-plugin/plugin.json | 2 +- plugins/zcode-plugin-langfuse/hooks/entry.mjs | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/marketplace.json b/marketplace.json index 218ebd3..53b7647 100644 --- a/marketplace.json +++ b/marketplace.json @@ -537,7 +537,7 @@ "name": "zcode-plugin-langfuse", "source": "./plugins/zcode-plugin-langfuse", "description": "Fail-open Langfuse tracing for ZCode sessions and tool calls.", - "version": "0.2.1", + "version": "0.2.2", "category": "observability", "tags": [ "langfuse", diff --git a/plugins/zcode-plugin-langfuse/.claude-plugin/plugin.json b/plugins/zcode-plugin-langfuse/.claude-plugin/plugin.json index 3c5318c..a278718 100644 --- a/plugins/zcode-plugin-langfuse/.claude-plugin/plugin.json +++ b/plugins/zcode-plugin-langfuse/.claude-plugin/plugin.json @@ -5,7 +5,7 @@ "en": "Fail-open Langfuse tracing for ZCode sessions, prompts, tool calls, and assistant responses.", "zh-CN": "为 ZCode 会话、提示词、工具调用和助手回复提供 fail-open 的 Langfuse 追踪。" }, - "version": "0.2.1", + "version": "0.2.2", "author": { "name": "Community contributor" }, diff --git a/plugins/zcode-plugin-langfuse/.zcode-plugin/plugin.json b/plugins/zcode-plugin-langfuse/.zcode-plugin/plugin.json index 3c5318c..a278718 100644 --- a/plugins/zcode-plugin-langfuse/.zcode-plugin/plugin.json +++ b/plugins/zcode-plugin-langfuse/.zcode-plugin/plugin.json @@ -5,7 +5,7 @@ "en": "Fail-open Langfuse tracing for ZCode sessions, prompts, tool calls, and assistant responses.", "zh-CN": "为 ZCode 会话、提示词、工具调用和助手回复提供 fail-open 的 Langfuse 追踪。" }, - "version": "0.2.1", + "version": "0.2.2", "author": { "name": "Community contributor" }, diff --git a/plugins/zcode-plugin-langfuse/hooks/entry.mjs b/plugins/zcode-plugin-langfuse/hooks/entry.mjs index 3296bdf..f10d506 100644 --- a/plugins/zcode-plugin-langfuse/hooks/entry.mjs +++ b/plugins/zcode-plugin-langfuse/hooks/entry.mjs @@ -568,7 +568,7 @@ var JsonStateStore = class { } }; -// node_modules/.pnpm/mustache@4.2.0/node_modules/mustache/mustache.mjs +// node_modules/mustache/mustache.mjs var objectToString = Object.prototype.toString; var isArray = Array.isArray || function isArrayPolyfill(object) { return objectToString.call(object) === "[object Array]"; @@ -1022,7 +1022,7 @@ mustache.Context = Context; mustache.Writer = Writer; var mustache_default = mustache; -// node_modules/.pnpm/langfuse-core@3.38.20/node_modules/langfuse-core/lib/index.mjs +// node_modules/langfuse-core/lib/index.mjs var SimpleEventEmitter = class { constructor() { this.events = {}; @@ -3152,7 +3152,7 @@ var LangfuseEventClient = class extends LangfuseObservationClient { } }; -// node_modules/.pnpm/langfuse@3.38.20/node_modules/langfuse/lib/index.mjs +// node_modules/langfuse/lib/index.mjs var cookieStore = { getItem(key) { try { From 40bf57d126b6e853c65e33ac67b4b74dc65ed49e Mon Sep 17 00:00:00 2001 From: erlinerd Date: Mon, 14 Sep 2026 19:05:08 +0800 Subject: [PATCH 12/13] chore(catalog): sync zcode-plugin-langfuse v0.2.3 --- marketplace.json | 2 +- plugins/zcode-plugin-langfuse/.claude-plugin/plugin.json | 2 +- plugins/zcode-plugin-langfuse/.zcode-plugin/plugin.json | 2 +- plugins/zcode-plugin-langfuse/README.md | 2 +- plugins/zcode-plugin-langfuse/README_CN.md | 2 +- plugins/zcode-plugin-langfuse/hooks/entry.mjs | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/marketplace.json b/marketplace.json index 53b7647..dbf380a 100644 --- a/marketplace.json +++ b/marketplace.json @@ -537,7 +537,7 @@ "name": "zcode-plugin-langfuse", "source": "./plugins/zcode-plugin-langfuse", "description": "Fail-open Langfuse tracing for ZCode sessions and tool calls.", - "version": "0.2.2", + "version": "0.2.3", "category": "observability", "tags": [ "langfuse", diff --git a/plugins/zcode-plugin-langfuse/.claude-plugin/plugin.json b/plugins/zcode-plugin-langfuse/.claude-plugin/plugin.json index a278718..8326021 100644 --- a/plugins/zcode-plugin-langfuse/.claude-plugin/plugin.json +++ b/plugins/zcode-plugin-langfuse/.claude-plugin/plugin.json @@ -5,7 +5,7 @@ "en": "Fail-open Langfuse tracing for ZCode sessions, prompts, tool calls, and assistant responses.", "zh-CN": "为 ZCode 会话、提示词、工具调用和助手回复提供 fail-open 的 Langfuse 追踪。" }, - "version": "0.2.2", + "version": "0.2.3", "author": { "name": "Community contributor" }, diff --git a/plugins/zcode-plugin-langfuse/.zcode-plugin/plugin.json b/plugins/zcode-plugin-langfuse/.zcode-plugin/plugin.json index a278718..8326021 100644 --- a/plugins/zcode-plugin-langfuse/.zcode-plugin/plugin.json +++ b/plugins/zcode-plugin-langfuse/.zcode-plugin/plugin.json @@ -5,7 +5,7 @@ "en": "Fail-open Langfuse tracing for ZCode sessions, prompts, tool calls, and assistant responses.", "zh-CN": "为 ZCode 会话、提示词、工具调用和助手回复提供 fail-open 的 Langfuse 追踪。" }, - "version": "0.2.2", + "version": "0.2.3", "author": { "name": "Community contributor" }, diff --git a/plugins/zcode-plugin-langfuse/README.md b/plugins/zcode-plugin-langfuse/README.md index aaf0607..85b3bb6 100644 --- a/plugins/zcode-plugin-langfuse/README.md +++ b/plugins/zcode-plugin-langfuse/README.md @@ -6,7 +6,7 @@ A community ZCode plugin that sends one Langfuse trace per completed ZCode turn. It is designed for review and possible inclusion in the ZCode official plugin marketplace. -> **Status:** early community contribution (`0.2.1`). The plugin is fail-open: +> **Status:** early community contribution (`0.2.3`). The plugin is fail-open: > a missing credential, malformed hook payload, local state error, or Langfuse > request error must never block a ZCode session. diff --git a/plugins/zcode-plugin-langfuse/README_CN.md b/plugins/zcode-plugin-langfuse/README_CN.md index 52f1eb9..2068418 100644 --- a/plugins/zcode-plugin-langfuse/README_CN.md +++ b/plugins/zcode-plugin-langfuse/README_CN.md @@ -5,7 +5,7 @@ 面向 ZCode 的社区 Langfuse 观测插件,按每个完成的 ZCode turn 生成一条 Langfuse trace,目标是提交给 ZCode 官方插件市场。 -> 当前版本:`0.2.1`。插件遵循 **fail-open**:缺少凭据、Hook 输入损坏、本地 +> 当前版本:`0.2.3`。插件遵循 **fail-open**:缺少凭据、Hook 输入损坏、本地 > 状态错误或 Langfuse 请求失败,都不能阻塞 ZCode 会话。 ## 采集范围 diff --git a/plugins/zcode-plugin-langfuse/hooks/entry.mjs b/plugins/zcode-plugin-langfuse/hooks/entry.mjs index f10d506..b069afb 100644 --- a/plugins/zcode-plugin-langfuse/hooks/entry.mjs +++ b/plugins/zcode-plugin-langfuse/hooks/entry.mjs @@ -8,7 +8,7 @@ import { readFileSync } from "node:fs"; import { homedir } from "node:os"; import { join } from "node:path"; var DEFAULT_BASE_URL = "https://cloud.langfuse.com"; -var DEFAULT_RELEASE = "0.1.1"; +var DEFAULT_RELEASE = "0.2.3"; var DEFAULT_MAX_CAPTURE_CHARS = 2e4; function asText(value) { if (value === void 0) return void 0; From 1de2456088b8c8f2c03d49fffdd25ca4a6d09efa Mon Sep 17 00:00:00 2001 From: erlinerd Date: Mon, 14 Sep 2026 19:23:54 +0800 Subject: [PATCH 13/13] chore(catalog): sync zcode-plugin-langfuse v0.2.3 --- marketplace.json | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/marketplace.json b/marketplace.json index dbf380a..a757acd 100644 --- a/marketplace.json +++ b/marketplace.json @@ -536,9 +536,13 @@ { "name": "zcode-plugin-langfuse", "source": "./plugins/zcode-plugin-langfuse", - "description": "Fail-open Langfuse tracing for ZCode sessions and tool calls.", + "description": "Fail-open Langfuse tracing for ZCode sessions, prompts, tool calls, and assistant responses.", + "description_i18n": { + "en": "Fail-open Langfuse tracing for ZCode sessions, prompts, tool calls, and assistant responses.", + "zh-CN": "为 ZCode 会话、提示词、工具调用和助手回复提供 fail-open 的 Langfuse 追踪。" + }, "version": "0.2.3", - "category": "observability", + "category": "developer-tools", "tags": [ "langfuse", "tracing",