@apifuse/provider-sdk 2.2.0-beta.55 → 2.2.0-beta.57

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.
@@ -1,7 +1,11 @@
1
- export const PROVIDER_TELEMETRY_HEADER = "X-ApiFuse-Provider-Telemetry";
2
- const MAX_HEADER_BYTES = 4_096;
1
+ import { closedEnum, RequestTelemetry, } from "./request-telemetry.js";
2
+ import { createTraceContext } from "./trace.js";
3
+ export { PROVIDER_TELEMETRY_HEADER } from "./request-telemetry.js";
3
4
  const MAX_PROXY_ATTEMPT_SAMPLES = 24;
4
5
  const MAX_PROXY_FAILOVER_SAMPLES = 12;
6
+ const MAX_RETAINED_PROXY_RESOLUTIONS = 64;
7
+ const PROXY_HEADER_TOKEN = /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,63}$/;
8
+ const PROXY_HEADER_HASH = /^[0-9a-f]{1,16}$/;
5
9
  const CACHE_STATUS_SEVERITY = {
6
10
  disabled: 0,
7
11
  memory_hit: 1,
@@ -23,17 +27,39 @@ function maxOptional(left, right) {
23
27
  function worseStatus(left, right) {
24
28
  return CACHE_STATUS_SEVERITY[right] > CACHE_STATUS_SEVERITY[left] ? right : left;
25
29
  }
26
- function encodeBase64Url(value) {
27
- return Buffer.from(value, "utf8").toString("base64url");
30
+ function aggregateResolution(acc, event) {
31
+ return {
32
+ provider: event.provider,
33
+ userAgentSource: event.userAgentSource ?? acc.userAgentSource,
34
+ cacheStatus: worseStatus(acc.cacheStatus, event.cacheStatus),
35
+ cacheHit: acc.cacheHit && event.cacheHit,
36
+ resolutionMs: acc.resolutionMs + event.resolutionMs,
37
+ allocatorMs: sumOptional(acc.allocatorMs, event.allocatorMs),
38
+ allocatorStatus: event.allocatorStatus ?? acc.allocatorStatus,
39
+ allocatorBodyClass: event.allocatorBodyClass ?? acc.allocatorBodyClass,
40
+ allocatorAttempts: sumOptional(acc.allocatorAttempts, event.allocatorAttempts),
41
+ lockWaitMs: sumOptional(acc.lockWaitMs, event.lockWaitMs),
42
+ redisReadMs: sumOptional(acc.redisReadMs, event.redisReadMs),
43
+ redisWriteMs: sumOptional(acc.redisWriteMs, event.redisWriteMs),
44
+ poolAgeMs: maxOptional(acc.poolAgeMs, event.poolAgeMs),
45
+ poolExpiresInMs: maxOptional(acc.poolExpiresInMs, event.poolExpiresInMs),
46
+ attempts: acc.attempts + event.attempts,
47
+ refreshes: sumOptional(acc.refreshes, event.refreshes),
48
+ };
28
49
  }
29
50
  export class ProxyTelemetryCollector {
51
+ key = "proxy";
30
52
  #events = [];
53
+ #resolutionCount = 0;
54
+ #aggregateResolution;
55
+ #lastSuccessfulResolution;
56
+ #vendors = [];
31
57
  #attempts = [];
32
58
  #failovers = [];
33
59
  #unresolvedVendors = [];
34
60
  recordProxyResolution(event) {
35
61
  const outcome = event.outcome === "error" ? "error" : "ok";
36
- this.#events.push({
62
+ const normalized = {
37
63
  provider: event.provider,
38
64
  outcome,
39
65
  ...(event.userAgentSource ? { userAgentSource: event.userAgentSource } : {}),
@@ -58,11 +84,29 @@ export class ProxyTelemetryCollector {
58
84
  : Math.max(0, Math.floor(event.poolExpiresInMs)),
59
85
  attempts: Math.max(1, Math.floor(event.attempts || 1)),
60
86
  refreshes: event.refreshes === undefined ? undefined : Math.max(0, Math.floor(event.refreshes)),
61
- });
87
+ };
88
+ this.#resolutionCount += 1;
89
+ if (this.#events.length < MAX_RETAINED_PROXY_RESOLUTIONS)
90
+ this.#events.push(normalized);
91
+ this.#aggregateResolution = this.#aggregateResolution
92
+ ? aggregateResolution(this.#aggregateResolution, normalized)
93
+ : normalized;
94
+ if (outcome === "ok")
95
+ this.#lastSuccessfulResolution = normalized;
96
+ if (!this.#vendors.includes(event.provider))
97
+ this.#vendors.push(event.provider);
62
98
  if (outcome === "error" && !this.#unresolvedVendors.includes(event.provider)) {
63
99
  this.#unresolvedVendors.push(event.provider);
64
100
  }
65
101
  }
102
+ /** Number of raw resolution events retained for bounded-history verification. */
103
+ get retainedResolutionEventCount() {
104
+ return this.#events.length;
105
+ }
106
+ /** Total resolutions included in the incremental aggregate. */
107
+ get resolutionEventCount() {
108
+ return this.#resolutionCount;
109
+ }
66
110
  recordProxyVendorFailover(event) {
67
111
  if (this.#failovers.length >= MAX_PROXY_FAILOVER_SAMPLES)
68
112
  return;
@@ -96,8 +140,7 @@ export class ProxyTelemetryCollector {
96
140
  });
97
141
  }
98
142
  toLogPayload() {
99
- const [first, ...rest] = this.#events;
100
- const okEvents = this.#events.filter((event) => event.outcome !== "error");
143
+ const aggregate = this.#aggregateResolution;
101
144
  const attemptSamples = this.#attempts.map((attempt, index) => ({
102
145
  n: index + 1,
103
146
  a: attempt.attempt,
@@ -115,31 +158,9 @@ export class ProxyTelemetryCollector {
115
158
  r: failover.reason,
116
159
  ...(failover.attempt === undefined ? {} : { a: failover.attempt }),
117
160
  }));
118
- if (okEvents.length === 0) {
119
- if (!first && failovers.length === 0)
161
+ if (!this.#lastSuccessfulResolution) {
162
+ if (!aggregate && failovers.length === 0)
120
163
  return undefined;
121
- const failureEvents = this.#events.filter((event) => event.outcome === "error");
122
- const [firstFailure, ...remainingFailures] = failureEvents;
123
- const aggregate = firstFailure
124
- ? remainingFailures.reduce((acc, event) => ({
125
- provider: event.provider,
126
- outcome: "error",
127
- cacheStatus: worseStatus(acc.cacheStatus, event.cacheStatus),
128
- cacheHit: acc.cacheHit && event.cacheHit,
129
- resolutionMs: acc.resolutionMs + event.resolutionMs,
130
- allocatorMs: sumOptional(acc.allocatorMs, event.allocatorMs),
131
- allocatorStatus: event.allocatorStatus ?? acc.allocatorStatus,
132
- allocatorBodyClass: event.allocatorBodyClass ?? acc.allocatorBodyClass,
133
- allocatorAttempts: sumOptional(acc.allocatorAttempts, event.allocatorAttempts),
134
- lockWaitMs: sumOptional(acc.lockWaitMs, event.lockWaitMs),
135
- redisReadMs: sumOptional(acc.redisReadMs, event.redisReadMs),
136
- redisWriteMs: sumOptional(acc.redisWriteMs, event.redisWriteMs),
137
- poolAgeMs: maxOptional(acc.poolAgeMs, event.poolAgeMs),
138
- poolExpiresInMs: maxOptional(acc.poolExpiresInMs, event.poolExpiresInMs),
139
- attempts: acc.attempts + event.attempts,
140
- refreshes: sumOptional(acc.refreshes, event.refreshes),
141
- }), firstFailure)
142
- : undefined;
143
164
  return {
144
165
  kind: "unresolved",
145
166
  vendors: [...this.#unresolvedVendors],
@@ -180,30 +201,9 @@ export class ProxyTelemetryCollector {
180
201
  };
181
202
  }
182
203
  // The serving vendor/protocol is the last successful resolution.
183
- const serving = okEvents[okEvents.length - 1] ?? first;
184
- const vendors = [];
185
- for (const event of this.#events) {
186
- if (!vendors.includes(event.provider))
187
- vendors.push(event.provider);
188
- }
189
- const aggregate = rest.reduce((acc, event) => ({
190
- provider: event.provider,
191
- userAgentSource: event.userAgentSource ?? acc.userAgentSource,
192
- cacheStatus: worseStatus(acc.cacheStatus, event.cacheStatus),
193
- cacheHit: acc.cacheHit && event.cacheHit,
194
- resolutionMs: acc.resolutionMs + event.resolutionMs,
195
- allocatorMs: sumOptional(acc.allocatorMs, event.allocatorMs),
196
- allocatorStatus: event.allocatorStatus ?? acc.allocatorStatus,
197
- allocatorBodyClass: event.allocatorBodyClass ?? acc.allocatorBodyClass,
198
- allocatorAttempts: sumOptional(acc.allocatorAttempts, event.allocatorAttempts),
199
- lockWaitMs: sumOptional(acc.lockWaitMs, event.lockWaitMs),
200
- redisReadMs: sumOptional(acc.redisReadMs, event.redisReadMs),
201
- redisWriteMs: sumOptional(acc.redisWriteMs, event.redisWriteMs),
202
- poolAgeMs: maxOptional(acc.poolAgeMs, event.poolAgeMs),
203
- poolExpiresInMs: maxOptional(acc.poolExpiresInMs, event.poolExpiresInMs),
204
- attempts: acc.attempts + event.attempts,
205
- refreshes: sumOptional(acc.refreshes, event.refreshes),
206
- }), first);
204
+ if (!aggregate)
205
+ return undefined;
206
+ const serving = this.#lastSuccessfulResolution;
207
207
  return {
208
208
  kind: "resolved",
209
209
  provider: serving.provider,
@@ -232,18 +232,59 @@ export class ProxyTelemetryCollector {
232
232
  attempts: aggregate.attempts,
233
233
  ...(aggregate.refreshes !== undefined ? { refreshes: aggregate.refreshes } : {}),
234
234
  ...(attemptSamples.length > 0 ? { attemptSamples } : {}),
235
- ...(vendors.length > 1 ? { vendors } : {}),
235
+ ...(this.#vendors.length > 1 ? { vendors: [...this.#vendors] } : {}),
236
236
  ...(failovers.length > 0 ? { failovers } : {}),
237
237
  };
238
238
  }
239
+ toHeaderPayload(log) {
240
+ const attemptSamples = log.attemptSamples?.map((sample) => ({
241
+ n: sample.n,
242
+ a: sample.a,
243
+ ...(sample.i === undefined ? {} : { i: sample.i }),
244
+ ...(sample.h && PROXY_HEADER_HASH.test(sample.h)
245
+ ? { h: closedEnum(sample.h) }
246
+ : {}),
247
+ o: closedEnum(sample.o),
248
+ ...(sample.c && PROXY_HEADER_TOKEN.test(sample.c) ? { c: closedEnum(sample.c) } : {}),
249
+ ...(sample.s === undefined ? {} : { s: sample.s }),
250
+ ...(sample.d === undefined ? {} : { d: sample.d }),
251
+ }));
252
+ const failovers = log.failovers?.map((failover) => ({
253
+ ...failover,
254
+ v: closedEnum(failover.v),
255
+ ...(failover.nx ? { nx: closedEnum(failover.nx) } : {}),
256
+ p: closedEnum(failover.p),
257
+ r: closedEnum(failover.r),
258
+ }));
259
+ if (log.kind === "resolved") {
260
+ return {
261
+ ...log,
262
+ kind: closedEnum(log.kind),
263
+ provider: closedEnum(log.provider),
264
+ ...(log.userAgentSource ? { userAgentSource: closedEnum(log.userAgentSource) } : {}),
265
+ ...(log.protocol ? { protocol: closedEnum(log.protocol) } : {}),
266
+ cacheStatus: closedEnum(log.cacheStatus),
267
+ ...(log.allocatorBodyClass
268
+ ? { allocatorBodyClass: closedEnum(log.allocatorBodyClass) }
269
+ : {}),
270
+ ...(attemptSamples ? { attemptSamples } : {}),
271
+ ...(log.vendors ? { vendors: log.vendors.map((vendor) => closedEnum(vendor)) } : {}),
272
+ ...(failovers ? { failovers } : {}),
273
+ };
274
+ }
275
+ return {
276
+ ...log,
277
+ kind: closedEnum(log.kind),
278
+ vendors: log.vendors.map((vendor) => closedEnum(vendor)),
279
+ ...(log.cacheStatus ? { cacheStatus: closedEnum(log.cacheStatus) } : {}),
280
+ ...(log.allocatorBodyClass ? { allocatorBodyClass: closedEnum(log.allocatorBodyClass) } : {}),
281
+ ...(failovers ? { failovers } : {}),
282
+ ...(attemptSamples ? { attemptSamples } : {}),
283
+ };
284
+ }
239
285
  toHeaderValue() {
240
- const proxy = this.toLogPayload();
241
- if (!proxy)
242
- return undefined;
243
- const payload = { v: 1, proxy };
244
- const encoded = encodeBase64Url(JSON.stringify(payload));
245
- if (encoded.length > MAX_HEADER_BYTES)
246
- return undefined;
247
- return encoded;
286
+ const telemetry = new RequestTelemetry(createTraceContext());
287
+ telemetry.register(this);
288
+ return telemetry.toHeaderValue();
248
289
  }
249
290
  }
@@ -0,0 +1,70 @@
1
+ import type { ProxyTelemetrySink } from "../config/loader.js";
2
+ import type { ProxyTelemetryLogPayload } from "./proxy-telemetry.js";
3
+ import type { Span, TraceContext } from "./trace.js";
4
+ export declare const PROVIDER_TELEMETRY_HEADER = "X-ApiFuse-Provider-Telemetry";
5
+ export type TelemetryKey = "proxy" | "resolver" | "http" | "stealth" | "native" | "browser" | "ocr" | "stt" | "cache" | "state" | "events";
6
+ declare const CLOSED_ENUM: unique symbol;
7
+ /** A string from an SDK-declared, closed observability taxonomy. */
8
+ export type ClosedEnum<T extends string> = T & {
9
+ readonly [CLOSED_ENUM]: true;
10
+ };
11
+ /** Brands a value from an SDK-declared closed string union without changing it. */
12
+ export declare function closedEnum<T extends string>(value: T): ClosedEnum<T>;
13
+ /** Explicit exemption for the existing implementation-shaped cache key list. */
14
+ export type TenantOpaqueKeys = string[] & {
15
+ readonly __tenantOpaqueCacheKeys: true;
16
+ };
17
+ /** Names the deliberate tenant cache-key exemption without changing its wire value. */
18
+ export declare function tenantOpaqueKeys(value: string[]): TenantOpaqueKeys;
19
+ /**
20
+ * Compile-time projection for the gateway ingestion contract. Plain strings,
21
+ * URLs, hostnames, free text, unknown values, and escape-hatch properties reduce to `never`.
22
+ */
23
+ export type GatewayIngestible<T> = 0 extends 1 & T ? never : T extends object ? keyof T extends never ? never : {
24
+ [K in keyof T]: 0 extends 1 & T[K] ? never : unknown extends T[K] ? never : [NonNullable<T[K]>] extends [number | boolean | ClosedEnum<string>] ? T[K] : [NonNullable<T[K]>] extends [readonly (infer U)[]] ? 0 extends 1 & U ? never : unknown extends U ? never : [U] extends [
25
+ number | boolean | ClosedEnum<string> | GatewayIngestible<U>
26
+ ] ? T[K] : never : [NonNullable<T[K]>] extends [object] ? [NonNullable<T[K]>] extends [GatewayIngestible<NonNullable<T[K]>>] ? T[K] : never : never;
27
+ } : never;
28
+ /** Compile-time projection for tenant-visible, identity-neutral metadata. */
29
+ export type TenantNeutral<T> = 0 extends 1 & T ? never : T extends object ? {
30
+ [K in keyof T]: K extends `vendor${string}` | "provider" | "engine" | "model" | `${string}Host` ? never : 0 extends 1 & T[K] ? never : unknown extends T[K] ? never : [NonNullable<T[K]>] extends [
31
+ string[] & {
32
+ readonly __tenantOpaqueCacheKeys: true;
33
+ }
34
+ ] ? K extends "keys" ? T[K] : never : [NonNullable<T[K]>] extends [number | boolean | ClosedEnum<string>] ? T[K] : [NonNullable<T[K]>] extends [object] ? [NonNullable<T[K]>] extends [TenantNeutral<NonNullable<T[K]>>] ? T[K] : never : never;
35
+ } : never;
36
+ export interface SpanIndex {
37
+ readonly spans: readonly Span[];
38
+ readonly byName: ReadonlyMap<string, readonly Span[]>;
39
+ count(name: string): number;
40
+ durationMs(name: string): number;
41
+ }
42
+ export interface TelemetryContributor<Log extends object, Header extends object> {
43
+ readonly key: TelemetryKey;
44
+ toLogPayload(spans: SpanIndex): Log | undefined;
45
+ toHeaderPayload(log: Log): 0 extends 1 & Header ? never : [Header] extends [GatewayIngestible<Header>] ? Header | undefined : never;
46
+ }
47
+ export type RequestTelemetryLogPayload = {
48
+ proxy?: ProxyTelemetryLogPayload;
49
+ } & Partial<Record<Exclude<TelemetryKey, "proxy">, object>>;
50
+ /**
51
+ * Bounded structural defence in depth against casts and accidental free text.
52
+ * Closed-enum brands are erased at runtime; the type-level guard is the contract,
53
+ * while this check only enforces safe token shapes and collection bounds.
54
+ */
55
+ export declare function isGatewayIngestible(value: unknown): boolean;
56
+ /** One finalisation owner for every request-scoped telemetry contributor. */
57
+ export declare class RequestTelemetry {
58
+ #private;
59
+ readonly trace: TraceContext;
60
+ readonly contributors: Readonly<Partial<Record<TelemetryKey, {
61
+ readonly key: TelemetryKey;
62
+ toLogPayload(spans: SpanIndex): object | undefined;
63
+ }>>>;
64
+ constructor(trace: TraceContext);
65
+ register<Log extends object, Header extends object>(contributor: TelemetryContributor<Log, Header> & (0 extends 1 & Log ? never : 0 extends 1 & Header ? never : [Header] extends [GatewayIngestible<Header>] ? unknown : never)): void;
66
+ get proxy(): ProxyTelemetrySink | undefined;
67
+ toLogPayload(): RequestTelemetryLogPayload | undefined;
68
+ toHeaderValue(): string | undefined;
69
+ }
70
+ export {};
@@ -0,0 +1,228 @@
1
+ import { PROVIDER_OBSERVABILITY_TAXONOMY_VERSION } from "../observability.js";
2
+ export const PROVIDER_TELEMETRY_HEADER = "X-ApiFuse-Provider-Telemetry";
3
+ /** Brands a value from an SDK-declared closed string union without changing it. */
4
+ export function closedEnum(value) {
5
+ return value;
6
+ }
7
+ /** Names the deliberate tenant cache-key exemption without changing its wire value. */
8
+ export function tenantOpaqueKeys(value) {
9
+ return value;
10
+ }
11
+ const MAX_HEADER_BYTES = 4_096;
12
+ const MAX_INGESTIBLE_ARRAY_LENGTH = 64;
13
+ const MAX_INGESTIBLE_OBJECT_KEYS = 32;
14
+ const MAX_INGESTIBLE_DEPTH = 4;
15
+ const INGESTIBLE_TOKEN = /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,63}$/;
16
+ const HEADER_PRIORITY = [
17
+ "proxy",
18
+ "resolver",
19
+ "native",
20
+ "http",
21
+ "stealth",
22
+ "browser",
23
+ "ocr",
24
+ "stt",
25
+ "cache",
26
+ "state",
27
+ ];
28
+ const warnedInvalidKeys = new Set();
29
+ function createSpanIndex(trace) {
30
+ const spans = trace.getSpans().slice();
31
+ const mutableByName = new Map();
32
+ for (const span of spans) {
33
+ const named = mutableByName.get(span.name);
34
+ if (named)
35
+ named.push(span);
36
+ else
37
+ mutableByName.set(span.name, [span]);
38
+ }
39
+ const byName = new Map(mutableByName);
40
+ return {
41
+ spans,
42
+ byName,
43
+ count(name) {
44
+ return byName.get(name)?.length ?? 0;
45
+ },
46
+ durationMs(name) {
47
+ return (byName.get(name) ?? []).reduce((total, span) => total + span.duration_ms, 0);
48
+ },
49
+ };
50
+ }
51
+ function isPlainRecord(value) {
52
+ if (value === null || typeof value !== "object" || Array.isArray(value))
53
+ return false;
54
+ const prototype = Object.getPrototypeOf(value);
55
+ return prototype === Object.prototype || prototype === null;
56
+ }
57
+ /**
58
+ * Bounded structural defence in depth against casts and accidental free text.
59
+ * Closed-enum brands are erased at runtime; the type-level guard is the contract,
60
+ * while this check only enforces safe token shapes and collection bounds.
61
+ */
62
+ export function isGatewayIngestible(value) {
63
+ const ancestors = new Set();
64
+ const visit = (candidate, depth) => {
65
+ if (typeof candidate === "number")
66
+ return Number.isFinite(candidate);
67
+ if (typeof candidate === "boolean")
68
+ return true;
69
+ if (typeof candidate === "string")
70
+ return INGESTIBLE_TOKEN.test(candidate);
71
+ if (candidate === null || typeof candidate !== "object")
72
+ return false;
73
+ if (depth >= MAX_INGESTIBLE_DEPTH || ancestors.has(candidate))
74
+ return false;
75
+ if (Array.isArray(candidate)) {
76
+ if (candidate.length > MAX_INGESTIBLE_ARRAY_LENGTH)
77
+ return false;
78
+ ancestors.add(candidate);
79
+ const valid = candidate.every((item, index) => Object.hasOwn(candidate, index) && visit(item, depth + 1));
80
+ ancestors.delete(candidate);
81
+ return valid;
82
+ }
83
+ if (!isPlainRecord(candidate))
84
+ return false;
85
+ const keys = Object.keys(candidate);
86
+ if (keys.length > MAX_INGESTIBLE_OBJECT_KEYS)
87
+ return false;
88
+ ancestors.add(candidate);
89
+ const valid = keys.every((key) => visit(candidate[key], depth + 1));
90
+ ancestors.delete(candidate);
91
+ return valid;
92
+ };
93
+ try {
94
+ return visit(value, 0);
95
+ }
96
+ catch {
97
+ return false;
98
+ }
99
+ }
100
+ function encodeEnvelope(envelope) {
101
+ return Buffer.from(JSON.stringify(envelope), "utf8").toString("base64url");
102
+ }
103
+ function warnInvalidSibling(key) {
104
+ if (warnedInvalidKeys.has(key))
105
+ return;
106
+ warnedInvalidKeys.add(key);
107
+ try {
108
+ console.warn(`[provider-sdk] Dropped invalid telemetry sibling "${key}"; contributors must return serializable log objects and bounded gateway-safe header values.`);
109
+ }
110
+ catch {
111
+ // A host-provided console must not let telemetry fail the request path.
112
+ }
113
+ }
114
+ function isProxySink(value) {
115
+ if (value === null || typeof value !== "object")
116
+ return false;
117
+ try {
118
+ return typeof Reflect.get(value, "recordProxyResolution") === "function";
119
+ }
120
+ catch {
121
+ return false;
122
+ }
123
+ }
124
+ /** One finalisation owner for every request-scoped telemetry contributor. */
125
+ export class RequestTelemetry {
126
+ trace;
127
+ contributors;
128
+ #registered = {};
129
+ #exposed = {};
130
+ #proxy;
131
+ constructor(trace) {
132
+ this.trace = trace;
133
+ this.contributors = this.#exposed;
134
+ }
135
+ register(contributor) {
136
+ if (this.#registered[contributor.key]) {
137
+ throw new TypeError(`Telemetry contributor "${contributor.key}" is already registered.`);
138
+ }
139
+ const registered = {
140
+ key: contributor.key,
141
+ toLogPayload: (spans) => contributor.toLogPayload(spans),
142
+ toHeaderPayload: (log) => contributor.toHeaderPayload(log),
143
+ };
144
+ this.#registered[contributor.key] = registered;
145
+ this.#exposed[contributor.key] = contributor;
146
+ if (contributor.key === "proxy" && isProxySink(contributor))
147
+ this.#proxy = contributor;
148
+ }
149
+ get proxy() {
150
+ return this.#proxy;
151
+ }
152
+ toLogPayload() {
153
+ const spans = createSpanIndex(this.trace);
154
+ const payload = {};
155
+ for (const key of Object.keys(this.#registered)) {
156
+ const contributor = this.#registered[key];
157
+ if (!contributor)
158
+ continue;
159
+ try {
160
+ const sibling = contributor.toLogPayload(spans);
161
+ if (sibling === undefined)
162
+ continue;
163
+ JSON.stringify(sibling);
164
+ payload[key] = sibling;
165
+ }
166
+ catch {
167
+ warnInvalidSibling(key);
168
+ }
169
+ }
170
+ return Object.keys(payload).length > 0 ? payload : undefined;
171
+ }
172
+ toHeaderValue() {
173
+ const spans = createSpanIndex(this.trace);
174
+ const siblings = {};
175
+ for (const key of HEADER_PRIORITY) {
176
+ const contributor = this.#registered[key];
177
+ if (!contributor)
178
+ continue;
179
+ try {
180
+ const log = contributor.toLogPayload(spans);
181
+ if (log === undefined)
182
+ continue;
183
+ const projected = contributor.toHeaderPayload(log);
184
+ if (projected === undefined)
185
+ continue;
186
+ if (!isGatewayIngestible(projected)) {
187
+ warnInvalidSibling(key);
188
+ continue;
189
+ }
190
+ const serialized = JSON.stringify(projected);
191
+ const decoded = JSON.parse(serialized);
192
+ if (!isGatewayIngestible(decoded) || !isPlainRecord(decoded)) {
193
+ warnInvalidSibling(key);
194
+ continue;
195
+ }
196
+ siblings[key] = decoded;
197
+ }
198
+ catch {
199
+ warnInvalidSibling(key);
200
+ }
201
+ }
202
+ if (Object.keys(siblings).length === 0)
203
+ return undefined;
204
+ // Gateway uses permissive json.Unmarshal (unknown keys are ignored) but
205
+ // requires v === 1. The observability taxonomy is additive and independent.
206
+ const envelope = {
207
+ v: 1,
208
+ taxonomy: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
209
+ ...siblings,
210
+ };
211
+ let encoded = encodeEnvelope(envelope);
212
+ if (encoded.length <= MAX_HEADER_BYTES)
213
+ return encoded;
214
+ for (let index = HEADER_PRIORITY.length - 1; index >= 0; index -= 1) {
215
+ const key = HEADER_PRIORITY[index];
216
+ if (!key || !(key in envelope))
217
+ continue;
218
+ delete envelope[key];
219
+ if (!HEADER_PRIORITY.some((candidate) => candidate in envelope))
220
+ return undefined;
221
+ envelope.truncated = true;
222
+ encoded = encodeEnvelope(envelope);
223
+ if (encoded.length <= MAX_HEADER_BYTES)
224
+ return encoded;
225
+ }
226
+ return undefined;
227
+ }
228
+ }
@@ -1,5 +1,7 @@
1
1
  export type { ProxyCacheStatus, ProxyProtocol, ProxyUserAgentSource, ProxyVendorName, SmartproxyAllocatorBodyClass, } from "../config/loader.js";
2
- export type { ProxyTelemetryLogPayload, ProxyTelemetryResolvedPayload, ProxyTelemetryUnresolvedPayload, } from "../runtime/proxy-telemetry.js";
2
+ export type { ProxyTelemetryHeaderPayload, ProxyHash, ProxyTelemetryLogPayload, ProxyTelemetryResolvedPayload, ProxyTelemetryUnresolvedPayload, } from "../runtime/proxy-telemetry.js";
3
+ export { RequestTelemetry, closedEnum, type ClosedEnum, type GatewayIngestible, type RequestTelemetryLogPayload, type SpanIndex, type TelemetryContributor, type TelemetryKey, type TenantNeutral, } from "../runtime/request-telemetry.js";
4
+ export type { Span, TraceContext } from "../runtime/trace.js";
3
5
  export { createServerApp, createServerAppAsync, ERROR_OBSERVABILITY_HEADER, type ErrorObservabilityDetails, type ProviderServerCloseOptions, type ProviderServerHandle, type ProviderErrorCauseFrame, type ProviderServerLogEvent, type ProviderServerLogger, type ProviderServerOperationExecutor, type ProviderServerOperationExecutorInput, type ProviderServerOptions, type ProviderServerStatefulForwardEnvelope, type ServeOptions, serve, } from "./serve.js";
4
6
  export type { ProviderErrorObservability } from "../errors.js";
5
7
  export { computeSelfTestPlanDigest, createSelfTestApp, createSelfTestAuthFlowInvoke, createSelfTestInvoke, DEFAULT_SELF_TEST_REQUEST_BUDGET_MS, isSelfTestReadOnlyOperation, PROVIDER_RUNTIME_SELF_TEST_REQUEST_BUDGET_MS_ENV, resolveSelfTestPort, SELF_TEST_AUTH_FLOW_MULTI_TURN_SKIP_REASON, SELF_TEST_AUTH_FLOW_REJECTED_SKIP_REASON, SELF_TEST_HEALTHZ_PATH, SELF_TEST_PATH, SELF_TEST_SCHEMA_VERSION, type SelfTestAppOptions, type SelfTestAuthFlowInvoke, type SelfTestAuthFlowRoute, type SelfTestCaseResult, type SelfTestCaseStatus, type SelfTestOperationInvoke, type SelfTestRequest, SelfTestRequestSchema, type SelfTestResponse, } from "./self-test.js";
@@ -1,3 +1,4 @@
1
+ export { RequestTelemetry, closedEnum, } from "../runtime/request-telemetry.js";
1
2
  export { createServerApp, createServerAppAsync, ERROR_OBSERVABILITY_HEADER, serve, } from "./serve.js";
2
3
  export { computeSelfTestPlanDigest, createSelfTestApp, createSelfTestAuthFlowInvoke, createSelfTestInvoke, DEFAULT_SELF_TEST_REQUEST_BUDGET_MS, isSelfTestReadOnlyOperation, PROVIDER_RUNTIME_SELF_TEST_REQUEST_BUDGET_MS_ENV, resolveSelfTestPort, SELF_TEST_AUTH_FLOW_MULTI_TURN_SKIP_REASON, SELF_TEST_AUTH_FLOW_REJECTED_SKIP_REASON, SELF_TEST_HEALTHZ_PATH, SELF_TEST_PATH, SELF_TEST_SCHEMA_VERSION, SelfTestRequestSchema, } from "./self-test.js";
3
4
  export { resolveHealthCheckInputDateTokens, } from "./self-test-input-tokens.js";