@narumitw/pi-langfuse 0.50.1 → 0.50.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +109 -51
- package/dist/chunks/runtime-NX5HM7IU.ts +218 -0
- package/dist/chunks/runtime-NX5HM7IU.ts.map +7 -0
- package/dist/index.ts +1478 -0
- package/dist/index.ts.map +7 -0
- package/package.json +11 -7
package/README.md
CHANGED
|
@@ -1,24 +1,18 @@
|
|
|
1
|
-
# 🪢 pi-langfuse —
|
|
1
|
+
# 🪢 pi-langfuse — Trace Pi Runs in Langfuse
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@narumitw/pi-langfuse) [](https://pi.dev) [](./LICENSE)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Trace Pi agent runs, model generations, retries, tools, compaction, usage, and timing in [Langfuse](https://langfuse.com/) through OpenTelemetry.
|
|
6
6
|
|
|
7
7
|
## ✨ Features
|
|
8
8
|
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
- Records final tool inputs and outputs, progress timing, duration, and failures without exporting partial-result content.
|
|
14
|
-
- Records active compactions structurally without exporting generated summary text.
|
|
15
|
-
- Groups traces with Pi's session id and adds bounded session/context snapshots and aggregate counters.
|
|
16
|
-
- Adds the run-start Git branch and commit as metadata plus a filterable branch tag.
|
|
17
|
-
- Reads Langfuse credentials and options only from a private `pi-langfuse.json` file.
|
|
18
|
-
- Batches routine exports without delaying normal Pi agent completion.
|
|
19
|
-
- Keeps its OpenTelemetry provider isolated so it coexists with other tracing extensions.
|
|
20
|
-
- Redacts Langfuse public and secret keys from exported trace data.
|
|
9
|
+
- Groups each settled agent run into one trace with indexed attempt spans for retries and queued continuations.
|
|
10
|
+
- Records bounded provider requests, final assistant output, identity, TTFT, usage, known costs, and safe response diagnostics.
|
|
11
|
+
- Captures final tool inputs and outputs, progress timing, duration, failures, and structural compaction events.
|
|
12
|
+
- Adds session, context, Git branch, commit, and aggregate counters as searchable metadata.
|
|
21
13
|
- Supports metadata-only tracing when content capture is disabled.
|
|
14
|
+
- Reads credentials only from a private local settings file and redacts them from exported data.
|
|
15
|
+
- Batches exports without delaying normal completion and isolates its OpenTelemetry provider from other extensions.
|
|
22
16
|
|
|
23
17
|
## 📦 Install
|
|
24
18
|
|
|
@@ -35,12 +29,19 @@ pi -e npm:@narumitw/pi-langfuse
|
|
|
35
29
|
Try a local checkout:
|
|
36
30
|
|
|
37
31
|
```bash
|
|
32
|
+
npm --workspace @narumitw/pi-langfuse run build
|
|
38
33
|
pi -e ./packages/pi-langfuse
|
|
39
34
|
```
|
|
40
35
|
|
|
36
|
+
An unbuilt local checkout must be built before loading the package directory.
|
|
41
37
|
The Langfuse v4 SDK requires Node.js 20 or newer.
|
|
42
38
|
|
|
43
|
-
##
|
|
39
|
+
## 🚀 Quick start
|
|
40
|
+
|
|
41
|
+
Run `/langfuse`, choose the setup action, enter the Langfuse credentials, and restart Pi.
|
|
42
|
+
Subsequent agent runs are traced according to the saved content-capture and metadata settings.
|
|
43
|
+
|
|
44
|
+
## ⚙️ Settings
|
|
44
45
|
|
|
45
46
|
Run the interactive manager, then choose **Set up Langfuse for this Pi agent directory** or **Update Langfuse for this Pi agent directory**:
|
|
46
47
|
|
|
@@ -48,9 +49,18 @@ Run the interactive manager, then choose **Set up Langfuse for this Pi agent dir
|
|
|
48
49
|
/langfuse
|
|
49
50
|
```
|
|
50
51
|
|
|
51
|
-
The setup flow prompts for the secret key, public key, and base URL in the same order Langfuse presents them.
|
|
52
|
+
The setup flow prompts for the secret key, public key, and base URL in the same order Langfuse presents them.
|
|
53
|
+
Leave either key blank to preserve its existing value when updating a valid config.
|
|
54
|
+
Leave the base URL blank to use `https://us.cloud.langfuse.com`.
|
|
55
|
+
The first successful setup creates the file.
|
|
56
|
+
Within one Pi process, updates run in invocation order, reread the latest valid private document, preserve unknown fields, and save atomically with mode `0600`.
|
|
57
|
+
Malformed JSON or invalid recognized fields block setup and update writes until repaired; errors redact entered and stored credentials.
|
|
58
|
+
A failed save leaves the current session and prior file unchanged.
|
|
52
59
|
|
|
53
|
-
Configuration belongs to the displayed Pi agent directory, not just the current conversation.
|
|
60
|
+
Configuration belongs to the displayed Pi agent directory, not just the current conversation.
|
|
61
|
+
Restart each running Pi process after saving; the new connection applies to subsequent sessions in that process.
|
|
62
|
+
`/reload` is not sufficient because the isolated Langfuse runtime is initialized once per process.
|
|
63
|
+
In print or JSON mode, edit the file manually because the interactive manager is unavailable.
|
|
54
64
|
|
|
55
65
|
You can also create the file manually:
|
|
56
66
|
|
|
@@ -66,17 +76,26 @@ You can also create the file manually:
|
|
|
66
76
|
}
|
|
67
77
|
```
|
|
68
78
|
|
|
69
|
-
`publicKey` and `secretKey` are required literal strings.
|
|
79
|
+
`publicKey` and `secretKey` are required literal strings.
|
|
80
|
+
Environment-variable and command interpolation are intentionally unsupported.
|
|
81
|
+
`baseUrl` defaults to `https://us.cloud.langfuse.com`; regional and self-hosted HTTP or HTTPS endpoints are supported.
|
|
82
|
+
Prefer HTTPS because HTTP sends Langfuse credentials and trace content without transport encryption.
|
|
70
83
|
|
|
71
|
-
`environment`, `release`, and `userId` are optional Langfuse trace attributes.
|
|
84
|
+
`environment`, `release`, and `userId` are optional Langfuse trace attributes.
|
|
85
|
+
An environment must match Langfuse's contract: at most 40 lowercase letters, numbers, hyphens, or underscores, and it cannot start with `langfuse`.
|
|
86
|
+
`userId` populates the Langfuse user dimension, which is what the Sessions and Traces views group by; Langfuse accepts at most 200 characters, and leaving it unset reports no user.
|
|
87
|
+
Set `captureContent` to `false` to trace timing, model, usage, cost, status, and bounded diagnostic metadata without sending prompts, provider-request snapshots, responses, or tool content.
|
|
72
88
|
|
|
73
|
-
The extension automatically restricts an existing config file to mode `0600` and refuses to load credentials if that protection cannot be enforced.
|
|
89
|
+
The extension automatically restricts an existing config file to mode `0600` and refuses to load credentials if that protection cannot be enforced.
|
|
90
|
+
You can also set it explicitly:
|
|
74
91
|
|
|
75
92
|
```bash
|
|
76
93
|
chmod 600 ~/.pi/agent/pi-langfuse.json
|
|
77
94
|
```
|
|
78
95
|
|
|
79
|
-
Restart Pi after changing credentials, endpoint, environment, release, or `captureContent`.
|
|
96
|
+
Restart Pi after changing credentials, endpoint, environment, release, or `captureContent`.
|
|
97
|
+
Changes to `userId` apply to new sessions without restarting Pi.
|
|
98
|
+
The isolated OpenTelemetry tracer provider is initialized once per Pi process and selected only for Langfuse; it does not replace Pi's process-global provider or send Langfuse observations to another extension's exporter.
|
|
80
99
|
|
|
81
100
|
## 🔭 What is traced
|
|
82
101
|
|
|
@@ -93,20 +112,27 @@ pi.trace
|
|
|
93
112
|
└── pi.attempt ...
|
|
94
113
|
```
|
|
95
114
|
|
|
96
|
-
All observations and the trace use schema version `2`.
|
|
115
|
+
All observations and the trace use schema version `2`.
|
|
116
|
+
Schema version 2 adds indexed `pi.attempt` observations and active `pi.compaction` spans beneath the root agent.
|
|
97
117
|
|
|
98
118
|
### Trace and attempt fields
|
|
99
119
|
|
|
100
|
-
The trace and root `pi.agent` retain the submitted prompt, final assistant output, Pi session id, working directory, mode, initial provider/model, and optional Git context.
|
|
120
|
+
The trace and root `pi.agent` retain the submitted prompt, final assistant output, Pi session id, working directory, mode, initial provider/model, and optional Git context.
|
|
121
|
+
Root metadata includes:
|
|
101
122
|
|
|
102
123
|
- `pi.trace.schema_version`, `pi.trace.outcome`, and `pi.trace.stop_reason`;
|
|
103
124
|
- `pi.trace.attempt_count`, `pi.trace.turn_count`, `pi.trace.generation_count`, `pi.trace.tool_count`, `pi.trace.tool_error_count`, `pi.trace.compaction_count`, and `pi.trace.recovered_error_count`;
|
|
104
125
|
- `pi.trace.start_leaf_id`, `pi.trace.end_leaf_id`, `pi.trace.start_context_tokens`, `pi.trace.end_context_tokens`, `pi.trace.start_context_window`, `pi.trace.end_context_window`, `pi.trace.start_context_percent`, and `pi.trace.end_context_percent` when Pi knows them;
|
|
105
126
|
- `pi.git.branch`, `pi.git.commit`, and `pi.git.detached`, plus a `branch:<branch-name>` tag or `git:detached` tag.
|
|
106
127
|
|
|
107
|
-
Outcomes are `success`, `recovered_success`, `error`, `aborted`, `length`, or `interrupted`.
|
|
128
|
+
Outcomes are `success`, `recovered_success`, `error`, `aborted`, `length`, or `interrupted`.
|
|
129
|
+
`pi.trace.recovered_error_count` includes recovered provider responses, tool failures handled by a later generation, and failed attempts followed by final success.
|
|
130
|
+
Errors use Langfuse `ERROR`; aborts, output limits, shutdown, replacement, and other interruption closures use `WARNING`.
|
|
131
|
+
High-cardinality correlation values stay in metadata rather than tags.
|
|
108
132
|
|
|
109
|
-
Each `pi.attempt` records `pi.attempt.index`, final `pi.attempt.outcome`, and `pi.attempt.stop_reason`.
|
|
133
|
+
Each `pi.attempt` records `pi.attempt.index`, final `pi.attempt.outcome`, and `pi.attempt.stop_reason`.
|
|
134
|
+
An attempt immediately following overflow compaction also sets `pi.attempt.reason` to `post_compaction`.
|
|
135
|
+
Failed attempts remain errors even when a later attempt makes the root a recovered success.
|
|
110
136
|
|
|
111
137
|
### Generation fields
|
|
112
138
|
|
|
@@ -117,39 +143,62 @@ Each `pi.llm` generation records:
|
|
|
117
143
|
- the Langfuse-native response model plus `pi.response.provider`, `pi.response.api`, `pi.response.model`, and `pi.response.id` when Pi reports them;
|
|
118
144
|
- Langfuse-native `completionStartTime` from the first non-empty text, thinking, or tool-call delta;
|
|
119
145
|
- ordered `http.response.status_codes`, final `http.response.status_code`, `http.response.attempt_count`, and `http.response.retry_count`;
|
|
120
|
-
- allowlisted `http.response.headers`: request ids, `cf-ray`, `retry-after`, and the supported OpenAI/Anthropic rate-limit headers.
|
|
146
|
+
- allowlisted `http.response.headers`: request ids, `cf-ray`, `retry-after`, and the supported OpenAI/Anthropic rate-limit headers.
|
|
147
|
+
Authorization, cookies, and unrecognized headers are never exported;
|
|
121
148
|
- additive input, output, cache-read, cache-write, and total token usage plus known positive input, output, cache-read, cache-write, and total cost buckets;
|
|
122
149
|
- non-additive `pi.usage.reasoning_tokens` and `pi.usage.cache_write_1h_tokens` in metadata so subsets are not double-counted.
|
|
123
150
|
|
|
124
|
-
The request snapshot is the payload visible at this handler, not a guaranteed final wire payload: later extensions can still replace it.
|
|
151
|
+
The request snapshot is the payload visible at this handler, not a guaranteed final wire payload: later extensions can still replace it.
|
|
152
|
+
Final assistant content is reconciled from `turn_end` and `agent_end` after message transformation.
|
|
153
|
+
A recovered sequence such as `429 -> 200` remains queryable in HTTP metadata but is not an error; the final assistant outcome decides generation severity.
|
|
125
154
|
|
|
126
155
|
### Tool and compaction fields
|
|
127
156
|
|
|
128
|
-
A `pi.tool.<tool-name>` observation starts with raw `tool_execution_start` arguments as a fallback for calls that never execute, including calls blocked during `tool_call`.
|
|
157
|
+
A `pi.tool.<tool-name>` observation starts with raw `tool_execution_start` arguments as a fallback for calls that never execute, including calls blocked during `tool_call`.
|
|
158
|
+
For executed calls, it uses the `tool_result` input as authoritative after all argument mutations.
|
|
159
|
+
It captures final transformed output from `tool_execution_end`, final error state, `pi.tool.progress_update_count`, and `pi.tool.time_to_first_progress_ms` when progress occurs.
|
|
160
|
+
An unrecovered tool failure also makes the attempt and root errors.
|
|
161
|
+
Existing `pi.tool.call_id` and `pi.tool.name` correlation fields remain.
|
|
162
|
+
`tool_execution_update` partial-result bodies are never captured.
|
|
163
|
+
Duplicate, parallel, failed, no-progress, and interrupted tools are closed independently.
|
|
129
164
|
|
|
130
|
-
An active `pi.compaction` records `pi.compaction.reason`, `pi.compaction.will_retry`, `pi.compaction.from_extension`, `pi.compaction.tokens_before`, `pi.compaction.messages_to_summarize`, `pi.compaction.turn_prefix_messages`, `pi.compaction.branch_entries`, and `pi.compaction.is_split_turn`.
|
|
165
|
+
An active `pi.compaction` records `pi.compaction.reason`, `pi.compaction.will_retry`, `pi.compaction.from_extension`, `pi.compaction.tokens_before`, `pi.compaction.messages_to_summarize`, `pi.compaction.turn_prefix_messages`, `pi.compaction.branch_entries`, and `pi.compaction.is_split_turn`.
|
|
166
|
+
It adds `pi.compaction.read_file_count`, `pi.compaction.modified_file_count`, and `pi.compaction.usage.*` / `pi.compaction.cost.*` when Pi reports them.
|
|
167
|
+
It never records the summary, custom instructions, or message bodies.
|
|
168
|
+
Manual compaction outside an active agent trace is ignored; incomplete compaction closes as a warning at settlement or shutdown.
|
|
131
169
|
|
|
132
170
|
### Boundaries and export
|
|
133
171
|
|
|
134
|
-
Images and embedded base64 data URIs are represented without their payloads, including provider data URLs.
|
|
172
|
+
Images and embedded base64 data URIs are represented without their payloads, including provider data URLs.
|
|
173
|
+
Opaque `thinkingSignature`, `textSignature`, and `thoughtSignature` continuity values are always removed.
|
|
174
|
+
Every captured input or output has one cumulative 64 KiB serialized UTF-8 budget, bounded object/array traversal, and deterministic truncation markers.
|
|
175
|
+
Langfuse credentials are masked again in the span processor before network export.
|
|
135
176
|
|
|
136
|
-
The root agent begins before the first agent loop and remains open across retries, overflow-compaction recovery, and queued continuations.
|
|
177
|
+
The root agent begins before the first agent loop and remains open across retries, overflow-compaction recovery, and queued continuations.
|
|
178
|
+
`agent_end` closes only the current attempt; `agent_settled` closes the root after no automatic work remains.
|
|
179
|
+
Activity that unexpectedly arrives without a submitted prompt gets a fallback root input labeled `[automatic continuation]`.
|
|
180
|
+
Session replacement, reload, quit, and a new unexpected prompt close all descendants defensively and idempotently.
|
|
137
181
|
|
|
138
|
-
At run start, the extension performs bounded, non-shell Git lookups in `ctx.cwd`.
|
|
182
|
+
At run start, the extension performs bounded, non-shell Git lookups in `ctx.cwd`.
|
|
183
|
+
A branch switch therefore applies to the next run.
|
|
184
|
+
Detached HEADs retain only commit/detached metadata and the `git:detached` tag.
|
|
185
|
+
Missing Git, non-repositories, timeouts, and lookup failures silently omit Git context without affecting tracing.
|
|
139
186
|
|
|
140
|
-
Completed observations are exported in batches while Pi remains live.
|
|
187
|
+
Completed observations are exported in batches while Pi remains live.
|
|
188
|
+
Neither `agent_end` nor `agent_settled` waits for Langfuse network I/O.
|
|
189
|
+
To wait for completed exports, run `/langfuse` and choose **Flush completed traces for this session**; quit shutdown also drains the provider.
|
|
141
190
|
|
|
142
|
-
## 💬
|
|
191
|
+
## 💬 Commands
|
|
143
192
|
|
|
144
193
|
```text
|
|
145
194
|
/langfuse
|
|
146
195
|
```
|
|
147
196
|
|
|
148
|
-
The command opens one context-aware standard menu in TUI or RPC mode.
|
|
149
|
-
current session's tracing state, endpoint, content-capture mode, initialization failure when
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
setup/update semantics rather than standard navigation.
|
|
197
|
+
The command opens one context-aware standard menu in TUI or RPC mode.
|
|
198
|
+
Its state lines show the current session's tracing state, endpoint, content-capture mode, initialization failure when applicable, and private configuration path.
|
|
199
|
+
It never displays credentials.
|
|
200
|
+
Escape closes the menu.
|
|
201
|
+
Credential and endpoint text inputs remain extension-owned because they enforce secret-preserving setup/update semantics rather than standard navigation.
|
|
153
202
|
|
|
154
203
|
Available actions depend on that state:
|
|
155
204
|
|
|
@@ -158,30 +207,37 @@ Available actions depend on that state:
|
|
|
158
207
|
- **Update Langfuse for this Pi agent directory** appears when a valid config exists.
|
|
159
208
|
- **Show setup and privacy help** explains the agent-directory scope, manual configuration path, and content-capture risk.
|
|
160
209
|
|
|
161
|
-
Connection actions state their agent-directory scope and per-process restart requirement before
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
includes current tracing state and the manual configuration path.
|
|
210
|
+
Connection actions state their agent-directory scope and per-process restart requirement before selection.
|
|
211
|
+
Command arguments are intentionally ignored so remembered subcommands cannot silently bypass the menu.
|
|
212
|
+
Print and JSON modes reject the interactive command with an observable error that includes current tracing state and the manual configuration path.
|
|
165
213
|
|
|
166
|
-
##
|
|
214
|
+
## 🔒 Security and privacy
|
|
167
215
|
|
|
168
|
-
With content capture enabled, traces can contain user prompts, model responses, tool arguments, and tool results.
|
|
216
|
+
With content capture enabled, traces can contain user prompts, model responses, tool arguments, and tool results.
|
|
217
|
+
These may include source code, file contents, shell output, or other sensitive project data.
|
|
218
|
+
Review your Langfuse retention and access controls before enabling this extension.
|
|
169
219
|
|
|
170
|
-
Git branch names, commit ids, working directory, session/leaf ids, the configured `userId`, model identity, usage/cost, aggregate counts, and allowlisted response-header values are metadata.
|
|
220
|
+
Git branch names, commit ids, working directory, session/leaf ids, the configured `userId`, model identity, usage/cost, aggregate counts, and allowlisted response-header values are metadata.
|
|
221
|
+
They remain exported when `captureContent` is `false`; branch names and diagnostic header values can themselves contain operational details, and a `userId` may itself be personally identifying if you set it to an email address or a real name.
|
|
222
|
+
Choose a pseudonymous value when that matters, or leave `userId` unset to export no user at all.
|
|
171
223
|
|
|
172
|
-
The built-in mask specifically protects Langfuse credentials; it is not a general secret scanner.
|
|
224
|
+
The built-in mask specifically protects Langfuse credentials; it is not a general secret scanner.
|
|
225
|
+
Set `"captureContent": false` in `pi-langfuse.json` when prompts, provider-request snapshots, responses, and tool content must remain local.
|
|
226
|
+
Compaction summaries, tool partial results, opaque continuation signatures, authorization headers, cookies, and unapproved response headers are never exported in either mode.
|
|
173
227
|
|
|
174
228
|
## 🗂️ Package layout
|
|
175
229
|
|
|
176
230
|
```txt
|
|
177
231
|
packages/pi-langfuse/
|
|
178
232
|
├── src/
|
|
179
|
-
│ ├── index.ts #
|
|
233
|
+
│ ├── index.ts # Thin repository entrypoint
|
|
180
234
|
│ ├── langfuse.ts # Pi lifecycle integration and slash command
|
|
181
235
|
│ ├── tracing.ts # Observation lifecycle, outcomes, and bounded metadata
|
|
182
236
|
│ ├── sanitizer.ts # Content bounding and opaque-signature removal
|
|
183
|
-
│ ├── runtime.ts # Langfuse/OpenTelemetry runtime
|
|
237
|
+
│ ├── runtime.ts # Lazy Langfuse/OpenTelemetry runtime
|
|
184
238
|
│ └── config.ts # Private pi-langfuse.json loading and validation
|
|
239
|
+
├── dist/ # Generated Jiti runtime and lazy chunks
|
|
240
|
+
├── scripts/build-runtime.mjs
|
|
185
241
|
├── test/
|
|
186
242
|
├── README.md
|
|
187
243
|
├── LICENSE
|
|
@@ -189,7 +245,8 @@ packages/pi-langfuse/
|
|
|
189
245
|
└── package.json
|
|
190
246
|
```
|
|
191
247
|
|
|
192
|
-
|
|
248
|
+
The package build generates the published Pi entrypoint at `dist/index.ts` and preserves `runtime.ts` as a first-use chunk.
|
|
249
|
+
The source modules remain authoritative.
|
|
193
250
|
|
|
194
251
|
## 🔎 Keywords
|
|
195
252
|
|
|
@@ -197,4 +254,5 @@ Pi extension, Pi coding agent, Langfuse, LLM observability, OpenTelemetry, traci
|
|
|
197
254
|
|
|
198
255
|
## 📄 License
|
|
199
256
|
|
|
200
|
-
MIT.
|
|
257
|
+
MIT.
|
|
258
|
+
See [`LICENSE`](./LICENSE).
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
// @generated by scripts/build-runtime.mjs; do not edit.
|
|
2
|
+
// @ts-nocheck -- generated JavaScript uses a .ts extension for Pi's Jiti loader.
|
|
3
|
+
|
|
4
|
+
// src/runtime.ts
|
|
5
|
+
import { createHash } from "node:crypto";
|
|
6
|
+
import { LangfuseSpanProcessor } from "@langfuse/otel";
|
|
7
|
+
import {
|
|
8
|
+
LangfuseOtelSpanAttributes,
|
|
9
|
+
setLangfuseTracerProvider,
|
|
10
|
+
startObservation
|
|
11
|
+
} from "@langfuse/tracing";
|
|
12
|
+
import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
|
|
13
|
+
var RUNTIME_KEY = /* @__PURE__ */ Symbol.for("@narumitw/pi-langfuse/runtime/v1");
|
|
14
|
+
var ProductionObservation = class {
|
|
15
|
+
constructor(native) {
|
|
16
|
+
this.native = native;
|
|
17
|
+
}
|
|
18
|
+
native;
|
|
19
|
+
update(attributes) {
|
|
20
|
+
const { sessionId, userId, ...observationAttributes } = attributes;
|
|
21
|
+
this.native.updateOtelSpanAttributes(observationAttributes);
|
|
22
|
+
applySessionId(this.native, sessionId);
|
|
23
|
+
applyUserId(this.native, userId);
|
|
24
|
+
return this;
|
|
25
|
+
}
|
|
26
|
+
updateTrace(attributes) {
|
|
27
|
+
const { input, output, metadata, name, sessionId, userId, tags, version } = attributes;
|
|
28
|
+
if (input !== void 0 || output !== void 0) {
|
|
29
|
+
this.native.setTraceIO({ input, output });
|
|
30
|
+
}
|
|
31
|
+
if (name !== void 0) {
|
|
32
|
+
this.native.otelSpan.setAttribute(LangfuseOtelSpanAttributes.TRACE_NAME, name);
|
|
33
|
+
}
|
|
34
|
+
applySessionId(this.native, sessionId);
|
|
35
|
+
applyUserId(this.native, userId);
|
|
36
|
+
if (tags !== void 0) {
|
|
37
|
+
this.native.otelSpan.setAttribute(LangfuseOtelSpanAttributes.TRACE_TAGS, tags);
|
|
38
|
+
}
|
|
39
|
+
if (version !== void 0) {
|
|
40
|
+
this.native.otelSpan.setAttribute(LangfuseOtelSpanAttributes.VERSION, version);
|
|
41
|
+
}
|
|
42
|
+
for (const [key, value] of Object.entries(metadata ?? {})) {
|
|
43
|
+
this.native.otelSpan.setAttribute(
|
|
44
|
+
`${LangfuseOtelSpanAttributes.TRACE_METADATA}.${key}`,
|
|
45
|
+
String(value)
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
return this;
|
|
49
|
+
}
|
|
50
|
+
end(endTime) {
|
|
51
|
+
this.native.end(endTime);
|
|
52
|
+
return this;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
var ProductionTraceBackend = class {
|
|
56
|
+
constructor(provider, processor) {
|
|
57
|
+
this.provider = provider;
|
|
58
|
+
this.processor = processor;
|
|
59
|
+
}
|
|
60
|
+
provider;
|
|
61
|
+
processor;
|
|
62
|
+
start(name, attributes, options) {
|
|
63
|
+
const { sessionId, userId, ...observationAttributes } = attributes;
|
|
64
|
+
const parent = options.parent;
|
|
65
|
+
const native = parent instanceof ProductionObservation ? startChild(parent.native, name, observationAttributes, options.asType) : startRoot(name, observationAttributes, options.asType);
|
|
66
|
+
applySessionId(native, sessionId);
|
|
67
|
+
applyUserId(native, userId);
|
|
68
|
+
return new ProductionObservation(native);
|
|
69
|
+
}
|
|
70
|
+
async forceFlush() {
|
|
71
|
+
await this.processor.forceFlush();
|
|
72
|
+
}
|
|
73
|
+
async shutdown() {
|
|
74
|
+
await this.provider.shutdown();
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
async function createProductionBackend(config, factoryOverrides = {}) {
|
|
78
|
+
const globalRuntime = globalThis;
|
|
79
|
+
const fingerprint = configFingerprint(config);
|
|
80
|
+
const existing = globalRuntime[RUNTIME_KEY];
|
|
81
|
+
if (existing) {
|
|
82
|
+
const runtime = await existing;
|
|
83
|
+
if (runtime.shutdown) {
|
|
84
|
+
throw new Error("Langfuse tracing was already shut down; restart Pi to enable it again.");
|
|
85
|
+
}
|
|
86
|
+
if (runtime.fingerprint !== fingerprint) {
|
|
87
|
+
throw new Error("Langfuse configuration changed; restart Pi to apply the new credentials.");
|
|
88
|
+
}
|
|
89
|
+
return runtime.backend;
|
|
90
|
+
}
|
|
91
|
+
const factories = { ...defaultFactories, ...factoryOverrides };
|
|
92
|
+
const initializing = initializeRuntime(config, fingerprint, factories);
|
|
93
|
+
globalRuntime[RUNTIME_KEY] = initializing;
|
|
94
|
+
try {
|
|
95
|
+
return (await initializing).backend;
|
|
96
|
+
} catch (error) {
|
|
97
|
+
delete globalRuntime[RUNTIME_KEY];
|
|
98
|
+
throw error;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
async function initializeRuntime(config, fingerprint, factories) {
|
|
102
|
+
const processor = factories.createProcessor(config);
|
|
103
|
+
let provider;
|
|
104
|
+
try {
|
|
105
|
+
provider = factories.createProvider(processor);
|
|
106
|
+
factories.selectProvider(provider);
|
|
107
|
+
} catch (error) {
|
|
108
|
+
await (provider?.shutdown() ?? processor.shutdown()).catch(() => void 0);
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
111
|
+
const backend = new ProductionTraceBackend(provider, processor);
|
|
112
|
+
const runtime = { fingerprint, backend, shutdown: false };
|
|
113
|
+
const originalShutdown = backend.shutdown.bind(backend);
|
|
114
|
+
backend.shutdown = async () => {
|
|
115
|
+
if (runtime.shutdown) return;
|
|
116
|
+
runtime.shutdown = true;
|
|
117
|
+
await originalShutdown();
|
|
118
|
+
};
|
|
119
|
+
return runtime;
|
|
120
|
+
}
|
|
121
|
+
var defaultFactories = {
|
|
122
|
+
createProcessor: (config) => {
|
|
123
|
+
const secrets = [config.secretKey, config.publicKey];
|
|
124
|
+
return new LangfuseSpanProcessor({
|
|
125
|
+
publicKey: config.publicKey,
|
|
126
|
+
secretKey: config.secretKey,
|
|
127
|
+
baseUrl: config.baseUrl,
|
|
128
|
+
environment: config.environment ?? "",
|
|
129
|
+
release: config.release ?? "",
|
|
130
|
+
flushAt: 512,
|
|
131
|
+
flushInterval: 5,
|
|
132
|
+
timeout: 5,
|
|
133
|
+
mask: ({ data }) => maskSecrets(data, secrets),
|
|
134
|
+
shouldExportSpan: ({ otelSpan }) => typeof otelSpan.attributes["langfuse.observation.type"] === "string"
|
|
135
|
+
});
|
|
136
|
+
},
|
|
137
|
+
createProvider: (processor) => new NodeTracerProvider({ spanProcessors: [processor] }),
|
|
138
|
+
selectProvider: setLangfuseTracerProvider
|
|
139
|
+
};
|
|
140
|
+
function applySessionId(observation, sessionId) {
|
|
141
|
+
if (sessionId !== void 0) {
|
|
142
|
+
observation.otelSpan.setAttribute(LangfuseOtelSpanAttributes.TRACE_SESSION_ID, sessionId);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function applyUserId(observation, userId) {
|
|
146
|
+
if (userId !== void 0) {
|
|
147
|
+
observation.otelSpan.setAttribute(LangfuseOtelSpanAttributes.TRACE_USER_ID, userId);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function startRoot(name, attributes, type) {
|
|
151
|
+
if (type === "agent") {
|
|
152
|
+
return startObservation(name, attributes, { asType: "agent" });
|
|
153
|
+
}
|
|
154
|
+
if (type === "generation") {
|
|
155
|
+
return startObservation(name, attributes, {
|
|
156
|
+
asType: "generation"
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
if (type === "tool") {
|
|
160
|
+
return startObservation(name, attributes, { asType: "tool" });
|
|
161
|
+
}
|
|
162
|
+
return startObservation(name, attributes, { asType: "span" });
|
|
163
|
+
}
|
|
164
|
+
function startChild(parent, name, attributes, type) {
|
|
165
|
+
if (type === "agent") {
|
|
166
|
+
return parent.startObservation(name, attributes, {
|
|
167
|
+
asType: "agent"
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
if (type === "generation") {
|
|
171
|
+
return parent.startObservation(name, attributes, {
|
|
172
|
+
asType: "generation"
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
if (type === "tool") {
|
|
176
|
+
return parent.startObservation(name, attributes, {
|
|
177
|
+
asType: "tool"
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
return parent.startObservation(name, attributes, {
|
|
181
|
+
asType: "span"
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
function configFingerprint(config) {
|
|
185
|
+
return createHash("sha256").update(
|
|
186
|
+
`${config.publicKey}\0${config.secretKey}\0${config.baseUrl}\0${config.environment ?? ""}\0${config.release ?? ""}`
|
|
187
|
+
).digest("hex");
|
|
188
|
+
}
|
|
189
|
+
function maskSecrets(data, secrets) {
|
|
190
|
+
return maskSecretValue(data, secrets, /* @__PURE__ */ new WeakSet());
|
|
191
|
+
}
|
|
192
|
+
function maskSecretValue(data, secrets, active) {
|
|
193
|
+
if (typeof data === "string") {
|
|
194
|
+
let masked = data.replace(/\b(?:sk|pk)-lf-[A-Za-z0-9_-]+\b/g, "[LANGFUSE_KEY_REDACTED]");
|
|
195
|
+
for (const secret of secrets) {
|
|
196
|
+
if (secret) masked = masked.replaceAll(secret, "[LANGFUSE_KEY_REDACTED]");
|
|
197
|
+
}
|
|
198
|
+
return masked;
|
|
199
|
+
}
|
|
200
|
+
if (!data || typeof data !== "object") return data;
|
|
201
|
+
if (active.has(data)) return "[circular]";
|
|
202
|
+
active.add(data);
|
|
203
|
+
try {
|
|
204
|
+
if (Array.isArray(data)) {
|
|
205
|
+
return data.map((item) => maskSecretValue(item, secrets, active));
|
|
206
|
+
}
|
|
207
|
+
return Object.fromEntries(
|
|
208
|
+
Object.entries(data).map(([key, value]) => [key, maskSecretValue(value, secrets, active)])
|
|
209
|
+
);
|
|
210
|
+
} finally {
|
|
211
|
+
active.delete(data);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
export {
|
|
215
|
+
createProductionBackend,
|
|
216
|
+
maskSecrets
|
|
217
|
+
};
|
|
218
|
+
//# sourceMappingURL=runtime-NX5HM7IU.ts.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/runtime.ts"],
|
|
4
|
+
"sourcesContent": ["import { createHash } from \"node:crypto\";\nimport { LangfuseSpanProcessor } from \"@langfuse/otel\";\nimport {\n\ttype LangfuseObservation,\n\ttype LangfuseObservationAttributes,\n\tLangfuseOtelSpanAttributes,\n\tsetLangfuseTracerProvider,\n\tstartObservation,\n} from \"@langfuse/tracing\";\nimport type { SpanProcessor } from \"@opentelemetry/sdk-trace-base\";\nimport { NodeTracerProvider } from \"@opentelemetry/sdk-trace-node\";\nimport type { LangfuseConfig } from \"./config.js\";\nimport type {\n\tObservation,\n\tObservationAttributes,\n\tObservationType,\n\tTraceBackend,\n} from \"./tracing.js\";\n\nconst RUNTIME_KEY = Symbol.for(\"@narumitw/pi-langfuse/runtime/v1\");\n\ninterface SharedRuntime {\n\tfingerprint: string;\n\tbackend: ProductionTraceBackend;\n\tshutdown: boolean;\n}\n\ntype GlobalWithRuntime = typeof globalThis & {\n\t[RUNTIME_KEY]?: Promise<SharedRuntime>;\n};\n\nexport interface RuntimeFactories {\n\tcreateProcessor(config: LangfuseConfig): SpanProcessor;\n\tcreateProvider(processor: SpanProcessor): NodeTracerProvider;\n\tselectProvider(provider: NodeTracerProvider): void;\n}\n\nclass ProductionObservation implements Observation {\n\tconstructor(readonly native: LangfuseObservation) {}\n\n\tupdate(attributes: ObservationAttributes): Observation {\n\t\tconst { sessionId, userId, ...observationAttributes } = attributes;\n\t\tthis.native.updateOtelSpanAttributes(observationAttributes as LangfuseObservationAttributes);\n\t\tapplySessionId(this.native, sessionId);\n\t\tapplyUserId(this.native, userId);\n\t\treturn this;\n\t}\n\n\tupdateTrace(attributes: ObservationAttributes): Observation {\n\t\tconst { input, output, metadata, name, sessionId, userId, tags, version } = attributes;\n\t\tif (input !== undefined || output !== undefined) {\n\t\t\tthis.native.setTraceIO({ input, output });\n\t\t}\n\t\tif (name !== undefined) {\n\t\t\tthis.native.otelSpan.setAttribute(LangfuseOtelSpanAttributes.TRACE_NAME, name);\n\t\t}\n\t\tapplySessionId(this.native, sessionId);\n\t\tapplyUserId(this.native, userId);\n\t\tif (tags !== undefined) {\n\t\t\tthis.native.otelSpan.setAttribute(LangfuseOtelSpanAttributes.TRACE_TAGS, tags);\n\t\t}\n\t\tif (version !== undefined) {\n\t\t\tthis.native.otelSpan.setAttribute(LangfuseOtelSpanAttributes.VERSION, version);\n\t\t}\n\t\tfor (const [key, value] of Object.entries(metadata ?? {})) {\n\t\t\tthis.native.otelSpan.setAttribute(\n\t\t\t\t`${LangfuseOtelSpanAttributes.TRACE_METADATA}.${key}`,\n\t\t\t\tString(value),\n\t\t\t);\n\t\t}\n\t\treturn this;\n\t}\n\n\tend(endTime?: number): Observation {\n\t\tthis.native.end(endTime);\n\t\treturn this;\n\t}\n}\n\nclass ProductionTraceBackend implements TraceBackend {\n\tconstructor(\n\t\tprivate readonly provider: NodeTracerProvider,\n\t\tprivate readonly processor: SpanProcessor,\n\t) {}\n\n\tstart(\n\t\tname: string,\n\t\tattributes: ObservationAttributes,\n\t\toptions: { asType: ObservationType; parent?: Observation },\n\t): Observation {\n\t\tconst { sessionId, userId, ...observationAttributes } = attributes;\n\t\tconst parent = options.parent;\n\t\tconst native =\n\t\t\tparent instanceof ProductionObservation\n\t\t\t\t? startChild(parent.native, name, observationAttributes, options.asType)\n\t\t\t\t: startRoot(name, observationAttributes, options.asType);\n\t\tapplySessionId(native, sessionId);\n\t\tapplyUserId(native, userId);\n\t\treturn new ProductionObservation(native);\n\t}\n\n\tasync forceFlush(): Promise<void> {\n\t\tawait this.processor.forceFlush();\n\t}\n\n\tasync shutdown(): Promise<void> {\n\t\tawait this.provider.shutdown();\n\t}\n}\n\nexport async function createProductionBackend(\n\tconfig: LangfuseConfig,\n\tfactoryOverrides: Partial<RuntimeFactories> = {},\n): Promise<TraceBackend> {\n\tconst globalRuntime = globalThis as GlobalWithRuntime;\n\tconst fingerprint = configFingerprint(config);\n\tconst existing = globalRuntime[RUNTIME_KEY];\n\tif (existing) {\n\t\tconst runtime = await existing;\n\t\tif (runtime.shutdown) {\n\t\t\tthrow new Error(\"Langfuse tracing was already shut down; restart Pi to enable it again.\");\n\t\t}\n\t\tif (runtime.fingerprint !== fingerprint) {\n\t\t\tthrow new Error(\"Langfuse configuration changed; restart Pi to apply the new credentials.\");\n\t\t}\n\t\treturn runtime.backend;\n\t}\n\n\tconst factories = { ...defaultFactories, ...factoryOverrides };\n\tconst initializing = initializeRuntime(config, fingerprint, factories);\n\tglobalRuntime[RUNTIME_KEY] = initializing;\n\ttry {\n\t\treturn (await initializing).backend;\n\t} catch (error) {\n\t\tdelete globalRuntime[RUNTIME_KEY];\n\t\tthrow error;\n\t}\n}\n\nasync function initializeRuntime(\n\tconfig: LangfuseConfig,\n\tfingerprint: string,\n\tfactories: RuntimeFactories,\n): Promise<SharedRuntime> {\n\tconst processor = factories.createProcessor(config);\n\tlet provider: NodeTracerProvider | undefined;\n\ttry {\n\t\tprovider = factories.createProvider(processor);\n\t\tfactories.selectProvider(provider);\n\t} catch (error) {\n\t\tawait (provider?.shutdown() ?? processor.shutdown()).catch(() => undefined);\n\t\tthrow error;\n\t}\n\tconst backend = new ProductionTraceBackend(provider, processor);\n\tconst runtime: SharedRuntime = { fingerprint, backend, shutdown: false };\n\tconst originalShutdown = backend.shutdown.bind(backend);\n\tbackend.shutdown = async () => {\n\t\tif (runtime.shutdown) return;\n\t\truntime.shutdown = true;\n\t\tawait originalShutdown();\n\t};\n\treturn runtime;\n}\n\nconst defaultFactories: RuntimeFactories = {\n\tcreateProcessor: (config) => {\n\t\tconst secrets = [config.secretKey, config.publicKey];\n\t\treturn new LangfuseSpanProcessor({\n\t\t\tpublicKey: config.publicKey,\n\t\t\tsecretKey: config.secretKey,\n\t\t\tbaseUrl: config.baseUrl,\n\t\t\tenvironment: config.environment ?? \"\",\n\t\t\trelease: config.release ?? \"\",\n\t\t\tflushAt: 512,\n\t\t\tflushInterval: 5,\n\t\t\ttimeout: 5,\n\t\t\tmask: ({ data }) => maskSecrets(data, secrets),\n\t\t\tshouldExportSpan: ({ otelSpan }) =>\n\t\t\t\ttypeof otelSpan.attributes[\"langfuse.observation.type\"] === \"string\",\n\t\t});\n\t},\n\tcreateProvider: (processor) => new NodeTracerProvider({ spanProcessors: [processor] }),\n\tselectProvider: setLangfuseTracerProvider,\n};\n\nfunction applySessionId(observation: LangfuseObservation, sessionId: string | undefined): void {\n\tif (sessionId !== undefined) {\n\t\tobservation.otelSpan.setAttribute(LangfuseOtelSpanAttributes.TRACE_SESSION_ID, sessionId);\n\t}\n}\n\nfunction applyUserId(observation: LangfuseObservation, userId: string | undefined): void {\n\tif (userId !== undefined) {\n\t\tobservation.otelSpan.setAttribute(LangfuseOtelSpanAttributes.TRACE_USER_ID, userId);\n\t}\n}\n\nfunction startRoot(\n\tname: string,\n\tattributes: ObservationAttributes,\n\ttype: ObservationType,\n): LangfuseObservation {\n\tif (type === \"agent\") {\n\t\treturn startObservation(name, attributes as LangfuseObservationAttributes, { asType: \"agent\" });\n\t}\n\tif (type === \"generation\") {\n\t\treturn startObservation(name, attributes as LangfuseObservationAttributes, {\n\t\t\tasType: \"generation\",\n\t\t});\n\t}\n\tif (type === \"tool\") {\n\t\treturn startObservation(name, attributes as LangfuseObservationAttributes, { asType: \"tool\" });\n\t}\n\treturn startObservation(name, attributes as LangfuseObservationAttributes, { asType: \"span\" });\n}\n\nfunction startChild(\n\tparent: LangfuseObservation,\n\tname: string,\n\tattributes: ObservationAttributes,\n\ttype: ObservationType,\n): LangfuseObservation {\n\tif (type === \"agent\") {\n\t\treturn parent.startObservation(name, attributes as LangfuseObservationAttributes, {\n\t\t\tasType: \"agent\",\n\t\t});\n\t}\n\tif (type === \"generation\") {\n\t\treturn parent.startObservation(name, attributes as LangfuseObservationAttributes, {\n\t\t\tasType: \"generation\",\n\t\t});\n\t}\n\tif (type === \"tool\") {\n\t\treturn parent.startObservation(name, attributes as LangfuseObservationAttributes, {\n\t\t\tasType: \"tool\",\n\t\t});\n\t}\n\treturn parent.startObservation(name, attributes as LangfuseObservationAttributes, {\n\t\tasType: \"span\",\n\t});\n}\n\nfunction configFingerprint(config: LangfuseConfig): string {\n\treturn createHash(\"sha256\")\n\t\t.update(\n\t\t\t`${config.publicKey}\\0${config.secretKey}\\0${config.baseUrl}\\0${config.environment ?? \"\"}\\0${config.release ?? \"\"}`,\n\t\t)\n\t\t.digest(\"hex\");\n}\n\nexport function maskSecrets(data: unknown, secrets: readonly string[]): unknown {\n\treturn maskSecretValue(data, secrets, new WeakSet<object>());\n}\n\nfunction maskSecretValue(\n\tdata: unknown,\n\tsecrets: readonly string[],\n\tactive: WeakSet<object>,\n): unknown {\n\tif (typeof data === \"string\") {\n\t\tlet masked = data.replace(/\\b(?:sk|pk)-lf-[A-Za-z0-9_-]+\\b/g, \"[LANGFUSE_KEY_REDACTED]\");\n\t\tfor (const secret of secrets) {\n\t\t\tif (secret) masked = masked.replaceAll(secret, \"[LANGFUSE_KEY_REDACTED]\");\n\t\t}\n\t\treturn masked;\n\t}\n\tif (!data || typeof data !== \"object\") return data;\n\tif (active.has(data)) return \"[circular]\";\n\n\tactive.add(data);\n\ttry {\n\t\tif (Array.isArray(data)) {\n\t\t\treturn data.map((item) => maskSecretValue(item, secrets, active));\n\t\t}\n\t\treturn Object.fromEntries(\n\t\t\tObject.entries(data).map(([key, value]) => [key, maskSecretValue(value, secrets, active)]),\n\t\t);\n\t} finally {\n\t\tactive.delete(data);\n\t}\n}\n"],
|
|
5
|
+
"mappings": ";;;;AAAA,SAAS,kBAAkB;AAC3B,SAAS,6BAA6B;AACtC;AAAA,EAGC;AAAA,EACA;AAAA,EACA;AAAA,OACM;AAEP,SAAS,0BAA0B;AASnC,IAAM,cAAc,uBAAO,IAAI,kCAAkC;AAkBjE,IAAM,wBAAN,MAAmD;AAAA,EAClD,YAAqB,QAA6B;AAA7B;AAAA,EAA8B;AAAA,EAA9B;AAAA,EAErB,OAAO,YAAgD;AACtD,UAAM,EAAE,WAAW,QAAQ,GAAG,sBAAsB,IAAI;AACxD,SAAK,OAAO,yBAAyB,qBAAsD;AAC3F,mBAAe,KAAK,QAAQ,SAAS;AACrC,gBAAY,KAAK,QAAQ,MAAM;AAC/B,WAAO;AAAA,EACR;AAAA,EAEA,YAAY,YAAgD;AAC3D,UAAM,EAAE,OAAO,QAAQ,UAAU,MAAM,WAAW,QAAQ,MAAM,QAAQ,IAAI;AAC5E,QAAI,UAAU,UAAa,WAAW,QAAW;AAChD,WAAK,OAAO,WAAW,EAAE,OAAO,OAAO,CAAC;AAAA,IACzC;AACA,QAAI,SAAS,QAAW;AACvB,WAAK,OAAO,SAAS,aAAa,2BAA2B,YAAY,IAAI;AAAA,IAC9E;AACA,mBAAe,KAAK,QAAQ,SAAS;AACrC,gBAAY,KAAK,QAAQ,MAAM;AAC/B,QAAI,SAAS,QAAW;AACvB,WAAK,OAAO,SAAS,aAAa,2BAA2B,YAAY,IAAI;AAAA,IAC9E;AACA,QAAI,YAAY,QAAW;AAC1B,WAAK,OAAO,SAAS,aAAa,2BAA2B,SAAS,OAAO;AAAA,IAC9E;AACA,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,YAAY,CAAC,CAAC,GAAG;AAC1D,WAAK,OAAO,SAAS;AAAA,QACpB,GAAG,2BAA2B,cAAc,IAAI,GAAG;AAAA,QACnD,OAAO,KAAK;AAAA,MACb;AAAA,IACD;AACA,WAAO;AAAA,EACR;AAAA,EAEA,IAAI,SAA+B;AAClC,SAAK,OAAO,IAAI,OAAO;AACvB,WAAO;AAAA,EACR;AACD;AAEA,IAAM,yBAAN,MAAqD;AAAA,EACpD,YACkB,UACA,WAChB;AAFgB;AACA;AAAA,EACf;AAAA,EAFe;AAAA,EACA;AAAA,EAGlB,MACC,MACA,YACA,SACc;AACd,UAAM,EAAE,WAAW,QAAQ,GAAG,sBAAsB,IAAI;AACxD,UAAM,SAAS,QAAQ;AACvB,UAAM,SACL,kBAAkB,wBACf,WAAW,OAAO,QAAQ,MAAM,uBAAuB,QAAQ,MAAM,IACrE,UAAU,MAAM,uBAAuB,QAAQ,MAAM;AACzD,mBAAe,QAAQ,SAAS;AAChC,gBAAY,QAAQ,MAAM;AAC1B,WAAO,IAAI,sBAAsB,MAAM;AAAA,EACxC;AAAA,EAEA,MAAM,aAA4B;AACjC,UAAM,KAAK,UAAU,WAAW;AAAA,EACjC;AAAA,EAEA,MAAM,WAA0B;AAC/B,UAAM,KAAK,SAAS,SAAS;AAAA,EAC9B;AACD;AAEA,eAAsB,wBACrB,QACA,mBAA8C,CAAC,GACvB;AACxB,QAAM,gBAAgB;AACtB,QAAM,cAAc,kBAAkB,MAAM;AAC5C,QAAM,WAAW,cAAc,WAAW;AAC1C,MAAI,UAAU;AACb,UAAM,UAAU,MAAM;AACtB,QAAI,QAAQ,UAAU;AACrB,YAAM,IAAI,MAAM,wEAAwE;AAAA,IACzF;AACA,QAAI,QAAQ,gBAAgB,aAAa;AACxC,YAAM,IAAI,MAAM,0EAA0E;AAAA,IAC3F;AACA,WAAO,QAAQ;AAAA,EAChB;AAEA,QAAM,YAAY,EAAE,GAAG,kBAAkB,GAAG,iBAAiB;AAC7D,QAAM,eAAe,kBAAkB,QAAQ,aAAa,SAAS;AACrE,gBAAc,WAAW,IAAI;AAC7B,MAAI;AACH,YAAQ,MAAM,cAAc;AAAA,EAC7B,SAAS,OAAO;AACf,WAAO,cAAc,WAAW;AAChC,UAAM;AAAA,EACP;AACD;AAEA,eAAe,kBACd,QACA,aACA,WACyB;AACzB,QAAM,YAAY,UAAU,gBAAgB,MAAM;AAClD,MAAI;AACJ,MAAI;AACH,eAAW,UAAU,eAAe,SAAS;AAC7C,cAAU,eAAe,QAAQ;AAAA,EAClC,SAAS,OAAO;AACf,WAAO,UAAU,SAAS,KAAK,UAAU,SAAS,GAAG,MAAM,MAAM,MAAS;AAC1E,UAAM;AAAA,EACP;AACA,QAAM,UAAU,IAAI,uBAAuB,UAAU,SAAS;AAC9D,QAAM,UAAyB,EAAE,aAAa,SAAS,UAAU,MAAM;AACvE,QAAM,mBAAmB,QAAQ,SAAS,KAAK,OAAO;AACtD,UAAQ,WAAW,YAAY;AAC9B,QAAI,QAAQ,SAAU;AACtB,YAAQ,WAAW;AACnB,UAAM,iBAAiB;AAAA,EACxB;AACA,SAAO;AACR;AAEA,IAAM,mBAAqC;AAAA,EAC1C,iBAAiB,CAAC,WAAW;AAC5B,UAAM,UAAU,CAAC,OAAO,WAAW,OAAO,SAAS;AACnD,WAAO,IAAI,sBAAsB;AAAA,MAChC,WAAW,OAAO;AAAA,MAClB,WAAW,OAAO;AAAA,MAClB,SAAS,OAAO;AAAA,MAChB,aAAa,OAAO,eAAe;AAAA,MACnC,SAAS,OAAO,WAAW;AAAA,MAC3B,SAAS;AAAA,MACT,eAAe;AAAA,MACf,SAAS;AAAA,MACT,MAAM,CAAC,EAAE,KAAK,MAAM,YAAY,MAAM,OAAO;AAAA,MAC7C,kBAAkB,CAAC,EAAE,SAAS,MAC7B,OAAO,SAAS,WAAW,2BAA2B,MAAM;AAAA,IAC9D,CAAC;AAAA,EACF;AAAA,EACA,gBAAgB,CAAC,cAAc,IAAI,mBAAmB,EAAE,gBAAgB,CAAC,SAAS,EAAE,CAAC;AAAA,EACrF,gBAAgB;AACjB;AAEA,SAAS,eAAe,aAAkC,WAAqC;AAC9F,MAAI,cAAc,QAAW;AAC5B,gBAAY,SAAS,aAAa,2BAA2B,kBAAkB,SAAS;AAAA,EACzF;AACD;AAEA,SAAS,YAAY,aAAkC,QAAkC;AACxF,MAAI,WAAW,QAAW;AACzB,gBAAY,SAAS,aAAa,2BAA2B,eAAe,MAAM;AAAA,EACnF;AACD;AAEA,SAAS,UACR,MACA,YACA,MACsB;AACtB,MAAI,SAAS,SAAS;AACrB,WAAO,iBAAiB,MAAM,YAA6C,EAAE,QAAQ,QAAQ,CAAC;AAAA,EAC/F;AACA,MAAI,SAAS,cAAc;AAC1B,WAAO,iBAAiB,MAAM,YAA6C;AAAA,MAC1E,QAAQ;AAAA,IACT,CAAC;AAAA,EACF;AACA,MAAI,SAAS,QAAQ;AACpB,WAAO,iBAAiB,MAAM,YAA6C,EAAE,QAAQ,OAAO,CAAC;AAAA,EAC9F;AACA,SAAO,iBAAiB,MAAM,YAA6C,EAAE,QAAQ,OAAO,CAAC;AAC9F;AAEA,SAAS,WACR,QACA,MACA,YACA,MACsB;AACtB,MAAI,SAAS,SAAS;AACrB,WAAO,OAAO,iBAAiB,MAAM,YAA6C;AAAA,MACjF,QAAQ;AAAA,IACT,CAAC;AAAA,EACF;AACA,MAAI,SAAS,cAAc;AAC1B,WAAO,OAAO,iBAAiB,MAAM,YAA6C;AAAA,MACjF,QAAQ;AAAA,IACT,CAAC;AAAA,EACF;AACA,MAAI,SAAS,QAAQ;AACpB,WAAO,OAAO,iBAAiB,MAAM,YAA6C;AAAA,MACjF,QAAQ;AAAA,IACT,CAAC;AAAA,EACF;AACA,SAAO,OAAO,iBAAiB,MAAM,YAA6C;AAAA,IACjF,QAAQ;AAAA,EACT,CAAC;AACF;AAEA,SAAS,kBAAkB,QAAgC;AAC1D,SAAO,WAAW,QAAQ,EACxB;AAAA,IACA,GAAG,OAAO,SAAS,KAAK,OAAO,SAAS,KAAK,OAAO,OAAO,KAAK,OAAO,eAAe,EAAE,KAAK,OAAO,WAAW,EAAE;AAAA,EAClH,EACC,OAAO,KAAK;AACf;AAEO,SAAS,YAAY,MAAe,SAAqC;AAC/E,SAAO,gBAAgB,MAAM,SAAS,oBAAI,QAAgB,CAAC;AAC5D;AAEA,SAAS,gBACR,MACA,SACA,QACU;AACV,MAAI,OAAO,SAAS,UAAU;AAC7B,QAAI,SAAS,KAAK,QAAQ,oCAAoC,yBAAyB;AACvF,eAAW,UAAU,SAAS;AAC7B,UAAI,OAAQ,UAAS,OAAO,WAAW,QAAQ,yBAAyB;AAAA,IACzE;AACA,WAAO;AAAA,EACR;AACA,MAAI,CAAC,QAAQ,OAAO,SAAS,SAAU,QAAO;AAC9C,MAAI,OAAO,IAAI,IAAI,EAAG,QAAO;AAE7B,SAAO,IAAI,IAAI;AACf,MAAI;AACH,QAAI,MAAM,QAAQ,IAAI,GAAG;AACxB,aAAO,KAAK,IAAI,CAAC,SAAS,gBAAgB,MAAM,SAAS,MAAM,CAAC;AAAA,IACjE;AACA,WAAO,OAAO;AAAA,MACb,OAAO,QAAQ,IAAI,EAAE,IAAI,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC,KAAK,gBAAgB,OAAO,SAAS,MAAM,CAAC,CAAC;AAAA,IAC1F;AAAA,EACD,UAAE;AACD,WAAO,OAAO,IAAI;AAAA,EACnB;AACD;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|