@apifuse/provider-sdk 2.2.0-beta.60 → 2.2.0-beta.61
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 +17 -16
- package/CHANGELOG.md +4 -0
- package/bin/apifuse-dev.ts +5 -7
- package/bin/apifuse-pack-types.ts +22 -0
- package/bin/apifuse-perf.ts +4 -6
- package/bin/apifuse-record.ts +4 -7
- package/bin/apifuse-submit-check.ts +2 -2
- package/dist/define.d.ts +3 -2
- package/dist/define.js +1 -1
- package/dist/dev.js +3 -1
- package/dist/engine.d.ts +7 -6
- package/dist/engine.js +3 -4
- package/dist/error-resolution.js +21 -4
- package/dist/errors.d.ts +3 -0
- package/dist/errors.js +9 -0
- package/dist/handle-meta.d.ts +39 -0
- package/dist/handle-meta.js +29 -0
- package/dist/handle.d.ts +187 -0
- package/dist/handle.js +254 -0
- package/dist/i18n/catalog.js +21 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +3 -2
- package/dist/lint.d.ts +3 -0
- package/dist/lint.js +352 -47
- package/dist/provider.d.ts +3 -3
- package/dist/provider.js +2 -2
- package/dist/runtime/handle-wordlist.d.ts +43 -0
- package/dist/runtime/{choice-wordlist.js → handle-wordlist.js} +127 -9
- package/dist/runtime/handle.d.ts +71 -0
- package/dist/runtime/handle.js +895 -0
- package/dist/runtime/native-network.d.ts +10 -1
- package/dist/runtime/native-network.js +230 -29
- package/dist/runtime/native-telemetry.d.ts +142 -0
- package/dist/runtime/native-telemetry.js +231 -0
- package/dist/runtime/state.d.ts +4 -1
- package/dist/runtime/state.js +32 -17
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +1 -0
- package/dist/server/sensitive-values.d.ts +1 -1
- package/dist/server/sensitive-values.js +0 -3
- package/dist/server/serve-implementation.d.ts +4 -11
- package/dist/server/serve-implementation.js +86 -18
- package/dist/testing/run.js +10 -7
- package/dist/types.d.ts +4 -107
- package/package.json +1 -1
- package/src/define.ts +4 -3
- package/src/dev.ts +3 -1
- package/src/engine.ts +5 -6
- package/src/error-resolution.ts +21 -4
- package/src/errors.ts +13 -0
- package/src/handle-meta.ts +58 -0
- package/src/handle.ts +529 -0
- package/src/i18n/catalog.ts +23 -1
- package/src/index.ts +26 -13
- package/src/lint.ts +414 -55
- package/src/provider.ts +9 -20
- package/src/runtime/{choice-wordlist.ts → handle-wordlist.ts} +130 -9
- package/src/runtime/handle.ts +1162 -0
- package/src/runtime/native-network.ts +281 -33
- package/src/runtime/native-telemetry.ts +366 -0
- package/src/runtime/state.ts +44 -16
- package/src/server/index.ts +17 -0
- package/src/server/sensitive-values.ts +0 -3
- package/src/server/serve-implementation.ts +87 -32
- package/src/testing/run.ts +11 -7
- package/src/types.ts +4 -137
- package/dist/choice-token.d.ts +0 -24
- package/dist/choice-token.js +0 -74
- package/dist/runtime/choice-wordlist.d.ts +0 -9
- package/dist/runtime/choice.d.ts +0 -27
- package/dist/runtime/choice.js +0 -801
- package/src/choice-token.ts +0 -165
- package/src/runtime/choice.ts +0 -1197
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
import { closedEnum } from "./request-telemetry.js";
|
|
3
|
+
const MAX_SAMPLES = 24;
|
|
4
|
+
function integer(value) {
|
|
5
|
+
return Math.max(0, Math.floor(Number.isFinite(value) ? value : 0));
|
|
6
|
+
}
|
|
7
|
+
function saturatingAdd(a, b) {
|
|
8
|
+
if (!Number.isFinite(a) || !Number.isFinite(b))
|
|
9
|
+
return Number.MAX_SAFE_INTEGER;
|
|
10
|
+
return Math.min(Number.MAX_SAFE_INTEGER, integer(a) + integer(b));
|
|
11
|
+
}
|
|
12
|
+
function boundedText(value, redact) {
|
|
13
|
+
let result;
|
|
14
|
+
try {
|
|
15
|
+
result = redact ? redact(value) : value;
|
|
16
|
+
if (typeof result !== "string")
|
|
17
|
+
return "[REDACTION_FAILED]";
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return "[REDACTION_FAILED]";
|
|
21
|
+
}
|
|
22
|
+
// Do not slice caller text: a substring may retain the entire vendor response.
|
|
23
|
+
const length = Math.min(result.length, 300);
|
|
24
|
+
const units = Buffer.alloc(length * 2);
|
|
25
|
+
for (let index = 0; index < length; index++)
|
|
26
|
+
units.writeUInt16LE(result.charCodeAt(index), index * 2);
|
|
27
|
+
return units.toString("utf16le");
|
|
28
|
+
}
|
|
29
|
+
function diagnostics(value, redact) {
|
|
30
|
+
const result = {};
|
|
31
|
+
for (const key of [
|
|
32
|
+
"host",
|
|
33
|
+
"serverName",
|
|
34
|
+
"protocol",
|
|
35
|
+
"sessionId",
|
|
36
|
+
"expiresAt",
|
|
37
|
+
"errorName",
|
|
38
|
+
"errorMessage",
|
|
39
|
+
"causeName",
|
|
40
|
+
"causeMessage",
|
|
41
|
+
"systemCode",
|
|
42
|
+
]) {
|
|
43
|
+
if (value[key] !== undefined)
|
|
44
|
+
result[key] = boundedText(value[key], redact);
|
|
45
|
+
}
|
|
46
|
+
for (const key of ["port", "timeoutMs", "idleTimeoutMs", "status", "socksReplyCode"]) {
|
|
47
|
+
if (value[key] !== undefined)
|
|
48
|
+
result[key] = integer(value[key]);
|
|
49
|
+
}
|
|
50
|
+
if (value.sticky !== undefined)
|
|
51
|
+
result.sticky = value.sticky;
|
|
52
|
+
if (value.missingFields)
|
|
53
|
+
result.missingFields = value.missingFields
|
|
54
|
+
.slice(0, MAX_SAMPLES)
|
|
55
|
+
.map((field) => boundedText(field, redact));
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
function copyDiagnostics(value) {
|
|
59
|
+
return { ...value, ...(value.missingFields ? { missingFields: [...value.missingFields] } : {}) };
|
|
60
|
+
}
|
|
61
|
+
export class NativeTelemetryCollector {
|
|
62
|
+
key = "native";
|
|
63
|
+
#redact;
|
|
64
|
+
#recorded = false;
|
|
65
|
+
#attempts = 0;
|
|
66
|
+
#connectMs = 0;
|
|
67
|
+
#tunnelMs = 0;
|
|
68
|
+
#vendorSkips = 0;
|
|
69
|
+
#vendorSkipReasons = new Map();
|
|
70
|
+
#lifecycle = {
|
|
71
|
+
drain: 0,
|
|
72
|
+
drain_acknowledged: 0,
|
|
73
|
+
drain_error: 0,
|
|
74
|
+
drain_missing_handler: 0,
|
|
75
|
+
idle: 0,
|
|
76
|
+
expiry: 0,
|
|
77
|
+
};
|
|
78
|
+
#bytesIn = 0;
|
|
79
|
+
#bytesOut = 0;
|
|
80
|
+
#lastErrorCode;
|
|
81
|
+
#lastErrorDiagnostics;
|
|
82
|
+
#attemptSamples = [];
|
|
83
|
+
#attemptSamplesDropped = 0;
|
|
84
|
+
#vendorSkipSamples = [];
|
|
85
|
+
#vendorSkipSamplesDropped = 0;
|
|
86
|
+
constructor(options = {}) {
|
|
87
|
+
this.#redact = options.redact;
|
|
88
|
+
}
|
|
89
|
+
recordConnect(event) {
|
|
90
|
+
this.#recorded = true;
|
|
91
|
+
const ms = saturatingAdd(0, event.ms);
|
|
92
|
+
const tunnelMs = saturatingAdd(0, event.tunnelMs);
|
|
93
|
+
this.#attempts = saturatingAdd(this.#attempts, 1);
|
|
94
|
+
this.#connectMs = saturatingAdd(this.#connectMs, ms);
|
|
95
|
+
this.#tunnelMs = saturatingAdd(this.#tunnelMs, tunnelMs);
|
|
96
|
+
if (event.errorCode)
|
|
97
|
+
this.recordError({ errorCode: event.errorCode, diagnostics: event.diagnostics });
|
|
98
|
+
if (this.#attemptSamples.length < MAX_SAMPLES) {
|
|
99
|
+
this.#attemptSamples.push({
|
|
100
|
+
n: this.#attempts,
|
|
101
|
+
kind: event.kind,
|
|
102
|
+
outcome: event.outcome,
|
|
103
|
+
ms,
|
|
104
|
+
tunnelMs,
|
|
105
|
+
proxyUsed: event.proxyUsed,
|
|
106
|
+
...(event.vendor ? { vendor: event.vendor } : {}),
|
|
107
|
+
...(event.errorCode ? { errorCode: event.errorCode } : {}),
|
|
108
|
+
...(event.diagnostics ? { diagnostics: diagnostics(event.diagnostics, this.#redact) } : {}),
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
else
|
|
112
|
+
this.#attemptSamplesDropped = saturatingAdd(this.#attemptSamplesDropped, 1);
|
|
113
|
+
}
|
|
114
|
+
recordVendorSkip(event) {
|
|
115
|
+
this.#recorded = true;
|
|
116
|
+
this.#vendorSkips = saturatingAdd(this.#vendorSkips, 1);
|
|
117
|
+
this.#vendorSkipReasons.set(event.reason, saturatingAdd(this.#vendorSkipReasons.get(event.reason) ?? 0, 1));
|
|
118
|
+
if (this.#vendorSkipSamples.length < MAX_SAMPLES) {
|
|
119
|
+
this.#vendorSkipSamples.push({
|
|
120
|
+
vendor: event.vendor,
|
|
121
|
+
reason: event.reason,
|
|
122
|
+
...(event.diagnostics ? { diagnostics: diagnostics(event.diagnostics, this.#redact) } : {}),
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
else
|
|
126
|
+
this.#vendorSkipSamplesDropped = saturatingAdd(this.#vendorSkipSamplesDropped, 1);
|
|
127
|
+
}
|
|
128
|
+
recordLifecycle(event) {
|
|
129
|
+
this.#recorded = true;
|
|
130
|
+
this.#lifecycle[event.kind] = saturatingAdd(this.#lifecycle[event.kind], 1);
|
|
131
|
+
if (event.errorCode)
|
|
132
|
+
this.recordError({ errorCode: event.errorCode, diagnostics: event.diagnostics });
|
|
133
|
+
}
|
|
134
|
+
recordBytes(event) {
|
|
135
|
+
this.#recorded = true;
|
|
136
|
+
if (event.direction === "in")
|
|
137
|
+
this.#bytesIn = saturatingAdd(this.#bytesIn, event.bytes);
|
|
138
|
+
else
|
|
139
|
+
this.#bytesOut = saturatingAdd(this.#bytesOut, event.bytes);
|
|
140
|
+
}
|
|
141
|
+
recordError(event) {
|
|
142
|
+
this.#recorded = true;
|
|
143
|
+
this.#lastErrorCode = event.errorCode;
|
|
144
|
+
this.#lastErrorDiagnostics = event.diagnostics
|
|
145
|
+
? diagnostics(event.diagnostics, this.#redact)
|
|
146
|
+
: undefined;
|
|
147
|
+
}
|
|
148
|
+
toLogPayload() {
|
|
149
|
+
if (!this.#recorded)
|
|
150
|
+
return undefined;
|
|
151
|
+
return {
|
|
152
|
+
attempts: this.#attempts,
|
|
153
|
+
connectMs: this.#connectMs,
|
|
154
|
+
tunnelMs: this.#tunnelMs,
|
|
155
|
+
vendorSkips: this.#vendorSkips,
|
|
156
|
+
vendorSkipReasons: [...this.#vendorSkipReasons].map(([reason, count]) => ({ reason, count })),
|
|
157
|
+
drain: this.#lifecycle.drain,
|
|
158
|
+
drainAcknowledged: this.#lifecycle.drain_acknowledged,
|
|
159
|
+
drainErrors: this.#lifecycle.drain_error,
|
|
160
|
+
drainMissingHandler: this.#lifecycle.drain_missing_handler,
|
|
161
|
+
idle: this.#lifecycle.idle,
|
|
162
|
+
expiry: this.#lifecycle.expiry,
|
|
163
|
+
bytesIn: this.#bytesIn,
|
|
164
|
+
bytesOut: this.#bytesOut,
|
|
165
|
+
...(this.#lastErrorCode ? { lastErrorCode: this.#lastErrorCode } : {}),
|
|
166
|
+
...(this.#lastErrorDiagnostics
|
|
167
|
+
? { lastErrorDiagnostics: copyDiagnostics(this.#lastErrorDiagnostics) }
|
|
168
|
+
: {}),
|
|
169
|
+
...(this.#attemptSamples.length
|
|
170
|
+
? {
|
|
171
|
+
attemptSamples: this.#attemptSamples.map((sample) => ({
|
|
172
|
+
...sample,
|
|
173
|
+
...(sample.diagnostics ? { diagnostics: copyDiagnostics(sample.diagnostics) } : {}),
|
|
174
|
+
})),
|
|
175
|
+
}
|
|
176
|
+
: {}),
|
|
177
|
+
...(this.#attemptSamplesDropped
|
|
178
|
+
? { attemptSamplesDropped: this.#attemptSamplesDropped }
|
|
179
|
+
: {}),
|
|
180
|
+
...(this.#vendorSkipSamples.length
|
|
181
|
+
? {
|
|
182
|
+
vendorSkipSamples: this.#vendorSkipSamples.map((sample) => ({
|
|
183
|
+
...sample,
|
|
184
|
+
...(sample.diagnostics ? { diagnostics: copyDiagnostics(sample.diagnostics) } : {}),
|
|
185
|
+
})),
|
|
186
|
+
}
|
|
187
|
+
: {}),
|
|
188
|
+
...(this.#vendorSkipSamplesDropped
|
|
189
|
+
? { vendorSkipSamplesDropped: this.#vendorSkipSamplesDropped }
|
|
190
|
+
: {}),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
toHeaderPayload(log) {
|
|
194
|
+
return {
|
|
195
|
+
attempts: log.attempts,
|
|
196
|
+
connectMs: log.connectMs,
|
|
197
|
+
tunnelMs: log.tunnelMs,
|
|
198
|
+
vendorSkips: log.vendorSkips,
|
|
199
|
+
vendorSkipReasons: log.vendorSkipReasons.map(({ reason, count }) => ({
|
|
200
|
+
reason: closedEnum(reason),
|
|
201
|
+
count,
|
|
202
|
+
})),
|
|
203
|
+
drain: log.drain,
|
|
204
|
+
drainAcknowledged: log.drainAcknowledged,
|
|
205
|
+
drainErrors: log.drainErrors,
|
|
206
|
+
drainMissingHandler: log.drainMissingHandler,
|
|
207
|
+
idle: log.idle,
|
|
208
|
+
expiry: log.expiry,
|
|
209
|
+
bytesIn: log.bytesIn,
|
|
210
|
+
bytesOut: log.bytesOut,
|
|
211
|
+
...(log.lastErrorCode ? { lastErrorCode: closedEnum(log.lastErrorCode) } : {}),
|
|
212
|
+
...(log.attemptSamples
|
|
213
|
+
? {
|
|
214
|
+
attemptSamples: log.attemptSamples.map((sample) => ({
|
|
215
|
+
n: sample.n,
|
|
216
|
+
kind: closedEnum(sample.kind),
|
|
217
|
+
outcome: closedEnum(sample.outcome),
|
|
218
|
+
ms: sample.ms,
|
|
219
|
+
tunnelMs: sample.tunnelMs,
|
|
220
|
+
proxyUsed: sample.proxyUsed,
|
|
221
|
+
...(sample.errorCode ? { errorCode: closedEnum(sample.errorCode) } : {}),
|
|
222
|
+
})),
|
|
223
|
+
}
|
|
224
|
+
: {}),
|
|
225
|
+
...(log.attemptSamplesDropped ? { attemptSamplesDropped: log.attemptSamplesDropped } : {}),
|
|
226
|
+
...(log.vendorSkipSamplesDropped
|
|
227
|
+
? { vendorSkipSamplesDropped: log.vendorSkipSamplesDropped }
|
|
228
|
+
: {}),
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
}
|
package/dist/runtime/state.d.ts
CHANGED
|
@@ -15,6 +15,9 @@ export declare function createProviderRuntimeStateFromEnv(options?: {
|
|
|
15
15
|
readonly providerId?: string;
|
|
16
16
|
readonly allowMemoryFallback?: boolean;
|
|
17
17
|
}): ProviderRuntimeState;
|
|
18
|
-
export declare function createMemoryProviderRuntimeState(
|
|
18
|
+
export declare function createMemoryProviderRuntimeState(options?: {
|
|
19
|
+
/** Clock for expiry and timestamps; defaults to `Date.now`. */
|
|
20
|
+
readonly now?: () => number;
|
|
21
|
+
}): ProviderRuntimeState;
|
|
19
22
|
export declare function createUnsupportedProviderRuntimeState(): ProviderRuntimeState;
|
|
20
23
|
export {};
|
package/dist/runtime/state.js
CHANGED
|
@@ -60,9 +60,12 @@ if not indexed and redis.call("ZCARD", KEYS[2]) >= max_entries then
|
|
|
60
60
|
end
|
|
61
61
|
|
|
62
62
|
if current_decoded and type(current_decoded.createdAt) == "string" then
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
envelope
|
|
63
|
+
-- Preserve the original createdAt by patching the encoded envelope in place.
|
|
64
|
+
-- The envelope puts its metadata before "value", so the first match is the
|
|
65
|
+
-- envelope's own key, never a provider value; re-encoding with cjson would
|
|
66
|
+
-- silently turn [] inside the value into {}.
|
|
67
|
+
local preserved = string.gsub(current_decoded.createdAt, "%%", "%%%%")
|
|
68
|
+
envelope = (string.gsub(envelope, '"createdAt":"[^"]*"', '"createdAt":"' .. preserved .. '"', 1))
|
|
66
69
|
end
|
|
67
70
|
redis.call("SET", KEYS[1], envelope, "PXAT", expires_at)
|
|
68
71
|
redis.call("ZADD", KEYS[2], expires_at, KEYS[1])
|
|
@@ -197,7 +200,11 @@ function isStateTombstone(raw) {
|
|
|
197
200
|
}
|
|
198
201
|
function redisEnvelope(value, version, createdAt, expiresAt) {
|
|
199
202
|
const updatedAt = new Date().toISOString();
|
|
200
|
-
|
|
203
|
+
// Metadata precedes `value` on purpose: the compare-and-set Lua script patches
|
|
204
|
+
// `createdAt` in the encoded string (first occurrence) instead of decoding and
|
|
205
|
+
// re-encoding with cjson, which would turn empty arrays inside `value` into
|
|
206
|
+
// objects. Keep the key order stable.
|
|
207
|
+
return { version, expiresAt, createdAt, updatedAt, value };
|
|
201
208
|
}
|
|
202
209
|
class RedisProviderStateNamespace {
|
|
203
210
|
backend;
|
|
@@ -404,10 +411,12 @@ class UnsupportedProviderRuntimeState {
|
|
|
404
411
|
}
|
|
405
412
|
class MemoryProviderStateNamespace {
|
|
406
413
|
options;
|
|
414
|
+
now;
|
|
407
415
|
// biome-ignore lint/suspicious/noExplicitAny: in-memory state stores heterogeneous generic values by key.
|
|
408
416
|
values = new Map();
|
|
409
|
-
constructor(options) {
|
|
417
|
+
constructor(options, now = Date.now) {
|
|
410
418
|
this.options = options;
|
|
419
|
+
this.now = now;
|
|
411
420
|
}
|
|
412
421
|
enforceValueSize(value) {
|
|
413
422
|
const bytes = Buffer.byteLength(JSON.stringify(value), "utf8");
|
|
@@ -426,7 +435,7 @@ class MemoryProviderStateNamespace {
|
|
|
426
435
|
throw new UnsupportedProviderStateError(`Provider runtime state namespace quota exceeded (${this.options.maxEntries + 1} > ${this.options.maxEntries})`);
|
|
427
436
|
}
|
|
428
437
|
}
|
|
429
|
-
pruneExpired(nowMs =
|
|
438
|
+
pruneExpired(nowMs = this.now()) {
|
|
430
439
|
for (const [key, row] of this.values.entries()) {
|
|
431
440
|
if (row.expiresAt && Date.parse(row.expiresAt) <= nowMs) {
|
|
432
441
|
this.values.delete(key);
|
|
@@ -445,9 +454,9 @@ class MemoryProviderStateNamespace {
|
|
|
445
454
|
async set(key, value, options) {
|
|
446
455
|
this.pruneExpired();
|
|
447
456
|
this.enforceWritePolicy(key, value, options?.ttl);
|
|
448
|
-
const now = new Date().toISOString();
|
|
457
|
+
const now = new Date(this.now()).toISOString();
|
|
449
458
|
const current = this.values.get(key);
|
|
450
|
-
const expiresAt = resolveMemoryStateExpiresAt(options?.ttl ?? this.options.defaultTtl);
|
|
459
|
+
const expiresAt = resolveMemoryStateExpiresAt(options?.ttl ?? this.options.defaultTtl, this.now());
|
|
451
460
|
const row = {
|
|
452
461
|
key,
|
|
453
462
|
value,
|
|
@@ -469,12 +478,12 @@ class MemoryProviderStateNamespace {
|
|
|
469
478
|
return { ok: false, current: current ?? null };
|
|
470
479
|
}
|
|
471
480
|
this.enforceWritePolicy(key, value, options?.ttl);
|
|
472
|
-
const now = new Date().toISOString();
|
|
481
|
+
const now = new Date(this.now()).toISOString();
|
|
473
482
|
const stored = {
|
|
474
483
|
key,
|
|
475
484
|
value,
|
|
476
485
|
version: expectedVersion + 1,
|
|
477
|
-
expiresAt: resolveMemoryStateExpiresAt(options?.ttl ?? this.options.defaultTtl),
|
|
486
|
+
expiresAt: resolveMemoryStateExpiresAt(options?.ttl ?? this.options.defaultTtl, this.now()),
|
|
478
487
|
createdAt: current?.createdAt ?? now,
|
|
479
488
|
updatedAt: now,
|
|
480
489
|
};
|
|
@@ -491,7 +500,10 @@ class MemoryProviderStateNamespace {
|
|
|
491
500
|
class MemoryProviderRuntimeState {
|
|
492
501
|
backend;
|
|
493
502
|
scopeDiscriminator;
|
|
494
|
-
constructor(backend = {
|
|
503
|
+
constructor(backend = {
|
|
504
|
+
namespaces: new Map(),
|
|
505
|
+
now: Date.now,
|
|
506
|
+
}, scopeDiscriminator = MISSING_CONNECTION_SCOPE_DISCRIMINATOR) {
|
|
495
507
|
this.backend = backend;
|
|
496
508
|
this.scopeDiscriminator = scopeDiscriminator;
|
|
497
509
|
}
|
|
@@ -504,15 +516,15 @@ class MemoryProviderRuntimeState {
|
|
|
504
516
|
const existing = this.backend.namespaces.get(namespaceIdentity);
|
|
505
517
|
if (existing)
|
|
506
518
|
return existing;
|
|
507
|
-
const created = new MemoryProviderStateNamespace(options);
|
|
519
|
+
const created = new MemoryProviderStateNamespace(options, this.backend.now);
|
|
508
520
|
this.backend.namespaces.set(namespaceIdentity, created);
|
|
509
521
|
return created;
|
|
510
522
|
}
|
|
511
523
|
}
|
|
512
|
-
function resolveMemoryStateExpiresAt(ttl) {
|
|
524
|
+
function resolveMemoryStateExpiresAt(ttl, nowMs = Date.now()) {
|
|
513
525
|
const match = /^(\d+)(ms|s|m|h|d)$/.exec(ttl ?? "1h");
|
|
514
526
|
if (!match)
|
|
515
|
-
return new Date(
|
|
527
|
+
return new Date(nowMs + 3_600_000).toISOString();
|
|
516
528
|
const amount = Number(match[1]);
|
|
517
529
|
const unit = match[2];
|
|
518
530
|
const multiplier = unit === "ms"
|
|
@@ -524,7 +536,7 @@ function resolveMemoryStateExpiresAt(ttl) {
|
|
|
524
536
|
: unit === "h"
|
|
525
537
|
? 3_600_000
|
|
526
538
|
: 86_400_000;
|
|
527
|
-
return new Date(
|
|
539
|
+
return new Date(nowMs + amount * multiplier).toISOString();
|
|
528
540
|
}
|
|
529
541
|
export function createRedisProviderRuntimeState(options) {
|
|
530
542
|
return new RedisProviderRuntimeState(options);
|
|
@@ -542,8 +554,11 @@ export function createProviderRuntimeStateFromEnv(options = {}) {
|
|
|
542
554
|
}
|
|
543
555
|
return createUnsupportedProviderRuntimeState();
|
|
544
556
|
}
|
|
545
|
-
export function createMemoryProviderRuntimeState() {
|
|
546
|
-
return new MemoryProviderRuntimeState(
|
|
557
|
+
export function createMemoryProviderRuntimeState(options) {
|
|
558
|
+
return new MemoryProviderRuntimeState({
|
|
559
|
+
namespaces: new Map(),
|
|
560
|
+
now: options?.now ?? Date.now,
|
|
561
|
+
});
|
|
547
562
|
}
|
|
548
563
|
export function createUnsupportedProviderRuntimeState() {
|
|
549
564
|
return new UnsupportedProviderRuntimeState();
|
package/dist/server/index.d.ts
CHANGED
|
@@ -12,3 +12,4 @@ export { collectSelfTestSensitiveValues, redactSelfTestText, SELF_TEST_MAX_TEXT_
|
|
|
12
12
|
export { DEFAULT_SELF_TEST_PORT, deriveSelfTestToken, PROVIDER_RUNTIME_SELF_TEST_MASTER_SECRET_ENV, PROVIDER_RUNTIME_SELF_TEST_MASTER_SECRET_PREVIOUS_ENV, PROVIDER_RUNTIME_SELF_TEST_PORT_ENV, resolveSelfTestMasterSecrets, type SelfTestMasterSecrets, verifySelfTestAuthorization, } from "./self-test-token.js";
|
|
13
13
|
export type { AuthFlowRequest, AuthFlowResponse, AuthFlowSuccessResponse, ConnectionMode, OperationConnection, OperationErrorResponse, OperationRequest, OperationResponse, OperationSuccessResponse, } from "./types.js";
|
|
14
14
|
export { AuthFlowRequestSchema, AuthFlowSuccessResponseSchema, ConnectionModeSchema, ErrorEnvelopeSchema, OperationConnectionSchema, OperationErrorResponseSchema, OperationRequestSchema, OperationSuccessResponseSchema, } from "./types.js";
|
|
15
|
+
export { NativeTelemetryCollector, type NativeAttemptSample, type NativeConnectTelemetryEvent, type NativeLifecycleTelemetryEvent, type NativeTelemetryDiagnostics, type NativeTelemetryErrorCode, type NativeTelemetryHeaderPayload, type NativeTelemetryKind, type NativeTelemetryLifecycleKind, type NativeTelemetryLogPayload, type NativeTelemetryOutcome, type NativeTelemetrySink, type NativeTelemetryVendorSkipReason, type NativeVendorSkipTelemetryEvent, } from "../runtime/native-telemetry.js";
|
package/dist/server/index.js
CHANGED
|
@@ -6,3 +6,4 @@ export { resolveHealthCheckInputDateTokens, } from "./self-test-input-tokens.js"
|
|
|
6
6
|
export { collectSelfTestSensitiveValues, redactSelfTestText, SELF_TEST_MAX_TEXT_LENGTH, SELF_TEST_REDACTED_PLACEHOLDER, } from "./self-test-redaction.js";
|
|
7
7
|
export { DEFAULT_SELF_TEST_PORT, deriveSelfTestToken, PROVIDER_RUNTIME_SELF_TEST_MASTER_SECRET_ENV, PROVIDER_RUNTIME_SELF_TEST_MASTER_SECRET_PREVIOUS_ENV, PROVIDER_RUNTIME_SELF_TEST_PORT_ENV, resolveSelfTestMasterSecrets, verifySelfTestAuthorization, } from "./self-test-token.js";
|
|
8
8
|
export { AuthFlowRequestSchema, AuthFlowSuccessResponseSchema, ConnectionModeSchema, ErrorEnvelopeSchema, OperationConnectionSchema, OperationErrorResponseSchema, OperationRequestSchema, OperationSuccessResponseSchema, } from "./types.js";
|
|
9
|
+
export { NativeTelemetryCollector, } from "../runtime/native-telemetry.js";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ProviderDefinition } from "../types.js";
|
|
2
2
|
export declare const SELF_TEST_SENSITIVE_SOURCE_KINDS: readonly ["selfTestMasterSecrets", "providerSecrets", "healthProbeSecrets", "requestCredentials"];
|
|
3
3
|
/** Explicit source allowlist: no environment-name or payload-shape secret sweep. */
|
|
4
|
-
export declare const DIAGNOSTIC_SENSITIVE_SOURCE_KINDS: readonly ["selfTestMasterSecrets", "providerSecrets", "healthProbeSecrets", "requestCredentials", "engineProxyCredentials", "engineSolverKeys", "engineCeremonyLeaseCredentials", "
|
|
4
|
+
export declare const DIAGNOSTIC_SENSITIVE_SOURCE_KINDS: readonly ["selfTestMasterSecrets", "providerSecrets", "healthProbeSecrets", "requestCredentials", "engineProxyCredentials", "engineSolverKeys", "engineCeremonyLeaseCredentials", "ocrCredentials", "sttCredentials", "cdpCredentials", "statefulForwardingSecret", "redisCredentials", "otlpHeaders", "cacheKeyPepper", "sensitiveParams"];
|
|
5
5
|
export type DiagnosticSensitiveSourceKind = (typeof DIAGNOSTIC_SENSITIVE_SOURCE_KINDS)[number];
|
|
6
6
|
/**
|
|
7
7
|
* Startup inventory; request-scoped readers additionally register live values on access.
|
|
@@ -2,7 +2,6 @@ import { resolvedStringVariants, urlCredentialComponents, otlpHeaderCredentialVa
|
|
|
2
2
|
import { PROVIDER_CACHE_REDIS_URL_ENV, PROVIDER_STATE_REDIS_URL_ENV, REDIS_URL_ENV, } from "../config/loader.js";
|
|
3
3
|
import { ENGINE_OWNED_PROXY_CREDENTIAL_ENV_NAMES, ENGINE_OWNED_RESOLVER_CREDENTIAL_ENV_NAMES, } from "../engine.js";
|
|
4
4
|
import { APIFUSE__CACHE__KEY_PEPPER_ENV } from "../runtime/cache.js";
|
|
5
|
-
import { PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV } from "../runtime/choice.js";
|
|
6
5
|
import { APIFUSE__ENGINE__CEREMONY_LEASE_KEY } from "../runtime/egress-lease.js";
|
|
7
6
|
import { APIFUSE__OCR__API_KEY_ENV, APIFUSE__OCR__CLOUDFLARE_API_TOKEN_ENV, } from "../runtime/ocr.js";
|
|
8
7
|
import { OTEL_EXPORTER_OTLP_HEADERS, OTEL_EXPORTER_OTLP_TRACES_HEADERS, OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, } from "../runtime/otlp.js";
|
|
@@ -21,7 +20,6 @@ export const DIAGNOSTIC_SENSITIVE_SOURCE_KINDS = [
|
|
|
21
20
|
"engineProxyCredentials",
|
|
22
21
|
"engineSolverKeys",
|
|
23
22
|
"engineCeremonyLeaseCredentials",
|
|
24
|
-
"choiceMasterSecret",
|
|
25
23
|
"ocrCredentials",
|
|
26
24
|
"sttCredentials",
|
|
27
25
|
"cdpCredentials",
|
|
@@ -68,7 +66,6 @@ export function collectDiagnosticSensitiveValues(provider, options = {}) {
|
|
|
68
66
|
// These are the resolver-config API keys after #251's engine credential projection.
|
|
69
67
|
engineSolverKeys: () => resolvedEnvValues(ENGINE_OWNED_RESOLVER_CREDENTIAL_ENV_NAMES),
|
|
70
68
|
engineCeremonyLeaseCredentials: () => resolvedEnvValues([APIFUSE__ENGINE__CEREMONY_LEASE_KEY]),
|
|
71
|
-
choiceMasterSecret: () => resolvedEnvValues([PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV]),
|
|
72
69
|
ocrCredentials: () => resolvedEnvValues([APIFUSE__OCR__CLOUDFLARE_API_TOKEN_ENV, APIFUSE__OCR__API_KEY_ENV]),
|
|
73
70
|
sttCredentials: () => resolvedEnvValues([APIFUSE__STT__CLOUDFLARE_API_TOKEN_ENV]),
|
|
74
71
|
cdpCredentials: () => resolvedStringVariants(env[APIFUSE__CDP_POOL__URL]).flatMap((url) => [
|
|
@@ -3,6 +3,7 @@ import { z } from "zod";
|
|
|
3
3
|
import { type ProviderEngine } from "../engine.js";
|
|
4
4
|
import { type ProviderErrorObservability } from "../errors.js";
|
|
5
5
|
import { type ProviderErrorCategory } from "../observability.js";
|
|
6
|
+
import type { HandleTelemetryEvent } from "../handle.js";
|
|
6
7
|
import { type ProxyTelemetryLogPayload } from "../runtime/proxy-telemetry.js";
|
|
7
8
|
import { type ResolverTelemetryLogPayload } from "../runtime/resolver-telemetry.js";
|
|
8
9
|
import type { OcrContext, ProviderContext, ProviderDefinition, ProviderFilesContext, ProviderRuntimeState, ResolverContext, SttContext } from "../types.js";
|
|
@@ -115,18 +116,10 @@ export type ProviderServerLogEvent = (ProviderServerLogEventBase & {
|
|
|
115
116
|
event: "provider_secrets_missing";
|
|
116
117
|
providerId: string;
|
|
117
118
|
missingSecrets: string[];
|
|
118
|
-
} | {
|
|
119
|
+
} | ({
|
|
119
120
|
level: "info";
|
|
120
|
-
event: "
|
|
121
|
-
|
|
122
|
-
purpose: string;
|
|
123
|
-
operation: "parse" | "consume";
|
|
124
|
-
format: "word" | "legacy";
|
|
125
|
-
outcome: "success" | "not-found" | "invalid" | "unsupported" | "error";
|
|
126
|
-
consumeMode: "never" | "on-parse" | "explicit";
|
|
127
|
-
consumed: boolean;
|
|
128
|
-
replay: boolean;
|
|
129
|
-
} | {
|
|
121
|
+
event: "provider_handle";
|
|
122
|
+
} & HandleTelemetryEvent) | {
|
|
130
123
|
level: "warn";
|
|
131
124
|
event: "provider_cleanup_failed";
|
|
132
125
|
providerId: string;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { NativeTelemetryCollector } from "../runtime/native-telemetry.js";
|
|
1
2
|
import { STATUS_CODES } from "node:http";
|
|
2
3
|
import { readDiagnosticEnv, withDiagnosticEnv } from "../runtime/diagnostic-env.js";
|
|
3
4
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
@@ -18,7 +19,7 @@ import { loadProviderLocaleCatalogs, localizeAuthTurn, } from "../i18n/catalog.j
|
|
|
18
19
|
import { categoryForStatus, isRetryableCategory, PROVIDER_OBSERVABILITY_TAXONOMY_VERSION, sourceForCategory, } from "../observability.js";
|
|
19
20
|
import { createScratchpad } from "../runtime/auth-flow.js";
|
|
20
21
|
import { createProviderCache } from "../runtime/cache.js";
|
|
21
|
-
import {
|
|
22
|
+
import { createHandleContext } from "../runtime/handle.js";
|
|
22
23
|
import { createCredentialContext } from "../runtime/credential.js";
|
|
23
24
|
import { bindDiagnosticSensitiveRegistry, compileProcessDiagnosticSensitiveValues, createDiagnosticRedactor, REDACTION_FAILED, isDiagnosticRedactionSuppressed, bindCriticalDiagnosticRedactor, redactCriticalDiagnosticText, redactDiagnosticText, registerDiagnosticSensitiveValues, } from "../runtime/diagnostic-redactor.js";
|
|
24
25
|
import { createCeremonyEgressLeaseRuntime, ENGINE_CEREMONY_EGRESS_LEASE, } from "../runtime/egress-lease.js";
|
|
@@ -525,10 +526,7 @@ function createProviderContext(provider, request, operationId, options, state =
|
|
|
525
526
|
: {}),
|
|
526
527
|
};
|
|
527
528
|
const logStealthCleanupError = (error) => logProviderCleanupError(options.logger, provider, "operation", operationId, request.requestId, "stealth", error, scope.redact);
|
|
528
|
-
const env = createEnvContext([
|
|
529
|
-
...providerSecretNames(provider),
|
|
530
|
-
PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV,
|
|
531
|
-
]);
|
|
529
|
+
const env = createEnvContext([...providerSecretNames(provider)]);
|
|
532
530
|
const credential = createCredentialContext({
|
|
533
531
|
allowedKeys: provider.credential?.keys,
|
|
534
532
|
mode: request.connection?.mode,
|
|
@@ -575,6 +573,8 @@ function createProviderContext(provider, request, operationId, options, state =
|
|
|
575
573
|
? {
|
|
576
574
|
native: {
|
|
577
575
|
network: capabilityModules.nativeNetwork.createNativeNetworkClient({
|
|
576
|
+
telemetry: scope.telemetry.proxy,
|
|
577
|
+
nativeTelemetry: scope.nativeTelemetry,
|
|
578
578
|
egress: provider.native.network,
|
|
579
579
|
proxyPolicy: resolveNativeProxyPolicy(provider),
|
|
580
580
|
affinityKey: proxyClientOptions.affinityKey,
|
|
@@ -596,15 +596,13 @@ function createProviderContext(provider, request, operationId, options, state =
|
|
|
596
596
|
? bindResolverTelemetry(options.resolver, resolverOptions.telemetry)
|
|
597
597
|
: undefined) ??
|
|
598
598
|
createUnsupportedResolverClient("Provider does not declare resolver capability"), signal),
|
|
599
|
-
|
|
599
|
+
handle: createHandleContext({
|
|
600
600
|
providerId: provider.id,
|
|
601
|
-
env,
|
|
602
601
|
request: requestContext,
|
|
603
|
-
credential,
|
|
604
602
|
state: requestState,
|
|
605
603
|
onTelemetry: (event) => (options.logger ?? defaultProviderServerLogger)({
|
|
606
604
|
level: "info",
|
|
607
|
-
event: "
|
|
605
|
+
event: "provider_handle",
|
|
608
606
|
...event,
|
|
609
607
|
}),
|
|
610
608
|
}),
|
|
@@ -732,6 +730,8 @@ function createAuthFlowContext(provider, request, route, options, state, scope,
|
|
|
732
730
|
? {
|
|
733
731
|
native: {
|
|
734
732
|
network: capabilityModules.nativeNetwork.createNativeNetworkClient({
|
|
733
|
+
telemetry: scope.telemetry.proxy,
|
|
734
|
+
nativeTelemetry: scope.nativeTelemetry,
|
|
735
735
|
egress: provider.native.network,
|
|
736
736
|
proxyPolicy: resolveNativeProxyPolicy(provider),
|
|
737
737
|
affinityKey: proxyClientOptions.affinityKey,
|
|
@@ -884,6 +884,52 @@ function toErrorResponse(error, requestId, observabilityDetails, redact) {
|
|
|
884
884
|
// narrowing from a ProviderError-typed value would collapse the negative branch
|
|
885
885
|
// to `never`. Narrowing from unknown avoids that while still recognizing errors
|
|
886
886
|
// from a duplicate SDK module instance.
|
|
887
|
+
function ownErrorCode(error) {
|
|
888
|
+
if (error === null || (typeof error !== "object" && typeof error !== "function")) {
|
|
889
|
+
return undefined;
|
|
890
|
+
}
|
|
891
|
+
try {
|
|
892
|
+
const descriptor = Object.getOwnPropertyDescriptor(error, "code");
|
|
893
|
+
return descriptor && Object.hasOwn(descriptor, "value") && typeof descriptor.value === "string"
|
|
894
|
+
? descriptor.value
|
|
895
|
+
: undefined;
|
|
896
|
+
}
|
|
897
|
+
catch {
|
|
898
|
+
return undefined;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
function errorChainHasCode(error, expected) {
|
|
902
|
+
const seen = new Set();
|
|
903
|
+
let current = error;
|
|
904
|
+
for (let depth = 0; depth < 5; depth++) {
|
|
905
|
+
if (current === null || (typeof current !== "object" && typeof current !== "function")) {
|
|
906
|
+
return false;
|
|
907
|
+
}
|
|
908
|
+
if (seen.has(current))
|
|
909
|
+
return false;
|
|
910
|
+
seen.add(current);
|
|
911
|
+
let code;
|
|
912
|
+
try {
|
|
913
|
+
code = providerErrorCode(current) ?? ownErrorCode(current);
|
|
914
|
+
}
|
|
915
|
+
catch {
|
|
916
|
+
code = ownErrorCode(current);
|
|
917
|
+
}
|
|
918
|
+
if (code === expected)
|
|
919
|
+
return true;
|
|
920
|
+
let cause;
|
|
921
|
+
try {
|
|
922
|
+
cause = Object.getOwnPropertyDescriptor(current, "cause");
|
|
923
|
+
}
|
|
924
|
+
catch {
|
|
925
|
+
return false;
|
|
926
|
+
}
|
|
927
|
+
if (!cause || !Object.hasOwn(cause, "value"))
|
|
928
|
+
return false;
|
|
929
|
+
current = cause.value;
|
|
930
|
+
}
|
|
931
|
+
return false;
|
|
932
|
+
}
|
|
887
933
|
function providerObservabilityDetails(error, declaredErrorCode) {
|
|
888
934
|
const declaredRetryable = sdkOwnsErrorResolution(error)
|
|
889
935
|
? undefined
|
|
@@ -909,24 +955,43 @@ function providerObservabilityDetails(error, declaredErrorCode) {
|
|
|
909
955
|
retryable: providerErrorOption(error, "retryable") ?? declaredRetryable ?? false,
|
|
910
956
|
};
|
|
911
957
|
}
|
|
958
|
+
// Native proxy resolution throws ProxyResolutionError directly, while HTTP
|
|
959
|
+
// normalizes it beneath a transport_network_error cause and stealth retains
|
|
960
|
+
// the original code. Preserve proxy attribution across all three forms.
|
|
961
|
+
if (errorChainHasCode(error, "PROXY_REQUIRED")) {
|
|
962
|
+
return {
|
|
963
|
+
category: "proxy_pool",
|
|
964
|
+
taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
|
|
965
|
+
retryable: true,
|
|
966
|
+
};
|
|
967
|
+
}
|
|
912
968
|
if (!isTransportError(error)) {
|
|
913
969
|
return undefined;
|
|
914
970
|
}
|
|
915
|
-
const
|
|
916
|
-
|
|
917
|
-
|
|
971
|
+
const errorCode = providerErrorCode(error);
|
|
972
|
+
const isProxyPoolCode = errorCode === PROXY_POOL_EXHAUSTED_CODE ||
|
|
973
|
+
errorCode === PROXY_EDGE_AUTH_REJECTED_CODE ||
|
|
974
|
+
errorCode === "PROXY_ALLOCATION_FAILED" ||
|
|
975
|
+
errorCode === "native_proxy_expired" ||
|
|
976
|
+
errorCode === "native_proxy_invalid";
|
|
918
977
|
const category = providerErrorOption(error, "category") ??
|
|
919
978
|
(isProxyPoolCode
|
|
920
979
|
? "proxy_pool"
|
|
921
|
-
:
|
|
980
|
+
: errorCode === PROXY_AUTH_IP_DENIED_CODE
|
|
922
981
|
? "anti_bot_blocked"
|
|
923
|
-
:
|
|
982
|
+
: errorCode === "transport_timeout" ||
|
|
983
|
+
errorCode === "native_connection_timeout" ||
|
|
984
|
+
errorCode === "native_connection_idle_timeout"
|
|
924
985
|
? "timeout"
|
|
925
|
-
:
|
|
986
|
+
: errorCode === "transport_network_error" ||
|
|
987
|
+
errorCode === "native_connection_failed" ||
|
|
988
|
+
errorCode === "native_connection_closed"
|
|
926
989
|
? "network"
|
|
927
|
-
:
|
|
928
|
-
?
|
|
929
|
-
:
|
|
990
|
+
: errorCode === "native_connection_aborted"
|
|
991
|
+
? "client_cancelled"
|
|
992
|
+
: error.upstreamStatus
|
|
993
|
+
? categoryForStatus(error.upstreamStatus)
|
|
994
|
+
: "upstream_http");
|
|
930
995
|
return {
|
|
931
996
|
category,
|
|
932
997
|
taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
|
|
@@ -1415,9 +1480,11 @@ function createRequestScope(input) {
|
|
|
1415
1480
|
bindDiagnosticSensitiveRegistry(trace, sensitiveRegistry);
|
|
1416
1481
|
const proxyCollector = new ProxyTelemetryCollector();
|
|
1417
1482
|
const resolverCollector = new ResolverTelemetryCollector({ redact });
|
|
1483
|
+
const nativeCollector = new NativeTelemetryCollector({ redact });
|
|
1418
1484
|
const telemetry = new RequestTelemetry(trace);
|
|
1419
1485
|
telemetry.register(proxyCollector);
|
|
1420
1486
|
telemetry.register(resolverCollector);
|
|
1487
|
+
telemetry.register(nativeCollector);
|
|
1421
1488
|
let rootRunner = (fn) => fn();
|
|
1422
1489
|
let resolveRoot;
|
|
1423
1490
|
const rootTerminal = new Promise((resolve) => {
|
|
@@ -1494,6 +1561,7 @@ function createRequestScope(input) {
|
|
|
1494
1561
|
trace,
|
|
1495
1562
|
telemetry,
|
|
1496
1563
|
resolverTelemetry: resolverCollector,
|
|
1564
|
+
nativeTelemetry: nativeCollector,
|
|
1497
1565
|
seedCredentials(rawBody, kind) {
|
|
1498
1566
|
const harvested = rawRequestCredentials(rawBody, kind);
|
|
1499
1567
|
sensitiveRegistry.add(harvested.values);
|