@apifuse/provider-sdk 2.2.0-beta.3 → 2.2.0-beta.31
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 +493 -5
- package/CHANGELOG.md +131 -1
- package/README.md +52 -6
- package/SUBMISSION.md +1 -1
- package/bin/apifuse-check.ts +106 -62
- package/bin/apifuse-create.ts +1 -1
- package/bin/apifuse-dev.ts +63 -55
- package/bin/apifuse-pack-check.ts +22 -2
- package/bin/apifuse-pack-smoke.ts +78 -82
- package/bin/apifuse-pack-types.ts +583 -0
- package/bin/apifuse-perf.ts +59 -140
- package/bin/apifuse-record.ts +698 -113
- package/bin/apifuse-submit-check.ts +517 -44
- 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.ts +1 -1
- package/dist/auth-turn/index.d.ts +4 -4
- package/dist/auth-turn/index.js +1 -1
- package/dist/auth.d.ts +16 -2
- package/dist/auth.js +76 -18
- package/dist/ceremonies/index.d.ts +9 -1
- package/dist/ceremonies/index.js +117 -29
- 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 +34 -35
- package/dist/cli/prompt-assets.d.ts +80 -0
- package/dist/cli/prompt-assets.js +743 -0
- package/dist/cli/templates/provider/AGENTS.md.tpl +17 -8
- package/dist/cli/templates/provider/README.md.tpl +4 -4
- package/dist/config/loader.d.ts +176 -17
- package/dist/config/loader.js +434 -161
- package/dist/contract-serialization.d.ts +2 -2
- package/dist/contract-serialization.js +7 -14
- package/dist/contract-types.d.ts +3 -2
- package/dist/contract.d.ts +3 -3
- package/dist/contract.js +6 -6
- package/dist/declaration-validation.d.ts +23 -0
- package/dist/declaration-validation.js +159 -0
- package/dist/define.d.ts +13 -1
- package/dist/define.js +391 -122
- package/dist/dev.d.ts +1 -1
- package/dist/dev.js +1 -1
- package/dist/error-resolution.d.ts +4 -0
- package/dist/error-resolution.js +123 -0
- package/dist/errors.d.ts +19 -1
- package/dist/errors.js +41 -3
- package/dist/fixture-sanitization.d.ts +26 -0
- package/dist/fixture-sanitization.js +216 -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 +50 -42
- package/dist/index.js +41 -37
- package/dist/lint.d.ts +6 -1
- package/dist/lint.js +370 -18
- package/dist/native-address.d.ts +43 -0
- package/dist/native-address.js +281 -0
- package/dist/native-egress-policy.d.ts +31 -0
- package/dist/native-egress-policy.js +288 -0
- package/dist/observability.d.ts +5 -2
- package/dist/observability.js +48 -1
- package/dist/provider.d.ts +13 -11
- package/dist/provider.js +10 -9
- package/dist/public-schema-field-lint.d.ts +1 -1
- package/dist/recipes/gov-api.js +1 -1
- package/dist/runtime/auth-flow.d.ts +3 -1
- package/dist/runtime/auth-flow.js +8 -3
- package/dist/runtime/browser.d.ts +1 -1
- package/dist/runtime/browser.js +138 -40
- package/dist/runtime/cache.d.ts +2 -1
- package/dist/runtime/cache.js +173 -23
- package/dist/runtime/choice-wordlist.d.ts +9 -0
- package/dist/runtime/choice-wordlist.js +138 -0
- package/dist/runtime/choice.d.ts +14 -1
- package/dist/runtime/choice.js +566 -101
- 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 +25 -3
- package/dist/runtime/http.d.ts +3 -2
- package/dist/runtime/http.js +517 -55
- 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 +371 -23
- package/dist/runtime/keyring.js +1 -1
- package/dist/runtime/namespace.js +1 -1
- package/dist/runtime/native-network-errors.d.ts +33 -0
- package/dist/runtime/native-network-errors.js +69 -0
- package/dist/runtime/native-network.d.ts +96 -0
- package/dist/runtime/native-network.js +1232 -0
- package/dist/runtime/ocr.d.ts +29 -0
- package/dist/runtime/ocr.js +440 -0
- 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 +56 -0
- package/dist/runtime/proxy-nodemaven.js +146 -0
- package/dist/runtime/proxy-retry-policy.d.ts +2 -2
- package/dist/runtime/proxy-retry-policy.js +2 -2
- package/dist/runtime/proxy-telemetry.d.ts +2 -1
- package/dist/runtime/proxy-telemetry.js +58 -52
- package/dist/runtime/redirects.d.ts +29 -0
- package/dist/runtime/redirects.js +36 -0
- package/dist/runtime/redis.d.ts +1 -1
- package/dist/runtime/redis.js +5 -5
- package/dist/runtime/request-options.d.ts +68 -1
- package/dist/runtime/request-options.js +548 -0
- package/dist/runtime/resolver-config.d.ts +6 -0
- package/dist/runtime/resolver-config.js +6 -0
- package/dist/runtime/resolver-public.d.ts +1 -0
- package/dist/runtime/resolver-public.js +1 -0
- package/dist/runtime/resolver-shared.d.ts +3 -0
- package/dist/runtime/resolver-shared.js +12 -0
- package/dist/runtime/resolver-vendors/bindings.d.ts +48 -0
- package/dist/runtime/resolver-vendors/bindings.js +40 -0
- package/dist/runtime/resolver-vendors/browser.d.ts +20 -0
- package/dist/runtime/resolver-vendors/browser.js +282 -0
- package/dist/runtime/resolver-vendors/hosts.d.ts +2 -0
- package/dist/runtime/resolver-vendors/hosts.js +33 -0
- package/dist/runtime/resolver-vendors/twocaptcha.d.ts +24 -0
- package/dist/runtime/resolver-vendors/twocaptcha.js +368 -0
- package/dist/runtime/resolver-vendors/types.d.ts +83 -0
- package/dist/runtime/resolver-vendors/types.js +69 -0
- package/dist/runtime/resolver.d.ts +59 -0
- package/dist/runtime/resolver.js +705 -0
- package/dist/runtime/secrets.d.ts +27 -0
- package/dist/runtime/secrets.js +51 -0
- package/dist/runtime/state.d.ts +5 -2
- package/dist/runtime/state.js +280 -74
- package/dist/runtime/stealth-cookies.d.ts +20 -0
- package/dist/runtime/stealth-cookies.js +111 -0
- package/dist/runtime/stealth.d.ts +30 -5
- package/dist/runtime/stealth.js +523 -259
- package/dist/runtime/stt.d.ts +1 -1
- package/dist/runtime/stt.js +12 -27
- package/dist/runtime/timeout.d.ts +5 -0
- package/dist/runtime/timeout.js +12 -0
- package/dist/runtime/trace.d.ts +2 -2
- package/dist/runtime/trace.js +2 -4
- package/dist/runtime/waterfall.d.ts +1 -1
- package/dist/schema.d.ts +1 -1
- package/dist/schema.js +7 -15
- package/dist/serve.d.ts +1 -1
- package/dist/serve.js +1 -1
- package/dist/server/index.d.ts +7 -7
- package/dist/server/index.js +6 -6
- package/dist/server/self-test-input-tokens.d.ts +2 -1
- package/dist/server/self-test-input-tokens.js +18 -14
- package/dist/server/self-test-redaction.d.ts +1 -1
- package/dist/server/self-test-redaction.js +1 -1
- package/dist/server/self-test.d.ts +117 -3
- package/dist/server/self-test.js +787 -151
- package/dist/server/serve-implementation.d.ts +210 -0
- package/dist/server/serve-implementation.js +2078 -0
- package/dist/server/serve.d.ts +1 -70
- package/dist/server/serve.js +1 -1143
- package/dist/server/types.d.ts +34 -9
- package/dist/server/types.js +8 -1
- package/dist/stateful/errors.d.ts +19 -0
- package/dist/stateful/errors.js +24 -0
- package/dist/stateful/http-provider-event-emitter.d.ts +40 -0
- package/dist/stateful/http-provider-event-emitter.js +237 -0
- package/dist/stateful/http-session-owner-registry.d.ts +44 -0
- package/dist/stateful/http-session-owner-registry.js +210 -0
- package/dist/stateful/index.d.ts +18 -0
- package/dist/stateful/index.js +18 -0
- package/dist/stateful/provider-event-delivery-failures.d.ts +32 -0
- package/dist/stateful/provider-event-delivery-failures.js +43 -0
- package/dist/stateful/provider-event-pipeline-metrics.d.ts +46 -0
- package/dist/stateful/provider-event-pipeline-metrics.js +48 -0
- package/dist/stateful/provider-event-pipeline.d.ts +50 -0
- package/dist/stateful/provider-event-pipeline.js +1 -0
- package/dist/stateful/provider-events.d.ts +101 -0
- package/dist/stateful/provider-events.js +289 -0
- package/dist/stateful/session-key.d.ts +15 -0
- package/dist/stateful/session-key.js +86 -0
- package/dist/stateful/stateful-provider-adapter-context.d.ts +5 -0
- package/dist/stateful/stateful-provider-adapter-context.js +42 -0
- package/dist/stateful/stateful-provider-adapter-metrics.d.ts +15 -0
- package/dist/stateful/stateful-provider-adapter-metrics.js +21 -0
- package/dist/stateful/stateful-provider-adapter.d.ts +98 -0
- package/dist/stateful/stateful-provider-adapter.js +287 -0
- package/dist/stateful/stateful-provider-observability.d.ts +62 -0
- package/dist/stateful/stateful-provider-observability.js +161 -0
- package/dist/stateful/stateful-provider-owner-forwarder.d.ts +41 -0
- package/dist/stateful/stateful-provider-owner-forwarder.js +215 -0
- package/dist/stateful/stateful-provider-runtime-context.d.ts +32 -0
- package/dist/stateful/stateful-provider-runtime-context.js +60 -0
- package/dist/stateful/stateful-provider-runtime-executor.d.ts +34 -0
- package/dist/stateful/stateful-provider-runtime-executor.js +52 -0
- package/dist/stateful/stateful-provider-session-routing.d.ts +67 -0
- package/dist/stateful/stateful-provider-session-routing.js +345 -0
- package/dist/stateful/stateful-provider-session-runtime.d.ts +98 -0
- package/dist/stateful/stateful-provider-session-runtime.js +245 -0
- package/dist/stateful-signing.d.ts +18 -0
- package/dist/stateful-signing.js +27 -0
- package/dist/stealth/profiles.d.ts +1 -1
- package/dist/stealth/profiles.js +21 -21
- package/dist/stream-evidence.d.ts +74 -0
- package/dist/stream-evidence.js +785 -0
- package/dist/stream.d.ts +1 -1
- package/dist/stream.js +7 -1
- package/dist/testing/index.d.ts +3 -2
- package/dist/testing/index.js +3 -2
- package/dist/testing/run.d.ts +32 -2
- package/dist/testing/run.js +488 -28
- package/dist/types.d.ts +566 -19
- package/dist/types.js +1 -0
- package/dist/user-input.d.ts +30 -0
- package/dist/user-input.js +66 -0
- package/package.json +42 -7
- package/src/auth-turn/index.ts +2 -2
- package/src/auth.ts +146 -86
- package/src/ceremonies/index.ts +167 -92
- package/src/cli/commands.ts +10 -0
- package/src/cli/create.ts +42 -35
- package/src/cli/prompt-assets.ts +865 -0
- package/src/cli/templates/provider/AGENTS.md.tpl +17 -8
- package/src/cli/templates/provider/README.md.tpl +4 -4
- package/src/config/loader.ts +667 -289
- package/src/contract-serialization.ts +10 -18
- package/src/contract-types.ts +3 -2
- package/src/contract.ts +14 -28
- package/src/declaration-validation.ts +202 -0
- package/src/define.ts +631 -495
- package/src/dev.ts +4 -9
- package/src/error-resolution.ts +128 -0
- package/src/errors.ts +56 -11
- package/src/fixture-sanitization.ts +247 -0
- 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 +158 -44
- package/src/lint.ts +488 -154
- package/src/native-address.ts +340 -0
- package/src/native-egress-policy.ts +358 -0
- package/src/observability.ts +51 -1
- package/src/provider.ts +66 -11
- package/src/public-schema-field-lint.ts +7 -33
- package/src/recipes/gov-api.ts +2 -5
- package/src/runtime/auth-flow.ts +13 -7
- package/src/runtime/browser.ts +252 -207
- package/src/runtime/cache.ts +209 -81
- package/src/runtime/choice-wordlist.ts +145 -0
- package/src/runtime/choice.ts +758 -197
- package/src/runtime/credential.ts +2 -2
- package/src/runtime/env.ts +1 -1
- package/src/runtime/executor.ts +37 -19
- package/src/runtime/http.ts +645 -65
- package/src/runtime/insights.ts +15 -53
- package/src/runtime/instrumentation.ts +530 -67
- package/src/runtime/keyring.ts +7 -19
- package/src/runtime/namespace.ts +2 -7
- package/src/runtime/native-network-errors.ts +99 -0
- package/src/runtime/native-network.ts +1605 -0
- package/src/runtime/ocr.ts +523 -0
- 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 +221 -0
- package/src/runtime/proxy-retry-policy.ts +3 -3
- package/src/runtime/proxy-telemetry.ts +84 -77
- package/src/runtime/redirects.ts +66 -0
- package/src/runtime/redis.ts +10 -13
- package/src/runtime/request-options.ts +679 -9
- package/src/runtime/resolver-config.ts +6 -0
- package/src/runtime/resolver-public.ts +18 -0
- package/src/runtime/resolver-shared.ts +17 -0
- package/src/runtime/resolver-vendors/bindings.ts +56 -0
- package/src/runtime/resolver-vendors/browser.ts +408 -0
- package/src/runtime/resolver-vendors/hosts.ts +38 -0
- package/src/runtime/resolver-vendors/twocaptcha.ts +500 -0
- package/src/runtime/resolver-vendors/types.ts +173 -0
- package/src/runtime/resolver.ts +1060 -0
- package/src/runtime/secrets.ts +64 -0
- package/src/runtime/state.ts +399 -161
- package/src/runtime/stealth-cookies.ts +132 -0
- package/src/runtime/stealth.ts +681 -295
- package/src/runtime/stt.ts +39 -113
- package/src/runtime/timeout.ts +18 -0
- package/src/runtime/trace.ts +14 -44
- package/src/runtime/waterfall.ts +5 -18
- package/src/schema.ts +23 -84
- package/src/serve.ts +6 -1
- package/src/server/index.ts +30 -7
- package/src/server/self-test-input-tokens.ts +29 -14
- package/src/server/self-test-redaction.ts +2 -2
- package/src/server/self-test.ts +1030 -180
- package/src/server/serve-implementation.ts +3062 -0
- package/src/server/serve.ts +1 -1781
- package/src/server/types.ts +12 -13
- package/src/stateful/README.md +146 -0
- package/src/stateful/errors.ts +35 -0
- package/src/stateful/http-provider-event-emitter.ts +314 -0
- package/src/stateful/http-session-owner-registry.ts +306 -0
- package/src/stateful/index.ts +18 -0
- package/src/stateful/provider-event-delivery-failures.ts +80 -0
- package/src/stateful/provider-event-pipeline-metrics.ts +95 -0
- package/src/stateful/provider-event-pipeline.ts +61 -0
- package/src/stateful/provider-events.ts +462 -0
- package/src/stateful/session-key.ts +111 -0
- package/src/stateful/stateful-provider-adapter-context.ts +59 -0
- package/src/stateful/stateful-provider-adapter-metrics.ts +48 -0
- package/src/stateful/stateful-provider-adapter.ts +562 -0
- package/src/stateful/stateful-provider-observability.ts +261 -0
- package/src/stateful/stateful-provider-owner-forwarder.ts +287 -0
- package/src/stateful/stateful-provider-runtime-context.ts +92 -0
- package/src/stateful/stateful-provider-runtime-executor.ts +96 -0
- package/src/stateful/stateful-provider-session-routing.ts +546 -0
- package/src/stateful/stateful-provider-session-runtime.ts +403 -0
- package/src/stateful-signing.ts +46 -0
- package/src/stealth/profiles.ts +27 -33
- package/src/stream-evidence.ts +988 -0
- package/src/stream.ts +16 -20
- package/src/testing/index.ts +11 -2
- package/src/testing/run.ts +668 -74
- package/src/types.ts +665 -35
- package/src/user-input.ts +118 -0
- package/dist/cli/templates/provider/CLAUDE.md.tpl +0 -1
- package/src/cli/templates/provider/CLAUDE.md.tpl +0 -1
- /package/dist/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
- /package/dist/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/health-checks-and-fail-closed/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-contract-verification/SKILL.md.tpl +0 -0
- /package/src/cli/templates/provider/{skills → .agents/skills}/upstream-notes/README.md.tpl +0 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
formatPromptAssetIssues,
|
|
5
|
+
installedSdkVersion,
|
|
6
|
+
syncPromptAssets,
|
|
7
|
+
verifyPromptAssets,
|
|
8
|
+
} from "../src/cli/prompt-assets.js";
|
|
9
|
+
import { resolveProviderRoot } from "./apifuse-check.js";
|
|
10
|
+
|
|
11
|
+
const HELP_TEXT = `Usage: apifuse sync-assets [path] [--check]
|
|
12
|
+
Example: apifuse sync-assets .
|
|
13
|
+
Default: apifuse sync-assets .
|
|
14
|
+
|
|
15
|
+
Regenerates the SDK-managed agent prompt assets for the installed SDK version:
|
|
16
|
+
AGENTS.md, .agents/skills/**, the CLAUDE.md/.claude/.codex symlinks, and the
|
|
17
|
+
.apifuse/prompt-assets.json manifest. Legacy top-level skills/ layouts are
|
|
18
|
+
migrated. Idempotent.
|
|
19
|
+
|
|
20
|
+
Options:
|
|
21
|
+
--check Verify only; exit 1 with a diff list when assets are stale/missing/modified
|
|
22
|
+
--help, -h Show this help`;
|
|
23
|
+
|
|
24
|
+
export async function main() {
|
|
25
|
+
const args = normalizeArgs(process.argv.slice(2));
|
|
26
|
+
|
|
27
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
28
|
+
console.log(HELP_TEXT);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let checkOnly = false;
|
|
33
|
+
let inputPath: string | undefined;
|
|
34
|
+
for (const arg of args) {
|
|
35
|
+
if (arg === "--check") {
|
|
36
|
+
checkOnly = true;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (arg.startsWith("-")) {
|
|
40
|
+
throw new Error(`Unknown option: ${arg}`);
|
|
41
|
+
}
|
|
42
|
+
if (inputPath !== undefined) {
|
|
43
|
+
throw new Error(`Unexpected argument: ${arg}`);
|
|
44
|
+
}
|
|
45
|
+
inputPath = arg;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const providerRoot = resolveProviderRoot(inputPath ?? ".");
|
|
49
|
+
|
|
50
|
+
if (checkOnly) {
|
|
51
|
+
const verification = verifyPromptAssets(providerRoot);
|
|
52
|
+
if (verification.ok) {
|
|
53
|
+
console.log(
|
|
54
|
+
`Prompt assets are in sync with the installed SDK (${installedSdkVersion()}): ${providerRoot}`,
|
|
55
|
+
);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
console.error(`Prompt assets are out of sync in ${providerRoot}:`);
|
|
59
|
+
for (const issue of formatPromptAssetIssues(verification)) {
|
|
60
|
+
console.error(` - ${issue}`);
|
|
61
|
+
}
|
|
62
|
+
console.error("\nRun `bun run sync-assets` (or `bunx apifuse sync-assets .`) to regenerate.");
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const result = syncPromptAssets(providerRoot);
|
|
67
|
+
|
|
68
|
+
// Honesty gate: writes alone never imply success. sync-assets intentionally
|
|
69
|
+
// PRESERVES (does not delete) unauthorized skills and symlinks under
|
|
70
|
+
// .agents/skills, so it can return changed:false while verify still fails.
|
|
71
|
+
// Re-verify AFTER writing and let the true post-sync state drive the exit
|
|
72
|
+
// code — never claim success while the freshness gate would reject the tree.
|
|
73
|
+
const verification = verifyPromptAssets(providerRoot);
|
|
74
|
+
|
|
75
|
+
if (result.changed) {
|
|
76
|
+
for (const removed of result.removed) {
|
|
77
|
+
console.log(`removed ${removed}`);
|
|
78
|
+
}
|
|
79
|
+
for (const wrote of result.wroteFiles) {
|
|
80
|
+
console.log(`wrote ${wrote}`);
|
|
81
|
+
}
|
|
82
|
+
for (const link of result.createdSymlinks) {
|
|
83
|
+
console.log(`symlink ${link}`);
|
|
84
|
+
}
|
|
85
|
+
console.log(`manifest ${result.manifestPath} (sdkVersion ${installedSdkVersion()})`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!verification.ok) {
|
|
89
|
+
console.error(`\nPrompt assets are still out of sync in ${providerRoot}:`);
|
|
90
|
+
for (const issue of formatPromptAssetIssues(verification)) {
|
|
91
|
+
console.error(` - ${issue}`);
|
|
92
|
+
}
|
|
93
|
+
console.error(
|
|
94
|
+
"\nsync-assets never deletes unrecognized content: resolve these by hand — remove any unauthorized skill directory or symlink under .agents/skills/, migrate a real .claude/.codex directory into .agents/ — then re-run `apifuse sync-assets .`.",
|
|
95
|
+
);
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!result.changed) {
|
|
100
|
+
console.log(
|
|
101
|
+
`Prompt assets already in sync with the installed SDK (${installedSdkVersion()}): ${providerRoot}`,
|
|
102
|
+
);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
console.log(`\nPrompt assets synced: ${providerRoot}`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function normalizeArgs(argv: string[]): string[] {
|
|
109
|
+
return argv[0] === "sync-assets" ? argv.slice(1) : argv;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (import.meta.main) {
|
|
113
|
+
await main().catch((error: unknown) => {
|
|
114
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
115
|
+
process.exit(1);
|
|
116
|
+
});
|
|
117
|
+
}
|
package/bin/apifuse.ts
CHANGED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const MIN_RECORDED_DELIMITED_TEXT_LENGTH = 200;
|
|
2
|
+
const MIN_RECORDED_DELIMITED_TEXT_LINES = 3;
|
|
3
|
+
const MAX_SAMPLED_DELIMITED_TEXT_LINES = 10;
|
|
4
|
+
const DELIMITER_CANDIDATES = [",", "\t", ";"] as const;
|
|
5
|
+
|
|
6
|
+
// Recognizes a recorded operation value that is a substantive tabular text
|
|
7
|
+
// payload. The length floor and repeated, quote-aware field structure keep
|
|
8
|
+
// short strings and prose from being treated as fixture provenance.
|
|
9
|
+
export function hasSubstantiveDelimitedTextStructure(value: string): boolean {
|
|
10
|
+
if (value.length < MIN_RECORDED_DELIMITED_TEXT_LENGTH) {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const lines = value
|
|
15
|
+
.replace(/^\uFEFF/, "")
|
|
16
|
+
.split(/\r\n?|\n/)
|
|
17
|
+
.filter((line) => line.trim().length > 0);
|
|
18
|
+
if (lines.length < MIN_RECORDED_DELIMITED_TEXT_LINES) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const sampledLines = lines.slice(0, MAX_SAMPLED_DELIMITED_TEXT_LINES);
|
|
23
|
+
return DELIMITER_CANDIDATES.some((delimiter) => {
|
|
24
|
+
const fieldCounts = sampledLines.map((line) => countDelimitedFields(line, delimiter));
|
|
25
|
+
const expectedFieldCount = fieldCounts[0];
|
|
26
|
+
return (
|
|
27
|
+
expectedFieldCount !== undefined &&
|
|
28
|
+
expectedFieldCount >= 2 &&
|
|
29
|
+
fieldCounts.every((fieldCount) => fieldCount === expectedFieldCount)
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function countDelimitedFields(line: string, delimiter: string): number | undefined {
|
|
35
|
+
let fieldCount = 1;
|
|
36
|
+
let insideQuotes = false;
|
|
37
|
+
for (let index = 0; index < line.length; index += 1) {
|
|
38
|
+
const character = line[index];
|
|
39
|
+
if (character === '"') {
|
|
40
|
+
if (insideQuotes && line[index + 1] === '"') {
|
|
41
|
+
index += 1;
|
|
42
|
+
} else {
|
|
43
|
+
insideQuotes = !insideQuotes;
|
|
44
|
+
}
|
|
45
|
+
} else if (!insideQuotes && character === delimiter) {
|
|
46
|
+
fieldCount += 1;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return insideQuotes ? undefined : fieldCount;
|
|
50
|
+
}
|
package/bin/submit-check-xml.ts
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
normalizedXmlName,
|
|
15
15
|
rootXmlContext,
|
|
16
16
|
type XmlSemanticBranch,
|
|
17
|
-
} from "./submit-check-xml-semantics";
|
|
17
|
+
} from "./submit-check-xml-semantics.js";
|
|
18
18
|
|
|
19
19
|
const MIN_RECORDED_XML_LENGTH = 128;
|
|
20
20
|
// Recorded fixtures must remain reviewable; this pre-allocation cap also bounds the parser tree.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AuthTurn } from "../types";
|
|
1
|
+
import type { AuthTurn } from "../types.js";
|
|
2
2
|
export type { AuthTurn };
|
|
3
3
|
/**
|
|
4
4
|
* Major version of the AuthTurn envelope contract.
|
|
@@ -20,7 +20,7 @@ export declare const AUTH_TURN_SCHEMA_ARTIFACT_PATH = "dist/auth-turn/auth-turn.
|
|
|
20
20
|
*
|
|
21
21
|
* This is the exact codification of the runtime validation the SDK applies to
|
|
22
22
|
* ceremony outputs (see `validateCeremonyOutput` in `src/ceremonies`), which
|
|
23
|
-
*
|
|
23
|
+
* evaluates this same document. `kind` is an OPEN string on the wire: the known
|
|
24
24
|
* kinds in {@link TURN_KINDS} are tooling metadata, never a wire constraint.
|
|
25
25
|
*
|
|
26
26
|
* The committed artifact at `src/auth-turn/auth-turn.v1.schema.json` (shipped
|
|
@@ -87,7 +87,7 @@ export declare const AUTH_TURN_SCHEMA: {
|
|
|
87
87
|
};
|
|
88
88
|
readonly $defs: {
|
|
89
89
|
readonly completeTurnData: {
|
|
90
|
-
readonly title:
|
|
90
|
+
readonly title: "Terminal payload for kind \"complete\"";
|
|
91
91
|
readonly description: "data payload of a complete turn. The gateway extracts data.credential for persistence; complete turns are never echoed to browsers.";
|
|
92
92
|
readonly type: "object";
|
|
93
93
|
readonly additionalProperties: true;
|
|
@@ -104,7 +104,7 @@ export declare const AUTH_TURN_SCHEMA: {
|
|
|
104
104
|
};
|
|
105
105
|
};
|
|
106
106
|
readonly abortTurnData: {
|
|
107
|
-
readonly title:
|
|
107
|
+
readonly title: "Terminal payload for kind \"abort\"";
|
|
108
108
|
readonly description: "data payload of an abort turn. code, when present, is the machine-readable abort reason.";
|
|
109
109
|
readonly type: "object";
|
|
110
110
|
readonly additionalProperties: true;
|
package/dist/auth-turn/index.js
CHANGED
|
@@ -18,7 +18,7 @@ export const AUTH_TURN_SCHEMA_ARTIFACT_PATH = "dist/auth-turn/auth-turn.v1.schem
|
|
|
18
18
|
*
|
|
19
19
|
* This is the exact codification of the runtime validation the SDK applies to
|
|
20
20
|
* ceremony outputs (see `validateCeremonyOutput` in `src/ceremonies`), which
|
|
21
|
-
*
|
|
21
|
+
* evaluates this same document. `kind` is an OPEN string on the wire: the known
|
|
22
22
|
* kinds in {@link TURN_KINDS} are tooling metadata, never a wire constraint.
|
|
23
23
|
*
|
|
24
24
|
* The committed artifact at `src/auth-turn/auth-turn.v1.schema.json` (shipped
|
package/dist/auth.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { AuthError } from "./errors";
|
|
2
|
-
import type { AuthConfig, AuthFlowTerminalContext, AuthTurn, ContextDeclaration, CredentialDeclaration, FlowContext, ProviderLocaleKeyInput } from "./types";
|
|
1
|
+
import { AuthError } from "./errors.js";
|
|
2
|
+
import type { AuthConfig, AuthFlowTerminalContext, AuthTurn, ContextDeclaration, CredentialDeclaration, FlowContext, ProviderLocaleKeyInput } from "./types.js";
|
|
3
3
|
export type CredentialsAuthFieldType = "string" | "email" | "password" | "otp";
|
|
4
4
|
export interface CredentialsAuthField {
|
|
5
5
|
type?: CredentialsAuthFieldType;
|
|
@@ -66,6 +66,20 @@ export interface DefineCredentialsAuthOptions<TFields extends CredentialsAuthFie
|
|
|
66
66
|
/** Extra auth-flow context keys used by custom login/challenge code. */
|
|
67
67
|
contextKeys?: readonly string[];
|
|
68
68
|
login(ctx: FlowContext, input: CredentialsAuthInput<TFields>): CredentialsAuthLoginResult<TCredentialKeys, keyof TChallenges & string> | Promise<CredentialsAuthLoginResult<TCredentialKeys, keyof TChallenges & string>>;
|
|
69
|
+
/**
|
|
70
|
+
* Optional re-mint of an expired session from the stored credential, wired
|
|
71
|
+
* to `auth.flow.refresh`.
|
|
72
|
+
*
|
|
73
|
+
* Credential-auth upstreams routinely invalidate a session well before the
|
|
74
|
+
* `expiresAt` the provider advertised, which leaves every operation failing
|
|
75
|
+
* with a reauth error until a human repeats the whole interactive login.
|
|
76
|
+
* Implement this to re-establish the session from what is already stored on
|
|
77
|
+
* the connection; the result is resolved exactly like `login`, so it may
|
|
78
|
+
* also raise a challenge when the upstream demands one. Omit it when the
|
|
79
|
+
* upstream has no non-interactive path and re-authentication genuinely
|
|
80
|
+
* requires the user.
|
|
81
|
+
*/
|
|
82
|
+
refresh?(ctx: FlowContext, input: Partial<CredentialsAuthInput<TFields>>): CredentialsAuthLoginResult<TCredentialKeys, keyof TChallenges & string> | Promise<CredentialsAuthLoginResult<TCredentialKeys, keyof TChallenges & string>>;
|
|
69
83
|
}
|
|
70
84
|
export interface DefinedCredentialsAuth {
|
|
71
85
|
auth: AuthConfig;
|
package/dist/auth.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { AuthError, ProviderError } from "./errors";
|
|
1
|
+
import { AuthError, ProviderError } from "./errors.js";
|
|
2
|
+
import { declarationInvalidError, DECLARATION_RULE_IDS, } from "./declaration-validation.js";
|
|
2
3
|
const CREDENTIALS_AUTH_CHALLENGE_CONTEXT_KEY = "__credentialsAuthChallenge";
|
|
3
4
|
const DEFAULT_COMPLETE_TURN_ID = "auth.complete";
|
|
4
5
|
const DEFAULT_ABORT_TURN_ID = "auth.abort";
|
|
@@ -97,7 +98,7 @@ export function createAuthFlowHelpers(options = {}) {
|
|
|
97
98
|
},
|
|
98
99
|
};
|
|
99
100
|
},
|
|
100
|
-
abort({ code, message, retry, actionHint, fieldErrors, data, turnId, expiresAt
|
|
101
|
+
abort({ code, message, retry, actionHint, fieldErrors, data, turnId, expiresAt }) {
|
|
101
102
|
return {
|
|
102
103
|
kind: "abort",
|
|
103
104
|
...authTurnBase({ turnId, defaultTurnId: DEFAULT_ABORT_TURN_ID, expiresAt }),
|
|
@@ -169,12 +170,8 @@ function expectedInputFromFields(fields) {
|
|
|
169
170
|
...(field.type === "password" ? { format: "password" } : {}),
|
|
170
171
|
...(field.type === "otp" ? { format: "otp" } : {}),
|
|
171
172
|
...(field.labelKey ? { nameKey: field.labelKey } : {}),
|
|
172
|
-
...(field.descriptionKey
|
|
173
|
-
|
|
174
|
-
: {}),
|
|
175
|
-
...(field.placeholderKey
|
|
176
|
-
? { placeholderKey: field.placeholderKey }
|
|
177
|
-
: {}),
|
|
173
|
+
...(field.descriptionKey ? { descriptionKey: field.descriptionKey } : {}),
|
|
174
|
+
...(field.placeholderKey ? { placeholderKey: field.placeholderKey } : {}),
|
|
178
175
|
...(field.sensitive || field.type === "password" || field.type === "otp"
|
|
179
176
|
? { sensitive: true }
|
|
180
177
|
: {}),
|
|
@@ -202,6 +199,23 @@ function normalizeInput(fields, input) {
|
|
|
202
199
|
}
|
|
203
200
|
return result;
|
|
204
201
|
}
|
|
202
|
+
/**
|
|
203
|
+
* Refresh variant of {@link normalizeInput}. `login` coerces every declared
|
|
204
|
+
* field to a string because the interactive turn has already enforced that they
|
|
205
|
+
* are present; refresh runs with no user present, so an absent field is omitted
|
|
206
|
+
* rather than turned into an empty string. That keeps "the user did not supply
|
|
207
|
+
* this" distinguishable from "the user supplied an empty value".
|
|
208
|
+
*/
|
|
209
|
+
function normalizePartialInput(fields, input) {
|
|
210
|
+
const result = {};
|
|
211
|
+
for (const name of Object.keys(fields)) {
|
|
212
|
+
const value = input?.[name];
|
|
213
|
+
if (typeof value === "string") {
|
|
214
|
+
result[name] = value;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return result;
|
|
218
|
+
}
|
|
205
219
|
function assertCredentialKeys(credentialKeys, credential) {
|
|
206
220
|
const missing = credentialKeys.filter((key) => {
|
|
207
221
|
const value = credential[key];
|
|
@@ -291,17 +305,15 @@ function completeTurn(credentialKeys, result, defaultTurnId) {
|
|
|
291
305
|
};
|
|
292
306
|
}
|
|
293
307
|
function challengeTurn(definition, request) {
|
|
294
|
-
const expectedInput = definition.fields
|
|
295
|
-
? expectedInputFromFields(definition.fields)
|
|
296
|
-
: undefined;
|
|
308
|
+
const expectedInput = definition.fields ? expectedInputFromFields(definition.fields) : undefined;
|
|
297
309
|
return {
|
|
298
310
|
kind: expectedInput ? "form" : "pending",
|
|
299
311
|
turnId: request.turnId ?? definition.turnId ?? `credentials.${request.challengeId}`,
|
|
300
312
|
...(request.expiresAt ? { expiresAt: request.expiresAt } : {}),
|
|
301
|
-
...(request.hintKey ?? definition.hintKey
|
|
313
|
+
...((request.hintKey ?? definition.hintKey)
|
|
302
314
|
? { hintKey: request.hintKey ?? definition.hintKey }
|
|
303
315
|
: {}),
|
|
304
|
-
...(request.timing ?? definition.timing
|
|
316
|
+
...((request.timing ?? definition.timing)
|
|
305
317
|
? { timing: request.timing ?? definition.timing }
|
|
306
318
|
: {}),
|
|
307
319
|
...(expectedInput ? { expectedInput } : {}),
|
|
@@ -378,13 +390,14 @@ async function pollPendingChallenge(ctx, credentialKeys, challenges, pending, co
|
|
|
378
390
|
export function defineCredentialsAuth(options) {
|
|
379
391
|
if (Object.keys(options.fields).length === 0) {
|
|
380
392
|
throw new ProviderError("defineCredentialsAuth requires at least one field", {
|
|
381
|
-
fix:
|
|
393
|
+
fix: 'Pass fields such as { email: { type: "email" }, password: { type: "password" } }.',
|
|
382
394
|
});
|
|
383
395
|
}
|
|
384
396
|
const expectedInput = expectedInputFromFields(options.fields);
|
|
385
397
|
const retryTurnId = options.retryTurnId ?? "credentials.retry";
|
|
386
398
|
const completeTurnId = options.completeTurnId ?? "credentials.complete";
|
|
387
399
|
const challenges = (options.challenges ?? {});
|
|
400
|
+
validateCredentialsAuthChallenges(challenges);
|
|
388
401
|
return {
|
|
389
402
|
auth: {
|
|
390
403
|
mode: "credentials",
|
|
@@ -417,6 +430,27 @@ export function defineCredentialsAuth(options) {
|
|
|
417
430
|
}
|
|
418
431
|
return await pollPendingChallenge(ctx, options.credentialKeys, challenges, pending, completeTurnId);
|
|
419
432
|
},
|
|
433
|
+
// Only advertise refresh when the provider implements it: the
|
|
434
|
+
// protocol treats the hook's presence as "this connection can be
|
|
435
|
+
// re-established without the user", and exposing a stub that
|
|
436
|
+
// cannot actually re-mint would turn a recoverable expiry into a
|
|
437
|
+
// silent failure.
|
|
438
|
+
...(options.refresh
|
|
439
|
+
? {
|
|
440
|
+
refresh: async (ctx, rawInput) => {
|
|
441
|
+
// A pending challenge belongs to the interactive flow that
|
|
442
|
+
// raised it; finish it there rather than restarting.
|
|
443
|
+
const pending = getPendingChallenge(ctx);
|
|
444
|
+
if (pending) {
|
|
445
|
+
return await continuePendingChallenge(ctx, options.credentialKeys, challenges, pending, rawInput, completeTurnId);
|
|
446
|
+
}
|
|
447
|
+
// Refresh runs without user input, so fields are optional
|
|
448
|
+
// here — unlike `continue`, missing ones are not a retry.
|
|
449
|
+
const result = await options.refresh(ctx, normalizePartialInput(options.fields, rawInput));
|
|
450
|
+
return await resolveAuthResult(ctx, options.credentialKeys, challenges, result, completeTurnId);
|
|
451
|
+
},
|
|
452
|
+
}
|
|
453
|
+
: {}),
|
|
420
454
|
},
|
|
421
455
|
},
|
|
422
456
|
credential: {
|
|
@@ -427,10 +461,34 @@ export function defineCredentialsAuth(options) {
|
|
|
427
461
|
...(options.justification ? { justification: options.justification } : {}),
|
|
428
462
|
},
|
|
429
463
|
context: {
|
|
430
|
-
keys: Array.from(new Set([
|
|
431
|
-
CREDENTIALS_AUTH_CHALLENGE_CONTEXT_KEY,
|
|
432
|
-
...(options.contextKeys ?? []),
|
|
433
|
-
])),
|
|
464
|
+
keys: Array.from(new Set([CREDENTIALS_AUTH_CHALLENGE_CONTEXT_KEY, ...(options.contextKeys ?? [])])),
|
|
434
465
|
},
|
|
435
466
|
};
|
|
436
467
|
}
|
|
468
|
+
function validateCredentialsAuthChallenges(challenges) {
|
|
469
|
+
const violations = [];
|
|
470
|
+
for (const [challengeId, challenge] of Object.entries(challenges)) {
|
|
471
|
+
const fieldCount = challenge.fields && typeof challenge.fields === "object"
|
|
472
|
+
? Object.keys(challenge.fields).length
|
|
473
|
+
: 0;
|
|
474
|
+
const fieldsDeclared = challenge.fields !== undefined && challenge.fields !== null;
|
|
475
|
+
const hasFields = fieldCount > 0;
|
|
476
|
+
const hasVerify = typeof challenge.verify === "function";
|
|
477
|
+
const hasPoll = typeof challenge.poll === "function";
|
|
478
|
+
const isInteractive = hasFields && hasVerify && !hasPoll;
|
|
479
|
+
const isPolling = !fieldsDeclared && !hasVerify && hasPoll;
|
|
480
|
+
const isHybrid = hasFields && hasVerify && hasPoll;
|
|
481
|
+
const emptyFieldsDeclared = fieldsDeclared && !hasFields;
|
|
482
|
+
if (!emptyFieldsDeclared && (isInteractive || isPolling || isHybrid))
|
|
483
|
+
continue;
|
|
484
|
+
const path = `challenges.${challengeId}`;
|
|
485
|
+
violations.push({
|
|
486
|
+
ruleId: DECLARATION_RULE_IDS.challengeShape,
|
|
487
|
+
path,
|
|
488
|
+
message: "challenge must be interactive, polling, or an explicit hybrid.",
|
|
489
|
+
fix: `Give ${path} non-empty fields plus verify, poll alone, or all three for a hybrid.`,
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
if (violations.length > 0)
|
|
493
|
+
throw declarationInvalidError(violations);
|
|
494
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import type { AuthFlowDefinition, AuthTurn } from "../types";
|
|
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;
|
package/dist/ceremonies/index.js
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import { FlowExpiredError, ProviderSecretError, TurnValidationError, ValidationError, } from "../errors";
|
|
5
|
-
const ajv = new Ajv2020({ allErrors: true, strict: true, strictSchema: true });
|
|
6
|
-
// Runtime ceremony-output validation derives from the exported versioned
|
|
7
|
-
// contract: it compiles the exact AUTH_TURN_SCHEMA document shipped at
|
|
8
|
-
// dist/auth-turn/auth-turn.v1.schema.json, so the two cannot drift.
|
|
9
|
-
const validateAuthTurn = ajv.compile(AUTH_TURN_SCHEMA);
|
|
2
|
+
import { AUTH_TURN_SCHEMA } from "../auth-turn/index.js";
|
|
3
|
+
import { FlowExpiredError, ProviderSecretError, TurnValidationError, ValidationError, } from "../errors.js";
|
|
10
4
|
const OAUTH2_STATE_KEY = "__oauth2_state";
|
|
11
5
|
const OAUTH2_PKCE_VERIFIER_KEY = "__oauth2_pkce_verifier";
|
|
6
|
+
export const OAUTH2_PROXIED_AUTH_PROXY_ORIGIN_ENV_KEY = "APIFUSE__AUTH_PROXY__URL";
|
|
7
|
+
export const OAUTH2_PROXIED_PKCE_VERIFIER_KEY = "__oauth2_proxied_pkce_verifier";
|
|
8
|
+
const OAUTH2_PROXIED_RESERVED_AUTHORIZE_PARAMS = new Set([
|
|
9
|
+
"client_id",
|
|
10
|
+
"response_type",
|
|
11
|
+
"state",
|
|
12
|
+
"code_challenge",
|
|
13
|
+
"code_challenge_method",
|
|
14
|
+
]);
|
|
12
15
|
const DEVICE_FLOW_KEY = "__device_flow";
|
|
13
16
|
const MAGIC_LINK_KEY = "__magic_link";
|
|
14
17
|
const COMBINED_STAGE_KEY = "__combined_stage";
|
|
@@ -17,6 +20,50 @@ const FORM_FIELD_ORDER_EXTENSION = "x-apifuse-field-order";
|
|
|
17
20
|
function isRecord(value) {
|
|
18
21
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
19
22
|
}
|
|
23
|
+
function validateSchemaNode(value, schema, path, errors) {
|
|
24
|
+
if (schema.type === "object") {
|
|
25
|
+
if (!isRecord(value)) {
|
|
26
|
+
errors.push(`${path} must be object`);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const properties = schema.properties ?? {};
|
|
30
|
+
for (const required of schema.required ?? []) {
|
|
31
|
+
if (!Object.hasOwn(value, required) || value[required] === undefined) {
|
|
32
|
+
errors.push(`${path} must have required property '${required}'`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (schema.additionalProperties === false) {
|
|
36
|
+
for (const key of Object.keys(value)) {
|
|
37
|
+
if (!Object.hasOwn(properties, key)) {
|
|
38
|
+
errors.push(`${path} must NOT have additional property '${key}'`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
for (const [key, childSchema] of Object.entries(properties)) {
|
|
43
|
+
if (Object.hasOwn(value, key) && value[key] !== undefined) {
|
|
44
|
+
validateSchemaNode(value[key], childSchema, `${path}/${key}`, errors);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (schema.type === "string") {
|
|
50
|
+
if (typeof value !== "string") {
|
|
51
|
+
errors.push(`${path} must be string`);
|
|
52
|
+
}
|
|
53
|
+
else if (schema.minLength !== undefined && value.length < schema.minLength) {
|
|
54
|
+
errors.push(`${path} must NOT have fewer than ${schema.minLength} characters`);
|
|
55
|
+
}
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (schema.type === "number") {
|
|
59
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
60
|
+
errors.push(`${path} must be number`);
|
|
61
|
+
}
|
|
62
|
+
else if (schema.minimum !== undefined && value < schema.minimum) {
|
|
63
|
+
errors.push(`${path} must be >= ${schema.minimum}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
20
67
|
function ensureRecord(value) {
|
|
21
68
|
return isRecord(value) ? value : {};
|
|
22
69
|
}
|
|
@@ -38,11 +85,7 @@ function getRequiredEnv(ctx, key) {
|
|
|
38
85
|
return value;
|
|
39
86
|
}
|
|
40
87
|
function toBase64Url(input) {
|
|
41
|
-
return input
|
|
42
|
-
.toString("base64")
|
|
43
|
-
.replace(/\+/g, "-")
|
|
44
|
-
.replace(/\//g, "_")
|
|
45
|
-
.replace(/=+$/g, "");
|
|
88
|
+
return input.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
46
89
|
}
|
|
47
90
|
function createCodeVerifier() {
|
|
48
91
|
return toBase64Url(randomBytes(32));
|
|
@@ -100,8 +143,7 @@ function withDeclaredFormFieldOrder(expectedInput) {
|
|
|
100
143
|
return expectedInput;
|
|
101
144
|
}
|
|
102
145
|
const existingOrder = expectedInput[FORM_FIELD_ORDER_EXTENSION];
|
|
103
|
-
if (Array.isArray(existingOrder) &&
|
|
104
|
-
existingOrder.every((value) => typeof value === "string")) {
|
|
146
|
+
if (Array.isArray(existingOrder) && existingOrder.every((value) => typeof value === "string")) {
|
|
105
147
|
return expectedInput;
|
|
106
148
|
}
|
|
107
149
|
return {
|
|
@@ -110,11 +152,14 @@ function withDeclaredFormFieldOrder(expectedInput) {
|
|
|
110
152
|
};
|
|
111
153
|
}
|
|
112
154
|
export function validateCeremonyOutput(turn) {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
155
|
+
// Evaluate the exact exported versioned schema without eagerly initializing
|
|
156
|
+
// a general-purpose JSON Schema compiler in every provider process. Contract
|
|
157
|
+
// parity tests compare this focused evaluator with AJV over all fixtures and
|
|
158
|
+
// edge probes, so the runtime and shipped document remain locked together.
|
|
159
|
+
const errors = [];
|
|
160
|
+
validateSchemaNode(turn, AUTH_TURN_SCHEMA, "", errors);
|
|
161
|
+
if (errors.length > 0) {
|
|
162
|
+
throw new TurnValidationError(errors.join("; "));
|
|
118
163
|
}
|
|
119
164
|
return turn;
|
|
120
165
|
}
|
|
@@ -178,6 +223,54 @@ export function createOAuth2Ceremony(options) {
|
|
|
178
223
|
abort: async () => validateCeremonyOutput(createTurn("abort", { hint: "OAuth flow aborted." })),
|
|
179
224
|
};
|
|
180
225
|
}
|
|
226
|
+
/**
|
|
227
|
+
* Builds the start handler for a custom-scheme OAuth provider. The shared
|
|
228
|
+
* auth-proxy owns state minting and callback capture, while the provider owns
|
|
229
|
+
* token exchange and any following authentication turns.
|
|
230
|
+
*/
|
|
231
|
+
export function createOAuth2ProxiedStart(options) {
|
|
232
|
+
const pkce = options.pkce ?? "S256";
|
|
233
|
+
return (ctx) => runCeremonyHandler(async () => {
|
|
234
|
+
for (const key of Object.keys(options.authorizeParams ?? {})) {
|
|
235
|
+
if (OAUTH2_PROXIED_RESERVED_AUTHORIZE_PARAMS.has(key.toLowerCase())) {
|
|
236
|
+
throw new ValidationError(`OAuth2 proxied authorizeParams cannot override reserved parameter "${key}".`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
const proxyOrigin = ctx.env.get(OAUTH2_PROXIED_AUTH_PROXY_ORIGIN_ENV_KEY);
|
|
240
|
+
if (!proxyOrigin) {
|
|
241
|
+
throw new ProviderSecretError(`Missing required platform environment: ${OAUTH2_PROXIED_AUTH_PROXY_ORIGIN_ENV_KEY}`);
|
|
242
|
+
}
|
|
243
|
+
const clientId = getRequiredEnv(ctx, options.clientIdEnvKey);
|
|
244
|
+
if (!ctx.flowId) {
|
|
245
|
+
throw new ValidationError("OAuth2 proxied start requires the gateway flow id.");
|
|
246
|
+
}
|
|
247
|
+
const authorizePath = new URL(options.authorizeUrl).pathname;
|
|
248
|
+
const url = new URL(`/p/${encodeURIComponent(ctx.providerId)}/f/${encodeURIComponent(ctx.flowId)}${authorizePath}`, proxyOrigin);
|
|
249
|
+
url.searchParams.set("client_id", clientId);
|
|
250
|
+
url.searchParams.set("response_type", "code");
|
|
251
|
+
if (pkce === "S256") {
|
|
252
|
+
const verifier = createCodeVerifier();
|
|
253
|
+
ctx.context.set(OAUTH2_PROXIED_PKCE_VERIFIER_KEY, verifier);
|
|
254
|
+
url.searchParams.set("code_challenge", createCodeChallenge(verifier));
|
|
255
|
+
url.searchParams.set("code_challenge_method", "S256");
|
|
256
|
+
}
|
|
257
|
+
for (const [key, value] of Object.entries(options.authorizeParams ?? {})) {
|
|
258
|
+
url.searchParams.set(key, value);
|
|
259
|
+
}
|
|
260
|
+
return createTurn("redirect", {
|
|
261
|
+
data: { url: url.toString() },
|
|
262
|
+
hint: "Open the provider authorization page to continue.",
|
|
263
|
+
expectedInput: {
|
|
264
|
+
type: "object",
|
|
265
|
+
required: ["code", "state"],
|
|
266
|
+
properties: {
|
|
267
|
+
code: { type: "string" },
|
|
268
|
+
state: { type: "string" },
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
});
|
|
272
|
+
}, "OAuth2 proxied start failed", ctx);
|
|
273
|
+
}
|
|
181
274
|
export function createDeviceFlowCeremony(options) {
|
|
182
275
|
return {
|
|
183
276
|
start: (ctx) => runCeremonyHandler(async () => {
|
|
@@ -218,8 +311,7 @@ export function createDeviceFlowCeremony(options) {
|
|
|
218
311
|
});
|
|
219
312
|
const data = ensureRecord(response.data);
|
|
220
313
|
const errorCode = getString(data, "error");
|
|
221
|
-
if (errorCode === "authorization_pending" ||
|
|
222
|
-
errorCode === "slow_down") {
|
|
314
|
+
if (errorCode === "authorization_pending" || errorCode === "slow_down") {
|
|
223
315
|
return createTurn("poll", {
|
|
224
316
|
data,
|
|
225
317
|
hint: "Authorization pending.",
|
|
@@ -345,7 +437,7 @@ export function createFormCeremony(options) {
|
|
|
345
437
|
return {
|
|
346
438
|
start: async () => validateCeremonyOutput(buildJsonSchemaForm(options.schema, options.hint ?? "Provide the required input to continue.")),
|
|
347
439
|
continue: (ctx, input = {}) => runCeremonyHandler(async () => {
|
|
348
|
-
const { prevalidate } = await import("../runtime/prevalidate");
|
|
440
|
+
const { prevalidate } = await import("../runtime/prevalidate.js");
|
|
349
441
|
const result = prevalidate(options.schema, input);
|
|
350
442
|
if (!result.valid) {
|
|
351
443
|
throw new ValidationError("Form input failed validation.", {
|
|
@@ -354,9 +446,7 @@ export function createFormCeremony(options) {
|
|
|
354
446
|
}
|
|
355
447
|
return createTurn("complete", {
|
|
356
448
|
data: {
|
|
357
|
-
credential: options.mapCredential
|
|
358
|
-
? options.mapCredential(input)
|
|
359
|
-
: input,
|
|
449
|
+
credential: options.mapCredential ? options.mapCredential(input) : input,
|
|
360
450
|
},
|
|
361
451
|
hint: "Form completed.",
|
|
362
452
|
});
|
|
@@ -428,9 +518,7 @@ export function createSwitchCeremony(options) {
|
|
|
428
518
|
})),
|
|
429
519
|
continue: (ctx, input = {}) => runCeremonyHandler(async () => {
|
|
430
520
|
const storedChoice = ctx.context.get(SWITCH_SELECTION_KEY);
|
|
431
|
-
const choice = typeof storedChoice === "string"
|
|
432
|
-
? storedChoice
|
|
433
|
-
: getString(input, "choice");
|
|
521
|
+
const choice = typeof storedChoice === "string" ? storedChoice : getString(input, "choice");
|
|
434
522
|
if (!choice || !options.choices[choice]) {
|
|
435
523
|
throw new ValidationError("A valid choice is required.");
|
|
436
524
|
}
|
package/dist/cli/commands.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type ApifuseCommandName = "create" | "dev" | "check" | "submit-check" | "bounty-check" | "record" | "test" | "perf";
|
|
1
|
+
export type ApifuseCommandName = "create" | "dev" | "check" | "sync-assets" | "submit-check" | "bounty-check" | "record" | "test" | "perf";
|
|
2
2
|
export type ApifuseCommandManifest = {
|
|
3
3
|
name: ApifuseCommandName;
|
|
4
4
|
summary: string;
|