@apifuse/provider-sdk 2.1.0-beta.9 → 2.2.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AUTHORING.md +134 -0
- package/CHANGELOG.md +61 -0
- package/README.md +21 -9
- package/SUBMISSION.md +10 -11
- package/bin/apifuse-pack-check.ts +22 -0
- package/bin/apifuse-submit-check.ts +1747 -388
- 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 +436 -0
- package/dist/ceremonies/index.js +7 -31
- package/dist/cli/create.js +45 -30
- 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 +87 -0
- package/dist/cli/templates/provider/CLAUDE.md.tpl +1 -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/skills/fixtures-and-recording/SKILL.md.tpl +58 -0
- package/dist/cli/templates/provider/skills/health-checks-and-fail-closed/SKILL.md.tpl +65 -0
- package/dist/cli/templates/provider/skills/normalization-standards/SKILL.md.tpl +57 -0
- package/dist/cli/templates/provider/skills/pagination-and-counts/SKILL.md.tpl +52 -0
- package/dist/cli/templates/provider/skills/upstream-contract-verification/SKILL.md.tpl +45 -0
- package/dist/cli/templates/provider/skills/upstream-notes/README.md.tpl +13 -0
- package/dist/cli/templates/provider/start.ts.tpl +5 -0
- package/dist/cli/templates/provider/upstream/README.md.tpl +3 -0
- package/dist/contract.js +1 -0
- package/dist/define.d.ts +6 -1
- package/dist/define.js +140 -70
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -1
- package/dist/lint.d.ts +1 -0
- package/dist/lint.js +27 -0
- package/dist/provider.d.ts +4 -2
- package/dist/provider.js +2 -1
- package/dist/runtime/auth-flow.js +2 -0
- package/dist/runtime/browser.js +203 -0
- package/dist/runtime/http.js +46 -336
- package/dist/runtime/proxy-retry-policy.d.ts +40 -0
- package/dist/runtime/proxy-retry-policy.js +326 -0
- package/dist/runtime/stealth.d.ts +5 -2
- package/dist/runtime/stealth.js +206 -206
- package/dist/server/index.d.ts +4 -0
- package/dist/server/index.js +4 -0
- 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 +98 -0
- package/dist/server/self-test.js +555 -0
- package/dist/server/serve.d.ts +6 -0
- package/dist/server/serve.js +38 -10
- package/dist/server/types.d.ts +1 -0
- package/dist/server/types.js +1 -0
- package/dist/testing/run.js +5 -1
- package/dist/types.d.ts +150 -0
- package/package.json +9 -2
- 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 +786 -0
- package/src/ceremonies/index.ts +9 -43
- package/src/cli/create.ts +60 -97
- package/src/cli/templates/provider/AGENTS.md.tpl +87 -0
- package/src/cli/templates/provider/CLAUDE.md.tpl +1 -0
- package/src/cli/templates/provider/README.md.tpl +7 -4
- package/src/cli/templates/provider/skills/fixtures-and-recording/SKILL.md.tpl +58 -0
- package/src/cli/templates/provider/skills/health-checks-and-fail-closed/SKILL.md.tpl +65 -0
- package/src/cli/templates/provider/skills/normalization-standards/SKILL.md.tpl +57 -0
- package/src/cli/templates/provider/skills/pagination-and-counts/SKILL.md.tpl +52 -0
- package/src/cli/templates/provider/skills/upstream-contract-verification/SKILL.md.tpl +45 -0
- package/src/cli/templates/provider/skills/upstream-notes/README.md.tpl +13 -0
- package/src/contract.ts +1 -0
- package/src/define.ts +198 -71
- package/src/index.ts +16 -0
- package/src/lint.ts +33 -0
- package/src/provider.ts +27 -0
- package/src/runtime/auth-flow.ts +2 -0
- package/src/runtime/browser.ts +293 -1
- package/src/runtime/http.ts +84 -530
- package/src/runtime/proxy-retry-policy.ts +469 -0
- package/src/runtime/stealth.ts +269 -353
- package/src/server/index.ts +36 -0
- 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 +725 -0
- package/src/server/serve.ts +75 -6
- package/src/server/types.ts +1 -0
- package/src/testing/run.ts +9 -1
- package/src/types.ts +186 -0
package/dist/define.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import ms from "ms";
|
|
1
2
|
import { ProviderError, ValidationError } from "./errors";
|
|
2
3
|
import { safeParseSchemaSync } from "./schema";
|
|
3
4
|
import { HEALTH_CHECK_DEGRADED_THRESHOLD_MS_MAX, HEALTH_CHECK_DEGRADED_THRESHOLD_MS_MIN, HEALTH_CHECK_TIMEOUT_MS_MAX, HEALTH_CHECK_TIMEOUT_MS_MIN, OPERATION_TIMEOUT_MS_MAX, OPERATION_TIMEOUT_MS_MIN, STREAM_CHUNK_BYTES_MAX, STREAM_CHUNK_BYTES_MIN, STREAM_HEARTBEAT_MS_MAX, STREAM_HEARTBEAT_MS_MIN, STREAM_IDLE_TIMEOUT_MS_MAX, STREAM_IDLE_TIMEOUT_MS_MIN, STREAM_MAX_DURATION_MS_MAX, STREAM_MAX_DURATION_MS_MIN, } from "./types";
|
|
@@ -50,51 +51,23 @@ const VALID_OPERATION_TRANSPORT_KINDS = [
|
|
|
50
51
|
];
|
|
51
52
|
const SSE_EVENT_NAME_REGEX = /^[A-Za-z][A-Za-z0-9_.-]{0,127}$/;
|
|
52
53
|
const WEBSOCKET_SUBPROTOCOL_REGEX = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
|
|
53
|
-
const MS_DURATION_UNITS = new Set([
|
|
54
|
-
"years",
|
|
55
|
-
"year",
|
|
56
|
-
"yrs",
|
|
57
|
-
"yr",
|
|
58
|
-
"y",
|
|
59
|
-
"weeks",
|
|
60
|
-
"week",
|
|
61
|
-
"w",
|
|
62
|
-
"days",
|
|
63
|
-
"day",
|
|
64
|
-
"d",
|
|
65
|
-
"hours",
|
|
66
|
-
"hour",
|
|
67
|
-
"hrs",
|
|
68
|
-
"hr",
|
|
69
|
-
"h",
|
|
70
|
-
"minutes",
|
|
71
|
-
"minute",
|
|
72
|
-
"mins",
|
|
73
|
-
"min",
|
|
74
|
-
"m",
|
|
75
|
-
"seconds",
|
|
76
|
-
"second",
|
|
77
|
-
"secs",
|
|
78
|
-
"sec",
|
|
79
|
-
"s",
|
|
80
|
-
"milliseconds",
|
|
81
|
-
"millisecond",
|
|
82
|
-
"msecs",
|
|
83
|
-
"msec",
|
|
84
|
-
"ms",
|
|
85
|
-
]);
|
|
86
54
|
const MS_DURATION_PATTERN = /^([+-]?(?:\d+(?:\.\d+)?|\.\d+))\s*([a-zA-Z]+)?$/;
|
|
87
55
|
function isPositiveMsDurationString(value) {
|
|
88
56
|
if (typeof value !== "string")
|
|
89
57
|
return false;
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
|
|
58
|
+
return parsePositiveMsDuration(value) !== undefined;
|
|
59
|
+
}
|
|
60
|
+
function msDurationMs(value) {
|
|
61
|
+
return parsePositiveMsDuration(value) ?? 0;
|
|
62
|
+
}
|
|
63
|
+
function parsePositiveMsDuration(value) {
|
|
64
|
+
const trimmed = value.trim();
|
|
65
|
+
if (!MS_DURATION_PATTERN.test(trimmed))
|
|
66
|
+
return undefined;
|
|
67
|
+
const parsed = ms((trimmed.startsWith("+") ? trimmed.slice(1) : trimmed));
|
|
68
|
+
if (!Number.isFinite(parsed) || parsed <= 0)
|
|
69
|
+
return undefined;
|
|
70
|
+
return parsed;
|
|
98
71
|
}
|
|
99
72
|
/** Define one provider operation with schema-driven handler inference. */
|
|
100
73
|
export function defineOperation(operation) {
|
|
@@ -141,6 +114,11 @@ function validateProviderShape(config) {
|
|
|
141
114
|
"mode" in auth &&
|
|
142
115
|
typeof auth.mode === "string")
|
|
143
116
|
assertLiteralField(auth.mode, "auth.mode", VALID_AUTH_MODES, String(config.id));
|
|
117
|
+
if (auth && typeof auth === "object" && "exchange" in auth) {
|
|
118
|
+
throw new ProviderError(`Provider "${String(config.id)}" auth.exchange is not part of the Provider SDK auth contract`, {
|
|
119
|
+
fix: "Use the single canonical auth interface: auth.flow. Gateway calls auth.flow.start/continue/poll/abort/refresh only and persists complete turn data.credential as-is, so put login/token/session exchange inside auth.flow.continue.",
|
|
120
|
+
});
|
|
121
|
+
}
|
|
144
122
|
if (auth &&
|
|
145
123
|
typeof auth === "object" &&
|
|
146
124
|
"flow" in auth &&
|
|
@@ -555,6 +533,7 @@ function validateOperationTransports(providerId, operations) {
|
|
|
555
533
|
}
|
|
556
534
|
const HEALTH_CHECK_SUITE_FIELDS = new Set([
|
|
557
535
|
"interval",
|
|
536
|
+
"schedule",
|
|
558
537
|
"timeoutMs",
|
|
559
538
|
"degradedThresholdMs",
|
|
560
539
|
"cases",
|
|
@@ -642,26 +621,26 @@ function assertBoundedIntegerMs(value, fieldPath, options) {
|
|
|
642
621
|
});
|
|
643
622
|
}
|
|
644
623
|
}
|
|
645
|
-
function validateProviderHealthMonitor(providerId, healthMonitor) {
|
|
624
|
+
function validateProviderHealthMonitor(providerId, healthMonitor, field = "healthMonitor") {
|
|
646
625
|
if (healthMonitor === undefined)
|
|
647
626
|
return;
|
|
648
627
|
if (!healthMonitor ||
|
|
649
628
|
typeof healthMonitor !== "object" ||
|
|
650
629
|
Array.isArray(healthMonitor))
|
|
651
|
-
throw new ValidationError(`Provider "${providerId}" has invalid
|
|
652
|
-
fix: `Set
|
|
630
|
+
throw new ValidationError(`Provider "${providerId}" has invalid ${field}: must be an object.`, {
|
|
631
|
+
fix: `Set ${field} to { requiredSecrets?: string[]; serviceAccount?: string }`,
|
|
653
632
|
});
|
|
654
633
|
const healthMonitorRecord = Object.fromEntries(Object.entries(healthMonitor));
|
|
655
|
-
rejectUnknownFields(healthMonitorRecord, PROVIDER_HEALTH_MONITOR_FIELDS,
|
|
634
|
+
rejectUnknownFields(healthMonitorRecord, PROVIDER_HEALTH_MONITOR_FIELDS, field);
|
|
656
635
|
if (healthMonitorRecord.defaultProbeTimeoutMs !== undefined) {
|
|
657
|
-
assertBoundedIntegerMs(healthMonitorRecord.defaultProbeTimeoutMs, `Provider "${providerId}"
|
|
636
|
+
assertBoundedIntegerMs(healthMonitorRecord.defaultProbeTimeoutMs, `Provider "${providerId}" ${field}.defaultProbeTimeoutMs`, {
|
|
658
637
|
min: HEALTH_CHECK_TIMEOUT_MS_MIN,
|
|
659
638
|
max: HEALTH_CHECK_TIMEOUT_MS_MAX,
|
|
660
639
|
label: "timeout",
|
|
661
640
|
});
|
|
662
641
|
}
|
|
663
642
|
if (healthMonitorRecord.defaultDegradedThresholdMs !== undefined) {
|
|
664
|
-
assertBoundedIntegerMs(healthMonitorRecord.defaultDegradedThresholdMs, `Provider "${providerId}"
|
|
643
|
+
assertBoundedIntegerMs(healthMonitorRecord.defaultDegradedThresholdMs, `Provider "${providerId}" ${field}.defaultDegradedThresholdMs`, {
|
|
665
644
|
min: HEALTH_CHECK_DEGRADED_THRESHOLD_MS_MIN,
|
|
666
645
|
max: HEALTH_CHECK_DEGRADED_THRESHOLD_MS_MAX,
|
|
667
646
|
label: "degraded threshold",
|
|
@@ -670,10 +649,10 @@ function validateProviderHealthMonitor(providerId, healthMonitor) {
|
|
|
670
649
|
const requiredSecrets = healthMonitorRecord.requiredSecrets;
|
|
671
650
|
if (requiredSecrets !== undefined) {
|
|
672
651
|
if (!Array.isArray(requiredSecrets))
|
|
673
|
-
throw new ValidationError(`Provider "${providerId}" has invalid
|
|
652
|
+
throw new ValidationError(`Provider "${providerId}" has invalid ${field}.requiredSecrets: must be string[].`);
|
|
674
653
|
for (const [index, secret] of requiredSecrets.entries()) {
|
|
675
654
|
if (typeof secret !== "string" || secret.length === 0)
|
|
676
|
-
throw new ValidationError(`Provider "${providerId}" has invalid
|
|
655
|
+
throw new ValidationError(`Provider "${providerId}" has invalid ${field}.requiredSecrets[${index}]: must be a non-empty string.`);
|
|
677
656
|
}
|
|
678
657
|
}
|
|
679
658
|
const credentialInputs = healthMonitorRecord.credentialInputs;
|
|
@@ -681,17 +660,17 @@ function validateProviderHealthMonitor(providerId, healthMonitor) {
|
|
|
681
660
|
if (!credentialInputs ||
|
|
682
661
|
typeof credentialInputs !== "object" ||
|
|
683
662
|
Array.isArray(credentialInputs)) {
|
|
684
|
-
throw new ValidationError(`Provider "${providerId}" has invalid
|
|
663
|
+
throw new ValidationError(`Provider "${providerId}" has invalid ${field}.credentialInputs: must be an object mapping auth input fields to env var names.`);
|
|
685
664
|
}
|
|
686
|
-
for (const [
|
|
687
|
-
if (
|
|
688
|
-
throw new ValidationError(`Provider "${providerId}" has invalid
|
|
665
|
+
for (const [inputField, envVar] of Object.entries(credentialInputs)) {
|
|
666
|
+
if (inputField.trim().length === 0) {
|
|
667
|
+
throw new ValidationError(`Provider "${providerId}" has invalid ${field}.credentialInputs key: must be a non-empty auth input field.`);
|
|
689
668
|
}
|
|
690
669
|
if (typeof envVar !== "string" || envVar.trim().length === 0) {
|
|
691
|
-
throw new ValidationError(`Provider "${providerId}" has invalid
|
|
670
|
+
throw new ValidationError(`Provider "${providerId}" has invalid ${field}.credentialInputs.${inputField}: must be a non-empty env var name.`);
|
|
692
671
|
}
|
|
693
672
|
if (Array.isArray(requiredSecrets) && !requiredSecrets.includes(envVar)) {
|
|
694
|
-
throw new ValidationError(`Provider "${providerId}"
|
|
673
|
+
throw new ValidationError(`Provider "${providerId}" ${field}.credentialInputs.${inputField} references ${envVar}, which must also be listed in ${field}.requiredSecrets.`);
|
|
695
674
|
}
|
|
696
675
|
}
|
|
697
676
|
}
|
|
@@ -700,26 +679,26 @@ function validateProviderHealthMonitor(providerId, healthMonitor) {
|
|
|
700
679
|
if (!probeOverrides ||
|
|
701
680
|
typeof probeOverrides !== "object" ||
|
|
702
681
|
Array.isArray(probeOverrides))
|
|
703
|
-
throw new ValidationError(`Provider "${providerId}" has invalid
|
|
682
|
+
throw new ValidationError(`Provider "${providerId}" has invalid ${field}.probeOverrides: must be an object keyed by probe id.`);
|
|
704
683
|
for (const [probeId, override] of Object.entries(probeOverrides)) {
|
|
705
684
|
if (probeId.length === 0)
|
|
706
|
-
throw new ValidationError(`Provider "${providerId}" has invalid
|
|
685
|
+
throw new ValidationError(`Provider "${providerId}" has invalid ${field}.probeOverrides key: must be a non-empty probe id.`);
|
|
707
686
|
if (!override || typeof override !== "object" || Array.isArray(override))
|
|
708
|
-
throw new ValidationError(`Provider "${providerId}" has invalid
|
|
687
|
+
throw new ValidationError(`Provider "${providerId}" has invalid ${field}.probeOverrides["${probeId}"]: must be an object.`);
|
|
709
688
|
const overrideRecord = Object.fromEntries(Object.entries(override));
|
|
710
|
-
rejectUnknownFields(overrideRecord, PROVIDER_HEALTH_MONITOR_PROBE_OVERRIDE_FIELDS,
|
|
689
|
+
rejectUnknownFields(overrideRecord, PROVIDER_HEALTH_MONITOR_PROBE_OVERRIDE_FIELDS, `${field}.probeOverrides["${probeId}"]`);
|
|
711
690
|
const interval = overrideRecord.interval;
|
|
712
691
|
if (interval !== undefined && !isPositiveMsDurationString(interval))
|
|
713
|
-
throw new ValidationError(`Provider "${providerId}" has invalid
|
|
692
|
+
throw new ValidationError(`Provider "${providerId}" has invalid ${field}.probeOverrides["${probeId}"].interval: must be a positive ms-style duration string such as 30s, 5m, 8h, or 1 day.`);
|
|
714
693
|
if (overrideRecord.timeoutMs !== undefined) {
|
|
715
|
-
assertBoundedIntegerMs(overrideRecord.timeoutMs, `Provider "${providerId}"
|
|
694
|
+
assertBoundedIntegerMs(overrideRecord.timeoutMs, `Provider "${providerId}" ${field}.probeOverrides["${probeId}"].timeoutMs`, {
|
|
716
695
|
min: HEALTH_CHECK_TIMEOUT_MS_MIN,
|
|
717
696
|
max: HEALTH_CHECK_TIMEOUT_MS_MAX,
|
|
718
697
|
label: "timeout",
|
|
719
698
|
});
|
|
720
699
|
}
|
|
721
700
|
if (overrideRecord.degradedThresholdMs !== undefined) {
|
|
722
|
-
assertBoundedIntegerMs(overrideRecord.degradedThresholdMs, `Provider "${providerId}"
|
|
701
|
+
assertBoundedIntegerMs(overrideRecord.degradedThresholdMs, `Provider "${providerId}" ${field}.probeOverrides["${probeId}"].degradedThresholdMs`, {
|
|
723
702
|
min: HEALTH_CHECK_DEGRADED_THRESHOLD_MS_MIN,
|
|
724
703
|
max: HEALTH_CHECK_DEGRADED_THRESHOLD_MS_MAX,
|
|
725
704
|
label: "degraded threshold",
|
|
@@ -730,7 +709,7 @@ function validateProviderHealthMonitor(providerId, healthMonitor) {
|
|
|
730
709
|
const serviceAccount = healthMonitorRecord.serviceAccount;
|
|
731
710
|
if (serviceAccount !== undefined &&
|
|
732
711
|
(typeof serviceAccount !== "string" || serviceAccount.length === 0))
|
|
733
|
-
throw new ValidationError(`Provider "${providerId}" has invalid
|
|
712
|
+
throw new ValidationError(`Provider "${providerId}" has invalid ${field}.serviceAccount: must be a non-empty string.`);
|
|
734
713
|
}
|
|
735
714
|
function validateHealthCheckCase(providerId, operationName, caseValue, caseIndex) {
|
|
736
715
|
const fieldPath = `operations.${operationName}.healthCheck.cases[${caseIndex}]`;
|
|
@@ -776,6 +755,21 @@ function validateHealthCheckSuite(providerId, operationName, suite) {
|
|
|
776
755
|
throw new ValidationError(`Provider "${providerId}" ${fieldPath}.interval must be a positive ms-style duration string such as 30s, 5m, 8h, or 1 day.`, {
|
|
777
756
|
fix: `Set ${fieldPath}.interval to a positive ms-style duration string.`,
|
|
778
757
|
});
|
|
758
|
+
if (s.schedule !== undefined) {
|
|
759
|
+
if (!s.schedule ||
|
|
760
|
+
typeof s.schedule !== "object" ||
|
|
761
|
+
Array.isArray(s.schedule)) {
|
|
762
|
+
throw new ValidationError(`Provider "${providerId}" ${fieldPath}.schedule must be an object.`);
|
|
763
|
+
}
|
|
764
|
+
if (Reflect.get(s.schedule, "jitter") !== undefined) {
|
|
765
|
+
throw new ValidationError(`Provider "${providerId}" ${fieldPath}.schedule.jitter is not supported for operation healthCheck schedules. Use schedule.randomize instead.`);
|
|
766
|
+
}
|
|
767
|
+
rejectUnknownFields(s.schedule, new Set(["randomize"]), `${fieldPath}.schedule`);
|
|
768
|
+
const randomize = Reflect.get(s.schedule, "randomize");
|
|
769
|
+
if (randomize !== undefined) {
|
|
770
|
+
validateScheduleRandomization(randomize, `Provider "${providerId}" ${fieldPath}.schedule.randomize`, msDurationMs(s.interval));
|
|
771
|
+
}
|
|
772
|
+
}
|
|
779
773
|
if (s.timeoutMs !== undefined) {
|
|
780
774
|
assertBoundedIntegerMs(s.timeoutMs, `Provider "${providerId}" ${fieldPath}.timeoutMs`, {
|
|
781
775
|
min: HEALTH_CHECK_TIMEOUT_MS_MIN,
|
|
@@ -837,7 +831,12 @@ const HEALTH_JOURNEY_FIELDS = new Set([
|
|
|
837
831
|
"steps",
|
|
838
832
|
"run",
|
|
839
833
|
]);
|
|
840
|
-
const HEALTH_JOURNEY_SCHEDULE_FIELDS = new Set([
|
|
834
|
+
const HEALTH_JOURNEY_SCHEDULE_FIELDS = new Set([
|
|
835
|
+
"kind",
|
|
836
|
+
"interval",
|
|
837
|
+
"jitter",
|
|
838
|
+
"randomize",
|
|
839
|
+
]);
|
|
841
840
|
const HEALTH_JOURNEY_STEP_FIELDS = new Set([
|
|
842
841
|
"id",
|
|
843
842
|
"description",
|
|
@@ -953,6 +952,40 @@ function isoDurationMs(value) {
|
|
|
953
952
|
const seconds = Number(/(\d+(?:\.\d+)?)S/.exec(value)?.[1] ?? 0);
|
|
954
953
|
return (days * 86_400_000 + hours * 3_600_000 + minutes * 60_000 + seconds * 1_000);
|
|
955
954
|
}
|
|
955
|
+
function scheduleRandomizationMs(randomize, fieldPath) {
|
|
956
|
+
const mode = Reflect.get(randomize, "mode");
|
|
957
|
+
switch (mode) {
|
|
958
|
+
case "centered": {
|
|
959
|
+
const maxOffset = Reflect.get(randomize, "maxOffset");
|
|
960
|
+
assertIsoDuration(maxOffset, `${fieldPath}.maxOffset`);
|
|
961
|
+
return isoDurationMs(maxOffset);
|
|
962
|
+
}
|
|
963
|
+
case "delayed": {
|
|
964
|
+
const maxDelay = Reflect.get(randomize, "maxDelay");
|
|
965
|
+
assertIsoDuration(maxDelay, `${fieldPath}.maxDelay`);
|
|
966
|
+
return isoDurationMs(maxDelay);
|
|
967
|
+
}
|
|
968
|
+
default:
|
|
969
|
+
throw new ValidationError(`${fieldPath}.mode must be "centered" or "delayed".`);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
function validateScheduleRandomization(randomize, fieldPath, intervalMs) {
|
|
973
|
+
if (!randomize || typeof randomize !== "object" || Array.isArray(randomize)) {
|
|
974
|
+
throw new ValidationError(`${fieldPath} must be an object.`);
|
|
975
|
+
}
|
|
976
|
+
const mode = Reflect.get(randomize, "mode");
|
|
977
|
+
const allowedFields = mode === "centered"
|
|
978
|
+
? new Set(["mode", "maxOffset"])
|
|
979
|
+
: new Set(["mode", "maxDelay"]);
|
|
980
|
+
rejectUnknownFields(randomize, allowedFields, fieldPath);
|
|
981
|
+
const offsetMs = scheduleRandomizationMs(randomize, fieldPath);
|
|
982
|
+
if (offsetMs <= 0) {
|
|
983
|
+
throw new ValidationError(`${fieldPath} duration must be positive.`);
|
|
984
|
+
}
|
|
985
|
+
if (offsetMs >= intervalMs) {
|
|
986
|
+
throw new ValidationError(`${fieldPath} duration must be shorter than schedule interval.`);
|
|
987
|
+
}
|
|
988
|
+
}
|
|
956
989
|
function assertIsoCountry(value, fieldPath) {
|
|
957
990
|
if (typeof value !== "string" || !ISO_COUNTRY_RE.test(value)) {
|
|
958
991
|
throw new ValidationError(`${fieldPath} must be an ISO 3166-1 alpha-2 country code for example KR.`);
|
|
@@ -962,11 +995,18 @@ function normalizeIntervalDuration(input) {
|
|
|
962
995
|
const trimmed = input.trim();
|
|
963
996
|
const shorthand = /^(\d+)(s|m|h|d)$/i.exec(trimmed);
|
|
964
997
|
if (shorthand) {
|
|
965
|
-
const
|
|
998
|
+
const durationMs = msDurationMs(trimmed);
|
|
999
|
+
const unit = shorthand[2]?.toLowerCase();
|
|
1000
|
+
const amount = unit === "s"
|
|
1001
|
+
? durationMs / 1_000
|
|
1002
|
+
: unit === "m"
|
|
1003
|
+
? durationMs / 60_000
|
|
1004
|
+
: unit === "h"
|
|
1005
|
+
? durationMs / 3_600_000
|
|
1006
|
+
: durationMs / 86_400_000;
|
|
966
1007
|
if (!Number.isInteger(amount) || amount <= 0) {
|
|
967
1008
|
throw new ValidationError(`Journey schedule interval must be a positive duration.`);
|
|
968
1009
|
}
|
|
969
|
-
const unit = shorthand[2]?.toLowerCase();
|
|
970
1010
|
if (unit === "s")
|
|
971
1011
|
return `PT${amount}S`;
|
|
972
1012
|
if (unit === "m")
|
|
@@ -980,15 +1020,27 @@ function normalizeIntervalDuration(input) {
|
|
|
980
1020
|
return trimmed;
|
|
981
1021
|
}
|
|
982
1022
|
export function every(interval, options = {}) {
|
|
1023
|
+
if (options.jitter !== undefined && options.randomize !== undefined) {
|
|
1024
|
+
throw new ValidationError(`Schedule cannot define both jitter and randomize. Use randomize instead.`);
|
|
1025
|
+
}
|
|
983
1026
|
const schedule = {
|
|
984
1027
|
kind: "interval",
|
|
985
1028
|
interval: normalizeIntervalDuration(interval),
|
|
986
1029
|
};
|
|
1030
|
+
if (options.randomize !== undefined) {
|
|
1031
|
+
schedule.randomize = options.randomize;
|
|
1032
|
+
}
|
|
987
1033
|
if (options.jitter !== undefined) {
|
|
988
1034
|
schedule.jitter = normalizeIntervalDuration(options.jitter);
|
|
989
1035
|
}
|
|
990
1036
|
return schedule;
|
|
991
1037
|
}
|
|
1038
|
+
export function centered(maxOffset) {
|
|
1039
|
+
return { mode: "centered", maxOffset: normalizeIntervalDuration(maxOffset) };
|
|
1040
|
+
}
|
|
1041
|
+
export function delayed(maxDelay) {
|
|
1042
|
+
return { mode: "delayed", maxDelay: normalizeIntervalDuration(maxDelay) };
|
|
1043
|
+
}
|
|
992
1044
|
function countCapturingGroups(pattern) {
|
|
993
1045
|
let count = 0;
|
|
994
1046
|
const source = pattern.source;
|
|
@@ -1140,9 +1192,18 @@ function validateHealthJourneySchedule(providerId, journeyId, schedule) {
|
|
|
1140
1192
|
rejectUnknownFields(schedule, HEALTH_JOURNEY_SCHEDULE_FIELDS, fieldPath);
|
|
1141
1193
|
if (Reflect.get(schedule, "kind") !== "interval")
|
|
1142
1194
|
throw new ValidationError(`Provider "${providerId}" ${fieldPath}.kind must be "interval".`);
|
|
1143
|
-
|
|
1195
|
+
const interval = Reflect.get(schedule, "interval");
|
|
1196
|
+
assertIsoDuration(interval, `Provider "${providerId}" ${fieldPath}.interval`);
|
|
1197
|
+
const randomize = Reflect.get(schedule, "randomize");
|
|
1198
|
+
if (Reflect.get(schedule, "jitter") !== undefined &&
|
|
1199
|
+
randomize !== undefined) {
|
|
1200
|
+
throw new ValidationError(`Provider "${providerId}" ${fieldPath} cannot define both jitter and randomize.`);
|
|
1201
|
+
}
|
|
1144
1202
|
if (Reflect.get(schedule, "jitter") !== undefined)
|
|
1145
1203
|
assertIsoDuration(Reflect.get(schedule, "jitter"), `Provider "${providerId}" ${fieldPath}.jitter`);
|
|
1204
|
+
if (randomize !== undefined) {
|
|
1205
|
+
validateScheduleRandomization(randomize, `Provider "${providerId}" ${fieldPath}.randomize`, isoDurationMs(interval));
|
|
1206
|
+
}
|
|
1146
1207
|
}
|
|
1147
1208
|
function validateHealthJourneys(providerId, operations, healthJourneys) {
|
|
1148
1209
|
const covered = new Set();
|
|
@@ -1278,7 +1339,9 @@ export function defineProvider(config) {
|
|
|
1278
1339
|
fix: 'Use lowercase alphanumeric with dashes, e.g., "korea-air-quality"',
|
|
1279
1340
|
});
|
|
1280
1341
|
if (Object.keys(config.operations).length === 0)
|
|
1281
|
-
throw new ProviderError(`Provider "${config.id}" must define at least one operation`, {
|
|
1342
|
+
throw new ProviderError(`Provider "${config.id}" must define at least one operation`, {
|
|
1343
|
+
fix: "Add at least one operation to the operations object",
|
|
1344
|
+
});
|
|
1282
1345
|
validateOperationIds(config.id, config.operations);
|
|
1283
1346
|
validateOperationAnnotations(config.id, config.operations);
|
|
1284
1347
|
validateOperationObservability(config.id, config.operations);
|
|
@@ -1287,7 +1350,11 @@ export function defineProvider(config) {
|
|
|
1287
1350
|
validateToolRouterMetadata(config.id, config.operations);
|
|
1288
1351
|
const journeyCoveredOperations = validateHealthJourneys(config.id, config.operations, config.healthJourneys);
|
|
1289
1352
|
validateOperationHealthChecks(config.id, config.operations, journeyCoveredOperations);
|
|
1290
|
-
|
|
1353
|
+
if (config.healthMonitor !== undefined && config.healthProbe !== undefined)
|
|
1354
|
+
throw new ValidationError(`Provider "${config.id}" declares both healthMonitor and healthProbe. They are aliases; declare exactly one.`, {
|
|
1355
|
+
fix: "Keep healthProbe (the new name) and delete the healthMonitor block.",
|
|
1356
|
+
});
|
|
1357
|
+
validateProviderHealthMonitor(config.id, config.healthProbe ?? config.healthMonitor, config.healthProbe !== undefined ? "healthProbe" : "healthMonitor");
|
|
1291
1358
|
validateOperationFixtures(config.id, config.operations);
|
|
1292
1359
|
validateProviderProxy(config);
|
|
1293
1360
|
validateProviderStt(config);
|
|
@@ -1314,7 +1381,10 @@ export function defineProvider(config) {
|
|
|
1314
1381
|
context: config.context,
|
|
1315
1382
|
meta: config.meta,
|
|
1316
1383
|
operations: config.operations,
|
|
1317
|
-
healthMonitor:
|
|
1384
|
+
// Transitional healthMonitor → healthProbe alias: mirror whichever field
|
|
1385
|
+
// was declared onto both so old and new consumers keep working.
|
|
1386
|
+
healthMonitor: config.healthMonitor ?? config.healthProbe,
|
|
1387
|
+
healthProbe: config.healthProbe ?? config.healthMonitor,
|
|
1318
1388
|
healthJourneys: config.healthJourneys,
|
|
1319
1389
|
};
|
|
1320
1390
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
export * from "./auth";
|
|
1
2
|
export * from "./ceremonies";
|
|
2
3
|
export * from "./choice-token";
|
|
3
4
|
export type { ApiFuseConfig, BrowserConfig, ProxyConfig, SessionConfig, } from "./config/loader";
|
|
4
5
|
export { defineConfig, loadApiFuseConfig } from "./config/loader";
|
|
5
6
|
export { canonicalJson, digestProviderContract, extractProviderContract, type JsonPrimitive, type JsonValue, PROVIDER_CONTRACT_SCHEMA_VERSION, type ProviderContractOperation, type ProviderContractSnapshot, } from "./contract";
|
|
6
|
-
export { defineHealthJourney, defineOperation, defineProvider, defineSmsOtpMatcher, defineStreamOperation, every, type ProviderConfig, } from "./define";
|
|
7
|
+
export { centered, delayed, defineHealthJourney, defineOperation, defineProvider, defineSmsOtpMatcher, defineStreamOperation, every, type ProviderConfig, } from "./define";
|
|
7
8
|
export type { DevServerOptions } from "./dev";
|
|
8
9
|
export { createDevServer, startDevServer } from "./dev";
|
|
9
10
|
export * from "./errors";
|
|
@@ -33,7 +34,7 @@ export { APIFUSE_DESCRIPTION_KEY_META_KEY, APIFUSE_REDACTION_MARKER, APIFUSE_SEN
|
|
|
33
34
|
export { createServerApp, type ServeOptions, serve } from "./server";
|
|
34
35
|
export { getStealthProfile, listStealthProfiles } from "./stealth/profiles";
|
|
35
36
|
export * from "./stream";
|
|
36
|
-
export type { ApiFuseResponse, AuthConfig, AuthContext, AuthFlowDefinition, AuthFlowInputHandler, AuthFlowStartHandler, AuthMode, AuthTurn, Bcp47Locale, BrowserEngine, BrowserOptions, ConnectionMode, ContextDeclaration, ContextScratchpad, CredentialContext, CredentialDeclaration, E164PhoneNumber, EnvContext, FlowContext, FlowContextStore, HealthCheckAssertionContext, HealthCheckCase, HealthCheckCaseResult, HealthCheckSuite, HealthCheckUnsupported, HealthJourneyDefinition, HealthJourneyEventContext, HealthJourneyGatewayContext, HealthJourneyJournalContext, HealthJourneyManualTriggerPolicy, HealthJourneyRunContext, HealthJourneyRunResult, HealthJourneySchedule, HealthJourneySmsContext, HealthJourneyStep, HttpClient, HttpMethod, HttpResponse, HttpRetryOptions, HttpRetrySummary, HttpStreamResponse, IanaTimeZone, InferSchemaOutput, Iso3166Alpha2CountryCode, Iso4217CurrencyCode, Iso8601Duration, OperationAnnotations, OperationApprovalPolicy, OperationContractMetadata, OperationDefinition, OperationDeprecationMetadata, OperationDocMeta, OperationErrorCode, OperationHandlerResult, OperationInputExample, OperationLifecycle, OperationObservabilityConfig, OperationObservabilitySensitiveConfig, OperationRelationships, OperationRiskClass, OperationSensitivePath, OperationToolRouterMetadata, OperationTransport, OperationTransportKind, ProbeInterval, ProviderAccessConfig, ProviderAccessVisibility, ProviderCache, ProviderCacheGetOrSetOptions, ProviderCacheKeyOptions, ProviderCacheLookupMeta, ProviderCacheResponseMeta, ProviderCacheResult, ProviderChoiceBindingOptions, ProviderChoiceContext, ProviderChoiceIssueOptions, ProviderChoiceParseOptions, ProviderContext, ProviderDefinition, ProviderHealthMonitorConfig, ProviderLocale, ProviderLocaleKey, ProviderLocaleKeyInput, ProviderLogoProfile, ProviderLogoSource, ProviderMeta, ProviderProxyConfig, ProviderProxyMode, ProviderProxyPolicy, ProviderProxyProvider, ProviderProxySessionAffinity, ProviderPublicConnectionMode, ProviderPublicProfile, ProviderReviewed, ProviderRuntimeState, ProviderSecretDeclaration, ProviderStateDurationString, ProviderStateNamespace, ProviderStreamEvent, ProviderSttConfig, ProviderSttMode, ProviderSupportLevel, RequestOptions, Rfc3339Instant, SchemaLike, SmsOrigin, SmsOtpExtractionPattern, SmsOtpMatcherDefinition, SseMessage, StandardSchemaV1, StateCasResult, StateNamespaceOptions, StateValue, StateWriteOptions, StealthClient, StealthFetchOptions, StealthPlatform, StealthProfile, StealthResponse, StealthSession, SttAudioInput, SttContext, SttPromptPolicy, SttSegment, SttTranscribeMode, SttTranscribeRequest, SttTranscript, SttUnsupportedOptionPolicy, SttUsage, SttVerificationCodeOptions, SttWarning, TraceConfig, TraceSpan, VerificationCodeCandidate, VerificationCodeCandidateSource, VerificationCodeExtractionResult, } from "./types";
|
|
37
|
+
export type { ApiFuseResponse, AuthConfig, AuthContext, AuthFlowDefinition, AuthFlowInputHandler, AuthFlowStartHandler, AuthMode, AuthTurn, Bcp47Locale, BrowserEngine, BrowserOptions, BrowserResourceBody, BrowserResourceDecision, BrowserResourceMethod, BrowserResourcePolicy, BrowserResourceRequest, BrowserResourceRoute, ConnectionMode, ContextDeclaration, CookieJar, ContextScratchpad, CredentialContext, CredentialDeclaration, E164PhoneNumber, EnvContext, FlowContext, FlowContextStore, HealthCheckAssertionContext, HealthCheckCase, HealthCheckCaseResult, HealthCheckSuite, HealthCheckUnsupported, HealthJourneyDefinition, HealthJourneyEventContext, HealthJourneyGatewayContext, HealthJourneyJournalContext, HealthJourneyManualTriggerPolicy, HealthJourneyRunContext, HealthJourneyRunResult, HealthJourneySchedule, HealthScheduleRandomization, HealthJourneySmsContext, HealthJourneyStep, HttpClient, HttpMethod, HttpResponse, HttpRetryOptions, HttpRetrySummary, HttpStreamResponse, IanaTimeZone, InferSchemaOutput, Iso3166Alpha2CountryCode, Iso4217CurrencyCode, Iso8601Duration, OperationAnnotations, OperationApprovalPolicy, OperationContractMetadata, OperationDefinition, OperationDeprecationMetadata, OperationDocMeta, OperationErrorCode, OperationHandlerResult, OperationInputExample, OperationLifecycle, OperationObservabilityConfig, OperationObservabilitySensitiveConfig, OperationRelationships, OperationRiskClass, OperationSensitivePath, OperationToolRouterMetadata, OperationTransport, OperationTransportKind, ProbeInterval, ProviderAccessConfig, ProviderAccessVisibility, ProviderCache, ProviderCacheGetOrSetOptions, ProviderCacheKeyOptions, ProviderCacheLookupMeta, ProviderCacheResponseMeta, ProviderCacheResult, ProviderChoiceBindingOptions, ProviderChoiceContext, ProviderChoiceIssueOptions, ProviderChoiceParseOptions, ProviderContext, ProviderDefinition, ProviderHealthMonitorConfig, ProviderHealthProbeConfig, ProviderLocale, ProviderLocaleKey, ProviderLocaleKeyInput, ProviderLogoProfile, ProviderLogoSource, ProviderMeta, ProviderProxyConfig, ProviderProxyMode, ProviderProxyPolicy, ProviderProxyProvider, ProviderProxySessionAffinity, ProviderPublicConnectionMode, ProviderPublicProfile, ProviderReviewed, ProviderRuntimeState, ProviderSecretDeclaration, ProviderStateDurationString, ProviderStateNamespace, ProviderStreamEvent, ProviderSttConfig, ProviderSttMode, ProviderSupportLevel, RequestOptions, Rfc3339Instant, SchemaLike, SmsOrigin, SmsOtpExtractionPattern, SmsOtpMatcherDefinition, SseMessage, StandardSchemaV1, StateCasResult, StateNamespaceOptions, StateValue, StateWriteOptions, StealthClient, StealthFetchOptions, StealthPlatform, StealthProfile, StealthRedirectHop, StealthRedirectRunOptions, StealthRedirectRunResult, StealthResponse, StealthSession, StealthSessionCookies, SttAudioInput, SttContext, SttPromptPolicy, SttSegment, SttTranscribeMode, SttTranscribeRequest, SttTranscript, SttUnsupportedOptionPolicy, SttUsage, SttVerificationCodeOptions, SttWarning, TraceConfig, TraceSpan, VerificationCodeCandidate, VerificationCodeCandidateSource, VerificationCodeExtractionResult, } from "./types";
|
|
37
38
|
export { DEFAULT_OPERATION_TRANSPORT, HttpRetryAfterPolicy, HttpRetryDelayStrategy, HttpRetryJitter, HttpRetryPreset, HttpRetryUnsafeMethodPolicy, PROBE_INTERVALS, STREAM_CHUNK_BYTES_MAX, STREAM_CHUNK_BYTES_MIN, STREAM_HEARTBEAT_MS_MAX, STREAM_HEARTBEAT_MS_MIN, STREAM_IDLE_TIMEOUT_MS_MAX, STREAM_IDLE_TIMEOUT_MS_MIN, STREAM_MAX_DURATION_MS_MAX, STREAM_MAX_DURATION_MS_MIN, } from "./types";
|
|
38
39
|
export * from "./utils/date";
|
|
39
40
|
export * from "./utils/parse";
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// @apifuse/provider-sdk
|
|
2
|
+
export * from "./auth";
|
|
2
3
|
export * from "./ceremonies";
|
|
3
4
|
export * from "./choice-token";
|
|
4
5
|
export { defineConfig, loadApiFuseConfig } from "./config/loader";
|
|
5
6
|
export { canonicalJson, digestProviderContract, extractProviderContract, PROVIDER_CONTRACT_SCHEMA_VERSION, } from "./contract";
|
|
6
|
-
export { defineHealthJourney, defineOperation, defineProvider, defineSmsOtpMatcher, defineStreamOperation, every, } from "./define";
|
|
7
|
+
export { centered, delayed, defineHealthJourney, defineOperation, defineProvider, defineSmsOtpMatcher, defineStreamOperation, every, } from "./define";
|
|
7
8
|
export { createDevServer, startDevServer } from "./dev";
|
|
8
9
|
export * from "./errors";
|
|
9
10
|
export * from "./i18n";
|
package/dist/lint.d.ts
CHANGED
package/dist/lint.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { lintPublicSchemaFieldNames } from "./public-schema-field-lint";
|
|
2
2
|
import { APIFUSE_DESCRIPTION_KEY_META_KEY, APIFUSE_SENSITIVE_META_KEY, } from "./schema";
|
|
3
|
+
const AUTH_OPERATION_ID_PATTERN = /^(?:auth[-_])?(?:login|exchange|continue|refresh|callback)(?:[-_]|$)/i;
|
|
3
4
|
function lintAllowedHosts(providerId, allowedHosts) {
|
|
4
5
|
const prefix = providerId ? `Provider "${providerId}"` : "Provider";
|
|
5
6
|
if (!allowedHosts) {
|
|
@@ -49,6 +50,9 @@ function lintReviewed(providerId, reviewed) {
|
|
|
49
50
|
},
|
|
50
51
|
];
|
|
51
52
|
}
|
|
53
|
+
function isProviderAuthLike(value) {
|
|
54
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
55
|
+
}
|
|
52
56
|
function hasReusableSecretKeys(keys) {
|
|
53
57
|
if (!keys) {
|
|
54
58
|
return false;
|
|
@@ -99,6 +103,14 @@ function lintAuthModel(provider) {
|
|
|
99
103
|
message: `${providerLabel} must define auth.flow.continue for ${authMode} auth mode.`,
|
|
100
104
|
});
|
|
101
105
|
}
|
|
106
|
+
if (isProviderAuthLike(provider.auth) && "exchange" in provider.auth) {
|
|
107
|
+
diagnostics.push({
|
|
108
|
+
rule: "auth-exchange-unsupported",
|
|
109
|
+
level: "error",
|
|
110
|
+
field: "auth.exchange",
|
|
111
|
+
message: `${providerLabel} must not define auth.exchange. The Provider SDK has one auth interface: auth.flow. Gateway only calls auth.flow.start/continue/poll/abort/refresh and persists complete turn data.credential as-is; put login/token/session exchange inside auth.flow.continue.`,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
102
114
|
if (authMode === "credentials" && credentialKeys.length === 0) {
|
|
103
115
|
diagnostics.push({
|
|
104
116
|
rule: "credential-keys-required-when-credentials-mode",
|
|
@@ -673,6 +685,21 @@ export function lintProvider(provider, options = {}) {
|
|
|
673
685
|
...lintPlaywrightDirectImports(provider),
|
|
674
686
|
...lintSelfHostedBrowserPatterns(provider, options),
|
|
675
687
|
];
|
|
688
|
+
if (provider.operations) {
|
|
689
|
+
const authMode = provider.auth?.mode;
|
|
690
|
+
if (authMode === "credentials" || authMode === "oauth2") {
|
|
691
|
+
for (const operationKey of Object.keys(provider.operations)) {
|
|
692
|
+
if (AUTH_OPERATION_ID_PATTERN.test(operationKey)) {
|
|
693
|
+
diagnostics.push({
|
|
694
|
+
rule: "auth-operation-unsupported",
|
|
695
|
+
level: "error",
|
|
696
|
+
field: `operations.${operationKey}`,
|
|
697
|
+
message: `Provider "${provider.id ?? "unknown"}" operation "${operationKey}" looks like a login/token/session exchange endpoint. Authenticated providers must expose login through the single auth.flow interface because Gateway persists only auth.flow complete turn data.credential as the connection credential. Move this logic into auth.flow.continue instead of a provider operation.`,
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
676
703
|
if (!provider.operations) {
|
|
677
704
|
return diagnostics;
|
|
678
705
|
}
|
package/dist/provider.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
export { AuthAbortError, credentialsAuthChallenge, createAuthFlowHelpers, defineCredentialsAuth, } from "./auth";
|
|
2
|
+
export type { CredentialsAuthChallengeDefinition, CredentialsAuthChallengeRequest, CredentialsAuthCompleteResult, CredentialsAuthCredential, CredentialsAuthField, CredentialsAuthFields, CredentialsAuthFieldType, CredentialsAuthInput, CredentialsAuthLoginResult, DefineCredentialsAuthOptions, DefinedCredentialsAuth, } from "./auth";
|
|
1
3
|
export { createFormCeremony } from "./ceremonies";
|
|
2
4
|
export { assertFreshProviderChoiceIssuedAt, createProviderChoiceToken, ProviderChoiceTokenError, type ProviderChoiceTokenErrorReason, type ProviderChoiceTokenPayload, parseProviderChoiceToken, } from "./choice-token";
|
|
3
|
-
export { defineHealthJourney, defineOperation, defineProvider, defineSmsOtpMatcher, every, } from "./define";
|
|
5
|
+
export { centered, delayed, defineHealthJourney, defineOperation, defineProvider, defineSmsOtpMatcher, every, } from "./define";
|
|
4
6
|
export { AuthError, ProviderError, SessionExpiredError, TransportError, ValidationError, } from "./errors";
|
|
5
7
|
export { getProviderLocalePath, providerLocaleKey, qualifyProviderLocaleKey, } from "./i18n";
|
|
6
8
|
export { type CreateProviderChoiceContextOptions, createProviderChoiceContext, createTestProviderChoiceContext, PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV, } from "./runtime/choice";
|
|
7
9
|
export { APIFUSE_DESCRIPTION_KEY_META_KEY, APIFUSE_REDACTION_MARKER, APIFUSE_SENSITIVE_KIND_META_KEY, APIFUSE_SENSITIVE_META_KEY, collectSensitivePaths, describeKey, field, fields, isSensitiveSchema, redactPayload, type SensitiveFieldKind, type SensitiveFieldOptions, type SensitivePath, sensitive, z, } from "./schema";
|
|
8
|
-
export type { AuthMode, FlowContext, HealthCheckAssertionContext, HealthCheckCase, HealthCheckSuite, HealthCheckUnsupported, HealthJourneyDefinition, HealthJourneyEventContext, HealthJourneyManualTriggerPolicy, HealthJourneyRunContext, HealthJourneyRunResult, HttpRetryOptions, HttpRetrySummary, InferSchemaOutput, OperationApprovalPolicy, OperationContractMetadata, OperationDefinition, OperationDocMeta, OperationErrorCode, OperationInputExample, OperationLifecycle, OperationObservabilityConfig, OperationObservabilitySensitiveConfig, OperationRelationships, OperationRiskClass, OperationSensitivePath, OperationToolRouterMetadata, OperationTransport, ProviderAccessVisibility, ProviderChoiceBindingOptions, ProviderChoiceContext, ProviderChoiceIssueOptions, ProviderChoiceParseOptions, ProviderContext, ProviderDefinition, ProviderLocale, ProviderLocaleKey, ProviderLocaleKeyInput, ProviderLogoProfile, ProviderProxyPolicy, ProviderPublicConnectionMode, ProviderPublicProfile, ProviderRuntimeState, ProviderStateDurationString, ProviderStateNamespace, ProviderSupportLevel, SchemaLike, SmsOtpMatcherDefinition, StandardSchemaV1, StateCasResult, StateNamespaceOptions, StateValue, StateWriteOptions, } from "./types";
|
|
10
|
+
export type { AuthAbortData, AuthAbortRetry, AuthFlowTerminalContext, AuthMode, AuthSafeData, AuthSafeJson, FlowContext, HealthCheckAssertionContext, HealthCheckCase, HealthCheckSuite, HealthCheckUnsupported, HealthJourneyDefinition, HealthJourneyEventContext, HealthJourneyManualTriggerPolicy, HealthJourneyRunContext, HealthJourneyRunResult, HealthScheduleRandomization, HttpRetryOptions, HttpRetrySummary, InferSchemaOutput, OperationApprovalPolicy, OperationContractMetadata, OperationDefinition, OperationDocMeta, OperationErrorCode, OperationInputExample, OperationLifecycle, OperationObservabilityConfig, OperationObservabilitySensitiveConfig, OperationRelationships, OperationRiskClass, OperationSensitivePath, OperationToolRouterMetadata, OperationTransport, ProviderAccessVisibility, ProviderChoiceBindingOptions, ProviderChoiceContext, ProviderChoiceIssueOptions, ProviderChoiceParseOptions, ProviderContext, ProviderDefinition, ProviderLocale, ProviderLocaleKey, ProviderLocaleKeyInput, ProviderLogoProfile, ProviderProxyPolicy, ProviderPublicConnectionMode, ProviderPublicProfile, ProviderRuntimeState, ProviderStateDurationString, ProviderStateNamespace, ProviderSupportLevel, SchemaLike, SmsOtpMatcherDefinition, StandardSchemaV1, StateCasResult, StateNamespaceOptions, StateValue, StateWriteOptions, } from "./types";
|
|
9
11
|
export { HttpRetryAfterPolicy, HttpRetryDelayStrategy, HttpRetryJitter, HttpRetryPreset, HttpRetryUnsafeMethodPolicy, } from "./types";
|
package/dist/provider.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
export { AuthAbortError, credentialsAuthChallenge, createAuthFlowHelpers, defineCredentialsAuth, } from "./auth";
|
|
1
2
|
export { createFormCeremony } from "./ceremonies";
|
|
2
3
|
export { assertFreshProviderChoiceIssuedAt, createProviderChoiceToken, ProviderChoiceTokenError, parseProviderChoiceToken, } from "./choice-token";
|
|
3
|
-
export { defineHealthJourney, defineOperation, defineProvider, defineSmsOtpMatcher, every, } from "./define";
|
|
4
|
+
export { centered, delayed, defineHealthJourney, defineOperation, defineProvider, defineSmsOtpMatcher, every, } from "./define";
|
|
4
5
|
export { AuthError, ProviderError, SessionExpiredError, TransportError, ValidationError, } from "./errors";
|
|
5
6
|
export { getProviderLocalePath, providerLocaleKey, qualifyProviderLocaleKey, } from "./i18n";
|
|
6
7
|
export { createProviderChoiceContext, createTestProviderChoiceContext, PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV, } from "./runtime/choice";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ContextAccessError } from "../errors";
|
|
2
|
+
import { createAuthFlowHelpers } from "../auth";
|
|
2
3
|
import { createUnsupportedSttClient } from "./stt";
|
|
3
4
|
function normalizeAllowedKeys(allowedKeys) {
|
|
4
5
|
return new Set(allowedKeys.filter((key) => key.trim().length > 0));
|
|
@@ -40,5 +41,6 @@ export function createFlowContext(options) {
|
|
|
40
41
|
env: options.env,
|
|
41
42
|
context: createScratchpad(options.allowedKeys, options.initialContext),
|
|
42
43
|
stt: options.stt ?? createUnsupportedSttClient(),
|
|
44
|
+
auth: createAuthFlowHelpers(),
|
|
43
45
|
};
|
|
44
46
|
}
|