@apifuse/provider-sdk 2.1.0-beta.0 → 2.1.0-beta.2

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.
@@ -83,13 +83,12 @@ async function doRequest(
83
83
  });
84
84
 
85
85
  if (!response.ok) {
86
- const text = await response.text().catch(() => "");
86
+ await drainFetchResponse(response);
87
87
  throw new TransportError(
88
- `HTTP ${response.status} ${response.statusText}: ${requestUrl}`,
88
+ `Upstream request failed with status ${response.status}`,
89
89
  {
90
90
  code: "upstream_http_error",
91
91
  status: response.status,
92
- fix: `Check the endpoint URL and request parameters. Response: ${text.slice(0, 200)}`,
93
92
  },
94
93
  );
95
94
  }
@@ -121,16 +120,12 @@ async function doRequest(
121
120
  }
122
121
 
123
122
  if (error instanceof Error && error.name === "AbortError") {
124
- throw new TransportError(
125
- `Request timed out: ${resolveUrl(baseUrl, url)}`,
126
- {
127
- code: "transport_timeout",
128
- fix: `Increase timeout option (current: ${timeout}ms)`,
129
- },
130
- );
123
+ throw new TransportError("Request timed out", {
124
+ code: "transport_timeout",
125
+ });
131
126
  }
132
127
 
