@apifuse/provider-sdk 2.2.0-beta.3 → 2.2.0-beta.4
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 +14 -0
- package/CHANGELOG.md +4 -0
- package/bin/apifuse-check.ts +18 -58
- package/bin/apifuse-create.ts +1 -1
- package/bin/apifuse-dev.ts +27 -52
- package/bin/apifuse-pack-smoke.ts +22 -81
- package/bin/apifuse-pack-types.ts +266 -0
- package/bin/apifuse-perf.ts +45 -127
- package/bin/apifuse-record.ts +23 -61
- package/bin/apifuse-submit-check.ts +26 -21
- package/bin/apifuse.ts +1 -1
- package/bin/submit-check-xml.ts +1 -1
- package/dist/auth-turn/index.d.ts +1 -1
- package/dist/auth.d.ts +2 -2
- package/dist/auth.js +9 -18
- package/dist/ceremonies/index.d.ts +1 -1
- package/dist/ceremonies/index.js +8 -18
- package/dist/config/loader.d.ts +2 -2
- package/dist/config/loader.js +19 -42
- package/dist/contract-serialization.d.ts +2 -2
- package/dist/contract-serialization.js +3 -6
- package/dist/contract-types.d.ts +2 -2
- package/dist/contract.d.ts +3 -3
- package/dist/contract.js +4 -6
- package/dist/define.d.ts +8 -1
- package/dist/define.js +57 -115
- package/dist/dev.d.ts +1 -1
- package/dist/dev.js +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/errors.js +1 -3
- package/dist/i18n/catalog.d.ts +2 -2
- package/dist/i18n/catalog.js +4 -10
- package/dist/i18n/index.d.ts +2 -2
- package/dist/i18n/index.js +2 -2
- package/dist/i18n/keys.d.ts +2 -2
- package/dist/index.d.ts +43 -42
- package/dist/index.js +38 -37
- package/dist/lint.js +8 -15
- package/dist/provider.d.ts +11 -11
- package/dist/provider.js +9 -9
- package/dist/public-schema-field-lint.d.ts +1 -1
- package/dist/recipes/gov-api.js +1 -1
- package/dist/runtime/auth-flow.d.ts +1 -1
- package/dist/runtime/auth-flow.js +3 -3
- package/dist/runtime/browser.d.ts +1 -1
- package/dist/runtime/browser.js +15 -29
- package/dist/runtime/cache.d.ts +1 -1
- package/dist/runtime/cache.js +4 -8
- package/dist/runtime/choice.d.ts +1 -1
- package/dist/runtime/choice.js +12 -34
- package/dist/runtime/credential.d.ts +1 -1
- package/dist/runtime/credential.js +1 -1
- package/dist/runtime/env.d.ts +1 -1
- package/dist/runtime/executor.d.ts +1 -1
- package/dist/runtime/executor.js +2 -2
- package/dist/runtime/http.d.ts +2 -2
- package/dist/runtime/http.js +5 -5
- package/dist/runtime/insights.d.ts +1 -1
- package/dist/runtime/insights.js +6 -13
- package/dist/runtime/instrumentation.d.ts +2 -2
- package/dist/runtime/instrumentation.js +5 -15
- package/dist/runtime/keyring.js +1 -1
- package/dist/runtime/namespace.js +1 -1
- package/dist/runtime/otlp.d.ts +1 -1
- package/dist/runtime/perf.d.ts +1 -1
- package/dist/runtime/provider.d.ts +1 -1
- package/dist/runtime/provider.js +1 -2
- package/dist/runtime/proxy-errors.d.ts +1 -1
- package/dist/runtime/proxy-errors.js +3 -5
- package/dist/runtime/proxy-retry-policy.d.ts +2 -2
- package/dist/runtime/proxy-retry-policy.js +2 -2
- package/dist/runtime/proxy-telemetry.d.ts +1 -1
- package/dist/runtime/proxy-telemetry.js +16 -48
- package/dist/runtime/redis.d.ts +1 -1
- package/dist/runtime/redis.js +2 -4
- package/dist/runtime/request-options.d.ts +1 -1
- package/dist/runtime/state.d.ts +2 -2
- package/dist/runtime/state.js +3 -3
- package/dist/runtime/stealth.d.ts +2 -2
- package/dist/runtime/stealth.js +6 -6
- package/dist/runtime/stt.d.ts +1 -1
- package/dist/runtime/stt.js +11 -15
- package/dist/runtime/trace.d.ts +2 -2
- package/dist/runtime/trace.js +2 -4
- package/dist/runtime/waterfall.d.ts +1 -1
- package/dist/schema.d.ts +1 -1
- package/dist/schema.js +7 -15
- package/dist/serve.d.ts +1 -1
- package/dist/serve.js +1 -1
- package/dist/server/index.d.ts +7 -7
- package/dist/server/index.js +6 -6
- package/dist/server/self-test-redaction.d.ts +1 -1
- package/dist/server/self-test-redaction.js +1 -1
- package/dist/server/self-test.d.ts +3 -3
- package/dist/server/self-test.js +3 -3
- package/dist/server/serve.d.ts +3 -3
- package/dist/server/serve.js +36 -49
- package/dist/server/types.js +2 -7
- package/dist/stealth/profiles.d.ts +1 -1
- package/dist/stealth/profiles.js +5 -14
- package/dist/stream.d.ts +1 -1
- package/dist/testing/index.d.ts +2 -2
- package/dist/testing/index.js +2 -2
- package/dist/testing/run.d.ts +1 -1
- package/dist/testing/run.js +7 -14
- package/dist/types.d.ts +57 -0
- package/dist/user-input.d.ts +30 -0
- package/dist/user-input.js +66 -0
- package/package.json +6 -4
- package/src/auth-turn/index.ts +1 -1
- package/src/auth.ts +28 -86
- package/src/ceremonies/index.ts +24 -78
- package/src/config/loader.ts +58 -176
- package/src/contract-serialization.ts +5 -11
- package/src/contract-types.ts +2 -2
- package/src/contract.ts +12 -28
- package/src/define.ts +174 -495
- package/src/dev.ts +4 -9
- package/src/errors.ts +4 -11
- package/src/i18n/catalog.ts +10 -32
- package/src/i18n/index.ts +2 -2
- package/src/i18n/keys.ts +5 -11
- package/src/index.ts +44 -42
- package/src/lint.ts +89 -159
- package/src/provider.ts +12 -11
- package/src/public-schema-field-lint.ts +7 -33
- package/src/recipes/gov-api.ts +2 -5
- package/src/runtime/auth-flow.ts +5 -7
- package/src/runtime/browser.ts +64 -187
- package/src/runtime/cache.ts +20 -67
- package/src/runtime/choice.ts +55 -129
- package/src/runtime/credential.ts +2 -2
- package/src/runtime/env.ts +1 -1
- package/src/runtime/executor.ts +9 -19
- package/src/runtime/http.ts +7 -7
- package/src/runtime/insights.ts +15 -53
- package/src/runtime/instrumentation.ts +19 -58
- package/src/runtime/keyring.ts +7 -19
- package/src/runtime/namespace.ts +2 -7
- package/src/runtime/otlp.ts +12 -23
- package/src/runtime/perf.ts +1 -1
- package/src/runtime/provider.ts +4 -9
- package/src/runtime/proxy-errors.ts +17 -38
- package/src/runtime/proxy-retry-policy.ts +3 -3
- package/src/runtime/proxy-telemetry.ts +23 -72
- package/src/runtime/redis.ts +4 -12
- package/src/runtime/request-options.ts +4 -13
- package/src/runtime/state.ts +30 -109
- package/src/runtime/stealth.ts +8 -8
- package/src/runtime/stt.ts +38 -94
- package/src/runtime/trace.ts +14 -44
- package/src/runtime/waterfall.ts +5 -18
- package/src/schema.ts +23 -84
- package/src/serve.ts +1 -1
- package/src/server/index.ts +7 -7
- package/src/server/self-test-redaction.ts +2 -2
- package/src/server/self-test.ts +5 -5
- package/src/server/serve.ts +87 -242
- package/src/server/types.ts +6 -19
- package/src/stealth/profiles.ts +10 -26
- package/src/stream.ts +8 -19
- package/src/testing/index.ts +2 -2
- package/src/testing/run.ts +19 -67
- package/src/types.ts +58 -0
- package/src/user-input.ts +118 -0
package/src/runtime/stt.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ProviderError, TransportError, ValidationError } from "../errors";
|
|
1
|
+
import { ProviderError, TransportError, ValidationError } from "../errors.js";
|
|
2
2
|
import type {
|
|
3
3
|
Bcp47Locale,
|
|
4
4
|
ProviderSttConfig,
|
|
@@ -13,16 +13,14 @@ import type {
|
|
|
13
13
|
VerificationCodeCandidate,
|
|
14
14
|
VerificationCodeCandidateSource,
|
|
15
15
|
VerificationCodeExtractionResult,
|
|
16
|
-
} from "../types";
|
|
16
|
+
} from "../types.js";
|
|
17
17
|
|
|
18
18
|
export const APIFUSE__STT__BACKEND_ENV = "APIFUSE__STT__BACKEND";
|
|
19
19
|
export const APIFUSE__STT__MODEL_ENV = "APIFUSE__STT__MODEL";
|
|
20
20
|
export const CLOUDFLARE_ACCOUNT_ID_ENV = "APIFUSE__CLOUDFLARE__ACCOUNT_ID";
|
|
21
|
-
export const APIFUSE__STT__CLOUDFLARE_API_TOKEN_ENV =
|
|
22
|
-
"APIFUSE__STT__CLOUDFLARE_API_TOKEN";
|
|
21
|
+
export const APIFUSE__STT__CLOUDFLARE_API_TOKEN_ENV = "APIFUSE__STT__CLOUDFLARE_API_TOKEN";
|
|
23
22
|
export const CLOUDFLARE_WORKERS_AI_STT_BACKEND = "cloudflare-workers-ai";
|
|
24
|
-
export const DEFAULT_CLOUDFLARE_WORKERS_AI_STT_MODEL =
|
|
25
|
-
"@cf/openai/whisper-large-v3-turbo";
|
|
23
|
+
export const DEFAULT_CLOUDFLARE_WORKERS_AI_STT_MODEL = "@cf/openai/whisper-large-v3-turbo";
|
|
26
24
|
export const DEFAULT_STT_MAX_AUDIO_BYTES = 10 * 1024 * 1024;
|
|
27
25
|
export const DEFAULT_STT_TIMEOUT_MS = 30_000;
|
|
28
26
|
|
|
@@ -45,10 +43,7 @@ type ErrorSttClientOptions = {
|
|
|
45
43
|
fix?: string;
|
|
46
44
|
};
|
|
47
45
|
|
|
48
|
-
function providerError(
|
|
49
|
-
message: string,
|
|
50
|
-
options: { code: string; fix?: string },
|
|
51
|
-
): ProviderError {
|
|
46
|
+
function providerError(message: string, options: { code: string; fix?: string }): ProviderError {
|
|
52
47
|
return new ProviderError(message, options);
|
|
53
48
|
}
|
|
54
49
|
|
|
@@ -82,9 +77,7 @@ export function createSttClientFromEnv(
|
|
|
82
77
|
env: EnvLike = process.env,
|
|
83
78
|
): SttContext {
|
|
84
79
|
if (!config) {
|
|
85
|
-
return createUnsupportedSttClient(
|
|
86
|
-
"Provider does not declare STT capability",
|
|
87
|
-
);
|
|
80
|
+
return createUnsupportedSttClient("Provider does not declare STT capability");
|
|
88
81
|
}
|
|
89
82
|
|
|
90
83
|
const backend = normalizedEnvValue(env, APIFUSE__STT__BACKEND_ENV);
|
|
@@ -116,25 +109,17 @@ export function createSttClientFromEnv(
|
|
|
116
109
|
accountId,
|
|
117
110
|
apiToken,
|
|
118
111
|
model:
|
|
119
|
-
normalizedEnvValue(env, APIFUSE__STT__MODEL_ENV) ??
|
|
120
|
-
DEFAULT_CLOUDFLARE_WORKERS_AI_STT_MODEL,
|
|
112
|
+
normalizedEnvValue(env, APIFUSE__STT__MODEL_ENV) ?? DEFAULT_CLOUDFLARE_WORKERS_AI_STT_MODEL,
|
|
121
113
|
});
|
|
122
114
|
}
|
|
123
115
|
|
|
124
116
|
function base64ByteLength(data: string): number {
|
|
125
117
|
const normalized = data.trim();
|
|
126
|
-
const padding = normalized.endsWith("==")
|
|
127
|
-
? 2
|
|
128
|
-
: normalized.endsWith("=")
|
|
129
|
-
? 1
|
|
130
|
-
: 0;
|
|
118
|
+
const padding = normalized.endsWith("==") ? 2 : normalized.endsWith("=") ? 1 : 0;
|
|
131
119
|
return Math.floor((normalized.length * 3) / 4) - padding;
|
|
132
120
|
}
|
|
133
121
|
|
|
134
|
-
function assertBase64Audio(
|
|
135
|
-
audio: SttAudioInput,
|
|
136
|
-
maxAudioBytes: number | undefined,
|
|
137
|
-
): number {
|
|
122
|
+
function assertBase64Audio(audio: SttAudioInput, maxAudioBytes: number | undefined): number {
|
|
138
123
|
if (audio.kind !== "base64") {
|
|
139
124
|
throw new ValidationError("Unsupported STT audio input kind", {
|
|
140
125
|
code: "UNSUPPORTED_STT_OPTION",
|
|
@@ -142,32 +127,22 @@ function assertBase64Audio(
|
|
|
142
127
|
});
|
|
143
128
|
}
|
|
144
129
|
const data = audio.data.trim();
|
|
145
|
-
if (
|
|
146
|
-
data
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
) {
|
|
150
|
-
throw new ValidationError(
|
|
151
|
-
"STT audio.data must be a base64-encoded string",
|
|
152
|
-
{
|
|
153
|
-
code: "INVALID_STT_AUDIO",
|
|
154
|
-
},
|
|
155
|
-
);
|
|
130
|
+
if (data.length === 0 || data.length % 4 !== 0 || !BASE64_AUDIO_PATTERN.test(data)) {
|
|
131
|
+
throw new ValidationError("STT audio.data must be a base64-encoded string", {
|
|
132
|
+
code: "INVALID_STT_AUDIO",
|
|
133
|
+
});
|
|
156
134
|
}
|
|
157
135
|
const bytes = base64ByteLength(data);
|
|
158
136
|
const maxBytes = maxAudioBytes ?? DEFAULT_STT_MAX_AUDIO_BYTES;
|
|
159
137
|
if (bytes > maxBytes) {
|
|
160
|
-
throw new ValidationError(
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
);
|
|
138
|
+
throw new ValidationError(`STT audio exceeds maxAudioBytes (${bytes} > ${maxBytes})`, {
|
|
139
|
+
code: "STT_AUDIO_TOO_LARGE",
|
|
140
|
+
});
|
|
164
141
|
}
|
|
165
142
|
return bytes;
|
|
166
143
|
}
|
|
167
144
|
|
|
168
|
-
export function resolveSttPrompt(
|
|
169
|
-
request: SttTranscribeRequest,
|
|
170
|
-
): string | undefined {
|
|
145
|
+
export function resolveSttPrompt(request: SttTranscribeRequest): string | undefined {
|
|
171
146
|
const policy = effectivePromptPolicy(request);
|
|
172
147
|
if (policy === "none") return undefined;
|
|
173
148
|
if (policy === "default-hint") return DEFAULT_OTP_HINT;
|
|
@@ -183,17 +158,14 @@ function warnOrThrowUnsupportedOption(
|
|
|
183
158
|
request: SttTranscribeRequest,
|
|
184
159
|
message: string,
|
|
185
160
|
): { code: "UNSUPPORTED_STT_OPTION"; message: string } | undefined {
|
|
186
|
-
const policy: SttUnsupportedOptionPolicy =
|
|
187
|
-
request.unsupportedOptionPolicy ?? "warn";
|
|
161
|
+
const policy: SttUnsupportedOptionPolicy = request.unsupportedOptionPolicy ?? "warn";
|
|
188
162
|
if (policy === "error") {
|
|
189
163
|
throw new ProviderError(message, { code: "UNSUPPORTED_STT_OPTION" });
|
|
190
164
|
}
|
|
191
165
|
return { code: "UNSUPPORTED_STT_OPTION", message };
|
|
192
166
|
}
|
|
193
167
|
|
|
194
|
-
function normalizeCloudflareLanguage(
|
|
195
|
-
language: Bcp47Locale | undefined,
|
|
196
|
-
): string | undefined {
|
|
168
|
+
function normalizeCloudflareLanguage(language: Bcp47Locale | undefined): string | undefined {
|
|
197
169
|
return language?.split("-")[0]?.toLowerCase();
|
|
198
170
|
}
|
|
199
171
|
|
|
@@ -232,9 +204,7 @@ function createTimeoutController(signalTimeoutMs: number): {
|
|
|
232
204
|
return { controller, clear: () => clearTimeout(timeout) };
|
|
233
205
|
}
|
|
234
206
|
|
|
235
|
-
function toCloudflareInput(
|
|
236
|
-
request: SttTranscribeRequest,
|
|
237
|
-
): Record<string, unknown> {
|
|
207
|
+
function toCloudflareInput(request: SttTranscribeRequest): Record<string, unknown> {
|
|
238
208
|
const prompt = resolveSttPrompt(request);
|
|
239
209
|
const input: Record<string, unknown> = {
|
|
240
210
|
audio: request.audio.data.trim(),
|
|
@@ -273,8 +243,7 @@ function parseSegments(value: unknown): SttSegment[] | undefined {
|
|
|
273
243
|
: typeof segment.endMs === "number"
|
|
274
244
|
? segment.endMs
|
|
275
245
|
: undefined,
|
|
276
|
-
confidence:
|
|
277
|
-
typeof segment.confidence === "number" ? segment.confidence : undefined,
|
|
246
|
+
confidence: typeof segment.confidence === "number" ? segment.confidence : undefined,
|
|
278
247
|
});
|
|
279
248
|
}
|
|
280
249
|
return segments.length > 0 ? segments : undefined;
|
|
@@ -288,13 +257,10 @@ function toSttTranscript(payload: unknown, audioBytes: number): SttTranscript {
|
|
|
288
257
|
(typeof result?.text === "string" ? result.text : undefined) ??
|
|
289
258
|
(typeof info?.text === "string" ? info.text : undefined);
|
|
290
259
|
if (!text) {
|
|
291
|
-
throw new TransportError(
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
status: 502,
|
|
296
|
-
},
|
|
297
|
-
);
|
|
260
|
+
throw new TransportError("STT upstream response did not include transcript text", {
|
|
261
|
+
code: "STT_UPSTREAM_FAILED",
|
|
262
|
+
status: 502,
|
|
263
|
+
});
|
|
298
264
|
}
|
|
299
265
|
const durationMs =
|
|
300
266
|
typeof result?.durationMs === "number"
|
|
@@ -327,23 +293,15 @@ export function createCloudflareWorkersAiSttClient(
|
|
|
327
293
|
return {
|
|
328
294
|
async transcribe(request) {
|
|
329
295
|
const warnings = [];
|
|
330
|
-
if (
|
|
331
|
-
request.initialPrompt &&
|
|
332
|
-
effectivePromptPolicy(request) !== "custom-hint"
|
|
333
|
-
) {
|
|
296
|
+
if (request.initialPrompt && effectivePromptPolicy(request) !== "custom-hint") {
|
|
334
297
|
const warning = warnOrThrowUnsupportedOption(
|
|
335
298
|
request,
|
|
336
299
|
"initialPrompt is honored only when promptPolicy is custom-hint",
|
|
337
300
|
);
|
|
338
301
|
if (warning) warnings.push(warning);
|
|
339
302
|
}
|
|
340
|
-
const audioBytes = assertBase64Audio(
|
|
341
|
-
|
|
342
|
-
request.maxAudioBytes,
|
|
343
|
-
);
|
|
344
|
-
const timeout = createTimeoutController(
|
|
345
|
-
request.timeoutMs ?? DEFAULT_STT_TIMEOUT_MS,
|
|
346
|
-
);
|
|
303
|
+
const audioBytes = assertBase64Audio(request.audio, request.maxAudioBytes);
|
|
304
|
+
const timeout = createTimeoutController(request.timeoutMs ?? DEFAULT_STT_TIMEOUT_MS);
|
|
347
305
|
try {
|
|
348
306
|
let response: Response;
|
|
349
307
|
try {
|
|
@@ -388,10 +346,7 @@ export function createCloudflareWorkersAiSttClient(
|
|
|
388
346
|
if (request.mode === "otp" || request.verificationCode) {
|
|
389
347
|
return {
|
|
390
348
|
...withWarnings,
|
|
391
|
-
verificationCode: extractVerificationCode(
|
|
392
|
-
withWarnings.text,
|
|
393
|
-
request.verificationCode,
|
|
394
|
-
),
|
|
349
|
+
verificationCode: extractVerificationCode(withWarnings.text, request.verificationCode),
|
|
395
350
|
};
|
|
396
351
|
}
|
|
397
352
|
return withWarnings;
|
|
@@ -433,12 +388,9 @@ const KO_DIGITS: Record<string, string> = {
|
|
|
433
388
|
구: "9",
|
|
434
389
|
};
|
|
435
390
|
|
|
436
|
-
function lengthSet(
|
|
437
|
-
codeLengths: SttVerificationCodeOptions["codeLengths"],
|
|
438
|
-
): Set<number> {
|
|
391
|
+
function lengthSet(codeLengths: SttVerificationCodeOptions["codeLengths"]): Set<number> {
|
|
439
392
|
if (codeLengths === undefined) return new Set([4, 5, 6, 7, 8]);
|
|
440
|
-
if (typeof codeLengths === "number")
|
|
441
|
-
return new Set([validCodeLength(codeLengths)]);
|
|
393
|
+
if (typeof codeLengths === "number") return new Set([validCodeLength(codeLengths)]);
|
|
442
394
|
if (Array.isArray(codeLengths)) {
|
|
443
395
|
const values = codeLengths.map((length) => validCodeLength(length));
|
|
444
396
|
return new Set(values);
|
|
@@ -469,10 +421,9 @@ function lengthSet(
|
|
|
469
421
|
|
|
470
422
|
function validCodeLength(value: number): number {
|
|
471
423
|
if (!Number.isInteger(value) || value < 1 || value > 32) {
|
|
472
|
-
throw new ValidationError(
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
);
|
|
424
|
+
throw new ValidationError("STT verification code length must be an integer between 1 and 32", {
|
|
425
|
+
code: "INVALID_STT_VERIFICATION_CODE_OPTIONS",
|
|
426
|
+
});
|
|
476
427
|
}
|
|
477
428
|
return value;
|
|
478
429
|
}
|
|
@@ -554,11 +505,7 @@ function tokenizeDigits(text: string): DigitToken[] {
|
|
|
554
505
|
return tokens.sort((a, b) => a.startIndex - b.startIndex);
|
|
555
506
|
}
|
|
556
507
|
|
|
557
|
-
function isAdjacent(
|
|
558
|
-
left: DigitToken,
|
|
559
|
-
right: DigitToken,
|
|
560
|
-
text: string,
|
|
561
|
-
): boolean {
|
|
508
|
+
function isAdjacent(left: DigitToken, right: DigitToken, text: string): boolean {
|
|
562
509
|
const between = text.slice(left.endIndex, right.startIndex);
|
|
563
510
|
return /^[\s,.:;\-_/]*$/u.test(between);
|
|
564
511
|
}
|
|
@@ -608,12 +555,9 @@ export function extractVerificationCode(
|
|
|
608
555
|
}
|
|
609
556
|
const candidates = [...candidatesByCode.values()];
|
|
610
557
|
if (candidates.length === 0) {
|
|
611
|
-
throw new ProviderError(
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
code: "NO_CODE_FOUND",
|
|
615
|
-
},
|
|
616
|
-
);
|
|
558
|
+
throw new ProviderError("No verification code candidate found in transcript", {
|
|
559
|
+
code: "NO_CODE_FOUND",
|
|
560
|
+
});
|
|
617
561
|
}
|
|
618
562
|
if (candidates.length > 1) {
|
|
619
563
|
throw new ProviderError("Multiple verification code candidates found", {
|
package/src/runtime/trace.ts
CHANGED
|
@@ -5,8 +5,8 @@ import type {
|
|
|
5
5
|
TraceAttributeValue,
|
|
6
6
|
TraceConfig,
|
|
7
7
|
TraceSpan,
|
|
8
|
-
} from "../types";
|
|
9
|
-
import { exportSpansOTLP, type OTLPExportOptions } from "./otlp";
|
|
8
|
+
} from "../types.js";
|
|
9
|
+
import { exportSpansOTLP, type OTLPExportOptions } from "./otlp.js";
|
|
10
10
|
|
|
11
11
|
export type SpanAttributeValue = TraceAttributeValue;
|
|
12
12
|
|
|
@@ -44,16 +44,10 @@ type CompletedSpanEntry = {
|
|
|
44
44
|
};
|
|
45
45
|
|
|
46
46
|
export interface TraceRecorder {
|
|
47
|
-
runSpan<T>(
|
|
48
|
-
name: string,
|
|
49
|
-
fn: () => Promise<T> | T,
|
|
50
|
-
options?: SpanHookOptions<T>,
|
|
51
|
-
): Promise<T>;
|
|
47
|
+
runSpan<T>(name: string, fn: () => Promise<T> | T, options?: SpanHookOptions<T>): Promise<T>;
|
|
52
48
|
}
|
|
53
49
|
|
|
54
|
-
export const TRACE_RECORDER = Symbol.for(
|
|
55
|
-
"@apifuse/provider-sdk/runtime/trace-recorder",
|
|
56
|
-
);
|
|
50
|
+
export const TRACE_RECORDER = Symbol.for("@apifuse/provider-sdk/runtime/trace-recorder");
|
|
57
51
|
|
|
58
52
|
type InternalTraceContext = TraceContext & {
|
|
59
53
|
[TRACE_RECORDER]: TraceRecorder;
|
|
@@ -61,9 +55,7 @@ type InternalTraceContext = TraceContext & {
|
|
|
61
55
|
_resourceAttributes?: Record<string, string>;
|
|
62
56
|
};
|
|
63
57
|
|
|
64
|
-
function buildOTLPExportOptions(
|
|
65
|
-
config?: TraceConfig,
|
|
66
|
-
): OTLPExportOptions | undefined {
|
|
58
|
+
function buildOTLPExportOptions(config?: TraceConfig): OTLPExportOptions | undefined {
|
|
67
59
|
if (config?.exporter !== "otlp") {
|
|
68
60
|
return undefined;
|
|
69
61
|
}
|
|
@@ -80,9 +72,7 @@ function buildOTLPExportOptions(
|
|
|
80
72
|
};
|
|
81
73
|
}
|
|
82
74
|
|
|
83
|
-
export function resolveTraceContextOptions(
|
|
84
|
-
config?: TraceConfig,
|
|
85
|
-
): CreateTraceContextOptions {
|
|
75
|
+
export function resolveTraceContextOptions(config?: TraceConfig): CreateTraceContextOptions {
|
|
86
76
|
return {
|
|
87
77
|
maxSpans: config?.maxSpans,
|
|
88
78
|
onSpan: config?.onSpan,
|
|
@@ -90,14 +80,8 @@ export function resolveTraceContextOptions(
|
|
|
90
80
|
};
|
|
91
81
|
}
|
|
92
82
|
|
|
93
|
-
function normalizeAttributeValue(
|
|
94
|
-
value
|
|
95
|
-
): SpanAttributeValue | undefined {
|
|
96
|
-
if (
|
|
97
|
-
typeof value === "string" ||
|
|
98
|
-
typeof value === "number" ||
|
|
99
|
-
typeof value === "boolean"
|
|
100
|
-
) {
|
|
83
|
+
function normalizeAttributeValue(value: unknown): SpanAttributeValue | undefined {
|
|
84
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
101
85
|
return value;
|
|
102
86
|
}
|
|
103
87
|
|
|
@@ -117,10 +101,7 @@ function normalizeAttributes(
|
|
|
117
101
|
|
|
118
102
|
const normalizedEntries = Object.entries(attributes)
|
|
119
103
|
.map(([key, value]) => [key, normalizeAttributeValue(value)] as const)
|
|
120
|
-
.filter(
|
|
121
|
-
(entry): entry is readonly [string, SpanAttributeValue] =>
|
|
122
|
-
entry[1] !== undefined,
|
|
123
|
-
);
|
|
104
|
+
.filter((entry): entry is readonly [string, SpanAttributeValue] => entry[1] !== undefined);
|
|
124
105
|
|
|
125
106
|
return Object.fromEntries(normalizedEntries);
|
|
126
107
|
}
|
|
@@ -130,9 +111,7 @@ function insertCompletedSpan(
|
|
|
130
111
|
entry: CompletedSpanEntry,
|
|
131
112
|
maxSpans: number,
|
|
132
113
|
): void {
|
|
133
|
-
const insertAt = completed.findIndex(
|
|
134
|
-
(existingEntry) => existingEntry.sequence > entry.sequence,
|
|
135
|
-
);
|
|
114
|
+
const insertAt = completed.findIndex((existingEntry) => existingEntry.sequence > entry.sequence);
|
|
136
115
|
|
|
137
116
|
if (insertAt === -1) {
|
|
138
117
|
completed.push(entry);
|
|
@@ -145,15 +124,11 @@ function insertCompletedSpan(
|
|
|
145
124
|
}
|
|
146
125
|
}
|
|
147
126
|
|
|
148
|
-
export function getTraceRecorder(
|
|
149
|
-
trace: BaseTraceContext,
|
|
150
|
-
): TraceRecorder | null {
|
|
127
|
+
export function getTraceRecorder(trace: BaseTraceContext): TraceRecorder | null {
|
|
151
128
|
return (trace as Partial<InternalTraceContext>)[TRACE_RECORDER] ?? null;
|
|
152
129
|
}
|
|
153
130
|
|
|
154
|
-
export function createTraceContext(
|
|
155
|
-
options: CreateTraceContextOptions = {},
|
|
156
|
-
): TraceContext {
|
|
131
|
+
export function createTraceContext(options: CreateTraceContextOptions = {}): TraceContext {
|
|
157
132
|
const maxSpans = options.maxSpans ?? 1000;
|
|
158
133
|
const completed: CompletedSpanEntry[] = [];
|
|
159
134
|
const activeSpanStorage = new AsyncLocalStorage<PendingSpan | undefined>();
|
|
@@ -214,11 +189,7 @@ export function createTraceContext(
|
|
|
214
189
|
...(pendingSpan.parentId ? { parentId: pendingSpan.parentId } : {}),
|
|
215
190
|
};
|
|
216
191
|
|
|
217
|
-
insertCompletedSpan(
|
|
218
|
-
completed,
|
|
219
|
-
{ sequence: pendingSpan.sequence, span },
|
|
220
|
-
maxSpans,
|
|
221
|
-
);
|
|
192
|
+
insertCompletedSpan(completed, { sequence: pendingSpan.sequence, span }, maxSpans);
|
|
222
193
|
options.onSpan?.(span);
|
|
223
194
|
|
|
224
195
|
if (!pendingSpan.parentId) {
|
|
@@ -233,8 +204,7 @@ export function createTraceContext(
|
|
|
233
204
|
finalize("ok", successAttributes ?? undefined);
|
|
234
205
|
return value;
|
|
235
206
|
} catch (error) {
|
|
236
|
-
const errorMessage =
|
|
237
|
-
error instanceof Error ? error.message : String(error);
|
|
207
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
238
208
|
const errorAttributes = spanOptions.onError?.(error);
|
|
239
209
|
finalize("error", errorAttributes ?? undefined, errorMessage);
|
|
240
210
|
throw error;
|
package/src/runtime/waterfall.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Span } from "./trace";
|
|
1
|
+
import type { Span } from "./trace.js";
|
|
2
2
|
|
|
3
3
|
export type WaterfallRequest = {
|
|
4
4
|
method: string;
|
|
@@ -52,11 +52,7 @@ export function renderWaterfall(
|
|
|
52
52
|
|
|
53
53
|
for (const node of tree) {
|
|
54
54
|
const operationDuration = formatDuration(node.span.duration_ms);
|
|
55
|
-
const operationBar = renderBar(
|
|
56
|
-
node.span.duration_ms,
|
|
57
|
-
totalDuration,
|
|
58
|
-
maxBarWidth,
|
|
59
|
-
);
|
|
55
|
+
const operationBar = renderBar(node.span.duration_ms, totalDuration, maxBarWidth);
|
|
60
56
|
const operationColor = spanColor(node.span, slowThresholdMs);
|
|
61
57
|
|
|
62
58
|
lines.push(
|
|
@@ -104,14 +100,9 @@ function renderChildren(
|
|
|
104
100
|
const provider = isLast ? "└─" : "├─";
|
|
105
101
|
|
|
106
102
|
const duration = formatDuration(node.span.duration_ms);
|
|
107
|
-
const bar = renderBar(
|
|
108
|
-
node.span.duration_ms,
|
|
109
|
-
ctx.totalDuration,
|
|
110
|
-
ctx.maxBarWidth,
|
|
111
|
-
);
|
|
103
|
+
const bar = renderBar(node.span.duration_ms, ctx.totalDuration, ctx.maxBarWidth);
|
|
112
104
|
const color = spanColor(node.span, ctx.slowThresholdMs);
|
|
113
|
-
const star =
|
|
114
|
-
node.span.id === ctx.bottleneckId ? ` ${ANSI.yellow}★${ANSI.reset}` : "";
|
|
105
|
+
const star = node.span.id === ctx.bottleneckId ? ` ${ANSI.yellow}★${ANSI.reset}` : "";
|
|
115
106
|
const barSuffix = bar ? ` ${bar}` : "";
|
|
116
107
|
|
|
117
108
|
const nameWidth = 20;
|
|
@@ -190,11 +181,7 @@ function findBottleneck(roots: SpanNode[]): string | null {
|
|
|
190
181
|
return longest.id;
|
|
191
182
|
}
|
|
192
183
|
|
|
193
|
-
function renderBar(
|
|
194
|
-
durationMs: number,
|
|
195
|
-
totalMs: number,
|
|
196
|
-
maxWidth: number,
|
|
197
|
-
): string {
|
|
184
|
+
function renderBar(durationMs: number, totalMs: number, maxWidth: number): string {
|
|
198
185
|
if (totalMs <= 0 || durationMs <= 0) {
|
|
199
186
|
return "";
|
|
200
187
|
}
|
package/src/schema.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { type ZodString, type ZodType, z } from "zod";
|
|
2
2
|
|
|
3
|
-
import { ValidationError } from "./errors";
|
|
4
|
-
import { providerLocaleKey } from "./i18n/keys";
|
|
3
|
+
import { ValidationError } from "./errors.js";
|
|
4
|
+
import { providerLocaleKey } from "./i18n/keys.js";
|
|
5
5
|
import type {
|
|
6
6
|
InferSchemaOutput,
|
|
7
7
|
ProviderLocaleKey,
|
|
8
8
|
SchemaLike,
|
|
9
9
|
StandardSchemaV1,
|
|
10
|
-
} from "./types";
|
|
10
|
+
} from "./types.js";
|
|
11
11
|
|
|
12
12
|
export { z };
|
|
13
13
|
|
|
@@ -25,15 +25,11 @@ function isFailureResult<Output>(
|
|
|
25
25
|
return "issues" in result;
|
|
26
26
|
}
|
|
27
27
|
function isPromiseResult<Output>(
|
|
28
|
-
result:
|
|
29
|
-
| StandardSchemaV1.Result<Output>
|
|
30
|
-
| Promise<StandardSchemaV1.Result<Output>>,
|
|
28
|
+
result: StandardSchemaV1.Result<Output> | Promise<StandardSchemaV1.Result<Output>>,
|
|
31
29
|
): result is Promise<StandardSchemaV1.Result<Output>> {
|
|
32
30
|
return result instanceof Promise;
|
|
33
31
|
}
|
|
34
|
-
function formatStandardSchemaIssues(
|
|
35
|
-
issues: readonly StandardSchemaV1.Issue[],
|
|
36
|
-
): string {
|
|
32
|
+
function formatStandardSchemaIssues(issues: readonly StandardSchemaV1.Issue[]): string {
|
|
37
33
|
return issues.map((issue) => issue.message).join("; ");
|
|
38
34
|
}
|
|
39
35
|
export function parseSchema<TSchema extends SchemaLike>(
|
|
@@ -46,8 +42,7 @@ export async function parseSchema(
|
|
|
46
42
|
value: unknown,
|
|
47
43
|
fieldPath: string,
|
|
48
44
|
): Promise<unknown> {
|
|
49
|
-
if ("parse" in schema && typeof schema.parse === "function")
|
|
50
|
-
return schema.parse(value);
|
|
45
|
+
if ("parse" in schema && typeof schema.parse === "function") return schema.parse(value);
|
|
51
46
|
const result = schema["~standard"].validate(value);
|
|
52
47
|
const resolved = isPromiseResult(result) ? await result : result;
|
|
53
48
|
if (isFailureResult(resolved))
|
|
@@ -78,8 +73,7 @@ export function safeParseSchemaSync(
|
|
|
78
73
|
`Schema validation for ${fieldPath} returned a Promise. defineProvider fixture validation requires synchronous Standard Schema validation.`,
|
|
79
74
|
),
|
|
80
75
|
};
|
|
81
|
-
if (isFailureResult(result))
|
|
82
|
-
return { success: false, error: result.issues };
|
|
76
|
+
if (isFailureResult(result)) return { success: false, error: result.issues };
|
|
83
77
|
return { success: true, data: result.value };
|
|
84
78
|
} catch (error) {
|
|
85
79
|
return { success: false, error };
|
|
@@ -147,9 +141,7 @@ const describeKeyMethod = function <TSchema extends ZodType>(
|
|
|
147
141
|
};
|
|
148
142
|
|
|
149
143
|
function installDescribeKeyOnPrototype(prototype: unknown): void {
|
|
150
|
-
const target = prototype as
|
|
151
|
-
| (Record<string, unknown> & { describeKey?: unknown })
|
|
152
|
-
| null;
|
|
144
|
+
const target = prototype as (Record<string, unknown> & { describeKey?: unknown }) | null;
|
|
153
145
|
if (!target || typeof target.describeKey === "function") {
|
|
154
146
|
return;
|
|
155
147
|
}
|
|
@@ -201,12 +193,8 @@ export function field<TSchema extends ZodType>(
|
|
|
201
193
|
const metadata = described.meta() ?? {};
|
|
202
194
|
return described.meta({
|
|
203
195
|
...metadata,
|
|
204
|
-
...((options.sensitive ?? true)
|
|
205
|
-
|
|
206
|
-
: {}),
|
|
207
|
-
...(options.kind
|
|
208
|
-
? { [APIFUSE_SENSITIVE_KIND_META_KEY]: options.kind }
|
|
209
|
-
: {}),
|
|
196
|
+
...((options.sensitive ?? true) ? { [APIFUSE_SENSITIVE_META_KEY]: true } : {}),
|
|
197
|
+
...(options.kind ? { [APIFUSE_SENSITIVE_KIND_META_KEY]: options.kind } : {}),
|
|
210
198
|
});
|
|
211
199
|
}
|
|
212
200
|
|
|
@@ -222,10 +210,7 @@ function sensitiveString(
|
|
|
222
210
|
description: string,
|
|
223
211
|
options: { description?: string; minLength?: number } = {},
|
|
224
212
|
): ZodString {
|
|
225
|
-
const schema =
|
|
226
|
-
options.minLength === undefined
|
|
227
|
-
? z.string()
|
|
228
|
-
: z.string().min(options.minLength);
|
|
213
|
+
const schema = options.minLength === undefined ? z.string() : z.string().min(options.minLength);
|
|
229
214
|
return field(schema, {
|
|
230
215
|
kind,
|
|
231
216
|
description: options.description ?? description,
|
|
@@ -234,33 +219,17 @@ function sensitiveString(
|
|
|
234
219
|
|
|
235
220
|
export const fields = {
|
|
236
221
|
apiKey: (options?: { description?: string }) =>
|
|
237
|
-
sensitiveString(
|
|
238
|
-
"api_key",
|
|
239
|
-
"Provider API key or credential secret.",
|
|
240
|
-
options,
|
|
241
|
-
),
|
|
222
|
+
sensitiveString("api_key", "Provider API key or credential secret.", options),
|
|
242
223
|
authorization: (options?: { description?: string }) =>
|
|
243
|
-
sensitiveString(
|
|
244
|
-
"authorization",
|
|
245
|
-
"Authorization header value or bearer credential.",
|
|
246
|
-
options,
|
|
247
|
-
),
|
|
224
|
+
sensitiveString("authorization", "Authorization header value or bearer credential.", options),
|
|
248
225
|
cookie: (options?: { description?: string }) =>
|
|
249
|
-
sensitiveString(
|
|
250
|
-
"cookie",
|
|
251
|
-
"Cookie header or browser session secret.",
|
|
252
|
-
options,
|
|
253
|
-
),
|
|
226
|
+
sensitiveString("cookie", "Cookie header or browser session secret.", options),
|
|
254
227
|
otp: (options?: { description?: string }) =>
|
|
255
228
|
sensitiveString("otp", "One-time verification code.", options),
|
|
256
229
|
password: (options?: { description?: string; minLength?: number }) =>
|
|
257
230
|
sensitiveString("password", "Password credential.", options),
|
|
258
231
|
paymentUrl: (options?: { description?: string }) =>
|
|
259
|
-
sensitiveString(
|
|
260
|
-
"payment_url",
|
|
261
|
-
"Sensitive payment or checkout URL.",
|
|
262
|
-
options,
|
|
263
|
-
),
|
|
232
|
+
sensitiveString("payment_url", "Sensitive payment or checkout URL.", options),
|
|
264
233
|
phone: (options?: { description?: string }) =>
|
|
265
234
|
sensitiveString("phone", "Phone number or phone-based identity.", options),
|
|
266
235
|
secret: (options?: { description?: string }) =>
|
|
@@ -271,10 +240,7 @@ export const fields = {
|
|
|
271
240
|
|
|
272
241
|
export function isSensitiveSchema(schema: unknown): boolean {
|
|
273
242
|
const metadata = readZodMetadata(schema);
|
|
274
|
-
return (
|
|
275
|
-
metadata !== undefined &&
|
|
276
|
-
Reflect.get(metadata, APIFUSE_SENSITIVE_META_KEY) === true
|
|
277
|
-
);
|
|
243
|
+
return metadata !== undefined && Reflect.get(metadata, APIFUSE_SENSITIVE_META_KEY) === true;
|
|
278
244
|
}
|
|
279
245
|
|
|
280
246
|
export function collectSensitivePaths(schema: unknown): SensitivePath[] {
|
|
@@ -283,10 +249,7 @@ export function collectSensitivePaths(schema: unknown): SensitivePath[] {
|
|
|
283
249
|
return out;
|
|
284
250
|
}
|
|
285
251
|
|
|
286
|
-
export function redactPayload(
|
|
287
|
-
value: unknown,
|
|
288
|
-
paths: readonly SensitivePath[] = [],
|
|
289
|
-
): unknown {
|
|
252
|
+
export function redactPayload(value: unknown, paths: readonly SensitivePath[] = []): unknown {
|
|
290
253
|
return redactValue(value, [], paths);
|
|
291
254
|
}
|
|
292
255
|
|
|
@@ -297,8 +260,7 @@ function collectSensitivePathsInto(
|
|
|
297
260
|
activeSchemas: Set<unknown>,
|
|
298
261
|
emittedPaths: Set<string>,
|
|
299
262
|
): void {
|
|
300
|
-
if (!schema || typeof schema !== "object" || activeSchemas.has(schema))
|
|
301
|
-
return;
|
|
263
|
+
if (!schema || typeof schema !== "object" || activeSchemas.has(schema)) return;
|
|
302
264
|
activeSchemas.add(schema);
|
|
303
265
|
try {
|
|
304
266
|
if (isSensitiveSchema(schema)) pushSensitivePath(out, emittedPaths, path);
|
|
@@ -308,13 +270,7 @@ function collectSensitivePathsInto(
|
|
|
308
270
|
case "object": {
|
|
309
271
|
const shape = readObjectShape(def);
|
|
310
272
|
for (const [key, child] of Object.entries(shape)) {
|
|
311
|
-
collectSensitivePathsInto(
|
|
312
|
-
child,
|
|
313
|
-
[...path, key],
|
|
314
|
-
out,
|
|
315
|
-
activeSchemas,
|
|
316
|
-
emittedPaths,
|
|
317
|
-
);
|
|
273
|
+
collectSensitivePathsInto(child, [...path, key], out, activeSchemas, emittedPaths);
|
|
318
274
|
}
|
|
319
275
|
break;
|
|
320
276
|
}
|
|
@@ -341,20 +297,8 @@ function collectSensitivePathsInto(
|
|
|
341
297
|
);
|
|
342
298
|
break;
|
|
343
299
|
case "pipe":
|
|
344
|
-
collectSensitivePathsInto(
|
|
345
|
-
|
|
346
|
-
path,
|
|
347
|
-
out,
|
|
348
|
-
activeSchemas,
|
|
349
|
-
emittedPaths,
|
|
350
|
-
);
|
|
351
|
-
collectSensitivePathsInto(
|
|
352
|
-
Reflect.get(def, "out"),
|
|
353
|
-
path,
|
|
354
|
-
out,
|
|
355
|
-
activeSchemas,
|
|
356
|
-
emittedPaths,
|
|
357
|
-
);
|
|
300
|
+
collectSensitivePathsInto(Reflect.get(def, "in"), path, out, activeSchemas, emittedPaths);
|
|
301
|
+
collectSensitivePathsInto(Reflect.get(def, "out"), path, out, activeSchemas, emittedPaths);
|
|
358
302
|
break;
|
|
359
303
|
}
|
|
360
304
|
} finally {
|
|
@@ -402,18 +346,13 @@ function pathMatches(
|
|
|
402
346
|
): boolean {
|
|
403
347
|
return patterns.some((pattern) => {
|
|
404
348
|
if (pattern.length !== path.length) return false;
|
|
405
|
-
return pattern.every(
|
|
406
|
-
(segment, index) => segment === "*" || segment === path[index],
|
|
407
|
-
);
|
|
349
|
+
return pattern.every((segment, index) => segment === "*" || segment === path[index]);
|
|
408
350
|
});
|
|
409
351
|
}
|
|
410
352
|
|
|
411
353
|
function isReservedSensitiveKey(key: string): boolean {
|
|
412
354
|
const normalized = key.toLowerCase().replace(/[-_\s]/g, "");
|
|
413
|
-
return (
|
|
414
|
-
RESERVED_SENSITIVE_KEYS.has(normalized) ||
|
|
415
|
-
RESERVED_SENSITIVE_KEYS.has(key.toLowerCase())
|
|
416
|
-
);
|
|
355
|
+
return RESERVED_SENSITIVE_KEYS.has(normalized) || RESERVED_SENSITIVE_KEYS.has(key.toLowerCase());
|
|
417
356
|
}
|
|
418
357
|
|
|
419
358
|
function readZodMetadata(schema: unknown): object | undefined {
|
package/src/serve.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { createServerApp, type ServeOptions, serve } from "./server/serve";
|
|
1
|
+
export { createServerApp, type ServeOptions, serve } from "./server/serve.js";
|