@apifuse/provider-sdk 2.2.0-beta.62 → 2.2.0-beta.63
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/AUTHORING.md +268 -9
- package/CHANGELOG.md +4 -0
- package/README.md +1 -0
- package/bin/apifuse-check.ts +84 -2
- package/bin/apifuse-dev.ts +11 -31
- package/bin/apifuse-migrate-deployment.ts +200 -0
- package/bin/apifuse-pack-check.ts +11 -4
- package/bin/apifuse-pack-types.ts +187 -4
- package/bin/apifuse-record.ts +44 -32
- package/bin/apifuse-test.ts +3 -1
- package/dist/cli/commands.d.ts +1 -1
- package/dist/cli/commands.js +11 -0
- package/dist/cli/deployment-intent.d.ts +178 -0
- package/dist/cli/deployment-intent.js +277 -0
- package/dist/cli/migrate-deployment-intent.d.ts +132 -0
- package/dist/cli/migrate-deployment-intent.js +1552 -0
- package/dist/cli/migrate-provider-shape.d.ts +5 -1
- package/dist/cli/migrate-provider-shape.js +4 -2
- package/dist/cli/resolver-runtime.d.ts +21 -0
- package/dist/cli/resolver-runtime.js +31 -0
- package/dist/define.d.ts +3 -0
- package/dist/define.js +5 -0
- package/dist/dev.d.ts +3 -1
- package/dist/dev.js +6 -1
- package/dist/engine.d.ts +35 -1
- package/dist/engine.js +21 -2
- package/dist/error-resolution.d.ts +1 -0
- package/dist/error-resolution.js +54 -0
- package/dist/errors.d.ts +39 -1
- package/dist/errors.js +50 -0
- package/dist/i18n/error-messages.d.ts +86 -0
- package/dist/i18n/error-messages.js +192 -0
- package/dist/i18n/index.d.ts +1 -0
- package/dist/i18n/index.js +1 -0
- package/dist/i18n/keys.d.ts +12 -0
- package/dist/i18n/keys.js +76 -5
- package/dist/index.d.ts +7 -1
- package/dist/index.js +6 -0
- package/dist/lint.d.ts +10 -0
- package/dist/lint.js +412 -49
- package/dist/provider.d.ts +5 -5
- package/dist/provider.js +2 -2
- package/dist/runtime/browser-telemetry-binding.d.ts +5 -0
- package/dist/runtime/browser-telemetry-binding.js +83 -0
- package/dist/runtime/browser-telemetry.d.ts +76 -0
- package/dist/runtime/browser-telemetry.js +165 -0
- package/dist/runtime/browser.d.ts +3 -0
- package/dist/runtime/browser.js +208 -47
- package/dist/runtime/cache-telemetry.d.ts +71 -0
- package/dist/runtime/cache-telemetry.js +177 -0
- package/dist/runtime/cache.js +67 -7
- package/dist/runtime/capability-response.d.ts +3 -0
- package/dist/runtime/capability-response.js +56 -0
- package/dist/runtime/capability-telemetry.d.ts +46 -0
- package/dist/runtime/capability-telemetry.js +68 -0
- package/dist/runtime/diagnostic-env.d.ts +3 -0
- package/dist/runtime/diagnostic-env.js +10 -1
- package/dist/runtime/engine-mode.d.ts +74 -0
- package/dist/runtime/engine-mode.js +152 -0
- package/dist/runtime/http-telemetry-guard.d.ts +10 -0
- package/dist/runtime/http-telemetry-guard.js +62 -10
- package/dist/runtime/http.js +141 -35
- package/dist/runtime/instrumentation.js +92 -13
- package/dist/runtime/ocr-telemetry.d.ts +82 -0
- package/dist/runtime/ocr-telemetry.js +192 -0
- package/dist/runtime/ocr.js +30 -8
- package/dist/runtime/request-telemetry.d.ts +2 -0
- package/dist/runtime/request-telemetry.js +2 -1
- package/dist/runtime/resolver-runtime-options.d.ts +14 -0
- package/dist/runtime/resolver-runtime-options.js +33 -0
- package/dist/runtime/resolver-shared.d.ts +3 -2
- package/dist/runtime/resolver-shared.js +4 -4
- package/dist/runtime/resolver-telemetry.d.ts +1 -0
- package/dist/runtime/resolver-telemetry.js +1 -1
- package/dist/runtime/resolver-vendors/browser.d.ts +2 -0
- package/dist/runtime/resolver-vendors/browser.js +46 -0
- package/dist/runtime/resolver-vendors/hosts.d.ts +21 -0
- package/dist/runtime/resolver-vendors/hosts.js +35 -0
- package/dist/runtime/resolver-vendors/hypersolutions.js +2 -2
- package/dist/runtime/resolver-vendors/types.d.ts +5 -4
- package/dist/runtime/resolver.d.ts +4 -1
- package/dist/runtime/resolver.js +46 -13
- package/dist/runtime/state-telemetry.d.ts +79 -0
- package/dist/runtime/state-telemetry.js +194 -0
- package/dist/runtime/state.d.ts +3 -0
- package/dist/runtime/state.js +26 -7
- package/dist/runtime/stealth-cookies.d.ts +5 -1
- package/dist/runtime/stealth-cookies.js +21 -1
- package/dist/runtime/stealth-owned-headers.d.ts +26 -0
- package/dist/runtime/stealth-owned-headers.js +37 -0
- package/dist/runtime/stealth-telemetry.d.ts +92 -0
- package/dist/runtime/stealth-telemetry.js +179 -0
- package/dist/runtime/stealth.d.ts +4 -1
- package/dist/runtime/stealth.js +228 -85
- package/dist/runtime/stt-telemetry.d.ts +77 -0
- package/dist/runtime/stt-telemetry.js +176 -0
- package/dist/runtime/stt.js +15 -6
- package/dist/server/error-stack-frames.d.ts +35 -0
- package/dist/server/error-stack-frames.js +219 -0
- package/dist/server/index.d.ts +7 -0
- package/dist/server/index.js +4 -0
- package/dist/server/sdk-version.d.ts +3 -0
- package/dist/server/sdk-version.js +19 -0
- package/dist/server/self-test.js +1 -18
- package/dist/server/sensitive-values.d.ts +1 -1
- package/dist/server/sensitive-values.js +9 -0
- package/dist/server/serve-implementation.d.ts +51 -2
- package/dist/server/serve-implementation.js +337 -90
- package/dist/server/types.d.ts +1 -0
- package/dist/server/types.js +7 -0
- package/dist/stateful/stateful-provider-owner-forwarder.js +22 -0
- package/dist/stateful/stateful-provider-runtime-context.d.ts +3 -0
- package/dist/stateful/stateful-provider-runtime-context.js +1 -0
- package/dist/types.d.ts +94 -1
- package/package.json +6 -1
- package/src/cli/commands.ts +14 -0
- package/src/cli/deployment-intent.ts +364 -0
- package/src/cli/migrate-deployment-intent.ts +1908 -0
- package/src/cli/migrate-provider-shape.ts +10 -4
- package/src/cli/resolver-runtime.ts +72 -0
- package/src/define.ts +13 -0
- package/src/dev.ts +9 -2
- package/src/engine.ts +38 -2
- package/src/error-resolution.ts +58 -0
- package/src/errors.ts +77 -1
- package/src/i18n/error-messages.ts +255 -0
- package/src/i18n/index.ts +1 -0
- package/src/i18n/keys.ts +80 -6
- package/src/index.ts +72 -0
- package/src/lint.ts +522 -50
- package/src/provider.ts +10 -0
- package/src/runtime/browser-telemetry-binding.ts +92 -0
- package/src/runtime/browser-telemetry.ts +289 -0
- package/src/runtime/browser.ts +249 -65
- package/src/runtime/cache-telemetry.ts +247 -0
- package/src/runtime/cache.ts +68 -17
- package/src/runtime/capability-response.ts +53 -0
- package/src/runtime/capability-telemetry.ts +101 -0
- package/src/runtime/diagnostic-env.ts +11 -1
- package/src/runtime/engine-mode.ts +233 -0
- package/src/runtime/http-telemetry-guard.ts +66 -13
- package/src/runtime/http.ts +168 -37
- package/src/runtime/instrumentation.ts +105 -16
- package/src/runtime/ocr-telemetry.ts +308 -0
- package/src/runtime/ocr.ts +109 -74
- package/src/runtime/request-telemetry.ts +2 -1
- package/src/runtime/resolver-runtime-options.ts +50 -0
- package/src/runtime/resolver-shared.ts +9 -4
- package/src/runtime/resolver-telemetry.ts +1 -1
- package/src/runtime/resolver-vendors/browser.ts +65 -7
- package/src/runtime/resolver-vendors/hosts.ts +43 -0
- package/src/runtime/resolver-vendors/hypersolutions.ts +2 -2
- package/src/runtime/resolver-vendors/types.ts +5 -4
- package/src/runtime/resolver.ts +64 -21
- package/src/runtime/state-telemetry.ts +298 -0
- package/src/runtime/state.ts +46 -45
- package/src/runtime/stealth-cookies.ts +24 -1
- package/src/runtime/stealth-owned-headers.ts +41 -0
- package/src/runtime/stealth-telemetry.ts +304 -0
- package/src/runtime/stealth.ts +355 -150
- package/src/runtime/stt-telemetry.ts +282 -0
- package/src/runtime/stt.ts +93 -71
- package/src/server/error-stack-frames.ts +214 -0
- package/src/server/index.ts +53 -0
- package/src/server/sdk-version.ts +22 -0
- package/src/server/self-test.ts +1 -21
- package/src/server/sensitive-values.ts +14 -0
- package/src/server/serve-implementation.ts +569 -108
- package/src/server/types.ts +7 -0
- package/src/stateful/stateful-provider-owner-forwarder.ts +23 -0
- package/src/stateful/stateful-provider-runtime-context.ts +4 -0
- package/src/types.ts +99 -1
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/approval-override.ts.txt +0 -6
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/codemod-syntax.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/connection-precedence.ts.txt +0 -10
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-ambiguous-a.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-ambiguous-b.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-ambiguous-barrel.ts.txt +0 -2
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-ambiguous-index.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-computed.ts.txt +0 -6
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-cycle-a.ts.txt +0 -1
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-cycle-b.ts.txt +0 -1
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-cycle-index.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-barrel.ts.txt +0 -7
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-catalog.ts.txt +0 -19
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-docs.ts.txt +0 -6
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-index.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-stores.ts.txt +0 -8
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-indirect-config.ts.txt +0 -5
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-nonstatic.ts.txt +0 -4
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-package-index.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-reexport-index.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-reexport-leaf.ts.txt +0 -10
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-reexport-one.ts.txt +0 -1
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-reexport-two.ts.txt +0 -1
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-runtime-composed-index.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-runtime-composed-one.ts.txt +0 -6
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-runtime-composed-two.ts.txt +0 -6
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-runtime-mixed-index.ts.txt +0 -14
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-runtime-registry.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-star-package-barrel.ts.txt +0 -2
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-star-package-index.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-star-package-leaf.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/docs-conflict.ts.txt +0 -8
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-hoisted-array-spread.ts.txt +0 -15
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-imported-array-spread.ts.txt +0 -9
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-kebab-operation-id.ts.txt +0 -17
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-map.ts.txt +0 -5
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-mixed-array-spread.ts.txt +0 -24
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-operation.ts.txt +0 -16
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-snake-operation-id.ts.txt +0 -21
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-duplicate-provider.ts.txt +0 -11
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-id-unresolved.ts.txt +0 -7
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-map.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-one-to-many.ts.txt +0 -18
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-one-to-one.ts.txt +0 -13
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/hoist-all.ts.txt +0 -31
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/hoisted-const.ts.txt +0 -11
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/imported-spread.ts.txt +0 -11
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-map.ts.txt +0 -11
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-cast-tail.ts.txt +0 -21
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-ekitan.ts.txt +0 -11
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-override.ts.txt +0 -14
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/locale-canonical-en.json +0 -1
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/locale-canonical-ja.json +0 -1
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/locale-canonical-ko.json +0 -1
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/locale-existing-operation-namespace-en.json +0 -8
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/missing-english-locale.ts.txt +0 -7
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/no-safety.ts.txt +0 -6
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/non-literal.ts.txt +0 -7
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/redundant-approval.ts.txt +0 -6
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-binding-ambiguous.ts.txt +0 -12
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-imported-other.ts.txt +0 -7
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-imported.ts.txt +0 -5
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-key-ambiguous.ts.txt +0 -19
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-shorthand.ts.txt +0 -9
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-typed.ts.txt +0 -11
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-unrelated.ts.txt +0 -13
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/safety-conflict.ts.txt +0 -7
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/stream.ts.txt +0 -7
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/tool-router-spread.ts.txt +0 -15
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/unparseable.ts.txt +0 -4
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/verbatim-template.ts.txt +0 -12
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
import type { ProviderCacheLookupMeta } from "../types.js";
|
|
3
|
+
import {
|
|
4
|
+
closedEnum,
|
|
5
|
+
type ClosedEnum,
|
|
6
|
+
type SpanIndex,
|
|
7
|
+
type TelemetryContributor,
|
|
8
|
+
} from "./request-telemetry.js";
|
|
9
|
+
import { createTelemetryObserver } from "./http-telemetry-guard.js";
|
|
10
|
+
|
|
11
|
+
export type CacheTelemetrySource = "memory" | "redis" | "loader";
|
|
12
|
+
|
|
13
|
+
export type CacheTelemetrySample = {
|
|
14
|
+
key: string;
|
|
15
|
+
hit: boolean;
|
|
16
|
+
stale: boolean;
|
|
17
|
+
ageMs?: number;
|
|
18
|
+
source: CacheTelemetrySource;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type CacheTelemetryLogPayload = {
|
|
22
|
+
telemetryFailed?: true;
|
|
23
|
+
lookups: number;
|
|
24
|
+
hits: number;
|
|
25
|
+
stale: number;
|
|
26
|
+
misses: number;
|
|
27
|
+
writes: number;
|
|
28
|
+
loaderErrors: number;
|
|
29
|
+
writeErrors: number;
|
|
30
|
+
source: { memory: number; redis: number; loader: number };
|
|
31
|
+
redisFallbacks: number;
|
|
32
|
+
redisMode: ClosedEnum<"not_configured" | "configured" | "degraded">;
|
|
33
|
+
/** Successful Redis commands, including reads, writes, and deletes; memory-only work counts zero. */
|
|
34
|
+
redisRoundTrips: number;
|
|
35
|
+
ageMs?: { p50: number; max: number };
|
|
36
|
+
samples: CacheTelemetrySample[];
|
|
37
|
+
dropped: number;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export type CacheTelemetryHeaderPayload = Omit<
|
|
41
|
+
CacheTelemetryLogPayload,
|
|
42
|
+
"samples" | "ageMs" | "telemetryFailed"
|
|
43
|
+
> & {
|
|
44
|
+
ageP50?: number;
|
|
45
|
+
ageMax?: number;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export interface CacheTelemetrySink {
|
|
49
|
+
markTelemetryFailed?(): void;
|
|
50
|
+
setRedisConfigured?(configured: boolean): void;
|
|
51
|
+
recordLookup(meta: ProviderCacheLookupMeta): void;
|
|
52
|
+
recordWrite(): void;
|
|
53
|
+
recordLoaderError?(): void;
|
|
54
|
+
recordWriteError?(): void;
|
|
55
|
+
recordRedisFallback(): void;
|
|
56
|
+
recordRedisRoundTrip?(): void;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const cacheTelemetryBindings = new WeakMap<object, CacheTelemetrySink>();
|
|
60
|
+
|
|
61
|
+
/** Internal construction binding; keeps sink plumbing off ProviderCacheOptions. */
|
|
62
|
+
export function bindCacheTelemetry<T extends object>(
|
|
63
|
+
options: T,
|
|
64
|
+
sink: CacheTelemetrySink | undefined,
|
|
65
|
+
): T {
|
|
66
|
+
if (sink) cacheTelemetryBindings.set(options, sink);
|
|
67
|
+
return options;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Internal lookup used only by the cache factory. */
|
|
71
|
+
export function cacheTelemetryFor(options: object): CacheTelemetrySink | undefined {
|
|
72
|
+
return cacheTelemetryBindings.get(options);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function guardCacheTelemetry(sink: CacheTelemetrySink | undefined): CacheTelemetrySink {
|
|
76
|
+
const observe = createTelemetryObserver(sink);
|
|
77
|
+
return {
|
|
78
|
+
setRedisConfigured: (configured) => observe(() => sink?.setRedisConfigured?.(configured)),
|
|
79
|
+
recordLookup: (meta) => observe(() => sink?.recordLookup(meta)),
|
|
80
|
+
recordWrite: () => observe(() => sink?.recordWrite()),
|
|
81
|
+
recordLoaderError: () => observe(() => sink?.recordLoaderError?.()),
|
|
82
|
+
recordWriteError: () => observe(() => sink?.recordWriteError?.()),
|
|
83
|
+
recordRedisFallback: () => observe(() => sink?.recordRedisFallback()),
|
|
84
|
+
recordRedisRoundTrip: () => observe(() => sink?.recordRedisRoundTrip?.()),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const MAX_SAMPLES = 24;
|
|
89
|
+
const MAX_TEXT = 300;
|
|
90
|
+
|
|
91
|
+
function integer(value: number): number {
|
|
92
|
+
return Number.isFinite(value)
|
|
93
|
+
? Math.min(Number.MAX_SAFE_INTEGER, Math.max(0, Math.floor(value)))
|
|
94
|
+
: Number.MAX_SAFE_INTEGER;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function add(a: number, b: number): number {
|
|
98
|
+
return Math.min(Number.MAX_SAFE_INTEGER, integer(a) + integer(b));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function boundedText(value: string, redact?: (text: string) => string): string {
|
|
102
|
+
let result: string;
|
|
103
|
+
try {
|
|
104
|
+
result = redact?.(value) ?? value;
|
|
105
|
+
} catch {
|
|
106
|
+
result = "[REDACTION_FAILED]";
|
|
107
|
+
}
|
|
108
|
+
const length = Math.min(result.length, MAX_TEXT);
|
|
109
|
+
const units = Buffer.alloc(length * 2);
|
|
110
|
+
for (let index = 0; index < length; index += 1)
|
|
111
|
+
units.writeUInt16LE(result.charCodeAt(index), index * 2);
|
|
112
|
+
return units.toString("utf16le");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export class CacheTelemetryCollector
|
|
116
|
+
implements
|
|
117
|
+
TelemetryContributor<CacheTelemetryLogPayload, CacheTelemetryHeaderPayload>,
|
|
118
|
+
CacheTelemetrySink
|
|
119
|
+
{
|
|
120
|
+
readonly key = "cache" as const;
|
|
121
|
+
readonly #redact?: (text: string) => string;
|
|
122
|
+
#telemetryFailed = false;
|
|
123
|
+
#lookups = 0;
|
|
124
|
+
#hits = 0;
|
|
125
|
+
#stale = 0;
|
|
126
|
+
#misses = 0;
|
|
127
|
+
#writes = 0;
|
|
128
|
+
#loaderErrors = 0;
|
|
129
|
+
#writeErrors = 0;
|
|
130
|
+
#source = { memory: 0, redis: 0, loader: 0 };
|
|
131
|
+
#redisFallbacks = 0;
|
|
132
|
+
#configured: "not_configured" | "configured" | "degraded";
|
|
133
|
+
#redisRoundTrips = 0;
|
|
134
|
+
#ages: number[] = [];
|
|
135
|
+
#maxAge = 0;
|
|
136
|
+
#samples: CacheTelemetrySample[] = [];
|
|
137
|
+
#dropped = 0;
|
|
138
|
+
|
|
139
|
+
constructor(options: { redact?: (text: string) => string } = {}) {
|
|
140
|
+
this.#redact = options.redact;
|
|
141
|
+
this.#configured = "not_configured";
|
|
142
|
+
}
|
|
143
|
+
setRedisConfigured(configured: boolean): void {
|
|
144
|
+
if (configured && this.#configured === "not_configured") this.#configured = "configured";
|
|
145
|
+
}
|
|
146
|
+
markTelemetryFailed(): void {
|
|
147
|
+
this.#telemetryFailed = true;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
recordLookup(meta: ProviderCacheLookupMeta): void {
|
|
151
|
+
if (!["memory", "redis", "loader"].includes(meta.source)) {
|
|
152
|
+
this.#telemetryFailed = true;
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
this.#lookups = add(this.#lookups, 1);
|
|
156
|
+
if (meta.hit) this.#hits = add(this.#hits, 1);
|
|
157
|
+
else this.#misses = add(this.#misses, 1);
|
|
158
|
+
if (meta.stale) this.#stale = add(this.#stale, 1);
|
|
159
|
+
this.#source[meta.source] = add(this.#source[meta.source], 1);
|
|
160
|
+
if (meta.source === "redis" && this.#configured === "not_configured")
|
|
161
|
+
this.#configured = "configured";
|
|
162
|
+
if (meta.ageMs !== undefined) {
|
|
163
|
+
const age = integer(meta.ageMs);
|
|
164
|
+
if (this.#ages.length < MAX_SAMPLES) this.#ages.push(age);
|
|
165
|
+
this.#maxAge = Math.max(this.#maxAge, age);
|
|
166
|
+
}
|
|
167
|
+
if (this.#samples.length < MAX_SAMPLES) {
|
|
168
|
+
this.#samples.push({
|
|
169
|
+
key: boundedText(meta.key, this.#redact),
|
|
170
|
+
hit: meta.hit,
|
|
171
|
+
stale: meta.stale,
|
|
172
|
+
...(meta.ageMs === undefined ? {} : { ageMs: integer(meta.ageMs) }),
|
|
173
|
+
source: meta.source,
|
|
174
|
+
});
|
|
175
|
+
} else this.#dropped = add(this.#dropped, 1);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
recordWrite(): void {
|
|
179
|
+
this.#writes = add(this.#writes, 1);
|
|
180
|
+
}
|
|
181
|
+
recordLoaderError(): void {
|
|
182
|
+
this.#loaderErrors = add(this.#loaderErrors, 1);
|
|
183
|
+
}
|
|
184
|
+
recordWriteError(): void {
|
|
185
|
+
this.#writeErrors = add(this.#writeErrors, 1);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
recordRedisFallback(): void {
|
|
189
|
+
this.#redisFallbacks = add(this.#redisFallbacks, 1);
|
|
190
|
+
this.#configured = "degraded";
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
recordRedisRoundTrip(): void {
|
|
194
|
+
this.#redisRoundTrips = add(this.#redisRoundTrips, 1);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
toLogPayload(_spans: SpanIndex): CacheTelemetryLogPayload | undefined {
|
|
198
|
+
if (
|
|
199
|
+
this.#lookups === 0 &&
|
|
200
|
+
this.#writes === 0 &&
|
|
201
|
+
this.#redisFallbacks === 0 &&
|
|
202
|
+
!this.#telemetryFailed
|
|
203
|
+
)
|
|
204
|
+
return undefined;
|
|
205
|
+
const sorted = [...this.#ages].sort((a, b) => a - b);
|
|
206
|
+
const p50 = sorted.length ? sorted[Math.floor((sorted.length - 1) / 2)]! : undefined;
|
|
207
|
+
return {
|
|
208
|
+
...(this.#telemetryFailed ? { telemetryFailed: true as const } : {}),
|
|
209
|
+
lookups: this.#lookups,
|
|
210
|
+
hits: this.#hits,
|
|
211
|
+
stale: this.#stale,
|
|
212
|
+
misses: this.#misses,
|
|
213
|
+
writes: this.#writes,
|
|
214
|
+
loaderErrors: this.#loaderErrors,
|
|
215
|
+
writeErrors: this.#writeErrors,
|
|
216
|
+
source: { ...this.#source },
|
|
217
|
+
redisFallbacks: this.#redisFallbacks,
|
|
218
|
+
redisMode: closedEnum(this.#configured),
|
|
219
|
+
redisRoundTrips: this.#redisRoundTrips,
|
|
220
|
+
...(p50 === undefined ? {} : { ageMs: { p50, max: this.#maxAge } }),
|
|
221
|
+
samples: this.#samples.map((sample) => ({ ...sample })),
|
|
222
|
+
dropped: this.#dropped,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
toHeaderPayload(log: CacheTelemetryLogPayload): CacheTelemetryHeaderPayload {
|
|
227
|
+
return {
|
|
228
|
+
lookups: log.lookups,
|
|
229
|
+
hits: log.hits,
|
|
230
|
+
stale: log.stale,
|
|
231
|
+
misses: log.misses,
|
|
232
|
+
writes: log.writes,
|
|
233
|
+
loaderErrors: log.loaderErrors,
|
|
234
|
+
writeErrors: log.writeErrors,
|
|
235
|
+
source: {
|
|
236
|
+
memory: log.source.memory,
|
|
237
|
+
redis: log.source.redis,
|
|
238
|
+
loader: log.source.loader,
|
|
239
|
+
},
|
|
240
|
+
redisFallbacks: log.redisFallbacks,
|
|
241
|
+
redisMode: log.redisMode,
|
|
242
|
+
redisRoundTrips: log.redisRoundTrips,
|
|
243
|
+
...(log.ageMs ? { ageP50: log.ageMs.p50, ageMax: log.ageMs.max } : {}),
|
|
244
|
+
dropped: log.dropped,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
}
|
package/src/runtime/cache.ts
CHANGED
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
ProviderCacheResponseMeta,
|
|
12
12
|
ProviderCacheResult,
|
|
13
13
|
} from "../types.js";
|
|
14
|
+
import { cacheTelemetryFor, guardCacheTelemetry } from "./cache-telemetry.js";
|
|
14
15
|
import {
|
|
15
16
|
createProviderRedisClient,
|
|
16
17
|
ensureRedisReady,
|
|
@@ -135,15 +136,13 @@ function assertJsonSafeSecretValue(
|
|
|
135
136
|
if (typeof value === "number") {
|
|
136
137
|
if (!Number.isFinite(value))
|
|
137
138
|
unsupportedSecretValue(reportedPath, "non-finite numbers are unsupported");
|
|
138
|
-
if (Object.is(value, -0))
|
|
139
|
-
unsupportedSecretValue(reportedPath, "negative zero is unsupported");
|
|
139
|
+
if (Object.is(value, -0)) unsupportedSecretValue(reportedPath, "negative zero is unsupported");
|
|
140
140
|
return;
|
|
141
141
|
}
|
|
142
142
|
if (typeof value !== "object") {
|
|
143
143
|
unsupportedSecretValue(reportedPath, `${typeof value} values are unsupported`);
|
|
144
144
|
}
|
|
145
|
-
if (ancestors.has(value))
|
|
146
|
-
unsupportedSecretValue(reportedPath, "cyclic values are unsupported");
|
|
145
|
+
if (ancestors.has(value)) unsupportedSecretValue(reportedPath, "cyclic values are unsupported");
|
|
147
146
|
|
|
148
147
|
ancestors.add(value);
|
|
149
148
|
try {
|
|
@@ -156,8 +155,7 @@ function assertJsonSafeSecretValue(
|
|
|
156
155
|
const key = String(index);
|
|
157
156
|
expectedNames.add(key);
|
|
158
157
|
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
159
|
-
if (!descriptor)
|
|
160
|
-
unsupportedSecretValue(reportedPath, "sparse arrays are unsupported");
|
|
158
|
+
if (!descriptor) unsupportedSecretValue(reportedPath, "sparse arrays are unsupported");
|
|
161
159
|
if (!descriptor.enumerable || !("value" in descriptor)) {
|
|
162
160
|
unsupportedSecretValue(reportedPath, "array accessors are unsupported");
|
|
163
161
|
}
|
|
@@ -275,10 +273,7 @@ function hashSecretValue(
|
|
|
275
273
|
return { value: `hmac-sha256:${digest}`, secretScoped: true };
|
|
276
274
|
}
|
|
277
275
|
|
|
278
|
-
function metadataKeys(
|
|
279
|
-
events: ProviderCacheLookupMeta[],
|
|
280
|
-
secretScopedKeys: Set<string>,
|
|
281
|
-
): string[] {
|
|
276
|
+
function metadataKeys(events: ProviderCacheLookupMeta[], secretScopedKeys: Set<string>): string[] {
|
|
282
277
|
let secretScopedIndex = 0;
|
|
283
278
|
return Array.from(new Set(events.map((event) => event.key))).map((key) => {
|
|
284
279
|
if (!secretScopedKeys.has(key)) return key;
|
|
@@ -369,10 +364,14 @@ export function createProviderCache(options: ProviderCacheOptions): ProviderCach
|
|
|
369
364
|
const memoryMaxEntries = Math.max(1, options.memoryMaxEntries ?? DEFAULT_MEMORY_MAX_ENTRIES);
|
|
370
365
|
const now = options.now ?? Date.now;
|
|
371
366
|
const events: ProviderCacheLookupMeta[] = [];
|
|
367
|
+
const telemetry = guardCacheTelemetry(cacheTelemetryFor(options));
|
|
368
|
+
telemetry.setRedisConfigured?.(!!redisUrl);
|
|
369
|
+
let redisUnavailable = false;
|
|
372
370
|
const secretScopedKeys = new Set<string>();
|
|
373
371
|
|
|
374
372
|
function record(meta: ProviderCacheLookupMeta): void {
|
|
375
373
|
events.push(meta);
|
|
374
|
+
telemetry?.recordLookup(meta);
|
|
376
375
|
}
|
|
377
376
|
|
|
378
377
|
function sweepMemory(currentTime: number): void {
|
|
@@ -416,13 +415,22 @@ export function createProviderCache(options: ProviderCacheOptions): ProviderCach
|
|
|
416
415
|
} | null> {
|
|
417
416
|
const redis = backend.redis;
|
|
418
417
|
if (!redis || !(await ensureRedisReady(redis, DEFAULT_REDIS_TIMEOUT_MS))) {
|
|
418
|
+
if (redis) {
|
|
419
|
+
redisUnavailable = true;
|
|
420
|
+
telemetry?.recordRedisFallback();
|
|
421
|
+
}
|
|
419
422
|
return null;
|
|
420
423
|
}
|
|
421
424
|
|
|
422
425
|
const raw = await withRedisFallback(async () => {
|
|
423
426
|
return await redis.get(key);
|
|
424
427
|
});
|
|
425
|
-
if (typeof raw !== "string" && raw !== null)
|
|
428
|
+
if (typeof raw !== "string" && raw !== null) {
|
|
429
|
+
redisUnavailable = true;
|
|
430
|
+
telemetry.recordRedisFallback();
|
|
431
|
+
return null;
|
|
432
|
+
}
|
|
433
|
+
telemetry?.recordRedisRoundTrip?.();
|
|
426
434
|
|
|
427
435
|
const envelope = safeParseEnvelope(raw);
|
|
428
436
|
if (!envelope) return null;
|
|
@@ -473,6 +481,7 @@ export function createProviderCache(options: ProviderCacheOptions): ProviderCach
|
|
|
473
481
|
value: T,
|
|
474
482
|
cacheOptions: ProviderCacheGetOrSetOptions,
|
|
475
483
|
): Promise<void> {
|
|
484
|
+
telemetry?.recordWrite();
|
|
476
485
|
const currentTime = now();
|
|
477
486
|
const freshTtlMs = jitteredTtlMs(cacheOptions.ttlMs, cacheOptions.jitterPct);
|
|
478
487
|
const staleIfErrorMs = cacheOptions.staleIfErrorMs ?? 0;
|
|
@@ -487,9 +496,21 @@ export function createProviderCache(options: ProviderCacheOptions): ProviderCach
|
|
|
487
496
|
|
|
488
497
|
const redis = backend.redis;
|
|
489
498
|
if (!redis || !(await ensureRedisReady(redis, DEFAULT_REDIS_TIMEOUT_MS))) {
|
|
499
|
+
if (redis) {
|
|
500
|
+
redisUnavailable = true;
|
|
501
|
+
telemetry?.recordRedisFallback();
|
|
502
|
+
telemetry?.recordWriteError?.();
|
|
503
|
+
}
|
|
490
504
|
return;
|
|
491
505
|
}
|
|
492
|
-
await withRedisFallback(() =>
|
|
506
|
+
const written = await withRedisFallback(() =>
|
|
507
|
+
redis.set(key, JSON.stringify(envelope), "PX", staleTtlMs),
|
|
508
|
+
);
|
|
509
|
+
if (written === undefined) {
|
|
510
|
+
redisUnavailable = true;
|
|
511
|
+
telemetry?.recordRedisFallback();
|
|
512
|
+
telemetry?.recordWriteError?.();
|
|
513
|
+
} else telemetry?.recordRedisRoundTrip?.();
|
|
493
514
|
}
|
|
494
515
|
|
|
495
516
|
async function loadAndStore<T>(
|
|
@@ -514,6 +535,13 @@ export function createProviderCache(options: ProviderCacheOptions): ProviderCach
|
|
|
514
535
|
if (staleCandidate?.meta.stale) {
|
|
515
536
|
return staleCandidate;
|
|
516
537
|
}
|
|
538
|
+
telemetry?.recordLookup({
|
|
539
|
+
key,
|
|
540
|
+
hit: false,
|
|
541
|
+
stale: false,
|
|
542
|
+
source: "loader",
|
|
543
|
+
});
|
|
544
|
+
telemetry?.recordLoaderError?.();
|
|
517
545
|
throw error;
|
|
518
546
|
}
|
|
519
547
|
}
|
|
@@ -530,18 +558,29 @@ export function createProviderCache(options: ProviderCacheOptions): ProviderCach
|
|
|
530
558
|
async get<T = unknown>(key: string): Promise<ProviderCacheResult<T> | null> {
|
|
531
559
|
const result = await read<T>(key);
|
|
532
560
|
if (result) record(result.meta);
|
|
561
|
+
else
|
|
562
|
+
telemetry.recordLookup({
|
|
563
|
+
key,
|
|
564
|
+
hit: false,
|
|
565
|
+
stale: false,
|
|
566
|
+
source: backend.redis && !redisUnavailable ? "redis" : "memory",
|
|
567
|
+
});
|
|
533
568
|
return result;
|
|
534
569
|
},
|
|
535
570
|
|
|
536
571
|
set: write,
|
|
537
572
|
|
|
538
573
|
async delete(key: string): Promise<void> {
|
|
574
|
+
telemetry?.recordWrite();
|
|
539
575
|
backend.memory.delete(key);
|
|
540
576
|
const redis = backend.redis;
|
|
541
577
|
if (!redis || !(await ensureRedisReady(redis, DEFAULT_REDIS_TIMEOUT_MS))) {
|
|
578
|
+
if (redis) telemetry?.recordRedisFallback();
|
|
542
579
|
return;
|
|
543
580
|
}
|
|
544
|
-
await withRedisFallback(() => redis.del(key));
|
|
581
|
+
const deleted = await withRedisFallback(() => redis.del(key));
|
|
582
|
+
if (deleted === undefined) telemetry?.recordRedisFallback();
|
|
583
|
+
else telemetry?.recordRedisRoundTrip?.();
|
|
545
584
|
},
|
|
546
585
|
|
|
547
586
|
async getOrSet<T = unknown>(
|
|
@@ -557,10 +596,22 @@ export function createProviderCache(options: ProviderCacheOptions): ProviderCach
|
|
|
557
596
|
|
|
558
597
|
const existingInflight = backend.inflight.get(key);
|
|
559
598
|
if (existingInflight) {
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
599
|
+
try {
|
|
600
|
+
const inflightResult = await existingInflight;
|
|
601
|
+
const result = resultWithValue<T>(inflightResult.value, inflightResult.meta);
|
|
602
|
+
record(result.meta);
|
|
603
|
+
return result;
|
|
604
|
+
} catch (error) {
|
|
605
|
+
const meta: ProviderCacheLookupMeta = {
|
|
606
|
+
key,
|
|
607
|
+
hit: false,
|
|
608
|
+
stale: false,
|
|
609
|
+
source: "loader",
|
|
610
|
+
};
|
|
611
|
+
telemetry.recordLookup(meta);
|
|
612
|
+
telemetry?.recordLoaderError?.();
|
|
613
|
+
throw error;
|
|
614
|
+
}
|
|
564
615
|
}
|
|
565
616
|
|
|
566
617
|
const promise: Promise<ProviderCacheResult<unknown>> = loadAndStore(
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export const CAPABILITY_BODY_UNAVAILABLE = "<body unavailable>";
|
|
2
|
+
|
|
3
|
+
/** Read optional diagnostic response text without letting a body stall the request failure. */
|
|
4
|
+
export async function readCapabilityErrorBody(
|
|
5
|
+
response: Response,
|
|
6
|
+
signal: AbortSignal,
|
|
7
|
+
): Promise<string> {
|
|
8
|
+
if (signal.aborted) return CAPABILITY_BODY_UNAVAILABLE;
|
|
9
|
+
|
|
10
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
11
|
+
let onAbort: (() => void) | undefined;
|
|
12
|
+
let reader: ReadableStreamDefaultReader<Uint8Array> | undefined;
|
|
13
|
+
let unavailableWon = false;
|
|
14
|
+
const cancelBody = (reason: unknown) => {
|
|
15
|
+
const cancellation = reader ? reader.cancel(reason) : response.body?.cancel(reason);
|
|
16
|
+
if (cancellation) void cancellation.catch(() => {});
|
|
17
|
+
};
|
|
18
|
+
const unavailable = new Promise<string>((resolve) => {
|
|
19
|
+
onAbort = () => {
|
|
20
|
+
unavailableWon = true;
|
|
21
|
+
resolve(CAPABILITY_BODY_UNAVAILABLE);
|
|
22
|
+
cancelBody(signal.reason);
|
|
23
|
+
};
|
|
24
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
25
|
+
timer = setTimeout(() => {
|
|
26
|
+
unavailableWon = true;
|
|
27
|
+
resolve(CAPABILITY_BODY_UNAVAILABLE);
|
|
28
|
+
cancelBody(new Error("capability error body read timeout"));
|
|
29
|
+
}, 250);
|
|
30
|
+
});
|
|
31
|
+
const body = Promise.resolve()
|
|
32
|
+
.then(async () => {
|
|
33
|
+
if (unavailableWon) return CAPABILITY_BODY_UNAVAILABLE;
|
|
34
|
+
if (!response.body) return response.text();
|
|
35
|
+
reader = response.body.getReader();
|
|
36
|
+
const decoder = new TextDecoder();
|
|
37
|
+
let text = "";
|
|
38
|
+
while (true) {
|
|
39
|
+
if (unavailableWon) return CAPABILITY_BODY_UNAVAILABLE;
|
|
40
|
+
const chunk = await reader.read();
|
|
41
|
+
if (chunk.done) return text + decoder.decode();
|
|
42
|
+
text += decoder.decode(chunk.value, { stream: true });
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
.catch(() => CAPABILITY_BODY_UNAVAILABLE);
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
return await Promise.race([body, unavailable]);
|
|
49
|
+
} finally {
|
|
50
|
+
if (timer !== undefined) clearTimeout(timer);
|
|
51
|
+
if (onAbort) signal.removeEventListener("abort", onAbort);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import { Buffer } from "node:buffer";
|
|
3
|
+
import { registerDiagnosticValue } from "./diagnostic-env.js";
|
|
4
|
+
import { redactDiagnosticText } from "./diagnostic-redactor.js";
|
|
5
|
+
import { observeTelemetry } from "./http-telemetry-guard.js";
|
|
6
|
+
import { boundedText } from "./resolver-telemetry.js";
|
|
7
|
+
|
|
8
|
+
/** Internal capture facts, isolated across concurrent calls and never attached to caller results. */
|
|
9
|
+
export type CapabilityFacts = { status?: number; finishReason?: string; diagnostics?: string };
|
|
10
|
+
export const capabilityFacts = new AsyncLocalStorage<CapabilityFacts>();
|
|
11
|
+
const metadata = new WeakMap<object, { backend: string; engine: string; model?: string }>();
|
|
12
|
+
export function tagCapability<T extends object>(
|
|
13
|
+
context: T,
|
|
14
|
+
facts: { backend: string; engine: string; model?: string },
|
|
15
|
+
): T {
|
|
16
|
+
metadata.set(context, facts);
|
|
17
|
+
return context;
|
|
18
|
+
}
|
|
19
|
+
export function capabilityMetadata(context: object) {
|
|
20
|
+
return metadata.get(context);
|
|
21
|
+
}
|
|
22
|
+
export function captureCapability(facts: CapabilityFacts): void {
|
|
23
|
+
const current = capabilityFacts.getStore();
|
|
24
|
+
if (current) Object.assign(current, facts);
|
|
25
|
+
}
|
|
26
|
+
export function registerCapabilityInput(input: {
|
|
27
|
+
kind: string;
|
|
28
|
+
data?: string;
|
|
29
|
+
url?: string;
|
|
30
|
+
}): void {
|
|
31
|
+
// Payloads are measurements, never redaction needles. Only bounded URLs belong
|
|
32
|
+
// in the request inventory; structural stripping covers oversized URL echoes.
|
|
33
|
+
if (input.kind === "url" && input.url && Buffer.byteLength(input.url) <= 2048)
|
|
34
|
+
registerDiagnosticValue(input.url.trim());
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Formats capability diagnostics after credential redaction.
|
|
38
|
+
* When `stripPayload` is enabled, long base64-shaped runs are replaced as a
|
|
39
|
+
* defence in depth; this may replace an unregistered long identifier, while
|
|
40
|
+
* protecting against the more likely case of payload data in an upstream body.
|
|
41
|
+
*/
|
|
42
|
+
export function capabilityText(
|
|
43
|
+
value: string,
|
|
44
|
+
redact?: (text: string) => string,
|
|
45
|
+
stripPayload = false,
|
|
46
|
+
): string {
|
|
47
|
+
const stripped = value
|
|
48
|
+
.replace(
|
|
49
|
+
/(?:authorization|proxy-authorization|cookie|set-cookie)\s*["']?\s*[:=]\s*[^\r\n]+/gi,
|
|
50
|
+
"[REDACTED]",
|
|
51
|
+
)
|
|
52
|
+
.replace(/Bearer\s+[^\r\n]+/gi, "[REDACTED]")
|
|
53
|
+
.replace(/https?:\/\/[^\s"'<>]+/gi, "[REDACTED]")
|
|
54
|
+
.replace(/data:[^\s"']+/gi, "[BASE64_STRIPPED]");
|
|
55
|
+
return boundedText(
|
|
56
|
+
stripPayload ? stripped.replace(/[A-Za-z0-9+/=]{64,}/g, "[BASE64_STRIPPED]") : stripped,
|
|
57
|
+
(text) => redactDiagnosticText(text, redact),
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
export function capabilityErrorText(error: unknown): string {
|
|
61
|
+
if (!(error instanceof Error)) return String(error);
|
|
62
|
+
const details = "details" in error ? error.details : undefined;
|
|
63
|
+
const body =
|
|
64
|
+
details && typeof details === "object" && "body" in details ? details.body : undefined;
|
|
65
|
+
return `${error.message}${typeof body === "string" ? ` ${body}` : ""}`;
|
|
66
|
+
}
|
|
67
|
+
export const integer = (n: number | undefined) =>
|
|
68
|
+
Math.min(
|
|
69
|
+
Number.MAX_SAFE_INTEGER,
|
|
70
|
+
Math.max(0, Math.floor(Number.isFinite(n ?? 0) ? (n ?? 0) : 0)),
|
|
71
|
+
);
|
|
72
|
+
export const add = (a: number, b: number) =>
|
|
73
|
+
Math.min(Number.MAX_SAFE_INTEGER, integer(a) + integer(b));
|
|
74
|
+
export function inputBytes(input: { kind: string; data?: string }): number | undefined {
|
|
75
|
+
if (input.kind !== "base64" || input.data === undefined) return undefined;
|
|
76
|
+
return Buffer.byteLength(input.data.trim(), "base64");
|
|
77
|
+
}
|
|
78
|
+
/** All capability hooks use the HTTP observer guard, including failure reporting. */
|
|
79
|
+
export function observeCapability(
|
|
80
|
+
sink: { markTelemetryFailed?(): void },
|
|
81
|
+
callback: () => unknown,
|
|
82
|
+
): void {
|
|
83
|
+
observeTelemetry(callback, () => {
|
|
84
|
+
observeTelemetry(
|
|
85
|
+
() => sink.markTelemetryFailed?.(),
|
|
86
|
+
() => {},
|
|
87
|
+
);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Only server-enrolled capability spans use the request root; standalone instrumentation
|
|
92
|
+
// continues to use its caller's active span. No existing namespace changes parentage.
|
|
93
|
+
type RootRunner = <T>(callback: () => Promise<T>) => Promise<T>;
|
|
94
|
+
const roots = new WeakMap<object, RootRunner>();
|
|
95
|
+
export function bindCapabilityRoot<T extends object>(context: T, run: RootRunner): T {
|
|
96
|
+
roots.set(context, run);
|
|
97
|
+
return context;
|
|
98
|
+
}
|
|
99
|
+
export function runCapabilitySpan<T>(context: object, callback: () => Promise<T>): Promise<T> {
|
|
100
|
+
return roots.get(context)?.(callback) ?? callback();
|
|
101
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
+
otlpHeaderCredentialValues,
|
|
4
5
|
resolvedStringVariants,
|
|
5
6
|
urlCredentialComponents,
|
|
6
|
-
otlpHeaderCredentialValues,
|
|
7
7
|
} from "./diagnostic-env-values.js";
|
|
8
8
|
import {
|
|
9
9
|
type CompiledDiagnosticSensitiveValues,
|
|
@@ -17,6 +17,7 @@ export interface DiagnosticEnvScope {
|
|
|
17
17
|
observe: EnvObserver;
|
|
18
18
|
finished: boolean;
|
|
19
19
|
staticValues?: CompiledDiagnosticSensitiveValues;
|
|
20
|
+
register?: (values: readonly string[]) => void;
|
|
20
21
|
}
|
|
21
22
|
const observers = new AsyncLocalStorage<DiagnosticEnvScope>();
|
|
22
23
|
|
|
@@ -48,6 +49,15 @@ export function readDiagnosticEnv(
|
|
|
48
49
|
return value;
|
|
49
50
|
}
|
|
50
51
|
|
|
52
|
+
/** Register a runtime-discovered sensitive value in the active request inventory. */
|
|
53
|
+
export function registerDiagnosticValue(value: string): void {
|
|
54
|
+
if (value.length < 4) return;
|
|
55
|
+
const scope = observers.getStore();
|
|
56
|
+
if (scope && !scope.finished && scope.register) {
|
|
57
|
+
scope.register([value]);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
51
61
|
export function withDiagnosticEnv<T>(observer: EnvObserver | DiagnosticEnvScope, fn: () => T): T {
|
|
52
62
|
const scope = typeof observer === "function" ? { observe: observer, finished: false } : observer;
|
|
53
63
|
const run = () => observers.run(scope, fn);
|