@apifuse/provider-sdk 2.2.0-beta.3 → 2.2.0-beta.5
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 +53 -0
- package/CHANGELOG.md +8 -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 +266 -34
- 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 +104 -3
- package/dist/server/self-test.js +673 -115
- package/dist/server/serve.d.ts +3 -3
- package/dist/server/serve.js +37 -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 +12 -7
- package/src/server/self-test-redaction.ts +2 -2
- package/src/server/self-test.ts +857 -132
- package/src/server/serve.ts +89 -238
- 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/cache.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
|
|
3
|
-
import { providerCacheRedisUrlFromEnv } from "../config/loader";
|
|
3
|
+
import { providerCacheRedisUrlFromEnv } from "../config/loader.js";
|
|
4
4
|
import type {
|
|
5
5
|
ProviderCache,
|
|
6
6
|
ProviderCacheGetOrSetOptions,
|
|
@@ -8,13 +8,13 @@ import type {
|
|
|
8
8
|
ProviderCacheLookupMeta,
|
|
9
9
|
ProviderCacheResponseMeta,
|
|
10
10
|
ProviderCacheResult,
|
|
11
|
-
} from "../types";
|
|
11
|
+
} from "../types.js";
|
|
12
12
|
import {
|
|
13
13
|
createProviderRedisClient,
|
|
14
14
|
ensureRedisReady,
|
|
15
15
|
type ProviderRedisClient,
|
|
16
16
|
withRedisTimeout,
|
|
17
|
-
} from "./redis";
|
|
17
|
+
} from "./redis.js";
|
|
18
18
|
|
|
19
19
|
type CacheSource = ProviderCacheLookupMeta["source"];
|
|
20
20
|
|
|
@@ -123,10 +123,7 @@ function normalizeKeyPart(value: unknown, extra: Set<string>): unknown {
|
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
function stableHash(value: unknown): string {
|
|
126
|
-
return createHash("sha256")
|
|
127
|
-
.update(JSON.stringify(value))
|
|
128
|
-
.digest("hex")
|
|
129
|
-
.slice(0, 32);
|
|
126
|
+
return createHash("sha256").update(JSON.stringify(value)).digest("hex").slice(0, 32);
|
|
130
127
|
}
|
|
131
128
|
|
|
132
129
|
function jitteredTtlMs(ttlMs: number, jitterPct: number | undefined): number {
|
|
@@ -161,10 +158,7 @@ function safeParseEnvelope(raw: string | null): CacheEnvelope | null {
|
|
|
161
158
|
}
|
|
162
159
|
}
|
|
163
160
|
|
|
164
|
-
function resultWithValue<T>(
|
|
165
|
-
value: unknown,
|
|
166
|
-
meta: ProviderCacheLookupMeta,
|
|
167
|
-
): ProviderCacheResult<T> {
|
|
161
|
+
function resultWithValue<T>(value: unknown, meta: ProviderCacheLookupMeta): ProviderCacheResult<T> {
|
|
168
162
|
return {
|
|
169
163
|
value: <T>value,
|
|
170
164
|
meta,
|
|
@@ -187,18 +181,14 @@ function resultFromEnvelope<T>(
|
|
|
187
181
|
});
|
|
188
182
|
}
|
|
189
183
|
|
|
190
|
-
function sourceSummary(
|
|
191
|
-
events: ProviderCacheLookupMeta[],
|
|
192
|
-
): ProviderCacheResponseMeta["source"] {
|
|
184
|
+
function sourceSummary(events: ProviderCacheLookupMeta[]): ProviderCacheResponseMeta["source"] {
|
|
193
185
|
const sources = new Set(events.map((event) => event.source));
|
|
194
186
|
if (sources.size === 0) return undefined;
|
|
195
187
|
if (sources.size === 1) return events[0]?.source;
|
|
196
188
|
return "mixed";
|
|
197
189
|
}
|
|
198
190
|
|
|
199
|
-
async function withRedisFallback<T>(
|
|
200
|
-
operation: () => Promise<T>,
|
|
201
|
-
): Promise<T | undefined> {
|
|
191
|
+
async function withRedisFallback<T>(operation: () => Promise<T>): Promise<T | undefined> {
|
|
202
192
|
return await withRedisTimeout(operation, {
|
|
203
193
|
timeoutMs: DEFAULT_REDIS_TIMEOUT_MS,
|
|
204
194
|
onTimeout: () => undefined,
|
|
@@ -206,15 +196,10 @@ async function withRedisFallback<T>(
|
|
|
206
196
|
});
|
|
207
197
|
}
|
|
208
198
|
|
|
209
|
-
export function createProviderCache(
|
|
210
|
-
options: ProviderCacheOptions,
|
|
211
|
-
): ProviderCache {
|
|
199
|
+
export function createProviderCache(options: ProviderCacheOptions): ProviderCache {
|
|
212
200
|
const redisUrl = options.redisUrl ?? providerCacheRedisUrlFromEnv();
|
|
213
201
|
const backend = getSharedBackend(redisUrl);
|
|
214
|
-
const memoryMaxEntries = Math.max(
|
|
215
|
-
1,
|
|
216
|
-
options.memoryMaxEntries ?? DEFAULT_MEMORY_MAX_ENTRIES,
|
|
217
|
-
);
|
|
202
|
+
const memoryMaxEntries = Math.max(1, options.memoryMaxEntries ?? DEFAULT_MEMORY_MAX_ENTRIES);
|
|
218
203
|
const now = options.now ?? Date.now;
|
|
219
204
|
const events: ProviderCacheLookupMeta[] = [];
|
|
220
205
|
|
|
@@ -238,11 +223,7 @@ export function createProviderCache(
|
|
|
238
223
|
}
|
|
239
224
|
}
|
|
240
225
|
|
|
241
|
-
function rememberEnvelope(
|
|
242
|
-
key: string,
|
|
243
|
-
envelope: CacheEnvelope,
|
|
244
|
-
currentTime: number,
|
|
245
|
-
): void {
|
|
226
|
+
function rememberEnvelope(key: string, envelope: CacheEnvelope, currentTime: number): void {
|
|
246
227
|
sweepMemory(currentTime);
|
|
247
228
|
backend.memory.delete(key);
|
|
248
229
|
backend.memory.set(key, {
|
|
@@ -253,11 +234,7 @@ export function createProviderCache(
|
|
|
253
234
|
enforceMemoryLimit();
|
|
254
235
|
}
|
|
255
236
|
|
|
256
|
-
function touchMemory(
|
|
257
|
-
key: string,
|
|
258
|
-
entry: MemoryEntry,
|
|
259
|
-
currentTime: number,
|
|
260
|
-
): void {
|
|
237
|
+
function touchMemory(key: string, entry: MemoryEntry, currentTime: number): void {
|
|
261
238
|
backend.memory.delete(key);
|
|
262
239
|
backend.memory.set(key, { ...entry, lastAccessedAt: currentTime });
|
|
263
240
|
}
|
|
@@ -298,12 +275,7 @@ export function createProviderCache(
|
|
|
298
275
|
if (memoryEntry.expiresAt <= currentTime) {
|
|
299
276
|
backend.memory.delete(key);
|
|
300
277
|
} else {
|
|
301
|
-
const memoryResult = resultFromEnvelope<T>(
|
|
302
|
-
key,
|
|
303
|
-
memoryEntry,
|
|
304
|
-
currentTime,
|
|
305
|
-
"memory",
|
|
306
|
-
);
|
|
278
|
+
const memoryResult = resultFromEnvelope<T>(key, memoryEntry, currentTime, "memory");
|
|
307
279
|
if (memoryResult && !memoryResult.meta.stale) {
|
|
308
280
|
touchMemory(key, memoryEntry, currentTime);
|
|
309
281
|
return memoryResult;
|
|
@@ -334,10 +306,7 @@ export function createProviderCache(
|
|
|
334
306
|
cacheOptions: ProviderCacheGetOrSetOptions,
|
|
335
307
|
): Promise<void> {
|
|
336
308
|
const currentTime = now();
|
|
337
|
-
const freshTtlMs = jitteredTtlMs(
|
|
338
|
-
cacheOptions.ttlMs,
|
|
339
|
-
cacheOptions.jitterPct,
|
|
340
|
-
);
|
|
309
|
+
const freshTtlMs = jitteredTtlMs(cacheOptions.ttlMs, cacheOptions.jitterPct);
|
|
341
310
|
const staleIfErrorMs = cacheOptions.staleIfErrorMs ?? 0;
|
|
342
311
|
const staleTtlMs = freshTtlMs + staleIfErrorMs;
|
|
343
312
|
const envelope: CacheEnvelope = {
|
|
@@ -352,9 +321,7 @@ export function createProviderCache(
|
|
|
352
321
|
if (!redis || !(await ensureRedisReady(redis, DEFAULT_REDIS_TIMEOUT_MS))) {
|
|
353
322
|
return;
|
|
354
323
|
}
|
|
355
|
-
await withRedisFallback(() =>
|
|
356
|
-
redis.set(key, JSON.stringify(envelope), "PX", staleTtlMs),
|
|
357
|
-
);
|
|
324
|
+
await withRedisFallback(() => redis.set(key, JSON.stringify(envelope), "PX", staleTtlMs));
|
|
358
325
|
}
|
|
359
326
|
|
|
360
327
|
async function loadAndStore<T>(
|
|
@@ -385,16 +352,12 @@ export function createProviderCache(
|
|
|
385
352
|
|
|
386
353
|
return {
|
|
387
354
|
key(namespace, parts, keyOptions?: ProviderCacheKeyOptions) {
|
|
388
|
-
const extra = new Set(
|
|
389
|
-
(keyOptions?.redactFields ?? []).map((field) => field.toLowerCase()),
|
|
390
|
-
);
|
|
355
|
+
const extra = new Set((keyOptions?.redactFields ?? []).map((field) => field.toLowerCase()));
|
|
391
356
|
const normalized = normalizeKeyPart(parts, extra);
|
|
392
357
|
return `${DEFAULT_PREFIX}:${options.providerId}:${namespace}:${stableHash(normalized)}`;
|
|
393
358
|
},
|
|
394
359
|
|
|
395
|
-
async get<T = unknown>(
|
|
396
|
-
key: string,
|
|
397
|
-
): Promise<ProviderCacheResult<T> | null> {
|
|
360
|
+
async get<T = unknown>(key: string): Promise<ProviderCacheResult<T> | null> {
|
|
398
361
|
const result = await read<T>(key);
|
|
399
362
|
if (result) record(result.meta);
|
|
400
363
|
return result;
|
|
@@ -405,10 +368,7 @@ export function createProviderCache(
|
|
|
405
368
|
async delete(key: string): Promise<void> {
|
|
406
369
|
backend.memory.delete(key);
|
|
407
370
|
const redis = backend.redis;
|
|
408
|
-
if (
|
|
409
|
-
!redis ||
|
|
410
|
-
!(await ensureRedisReady(redis, DEFAULT_REDIS_TIMEOUT_MS))
|
|
411
|
-
) {
|
|
371
|
+
if (!redis || !(await ensureRedisReady(redis, DEFAULT_REDIS_TIMEOUT_MS))) {
|
|
412
372
|
return;
|
|
413
373
|
}
|
|
414
374
|
await withRedisFallback(() => redis.del(key));
|
|
@@ -428,10 +388,7 @@ export function createProviderCache(
|
|
|
428
388
|
const existingInflight = backend.inflight.get(key);
|
|
429
389
|
if (existingInflight) {
|
|
430
390
|
const inflightResult = await existingInflight;
|
|
431
|
-
const result = resultWithValue<T>(
|
|
432
|
-
inflightResult.value,
|
|
433
|
-
inflightResult.meta,
|
|
434
|
-
);
|
|
391
|
+
const result = resultWithValue<T>(inflightResult.value, inflightResult.meta);
|
|
435
392
|
record(result.meta);
|
|
436
393
|
return result;
|
|
437
394
|
}
|
|
@@ -470,16 +427,12 @@ export function createBypassProviderCache(
|
|
|
470
427
|
|
|
471
428
|
return {
|
|
472
429
|
key(namespace, parts, keyOptions?: ProviderCacheKeyOptions) {
|
|
473
|
-
const extra = new Set(
|
|
474
|
-
(keyOptions?.redactFields ?? []).map((field) => field.toLowerCase()),
|
|
475
|
-
);
|
|
430
|
+
const extra = new Set((keyOptions?.redactFields ?? []).map((field) => field.toLowerCase()));
|
|
476
431
|
const normalized = normalizeKeyPart(parts, extra);
|
|
477
432
|
return `${DEFAULT_PREFIX}:${options.providerId}:${namespace}:${stableHash(normalized)}`;
|
|
478
433
|
},
|
|
479
434
|
|
|
480
|
-
async get<T = unknown>(
|
|
481
|
-
_key: string,
|
|
482
|
-
): Promise<ProviderCacheResult<T> | null> {
|
|
435
|
+
async get<T = unknown>(_key: string): Promise<ProviderCacheResult<T> | null> {
|
|
483
436
|
return null;
|
|
484
437
|
},
|
|
485
438
|
|
package/src/runtime/choice.ts
CHANGED
|
@@ -10,8 +10,8 @@ import {
|
|
|
10
10
|
assertFreshProviderChoiceIssuedAt,
|
|
11
11
|
ProviderChoiceTokenError,
|
|
12
12
|
type ProviderChoiceTokenPayload,
|
|
13
|
-
} from "../choice-token";
|
|
14
|
-
import { ProviderError } from "../errors";
|
|
13
|
+
} from "../choice-token.js";
|
|
14
|
+
import { ProviderError } from "../errors.js";
|
|
15
15
|
import type {
|
|
16
16
|
CredentialContext,
|
|
17
17
|
EnvContext,
|
|
@@ -23,7 +23,7 @@ import type {
|
|
|
23
23
|
ProviderRequestContext,
|
|
24
24
|
ProviderRuntimeState,
|
|
25
25
|
ProviderStateDurationString,
|
|
26
|
-
} from "../types";
|
|
26
|
+
} from "../types.js";
|
|
27
27
|
|
|
28
28
|
export const PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV =
|
|
29
29
|
"APIFUSE__PROVIDER_RUNTIME__CHOICE_TOKEN_MASTER_SECRET";
|
|
@@ -74,18 +74,12 @@ export function createProviderChoiceContext(
|
|
|
74
74
|
): string;
|
|
75
75
|
function issue<TPayload extends ProviderChoiceTokenPayload>(
|
|
76
76
|
issueOptions: ProviderChoiceIssueOptions<TPayload> & {
|
|
77
|
-
readonly storage: Extract<
|
|
78
|
-
ProviderChoiceStorageOptions,
|
|
79
|
-
{ readonly mode: "server" }
|
|
80
|
-
>;
|
|
77
|
+
readonly storage: Extract<ProviderChoiceStorageOptions, { readonly mode: "server" }>;
|
|
81
78
|
},
|
|
82
79
|
): Promise<string>;
|
|
83
80
|
function issue<TPayload extends ProviderChoiceTokenPayload>(
|
|
84
81
|
issueOptions: ProviderChoiceIssueOptions<TPayload> & {
|
|
85
|
-
readonly storage: Extract<
|
|
86
|
-
ProviderChoiceStorageOptions,
|
|
87
|
-
{ readonly mode: "auto" }
|
|
88
|
-
>;
|
|
82
|
+
readonly storage: Extract<ProviderChoiceStorageOptions, { readonly mode: "auto" }>;
|
|
89
83
|
},
|
|
90
84
|
): string | Promise<string>;
|
|
91
85
|
function issue<TPayload extends ProviderChoiceTokenPayload>(
|
|
@@ -112,10 +106,7 @@ export function createProviderChoiceContext(
|
|
|
112
106
|
required: true,
|
|
113
107
|
}),
|
|
114
108
|
};
|
|
115
|
-
const resolvedStorage = resolveIssueStorage(
|
|
116
|
-
issueOptions.storage,
|
|
117
|
-
issueOptions.payload,
|
|
118
|
-
);
|
|
109
|
+
const resolvedStorage = resolveIssueStorage(issueOptions.storage, issueOptions.payload);
|
|
119
110
|
if (resolvedStorage.mode === "server") {
|
|
120
111
|
return issueServerStoredChoice({
|
|
121
112
|
baseEnvelope,
|
|
@@ -146,31 +137,19 @@ export function createProviderChoiceContext(
|
|
|
146
137
|
): ProviderChoiceTokenPayload;
|
|
147
138
|
function parse(
|
|
148
139
|
parseOptions: ProviderChoiceParseOptions & {
|
|
149
|
-
readonly storage: Extract<
|
|
150
|
-
ProviderChoiceStorageOptions,
|
|
151
|
-
{ readonly mode: "server" }
|
|
152
|
-
>;
|
|
140
|
+
readonly storage: Extract<ProviderChoiceStorageOptions, { readonly mode: "server" }>;
|
|
153
141
|
},
|
|
154
142
|
): Promise<ProviderChoiceTokenPayload>;
|
|
155
143
|
function parse(
|
|
156
144
|
parseOptions: ProviderChoiceParseOptions & {
|
|
157
|
-
readonly storage: Extract<
|
|
158
|
-
ProviderChoiceStorageOptions,
|
|
159
|
-
{ readonly mode: "auto" }
|
|
160
|
-
>;
|
|
145
|
+
readonly storage: Extract<ProviderChoiceStorageOptions, { readonly mode: "auto" }>;
|
|
161
146
|
},
|
|
162
147
|
): ProviderChoiceTokenPayload | Promise<ProviderChoiceTokenPayload>;
|
|
163
148
|
function parse(
|
|
164
149
|
parseOptions: ProviderChoiceParseOptions,
|
|
165
150
|
): ProviderChoiceTokenPayload | Promise<ProviderChoiceTokenPayload> {
|
|
166
|
-
const [
|
|
167
|
-
|
|
168
|
-
tokenKid,
|
|
169
|
-
encodedIv,
|
|
170
|
-
encryptedPayload,
|
|
171
|
-
authTag,
|
|
172
|
-
signature,
|
|
173
|
-
] = parseManagedChoiceTokenParts(parseOptions.token);
|
|
151
|
+
const [actualPrefix, tokenKid, encodedIv, encryptedPayload, authTag, signature] =
|
|
152
|
+
parseManagedChoiceTokenParts(parseOptions.token);
|
|
174
153
|
if (
|
|
175
154
|
actualPrefix !== parseOptions.prefix ||
|
|
176
155
|
tokenKid !== kid ||
|
|
@@ -191,13 +170,9 @@ export function createProviderChoiceContext(
|
|
|
191
170
|
purpose: parseOptions.purpose,
|
|
192
171
|
kid: tokenKid,
|
|
193
172
|
});
|
|
194
|
-
const signedBody = [
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
encodedIv,
|
|
198
|
-
encryptedPayload,
|
|
199
|
-
authTag,
|
|
200
|
-
].join(".");
|
|
173
|
+
const signedBody = [parseOptions.prefix, tokenKid, encodedIv, encryptedPayload, authTag].join(
|
|
174
|
+
".",
|
|
175
|
+
);
|
|
201
176
|
assertManagedChoiceSignature({
|
|
202
177
|
signedBody,
|
|
203
178
|
signature,
|
|
@@ -247,30 +222,23 @@ export function createTestProviderChoiceContext(
|
|
|
247
222
|
return createProviderChoiceContext({
|
|
248
223
|
...options,
|
|
249
224
|
masterSecret:
|
|
250
|
-
options.masterSecret ??
|
|
251
|
-
"apifuse-test-provider-runtime-choice-token-master-secret",
|
|
225
|
+
options.masterSecret ?? "apifuse-test-provider-runtime-choice-token-master-secret",
|
|
252
226
|
});
|
|
253
227
|
}
|
|
254
228
|
|
|
255
|
-
function resolveChoiceMasterSecret(
|
|
256
|
-
options: CreateProviderChoiceContextOptions,
|
|
257
|
-
): string {
|
|
229
|
+
function resolveChoiceMasterSecret(options: CreateProviderChoiceContextOptions): string {
|
|
258
230
|
const configured =
|
|
259
|
-
options.masterSecret ??
|
|
260
|
-
options.env?.get(PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV);
|
|
231
|
+
options.masterSecret ?? options.env?.get(PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV);
|
|
261
232
|
const trimmed = configured?.trim();
|
|
262
233
|
if (trimmed) return trimmed;
|
|
263
|
-
throw new ProviderError(
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
details: {
|
|
270
|
-
secret: PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV,
|
|
271
|
-
},
|
|
234
|
+
throw new ProviderError("Provider runtime choice-token master secret is not configured.", {
|
|
235
|
+
code: "CHOICE_TOKEN_MASTER_SECRET_NOT_CONFIGURED",
|
|
236
|
+
category: "internal_error",
|
|
237
|
+
retryable: false,
|
|
238
|
+
details: {
|
|
239
|
+
secret: PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV,
|
|
272
240
|
},
|
|
273
|
-
);
|
|
241
|
+
});
|
|
274
242
|
}
|
|
275
243
|
|
|
276
244
|
type ManagedChoiceKeyInput = {
|
|
@@ -286,9 +254,7 @@ type ManagedChoiceKeys = {
|
|
|
286
254
|
readonly binding: Buffer;
|
|
287
255
|
};
|
|
288
256
|
|
|
289
|
-
function deriveManagedChoiceKeys(
|
|
290
|
-
input: ManagedChoiceKeyInput,
|
|
291
|
-
): ManagedChoiceKeys {
|
|
257
|
+
function deriveManagedChoiceKeys(input: ManagedChoiceKeyInput): ManagedChoiceKeys {
|
|
292
258
|
return {
|
|
293
259
|
encryption: deriveManagedChoiceKey(input, "encryption"),
|
|
294
260
|
signing: deriveManagedChoiceKey(input, "signing"),
|
|
@@ -327,22 +293,14 @@ function encryptManagedChoiceToken(options: {
|
|
|
327
293
|
]).toString("base64url");
|
|
328
294
|
const authTag = cipher.getAuthTag().toString("base64url");
|
|
329
295
|
const encodedIv = iv.toString("base64url");
|
|
330
|
-
const signedBody = [
|
|
331
|
-
options.prefix,
|
|
332
|
-
options.kid,
|
|
333
|
-
encodedIv,
|
|
334
|
-
encryptedPayload,
|
|
335
|
-
authTag,
|
|
336
|
-
].join(".");
|
|
296
|
+
const signedBody = [options.prefix, options.kid, encodedIv, encryptedPayload, authTag].join(".");
|
|
337
297
|
const signature = createHmac("sha256", options.keys.signing)
|
|
338
298
|
.update(signedBody)
|
|
339
299
|
.digest("base64url");
|
|
340
300
|
return `${signedBody}.${signature}`;
|
|
341
301
|
}
|
|
342
302
|
|
|
343
|
-
async function issueServerStoredChoice<
|
|
344
|
-
TPayload extends ProviderChoiceTokenPayload,
|
|
345
|
-
>(options: {
|
|
303
|
+
async function issueServerStoredChoice<TPayload extends ProviderChoiceTokenPayload>(options: {
|
|
346
304
|
readonly baseEnvelope: Omit<ManagedChoiceEnvelope, "payload">;
|
|
347
305
|
readonly issueOptions: ProviderChoiceIssueOptions<TPayload>;
|
|
348
306
|
readonly storage: ServerProviderChoiceStorageOptions;
|
|
@@ -351,23 +309,18 @@ async function issueServerStoredChoice<
|
|
|
351
309
|
readonly keys: ManagedChoiceKeys;
|
|
352
310
|
readonly issuedAtMs: number;
|
|
353
311
|
}): Promise<string> {
|
|
354
|
-
const serializedPayload = serializeChoicePayload(
|
|
355
|
-
options.issueOptions.payload,
|
|
356
|
-
);
|
|
312
|
+
const serializedPayload = serializeChoicePayload(options.issueOptions.payload);
|
|
357
313
|
const payloadBytes = Buffer.byteLength(serializedPayload, "utf8");
|
|
358
314
|
if (payloadBytes > options.storage.maxValueBytes) {
|
|
359
|
-
throw new ProviderError(
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
maxValueBytes: options.storage.maxValueBytes,
|
|
367
|
-
payloadBytes,
|
|
368
|
-
},
|
|
315
|
+
throw new ProviderError("Provider choice payload exceeds state storage policy.", {
|
|
316
|
+
code: "CHOICE_STATE_PAYLOAD_TOO_LARGE",
|
|
317
|
+
category: "input_validation",
|
|
318
|
+
retryable: false,
|
|
319
|
+
details: {
|
|
320
|
+
maxValueBytes: options.storage.maxValueBytes,
|
|
321
|
+
payloadBytes,
|
|
369
322
|
},
|
|
370
|
-
);
|
|
323
|
+
});
|
|
371
324
|
}
|
|
372
325
|
const stateId = `choice_${randomBytes(16).toString("base64url")}`;
|
|
373
326
|
const digest = digestChoicePayload(serializedPayload);
|
|
@@ -439,10 +392,7 @@ function resolveIssueStorage<TPayload extends ProviderChoiceTokenPayload>(
|
|
|
439
392
|
} {
|
|
440
393
|
if (!storage || storage.mode === "inline") return { mode: "inline" };
|
|
441
394
|
if (storage.mode === "server") return { mode: "server", storage };
|
|
442
|
-
const payloadBytes = Buffer.byteLength(
|
|
443
|
-
serializeChoicePayload(payload),
|
|
444
|
-
"utf8",
|
|
445
|
-
);
|
|
395
|
+
const payloadBytes = Buffer.byteLength(serializeChoicePayload(payload), "utf8");
|
|
446
396
|
if (payloadBytes <= storage.maxInlineBytes) return { mode: "inline" };
|
|
447
397
|
return { mode: "server", storage };
|
|
448
398
|
}
|
|
@@ -536,10 +486,7 @@ function parseManagedChoiceTokenParts(
|
|
|
536
486
|
] {
|
|
537
487
|
const parts = token.split(".");
|
|
538
488
|
if (parts.length !== 6) {
|
|
539
|
-
throw new ProviderChoiceTokenError(
|
|
540
|
-
"invalid_shape",
|
|
541
|
-
"Provider choice token shape is invalid.",
|
|
542
|
-
);
|
|
489
|
+
throw new ProviderChoiceTokenError("invalid_shape", "Provider choice token shape is invalid.");
|
|
543
490
|
}
|
|
544
491
|
return [parts[0], parts[1], parts[2], parts[3], parts[4], parts[5]];
|
|
545
492
|
}
|
|
@@ -601,9 +548,7 @@ function decryptManagedChoiceToken(options: {
|
|
|
601
548
|
}
|
|
602
549
|
}
|
|
603
550
|
|
|
604
|
-
function isManagedChoiceEnvelope(
|
|
605
|
-
value: unknown,
|
|
606
|
-
): value is ManagedChoiceEnvelope {
|
|
551
|
+
function isManagedChoiceEnvelope(value: unknown): value is ManagedChoiceEnvelope {
|
|
607
552
|
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
608
553
|
if (!("payload" in value) || !isChoicePayload(value.payload)) return false;
|
|
609
554
|
return (
|
|
@@ -617,9 +562,7 @@ function isManagedChoiceEnvelope(
|
|
|
617
562
|
typeof value.issued_at_ms === "number" &&
|
|
618
563
|
"ttl_ms" in value &&
|
|
619
564
|
typeof value.ttl_ms === "number" &&
|
|
620
|
-
(!("binding" in value) ||
|
|
621
|
-
value.binding === undefined ||
|
|
622
|
-
isChoiceBinding(value.binding))
|
|
565
|
+
(!("binding" in value) || value.binding === undefined || isChoiceBinding(value.binding))
|
|
623
566
|
);
|
|
624
567
|
}
|
|
625
568
|
|
|
@@ -627,13 +570,10 @@ function isChoicePayload(value: unknown): value is ProviderChoiceTokenPayload {
|
|
|
627
570
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
628
571
|
}
|
|
629
572
|
|
|
630
|
-
function isChoiceBinding(
|
|
631
|
-
value: unknown,
|
|
632
|
-
): value is ManagedChoiceEnvelope["binding"] {
|
|
573
|
+
function isChoiceBinding(value: unknown): value is ManagedChoiceEnvelope["binding"] {
|
|
633
574
|
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
634
575
|
return (
|
|
635
|
-
(!("connection_hash" in value) ||
|
|
636
|
-
typeof value.connection_hash === "string") &&
|
|
576
|
+
(!("connection_hash" in value) || typeof value.connection_hash === "string") &&
|
|
637
577
|
(!("credential_hash" in value) || typeof value.credential_hash === "string")
|
|
638
578
|
);
|
|
639
579
|
}
|
|
@@ -648,10 +588,7 @@ function assertManagedChoiceEnvelope(
|
|
|
648
588
|
readonly futureToleranceMs?: number;
|
|
649
589
|
},
|
|
650
590
|
): void {
|
|
651
|
-
if (
|
|
652
|
-
envelope.provider_id !== options.providerId ||
|
|
653
|
-
envelope.purpose !== options.purpose
|
|
654
|
-
) {
|
|
591
|
+
if (envelope.provider_id !== options.providerId || envelope.purpose !== options.purpose) {
|
|
655
592
|
throw new ProviderChoiceTokenError(
|
|
656
593
|
"invalid_payload",
|
|
657
594
|
"Provider choice token payload is invalid.",
|
|
@@ -660,10 +597,7 @@ function assertManagedChoiceEnvelope(
|
|
|
660
597
|
assertFreshProviderChoiceIssuedAt(envelope.issued_at_ms, {
|
|
661
598
|
// Clamp to the issuer's embedded TTL so a caller-supplied value cannot
|
|
662
599
|
// silently extend token validity past the deadline the issuer intended.
|
|
663
|
-
ttlMs:
|
|
664
|
-
options.ttlMs != null
|
|
665
|
-
? Math.min(options.ttlMs, envelope.ttl_ms)
|
|
666
|
-
: envelope.ttl_ms,
|
|
600
|
+
ttlMs: options.ttlMs != null ? Math.min(options.ttlMs, envelope.ttl_ms) : envelope.ttl_ms,
|
|
667
601
|
nowMs: options.nowMs,
|
|
668
602
|
futureToleranceMs: options.futureToleranceMs,
|
|
669
603
|
});
|
|
@@ -676,9 +610,7 @@ function createChoiceBinding(options: {
|
|
|
676
610
|
readonly credential?: CredentialContext;
|
|
677
611
|
readonly required: boolean;
|
|
678
612
|
}): ManagedChoiceEnvelope["binding"] {
|
|
679
|
-
const connectionHash = options.options?.connection
|
|
680
|
-
? hashRequiredConnection(options)
|
|
681
|
-
: undefined;
|
|
613
|
+
const connectionHash = options.options?.connection ? hashRequiredConnection(options) : undefined;
|
|
682
614
|
const credentialHash = options.options?.credentialKeys?.length
|
|
683
615
|
? hashCredentialKeys(options)
|
|
684
616
|
: undefined;
|
|
@@ -697,14 +629,11 @@ function hashRequiredConnection(options: {
|
|
|
697
629
|
const connectionId = options.request?.connectionId;
|
|
698
630
|
if (!connectionId) {
|
|
699
631
|
if (!options.required) return undefined;
|
|
700
|
-
throw new ProviderError(
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
retryable: false,
|
|
706
|
-
},
|
|
707
|
-
);
|
|
632
|
+
throw new ProviderError("Provider choice tokens require connection context.", {
|
|
633
|
+
code: "CHOICE_CONTEXT_REQUIRED",
|
|
634
|
+
category: "input_validation",
|
|
635
|
+
retryable: false,
|
|
636
|
+
});
|
|
708
637
|
}
|
|
709
638
|
return createHmac("sha256", options.keys.binding)
|
|
710
639
|
.update("connection")
|
|
@@ -722,15 +651,12 @@ function hashCredentialKeys(options: {
|
|
|
722
651
|
const material = credentialKeys.map((key) => {
|
|
723
652
|
const value = options.credential?.get(key);
|
|
724
653
|
if (typeof value !== "string" || value.length === 0) {
|
|
725
|
-
throw new ProviderError(
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
details: { credentialKey: key },
|
|
732
|
-
},
|
|
733
|
-
);
|
|
654
|
+
throw new ProviderError("Provider choice tokens require configured credential binding.", {
|
|
655
|
+
code: "CHOICE_CONTEXT_REQUIRED",
|
|
656
|
+
category: "input_validation",
|
|
657
|
+
retryable: false,
|
|
658
|
+
details: { credentialKey: key },
|
|
659
|
+
});
|
|
734
660
|
}
|
|
735
661
|
return [key, value];
|
|
736
662
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { CredentialModeError } from "../errors";
|
|
2
|
-
import type { AuthMode, CredentialContext } from "../types";
|
|
1
|
+
import { CredentialModeError } from "../errors.js";
|
|
2
|
+
import type { AuthMode, CredentialContext } from "../types.js";
|
|
3
3
|
|
|
4
4
|
export interface CreateCredentialContextOptions {
|
|
5
5
|
allowedKeys?: string[];
|
package/src/runtime/env.ts
CHANGED
package/src/runtime/executor.ts
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
import { isSessionExpiredError, ProviderError, SessionExpiredError } from "../errors";
|
|
2
|
-
import { parseSchema } from "../schema";
|
|
3
|
-
import type { ProviderContext, ProviderDefinition } from "../types";
|
|
1
|
+
import { isSessionExpiredError, ProviderError, SessionExpiredError } from "../errors.js";
|
|
2
|
+
import { parseSchema } from "../schema.js";
|
|
3
|
+
import type { ProviderContext, ProviderDefinition } from "../types.js";
|
|
4
4
|
|
|
5
|
-
export function isStreamingOperation(
|
|
6
|
-
provider: ProviderDefinition,
|
|
7
|
-
operationId: string,
|
|
8
|
-
): boolean {
|
|
5
|
+
export function isStreamingOperation(provider: ProviderDefinition, operationId: string): boolean {
|
|
9
6
|
const kind = provider.operations[operationId]?.transport?.kind ?? "json";
|
|
10
7
|
return kind !== "json";
|
|
11
8
|
}
|
|
@@ -31,13 +28,10 @@ export async function executeOperation(
|
|
|
31
28
|
const operation = provider.operations[operationId];
|
|
32
29
|
|
|
33
30
|
if (!operation) {
|
|
34
|
-
throw new ProviderError(
|
|
35
|
-
|
|
36
|
-
{
|
|
37
|
-
|
|
38
|
-
fix: `Valid operations: ${Object.keys(provider.operations).join(", ")}`,
|
|
39
|
-
},
|
|
40
|
-
);
|
|
31
|
+
throw new ProviderError(`Unknown operation: ${provider.id}/${operationId}`, {
|
|
32
|
+
code: "NOT_FOUND",
|
|
33
|
+
fix: `Valid operations: ${Object.keys(provider.operations).join(", ")}`,
|
|
34
|
+
});
|
|
41
35
|
}
|
|
42
36
|
|
|
43
37
|
const validatedInput = await parseSchema(
|
|
@@ -79,9 +73,5 @@ export async function executeOperation(
|
|
|
79
73
|
return result;
|
|
80
74
|
}
|
|
81
75
|
|
|
82
|
-
return parseSchema(
|
|
83
|
-
operation.output,
|
|
84
|
-
result,
|
|
85
|
-
`operations.${operationId}.output`,
|
|
86
|
-
);
|
|
76
|
+
return parseSchema(operation.output, result, `operations.${operationId}.output`);
|
|
87
77
|
}
|
package/src/runtime/http.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { ProxyResolutionOptions } from "../config/loader";
|
|
2
|
-
import { resolveProxyConfigAsync } from "../config/loader";
|
|
3
|
-
import { ProviderError, TransportError } from "../errors";
|
|
4
|
-
import { parseSseStream, readableBytes, readableLines, readableTextChunks } from "../stream";
|
|
1
|
+
import type { ProxyResolutionOptions } from "../config/loader.js";
|
|
2
|
+
import { resolveProxyConfigAsync } from "../config/loader.js";
|
|
3
|
+
import { ProviderError, TransportError } from "../errors.js";
|
|
4
|
+
import { parseSseStream, readableBytes, readableLines, readableTextChunks } from "../stream.js";
|
|
5
5
|
import type {
|
|
6
6
|
HttpClient,
|
|
7
7
|
HttpMethod,
|
|
@@ -11,7 +11,7 @@ import type {
|
|
|
11
11
|
RequestOptions,
|
|
12
12
|
RequestWithMethodOptions,
|
|
13
13
|
SseMessage,
|
|
14
|
-
} from "../types";
|
|
14
|
+
} from "../types.js";
|
|
15
15
|
import {
|
|
16
16
|
computeProxyAttemptIndex,
|
|
17
17
|
computeProxyTransportRetryDelayMs,
|
|
@@ -22,8 +22,8 @@ import {
|
|
|
22
22
|
proxyTransportRetryErrorStatus,
|
|
23
23
|
shouldRetryProxyTransportAttempt,
|
|
24
24
|
validateUnsafeProxyTransportRetryMethods,
|
|
25
|
-
} from "./proxy-retry-policy";
|
|
26
|
-
import { appendQueryParams, normalizeHttpRequestBody } from "./request-options";
|
|
25
|
+
} from "./proxy-retry-policy.js";
|
|
26
|
+
import { appendQueryParams, normalizeHttpRequestBody } from "./request-options.js";
|
|
27
27
|
|
|
28
28
|
const DEFAULT_HTTP_BASE_URL = "http://localhost";
|
|
29
29
|
|