133
- throw new TransportError(`Network error: ${String(error)}`, {
128
+ throw new TransportError("Network error", {
134
129
  code: "transport_network_error",
135
130
  cause: error instanceof Error ? error : undefined,
136
131
  });
@@ -141,6 +136,27 @@ async function doRequest(
141
136
  }
142
137
  }
143
138
 
139
+ async function drainFetchResponse(response: Response): Promise<void> {
140
+ const reader = response.body?.getReader();
141
+ if (!reader) {
142
+ return;
143
+ }
144
+
145
+ try {
146
+ while (true) {
147
+ const { done } = await reader.read();
148
+ if (done) {
149
+ return;
150
+ }
151
+ }
152
+ } catch {
153
+ // Best-effort drain for transport reuse only; callers still receive the
154
+ // sanitized upstream error below.
155
+ } finally {
156
+ reader.releaseLock();
157
+ }
158
+ }
159
+
144
160
  function createProxyInit(
145
161
  proxy?: string,
146
162
  ): Pick<FetchProxyInit, "dispatcher" | "proxy"> {
@@ -19,6 +19,12 @@ const MISSING_PROXY_WARNING =
19
19
 
20
20
  export type TlsClientOptions = ProxyResolutionOptions & {
21
21
  warn?: (message: string) => void;
22
+ /**
23
+ * Proxy-only TLS transport overrides. Use only for upstream proxy products
24
+ * that terminate CONNECT with a private CA instead of tunneling the origin
25
+ * certificate chain.
26
+ */
27
+ proxyTls?: { insecureSkipVerify?: boolean };
22
28
  };
23
29
 
24
30
  const REMOVED_CHROME_PROFILE_NAMES = new Set([
@@ -187,14 +193,6 @@ export function normalizeResponse(
187
193
  };
188
194
  }
189
195
 
190
- function getErrorMessage(error: unknown): string {
191
- if (error instanceof Error) {
192
- return error.toString();
193
- }
194
-
195
- return String(error);
196
- }
197
-
198
196
  function normalizeBody(body: TlsFetchOptions["body"]): string | null {
199
197
  if (body === undefined) {
200
198
  return null;
@@ -274,6 +272,7 @@ function createSessionFetcher(
274
272
  let sessionClient: SessionClient | null = null;
275
273
  let activeProxy: string | undefined;
276
274
  let activeTlsIdentifier: string | undefined;
275
+ let activeInsecureSkipVerify = false;
277
276
  let hasWarnedMissingProxy = false;
278
277
  const warn = clientOptions.warn ?? console.warn;
279
278
 
@@ -305,19 +304,22 @@ function createSessionFetcher(
305
304
  sessionClient = null;
306
305
  activeProxy = undefined;
307
306
  activeTlsIdentifier = undefined;
307
+ activeInsecureSkipVerify = false;
308
308
  }
309
309
 
310
310
  function getSessionClient(
311
311
  profile?: string,
312
312
  proxy?: string,
313
313
  ja3?: string,
314
+ insecureSkipVerify = false,
314
315
  ): SessionClient {
315
316
  const tlsIdentifier = ja3 ?? resolveIdentifier(profile ?? defaultProfile);
316
317
 
317
318
  if (
318
319
  !sessionClient ||
319
320
  activeProxy !== proxy ||
320
- activeTlsIdentifier !== tlsIdentifier
321
+ activeTlsIdentifier !== tlsIdentifier ||
322
+ activeInsecureSkipVerify !== insecureSkipVerify
321
323
  ) {
322
324
  if (sessionClient) {
323
325
  closeCurrentSession();
@@ -326,10 +328,12 @@ function createSessionFetcher(
326
328
  sessionClient = new SessionClient(moduleClient, {
327
329
  tlsClientIdentifier: tlsIdentifier,
328
330
  ...(proxy ? { proxyUrl: proxy } : {}),
331
+ ...(insecureSkipVerify ? { insecureSkipVerify: true } : {}),
329
332
  timeoutSeconds: 30,
330
333
  } as ConstructorParameters<typeof SessionClient>[1]);
331
334
  activeProxy = proxy;
332
335
  activeTlsIdentifier = tlsIdentifier;
336
+ activeInsecureSkipVerify = insecureSkipVerify;
333
337
  }
334
338
 
335
339
  return sessionClient;
@@ -338,10 +342,15 @@ function createSessionFetcher(
338
342
  return {
339
343
  async fetch(url, options = {}) {
340
344
  const proxy = resolveRequestProxy(options);
345
+ const insecureSkipVerify = Boolean(
346
+ options.tls?.insecureSkipVerify ??
347
+ (proxy && clientOptions.proxyTls?.insecureSkipVerify),
348
+ );
341
349
  const session = getSessionClient(
342
350
  options.profile,
343
351
  proxy,
344
352
  options.tls?.ja3,
353
+ insecureSkipVerify,
345
354
  );
346
355
  const requestUrl = resolveUrl(baseUrl, url);
347
356
 
@@ -352,9 +361,9 @@ function createSessionFetcher(
352
361
  proxy,
353
362
  });
354
363
 
355
- if (response.status >= 400) {
364
+ if (response.status >= 400 && options.throwOnHttpError !== false) {
356
365
  throw new TransportError(
357
- `HTTP ${response.status}: ${response.body || "Request failed"}`,
366
+ `Upstream request failed with status ${response.status}`,
358
367
  {
359
368
  status: response.status,
360
369
  },
@@ -367,7 +376,7 @@ function createSessionFetcher(
367
376
  throw error;
368
377
  }
369
378
 
370
- throw new TransportError(`Network error: ${getErrorMessage(error)}`, {
379
+ throw new TransportError("Network error", {
371
380
  status: 0,
372
381
  cause: error instanceof Error ? error : undefined,
373
382
  });
@@ -215,9 +215,8 @@ function toErrorResponse(
215
215
  return {
216
216
  error: {
217
217
  code: error.code ?? "provider_error",
218
- message: error.message,
218
+ message: publicProviderErrorMessage(error),
219
219
  ...(requestId ? { requestId } : {}),
220
- ...(error.fix ? { fix: error.fix } : {}),
221
220
  ...(error instanceof TransportError && error.status
222
221
  ? { details: { upstreamStatus: error.status } }
223
222
  : {}),
@@ -245,6 +244,21 @@ function toErrorResponse(
245
244
  };
246
245
  }
247
246
 
247
+ function publicProviderErrorMessage(error: ProviderError): string {
248
+ if (error instanceof TransportError) {
249
+ if (error.code === "transport_timeout") return "Request timed out";
250
+ if (error.code === "transport_network_error") return "Network error";
251
+ if (error.code === "upstream_http_error" && error.status) {
252
+ return `Upstream request failed with status ${error.status}`;
253
+ }
254
+ if (error.status) {
255
+ return `Upstream request failed with status ${error.status}`;
256
+ }
257
+ return "Upstream request failed";
258
+ }
259
+ return error.message;
260
+ }
261
+
248
262
  function toStatusCode(error: unknown): 400 | 404 | 500 | 502 | 504 {
249
263
  if (error instanceof z.ZodError) {
250
264
  return 400;
@@ -255,7 +269,7 @@ function toStatusCode(error: unknown): 400 | 404 | 500 | 502 | 504 {
255
269
  }
256
270
 
257
271
  if (error instanceof ProviderError) {
258
- if (error.code === "NOT_FOUND") {
272
+ if (error.code === "NOT_FOUND" || error.code === "NO_DATA") {
259
273
  return 404;
260
274
  }
261
275
 
package/src/types.ts CHANGED
@@ -82,7 +82,6 @@ export const OPERATION_TIMEOUT_MS_MAX = 60_000;
82
82
 
83
83
  export interface OperationRelationships {
84
84
  alternatives?: string[];
85
- chainsWith?: string[];
86
85
  }
87
86
 
88
87
  /**
@@ -97,7 +96,15 @@ export interface OperationRelationships {
97
96
  */
98
97
 
99
98
  /** Polling intervals supported by the health-monitor runtime. */
100
- export type ProbeInterval = "30s" | "1m" | "3m" | "5m" | "15m" | "30m" | "1h";
99
+ export type ProbeInterval =
100
+ | "30s"
101
+ | "1m"
102
+ | "3m"
103
+ | "5m"
104
+ | "15m"
105
+ | "30m"
106
+ | "1h"
107
+ | "24h";
101
108
 
102
109
  export const PROBE_INTERVALS: readonly ProbeInterval[] = [
103
110
  "30s",
@@ -107,6 +114,7 @@ export const PROBE_INTERVALS: readonly ProbeInterval[] = [
107
114
  "15m",
108
115
  "30m",
109
116
  "1h",
117
+ "24h",
110
118
  ] as const;
111
119
 
112
120
  /**
@@ -222,6 +230,13 @@ export interface ProviderHealthMonitorConfig {
222
230
  * `requiresConnection: true`.
223
231
  */
224
232
  requiredSecrets?: string[];
233
+ /**
234
+ * Runtime probe overrides keyed by probe id (for example
235
+ * "catchtable/auth-flow" or "catchtable/waiting-lifecycle"). Use this for
236
+ * health-monitor probes that are provider-scoped or cross-operation and
237
+ * therefore cannot declare an `OperationDefinition.healthCheck.interval`.
238
+ */
239
+ probeOverrides?: Record<string, HealthMonitorProbeOverride>;
225
240
  /**
226
241
  * Override the default service account ID for this provider's probes.
227
242
  * Defaults to the runtime's `APIFUSE_SERVICE_ACCOUNT_ID` env var.
@@ -229,6 +244,11 @@ export interface ProviderHealthMonitorConfig {
229
244
  serviceAccount?: string;
230
245
  }
231
246
 
247
+ export interface HealthMonitorProbeOverride {
248
+ /** Optional runtime interval override. Must be one of PROBE_INTERVALS. */
249
+ interval?: ProbeInterval;
250
+ }
251
+
232
252
  export interface OperationErrorCode {
233
253
  code: string;
234
254
  status?: number;
@@ -300,9 +320,15 @@ export interface TlsFetchOptions extends RequestOptions {
300
320
  method?: string;
301
321
  body?: string | Buffer;
302
322
  profile?: string;
323
+ /**
324
+ * Defaults to true. Set to false when callers need to inspect upstream
325
+ * non-2xx bodies themselves instead of converting them to TransportError.
326
+ */
327
+ throwOnHttpError?: boolean;
303
328
  tls?: {
304
329
  ja3?: string;
305
330
  h2?: Record<string, unknown>;
331
+ insecureSkipVerify?: boolean;
306
332
  };
307
333
  headerOrder?: string[];
308
334
  }
package/src/composite.ts DELETED
@@ -1,43 +0,0 @@
1
- import type { infer as ZodInfer, ZodType } from "zod";
2
-
3
- export interface ClarifyResponse {
4
- _type: "clarify";
5
- question: string;
6
- missing: Array<{ name: string; description: string }>;
7
- }
8
-
9
- export interface CompositeContext {
10
- chain: {
11
- call: <T>(operationKey: string, params: unknown) => Promise<T>;
12
- };
13
- clarify: (opts: {
14
- question: string;
15
- missing: Array<{ name: string; description: string }>;
16
- }) => ClarifyResponse;
17
- setSlot: (name: string, value: unknown) => void;
18
- }
19
-
20
- export interface CompositeOperationDefinition<
21
- TInput extends ZodType = ZodType,
22
- TOutput extends ZodType = ZodType,
23
- > {
24
- id: string;
25
- description: string;
26
- input: TInput;
27
- output: TOutput;
28
- tags?: string[];
29
- chainsWith?: string[];
30
- steps: (
31
- ctx: CompositeContext,
32
- input: ZodInfer<TInput>,
33
- ) => Promise<ZodInfer<TOutput> | ClarifyResponse>;
34
- }
35
-
36
- export function defineCompositeOperation<
37
- TInput extends ZodType,
38
- TOutput extends ZodType,
39
- >(
40
- config: CompositeOperationDefinition<TInput, TOutput>,
41
- ): CompositeOperationDefinition<TInput, TOutput> {
42
- return config;
43
- }