@traceglass/otel 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +70 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/map.d.ts +52 -0
- package/dist/map.d.ts.map +1 -0
- package/dist/map.js +157 -0
- package/dist/map.js.map +1 -0
- package/dist/processor.d.ts +84 -0
- package/dist/processor.d.ts.map +1 -0
- package/dist/processor.js +131 -0
- package/dist/processor.js.map +1 -0
- package/dist/span-types.d.ts +62 -0
- package/dist/span-types.d.ts.map +1 -0
- package/dist/span-types.js +25 -0
- package/dist/span-types.js.map +1 -0
- package/dist/type-compat.d.ts +2 -0
- package/dist/type-compat.d.ts.map +1 -0
- package/dist/type-compat.js +10 -0
- package/dist/type-compat.js.map +1 -0
- package/dist/writer.d.ts +79 -0
- package/dist/writer.d.ts.map +1 -0
- package/dist/writer.js +140 -0
- package/dist/writer.js.map +1 -0
- package/package.json +31 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Rahul Bhardwaj
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# @traceglass/otel
|
|
2
|
+
|
|
3
|
+
An OpenTelemetry **span processor** that writes [traceglass](https://www.npmjs.com/package/traceglass)
|
|
4
|
+
runs. If your agent already emits `gen_ai.*` spans, this turns them into hash-chained,
|
|
5
|
+
signed records from a config change — no code edits, no collector, no network.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @traceglass/otel
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Register it — that is the whole integration
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
|
|
15
|
+
import { TraceglassSpanProcessor } from '@traceglass/otel';
|
|
16
|
+
|
|
17
|
+
const provider = new NodeTracerProvider({
|
|
18
|
+
spanProcessors: [new TraceglassSpanProcessor({ currency: 'INR' })],
|
|
19
|
+
});
|
|
20
|
+
provider.register();
|
|
21
|
+
|
|
22
|
+
// ...your instrumented agent runs unchanged...
|
|
23
|
+
|
|
24
|
+
await provider.shutdown(); // finalizes any still-open run
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
One **trace** becomes one run, id `otel-<traceId>`, finalized when its root span ends.
|
|
28
|
+
Verify with `npx traceglass verify otel-<traceId>`, replay with `npx traceglass open`.
|
|
29
|
+
|
|
30
|
+
## Span → step mapping
|
|
31
|
+
|
|
32
|
+
Same attributes the offline OTLP ingester consumes (`traceglass ingest trace.json`), so a
|
|
33
|
+
span records identically whichever path it takes:
|
|
34
|
+
|
|
35
|
+
| Step field | Attribute |
|
|
36
|
+
| ------------------------ | ------------------------------------------------------------------------------------- |
|
|
37
|
+
| `type` | `traceglass.step.type`, else ERROR status → `error`, else inferred from the span name |
|
|
38
|
+
| `label` | `traceglass.step.label`, else the span name |
|
|
39
|
+
| `toolName` | `gen_ai.tool.name` / `traceglass.tool.name` |
|
|
40
|
+
| `tokens` | `gen_ai.usage.total_tokens`, else input + output tokens |
|
|
41
|
+
| `cost` | `traceglass.cost` |
|
|
42
|
+
| `input` | `gen_ai.prompt` / `traceglass.input` (JSON strings are re-inflated) |
|
|
43
|
+
| `output` | `gen_ai.completion` / `traceglass.output` |
|
|
44
|
+
| `dataPayload` | `traceglass.data_payload`; otherwise every remaining attribute under `attributes` |
|
|
45
|
+
| `spanId`, `parentSpanId` | the real OTel ids, so a step points back at its span |
|
|
46
|
+
|
|
47
|
+
Run name and currency come from the resource (`traceglass.run.name`, `service.name`,
|
|
48
|
+
`traceglass.run.currency`) unless you pass `name` / `currency`.
|
|
49
|
+
|
|
50
|
+
That last payload row is why `gen_ai.request.model` survives into the record. Those keys
|
|
51
|
+
are **dotted**, which is exactly the payload shape `tgcanon/2` had to fix — the tests
|
|
52
|
+
record real dotted semantic-convention keys and then redact one, leaving the anchor and
|
|
53
|
+
the chain intact.
|
|
54
|
+
|
|
55
|
+
## Options worth knowing
|
|
56
|
+
|
|
57
|
+
- `filter: (span) => boolean` — record only the spans you care about.
|
|
58
|
+
- `recordAttributes: false` — skip the attribute snapshot.
|
|
59
|
+
- `redactPatterns: ['email', 'credit-card']` — scrub at capture time, before hashing.
|
|
60
|
+
- `dir: null` — record in memory only; `onRun` still receives each finalized run.
|
|
61
|
+
- `onError` — recording failures are reported here (default: `process.emitWarning`) and
|
|
62
|
+
never thrown into your telemetry pipeline.
|
|
63
|
+
|
|
64
|
+
- **Capture order is span-end order** and is never re-sorted; the chain is fixed as it is
|
|
65
|
+
written.
|
|
66
|
+
- **Crash-safe:** an interrupted run leaves a journal `npx traceglass recover` finalizes
|
|
67
|
+
into a `failed` run that still verifies up to the crash point.
|
|
68
|
+
- **Local-first:** written under `~/.traceglass` (or `TRACEGLASS_HOME`). Zero network
|
|
69
|
+
egress, and no `@opentelemetry/*` production dependency — the processor is structurally
|
|
70
|
+
typed against the SDK interfaces, so it runs against whatever OTel version you have.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { TraceglassSpanProcessor, createTraceglassSpanProcessor, type TraceglassSpanProcessorOptions, } from './processor.js';
|
|
2
|
+
export { MAPPED_ATTRIBUTES, SPAN_STATUS_ERROR, attributeSnapshot, deriveStepType, maybeJson, spanToStep, tokensFrom, type SpanMapOptions, } from './map.js';
|
|
3
|
+
export { hrToMs, parentSpanIdOf, type AttributesLike, type HrTimeLike, type RecordableSpan, type SpanContextLike, type SpanProcessorLike, } from './span-types.js';
|
|
4
|
+
export { RunWriter, type RunWriterOptions, type WriterStepInput } from './writer.js';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,uBAAuB,EACvB,6BAA6B,EAC7B,KAAK,8BAA8B,GACpC,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EACjB,cAAc,EACd,SAAS,EACT,UAAU,EACV,UAAU,EACV,KAAK,cAAc,GACpB,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,MAAM,EACN,cAAc,EACd,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,iBAAiB,GACvB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,SAAS,EAAE,KAAK,gBAAgB,EAAE,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Public barrel for @traceglass/otel.
|
|
2
|
+
export { TraceglassSpanProcessor, createTraceglassSpanProcessor, } from './processor.js';
|
|
3
|
+
export { MAPPED_ATTRIBUTES, SPAN_STATUS_ERROR, attributeSnapshot, deriveStepType, maybeJson, spanToStep, tokensFrom, } from './map.js';
|
|
4
|
+
export { hrToMs, parentSpanIdOf, } from './span-types.js';
|
|
5
|
+
export { RunWriter } from './writer.js';
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,OAAO,EACL,uBAAuB,EACvB,6BAA6B,GAE9B,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EACjB,cAAc,EACd,SAAS,EACT,UAAU,EACV,UAAU,GAEX,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,MAAM,EACN,cAAc,GAMf,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,SAAS,EAA+C,MAAM,aAAa,CAAC"}
|
package/dist/map.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { type StepType } from '@traceglass/core';
|
|
2
|
+
import { type AttributesLike, type RecordableSpan } from './span-types.js';
|
|
3
|
+
import type { WriterStepInput } from './writer.js';
|
|
4
|
+
/**
|
|
5
|
+
* Span → step mapping.
|
|
6
|
+
*
|
|
7
|
+
* The attribute names are deliberately the SAME set the offline OTLP ingester
|
|
8
|
+
* consumes (`packages/core/src/ingest/otel.ts`): `gen_ai.*` where a semantic
|
|
9
|
+
* convention exists, `traceglass.*` for the audit fields OTel has no convention
|
|
10
|
+
* for. A span that produces a given step through `traceglass ingest` produces
|
|
11
|
+
* the same step through this processor.
|
|
12
|
+
*
|
|
13
|
+
* Two deliberate additions over the ingester, both documented in the README:
|
|
14
|
+
* 1. a span whose status code is ERROR (2) becomes an `error` step, so a
|
|
15
|
+
* failed run is recorded as failed. The ingester ignores span status.
|
|
16
|
+
* 2. attributes not copied verbatim into a step field are recorded as
|
|
17
|
+
* `dataPayload.attributes` (unless `traceglass.data_payload` is set), so
|
|
18
|
+
* `gen_ai.request.model` and friends survive into the record instead of
|
|
19
|
+
* being dropped. Those keys are DOTTED, which is precisely the payload
|
|
20
|
+
* shape `tgcanon/2` had to fix — see the tests.
|
|
21
|
+
*/
|
|
22
|
+
/** OTel `SpanStatusCode.ERROR`. */
|
|
23
|
+
export declare const SPAN_STATUS_ERROR = 2;
|
|
24
|
+
/** Attributes copied verbatim into a step field, hence excluded from the snapshot. */
|
|
25
|
+
export declare const MAPPED_ATTRIBUTES: readonly ["traceglass.step.type", "traceglass.step.label", "traceglass.tool.name", "traceglass.cost", "traceglass.input", "traceglass.output", "traceglass.data_payload", "gen_ai.tool.name", "gen_ai.prompt", "gen_ai.completion"];
|
|
26
|
+
/**
|
|
27
|
+
* Parse a value that may be a JSON string into structured data, else return it
|
|
28
|
+
* as-is. Same rule as the ingester: attributes are flat scalars on the wire, so
|
|
29
|
+
* a JSON-encoded prompt or payload has to be re-inflated to be readable (and
|
|
30
|
+
* redactable leaf by leaf) in the record.
|
|
31
|
+
*/
|
|
32
|
+
export declare function maybeJson(value: unknown): unknown;
|
|
33
|
+
/** Token count: the reported total, else input+output, else the legacy names. */
|
|
34
|
+
export declare function tokensFrom(attrs: AttributesLike): number;
|
|
35
|
+
/**
|
|
36
|
+
* Step type: an explicit `traceglass.step.type` wins, then an ERROR span
|
|
37
|
+
* status, then the ingester's heuristics over the gen_ai attributes and the
|
|
38
|
+
* span name.
|
|
39
|
+
*/
|
|
40
|
+
export declare function deriveStepType(attrs: AttributesLike, name: string, statusCode?: number): StepType;
|
|
41
|
+
/**
|
|
42
|
+
* Attributes not already copied into a step field, dotted keys intact.
|
|
43
|
+
* Undefined values are dropped (OTel allows them; JSON does not carry them).
|
|
44
|
+
*/
|
|
45
|
+
export declare function attributeSnapshot(attrs: AttributesLike): Record<string, unknown>;
|
|
46
|
+
export interface SpanMapOptions {
|
|
47
|
+
/** Record unmapped span attributes as `dataPayload.attributes` (default true). */
|
|
48
|
+
recordAttributes?: boolean;
|
|
49
|
+
}
|
|
50
|
+
/** Map one ended span onto the step the writer will chain. */
|
|
51
|
+
export declare function spanToStep(span: RecordableSpan, opts?: SpanMapOptions): WriterStepInput;
|
|
52
|
+
//# sourceMappingURL=map.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"map.d.ts","sourceRoot":"","sources":["../src/map.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACjE,OAAO,EAA0B,KAAK,cAAc,EAAE,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACnG,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD;;;;;;;;;;;;;;;;;GAiBG;AAEH,mCAAmC;AACnC,eAAO,MAAM,iBAAiB,IAAI,CAAC;AAEnC,sFAAsF;AACtF,eAAO,MAAM,iBAAiB,qOAWpB,CAAC;AAYX;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAWjD;AAED,iFAAiF;AACjF,wBAAgB,UAAU,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,CAOxD;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,QAAQ,CAcjG;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAUhF;AAED,MAAM,WAAW,cAAc;IAC7B,kFAAkF;IAClF,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,8DAA8D;AAC9D,wBAAgB,UAAU,CAAC,IAAI,EAAE,cAAc,EAAE,IAAI,GAAE,cAAmB,GAAG,eAAe,CAoC3F"}
|
package/dist/map.js
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { StepTypeSchema } from '@traceglass/core';
|
|
2
|
+
import { hrToMs, parentSpanIdOf } from './span-types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Span → step mapping.
|
|
5
|
+
*
|
|
6
|
+
* The attribute names are deliberately the SAME set the offline OTLP ingester
|
|
7
|
+
* consumes (`packages/core/src/ingest/otel.ts`): `gen_ai.*` where a semantic
|
|
8
|
+
* convention exists, `traceglass.*` for the audit fields OTel has no convention
|
|
9
|
+
* for. A span that produces a given step through `traceglass ingest` produces
|
|
10
|
+
* the same step through this processor.
|
|
11
|
+
*
|
|
12
|
+
* Two deliberate additions over the ingester, both documented in the README:
|
|
13
|
+
* 1. a span whose status code is ERROR (2) becomes an `error` step, so a
|
|
14
|
+
* failed run is recorded as failed. The ingester ignores span status.
|
|
15
|
+
* 2. attributes not copied verbatim into a step field are recorded as
|
|
16
|
+
* `dataPayload.attributes` (unless `traceglass.data_payload` is set), so
|
|
17
|
+
* `gen_ai.request.model` and friends survive into the record instead of
|
|
18
|
+
* being dropped. Those keys are DOTTED, which is precisely the payload
|
|
19
|
+
* shape `tgcanon/2` had to fix — see the tests.
|
|
20
|
+
*/
|
|
21
|
+
/** OTel `SpanStatusCode.ERROR`. */
|
|
22
|
+
export const SPAN_STATUS_ERROR = 2;
|
|
23
|
+
/** Attributes copied verbatim into a step field, hence excluded from the snapshot. */
|
|
24
|
+
export const MAPPED_ATTRIBUTES = [
|
|
25
|
+
'traceglass.step.type',
|
|
26
|
+
'traceglass.step.label',
|
|
27
|
+
'traceglass.tool.name',
|
|
28
|
+
'traceglass.cost',
|
|
29
|
+
'traceglass.input',
|
|
30
|
+
'traceglass.output',
|
|
31
|
+
'traceglass.data_payload',
|
|
32
|
+
'gen_ai.tool.name',
|
|
33
|
+
'gen_ai.prompt',
|
|
34
|
+
'gen_ai.completion',
|
|
35
|
+
];
|
|
36
|
+
function str(value) {
|
|
37
|
+
return typeof value === 'string' ? value : undefined;
|
|
38
|
+
}
|
|
39
|
+
/** Finite non-negative number, else 0 — step fields reject anything else. */
|
|
40
|
+
function num(value) {
|
|
41
|
+
const n = typeof value === 'number' ? value : typeof value === 'string' ? Number(value) : NaN;
|
|
42
|
+
return Number.isFinite(n) && n >= 0 ? n : 0;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Parse a value that may be a JSON string into structured data, else return it
|
|
46
|
+
* as-is. Same rule as the ingester: attributes are flat scalars on the wire, so
|
|
47
|
+
* a JSON-encoded prompt or payload has to be re-inflated to be readable (and
|
|
48
|
+
* redactable leaf by leaf) in the record.
|
|
49
|
+
*/
|
|
50
|
+
export function maybeJson(value) {
|
|
51
|
+
if (typeof value !== 'string')
|
|
52
|
+
return value;
|
|
53
|
+
const trimmed = value.trim();
|
|
54
|
+
if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
|
|
55
|
+
try {
|
|
56
|
+
return JSON.parse(trimmed);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return value;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return value;
|
|
63
|
+
}
|
|
64
|
+
/** Token count: the reported total, else input+output, else the legacy names. */
|
|
65
|
+
export function tokensFrom(attrs) {
|
|
66
|
+
const total = num(attrs['gen_ai.usage.total_tokens']);
|
|
67
|
+
if (total > 0)
|
|
68
|
+
return total;
|
|
69
|
+
const input = num(attrs['gen_ai.usage.input_tokens']) || num(attrs['gen_ai.usage.prompt_tokens']);
|
|
70
|
+
const output = num(attrs['gen_ai.usage.output_tokens']) || num(attrs['gen_ai.usage.completion_tokens']);
|
|
71
|
+
return input + output;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Step type: an explicit `traceglass.step.type` wins, then an ERROR span
|
|
75
|
+
* status, then the ingester's heuristics over the gen_ai attributes and the
|
|
76
|
+
* span name.
|
|
77
|
+
*/
|
|
78
|
+
export function deriveStepType(attrs, name, statusCode) {
|
|
79
|
+
const explicit = str(attrs['traceglass.step.type']);
|
|
80
|
+
if (explicit !== undefined) {
|
|
81
|
+
const parsed = StepTypeSchema.safeParse(explicit);
|
|
82
|
+
if (parsed.success)
|
|
83
|
+
return parsed.data;
|
|
84
|
+
}
|
|
85
|
+
if (statusCode === SPAN_STATUS_ERROR)
|
|
86
|
+
return 'error';
|
|
87
|
+
if (attrs['gen_ai.tool.name'] !== undefined || /tool|db|query|api/i.test(name))
|
|
88
|
+
return 'tool_call';
|
|
89
|
+
if (/reason|think|llm|completion/i.test(name))
|
|
90
|
+
return 'llm_reasoning';
|
|
91
|
+
if (/plan/i.test(name))
|
|
92
|
+
return 'plan';
|
|
93
|
+
if (/input|prompt|user/i.test(name))
|
|
94
|
+
return 'user_input';
|
|
95
|
+
if (/output|final|answer|decision/i.test(name))
|
|
96
|
+
return 'final_output';
|
|
97
|
+
return 'llm_reasoning';
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Attributes not already copied into a step field, dotted keys intact.
|
|
101
|
+
* Undefined values are dropped (OTel allows them; JSON does not carry them).
|
|
102
|
+
*/
|
|
103
|
+
export function attributeSnapshot(attrs) {
|
|
104
|
+
const skip = new Set(MAPPED_ATTRIBUTES);
|
|
105
|
+
const out = {};
|
|
106
|
+
for (const key of Object.keys(attrs)) {
|
|
107
|
+
if (skip.has(key))
|
|
108
|
+
continue;
|
|
109
|
+
const value = attrs[key];
|
|
110
|
+
if (value === undefined)
|
|
111
|
+
continue;
|
|
112
|
+
out[key] = value;
|
|
113
|
+
}
|
|
114
|
+
return out;
|
|
115
|
+
}
|
|
116
|
+
/** Map one ended span onto the step the writer will chain. */
|
|
117
|
+
export function spanToStep(span, opts = {}) {
|
|
118
|
+
const attrs = span.attributes;
|
|
119
|
+
const name = span.name;
|
|
120
|
+
const startMs = hrToMs(span.startTime);
|
|
121
|
+
const endMs = hrToMs(span.endTime);
|
|
122
|
+
const durationMs = span.duration
|
|
123
|
+
? Math.max(0, Math.round(hrToMs(span.duration)))
|
|
124
|
+
: Math.max(0, Math.round(endMs - startMs));
|
|
125
|
+
const toolName = str(attrs['gen_ai.tool.name']) ?? str(attrs['traceglass.tool.name']);
|
|
126
|
+
const input = maybeJson(attrs['gen_ai.prompt'] ?? attrs['traceglass.input']);
|
|
127
|
+
const output = maybeJson(attrs['gen_ai.completion'] ?? attrs['traceglass.output']);
|
|
128
|
+
const explicitPayload = maybeJson(attrs['traceglass.data_payload']);
|
|
129
|
+
let dataPayload = explicitPayload;
|
|
130
|
+
if (dataPayload === undefined && opts.recordAttributes !== false) {
|
|
131
|
+
const snapshot = attributeSnapshot(attrs);
|
|
132
|
+
if (Object.keys(snapshot).length > 0)
|
|
133
|
+
dataPayload = { attributes: snapshot };
|
|
134
|
+
}
|
|
135
|
+
const parentSpanId = parentSpanIdOf(span);
|
|
136
|
+
const step = {
|
|
137
|
+
type: deriveStepType(attrs, name, span.status?.code),
|
|
138
|
+
label: str(attrs['traceglass.step.label']) ?? name,
|
|
139
|
+
startedAt: new Date(Math.round(startMs)).toISOString(),
|
|
140
|
+
durationMs,
|
|
141
|
+
tokens: tokensFrom(attrs),
|
|
142
|
+
cost: num(attrs['traceglass.cost']),
|
|
143
|
+
spanId: span.spanContext().spanId,
|
|
144
|
+
};
|
|
145
|
+
if (toolName !== undefined)
|
|
146
|
+
step.toolName = toolName;
|
|
147
|
+
if (parentSpanId !== '')
|
|
148
|
+
step.parentSpanId = parentSpanId;
|
|
149
|
+
if (input !== undefined)
|
|
150
|
+
step.input = input;
|
|
151
|
+
if (output !== undefined)
|
|
152
|
+
step.output = output;
|
|
153
|
+
if (dataPayload !== undefined)
|
|
154
|
+
step.dataPayload = dataPayload;
|
|
155
|
+
return step;
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=map.js.map
|
package/dist/map.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"map.js","sourceRoot":"","sources":["../src/map.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { Run } from '@traceglass/core';
|
|
2
|
+
import { type SpanMapOptions } from './map.js';
|
|
3
|
+
import { type RecordableSpan, type SpanProcessorLike } from './span-types.js';
|
|
4
|
+
/**
|
|
5
|
+
* OpenTelemetry span processor that writes traceglass runs.
|
|
6
|
+
*
|
|
7
|
+
* Register it on your existing tracer provider and an application that already
|
|
8
|
+
* emits `gen_ai.*` spans produces signed, hash-chained evidence with no code
|
|
9
|
+
* changes beyond the registration itself:
|
|
10
|
+
*
|
|
11
|
+
* new NodeTracerProvider({ spanProcessors: [new TraceglassSpanProcessor()] })
|
|
12
|
+
* // or, on OTel JS 1.x: provider.addSpanProcessor(new TraceglassSpanProcessor())
|
|
13
|
+
*
|
|
14
|
+
* One TRACE becomes one run. Spans are recorded as they END, which is when
|
|
15
|
+
* their duration, status and attributes are final; the chain is fixed in that
|
|
16
|
+
* arrival order and never re-sorted. A trace is finalized when its root span
|
|
17
|
+
* ends, and any still-open trace is finalized on `forceFlush()`/`shutdown()`.
|
|
18
|
+
*
|
|
19
|
+
* The processor never throws into the telemetry pipeline: a failure to record
|
|
20
|
+
* must not take down the application being recorded. Failures are reported
|
|
21
|
+
* through `onError` (default: `process.emitWarning`) so they are loud without
|
|
22
|
+
* being fatal — silence would be the worst outcome for an evidence tool.
|
|
23
|
+
*
|
|
24
|
+
* Zero network egress: everything is written under TRACEGLASS_HOME
|
|
25
|
+
* (default ~/.traceglass). This is not an exporter and talks to no collector.
|
|
26
|
+
*/
|
|
27
|
+
export interface TraceglassSpanProcessorOptions extends SpanMapOptions {
|
|
28
|
+
/**
|
|
29
|
+
* Run name. A function is called with the first span seen for a trace.
|
|
30
|
+
* Defaults to the resource's `traceglass.run.name`, then `service.name`,
|
|
31
|
+
* then `OpenTelemetry run`.
|
|
32
|
+
*/
|
|
33
|
+
name?: string | ((span: RecordableSpan) => string);
|
|
34
|
+
/**
|
|
35
|
+
* Run id for a trace. Defaults to `otel-<traceId>`, which is unique per trace
|
|
36
|
+
* and points straight back at the trace in your tracing backend.
|
|
37
|
+
*
|
|
38
|
+
* Note the resource attribute `traceglass.run.id` — which the offline OTLP
|
|
39
|
+
* ingester honours — is deliberately NOT used here: one processor sees many
|
|
40
|
+
* traces from the same resource, and they must not all claim one run id.
|
|
41
|
+
*/
|
|
42
|
+
runId?: (traceId: string, span: RecordableSpan) => string;
|
|
43
|
+
/** Currency for cost fields. Defaults to the resource's, then USD. */
|
|
44
|
+
currency?: string;
|
|
45
|
+
/** traceglass data dir; `null` records in memory only (useful in tests). */
|
|
46
|
+
dir?: string | null;
|
|
47
|
+
/** Per-leaf commitments so payloads can be redacted later (default on). */
|
|
48
|
+
redactable?: boolean;
|
|
49
|
+
/** Built-in redaction patterns applied at capture time, before hashing. */
|
|
50
|
+
redactPatterns?: string[];
|
|
51
|
+
/** Record only the spans this returns true for. Default: every ended span. */
|
|
52
|
+
filter?: (span: RecordableSpan) => boolean;
|
|
53
|
+
/** Finalize a trace's run when its root span ends. Default true. */
|
|
54
|
+
finalizeOnRootEnd?: boolean;
|
|
55
|
+
/** Called with each finalized run. */
|
|
56
|
+
onRun?: (run: Run) => void;
|
|
57
|
+
/** Called when recording a span or finalizing a run fails. */
|
|
58
|
+
onError?: (error: Error) => void;
|
|
59
|
+
}
|
|
60
|
+
export declare class TraceglassSpanProcessor implements SpanProcessorLike {
|
|
61
|
+
private readonly opts;
|
|
62
|
+
private readonly writers;
|
|
63
|
+
/** traceId -> how many runs have already been finalized for it. */
|
|
64
|
+
private readonly finalized;
|
|
65
|
+
private shutDown;
|
|
66
|
+
constructor(opts?: TraceglassSpanProcessorOptions);
|
|
67
|
+
/** Nothing to do on start: a span's attributes are not final until it ends. */
|
|
68
|
+
onStart(_span: unknown, _parentContext?: unknown): void;
|
|
69
|
+
onEnd(span: RecordableSpan): void;
|
|
70
|
+
/** Finalize every open run. Returns once they are stored. */
|
|
71
|
+
forceFlush(): Promise<void>;
|
|
72
|
+
/** Finalize every open run and stop recording. */
|
|
73
|
+
shutdown(): Promise<void>;
|
|
74
|
+
/** Finalize one trace's run early (e.g. a session boundary you know about). */
|
|
75
|
+
finalize(traceId: string): Run | null;
|
|
76
|
+
/** Run ids currently open, keyed by trace id. Mostly useful in tests. */
|
|
77
|
+
get openRuns(): Map<string, string>;
|
|
78
|
+
private writerFor;
|
|
79
|
+
private rememberFinalized;
|
|
80
|
+
private reportError;
|
|
81
|
+
}
|
|
82
|
+
/** Convenience factory, for config files that prefer a function call. */
|
|
83
|
+
export declare function createTraceglassSpanProcessor(opts?: TraceglassSpanProcessorOptions): TraceglassSpanProcessor;
|
|
84
|
+
//# sourceMappingURL=processor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"processor.d.ts","sourceRoot":"","sources":["../src/processor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,UAAU,CAAC;AAC3D,OAAO,EAAkB,KAAK,cAAc,EAAE,KAAK,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAG9F;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,MAAM,WAAW,8BAA+B,SAAQ,cAAc;IACpE;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE,cAAc,KAAK,MAAM,CAAC,CAAC;IACnD;;;;;;;OAOG;IACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,KAAK,MAAM,CAAC;IAC1D,sEAAsE;IACtE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,4EAA4E;IAC5E,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,2EAA2E;IAC3E,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,2EAA2E;IAC3E,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,8EAA8E;IAC9E,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,OAAO,CAAC;IAC3C,oEAAoE;IACpE,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,sCAAsC;IACtC,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,CAAC;IAC3B,8DAA8D;IAC9D,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC;AAcD,qBAAa,uBAAwB,YAAW,iBAAiB;IAC/D,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAiC;IACtD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAgC;IACxD,mEAAmE;IACnE,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA6B;IACvD,OAAO,CAAC,QAAQ,CAAS;gBAEb,IAAI,GAAE,8BAAmC;IAIrD,+EAA+E;IAC/E,OAAO,CAAC,KAAK,EAAE,OAAO,EAAE,cAAc,CAAC,EAAE,OAAO,GAAG,IAAI;IAIvD,KAAK,CAAC,IAAI,EAAE,cAAc,GAAG,IAAI;IAsBjC,6DAA6D;IAC7D,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAK3B,kDAAkD;IAClD,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAMzB,+EAA+E;IAC/E,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,GAAG,GAAG,IAAI;IAerC,yEAAyE;IACzE,IAAI,QAAQ,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAElC;IAED,OAAO,CAAC,SAAS;IAqCjB,OAAO,CAAC,iBAAiB;IAUzB,OAAO,CAAC,WAAW;CAGpB;AAED,yEAAyE;AACzE,wBAAgB,6BAA6B,CAC3C,IAAI,GAAE,8BAAmC,GACxC,uBAAuB,CAEzB"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { spanToStep } from './map.js';
|
|
2
|
+
import { parentSpanIdOf } from './span-types.js';
|
|
3
|
+
import { RunWriter } from './writer.js';
|
|
4
|
+
/** How many finalized trace ids to remember for late-span detection. */
|
|
5
|
+
const FINALIZED_MEMORY = 512;
|
|
6
|
+
function defaultOnError(error) {
|
|
7
|
+
process.emitWarning(error, 'TraceglassOtelWarning');
|
|
8
|
+
}
|
|
9
|
+
function toError(value) {
|
|
10
|
+
if (value instanceof Error)
|
|
11
|
+
return value;
|
|
12
|
+
return new Error(typeof value === 'string' ? value : `non-Error thrown (${typeof value})`);
|
|
13
|
+
}
|
|
14
|
+
export class TraceglassSpanProcessor {
|
|
15
|
+
opts;
|
|
16
|
+
writers = new Map();
|
|
17
|
+
/** traceId -> how many runs have already been finalized for it. */
|
|
18
|
+
finalized = new Map();
|
|
19
|
+
shutDown = false;
|
|
20
|
+
constructor(opts = {}) {
|
|
21
|
+
this.opts = opts;
|
|
22
|
+
}
|
|
23
|
+
/** Nothing to do on start: a span's attributes are not final until it ends. */
|
|
24
|
+
onStart(_span, _parentContext) {
|
|
25
|
+
// intentionally empty
|
|
26
|
+
}
|
|
27
|
+
onEnd(span) {
|
|
28
|
+
try {
|
|
29
|
+
if (this.shutDown) {
|
|
30
|
+
throw new Error(`Span "${span.name}" ended after the traceglass processor was shut down; not recorded.`);
|
|
31
|
+
}
|
|
32
|
+
if (this.opts.filter && !this.opts.filter(span))
|
|
33
|
+
return;
|
|
34
|
+
const traceId = span.spanContext().traceId;
|
|
35
|
+
// Mapped BEFORE the writer exists, so a span this processor cannot map
|
|
36
|
+
// does not leave an empty journal behind for `traceglass recover`.
|
|
37
|
+
const stepInput = spanToStep(span, this.opts);
|
|
38
|
+
const writer = this.writerFor(traceId, span);
|
|
39
|
+
writer.step(stepInput);
|
|
40
|
+
if (this.opts.finalizeOnRootEnd !== false && parentSpanIdOf(span) === '') {
|
|
41
|
+
this.finalize(traceId);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
this.reportError(toError(err));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/** Finalize every open run. Returns once they are stored. */
|
|
49
|
+
forceFlush() {
|
|
50
|
+
for (const traceId of [...this.writers.keys()])
|
|
51
|
+
this.finalize(traceId);
|
|
52
|
+
return Promise.resolve();
|
|
53
|
+
}
|
|
54
|
+
/** Finalize every open run and stop recording. */
|
|
55
|
+
shutdown() {
|
|
56
|
+
const flushed = this.forceFlush();
|
|
57
|
+
this.shutDown = true;
|
|
58
|
+
return flushed;
|
|
59
|
+
}
|
|
60
|
+
/** Finalize one trace's run early (e.g. a session boundary you know about). */
|
|
61
|
+
finalize(traceId) {
|
|
62
|
+
const writer = this.writers.get(traceId);
|
|
63
|
+
if (!writer)
|
|
64
|
+
return null;
|
|
65
|
+
this.writers.delete(traceId);
|
|
66
|
+
this.rememberFinalized(traceId);
|
|
67
|
+
try {
|
|
68
|
+
const run = writer.end();
|
|
69
|
+
this.opts.onRun?.(run);
|
|
70
|
+
return run;
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
this.reportError(toError(err));
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/** Run ids currently open, keyed by trace id. Mostly useful in tests. */
|
|
78
|
+
get openRuns() {
|
|
79
|
+
return new Map([...this.writers].map(([traceId, w]) => [traceId, w.runId]));
|
|
80
|
+
}
|
|
81
|
+
writerFor(traceId, span) {
|
|
82
|
+
const existing = this.writers.get(traceId);
|
|
83
|
+
if (existing)
|
|
84
|
+
return existing;
|
|
85
|
+
const resource = span.resource?.attributes ?? {};
|
|
86
|
+
const resourceName = resource['traceglass.run.name'] ?? resource['service.name'];
|
|
87
|
+
const name = typeof this.opts.name === 'function'
|
|
88
|
+
? this.opts.name(span)
|
|
89
|
+
: (this.opts.name ??
|
|
90
|
+
(typeof resourceName === 'string' ? resourceName : 'OpenTelemetry run'));
|
|
91
|
+
const resourceCurrency = resource['traceglass.run.currency'];
|
|
92
|
+
const currency = this.opts.currency ?? (typeof resourceCurrency === 'string' ? resourceCurrency : 'USD');
|
|
93
|
+
// A span arriving after its trace was already finalized (a late child, or a
|
|
94
|
+
// second root) opens a CONTINUATION run rather than being dropped: losing
|
|
95
|
+
// evidence silently is worse than an extra record, and reusing the run id
|
|
96
|
+
// would collide in the store.
|
|
97
|
+
const generation = this.finalized.get(traceId) ?? 0;
|
|
98
|
+
const base = this.opts.runId?.(traceId, span) ?? `otel-${traceId}`;
|
|
99
|
+
const id = generation === 0 ? base : `${base}-${generation + 1}`;
|
|
100
|
+
const writer = new RunWriter({
|
|
101
|
+
id,
|
|
102
|
+
name,
|
|
103
|
+
currency,
|
|
104
|
+
...(this.opts.dir !== undefined ? { dir: this.opts.dir } : {}),
|
|
105
|
+
...(this.opts.redactable !== undefined ? { redactable: this.opts.redactable } : {}),
|
|
106
|
+
...(this.opts.redactPatterns !== undefined
|
|
107
|
+
? { redactPatterns: this.opts.redactPatterns }
|
|
108
|
+
: {}),
|
|
109
|
+
});
|
|
110
|
+
this.writers.set(traceId, writer);
|
|
111
|
+
return writer;
|
|
112
|
+
}
|
|
113
|
+
rememberFinalized(traceId) {
|
|
114
|
+
this.finalized.set(traceId, (this.finalized.get(traceId) ?? 0) + 1);
|
|
115
|
+
// Bounded: a long-lived process must not grow a map per trace forever.
|
|
116
|
+
while (this.finalized.size > FINALIZED_MEMORY) {
|
|
117
|
+
const oldest = this.finalized.keys().next();
|
|
118
|
+
if (oldest.done)
|
|
119
|
+
break;
|
|
120
|
+
this.finalized.delete(oldest.value);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
reportError(error) {
|
|
124
|
+
(this.opts.onError ?? defaultOnError)(error);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/** Convenience factory, for config files that prefer a function call. */
|
|
128
|
+
export function createTraceglassSpanProcessor(opts = {}) {
|
|
129
|
+
return new TraceglassSpanProcessor(opts);
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=processor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"processor.js","sourceRoot":"","sources":["../src/processor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structural mirrors of the OpenTelemetry SDK types this processor reads.
|
|
3
|
+
*
|
|
4
|
+
* Nothing from `@opentelemetry/*` is imported. That is a supply-chain decision:
|
|
5
|
+
* the OTel JS SDK is a large, fast-moving dependency tree, and this package
|
|
6
|
+
* exists to make an AUDIT record — so it works against whatever OTel version
|
|
7
|
+
* the host application already installed instead of pinning a second one. The
|
|
8
|
+
* price is that compatibility has to be asserted rather than inferred; that
|
|
9
|
+
* assertion lives in `type-compat.ts` and is checked by `npm run typecheck`.
|
|
10
|
+
*
|
|
11
|
+
* Every member below is a strict subset of the real `ReadableSpan`, so a real
|
|
12
|
+
* span is assignable to `RecordableSpan` without a cast.
|
|
13
|
+
*/
|
|
14
|
+
/** OTel `HrTime`: [epoch seconds, nanoseconds]. */
|
|
15
|
+
export type HrTimeLike = readonly [number, number];
|
|
16
|
+
/**
|
|
17
|
+
* Attribute values are `string | number | boolean | Array<...>` in OTel. They
|
|
18
|
+
* are read as `unknown` and narrowed at runtime, which keeps this type a
|
|
19
|
+
* supertype of `Attributes` for every SDK version.
|
|
20
|
+
*/
|
|
21
|
+
export type AttributesLike = Readonly<Record<string, unknown>>;
|
|
22
|
+
/** The subset of `SpanContext` needed to correlate a step with its trace. */
|
|
23
|
+
export interface SpanContextLike {
|
|
24
|
+
readonly traceId: string;
|
|
25
|
+
readonly spanId: string;
|
|
26
|
+
}
|
|
27
|
+
/** The subset of `ReadableSpan` this processor consumes. */
|
|
28
|
+
export interface RecordableSpan {
|
|
29
|
+
readonly name: string;
|
|
30
|
+
spanContext(): SpanContextLike;
|
|
31
|
+
readonly startTime: HrTimeLike;
|
|
32
|
+
readonly endTime: HrTimeLike;
|
|
33
|
+
/** Present on real spans; recomputed from start/end when absent. */
|
|
34
|
+
readonly duration?: HrTimeLike;
|
|
35
|
+
readonly attributes: AttributesLike;
|
|
36
|
+
/** `SpanStatusCode`: 0 UNSET, 1 OK, 2 ERROR. */
|
|
37
|
+
readonly status?: {
|
|
38
|
+
readonly code: number;
|
|
39
|
+
readonly message?: string;
|
|
40
|
+
};
|
|
41
|
+
/** OTel <= 1.x. Replaced by `parentSpanContext` in later releases. */
|
|
42
|
+
readonly parentSpanId?: string | undefined;
|
|
43
|
+
/** OTel >= 1.30 / 2.x. */
|
|
44
|
+
readonly parentSpanContext?: {
|
|
45
|
+
readonly spanId: string;
|
|
46
|
+
} | undefined;
|
|
47
|
+
readonly resource?: {
|
|
48
|
+
readonly attributes?: AttributesLike;
|
|
49
|
+
} | undefined;
|
|
50
|
+
}
|
|
51
|
+
/** The `SpanProcessor` surface an OTel `TracerProvider` calls. */
|
|
52
|
+
export interface SpanProcessorLike {
|
|
53
|
+
onStart(span: unknown, parentContext?: unknown): void;
|
|
54
|
+
onEnd(span: RecordableSpan): void;
|
|
55
|
+
forceFlush(): Promise<void>;
|
|
56
|
+
shutdown(): Promise<void>;
|
|
57
|
+
}
|
|
58
|
+
/** Milliseconds since the epoch from an OTel `HrTime`. */
|
|
59
|
+
export declare function hrToMs(hr: HrTimeLike | undefined): number;
|
|
60
|
+
/** The span's parent id under either OTel spelling; '' for a root span. */
|
|
61
|
+
export declare function parentSpanIdOf(span: RecordableSpan): string;
|
|
62
|
+
//# sourceMappingURL=span-types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"span-types.d.ts","sourceRoot":"","sources":["../src/span-types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,mDAAmD;AACnD,MAAM,MAAM,UAAU,GAAG,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAEnD;;;;GAIG;AACH,MAAM,MAAM,cAAc,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAE/D,6EAA6E;AAC7E,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,4DAA4D;AAC5D,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,WAAW,IAAI,eAAe,CAAC;IAC/B,QAAQ,CAAC,SAAS,EAAE,UAAU,CAAC;IAC/B,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAC7B,oEAAoE;IACpE,QAAQ,CAAC,QAAQ,CAAC,EAAE,UAAU,CAAC;IAC/B,QAAQ,CAAC,UAAU,EAAE,cAAc,CAAC;IACpC,gDAAgD;IAChD,QAAQ,CAAC,MAAM,CAAC,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACvE,sEAAsE;IACtE,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3C,0BAA0B;IAC1B,QAAQ,CAAC,iBAAiB,CAAC,EAAE;QAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;IACrE,QAAQ,CAAC,QAAQ,CAAC,EAAE;QAAE,QAAQ,CAAC,UAAU,CAAC,EAAE,cAAc,CAAA;KAAE,GAAG,SAAS,CAAC;CAC1E;AAED,kEAAkE;AAClE,MAAM,WAAW,iBAAiB;IAChC,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,aAAa,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IACtD,KAAK,CAAC,IAAI,EAAE,cAAc,GAAG,IAAI,CAAC;IAClC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B;AAED,0DAA0D;AAC1D,wBAAgB,MAAM,CAAC,EAAE,EAAE,UAAU,GAAG,SAAS,GAAG,MAAM,CAIzD;AAED,2EAA2E;AAC3E,wBAAgB,cAAc,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,CAE3D"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structural mirrors of the OpenTelemetry SDK types this processor reads.
|
|
3
|
+
*
|
|
4
|
+
* Nothing from `@opentelemetry/*` is imported. That is a supply-chain decision:
|
|
5
|
+
* the OTel JS SDK is a large, fast-moving dependency tree, and this package
|
|
6
|
+
* exists to make an AUDIT record — so it works against whatever OTel version
|
|
7
|
+
* the host application already installed instead of pinning a second one. The
|
|
8
|
+
* price is that compatibility has to be asserted rather than inferred; that
|
|
9
|
+
* assertion lives in `type-compat.ts` and is checked by `npm run typecheck`.
|
|
10
|
+
*
|
|
11
|
+
* Every member below is a strict subset of the real `ReadableSpan`, so a real
|
|
12
|
+
* span is assignable to `RecordableSpan` without a cast.
|
|
13
|
+
*/
|
|
14
|
+
/** Milliseconds since the epoch from an OTel `HrTime`. */
|
|
15
|
+
export function hrToMs(hr) {
|
|
16
|
+
if (!hr)
|
|
17
|
+
return 0;
|
|
18
|
+
const [seconds, nanos] = hr;
|
|
19
|
+
return seconds * 1000 + nanos / 1e6;
|
|
20
|
+
}
|
|
21
|
+
/** The span's parent id under either OTel spelling; '' for a root span. */
|
|
22
|
+
export function parentSpanIdOf(span) {
|
|
23
|
+
return span.parentSpanId ?? span.parentSpanContext?.spanId ?? '';
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=span-types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"span-types.js","sourceRoot":"","sources":["../src/span-types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AA4CH,0DAA0D;AAC1D,MAAM,UAAU,MAAM,CAAC,EAA0B;IAC/C,IAAI,CAAC,EAAE;QAAE,OAAO,CAAC,CAAC;IAClB,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,GAAG,EAAE,CAAC;IAC5B,OAAO,OAAO,GAAG,IAAI,GAAG,KAAK,GAAG,GAAG,CAAC;AACtC,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,cAAc,CAAC,IAAoB;IACjD,OAAO,IAAI,CAAC,YAAY,IAAI,IAAI,CAAC,iBAAiB,EAAE,MAAM,IAAI,EAAE,CAAC;AACnE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"type-compat.d.ts","sourceRoot":"","sources":["../src/type-compat.ts"],"names":[],"mappings":"AA8EA,eAAO,MAAM,sBAAsB,IAAI,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const _v1 = true;
|
|
2
|
+
const _v2 = true;
|
|
3
|
+
const _registerable = _processor;
|
|
4
|
+
const _alsoSatisfiesOwnInterface = _processor;
|
|
5
|
+
export const TYPE_COMPAT_ASSERTIONS = 4;
|
|
6
|
+
void _v1;
|
|
7
|
+
void _v2;
|
|
8
|
+
void _registerable;
|
|
9
|
+
void _alsoSatisfiesOwnInterface;
|
|
10
|
+
//# sourceMappingURL=type-compat.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"type-compat.js","sourceRoot":"","sources":["../src/type-compat.ts"],"names":[],"mappings":"AA+DA,MAAM,GAAG,GAAoB,IAAI,CAAC;AAClC,MAAM,GAAG,GAAoB,IAAI,CAAC;AAWlC,MAAM,aAAa,GAA0B,UAAU,CAAC;AACxD,MAAM,0BAA0B,GAAsB,UAAU,CAAC;AAEjE,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC;AACxC,KAAK,GAAG,CAAC;AACT,KAAK,GAAG,CAAC;AACT,KAAK,aAAa,CAAC;AACnB,KAAK,0BAA0B,CAAC"}
|
package/dist/writer.d.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { type Run, type Step, type StepType } from '@traceglass/core';
|
|
2
|
+
/**
|
|
3
|
+
* Capture-time chain writer for span-shaped input.
|
|
4
|
+
*
|
|
5
|
+
* This is deliberately NOT `@traceglass/sdk`'s `startRecording`. That recorder
|
|
6
|
+
* synthesizes a random `spanId` per step and has no way to accept a parent, so
|
|
7
|
+
* routing OTel through it would throw away the two identifiers that let an
|
|
8
|
+
* auditor tie a traceglass record back to the trace it came from. Everything
|
|
9
|
+
* else is the same contract, and the parts that matter are core's, not a second
|
|
10
|
+
* implementation of them:
|
|
11
|
+
*
|
|
12
|
+
* - steps are hashed the moment they arrive (index = arrival order,
|
|
13
|
+
* hash = hashStep(step, prevHash)), so the chain is fixed at capture time
|
|
14
|
+
* and cannot be re-ordered afterwards;
|
|
15
|
+
* - each step is appended to the same JSONL journal format the SDK writes, so
|
|
16
|
+
* `traceglass live`, `traceglass recover` and the dashboard read an
|
|
17
|
+
* in-flight OTel recording with no new machinery;
|
|
18
|
+
* - finalization goes through core's `finalizeJournal`, which is the exact
|
|
19
|
+
* code path `traceglass recover` uses — totals, anchor and the verify
|
|
20
|
+
* self-check are computed once, in one place. A recovered journal and a
|
|
21
|
+
* cleanly ended run are therefore byte-identical records.
|
|
22
|
+
*/
|
|
23
|
+
/** One step to append. Unlike the SDK recorder, span ids are caller-supplied. */
|
|
24
|
+
export interface WriterStepInput {
|
|
25
|
+
type: StepType;
|
|
26
|
+
label: string;
|
|
27
|
+
startedAt: string;
|
|
28
|
+
durationMs: number;
|
|
29
|
+
tokens: number;
|
|
30
|
+
cost: number;
|
|
31
|
+
toolName?: string;
|
|
32
|
+
input?: unknown;
|
|
33
|
+
output?: unknown;
|
|
34
|
+
dataPayload?: unknown;
|
|
35
|
+
spanId: string;
|
|
36
|
+
parentSpanId?: string;
|
|
37
|
+
}
|
|
38
|
+
export interface RunWriterOptions {
|
|
39
|
+
id: string;
|
|
40
|
+
name: string;
|
|
41
|
+
currency?: string;
|
|
42
|
+
/**
|
|
43
|
+
* traceglass data dir (journal + SQLite store). Defaults to
|
|
44
|
+
* TRACEGLASS_HOME ?? ~/.traceglass. `null` keeps the run in memory only.
|
|
45
|
+
*/
|
|
46
|
+
dir?: string | null;
|
|
47
|
+
/** Per-leaf commitments, so payloads can be redacted later (default on). */
|
|
48
|
+
redactable?: boolean;
|
|
49
|
+
/** Built-in pattern names scrubbed at capture time, before anything is hashed. */
|
|
50
|
+
redactPatterns?: string[];
|
|
51
|
+
/** Run start; defaults to now. */
|
|
52
|
+
startedAt?: string;
|
|
53
|
+
}
|
|
54
|
+
export declare class RunWriter {
|
|
55
|
+
readonly runId: string;
|
|
56
|
+
readonly startedAt: string;
|
|
57
|
+
private readonly dir;
|
|
58
|
+
private readonly meta;
|
|
59
|
+
private readonly journalFile;
|
|
60
|
+
private readonly redactable;
|
|
61
|
+
private readonly patterns;
|
|
62
|
+
private readonly steps;
|
|
63
|
+
private prevHash;
|
|
64
|
+
private ended;
|
|
65
|
+
constructor(opts: RunWriterOptions);
|
|
66
|
+
get stepCount(): number;
|
|
67
|
+
get isEnded(): boolean;
|
|
68
|
+
/** Append one step: scrubbed, committed, hashed and journaled before returning. */
|
|
69
|
+
step(input: WriterStepInput): Step;
|
|
70
|
+
/**
|
|
71
|
+
* Finalize: totals + warnings + anchor via core's journal finalizer, sign if
|
|
72
|
+
* keys exist, save, delete the journal. Synchronous — every write involved is
|
|
73
|
+
* (the SDK recorder's `end` is async only because its signature is).
|
|
74
|
+
*/
|
|
75
|
+
end(opts?: {
|
|
76
|
+
status?: 'completed' | 'failed';
|
|
77
|
+
}): Run;
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=writer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"writer.d.ts","sourceRoot":"","sources":["../src/writer.ts"],"names":[],"mappings":"AAGA,OAAO,EAaL,KAAK,GAAG,EACR,KAAK,IAAI,EACT,KAAK,QAAQ,EACd,MAAM,kBAAkB,CAAC;AAE1B;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,iFAAiF;AACjF,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,QAAQ,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,4EAA4E;IAC5E,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,kFAAkF;IAClF,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,kCAAkC;IAClC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAmBD,qBAAa,SAAS;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAE3B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAgB;IACpC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAc;IACnC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAgB;IAC5C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAU;IACrC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqB;IAC9C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAc;IACpC,OAAO,CAAC,QAAQ,CAAM;IACtB,OAAO,CAAC,KAAK,CAAS;gBAEV,IAAI,EAAE,gBAAgB;IAsBlC,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,mFAAmF;IACnF,IAAI,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI;IA6ClC;;;;OAIG;IACH,GAAG,CAAC,IAAI,GAAE;QAAE,MAAM,CAAC,EAAE,WAAW,GAAG,QAAQ,CAAA;KAAO,GAAG,GAAG;CA2BzD"}
|
package/dist/writer.js
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, unlinkSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { DEFAULT_HASH_VERSION, JOURNAL_FORMAT_VERSION, RunStore, buildCommitments, finalizeJournal, hashStep, journalLine, patternsByName, scrubStepPayload, signRun, } from '@traceglass/core';
|
|
5
|
+
function defaultDir() {
|
|
6
|
+
return process.env.TRACEGLASS_HOME ?? join(homedir(), '.traceglass');
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Load signing keys from <dir>/keys if present. Mirrors the CLI's key layout
|
|
10
|
+
* (packages/cli/src/keys.ts) and the SDK recorder's loader — the file paths are
|
|
11
|
+
* the contract between packages; the ~10 lines are duplicated to avoid pulling
|
|
12
|
+
* the CLI into a library dependency.
|
|
13
|
+
*/
|
|
14
|
+
function loadKeys(dir) {
|
|
15
|
+
const priv = join(dir, 'keys', 'private.pem');
|
|
16
|
+
const pub = join(dir, 'keys', 'public.pem');
|
|
17
|
+
if (!existsSync(priv) || !existsSync(pub))
|
|
18
|
+
return null;
|
|
19
|
+
return { privateKeyPem: readFileSync(priv, 'utf8'), publicKeyPem: readFileSync(pub, 'utf8') };
|
|
20
|
+
}
|
|
21
|
+
export class RunWriter {
|
|
22
|
+
runId;
|
|
23
|
+
startedAt;
|
|
24
|
+
dir;
|
|
25
|
+
meta;
|
|
26
|
+
journalFile;
|
|
27
|
+
redactable;
|
|
28
|
+
patterns;
|
|
29
|
+
steps = [];
|
|
30
|
+
prevHash = '';
|
|
31
|
+
ended = false;
|
|
32
|
+
constructor(opts) {
|
|
33
|
+
this.runId = opts.id;
|
|
34
|
+
this.startedAt = opts.startedAt ?? new Date().toISOString();
|
|
35
|
+
this.dir = opts.dir === null ? null : (opts.dir ?? defaultDir());
|
|
36
|
+
this.redactable = opts.redactable !== false;
|
|
37
|
+
this.patterns = opts.redactPatterns ? patternsByName(opts.redactPatterns) : [];
|
|
38
|
+
this.meta = {
|
|
39
|
+
kind: 'meta',
|
|
40
|
+
formatVersion: JOURNAL_FORMAT_VERSION,
|
|
41
|
+
id: this.runId,
|
|
42
|
+
name: opts.name,
|
|
43
|
+
currency: opts.currency ?? 'USD',
|
|
44
|
+
startedAt: this.startedAt,
|
|
45
|
+
hashVersion: DEFAULT_HASH_VERSION,
|
|
46
|
+
};
|
|
47
|
+
this.journalFile = this.dir ? join(this.dir, 'journal', `${this.runId}.jsonl`) : null;
|
|
48
|
+
if (this.journalFile && this.dir) {
|
|
49
|
+
mkdirSync(join(this.dir, 'journal'), { recursive: true });
|
|
50
|
+
appendFileSync(this.journalFile, journalLine(this.meta));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
get stepCount() {
|
|
54
|
+
return this.steps.length;
|
|
55
|
+
}
|
|
56
|
+
get isEnded() {
|
|
57
|
+
return this.ended;
|
|
58
|
+
}
|
|
59
|
+
/** Append one step: scrubbed, committed, hashed and journaled before returning. */
|
|
60
|
+
step(input) {
|
|
61
|
+
if (this.ended)
|
|
62
|
+
throw new Error(`Recording "${this.runId}" has already ended.`);
|
|
63
|
+
const index = this.steps.length;
|
|
64
|
+
const hashVersion = DEFAULT_HASH_VERSION;
|
|
65
|
+
// Capture-time scrubbing runs BEFORE commitments/hashing, so a matched
|
|
66
|
+
// value is never committed to and never written anywhere.
|
|
67
|
+
let payload = {
|
|
68
|
+
...(input.input !== undefined ? { input: input.input } : {}),
|
|
69
|
+
...(input.output !== undefined ? { output: input.output } : {}),
|
|
70
|
+
...(input.dataPayload !== undefined ? { dataPayload: input.dataPayload } : {}),
|
|
71
|
+
};
|
|
72
|
+
let redactions;
|
|
73
|
+
if (this.patterns.length > 0) {
|
|
74
|
+
const scrubbed = scrubStepPayload(payload, this.patterns, hashVersion);
|
|
75
|
+
payload = scrubbed.payload;
|
|
76
|
+
if (scrubbed.redactions.length > 0)
|
|
77
|
+
redactions = scrubbed.redactions;
|
|
78
|
+
}
|
|
79
|
+
const step = {
|
|
80
|
+
id: `${this.runId}:${index}`,
|
|
81
|
+
runId: this.runId,
|
|
82
|
+
index,
|
|
83
|
+
type: input.type,
|
|
84
|
+
label: input.label,
|
|
85
|
+
startedAt: input.startedAt,
|
|
86
|
+
durationMs: input.durationMs,
|
|
87
|
+
tokens: input.tokens,
|
|
88
|
+
cost: input.cost,
|
|
89
|
+
...(input.toolName !== undefined ? { toolName: input.toolName } : {}),
|
|
90
|
+
...payload,
|
|
91
|
+
spanId: input.spanId,
|
|
92
|
+
...(input.parentSpanId !== undefined ? { parentSpanId: input.parentSpanId } : {}),
|
|
93
|
+
hash: '',
|
|
94
|
+
prevHash: this.prevHash,
|
|
95
|
+
...(this.redactable ? buildCommitments(payload, hashVersion) : {}),
|
|
96
|
+
...(redactions !== undefined ? { redactions } : {}),
|
|
97
|
+
};
|
|
98
|
+
step.hash = hashStep(step, this.prevHash, hashVersion);
|
|
99
|
+
this.prevHash = step.hash;
|
|
100
|
+
this.steps.push(step);
|
|
101
|
+
if (this.journalFile)
|
|
102
|
+
appendFileSync(this.journalFile, journalLine({ kind: 'step', step }));
|
|
103
|
+
return step;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Finalize: totals + warnings + anchor via core's journal finalizer, sign if
|
|
107
|
+
* keys exist, save, delete the journal. Synchronous — every write involved is
|
|
108
|
+
* (the SDK recorder's `end` is async only because its signature is).
|
|
109
|
+
*/
|
|
110
|
+
end(opts = {}) {
|
|
111
|
+
if (this.ended)
|
|
112
|
+
throw new Error(`Recording "${this.runId}" has already ended.`);
|
|
113
|
+
this.ended = true;
|
|
114
|
+
if (this.steps.length === 0) {
|
|
115
|
+
throw new Error(`Recording "${this.runId}" has no steps; nothing to finalize.`);
|
|
116
|
+
}
|
|
117
|
+
const status = opts.status ?? (this.steps.some((s) => s.type === 'error') ? 'failed' : 'completed');
|
|
118
|
+
if (this.journalFile)
|
|
119
|
+
appendFileSync(this.journalFile, journalLine({ kind: 'end', status }));
|
|
120
|
+
// finalizeJournal verifies the assembled record and throws if the chain
|
|
121
|
+
// does not hold, so a broken run is never stored.
|
|
122
|
+
let run = finalizeJournal({ meta: this.meta, steps: this.steps, endedStatus: status });
|
|
123
|
+
if (this.dir) {
|
|
124
|
+
const keys = loadKeys(this.dir);
|
|
125
|
+
if (keys)
|
|
126
|
+
run = signRun(run, keys.privateKeyPem, keys.publicKeyPem);
|
|
127
|
+
const store = new RunStore(join(this.dir, 'traceglass.sqlite'));
|
|
128
|
+
try {
|
|
129
|
+
store.saveRun(run);
|
|
130
|
+
}
|
|
131
|
+
finally {
|
|
132
|
+
store.close();
|
|
133
|
+
}
|
|
134
|
+
if (this.journalFile)
|
|
135
|
+
unlinkSync(this.journalFile);
|
|
136
|
+
}
|
|
137
|
+
return run;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=writer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"writer.js","sourceRoot":"","sources":["../src/writer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC1F,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EACL,oBAAoB,EACpB,sBAAsB,EACtB,QAAQ,EACR,gBAAgB,EAChB,eAAe,EACf,QAAQ,EACR,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,OAAO,GAMR,MAAM,kBAAkB,CAAC;AAyD1B,SAAS,UAAU;IACjB,OAAO,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,aAAa,CAAC,CAAC;AACvE,CAAC;AAED;;;;;GAKG;AACH,SAAS,QAAQ,CAAC,GAAW;IAC3B,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,aAAa,CAAC,CAAC;IAC9C,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC;IAC5C,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IACvD,OAAO,EAAE,aAAa,EAAE,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,YAAY,EAAE,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,EAAE,CAAC;AAChG,CAAC;AAED,MAAM,OAAO,SAAS;IACX,KAAK,CAAS;IACd,SAAS,CAAS;IAEV,GAAG,CAAgB;IACnB,IAAI,CAAc;IAClB,WAAW,CAAgB;IAC3B,UAAU,CAAU;IACpB,QAAQ,CAAqB;IAC7B,KAAK,GAAW,EAAE,CAAC;IAC5B,QAAQ,GAAG,EAAE,CAAC;IACd,KAAK,GAAG,KAAK,CAAC;IAEtB,YAAY,IAAsB;QAChC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,EAAE,CAAC;QACrB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAC5D,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,IAAI,UAAU,EAAE,CAAC,CAAC;QACjE,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,KAAK,KAAK,CAAC;QAC5C,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,cAAc,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/E,IAAI,CAAC,IAAI,GAAG;YACV,IAAI,EAAE,MAAM;YACZ,aAAa,EAAE,sBAAsB;YACrC,EAAE,EAAE,IAAI,CAAC,KAAK;YACd,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,QAAQ,EAAE,IAAI,CAAC,QAAQ,IAAI,KAAK;YAChC,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,WAAW,EAAE,oBAAoB;SAClC,CAAC;QACF,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QACtF,IAAI,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;YACjC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YAC1D,cAAc,CAAC,IAAI,CAAC,WAAW,EAAE,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;QAC3D,CAAC;IACH,CAAC;IAED,IAAI,SAAS;QACX,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC;IAC3B,CAAC;IAED,IAAI,OAAO;QACT,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAED,mFAAmF;IACnF,IAAI,CAAC,KAAsB;QACzB,IAAI,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,KAAK,CAAC,cAAc,IAAI,CAAC,KAAK,sBAAsB,CAAC,CAAC;QAChF,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC;QAChC,MAAM,WAAW,GAAG,oBAAoB,CAAC;QAEzC,uEAAuE;QACvE,0DAA0D;QAC1D,IAAI,OAAO,GAAiE;YAC1E,GAAG,CAAC,KAAK,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC5D,GAAG,CAAC,KAAK,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC/D,GAAG,CAAC,KAAK,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC/E,CAAC;QACF,IAAI,UAA8B,CAAC;QACnC,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC7B,MAAM,QAAQ,GAAG,gBAAgB,CAAC,OAAO,EAAE,IAAI,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;YACvE,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC;YAC3B,IAAI,QAAQ,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC;gBAAE,UAAU,GAAG,QAAQ,CAAC,UAAU,CAAC;QACvE,CAAC;QAED,MAAM,IAAI,GAAS;YACjB,EAAE,EAAE,GAAG,IAAI,CAAC,KAAK,IAAI,KAAK,EAAE;YAC5B,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,KAAK;YACL,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,UAAU,EAAE,KAAK,CAAC,UAAU;YAC5B,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,GAAG,CAAC,KAAK,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACrE,GAAG,OAAO;YACV,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,GAAG,CAAC,KAAK,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,KAAK,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACjF,IAAI,EAAE,EAAE;YACR,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,gBAAgB,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAClE,GAAG,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACpD,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;QACvD,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC;QAC1B,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtB,IAAI,IAAI,CAAC,WAAW;YAAE,cAAc,CAAC,IAAI,CAAC,WAAW,EAAE,WAAW,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QAC5F,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;OAIG;IACH,GAAG,CAAC,OAA4C,EAAE;QAChD,IAAI,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,KAAK,CAAC,cAAc,IAAI,CAAC,KAAK,sBAAsB,CAAC,CAAC;QAChF,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAClB,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC5B,MAAM,IAAI,KAAK,CAAC,cAAc,IAAI,CAAC,KAAK,sCAAsC,CAAC,CAAC;QAClF,CAAC;QACD,MAAM,MAAM,GACV,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;QACvF,IAAI,IAAI,CAAC,WAAW;YAAE,cAAc,CAAC,IAAI,CAAC,WAAW,EAAE,WAAW,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;QAE7F,wEAAwE;QACxE,kDAAkD;QAClD,IAAI,GAAG,GAAG,eAAe,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,CAAC,CAAC;QAEvF,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;YACb,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAChC,IAAI,IAAI;gBAAE,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;YACpE,MAAM,KAAK,GAAG,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,mBAAmB,CAAC,CAAC,CAAC;YAChE,IAAI,CAAC;gBACH,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YACrB,CAAC;oBAAS,CAAC;gBACT,KAAK,CAAC,KAAK,EAAE,CAAC;YAChB,CAAC;YACD,IAAI,IAAI,CAAC,WAAW;gBAAE,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACrD,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;CACF"}
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@traceglass/otel",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "OpenTelemetry span processor that records gen_ai.* spans as tamper-evident traceglass runs",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist"
|
|
13
|
+
],
|
|
14
|
+
"keywords": [
|
|
15
|
+
"opentelemetry",
|
|
16
|
+
"otel",
|
|
17
|
+
"gen-ai",
|
|
18
|
+
"ai",
|
|
19
|
+
"agents",
|
|
20
|
+
"audit",
|
|
21
|
+
"compliance"
|
|
22
|
+
],
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsc -b",
|
|
26
|
+
"test": "vitest run"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@traceglass/core": "1.0.0"
|
|
30
|
+
}
|
|
31
|
+
}
|