@x-otto/otel 0.0.1-alpha.0

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 ADDED
@@ -0,0 +1,122 @@
1
+ # @x-otto/otel
2
+
3
+ OpenTelemetry GenAI integration — transforms session `TraceEvent` streams into OTLP-compatible spans for export to Jaeger, Langfuse, or any standard OTLP collector.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pnpm add @x-otto/otel
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```ts
14
+ import {
15
+ traceToGenAiSpans,
16
+ buildOtlpTracesPayload,
17
+ OtlpHttpExporter,
18
+ otlpExporterFromEnv,
19
+ exportSessionTrace,
20
+ } from '@x-otto/otel'
21
+
22
+ // 1. Transform trace events to GenAI semantic convention spans
23
+ const trace = traceToGenAiSpans(sessionId, events)
24
+
25
+ // 2. Build OTLP HTTP payload
26
+ const payload = buildOtlpTracesPayload(trace)
27
+
28
+ // 3. Export via collector
29
+ const exporter = new OtlpHttpExporter({
30
+ endpoint: 'http://localhost:4318/v1/traces',
31
+ })
32
+ await exporter.export(payload)
33
+
34
+ // Or: auto-configure from environment variables
35
+ const auto = otlpExporterFromEnv()
36
+ if (auto) {
37
+ await exportSessionTrace(sessionId, events, auto)
38
+ }
39
+ ```
40
+
41
+ ## Core Functions
42
+
43
+ ### traceToGenAiSpans (`genai-spans.ts`)
44
+
45
+ Pure function: `(sessionId, events[], options?) → GenAiTrace`
46
+
47
+ Span tree structure:
48
+ ```
49
+ invoke_agent (root, spanKind=INTERNAL)
50
+ ├── chat (per LLM turn, spanKind=CLIENT)
51
+ │ ├── execute_tool (per tool call, spanKind=INTERNAL)
52
+ │ └── ...
53
+ └── ...
54
+ ```
55
+
56
+ - `traceId` / `spanId` deterministic: SHA-256 derived from sessionId (+ turn/toolCallId) — stable across re-exports
57
+ - Tool spans parented to requesting chat turn (by event order proximity, not turn numbering)
58
+ - Unclosed spans (abort) closed at session end timestamp
59
+ - `includeContent` option (default `false` — privacy-first, omits prompt/response/tool content)
60
+
61
+ ### buildOtlpTracesPayload (`otlp.ts`)
62
+
63
+ Pure function: `(trace: GenAiTrace, options?) → OtlpTracesPayload`
64
+
65
+ - Wraps GenAiSpans into OTLP/HTTP JSON structure: `resourceSpans → scopeSpans → spans`
66
+ - Default `service.name=otto`, scope `@x-otto/devtools/otel`
67
+ - Customizable resource attributes and scope name
68
+
69
+ ### OtlpHttpExporter (`exporter.ts`)
70
+
71
+ Implements `OtelSpanExporter` interface:
72
+
73
+ - Fetch-injectable (test with mock)
74
+ - 10s default timeout (`AbortSignal.timeout`)
75
+ - Logging via `@x-otto/shared` logger
76
+ - Error and non-200 responses logged as warnings
77
+
78
+ ### otlpExporterFromEnv (`exporter.ts`)
79
+
80
+ Opt-in from environment variables. Returns `undefined` when unconfigured:
81
+
82
+ | Env Var | Example |
83
+ |---------|---------|
84
+ | `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` | `http://collector:4318/v1/traces` |
85
+ | `OTEL_EXPORTER_OTLP_ENDPOINT` | `http://collector:4318` (auto-appends `/v1/traces`) |
86
+ | `OTEL_EXPORTER_OTLP_HEADERS` | `Authorization=Bearer xxx` (comma-separated key=value pairs) |
87
+
88
+ `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` takes priority when both are set.
89
+
90
+ ### exportSessionTrace (`exporter.ts`)
91
+
92
+ Convenience: trace → span → OTLP → export. Empty trace short-circuits to success.
93
+
94
+ ## Key Files
95
+
96
+ ```
97
+ src/
98
+ genai-spans.ts # traceToGenAiSpans — TraceEvent → GenAI span tree (deterministic IDs)
99
+ otlp.ts # buildOtlpTracesPayload — GenAiSpan → OTLP/HTTP JSON
100
+ exporter.ts # OtlpHttpExporter, exportSessionTrace, otlpExporterFromEnv
101
+ index.ts # Barrel exports
102
+ tests/ # Test files
103
+ ```
104
+
105
+ ## Dependencies
106
+
107
+ - Internal: `@x-otto/agent` (TraceEvent), `@x-otto/shared` (logger)
108
+ - External: none (OTLP export uses global fetch — no OTel SDK required)
109
+
110
+ ## Testing
111
+
112
+ ```bash
113
+ pnpm --filter @x-otto/otel typecheck
114
+ pnpm --filter @x-otto/otel build
115
+ pnpm vitest run packages/otel/tests/
116
+ ```
117
+
118
+ ## Related
119
+
120
+ - [@x-otto/devtools OTEL submodule](../devtools/README.md) — upstream integration
121
+ - [OpenInference GenAI Semantic Conventions](https://github.com/open-telemetry/semantic-conventions/tree/main/docs/gen-ai)
122
+ - [OTLP HTTP Protocol](https://opentelemetry.io/docs/specs/otlp/)
@@ -0,0 +1,145 @@
1
+ import { TraceEvent } from "@x-otto/agent";
2
+
3
+ //#region src/genai-spans.d.ts
4
+ declare const SPAN_KIND_INTERNAL = 1;
5
+ declare const SPAN_KIND_CLIENT = 3;
6
+ declare const STATUS_UNSET = 0;
7
+ declare const STATUS_OK = 1;
8
+ declare const STATUS_ERROR = 2;
9
+ type SpanAttributeValue = string | number | boolean | string[];
10
+ interface GenAiSpan {
11
+ spanId: string;
12
+ parentSpanId?: string;
13
+ name: string;
14
+ kind: number;
15
+ startTimeUnixNano: string;
16
+ endTimeUnixNano: string;
17
+ attributes: Record<string, SpanAttributeValue>;
18
+ status: {
19
+ code: number;
20
+ message?: string;
21
+ };
22
+ }
23
+ interface GenAiTrace {
24
+ traceId: string;
25
+ spans: GenAiSpan[];
26
+ }
27
+ interface GenAiExportOptions {
28
+ /** 默认 false:不导出 prompt/响应/工具内容(防泄漏)。仅 true 时附内容属性。 */
29
+ includeContent?: boolean;
30
+ }
31
+ /**
32
+ * 把一个会话的 trace 事件序列映射为 GenAI span 树。
33
+ *
34
+ * traceId / spanId 由 sessionId(+ turn / toolCallId)确定性派生(sha256 截断)——同一 trace 重复导出
35
+ * 得到稳定 id,便于幂等再导出与测试断言。tool span 归属"请求它的那个 chat turn"(按事件次序就近父挂,
36
+ * 不依赖 turn 编号偏移)。未闭合的 span(abort)以会话末尾时刻收尾。
37
+ */
38
+ declare function traceToGenAiSpans(sessionId: string, events: TraceEvent[], options?: GenAiExportOptions): GenAiTrace;
39
+ //#endregion
40
+ //#region src/otlp.d.ts
41
+ type OtlpAnyValue = {
42
+ stringValue: string;
43
+ } | {
44
+ boolValue: boolean;
45
+ } | {
46
+ intValue: string;
47
+ } | {
48
+ doubleValue: number;
49
+ } | {
50
+ arrayValue: {
51
+ values: OtlpAnyValue[];
52
+ };
53
+ };
54
+ interface OtlpKeyValue {
55
+ key: string;
56
+ value: OtlpAnyValue;
57
+ }
58
+ interface OtlpSpan {
59
+ traceId: string;
60
+ spanId: string;
61
+ parentSpanId?: string;
62
+ name: string;
63
+ kind: number;
64
+ startTimeUnixNano: string;
65
+ endTimeUnixNano: string;
66
+ attributes: OtlpKeyValue[];
67
+ status: {
68
+ code: number;
69
+ message?: string;
70
+ };
71
+ }
72
+ interface OtlpTracesPayload {
73
+ resourceSpans: Array<{
74
+ resource: {
75
+ attributes: OtlpKeyValue[];
76
+ };
77
+ scopeSpans: Array<{
78
+ scope: {
79
+ name: string;
80
+ version?: string;
81
+ };
82
+ spans: OtlpSpan[];
83
+ }>;
84
+ }>;
85
+ }
86
+ interface OtlpBuildOptions {
87
+ /** Resource 属性(service.name 等)。缺省 service.name=otto。 */
88
+ resourceAttributes?: Record<string, SpanAttributeValue>;
89
+ scopeName?: string;
90
+ scopeVersion?: string;
91
+ }
92
+ declare function buildOtlpTracesPayload(trace: GenAiTrace, options?: OtlpBuildOptions): OtlpTracesPayload;
93
+ //#endregion
94
+ //#region src/exporter.d.ts
95
+ interface OtelExportResult {
96
+ ok: boolean;
97
+ status?: number;
98
+ error?: string;
99
+ }
100
+ interface OtelSpanExporter {
101
+ export(payload: OtlpTracesPayload): Promise<OtelExportResult>;
102
+ }
103
+ type FetchLike = (input: string, init: {
104
+ method: string;
105
+ headers: Record<string, string>;
106
+ body: string;
107
+ signal?: AbortSignal;
108
+ }) => Promise<{
109
+ ok: boolean;
110
+ status: number;
111
+ text(): Promise<string>;
112
+ }>;
113
+ interface OtlpHttpExporterOptions {
114
+ /** collector 的 traces 端点,如 http://localhost:4318/v1/traces。 */
115
+ endpoint: string;
116
+ /** 附加请求头(鉴权等),如 { Authorization: 'Bearer ...' }。 */
117
+ headers?: Record<string, string>;
118
+ /** 注入 fetch(缺省用全局 fetch)。测试可注入假实现。 */
119
+ fetch?: FetchLike;
120
+ /** 请求超时(ms),缺省 10s。 */
121
+ timeoutMs?: number;
122
+ }
123
+ declare class OtlpHttpExporter implements OtelSpanExporter {
124
+ private readonly endpoint;
125
+ private readonly headers;
126
+ private readonly fetchImpl;
127
+ private readonly timeoutMs;
128
+ constructor(options: OtlpHttpExporterOptions);
129
+ export(payload: OtlpTracesPayload): Promise<OtelExportResult>;
130
+ }
131
+ interface ExportSessionTraceOptions extends GenAiExportOptions, OtlpBuildOptions {}
132
+ /**
133
+ * 把一个会话的 trace 事件导出为 OTLP span(opt-in 入口)。宿主仅在持有 exporter 时调用。
134
+ * 空 trace 直接短路成功(不发空负载)。
135
+ */
136
+ declare function exportSessionTrace(sessionId: string, events: TraceEvent[], exporter: OtelSpanExporter, options?: ExportSessionTraceOptions): Promise<OtelExportResult>;
137
+ /**
138
+ * 从环境变量构造 opt-in 导出器:仅当 `OTEL_EXPORTER_OTLP_ENDPOINT`(或 traces 专用
139
+ * `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`)已设置时返回 exporter,否则 undefined(= 不导出)。
140
+ * 标准 OTel 端点约定:base endpoint 需补 `/v1/traces` 路径;traces 专用端点直接用。
141
+ */
142
+ declare function otlpExporterFromEnv(env?: Record<string, string | undefined>): OtlpHttpExporter | undefined;
143
+ //#endregion
144
+ export { type ExportSessionTraceOptions, type GenAiExportOptions, type GenAiSpan, type GenAiTrace, type OtelExportResult, type OtelSpanExporter, type OtlpBuildOptions, OtlpHttpExporter, type OtlpHttpExporterOptions, type OtlpTracesPayload, SPAN_KIND_CLIENT, SPAN_KIND_INTERNAL, STATUS_ERROR, STATUS_OK, STATUS_UNSET, type SpanAttributeValue, buildOtlpTracesPayload, exportSessionTrace, otlpExporterFromEnv, traceToGenAiSpans };
145
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/genai-spans.ts","../src/otlp.ts","../src/exporter.ts"],"mappings":";;;cAGa,kBAAA;AAAA,cACA,gBAAA;AAAA,cACA,YAAA;AAAA,cACA,SAAA;AAAA,cACA,YAAA;AAAA,KAED,kBAAA;AAAA,UAEK,SAAA;EACf,MAAA;EACA,YAAA;EACA,IAAA;EACA,IAAA;EACA,iBAAA;EACA,eAAA;EACA,UAAA,EAAY,MAAA,SAAe,kBAAA;EAC3B,MAAA;IAAU,IAAA;IAAc,OAAA;EAAA;AAAA;AAAA,UAGT,UAAA;EACf,OAAA;EACA,KAAA,EAAO,SAAA;AAAA;AAAA,UAGQ,kBAAA;;EAEf,cAAA;AAAA;AApBF;;;;;AAEA;;AAFA,iBA2DgB,iBAAA,CACd,SAAA,UACA,MAAA,EAAQ,UAAA,IACR,OAAA,GAAS,kBAAA,GACR,UAAA;;;KCtEE,YAAA;EACC,WAAA;AAAA;EACA,SAAA;AAAA;EACA,QAAA;AAAA;EACA,WAAA;AAAA;EACA,UAAA;IAAc,MAAA,EAAQ,YAAA;EAAA;AAAA;AAAA,UAElB,YAAA;EACR,GAAA;EACA,KAAA,EAAO,YAAA;AAAA;AAAA,UAGC,QAAA;EACR,OAAA;EACA,MAAA;EACA,YAAA;EACA,IAAA;EACA,IAAA;EACA,iBAAA;EACA,eAAA;EACA,UAAA,EAAY,YAAA;EACZ,MAAA;IAAU,IAAA;IAAc,OAAA;EAAA;AAAA;AAAA,UAGT,iBAAA;EACf,aAAA,EAAe,KAAA;IACb,QAAA;MAAY,UAAA,EAAY,YAAA;IAAA;IACxB,UAAA,EAAY,KAAA;MACV,KAAA;QAAS,IAAA;QAAc,OAAA;MAAA;MACvB,KAAA,EAAO,QAAA;IAAA;EAAA;AAAA;AAAA,UAKI,gBAAA;EDjBL;ECmBV,kBAAA,GAAqB,MAAA,SAAe,kBAAA;EACpC,SAAA;EACA,YAAA;AAAA;AAAA,iBAyCc,sBAAA,CACd,KAAA,EAAO,UAAA,EACP,OAAA,GAAS,gBAAA,GACR,iBAAA;;;UC7Ec,gBAAA;EACf,EAAA;EACA,MAAA;EACA,KAAA;AAAA;AAAA,UAGe,gBAAA;EACf,MAAA,CAAO,OAAA,EAAS,iBAAA,GAAoB,OAAA,CAAQ,gBAAA;AAAA;AAAA,KAGzC,SAAA,IACH,KAAA,UACA,IAAA;EACE,MAAA;EACA,OAAA,EAAS,MAAA;EACT,IAAA;EACA,MAAA,GAAS,WAAA;AAAA,MAER,OAAA;EAAU,EAAA;EAAa,MAAA;EAAgB,IAAA,IAAQ,OAAA;AAAA;AAAA,UAEnC,uBAAA;EFrBK;EEuBpB,QAAA;EFtBuB;EEwBvB,OAAA,GAAU,MAAA;EFxBa;EE0BvB,KAAA,GAAQ,SAAA;EFxBE;EE0BV,SAAA;AAAA;AAAA,cAGW,gBAAA,YAA4B,gBAAA;EAAA,iBACtB,QAAA;EAAA,iBACA,OAAA;EAAA,iBACA,SAAA;EAAA,iBACA,SAAA;cAEL,OAAA,EAAS,uBAAA;EAOf,MAAA,CAAO,OAAA,EAAS,iBAAA,GAAoB,OAAA,CAAQ,gBAAA;AAAA;AAAA,UAsBnC,yBAAA,SAAkC,kBAAA,EAAoB,gBAAA;;;;;iBAMjD,kBAAA,CACpB,SAAA,UACA,MAAA,EAAQ,UAAA,IACR,QAAA,EAAU,gBAAA,EACV,OAAA,GAAS,yBAAA,GACR,OAAA,CAAQ,gBAAA;;;;;;iBAcK,mBAAA,CACd,GAAA,GAAK,MAAA,+BACJ,gBAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ import{createHash as e}from"node:crypto";import{createLogger as t}from"@x-otto/shared";const n=1,r=3,i=0,a=1,o=2;function s(t,n){return e(`sha256`).update(t).digest(`hex`).slice(0,n*2)}function c(e){return(BigInt(Math.round(e))*1000000n).toString()}function l(e){return Array.isArray(e)?e.filter(e=>!!e&&typeof e==`object`&&e.type===`text`).map(e=>e.text).join(``):``}function u(e,t,n={}){let r=s(`trace:${e}`,16),i=t=>s(`span:${e}:${t}`,8),a=n.includeContent??!1;if(t.length===0)return{traceId:r,spans:[]};let o=Math.min(...t.map(e=>e.ts)),u=Math.max(...t.map(e=>e.ts)),d=i(`root`),f=[],p=new Map,m=new Map,h=new Map,g=new Set,_=[],v=d,y=0,b,x=e=>e.payload??{};for(let e of t){let t=c(e.ts);if(e.kind===`lifecycle`){if(e.node===`prompt.before`){if(a){let t=x(e).text;typeof t==`string`&&_.push(t)}}else if(e.node===`turn.start`&&e.turn!==void 0){let n={spanId:i(`turn:${e.turn}`),parentSpanId:d,name:`chat`,kind:3,startTimeUnixNano:t,endTimeUnixNano:t,attributes:{"gen_ai.operation.name":`chat`},status:{code:0}};p.set(e.turn,n),f.push(n),g.add(n),v=n.spanId}else if(e.node===`turn.end`&&e.turn!==void 0){let t=p.get(e.turn);if(t){t.endTimeUnixNano=c(e.ts),g.delete(t);let n=x(e),r=n.usage;r&&(typeof r.inputTokens==`number`&&(t.attributes[`gen_ai.usage.input_tokens`]=r.inputTokens),typeof r.outputTokens==`number`&&(t.attributes[`gen_ai.usage.output_tokens`]=r.outputTokens)),typeof n.stopReason==`string`&&(t.attributes[`gen_ai.response.finish_reasons`]=[n.stopReason]);let i=h.get(e.turn);i?.model&&(t.attributes[`gen_ai.request.model`]=i.model,t.attributes[`gen_ai.response.model`]=i.model,t.name=`chat ${i.model}`),i?.system&&(t.attributes[`gen_ai.system`]=i.system),a&&i?.completion&&(t.attributes[`gen_ai.completion`]=i.completion)}}else if(e.node===`agent.error`){y=2;let t=x(e).message;b=typeof t==`string`?t:`agent error`}}else if(e.kind===`stream`&&e.node===`stream.event`&&e.turn!==void 0){let t=e.payload;if(t?.type===`done`&&t.message){let n=h.get(e.turn)??{};typeof t.message.model==`string`&&(n.model=t.message.model),typeof t.message.provider==`string`&&(n.system=t.message.provider),a&&(n.completion=l(t.message.content)),h.set(e.turn,n)}}else if(e.kind===`tool`){let n=x(e),r=typeof n.id==`string`?n.id:``;if(e.node===`tool.call.start`){let e=typeof n.name==`string`?n.name:`tool`,o={spanId:i(`tool:${r}`),parentSpanId:v,name:`execute_tool ${e}`,kind:1,startTimeUnixNano:t,endTimeUnixNano:t,attributes:{"gen_ai.operation.name":`execute_tool`,"gen_ai.tool.name":e,"gen_ai.tool.call.id":r},status:{code:0}};a&&(o.attributes[`gen_ai.tool.arguments`]=JSON.stringify(n.arguments??{})),m.set(r,o),f.push(o),g.add(o)}else if(e.node===`tool.call.end`){let t=m.get(r);if(t){t.endTimeUnixNano=c(e.ts),g.delete(t);let r=n.resultSummary??n.result;r?.isError&&(t.status={code:2}),a&&r&&(t.attributes[`gen_ai.tool.result`]=l(r.content??r.contentPreview)),typeof n.durationMs==`number`&&(t.attributes[`gen_ai.tool.duration_ms`]=n.durationMs),typeof n.toolCallIndex==`number`&&(t.attributes[`gen_ai.tool.call_index`]=n.toolCallIndex),typeof n.errorCategory==`string`&&(t.attributes[`gen_ai.tool.error_category`]=n.errorCategory)}}}}for(let e of g)e.endTimeUnixNano=c(u);let S={spanId:d,name:`invoke_agent`,kind:1,startTimeUnixNano:c(o),endTimeUnixNano:c(u),attributes:{"gen_ai.operation.name":`invoke_agent`,"session.id":e},status:b?{code:y,message:b}:{code:y}};return a&&_.length>0&&(S.attributes[`gen_ai.prompt`]=_.join(`
2
+ `)),f.unshift(S),{traceId:r,spans:f}}function d(e){return typeof e==`string`?{stringValue:e}:typeof e==`boolean`?{boolValue:e}:typeof e==`number`?Number.isInteger(e)?{intValue:String(e)}:{doubleValue:e}:{arrayValue:{values:e.map(e=>({stringValue:e}))}}}function f(e){return Object.entries(e).map(([e,t])=>({key:e,value:d(t)}))}function p(e,t){let n={traceId:e,spanId:t.spanId,name:t.name,kind:t.kind,startTimeUnixNano:t.startTimeUnixNano,endTimeUnixNano:t.endTimeUnixNano,attributes:f(t.attributes),status:t.status.message?{code:t.status.code,message:t.status.message}:{code:t.status.code}};return t.parentSpanId&&(n.parentSpanId=t.parentSpanId),n}function m(e,t={}){return{resourceSpans:[{resource:{attributes:f({"service.name":`otto`,...t.resourceAttributes})},scopeSpans:[{scope:{name:t.scopeName??`@x-otto/devtools/otel`,...t.scopeVersion?{version:t.scopeVersion}:{}},spans:e.spans.map(t=>p(e.traceId,t))}]}]}}const h=t(`@x-otto/devtools:otel`);var g=class{endpoint;headers;fetchImpl;timeoutMs;constructor(e){this.endpoint=e.endpoint,this.headers=e.headers??{},this.fetchImpl=e.fetch??globalThis.fetch,this.timeoutMs=e.timeoutMs??1e4}async export(e){try{let t=await this.fetchImpl(this.endpoint,{method:`POST`,headers:{"content-type":`application/json`,...this.headers},body:JSON.stringify(e),signal:AbortSignal.timeout(this.timeoutMs)});if(!t.ok){let e=await t.text().catch(()=>``);return h.warn(`OTLP export rejected: %d %s`,t.status,e),{ok:!1,status:t.status,error:e}}return{ok:!0,status:t.status}}catch(e){let t=e instanceof Error?e.message:String(e);return h.warn(`OTLP export failed: %s`,t),{ok:!1,error:t}}}};async function _(e,t,n,r={}){let i=u(e,t,r);if(i.spans.length===0)return{ok:!0};let a=m(i,r);return n.export(a)}function v(e=process.env){let t=e.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,n=e.OTEL_EXPORTER_OTLP_ENDPOINT,r=t||(n?`${n.replace(/\/$/,``)}/v1/traces`:void 0);if(r)return new g({endpoint:r,headers:y(e.OTEL_EXPORTER_OTLP_HEADERS)})}function y(e){if(!e)return{};let t={};for(let n of e.split(`,`)){let e=n.indexOf(`=`);e>0&&(t[n.slice(0,e).trim()]=n.slice(e+1).trim())}return t}export{g as OtlpHttpExporter,r as SPAN_KIND_CLIENT,n as SPAN_KIND_INTERNAL,o as STATUS_ERROR,a as STATUS_OK,i as STATUS_UNSET,m as buildOtlpTracesPayload,_ as exportSessionTrace,v as otlpExporterFromEnv,u as traceToGenAiSpans};
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/genai-spans.ts","../src/otlp.ts","../src/exporter.ts"],"sourcesContent":["import { createHash } from 'node:crypto'\nimport type { TraceEvent } from '@x-otto/agent'\n\nexport const SPAN_KIND_INTERNAL = 1\nexport const SPAN_KIND_CLIENT = 3\nexport const STATUS_UNSET = 0\nexport const STATUS_OK = 1\nexport const STATUS_ERROR = 2\n\nexport type SpanAttributeValue = string | number | boolean | string[]\n\nexport interface GenAiSpan {\n spanId: string\n parentSpanId?: string\n name: string\n kind: number\n startTimeUnixNano: string\n endTimeUnixNano: string\n attributes: Record<string, SpanAttributeValue>\n status: { code: number; message?: string }\n}\n\nexport interface GenAiTrace {\n traceId: string\n spans: GenAiSpan[]\n}\n\nexport interface GenAiExportOptions {\n /** 默认 false:不导出 prompt/响应/工具内容(防泄漏)。仅 true 时附内容属性。 */\n includeContent?: boolean\n}\n\nfunction hashHex(input: string, bytes: number): string {\n return createHash('sha256')\n .update(input)\n .digest('hex')\n .slice(0, bytes * 2)\n}\n\nfunction msToNano(ms: number): string {\n return (BigInt(Math.round(ms)) * 1_000_000n).toString()\n}\n\nfunction textOf(content: unknown): string {\n if (!Array.isArray(content)) {\n return ''\n }\n return content\n .filter((c): c is { type: 'text'; text: string } => {\n return !!c && typeof c === 'object' && (c as { type?: unknown }).type === 'text'\n })\n .map((c) => c.text)\n .join('')\n}\n\ninterface TurnMeta {\n model?: string\n system?: string\n completion?: string\n}\n\n/**\n * 把一个会话的 trace 事件序列映射为 GenAI span 树。\n *\n * traceId / spanId 由 sessionId(+ turn / toolCallId)确定性派生(sha256 截断)——同一 trace 重复导出\n * 得到稳定 id,便于幂等再导出与测试断言。tool span 归属\"请求它的那个 chat turn\"(按事件次序就近父挂,\n * 不依赖 turn 编号偏移)。未闭合的 span(abort)以会话末尾时刻收尾。\n */\nexport function traceToGenAiSpans(\n sessionId: string,\n events: TraceEvent[],\n options: GenAiExportOptions = {},\n): GenAiTrace {\n const traceId = hashHex(`trace:${sessionId}`, 16)\n const spanId = (key: string): string => hashHex(`span:${sessionId}:${key}`, 8)\n const includeContent = options.includeContent ?? false\n\n if (events.length === 0) {\n return { traceId, spans: [] }\n }\n\n const rootStart = Math.min(...events.map((e) => e.ts))\n const rootEnd = Math.max(...events.map((e) => e.ts))\n const rootId = spanId('root')\n\n const spans: GenAiSpan[] = []\n const turnSpans = new Map<number, GenAiSpan>()\n const toolSpans = new Map<string, GenAiSpan>()\n const turnMeta = new Map<number, TurnMeta>()\n const open = new Set<GenAiSpan>()\n const promptTexts: string[] = []\n let lastTurnSpanId = rootId\n let rootStatus = STATUS_UNSET\n let rootMessage: string | undefined\n\n const payloadOf = (e: TraceEvent): Record<string, unknown> =>\n (e.payload ?? {}) as Record<string, unknown>\n\n for (const e of events) {\n const startNano = msToNano(e.ts)\n\n if (e.kind === 'lifecycle') {\n if (e.node === 'prompt.before') {\n if (includeContent) {\n const text = payloadOf(e).text\n if (typeof text === 'string') {\n promptTexts.push(text)\n }\n }\n } else if (e.node === 'turn.start' && e.turn !== undefined) {\n const sp: GenAiSpan = {\n spanId: spanId(`turn:${e.turn}`),\n parentSpanId: rootId,\n name: 'chat',\n kind: SPAN_KIND_CLIENT,\n startTimeUnixNano: startNano,\n endTimeUnixNano: startNano,\n attributes: { 'gen_ai.operation.name': 'chat' },\n status: { code: STATUS_UNSET },\n }\n turnSpans.set(e.turn, sp)\n spans.push(sp)\n open.add(sp)\n lastTurnSpanId = sp.spanId\n } else if (e.node === 'turn.end' && e.turn !== undefined) {\n const sp = turnSpans.get(e.turn)\n if (sp) {\n sp.endTimeUnixNano = msToNano(e.ts)\n open.delete(sp)\n const payload = payloadOf(e)\n const usage = payload.usage as { inputTokens?: number; outputTokens?: number } | undefined\n if (usage) {\n if (typeof usage.inputTokens === 'number') {\n sp.attributes['gen_ai.usage.input_tokens'] = usage.inputTokens\n }\n if (typeof usage.outputTokens === 'number') {\n sp.attributes['gen_ai.usage.output_tokens'] = usage.outputTokens\n }\n }\n if (typeof payload.stopReason === 'string') {\n sp.attributes['gen_ai.response.finish_reasons'] = [payload.stopReason]\n }\n const meta = turnMeta.get(e.turn)\n if (meta?.model) {\n sp.attributes['gen_ai.request.model'] = meta.model\n sp.attributes['gen_ai.response.model'] = meta.model\n sp.name = `chat ${meta.model}`\n }\n if (meta?.system) {\n sp.attributes['gen_ai.system'] = meta.system\n }\n if (includeContent && meta?.completion) {\n sp.attributes['gen_ai.completion'] = meta.completion\n }\n }\n } else if (e.node === 'agent.error') {\n rootStatus = STATUS_ERROR\n const msg = payloadOf(e).message\n rootMessage = typeof msg === 'string' ? msg : 'agent error'\n }\n } else if (e.kind === 'stream' && e.node === 'stream.event' && e.turn !== undefined) {\n const ev = e.payload as { type?: string; message?: Record<string, unknown> } | undefined\n if (ev?.type === 'done' && ev.message) {\n const meta = turnMeta.get(e.turn) ?? {}\n if (typeof ev.message.model === 'string') {\n meta.model = ev.message.model\n }\n if (typeof ev.message.provider === 'string') {\n meta.system = ev.message.provider\n }\n if (includeContent) {\n meta.completion = textOf(ev.message.content)\n }\n turnMeta.set(e.turn, meta)\n }\n } else if (e.kind === 'tool') {\n const payload = payloadOf(e)\n const id = typeof payload.id === 'string' ? payload.id : ''\n if (e.node === 'tool.call.start') {\n const name = typeof payload.name === 'string' ? payload.name : 'tool'\n const sp: GenAiSpan = {\n spanId: spanId(`tool:${id}`),\n parentSpanId: lastTurnSpanId,\n name: `execute_tool ${name}`,\n kind: SPAN_KIND_INTERNAL,\n startTimeUnixNano: startNano,\n endTimeUnixNano: startNano,\n attributes: {\n 'gen_ai.operation.name': 'execute_tool',\n 'gen_ai.tool.name': name,\n 'gen_ai.tool.call.id': id,\n },\n status: { code: STATUS_UNSET },\n }\n if (includeContent) {\n sp.attributes['gen_ai.tool.arguments'] = JSON.stringify(payload.arguments ?? {})\n }\n toolSpans.set(id, sp)\n spans.push(sp)\n open.add(sp)\n } else if (e.node === 'tool.call.end') {\n const sp = toolSpans.get(id)\n if (sp) {\n sp.endTimeUnixNano = msToNano(e.ts)\n open.delete(sp)\n\n const resultObj = (payload.resultSummary ?? payload.result) as\n | { isError?: boolean; content?: unknown; contentPreview?: unknown[] }\n | undefined\n if (resultObj?.isError) {\n sp.status = { code: STATUS_ERROR }\n }\n if (includeContent && resultObj) {\n sp.attributes['gen_ai.tool.result'] = textOf(\n resultObj.content ?? resultObj.contentPreview,\n )\n }\n\n if (typeof payload.durationMs === 'number') {\n sp.attributes['gen_ai.tool.duration_ms'] = payload.durationMs\n }\n if (typeof payload.toolCallIndex === 'number') {\n sp.attributes['gen_ai.tool.call_index'] = payload.toolCallIndex\n }\n if (typeof payload.errorCategory === 'string') {\n sp.attributes['gen_ai.tool.error_category'] = payload.errorCategory\n }\n }\n }\n }\n }\n\n for (const sp of open) {\n sp.endTimeUnixNano = msToNano(rootEnd)\n }\n\n const root: GenAiSpan = {\n spanId: rootId,\n name: 'invoke_agent',\n kind: SPAN_KIND_INTERNAL,\n startTimeUnixNano: msToNano(rootStart),\n endTimeUnixNano: msToNano(rootEnd),\n attributes: { 'gen_ai.operation.name': 'invoke_agent', 'session.id': sessionId },\n status: rootMessage ? { code: rootStatus, message: rootMessage } : { code: rootStatus },\n }\n if (includeContent && promptTexts.length > 0) {\n root.attributes['gen_ai.prompt'] = promptTexts.join('\\n')\n }\n spans.unshift(root)\n\n return { traceId, spans }\n}\n","import type { GenAiSpan, GenAiTrace, SpanAttributeValue } from './genai-spans'\n\ntype OtlpAnyValue =\n | { stringValue: string }\n | { boolValue: boolean }\n | { intValue: string }\n | { doubleValue: number }\n | { arrayValue: { values: OtlpAnyValue[] } }\n\ninterface OtlpKeyValue {\n key: string\n value: OtlpAnyValue\n}\n\ninterface OtlpSpan {\n traceId: string\n spanId: string\n parentSpanId?: string\n name: string\n kind: number\n startTimeUnixNano: string\n endTimeUnixNano: string\n attributes: OtlpKeyValue[]\n status: { code: number; message?: string }\n}\n\nexport interface OtlpTracesPayload {\n resourceSpans: Array<{\n resource: { attributes: OtlpKeyValue[] }\n scopeSpans: Array<{\n scope: { name: string; version?: string }\n spans: OtlpSpan[]\n }>\n }>\n}\n\nexport interface OtlpBuildOptions {\n /** Resource 属性(service.name 等)。缺省 service.name=otto。 */\n resourceAttributes?: Record<string, SpanAttributeValue>\n scopeName?: string\n scopeVersion?: string\n}\n\nconst DEFAULT_SCOPE = '@x-otto/devtools/otel'\n\nfunction toAnyValue(value: SpanAttributeValue): OtlpAnyValue {\n if (typeof value === 'string') {\n return { stringValue: value }\n }\n if (typeof value === 'boolean') {\n return { boolValue: value }\n }\n if (typeof value === 'number') {\n return Number.isInteger(value) ? { intValue: String(value) } : { doubleValue: value }\n }\n return { arrayValue: { values: value.map((v) => ({ stringValue: v })) } }\n}\n\nfunction toKeyValues(attributes: Record<string, SpanAttributeValue>): OtlpKeyValue[] {\n return Object.entries(attributes).map(([key, value]) => ({ key, value: toAnyValue(value) }))\n}\n\nfunction toOtlpSpan(traceId: string, span: GenAiSpan): OtlpSpan {\n const out: OtlpSpan = {\n traceId,\n spanId: span.spanId,\n name: span.name,\n kind: span.kind,\n startTimeUnixNano: span.startTimeUnixNano,\n endTimeUnixNano: span.endTimeUnixNano,\n attributes: toKeyValues(span.attributes),\n status: span.status.message\n ? { code: span.status.code, message: span.status.message }\n : { code: span.status.code },\n }\n if (span.parentSpanId) {\n out.parentSpanId = span.parentSpanId\n }\n return out\n}\n\nexport function buildOtlpTracesPayload(\n trace: GenAiTrace,\n options: OtlpBuildOptions = {},\n): OtlpTracesPayload {\n const resourceAttributes = {\n 'service.name': 'otto',\n ...options.resourceAttributes,\n }\n return {\n resourceSpans: [\n {\n resource: { attributes: toKeyValues(resourceAttributes) },\n scopeSpans: [\n {\n scope: {\n name: options.scopeName ?? DEFAULT_SCOPE,\n ...(options.scopeVersion ? { version: options.scopeVersion } : {}),\n },\n spans: trace.spans.map((span) => toOtlpSpan(trace.traceId, span)),\n },\n ],\n },\n ],\n }\n}\n","import { createLogger } from '@x-otto/shared'\nimport { traceToGenAiSpans, type GenAiExportOptions } from './genai-spans'\nimport { buildOtlpTracesPayload, type OtlpBuildOptions, type OtlpTracesPayload } from './otlp'\nimport type { TraceEvent } from '@x-otto/agent'\n\nconst logger = createLogger('@x-otto/devtools:otel')\n\nexport interface OtelExportResult {\n ok: boolean\n status?: number\n error?: string\n}\n\nexport interface OtelSpanExporter {\n export(payload: OtlpTracesPayload): Promise<OtelExportResult>\n}\n\ntype FetchLike = (\n input: string,\n init: {\n method: string\n headers: Record<string, string>\n body: string\n signal?: AbortSignal\n },\n) => Promise<{ ok: boolean; status: number; text(): Promise<string> }>\n\nexport interface OtlpHttpExporterOptions {\n /** collector 的 traces 端点,如 http://localhost:4318/v1/traces。 */\n endpoint: string\n /** 附加请求头(鉴权等),如 { Authorization: 'Bearer ...' }。 */\n headers?: Record<string, string>\n /** 注入 fetch(缺省用全局 fetch)。测试可注入假实现。 */\n fetch?: FetchLike\n /** 请求超时(ms),缺省 10s。 */\n timeoutMs?: number\n}\n\nexport class OtlpHttpExporter implements OtelSpanExporter {\n private readonly endpoint: string\n private readonly headers: Record<string, string>\n private readonly fetchImpl: FetchLike\n private readonly timeoutMs: number\n\n constructor(options: OtlpHttpExporterOptions) {\n this.endpoint = options.endpoint\n this.headers = options.headers ?? {}\n this.fetchImpl = options.fetch ?? (globalThis.fetch as unknown as FetchLike)\n this.timeoutMs = options.timeoutMs ?? 10_000\n }\n\n async export(payload: OtlpTracesPayload): Promise<OtelExportResult> {\n try {\n const response = await this.fetchImpl(this.endpoint, {\n method: 'POST',\n headers: { 'content-type': 'application/json', ...this.headers },\n body: JSON.stringify(payload),\n signal: AbortSignal.timeout(this.timeoutMs),\n })\n if (!response.ok) {\n const text = await response.text().catch(() => '')\n logger.warn('OTLP export rejected: %d %s', response.status, text)\n return { ok: false, status: response.status, error: text }\n }\n return { ok: true, status: response.status }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n logger.warn('OTLP export failed: %s', message)\n return { ok: false, error: message }\n }\n }\n}\n\nexport interface ExportSessionTraceOptions extends GenAiExportOptions, OtlpBuildOptions {}\n\n/**\n * 把一个会话的 trace 事件导出为 OTLP span(opt-in 入口)。宿主仅在持有 exporter 时调用。\n * 空 trace 直接短路成功(不发空负载)。\n */\nexport async function exportSessionTrace(\n sessionId: string,\n events: TraceEvent[],\n exporter: OtelSpanExporter,\n options: ExportSessionTraceOptions = {},\n): Promise<OtelExportResult> {\n const trace = traceToGenAiSpans(sessionId, events, options)\n if (trace.spans.length === 0) {\n return { ok: true }\n }\n const payload = buildOtlpTracesPayload(trace, options)\n return exporter.export(payload)\n}\n\n/**\n * 从环境变量构造 opt-in 导出器:仅当 `OTEL_EXPORTER_OTLP_ENDPOINT`(或 traces 专用\n * `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`)已设置时返回 exporter,否则 undefined(= 不导出)。\n * 标准 OTel 端点约定:base endpoint 需补 `/v1/traces` 路径;traces 专用端点直接用。\n */\nexport function otlpExporterFromEnv(\n env: Record<string, string | undefined> = process.env,\n): OtlpHttpExporter | undefined {\n const tracesEndpoint = env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT\n const baseEndpoint = env.OTEL_EXPORTER_OTLP_ENDPOINT\n const endpoint = tracesEndpoint\n ? tracesEndpoint\n : baseEndpoint\n ? `${baseEndpoint.replace(/\\/$/, '')}/v1/traces`\n : undefined\n if (!endpoint) {\n return undefined\n }\n const headers = parseOtlpHeaders(env.OTEL_EXPORTER_OTLP_HEADERS)\n return new OtlpHttpExporter({ endpoint, headers })\n}\n\nfunction parseOtlpHeaders(raw: string | undefined): Record<string, string> {\n if (!raw) {\n return {}\n }\n const headers: Record<string, string> = {}\n for (const pair of raw.split(',')) {\n const idx = pair.indexOf('=')\n if (idx > 0) {\n headers[pair.slice(0, idx).trim()] = pair.slice(idx + 1).trim()\n }\n }\n return headers\n}\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@x-otto/otel",
3
+ "version": "0.0.1-alpha.0",
4
+ "files": [
5
+ "dist"
6
+ ],
7
+ "type": "module",
8
+ "main": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js"
14
+ }
15
+ },
16
+ "dependencies": {
17
+ "@x-otto/agent": "0.0.1-alpha.0",
18
+ "@x-otto/shared": "0.1.0-alpha.1"
19
+ },
20
+ "devDependencies": {
21
+ "typescript": "6.0.2",
22
+ "vitest": "^3.2.4"
23
+ },
24
+ "private": false,
25
+ "publishConfig": {
26
+ "access": "public",
27
+ "registry": "https://registry.npmjs.org",
28
+ "tag": "alpha"
29
+ },
30
+ "scripts": {
31
+ "build": "tsdown",
32
+ "typecheck": "tsc --noEmit",
33
+ "test": "vitest run",
34
+ "clean": "rm -rf dist"
35
+ }
36
+ }