pi-langfuse 1.5.8 → 1.5.10

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 CHANGED
@@ -77,6 +77,14 @@ export LANGFUSE_BASE_URL="https://cloud.langfuse.com" # optional; LANGFUSE_HOST
77
77
 
78
78
  Saved config takes precedence. Environment variables are only used when `~/.pi/agent/pi-langfuse/config.json` is missing or incomplete.
79
79
 
80
+ For short-lived SDK hosts, set the bounded final score-delivery attempt during shutdown:
81
+
82
+ ```bash
83
+ export PI_LANGFUSE_SCORE_SHUTDOWN_TIMEOUT=2 # seconds; defaults to 2 seconds
84
+ ```
85
+
86
+ The extension attempts queued trace-level scores before other shutdown telemetry work. This value cannot extend the overall shutdown deadline.
87
+
80
88
  Privacy controls can also be set through environment variables:
81
89
 
82
90
  ```bash
package/README_CN.md CHANGED
@@ -77,6 +77,14 @@ export LANGFUSE_BASE_URL="https://cloud.langfuse.com" # 可选;也支持 LANG
77
77
 
78
78
  保存的配置优先级更高。只有当 `~/.pi/agent/pi-langfuse/config.json` 缺失或不完整时,扩展才会使用环境变量。
79
79
 
80
+ 对于短生命周期的 SDK 宿主,可设置关闭时最终分数发送尝试的上限:
81
+
82
+ ```bash
83
+ export PI_LANGFUSE_SCORE_SHUTDOWN_TIMEOUT=2 # 单位为秒;默认 2 秒
84
+ ```
85
+
86
+ 扩展会在其他关闭遥测工作之前尝试发送已排队的 trace 级分数。该值不会延长总关闭超时。
87
+
80
88
  隐私采集策略也可以通过环境变量设置:
81
89
 
82
90
  ```bash
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";
@@ -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
  }
@@ -159,11 +167,11 @@ export default async function (pi: ExtensionAPI) {
159
167
  pi.on("agent_end", async (event, ctx) => withSession(ctx, async () => {
160
168
  await finishAgentRun(event);
161
169
  const sessionId = state.currentSessionId;
162
- setTimeout(() => {
163
- shutdownRuntime(sessionId).catch((error) => {
164
- console.warn("📊 Langfuse: Deferred shutdown failed", error);
165
- });
166
- }, 0);
170
+ try {
171
+ await shutdownRuntime(sessionId);
172
+ } catch (error) {
173
+ console.warn("📊 Langfuse: Shutdown failed", error);
174
+ }
167
175
  }));
168
176
 
169
177
  const handleSessionInterruption = (reason: string) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-langfuse",
3
- "version": "1.5.8",
3
+ "version": "1.5.10",
4
4
  "description": "Langfuse extension for Pi coding agent",
5
5
  "repository": {
6
6
  "type": "git",
package/src/langfuse.ts CHANGED
@@ -60,6 +60,7 @@ interface RestFallbackStore {
60
60
  const OTEL_VISIBILITY_TIMEOUT_MS = 1_500;
61
61
  const OTEL_VISIBILITY_POLL_INTERVAL_MS = 200;
62
62
  const DEFAULT_SHUTDOWN_STEP_TIMEOUT_MS = 2_000;
63
+ const DEFAULT_SCORE_SHUTDOWN_TIMEOUT_MS = 2_000;
63
64
  const DEFAULT_LANGFUSE_REQUEST_TIMEOUT_SECONDS = 5;
64
65
  const DEFAULT_SCORE_FLUSH_AT = 10;
65
66
  const DEFAULT_SCORE_FLUSH_INTERVAL_MS = 1_000;
@@ -80,6 +81,13 @@ function resolvePositiveEnvNumber(name: string, fallback: number, integer = fals
80
81
  return integer ? Math.floor(parsed) : parsed;
81
82
  }
82
83
 
84
+ function getScoreShutdownTimeoutMs(): number {
85
+ return resolvePositiveEnvNumber(
86
+ "PI_LANGFUSE_SCORE_SHUTDOWN_TIMEOUT",
87
+ DEFAULT_SCORE_SHUTDOWN_TIMEOUT_MS / 1_000,
88
+ ) * 1_000;
89
+ }
90
+
83
91
  function delay(ms: number, signal?: AbortSignal) {
84
92
  return new Promise<void>((resolve, reject) => {
85
93
  if (signal?.aborted) {
@@ -211,6 +219,9 @@ async function flushPendingScores(rt: LangfuseRuntime, signal: AbortSignal): Pro
211
219
 
212
220
  while (pendingScores.length > 0) {
213
221
  const scores = pendingScores.slice(0, MAX_SCORE_BATCH_SIZE);
222
+ for (const score of scores) {
223
+ score.id ??= randomUUID();
224
+ }
214
225
  try {
215
226
  const errors = await ingestBatch(
216
227
  rt,
@@ -642,6 +653,12 @@ function doShutdownRuntime(): Promise<void> {
642
653
  const deadline = Date.now() + shutdownStepTimeoutMs;
643
654
  const controller = new AbortController();
644
655
  const abortTimeout = setTimeout(() => controller.abort(), shutdownStepTimeoutMs);
656
+ const scoreController = new AbortController();
657
+ const scoreAbortTimeout = setTimeout(
658
+ () => scoreController.abort(),
659
+ Math.min(getScoreShutdownTimeoutMs(), shutdownStepTimeoutMs),
660
+ );
661
+ scoreAbortTimeout.unref?.();
645
662
  stopScoreFlush(rt);
646
663
 
647
664
  try {
@@ -650,13 +667,13 @@ function doShutdownRuntime(): Promise<void> {
650
667
  () => rt.scoreFlushPromise,
651
668
  deadline,
652
669
  );
670
+ await flushPendingScores(rt, scoreController.signal);
653
671
  await withShutdownDeadline("OTel force flush", () => rt.tracerProvider?.forceFlush?.(), deadline);
654
672
  await withShutdownDeadline(
655
673
  "REST fallback ingestion",
656
674
  () => fallbackToRestIngestion(rt, controller.signal),
657
675
  deadline,
658
676
  );
659
- await flushPendingScores(rt, controller.signal);
660
677
  await withShutdownDeadline("Langfuse score flush", () => rt.scoreClient.flush?.(), deadline);
661
678
  await withShutdownDeadline("Langfuse client shutdown", () => rt.scoreClient.shutdown?.(), deadline);
662
679
  await withShutdownDeadline("OTel tracer shutdown", () => rt.tracerProvider?.shutdown?.(), deadline);
@@ -665,6 +682,8 @@ function doShutdownRuntime(): Promise<void> {
665
682
  console.warn("📊 Langfuse: Failed to flush/shutdown cleanly", e);
666
683
  } finally {
667
684
  clearTimeout(abortTimeout);
685
+ clearTimeout(scoreAbortTimeout);
686
+ scoreController.abort();
668
687
  clearScoreFlushTimer(rt);
669
688
  rt.scoreFlushController?.abort();
670
689
  rt.scoreFlushController = undefined;
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(