@x-otto/otel 0.0.1-alpha.3 → 0.1.0-alpha.9

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
@@ -54,7 +54,7 @@ invoke_agent (root, spanKind=INTERNAL)
54
54
  ```
55
55
 
56
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)
57
+ - Tool spans parented to the chat turn that requested them (event `turn` field, single source via agent's `turnToolTopology` — RFC-375)
58
58
  - Unclosed spans (abort) closed at session end timestamp
59
59
  - `includeContent` option (default `false` — privacy-first, omits prompt/response/tool content)
60
60
 
@@ -63,8 +63,8 @@ invoke_agent (root, spanKind=INTERNAL)
63
63
  Pure function: `(trace: GenAiTrace, options?) → OtlpTracesPayload`
64
64
 
65
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
66
+ - Default `service.name=otto`, scope `@x-otto/otel` (version read from package.json — single source)
67
+ - Customizable resource attributes and scope name/version
68
68
 
69
69
  ### OtlpHttpExporter (`exporter.ts`)
70
70
 
@@ -104,7 +104,7 @@ tests/ # Test files
104
104
 
105
105
  ## Dependencies
106
106
 
107
- - Internal: `@x-otto/agent` (TraceEvent), `@x-otto/shared` (logger)
107
+ - Internal: `@x-otto/agent` (TraceEvent types + `turnToolTopology` for tool→turn topology), `@x-otto/shared` (logger)
108
108
  - External: none (OTLP export uses global fetch — no OTel SDK required)
109
109
 
110
110
  ## Testing
@@ -117,6 +117,5 @@ pnpm vitest run packages/otel/tests/
117
117
 
118
118
  ## Related
119
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)
120
+ - [OpenTelemetry GenAI Semantic Conventions](https://github.com/open-telemetry/semantic-conventions-genai)
122
121
  - [OTLP HTTP Protocol](https://opentelemetry.io/docs/specs/otlp/)
package/dist/index.d.ts CHANGED
@@ -1,5 +1,33 @@
1
1
  import { TraceEvent } from "@x-otto/agent";
2
2
 
3
+ //#region src/w3c-trace-context.d.ts
4
+ /**
5
+ * w3c-trace-context.ts —— RFC review D9:W3C traceparent 解析(最小实现)。
6
+ *
7
+ * traceparent 格式(W3C Trace Context spec):
8
+ * `version-traceId-parentId-flags`,如 `00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01`
9
+ *
10
+ * 本模块只做「解析 → 结构化」:traceId(32 hex)/ parentSpanId(16 hex)/ flags。
11
+ * 不实现 tracestate(当前消费方仅需 trace 关联,不需要 vendor 扩展传播)。
12
+ * 纯函数、无 IO。
13
+ */
14
+ interface W3CTraceContext {
15
+ /** 32 位 hex traceId(外部调用方的 trace 根)。 */
16
+ traceId: string;
17
+ /** 16 位 hex 父 spanId(otto root span 挂载点)。 */
18
+ parentSpanId: string;
19
+ /** 采样 flags(01 = sampled);本模块只透传不决策。 */
20
+ flags: string;
21
+ }
22
+ /**
23
+ * 解析 W3C traceparent 头。非法/缺失返回 undefined(调用方回退确定性派生)。
24
+ * 校验:version 00(当前唯一规范版本)、traceId 32 hex 且非全零、parentSpanId 16 hex
25
+ * 且非全零、flags 2 hex。
26
+ */
27
+ declare function parseTraceParent(header: string | undefined | null): W3CTraceContext | undefined;
28
+ /** 从环境变量(W3C traceparent 常用注入名)解析;缺省 undefined。 */
29
+ declare function traceParentFromEnv(env?: NodeJS.ProcessEnv): W3CTraceContext | undefined;
30
+ //#endregion
3
31
  //#region src/genai-spans.d.ts
4
32
  declare const SPAN_KIND_INTERNAL = 1;
5
33
  declare const SPAN_KIND_CLIENT = 3;
@@ -27,13 +55,24 @@ interface GenAiTrace {
27
55
  interface GenAiExportOptions {
28
56
  /** 默认 false:不导出 prompt/响应/工具内容(防泄漏)。仅 true 时附内容属性。 */
29
57
  includeContent?: boolean;
58
+ /**
59
+ * RFC review D9:W3C trace context 传播(可选覆盖)——宿主从上游请求头/环境
60
+ * (`traceparent`)解析外部 trace 上下文并传入;提供时 otto 的 traceId 使用外部
61
+ * traceId、root span 挂到外部 parentSpanId 下(链路关联进调用方的分布式 trace)。
62
+ * 缺省(未提供)保持确定性派生 traceId(sha256(sessionId)),测试/幂等导出语义不变。
63
+ */
64
+ traceParent?: W3CTraceContext;
30
65
  }
31
66
  /**
32
67
  * 把一个会话的 trace 事件序列映射为 GenAI span 树。
33
68
  *
34
69
  * traceId / spanId 由 sessionId(+ turn / toolCallId)确定性派生(sha256 截断)——同一 trace 重复导出
35
- * 得到稳定 id,便于幂等再导出与测试断言。tool span 归属"请求它的那个 chat turn"(按事件次序就近父挂,
36
- * 不依赖 turn 编号偏移)。未闭合的 span(abort)以会话末尾时刻收尾。
70
+ * 得到稳定 id,便于幂等再导出与测试断言。tool span 归属消费 agent 的拓扑单源(turnToolTopology,
71
+ * RFC-375 M1:事件自身 turn 字段的因果语义),不再本地重建第二套推导。
72
+ *
73
+ * 输入契约:events 需按时间(ts)升序——root 边界与未闭合 span 收尾依赖有序性。
74
+ * 属性键遵循 OpenTelemetry GenAI 语义约定(semantic-conventions-genai);内容类属性为 Opt-In
75
+ * (includeContent),spans 上以 JSON string 形态记录。
37
76
  */
38
77
  declare function traceToGenAiSpans(sessionId: string, events: TraceEvent[], options?: GenAiExportOptions): GenAiTrace;
39
78
  //#endregion
@@ -99,6 +138,11 @@ interface OtelExportResult {
99
138
  }
100
139
  interface OtelSpanExporter {
101
140
  export(payload: OtlpTracesPayload): Promise<OtelExportResult>;
141
+ /**
142
+ * 排空并静默:保证此前已发出的导出请求完成。单发导出器实现为 no-op
143
+ * (export() 本身 await 完成,无排队中的负载);未来批量/缓冲形态在此排空队列。
144
+ */
145
+ shutdown(): Promise<void>;
102
146
  }
103
147
  type FetchLike = (input: string, init: {
104
148
  method: string;
@@ -127,6 +171,7 @@ declare class OtlpHttpExporter implements OtelSpanExporter {
127
171
  private readonly timeoutMs;
128
172
  constructor(options: OtlpHttpExporterOptions);
129
173
  export(payload: OtlpTracesPayload): Promise<OtelExportResult>;
174
+ shutdown(): Promise<void>;
130
175
  }
131
176
  interface ExportSessionTraceOptions extends GenAiExportOptions, OtlpBuildOptions {}
132
177
  /**
@@ -141,5 +186,5 @@ declare function exportSessionTrace(sessionId: string, events: TraceEvent[], exp
141
186
  */
142
187
  declare function otlpExporterFromEnv(env?: Record<string, string | undefined>): OtlpHttpExporter | undefined;
143
188
  //#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 };
189
+ 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, type W3CTraceContext, buildOtlpTracesPayload, exportSessionTrace, otlpExporterFromEnv, parseTraceParent, traceParentFromEnv, traceToGenAiSpans };
145
190
  //# sourceMappingURL=index.d.ts.map
@@ -1 +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"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/w3c-trace-context.ts","../src/genai-spans.ts","../src/otlp.ts","../src/exporter.ts"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,3 +1,2 @@
1
- import{createHash as e}from"node:crypto";import{turnToolTopology as t}from"@x-otto/agent";import{createLogger as n}from"@x-otto/shared";const r=1,i=3,a=0,o=1,s=2;function c(t,n){return e(`sha256`).update(t).digest(`hex`).slice(0,n*2)}function l(e){return(BigInt(Math.round(e))*1000000n).toString()}function u(e){return Array.isArray(e)?e.filter(e=>!!e&&typeof e==`object`&&e.type===`text`).map(e=>e.text).join(``):``}function d(e,n,r={}){let i=c(`trace:${e}`,16),a=t=>c(`span:${e}:${t}`,8),o=r.includeContent??!1;if(n.length===0)return{traceId:i,spans:[]};let s=Math.min(...n.map(e=>e.ts)),d=Math.max(...n.map(e=>e.ts)),f=a(`root`),p=t(n),m=[],h=new Map,g=new Map,_=new Map,v=new Set,y=[],b=f,x=0,S,C=e=>e.payload??{};for(let e of n){let t=l(e.ts);if(e.kind===`lifecycle`){if(e.node===`prompt.before`){if(o){let t=C(e).text;typeof t==`string`&&y.push(t)}}else if(e.node===`turn.start`&&e.turn!==void 0){let n={spanId:a(`turn:${e.turn}`),parentSpanId:f,name:`chat`,kind:3,startTimeUnixNano:t,endTimeUnixNano:t,attributes:{"gen_ai.operation.name":`chat`},status:{code:0}};h.set(e.turn,n),m.push(n),v.add(n),b=n.spanId}else if(e.node===`turn.end`&&e.turn!==void 0){let t=h.get(e.turn);if(t){t.endTimeUnixNano=l(e.ts),v.delete(t);let n=C(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=_.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),o&&i?.completion&&(t.attributes[`gen_ai.completion`]=i.completion)}}else if(e.node===`agent.error`){x=2;let t=C(e).message;S=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=_.get(e.turn)??{};typeof t.message.model==`string`&&(n.model=t.message.model),typeof t.message.provider==`string`&&(n.system=t.message.provider),o&&(n.completion=u(t.message.content)),_.set(e.turn,n)}}else if(e.kind===`tool`){let n=C(e),r=typeof n.id==`string`?n.id:``;if(e.node===`tool.call.start`){let e=typeof n.name==`string`?n.name:`tool`,i=r?p.toolToTurn.get(r):void 0,s=i===void 0?b:a(`turn:${i}`),c={spanId:a(`tool:${r}`),parentSpanId:s,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}};o&&(c.attributes[`gen_ai.tool.arguments`]=JSON.stringify(n.arguments??{})),g.set(r,c),m.push(c),v.add(c)}else if(e.node===`tool.call.end`){let t=g.get(r);if(t){t.endTimeUnixNano=l(e.ts),v.delete(t);let r=n.resultSummary??n.result;r?.isError&&(t.status={code:2}),o&&r&&(t.attributes[`gen_ai.tool.result`]=u(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 v)e.endTimeUnixNano=l(d);let w={spanId:f,name:`invoke_agent`,kind:1,startTimeUnixNano:l(s),endTimeUnixNano:l(d),attributes:{"gen_ai.operation.name":`invoke_agent`,"session.id":e},status:S?{code:x,message:S}:{code:x}};return o&&y.length>0&&(w.attributes[`gen_ai.prompt`]=y.join(`
2
- `)),m.unshift(w),{traceId:i,spans:m}}function f(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 p(e){return Object.entries(e).map(([e,t])=>({key:e,value:f(t)}))}function m(e,t){let n={traceId:e,spanId:t.spanId,name:t.name,kind:t.kind,startTimeUnixNano:t.startTimeUnixNano,endTimeUnixNano:t.endTimeUnixNano,attributes:p(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 h(e,t={}){return{resourceSpans:[{resource:{attributes:p({"service.name":`otto`,...t.resourceAttributes})},scopeSpans:[{scope:{name:t.scopeName??`@x-otto/devtools/otel`,...t.scopeVersion?{version:t.scopeVersion}:{}},spans:e.spans.map(t=>m(e.traceId,t))}]}]}}const g=n(`@x-otto/devtools:otel`);var _=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 g.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 g.warn(`OTLP export failed: %s`,t),{ok:!1,error:t}}}};async function v(e,t,n,r={}){let i=d(e,t,r);if(i.spans.length===0)return{ok:!0};let a=h(i,r);return n.export(a)}function y(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 _({endpoint:r,headers:b(e.OTEL_EXPORTER_OTLP_HEADERS)})}function b(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{_ as OtlpHttpExporter,i as SPAN_KIND_CLIENT,r as SPAN_KIND_INTERNAL,s as STATUS_ERROR,o as STATUS_OK,a as STATUS_UNSET,h as buildOtlpTracesPayload,v as exportSessionTrace,y as otlpExporterFromEnv,d as traceToGenAiSpans};
1
+ import{createRequire as e}from"node:module";import{createHash as t}from"node:crypto";import{turnToolTopology as n}from"@x-otto/agent";import{createLogger as r}from"@x-otto/shared";const i=1,a=3,o=0,s=1,c=2;function l(e,n){return t(`sha256`).update(e).digest(`hex`).slice(0,n*2)}function u(e){return(BigInt(Math.round(e))*1000000n).toString()}function d(e){return Array.isArray(e)?e.filter(e=>!!e&&typeof e==`object`&&e.type===`text`).map(e=>e.text).join(``):``}function f(e,t,r={}){let i=l(`trace:${e}`,16),a=t=>l(`span:${e}:${t}`,8),o=r.includeContent??!1,s=r.traceParent,c=s?.traceId??i,f=s?.parentSpanId;if(t.length===0)return{traceId:c,spans:[]};let p=Math.min(...t.map(e=>e.ts)),m=Math.max(...t.map(e=>e.ts)),h=a(`root`),g=n(t),_=[],v=new Map,y=new Map,b=new Map,x=new Set,S=[],C=h,w=0,T,E=e=>e.payload??{};for(let e of t){let t=u(e.ts);if(e.kind===`lifecycle`){if(e.node===`prompt.before`){if(o){let t=E(e).text;typeof t==`string`&&S.push(t)}}else if(e.node===`turn.start`&&e.turn!==void 0){let n={spanId:a(`turn:${e.turn}`),parentSpanId:h,name:`chat`,kind:3,startTimeUnixNano:t,endTimeUnixNano:t,attributes:{"gen_ai.operation.name":`chat`},status:{code:0}};v.set(e.turn,n),_.push(n),x.add(n),C=n.spanId}else if(e.node===`turn.end`&&e.turn!==void 0){let t=v.get(e.turn);if(t){t.endTimeUnixNano=u(e.ts),x.delete(t);let n=E(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=b.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.provider.name`]=i.system),o&&i?.completion&&(t.attributes[`gen_ai.output.messages`]=JSON.stringify([{role:`assistant`,parts:[{type:`text`,content:i.completion}]}]))}}else if(e.node===`agent.error`){w=2;let t=E(e).message;T=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=b.get(e.turn)??{};typeof t.message.model==`string`&&(n.model=t.message.model),typeof t.message.provider==`string`&&(n.system=t.message.provider),o&&(n.completion=d(t.message.content)),b.set(e.turn,n)}}else if(e.kind===`tool`){let n=E(e),r=typeof n.id==`string`?n.id:``;if(e.node===`tool.call.start`){let e=typeof n.name==`string`?n.name:`tool`,i=r?g.toolToTurn.get(r):void 0,s=i===void 0?C:a(`turn:${i}`),c={spanId:a(`tool:${r}`),parentSpanId:s,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}};o&&(c.attributes[`gen_ai.tool.call.arguments`]=JSON.stringify(n.arguments??{})),y.set(r,c),_.push(c),x.add(c)}else if(e.node===`tool.call.end`){let t=y.get(r);if(t){t.endTimeUnixNano=u(e.ts),x.delete(t);let r=n.resultSummary??n.result;r?.isError&&(t.status={code:2},t.attributes[`error.type`]=typeof n.errorCategory==`string`?n.errorCategory:`_OTHER`),o&&r&&(t.attributes[`gen_ai.tool.call.result`]=d(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 x)e.endTimeUnixNano=u(m);let D={"gen_ai.operation.name":`invoke_agent`,"session.id":e};T&&(D[`error.type`]=`_OTHER`);let O={spanId:h,name:`invoke_agent`,kind:1,startTimeUnixNano:u(p),endTimeUnixNano:u(m),attributes:D,status:T?{code:w,message:T}:{code:w}};return f&&(O.parentSpanId=f),o&&S.length>0&&(O.attributes[`gen_ai.input.messages`]=JSON.stringify(S.map(e=>({role:`user`,parts:[{type:`text`,content:e}]})))),_.unshift(O),{traceId:c,spans:_}}const{version:p}=e(import.meta.url)(`../package.json`);function m(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 h(e){return Object.entries(e).map(([e,t])=>({key:e,value:m(t)}))}function g(e,t){let n={traceId:e,spanId:t.spanId,name:t.name,kind:t.kind,startTimeUnixNano:t.startTimeUnixNano,endTimeUnixNano:t.endTimeUnixNano,attributes:h(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 _(e,t={}){return{resourceSpans:[{resource:{attributes:h({"service.name":`otto`,...t.resourceAttributes})},scopeSpans:[{scope:{name:t.scopeName??`@x-otto/otel`,version:t.scopeVersion??p},spans:e.spans.map(t=>g(e.traceId,t))}]}]}}const v=/^[0-9a-f]{32}$/i,y=/^[0-9a-f]{16}$/i;function b(e){if(!e)return;let t=e.trim().split(`-`);if(t.length!==4)return;let[n,r,i,a]=t;if(n===`00`&&!(!v.test(r)||/^0{32}$/i.test(r))&&!(!y.test(i)||/^0{16}$/i.test(i))&&/^[0-9a-f]{2}$/i.test(a))return{traceId:r.toLowerCase(),parentSpanId:i.toLowerCase(),flags:a}}function x(e=process.env){return b(e.TRACEPARENT??e.traceparent)}const S=r(`@x-otto/otel`);var C=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 S.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 S.warn(`OTLP export failed: %s`,t),{ok:!1,error:t}}}async shutdown(){}};async function w(e,t,n,r={}){let i=f(e,t,r);if(i.spans.length===0)return{ok:!0};let a=_(i,r);return n.export(a)}function T(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;let i;try{i=new URL(r)}catch{S.warn(`OTLP endpoint 非法,忽略: %s`,r);return}if(i.protocol!==`http:`&&i.protocol!==`https:`){S.warn(`OTLP endpoint 必须为 http(s),忽略: %s`,r);return}return new C({endpoint:r,headers:E(e.OTEL_EXPORTER_OTLP_HEADERS)})}function E(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{C as OtlpHttpExporter,a as SPAN_KIND_CLIENT,i as SPAN_KIND_INTERNAL,c as STATUS_ERROR,s as STATUS_OK,o as STATUS_UNSET,_ as buildOtlpTracesPayload,w as exportSessionTrace,T as otlpExporterFromEnv,b as parseTraceParent,x as traceParentFromEnv,f as traceToGenAiSpans};
3
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +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 { turnToolTopology, 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 // RFC-375 M1:tool→turn 归属消费 agent 的拓扑单源(事件自身 turn 字段的因果语义),\n // 不再用「就近父挂」(lastTurnSpanId)——那是与 UI 视图(trace-view 按 turn 字段\n // 分组)分叉的第二套推导。找不到对应 turn span(重放过滤/乱序)时回退 lastTurnSpanId,\n // 保行为等价不引入新故障模式。\n const topology = turnToolTopology(events)\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 // RFC-375 M1:归属 turn = 拓扑单源(事件 turn 字段)。turn span 的 ID 是\n // spanId(`turn:${n}`) 确定性派生——tool.call.start 先于同 turn 的\n // turn.start 到达时也可直接前向引用(parentSpanId 只是字符串,不要求\n // span 已创建)。turn 字段缺失时才回退 lastTurnSpanId(就近挂靠)。\n const ownerTurn = id ? topology.toolToTurn.get(id) : undefined\n const parentSpanId =\n ownerTurn !== undefined ? spanId(`turn:${ownerTurn}`) : lastTurnSpanId\n const sp: GenAiSpan = {\n spanId: spanId(`tool:${id}`),\n parentSpanId,\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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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/genai-spans.ts","../src/otlp.ts","../src/w3c-trace-context.ts","../src/exporter.ts"],"sourcesContent":["import { createHash } from 'node:crypto'\nimport { turnToolTopology, 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 * RFC review D9:W3C trace context 传播(可选覆盖)——宿主从上游请求头/环境\n * (`traceparent`)解析外部 trace 上下文并传入;提供时 otto 的 traceId 使用外部\n * traceId、root span 挂到外部 parentSpanId 下(链路关联进调用方的分布式 trace)。\n * 缺省(未提供)保持确定性派生 traceId(sha256(sessionId)),测试/幂等导出语义不变。\n */\n traceParent?: import('./w3c-trace-context').W3CTraceContext\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 归属消费 agent 的拓扑单源(turnToolTopology,\n * RFC-375 M1:事件自身 turn 字段的因果语义),不再本地重建第二套推导。\n *\n * 输入契约:events 需按时间(ts)升序——root 边界与未闭合 span 收尾依赖有序性。\n * 属性键遵循 OpenTelemetry GenAI 语义约定(semantic-conventions-genai);内容类属性为 Opt-In\n * (includeContent),spans 上以 JSON string 形态记录。\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 // RFC review D9:外部 W3C trace context 覆盖——traceId 采用外部值、root span 挂\n // 外部 parentSpanId(链路关联进调用方分布式 trace)。缺省保持确定性派生。\n const external = options.traceParent\n const effectiveTraceId = external?.traceId ?? traceId\n const externalParentSpanId = external?.parentSpanId\n\n if (events.length === 0) {\n return { traceId: effectiveTraceId, 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 // RFC-375 M1:tool→turn 归属消费 agent 的拓扑单源(事件自身 turn 字段的因果语义),\n // 不再用「就近父挂」(lastTurnSpanId)——那是与 UI 视图(trace-view 按 turn 字段\n // 分组)分叉的第二套推导。找不到对应 turn span(重放过滤/乱序)时回退 lastTurnSpanId,\n // 保行为等价不引入新故障模式。\n const topology = turnToolTopology(events)\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.provider.name'] = meta.system\n }\n if (includeContent && meta?.completion) {\n sp.attributes['gen_ai.output.messages'] = JSON.stringify([\n { role: 'assistant', parts: [{ type: 'text', content: meta.completion }] },\n ])\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 // RFC-375 M1:归属 turn = 拓扑单源(事件 turn 字段)。turn span 的 ID 是\n // spanId(`turn:${n}`) 确定性派生——tool.call.start 先于同 turn 的\n // turn.start 到达时也可直接前向引用(parentSpanId 只是字符串,不要求\n // span 已创建)。turn 字段缺失时才回退 lastTurnSpanId(就近挂靠)。\n const ownerTurn = id ? topology.toolToTurn.get(id) : undefined\n const parentSpanId =\n ownerTurn !== undefined ? spanId(`turn:${ownerTurn}`) : lastTurnSpanId\n const sp: GenAiSpan = {\n spanId: spanId(`tool:${id}`),\n parentSpanId,\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.call.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 sp.attributes['error.type'] =\n typeof payload.errorCategory === 'string' ? payload.errorCategory : '_OTHER'\n }\n if (includeContent && resultObj) {\n sp.attributes['gen_ai.tool.call.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 rootAttributes: Record<string, SpanAttributeValue> = {\n 'gen_ai.operation.name': 'invoke_agent',\n 'session.id': sessionId,\n }\n if (rootMessage) {\n rootAttributes['error.type'] = '_OTHER'\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: rootAttributes,\n status: rootMessage ? { code: rootStatus, message: rootMessage } : { code: rootStatus },\n }\n // RFC review D9:外部 trace context 存在时 root span 挂到外部父 span 下。\n if (externalParentSpanId) {\n root.parentSpanId = externalParentSpanId\n }\n if (includeContent && promptTexts.length > 0) {\n root.attributes['gen_ai.input.messages'] = JSON.stringify(\n promptTexts.map((text) => ({ role: 'user', parts: [{ type: 'text', content: text }] })),\n )\n }\n spans.unshift(root)\n\n return { traceId: effectiveTraceId, spans }\n}\n","import { createRequire } from 'node:module'\nimport 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\n// Instrumentation scope 名与版本:名字 = 包名(独立包后不再沿用 devtools 历史名);\n// 版本从本包 package.json 读取(单源,避免手写漂移——与 dsh-session-telemetry-otel 同款做法)。\nconst SCOPE_NAME = '@x-otto/otel'\nconst { version: SCOPE_VERSION } = createRequire(import.meta.url)('../package.json') as {\n version: string\n}\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 ?? SCOPE_NAME,\n version: options.scopeVersion ?? SCOPE_VERSION,\n },\n spans: trace.spans.map((span) => toOtlpSpan(trace.traceId, span)),\n },\n ],\n },\n ],\n }\n}\n","/**\n * w3c-trace-context.ts —— RFC review D9:W3C traceparent 解析(最小实现)。\n *\n * traceparent 格式(W3C Trace Context spec):\n * `version-traceId-parentId-flags`,如 `00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01`\n *\n * 本模块只做「解析 → 结构化」:traceId(32 hex)/ parentSpanId(16 hex)/ flags。\n * 不实现 tracestate(当前消费方仅需 trace 关联,不需要 vendor 扩展传播)。\n * 纯函数、无 IO。\n */\n\nexport interface W3CTraceContext {\n /** 32 位 hex traceId(外部调用方的 trace 根)。 */\n traceId: string\n /** 16 位 hex 父 spanId(otto root span 挂载点)。 */\n parentSpanId: string\n /** 采样 flags(01 = sampled);本模块只透传不决策。 */\n flags: string\n}\n\nconst HEX32 = /^[0-9a-f]{32}$/i\nconst HEX16 = /^[0-9a-f]{16}$/i\n\n/**\n * 解析 W3C traceparent 头。非法/缺失返回 undefined(调用方回退确定性派生)。\n * 校验:version 00(当前唯一规范版本)、traceId 32 hex 且非全零、parentSpanId 16 hex\n * 且非全零、flags 2 hex。\n */\nexport function parseTraceParent(header: string | undefined | null): W3CTraceContext | undefined {\n if (!header) return undefined\n const parts = header.trim().split('-')\n if (parts.length !== 4) return undefined\n const [version, traceId, parentSpanId, flags] = parts as [string, string, string, string]\n if (version !== '00') return undefined\n if (!HEX32.test(traceId) || /^0{32}$/i.test(traceId)) return undefined\n if (!HEX16.test(parentSpanId) || /^0{16}$/i.test(parentSpanId)) return undefined\n if (!/^[0-9a-f]{2}$/i.test(flags)) return undefined\n return { traceId: traceId.toLowerCase(), parentSpanId: parentSpanId.toLowerCase(), flags }\n}\n\n/** 从环境变量(W3C traceparent 常用注入名)解析;缺省 undefined。 */\nexport function traceParentFromEnv(env: NodeJS.ProcessEnv = process.env): W3CTraceContext | undefined {\n return parseTraceParent(env['TRACEPARENT'] ?? env['traceparent'])\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/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 * 排空并静默:保证此前已发出的导出请求完成。单发导出器实现为 no-op\n * (export() 本身 await 完成,无排队中的负载);未来批量/缓冲形态在此排空队列。\n */\n shutdown(): Promise<void>\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 async shutdown(): Promise<void> {\n // 单发导出:export() 已 await 完成,无排队中的负载需要排空。\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 // 配置 fail-closed:非法/非 http(s) endpoint 在构造期拒绝(而不是运行时 POST 才暴露)。\n let parsed: URL\n try {\n parsed = new URL(endpoint)\n } catch {\n logger.warn('OTLP endpoint 非法,忽略: %s', endpoint)\n return undefined\n }\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n logger.warn('OTLP endpoint 必须为 http(s),忽略: %s', 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@x-otto/otel",
3
- "version": "0.0.1-alpha.3",
3
+ "version": "0.1.0-alpha.9",
4
4
  "files": [
5
5
  "dist"
6
6
  ],
@@ -14,8 +14,8 @@
14
14
  }
15
15
  },
16
16
  "dependencies": {
17
- "@x-otto/agent": "0.0.1-alpha.1",
18
- "@x-otto/shared": "0.1.0-alpha.5"
17
+ "@x-otto/agent": "0.1.0-alpha.9",
18
+ "@x-otto/shared": "0.1.0-alpha.9"
19
19
  },
20
20
  "devDependencies": {
21
21
  "typescript": "6.0.2",