@breadcrumb-sdk/core 0.0.2 → 0.0.4
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/LICENSE +661 -0
- package/README.md +25 -3
- package/dist/index.cjs +221 -113
- package/dist/index.d.cts +23 -3
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +23 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +221 -113
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,6 +16,7 @@ import { init } from "@breadcrumb-sdk/core";
|
|
|
16
16
|
const bc = init({
|
|
17
17
|
apiKey: "bc_...",
|
|
18
18
|
baseUrl: "https://your-breadcrumb-instance.com",
|
|
19
|
+
environment: "production",
|
|
19
20
|
});
|
|
20
21
|
|
|
21
22
|
const answer = await bc.trace("answer-question", async (root) => {
|
|
@@ -54,6 +55,7 @@ Call once at startup. Returns a `bc` instance you use to create traces and spans
|
|
|
54
55
|
const bc = init({
|
|
55
56
|
apiKey: string,
|
|
56
57
|
baseUrl: string,
|
|
58
|
+
environment?: string, // e.g. "production", "staging", "development"
|
|
57
59
|
batching?: false | {
|
|
58
60
|
flushInterval?: number, // ms between sends (default: 5000)
|
|
59
61
|
maxBatchSize?: number, // spans per send (default: 100)
|
|
@@ -61,6 +63,8 @@ const bc = init({
|
|
|
61
63
|
})
|
|
62
64
|
```
|
|
63
65
|
|
|
66
|
+
Set `environment` once at init time to attach it to every root trace created by this SDK instance. This powers environment filtering in the Breadcrumb UI.
|
|
67
|
+
|
|
64
68
|
Set `batching: false` to send each span as it finishes. The default batches them. Either way, everything is flushed before the process exits.
|
|
65
69
|
|
|
66
70
|
---
|
|
@@ -123,17 +127,31 @@ span.set({
|
|
|
123
127
|
output_cost_usd: 0.00087,
|
|
124
128
|
metadata: {
|
|
125
129
|
score: 0.95,
|
|
126
|
-
|
|
130
|
+
region: "eu-central-1",
|
|
127
131
|
},
|
|
128
132
|
});
|
|
129
133
|
```
|
|
130
134
|
|
|
131
135
|
All fields are optional. `null` and `undefined` are ignored.
|
|
132
136
|
|
|
137
|
+
For `input`, passing a `Message[]` array renders the conversation with role labels in the UI — the same way AI SDK spans appear:
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
import type { Message } from "@breadcrumb-sdk/core";
|
|
141
|
+
|
|
142
|
+
span.set({
|
|
143
|
+
input: [
|
|
144
|
+
{ role: "system", content: "You are a helpful assistant." },
|
|
145
|
+
{ role: "user", content: "What is TypeScript?" },
|
|
146
|
+
] satisfies Message[],
|
|
147
|
+
output: "TypeScript is a typed superset of JavaScript.",
|
|
148
|
+
});
|
|
149
|
+
```
|
|
150
|
+
|
|
133
151
|
| Field | Type | Description |
|
|
134
152
|
|-------|------|-------------|
|
|
135
|
-
| `input` | `
|
|
136
|
-
| `output` | `
|
|
153
|
+
| `input` | `string \| Message[] \| object` | Input passed to this step |
|
|
154
|
+
| `output` | `string \| object` | Output produced by this step |
|
|
137
155
|
| `model` | `string` | Model name, e.g. `"gpt-4o"` |
|
|
138
156
|
| `provider` | `string` | Provider, e.g. `"openai"` |
|
|
139
157
|
| `input_tokens` | `number` | Input token count |
|
|
@@ -141,3 +159,7 @@ All fields are optional. `null` and `undefined` are ignored.
|
|
|
141
159
|
| `input_cost_usd` | `number` | Input cost in USD |
|
|
142
160
|
| `output_cost_usd` | `number` | Output cost in USD |
|
|
143
161
|
| `metadata` | `Record<string, string \| number \| boolean>` | Any extra data |
|
|
162
|
+
|
|
163
|
+
## License
|
|
164
|
+
|
|
165
|
+
AGPL-3.0 — see [LICENSE](./LICENSE) for details.
|
package/dist/index.cjs
CHANGED
|
@@ -27,7 +27,38 @@ const __opentelemetry_sdk_trace_node = __toESM(require("@opentelemetry/sdk-trace
|
|
|
27
27
|
const __opentelemetry_sdk_trace_base = __toESM(require("@opentelemetry/sdk-trace-base"));
|
|
28
28
|
const __opentelemetry_core = __toESM(require("@opentelemetry/core"));
|
|
29
29
|
|
|
30
|
-
//#region src/
|
|
30
|
+
//#region src/mappers/utils.ts
|
|
31
|
+
function tryJson(s) {
|
|
32
|
+
try {
|
|
33
|
+
return JSON.parse(s);
|
|
34
|
+
} catch {
|
|
35
|
+
return void 0;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function strAttr(attrs, ...keys) {
|
|
39
|
+
for (const key of keys) {
|
|
40
|
+
const v = attrs[key];
|
|
41
|
+
if (typeof v === "string" && v !== "") return v;
|
|
42
|
+
}
|
|
43
|
+
return void 0;
|
|
44
|
+
}
|
|
45
|
+
function intAttr(attrs, ...keys) {
|
|
46
|
+
for (const key of keys) {
|
|
47
|
+
const v = attrs[key];
|
|
48
|
+
if (typeof v === "number") return Math.round(v);
|
|
49
|
+
}
|
|
50
|
+
return void 0;
|
|
51
|
+
}
|
|
52
|
+
function floatAttr(attrs, ...keys) {
|
|
53
|
+
for (const key of keys) {
|
|
54
|
+
const v = attrs[key];
|
|
55
|
+
if (typeof v === "number") return v;
|
|
56
|
+
}
|
|
57
|
+
return void 0;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
//#endregion
|
|
61
|
+
//#region src/mappers/ai-sdk.ts
|
|
31
62
|
const LLM_SPAN_NAMES = new Set([
|
|
32
63
|
"ai.generateText",
|
|
33
64
|
"ai.generateText.doGenerate",
|
|
@@ -42,18 +73,15 @@ const TOOL_SPAN_NAMES = new Set([
|
|
|
42
73
|
"ai.toolExecution",
|
|
43
74
|
"ai.executeToolCall"
|
|
44
75
|
]);
|
|
45
|
-
const
|
|
46
|
-
"
|
|
47
|
-
"
|
|
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",
|
|
76
|
+
const HANDLED = new Set([
|
|
77
|
+
"resource.name",
|
|
78
|
+
"ai.prompt",
|
|
55
79
|
"ai.prompt.messages",
|
|
56
80
|
"ai.response.text",
|
|
81
|
+
"ai.response.toolCalls",
|
|
82
|
+
"ai.toolCall.name",
|
|
83
|
+
"ai.toolCall.args",
|
|
84
|
+
"ai.toolCall.result",
|
|
57
85
|
"ai.model.id",
|
|
58
86
|
"ai.model.provider",
|
|
59
87
|
"ai.response.model",
|
|
@@ -68,61 +96,25 @@ const HANDLED_ATTRS = new Set([
|
|
|
68
96
|
"ai.response.providerMetadata",
|
|
69
97
|
"ai.response.finishReason"
|
|
70
98
|
]);
|
|
71
|
-
const
|
|
99
|
+
const DROP = new Set([
|
|
72
100
|
"operation.name",
|
|
73
|
-
"resource.name",
|
|
74
101
|
"ai.operationId",
|
|
75
102
|
"ai.telemetry.functionId",
|
|
76
103
|
"gen_ai.response.finish_reasons",
|
|
77
104
|
"gen_ai.response.id",
|
|
78
105
|
"gen_ai.response.model",
|
|
79
106
|
"ai.response.id",
|
|
80
|
-
"ai.response.timestamp"
|
|
107
|
+
"ai.response.timestamp",
|
|
108
|
+
"ai.prompt.tools",
|
|
109
|
+
"ai.prompt.toolChoice",
|
|
110
|
+
"ai.toolCall.id"
|
|
81
111
|
]);
|
|
82
|
-
function
|
|
83
|
-
|
|
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";
|
|
112
|
+
function inferType(spanName) {
|
|
113
|
+
if (LLM_SPAN_NAMES.has(spanName)) return "llm";
|
|
114
|
+
if (TOOL_SPAN_NAMES.has(spanName)) return "tool";
|
|
121
115
|
return "custom";
|
|
122
116
|
}
|
|
123
|
-
function extractCost(
|
|
124
|
-
const raw = span.attributes["ai.response.providerMetadata"];
|
|
125
|
-
if (typeof raw !== "string") return {};
|
|
117
|
+
function extractCost(raw) {
|
|
126
118
|
try {
|
|
127
119
|
const parsed = tryJson(raw);
|
|
128
120
|
if (!parsed || typeof parsed !== "object") return {};
|
|
@@ -143,10 +135,164 @@ function extractCost(span) {
|
|
|
143
135
|
} catch {}
|
|
144
136
|
return {};
|
|
145
137
|
}
|
|
138
|
+
function mapAiSdk(span) {
|
|
139
|
+
const attrs = span.attributes;
|
|
140
|
+
const result = {};
|
|
141
|
+
const toolCallName = strAttr(attrs, "ai.toolCall.name");
|
|
142
|
+
const resourceName = strAttr(attrs, "resource.name");
|
|
143
|
+
if (toolCallName) result.name = toolCallName;
|
|
144
|
+
else if (resourceName) result.name = resourceName;
|
|
145
|
+
result.type = inferType(span.name);
|
|
146
|
+
const toolArgs = attrs["ai.toolCall.args"];
|
|
147
|
+
const aiMessages = attrs["ai.prompt.messages"];
|
|
148
|
+
const aiPrompt = attrs["ai.prompt"];
|
|
149
|
+
if (typeof toolArgs === "string") result.input = tryJson(toolArgs) ?? toolArgs;
|
|
150
|
+
else if (typeof aiMessages === "string") result.input = tryJson(aiMessages) ?? aiMessages;
|
|
151
|
+
else if (typeof aiPrompt === "string") {
|
|
152
|
+
const parsed = tryJson(aiPrompt);
|
|
153
|
+
if (parsed && typeof parsed === "object") {
|
|
154
|
+
const messages = [];
|
|
155
|
+
if (parsed["system"]) messages.push({
|
|
156
|
+
role: "system",
|
|
157
|
+
content: parsed["system"]
|
|
158
|
+
});
|
|
159
|
+
if (Array.isArray(parsed["messages"])) messages.push(...parsed["messages"]);
|
|
160
|
+
else if (parsed["prompt"] !== void 0) messages.push({
|
|
161
|
+
role: "user",
|
|
162
|
+
content: parsed["prompt"]
|
|
163
|
+
});
|
|
164
|
+
result.input = messages.length > 0 ? messages : parsed;
|
|
165
|
+
} else result.input = aiPrompt;
|
|
166
|
+
}
|
|
167
|
+
const toolResult = attrs["ai.toolCall.result"];
|
|
168
|
+
const aiText = attrs["ai.response.text"];
|
|
169
|
+
const aiToolCalls = attrs["ai.response.toolCalls"];
|
|
170
|
+
if (typeof toolResult === "string") result.output = tryJson(toolResult) ?? toolResult;
|
|
171
|
+
else if (typeof aiText === "string") result.output = aiText;
|
|
172
|
+
else if (typeof aiToolCalls === "string") {
|
|
173
|
+
const parsed = tryJson(aiToolCalls);
|
|
174
|
+
if (Array.isArray(parsed)) result.output = parsed.map((tc) => ({
|
|
175
|
+
...tc,
|
|
176
|
+
input: typeof tc["input"] === "string" ? tryJson(tc["input"]) ?? tc["input"] : tc["input"]
|
|
177
|
+
}));
|
|
178
|
+
else result.output = parsed ?? aiToolCalls;
|
|
179
|
+
}
|
|
180
|
+
const model = strAttr(attrs, "ai.model.id", "ai.response.model", "gen_ai.request.model");
|
|
181
|
+
if (model) result.model = model;
|
|
182
|
+
const provider = strAttr(attrs, "ai.model.provider", "gen_ai.system");
|
|
183
|
+
if (provider) result.provider = provider;
|
|
184
|
+
const input_tokens = intAttr(attrs, "ai.usage.inputTokens", "ai.usage.promptTokens", "gen_ai.usage.input_tokens");
|
|
185
|
+
if (input_tokens != null) result.input_tokens = input_tokens;
|
|
186
|
+
const output_tokens = intAttr(attrs, "ai.usage.outputTokens", "ai.usage.completionTokens", "gen_ai.usage.output_tokens");
|
|
187
|
+
if (output_tokens != null) result.output_tokens = output_tokens;
|
|
188
|
+
const providerMeta = attrs["ai.response.providerMetadata"];
|
|
189
|
+
if (typeof providerMeta === "string") {
|
|
190
|
+
const cost = extractCost(providerMeta);
|
|
191
|
+
if (cost.input_cost_usd != null) result.input_cost_usd = cost.input_cost_usd;
|
|
192
|
+
if (cost.output_cost_usd != null) result.output_cost_usd = cost.output_cost_usd;
|
|
193
|
+
}
|
|
194
|
+
const metadata = {};
|
|
195
|
+
const finishReason = attrs["ai.response.finishReason"];
|
|
196
|
+
if (typeof finishReason === "string" && finishReason !== "") metadata["finish_reason"] = finishReason;
|
|
197
|
+
for (const [k, v] of Object.entries(attrs)) {
|
|
198
|
+
if (v == null) continue;
|
|
199
|
+
if (HANDLED.has(k) || DROP.has(k)) continue;
|
|
200
|
+
if (k.startsWith("ai.settings.") || k.startsWith("ai.request.headers.")) continue;
|
|
201
|
+
if (k.startsWith("ai.telemetry.metadata.")) {
|
|
202
|
+
metadata[k.slice(22)] = String(v);
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
if (k.startsWith("ai.") || k.startsWith("gen_ai.")) metadata[k] = String(v);
|
|
206
|
+
}
|
|
207
|
+
if (Object.keys(metadata).length > 0) result.metadata = metadata;
|
|
208
|
+
return result;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
//#endregion
|
|
212
|
+
//#region src/mappers/breadcrumb.ts
|
|
213
|
+
const VALID_TYPES = new Set([
|
|
214
|
+
"llm",
|
|
215
|
+
"tool",
|
|
216
|
+
"retrieval",
|
|
217
|
+
"step",
|
|
218
|
+
"custom"
|
|
219
|
+
]);
|
|
220
|
+
function mapBreadcrumb(span) {
|
|
221
|
+
const attrs = span.attributes;
|
|
222
|
+
const result = {};
|
|
223
|
+
const explicitType = attrs["breadcrumb.span.type"];
|
|
224
|
+
if (typeof explicitType === "string" && VALID_TYPES.has(explicitType)) result.type = explicitType;
|
|
225
|
+
const rawInput = attrs["breadcrumb.input"];
|
|
226
|
+
if (rawInput != null) result.input = typeof rawInput === "string" ? tryJson(rawInput) ?? rawInput : rawInput;
|
|
227
|
+
const rawOutput = attrs["breadcrumb.output"];
|
|
228
|
+
if (rawOutput != null) result.output = typeof rawOutput === "string" ? tryJson(rawOutput) ?? rawOutput : rawOutput;
|
|
229
|
+
const model = strAttr(attrs, "breadcrumb.model");
|
|
230
|
+
if (model) result.model = model;
|
|
231
|
+
const provider = strAttr(attrs, "breadcrumb.provider");
|
|
232
|
+
if (provider) result.provider = provider;
|
|
233
|
+
const input_tokens = intAttr(attrs, "breadcrumb.input_tokens");
|
|
234
|
+
if (input_tokens != null) result.input_tokens = input_tokens;
|
|
235
|
+
const output_tokens = intAttr(attrs, "breadcrumb.output_tokens");
|
|
236
|
+
if (output_tokens != null) result.output_tokens = output_tokens;
|
|
237
|
+
const input_cost_usd = floatAttr(attrs, "breadcrumb.input_cost_usd");
|
|
238
|
+
if (input_cost_usd != null) result.input_cost_usd = input_cost_usd;
|
|
239
|
+
const output_cost_usd = floatAttr(attrs, "breadcrumb.output_cost_usd");
|
|
240
|
+
if (output_cost_usd != null) result.output_cost_usd = output_cost_usd;
|
|
241
|
+
const metadata = {};
|
|
242
|
+
for (const [k, v] of Object.entries(attrs)) {
|
|
243
|
+
if (v == null) continue;
|
|
244
|
+
if (k.startsWith("breadcrumb.meta.")) metadata[k.slice(16)] = String(v);
|
|
245
|
+
}
|
|
246
|
+
if (Object.keys(metadata).length > 0) result.metadata = metadata;
|
|
247
|
+
return result;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
//#endregion
|
|
251
|
+
//#region src/exporter.ts
|
|
252
|
+
const MAPPER_NAMESPACES = [
|
|
253
|
+
"ai.",
|
|
254
|
+
"gen_ai.",
|
|
255
|
+
"breadcrumb.",
|
|
256
|
+
"resource.name"
|
|
257
|
+
];
|
|
258
|
+
const GLOBAL_DROP = new Set(["operation.name"]);
|
|
259
|
+
function hrTimeToISO(hrTime) {
|
|
260
|
+
return new Date(hrTime[0] * 1e3 + hrTime[1] / 1e6).toISOString();
|
|
261
|
+
}
|
|
262
|
+
function spanStatus(span) {
|
|
263
|
+
return span.status.code === __opentelemetry_api.SpanStatusCode.ERROR ? "error" : "ok";
|
|
264
|
+
}
|
|
265
|
+
function mergeMappers(span) {
|
|
266
|
+
const aiSdk = mapAiSdk(span);
|
|
267
|
+
const bc = mapBreadcrumb(span);
|
|
268
|
+
return {
|
|
269
|
+
...aiSdk,
|
|
270
|
+
...bc,
|
|
271
|
+
metadata: aiSdk.metadata || bc.metadata ? {
|
|
272
|
+
...aiSdk.metadata,
|
|
273
|
+
...bc.metadata
|
|
274
|
+
} : void 0
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
function passthroughMetadata(span, existing) {
|
|
278
|
+
const extra = {};
|
|
279
|
+
for (const [k, v] of Object.entries(span.attributes)) {
|
|
280
|
+
if (v == null) continue;
|
|
281
|
+
const handled = MAPPER_NAMESPACES.some((ns) => k === ns || k.startsWith(ns));
|
|
282
|
+
if (handled || GLOBAL_DROP.has(k)) continue;
|
|
283
|
+
extra[k] = String(v);
|
|
284
|
+
}
|
|
285
|
+
if (Object.keys(extra).length === 0) return existing || void 0;
|
|
286
|
+
return {
|
|
287
|
+
...extra,
|
|
288
|
+
...existing
|
|
289
|
+
};
|
|
290
|
+
}
|
|
146
291
|
var BreadcrumbSpanExporter = class {
|
|
147
|
-
constructor(apiKey, baseUrl) {
|
|
292
|
+
constructor(apiKey, baseUrl, environment) {
|
|
148
293
|
this.apiKey = apiKey;
|
|
149
294
|
this.baseUrl = baseUrl;
|
|
295
|
+
this.environment = environment;
|
|
150
296
|
}
|
|
151
297
|
export(spans, resultCallback) {
|
|
152
298
|
this._export(spans).then(() => resultCallback({ code: __opentelemetry_core.ExportResultCode.SUCCESS }), () => resultCallback({ code: __opentelemetry_core.ExportResultCode.SUCCESS }));
|
|
@@ -162,78 +308,40 @@ var BreadcrumbSpanExporter = class {
|
|
|
162
308
|
}
|
|
163
309
|
_mapSpan(span) {
|
|
164
310
|
const ctx = span.spanContext();
|
|
165
|
-
const
|
|
166
|
-
|
|
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
|
-
}
|
|
311
|
+
const mapped = mergeMappers(span);
|
|
312
|
+
const metadata = passthroughMetadata(span, mapped.metadata);
|
|
207
313
|
return {
|
|
208
314
|
id: ctx.spanId,
|
|
209
315
|
trace_id: ctx.traceId,
|
|
210
316
|
parent_span_id: span.parentSpanId || void 0,
|
|
211
|
-
name: span.name,
|
|
212
|
-
type:
|
|
317
|
+
name: mapped.name ?? span.name,
|
|
318
|
+
type: mapped.type ?? "custom",
|
|
213
319
|
start_time: hrTimeToISO(span.startTime),
|
|
214
320
|
end_time: hrTimeToISO(span.endTime),
|
|
215
321
|
status: spanStatus(span),
|
|
216
322
|
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
|
|
323
|
+
input: mapped.input !== void 0 ? mapped.input : void 0,
|
|
324
|
+
output: mapped.output !== void 0 ? mapped.output : void 0,
|
|
325
|
+
provider: mapped.provider,
|
|
326
|
+
model: mapped.model,
|
|
327
|
+
input_tokens: mapped.input_tokens,
|
|
328
|
+
output_tokens: mapped.output_tokens,
|
|
329
|
+
input_cost_usd: mapped.input_cost_usd,
|
|
330
|
+
output_cost_usd: mapped.output_cost_usd,
|
|
331
|
+
metadata: metadata && Object.keys(metadata).length > 0 ? metadata : void 0
|
|
226
332
|
};
|
|
227
333
|
}
|
|
228
334
|
async _sendTrace(span) {
|
|
229
335
|
const ctx = span.spanContext();
|
|
336
|
+
const mapped = mergeMappers(span);
|
|
230
337
|
await this._post("/v1/traces", {
|
|
231
338
|
id: ctx.traceId,
|
|
232
|
-
name: span.name,
|
|
339
|
+
name: mapped.name ?? span.name,
|
|
233
340
|
start_time: hrTimeToISO(span.startTime),
|
|
234
341
|
end_time: hrTimeToISO(span.endTime),
|
|
235
342
|
status: spanStatus(span),
|
|
236
|
-
status_message: span.status.message || void 0
|
|
343
|
+
status_message: span.status.message || void 0,
|
|
344
|
+
environment: this.environment
|
|
237
345
|
});
|
|
238
346
|
}
|
|
239
347
|
async _post(path, body) {
|
|
@@ -259,7 +367,7 @@ var BreadcrumbSpanExporter = class {
|
|
|
259
367
|
//#endregion
|
|
260
368
|
//#region src/index.ts
|
|
261
369
|
function init(options) {
|
|
262
|
-
const exporter = new BreadcrumbSpanExporter(options.apiKey, options.baseUrl);
|
|
370
|
+
const exporter = new BreadcrumbSpanExporter(options.apiKey, options.baseUrl, options.environment);
|
|
263
371
|
const batchOpts = typeof options.batching === "object" ? options.batching : void 0;
|
|
264
372
|
const processor = options.batching === false ? new __opentelemetry_sdk_trace_base.SimpleSpanProcessor(exporter) : new __opentelemetry_sdk_trace_base.BatchSpanProcessor(exporter, {
|
|
265
373
|
scheduledDelayMillis: batchOpts?.flushInterval ?? 5e3,
|
package/dist/index.d.cts
CHANGED
|
@@ -1,7 +1,25 @@
|
|
|
1
1
|
//#region src/types.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* A single message in a conversation. Pass an array of these as `input` to
|
|
4
|
+
* display the conversation in the Breadcrumb UI the same way LLM spans appear.
|
|
5
|
+
*/
|
|
6
|
+
interface Message {
|
|
7
|
+
role: "system" | "user" | "assistant" | "tool";
|
|
8
|
+
content: string;
|
|
9
|
+
}
|
|
2
10
|
interface SpanData {
|
|
3
|
-
|
|
4
|
-
|
|
11
|
+
/**
|
|
12
|
+
* The input to this step. Use a plain string for simple inputs, or a
|
|
13
|
+
* `Message[]` array for conversation-style inputs — the UI renders both
|
|
14
|
+
* with role labels (system / user / assistant).
|
|
15
|
+
* Any other JSON-serializable value is also accepted and shown as raw data.
|
|
16
|
+
*/
|
|
17
|
+
input?: string | Message[] | Record<string, unknown>;
|
|
18
|
+
/**
|
|
19
|
+
* The output from this step. Use a plain string for text outputs.
|
|
20
|
+
* Any JSON-serializable value is accepted.
|
|
21
|
+
*/
|
|
22
|
+
output?: string | Record<string, unknown>;
|
|
5
23
|
model?: string;
|
|
6
24
|
provider?: string;
|
|
7
25
|
input_tokens?: number;
|
|
@@ -21,10 +39,12 @@ interface Breadcrumb {
|
|
|
21
39
|
span<T>(name: string, fn: (span: BreadcrumbSpan) => Promise<T>, options?: SpanOptions): Promise<T>;
|
|
22
40
|
} //#endregion
|
|
23
41
|
//#region src/index.d.ts
|
|
42
|
+
|
|
24
43
|
//# sourceMappingURL=types.d.ts.map
|
|
25
44
|
interface InitOptions {
|
|
26
45
|
apiKey: string;
|
|
27
46
|
baseUrl: string;
|
|
47
|
+
environment?: string;
|
|
28
48
|
batching?: false | {
|
|
29
49
|
flushInterval?: number;
|
|
30
50
|
maxBatchSize?: number;
|
|
@@ -35,5 +55,5 @@ declare function init(options: InitOptions): Breadcrumb;
|
|
|
35
55
|
//#endregion
|
|
36
56
|
//# sourceMappingURL=index.d.ts.map
|
|
37
57
|
|
|
38
|
-
export { Breadcrumb, BreadcrumbSpan, InitOptions, SpanOptions, init };
|
|
58
|
+
export { Breadcrumb, BreadcrumbSpan, InitOptions, Message, SpanData, SpanOptions, init };
|
|
39
59
|
//# sourceMappingURL=index.d.cts.map
|
package/dist/index.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/types.ts","../src/index.ts"],"sourcesContent":null,"mappings":";
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/types.ts","../src/index.ts"],"sourcesContent":null,"mappings":";;;;;UAIiB,OAAA;EAAA,IAAA,EAAA,QAAO,GAAA,MAAA,GAAA,WAAA,GAAA,MAAA;EAKP,OAAA,EAAA,MAAQ;;AAON,UAPF,QAAA,CAOE;EAAO;;;AAYP;AAGnB;AAIA;EAIiB,KAAA,CAAA,EAAA,MAAU,GAvBR,OAuBQ,EAAA,GAvBI,MAuBJ,CAAA,MAAA,EAAA,OAAA,CAAA;EAAA;;;;EACmC,MAAc,CAAA,EAAA,MAAA,GAnBxD,MAmBwD,CAAA,MAAA,EAAA,OAAA,CAAA;EAAC,KAAT,CAAA,EAAA,MAAA;EAAO,QAG5D,CAAA,EAAA,MAAA;EAAc,YAAa,CAAA,EAAA,MAAA;EAAC,aAAT,CAAA,EAAA,MAAA;EAAO,cAC3B,CAAA,EAAA,MAAA;EAAW,eACZ,CAAA,EAAA,MAAA;EAAC,QAAT,CAAA,EAjBQ,MAiBR,CAAA,MAAA,EAAA,MAAA,GAAA,MAAA,GAAA,OAAA,CAAA;AAAO;UAdK,cAAA;YACL;;ACfK,UDkBA,WAAA,CClBW;EAYZ,IAAA,CAAA,EAAI,KAAA,GAAA,MAAA,GAAA,WAAA,GAAA,MAAA;;AAAU,UDUb,UAAA,CCVa;EAAW,KAAG,CAAA,CAAA,CAAA,CAAA,IAAA,EAAA,MAAA,EAAA,EAAA,EAAA,CAAA,IAAA,EDWR,cCXQ,EAAA,GDWW,OCXX,CDWmB,CCXnB,CAAA,CAAA,EDWwB,OCXxB,CDWgC,CCXhC,CAAA;EAAU,IAAA,CAAA,CAAA,CAAA,CAAA,IAAA,EAAA,MAAA,EAAA,EAAA,EAAA,CAAA,IAAA,EDcvC,cCduC,EAAA,GDcpB,OCdoB,CDcZ,CCdY,CAAA,EAAA,OAAA,CAAA,EDexC,WCfwC,CAAA,EDgBjD,OChBiD,CDgBzC,CChByC,CAAA;;;;;UAZrC,WAAA;;;EDbA,WAAO,CAAA,EAAA,MAAA;EAKP,QAAA,CAAA,EAAQ,KAAA,GAAA;IAAA,aAAA,CAAA,EAAA,MAAA;IAON,YAAA,CAAA,EAAA,MAAA;EAAO,CAAA;;AAYb,iBCCG,IAAA,CDDH,OAAA,ECCiB,WDDjB,CAAA,ECC+B,UDD/B;;;AAAM"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,25 @@
|
|
|
1
1
|
//#region src/types.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* A single message in a conversation. Pass an array of these as `input` to
|
|
4
|
+
* display the conversation in the Breadcrumb UI the same way LLM spans appear.
|
|
5
|
+
*/
|
|
6
|
+
interface Message {
|
|
7
|
+
role: "system" | "user" | "assistant" | "tool";
|
|
8
|
+
content: string;
|
|
9
|
+
}
|
|
2
10
|
interface SpanData {
|
|
3
|
-
|
|
4
|
-
|
|
11
|
+
/**
|
|
12
|
+
* The input to this step. Use a plain string for simple inputs, or a
|
|
13
|
+
* `Message[]` array for conversation-style inputs — the UI renders both
|
|
14
|
+
* with role labels (system / user / assistant).
|
|
15
|
+
* Any other JSON-serializable value is also accepted and shown as raw data.
|
|
16
|
+
*/
|
|
17
|
+
input?: string | Message[] | Record<string, unknown>;
|
|
18
|
+
/**
|
|
19
|
+
* The output from this step. Use a plain string for text outputs.
|
|
20
|
+
* Any JSON-serializable value is accepted.
|
|
21
|
+
*/
|
|
22
|
+
output?: string | Record<string, unknown>;
|
|
5
23
|
model?: string;
|
|
6
24
|
provider?: string;
|
|
7
25
|
input_tokens?: number;
|
|
@@ -21,10 +39,12 @@ interface Breadcrumb {
|
|
|
21
39
|
span<T>(name: string, fn: (span: BreadcrumbSpan) => Promise<T>, options?: SpanOptions): Promise<T>;
|
|
22
40
|
} //#endregion
|
|
23
41
|
//#region src/index.d.ts
|
|
42
|
+
|
|
24
43
|
//# sourceMappingURL=types.d.ts.map
|
|
25
44
|
interface InitOptions {
|
|
26
45
|
apiKey: string;
|
|
27
46
|
baseUrl: string;
|
|
47
|
+
environment?: string;
|
|
28
48
|
batching?: false | {
|
|
29
49
|
flushInterval?: number;
|
|
30
50
|
maxBatchSize?: number;
|
|
@@ -35,5 +55,5 @@ declare function init(options: InitOptions): Breadcrumb;
|
|
|
35
55
|
//#endregion
|
|
36
56
|
//# sourceMappingURL=index.d.ts.map
|
|
37
57
|
|
|
38
|
-
export { Breadcrumb, BreadcrumbSpan, InitOptions, SpanOptions, init };
|
|
58
|
+
export { Breadcrumb, BreadcrumbSpan, InitOptions, Message, SpanData, SpanOptions, init };
|
|
39
59
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/types.ts","../src/index.ts"],"sourcesContent":null,"mappings":";
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/types.ts","../src/index.ts"],"sourcesContent":null,"mappings":";;;;;UAIiB,OAAA;EAAA,IAAA,EAAA,QAAO,GAAA,MAAA,GAAA,WAAA,GAAA,MAAA;EAKP,OAAA,EAAA,MAAQ;;AAON,UAPF,QAAA,CAOE;EAAO;;;AAYP;AAGnB;AAIA;EAIiB,KAAA,CAAA,EAAA,MAAU,GAvBR,OAuBQ,EAAA,GAvBI,MAuBJ,CAAA,MAAA,EAAA,OAAA,CAAA;EAAA;;;;EACmC,MAAc,CAAA,EAAA,MAAA,GAnBxD,MAmBwD,CAAA,MAAA,EAAA,OAAA,CAAA;EAAC,KAAT,CAAA,EAAA,MAAA;EAAO,QAG5D,CAAA,EAAA,MAAA;EAAc,YAAa,CAAA,EAAA,MAAA;EAAC,aAAT,CAAA,EAAA,MAAA;EAAO,cAC3B,CAAA,EAAA,MAAA;EAAW,eACZ,CAAA,EAAA,MAAA;EAAC,QAAT,CAAA,EAjBQ,MAiBR,CAAA,MAAA,EAAA,MAAA,GAAA,MAAA,GAAA,OAAA,CAAA;AAAO;UAdK,cAAA;YACL;;ACfK,UDkBA,WAAA,CClBW;EAYZ,IAAA,CAAA,EAAI,KAAA,GAAA,MAAA,GAAA,WAAA,GAAA,MAAA;;AAAU,UDUb,UAAA,CCVa;EAAW,KAAG,CAAA,CAAA,CAAA,CAAA,IAAA,EAAA,MAAA,EAAA,EAAA,EAAA,CAAA,IAAA,EDWR,cCXQ,EAAA,GDWW,OCXX,CDWmB,CCXnB,CAAA,CAAA,EDWwB,OCXxB,CDWgC,CCXhC,CAAA;EAAU,IAAA,CAAA,CAAA,CAAA,CAAA,IAAA,EAAA,MAAA,EAAA,EAAA,EAAA,CAAA,IAAA,EDcvC,cCduC,EAAA,GDcpB,OCdoB,CDcZ,CCdY,CAAA,EAAA,OAAA,CAAA,EDexC,WCfwC,CAAA,EDgBjD,OChBiD,CDgBzC,CChByC,CAAA;;;;;UAZrC,WAAA;;;EDbA,WAAO,CAAA,EAAA,MAAA;EAKP,QAAA,CAAA,EAAQ,KAAA,GAAA;IAAA,aAAA,CAAA,EAAA,MAAA;IAON,YAAA,CAAA,EAAA,MAAA;EAAO,CAAA;;AAYb,iBCCG,IAAA,CDDH,OAAA,ECCiB,WDDjB,CAAA,ECC+B,UDD/B;;;AAAM"}
|