@apifuse/provider-sdk 2.1.0-beta.9 → 2.2.0-beta.10
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 +240 -0
- package/CHANGELOG.md +93 -0
- package/README.md +26 -10
- package/SUBMISSION.md +11 -12
- package/bin/apifuse-check.ts +44 -59
- package/bin/apifuse-create.ts +1 -1
- package/bin/apifuse-dev.ts +27 -52
- package/bin/apifuse-pack-check.ts +36 -0
- 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 +53 -70
- package/bin/apifuse-submit-check.ts +2177 -353
- package/bin/apifuse-sync-assets.ts +117 -0
- package/bin/apifuse.ts +1 -1
- package/bin/submit-check-delimited-text.ts +50 -0
- package/bin/submit-check-xml-semantics.ts +204 -0
- package/bin/submit-check-xml.ts +134 -0
- package/dist/auth-turn/auth-turn.v1.schema.json +89 -0
- package/dist/auth-turn/fixtures/invalid/empty-kind.json +4 -0
- package/dist/auth-turn/fixtures/invalid/expires-at-not-string.json +5 -0
- package/dist/auth-turn/fixtures/invalid/missing-kind.json +3 -0
- package/dist/auth-turn/fixtures/invalid/missing-turn-id.json +3 -0
- package/dist/auth-turn/fixtures/invalid/timing-unknown-field.json +7 -0
- package/dist/auth-turn/fixtures/invalid/turn-id-snake-case.json +4 -0
- package/dist/auth-turn/fixtures/invalid/unknown-top-level-field.json +5 -0
- package/dist/auth-turn/fixtures/valid/abort.json +8 -0
- package/dist/auth-turn/fixtures/valid/challenge.json +17 -0
- package/dist/auth-turn/fixtures/valid/complete.json +13 -0
- package/dist/auth-turn/fixtures/valid/form.json +14 -0
- package/dist/auth-turn/fixtures/valid/message.json +13 -0
- package/dist/auth-turn/fixtures/valid/multi_choice.json +15 -0
- package/dist/auth-turn/fixtures/valid/pending.json +5 -0
- package/dist/auth-turn/fixtures/valid/poll.json +9 -0
- package/dist/auth-turn/fixtures/valid/redirect.json +16 -0
- package/dist/auth-turn/fixtures/valid/retry.json +8 -0
- package/dist/auth-turn/fixtures/valid/unknown-kind.json +7 -0
- package/dist/auth-turn/index.d.ts +195 -0
- package/dist/auth-turn/index.js +133 -0
- package/dist/auth.d.ts +76 -0
- package/dist/auth.js +427 -0
- package/dist/ceremonies/index.d.ts +1 -1
- package/dist/ceremonies/index.js +14 -48
- package/dist/cli/commands.d.ts +1 -1
- package/dist/cli/commands.js +8 -0
- package/dist/cli/create.d.ts +3 -0
- package/dist/cli/create.js +47 -33
- package/dist/cli/prompt-assets.d.ts +80 -0
- package/dist/cli/prompt-assets.js +743 -0
- package/dist/cli/templates/provider/.agents/skills/fixtures-and-recording/SKILL.md.tpl +58 -0
- package/dist/cli/templates/provider/.agents/skills/health-checks-and-fail-closed/SKILL.md.tpl +65 -0
- package/dist/cli/templates/provider/.agents/skills/normalization-standards/SKILL.md.tpl +57 -0
- package/dist/cli/templates/provider/.agents/skills/pagination-and-counts/SKILL.md.tpl +52 -0
- package/dist/cli/templates/provider/.agents/skills/upstream-contract-verification/SKILL.md.tpl +45 -0
- package/dist/cli/templates/provider/.agents/skills/upstream-notes/README.md.tpl +13 -0
- package/dist/cli/templates/provider/.dockerignore.tpl +22 -0
- package/dist/cli/templates/provider/.gitignore.tpl +22 -0
- package/dist/cli/templates/provider/AGENTS.md.tpl +96 -0
- package/dist/cli/templates/provider/Dockerfile.tpl +7 -0
- package/dist/cli/templates/provider/README.md.tpl +163 -0
- package/dist/cli/templates/provider/dev.ts.tpl +5 -0
- package/dist/cli/templates/provider/domain/README.md.tpl +3 -0
- package/dist/cli/templates/provider/index.test.ts.tpl +13 -0
- package/dist/cli/templates/provider/index.ts.tpl +15 -0
- package/dist/cli/templates/provider/mappers/README.md.tpl +3 -0
- package/dist/cli/templates/provider/meta.ts.tpl +7 -0
- package/dist/cli/templates/provider/operations/index.ts.tpl +5 -0
- package/dist/cli/templates/provider/operations/ping.ts.tpl +24 -0
- package/dist/cli/templates/provider/schemas/ping.ts.tpl +24 -0
- package/dist/cli/templates/provider/start.ts.tpl +5 -0
- package/dist/cli/templates/provider/upstream/README.md.tpl +3 -0
- package/dist/config/loader.d.ts +149 -8
- package/dist/config/loader.js +378 -89
- 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 +5 -6
- package/dist/define.d.ts +13 -1
- package/dist/define.js +245 -178
- package/dist/dev.d.ts +1 -1
- package/dist/dev.js +1 -1
- package/dist/errors.d.ts +4 -1
- package/dist/errors.js +48 -0
- 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 +44 -41
- package/dist/index.js +39 -36
- package/dist/lint.d.ts +1 -0
- package/dist/lint.js +35 -15
- package/dist/provider.d.ts +11 -9
- package/dist/provider.js +9 -8
- 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 +4 -2
- package/dist/runtime/browser.d.ts +1 -1
- package/dist/runtime/browser.js +214 -25
- 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 +31 -35
- 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 +15 -3
- package/dist/runtime/http.d.ts +2 -2
- package/dist/runtime/http.js +160 -344
- 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 +56 -19
- 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 +9 -7
- package/dist/runtime/proxy-nodemaven.d.ts +35 -0
- package/dist/runtime/proxy-nodemaven.js +128 -0
- package/dist/runtime/proxy-retry-policy.d.ts +40 -0
- package/dist/runtime/proxy-retry-policy.js +326 -0
- package/dist/runtime/proxy-telemetry.d.ts +2 -1
- package/dist/runtime/proxy-telemetry.js +55 -52
- 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/secrets.d.ts +27 -0
- package/dist/runtime/secrets.js +51 -0
- package/dist/runtime/state.d.ts +2 -2
- package/dist/runtime/state.js +15 -4
- package/dist/runtime/stealth.d.ts +7 -4
- package/dist/runtime/stealth.js +257 -215
- 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 -3
- package/dist/server/index.js +6 -2
- package/dist/server/self-test-input-tokens.d.ts +1 -0
- package/dist/server/self-test-input-tokens.js +37 -0
- package/dist/server/self-test-redaction.d.ts +20 -0
- package/dist/server/self-test-redaction.js +70 -0
- package/dist/server/self-test-token.d.ts +30 -0
- package/dist/server/self-test-token.js +50 -0
- package/dist/server/self-test.d.ts +199 -0
- package/dist/server/self-test.js +1113 -0
- package/dist/server/serve.d.ts +14 -3
- package/dist/server/serve.js +135 -64
- package/dist/server/types.d.ts +10 -9
- package/dist/server/types.js +3 -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 +12 -15
- package/dist/types.d.ts +237 -1
- package/dist/user-input.d.ts +30 -0
- package/dist/user-input.js +66 -0
- package/package.json +16 -5
- package/src/auth-turn/auth-turn.v1.schema.json +89 -0
- package/src/auth-turn/fixtures/invalid/empty-kind.json +4 -0
- package/src/auth-turn/fixtures/invalid/expires-at-not-string.json +5 -0
- package/src/auth-turn/fixtures/invalid/missing-kind.json +3 -0
- package/src/auth-turn/fixtures/invalid/missing-turn-id.json +3 -0
- package/src/auth-turn/fixtures/invalid/timing-unknown-field.json +7 -0
- package/src/auth-turn/fixtures/invalid/turn-id-snake-case.json +4 -0
- package/src/auth-turn/fixtures/invalid/unknown-top-level-field.json +5 -0
- package/src/auth-turn/fixtures/valid/abort.json +8 -0
- package/src/auth-turn/fixtures/valid/challenge.json +17 -0
- package/src/auth-turn/fixtures/valid/complete.json +13 -0
- package/src/auth-turn/fixtures/valid/form.json +14 -0
- package/src/auth-turn/fixtures/valid/message.json +13 -0
- package/src/auth-turn/fixtures/valid/multi_choice.json +15 -0
- package/src/auth-turn/fixtures/valid/pending.json +5 -0
- package/src/auth-turn/fixtures/valid/poll.json +9 -0
- package/src/auth-turn/fixtures/valid/redirect.json +16 -0
- package/src/auth-turn/fixtures/valid/retry.json +8 -0
- package/src/auth-turn/fixtures/valid/unknown-kind.json +7 -0
- package/src/auth-turn/index.ts +177 -0
- package/src/auth.ts +728 -0
- package/src/ceremonies/index.ts +33 -121
- package/src/cli/commands.ts +10 -0
- package/src/cli/create.ts +69 -99
- package/src/cli/prompt-assets.ts +865 -0
- package/src/cli/templates/provider/.agents/skills/fixtures-and-recording/SKILL.md.tpl +58 -0
- package/src/cli/templates/provider/.agents/skills/health-checks-and-fail-closed/SKILL.md.tpl +65 -0
- package/src/cli/templates/provider/.agents/skills/normalization-standards/SKILL.md.tpl +57 -0
- package/src/cli/templates/provider/.agents/skills/pagination-and-counts/SKILL.md.tpl +52 -0
- package/src/cli/templates/provider/.agents/skills/upstream-contract-verification/SKILL.md.tpl +45 -0
- package/src/cli/templates/provider/.agents/skills/upstream-notes/README.md.tpl +13 -0
- package/src/cli/templates/provider/AGENTS.md.tpl +96 -0
- package/src/cli/templates/provider/README.md.tpl +7 -4
- package/src/config/loader.ts +543 -208
- package/src/contract-serialization.ts +5 -11
- package/src/contract-types.ts +2 -2
- package/src/contract.ts +13 -28
- package/src/define.ts +397 -528
- package/src/dev.ts +4 -9
- package/src/errors.ts +58 -5
- 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 +64 -41
- package/src/lint.ts +122 -159
- package/src/provider.ts +40 -9
- package/src/public-schema-field-lint.ts +7 -33
- package/src/recipes/gov-api.ts +2 -5
- package/src/runtime/auth-flow.ts +6 -6
- package/src/runtime/browser.ts +320 -151
- package/src/runtime/cache.ts +20 -67
- package/src/runtime/choice.ts +79 -132
- package/src/runtime/credential.ts +2 -2
- package/src/runtime/env.ts +1 -1
- package/src/runtime/executor.ts +23 -20
- package/src/runtime/http.ts +216 -539
- package/src/runtime/insights.ts +15 -53
- package/src/runtime/instrumentation.ts +78 -65
- 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 +29 -42
- package/src/runtime/proxy-nodemaven.ts +178 -0
- package/src/runtime/proxy-retry-policy.ts +469 -0
- package/src/runtime/proxy-telemetry.ts +79 -77
- package/src/runtime/redis.ts +4 -12
- package/src/runtime/request-options.ts +4 -13
- package/src/runtime/secrets.ts +64 -0
- package/src/runtime/state.ts +41 -110
- package/src/runtime/stealth.ts +331 -369
- 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 +44 -3
- package/src/server/self-test-input-tokens.ts +46 -0
- package/src/server/self-test-redaction.ts +97 -0
- package/src/server/self-test-token.ts +70 -0
- package/src/server/self-test.ts +1450 -0
- package/src/server/serve.ts +206 -216
- package/src/server/types.ts +7 -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 +24 -64
- package/src/types.ts +274 -1
- package/src/user-input.ts +118 -0
package/src/server/serve.ts
CHANGED
|
@@ -3,61 +3,65 @@ import { join } from "node:path";
|
|
|
3
3
|
|
|
4
4
|
import { Hono } from "hono";
|
|
5
5
|
import { z } from "zod";
|
|
6
|
+
import { AuthAbortError, createAuthFlowHelpers } from "../auth.js";
|
|
6
7
|
import {
|
|
7
8
|
AuthError,
|
|
9
|
+
isProviderError,
|
|
10
|
+
isSessionExpiredError,
|
|
11
|
+
isTransportError,
|
|
8
12
|
ProviderError,
|
|
9
|
-
|
|
10
|
-
TransportError,
|
|
11
|
-
} from "../errors";
|
|
13
|
+
} from "../errors.js";
|
|
12
14
|
import {
|
|
13
15
|
loadProviderLocaleCatalogs,
|
|
14
16
|
localizeAuthTurn,
|
|
15
17
|
type ProviderLocaleCatalogMap,
|
|
16
|
-
} from "../i18n/catalog";
|
|
17
|
-
import type { ProviderLocale } from "../i18n/keys";
|
|
18
|
+
} from "../i18n/catalog.js";
|
|
19
|
+
import type { ProviderLocale } from "../i18n/keys.js";
|
|
18
20
|
import {
|
|
19
21
|
categoryForStatus,
|
|
20
22
|
isRetryableCategory,
|
|
21
23
|
PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
|
|
22
24
|
type ProviderErrorCategory,
|
|
23
|
-
} from "../observability";
|
|
24
|
-
import { createScratchpad } from "../runtime/auth-flow";
|
|
25
|
-
import { createBrowserClient } from "../runtime/browser";
|
|
26
|
-
import { createProviderCache } from "../runtime/cache";
|
|
25
|
+
} from "../observability.js";
|
|
26
|
+
import { createScratchpad } from "../runtime/auth-flow.js";
|
|
27
|
+
import { createBrowserClient } from "../runtime/browser.js";
|
|
28
|
+
import { createProviderCache } from "../runtime/cache.js";
|
|
27
29
|
import {
|
|
28
30
|
createProviderChoiceContext,
|
|
29
31
|
PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV,
|
|
30
|
-
} from "../runtime/choice";
|
|
31
|
-
import { createCredentialContext } from "../runtime/credential";
|
|
32
|
-
import { createEnvContext } from "../runtime/env";
|
|
33
|
-
import { executeOperation } from "../runtime/executor";
|
|
34
|
-
import { createHttpClient } from "../runtime/http";
|
|
35
|
-
import { wrapWithInstrumentation } from "../runtime/instrumentation";
|
|
36
|
-
import { getProviderBaseUrl } from "../runtime/provider";
|
|
32
|
+
} from "../runtime/choice.js";
|
|
33
|
+
import { createCredentialContext } from "../runtime/credential.js";
|
|
34
|
+
import { createEnvContext } from "../runtime/env.js";
|
|
35
|
+
import { executeOperation } from "../runtime/executor.js";
|
|
36
|
+
import { createHttpClient } from "../runtime/http.js";
|
|
37
|
+
import { wrapWithInstrumentation } from "../runtime/instrumentation.js";
|
|
38
|
+
import { getProviderBaseUrl } from "../runtime/provider.js";
|
|
37
39
|
import {
|
|
38
40
|
PROXY_AUTH_IP_DENIED_CODE,
|
|
39
41
|
PROXY_EDGE_AUTH_REJECTED_CODE,
|
|
40
42
|
PROXY_POOL_EXHAUSTED_CODE,
|
|
41
|
-
} from "../runtime/proxy-errors";
|
|
43
|
+
} from "../runtime/proxy-errors.js";
|
|
44
|
+
import { PROVIDER_TELEMETRY_HEADER, ProxyTelemetryCollector } from "../runtime/proxy-telemetry.js";
|
|
42
45
|
import {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
+
assertRequiredSecretsPresent,
|
|
47
|
+
listMissingRequiredSecrets,
|
|
48
|
+
MISSING_SECRET_CODE,
|
|
49
|
+
} from "../runtime/secrets.js";
|
|
46
50
|
import {
|
|
47
51
|
createProviderRuntimeStateFromEnv,
|
|
48
52
|
createUnsupportedProviderRuntimeState,
|
|
49
|
-
} from "../runtime/state";
|
|
50
|
-
import { createStealthClient } from "../runtime/stealth";
|
|
51
|
-
import { createSttClientFromEnv } from "../runtime/stt";
|
|
52
|
-
import { createTraceContext } from "../runtime/trace";
|
|
53
|
-
import { parseSchema } from "../schema";
|
|
54
|
-
import { getStealthProfile } from "../stealth/profiles";
|
|
53
|
+
} from "../runtime/state.js";
|
|
54
|
+
import { createStealthClient } from "../runtime/stealth.js";
|
|
55
|
+
import { createSttClientFromEnv } from "../runtime/stt.js";
|
|
56
|
+
import { createTraceContext } from "../runtime/trace.js";
|
|
57
|
+
import { parseSchema } from "../schema.js";
|
|
58
|
+
import { getStealthProfile } from "../stealth/profiles.js";
|
|
55
59
|
import {
|
|
56
60
|
APIFUSE_STREAM_DONE_EVENT,
|
|
57
61
|
APIFUSE_STREAM_ERROR_EVENT,
|
|
58
62
|
encodeSseEvent,
|
|
59
63
|
error as streamError,
|
|
60
|
-
} from "../stream";
|
|
64
|
+
} from "../stream.js";
|
|
61
65
|
import type {
|
|
62
66
|
AuthContext,
|
|
63
67
|
AuthTurn,
|
|
@@ -74,7 +78,14 @@ import type {
|
|
|
74
78
|
ProviderStreamEvent,
|
|
75
79
|
StealthClient,
|
|
76
80
|
SttContext,
|
|
77
|
-
} from "../types";
|
|
81
|
+
} from "../types.js";
|
|
82
|
+
import {
|
|
83
|
+
createSelfTestApp,
|
|
84
|
+
createSelfTestAuthFlowInvoke,
|
|
85
|
+
createSelfTestInvoke,
|
|
86
|
+
resolveSelfTestPort,
|
|
87
|
+
} from "./self-test.js";
|
|
88
|
+
import { resolveSelfTestMasterSecrets } from "./self-test-token.js";
|
|
78
89
|
import {
|
|
79
90
|
type AuthFlowRequest,
|
|
80
91
|
AuthFlowRequestSchema,
|
|
@@ -85,7 +96,7 @@ import {
|
|
|
85
96
|
OperationRequestSchema,
|
|
86
97
|
type OperationResponse,
|
|
87
98
|
type OperationSuccessResponse,
|
|
88
|
-
} from "./types";
|
|
99
|
+
} from "./types.js";
|
|
89
100
|
|
|
90
101
|
const DEFAULT_HOST = "0.0.0.0";
|
|
91
102
|
const DEFAULT_PORT = 3000;
|
|
@@ -149,9 +160,7 @@ function createStealthStub(): StealthClient {
|
|
|
149
160
|
};
|
|
150
161
|
}
|
|
151
162
|
|
|
152
|
-
function getProviderStealthBaseUrl(
|
|
153
|
-
provider: ProviderDefinition,
|
|
154
|
-
): string | undefined {
|
|
163
|
+
function getProviderStealthBaseUrl(provider: ProviderDefinition): string | undefined {
|
|
155
164
|
const baseUrl = getProviderBaseUrl(provider);
|
|
156
165
|
if (baseUrl) {
|
|
157
166
|
return baseUrl;
|
|
@@ -161,15 +170,10 @@ function getProviderStealthBaseUrl(
|
|
|
161
170
|
}
|
|
162
171
|
|
|
163
172
|
function getProviderStealthProfile(provider: ProviderDefinition) {
|
|
164
|
-
return provider.stealth?.profile
|
|
165
|
-
? getStealthProfile(provider.stealth.profile)
|
|
166
|
-
: undefined;
|
|
173
|
+
return provider.stealth?.profile ? getStealthProfile(provider.stealth.profile) : undefined;
|
|
167
174
|
}
|
|
168
175
|
|
|
169
|
-
function isProductionProviderBrowserMode(
|
|
170
|
-
provider: ProviderDefinition,
|
|
171
|
-
env = process.env,
|
|
172
|
-
): boolean {
|
|
176
|
+
function isProductionProviderBrowserMode(provider: ProviderDefinition, env = process.env): boolean {
|
|
173
177
|
if (provider.runtime !== "browser") {
|
|
174
178
|
return false;
|
|
175
179
|
}
|
|
@@ -178,9 +182,7 @@ function isProductionProviderBrowserMode(
|
|
|
178
182
|
return true;
|
|
179
183
|
}
|
|
180
184
|
|
|
181
|
-
return
|
|
182
|
-
env.NODE_ENV === "production" && env.APIFUSE__PROVIDER__ID === provider.id
|
|
183
|
-
);
|
|
185
|
+
return env.NODE_ENV === "production" && env.APIFUSE__PROVIDER__ID === provider.id;
|
|
184
186
|
}
|
|
185
187
|
|
|
186
188
|
export function resolveProviderProxyAffinityKey(
|
|
@@ -188,18 +190,19 @@ export function resolveProviderProxyAffinityKey(
|
|
|
188
190
|
request: OperationRequest,
|
|
189
191
|
operationId: string,
|
|
190
192
|
): string {
|
|
191
|
-
const connectionKey =
|
|
192
|
-
request.connection?.id ?? request.connection?.externalRef;
|
|
193
|
+
const connectionKey = resolveOperationConnectionId(request) ?? request.connection?.externalRef;
|
|
193
194
|
const affinity =
|
|
194
|
-
typeof provider.proxy === "object"
|
|
195
|
-
? provider.proxy.session?.affinity
|
|
196
|
-
: undefined;
|
|
195
|
+
typeof provider.proxy === "object" ? provider.proxy.session?.affinity : undefined;
|
|
197
196
|
if (affinity === "operation") {
|
|
198
197
|
return `${provider.id}/${operationId}`;
|
|
199
198
|
}
|
|
200
199
|
return connectionKey ?? provider.id;
|
|
201
200
|
}
|
|
202
201
|
|
|
202
|
+
function resolveOperationConnectionId(request: OperationRequest): string | undefined {
|
|
203
|
+
return request.connection?.id ?? request.connectionId;
|
|
204
|
+
}
|
|
205
|
+
|
|
203
206
|
function createProviderContext(
|
|
204
207
|
provider: ProviderDefinition,
|
|
205
208
|
request: OperationRequest,
|
|
@@ -213,11 +216,7 @@ function createProviderContext(
|
|
|
213
216
|
const stealthProfile = getProviderStealthProfile(provider);
|
|
214
217
|
const proxyClientOptions = {
|
|
215
218
|
upstream: { proxy: provider.proxy },
|
|
216
|
-
affinityKey: resolveProviderProxyAffinityKey(
|
|
217
|
-
provider,
|
|
218
|
-
request,
|
|
219
|
-
operationId,
|
|
220
|
-
),
|
|
219
|
+
affinityKey: resolveProviderProxyAffinityKey(provider, request, operationId),
|
|
221
220
|
telemetry: proxyTelemetry,
|
|
222
221
|
};
|
|
223
222
|
let wrappedContext: ProviderContext | undefined;
|
|
@@ -238,7 +237,7 @@ function createProviderContext(
|
|
|
238
237
|
values: request.connection?.secrets,
|
|
239
238
|
});
|
|
240
239
|
const requestContext = {
|
|
241
|
-
connectionId: request
|
|
240
|
+
connectionId: resolveOperationConnectionId(request),
|
|
242
241
|
headers: request.headers ?? {},
|
|
243
242
|
};
|
|
244
243
|
const context = wrapWithInstrumentation({
|
|
@@ -256,11 +255,7 @@ function createProviderContext(
|
|
|
256
255
|
state,
|
|
257
256
|
stealth: stealthBaseUrl
|
|
258
257
|
? stealthProfile
|
|
259
|
-
? createStealthClient(
|
|
260
|
-
stealthBaseUrl,
|
|
261
|
-
stealthProfile.name,
|
|
262
|
-
stealthClientOptions,
|
|
263
|
-
)
|
|
258
|
+
? createStealthClient(stealthBaseUrl, stealthProfile.name, stealthClientOptions)
|
|
264
259
|
: createStealthClient(stealthBaseUrl, stealthClientOptions)
|
|
265
260
|
: createStealthStub(),
|
|
266
261
|
browser:
|
|
@@ -329,6 +324,7 @@ function createAuthFlowContext(
|
|
|
329
324
|
provider: ProviderDefinition,
|
|
330
325
|
request: AuthFlowRequest,
|
|
331
326
|
options: ProviderServerOptions = {},
|
|
327
|
+
signal?: AbortSignal,
|
|
332
328
|
): {
|
|
333
329
|
context: FlowContext;
|
|
334
330
|
getPatch: () => Record<string, unknown | null> | undefined;
|
|
@@ -372,17 +368,14 @@ function createAuthFlowContext(
|
|
|
372
368
|
http: createHttpClient(baseUrl, proxyClientOptions),
|
|
373
369
|
stealth: stealthBaseUrl
|
|
374
370
|
? stealthProfile
|
|
375
|
-
? createStealthClient(
|
|
376
|
-
stealthBaseUrl,
|
|
377
|
-
stealthProfile.name,
|
|
378
|
-
stealthClientOptions,
|
|
379
|
-
)
|
|
371
|
+
? createStealthClient(stealthBaseUrl, stealthProfile.name, stealthClientOptions)
|
|
380
372
|
: createStealthClient(stealthBaseUrl, stealthClientOptions)
|
|
381
373
|
: createStealthStub(),
|
|
382
374
|
env: createEnvContext(provider.secrets?.map((secret) => secret.name)),
|
|
383
375
|
credential,
|
|
384
376
|
context: flowContextStore.context,
|
|
385
377
|
stt: options.stt ?? createSttClientFromEnv(provider.stt),
|
|
378
|
+
auth: createAuthFlowHelpers({ signal }),
|
|
386
379
|
},
|
|
387
380
|
getPatch: flowContextStore.getPatch,
|
|
388
381
|
};
|
|
@@ -420,6 +413,12 @@ export type ProviderServerLogEvent =
|
|
|
420
413
|
retryable?: boolean;
|
|
421
414
|
issues?: Array<{ path: string; code: string; message: string }>;
|
|
422
415
|
})
|
|
416
|
+
| {
|
|
417
|
+
level: "warn";
|
|
418
|
+
event: "provider_secrets_missing";
|
|
419
|
+
providerId: string;
|
|
420
|
+
missingSecrets: string[];
|
|
421
|
+
}
|
|
423
422
|
| {
|
|
424
423
|
level: "warn";
|
|
425
424
|
event: "provider_cleanup_failed";
|
|
@@ -488,11 +487,8 @@ function zodDetails(error: z.ZodError): Array<{
|
|
|
488
487
|
}));
|
|
489
488
|
}
|
|
490
489
|
|
|
491
|
-
function toErrorResponse(
|
|
492
|
-
error
|
|
493
|
-
requestId?: string,
|
|
494
|
-
): OperationErrorResponse {
|
|
495
|
-
if (error instanceof ProviderError) {
|
|
490
|
+
function toErrorResponse(error: unknown, requestId?: string): OperationErrorResponse {
|
|
491
|
+
if (isProviderError(error)) {
|
|
496
492
|
const details = publicProviderErrorDetails(error);
|
|
497
493
|
return {
|
|
498
494
|
error: {
|
|
@@ -516,11 +512,22 @@ function toErrorResponse(
|
|
|
516
512
|
};
|
|
517
513
|
}
|
|
518
514
|
|
|
515
|
+
// A masked internal error MUST NOT be advertised as retryable: without an
|
|
516
|
+
// explicit retryable:false the hub (bori provider-backed engine) defaults 5xx
|
|
517
|
+
// to retryable:true, which turns a deterministic pre-upstream crash into an
|
|
518
|
+
// infinite START->CONTINUE->restart loop (2026-07-22 catchtable reserve RCA).
|
|
519
|
+
// We still refuse to leak message/stack — only the error class name (or the
|
|
520
|
+
// primitive type for non-Error throwables) is surfaced for ops triage.
|
|
519
521
|
return {
|
|
520
522
|
error: {
|
|
521
523
|
code: "internal_error",
|
|
522
524
|
message: "Internal error",
|
|
523
525
|
...(requestId ? { requestId } : {}),
|
|
526
|
+
details: {
|
|
527
|
+
retryable: false,
|
|
528
|
+
category: "internal_error",
|
|
529
|
+
errorClass: error instanceof Error ? error.name : typeof error,
|
|
530
|
+
},
|
|
524
531
|
},
|
|
525
532
|
};
|
|
526
533
|
}
|
|
@@ -548,7 +555,12 @@ function isPlainRecord(value: unknown): value is Record<string, unknown> {
|
|
|
548
555
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
549
556
|
}
|
|
550
557
|
|
|
551
|
-
|
|
558
|
+
// Accepts `unknown` so the branded guards narrow cleanly from the top: the
|
|
559
|
+
// subtype error classes are structurally compatible with ProviderError, so
|
|
560
|
+
// narrowing from a ProviderError-typed value would collapse the negative branch
|
|
561
|
+
// to `never`. Narrowing from unknown avoids that while still recognizing errors
|
|
562
|
+
// from a duplicate SDK module instance.
|
|
563
|
+
function providerObservabilityDetails(error: unknown):
|
|
552
564
|
| {
|
|
553
565
|
category: ProviderErrorCategory;
|
|
554
566
|
taxonomyVersion: string;
|
|
@@ -561,14 +573,26 @@ function providerObservabilityDetails(error: ProviderError):
|
|
|
561
573
|
// operation (see design.md §4.3 D3). Without this branch the auth error would
|
|
562
574
|
// serialize as a bare 401 with no retryable/category, losing the refresh
|
|
563
575
|
// signal for exactly the retryOnAuthRefresh operations it is meant to enable.
|
|
564
|
-
if (error
|
|
576
|
+
if (isSessionExpiredError(error)) {
|
|
565
577
|
return {
|
|
566
578
|
category: error.options?.category ?? "credential_expired",
|
|
567
579
|
taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
|
|
568
580
|
retryable: error.options?.retryable ?? false,
|
|
569
581
|
};
|
|
570
582
|
}
|
|
571
|
-
|
|
583
|
+
// Missing-secret errors carry the canonical credential_unavailable category
|
|
584
|
+
// so Gateway/observability can attribute the failure to provisioning, not
|
|
585
|
+
// the upstream. Matched by code (not constructor) so both the SDK-owned
|
|
586
|
+
// runtime gate and any not-yet-migrated provider-thrown MISSING_SECRET
|
|
587
|
+
// serialize identically, including across duplicate SDK module instances.
|
|
588
|
+
if (isProviderError(error) && error.code === MISSING_SECRET_CODE) {
|
|
589
|
+
return {
|
|
590
|
+
category: error.options?.category ?? "credential_unavailable",
|
|
591
|
+
taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
|
|
592
|
+
retryable: error.options?.retryable ?? false,
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
if (!isTransportError(error)) {
|
|
572
596
|
return undefined;
|
|
573
597
|
}
|
|
574
598
|
const isProxyPoolCode =
|
|
@@ -601,7 +625,7 @@ function providerObservabilityDetails(error: ProviderError):
|
|
|
601
625
|
}
|
|
602
626
|
|
|
603
627
|
function publicProviderErrorMessage(error: ProviderError): string {
|
|
604
|
-
if (error
|
|
628
|
+
if (isTransportError(error)) {
|
|
605
629
|
if (error.code === PROXY_AUTH_IP_DENIED_CODE) {
|
|
606
630
|
return error.message;
|
|
607
631
|
}
|
|
@@ -624,22 +648,24 @@ function publicProviderErrorMessage(error: ProviderError): string {
|
|
|
624
648
|
return error.message;
|
|
625
649
|
}
|
|
626
650
|
|
|
627
|
-
function toStatusCode(
|
|
628
|
-
error: unknown,
|
|
629
|
-
): 400 | 401 | 404 | 429 | 500 | 502 | 503 | 504 {
|
|
651
|
+
function toStatusCode(error: unknown): 400 | 401 | 404 | 429 | 500 | 502 | 503 | 504 {
|
|
630
652
|
if (error instanceof z.ZodError) {
|
|
631
653
|
return 400;
|
|
632
654
|
}
|
|
633
655
|
|
|
634
|
-
if (error
|
|
656
|
+
if (isTransportError(error)) {
|
|
635
657
|
return error.code === "transport_timeout" ? 504 : 502;
|
|
636
658
|
}
|
|
637
659
|
|
|
638
|
-
if (error
|
|
660
|
+
if (isProviderError(error)) {
|
|
639
661
|
switch (error.code) {
|
|
640
662
|
case "AUTH_REQUIRED":
|
|
641
663
|
case "reauth_required":
|
|
642
664
|
return 401;
|
|
665
|
+
// Unprovisioned declared secret: a deployment/config defect, never an
|
|
666
|
+
// upstream failure — explicit 400 (was only reached via fallthrough).
|
|
667
|
+
case MISSING_SECRET_CODE:
|
|
668
|
+
return 400;
|
|
643
669
|
case "NOT_FOUND":
|
|
644
670
|
case "not_found":
|
|
645
671
|
case "NO_DATA":
|
|
@@ -681,20 +707,15 @@ function logProviderError(
|
|
|
681
707
|
status: number,
|
|
682
708
|
cost: ProviderRequestCost,
|
|
683
709
|
): void {
|
|
684
|
-
const code =
|
|
685
|
-
error
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
: "internal_error";
|
|
710
|
+
const code = isProviderError(error)
|
|
711
|
+
? (error.code ?? "provider_error")
|
|
712
|
+
: error instanceof z.ZodError
|
|
713
|
+
? "invalid_request"
|
|
714
|
+
: "internal_error";
|
|
690
715
|
const errorClass = error instanceof Error ? error.name : typeof error;
|
|
691
716
|
const message = error instanceof Error ? error.message : String(error);
|
|
692
|
-
const details =
|
|
693
|
-
|
|
694
|
-
? providerObservabilityDetails(error)
|
|
695
|
-
: undefined;
|
|
696
|
-
const emit =
|
|
697
|
-
typeof logger === "function" ? logger : defaultProviderServerLogger;
|
|
717
|
+
const details = isProviderError(error) ? providerObservabilityDetails(error) : undefined;
|
|
718
|
+
const emit = typeof logger === "function" ? logger : defaultProviderServerLogger;
|
|
698
719
|
emit({
|
|
699
720
|
level: status >= 500 ? "error" : "warn",
|
|
700
721
|
event: "provider_request_failed",
|
|
@@ -707,7 +728,7 @@ function logProviderError(
|
|
|
707
728
|
code,
|
|
708
729
|
errorClass,
|
|
709
730
|
message,
|
|
710
|
-
...(error
|
|
731
|
+
...(isTransportError(error) && error.upstreamStatus
|
|
711
732
|
? { upstreamStatus: error.upstreamStatus }
|
|
712
733
|
: {}),
|
|
713
734
|
...(details
|
|
@@ -729,8 +750,7 @@ function logProviderCleanupError(
|
|
|
729
750
|
resource: "browser" | "stealth",
|
|
730
751
|
error: unknown,
|
|
731
752
|
): void {
|
|
732
|
-
const emit =
|
|
733
|
-
typeof logger === "function" ? logger : defaultProviderServerLogger;
|
|
753
|
+
const emit = typeof logger === "function" ? logger : defaultProviderServerLogger;
|
|
734
754
|
const errorClass = error instanceof Error ? error.name : typeof error;
|
|
735
755
|
const message = error instanceof Error ? error.message : String(error);
|
|
736
756
|
emit({
|
|
@@ -755,8 +775,7 @@ function logProviderSuccess(
|
|
|
755
775
|
status: number,
|
|
756
776
|
cost: ProviderRequestCost,
|
|
757
777
|
): void {
|
|
758
|
-
const emit =
|
|
759
|
-
typeof logger === "function" ? logger : defaultProviderServerLogger;
|
|
778
|
+
const emit = typeof logger === "function" ? logger : defaultProviderServerLogger;
|
|
760
779
|
emit({
|
|
761
780
|
level: "info",
|
|
762
781
|
event: "provider_request_completed",
|
|
@@ -802,18 +821,13 @@ function toJsonSuccessResponse(
|
|
|
802
821
|
};
|
|
803
822
|
}
|
|
804
823
|
|
|
805
|
-
function isAsyncIterable<T = unknown>(
|
|
806
|
-
value: unknown,
|
|
807
|
-
): value is AsyncIterable<T> {
|
|
824
|
+
function isAsyncIterable<T = unknown>(value: unknown): value is AsyncIterable<T> {
|
|
808
825
|
if (!value || typeof value !== "object") return false;
|
|
809
826
|
const iterator = Reflect.get(value, Symbol.asyncIterator);
|
|
810
827
|
return typeof iterator === "function";
|
|
811
828
|
}
|
|
812
829
|
|
|
813
|
-
function responseWithCleanup(
|
|
814
|
-
response: Response,
|
|
815
|
-
cleanup: RequestCleanup,
|
|
816
|
-
): Response {
|
|
830
|
+
function responseWithCleanup(response: Response, cleanup: RequestCleanup): Response {
|
|
817
831
|
if (!response.body) {
|
|
818
832
|
void cleanup();
|
|
819
833
|
return response;
|
|
@@ -862,10 +876,7 @@ async function validateSseEvent(
|
|
|
862
876
|
const transport = getSseTransport(operation);
|
|
863
877
|
const schema = transport?.events?.[event.event];
|
|
864
878
|
if (!schema) {
|
|
865
|
-
if (
|
|
866
|
-
event.event === APIFUSE_STREAM_ERROR_EVENT ||
|
|
867
|
-
event.event === APIFUSE_STREAM_DONE_EVENT
|
|
868
|
-
) {
|
|
879
|
+
if (event.event === APIFUSE_STREAM_ERROR_EVENT || event.event === APIFUSE_STREAM_DONE_EVENT) {
|
|
869
880
|
return event;
|
|
870
881
|
}
|
|
871
882
|
throw new ProviderError(
|
|
@@ -878,11 +889,7 @@ async function validateSseEvent(
|
|
|
878
889
|
},
|
|
879
890
|
);
|
|
880
891
|
}
|
|
881
|
-
const data = await parseSchema(
|
|
882
|
-
schema,
|
|
883
|
-
event.data,
|
|
884
|
-
`transport.events.${event.event}`,
|
|
885
|
-
);
|
|
892
|
+
const data = await parseSchema(schema, event.data, `transport.events.${event.event}`);
|
|
886
893
|
return { ...event, data };
|
|
887
894
|
}
|
|
888
895
|
|
|
@@ -902,8 +909,7 @@ function assertStreamPayloadWithinLimit(
|
|
|
902
909
|
throw new ProviderError(
|
|
903
910
|
`Stream ${kind} exceeded declared byte limit (${actualBytes} > ${maxBytes}).`,
|
|
904
911
|
{
|
|
905
|
-
code:
|
|
906
|
-
kind === "event" ? "STREAM_EVENT_TOO_LARGE" : "STREAM_CHUNK_TOO_LARGE",
|
|
912
|
+
code: kind === "event" ? "STREAM_EVENT_TOO_LARGE" : "STREAM_CHUNK_TOO_LARGE",
|
|
907
913
|
retryable: false,
|
|
908
914
|
category: "input_validation",
|
|
909
915
|
fix:
|
|
@@ -948,15 +954,10 @@ function toSseResponse(
|
|
|
948
954
|
const validated = await validateSseEvent(operation, next.value);
|
|
949
955
|
const encodedEvent = encodeSseEvent(validated);
|
|
950
956
|
const encodedBytes = encoder.encode(encodedEvent);
|
|
951
|
-
assertStreamPayloadWithinLimit(
|
|
952
|
-
encodedBytes.byteLength,
|
|
953
|
-
transport?.maxEventBytes,
|
|
954
|
-
"event",
|
|
955
|
-
);
|
|
957
|
+
assertStreamPayloadWithinLimit(encodedBytes.byteLength, transport?.maxEventBytes, "event");
|
|
956
958
|
controller.enqueue(encodedBytes);
|
|
957
959
|
} catch (error) {
|
|
958
|
-
const message =
|
|
959
|
-
error instanceof Error ? error.message : "Stream failed";
|
|
960
|
+
const message = error instanceof Error ? error.message : "Stream failed";
|
|
960
961
|
controller.enqueue(
|
|
961
962
|
encoder.encode(
|
|
962
963
|
encodeSseEvent(
|
|
@@ -1003,11 +1004,7 @@ function enforceStreamChunkLimit(
|
|
|
1003
1004
|
return;
|
|
1004
1005
|
}
|
|
1005
1006
|
if (value) {
|
|
1006
|
-
assertStreamPayloadWithinLimit(
|
|
1007
|
-
byteLength(value),
|
|
1008
|
-
maxChunkBytes,
|
|
1009
|
-
"chunk",
|
|
1010
|
-
);
|
|
1007
|
+
assertStreamPayloadWithinLimit(byteLength(value), maxChunkBytes, "chunk");
|
|
1011
1008
|
controller.enqueue(value);
|
|
1012
1009
|
}
|
|
1013
1010
|
} catch (error) {
|
|
@@ -1027,10 +1024,7 @@ function toStreamingResponse(
|
|
|
1027
1024
|
requestId?: string,
|
|
1028
1025
|
): Response {
|
|
1029
1026
|
const transport = operation.transport?.kind ?? "json";
|
|
1030
|
-
if (
|
|
1031
|
-
transport === "sse" &&
|
|
1032
|
-
(result instanceof Response || result instanceof ReadableStream)
|
|
1033
|
-
) {
|
|
1027
|
+
if (transport === "sse" && (result instanceof Response || result instanceof ReadableStream)) {
|
|
1034
1028
|
void cleanup();
|
|
1035
1029
|
throw new ProviderError(
|
|
1036
1030
|
"SSE operations must return an AsyncIterable of typed stream.event(...) values.",
|
|
@@ -1044,20 +1038,13 @@ function toStreamingResponse(
|
|
|
1044
1038
|
}
|
|
1045
1039
|
if (result instanceof Response) {
|
|
1046
1040
|
const httpTransport = getHttpStreamTransport(operation);
|
|
1047
|
-
if (
|
|
1048
|
-
httpTransport &&
|
|
1049
|
-
result.body &&
|
|
1050
|
-
httpTransport?.maxChunkBytes !== undefined
|
|
1051
|
-
) {
|
|
1041
|
+
if (httpTransport && result.body && httpTransport?.maxChunkBytes !== undefined) {
|
|
1052
1042
|
return responseWithCleanup(
|
|
1053
|
-
new Response(
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
statusText: result.statusText,
|
|
1059
|
-
},
|
|
1060
|
-
),
|
|
1043
|
+
new Response(enforceStreamChunkLimit(result.body, httpTransport.maxChunkBytes), {
|
|
1044
|
+
headers: result.headers,
|
|
1045
|
+
status: result.status,
|
|
1046
|
+
statusText: result.statusText,
|
|
1047
|
+
}),
|
|
1061
1048
|
cleanup,
|
|
1062
1049
|
);
|
|
1063
1050
|
}
|
|
@@ -1077,8 +1064,7 @@ function toStreamingResponse(
|
|
|
1077
1064
|
: {
|
|
1078
1065
|
"Content-Type":
|
|
1079
1066
|
operation.transport?.kind === "http-stream"
|
|
1080
|
-
? (operation.transport.contentType ??
|
|
1081
|
-
"application/octet-stream")
|
|
1067
|
+
? (operation.transport.contentType ?? "application/octet-stream")
|
|
1082
1068
|
: "application/octet-stream",
|
|
1083
1069
|
},
|
|
1084
1070
|
}),
|
|
@@ -1098,18 +1084,14 @@ function toStreamingResponse(
|
|
|
1098
1084
|
);
|
|
1099
1085
|
}
|
|
1100
1086
|
|
|
1101
|
-
function getSseTransport(
|
|
1102
|
-
operation: OperationDefinition,
|
|
1103
|
-
): OperationSseTransport | undefined {
|
|
1087
|
+
function getSseTransport(operation: OperationDefinition): OperationSseTransport | undefined {
|
|
1104
1088
|
return operation.transport?.kind === "sse" ? operation.transport : undefined;
|
|
1105
1089
|
}
|
|
1106
1090
|
|
|
1107
1091
|
function getHttpStreamTransport(
|
|
1108
1092
|
operation: OperationDefinition,
|
|
1109
1093
|
): OperationHttpStreamTransport | undefined {
|
|
1110
|
-
return operation.transport?.kind === "http-stream"
|
|
1111
|
-
? operation.transport
|
|
1112
|
-
: undefined;
|
|
1094
|
+
return operation.transport?.kind === "http-stream" ? operation.transport : undefined;
|
|
1113
1095
|
}
|
|
1114
1096
|
|
|
1115
1097
|
function toAuthFlowResponse(
|
|
@@ -1130,9 +1112,7 @@ function toAuthFlowResponse(
|
|
|
1130
1112
|
};
|
|
1131
1113
|
}
|
|
1132
1114
|
|
|
1133
|
-
function authFlowLocaleFromHeaders(
|
|
1134
|
-
headers?: Record<string, string>,
|
|
1135
|
-
): ProviderLocale {
|
|
1115
|
+
function authFlowLocaleFromHeaders(headers?: Record<string, string>): ProviderLocale {
|
|
1136
1116
|
const header = Object.entries(headers ?? {}).find(
|
|
1137
1117
|
([key]) => key.toLowerCase() === "accept-language",
|
|
1138
1118
|
)?.[1];
|
|
@@ -1150,9 +1130,7 @@ function isAuthFlowLocale(value: string | undefined): value is ProviderLocale {
|
|
|
1150
1130
|
}
|
|
1151
1131
|
|
|
1152
1132
|
function isAuthTurn(value: unknown): value is AuthTurn {
|
|
1153
|
-
return
|
|
1154
|
-
!!value && typeof value === "object" && "kind" in value && "turnId" in value
|
|
1155
|
-
);
|
|
1133
|
+
return !!value && typeof value === "object" && "kind" in value && "turnId" in value;
|
|
1156
1134
|
}
|
|
1157
1135
|
|
|
1158
1136
|
function loadAuthFlowLocaleCatalogs(
|
|
@@ -1189,10 +1167,7 @@ function materializeAuthFlowTurn(
|
|
|
1189
1167
|
});
|
|
1190
1168
|
}
|
|
1191
1169
|
|
|
1192
|
-
function withAuthRequestHeaders(
|
|
1193
|
-
request: AuthFlowRequest,
|
|
1194
|
-
headers: Headers,
|
|
1195
|
-
): AuthFlowRequest {
|
|
1170
|
+
function withAuthRequestHeaders(request: AuthFlowRequest, headers: Headers): AuthFlowRequest {
|
|
1196
1171
|
return {
|
|
1197
1172
|
...request,
|
|
1198
1173
|
headers: {
|
|
@@ -1210,17 +1185,9 @@ async function handleOperation(
|
|
|
1210
1185
|
state: ProviderRuntimeState = createUnsupportedProviderRuntimeState(),
|
|
1211
1186
|
proxyTelemetry?: ProxyTelemetryCollector,
|
|
1212
1187
|
): Promise<Response | OperationResponse> {
|
|
1213
|
-
const ctx = createProviderContext(
|
|
1214
|
-
provider,
|
|
1215
|
-
request,
|
|
1216
|
-
operationId,
|
|
1217
|
-
options,
|
|
1218
|
-
state,
|
|
1219
|
-
proxyTelemetry,
|
|
1220
|
-
);
|
|
1188
|
+
const ctx = createProviderContext(provider, request, operationId, options, state, proxyTelemetry);
|
|
1221
1189
|
const operation = provider.operations[operationId];
|
|
1222
|
-
const streaming =
|
|
1223
|
-
operation?.transport?.kind && operation.transport.kind !== "json";
|
|
1190
|
+
const streaming = operation?.transport?.kind && operation.transport.kind !== "json";
|
|
1224
1191
|
let cleanupCalled = false;
|
|
1225
1192
|
const cleanup = async () => {
|
|
1226
1193
|
if (cleanupCalled) return;
|
|
@@ -1251,12 +1218,7 @@ async function handleOperation(
|
|
|
1251
1218
|
}
|
|
1252
1219
|
};
|
|
1253
1220
|
try {
|
|
1254
|
-
const result = await executeOperation(
|
|
1255
|
-
provider,
|
|
1256
|
-
operationId,
|
|
1257
|
-
ctx,
|
|
1258
|
-
request.input,
|
|
1259
|
-
);
|
|
1221
|
+
const result = await executeOperation(provider, operationId, ctx, request.input);
|
|
1260
1222
|
if (streaming && operation) {
|
|
1261
1223
|
return toStreamingResponse(operation, result, cleanup, request.requestId);
|
|
1262
1224
|
}
|
|
@@ -1291,6 +1253,7 @@ async function handleAuthFlow(
|
|
|
1291
1253
|
request: AuthFlowRequest,
|
|
1292
1254
|
route: AuthRoute,
|
|
1293
1255
|
options: ProviderServerOptions = {},
|
|
1256
|
+
signal?: AbortSignal,
|
|
1294
1257
|
): Promise<Response | AuthFlowResponse> {
|
|
1295
1258
|
const flow = provider.auth?.flow;
|
|
1296
1259
|
if (!flow) {
|
|
@@ -1299,12 +1262,16 @@ async function handleAuthFlow(
|
|
|
1299
1262
|
});
|
|
1300
1263
|
}
|
|
1301
1264
|
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1265
|
+
// Same SDK-owned gate as executeOperation: OAuth/credentials ceremonies
|
|
1266
|
+
// depend on declared secrets (client ids/secrets), so fail structured before
|
|
1267
|
+
// any flow code runs instead of at whatever point the ceremony first reads
|
|
1268
|
+
// the env. `abort` stays exempt: a user must always be able to cancel a
|
|
1269
|
+
// stranded flow even when provisioning is broken.
|
|
1270
|
+
const { context, getPatch } = createAuthFlowContext(provider, request, options, signal);
|
|
1307
1271
|
try {
|
|
1272
|
+
if (route !== "abort") {
|
|
1273
|
+
assertRequiredSecretsPresent(provider, context.env);
|
|
1274
|
+
}
|
|
1308
1275
|
const result =
|
|
1309
1276
|
route === "start"
|
|
1310
1277
|
? await flow.start(context)
|
|
@@ -1336,6 +1303,11 @@ async function handleAuthFlow(
|
|
|
1336
1303
|
? materializeAuthFlowTurn(provider, request, result)
|
|
1337
1304
|
: result;
|
|
1338
1305
|
return toAuthFlowResponse(materializedResult, getPatch());
|
|
1306
|
+
} catch (error) {
|
|
1307
|
+
if (error instanceof AuthAbortError) {
|
|
1308
|
+
return toAuthFlowResponse(error.turn, getPatch());
|
|
1309
|
+
}
|
|
1310
|
+
throw error;
|
|
1339
1311
|
} finally {
|
|
1340
1312
|
context.stealth.close?.();
|
|
1341
1313
|
}
|
|
@@ -1354,6 +1326,24 @@ export function createServerApp(
|
|
|
1354
1326
|
allowMemoryFallback: options.allowMemoryStateFallback === true,
|
|
1355
1327
|
});
|
|
1356
1328
|
|
|
1329
|
+
// Boot-time visibility for unprovisioned declared secrets: emit a structured
|
|
1330
|
+
// warn so deploy tooling/alerting sees the gap the moment the pod boots,
|
|
1331
|
+
// instead of discovering it request-by-request. Deliberately log-only — a
|
|
1332
|
+
// boot crash would trade a structured MISSING_SECRET signal for
|
|
1333
|
+
// CrashLoopBackOff. Requests still fail closed via the executeOperation gate.
|
|
1334
|
+
const missingSecretsAtBoot = listMissingRequiredSecrets(
|
|
1335
|
+
provider,
|
|
1336
|
+
createEnvContext(provider.secrets?.map((secret) => secret.name)),
|
|
1337
|
+
);
|
|
1338
|
+
if (missingSecretsAtBoot.length > 0) {
|
|
1339
|
+
logger({
|
|
1340
|
+
level: "warn",
|
|
1341
|
+
event: "provider_secrets_missing",
|
|
1342
|
+
providerId: provider.id,
|
|
1343
|
+
missingSecrets: missingSecretsAtBoot,
|
|
1344
|
+
});
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1357
1347
|
app.notFound((c) =>
|
|
1358
1348
|
c.json(
|
|
1359
1349
|
{
|
|
@@ -1446,11 +1436,8 @@ export function createServerApp(
|
|
|
1446
1436
|
.clone()
|
|
1447
1437
|
.json()
|
|
1448
1438
|
.catch(() => undefined);
|
|
1449
|
-
const body = withAuthRequestHeaders(
|
|
1450
|
-
|
|
1451
|
-
c.req.raw.headers,
|
|
1452
|
-
);
|
|
1453
|
-
const response = await handleAuthFlow(provider, body, "start", options);
|
|
1439
|
+
const body = withAuthRequestHeaders(AuthFlowRequestSchema.parse(rawBody), c.req.raw.headers);
|
|
1440
|
+
const response = await handleAuthFlow(provider, body, "start", options, c.req.raw.signal);
|
|
1454
1441
|
logProviderSuccess(
|
|
1455
1442
|
logger,
|
|
1456
1443
|
provider,
|
|
@@ -1486,16 +1473,8 @@ export function createServerApp(
|
|
|
1486
1473
|
.clone()
|
|
1487
1474
|
.json()
|
|
1488
1475
|
.catch(() => undefined);
|
|
1489
|
-
const body = withAuthRequestHeaders(
|
|
1490
|
-
|
|
1491
|
-
c.req.raw.headers,
|
|
1492
|
-
);
|
|
1493
|
-
const response = await handleAuthFlow(
|
|
1494
|
-
provider,
|
|
1495
|
-
body,
|
|
1496
|
-
"continue",
|
|
1497
|
-
options,
|
|
1498
|
-
);
|
|
1476
|
+
const body = withAuthRequestHeaders(AuthFlowRequestSchema.parse(rawBody), c.req.raw.headers);
|
|
1477
|
+
const response = await handleAuthFlow(provider, body, "continue", options, c.req.raw.signal);
|
|
1499
1478
|
logProviderSuccess(
|
|
1500
1479
|
logger,
|
|
1501
1480
|
provider,
|
|
@@ -1531,11 +1510,8 @@ export function createServerApp(
|
|
|
1531
1510
|
.clone()
|
|
1532
1511
|
.json()
|
|
1533
1512
|
.catch(() => undefined);
|
|
1534
|
-
const body = withAuthRequestHeaders(
|
|
1535
|
-
|
|
1536
|
-
c.req.raw.headers,
|
|
1537
|
-
);
|
|
1538
|
-
const response = await handleAuthFlow(provider, body, "poll", options);
|
|
1513
|
+
const body = withAuthRequestHeaders(AuthFlowRequestSchema.parse(rawBody), c.req.raw.headers);
|
|
1514
|
+
const response = await handleAuthFlow(provider, body, "poll", options, c.req.raw.signal);
|
|
1539
1515
|
logProviderSuccess(
|
|
1540
1516
|
logger,
|
|
1541
1517
|
provider,
|
|
@@ -1571,11 +1547,8 @@ export function createServerApp(
|
|
|
1571
1547
|
.clone()
|
|
1572
1548
|
.json()
|
|
1573
1549
|
.catch(() => undefined);
|
|
1574
|
-
const body = withAuthRequestHeaders(
|
|
1575
|
-
|
|
1576
|
-
c.req.raw.headers,
|
|
1577
|
-
);
|
|
1578
|
-
const response = await handleAuthFlow(provider, body, "refresh", options);
|
|
1550
|
+
const body = withAuthRequestHeaders(AuthFlowRequestSchema.parse(rawBody), c.req.raw.headers);
|
|
1551
|
+
const response = await handleAuthFlow(provider, body, "refresh", options, c.req.raw.signal);
|
|
1579
1552
|
logProviderSuccess(
|
|
1580
1553
|
logger,
|
|
1581
1554
|
provider,
|
|
@@ -1611,11 +1584,8 @@ export function createServerApp(
|
|
|
1611
1584
|
.clone()
|
|
1612
1585
|
.json()
|
|
1613
1586
|
.catch(() => undefined);
|
|
1614
|
-
const body = withAuthRequestHeaders(
|
|
1615
|
-
|
|
1616
|
-
c.req.raw.headers,
|
|
1617
|
-
);
|
|
1618
|
-
const response = await handleAuthFlow(provider, body, "abort", options);
|
|
1587
|
+
const body = withAuthRequestHeaders(AuthFlowRequestSchema.parse(rawBody), c.req.raw.headers);
|
|
1588
|
+
const response = await handleAuthFlow(provider, body, "abort", options, c.req.raw.signal);
|
|
1619
1589
|
logProviderSuccess(
|
|
1620
1590
|
logger,
|
|
1621
1591
|
provider,
|
|
@@ -1675,6 +1645,12 @@ function getBunServeRuntime(): BunServeRuntime | undefined {
|
|
|
1675
1645
|
export interface ServeOptions extends ProviderServerOptions {
|
|
1676
1646
|
host?: string;
|
|
1677
1647
|
port?: number;
|
|
1648
|
+
/**
|
|
1649
|
+
* Port for the internal self-test listener (default 3001 or
|
|
1650
|
+
* APIFUSE__PROVIDER_RUNTIME__SELF_TEST_PORT). The listener only starts
|
|
1651
|
+
* when APIFUSE__PROVIDER_RUNTIME__SELF_TEST_MASTER_SECRET is present.
|
|
1652
|
+
*/
|
|
1653
|
+
selfTestPort?: number;
|
|
1678
1654
|
}
|
|
1679
1655
|
|
|
1680
1656
|
export async function serve(
|
|
@@ -1684,12 +1660,9 @@ export async function serve(
|
|
|
1684
1660
|
const bunRuntime = getBunServeRuntime();
|
|
1685
1661
|
|
|
1686
1662
|
if (bunRuntime === undefined) {
|
|
1687
|
-
throw new ProviderError(
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
code: "RUNTIME_UNSUPPORTED",
|
|
1691
|
-
},
|
|
1692
|
-
);
|
|
1663
|
+
throw new ProviderError("Bun runtime is required to start the provider server", {
|
|
1664
|
+
code: "RUNTIME_UNSUPPORTED",
|
|
1665
|
+
});
|
|
1693
1666
|
}
|
|
1694
1667
|
|
|
1695
1668
|
const app = createServerApp(provider, {
|
|
@@ -1702,5 +1675,22 @@ export async function serve(
|
|
|
1702
1675
|
hostname: options.host ?? DEFAULT_HOST,
|
|
1703
1676
|
fetch: app.fetch,
|
|
1704
1677
|
});
|
|
1678
|
+
|
|
1679
|
+
// Internal self-test listener (health dependency inversion): a SEPARATE
|
|
1680
|
+
// socket the tenant-facing gateway never dials. Off by default — it only
|
|
1681
|
+
// starts when the shared self-test master secret env is present.
|
|
1682
|
+
const selfTestSecrets = resolveSelfTestMasterSecrets();
|
|
1683
|
+
if (selfTestSecrets) {
|
|
1684
|
+
const selfTestApp = createSelfTestApp(provider, {
|
|
1685
|
+
secrets: selfTestSecrets,
|
|
1686
|
+
invoke: createSelfTestInvoke(app),
|
|
1687
|
+
authFlow: createSelfTestAuthFlowInvoke(app),
|
|
1688
|
+
});
|
|
1689
|
+
bunRuntime.serve({
|
|
1690
|
+
port: options.selfTestPort ?? resolveSelfTestPort(),
|
|
1691
|
+
hostname: options.host ?? DEFAULT_HOST,
|
|
1692
|
+
fetch: selfTestApp.fetch,
|
|
1693
|
+
});
|
|
1694
|
+
}
|
|
1705
1695
|
await Promise.resolve();
|
|
1706
1696
|
}
|