@apifuse/provider-sdk 2.2.0-beta.20 → 2.2.0-beta.22

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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # @apifuse/provider-sdk Changelog
2
2
 
3
+ ## 2.2.0-beta.22
4
+
5
+ - Release candidate for main commit b8bf920b5ca053d1bf43018167fd4eedff01700d.
6
+
7
+ ## Unreleased
8
+
9
+ - Added the `thrown-error-code-undeclared` authoring lint (warning level): `apifuse check` now statically flags literal `ProviderError`/`ValidationError` codes that are neither SDK-registered nor declared in any operation's `docs.errorCodes`, surfacing the runtime `unregistered_provider_error_code` signal at check time. The canonical SDK code→status mapping moved to `SDK_STATUS_MAPPED_PROVIDER_ERROR_CODES` in `error-resolution.ts`, shared by the runtime status resolver and the lint.
10
+
11
+ ## 2.2.0-beta.21
12
+
13
+ - Release candidate for main commit 00f61024fb18db39711dc5076c4621508baa49f5.
14
+
3
15
  ## 2.2.0-beta.20
4
16
 
5
17
  - Release candidate for main commit 8043d2ef0047e431aace750d8abca2e1149ec1d6.
@@ -110,6 +122,7 @@
110
122
 
111
123
  ## Unreleased
112
124
 
125
+ - Upstream the platform monorepo's beta.16 dist patch: OAuth2-proxied auth ceremony (`createOAuth2ProxiedStart`, `OAUTH2_PROXIED_PKCE_VERIFIER_KEY`, `APIFUSE__AUTH_PROXY__URL` origin key, proxied redirect/callback turns) and the provider runtime state upgrades it depends on (in-memory compareAndSet with quota-aware write policy, Redis CAS/set Lua scripts with entry quotas and legacy index migration). The monorepo drops `patchedDependencies` once it pins this release.
113
126
  - **honest-provider-error-contract (phase 2):** `UPSTREAM_REJECTED` is a registered code family serving HTTP 409 with `retryable: false` — deterministic upstream business refusals are no longer 502s. Operation-declared `docs.errorCodes` may now use 409/410/422. Public error envelopes carry a `source` field (`client` | `upstream_rule` | `upstream_failure` | `apifuse`) derived from the observability category. Taxonomy version bumps to `2026-08-07` with the `upstream_rejected`, `dependency_unavailable`, `unsupported_transport`, and `client_cancelled` categories (409/410/422 map to `upstream_rejected`), matching the platform monorepo SoT. The `unregistered_provider_error_code` signal now carries a `signalFix` pointing at `docs.errorCodes` declaration.
114
127
  - **Breaking for custom native gateway adapters:** `NativeGatewayProxySynthesisInput` now includes an injected `credentials` resolver and selected `protocol`; synthesizers may return promises and structured skip reasons, and `resolveNativeGatewayProxy` is async. Default callers retain env-backed behavior. Native transport now supports both HTTP CONNECT and SOCKS5, defaults per vendor with an explicit runtime override, registers smartproxy allocation ahead of nodemaven when declared in that order, and reports every exhausted vendor reason without exposing proxy credentials.
115
128
  - Honor operation `docs.errorCodes` at runtime: declared provider-owned statuses and retryability now drive the HTTP envelope, observability header, and structured log; invalid statuses fail `defineProvider`, declared codes no longer emit the unregistered-code signal, and `TransportError` status-preservation workarounds are obsolete.
@@ -1,5 +1,7 @@
1
1
  import type { AuthFlowDefinition, AuthTurn } from "../types.js";
2
2
  type JsonObject = Record<string, unknown>;
3
+ export declare const OAUTH2_PROXIED_AUTH_PROXY_ORIGIN_ENV_KEY = "APIFUSE__AUTH_PROXY__URL";
4
+ export declare const OAUTH2_PROXIED_PKCE_VERIFIER_KEY = "__oauth2_proxied_pkce_verifier";
3
5
  export declare function validateCeremonyOutput(turn: unknown): AuthTurn;
