pi-langfuse 1.5.9 → 1.5.11

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/index.ts CHANGED
@@ -8,7 +8,7 @@
8
8
  */
9
9
 
10
10
  import { basename } from "node:path";
11
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
11
+ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
12
12
 
13
13
  import { state, resetRunState, runWithSession, setCurrentSession } from "./src/state.js";
14
14
  import { ensureConfig, promptForConfig, loadConfig } from "./src/config.js";
@@ -16,7 +16,7 @@ import { shutdownRuntime } from "./src/langfuse.js";
16
16
  import { handleLangfusePrivacyCommand, handleLangfuseStatusCommand, handleLangfuseTestCommand } from "./src/commands.js";
17
17
  import { getMessageFromEvent, extractAssistantOutput, getCapturePolicy } from "./src/utils.js";
18
18
  import { applyCapturePolicy } from "./src/capture-policy.js";
19
- import { startAgentRun, finishAgentRun } from "./src/handlers/agent.js";
19
+ import { startAgentRun, finishAgentRun, recordSystemPrompt } from "./src/handlers/agent.js";
20
20
  import { startTurnObservation, finishTurnObservation } from "./src/handlers/turn.js";
21
21
  import {
22
22
  startGeneration,
@@ -74,10 +74,18 @@ export default async function (pi: ExtensionAPI) {
74
74
  },
75
75
  });
76
76
 
77
- const getSessionId = (ctx?: any) => {
77
+ const getSessionId = (ctx?: unknown): string | undefined => {
78
78
  try {
79
- const sessionFile = ctx?.sessionManager?.getSessionFile?.();
80
- return sessionFile ? basename(sessionFile, ".jsonl") : undefined;
79
+ const sessionManager = (ctx as ExtensionContext | undefined)?.sessionManager;
80
+ const sessionId = sessionManager?.getSessionId?.();
81
+ if (typeof sessionId === "string" && sessionId) {
82
+ return sessionId;
83
+ }
84
+
85
+ const sessionFile = sessionManager?.getSessionFile?.();
86
+ return typeof sessionFile === "string" && sessionFile
87
+ ? basename(sessionFile, ".jsonl")
88
+ : undefined;
81
89
  } catch {
82
90
  return undefined;
83
91
  }
@@ -104,6 +112,9 @@ export default async function (pi: ExtensionAPI) {
104
112
  if (!state.agentState?.root) {
105
113
  await startAgentRun(event, ctx);
106
114
  }
115
+ // The system prompt is only final here: before_agent_start handlers that
116
+ // run after this extension may still rewrite it.
117
+ await recordSystemPrompt(ctx);
107
118
  }));
108
119
 
