@apifuse/provider-sdk 2.2.0-beta.5 → 2.2.0-beta.7
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 +53 -0
- package/CHANGELOG.md +8 -0
- package/README.md +5 -1
- package/SUBMISSION.md +1 -1
- package/bin/apifuse-check.ts +26 -1
- package/bin/apifuse-pack-check.ts +14 -0
- package/bin/apifuse-submit-check.ts +193 -2
- package/bin/apifuse-sync-assets.ts +117 -0
- 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/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/runtime/executor.js +7 -0
- package/dist/runtime/secrets.d.ts +27 -0
- package/dist/runtime/secrets.js +51 -0
- package/dist/server/serve.d.ts +5 -0
- package/dist/server/serve.js +39 -0
- package/package.json +1 -1
- 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/index.ts +5 -0
- package/src/runtime/executor.ts +8 -0
- package/src/runtime/secrets.ts +64 -0
- package/src/server/serve.ts +53 -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
|
@@ -4,8 +4,8 @@ You are building an APIFuse provider. APIFuse turns messy upstream APIs into
|
|
|
4
4
|
normalized, typed, evidence-backed public APIs. A provider that merely proxies
|
|
5
5
|
the upstream is a failed provider, even if every check passes.
|
|
6
6
|
|
|
7
|
-
This file is the core contract. Detailed procedures live in
|
|
8
|
-
the matching skill BEFORE working on that area (index at the bottom).
|
|
7
|
+
This file is the core contract. Detailed procedures live in `.agents/skills/` —
|
|
8
|
+
load the matching skill BEFORE working on that area (index at the bottom).
|
|
9
9
|
|
|
10
10
|
## Non-negotiable principles
|
|
11
11
|
|
|
@@ -75,13 +75,22 @@ bun run submit-check # structural score; a high score does NOT prove quality
|
|
|
75
75
|
`submit-check` is a structural gate. Every principle above can be violated
|
|
76
76
|
while scoring 95/100 — reviewers and CI audit for exactly these classes.
|
|
77
77
|
|
|
78
|
+
## Managed prompt assets
|
|
79
|
+
|
|
80
|
+
`AGENTS.md` and `.agents/skills/**` are generated by the SDK; `CLAUDE.md`,
|
|
81
|
+
`.claude`, and `.codex` are symlinks onto them so every agent CLI reads the
|
|
82
|
+
same contract. Do not hand-edit these files — regenerate them with
|
|
83
|
+
`bun run sync-assets` (or `bunx apifuse sync-assets .`). `apifuse check` and
|
|
84
|
+
`submit-check` enforce a freshness gate: stale or modified prompt assets
|
|
85
|
+
(tracked in `.apifuse/prompt-assets.json`) block submission until re-synced.
|
|
86
|
+
|
|
78
87
|
## Skill index — load before working on:
|
|
79
88
|
|
|
80
89
|
| Area | Load |
|
|
81
90
|
| --- | --- |
|
|
82
|
-
| Output schemas, mappers, field naming, timestamps, enums |
|
|
83
|
-
| Upstream request params, new endpoint wiring, field mapping |
|
|
84
|
-
| Recording fixtures, writing tests against fixtures |
|
|
85
|
-
| List operations, paging, totals, client-side filtering |
|
|
86
|
-
| healthCheck blocks, error classification, fail-closed guards |
|
|
87
|
-
| Upstream-specific known pitfalls for THIS bounty |
|
|
91
|
+
| Output schemas, mappers, field naming, timestamps, enums | `.agents/skills/normalization-standards/SKILL.md` |
|
|
92
|
+
| Upstream request params, new endpoint wiring, field mapping | `.agents/skills/upstream-contract-verification/SKILL.md` |
|
|
93
|
+
| Recording fixtures, writing tests against fixtures | `.agents/skills/fixtures-and-recording/SKILL.md` |
|
|
94
|
+
| List operations, paging, totals, client-side filtering | `.agents/skills/pagination-and-counts/SKILL.md` |
|
|
95
|
+
| healthCheck blocks, error classification, fail-closed guards | `.agents/skills/health-checks-and-fail-closed/SKILL.md` |
|
|
96
|
+
| Upstream-specific known pitfalls for THIS bounty | `.agents/skills/upstream-notes/` (read every file) |
|
package/src/index.ts
CHANGED
|
@@ -74,6 +74,11 @@ export {
|
|
|
74
74
|
} from "./runtime/instrumentation.js";
|
|
75
75
|
export { type PrevalidateResult, prevalidate } from "./runtime/prevalidate.js";
|
|
76
76
|
export { getProviderBaseUrl } from "./runtime/provider.js";
|
|
77
|
+
export {
|
|
78
|
+
assertRequiredSecretsPresent,
|
|
79
|
+
listMissingRequiredSecrets,
|
|
80
|
+
MISSING_SECRET_CODE,
|
|
81
|
+
} from "./runtime/secrets.js";
|
|
77
82
|
export {
|
|
78
83
|
createUnsupportedProviderRuntimeState,
|
|
79
84
|
UnsupportedProviderStateError,
|
package/src/runtime/executor.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isSessionExpiredError, ProviderError, SessionExpiredError } from "../errors.js";
|
|
2
2
|
import { parseSchema } from "../schema.js";
|
|
3
3
|
import type { ProviderContext, ProviderDefinition } from "../types.js";
|
|
4
|
+
import { assertRequiredSecretsPresent } from "./secrets.js";
|
|
4
5
|
|
|
5
6
|
export function isStreamingOperation(provider: ProviderDefinition, operationId: string): boolean {
|
|
6
7
|
const kind = provider.operations[operationId]?.transport?.kind ?? "json";
|
|
@@ -34,6 +35,13 @@ export async function executeOperation(
|
|
|
34
35
|
});
|
|
35
36
|
}
|
|
36
37
|
|
|
38
|
+
// SDK-owned secret presence gate (single source of truth): declared
|
|
39
|
+
// `required: true` secrets are validated here, before input parsing and the
|
|
40
|
+
// handler, so every invocation path (serve /v1, self-test probes, perf,
|
|
41
|
+
// record) fails with the same structured MISSING_SECRET error instead of a
|
|
42
|
+
// handler-specific crash. Providers must not re-check presence locally.
|
|
43
|
+
assertRequiredSecretsPresent(provider, ctx.env);
|
|
44
|
+
|
|
37
45
|
const validatedInput = await parseSchema(
|
|
38
46
|
operation.input,
|
|
39
47
|
input,
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { ProviderSecretError } from "../errors.js";
|
|
2
|
+
import type { EnvContext, ProviderDefinition } from "../types.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Canonical error code for a declared-but-unprovisioned provider secret.
|
|
6
|
+
*
|
|
7
|
+
* The SDK is the single source of truth for env/secret presence validation:
|
|
8
|
+
* providers declare secrets in `defineProvider({ secrets: [...] })` and the
|
|
9
|
+
* runtime enforces presence before any handler or auth-flow code runs.
|
|
10
|
+
* Provider-local presence guards (requireServiceKey/requireApiKey style) are a
|
|
11
|
+
* deprecated antipattern — see the `sdk-owned-secret-presence` submit-check
|
|
12
|
+
* rule.
|
|
13
|
+
*/
|
|
14
|
+
export const MISSING_SECRET_CODE = "MISSING_SECRET";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Names of declared `required: true` secrets whose env values are unset or
|
|
18
|
+
* whitespace-only. Whitespace-only values count as missing for parity with the
|
|
19
|
+
* `.trim()` guards well-built providers used before the SDK owned this check —
|
|
20
|
+
* a blank value provisioned by a broken secret pipeline must not pass the gate.
|
|
21
|
+
*/
|
|
22
|
+
export function listMissingRequiredSecrets(
|
|
23
|
+
provider: ProviderDefinition,
|
|
24
|
+
env: EnvContext,
|
|
25
|
+
): string[] {
|
|
26
|
+
const missing: string[] = [];
|
|
27
|
+
for (const secret of provider.secrets ?? []) {
|
|
28
|
+
if (secret.required !== true) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const value = env.get(secret.name);
|
|
32
|
+
if (value === undefined || value.trim() === "") {
|
|
33
|
+
missing.push(secret.name);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return missing;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Throws the canonical structured missing-secret error when any declared
|
|
41
|
+
* `required: true` secret is absent. All missing names are reported in a
|
|
42
|
+
* single error so operators can provision the full set in one pass instead of
|
|
43
|
+
* discovering them one deploy at a time (the 2026-07-22 unprovisioned-secret
|
|
44
|
+
* incident failure mode).
|
|
45
|
+
*/
|
|
46
|
+
export function assertRequiredSecretsPresent(
|
|
47
|
+
provider: ProviderDefinition,
|
|
48
|
+
env: EnvContext,
|
|
49
|
+
): void {
|
|
50
|
+
const missing = listMissingRequiredSecrets(provider, env);
|
|
51
|
+
if (missing.length === 0) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const names = missing.join(", ");
|
|
55
|
+
throw new ProviderSecretError(
|
|
56
|
+
`Missing required provider secret${missing.length > 1 ? "s" : ""}: ${names}`,
|
|
57
|
+
{
|
|
58
|
+
code: MISSING_SECRET_CODE,
|
|
59
|
+
category: "credential_unavailable",
|
|
60
|
+
retryable: false,
|
|
61
|
+
fix: `Provision ${names} in the provider environment (e.g. Doppler). Declared in defineProvider({ secrets: [...] }).`,
|
|
62
|
+
},
|
|
63
|
+
);
|
|
64
|
+
}
|
package/src/server/serve.ts
CHANGED
|
@@ -42,6 +42,11 @@ import {
|
|
|
42
42
|
PROXY_POOL_EXHAUSTED_CODE,
|
|
43
43
|
} from "../runtime/proxy-errors.js";
|
|
44
44
|
import { PROVIDER_TELEMETRY_HEADER, ProxyTelemetryCollector } from "../runtime/proxy-telemetry.js";
|
|
45
|
+
import {
|
|
46
|
+
assertRequiredSecretsPresent,
|
|
47
|
+
listMissingRequiredSecrets,
|
|
48
|
+
MISSING_SECRET_CODE,
|
|
49
|
+
} from "../runtime/secrets.js";
|
|
45
50
|
import {
|
|
46
51
|
createProviderRuntimeStateFromEnv,
|
|
47
52
|
createUnsupportedProviderRuntimeState,
|
|
@@ -408,6 +413,12 @@ export type ProviderServerLogEvent =
|
|
|
408
413
|
retryable?: boolean;
|
|
409
414
|
issues?: Array<{ path: string; code: string; message: string }>;
|
|
410
415
|
})
|
|
416
|
+
| {
|
|
417
|
+
level: "warn";
|
|
418
|
+
event: "provider_secrets_missing";
|
|
419
|
+
providerId: string;
|
|
420
|
+
missingSecrets: string[];
|
|
421
|
+
}
|
|
411
422
|
| {
|
|
412
423
|
level: "warn";
|
|
413
424
|
event: "provider_cleanup_failed";
|
|
@@ -558,6 +569,18 @@ function providerObservabilityDetails(error: unknown):
|
|
|
558
569
|
retryable: error.options?.retryable ?? false,
|
|
559
570
|
};
|
|
560
571
|
}
|
|
572
|
+
// Missing-secret errors carry the canonical credential_unavailable category
|
|
573
|
+
// so Gateway/observability can attribute the failure to provisioning, not
|
|
574
|
+
// the upstream. Matched by code (not constructor) so both the SDK-owned
|
|
575
|
+
// runtime gate and any not-yet-migrated provider-thrown MISSING_SECRET
|
|
576
|
+
// serialize identically, including across duplicate SDK module instances.
|
|
577
|
+
if (isProviderError(error) && error.code === MISSING_SECRET_CODE) {
|
|
578
|
+
return {
|
|
579
|
+
category: error.options?.category ?? "credential_unavailable",
|
|
580
|
+
taxonomyVersion: PROVIDER_OBSERVABILITY_TAXONOMY_VERSION,
|
|
581
|
+
retryable: error.options?.retryable ?? false,
|
|
582
|
+
};
|
|
583
|
+
}
|
|
561
584
|
if (!isTransportError(error)) {
|
|
562
585
|
return undefined;
|
|
563
586
|
}
|
|
@@ -628,6 +651,10 @@ function toStatusCode(error: unknown): 400 | 401 | 404 | 429 | 500 | 502 | 503 |
|
|
|
628
651
|
case "AUTH_REQUIRED":
|
|
629
652
|
case "reauth_required":
|
|
630
653
|
return 401;
|
|
654
|
+
// Unprovisioned declared secret: a deployment/config defect, never an
|
|
655
|
+
// upstream failure — explicit 400 (was only reached via fallthrough).
|
|
656
|
+
case MISSING_SECRET_CODE:
|
|
657
|
+
return 400;
|
|
631
658
|
case "NOT_FOUND":
|
|
632
659
|
case "not_found":
|
|
633
660
|
case "NO_DATA":
|
|
@@ -1224,8 +1251,16 @@ async function handleAuthFlow(
|
|
|
1224
1251
|
});
|
|
1225
1252
|
}
|
|
1226
1253
|
|
|
1254
|
+
// Same SDK-owned gate as executeOperation: OAuth/credentials ceremonies
|
|
1255
|
+
// depend on declared secrets (client ids/secrets), so fail structured before
|
|
1256
|
+
// any flow code runs instead of at whatever point the ceremony first reads
|
|
1257
|
+
// the env. `abort` stays exempt: a user must always be able to cancel a
|
|
1258
|
+
// stranded flow even when provisioning is broken.
|
|
1227
1259
|
const { context, getPatch } = createAuthFlowContext(provider, request, options, signal);
|
|
1228
1260
|
try {
|
|
1261
|
+
if (route !== "abort") {
|
|
1262
|
+
assertRequiredSecretsPresent(provider, context.env);
|
|
1263
|
+
}
|
|
1229
1264
|
const result =
|
|
1230
1265
|
route === "start"
|
|
1231
1266
|
? await flow.start(context)
|
|
@@ -1280,6 +1315,24 @@ export function createServerApp(
|
|
|
1280
1315
|
allowMemoryFallback: options.allowMemoryStateFallback === true,
|
|
1281
1316
|
});
|
|
1282
1317
|
|
|
1318
|
+
// Boot-time visibility for unprovisioned declared secrets: emit a structured
|
|
1319
|
+
// warn so deploy tooling/alerting sees the gap the moment the pod boots,
|
|
1320
|
+
// instead of discovering it request-by-request. Deliberately log-only — a
|
|
1321
|
+
// boot crash would trade a structured MISSING_SECRET signal for
|
|
1322
|
+
// CrashLoopBackOff. Requests still fail closed via the executeOperation gate.
|
|
1323
|
+
const missingSecretsAtBoot = listMissingRequiredSecrets(
|
|
1324
|
+
provider,
|
|
1325
|
+
createEnvContext(provider.secrets?.map((secret) => secret.name)),
|
|
1326
|
+
);
|
|
1327
|
+
if (missingSecretsAtBoot.length > 0) {
|
|
1328
|
+
logger({
|
|
1329
|
+
level: "warn",
|
|
1330
|
+
event: "provider_secrets_missing",
|
|
1331
|
+
providerId: provider.id,
|
|
1332
|
+
missingSecrets: missingSecretsAtBoot,
|
|
1333
|
+
});
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1283
1336
|
app.notFound((c) =>
|
|
1284
1337
|
c.json(
|
|
1285
1338
|
{
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
@AGENTS.md
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
@AGENTS.md
|
/package/dist/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl
RENAMED
|
File without changes
|
|
File without changes
|
/package/dist/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl
RENAMED
|
File without changes
|
/package/dist/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
/package/src/cli/templates/provider/{skills → .agents/skills}/fixtures-and-recording/SKILL.md.tpl
RENAMED
|
File without changes
|
|
File without changes
|
/package/src/cli/templates/provider/{skills → .agents/skills}/normalization-standards/SKILL.md.tpl
RENAMED
|
File without changes
|
/package/src/cli/templates/provider/{skills → .agents/skills}/pagination-and-counts/SKILL.md.tpl
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|