@nekuda/webmcp-sdk 0.4.0-dev.7.3
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/CHANGELOG.md +244 -0
- package/dist/define.d.ts +79 -0
- package/dist/index.d.ts +82 -0
- package/dist/index.js +1607 -0
- package/dist/register.d.ts +80 -0
- package/dist/spec.d.ts +56 -0
- package/dist/telemetry-context.d.ts +164 -0
- package/dist/telemetry-events.d.ts +224 -0
- package/dist/telemetry-fields.d.ts +119 -0
- package/dist/telemetry.d.ts +471 -0
- package/dist/tool-metrics.d.ts +52 -0
- package/dist/tracking.d.ts +208 -0
- package/dist/transport.d.ts +94 -0
- package/package.json +45 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anonymous visitor/session tracking for WebMCP tool calls. Opt-in via the
|
|
3
|
+
* `tracking` option on `registerTools`.
|
|
4
|
+
*
|
|
5
|
+
* This module owns three things: anonymous identity, event payload assembly
|
|
6
|
+
* (`buildEventPayload` + `boundEventPayload`), and emission (`track`).
|
|
7
|
+
*
|
|
8
|
+
* Identity is intentionally minimal and PII-free: random UUIDs only, namespaced
|
|
9
|
+
* per `apiKey` (a non-reversible hash — the raw key never lands in storage) so
|
|
10
|
+
* two tenants sharing an origin never mix identity. Event *payloads* are a
|
|
11
|
+
* separate matter — page URL, tool `input`, and `response` are captured verbatim
|
|
12
|
+
* and may carry PII; redaction is a downstream/backend concern.
|
|
13
|
+
*
|
|
14
|
+
* - `visitorId` lives in `localStorage` and is stable across visits.
|
|
15
|
+
* - `sessionId` lives in `sessionStorage` and is refreshed by a `last_seen`
|
|
16
|
+
* timestamp; after `SESSION_TIMEOUT_MS` of inactivity a new session is minted.
|
|
17
|
+
*
|
|
18
|
+
* Browser storage can be absent (non-DOM env) or throw (privacy mode). Every
|
|
19
|
+
* access is guarded, and on failure the module falls back to per-page-load
|
|
20
|
+
* in-memory IDs so identity resolution never throws.
|
|
21
|
+
*
|
|
22
|
+
* Consent + outputs: *this channel* is silent by default and `disabled: true` is
|
|
23
|
+
* its hard consent gate — but it is not an SDK-wide one. The default-on usage
|
|
24
|
+
* telemetry channel (`src/telemetry.ts`) emits derived page and tool signals with
|
|
25
|
+
* no `apiKey`, has its own opt-outs (`telemetry: false`, `__WEBMCP_TELEMETRY__`,
|
|
26
|
+
* and GPC), and touches no browser storage at all — so `disabled` does not narrow
|
|
27
|
+
* it in any way. The identity below is this channel's alone. Otherwise the
|
|
28
|
+
* two outputs here are independent and composable —
|
|
29
|
+
* `apiKey` enables the backend transport (`sendToCollect`) and `otel: true`
|
|
30
|
+
* enables OTEL LogRecords (`emitOtelLog`), both in `src/transport.ts`. With
|
|
31
|
+
* neither configured `track()` builds nothing at all: no identity resolution,
|
|
32
|
+
* no storage access, no output. Emission is wrapped end-to-end so telemetry can
|
|
33
|
+
* never throw into the caller.
|
|
34
|
+
*/
|
|
35
|
+
import { type CollectConfig } from "./transport.js";
|
|
36
|
+
/**
|
|
37
|
+
* 32-bit FNV-1a over UTF-16 code units, rendered as 8 lowercase hex chars. Not
|
|
38
|
+
* a security primitive — a cheap, dependency-free way to derive a stable,
|
|
39
|
+
* non-reversible key from a caller-supplied string (storage namespaces here,
|
|
40
|
+
* tool schema hashes in the telemetry channel). Collisions are possible and
|
|
41
|
+
* acceptable for both uses.
|
|
42
|
+
*/
|
|
43
|
+
export declare function fnv1a(input: string): string;
|
|
44
|
+
/**
|
|
45
|
+
* Stable, non-reversible storage namespace derived from the `apiKey` so one key
|
|
46
|
+
* ≈ one site keeps its own visitor/session identity without the customer ever
|
|
47
|
+
* knowing their `org_id` (server-injected) and without exposing the key itself
|
|
48
|
+
* in `localStorage`. Not for security, only to separate tenants sharing an
|
|
49
|
+
* origin. An absent/empty key falls back to a shared constant namespace.
|
|
50
|
+
*/
|
|
51
|
+
export declare function storageNamespace(apiKey?: string): string;
|
|
52
|
+
/**
|
|
53
|
+
* A v4 UUID that resolves in every context. `crypto.randomUUID` is
|
|
54
|
+
* secure-context-only — `undefined` on plain `http://` pages — so we fall back
|
|
55
|
+
* to `crypto.getRandomValues` (available in insecure contexts) and finally
|
|
56
|
+
* {@link fillPseudoRandom}, preserving this module's "identity resolution never
|
|
57
|
+
* throws" guarantee even on non-HTTPS sites.
|
|
58
|
+
*/
|
|
59
|
+
export declare function randomId(): string;
|
|
60
|
+
/** Stable anonymous visitor ID, persisted in `localStorage` across visits. */
|
|
61
|
+
export declare function getOrCreateVisitorId(namespace: string): string;
|
|
62
|
+
/**
|
|
63
|
+
* Journey session ID in `sessionStorage`. Reuses the existing session while
|
|
64
|
+
* `last_seen` is within the timeout, otherwise mints a new one. Every call
|
|
65
|
+
* refreshes `last_seen`.
|
|
66
|
+
*/
|
|
67
|
+
export declare function getOrCreateSessionId(namespace: string): string;
|
|
68
|
+
/**
|
|
69
|
+
* Configures tool-call tracking for a `registerTools` batch.
|
|
70
|
+
*
|
|
71
|
+
* `apiKey` is the sole tenant credential: it enables the backend transport
|
|
72
|
+
* (POST to the collect endpoint, authenticated via the `x-api-key` header) and
|
|
73
|
+
* namespaces identity storage; the backend resolves it to the `org_id`, which
|
|
74
|
+
* the client never sends or knows. `otel: true` emits each event as an OTEL
|
|
75
|
+
* LogRecord via the global `LoggerProvider`. These two outputs are independent
|
|
76
|
+
* and composable. `endpoint` overrides the backend URL. `disabled: true` is the
|
|
77
|
+
* hard consent gate for both of them — it does not affect the default-on
|
|
78
|
+
* telemetry channel at all, see the module comment. With neither `apiKey` nor
|
|
79
|
+
* `otel` set, no event is built at all.
|
|
80
|
+
*/
|
|
81
|
+
export interface TrackingOptions {
|
|
82
|
+
/** Presence (non-empty) enables the backend transport and namespaces storage. */
|
|
83
|
+
apiKey?: string;
|
|
84
|
+
/** Override the collect endpoint; ignored without `apiKey`. */
|
|
85
|
+
endpoint?: string;
|
|
86
|
+
/** Emit each event as an OTEL LogRecord via the global `LoggerProvider`. */
|
|
87
|
+
otel?: boolean;
|
|
88
|
+
/**
|
|
89
|
+
* Consent gate for this channel: nothing is built or emitted here. It does not
|
|
90
|
+
* reach the default-on telemetry channel, which resolves no identity and touches
|
|
91
|
+
* no storage — `telemetry: false` on `registerTools` is that channel's opt-out.
|
|
92
|
+
*/
|
|
93
|
+
disabled?: boolean;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Which of this channel's two outputs these options actually enable, after the
|
|
97
|
+
* consent gate. The single source of truth for "is this channel live", because
|
|
98
|
+
* three call sites need exactly this answer and must not drift: {@link track}
|
|
99
|
+
* fans out to the enabled outputs, `register.ts` skips building a per-call
|
|
100
|
+
* emitter (and the `callId` behind it) when neither is live, and telemetry's
|
|
101
|
+
* `config.*` reports what this channel is *doing*.
|
|
102
|
+
*
|
|
103
|
+
* Reads a caller-supplied object, so it is guarded: a hostile `apiKey` getter
|
|
104
|
+
* means "no outputs", never a throw into tool execution or page load.
|
|
105
|
+
*/
|
|
106
|
+
export declare function trackingOutputs(options?: TrackingOptions): {
|
|
107
|
+
toBackend: boolean;
|
|
108
|
+
toOtel: boolean;
|
|
109
|
+
};
|
|
110
|
+
/** Best-effort page context on a tracking event; this channel's alone. */
|
|
111
|
+
export interface PageFields {
|
|
112
|
+
siteOrigin?: string;
|
|
113
|
+
url?: string;
|
|
114
|
+
path?: string;
|
|
115
|
+
referrer?: string;
|
|
116
|
+
title?: string;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* A single tracking event. Identity + timing fields are always present; page
|
|
120
|
+
* fields are best-effort (absent in a non-DOM env); event-specific data (e.g.
|
|
121
|
+
* `toolStableKey`, `input`, `response`) is spread in by the caller.
|
|
122
|
+
*/
|
|
123
|
+
export interface TrackingEvent extends PageFields {
|
|
124
|
+
/** Dedup key, unique per event. */
|
|
125
|
+
eventId: string;
|
|
126
|
+
visitorId: string;
|
|
127
|
+
sessionId: string;
|
|
128
|
+
eventName: string;
|
|
129
|
+
/** Event time as an ISO-8601 string. */
|
|
130
|
+
ts: string;
|
|
131
|
+
[key: string]: unknown;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* The error's message, or its string form for a non-`Error` throw. Both a throwing
|
|
135
|
+
* `message` getter and a throwing `toString` are possible on a value the page threw
|
|
136
|
+
* at us, so both are guarded; an unreadable value reports as empty rather than
|
|
137
|
+
* breaking event assembly — or, worse, replacing the tool's own error with ours.
|
|
138
|
+
*
|
|
139
|
+
* Shared by both channels, but only one sends it raw: the authenticated one reports
|
|
140
|
+
* it as `error` on the response event, sliced by the 64 KB bound downstream. The
|
|
141
|
+
* default-on one carries no raw message at all — this is only the input to
|
|
142
|
+
* `errorSignature`, which templates and caps it where it is read.
|
|
143
|
+
*/
|
|
144
|
+
export declare function errorMessage(error: unknown): string;
|
|
145
|
+
/** Assemble a tracking event from resolved identity, an event name, and extra data. */
|
|
146
|
+
export declare function buildEventPayload(params: {
|
|
147
|
+
visitorId: string;
|
|
148
|
+
sessionId: string;
|
|
149
|
+
eventName: string;
|
|
150
|
+
data?: Record<string, unknown>;
|
|
151
|
+
}): TrackingEvent;
|
|
152
|
+
/**
|
|
153
|
+
* Truncate a string so its JSON-escaped form fits in `maxBytes` UTF-8 bytes,
|
|
154
|
+
* without splitting a code point. The event size bound (schema §1.5) is on the
|
|
155
|
+
* *serialized* event, so a raw byte slice is not enough: `"`/`\`/control chars
|
|
156
|
+
* expand under escaping, so a 16 KB raw slice of escapable text can serialize
|
|
157
|
+
* to ~96 KB and blow the bound on its own. Measure the escaped length and
|
|
158
|
+
* binary-search the longest byte-prefix that still fits.
|
|
159
|
+
*
|
|
160
|
+
* Exported because the default-on telemetry channel (`src/telemetry.ts`) caps every
|
|
161
|
+
* string at its source — brands, error signatures, per-tool identities — since the
|
|
162
|
+
* ladder below reaches none of them.
|
|
163
|
+
*/
|
|
164
|
+
export declare function sliceToBytes(value: string, maxBytes: number): string;
|
|
165
|
+
/**
|
|
166
|
+
* Serialized UTF-8 byte size of a value, or `undefined` when it cannot be
|
|
167
|
+
* serialized (a circular structure). Exported because the default-on telemetry
|
|
168
|
+
* channel reports a tool result's size as `response.bytes` instead of its content
|
|
169
|
+
* — the same measurement the truncation ladder below makes its decisions on, so
|
|
170
|
+
* one implementation keeps the reported size and the enforced bound in agreement.
|
|
171
|
+
*/
|
|
172
|
+
export declare function serializedBytes(value: unknown): number | undefined;
|
|
173
|
+
/**
|
|
174
|
+
* Keep a serialized event under the schema §1.5 64 KB bound. Fields are dropped
|
|
175
|
+
* in order — `response`, then `input`, then `error` (a string sliced to 16 KB,
|
|
176
|
+
* anything else replaced) — each replaced by a `{ __truncated: true,
|
|
177
|
+
* originalBytes }` marker, and only as far as needed to fit. Unserializable
|
|
178
|
+
* (circular) payload fields are always replaced with a marker. Then the telemetry
|
|
179
|
+
* channel's `tools[]` degrades in two steps of its own ({@link boundTools}). As a
|
|
180
|
+
* final guarantee, every caller-uncontrolled page string ({@link pageFields}) is
|
|
181
|
+
* sliced to a byte cap so a giant URL alone can't exceed the bound. Never
|
|
182
|
+
* throws: on any failure the original event is returned unchanged.
|
|
183
|
+
*
|
|
184
|
+
* Generic over the event shape so one bound serves both channels and the returned
|
|
185
|
+
* event keeps its type. The rungs are per-field, and a rung whose field the event
|
|
186
|
+
* does not carry is a no-op: `tool_registration` reaches only the `tools` one, and
|
|
187
|
+
* `tool_call` reaches none of them — every string it carries is capped where it is
|
|
188
|
+
* read, and its `response` is metrics rather than the result itself.
|
|
189
|
+
*/
|
|
190
|
+
export declare function boundEventPayload<T extends Record<string, unknown>>(event: T): T;
|
|
191
|
+
/**
|
|
192
|
+
* The two independent tracking outputs. Injectable so orchestration can be
|
|
193
|
+
* tested without touching the network or the OTEL packages (spec.ts pattern).
|
|
194
|
+
*/
|
|
195
|
+
export interface TrackingSinks {
|
|
196
|
+
sendToCollect: (event: TrackingEvent, config: CollectConfig) => void;
|
|
197
|
+
emitOtelLog: (event: TrackingEvent) => void;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Emit a tracking event. Silent by default: a no-op when `disabled`, and when
|
|
201
|
+
* neither `apiKey` nor `otel` is configured it builds nothing at all — no
|
|
202
|
+
* identity resolution, no storage access, no output. Otherwise it resolves
|
|
203
|
+
* identity, builds the payload, bounds it to the 64 KB schema limit, and fans
|
|
204
|
+
* it out to each enabled output (backend transport and/or OTEL), both of which
|
|
205
|
+
* are themselves best-effort. Wrapped end-to-end in try/catch so tracking can
|
|
206
|
+
* never break the caller (tool execution owns correctness, not telemetry).
|
|
207
|
+
*/
|
|
208
|
+
export declare function track(options: TrackingOptions, eventName: string, data?: Record<string, unknown>, sinks?: TrackingSinks): void;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Backend transport for tracking events (schema §1). Off unless an `apiKey` is
|
|
3
|
+
* configured; then `sendToCollect` POSTs each bounded event to the collect
|
|
4
|
+
* endpoint, fire-and-forget, one request per event — no batching or retries in
|
|
5
|
+
* v0.
|
|
6
|
+
*
|
|
7
|
+
* Transport is `fetch(..., { keepalive: true })` — unload-safe and, unlike
|
|
8
|
+
* `sendBeacon`, able to set custom headers. Auth travels as the `x-api-key`
|
|
9
|
+
* **header** (never a query param or body field): the deployed API Gateway
|
|
10
|
+
* authorizer's identity source is that header, so a request without it is
|
|
11
|
+
* rejected 401 before the authorizer runs, and keeping the key out of the URL
|
|
12
|
+
* keeps it out of referrer logs and out of the canonical `payload` column. The
|
|
13
|
+
* event JSON is the bare POST body; `org_id` is server-injected from the key.
|
|
14
|
+
*
|
|
15
|
+
* The scope is injectable for tests (spec.ts pattern). Every browser global is
|
|
16
|
+
* guarded and the whole function is wrapped so telemetry never throws into the
|
|
17
|
+
* caller.
|
|
18
|
+
*
|
|
19
|
+
* `sendTelemetry` is the sibling sender for the default-on usage-telemetry
|
|
20
|
+
* channel (`src/telemetry.ts`): same keepalive/fire-and-forget discipline and the
|
|
21
|
+
* same `x-api-key` header *when the page configured a key*, on a distinct
|
|
22
|
+
* `/v1/telemetry` path that accepts the request either way. That channel runs on
|
|
23
|
+
* pages that never key the authenticated one, so a keyless send is the normal
|
|
24
|
+
* case, not a failure — the backend attributes those by CORS `Origin` instead.
|
|
25
|
+
*
|
|
26
|
+
* The second, independent output is OTEL (`emitOtelLog`): each event becomes a
|
|
27
|
+
* LogRecord on the global `LoggerProvider` via the optional peer dep
|
|
28
|
+
* `@opentelemetry/api-logs`. The host app owns exporters and processing; a
|
|
29
|
+
* missing package or unregistered provider is a permanent silent no-op.
|
|
30
|
+
*/
|
|
31
|
+
import type { TrackingEvent } from "./tracking.js";
|
|
32
|
+
/** Default ingest endpoint; the base host is baked per flavor (see {@link INGEST_BASE}). */
|
|
33
|
+
export declare const DEFAULT_COLLECT_ENDPOINT: string;
|
|
34
|
+
/**
|
|
35
|
+
* Default ingest endpoint for the default-on usage-telemetry channel — a
|
|
36
|
+
* **separate path** from {@link DEFAULT_COLLECT_ENDPOINT} so the backend can route
|
|
37
|
+
* the two channels independently: one authorizer-protected, this one unauthenticated
|
|
38
|
+
* by default and accepting the same request with an optional `x-api-key` for tenant
|
|
39
|
+
* attribution. Same per-flavor base host (see {@link INGEST_BASE}).
|
|
40
|
+
*/
|
|
41
|
+
export declare const DEFAULT_TELEMETRY_ENDPOINT: string;
|
|
42
|
+
/** Backend transport config; `apiKey` presence is what enables this output. */
|
|
43
|
+
export interface CollectConfig {
|
|
44
|
+
apiKey: string;
|
|
45
|
+
/** Override the ingest endpoint; defaults to {@link DEFAULT_COLLECT_ENDPOINT}. */
|
|
46
|
+
endpoint?: string;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* POST a tracking event to the collect endpoint via `fetch(keepalive)`, sending
|
|
50
|
+
* the key as the `x-api-key` header. Never throws: any failure is swallowed and
|
|
51
|
+
* a no-`fetch` environment is a silent no-op.
|
|
52
|
+
*/
|
|
53
|
+
export declare function sendToCollect(event: TrackingEvent, config: CollectConfig, scope?: object): void;
|
|
54
|
+
/**
|
|
55
|
+
* POST a usage-telemetry event to the telemetry endpoint via `fetch(keepalive)`.
|
|
56
|
+
* The path is the same whether or not `apiKey` is given: the header only *adds*
|
|
57
|
+
* tenant attribution to an event the backend would accept anyway, so a page that
|
|
58
|
+
* never configures the authenticated channel keeps sending, and the backend falls
|
|
59
|
+
* back to the CORS `Origin` header. Never throws: any failure is swallowed and a
|
|
60
|
+
* no-`fetch` environment is a silent no-op.
|
|
61
|
+
*
|
|
62
|
+
* `apiKey` is `unknown` because it originates in caller-supplied options that no
|
|
63
|
+
* compiler checked; anything that is not a non-blank string sends unauthenticated
|
|
64
|
+
* rather than putting `"undefined"` (or a hostile object's `toString`) on the
|
|
65
|
+
* wire. Note that adding the header makes the request non-simple under CORS, so
|
|
66
|
+
* an authenticated beacon costs a preflight the anonymous one does not.
|
|
67
|
+
*
|
|
68
|
+
* The event stays a bare `object` here so this module never has to import the
|
|
69
|
+
* assembled shape from `telemetry.ts` (which imports this function).
|
|
70
|
+
*/
|
|
71
|
+
export declare function sendTelemetry(event: object, scope?: object, endpoint?: string, apiKey?: unknown): void;
|
|
72
|
+
/** The `Logger.emit` slice we use — structural, no hard OTEL type coupling. */
|
|
73
|
+
interface LoggerLike {
|
|
74
|
+
emit(record: {
|
|
75
|
+
severityNumber?: number;
|
|
76
|
+
body?: unknown;
|
|
77
|
+
attributes?: Record<string, unknown>;
|
|
78
|
+
}): void;
|
|
79
|
+
}
|
|
80
|
+
/** The `logs` singleton slice we use from `@opentelemetry/api-logs`. */
|
|
81
|
+
interface LogsApiLike {
|
|
82
|
+
getLogger(name: string): LoggerLike;
|
|
83
|
+
}
|
|
84
|
+
/** Resolves the logs API, or a nullish value when OTEL is unavailable. */
|
|
85
|
+
type OtelLoader = () => LogsApiLike | null | undefined | Promise<LogsApiLike | null | undefined>;
|
|
86
|
+
/**
|
|
87
|
+
* Emit a bounded tracking event as an OTEL LogRecord through the global
|
|
88
|
+
* `LoggerProvider`. Off unless `otel` is configured. Fire-and-forget: the
|
|
89
|
+
* returned promise always resolves (never rejects) so callers can ignore it.
|
|
90
|
+
* No provider registered / package absent → silent no-op; `emit` throwing is
|
|
91
|
+
* swallowed. The host app owns all downstream exporting.
|
|
92
|
+
*/
|
|
93
|
+
export declare function emitOtelLog(event: TrackingEvent, loadLogs?: OtelLoader): Promise<void>;
|
|
94
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nekuda/webmcp-sdk",
|
|
3
|
+
"version": "0.4.0-dev.7.3",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Phase-1 WebMCP SDK: a thin wrapper over document.modelContext that plugin-generated code targets — defineTool + register/unregister lifecycle. This package pins the plugin↔SDK seam; anonymous tool-call tracking (backend transport via apiKey, OTEL LogRecords via otel) is opt-in through registerTools and default-silent, while anonymous usage telemetry is a separate unauthenticated channel that is on by default (opt out with telemetry: false).",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"CHANGELOG.md"
|
|
17
|
+
],
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"test": "bun test",
|
|
23
|
+
"typecheck": "tsc --noEmit",
|
|
24
|
+
"lint": "biome check .",
|
|
25
|
+
"build": "bun build src/index.ts --outdir dist --target browser --format esm --external @opentelemetry/api-logs",
|
|
26
|
+
"build:types": "tsc -p tsconfig.build.json"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@biomejs/biome": "1.9.4",
|
|
30
|
+
"@opentelemetry/api-logs": "^0.205.0",
|
|
31
|
+
"@types/bun": "^1.3.14",
|
|
32
|
+
"typescript": "^7.0.2"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"@opentelemetry/api-logs": ">=0.50.0"
|
|
36
|
+
},
|
|
37
|
+
"peerDependenciesMeta": {
|
|
38
|
+
"@opentelemetry/api-logs": {
|
|
39
|
+
"optional": true
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"trustedDependencies": [
|
|
43
|
+
"@biomejs/biome"
|
|
44
|
+
]
|
|
45
|
+
}
|