109
120
  pi.on("turn_start", async (event, ctx) => withSession(ctx, async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-langfuse",
3
- "version": "1.5.9",
3
+ "version": "1.5.11",
4
4
  "description": "Langfuse extension for Pi coding agent",
5
5
  "repository": {
6
6
  "type": "git",
@@ -54,15 +54,6 @@ export async function startAgentRun(event: Record<string, unknown>, ctx: any) {
54
54
  state.currentProvider = ctx.model.provider || "";
55
55
  }
56
56
 
57
- let systemPrompt = undefined;
58
- try {
59
- if (ctx.getSystemPrompt) {
60
- systemPrompt = await ctx.getSystemPrompt();
61
- }
62
- } catch {
63
- // Ignore if getSystemPrompt is not available or fails
64
- }
65
-
66
57
  const rawPromptInput = shapePayload({
67
58
  prompt: event.prompt,
68
59
  images: event.images,
@@ -79,7 +70,6 @@ export async function startAgentRun(event: Record<string, unknown>, ctx: any) {
79
70
  ...(state.currentProvider ? { provider: state.currentProvider } : {}),
80
71
  sessionId: state.currentSessionId || undefined,
81
72
  },
82
- systemPrompt: systemPrompt ? truncate(String(systemPrompt), getLimits().maxString) : undefined,
83
73
  },
84
74
  getCapturePolicy(),
85
75
  );
@@ -106,10 +96,7 @@ export async function startAgentRun(event: Record<string, unknown>, ctx: any) {
106
96
  "pi-agent",
107
97
  {
108
98
  input: captured.input,
109
- metadata: {
110
- ...(captured.metadata ?? {}),
111
- ...(captured.systemPrompt ? { systemPrompt: captured.systemPrompt } : {}),
112
- },
99
+ metadata: captured.metadata ?? {},
113
100
  },
114
101
  { asType: "agent" },
115
102
  ),
@@ -124,6 +111,49 @@ export async function startAgentRun(event: Record<string, unknown>, ctx: any) {
124
111
  }
125
112
  }
126
113
 
114
+ /**
115
+ * Records the effective system prompt on the root agent observation.
116
+ *
117
+ * Deliberately called from `agent_start` rather than `before_agent_start`:
118
+ * during `before_agent_start` the extension runner hands each handler the
119
+ * prompt as it stands mid-chain, so extensions registered after this one
120
+ * (e.g. inline factories that rewrite the system prompt) are not reflected
121
+ * yet. By `agent_start` the session has applied the final override, and
122
+ * `ctx.getSystemPrompt()` returns the prompt actually sent to the model.
123
+ */
124
+ export async function recordSystemPrompt(ctx: any) {
125
+ const root = state.agentState?.root;
126
+ if (state.isTracingDisabled || !root) {
127
+ return;
128
+ }
129
+
130
+ let systemPrompt = undefined;
131
+ try {
132
+ if (ctx.getSystemPrompt) {
133
+ systemPrompt = await ctx.getSystemPrompt();
134
+ }
135
+ } catch {
136
+ // Ignore if getSystemPrompt is not available or fails
137
+ }
138
+ if (!systemPrompt) {
139
+ return;
140
+ }
141
+
142
+ const captured = applyCapturePolicy(
143
+ { systemPrompt: truncate(String(systemPrompt), getLimits().maxString) },
144
+ getCapturePolicy(),
145
+ );
146
+ if (!captured.systemPrompt) {
147
+ return;
148
+ }
149
+
150
+ try {
151
+ root.update({ metadata: { systemPrompt: captured.systemPrompt } });
152
+ } catch (e) {
153
+ console.warn("\u{1F4CA} Langfuse: Failed to record system prompt", e);
154
+ }
155
+ }
156
+
127
157
  export async function finishAgentRun(event: Record<string, unknown> = {}) {
128
158
  if (!state.agentState?.root) {
129
159
  resetRunState();
package/src/langfuse.ts CHANGED
@@ -219,6 +219,9 @@ async function flushPendingScores(rt: LangfuseRuntime, signal: AbortSignal): Pro
219
219
 
220
220
  while (pendingScores.length > 0) {
221
221
  const scores = pendingScores.slice(0, MAX_SCORE_BATCH_SIZE);
222
+ for (const score of scores) {
223
+ score.id ??= randomUUID();
224
+ }
222
225
  try {
223
226
  const errors = await ingestBatch(
224
227
  rt,
package/src/types.ts CHANGED
@@ -60,6 +60,7 @@ export interface LangfuseScoreClient {
60
60
  }
61
61
 
62
62
  export interface PendingScore {
63
+ id?: string;
63
64
  traceId?: string;
64
65
  sessionId?: string;
65
66
  observationId?: string;
@@ -1,4 +1,11 @@
1
1
  declare module "@earendil-works/pi-coding-agent" {
2
+ export interface ExtensionContext {
3
+ sessionManager?: {
4
+ getSessionId?: () => unknown;
5
+ getSessionFile?: () => unknown;
6
+ };
7
+ }
8
+
2
9
  export interface ExtensionAPI {
3
10
  on(event: string, handler: (event: any, ctx: any) => unknown): void;
4
11
  registerCommand(