@amaster.ai/dsh-langfuse 0.1.15 → 0.1.17
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 +55 -3
- package/lib/client.js +154 -21
- package/lib/index.js +333 -124
- package/lib/media.js +57 -0
- package/lib/types/client.d.ts +22 -4
- package/lib/types/index.d.ts +36 -5
- package/lib/types/media.d.ts +11 -0
- package/package.json +23 -13
package/lib/media.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/** Display-only projection. The attachment service owns byte/digest validation. */
|
|
2
|
+
export async function displayBlocks(content, attachments, options, signal, budget = { remaining: options.maxBytes }) {
|
|
3
|
+
return Promise.all(content.map(async (block) => {
|
|
4
|
+
if (block.type !== 'image')
|
|
5
|
+
return block;
|
|
6
|
+
const ref = block.attachment;
|
|
7
|
+
const fallback = {
|
|
8
|
+
type: 'image',
|
|
9
|
+
mediaType: ref?.mediaType,
|
|
10
|
+
bytes: ref?.bytes,
|
|
11
|
+
width: ref?.width,
|
|
12
|
+
height: ref?.height,
|
|
13
|
+
offloaded: block.offloaded,
|
|
14
|
+
};
|
|
15
|
+
if (!options.enabled ||
|
|
16
|
+
!attachments ||
|
|
17
|
+
block.offloaded ||
|
|
18
|
+
!ref ||
|
|
19
|
+
!['image/png', 'image/jpeg', 'image/webp', 'image/gif'].includes(ref.mediaType) ||
|
|
20
|
+
!Number.isSafeInteger(ref.bytes) ||
|
|
21
|
+
ref.bytes <= 0 ||
|
|
22
|
+
ref.bytes > budget.remaining)
|
|
23
|
+
return fallback;
|
|
24
|
+
budget.remaining -= ref.bytes;
|
|
25
|
+
const controller = new AbortController();
|
|
26
|
+
const timer = setTimeout(() => controller.abort(new Error('image display timed out')), options.timeoutMs);
|
|
27
|
+
timer.unref();
|
|
28
|
+
const readSignal = signal ? AbortSignal.any([signal, controller.signal]) : controller.signal;
|
|
29
|
+
let onAbort;
|
|
30
|
+
try {
|
|
31
|
+
readSignal.throwIfAborted();
|
|
32
|
+
const aborted = new Promise((_, reject) => {
|
|
33
|
+
onAbort = () => reject(readSignal.reason);
|
|
34
|
+
readSignal.addEventListener('abort', onAbort, { once: true });
|
|
35
|
+
});
|
|
36
|
+
const { data } = await Promise.race([attachments.readImage(ref, readSignal), aborted]);
|
|
37
|
+
readSignal.throwIfAborted();
|
|
38
|
+
if (data.byteLength !== ref.bytes)
|
|
39
|
+
throw new Error('image byte length does not match its reference');
|
|
40
|
+
return {
|
|
41
|
+
type: 'image_url',
|
|
42
|
+
image_url: {
|
|
43
|
+
url: `data:${ref.mediaType};base64,${Buffer.from(data).toString('base64')}`,
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
console.error('[dsh-langfuse] image display failed:', error);
|
|
49
|
+
return fallback;
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
clearTimeout(timer);
|
|
53
|
+
if (onAbort)
|
|
54
|
+
readSignal.removeEventListener('abort', onAbort);
|
|
55
|
+
}
|
|
56
|
+
}));
|
|
57
|
+
}
|
package/lib/types/client.d.ts
CHANGED
|
@@ -39,8 +39,21 @@ export interface LangfuseConnectionConfig {
|
|
|
39
39
|
publicKey: string;
|
|
40
40
|
secretKey: string;
|
|
41
41
|
baseUrl: string;
|
|
42
|
+
redactFields?: string[];
|
|
43
|
+
environment?: string;
|
|
44
|
+
release?: string;
|
|
45
|
+
userId?: string;
|
|
46
|
+
tags?: string[];
|
|
47
|
+
captureContent?: boolean;
|
|
48
|
+
captureMedia?: boolean;
|
|
42
49
|
}
|
|
43
50
|
export type ObservationLevel = 'DEFAULT' | 'WARNING' | 'ERROR';
|
|
51
|
+
/** Standard request context supplied by a host for one session's next trace. */
|
|
52
|
+
export interface LangfuseTraceContext {
|
|
53
|
+
userId?: string;
|
|
54
|
+
tags?: string[];
|
|
55
|
+
traceparent?: string;
|
|
56
|
+
}
|
|
44
57
|
/** Any observation that can parent a span (trace root span or nested span/generation). */
|
|
45
58
|
type Observation = LangfuseSpan | LangfuseGeneration;
|
|
46
59
|
/**
|
|
@@ -52,12 +65,13 @@ type Observation = LangfuseSpan | LangfuseGeneration;
|
|
|
52
65
|
* INCLUDES reasoning (verified in dsh-llm-deepseek@0.1.6-alpha.2:
|
|
53
66
|
* `outputTokens: usage.completion_tokens`, with reasoning split out of
|
|
54
67
|
* `completion_tokens_details`), so the `output` bucket subtracts
|
|
55
|
-
* `reasoningTokens` into `
|
|
68
|
+
* `reasoningTokens` into `output_reasoning_tokens`. Only `usageDetails` is sent.
|
|
56
69
|
*/
|
|
57
70
|
export declare function usageOf(usage: TokenUsage): Record<string, number>;
|
|
58
71
|
export declare class LangfuseReporter {
|
|
59
72
|
private config;
|
|
60
73
|
private tracing;
|
|
74
|
+
private api;
|
|
61
75
|
private provider;
|
|
62
76
|
/**
|
|
63
77
|
* Observation → its trace's correlating attributes; children inherit their
|
|
@@ -76,6 +90,8 @@ export declare class LangfuseReporter {
|
|
|
76
90
|
/** Settles (never rejects) once the lazy SDK import+wiring finished. */
|
|
77
91
|
readonly ready: Promise<void>;
|
|
78
92
|
constructor(config: LangfuseConnectionConfig);
|
|
93
|
+
/** Clone telemetry only; never mutate harness-owned messages or tool results. */
|
|
94
|
+
mask<T>(data: T): T;
|
|
79
95
|
/** Dynamically import the SDK and wire the isolated export pipeline. */
|
|
80
96
|
private init;
|
|
81
97
|
/**
|
|
@@ -89,6 +105,7 @@ export declare class LangfuseReporter {
|
|
|
89
105
|
name: string;
|
|
90
106
|
sessionId?: string;
|
|
91
107
|
metadata?: Record<string, unknown>;
|
|
108
|
+
context?: LangfuseTraceContext;
|
|
92
109
|
}): LangfuseSpan | null;
|
|
93
110
|
/** Open a generation (one per LLM call) under any observation parent. */
|
|
94
111
|
startGeneration(parent: Observation | null, input: {
|
|
@@ -106,12 +123,13 @@ export declare class LangfuseReporter {
|
|
|
106
123
|
level?: ObservationLevel;
|
|
107
124
|
statusMessage?: string;
|
|
108
125
|
metadata?: Record<string, unknown>;
|
|
109
|
-
}): void;
|
|
126
|
+
}, endedAt?: Date): void;
|
|
110
127
|
/** Open a span (one per tool dispatch, or a nested detail span) under any observation parent. */
|
|
111
128
|
startSpan(parent: Observation | null, input: {
|
|
112
129
|
name: string;
|
|
113
130
|
input?: unknown;
|
|
114
131
|
metadata?: Record<string, unknown>;
|
|
132
|
+
asType?: 'tool';
|
|
115
133
|
}): LangfuseSpan | null;
|
|
116
134
|
/**
|
|
117
135
|
* Merge fields into an open span — subagent enrichment (label, provider),
|
|
@@ -121,7 +139,7 @@ export declare class LangfuseReporter {
|
|
|
121
139
|
* deprecated `langfuse.trace.*` attributes (setTraceIO): older Langfuse
|
|
122
140
|
* servers derive the trace row's IO from exactly those keys.
|
|
123
141
|
*/
|
|
124
|
-
updateSpan(span:
|
|
142
|
+
updateSpan(span: Observation | null, update: {
|
|
125
143
|
name?: string;
|
|
126
144
|
input?: unknown;
|
|
127
145
|
metadata?: Record<string, unknown>;
|
|
@@ -137,7 +155,7 @@ export declare class LangfuseReporter {
|
|
|
137
155
|
level?: ObservationLevel;
|
|
138
156
|
statusMessage?: string;
|
|
139
157
|
metadata?: Record<string, unknown>;
|
|
140
|
-
}): void;
|
|
158
|
+
}, endedAt?: Date): void;
|
|
141
159
|
/** Drain buffered spans (the `session/flush` checkpoint). */
|
|
142
160
|
flush(): Promise<void>;
|
|
143
161
|
/** Shut the exporter down at fiber unload (provider.shutdown flushes internally). */
|
package/lib/types/index.d.ts
CHANGED
|
@@ -4,11 +4,10 @@
|
|
|
4
4
|
* Instruments the documented seams, with zero core patches:
|
|
5
5
|
*
|
|
6
6
|
* - `llm/stream` waterfall → one Langfuse **generation** per LLM call
|
|
7
|
-
* (input =
|
|
7
|
+
* (input = loop-built options; output, token usage and finish reason collected
|
|
8
8
|
* from the chunk stream), plus a nested `llm-request` **span** holding the
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* assembled inside the adapter).
|
|
9
|
+
* same options and collected chunk stream (the rawest request/response a
|
|
10
|
+
* plugin can observe — the provider HTTP body is assembled inside the adapter).
|
|
12
11
|
* - `tools/execute` waterfall → one Langfuse **span** per tool dispatch
|
|
13
12
|
* (routed to the session's trace via `exec.agent.id` — the agent/session
|
|
14
13
|
* shared identity).
|
|
@@ -24,13 +23,21 @@
|
|
|
24
23
|
* `subagent/descriptor` event).
|
|
25
24
|
*
|
|
26
25
|
* Event/waterfall shapes verified against the installed
|
|
27
|
-
* @deepseek-ai/dsh-{llm,session,tools,agent,subagent}@0.
|
|
26
|
+
* @deepseek-ai/dsh-{llm,session,tools,agent,subagent}@0.2.0-rc.1 package sources
|
|
28
27
|
* (types for shapes; `lib/*.js` for the session/created ordering claim).
|
|
29
28
|
*
|
|
30
29
|
* @module dsh-langfuse
|
|
31
30
|
*/
|
|
32
31
|
import type { Context } from '@deepseek-ai/cordis';
|
|
33
32
|
import Schema from '@deepseek-ai/schemastery';
|
|
33
|
+
import { type LangfuseTraceContext } from './client.js';
|
|
34
|
+
export type { LangfuseTraceContext } from './client.js';
|
|
35
|
+
declare module '@deepseek-ai/cordis' {
|
|
36
|
+
interface Events {
|
|
37
|
+
/** Bind standard labels/parent context before a session's next trace starts. */
|
|
38
|
+
'langfuse/context': (sessionId: string, context: LangfuseTraceContext) => void;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
34
41
|
export declare const name = "dsh-langfuse";
|
|
35
42
|
/**
|
|
36
43
|
* Waterfall listeners only need the events to exist; no hard service
|
|
@@ -47,6 +54,14 @@ export declare const Config: Schema<Schemastery.ObjectS<{
|
|
|
47
54
|
traceName: Schema<string, string>;
|
|
48
55
|
/** Include full message/tool payloads in Langfuse IO. Disable to log metadata only. */
|
|
49
56
|
captureContent: Schema<boolean, boolean>;
|
|
57
|
+
redactFields: Schema<string[], string[]>;
|
|
58
|
+
userId: Schema<string, string>;
|
|
59
|
+
tags: Schema<string[], string[]>;
|
|
60
|
+
environment: Schema<string, string>;
|
|
61
|
+
release: Schema<string, string>;
|
|
62
|
+
captureMedia: Schema<boolean, boolean>;
|
|
63
|
+
maxMediaBytes: Schema<number, number>;
|
|
64
|
+
mediaTimeoutMs: Schema<number, number>;
|
|
50
65
|
}>, Schemastery.ObjectT<{
|
|
51
66
|
enabled: Schema<boolean, boolean>;
|
|
52
67
|
publicKey: Schema<string, string>;
|
|
@@ -55,6 +70,14 @@ export declare const Config: Schema<Schemastery.ObjectS<{
|
|
|
55
70
|
traceName: Schema<string, string>;
|
|
56
71
|
/** Include full message/tool payloads in Langfuse IO. Disable to log metadata only. */
|
|
57
72
|
captureContent: Schema<boolean, boolean>;
|
|
73
|
+
redactFields: Schema<string[], string[]>;
|
|
74
|
+
userId: Schema<string, string>;
|
|
75
|
+
tags: Schema<string[], string[]>;
|
|
76
|
+
environment: Schema<string, string>;
|
|
77
|
+
release: Schema<string, string>;
|
|
78
|
+
captureMedia: Schema<boolean, boolean>;
|
|
79
|
+
maxMediaBytes: Schema<number, number>;
|
|
80
|
+
mediaTimeoutMs: Schema<number, number>;
|
|
58
81
|
}>>;
|
|
59
82
|
export interface LangfusePluginConfig {
|
|
60
83
|
enabled: boolean;
|
|
@@ -63,5 +86,13 @@ export interface LangfusePluginConfig {
|
|
|
63
86
|
baseUrl: string;
|
|
64
87
|
traceName: string;
|
|
65
88
|
captureContent: boolean;
|
|
89
|
+
redactFields?: string[];
|
|
90
|
+
userId?: string;
|
|
91
|
+
tags?: string[];
|
|
92
|
+
environment?: string;
|
|
93
|
+
release?: string;
|
|
94
|
+
captureMedia?: boolean;
|
|
95
|
+
maxMediaBytes?: number;
|
|
96
|
+
mediaTimeoutMs?: number;
|
|
66
97
|
}
|
|
67
98
|
export declare function apply(ctx: Context, config: LangfusePluginConfig): Promise<void> | void;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { AttachmentStore } from '@deepseek-ai/dsh-attachment';
|
|
2
|
+
import type { ContentBlock } from '@deepseek-ai/dsh-llm';
|
|
3
|
+
export interface MediaOptions {
|
|
4
|
+
enabled: boolean;
|
|
5
|
+
maxBytes: number;
|
|
6
|
+
timeoutMs: number;
|
|
7
|
+
}
|
|
8
|
+
/** Display-only projection. The attachment service owns byte/digest validation. */
|
|
9
|
+
export declare function displayBlocks(content: readonly ContentBlock[], attachments: AttachmentStore | undefined, options: MediaOptions, signal?: AbortSignal, budget?: {
|
|
10
|
+
remaining: number;
|
|
11
|
+
}): Promise<unknown[]>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@amaster.ai/dsh-langfuse",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.17",
|
|
4
4
|
"description": "Langfuse observability plugin for DeepSeek Harness: one generation per LLM call with a nested llm-request span, one span per tool call, one trace per session turn, subagent child sessions nested under the parent's tree.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -36,15 +36,23 @@
|
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
38
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
39
|
-
"@deepseek-ai/dsh-
|
|
40
|
-
"@deepseek-ai/dsh-
|
|
41
|
-
"@deepseek-ai/dsh-
|
|
42
|
-
"@deepseek-ai/dsh-
|
|
39
|
+
"@deepseek-ai/dsh-attachment": ">=0.2.0-rc.1",
|
|
40
|
+
"@deepseek-ai/dsh-llm": ">=0.1.5-rc.1 || >=0.1.6-alpha.1 || >=0.1.7-alpha.1 || >=0.2.0-rc.1",
|
|
41
|
+
"@deepseek-ai/dsh-session": ">=0.1.5-rc.1 || >=0.1.6-alpha.1 || >=0.1.7-alpha.1 || >=0.2.0-rc.1",
|
|
42
|
+
"@deepseek-ai/dsh-subagent": ">=0.1.5-rc.1 || >=0.1.6-alpha.1 || >=0.1.7-alpha.1 || >=0.2.0-rc.1",
|
|
43
|
+
"@deepseek-ai/dsh-tools": ">=0.1.5-rc.1 || >=0.1.6-alpha.1 || >=0.1.7-alpha.1 || >=0.2.0-rc.1",
|
|
43
44
|
"@langfuse/otel": "^5.10.1",
|
|
44
45
|
"@langfuse/tracing": "^5.10.1",
|
|
46
|
+
"@opentelemetry/api": "^1.9.0",
|
|
45
47
|
"@opentelemetry/sdk-trace-node": "^2.10.0"
|
|
46
48
|
},
|
|
47
49
|
"peerDependenciesMeta": {
|
|
50
|
+
"@opentelemetry/api": {
|
|
51
|
+
"optional": true
|
|
52
|
+
},
|
|
53
|
+
"@deepseek-ai/dsh-attachment": {
|
|
54
|
+
"optional": true
|
|
55
|
+
},
|
|
48
56
|
"@langfuse/otel": {
|
|
49
57
|
"optional": true
|
|
50
58
|
},
|
|
@@ -57,17 +65,19 @@
|
|
|
57
65
|
},
|
|
58
66
|
"devDependencies": {
|
|
59
67
|
"@deepseek-ai/cordis": "^4.0.4",
|
|
60
|
-
"@deepseek-ai/dsh-agent": "0.
|
|
61
|
-
"@deepseek-ai/dsh-
|
|
62
|
-
"@deepseek-ai/dsh-
|
|
63
|
-
"@deepseek-ai/dsh-
|
|
64
|
-
"@deepseek-ai/dsh-session
|
|
65
|
-
"@deepseek-ai/dsh-
|
|
66
|
-
"@deepseek-ai/dsh-
|
|
67
|
-
"@deepseek-ai/dsh-
|
|
68
|
+
"@deepseek-ai/dsh-agent": "0.2.0-rc.1",
|
|
69
|
+
"@deepseek-ai/dsh-attachment": "0.2.0-rc.1",
|
|
70
|
+
"@deepseek-ai/dsh-llm": "0.2.0-rc.1",
|
|
71
|
+
"@deepseek-ai/dsh-ptc-runtime": "0.2.0-rc.1",
|
|
72
|
+
"@deepseek-ai/dsh-session": "0.2.0-rc.1",
|
|
73
|
+
"@deepseek-ai/dsh-session-projection": "0.2.0-rc.1",
|
|
74
|
+
"@deepseek-ai/dsh-subagent": "0.2.0-rc.1",
|
|
75
|
+
"@deepseek-ai/dsh-tools": "0.2.0-rc.1",
|
|
76
|
+
"@deepseek-ai/dsh-util-time": "0.2.0-rc.1",
|
|
68
77
|
"@langfuse/otel": "5.10.1",
|
|
69
78
|
"@langfuse/tracing": "5.10.1",
|
|
70
79
|
"@opentelemetry/api": "^1.9.1",
|
|
80
|
+
"@opentelemetry/exporter-trace-otlp-http": "0.221.0",
|
|
71
81
|
"@opentelemetry/sdk-trace-node": "^2.10.0",
|
|
72
82
|
"@types/node": "^22.0.0",
|
|
73
83
|
"typescript": "^5.6.3",
|