@apifuse/provider-sdk 2.2.0-beta.26 → 2.2.0-beta.28
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/CHANGELOG.md +8 -0
- package/bin/apifuse-dev.ts +2 -2
- package/bin/apifuse-pack-smoke.ts +1 -1
- package/bin/apifuse-pack-types.ts +2 -1
- package/bin/apifuse-perf.ts +2 -4
- package/bin/apifuse-record.ts +1 -1
- package/dist/auth-turn/index.d.ts +1 -1
- package/dist/auth-turn/index.js +1 -1
- package/dist/ceremonies/index.js +52 -11
- package/dist/config/loader.d.ts +1 -1
- package/dist/config/loader.js +4 -2
- package/dist/contract-serialization.d.ts +1 -20
- package/dist/contract-serialization.js +8 -587
- package/dist/contract.d.ts +0 -2
- package/dist/contract.js +5 -9
- package/dist/index.d.ts +9 -8
- package/dist/index.js +6 -8
- package/dist/provider.d.ts +3 -2
- package/dist/provider.js +2 -2
- package/dist/runtime/auth-flow.js +1 -1
- package/dist/runtime/http.d.ts +1 -0
- package/dist/runtime/http.js +135 -12
- package/dist/runtime/instrumentation.js +1 -1
- package/dist/runtime/native-network-errors.d.ts +33 -0
- package/dist/runtime/native-network-errors.js +69 -0
- package/dist/runtime/native-network.d.ts +2 -33
- package/dist/runtime/native-network.js +2 -68
- package/dist/runtime/redis.d.ts +1 -1
- package/dist/runtime/redis.js +4 -2
- package/dist/runtime/resolver-config.d.ts +6 -0
- package/dist/runtime/resolver-config.js +6 -0
- package/dist/runtime/resolver-shared.d.ts +3 -0
- package/dist/runtime/resolver-shared.js +12 -0
- package/dist/runtime/resolver-vendors/twocaptcha.d.ts +2 -1
- package/dist/runtime/resolver-vendors/twocaptcha.js +80 -43
- package/dist/runtime/resolver-vendors/types.d.ts +1 -1
- package/dist/runtime/resolver.d.ts +6 -10
- package/dist/runtime/resolver.js +19 -18
- package/dist/runtime/state.js +5 -115
- package/dist/runtime/stealth-cookies.d.ts +20 -0
- package/dist/runtime/stealth-cookies.js +111 -0
- package/dist/runtime/stealth.js +7 -130
- package/dist/schema.d.ts +0 -63
- package/dist/schema.js +8 -808
- package/dist/serve.d.ts +1 -1
- package/dist/serve.js +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/self-test.d.ts +13 -0
- package/dist/server/self-test.js +124 -46
- package/dist/server/serve-implementation.d.ts +199 -0
- package/dist/server/serve-implementation.js +2054 -0
- package/dist/server/serve.d.ts +1 -187
- package/dist/server/serve.js +1 -1827
- package/dist/stateful/errors.d.ts +5 -0
- package/dist/stateful/errors.js +10 -0
- package/dist/stateful/stateful-provider-session-routing.d.ts +1 -5
- package/dist/stateful/stateful-provider-session-routing.js +2 -10
- package/dist/stream.js +7 -1
- package/package.json +27 -2
- package/src/auth-turn/index.ts +1 -1
- package/src/ceremonies/index.ts +68 -18
- package/src/config/loader.ts +5 -2
- package/src/contract-serialization.ts +8 -859
- package/src/contract.ts +5 -16
- package/src/index.ts +18 -34
- package/src/provider.ts +12 -24
- package/src/runtime/auth-flow.ts +1 -1
- package/src/runtime/http.ts +155 -11
- package/src/runtime/instrumentation.ts +1 -1
- package/src/runtime/native-network-errors.ts +99 -0
- package/src/runtime/native-network.ts +16 -97
- package/src/runtime/redis.ts +7 -2
- package/src/runtime/resolver-config.ts +6 -0
- package/src/runtime/resolver-shared.ts +17 -0
- package/src/runtime/resolver-vendors/twocaptcha.ts +100 -49
- package/src/runtime/resolver-vendors/types.ts +1 -0
- package/src/runtime/resolver.ts +47 -22
- package/src/runtime/state.ts +5 -144
- package/src/runtime/stealth-cookies.ts +132 -0
- package/src/runtime/stealth.ts +14 -157
- package/src/schema.ts +9 -1060
- package/src/serve.ts +6 -1
- package/src/server/index.ts +1 -0
- package/src/server/self-test.ts +184 -59
- package/src/server/serve-implementation.ts +3024 -0
- package/src/server/serve.ts +1 -2661
- package/src/stateful/errors.ts +12 -0
- package/src/stateful/stateful-provider-session-routing.ts +2 -11
- package/src/stream.ts +8 -1
package/src/contract.ts
CHANGED
|
@@ -7,11 +7,7 @@ import {
|
|
|
7
7
|
type JsonValue,
|
|
8
8
|
toJsonValue,
|
|
9
9
|
} from "./contract-json.js";
|
|
10
|
-
import {
|
|
11
|
-
describeSchema,
|
|
12
|
-
OutputTextTrustProjectionError,
|
|
13
|
-
serializeSmsMatcher,
|
|
14
|
-
} from "./contract-serialization.js";
|
|
10
|
+
import { describeSchema, serializeSmsMatcher } from "./contract-serialization.js";
|
|
15
11
|
import {
|
|
16
12
|
PROVIDER_CONTRACT_SCHEMA_VERSION,
|
|
17
13
|
type ProviderContractOperation,
|
|
@@ -34,7 +30,6 @@ export {
|
|
|
34
30
|
type ProviderContractOperation,
|
|
35
31
|
type ProviderContractSnapshot,
|
|
36
32
|
};
|
|
37
|
-
export { OutputTextTrustProjectionError };
|
|
38
33
|
|
|
39
34
|
export function extractProviderContract(provider: ProviderDefinition): ProviderContractSnapshot {
|
|
40
35
|
const auth = extractAuth(provider.auth);
|
|
@@ -99,7 +94,7 @@ function extractOperation(
|
|
|
99
94
|
const relatedOperations = toJsonValue(operation.relatedOperations);
|
|
100
95
|
const toolRouter = toJsonValue(operation.toolRouter);
|
|
101
96
|
const observability = toJsonValue(operation.observability);
|
|
102
|
-
const transport = extractTransport(operation.transport
|
|
97
|
+
const transport = extractTransport(operation.transport);
|
|
103
98
|
const fixtures = toJsonValue(operation.fixtures);
|
|
104
99
|
const upstream = toJsonValue(operation.upstream);
|
|
105
100
|
const hints = toJsonValue(operation.hints);
|
|
@@ -109,7 +104,7 @@ function extractOperation(
|
|
|
109
104
|
return {
|
|
110
105
|
id: operationId,
|
|
111
106
|
inputSchema: describeSchema(operation.input),
|
|
112
|
-
outputSchema: describeSchema(operation.output
|
|
107
|
+
outputSchema: describeSchema(operation.output),
|
|
113
108
|
...(descriptionKey === undefined ? {} : { descriptionKey }),
|
|
114
109
|
...(docs === undefined ? {} : { docs }),
|
|
115
110
|
...(whenToUseKeys === undefined ? {} : { whenToUseKeys }),
|
|
@@ -146,10 +141,7 @@ function extractAuth(value: ProviderDefinition["auth"]): JsonValue | undefined {
|
|
|
146
141
|
});
|
|
147
142
|
}
|
|
148
143
|
|
|
149
|
-
function extractTransport(
|
|
150
|
-
value: OperationTransport | undefined,
|
|
151
|
-
operationId: string,
|
|
152
|
-
): JsonValue | undefined {
|
|
144
|
+
function extractTransport(value: OperationTransport | undefined): JsonValue | undefined {
|
|
153
145
|
if (!value) return undefined;
|
|
154
146
|
if (value.kind !== "sse") return toJsonValue(value);
|
|
155
147
|
return compactObject({
|
|
@@ -157,10 +149,7 @@ function extractTransport(
|
|
|
157
149
|
events: Object.fromEntries(
|
|
158
150
|
Object.entries(value.events)
|
|
159
151
|
.sort(([leftId], [rightId]) => leftId.localeCompare(rightId))
|
|
160
|
-
.map(([eventName, schema]) => [
|
|
161
|
-
eventName,
|
|
162
|
-
describeSchema(schema, { eventName, operationId, outputTextTrust: true }),
|
|
163
|
-
]),
|
|
152
|
+
.map(([eventName, schema]) => [eventName, describeSchema(schema)]),
|
|
164
153
|
),
|
|
165
154
|
});
|
|
166
155
|
}
|
package/src/index.ts
CHANGED
|
@@ -20,7 +20,6 @@ export {
|
|
|
20
20
|
extractProviderContract,
|
|
21
21
|
type JsonPrimitive,
|
|
22
22
|
type JsonValue,
|
|
23
|
-
OutputTextTrustProjectionError,
|
|
24
23
|
PROVIDER_CONTRACT_SCHEMA_VERSION,
|
|
25
24
|
type ProviderContractOperation,
|
|
26
25
|
type ProviderContractSnapshot,
|
|
@@ -51,7 +50,6 @@ export * from "./recipes/gov-api.js";
|
|
|
51
50
|
export * from "./recipes/rest-api.js";
|
|
52
51
|
export { createFlowContext, createScratchpad } from "./runtime/auth-flow.js";
|
|
53
52
|
export type { BrowserClientOptions } from "./runtime/browser.js";
|
|
54
|
-
export { BrowserClient, createBrowserClient } from "./runtime/browser.js";
|
|
55
53
|
export {
|
|
56
54
|
APIFUSE__CACHE__KEY_PEPPER_ENV,
|
|
57
55
|
createBypassProviderCache,
|
|
@@ -73,25 +71,23 @@ export { createEnvContext } from "./runtime/env.js";
|
|
|
73
71
|
export { executeOperation } from "./runtime/executor.js";
|
|
74
72
|
export { createHttpClient } from "./runtime/http.js";
|
|
75
73
|
export {
|
|
76
|
-
createNativeNetworkClient,
|
|
77
|
-
createEnvVendorCredentialResolver,
|
|
78
|
-
deriveNativeCredentialAffinityKey,
|
|
79
74
|
NativeEgressGrantExpiredError,
|
|
80
75
|
NativeEgressNotDeclaredError,
|
|
81
76
|
NativeIdleTimeoutError,
|
|
82
77
|
NativeNetworkError,
|
|
83
78
|
NativeProxyExpiredError,
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
79
|
+
} from "./runtime/native-network-errors.js";
|
|
80
|
+
export type {
|
|
81
|
+
NativeGatewayProxy,
|
|
82
|
+
NativeGatewayProxyResolutionInput,
|
|
83
|
+
NativeGatewayProxySkipReason,
|
|
84
|
+
NativeGatewayProxySynthesizer,
|
|
85
|
+
NativeGatewayProxySynthesisResult,
|
|
86
|
+
NativeGatewayProxySynthesisInput,
|
|
87
|
+
NativeNetworkClientOptions,
|
|
88
|
+
NativeNetworkErrorCode,
|
|
89
|
+
VendorCredentialLookup,
|
|
90
|
+
VendorCredentialResolver,
|
|
95
91
|
} from "./runtime/native-network.js";
|
|
96
92
|
export type { Insight, InsightSeverity } from "./runtime/insights.js";
|
|
97
93
|
export { generateInsights } from "./runtime/insights.js";
|
|
@@ -100,7 +96,7 @@ export {
|
|
|
100
96
|
type InstrumentedProviderContext,
|
|
101
97
|
wrapWithInstrumentation,
|
|
102
98
|
} from "./runtime/instrumentation.js";
|
|
103
|
-
export {
|
|
99
|
+
export type { PrevalidateResult } from "./runtime/prevalidate.js";
|
|
104
100
|
export { getProviderBaseUrl } from "./runtime/provider.js";
|
|
105
101
|
export {
|
|
106
102
|
APIFUSE__CDP_POOL__URL,
|
|
@@ -108,12 +104,10 @@ export {
|
|
|
108
104
|
APIFUSE__RESOLVER__CAPMONSTER__API_KEY,
|
|
109
105
|
APIFUSE__RESOLVER__CAPSOLVER__API_KEY,
|
|
110
106
|
APIFUSE__RESOLVER__TIMEOUT_MS,
|
|
111
|
-
createResolverClientFromEnv,
|
|
112
|
-
createUnsupportedResolverClient,
|
|
113
107
|
DEFAULT_RESOLVER_TIMEOUT_MS,
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
} from "./runtime/resolver.js";
|
|
108
|
+
} from "./runtime/resolver-config.js";
|
|
109
|
+
export { createUnsupportedResolverClient } from "./runtime/resolver-shared.js";
|
|
110
|
+
export type { ResolverRuntimeOptions } from "./runtime/resolver.js";
|
|
117
111
|
export type { ResolverVendorTransport } from "./runtime/resolver-vendors/types.js";
|
|
118
112
|
export {
|
|
119
113
|
assertRequiredSecretsPresent,
|
|
@@ -124,7 +118,6 @@ export {
|
|
|
124
118
|
createUnsupportedProviderRuntimeState,
|
|
125
119
|
UnsupportedProviderStateError,
|
|
126
120
|
} from "./runtime/state.js";
|
|
127
|
-
export { createStealthClient } from "./runtime/stealth.js";
|
|
128
121
|
export {
|
|
129
122
|
APIFUSE__OCR__API_KEY_ENV,
|
|
130
123
|
APIFUSE__OCR__BACKEND_ENV,
|
|
@@ -157,38 +150,29 @@ export {
|
|
|
157
150
|
type TraceContext,
|
|
158
151
|
} from "./runtime/trace.js";
|
|
159
152
|
export {
|
|
160
|
-
APIFUSE_TEXT_TRUST_META_KEY,
|
|
161
153
|
APIFUSE_DESCRIPTION_KEY_META_KEY,
|
|
162
154
|
APIFUSE_REDACTION_MARKER,
|
|
163
155
|
APIFUSE_SENSITIVE_KIND_META_KEY,
|
|
164
156
|
APIFUSE_SENSITIVE_META_KEY,
|
|
165
|
-
AUTO_TRUSTED_ZOD_STRING_FORMATS,
|
|
166
|
-
collectOutputTextTrust,
|
|
167
157
|
collectSensitivePaths,
|
|
168
158
|
describeKey,
|
|
169
159
|
field,
|
|
170
160
|
fields,
|
|
171
|
-
findUnclassifiedOutputTextPaths,
|
|
172
161
|
isSensitiveSchema,
|
|
173
|
-
OutputTextTrustCollectionError,
|
|
174
|
-
OutputTextTrustSchemaError,
|
|
175
162
|
redactPayload,
|
|
176
|
-
type OutputTextTrustMap,
|
|
177
163
|
type SensitiveFieldKind,
|
|
178
164
|
type SensitiveFieldOptions,
|
|
179
165
|
type SensitivePath,
|
|
180
166
|
sensitive,
|
|
181
|
-
textTrust,
|
|
182
|
-
type TextTrust,
|
|
183
|
-
type TextTrustMetadata,
|
|
184
167
|
z,
|
|
185
168
|
} from "./schema.js";
|
|
186
169
|
export {
|
|
187
170
|
createServerApp,
|
|
171
|
+
createServerAppAsync,
|
|
188
172
|
ERROR_OBSERVABILITY_HEADER,
|
|
189
173
|
type ServeOptions,
|
|
190
174
|
serve,
|
|
191
|
-
} from "./server/
|
|
175
|
+
} from "./server/serve.js";
|
|
192
176
|
export { getStealthProfile, listStealthProfiles } from "./stealth/profiles.js";
|
|
193
177
|
export * from "./stream.js";
|
|
194
178
|
export type {
|
package/src/provider.ts
CHANGED
|
@@ -58,30 +58,20 @@ export {
|
|
|
58
58
|
PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV,
|
|
59
59
|
} from "./runtime/choice.js";
|
|
60
60
|
export {
|
|
61
|
-
APIFUSE_TEXT_TRUST_META_KEY,
|
|
62
61
|
APIFUSE_DESCRIPTION_KEY_META_KEY,
|
|
63
62
|
APIFUSE_REDACTION_MARKER,
|
|
64
63
|
APIFUSE_SENSITIVE_KIND_META_KEY,
|
|
65
64
|
APIFUSE_SENSITIVE_META_KEY,
|
|
66
|
-
AUTO_TRUSTED_ZOD_STRING_FORMATS,
|
|
67
|
-
collectOutputTextTrust,
|
|
68
65
|
collectSensitivePaths,
|
|
69
66
|
describeKey,
|
|
70
67
|
field,
|
|
71
68
|
fields,
|
|
72
|
-
findUnclassifiedOutputTextPaths,
|
|
73
69
|
isSensitiveSchema,
|
|
74
|
-
OutputTextTrustCollectionError,
|
|
75
|
-
OutputTextTrustSchemaError,
|
|
76
70
|
redactPayload,
|
|
77
|
-
type OutputTextTrustMap,
|
|
78
71
|
type SensitiveFieldKind,
|
|
79
72
|
type SensitiveFieldOptions,
|
|
80
73
|
type SensitivePath,
|
|
81
74
|
sensitive,
|
|
82
|
-
textTrust,
|
|
83
|
-
type TextTrust,
|
|
84
|
-
type TextTrustMetadata,
|
|
85
75
|
z,
|
|
86
76
|
} from "./schema.js";
|
|
87
77
|
export type {
|
|
@@ -177,25 +167,23 @@ export type {
|
|
|
177
167
|
StateWriteOptions,
|
|
178
168
|
} from "./types.js";
|
|
179
169
|
export {
|
|
180
|
-
createNativeNetworkClient,
|
|
181
|
-
createEnvVendorCredentialResolver,
|
|
182
|
-
deriveNativeCredentialAffinityKey,
|
|
183
170
|
NativeEgressGrantExpiredError,
|
|
184
171
|
NativeEgressNotDeclaredError,
|
|
185
172
|
NativeIdleTimeoutError,
|
|
186
173
|
NativeNetworkError,
|
|
187
174
|
NativeProxyExpiredError,
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
175
|
+
} from "./runtime/native-network-errors.js";
|
|
176
|
+
export type {
|
|
177
|
+
NativeGatewayProxy,
|
|
178
|
+
NativeGatewayProxyResolutionInput,
|
|
179
|
+
NativeGatewayProxySkipReason,
|
|
180
|
+
NativeGatewayProxySynthesizer,
|
|
181
|
+
NativeGatewayProxySynthesisResult,
|
|
182
|
+
NativeGatewayProxySynthesisInput,
|
|
183
|
+
NativeNetworkClientOptions,
|
|
184
|
+
NativeNetworkErrorCode,
|
|
185
|
+
VendorCredentialLookup,
|
|
186
|
+
VendorCredentialResolver,
|
|
199
187
|
} from "./runtime/native-network.js";
|
|
200
188
|
export {
|
|
201
189
|
HttpRetryAfterPolicy,
|
package/src/runtime/auth-flow.ts
CHANGED
|
@@ -10,7 +10,7 @@ import type {
|
|
|
10
10
|
SttContext,
|
|
11
11
|
} from "../types.js";
|
|
12
12
|
import { createUnsupportedOcrClient } from "./ocr.js";
|
|
13
|
-
import { createUnsupportedResolverClient } from "./resolver.js";
|
|
13
|
+
import { createUnsupportedResolverClient } from "./resolver-shared.js";
|
|
14
14
|
import { createUnsupportedSttClient } from "./stt.js";
|
|
15
15
|
|
|
16
16
|
function normalizeAllowedKeys(allowedKeys: string[]): Set<string> {
|
package/src/runtime/http.ts
CHANGED
|
@@ -44,6 +44,7 @@ export type HttpClientOptions = ProxyResolutionOptions & {
|
|
|
44
44
|
warn?: (message: string) => void;
|
|
45
45
|
userAgent?: string;
|
|
46
46
|
onRetrySummary?: (summary: HttpRetrySummary) => void;
|
|
47
|
+
signal?: AbortSignal;
|
|
47
48
|
};
|
|
48
49
|
|
|
49
50
|
type HttpStatusOutcome = {
|
|
@@ -76,9 +77,45 @@ function isDedupeSkipOutcome(outcome: NativeHttpAttemptOutcome): outcome is Nati
|
|
|
76
77
|
return "kind" in outcome && outcome.kind === "dedupe-skip";
|
|
77
78
|
}
|
|
78
79
|
|
|
79
|
-
|
|
80
|
+
function toAmbientCancellationError(
|
|
81
|
+
signal: AbortSignal,
|
|
82
|
+
error: unknown = signal.reason,
|
|
83
|
+
): TransportError {
|
|
84
|
+
if (error instanceof TransportError && error.code === "transport_cancelled") {
|
|
85
|
+
return error;
|
|
86
|
+
}
|
|
87
|
+
return new TransportError("Request cancelled", {
|
|
88
|
+
code: "transport_cancelled",
|
|
89
|
+
status: 0,
|
|
90
|
+
retryable: false,
|
|
91
|
+
...(error !== undefined
|
|
92
|
+
? { cause: error instanceof Error ? error : new Error(String(error)) }
|
|
93
|
+
: {}),
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function throwIfAmbientAborted(signal: AbortSignal | undefined): void {
|
|
98
|
+
if (signal?.aborted) throw toAmbientCancellationError(signal);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function sleep(ms: number, signal?: AbortSignal): Promise<void> {
|
|
102
|
+
throwIfAmbientAborted(signal);
|
|
80
103
|
if (ms <= 0) return;
|
|
81
|
-
|
|
104
|
+
if (!signal) {
|
|
105
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
await new Promise<void>((resolve, reject) => {
|
|
109
|
+
const onAbort = () => {
|
|
110
|
+
clearTimeout(timer);
|
|
111
|
+
reject(toAmbientCancellationError(signal));
|
|
112
|
+
};
|
|
113
|
+
const timer = setTimeout(() => {
|
|
114
|
+
signal.removeEventListener("abort", onAbort);
|
|
115
|
+
resolve();
|
|
116
|
+
}, ms);
|
|
117
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
118
|
+
});
|
|
82
119
|
}
|
|
83
120
|
|
|
84
121
|
function toUpstreamHttpError(status: number): TransportError {
|
|
@@ -132,7 +169,21 @@ function isTimeoutMessage(message: string): boolean {
|
|
|
132
169
|
return /\b(timed out|timeout|deadline exceeded)\b/i.test(message);
|
|
133
170
|
}
|
|
134
171
|
|
|
135
|
-
function toHttpTransportError(
|
|
172
|
+
function toHttpTransportError(
|
|
173
|
+
error: unknown,
|
|
174
|
+
ambientSignal?: AbortSignal,
|
|
175
|
+
timeoutSignal?: AbortSignal,
|
|
176
|
+
): TransportError {
|
|
177
|
+
if (ambientSignal?.aborted) {
|
|
178
|
+
return toAmbientCancellationError(ambientSignal, error);
|
|
179
|
+
}
|
|
180
|
+
if (timeoutSignal?.aborted) {
|
|
181
|
+
return new TransportError("Request timed out", {
|
|
182
|
+
code: "transport_timeout",
|
|
183
|
+
status: 0,
|
|
184
|
+
...(error instanceof Error ? { cause: error } : {}),
|
|
185
|
+
});
|
|
186
|
+
}
|
|
136
187
|
if (error instanceof TransportError) {
|
|
137
188
|
if (error.code) {
|
|
138
189
|
return error;
|
|
@@ -216,6 +267,78 @@ function requireNativeResponseBody(response: Response): ReadableStream<Uint8Arra
|
|
|
216
267
|
return response.body;
|
|
217
268
|
}
|
|
218
269
|
|
|
270
|
+
function mergeAbortSignals(
|
|
271
|
+
...signals: Array<AbortSignal | null | undefined>
|
|
272
|
+
): AbortSignal | undefined {
|
|
273
|
+
const activeSignals = signals.filter((signal): signal is AbortSignal => signal != null);
|
|
274
|
+
if (activeSignals.length === 0) return undefined;
|
|
275
|
+
if (activeSignals.length === 1) return activeSignals[0];
|
|
276
|
+
return AbortSignal.any(activeSignals);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function cancelStreamOnAbort(
|
|
280
|
+
body: ReadableStream<Uint8Array>,
|
|
281
|
+
signal: AbortSignal | undefined,
|
|
282
|
+
): ReadableStream<Uint8Array> {
|
|
283
|
+
if (!signal) return body;
|
|
284
|
+
|
|
285
|
+
let reader: ReadableStreamDefaultReader<Uint8Array> | undefined;
|
|
286
|
+
let finished = false;
|
|
287
|
+
let streamController: ReadableStreamDefaultController<Uint8Array> | undefined;
|
|
288
|
+
const cleanup = () => signal.removeEventListener("abort", onAbort);
|
|
289
|
+
const onAbort = () => {
|
|
290
|
+
if (finished) return;
|
|
291
|
+
finished = true;
|
|
292
|
+
cleanup();
|
|
293
|
+
const reason = toAmbientCancellationError(signal);
|
|
294
|
+
streamController?.error(reason);
|
|
295
|
+
const cancellation = reader ? reader.cancel(reason) : body.cancel(reason);
|
|
296
|
+
void cancellation.catch(() => undefined).finally(() => reader?.releaseLock());
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
return new ReadableStream<Uint8Array>(
|
|
300
|
+
{
|
|
301
|
+
start(controller) {
|
|
302
|
+
streamController = controller;
|
|
303
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
304
|
+
if (signal.aborted) onAbort();
|
|
305
|
+
},
|
|
306
|
+
async pull(controller) {
|
|
307
|
+
try {
|
|
308
|
+
reader ??= body.getReader();
|
|
309
|
+
const chunk = await reader.read();
|
|
310
|
+
if (finished) return;
|
|
311
|
+
if (chunk.done) {
|
|
312
|
+
finished = true;
|
|
313
|
+
cleanup();
|
|
314
|
+
controller.close();
|
|
315
|
+
reader.releaseLock();
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
controller.enqueue(chunk.value);
|
|
319
|
+
} catch (error) {
|
|
320
|
+
if (finished) return;
|
|
321
|
+
finished = true;
|
|
322
|
+
cleanup();
|
|
323
|
+
controller.error(error);
|
|
324
|
+
reader?.releaseLock();
|
|
325
|
+
}
|
|
326
|
+
},
|
|
327
|
+
async cancel(reason) {
|
|
328
|
+
if (finished) return;
|
|
329
|
+
finished = true;
|
|
330
|
+
cleanup();
|
|
331
|
+
try {
|
|
332
|
+
await (reader ? reader.cancel(reason) : body.cancel(reason));
|
|
333
|
+
} finally {
|
|
334
|
+
reader?.releaseLock();
|
|
335
|
+
}
|
|
336
|
+
},
|
|
337
|
+
},
|
|
338
|
+
{ highWaterMark: 0 },
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
|
|
219
342
|
function sanitizeStreamErrors(
|
|
220
343
|
body: ReadableStream<Uint8Array>,
|
|
221
344
|
serializedUrl: SerializedRequestUrl,
|
|
@@ -265,9 +388,13 @@ function sanitizeStreamErrors(
|
|
|
265
388
|
function toNativeHttpStreamResponse(
|
|
266
389
|
response: Response,
|
|
267
390
|
serializedUrl: SerializedRequestUrl,
|
|
391
|
+
signal?: AbortSignal,
|
|
268
392
|
): HttpStreamResponse {
|
|
269
393
|
const headers = Object.fromEntries(response.headers.entries());
|
|
270
|
-
const body = sanitizeStreamErrors(
|
|
394
|
+
const body = sanitizeStreamErrors(
|
|
395
|
+
cancelStreamOnAbort(requireNativeResponseBody(response), signal),
|
|
396
|
+
serializedUrl,
|
|
397
|
+
);
|
|
271
398
|
return {
|
|
272
399
|
body,
|
|
273
400
|
headers,
|
|
@@ -585,16 +712,19 @@ async function fetchNativeHttp(
|
|
|
585
712
|
const serializedUrl = serializeHttpRequestUrl(baseUrl, url, options);
|
|
586
713
|
const { requestUrl } = serializedUrl;
|
|
587
714
|
const controller = options.timeout ? new AbortController() : undefined;
|
|
715
|
+
const signal = mergeAbortSignals(clientOptions.signal, controller?.signal);
|
|
588
716
|
const timeoutHandle = options.timeout
|
|
589
717
|
? setTimeout(() => controller?.abort(), options.timeout)
|
|
590
718
|
: undefined;
|
|
591
719
|
|
|
592
720
|
let proxy: string | undefined;
|
|
593
721
|
try {
|
|
722
|
+
throwIfAmbientAborted(clientOptions.signal);
|
|
594
723
|
// Resolve inside the try (and after the timeout is armed) so allocator
|
|
595
724
|
// failures are branded as TransportErrors and count against the request
|
|
596
725
|
// deadline, exactly as an inline resolve would.
|
|
597
726
|
proxy = await resolveNativeProxy(options, clientOptions, warn, proxyAttemptOffset);
|
|
727
|
+
throwIfAmbientAborted(clientOptions.signal);
|
|
598
728
|
// For a registry allocator chain, skip an endpoint a prior attempt already
|
|
599
729
|
// tried rather than re-issuing the same request. Returning the sentinel
|
|
600
730
|
// (instead of breaking) lets the loop keep advancing the flat offset until
|
|
@@ -609,7 +739,7 @@ async function fetchNativeHttp(
|
|
|
609
739
|
headers: options.headers,
|
|
610
740
|
method,
|
|
611
741
|
...(proxy ? { proxy } : {}),
|
|
612
|
-
signal:
|
|
742
|
+
...(signal ? { signal } : {}),
|
|
613
743
|
};
|
|
614
744
|
if (options.body !== undefined) {
|
|
615
745
|
requestInit.body = normalizeNativeFetchBody(options.body);
|
|
@@ -651,7 +781,7 @@ async function fetchNativeHttp(
|
|
|
651
781
|
);
|
|
652
782
|
}
|
|
653
783
|
const transportError: NativeHttpAttemptError = redactSensitiveError(
|
|
654
|
-
toHttpTransportError(error),
|
|
784
|
+
toHttpTransportError(error, clientOptions.signal, controller?.signal),
|
|
655
785
|
serializedUrl.sensitiveValues,
|
|
656
786
|
serializedUrl.requestUrl,
|
|
657
787
|
serializedUrl.redactedUrl,
|
|
@@ -674,17 +804,20 @@ async function fetchNativeHttpStream(
|
|
|
674
804
|
const serializedUrl = serializeHttpRequestUrl(baseUrl, url, options);
|
|
675
805
|
const { requestUrl } = serializedUrl;
|
|
676
806
|
const controller = options.timeout ? new AbortController() : undefined;
|
|
807
|
+
const signal = mergeAbortSignals(clientOptions.signal, controller?.signal);
|
|
677
808
|
const timeoutHandle = options.timeout
|
|
678
809
|
? setTimeout(() => controller?.abort(), options.timeout)
|
|
679
810
|
: undefined;
|
|
680
811
|
|
|
681
812
|
try {
|
|
813
|
+
throwIfAmbientAborted(clientOptions.signal);
|
|
682
814
|
const proxy = await resolveNativeProxy(options, clientOptions, warn);
|
|
815
|
+
throwIfAmbientAborted(clientOptions.signal);
|
|
683
816
|
const requestInit: NativeFetchInit = {
|
|
684
817
|
headers: options.headers,
|
|
685
818
|
method,
|
|
686
819
|
...(proxy ? { proxy } : {}),
|
|
687
|
-
signal:
|
|
820
|
+
...(signal ? { signal } : {}),
|
|
688
821
|
};
|
|
689
822
|
if (options.body !== undefined) {
|
|
690
823
|
requestInit.body = normalizeNativeFetchBody(options.body);
|
|
@@ -703,7 +836,9 @@ async function fetchNativeHttpStream(
|
|
|
703
836
|
});
|
|
704
837
|
}
|
|
705
838
|
|
|
706
|
-
|
|
839
|
+
// Per-call timeout remains header-scoped, while the ambient request signal
|
|
840
|
+
// stays attached to the response body for its full consumption lifetime.
|
|
841
|
+
return toNativeHttpStreamResponse(response, serializedUrl, clientOptions.signal);
|
|
707
842
|
} catch (error) {
|
|
708
843
|
if (error instanceof SyntaxError) {
|
|
709
844
|
throw redactSensitiveError(
|
|
@@ -714,7 +849,7 @@ async function fetchNativeHttpStream(
|
|
|
714
849
|
);
|
|
715
850
|
}
|
|
716
851
|
throw redactSensitiveError(
|
|
717
|
-
toHttpTransportError(error),
|
|
852
|
+
toHttpTransportError(error, clientOptions.signal, controller?.signal),
|
|
718
853
|
serializedUrl.sensitiveValues,
|
|
719
854
|
serializedUrl.requestUrl,
|
|
720
855
|
serializedUrl.redactedUrl,
|
|
@@ -850,6 +985,7 @@ export function createHttpClient(
|
|
|
850
985
|
);
|
|
851
986
|
|
|
852
987
|
if (!retryEnabled || !retryOptions) {
|
|
988
|
+
throwIfAmbientAborted(clientOptions.signal);
|
|
853
989
|
const outcome = await executeOnce();
|
|
854
990
|
if (isDedupeSkipOutcome(outcome)) {
|
|
855
991
|
// Single-shot path never de-duplicates (dedupeContext is undefined),
|
|
@@ -874,6 +1010,7 @@ export function createHttpClient(
|
|
|
874
1010
|
// allocations skip offsets.
|
|
875
1011
|
let issued = 0;
|
|
876
1012
|
for (let attempt = 1; attempt <= transportAttemptCap; attempt += 1) {
|
|
1013
|
+
throwIfAmbientAborted(clientOptions.signal);
|
|
877
1014
|
// Whether this offset actually issued a request (vs. a skipped duplicate),
|
|
878
1015
|
// so the catch counts a thrown *transport* failure once without
|
|
879
1016
|
// double-counting a status outcome that already incremented before it
|
|
@@ -892,7 +1029,10 @@ export function createHttpClient(
|
|
|
892
1029
|
if (isHttpStatusOutcome(outcome)) {
|
|
893
1030
|
lastStatus = outcome.status;
|
|
894
1031
|
if (outcome.retryable && issued < retryOptions.attempts) {
|
|
895
|
-
await sleep(
|
|
1032
|
+
await sleep(
|
|
1033
|
+
computeProxyTransportRetryDelayMs(retryOptions, attempt, outcome.headers),
|
|
1034
|
+
clientOptions.signal,
|
|
1035
|
+
);
|
|
896
1036
|
continue;
|
|
897
1037
|
}
|
|
898
1038
|
throw toUpstreamHttpError(outcome.status);
|
|
@@ -916,6 +1056,7 @@ export function createHttpClient(
|
|
|
916
1056
|
}
|
|
917
1057
|
return response;
|
|
918
1058
|
} catch (error) {
|
|
1059
|
+
throwIfAmbientAborted(clientOptions.signal);
|
|
919
1060
|
if (!issuedThisAttempt) issued += 1;
|
|
920
1061
|
lastError = error;
|
|
921
1062
|
lastErrorCode = proxyTransportRetryErrorCode(error);
|
|
@@ -931,7 +1072,10 @@ export function createHttpClient(
|
|
|
931
1072
|
proxyUsed,
|
|
932
1073
|
})
|
|
933
1074
|
) {
|
|
934
|
-
await sleep(
|
|
1075
|
+
await sleep(
|
|
1076
|
+
computeProxyTransportRetryDelayMs(retryOptions, attempt),
|
|
1077
|
+
clientOptions.signal,
|
|
1078
|
+
);
|
|
935
1079
|
continue;
|
|
936
1080
|
}
|
|
937
1081
|
throw error;
|
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
getTraceRecorder,
|
|
24
24
|
type TraceContext,
|
|
25
25
|
} from "./trace.js";
|
|
26
|
-
import { RESOLVER_INSTRUMENTATION_METADATA } from "./resolver.js";
|
|
26
|
+
import { RESOLVER_INSTRUMENTATION_METADATA } from "./resolver-shared.js";
|
|
27
27
|
|
|
28
28
|
export interface InstrumentationOptions extends CreateTraceContextOptions {}
|
|
29
29
|
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { TransportError } from "../errors.js";
|
|
2
|
+
import { canonicalizeEgressHost } from "../native-address.js";
|
|
3
|
+
|
|
4
|
+
export type NativeNetworkErrorCode =
|
|
5
|
+
| "native_connection_aborted"
|
|
6
|
+
| "native_connection_closed"
|
|
7
|
+
| "native_connection_failed"
|
|
8
|
+
| "native_connection_idle_timeout"
|
|
9
|
+
| "native_connection_timeout"
|
|
10
|
+
| "native_egress_authorization_failed"
|
|
11
|
+
| "native_egress_grant_expired"
|
|
12
|
+
| "native_egress_grant_invalid"
|
|
13
|
+
| "native_egress_grant_limit_exceeded"
|
|
14
|
+
| "native_egress_input_invalid"
|
|
15
|
+
| "native_egress_not_declared"
|
|
16
|
+
| "native_egress_policy_invalid"
|
|
17
|
+
| "native_dynamic_egress_unsupported"
|
|
18
|
+
| "native_proxy_expired"
|
|
19
|
+
| "native_proxy_invalid";
|
|
20
|
+
|
|
21
|
+
export class NativeNetworkError extends TransportError {
|
|
22
|
+
constructor(message: string, code: NativeNetworkErrorCode, cause?: Error) {
|
|
23
|
+
const isEgressPolicyFailure =
|
|
24
|
+
code.startsWith("native_egress_") || code === "native_dynamic_egress_unsupported";
|
|
25
|
+
super(message, {
|
|
26
|
+
code,
|
|
27
|
+
status: 0,
|
|
28
|
+
...(cause ? { cause } : {}),
|
|
29
|
+
...(isEgressPolicyFailure ? { category: "provider_error" as const, retryable: false } : {}),
|
|
30
|
+
});
|
|
31
|
+
this.name = "NativeNetworkError";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
override get code(): NativeNetworkErrorCode {
|
|
35
|
+
return super.code as NativeNetworkErrorCode;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function safeDiagnosticEgressHost(value: unknown): string {
|
|
40
|
+
const canonical = canonicalizeEgressHost(value);
|
|
41
|
+
return canonical.ok ? canonical.host : `<invalid-host:${canonical.reason}>`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export class NativeProxyExpiredError extends NativeNetworkError {
|
|
45
|
+
constructor(readonly expiresAt: string) {
|
|
46
|
+
super("Native connection closed at sticky proxy expiry", "native_proxy_expired");
|
|
47
|
+
this.name = "NativeProxyExpiredError";
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Raised before transport setup when a native destination is not authorized. */
|
|
52
|
+
export class NativeEgressNotDeclaredError extends NativeNetworkError {
|
|
53
|
+
readonly host: string;
|
|
54
|
+
|
|
55
|
+
constructor(
|
|
56
|
+
host: string,
|
|
57
|
+
readonly port: number,
|
|
58
|
+
readonly tls: "required" | "disabled",
|
|
59
|
+
) {
|
|
60
|
+
const diagnosticHost = safeDiagnosticEgressHost(host);
|
|
61
|
+
super(
|
|
62
|
+
`Native ${tls === "required" ? "TLS" : "TCP"} egress is not declared for ${diagnosticHost}:${port}`,
|
|
63
|
+
"native_egress_not_declared",
|
|
64
|
+
);
|
|
65
|
+
this.host = diagnosticHost;
|
|
66
|
+
this.name = "NativeEgressNotDeclaredError";
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Raised when the destination was authorized by a grant whose TTL elapsed and
|
|
72
|
+
* its expiry remains in the client's bounded recent-expiry evidence window.
|
|
73
|
+
*/
|
|
74
|
+
export class NativeEgressGrantExpiredError extends NativeNetworkError {
|
|
75
|
+
readonly host: string;
|
|
76
|
+
|
|
77
|
+
constructor(
|
|
78
|
+
host: string,
|
|
79
|
+
readonly port: number,
|
|
80
|
+
readonly tls: "required" | "disabled",
|
|
81
|
+
readonly expiresAt: string,
|
|
82
|
+
) {
|
|
83
|
+
const diagnosticHost = safeDiagnosticEgressHost(host);
|
|
84
|
+
super(
|
|
85
|
+
`Native ${tls === "required" ? "TLS" : "TCP"} egress grant expired for ${diagnosticHost}:${port}`,
|
|
86
|
+
"native_egress_grant_expired",
|
|
87
|
+
);
|
|
88
|
+
this.host = diagnosticHost;
|
|
89
|
+
this.name = "NativeEgressGrantExpiredError";
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Raised when an established connection exceeds its opt-in read-idle window. */
|
|
94
|
+
export class NativeIdleTimeoutError extends NativeNetworkError {
|
|
95
|
+
constructor() {
|
|
96
|
+
super("Native network socket timed out while reading.", "native_connection_idle_timeout");
|
|
97
|
+
this.name = "NativeIdleTimeoutError";
|
|
98
|
+
}
|
|
99
|
+
}
|