@breadcrumb-sdk/core 0.0.2
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 +143 -0
- package/dist/index.cjs +319 -0
- package/dist/index.d.cts +39 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +296 -0
- package/dist/index.js.map +1 -0
- package/package.json +35 -0
package/README.md
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# @breadcrumb-sdk/core
|
|
2
|
+
|
|
3
|
+
Trace your AI agents and pipelines with Breadcrumb.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @breadcrumb-sdk/core
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick start
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { init } from "@breadcrumb-sdk/core";
|
|
15
|
+
|
|
16
|
+
const bc = init({
|
|
17
|
+
apiKey: "bc_...",
|
|
18
|
+
baseUrl: "https://your-breadcrumb-instance.com",
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const answer = await bc.trace("answer-question", async (root) => {
|
|
22
|
+
root.set({ input: "What is TypeScript?" });
|
|
23
|
+
|
|
24
|
+
const docs = await bc.span("retrieve", async (span) => {
|
|
25
|
+
span.set({ metadata: { source: "docs", top_k: 5 } });
|
|
26
|
+
return await fetchDocs(query);
|
|
27
|
+
}, { type: "retrieval" });
|
|
28
|
+
|
|
29
|
+
const result = await bc.span("generate", async (span) => {
|
|
30
|
+
const output = await callLlm(docs);
|
|
31
|
+
span.set({
|
|
32
|
+
input: docs.join("\n"),
|
|
33
|
+
output: output.text,
|
|
34
|
+
model: "claude-opus-4-6",
|
|
35
|
+
provider: "anthropic",
|
|
36
|
+
input_tokens: output.inputTokens,
|
|
37
|
+
output_tokens: output.outputTokens,
|
|
38
|
+
});
|
|
39
|
+
return output.text;
|
|
40
|
+
}, { type: "llm" });
|
|
41
|
+
|
|
42
|
+
root.set({ output: result });
|
|
43
|
+
return result;
|
|
44
|
+
});
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## API
|
|
48
|
+
|
|
49
|
+
### `init(options)`
|
|
50
|
+
|
|
51
|
+
Call once at startup. Returns a `bc` instance you use to create traces and spans.
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
const bc = init({
|
|
55
|
+
apiKey: string,
|
|
56
|
+
baseUrl: string,
|
|
57
|
+
batching?: false | {
|
|
58
|
+
flushInterval?: number, // ms between sends (default: 5000)
|
|
59
|
+
maxBatchSize?: number, // spans per send (default: 100)
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Set `batching: false` to send each span as it finishes. The default batches them. Either way, everything is flushed before the process exits.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
### `bc.trace(name, fn)`
|
|
69
|
+
|
|
70
|
+
Starts a new top-level trace. Everything you call inside `fn` that uses `bc.span()` will be nested under it in the UI.
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
await bc.trace("my-agent", async (span) => {
|
|
74
|
+
span.set({ input: userMessage });
|
|
75
|
+
// ... your agent logic
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Always creates a fresh trace — if you call `bc.trace()` inside another `bc.trace()`, you get two separate traces, not a nested one. Use `bc.span()` for nesting.
|
|
80
|
+
|
|
81
|
+
The span closes automatically when `fn` returns. If `fn` throws, the span is marked as failed and the error is rethrown.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
### `bc.span(name, fn, options?)`
|
|
86
|
+
|
|
87
|
+
Adds a step inside the currently running trace. Spans nest automatically — a span inside a span inside a trace shows as a tree in the UI.
|
|
88
|
+
|
|
89
|
+
If called outside any trace, it starts its own trace.
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
const result = await bc.span(
|
|
93
|
+
"classify",
|
|
94
|
+
async (span) => {
|
|
95
|
+
span.set({ model: "gpt-4o", provider: "openai" });
|
|
96
|
+
return await classify(input);
|
|
97
|
+
},
|
|
98
|
+
{ type: "llm" }
|
|
99
|
+
);
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**Options:**
|
|
103
|
+
|
|
104
|
+
| Option | Values |
|
|
105
|
+
|--------|--------|
|
|
106
|
+
| `type` | `"llm"` `"tool"` `"retrieval"` `"step"` |
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
### `span.set(data)`
|
|
111
|
+
|
|
112
|
+
Attach data to a span. Call it any time while the span is open.
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
span.set({
|
|
116
|
+
input: "What is TypeScript?", // shown in the UI
|
|
117
|
+
output: "A typed superset of JS.", // shown in the UI
|
|
118
|
+
model: "claude-opus-4-6",
|
|
119
|
+
provider: "anthropic",
|
|
120
|
+
input_tokens: 312,
|
|
121
|
+
output_tokens: 58,
|
|
122
|
+
input_cost_usd: 0.00093,
|
|
123
|
+
output_cost_usd: 0.00087,
|
|
124
|
+
metadata: {
|
|
125
|
+
score: 0.95,
|
|
126
|
+
environment: "production",
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
All fields are optional. `null` and `undefined` are ignored.
|
|
132
|
+
|
|
133
|
+
| Field | Type | Description |
|
|
134
|
+
|-------|------|-------------|
|
|
135
|
+
| `input` | `unknown` | Input passed to this step |
|
|
136
|
+
| `output` | `unknown` | Output produced by this step |
|
|
137
|
+
| `model` | `string` | Model name, e.g. `"gpt-4o"` |
|
|
138
|
+
| `provider` | `string` | Provider, e.g. `"openai"` |
|
|
139
|
+
| `input_tokens` | `number` | Input token count |
|
|
140
|
+
| `output_tokens` | `number` | Output token count |
|
|
141
|
+
| `input_cost_usd` | `number` | Input cost in USD |
|
|
142
|
+
| `output_cost_usd` | `number` | Output cost in USD |
|
|
143
|
+
| `metadata` | `Record<string, string \| number \| boolean>` | Any extra data |
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
//#region rolldown:runtime
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
11
|
+
key = keys[i];
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
13
|
+
get: ((k) => from[k]).bind(null, key),
|
|
14
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
20
|
+
value: mod,
|
|
21
|
+
enumerable: true
|
|
22
|
+
}) : target, mod));
|
|
23
|
+
|
|
24
|
+
//#endregion
|
|
25
|
+
const __opentelemetry_api = __toESM(require("@opentelemetry/api"));
|
|
26
|
+
const __opentelemetry_sdk_trace_node = __toESM(require("@opentelemetry/sdk-trace-node"));
|
|
27
|
+
const __opentelemetry_sdk_trace_base = __toESM(require("@opentelemetry/sdk-trace-base"));
|
|
28
|
+
const __opentelemetry_core = __toESM(require("@opentelemetry/core"));
|
|
29
|
+
|
|
30
|
+
//#region src/exporter.ts
|
|
31
|
+
const LLM_SPAN_NAMES = new Set([
|
|
32
|
+
"ai.generateText",
|
|
33
|
+
"ai.generateText.doGenerate",
|
|
34
|
+
"ai.streamText",
|
|
35
|
+
"ai.streamText.doStream",
|
|
36
|
+
"ai.generateObject",
|
|
37
|
+
"ai.generateObject.doGenerate",
|
|
38
|
+
"ai.generateObject.doStream"
|
|
39
|
+
]);
|
|
40
|
+
const TOOL_SPAN_NAMES = new Set([
|
|
41
|
+
"ai.toolCall",
|
|
42
|
+
"ai.toolExecution",
|
|
43
|
+
"ai.executeToolCall"
|
|
44
|
+
]);
|
|
45
|
+
const HANDLED_ATTRS = new Set([
|
|
46
|
+
"breadcrumb.span.type",
|
|
47
|
+
"breadcrumb.input",
|
|
48
|
+
"breadcrumb.output",
|
|
49
|
+
"breadcrumb.model",
|
|
50
|
+
"breadcrumb.provider",
|
|
51
|
+
"breadcrumb.input_tokens",
|
|
52
|
+
"breadcrumb.output_tokens",
|
|
53
|
+
"breadcrumb.input_cost_usd",
|
|
54
|
+
"breadcrumb.output_cost_usd",
|
|
55
|
+
"ai.prompt.messages",
|
|
56
|
+
"ai.response.text",
|
|
57
|
+
"ai.model.id",
|
|
58
|
+
"ai.model.provider",
|
|
59
|
+
"ai.response.model",
|
|
60
|
+
"gen_ai.request.model",
|
|
61
|
+
"gen_ai.system",
|
|
62
|
+
"ai.usage.inputTokens",
|
|
63
|
+
"ai.usage.promptTokens",
|
|
64
|
+
"ai.usage.outputTokens",
|
|
65
|
+
"ai.usage.completionTokens",
|
|
66
|
+
"gen_ai.usage.input_tokens",
|
|
67
|
+
"gen_ai.usage.output_tokens",
|
|
68
|
+
"ai.response.providerMetadata",
|
|
69
|
+
"ai.response.finishReason"
|
|
70
|
+
]);
|
|
71
|
+
const DROP_ATTRS = new Set([
|
|
72
|
+
"operation.name",
|
|
73
|
+
"resource.name",
|
|
74
|
+
"ai.operationId",
|
|
75
|
+
"ai.telemetry.functionId",
|
|
76
|
+
"gen_ai.response.finish_reasons",
|
|
77
|
+
"gen_ai.response.id",
|
|
78
|
+
"gen_ai.response.model",
|
|
79
|
+
"ai.response.id",
|
|
80
|
+
"ai.response.timestamp"
|
|
81
|
+
]);
|
|
82
|
+
function hrTimeToISO(hrTime) {
|
|
83
|
+
return new Date(hrTime[0] * 1e3 + hrTime[1] / 1e6).toISOString();
|
|
84
|
+
}
|
|
85
|
+
function spanStatus(span) {
|
|
86
|
+
return span.status.code === __opentelemetry_api.SpanStatusCode.ERROR ? "error" : "ok";
|
|
87
|
+
}
|
|
88
|
+
function strAttr(span, ...keys) {
|
|
89
|
+
for (const key of keys) {
|
|
90
|
+
const v = span.attributes[key];
|
|
91
|
+
if (typeof v === "string" && v !== "") return v;
|
|
92
|
+
}
|
|
93
|
+
return void 0;
|
|
94
|
+
}
|
|
95
|
+
function intAttr(span, ...keys) {
|
|
96
|
+
for (const key of keys) {
|
|
97
|
+
const v = span.attributes[key];
|
|
98
|
+
if (typeof v === "number") return Math.round(v);
|
|
99
|
+
}
|
|
100
|
+
return void 0;
|
|
101
|
+
}
|
|
102
|
+
function floatAttr(span, ...keys) {
|
|
103
|
+
for (const key of keys) {
|
|
104
|
+
const v = span.attributes[key];
|
|
105
|
+
if (typeof v === "number") return v;
|
|
106
|
+
}
|
|
107
|
+
return void 0;
|
|
108
|
+
}
|
|
109
|
+
function tryJson(s) {
|
|
110
|
+
try {
|
|
111
|
+
return JSON.parse(s);
|
|
112
|
+
} catch {
|
|
113
|
+
return void 0;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function inferSpanType(span) {
|
|
117
|
+
const explicit = span.attributes["breadcrumb.span.type"];
|
|
118
|
+
if (typeof explicit === "string" && explicit !== "") return explicit;
|
|
119
|
+
if (LLM_SPAN_NAMES.has(span.name)) return "llm";
|
|
120
|
+
if (TOOL_SPAN_NAMES.has(span.name)) return "tool";
|
|
121
|
+
return "custom";
|
|
122
|
+
}
|
|
123
|
+
function extractCost(span) {
|
|
124
|
+
const raw = span.attributes["ai.response.providerMetadata"];
|
|
125
|
+
if (typeof raw !== "string") return {};
|
|
126
|
+
try {
|
|
127
|
+
const parsed = tryJson(raw);
|
|
128
|
+
if (!parsed || typeof parsed !== "object") return {};
|
|
129
|
+
for (const providerData of Object.values(parsed)) {
|
|
130
|
+
const usage = providerData?.usage;
|
|
131
|
+
if (!usage) continue;
|
|
132
|
+
const totalCost = usage["cost"];
|
|
133
|
+
if (typeof totalCost !== "number") continue;
|
|
134
|
+
const prompt = typeof usage["promptTokens"] === "number" ? usage["promptTokens"] : 0;
|
|
135
|
+
const completion = typeof usage["completionTokens"] === "number" ? usage["completionTokens"] : 0;
|
|
136
|
+
const total = prompt + completion;
|
|
137
|
+
if (total > 0) return {
|
|
138
|
+
input_cost_usd: totalCost * (prompt / total),
|
|
139
|
+
output_cost_usd: totalCost * (completion / total)
|
|
140
|
+
};
|
|
141
|
+
return { input_cost_usd: totalCost };
|
|
142
|
+
}
|
|
143
|
+
} catch {}
|
|
144
|
+
return {};
|
|
145
|
+
}
|
|
146
|
+
var BreadcrumbSpanExporter = class {
|
|
147
|
+
constructor(apiKey, baseUrl) {
|
|
148
|
+
this.apiKey = apiKey;
|
|
149
|
+
this.baseUrl = baseUrl;
|
|
150
|
+
}
|
|
151
|
+
export(spans, resultCallback) {
|
|
152
|
+
this._export(spans).then(() => resultCallback({ code: __opentelemetry_core.ExportResultCode.SUCCESS }), () => resultCallback({ code: __opentelemetry_core.ExportResultCode.SUCCESS }));
|
|
153
|
+
}
|
|
154
|
+
async _export(spans) {
|
|
155
|
+
try {
|
|
156
|
+
const roots = spans.filter((s) => !s.parentSpanId);
|
|
157
|
+
const spanPayloads = spans.map((s) => this._mapSpan(s));
|
|
158
|
+
const sends = roots.map((s) => this._sendTrace(s));
|
|
159
|
+
if (spanPayloads.length > 0) sends.push(this._post("/v1/spans", spanPayloads));
|
|
160
|
+
await Promise.all(sends);
|
|
161
|
+
} catch {}
|
|
162
|
+
}
|
|
163
|
+
_mapSpan(span) {
|
|
164
|
+
const ctx = span.spanContext();
|
|
165
|
+
const attrs = span.attributes;
|
|
166
|
+
let input;
|
|
167
|
+
const bcInput = attrs["breadcrumb.input"];
|
|
168
|
+
const aiMessages = attrs["ai.prompt.messages"];
|
|
169
|
+
if (bcInput != null) input = typeof bcInput === "string" ? tryJson(bcInput) ?? bcInput : bcInput;
|
|
170
|
+
else if (typeof aiMessages === "string") input = tryJson(aiMessages) ?? aiMessages;
|
|
171
|
+
let output;
|
|
172
|
+
const bcOutput = attrs["breadcrumb.output"];
|
|
173
|
+
const aiText = attrs["ai.response.text"];
|
|
174
|
+
if (bcOutput != null) output = typeof bcOutput === "string" ? tryJson(bcOutput) ?? bcOutput : bcOutput;
|
|
175
|
+
else if (typeof aiText === "string") output = aiText;
|
|
176
|
+
const model = strAttr(span, "breadcrumb.model", "ai.model.id", "ai.response.model", "gen_ai.request.model");
|
|
177
|
+
const provider = strAttr(span, "breadcrumb.provider", "ai.model.provider", "gen_ai.system");
|
|
178
|
+
const input_tokens = intAttr(span, "breadcrumb.input_tokens", "ai.usage.inputTokens", "ai.usage.promptTokens", "gen_ai.usage.input_tokens");
|
|
179
|
+
const output_tokens = intAttr(span, "breadcrumb.output_tokens", "ai.usage.outputTokens", "ai.usage.completionTokens", "gen_ai.usage.output_tokens");
|
|
180
|
+
let input_cost_usd = floatAttr(span, "breadcrumb.input_cost_usd");
|
|
181
|
+
let output_cost_usd = floatAttr(span, "breadcrumb.output_cost_usd");
|
|
182
|
+
if (input_cost_usd == null && output_cost_usd == null) {
|
|
183
|
+
const cost = extractCost(span);
|
|
184
|
+
input_cost_usd = cost.input_cost_usd;
|
|
185
|
+
output_cost_usd = cost.output_cost_usd;
|
|
186
|
+
}
|
|
187
|
+
const metadata = {};
|
|
188
|
+
for (const [k, v] of Object.entries(attrs)) {
|
|
189
|
+
if (v == null) continue;
|
|
190
|
+
if (HANDLED_ATTRS.has(k) || DROP_ATTRS.has(k)) continue;
|
|
191
|
+
if (k.startsWith("ai.settings.")) continue;
|
|
192
|
+
if (k.startsWith("ai.request.headers.")) continue;
|
|
193
|
+
if (k.startsWith("breadcrumb.")) {
|
|
194
|
+
if (k.startsWith("breadcrumb.meta.")) metadata[k.slice(16)] = String(v);
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
if (k.startsWith("ai.telemetry.metadata.")) {
|
|
198
|
+
metadata[k.slice(22)] = String(v);
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
if (k === "ai.response.finishReason") {
|
|
202
|
+
metadata["finish_reason"] = String(v);
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
metadata[k] = String(v);
|
|
206
|
+
}
|
|
207
|
+
return {
|
|
208
|
+
id: ctx.spanId,
|
|
209
|
+
trace_id: ctx.traceId,
|
|
210
|
+
parent_span_id: span.parentSpanId || void 0,
|
|
211
|
+
name: span.name,
|
|
212
|
+
type: inferSpanType(span),
|
|
213
|
+
start_time: hrTimeToISO(span.startTime),
|
|
214
|
+
end_time: hrTimeToISO(span.endTime),
|
|
215
|
+
status: spanStatus(span),
|
|
216
|
+
status_message: span.status.message || void 0,
|
|
217
|
+
input: input !== void 0 ? input : void 0,
|
|
218
|
+
output: output !== void 0 ? output : void 0,
|
|
219
|
+
provider,
|
|
220
|
+
model,
|
|
221
|
+
input_tokens,
|
|
222
|
+
output_tokens,
|
|
223
|
+
input_cost_usd,
|
|
224
|
+
output_cost_usd,
|
|
225
|
+
metadata: Object.keys(metadata).length > 0 ? metadata : void 0
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
async _sendTrace(span) {
|
|
229
|
+
const ctx = span.spanContext();
|
|
230
|
+
await this._post("/v1/traces", {
|
|
231
|
+
id: ctx.traceId,
|
|
232
|
+
name: span.name,
|
|
233
|
+
start_time: hrTimeToISO(span.startTime),
|
|
234
|
+
end_time: hrTimeToISO(span.endTime),
|
|
235
|
+
status: spanStatus(span),
|
|
236
|
+
status_message: span.status.message || void 0
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
async _post(path, body) {
|
|
240
|
+
try {
|
|
241
|
+
await fetch(`${this.baseUrl}${path}`, {
|
|
242
|
+
method: "POST",
|
|
243
|
+
headers: {
|
|
244
|
+
"Content-Type": "application/json",
|
|
245
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
246
|
+
},
|
|
247
|
+
body: JSON.stringify(body)
|
|
248
|
+
});
|
|
249
|
+
} catch {}
|
|
250
|
+
}
|
|
251
|
+
shutdown() {
|
|
252
|
+
return Promise.resolve();
|
|
253
|
+
}
|
|
254
|
+
forceFlush() {
|
|
255
|
+
return Promise.resolve();
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
//#endregion
|
|
260
|
+
//#region src/index.ts
|
|
261
|
+
function init(options) {
|
|
262
|
+
const exporter = new BreadcrumbSpanExporter(options.apiKey, options.baseUrl);
|
|
263
|
+
const batchOpts = typeof options.batching === "object" ? options.batching : void 0;
|
|
264
|
+
const processor = options.batching === false ? new __opentelemetry_sdk_trace_base.SimpleSpanProcessor(exporter) : new __opentelemetry_sdk_trace_base.BatchSpanProcessor(exporter, {
|
|
265
|
+
scheduledDelayMillis: batchOpts?.flushInterval ?? 5e3,
|
|
266
|
+
maxExportBatchSize: batchOpts?.maxBatchSize ?? 100
|
|
267
|
+
});
|
|
268
|
+
const provider = new __opentelemetry_sdk_trace_node.NodeTracerProvider({ spanProcessors: [processor] });
|
|
269
|
+
provider.register();
|
|
270
|
+
process.once("beforeExit", async () => {
|
|
271
|
+
await provider.shutdown().catch(() => {});
|
|
272
|
+
});
|
|
273
|
+
const tracer = provider.getTracer("@breadcrumb-sdk/core");
|
|
274
|
+
function runSpan(otelSpan, activeCtx, fn) {
|
|
275
|
+
const bcSpan = { set(data) {
|
|
276
|
+
const { metadata,...semantic } = data;
|
|
277
|
+
for (const [key, value] of Object.entries(semantic)) {
|
|
278
|
+
if (value == null) continue;
|
|
279
|
+
const attrKey = `breadcrumb.${key}`;
|
|
280
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") otelSpan.setAttribute(attrKey, value);
|
|
281
|
+
else otelSpan.setAttribute(attrKey, JSON.stringify(value));
|
|
282
|
+
}
|
|
283
|
+
if (metadata) for (const [key, value] of Object.entries(metadata)) {
|
|
284
|
+
if (value == null) continue;
|
|
285
|
+
otelSpan.setAttribute(`breadcrumb.meta.${key}`, value);
|
|
286
|
+
}
|
|
287
|
+
} };
|
|
288
|
+
return __opentelemetry_api.context.with(__opentelemetry_api.trace.setSpan(activeCtx, otelSpan), async () => {
|
|
289
|
+
try {
|
|
290
|
+
const result = await fn(bcSpan);
|
|
291
|
+
otelSpan.setStatus({ code: __opentelemetry_api.SpanStatusCode.OK });
|
|
292
|
+
otelSpan.end();
|
|
293
|
+
return result;
|
|
294
|
+
} catch (err) {
|
|
295
|
+
otelSpan.setStatus({
|
|
296
|
+
code: __opentelemetry_api.SpanStatusCode.ERROR,
|
|
297
|
+
message: err instanceof Error ? err.message : String(err)
|
|
298
|
+
});
|
|
299
|
+
otelSpan.end();
|
|
300
|
+
throw err;
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
return {
|
|
305
|
+
trace(name, fn) {
|
|
306
|
+
const otelSpan = tracer.startSpan(name, {}, __opentelemetry_api.ROOT_CONTEXT);
|
|
307
|
+
return runSpan(otelSpan, __opentelemetry_api.ROOT_CONTEXT, fn);
|
|
308
|
+
},
|
|
309
|
+
span(name, fn, options$1) {
|
|
310
|
+
const activeCtx = __opentelemetry_api.context.active();
|
|
311
|
+
const otelSpan = tracer.startSpan(name, {}, activeCtx);
|
|
312
|
+
if (options$1?.type) otelSpan.setAttribute("breadcrumb.span.type", options$1.type);
|
|
313
|
+
return runSpan(otelSpan, activeCtx, fn);
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
//#endregion
|
|
319
|
+
exports.init = init
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
//#region src/types.d.ts
|
|
2
|
+
interface SpanData {
|
|
3
|
+
input?: unknown;
|
|
4
|
+
output?: unknown;
|
|
5
|
+
model?: string;
|
|
6
|
+
provider?: string;
|
|
7
|
+
input_tokens?: number;
|
|
8
|
+
output_tokens?: number;
|
|
9
|
+
input_cost_usd?: number;
|
|
10
|
+
output_cost_usd?: number;
|
|
11
|
+
metadata?: Record<string, string | number | boolean>;
|
|
12
|
+
}
|
|
13
|
+
interface BreadcrumbSpan {
|
|
14
|
+
set(data: SpanData): void;
|
|
15
|
+
}
|
|
16
|
+
interface SpanOptions {
|
|
17
|
+
type?: "llm" | "tool" | "retrieval" | "step";
|
|
18
|
+
}
|
|
19
|
+
interface Breadcrumb {
|
|
20
|
+
trace<T>(name: string, fn: (span: BreadcrumbSpan) => Promise<T>): Promise<T>;
|
|
21
|
+
span<T>(name: string, fn: (span: BreadcrumbSpan) => Promise<T>, options?: SpanOptions): Promise<T>;
|
|
22
|
+
} //#endregion
|
|
23
|
+
//#region src/index.d.ts
|
|
24
|
+
//# sourceMappingURL=types.d.ts.map
|
|
25
|
+
interface InitOptions {
|
|
26
|
+
apiKey: string;
|
|
27
|
+
baseUrl: string;
|
|
28
|
+
batching?: false | {
|
|
29
|
+
flushInterval?: number;
|
|
30
|
+
maxBatchSize?: number;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
declare function init(options: InitOptions): Breadcrumb;
|
|
34
|
+
|
|
35
|
+
//#endregion
|
|
36
|
+
//# sourceMappingURL=index.d.ts.map
|
|
37
|
+
|
|
38
|
+
export { Breadcrumb, BreadcrumbSpan, InitOptions, SpanOptions, init };
|
|
39
|
+
//# sourceMappingURL=index.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/types.ts","../src/index.ts"],"sourcesContent":null,"mappings":";UAAiB,QAAA;EAAA,KAAA,CAAA,EAAA,OAAQ;EAYR,MAAA,CAAA,EAAA,OAAA;EAIA,KAAA,CAAA,EAAA,MAAW;EAIX,QAAA,CAAA,EAAA,MAAU;EAAA,YAAA,CAAA,EAAA,MAAA;EAAA,aACS,CAAA,EAAA,MAAA;EAAc,cAAa,CAAA,EAAA,MAAA;EAAC,eAAT,CAAA,EAAA,MAAA;EAAO,QAAc,CAAA,EAZ/D,MAY+D,CAAA,MAAA,EAAA,MAAA,GAAA,MAAA,GAAA,OAAA,CAAA;;AAG7D,UAZE,cAAA,CAYF;EAAc,GAAa,CAAA,IAAA,EAX9B,QAW8B,CAAA,EAAA,IAAA;;AAC5B,UATG,WAAA,CASH;EAAW,IACZ,CAAA,EAAA,KAAA,GAAA,MAAA,GAAA,WAAA,GAAA,MAAA;;AAAD,UANK,UAAA,CAML;oCALwB,mBAAmB,QAAQ,KAAK,QAAQ;mCAG7D,mBAAmB,QAAQ,cAC5B,cACT,QAAQ;;;ACTb;ADLiB,UCKA,WAAA,CDLc;EAId,MAAA,EAAA,MAAW;EAIX,OAAA,EAAA,MAAU;EAAA,QAAA,CAAA,EAAA,KAAA,GAAA;IACS,aAAA,CAAA,EAAA,MAAA;IAA2B,YAAA,CAAA,EAAA,MAAA;EAAC,CAAA;;AAAI,iBCOpD,IAAA,CDPoD,OAAA,ECOtC,WDPsC,CAAA,ECOxB,UDPwB"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
//#region src/types.d.ts
|
|
2
|
+
interface SpanData {
|
|
3
|
+
input?: unknown;
|
|
4
|
+
output?: unknown;
|
|
5
|
+
model?: string;
|
|
6
|
+
provider?: string;
|
|
7
|
+
input_tokens?: number;
|
|
8
|
+
output_tokens?: number;
|
|
9
|
+
input_cost_usd?: number;
|
|
10
|
+
output_cost_usd?: number;
|
|
11
|
+
metadata?: Record<string, string | number | boolean>;
|
|
12
|
+
}
|
|
13
|
+
interface BreadcrumbSpan {
|
|
14
|
+
set(data: SpanData): void;
|
|
15
|
+
}
|
|
16
|
+
interface SpanOptions {
|
|
17
|
+
type?: "llm" | "tool" | "retrieval" | "step";
|
|
18
|
+
}
|
|
19
|
+
interface Breadcrumb {
|
|
20
|
+
trace<T>(name: string, fn: (span: BreadcrumbSpan) => Promise<T>): Promise<T>;
|
|
21
|
+
span<T>(name: string, fn: (span: BreadcrumbSpan) => Promise<T>, options?: SpanOptions): Promise<T>;
|
|
22
|
+
} //#endregion
|
|
23
|
+
//#region src/index.d.ts
|
|
24
|
+
//# sourceMappingURL=types.d.ts.map
|
|
25
|
+
interface InitOptions {
|
|
26
|
+
apiKey: string;
|
|
27
|
+
baseUrl: string;
|
|
28
|
+
batching?: false | {
|
|
29
|
+
flushInterval?: number;
|
|
30
|
+
maxBatchSize?: number;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
declare function init(options: InitOptions): Breadcrumb;
|
|
34
|
+
|
|
35
|
+
//#endregion
|
|
36
|
+
//# sourceMappingURL=index.d.ts.map
|
|
37
|
+
|
|
38
|
+
export { Breadcrumb, BreadcrumbSpan, InitOptions, SpanOptions, init };
|
|
39
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/types.ts","../src/index.ts"],"sourcesContent":null,"mappings":";UAAiB,QAAA;EAAA,KAAA,CAAA,EAAA,OAAQ;EAYR,MAAA,CAAA,EAAA,OAAA;EAIA,KAAA,CAAA,EAAA,MAAW;EAIX,QAAA,CAAA,EAAA,MAAU;EAAA,YAAA,CAAA,EAAA,MAAA;EAAA,aACS,CAAA,EAAA,MAAA;EAAc,cAAa,CAAA,EAAA,MAAA;EAAC,eAAT,CAAA,EAAA,MAAA;EAAO,QAAc,CAAA,EAZ/D,MAY+D,CAAA,MAAA,EAAA,MAAA,GAAA,MAAA,GAAA,OAAA,CAAA;;AAG7D,UAZE,cAAA,CAYF;EAAc,GAAa,CAAA,IAAA,EAX9B,QAW8B,CAAA,EAAA,IAAA;;AAC5B,UATG,WAAA,CASH;EAAW,IACZ,CAAA,EAAA,KAAA,GAAA,MAAA,GAAA,WAAA,GAAA,MAAA;;AAAD,UANK,UAAA,CAML;oCALwB,mBAAmB,QAAQ,KAAK,QAAQ;mCAG7D,mBAAmB,QAAQ,cAC5B,cACT,QAAQ;;;ACTb;ADLiB,UCKA,WAAA,CDLc;EAId,MAAA,EAAA,MAAW;EAIX,OAAA,EAAA,MAAU;EAAA,QAAA,CAAA,EAAA,KAAA,GAAA;IACS,aAAA,CAAA,EAAA,MAAA;IAA2B,YAAA,CAAA,EAAA,MAAA;EAAC,CAAA;;AAAI,iBCOpD,IAAA,CDPoD,OAAA,ECOtC,WDPsC,CAAA,ECOxB,UDPwB"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import { ROOT_CONTEXT, SpanStatusCode, context, trace } from "@opentelemetry/api";
|
|
2
|
+
import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
|
|
3
|
+
import { BatchSpanProcessor, SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base";
|
|
4
|
+
import { ExportResultCode } from "@opentelemetry/core";
|
|
5
|
+
|
|
6
|
+
//#region src/exporter.ts
|
|
7
|
+
const LLM_SPAN_NAMES = new Set([
|
|
8
|
+
"ai.generateText",
|
|
9
|
+
"ai.generateText.doGenerate",
|
|
10
|
+
"ai.streamText",
|
|
11
|
+
"ai.streamText.doStream",
|
|
12
|
+
"ai.generateObject",
|
|
13
|
+
"ai.generateObject.doGenerate",
|
|
14
|
+
"ai.generateObject.doStream"
|
|
15
|
+
]);
|
|
16
|
+
const TOOL_SPAN_NAMES = new Set([
|
|
17
|
+
"ai.toolCall",
|
|
18
|
+
"ai.toolExecution",
|
|
19
|
+
"ai.executeToolCall"
|
|
20
|
+
]);
|
|
21
|
+
const HANDLED_ATTRS = new Set([
|
|
22
|
+
"breadcrumb.span.type",
|
|
23
|
+
"breadcrumb.input",
|
|
24
|
+
"breadcrumb.output",
|
|
25
|
+
"breadcrumb.model",
|
|
26
|
+
"breadcrumb.provider",
|
|
27
|
+
"breadcrumb.input_tokens",
|
|
28
|
+
"breadcrumb.output_tokens",
|
|
29
|
+
"breadcrumb.input_cost_usd",
|
|
30
|
+
"breadcrumb.output_cost_usd",
|
|
31
|
+
"ai.prompt.messages",
|
|
32
|
+
"ai.response.text",
|
|
33
|
+
"ai.model.id",
|
|
34
|
+
"ai.model.provider",
|
|
35
|
+
"ai.response.model",
|
|
36
|
+
"gen_ai.request.model",
|
|
37
|
+
"gen_ai.system",
|
|
38
|
+
"ai.usage.inputTokens",
|
|
39
|
+
"ai.usage.promptTokens",
|
|
40
|
+
"ai.usage.outputTokens",
|
|
41
|
+
"ai.usage.completionTokens",
|
|
42
|
+
"gen_ai.usage.input_tokens",
|
|
43
|
+
"gen_ai.usage.output_tokens",
|
|
44
|
+
"ai.response.providerMetadata",
|
|
45
|
+
"ai.response.finishReason"
|
|
46
|
+
]);
|
|
47
|
+
const DROP_ATTRS = new Set([
|
|
48
|
+
"operation.name",
|
|
49
|
+
"resource.name",
|
|
50
|
+
"ai.operationId",
|
|
51
|
+
"ai.telemetry.functionId",
|
|
52
|
+
"gen_ai.response.finish_reasons",
|
|
53
|
+
"gen_ai.response.id",
|
|
54
|
+
"gen_ai.response.model",
|
|
55
|
+
"ai.response.id",
|
|
56
|
+
"ai.response.timestamp"
|
|
57
|
+
]);
|
|
58
|
+
function hrTimeToISO(hrTime) {
|
|
59
|
+
return new Date(hrTime[0] * 1e3 + hrTime[1] / 1e6).toISOString();
|
|
60
|
+
}
|
|
61
|
+
function spanStatus(span) {
|
|
62
|
+
return span.status.code === SpanStatusCode.ERROR ? "error" : "ok";
|
|
63
|
+
}
|
|
64
|
+
function strAttr(span, ...keys) {
|
|
65
|
+
for (const key of keys) {
|
|
66
|
+
const v = span.attributes[key];
|
|
67
|
+
if (typeof v === "string" && v !== "") return v;
|
|
68
|
+
}
|
|
69
|
+
return void 0;
|
|
70
|
+
}
|
|
71
|
+
function intAttr(span, ...keys) {
|
|
72
|
+
for (const key of keys) {
|
|
73
|
+
const v = span.attributes[key];
|
|
74
|
+
if (typeof v === "number") return Math.round(v);
|
|
75
|
+
}
|
|
76
|
+
return void 0;
|
|
77
|
+
}
|
|
78
|
+
function floatAttr(span, ...keys) {
|
|
79
|
+
for (const key of keys) {
|
|
80
|
+
const v = span.attributes[key];
|
|
81
|
+
if (typeof v === "number") return v;
|
|
82
|
+
}
|
|
83
|
+
return void 0;
|
|
84
|
+
}
|
|
85
|
+
function tryJson(s) {
|
|
86
|
+
try {
|
|
87
|
+
return JSON.parse(s);
|
|
88
|
+
} catch {
|
|
89
|
+
return void 0;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function inferSpanType(span) {
|
|
93
|
+
const explicit = span.attributes["breadcrumb.span.type"];
|
|
94
|
+
if (typeof explicit === "string" && explicit !== "") return explicit;
|
|
95
|
+
if (LLM_SPAN_NAMES.has(span.name)) return "llm";
|
|
96
|
+
if (TOOL_SPAN_NAMES.has(span.name)) return "tool";
|
|
97
|
+
return "custom";
|
|
98
|
+
}
|
|
99
|
+
function extractCost(span) {
|
|
100
|
+
const raw = span.attributes["ai.response.providerMetadata"];
|
|
101
|
+
if (typeof raw !== "string") return {};
|
|
102
|
+
try {
|
|
103
|
+
const parsed = tryJson(raw);
|
|
104
|
+
if (!parsed || typeof parsed !== "object") return {};
|
|
105
|
+
for (const providerData of Object.values(parsed)) {
|
|
106
|
+
const usage = providerData?.usage;
|
|
107
|
+
if (!usage) continue;
|
|
108
|
+
const totalCost = usage["cost"];
|
|
109
|
+
if (typeof totalCost !== "number") continue;
|
|
110
|
+
const prompt = typeof usage["promptTokens"] === "number" ? usage["promptTokens"] : 0;
|
|
111
|
+
const completion = typeof usage["completionTokens"] === "number" ? usage["completionTokens"] : 0;
|
|
112
|
+
const total = prompt + completion;
|
|
113
|
+
if (total > 0) return {
|
|
114
|
+
input_cost_usd: totalCost * (prompt / total),
|
|
115
|
+
output_cost_usd: totalCost * (completion / total)
|
|
116
|
+
};
|
|
117
|
+
return { input_cost_usd: totalCost };
|
|
118
|
+
}
|
|
119
|
+
} catch {}
|
|
120
|
+
return {};
|
|
121
|
+
}
|
|
122
|
+
var BreadcrumbSpanExporter = class {
|
|
123
|
+
constructor(apiKey, baseUrl) {
|
|
124
|
+
this.apiKey = apiKey;
|
|
125
|
+
this.baseUrl = baseUrl;
|
|
126
|
+
}
|
|
127
|
+
export(spans, resultCallback) {
|
|
128
|
+
this._export(spans).then(() => resultCallback({ code: ExportResultCode.SUCCESS }), () => resultCallback({ code: ExportResultCode.SUCCESS }));
|
|
129
|
+
}
|
|
130
|
+
async _export(spans) {
|
|
131
|
+
try {
|
|
132
|
+
const roots = spans.filter((s) => !s.parentSpanId);
|
|
133
|
+
const spanPayloads = spans.map((s) => this._mapSpan(s));
|
|
134
|
+
const sends = roots.map((s) => this._sendTrace(s));
|
|
135
|
+
if (spanPayloads.length > 0) sends.push(this._post("/v1/spans", spanPayloads));
|
|
136
|
+
await Promise.all(sends);
|
|
137
|
+
} catch {}
|
|
138
|
+
}
|
|
139
|
+
_mapSpan(span) {
|
|
140
|
+
const ctx = span.spanContext();
|
|
141
|
+
const attrs = span.attributes;
|
|
142
|
+
let input;
|
|
143
|
+
const bcInput = attrs["breadcrumb.input"];
|
|
144
|
+
const aiMessages = attrs["ai.prompt.messages"];
|
|
145
|
+
if (bcInput != null) input = typeof bcInput === "string" ? tryJson(bcInput) ?? bcInput : bcInput;
|
|
146
|
+
else if (typeof aiMessages === "string") input = tryJson(aiMessages) ?? aiMessages;
|
|
147
|
+
let output;
|
|
148
|
+
const bcOutput = attrs["breadcrumb.output"];
|
|
149
|
+
const aiText = attrs["ai.response.text"];
|
|
150
|
+
if (bcOutput != null) output = typeof bcOutput === "string" ? tryJson(bcOutput) ?? bcOutput : bcOutput;
|
|
151
|
+
else if (typeof aiText === "string") output = aiText;
|
|
152
|
+
const model = strAttr(span, "breadcrumb.model", "ai.model.id", "ai.response.model", "gen_ai.request.model");
|
|
153
|
+
const provider = strAttr(span, "breadcrumb.provider", "ai.model.provider", "gen_ai.system");
|
|
154
|
+
const input_tokens = intAttr(span, "breadcrumb.input_tokens", "ai.usage.inputTokens", "ai.usage.promptTokens", "gen_ai.usage.input_tokens");
|
|
155
|
+
const output_tokens = intAttr(span, "breadcrumb.output_tokens", "ai.usage.outputTokens", "ai.usage.completionTokens", "gen_ai.usage.output_tokens");
|
|
156
|
+
let input_cost_usd = floatAttr(span, "breadcrumb.input_cost_usd");
|
|
157
|
+
let output_cost_usd = floatAttr(span, "breadcrumb.output_cost_usd");
|
|
158
|
+
if (input_cost_usd == null && output_cost_usd == null) {
|
|
159
|
+
const cost = extractCost(span);
|
|
160
|
+
input_cost_usd = cost.input_cost_usd;
|
|
161
|
+
output_cost_usd = cost.output_cost_usd;
|
|
162
|
+
}
|
|
163
|
+
const metadata = {};
|
|
164
|
+
for (const [k, v] of Object.entries(attrs)) {
|
|
165
|
+
if (v == null) continue;
|
|
166
|
+
if (HANDLED_ATTRS.has(k) || DROP_ATTRS.has(k)) continue;
|
|
167
|
+
if (k.startsWith("ai.settings.")) continue;
|
|
168
|
+
if (k.startsWith("ai.request.headers.")) continue;
|
|
169
|
+
if (k.startsWith("breadcrumb.")) {
|
|
170
|
+
if (k.startsWith("breadcrumb.meta.")) metadata[k.slice(16)] = String(v);
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
if (k.startsWith("ai.telemetry.metadata.")) {
|
|
174
|
+
metadata[k.slice(22)] = String(v);
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
if (k === "ai.response.finishReason") {
|
|
178
|
+
metadata["finish_reason"] = String(v);
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
metadata[k] = String(v);
|
|
182
|
+
}
|
|
183
|
+
return {
|
|
184
|
+
id: ctx.spanId,
|
|
185
|
+
trace_id: ctx.traceId,
|
|
186
|
+
parent_span_id: span.parentSpanId || void 0,
|
|
187
|
+
name: span.name,
|
|
188
|
+
type: inferSpanType(span),
|
|
189
|
+
start_time: hrTimeToISO(span.startTime),
|
|
190
|
+
end_time: hrTimeToISO(span.endTime),
|
|
191
|
+
status: spanStatus(span),
|
|
192
|
+
status_message: span.status.message || void 0,
|
|
193
|
+
input: input !== void 0 ? input : void 0,
|
|
194
|
+
output: output !== void 0 ? output : void 0,
|
|
195
|
+
provider,
|
|
196
|
+
model,
|
|
197
|
+
input_tokens,
|
|
198
|
+
output_tokens,
|
|
199
|
+
input_cost_usd,
|
|
200
|
+
output_cost_usd,
|
|
201
|
+
metadata: Object.keys(metadata).length > 0 ? metadata : void 0
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
async _sendTrace(span) {
|
|
205
|
+
const ctx = span.spanContext();
|
|
206
|
+
await this._post("/v1/traces", {
|
|
207
|
+
id: ctx.traceId,
|
|
208
|
+
name: span.name,
|
|
209
|
+
start_time: hrTimeToISO(span.startTime),
|
|
210
|
+
end_time: hrTimeToISO(span.endTime),
|
|
211
|
+
status: spanStatus(span),
|
|
212
|
+
status_message: span.status.message || void 0
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
async _post(path, body) {
|
|
216
|
+
try {
|
|
217
|
+
await fetch(`${this.baseUrl}${path}`, {
|
|
218
|
+
method: "POST",
|
|
219
|
+
headers: {
|
|
220
|
+
"Content-Type": "application/json",
|
|
221
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
222
|
+
},
|
|
223
|
+
body: JSON.stringify(body)
|
|
224
|
+
});
|
|
225
|
+
} catch {}
|
|
226
|
+
}
|
|
227
|
+
shutdown() {
|
|
228
|
+
return Promise.resolve();
|
|
229
|
+
}
|
|
230
|
+
forceFlush() {
|
|
231
|
+
return Promise.resolve();
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
//#endregion
|
|
236
|
+
//#region src/index.ts
|
|
237
|
+
function init(options) {
|
|
238
|
+
const exporter = new BreadcrumbSpanExporter(options.apiKey, options.baseUrl);
|
|
239
|
+
const batchOpts = typeof options.batching === "object" ? options.batching : void 0;
|
|
240
|
+
const processor = options.batching === false ? new SimpleSpanProcessor(exporter) : new BatchSpanProcessor(exporter, {
|
|
241
|
+
scheduledDelayMillis: batchOpts?.flushInterval ?? 5e3,
|
|
242
|
+
maxExportBatchSize: batchOpts?.maxBatchSize ?? 100
|
|
243
|
+
});
|
|
244
|
+
const provider = new NodeTracerProvider({ spanProcessors: [processor] });
|
|
245
|
+
provider.register();
|
|
246
|
+
process.once("beforeExit", async () => {
|
|
247
|
+
await provider.shutdown().catch(() => {});
|
|
248
|
+
});
|
|
249
|
+
const tracer = provider.getTracer("@breadcrumb-sdk/core");
|
|
250
|
+
function runSpan(otelSpan, activeCtx, fn) {
|
|
251
|
+
const bcSpan = { set(data) {
|
|
252
|
+
const { metadata,...semantic } = data;
|
|
253
|
+
for (const [key, value] of Object.entries(semantic)) {
|
|
254
|
+
if (value == null) continue;
|
|
255
|
+
const attrKey = `breadcrumb.${key}`;
|
|
256
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") otelSpan.setAttribute(attrKey, value);
|
|
257
|
+
else otelSpan.setAttribute(attrKey, JSON.stringify(value));
|
|
258
|
+
}
|
|
259
|
+
if (metadata) for (const [key, value] of Object.entries(metadata)) {
|
|
260
|
+
if (value == null) continue;
|
|
261
|
+
otelSpan.setAttribute(`breadcrumb.meta.${key}`, value);
|
|
262
|
+
}
|
|
263
|
+
} };
|
|
264
|
+
return context.with(trace.setSpan(activeCtx, otelSpan), async () => {
|
|
265
|
+
try {
|
|
266
|
+
const result = await fn(bcSpan);
|
|
267
|
+
otelSpan.setStatus({ code: SpanStatusCode.OK });
|
|
268
|
+
otelSpan.end();
|
|
269
|
+
return result;
|
|
270
|
+
} catch (err) {
|
|
271
|
+
otelSpan.setStatus({
|
|
272
|
+
code: SpanStatusCode.ERROR,
|
|
273
|
+
message: err instanceof Error ? err.message : String(err)
|
|
274
|
+
});
|
|
275
|
+
otelSpan.end();
|
|
276
|
+
throw err;
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
return {
|
|
281
|
+
trace(name, fn) {
|
|
282
|
+
const otelSpan = tracer.startSpan(name, {}, ROOT_CONTEXT);
|
|
283
|
+
return runSpan(otelSpan, ROOT_CONTEXT, fn);
|
|
284
|
+
},
|
|
285
|
+
span(name, fn, options$1) {
|
|
286
|
+
const activeCtx = context.active();
|
|
287
|
+
const otelSpan = tracer.startSpan(name, {}, activeCtx);
|
|
288
|
+
if (options$1?.type) otelSpan.setAttribute("breadcrumb.span.type", options$1.type);
|
|
289
|
+
return runSpan(otelSpan, activeCtx, fn);
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
//#endregion
|
|
295
|
+
export { init };
|
|
296
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":["hrTime: [number, number]","span: ReadableSpan","s: string","apiKey: string","baseUrl: string","spans: ReadableSpan[]","resultCallback: (result: ExportResult) => void","sends: Promise<void>[]","input: unknown","output: unknown","metadata: Record<string, string>","path: string","body: unknown","options: InitOptions","otelSpan: OtelSpan","activeCtx: typeof ROOT_CONTEXT","fn: (span: BreadcrumbSpan) => Promise<T>","bcSpan: BreadcrumbSpan","name: string","options?: SpanOptions","options"],"sources":["../src/exporter.ts","../src/index.ts"],"sourcesContent":["import type { ExportResult } from \"@opentelemetry/core\";\nimport { ExportResultCode } from \"@opentelemetry/core\";\nimport type { SpanExporter, ReadableSpan } from \"@opentelemetry/sdk-trace-base\";\nimport { SpanStatusCode } from \"@opentelemetry/api\";\n\ntype SpanType = \"llm\" | \"tool\" | \"retrieval\" | \"step\" | \"custom\";\n\nconst LLM_SPAN_NAMES = new Set([\n \"ai.generateText\",\n \"ai.generateText.doGenerate\",\n \"ai.streamText\",\n \"ai.streamText.doStream\",\n \"ai.generateObject\",\n \"ai.generateObject.doGenerate\",\n \"ai.generateObject.doStream\",\n]);\n\nconst TOOL_SPAN_NAMES = new Set([\n \"ai.toolCall\",\n \"ai.toolExecution\",\n \"ai.executeToolCall\",\n]);\n\n// Attributes handled explicitly — excluded from metadata pass-through\nconst HANDLED_ATTRS = new Set([\n // Breadcrumb native\n \"breadcrumb.span.type\",\n \"breadcrumb.input\",\n \"breadcrumb.output\",\n \"breadcrumb.model\",\n \"breadcrumb.provider\",\n \"breadcrumb.input_tokens\",\n \"breadcrumb.output_tokens\",\n \"breadcrumb.input_cost_usd\",\n \"breadcrumb.output_cost_usd\",\n // AI SDK - input/output\n \"ai.prompt.messages\",\n \"ai.response.text\",\n // AI SDK - model/provider\n \"ai.model.id\",\n \"ai.model.provider\",\n \"ai.response.model\",\n \"gen_ai.request.model\",\n \"gen_ai.system\",\n // AI SDK - tokens\n \"ai.usage.inputTokens\",\n \"ai.usage.promptTokens\",\n \"ai.usage.outputTokens\",\n \"ai.usage.completionTokens\",\n \"gen_ai.usage.input_tokens\",\n \"gen_ai.usage.output_tokens\",\n // AI SDK - cost source\n \"ai.response.providerMetadata\",\n // AI SDK - finish reason (renamed in metadata)\n \"ai.response.finishReason\",\n]);\n\n// Attributes to drop entirely\nconst DROP_ATTRS = new Set([\n \"operation.name\",\n \"resource.name\",\n \"ai.operationId\",\n \"ai.telemetry.functionId\",\n \"gen_ai.response.finish_reasons\",\n \"gen_ai.response.id\",\n \"gen_ai.response.model\",\n \"ai.response.id\",\n \"ai.response.timestamp\",\n]);\n\nfunction hrTimeToISO(hrTime: [number, number]): string {\n return new Date(hrTime[0] * 1000 + hrTime[1] / 1_000_000).toISOString();\n}\n\nfunction spanStatus(span: ReadableSpan): \"ok\" | \"error\" {\n return span.status.code === SpanStatusCode.ERROR ? \"error\" : \"ok\";\n}\n\nfunction strAttr(span: ReadableSpan, ...keys: string[]): string | undefined {\n for (const key of keys) {\n const v = span.attributes[key];\n if (typeof v === \"string\" && v !== \"\") return v;\n }\n return undefined;\n}\n\nfunction intAttr(span: ReadableSpan, ...keys: string[]): number | undefined {\n for (const key of keys) {\n const v = span.attributes[key];\n if (typeof v === \"number\") return Math.round(v);\n }\n return undefined;\n}\n\nfunction floatAttr(span: ReadableSpan, ...keys: string[]): number | undefined {\n for (const key of keys) {\n const v = span.attributes[key];\n if (typeof v === \"number\") return v;\n }\n return undefined;\n}\n\nfunction tryJson(s: string): unknown {\n try {\n return JSON.parse(s);\n } catch {\n return undefined;\n }\n}\n\nfunction inferSpanType(span: ReadableSpan): SpanType {\n const explicit = span.attributes[\"breadcrumb.span.type\"];\n if (typeof explicit === \"string\" && explicit !== \"\") return explicit as SpanType;\n if (LLM_SPAN_NAMES.has(span.name)) return \"llm\";\n if (TOOL_SPAN_NAMES.has(span.name)) return \"tool\";\n return \"custom\";\n}\n\nfunction extractCost(\n span: ReadableSpan,\n): { input_cost_usd?: number; output_cost_usd?: number } {\n const raw = span.attributes[\"ai.response.providerMetadata\"];\n if (typeof raw !== \"string\") return {};\n try {\n const parsed = tryJson(raw) as Record<string, unknown> | null;\n if (!parsed || typeof parsed !== \"object\") return {};\n for (const providerData of Object.values(parsed)) {\n const usage = (providerData as Record<string, unknown>)?.usage as\n | Record<string, unknown>\n | undefined;\n if (!usage) continue;\n const totalCost = usage[\"cost\"];\n if (typeof totalCost !== \"number\") continue;\n const prompt = typeof usage[\"promptTokens\"] === \"number\" ? usage[\"promptTokens\"] : 0;\n const completion =\n typeof usage[\"completionTokens\"] === \"number\" ? usage[\"completionTokens\"] : 0;\n const total = prompt + completion;\n if (total > 0) {\n return {\n input_cost_usd: totalCost * (prompt / total),\n output_cost_usd: totalCost * (completion / total),\n };\n }\n return { input_cost_usd: totalCost };\n }\n } catch {\n // ignore\n }\n return {};\n}\n\nexport class BreadcrumbSpanExporter implements SpanExporter {\n constructor(\n private readonly apiKey: string,\n private readonly baseUrl: string,\n ) {}\n\n export(\n spans: ReadableSpan[],\n resultCallback: (result: ExportResult) => void,\n ): void {\n this._export(spans).then(\n () => resultCallback({ code: ExportResultCode.SUCCESS }),\n () => resultCallback({ code: ExportResultCode.SUCCESS }), // fail silently\n );\n }\n\n private async _export(spans: ReadableSpan[]): Promise<void> {\n try {\n const roots = spans.filter((s) => !s.parentSpanId);\n const spanPayloads = spans.map((s) => this._mapSpan(s));\n\n const sends: Promise<void>[] = roots.map((s) => this._sendTrace(s));\n if (spanPayloads.length > 0) {\n sends.push(this._post(\"/v1/spans\", spanPayloads));\n }\n\n await Promise.all(sends);\n } catch {\n // Fail silently\n }\n }\n\n private _mapSpan(span: ReadableSpan) {\n const ctx = span.spanContext();\n const attrs = span.attributes;\n\n // ── Input ────────────────────────────────────────────────────────────────\n let input: unknown;\n const bcInput = attrs[\"breadcrumb.input\"];\n const aiMessages = attrs[\"ai.prompt.messages\"];\n if (bcInput != null) {\n input = typeof bcInput === \"string\" ? (tryJson(bcInput) ?? bcInput) : bcInput;\n } else if (typeof aiMessages === \"string\") {\n input = tryJson(aiMessages) ?? aiMessages;\n }\n\n // ── Output ───────────────────────────────────────────────────────────────\n let output: unknown;\n const bcOutput = attrs[\"breadcrumb.output\"];\n const aiText = attrs[\"ai.response.text\"];\n if (bcOutput != null) {\n output = typeof bcOutput === \"string\" ? (tryJson(bcOutput) ?? bcOutput) : bcOutput;\n } else if (typeof aiText === \"string\") {\n output = aiText;\n }\n\n // ── Model / provider ─────────────────────────────────────────────────────\n const model = strAttr(span, \"breadcrumb.model\", \"ai.model.id\", \"ai.response.model\", \"gen_ai.request.model\");\n const provider = strAttr(span, \"breadcrumb.provider\", \"ai.model.provider\", \"gen_ai.system\");\n\n // ── Tokens ───────────────────────────────────────────────────────────────\n const input_tokens = intAttr(\n span,\n \"breadcrumb.input_tokens\",\n \"ai.usage.inputTokens\",\n \"ai.usage.promptTokens\",\n \"gen_ai.usage.input_tokens\",\n );\n const output_tokens = intAttr(\n span,\n \"breadcrumb.output_tokens\",\n \"ai.usage.outputTokens\",\n \"ai.usage.completionTokens\",\n \"gen_ai.usage.output_tokens\",\n );\n\n // ── Cost ─────────────────────────────────────────────────────────────────\n let input_cost_usd = floatAttr(span, \"breadcrumb.input_cost_usd\");\n let output_cost_usd = floatAttr(span, \"breadcrumb.output_cost_usd\");\n if (input_cost_usd == null && output_cost_usd == null) {\n const cost = extractCost(span);\n input_cost_usd = cost.input_cost_usd;\n output_cost_usd = cost.output_cost_usd;\n }\n\n // ── Metadata ─────────────────────────────────────────────────────────────\n const metadata: Record<string, string> = {};\n for (const [k, v] of Object.entries(attrs)) {\n if (v == null) continue;\n if (HANDLED_ATTRS.has(k) || DROP_ATTRS.has(k)) continue;\n if (k.startsWith(\"ai.settings.\")) continue;\n if (k.startsWith(\"ai.request.headers.\")) continue;\n if (k.startsWith(\"breadcrumb.\")) {\n // breadcrumb.meta.* keys from span.set({ metadata: {...} })\n if (k.startsWith(\"breadcrumb.meta.\")) {\n metadata[k.slice(\"breadcrumb.meta.\".length)] = String(v);\n }\n continue;\n }\n if (k.startsWith(\"ai.telemetry.metadata.\")) {\n metadata[k.slice(\"ai.telemetry.metadata.\".length)] = String(v);\n continue;\n }\n if (k === \"ai.response.finishReason\") {\n metadata[\"finish_reason\"] = String(v);\n continue;\n }\n metadata[k] = String(v);\n }\n\n return {\n id: ctx.spanId,\n trace_id: ctx.traceId,\n parent_span_id: span.parentSpanId || undefined,\n name: span.name,\n type: inferSpanType(span),\n start_time: hrTimeToISO(span.startTime),\n end_time: hrTimeToISO(span.endTime),\n status: spanStatus(span),\n status_message: span.status.message || undefined,\n input: input !== undefined ? input : undefined,\n output: output !== undefined ? output : undefined,\n provider,\n model,\n input_tokens,\n output_tokens,\n input_cost_usd,\n output_cost_usd,\n metadata: Object.keys(metadata).length > 0 ? metadata : undefined,\n };\n }\n\n private async _sendTrace(span: ReadableSpan): Promise<void> {\n const ctx = span.spanContext();\n await this._post(\"/v1/traces\", {\n id: ctx.traceId,\n name: span.name,\n start_time: hrTimeToISO(span.startTime),\n end_time: hrTimeToISO(span.endTime),\n status: spanStatus(span),\n status_message: span.status.message || undefined,\n });\n }\n\n private async _post(path: string, body: unknown): Promise<void> {\n try {\n await fetch(`${this.baseUrl}${path}`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.apiKey}`,\n },\n body: JSON.stringify(body),\n });\n } catch {\n // Fail silently — backend unreachable\n }\n }\n\n shutdown(): Promise<void> {\n return Promise.resolve();\n }\n\n forceFlush(): Promise<void> {\n return Promise.resolve();\n }\n}\n","import {\n context,\n trace,\n ROOT_CONTEXT,\n SpanStatusCode,\n type Span as OtelSpan,\n} from \"@opentelemetry/api\";\nimport { NodeTracerProvider } from \"@opentelemetry/sdk-trace-node\";\nimport {\n SimpleSpanProcessor,\n BatchSpanProcessor,\n} from \"@opentelemetry/sdk-trace-base\";\nimport { BreadcrumbSpanExporter } from \"./exporter.js\";\nimport type { Breadcrumb, BreadcrumbSpan, SpanOptions } from \"./types.js\";\n\nexport type { Breadcrumb, BreadcrumbSpan, SpanOptions };\n\nexport interface InitOptions {\n apiKey: string;\n baseUrl: string;\n batching?:\n | false\n | {\n flushInterval?: number;\n maxBatchSize?: number;\n };\n}\n\nexport function init(options: InitOptions): Breadcrumb {\n const exporter = new BreadcrumbSpanExporter(options.apiKey, options.baseUrl);\n\n const batchOpts =\n typeof options.batching === \"object\" ? options.batching : undefined;\n\n const processor =\n options.batching === false\n ? new SimpleSpanProcessor(exporter)\n : new BatchSpanProcessor(exporter, {\n scheduledDelayMillis: batchOpts?.flushInterval ?? 5000,\n maxExportBatchSize: batchOpts?.maxBatchSize ?? 100,\n });\n\n const provider = new NodeTracerProvider({ spanProcessors: [processor] });\n provider.register();\n\n process.once(\"beforeExit\", async () => {\n await provider.shutdown().catch(() => {});\n });\n\n const tracer = provider.getTracer(\"@breadcrumb-sdk/core\");\n\n function runSpan<T>(\n otelSpan: OtelSpan,\n activeCtx: typeof ROOT_CONTEXT,\n fn: (span: BreadcrumbSpan) => Promise<T>,\n ): Promise<T> {\n const bcSpan: BreadcrumbSpan = {\n set(data) {\n const { metadata, ...semantic } = data;\n for (const [key, value] of Object.entries(semantic)) {\n if (value == null) continue;\n const attrKey = `breadcrumb.${key}`;\n if (typeof value === \"string\" || typeof value === \"number\" || typeof value === \"boolean\") {\n otelSpan.setAttribute(attrKey, value);\n } else {\n otelSpan.setAttribute(attrKey, JSON.stringify(value));\n }\n }\n if (metadata) {\n for (const [key, value] of Object.entries(metadata)) {\n if (value == null) continue;\n otelSpan.setAttribute(`breadcrumb.meta.${key}`, value);\n }\n }\n },\n };\n\n return context.with(trace.setSpan(activeCtx, otelSpan), async () => {\n try {\n const result = await fn(bcSpan);\n otelSpan.setStatus({ code: SpanStatusCode.OK });\n otelSpan.end();\n return result;\n } catch (err) {\n otelSpan.setStatus({\n code: SpanStatusCode.ERROR,\n message: err instanceof Error ? err.message : String(err),\n });\n otelSpan.end();\n throw err;\n }\n });\n }\n\n return {\n trace<T>(\n name: string,\n fn: (span: BreadcrumbSpan) => Promise<T>,\n ): Promise<T> {\n const otelSpan = tracer.startSpan(name, {}, ROOT_CONTEXT);\n return runSpan(otelSpan, ROOT_CONTEXT, fn);\n },\n\n span<T>(\n name: string,\n fn: (span: BreadcrumbSpan) => Promise<T>,\n options?: SpanOptions,\n ): Promise<T> {\n const activeCtx = context.active();\n const otelSpan = tracer.startSpan(name, {}, activeCtx);\n if (options?.type) {\n otelSpan.setAttribute(\"breadcrumb.span.type\", options.type);\n }\n return runSpan(otelSpan, activeCtx, fn);\n },\n };\n}\n"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@breadcrumb-sdk/core",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"files": [
|
|
6
|
+
"dist"
|
|
7
|
+
],
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"access": "public"
|
|
10
|
+
},
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"require": "./dist/index.cjs",
|
|
15
|
+
"types": "./dist/index.d.ts"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsdown",
|
|
20
|
+
"dev": "tsdown --watch",
|
|
21
|
+
"typecheck": "tsc --noEmit",
|
|
22
|
+
"test": "vitest run"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@opentelemetry/api": "^1.9.0",
|
|
26
|
+
"@opentelemetry/core": "^1.26.0",
|
|
27
|
+
"@opentelemetry/sdk-trace-base": "^1.26.0",
|
|
28
|
+
"@opentelemetry/sdk-trace-node": "^1.26.0"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"tsdown": "^0.9",
|
|
32
|
+
"typescript": "^5.7",
|
|
33
|
+
"vitest": "^3"
|
|
34
|
+
}
|
|
35
|
+
}
|