pi-langfuse 1.4.2 → 1.4.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-langfuse",
3
- "version": "1.4.2",
3
+ "version": "1.4.3",
4
4
  "description": "Langfuse extension for Pi coding agent",
5
5
  "repository": {
6
6
  "type": "git",
package/src/constants.ts CHANGED
@@ -10,3 +10,4 @@ export const MAX_TOOL_PAYLOAD_LENGTH = 24_000;
10
10
  export const MAX_DEPTH = 6;
11
11
  export const MAX_ARRAY_ITEMS = 50;
12
12
  export const MAX_OBJECT_KEYS = 80;
13
+ export const MAX_PAYLOAD_NODES = 2_000;
@@ -19,6 +19,7 @@ export function updateTraceIO(input?: unknown, output?: unknown) {
19
19
 
20
20
  export async function startAgentRun(event: Record<string, unknown>, ctx: any) {
21
21
  if (!(await ensureConfig(ctx))) {
22
+ state.isTracingDisabled = true;
22
23
  return;
23
24
  }
24
25
 
@@ -92,6 +93,7 @@ export async function startAgentRun(event: Record<string, unknown>, ctx: any) {
92
93
  updateTraceIO(promptInput, undefined);
93
94
  } catch (e) {
94
95
  console.warn("📊 Langfuse: Failed to create agent observation", e);
96
+ state.isTracingDisabled = true;
95
97
  }
96
98
  }
97
99
 
@@ -13,7 +13,7 @@ import {
13
13
  import type { GenerationState, ObservationUpdate } from "../types.js";
14
14
 
15
15
  export function getOpenGeneration(): GenerationState | undefined {
16
- if (!state.agentState) {
16
+ if (state.isTracingDisabled || !state.agentState) {
17
17
  return undefined;
18
18
  }
19
19
 
@@ -29,7 +29,7 @@ export function getOpenGeneration(): GenerationState | undefined {
29
29
  }
30
30
 
31
31
  export async function startGeneration(event: Record<string, unknown>) {
32
- if (!state.agentState?.root) {
32
+ if (state.isTracingDisabled || !state.agentState?.root) {
33
33
  return;
34
34
  }
35
35
 
@@ -79,7 +79,7 @@ export async function startGeneration(event: Record<string, unknown>) {
79
79
  }
80
80
 
81
81
  export function updateGenerationMetadata(event: Record<string, unknown>) {
82
- if (!state.agentState) {
82
+ if (state.isTracingDisabled || !state.agentState) {
83
83
  return;
84
84
  }
85
85
 
@@ -132,7 +132,7 @@ export function updateGenerationMetadata(event: Record<string, unknown>) {
132
132
  }
133
133
 
134
134
  export function recordTTFT(event: Record<string, unknown>) {
135
- if (!state.agentState) {
135
+ if (state.isTracingDisabled || !state.agentState) {
136
136
  return;
137
137
  }
138
138
 
@@ -150,7 +150,7 @@ export function recordTTFT(event: Record<string, unknown>) {
150
150
  }
151
151
 
152
152
  export async function finishGenerationFromMessage(event: Record<string, unknown>) {
153
- if (!state.agentState) {
153
+ if (state.isTracingDisabled || !state.agentState) {
154
154
  return;
155
155
  }
156
156
 
@@ -190,7 +190,7 @@ export async function finishGenerationFromMessage(event: Record<string, unknown>
190
190
  }
191
191
 
192
192
  export async function createFallbackGenerationFromTurn(event: Record<string, unknown>, message: Record<string, unknown>) {
193
- if (!state.agentState?.root || state.agentState.generationOrder.length > 0) {
193
+ if (state.isTracingDisabled || !state.agentState?.root || state.agentState.generationOrder.length > 0) {
194
194
  return;
195
195
  }
196
196
 
@@ -12,7 +12,7 @@ import {
12
12
  import { MAX_TOOL_PAYLOAD_LENGTH } from "../constants.js";
13
13
 
14
14
  export async function startToolObservation(event: Record<string, unknown>) {
15
- if (!state.agentState?.root) {
15
+ if (state.isTracingDisabled || !state.agentState?.root) {
16
16
  return;
17
17
  }
18
18
 
@@ -59,7 +59,7 @@ export async function startToolObservation(event: Record<string, unknown>) {
59
59
  }
60
60
 
61
61
  export async function finishToolObservation(event: Record<string, unknown>) {
62
- if (!state.agentState) {
62
+ if (state.isTracingDisabled || !state.agentState) {
63
63
  return;
64
64
  }
65
65
 
@@ -119,7 +119,7 @@ export async function finishToolObservation(event: Record<string, unknown>) {
119
119
  }
120
120
 
121
121
  export function closeDanglingObservations(statusMessage: string) {
122
- if (!state.agentState) {
122
+ if (state.isTracingDisabled || !state.agentState) {
123
123
  return;
124
124
  }
125
125
 
@@ -3,7 +3,7 @@ import { getRuntime } from "../langfuse.js";
3
3
  import { shapePayload } from "../utils.js";
4
4
 
5
5
  export async function startTurnObservation(event: Record<string, unknown>) {
6
- if (!state.agentState?.root) {
6
+ if (state.isTracingDisabled || !state.agentState?.root) {
7
7
  return;
8
8
  }
9
9
 
@@ -40,7 +40,7 @@ export async function startTurnObservation(event: Record<string, unknown>) {
40
40
  }
41
41
 
42
42
  export function finishTurnObservation(event?: Record<string, unknown>) {
43
- if (!state.agentState?.activeTurn) {
43
+ if (state.isTracingDisabled || !state.agentState?.activeTurn) {
44
44
  return;
45
45
  }
46
46
 
package/src/langfuse.ts CHANGED
@@ -42,7 +42,8 @@ interface RestFallbackStore {
42
42
  attempted: boolean;
43
43
  }
44
44
 
45
- const OTEL_VISIBILITY_DELAY_MS = 1_500;
45
+ const OTEL_VISIBILITY_TIMEOUT_MS = 1_500;
46
+ const OTEL_VISIBILITY_POLL_INTERVAL_MS = 200;
46
47
 
47
48
  function nowIso() {
48
49
  return new Date().toISOString();
@@ -190,6 +191,23 @@ async function traceExists(rt: LangfuseRuntime, traceId: string): Promise<boolea
190
191
  }
191
192
  }
192
193
 
194
+ async function waitForTraceVisibility(rt: LangfuseRuntime, traceId: string): Promise<boolean> {
195
+ const deadline = Date.now() + OTEL_VISIBILITY_TIMEOUT_MS;
196
+
197
+ while (true) {
198
+ if (await traceExists(rt, traceId)) {
199
+ return true;
200
+ }
201
+
202
+ const remainingMs = deadline - Date.now();
203
+ if (remainingMs <= 0) {
204
+ return false;
205
+ }
206
+
207
+ await delay(Math.min(OTEL_VISIBILITY_POLL_INTERVAL_MS, remainingMs));
208
+ }
209
+ }
210
+
193
211
  function eventTimestamp(record: { endTime?: string; startTime?: string; timestamp?: string }) {
194
212
  return record.endTime ?? record.startTime ?? record.timestamp ?? nowIso();
195
213
  }
@@ -201,8 +219,7 @@ async function fallbackToRestIngestion(rt: LangfuseRuntime) {
201
219
  }
202
220
  store.attempted = true;
203
221
 
204
- await delay(OTEL_VISIBILITY_DELAY_MS);
205
- if (await traceExists(rt, store.trace.id)) {
222
+ if (await waitForTraceVisibility(rt, store.trace.id)) {
206
223
  return;
207
224
  }
208
225
 
package/src/state.ts CHANGED
@@ -8,6 +8,7 @@ export interface SessionRunState {
8
8
  toolCallCount: number;
9
9
  errorCount: number;
10
10
  turnCount: number;
11
+ tracingDisabled: boolean;
11
12
  }
12
13
 
13
14
  const DEFAULT_SESSION_ID = "__pi_langfuse_default_session__";
@@ -23,6 +24,7 @@ function createSessionRunState(): SessionRunState {
23
24
  toolCallCount: 0,
24
25
  errorCount: 0,
25
26
  turnCount: 0,
27
+ tracingDisabled: false,
26
28
  };
27
29
  }
28
30
 
@@ -109,6 +111,13 @@ export const state = {
109
111
  set turnCount(turnCount: number) {
110
112
  getSessionRunState().turnCount = turnCount;
111
113
  },
114
+
115
+ get isTracingDisabled() {
116
+ return getSessionRunState().tracingDisabled;
117
+ },
118
+ set isTracingDisabled(disabled: boolean) {
119
+ getSessionRunState().tracingDisabled = disabled;
120
+ },
112
121
  };
113
122
 
114
123
  export function resetRunState(sessionId = getActiveSessionId()) {
package/src/utils.ts CHANGED
@@ -2,6 +2,7 @@ import {
2
2
  MAX_ARRAY_ITEMS,
3
3
  MAX_DEPTH,
4
4
  MAX_OBJECT_KEYS,
5
+ MAX_PAYLOAD_NODES,
5
6
  MAX_STRING_LENGTH,
6
7
  MAX_TOOL_PAYLOAD_LENGTH,
7
8
  } from "./constants.js";
@@ -23,11 +24,25 @@ export function tryParseJson(value: string): unknown {
23
24
  }
24
25
  }
25
26
 
26
- export function shapePayload(value: unknown, options: { maxString?: number; depth?: number } = {}): unknown {
27
+ const PAYLOAD_TOO_LARGE = "[payload too large]";
28
+
29
+ export function shapePayload(value: unknown, options: { maxString?: number; depth?: number; maxNodes?: number } = {}): unknown {
27
30
  const maxString = options.maxString ?? MAX_STRING_LENGTH;
28
31
  const depth = options.depth ?? MAX_DEPTH;
32
+ const maxNodes = options.maxNodes ?? MAX_PAYLOAD_NODES;
33
+ const budget = { exhausted: false, nodeCount: 0 };
29
34
 
30
35
  function visit(item: unknown, remainingDepth: number, seen: WeakSet<object>): unknown {
36
+ if (budget.exhausted) {
37
+ return PAYLOAD_TOO_LARGE;
38
+ }
39
+
40
+ budget.nodeCount++;
41
+ if (budget.nodeCount > maxNodes) {
42
+ budget.exhausted = true;
43
+ return PAYLOAD_TOO_LARGE;
44
+ }
45
+
31
46
  if (typeof item === "string") {
32
47
  const truncated = truncate(item, maxString);
33
48
  const parsed = tryParseJson(truncated);
@@ -59,7 +74,15 @@ export function shapePayload(value: unknown, options: { maxString?: number; dept
59
74
  }
60
75
 
61
76
  if (Array.isArray(item)) {
62
- return item.slice(0, MAX_ARRAY_ITEMS).map((entry) => visit(entry, remainingDepth - 1, seen));
77
+ const output: unknown[] = [];
78
+ const limit = Math.min(item.length, MAX_ARRAY_ITEMS);
79
+ for (let index = 0; index < limit; index++) {
80
+ output.push(visit(item[index], remainingDepth - 1, seen));
81
+ if (budget.exhausted) {
82
+ break;
83
+ }
84
+ }
85
+ return output;
63
86
  }
64
87
 
65
88
  if (item instanceof Error) {
@@ -77,8 +100,16 @@ export function shapePayload(value: unknown, options: { maxString?: number; dept
77
100
  seen.add(item);
78
101
 
79
102
  const output: Record<string, unknown> = {};
80
- for (const [key, entry] of Object.entries(item as Record<string, unknown>).slice(0, MAX_OBJECT_KEYS)) {
81
- output[key] = visit(entry, remainingDepth - 1, seen);
103
+ let keyCount = 0;
104
+ for (const key in item as Record<string, unknown>) {
105
+ if (!Object.hasOwn(item, key)) {
106
+ continue;
107
+ }
108
+ output[key] = visit((item as Record<string, unknown>)[key], remainingDepth - 1, seen);
109
+ keyCount++;
110
+ if (budget.exhausted || keyCount >= MAX_OBJECT_KEYS) {
111
+ break;
112
+ }
82
113
  }
83
114
  return output;
84
115
  }
@@ -0,0 +1,49 @@
1
+ declare module "@opentelemetry/sdk-trace-base" {
2
+ export class BasicTracerProvider {
3
+ constructor(options?: { spanProcessors?: unknown[] });
4
+ forceFlush?(): Promise<void>;
5
+ shutdown?(): Promise<void>;
6
+ }
7
+ }
8
+
9
+ declare module "@langfuse/otel" {
10
+ export class LangfuseSpanProcessor {
11
+ constructor(options: {
12
+ publicKey: string;
13
+ secretKey: string;
14
+ baseUrl: string;
15
+ });
16
+ forceFlush?(): Promise<void>;
17
+ shutdown?(): Promise<void>;
18
+ }
19
+ }
20
+
21
+ declare module "@langfuse/tracing" {
22
+ export function setLangfuseTracerProvider(provider: unknown): void;
23
+
24
+ export function startObservation(
25
+ name: string,
26
+ body?: Record<string, unknown>,
27
+ options?: { asType?: string },
28
+ ): unknown;
29
+
30
+ export function propagateAttributes<T>(
31
+ params: {
32
+ sessionId?: string;
33
+ traceName?: string;
34
+ metadata?: Record<string, string>;
35
+ tags?: string[];
36
+ },
37
+ fn: () => T,
38
+ ): T;
39
+ }
40
+
41
+ declare module "@langfuse/client" {
42
+ export class LangfuseClient {
43
+ constructor(options: {
44
+ publicKey: string;
45
+ secretKey: string;
46
+ baseUrl: string;
47
+ });
48
+ }
49
+ }