pi-langfuse 1.4.3 → 1.4.5
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 +104 -248
- package/README_CN.md +108 -248
- package/image.png +0 -0
- package/index.ts +20 -3
- package/package.json +2 -1
- package/src/capture-policy.ts +141 -0
- package/src/commands.ts +261 -0
- package/src/config.ts +26 -16
- package/src/handlers/agent.ts +59 -29
- package/src/handlers/generation.ts +52 -53
- package/src/handlers/tool.ts +37 -24
- package/src/handlers/turn.ts +21 -19
- package/src/langfuse.ts +58 -14
- package/src/observation.ts +21 -0
- package/src/redaction.ts +115 -0
- package/src/state.ts +16 -2
- package/src/types.ts +3 -0
- package/src/utils.ts +20 -2
package/src/handlers/tool.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { state } from "../state.js";
|
|
2
2
|
import { getRuntime, sendScore } from "../langfuse.js";
|
|
3
|
+
import { startChildObservation } from "../observation.js";
|
|
3
4
|
import {
|
|
4
5
|
getToolCallId,
|
|
5
6
|
getToolName,
|
|
@@ -8,8 +9,11 @@ import {
|
|
|
8
9
|
extractTextContent,
|
|
9
10
|
truncate,
|
|
10
11
|
estimatePayloadBytes,
|
|
12
|
+
getCapturePolicy,
|
|
11
13
|
} from "../utils.js";
|
|
12
14
|
import { MAX_TOOL_PAYLOAD_LENGTH } from "../constants.js";
|
|
15
|
+
import { applyCapturePolicy } from "../capture-policy.js";
|
|
16
|
+
import { redactString } from "../redaction.js";
|
|
13
17
|
|
|
14
18
|
export async function startToolObservation(event: Record<string, unknown>) {
|
|
15
19
|
if (state.isTracingDisabled || !state.agentState?.root) {
|
|
@@ -25,25 +29,25 @@ export async function startToolObservation(event: Record<string, unknown>) {
|
|
|
25
29
|
const toolName = getToolName(event);
|
|
26
30
|
const toolInput = getToolInput(event);
|
|
27
31
|
const shapedInput = shapePayload(toolInput, { maxString: MAX_TOOL_PAYLOAD_LENGTH });
|
|
28
|
-
const
|
|
32
|
+
const captured = applyCapturePolicy(
|
|
33
|
+
{
|
|
34
|
+
toolInput: shapedInput,
|
|
35
|
+
metadata: { toolName, toolCallId },
|
|
36
|
+
},
|
|
37
|
+
getCapturePolicy(),
|
|
38
|
+
);
|
|
39
|
+
const inputBytes = estimatePayloadBytes(captured.toolInput, MAX_TOOL_PAYLOAD_LENGTH);
|
|
29
40
|
const parent = state.agentState.activeTurn ?? state.agentState.root;
|
|
30
|
-
const tool =
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
toolName,
|
|
41
|
-
{
|
|
42
|
-
input: shapedInput,
|
|
43
|
-
metadata: { toolName, toolCallId, inputBytes },
|
|
44
|
-
},
|
|
45
|
-
{ asType: "tool" },
|
|
46
|
-
);
|
|
41
|
+
const tool = await startChildObservation({
|
|
42
|
+
parent,
|
|
43
|
+
runtime: getRuntime,
|
|
44
|
+
name: toolName,
|
|
45
|
+
body: {
|
|
46
|
+
input: captured.toolInput,
|
|
47
|
+
metadata: { ...(captured.metadata ?? {}), inputBytes },
|
|
48
|
+
},
|
|
49
|
+
asType: "tool",
|
|
50
|
+
});
|
|
47
51
|
|
|
48
52
|
state.toolCallCount++;
|
|
49
53
|
state.agentState.activeTools.set(toolCallId, {
|
|
@@ -84,18 +88,27 @@ export async function finishToolObservation(event: Record<string, unknown>) {
|
|
|
84
88
|
|
|
85
89
|
try {
|
|
86
90
|
const shapedOutput = shapePayload(output, { maxString: MAX_TOOL_PAYLOAD_LENGTH });
|
|
87
|
-
const
|
|
91
|
+
const captured = applyCapturePolicy(
|
|
92
|
+
{
|
|
93
|
+
toolOutput: shapedOutput,
|
|
94
|
+
metadata: {
|
|
95
|
+
toolName: activeTool.toolName,
|
|
96
|
+
toolCallId,
|
|
97
|
+
isError,
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
getCapturePolicy(),
|
|
101
|
+
);
|
|
102
|
+
const outputBytes = estimatePayloadBytes(captured.toolOutput, MAX_TOOL_PAYLOAD_LENGTH);
|
|
88
103
|
const durationMs = Math.max(0, Date.now() - activeTool.startedAt);
|
|
89
104
|
|
|
90
105
|
activeTool.observation
|
|
91
106
|
.update({
|
|
92
|
-
output:
|
|
107
|
+
output: captured.toolOutput,
|
|
93
108
|
level: isError ? "ERROR" : "DEFAULT",
|
|
94
|
-
statusMessage: isError ? truncate(String(event.error ?? output), 1_000) : undefined,
|
|
109
|
+
statusMessage: isError ? redactString(truncate(String(event.error ?? output), 1_000)) : undefined,
|
|
95
110
|
metadata: {
|
|
96
|
-
|
|
97
|
-
toolCallId,
|
|
98
|
-
isError,
|
|
111
|
+
...(captured.metadata ?? {}),
|
|
99
112
|
durationMs,
|
|
100
113
|
inputBytes: activeTool.inputBytes,
|
|
101
114
|
outputBytes,
|
package/src/handlers/turn.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { state } from "../state.js";
|
|
2
2
|
import { getRuntime } from "../langfuse.js";
|
|
3
|
-
import {
|
|
3
|
+
import { startChildObservation } from "../observation.js";
|
|
4
|
+
import { shapePayload, getCapturePolicy } from "../utils.js";
|
|
5
|
+
import { applyCapturePolicy } from "../capture-policy.js";
|
|
4
6
|
|
|
5
7
|
export async function startTurnObservation(event: Record<string, unknown>) {
|
|
6
8
|
if (state.isTracingDisabled || !state.agentState?.root) {
|
|
@@ -15,23 +17,23 @@ export async function startTurnObservation(event: Record<string, unknown>) {
|
|
|
15
17
|
|
|
16
18
|
try {
|
|
17
19
|
const turnIndex = event.turnIndex ?? state.turnCount;
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
:
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
20
|
+
const captured = applyCapturePolicy(
|
|
21
|
+
{
|
|
22
|
+
input: shapePayload(event.context ?? event),
|
|
23
|
+
metadata: { turnIndex },
|
|
24
|
+
},
|
|
25
|
+
getCapturePolicy(),
|
|
26
|
+
);
|
|
27
|
+
const observation = await startChildObservation({
|
|
28
|
+
parent: state.agentState.root,
|
|
29
|
+
runtime: getRuntime,
|
|
30
|
+
name: "turn",
|
|
31
|
+
body: {
|
|
32
|
+
input: captured.input,
|
|
33
|
+
metadata: captured.metadata,
|
|
34
|
+
},
|
|
35
|
+
asType: "span",
|
|
36
|
+
});
|
|
35
37
|
|
|
36
38
|
state.agentState.activeTurn = observation;
|
|
37
39
|
} catch (e) {
|
|
@@ -39,7 +41,7 @@ export async function startTurnObservation(event: Record<string, unknown>) {
|
|
|
39
41
|
}
|
|
40
42
|
}
|
|
41
43
|
|
|
42
|
-
export function finishTurnObservation(
|
|
44
|
+
export function finishTurnObservation(_event?: Record<string, unknown>) {
|
|
43
45
|
if (state.isTracingDisabled || !state.agentState?.activeTurn) {
|
|
44
46
|
return;
|
|
45
47
|
}
|
package/src/langfuse.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { state } from "./state.js";
|
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
|
|
5
5
|
let runtime: LangfuseRuntime | null = null;
|
|
6
|
+
const activeSessions = new Set<string>();
|
|
6
7
|
|
|
7
8
|
type FallbackObservationType = "SPAN" | "GENERATION";
|
|
8
9
|
|
|
@@ -294,6 +295,14 @@ export async function getRuntime(): Promise<LangfuseRuntime> {
|
|
|
294
295
|
throw new Error("Langfuse config is not set");
|
|
295
296
|
}
|
|
296
297
|
|
|
298
|
+
// Track the current session as a runtime consumer.
|
|
299
|
+
// Multiple sessions can share the same runtime; shutdown is deferred
|
|
300
|
+
// until the last session releases it.
|
|
301
|
+
const sessionId = state.currentSessionId;
|
|
302
|
+
if (sessionId) {
|
|
303
|
+
activeSessions.add(sessionId);
|
|
304
|
+
}
|
|
305
|
+
|
|
297
306
|
if (!runtime) {
|
|
298
307
|
const [{ BasicTracerProvider }, { LangfuseSpanProcessor }, tracing, { LangfuseClient }] = await Promise.all([
|
|
299
308
|
import("@opentelemetry/sdk-trace-base"),
|
|
@@ -337,23 +346,58 @@ export async function getRuntime(): Promise<LangfuseRuntime> {
|
|
|
337
346
|
return runtime as LangfuseRuntime;
|
|
338
347
|
}
|
|
339
348
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
349
|
+
function doShutdownRuntime(): Promise<void> {
|
|
350
|
+
return (async () => {
|
|
351
|
+
if (!runtime) {
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
344
354
|
|
|
345
|
-
|
|
346
|
-
await runtime.tracerProvider?.forceFlush?.();
|
|
347
|
-
await fallbackToRestIngestion(runtime);
|
|
348
|
-
await runtime.scoreClient.flush?.();
|
|
349
|
-
await runtime.scoreClient.shutdown?.();
|
|
350
|
-
await runtime.tracerProvider?.shutdown?.();
|
|
351
|
-
} catch (e) {
|
|
352
|
-
console.warn("📊 Langfuse: Failed to flush/shutdown cleanly", e);
|
|
353
|
-
} finally {
|
|
354
|
-
runtime.clearTracerProvider?.();
|
|
355
|
+
const rt = runtime;
|
|
355
356
|
runtime = null;
|
|
357
|
+
|
|
358
|
+
try {
|
|
359
|
+
await rt.tracerProvider?.forceFlush?.();
|
|
360
|
+
await fallbackToRestIngestion(rt);
|
|
361
|
+
await rt.scoreClient.flush?.();
|
|
362
|
+
await rt.scoreClient.shutdown?.();
|
|
363
|
+
await rt.tracerProvider?.shutdown?.();
|
|
364
|
+
} catch (e) {
|
|
365
|
+
console.warn("📊 Langfuse: Failed to flush/shutdown cleanly", e);
|
|
366
|
+
} finally {
|
|
367
|
+
if (!runtime) {
|
|
368
|
+
rt.clearTracerProvider?.();
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
})();
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Release the current session's reference to the Langfuse runtime.
|
|
376
|
+
* Only actually shuts down the runtime when the last session releases it.
|
|
377
|
+
* Accepts an optional sessionId for use outside of withSession (e.g. deferred callbacks).
|
|
378
|
+
*/
|
|
379
|
+
export async function shutdownRuntime(sessionId?: string): Promise<void> {
|
|
380
|
+
const sid = sessionId ?? state.currentSessionId;
|
|
381
|
+
if (sid) {
|
|
382
|
+
activeSessions.delete(sid);
|
|
356
383
|
}
|
|
384
|
+
|
|
385
|
+
// Still have active sessions — keep the runtime alive.
|
|
386
|
+
if (activeSessions.size > 0) {
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
await doShutdownRuntime();
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Force-shutdown the Langfuse runtime regardless of active session references.
|
|
395
|
+
* Used when the user manually reconfigures (e.g. /langfuse-setup) and needs
|
|
396
|
+
* a fresh runtime with new credentials.
|
|
397
|
+
*/
|
|
398
|
+
export async function forceShutdownRuntime(): Promise<void> {
|
|
399
|
+
activeSessions.clear();
|
|
400
|
+
await doShutdownRuntime();
|
|
357
401
|
}
|
|
358
402
|
|
|
359
403
|
export async function sendScore(name: string, value: number, options: { traceId?: string; observationId?: string } = {}) {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { LangfuseObservation, LangfuseRuntime, ObservationUpdate } from "./types.js";
|
|
2
|
+
|
|
3
|
+
export async function startChildObservation({
|
|
4
|
+
parent,
|
|
5
|
+
runtime,
|
|
6
|
+
name,
|
|
7
|
+
body,
|
|
8
|
+
asType,
|
|
9
|
+
}: {
|
|
10
|
+
parent: LangfuseObservation;
|
|
11
|
+
runtime: () => Promise<LangfuseRuntime>;
|
|
12
|
+
name: string;
|
|
13
|
+
body?: ObservationUpdate;
|
|
14
|
+
asType: "generation" | "tool" | "span";
|
|
15
|
+
}): Promise<LangfuseObservation> {
|
|
16
|
+
if (parent.startObservation) {
|
|
17
|
+
return parent.startObservation(name, body, { asType });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return (await runtime()).startObservation(name, body, { asType });
|
|
21
|
+
}
|
package/src/redaction.ts
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
export const REDACTED = "[REDACTED_SECRET]";
|
|
4
|
+
|
|
5
|
+
export interface RedactOptions {
|
|
6
|
+
maxDepth: number;
|
|
7
|
+
maxArrayItems: number;
|
|
8
|
+
maxObjectKeys: number;
|
|
9
|
+
maxStringLength: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const DEFAULT_OPTIONS: RedactOptions = {
|
|
13
|
+
maxDepth: 6,
|
|
14
|
+
maxArrayItems: 50,
|
|
15
|
+
maxObjectKeys: 80,
|
|
16
|
+
maxStringLength: 12_000,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const SECRET_ASSIGNMENT_RE =
|
|
20
|
+
/\b([A-Z0-9_]*(?:SECRET|TOKEN|PASSWORD|PASS|API[_-]?KEY|PRIVATE[_-]?KEY|AUTH|COOKIE)[A-Z0-9_]*)\s*=\s*([^\s"'`]+)/gi;
|
|
21
|
+
const PRIVATE_KEY_RE = /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g;
|
|
22
|
+
const BEARER_RE = /\bBearer\s+[A-Za-z0-9._~+/=-]{12,}/gi;
|
|
23
|
+
const KNOWN_TOKEN_RE =
|
|
24
|
+
/\b(?:sk-(?:lf|ant|proj|live|test)[A-Za-z0-9_-]*|pk-lf-[A-Za-z0-9_-]+|gh[pousr]_[A-Za-z0-9_]{20,}|npm_[A-Za-z0-9_-]{20,}|AKIA[0-9A-Z]{16})\b/g;
|
|
25
|
+
const ABSOLUTE_PATH_RE =
|
|
26
|
+
/(?:\/Users\/[^/\s]+|\/home\/[^/\s]+|\/private\/tmp|\/tmp|[A-Za-z]:\\Users\\[^\\\s]+)(?:[^\s"'`]*)/g;
|
|
27
|
+
const SENSITIVE_FIELD_RE =
|
|
28
|
+
/^(authorization|cookie|setcookie|xapikey|apikey|token|accesstoken|refreshtoken|secret|secretkey|password|passwd|privatekey)$/;
|
|
29
|
+
|
|
30
|
+
export function hashPath(path: string): string {
|
|
31
|
+
return `[PATH_HASH:${createHash("sha256").update(path).digest("hex").slice(0, 12)}]`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function truncate(value: string, maxStringLength: number): string {
|
|
35
|
+
return value.length > maxStringLength ? `${value.slice(0, maxStringLength)}... [truncated]` : value;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function redactString(value: string, options: Partial<RedactOptions> = {}): string {
|
|
39
|
+
const merged = { ...DEFAULT_OPTIONS, ...options };
|
|
40
|
+
const truncated = truncate(value, merged.maxStringLength);
|
|
41
|
+
return truncated
|
|
42
|
+
.replace(PRIVATE_KEY_RE, REDACTED)
|
|
43
|
+
.replace(BEARER_RE, REDACTED)
|
|
44
|
+
.replace(KNOWN_TOKEN_RE, REDACTED)
|
|
45
|
+
.replace(SECRET_ASSIGNMENT_RE, (_match, key: string) => `${key}=${REDACTED}`)
|
|
46
|
+
.replace(ABSOLUTE_PATH_RE, (path: string) => {
|
|
47
|
+
const envSuffix = path.match(/([/\\]\.env(?:\.[A-Za-z0-9_-]+)?)$/)?.[1];
|
|
48
|
+
return `${hashPath(envSuffix ? path.slice(0, -envSuffix.length) : path)}${envSuffix ?? ""}`;
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function visit(value: unknown, options: RedactOptions, depth: number, seen: WeakSet<object>): unknown {
|
|
53
|
+
if (value === null || value === undefined || typeof value === "number" || typeof value === "boolean") {
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (typeof value === "bigint") {
|
|
58
|
+
return value.toString();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (typeof value === "string") {
|
|
62
|
+
return redactString(value, options);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (typeof value === "function" || typeof value === "symbol") {
|
|
66
|
+
return `[${typeof value}]`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (depth <= 0) {
|
|
70
|
+
return `[max depth ${options.maxDepth} reached]`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (value instanceof Error) {
|
|
74
|
+
return {
|
|
75
|
+
name: redactString(value.name, options),
|
|
76
|
+
message: redactString(value.message, options),
|
|
77
|
+
stack: value.stack ? redactString(value.stack, options) : undefined,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (typeof value !== "object") {
|
|
82
|
+
return redactString(String(value), options);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (seen.has(value)) {
|
|
86
|
+
return "[circular]";
|
|
87
|
+
}
|
|
88
|
+
seen.add(value);
|
|
89
|
+
|
|
90
|
+
if (Array.isArray(value)) {
|
|
91
|
+
const output = value
|
|
92
|
+
.slice(0, options.maxArrayItems)
|
|
93
|
+
.map((item) => visit(item, options, depth - 1, seen));
|
|
94
|
+
if (value.length > options.maxArrayItems) {
|
|
95
|
+
output.push(`[${value.length - options.maxArrayItems} truncated items]`);
|
|
96
|
+
}
|
|
97
|
+
return output;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const entries = Object.entries(value as Record<string, unknown>);
|
|
101
|
+
const output: Record<string, unknown> = {};
|
|
102
|
+
for (const [key, item] of entries.slice(0, options.maxObjectKeys)) {
|
|
103
|
+
const normalizedKey = key.replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
|
|
104
|
+
output[key] = SENSITIVE_FIELD_RE.test(normalizedKey) ? REDACTED : visit(item, options, depth - 1, seen);
|
|
105
|
+
}
|
|
106
|
+
if (entries.length > options.maxObjectKeys) {
|
|
107
|
+
output.__truncatedKeys = entries.length - options.maxObjectKeys;
|
|
108
|
+
}
|
|
109
|
+
return output;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function redactValue(value: unknown, options: Partial<RedactOptions> = {}): unknown {
|
|
113
|
+
const merged: RedactOptions = { ...DEFAULT_OPTIONS, ...options };
|
|
114
|
+
return visit(value, merged, merged.maxDepth, new WeakSet<object>());
|
|
115
|
+
}
|
package/src/state.ts
CHANGED
|
@@ -9,6 +9,7 @@ export interface SessionRunState {
|
|
|
9
9
|
errorCount: number;
|
|
10
10
|
turnCount: number;
|
|
11
11
|
tracingDisabled: boolean;
|
|
12
|
+
setupAttemptedThisSession: boolean;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
const DEFAULT_SESSION_ID = "__pi_langfuse_default_session__";
|
|
@@ -25,6 +26,7 @@ function createSessionRunState(): SessionRunState {
|
|
|
25
26
|
errorCount: 0,
|
|
26
27
|
turnCount: 0,
|
|
27
28
|
tracingDisabled: false,
|
|
29
|
+
setupAttemptedThisSession: false,
|
|
28
30
|
};
|
|
29
31
|
}
|
|
30
32
|
|
|
@@ -59,7 +61,6 @@ export function runWithSession<T>(sessionId: string | undefined, fn: () => T): T
|
|
|
59
61
|
|
|
60
62
|
export const state = {
|
|
61
63
|
config: null as Config | null,
|
|
62
|
-
setupAttemptedThisSession: false,
|
|
63
64
|
sessionStates: new Map<string, SessionRunState>(),
|
|
64
65
|
|
|
65
66
|
get currentSessionId() {
|
|
@@ -118,10 +119,23 @@ export const state = {
|
|
|
118
119
|
set isTracingDisabled(disabled: boolean) {
|
|
119
120
|
getSessionRunState().tracingDisabled = disabled;
|
|
120
121
|
},
|
|
122
|
+
|
|
123
|
+
get setupAttemptedThisSession() {
|
|
124
|
+
return getSessionRunState().setupAttemptedThisSession;
|
|
125
|
+
},
|
|
126
|
+
set setupAttemptedThisSession(attempted: boolean) {
|
|
127
|
+
getSessionRunState().setupAttemptedThisSession = attempted;
|
|
128
|
+
},
|
|
121
129
|
};
|
|
122
130
|
|
|
123
131
|
export function resetRunState(sessionId = getActiveSessionId()) {
|
|
124
|
-
|
|
132
|
+
const normalizedSessionId = normalizeSessionId(sessionId);
|
|
133
|
+
const setupAttemptedThisSession =
|
|
134
|
+
state.sessionStates.get(normalizedSessionId)?.setupAttemptedThisSession ?? false;
|
|
135
|
+
state.sessionStates.set(normalizedSessionId, {
|
|
136
|
+
...createSessionRunState(),
|
|
137
|
+
setupAttemptedThisSession,
|
|
138
|
+
});
|
|
125
139
|
}
|
|
126
140
|
|
|
127
141
|
export function clearAllSessionStates() {
|
package/src/types.ts
CHANGED
package/src/utils.ts
CHANGED
|
@@ -6,6 +6,13 @@ import {
|
|
|
6
6
|
MAX_STRING_LENGTH,
|
|
7
7
|
MAX_TOOL_PAYLOAD_LENGTH,
|
|
8
8
|
} from "./constants.js";
|
|
9
|
+
import { createCapturePolicy, type CapturePolicy } from "./capture-policy.js";
|
|
10
|
+
import { redactValue } from "./redaction.js";
|
|
11
|
+
import { state } from "./state.js";
|
|
12
|
+
|
|
13
|
+
export function getCapturePolicy(): CapturePolicy {
|
|
14
|
+
return state.config?.capturePolicy ?? createCapturePolicy();
|
|
15
|
+
}
|
|
9
16
|
|
|
10
17
|
export function truncate(value: string, maxLength = MAX_STRING_LENGTH): string {
|
|
11
18
|
return value.length > maxLength ? `${value.slice(0, maxLength)}... [truncated]` : value;
|
|
@@ -26,7 +33,10 @@ export function tryParseJson(value: string): unknown {
|
|
|
26
33
|
|
|
27
34
|
const PAYLOAD_TOO_LARGE = "[payload too large]";
|
|
28
35
|
|
|
29
|
-
export function shapePayload(
|
|
36
|
+
export function shapePayload(
|
|
37
|
+
value: unknown,
|
|
38
|
+
options: { maxString?: number; depth?: number; maxNodes?: number; redact?: boolean } = {},
|
|
39
|
+
): unknown {
|
|
30
40
|
const maxString = options.maxString ?? MAX_STRING_LENGTH;
|
|
31
41
|
const depth = options.depth ?? MAX_DEPTH;
|
|
32
42
|
const maxNodes = options.maxNodes ?? MAX_PAYLOAD_NODES;
|
|
@@ -117,7 +127,15 @@ export function shapePayload(value: unknown, options: { maxString?: number; dept
|
|
|
117
127
|
return String(item);
|
|
118
128
|
}
|
|
119
129
|
|
|
120
|
-
|
|
130
|
+
const shaped = visit(value, depth, new WeakSet<object>());
|
|
131
|
+
return options.redact === false
|
|
132
|
+
? shaped
|
|
133
|
+
: redactValue(shaped, {
|
|
134
|
+
maxDepth: depth,
|
|
135
|
+
maxStringLength: maxString,
|
|
136
|
+
maxArrayItems: MAX_ARRAY_ITEMS,
|
|
137
|
+
maxObjectKeys: MAX_OBJECT_KEYS,
|
|
138
|
+
});
|
|
121
139
|
}
|
|
122
140
|
|
|
123
141
|
export function safeSerialize(value: unknown, maxLength = MAX_TOOL_PAYLOAD_LENGTH): string {
|