4
6
  export declare function createOAuth2Ceremony(options: {
5
7
  authorizeUrl: string;
@@ -9,6 +11,12 @@ export declare function createOAuth2Ceremony(options: {
9
11
  scopes: string[];
10
12
  usePKCE?: boolean;
11
13
  }): AuthFlowDefinition;
14
+ /**
15
+ * Builds the start handler for a custom-scheme OAuth provider. The shared
16
+ * auth-proxy owns state minting and callback capture, while the provider owns
17
+ * token exchange and any following authentication turns.
18
+ */
19
+ export declare function createOAuth2ProxiedStart(options: import("../types.js").ProxiedOAuthConfig): import("../types.js").AuthFlowStartHandler;
12
20
  export declare function createDeviceFlowCeremony(options: {
13
21
  deviceCodeUrl: string;
14
22
  tokenUrl: string;
@@ -9,6 +9,15 @@ const ajv = new Ajv2020({ allErrors: true, strict: true, strictSchema: true });
9
9
  const validateAuthTurn = ajv.compile(AUTH_TURN_SCHEMA);
10
10
  const OAUTH2_STATE_KEY = "__oauth2_state";
11
11
  const OAUTH2_PKCE_VERIFIER_KEY = "__oauth2_pkce_verifier";
12
+ export const OAUTH2_PROXIED_AUTH_PROXY_ORIGIN_ENV_KEY = "APIFUSE__AUTH_PROXY__URL";
13
+ export const OAUTH2_PROXIED_PKCE_VERIFIER_KEY = "__oauth2_proxied_pkce_verifier";
14
+ const OAUTH2_PROXIED_RESERVED_AUTHORIZE_PARAMS = new Set([
15
+ "client_id",
16
+ "response_type",
17
+ "state",
18
+ "code_challenge",
19
+ "code_challenge_method",
20
+ ]);
12
21
  const DEVICE_FLOW_KEY = "__device_flow";
13
22
  const MAGIC_LINK_KEY = "__magic_link";
14
23
  const COMBINED_STAGE_KEY = "__combined_stage";
@@ -173,6 +182,54 @@ export function createOAuth2Ceremony(options) {
173
182
  abort: async () => validateCeremonyOutput(createTurn("abort", { hint: "OAuth flow aborted." })),
174
183
  };
175
184
  }
185
+ /**
186
+ * Builds the start handler for a custom-scheme OAuth provider. The shared
187
+ * auth-proxy owns state minting and callback capture, while the provider owns
188
+ * token exchange and any following authentication turns.
189
+ */
190
+ export function createOAuth2ProxiedStart(options) {
191
+ const pkce = options.pkce ?? "S256";
192
+ return (ctx) => runCeremonyHandler(async () => {
193
+ for (const key of Object.keys(options.authorizeParams ?? {})) {
194
+ if (OAUTH2_PROXIED_RESERVED_AUTHORIZE_PARAMS.has(key.toLowerCase())) {
195
+ throw new ValidationError(`OAuth2 proxied authorizeParams cannot override reserved parameter "${key}".`);
196
+ }
197
+ }
198
+ const proxyOrigin = ctx.env.get(OAUTH2_PROXIED_AUTH_PROXY_ORIGIN_ENV_KEY);
199
+ if (!proxyOrigin) {
200
+ throw new ProviderSecretError(`Missing required platform environment: ${OAUTH2_PROXIED_AUTH_PROXY_ORIGIN_ENV_KEY}`);
201
+ }
202
+ const clientId = getRequiredEnv(ctx, options.clientIdEnvKey);
203
+ if (!ctx.flowId) {
204
+ throw new ValidationError("OAuth2 proxied start requires the gateway flow id.");
205
+ }
206
+ const authorizePath = new URL(options.authorizeUrl).pathname;
207
+ const url = new URL(`/p/${encodeURIComponent(ctx.providerId)}/f/${encodeURIComponent(ctx.flowId)}${authorizePath}`, proxyOrigin);
208
+ url.searchParams.set("client_id", clientId);
209
+ url.searchParams.set("response_type", "code");
210
+ if (pkce === "S256") {
211
+ const verifier = createCodeVerifier();
212
+ ctx.context.set(OAUTH2_PROXIED_PKCE_VERIFIER_KEY, verifier);
213
+ url.searchParams.set("code_challenge", createCodeChallenge(verifier));
214
+ url.searchParams.set("code_challenge_method", "S256");
215
+ }
216
+ for (const [key, value] of Object.entries(options.authorizeParams ?? {})) {
217
+ url.searchParams.set(key, value);
218
+ }
219
+ return createTurn("redirect", {
220
+ data: { url: url.toString() },
221
+ hint: "Open the provider authorization page to continue.",
222
+ expectedInput: {
223
+ type: "object",
224
+ required: ["code", "state"],
225
+ properties: {
226
+ code: { type: "string" },
227
+ state: { type: "string" },
228
+ },
229
+ },
230
+ });
231
+ }, "OAuth2 proxied start failed", ctx);
232
+ }
176
233
  export function createDeviceFlowCeremony(options) {
177
234
  return {
178
235
  start: (ctx) => runCeremonyHandler(async () => {
package/dist/define.js CHANGED
@@ -8,7 +8,36 @@ import { HEALTH_CHECK_DEGRADED_THRESHOLD_MS_MAX, HEALTH_CHECK_DEGRADED_THRESHOLD
8
8
  const CONNECTOR_ID_REGEX = /^[a-z][a-z0-9]*(-[a-z][a-z0-9]*)*$/;
9
9
  const OPERATION_ID_REGEX = /^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*$/;
10
10
  const VALID_RUNTIMES = ["standard", "shared", "browser"];
11
- const VALID_AUTH_MODES = ["none", "platform-managed", "credentials", "oauth2"];
11
+ const VALID_AUTH_MODES = [
12
+ "none",
13
+ "platform-managed",
14
+ "credentials",
15
+ "oauth2",
16
+ "oauth2_proxied",
17
+ ];
18
+ const PROXIED_OAUTH_REQUIRED_FIELDS = [
19
+ "authorizeUrl",
20
+ "tokenUrl",
21
+ "customScheme",
22
+ "rewriteProfile",
23
+ "clientIdEnvKey",
24
+ ];
25
+ const PROXIED_OAUTH_ALLOWED_FIELDS = new Set([
26
+ ...PROXIED_OAUTH_REQUIRED_FIELDS,
27
+ "pkce",
28
+ "authorizeParams",
29
+ "tokenParams",
30
+ ]);
31
+ const PROXIED_OAUTH_RESERVED_AUTHORIZE_PARAMS = new Set([
32
+ "client_id",
33
+ "response_type",
34
+ "state",
35
+ "code_challenge",
36
+ "code_challenge_method",
37
+ ]);
38
+ const PROXIED_OAUTH_PROFILE_REGEX = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
39
+ const PROXIED_OAUTH_ENV_KEY_REGEX = /^[A-Z][A-Z0-9_]*__[A-Z0-9_]+$/;
40
+ const CUSTOM_SCHEME_REGEX = /^[A-Za-z][A-Za-z0-9+.-]*:\/\/\S+$/;
12
41
  const VALID_PROVIDER_ACCESS_VISIBILITIES = ["public", "early_access"];
13
42
  const VALID_PROVIDER_PROXY_MODES = ["disabled", "optional", "required"];
14
43
  const VALID_PROVIDER_PROXY_PROVIDERS = ["smartproxy", "nodemaven", "decodo", "custom"];
@@ -89,6 +118,76 @@ function assertLiteralField(value, field, validValues, providerId) {
89
118
  });
90
119
  }
91
120
  }
121
+ function validateProxiedOAuthParams(value, field, providerId) {
122
+ if (value === undefined)
123
+ return;
124
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
125
+ throw new ValidationError(`Provider "${providerId}" auth.proxied.${field} must be an object of string values.`);
126
+ }
127
+ for (const [key, paramValue] of Object.entries(value)) {
128
+ if (!key.trim() || typeof paramValue !== "string") {
129
+ throw new ValidationError(`Provider "${providerId}" auth.proxied.${field} must contain non-empty keys and string values.`);
130
+ }
131
+ if (field === "authorizeParams" &&
132
+ PROXIED_OAUTH_RESERVED_AUTHORIZE_PARAMS.has(key.toLowerCase())) {
133
+ throw new ValidationError(`Provider "${providerId}" auth.proxied.authorizeParams cannot override reserved parameter "${key}".`);
134
+ }
135
+ }
136
+ }
137
+ function validateProxiedOAuthAuth(auth, providerId) {
138
+ const proxied = auth.proxied;
139
+ if (auth.mode !== "oauth2_proxied") {
140
+ if (proxied !== undefined) {
141
+ throw new ValidationError(`Provider "${providerId}" auth.proxied is only valid when auth.mode is "oauth2_proxied".`);
142
+ }
143
+ return;
144
+ }
145
+ if (!proxied || typeof proxied !== "object" || Array.isArray(proxied)) {
146
+ throw new ValidationError(`Provider "${providerId}" with auth.mode "oauth2_proxied" must declare auth.proxied.`);
147
+ }
148
+ const config = Object.fromEntries(Object.entries(proxied));
149
+ for (const key of Object.keys(config)) {
150
+ if (!PROXIED_OAUTH_ALLOWED_FIELDS.has(key)) {
151
+ throw new ValidationError(`Provider "${providerId}" has unknown auth.proxied field "${key}".`);
152
+ }
153
+ }
154
+ for (const field of PROXIED_OAUTH_REQUIRED_FIELDS) {
155
+ if (typeof config[field] !== "string" || !config[field].trim()) {
156
+ throw new ValidationError(`Provider "${providerId}" auth.proxied.${field} must be a non-empty string.`);
157
+ }
158
+ }
159
+ for (const field of ["authorizeUrl", "tokenUrl"]) {
160
+ try {
161
+ const endpoint = new URL(String(config[field]));
162
+ if (endpoint.protocol !== "https:" ||
163
+ endpoint.username ||
164
+ endpoint.password ||
165
+ endpoint.hash) {
166
+ throw new Error("invalid endpoint");
167
+ }
168
+ }
169
+ catch {
170
+ throw new ValidationError(`Provider "${providerId}" auth.proxied.${field} must be an absolute HTTPS URL without credentials or a fragment.`);
171
+ }
172
+ }
173
+ const customScheme = String(config.customScheme);
174
+ if (!CUSTOM_SCHEME_REGEX.test(customScheme) ||
175
+ customScheme.toLowerCase().startsWith("http://") ||
176
+ customScheme.toLowerCase().startsWith("https://")) {
177
+ throw new ValidationError(`Provider "${providerId}" auth.proxied.customScheme must be a non-HTTP custom-scheme URL prefix.`);
178
+ }
179
+ if (!PROXIED_OAUTH_PROFILE_REGEX.test(String(config.rewriteProfile))) {
180
+ throw new ValidationError(`Provider "${providerId}" auth.proxied.rewriteProfile must be a kebab-case profile name.`);
181
+ }
182
+ if (!PROXIED_OAUTH_ENV_KEY_REGEX.test(String(config.clientIdEnvKey))) {
183
+ throw new ValidationError(`Provider "${providerId}" auth.proxied.clientIdEnvKey must be an APIFuse-style uppercase environment key.`);
184
+ }
185
+ if (config.pkce !== undefined && config.pkce !== "S256" && config.pkce !== "none") {
186
+ throw new ValidationError(`Provider "${providerId}" auth.proxied.pkce must be "S256" or "none".`);
187
+ }
188
+ validateProxiedOAuthParams(config.authorizeParams, "authorizeParams", providerId);
189
+ validateProxiedOAuthParams(config.tokenParams, "tokenParams", providerId);
190
+ }
92
191
  function validateProviderShape(config) {
93
192
  assertObjectConfig(config);
94
193
  assertRequiredField(config, "id");
@@ -101,6 +200,9 @@ function validateProviderShape(config) {
101
200
  const auth = config.auth;
102
201
  if (auth && typeof auth === "object" && "mode" in auth && typeof auth.mode === "string")
103
202
  assertLiteralField(auth.mode, "auth.mode", VALID_AUTH_MODES, String(config.id));
203
+ if (auth && typeof auth === "object" && !Array.isArray(auth)) {
204
+ validateProxiedOAuthAuth(Object.fromEntries(Object.entries(auth)), String(config.id));
205
+ }
104
206
  if (auth && typeof auth === "object" && "exchange" in auth) {
105
207
  throw new ProviderError(`Provider "${String(config.id)}" auth.exchange is not part of the Provider SDK auth contract`, {
106
208
  fix: "Use the single canonical auth interface: auth.flow. Gateway calls auth.flow.start/continue/poll/abort/refresh only and persists complete turn data.credential as-is, so put login/token/session exchange inside auth.flow.continue.",
@@ -1,2 +1,4 @@
1
+ import type { ProviderErrorStatus } from "./types.js";
1
2
  export declare const SDK_OWNED_PROVIDER_ERROR_CODES: Set<string>;
2
3
  export declare const SDK_RUNTIME_OWNED_ERROR_CODES: Set<string>;
4
+ export declare const SDK_STATUS_MAPPED_PROVIDER_ERROR_CODES: ReadonlyMap<string, ProviderErrorStatus>;
@@ -88,3 +88,30 @@ export const SDK_RUNTIME_OWNED_ERROR_CODES = new Set([
88
88
  "NOT_FOUND",
89
89
  "not_found",
90
90
  ]);
91
+ // Canonical SDK status mapping for recognized provider-thrown error codes.
92
+ // serve.ts toStatusCode consults this map (after operation-declared overrides
93
+ // for non-SDK-owned codes), and the authoring lint treats these codes as
94
+ // SDK-registered. Add new codes here instead of duplicating literals in
95
+ // either consumer.
96
+ export const SDK_STATUS_MAPPED_PROVIDER_ERROR_CODES = new Map([
97
+ ["AUTH_REQUIRED", 401],
98
+ ["reauth_required", 401],
99
+ // Unprovisioned declared secret: a deployment/config defect, never an
100
+ // upstream failure — explicit 400.
101
+ ["MISSING_SECRET", 400],
102
+ ["NOT_FOUND", 404],
103
+ ["not_found", 404],
104
+ ["NO_DATA", 404],
105
+ ["RATE_LIMITED", 429],
106
+ ["UPSTREAM_RATE_LIMIT", 429],
107
+ ["LIMITED_NUMBER_OF_SERVICE_REQUESTS_EXCEEDS_ERROR", 429],
108
+ // Deterministic upstream business refusal (honest-provider-error-
109
+ // contract): the upstream evaluated the request and said no under its
110
+ // own rules — a conflict with upstream state, never a 5xx.
111
+ ["UPSTREAM_REJECTED", 409],
112
+ ["UPSTREAM_ERROR", 502],
113
+ ["BLOCKED", 502],
114
+ ["STT_UNAVAILABLE", 503],
115
+ ["UNSUPPORTED_STT_BACKEND", 503],
116
+ ["STATEFUL_FORWARDING_REPLAY_CACHE_FULL", 503],
117
+ ]);
package/dist/index.d.ts CHANGED
@@ -38,7 +38,7 @@ export { APIFUSE_DESCRIPTION_KEY_META_KEY, APIFUSE_REDACTION_MARKER, APIFUSE_SEN
38
38
  export { createServerApp, ERROR_OBSERVABILITY_HEADER, type ServeOptions, serve, } from "./server/index.js";
39
39
  export { getStealthProfile, listStealthProfiles } from "./stealth/profiles.js";
40
40
  export * from "./stream.js";
41
- export type { ApiFuseResponse, AuthConfig, AuthContext, AuthFlowDefinition, AuthFlowInputHandler, AuthFlowStartHandler, AuthMode, AuthTurn, Bcp47Locale, BrowserEngine, BrowserOptions, BrowserResourceBody, BrowserResourceDecision, BrowserResourceMethod, BrowserResourcePolicy, BrowserResourceRequest, BrowserResourceRoute, ConnectionMode, ContextDeclaration, CookieJar, ContextScratchpad, CredentialContext, CredentialDeclaration, E164PhoneNumber, EnvContext, FlowContext, FlowContextStore, HealthCheckAssertionContext, HealthCheckCase, HealthCheckCaseResult, HealthCheckSuite, HealthCheckUnsupported, HealthJourneyDefinition, HealthJourneyEventContext, HealthJourneyGatewayContext, HealthJourneyJournalContext, HealthJourneyManualTriggerPolicy, HealthJourneyRunContext, HealthJourneyRunResult, HealthJourneySchedule, HealthScheduleRandomization, HealthJourneySmsContext, HealthJourneyStep, HttpClient, HttpMethod, HttpRedirectFailureReason, HttpRedirectPolicy, HttpRedirectPolicyMode, HttpResponse, HttpRetryOptions, HttpRetrySummary, HttpStreamResponse, IanaTimeZone, InferSchemaOutput, Iso3166Alpha2CountryCode, Iso4217CurrencyCode, Iso8601Duration, NativeContext, NativeNetworkClient, NativeNetworkCloseReason, NativeNetworkConnection, NativeNetworkConnectInput, NativeNetworkConnectOptions, NativeNetworkDynamicGrantOptions, NativeNetworkEgressGrant, NativeProviderConfig, NativeProviderContext, NativeProxyDrainHandler, NativeProxyEgressInfo, NativeProxyExpiringEvent, NativeProxyExpiringReason, NativeTcpDynamicEgressRule, NativeTcpEgressGrant, NativeTcpEgressRule, NativeTcpPortRange, NativeTcpTlsMode, NativeTlsConnectOptions, OperationAnnotations, OperationApprovalPolicy, OperationContractMetadata, OperationDefinition, OperationDeprecationMetadata, OperationDocMeta, OperationErrorCode, ProviderErrorStatus, OperationHandlerResult, OperationInputExample, OperationLifecycle, OperationObservabilityConfig, OperationObservabilitySensitiveConfig, OperationRelationships, OperationRiskClass, OperationSensitivePath, OperationToolRouterMetadata, OperationTransport, OperationTransportKind, ProbeInterval, ProviderAccessConfig, ProviderAccessVisibility, ProviderCache, ProviderCacheGetOrSetOptions, ProviderCacheKeyOptions, ProviderCacheLookupMeta, ProviderCacheResponseMeta, ProviderCacheResult, ProviderChoiceBindingOptions, ProviderChoiceContext, ProviderChoiceIssueOptions, ProviderChoiceParseOptions, ProviderContext, ProviderDefinition, ProviderDeploymentOverrides, ProviderFileRef, ProviderFilesContext, ProviderHealthMonitorConfig, ProviderHealthProbeConfig, ProviderLocale, ProviderLocaleKey, ProviderLocaleKeyInput, ProviderLogoProfile, ProviderLogoSource, ProviderMeta, ProviderProxyConfig, ProviderProxyMode, ProviderProxyPolicy, ProviderProxyProvider, ProviderProxySessionAffinity, ProviderPublicConnectionMode, ProviderPublicProfile, ProviderReviewed, ProviderResolvedFile, ProviderRuntimeState, ProviderSecretDeclaration, ProviderStateDurationString, ProviderStateNamespace, ProviderStreamEvent, ProviderSttConfig, ProviderSttMode, ProviderSupportLevel, RequestOptions, RedirectRunReason, Rfc3339Instant, SchemaLike, SmsOrigin, SmsOtpExtractionPattern, SmsOtpMatcherDefinition, SseMessage, StandardSchemaV1, StateCasResult, StateNamespaceOptions, StateValue, StateWriteOptions, StealthClient, StealthCookieStore, StealthCookieStoreV1, StealthFetchOptions, StealthPlatform, StealthProfile, StealthRedirectHop, StealthRedirectRunOptions, StealthRedirectRunResult, StealthResponse, StealthSession, StealthSessionCookies, SttAudioInput, SttContext, SttPromptPolicy, SttSegment, SttTranscribeMode, SttTranscribeRequest, SttTranscript, SttUnsupportedOptionPolicy, SttUsage, SttVerificationCodeOptions, SttWarning, TraceConfig, TraceSpan, VerificationCodeCandidate, VerificationCodeCandidateSource, VerificationCodeExtractionResult, } from "./types.js";
41
+ export type { ApiFuseResponse, AuthConfig, AuthContext, AuthFlowDefinition, AuthFlowInputHandler, AuthFlowStartHandler, AuthMode, AuthTurn, Bcp47Locale, BrowserEngine, BrowserOptions, BrowserResourceBody, BrowserResourceDecision, BrowserResourceMethod, BrowserResourcePolicy, BrowserResourceRequest, BrowserResourceRoute, ConnectionMode, ContextDeclaration, CookieJar, ContextScratchpad, CredentialContext, CredentialDeclaration, E164PhoneNumber, EnvContext, FlowContext, FlowContextStore, HealthCheckAssertionContext, HealthCheckCase, HealthCheckCaseResult, HealthCheckSuite, HealthCheckUnsupported, HealthJourneyDefinition, HealthJourneyEventContext, HealthJourneyGatewayContext, HealthJourneyJournalContext, HealthJourneyManualTriggerPolicy, HealthJourneyRunContext, HealthJourneyRunResult, HealthJourneySchedule, HealthScheduleRandomization, HealthJourneySmsContext, HealthJourneyStep, HttpClient, HttpMethod, HttpRedirectFailureReason, HttpRedirectPolicy, HttpRedirectPolicyMode, HttpResponse, HttpRetryOptions, HttpRetrySummary, HttpStreamResponse, IanaTimeZone, InferSchemaOutput, Iso3166Alpha2CountryCode, Iso4217CurrencyCode, Iso8601Duration, NativeContext, NativeNetworkClient, NativeNetworkCloseReason, NativeNetworkConnection, NativeNetworkConnectInput, NativeNetworkConnectOptions, NativeNetworkDynamicGrantOptions, NativeNetworkEgressGrant, NativeProviderConfig, NativeProviderContext, NativeProxyDrainHandler, NativeProxyEgressInfo, NativeProxyExpiringEvent, NativeProxyExpiringReason, NativeTcpDynamicEgressRule, NativeTcpEgressGrant, NativeTcpEgressRule, NativeTcpPortRange, NativeTcpTlsMode, NativeTlsConnectOptions, OperationAnnotations, OperationApprovalPolicy, OperationContractMetadata, OperationDefinition, OperationDeprecationMetadata, OperationDocMeta, OperationErrorCode, ProviderErrorStatus, OperationHandlerResult, OperationInputExample, OperationLifecycle, OperationObservabilityConfig, OperationObservabilitySensitiveConfig, OperationRelationships, OperationRiskClass, OperationSensitivePath, OperationToolRouterMetadata, OperationTransport, OperationTransportKind, ProbeInterval, ProviderAccessConfig, ProviderAccessVisibility, ProviderCache, ProviderCacheGetOrSetOptions, ProviderCacheKeyOptions, ProviderCacheLookupMeta, ProviderCacheResponseMeta, ProviderCacheResult, ProviderChoiceBindingOptions, ProviderChoiceContext, ProviderChoiceIssueOptions, ProviderChoiceParseOptions, ProviderContext, ProviderDefinition, ProviderDeploymentOverrides, ProviderFileRef, ProviderFilesContext, ProviderHealthMonitorConfig, ProviderHealthProbeConfig, ProviderLocale, ProviderLocaleKey, ProviderLocaleKeyInput, ProviderLogoProfile, ProviderLogoSource, ProviderMeta, ProviderProxyConfig, ProviderProxyMode, ProviderProxyPolicy, ProxiedOAuthConfig, ProviderProxyProvider, ProviderProxySessionAffinity, ProviderPublicConnectionMode, ProviderPublicProfile, ProviderReviewed, ProviderResolvedFile, ProviderRuntimeState, ProviderSecretDeclaration, ProviderStateDurationString, ProviderStateNamespace, ProviderStreamEvent, ProviderSttConfig, ProviderSttMode, ProviderSupportLevel, RequestOptions, RedirectRunReason, Rfc3339Instant, SchemaLike, SmsOrigin, SmsOtpExtractionPattern, SmsOtpMatcherDefinition, SseMessage, StandardSchemaV1, StateCasResult, StateNamespaceOptions, StateValue, StateWriteOptions, StealthClient, StealthCookieStore, StealthCookieStoreV1, StealthFetchOptions, StealthPlatform, StealthProfile, StealthRedirectHop, StealthRedirectRunOptions, StealthRedirectRunResult, StealthResponse, StealthSession, StealthSessionCookies, SttAudioInput, SttContext, SttPromptPolicy, SttSegment, SttTranscribeMode, SttTranscribeRequest, SttTranscript, SttUnsupportedOptionPolicy, SttUsage, SttVerificationCodeOptions, SttWarning, TraceConfig, TraceSpan, VerificationCodeCandidate, VerificationCodeCandidateSource, VerificationCodeExtractionResult, } from "./types.js";
42
42
  export { DEFAULT_OPERATION_TRANSPORT, HttpRetryAfterPolicy, HttpRetryDelayStrategy, HttpRetryJitter, HttpRetryPreset, HttpRetryUnsafeMethodPolicy, PROBE_INTERVALS, STREAM_CHUNK_BYTES_MAX, STREAM_CHUNK_BYTES_MIN, STREAM_HEARTBEAT_MS_MAX, STREAM_HEARTBEAT_MS_MIN, STREAM_IDLE_TIMEOUT_MS_MAX, STREAM_IDLE_TIMEOUT_MS_MIN, STREAM_MAX_DURATION_MS_MAX, STREAM_MAX_DURATION_MS_MIN, } from "./types.js";
43
43
  export * from "./utils/date.js";
44
44
  export * from "./utils/parse.js";
package/dist/lint.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- type AuthModeLike = "none" | "platform-managed" | "credentials" | "oauth2" | "api-key";
1
+ type AuthModeLike = "none" | "platform-managed" | "credentials" | "oauth2" | "oauth2_proxied" | "api-key";
2
2
  type ProviderAuthLike = {
3
3
  mode?: AuthModeLike;
4
4
  flow?: {
@@ -65,6 +65,11 @@ export declare function lintProvider(provider: {
65
65
  derivations?: Record<string, string>;
66
66
  handler?: unknown;
67
67
  source?: string;
68
+ docs?: {
69
+ errorCodes?: ReadonlyArray<{
70
+ code: string;
71
+ }>;
72
+ };
68
73
  }>;
69
74
  meta?: {
70
75
  contract?: ProviderContractMetaLike;
package/dist/lint.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { SDK_RUNTIME_OWNED_ERROR_CODES, SDK_STATUS_MAPPED_PROVIDER_ERROR_CODES, } from "./error-resolution.js";
1
2
  import { lintPublicSchemaFieldNames } from "./public-schema-field-lint.js";
2
3
  import { APIFUSE_DESCRIPTION_KEY_META_KEY, APIFUSE_SENSITIVE_META_KEY } from "./schema.js";
3
4
  const AUTH_OPERATION_ID_PATTERN = /^(?:auth[-_])?(?:login|exchange|continue|refresh|callback)(?:[-_]|$)/i;
@@ -587,6 +588,281 @@ function lintSelfHostedBrowserPatterns(provider, options) {
587
588
  }
588
589
  return diagnostics;
589
590
  }
591
+ const THROWN_ERROR_CONSTRUCTION_PATTERN = /new\s+(?:ProviderError|ValidationError)\s*\(/g;
592
+ const TEST_SOURCE_FILE_PATTERN = /(?:^|\/)(?:__tests__|__mocks__)\/|\.(?:test|spec)\.[cm]?[jt]sx?$/;
593
+ /**
594
+ * Skips a string literal starting at `startIndex` (which must point at the
595
+ * opening quote). Returns the index of the closing quote, or -1 when the
596
+ * literal is unterminated. Template literals handle nested `${...}`
597
+ * expressions, including strings inside them.
598
+ */
599
+ function skipStringLiteral(source, startIndex) {
600
+ const quote = source[startIndex];
601
+ for (let index = startIndex + 1; index < source.length; index++) {
602
+ const char = source[index];
603
+ if (char === "\\") {
604
+ index++;
605
+ continue;
606
+ }
607
+ if (quote === "`" && char === "$" && source[index + 1] === "{") {
608
+ index = skipTemplateExpression(source, index + 2);
609
+ if (index < 0) {
610
+ return -1;
611
+ }
612
+ continue;
613
+ }
614
+ if (char === quote) {
615
+ return index;
616
+ }
617
+ if (quote !== "`" && char === "\n") {
618
+ return -1;
619
+ }
620
+ }
621
+ return -1;
622
+ }
623
+ function skipTemplateExpression(source, startIndex) {
624
+ let depth = 1;
625
+ for (let index = startIndex; index < source.length; index++) {
626
+ const char = source[index];
627
+ if (char === '"' || char === "'" || char === "`") {
628
+ index = skipStringLiteral(source, index);
629
+ if (index < 0) {
630
+ return -1;
631
+ }
632
+ continue;
633
+ }
634
+ if (char === "{") {
635
+ depth++;
636
+ }
637
+ else if (char === "}") {
638
+ depth--;
639
+ if (depth === 0) {
640
+ return index;
641
+ }
642
+ }
643
+ }
644
+ return -1;
645
+ }
646
+ /**
647
+ * Extracts the argument text of a call whose opening paren has already been
648
+ * consumed (`startIndex` points just past it). Returns undefined when the
649
+ * call never closes in this source, which the caller treats as "skip
650
+ * silently" — this scanner is conservative by design.
651
+ */
652
+ function extractBalancedCallArguments(source, startIndex) {
653
+ let depth = 1;
654
+ for (let index = startIndex; index < source.length; index++) {
655
+ const char = source[index];
656
+ if (char === '"' || char === "'" || char === "`") {
657
+ index = skipStringLiteral(source, index);
658
+ if (index < 0) {
659
+ return undefined;
660
+ }
661
+ continue;
662
+ }
663
+ if (char === "/" && source[index + 1] === "/") {
664
+ const newline = source.indexOf("\n", index);
665
+ if (newline === -1) {
666
+ return undefined;
667
+ }
668
+ index = newline;
669
+ continue;
670
+ }
671
+ if (char === "/" && source[index + 1] === "*") {
672
+ const end = source.indexOf("*/", index + 2);
673
+ if (end === -1) {
674
+ return undefined;
675
+ }
676
+ index = end + 1;
677
+ continue;
678
+ }
679
+ if (char === "(") {
680
+ depth++;
681
+ }
682
+ else if (char === ")") {
683
+ depth--;
684
+ if (depth === 0) {
685
+ return source.slice(startIndex, index);
686
+ }
687
+ }
688
+ }
689
+ return undefined;
690
+ }
691
+ /**
692
+ * Collects literal string values of top-level `code:` properties inside a
693
+ * ProviderError/ValidationError options object. Only plain `"..."` / `'...'`
694
+ * literals at options-object depth count; computed codes (identifiers,
695
+ * ternaries, template substitutions, concatenations, escapes) are skipped
696
+ * silently so the rule never guesses.
697
+ */
698
+ function collectLiteralErrorCodeValues(args) {
699
+ const codes = [];
700
+ let braceDepth = 0;
701
+ let parenDepth = 0;
702
+ let bracketDepth = 0;
703
+ let previousSignificantChar = "";
704
+ for (let index = 0; index < args.length; index++) {
705
+ const char = args[index] ?? "";
706
+ if (char === '"' || char === "'" || char === "`") {
707
+ const end = skipStringLiteral(args, index);
708
+ if (end < 0) {
709
+ return codes;
710
+ }
711
+ index = end;
712
+ previousSignificantChar = char;
713
+ continue;
714
+ }
715
+ if (char === "/" && args[index + 1] === "/") {
716
+ const newline = args.indexOf("\n", index);
717
+ if (newline === -1) {
718
+ return codes;
719
+ }
720
+ index = newline;
721
+ continue;
722
+ }
723
+ if (char === "/" && args[index + 1] === "*") {
724
+ const end = args.indexOf("*/", index + 2);
725
+ if (end === -1) {
726
+ return codes;
727
+ }
728
+ index = end + 1;
729
+ continue;
730
+ }
731
+ if (/\s/.test(char)) {
732
+ continue;
733
+ }
734
+ if (char === "{") {
735
+ braceDepth++;
736
+ }
737
+ else if (char === "}") {
738
+ braceDepth--;
739
+ }
740
+ else if (char === "(") {
741
+ parenDepth++;
742
+ }
743
+ else if (char === ")") {
744
+ parenDepth--;
745
+ }
746
+ else if (char === "[") {
747
+ bracketDepth++;
748
+ }
749
+ else if (char === "]") {
750
+ bracketDepth--;
751
+ }
752
+ else if (braceDepth === 1 &&
753
+ parenDepth === 0 &&
754
+ bracketDepth === 0 &&
755
+ (previousSignificantChar === "{" || previousSignificantChar === ",") &&
756
+ args.startsWith("code", index)) {
757
+ let cursor = index + "code".length;
758
+ while (cursor < args.length && /\s/.test(args[cursor] ?? "")) {
759
+ cursor++;
760
+ }
761
+ if (args[cursor] === ":") {
762
+ cursor++;
763
+ while (cursor < args.length && /\s/.test(args[cursor] ?? "")) {
764
+ cursor++;
765
+ }
766
+ const quote = args[cursor];
767
+ if (quote === '"' || quote === "'") {
768
+ const end = skipStringLiteral(args, cursor);
769
+ if (end > cursor) {
770
+ const value = args.slice(cursor + 1, end);
771
+ let after = end + 1;
772
+ while (after < args.length && /\s/.test(args[after] ?? "")) {
773
+ after++;
774
+ }
775
+ const nextChar = after < args.length ? (args[after] ?? "") : "";
776
+ if (!value.includes("\\") && (nextChar === "," || nextChar === "}" || nextChar === "")) {
777
+ codes.push(value);
778
+ }
779
+ index = end;
780
+ previousSignificantChar = quote;
781
+ continue;
782
+ }
783
+ return codes;
784
+ }
785
+ }
786
+ }
787
+ previousSignificantChar = char;
788
+ }
789
+ return codes;
790
+ }
791
+ function collectLiteralThrownErrorCodes(source) {
792
+ const codes = [];
793
+ THROWN_ERROR_CONSTRUCTION_PATTERN.lastIndex = 0;
794
+ for (let match = THROWN_ERROR_CONSTRUCTION_PATTERN.exec(source); match; match = THROWN_ERROR_CONSTRUCTION_PATTERN.exec(source)) {
795
+ const argsStart = match.index + match[0].length;
796
+ const args = extractBalancedCallArguments(source, argsStart);
797
+ if (args !== undefined) {
798
+ codes.push(...collectLiteralErrorCodeValues(args));
799
+ }
800
+ THROWN_ERROR_CONSTRUCTION_PATTERN.lastIndex = argsStart;
801
+ }
802
+ return codes;
803
+ }
804
+ /**
805
+ * Static counterpart of the runtime `unregistered_provider_error_code`
806
+ * signal (honest-provider-error-contract Phase 3.5.5): flags
807
+ * `new ProviderError(...)` / `new ValidationError(...)` constructions whose
808
+ * literal `code` is neither SDK-registered (SDK_RUNTIME_OWNED_ERROR_CODES
809
+ * plus the canonical status-mapped codes shared with serve.ts toStatusCode)
810
+ * nor declared in any operation's docs.errorCodes. At runtime such a code
811
+ * serves HTTP 500 and emits the signal; this rule surfaces it at check time.
812
+ *
813
+ * A throw site cannot be attributed to a specific operation statically —
814
+ * providers routinely throw from helpers shared across operations — so this
815
+ * rule matches against the provider-level union of declared codes. That is
816
+ * the honest scope: it will not catch a code declared only on the "wrong"
817
+ * operation, and it never claims per-operation attribution it cannot prove.
818
+ * Only literal string codes are checked; computed/dynamic codes and test
819
+ * sources are skipped silently. Warning level: the long tail of existing
820
+ * providers converges gradually, so this must not fail `apifuse check`.
821
+ */
822
+ function lintUndeclaredThrownErrorCodes(provider) {
823
+ const knownCodes = new Set([
824
+ ...SDK_RUNTIME_OWNED_ERROR_CODES,
825
+ ...SDK_STATUS_MAPPED_PROVIDER_ERROR_CODES.keys(),
826
+ ]);
827
+ for (const operation of Object.values(provider.operations ?? {})) {
828
+ for (const entry of operation.docs?.errorCodes ?? []) {
829
+ if (typeof entry?.code === "string") {
830
+ knownCodes.add(entry.code);
831
+ }
832
+ }
833
+ }
834
+ const sources = [];
835
+ const sourceFiles = Object.entries(provider.providerSourceFiles ?? {}).filter(([filePath]) => !TEST_SOURCE_FILE_PATTERN.test(filePath));
836
+ if (sourceFiles.length > 0) {
837
+ for (const [filePath, source] of sourceFiles) {
838
+ sources.push({ field: `sourceFiles.${filePath}`, source });
839
+ }
840
+ }
841
+ else {
842
+ if (provider.authFlowSource) {
843
+ sources.push({ field: "auth.flow", source: provider.authFlowSource });
844
+ }
845
+ for (const [operationKey, operation] of Object.entries(provider.operations ?? {})) {
846
+ const source = getOperationSource(operation);
847
+ if (source) {
848
+ sources.push({ field: `operations.${operationKey}.handler`, source });
849
+ }
850
+ }
851
+ }
852
+ const diagnostics = [];
853
+ for (const { field, source } of sources) {
854
+ const undeclaredCodes = new Set(collectLiteralThrownErrorCodes(source).filter((code) => !knownCodes.has(code)));
855
+ for (const code of undeclaredCodes) {
856
+ diagnostics.push({
857
+ rule: "thrown-error-code-undeclared",
858
+ level: "warn",
859
+ field,
860
+ message: `Thrown error code "${code}" (${field}) is neither SDK-registered nor declared in any operation's docs.errorCodes; at runtime it serves HTTP 500 and emits the unregistered_provider_error_code signal. Declare it in the owning operation's docs.errorCodes with status and retryable.`,
861
+ });
862
+ }
863
+ }
864
+ return diagnostics;
865
+ }
590
866
  export function lintOperation(op) {
591
867
  const diagnostics = [];
592
868
  const description = op.description ?? "";
@@ -677,6 +953,7 @@ export function lintProvider(provider, options = {}) {
677
953
  ...lintCredentialWriteUsage(provider),
678
954
  ...lintPlaywrightDirectImports(provider),
679
955
  ...lintSelfHostedBrowserPatterns(provider, options),
956
+ ...lintUndeclaredThrownErrorCodes(provider),
680
957
  ];
681
958
  if (provider.operations) {
682
959
  const authMode = provider.auth?.mode;
@@ -7,6 +7,6 @@ export { AuthError, HttpRedirectError, isProviderError, isSessionExpiredError, i
7
7
  export { getProviderLocalePath, providerLocaleKey, qualifyProviderLocaleKey, } from "./i18n/index.js";
8
8
  export { type CreateProviderChoiceContextOptions, createProviderChoiceContext, createTestProviderChoiceContext, PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV, } from "./runtime/choice.js";
9
9
  export { APIFUSE_DESCRIPTION_KEY_META_KEY, APIFUSE_REDACTION_MARKER, APIFUSE_SENSITIVE_KIND_META_KEY, APIFUSE_SENSITIVE_META_KEY, collectSensitivePaths, describeKey, field, fields, isSensitiveSchema, redactPayload, type SensitiveFieldKind, type SensitiveFieldOptions, type SensitivePath, sensitive, z, } from "./schema.js";
10
- export type { AuthAbortData, AuthAbortRetry, AuthFlowTerminalContext, AuthMode, AuthSafeData, AuthSafeJson, FlowContext, HealthCheckAssertionContext, HealthCheckCase, HealthCheckSuite, HealthCheckUnsupported, HealthJourneyDefinition, HealthJourneyEventContext, HealthJourneyManualTriggerPolicy, HealthJourneyRunContext, HealthJourneyRunResult, HealthScheduleRandomization, HttpRedirectFailureReason, HttpRedirectPolicy, HttpRedirectPolicyMode, HttpRetryOptions, HttpRetrySummary, InferSchemaOutput, NativeContext, NativeNetworkClient, NativeNetworkCloseReason, NativeNetworkConnection, NativeNetworkConnectInput, NativeNetworkConnectOptions, NativeNetworkDynamicGrantOptions, NativeNetworkEgressGrant, NativeProviderConfig, NativeProviderContext, NativeProxyDrainHandler, NativeProxyEgressInfo, NativeProxyExpiringEvent, NativeProxyExpiringReason, NativeTcpDynamicEgressRule, NativeTcpEgressGrant, NativeTcpEgressRule, NativeTcpPortRange, NativeTcpTlsMode, NativeTlsConnectOptions, OperationApprovalPolicy, OperationContractMetadata, OperationDefinition, OperationDocMeta, OperationErrorCode, ProviderErrorStatus, OperationInputExample, OperationLifecycle, OperationObservabilityConfig, OperationObservabilitySensitiveConfig, OperationRelationships, OperationRiskClass, OperationSensitivePath, OperationToolRouterMetadata, OperationTransport, ProviderAccessVisibility, ProviderChoiceBindingOptions, ProviderChoiceContext, ProviderChoiceIssueOptions, ProviderChoiceParseOptions, ProviderContext, ProviderDefinition, ProviderDeploymentOverrides, ProviderFileRef, ProviderFilesContext, ProviderLocale, ProviderLocaleKey, ProviderLocaleKeyInput, ProviderLogoProfile, ProviderProxyPolicy, ProviderPublicConnectionMode, ProviderPublicProfile, ProviderResolvedFile, ProviderRuntimeState, ProviderStateDurationString, ProviderStateNamespace, ProviderSupportLevel, RedirectRunReason, SchemaLike, SmsOtpMatcherDefinition, StandardSchemaV1, StateCasResult, StateNamespaceOptions, StateValue, StateWriteOptions, } from "./types.js";
10
+ export type { AuthAbortData, AuthAbortRetry, AuthFlowTerminalContext, AuthMode, AuthSafeData, AuthSafeJson, FlowContext, HealthCheckAssertionContext, HealthCheckCase, HealthCheckSuite, HealthCheckUnsupported, HealthJourneyDefinition, HealthJourneyEventContext, HealthJourneyManualTriggerPolicy, HealthJourneyRunContext, HealthJourneyRunResult, HealthScheduleRandomization, HttpRedirectFailureReason, HttpRedirectPolicy, HttpRedirectPolicyMode, HttpRetryOptions, HttpRetrySummary, InferSchemaOutput, NativeContext, NativeNetworkClient, NativeNetworkCloseReason, NativeNetworkConnection, NativeNetworkConnectInput, NativeNetworkConnectOptions, NativeNetworkDynamicGrantOptions, NativeNetworkEgressGrant, NativeProviderConfig, NativeProviderContext, NativeProxyDrainHandler, NativeProxyEgressInfo, NativeProxyExpiringEvent, NativeProxyExpiringReason, NativeTcpDynamicEgressRule, NativeTcpEgressGrant, NativeTcpEgressRule, NativeTcpPortRange, NativeTcpTlsMode, NativeTlsConnectOptions, OperationApprovalPolicy, OperationContractMetadata, OperationDefinition, OperationDocMeta, OperationErrorCode, ProviderErrorStatus, OperationInputExample, OperationLifecycle, OperationObservabilityConfig, OperationObservabilitySensitiveConfig, OperationRelationships, OperationRiskClass, OperationSensitivePath, OperationToolRouterMetadata, OperationTransport, ProviderAccessVisibility, ProviderChoiceBindingOptions, ProviderChoiceContext, ProviderChoiceIssueOptions, ProviderChoiceParseOptions, ProviderContext, ProviderDefinition, ProviderDeploymentOverrides, ProviderFileRef, ProviderFilesContext, ProviderLocale, ProviderLocaleKey, ProviderLocaleKeyInput, ProviderLogoProfile, ProviderProxyPolicy, ProxiedOAuthConfig, ProviderPublicConnectionMode, ProviderPublicProfile, ProviderResolvedFile, ProviderRuntimeState, ProviderStateDurationString, ProviderStateNamespace, ProviderSupportLevel, RedirectRunReason, SchemaLike, SmsOtpMatcherDefinition, StandardSchemaV1, StateCasResult, StateNamespaceOptions, StateValue, StateWriteOptions, } from "./types.js";
11
11
  export { createNativeNetworkClient, createEnvVendorCredentialResolver, deriveNativeCredentialAffinityKey, NativeEgressGrantExpiredError, NativeEgressNotDeclaredError, NativeIdleTimeoutError, NativeNetworkError, NativeProxyExpiredError, resolveNativeGatewayProxy, type NativeGatewayProxy, type NativeGatewayProxyResolutionInput, type NativeGatewayProxySkipReason, type NativeGatewayProxySynthesizer, type NativeGatewayProxySynthesisResult, type NativeGatewayProxySynthesisInput, type NativeNetworkClientOptions, type NativeNetworkErrorCode, type VendorCredentialLookup, type VendorCredentialResolver, } from "./runtime/native-network.js";
12
12
  export { HttpRetryAfterPolicy, HttpRetryDelayStrategy, HttpRetryJitter, HttpRetryPreset, HttpRetryUnsafeMethodPolicy, } from "./types.js";
@@ -1,6 +1,7 @@
1
1
  import type { ContextScratchpad, EnvContext, FlowContext, HttpClient, StealthClient, SttContext } from "../types.js";
2
2
  export declare function createScratchpad(allowedKeys: string[], initial?: Record<string, unknown>): ContextScratchpad;
3
3
  export declare function createFlowContext(options: {
4
+ flowId?: string;
4
5
  http: HttpClient;
5
6
  stealth: StealthClient;
6
7
  env: EnvContext;
@@ -32,6 +32,7 @@ export function createScratchpad(allowedKeys, initial = {}) {
32
32
  }
33
33
  export function createFlowContext(options) {
34
34
  return {
35
+ flowId: options.flowId,
35
36
  connectionId: options.connectionId,
36
37
  externalRef: options.externalRef,
37
38
  tenantId: options.tenantId,