@apifuse/provider-sdk 2.2.0-beta.60 → 2.2.0-beta.61
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 +17 -16
- package/CHANGELOG.md +4 -0
- package/bin/apifuse-dev.ts +5 -7
- package/bin/apifuse-pack-types.ts +22 -0
- package/bin/apifuse-perf.ts +4 -6
- package/bin/apifuse-record.ts +4 -7
- package/bin/apifuse-submit-check.ts +2 -2
- package/dist/define.d.ts +3 -2
- package/dist/define.js +1 -1
- package/dist/dev.js +3 -1
- package/dist/engine.d.ts +7 -6
- package/dist/engine.js +3 -4
- package/dist/error-resolution.js +21 -4
- package/dist/errors.d.ts +3 -0
- package/dist/errors.js +9 -0
- package/dist/handle-meta.d.ts +39 -0
- package/dist/handle-meta.js +29 -0
- package/dist/handle.d.ts +187 -0
- package/dist/handle.js +254 -0
- package/dist/i18n/catalog.js +21 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +3 -2
- package/dist/lint.d.ts +3 -0
- package/dist/lint.js +352 -47
- package/dist/provider.d.ts +3 -3
- package/dist/provider.js +2 -2
- package/dist/runtime/handle-wordlist.d.ts +43 -0
- package/dist/runtime/{choice-wordlist.js → handle-wordlist.js} +127 -9
- package/dist/runtime/handle.d.ts +71 -0
- package/dist/runtime/handle.js +895 -0
- package/dist/runtime/native-network.d.ts +10 -1
- package/dist/runtime/native-network.js +230 -29
- package/dist/runtime/native-telemetry.d.ts +142 -0
- package/dist/runtime/native-telemetry.js +231 -0
- package/dist/runtime/state.d.ts +4 -1
- package/dist/runtime/state.js +32 -17
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +1 -0
- package/dist/server/sensitive-values.d.ts +1 -1
- package/dist/server/sensitive-values.js +0 -3
- package/dist/server/serve-implementation.d.ts +4 -11
- package/dist/server/serve-implementation.js +86 -18
- package/dist/testing/run.js +10 -7
- package/dist/types.d.ts +4 -107
- package/package.json +1 -1
- package/src/define.ts +4 -3
- package/src/dev.ts +3 -1
- package/src/engine.ts +5 -6
- package/src/error-resolution.ts +21 -4
- package/src/errors.ts +13 -0
- package/src/handle-meta.ts +58 -0
- package/src/handle.ts +529 -0
- package/src/i18n/catalog.ts +23 -1
- package/src/index.ts +26 -13
- package/src/lint.ts +414 -55
- package/src/provider.ts +9 -20
- package/src/runtime/{choice-wordlist.ts → handle-wordlist.ts} +130 -9
- package/src/runtime/handle.ts +1162 -0
- package/src/runtime/native-network.ts +281 -33
- package/src/runtime/native-telemetry.ts +366 -0
- package/src/runtime/state.ts +44 -16
- package/src/server/index.ts +17 -0
- package/src/server/sensitive-values.ts +0 -3
- package/src/server/serve-implementation.ts +87 -32
- package/src/testing/run.ts +11 -7
- package/src/types.ts +4 -137
- package/dist/choice-token.d.ts +0 -24
- package/dist/choice-token.js +0 -74
- package/dist/runtime/choice-wordlist.d.ts +0 -9
- package/dist/runtime/choice.d.ts +0 -27
- package/dist/runtime/choice.js +0 -801
- package/src/choice-token.ts +0 -165
- package/src/runtime/choice.ts +0 -1197
package/AUTHORING.md
CHANGED
|
@@ -41,9 +41,10 @@ rounds — weak models drop or corrupt it. Split the operation into a
|
|
|
41
41
|
|
|
42
42
|
- The prepare operation is non-destructive. A start call takes only the
|
|
43
43
|
scalar intent fields; every response returns a fresh `attempt_token`
|
|
44
|
-
referencing a server-side record (`ctx.
|
|
45
|
-
`
|
|
46
|
-
calls take `attempt_token` plus only the
|
|
44
|
+
referencing a server-side record (a `ctx.handle` draft kind from
|
|
45
|
+
`defineDraft`, declared on the schema with `kind.field()`) that stores
|
|
46
|
+
every settled decision. Continue calls take `attempt_token` plus only the
|
|
47
|
+
NEW answers.
|
|
47
48
|
- `needs_input` rounds list only the still-pending selections; settled
|
|
48
49
|
decisions may ride along in a display-only field but are never re-sent.
|
|
49
50
|
- When nothing is pending, the prepare operation returns `status: "ready"`
|
|
@@ -55,15 +56,14 @@ rounds — weak models drop or corrupt it. Split the operation into a
|
|
|
55
56
|
for what the user picked.
|
|
56
57
|
- Expired or foreign tokens fail factually (nothing happened; start a new
|
|
57
58
|
attempt with the scalar fields) — no answer salvage from a dead token.
|
|
58
|
-
- **
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
interrupted confirm stays retryable.
|
|
59
|
+
- **Commit is one-shot with replay; providers no longer hand-roll dedup.**
|
|
60
|
+
`ctx.handle.read` and `ctx.handle.update` never consume a draft, so a
|
|
61
|
+
confirm handler must execute upstream work through `ctx.handle.commit`. It
|
|
62
|
+
runs the work once, stores the result under the handle, and a repeated
|
|
63
|
+
confirm returns `{ status: "replayed", result }` without touching upstream.
|
|
64
|
+
A failing upstream call restores the draft to `active`, so an interrupted
|
|
65
|
+
confirm stays retryable; later prepare rounds on a committed draft fail
|
|
66
|
+
factually with `HANDLE_COMMITTED`.
|
|
67
67
|
|
|
68
68
|
The invariant behind all of it: complex flow state is the system's job, not
|
|
69
69
|
the model's. The model carries exactly one opaque key between calls.
|
|
@@ -114,8 +114,10 @@ descriptionKey: "operations.realtimeWeather.description",
|
|
|
114
114
|
### Capability declarations
|
|
115
115
|
|
|
116
116
|
Declare each capability used by provider operations. Capabilities without
|
|
117
|
-
configuration use a bare object, for example `http: {}`, `
|
|
118
|
-
`cache: {}`. `
|
|
117
|
+
configuration use a bare object, for example `http: {}`, `state: {}`, or
|
|
118
|
+
`cache: {}`. `handle` is the exception: it takes an array of kinds
|
|
119
|
+
(`handle: [WaitingDraft]`) and requires `state: {}` alongside it. `trace` is
|
|
120
|
+
ambient and must not be declared: every operation
|
|
119
121
|
context receives it. `allowedHosts`, `proxy`, `secrets`, and `context` declare
|
|
120
122
|
policy or metadata only; they do not create context members and are not
|
|
121
123
|
capability bindings.
|
|
@@ -132,8 +134,7 @@ policy intent only.
|
|
|
132
134
|
|
|
133
135
|
Hosted runtime settings are engine-owned as well: every `APIFUSE__CDP_POOL__*`
|
|
134
136
|
variable, the Cloudflare OCR/STT tokens and account identifier,
|
|
135
|
-
`APIFUSE__OCR__API_KEY`, `APIFUSE__CACHE__KEY_PEPPER
|
|
136
|
-
`APIFUSE__PROVIDER_RUNTIME__CHOICE_TOKEN_MASTER_SECRET`. `defineProvider`
|
|
137
|
+
`APIFUSE__OCR__API_KEY`, and `APIFUSE__CACHE__KEY_PEPPER`. `defineProvider`
|
|
137
138
|
rejects them in `secrets`; the engine reads them from its own environment.
|
|
138
139
|
|
|
139
140
|
### Factored operations
|
package/CHANGELOG.md
CHANGED
package/bin/apifuse-dev.ts
CHANGED
|
@@ -5,16 +5,14 @@ import { existsSync } from "node:fs";
|
|
|
5
5
|
import { dirname, relative, resolve } from "node:path";
|
|
6
6
|
import {
|
|
7
7
|
createCredentialContext,
|
|
8
|
-
createEnvContext,
|
|
9
8
|
createHttpClient,
|
|
10
9
|
createInProcessProviderEngine,
|
|
11
10
|
createOcrClientFromEnv,
|
|
12
11
|
createProviderCache,
|
|
13
|
-
|
|
12
|
+
createHandleContext,
|
|
14
13
|
createProviderEnvironment,
|
|
15
14
|
createUnsupportedResolverClient,
|
|
16
15
|
createSttClientFromEnv,
|
|
17
|
-
PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV,
|
|
18
16
|
readEngineProxyCredentials,
|
|
19
17
|
type ProviderDefinition,
|
|
20
18
|
type ProviderEngineBindingCandidates,
|
|
@@ -89,7 +87,6 @@ export function createProviderContext(provider: ProviderDefinition): {
|
|
|
89
87
|
provider.secrets?.map((secret) => secret.name) ?? [],
|
|
90
88
|
);
|
|
91
89
|
const providerEnv = { get: (key: string) => readDiagnosticEnv(key, providerEnvironment) };
|
|
92
|
-
const engineEnv = createEnvContext([PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV]);
|
|
93
90
|
const engineCredentials = readEngineProxyCredentials();
|
|
94
91
|
const credential = createCredentialContext();
|
|
95
92
|
const state = createMemoryProviderRuntimeState();
|
|
@@ -131,10 +128,11 @@ export function createProviderContext(provider: ProviderDefinition): {
|
|
|
131
128
|
: {}),
|
|
132
129
|
})
|
|
133
130
|
: createUnsupportedResolverClient("Provider does not declare resolver capability"),
|
|
134
|
-
|
|
131
|
+
handle: createHandleContext({
|
|
135
132
|
providerId: provider.id,
|
|
136
|
-
|
|
137
|
-
|
|
133
|
+
// Local dev is a single-user session: one stable connection scope so
|
|
134
|
+
// bound handles (drafts, default cursors) work exactly as in production.
|
|
135
|
+
request: { headers: {}, connectionId: "local-dev" },
|
|
138
136
|
state,
|
|
139
137
|
}),
|
|
140
138
|
};
|
|
@@ -450,6 +450,28 @@ const NEGATIVE_CONTROLS = [
|
|
|
450
450
|
"",
|
|
451
451
|
].join("\n"),
|
|
452
452
|
},
|
|
453
|
+
{
|
|
454
|
+
filename: "negative-control-native-telemetry-description.ts",
|
|
455
|
+
expectedCode: "TS2322",
|
|
456
|
+
description: "native gateway telemetry rejects free-string diagnostic fields",
|
|
457
|
+
source: [
|
|
458
|
+
'import type { GatewayIngestible, NativeTelemetryHeaderPayload } from "@apifuse/provider-sdk";',
|
|
459
|
+
"type UnsafeHeader = NativeTelemetryHeaderPayload & { vendorErrorMessage: string };",
|
|
460
|
+
"declare const header: NativeTelemetryHeaderPayload;",
|
|
461
|
+
'export const mustNotCompile: GatewayIngestible<UnsafeHeader> = { ...header, vendorErrorMessage: "free text stays log-only" };',
|
|
462
|
+
].join("\n"),
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
filename: "positive-control-native-telemetry-contributor.ts",
|
|
466
|
+
expectedCode: "",
|
|
467
|
+
description: "native telemetry contributor satisfies the gateway-ingestible contract",
|
|
468
|
+
source: [
|
|
469
|
+
'import { NativeTelemetryCollector, type GatewayIngestible, type NativeTelemetryHeaderPayload, type NativeTelemetryLogPayload, type TelemetryContributor } from "@apifuse/provider-sdk";',
|
|
470
|
+
"export const native: TelemetryContributor<NativeTelemetryLogPayload, NativeTelemetryHeaderPayload> = new NativeTelemetryCollector();",
|
|
471
|
+
"declare const header: NativeTelemetryHeaderPayload;",
|
|
472
|
+
"export const ingestible: GatewayIngestible<NativeTelemetryHeaderPayload> = header;",
|
|
473
|
+
].join("\n"),
|
|
474
|
+
},
|
|
453
475
|
] as const;
|
|
454
476
|
|
|
455
477
|
const tempRoot = mkdtempSync(join(tmpdir(), "apifuse-provider-sdk-pack-types-"));
|
package/bin/apifuse-perf.ts
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
type ApiFuseConfig,
|
|
12
12
|
createBypassProviderCache,
|
|
13
13
|
createHttpClient,
|
|
14
|
-
|
|
14
|
+
createHandleContext,
|
|
15
15
|
createSttClientFromEnv,
|
|
16
16
|
executeOperation,
|
|
17
17
|
getProviderBaseUrl,
|
|
@@ -689,7 +689,7 @@ function createBaseContext(options: {
|
|
|
689
689
|
return {
|
|
690
690
|
env,
|
|
691
691
|
credential,
|
|
692
|
-
request: { headers: {} },
|
|
692
|
+
request: { headers: {}, connectionId: "local-perf" },
|
|
693
693
|
http,
|
|
694
694
|
cache: createBypassProviderCache({ providerId: options.provider.id }),
|
|
695
695
|
state,
|
|
@@ -698,11 +698,9 @@ function createBaseContext(options: {
|
|
|
698
698
|
trace: options.traceContext,
|
|
699
699
|
auth: createAuthStub(),
|
|
700
700
|
stt: createSttClientFromEnv(options.provider.stt),
|
|
701
|
-
|
|
701
|
+
handle: createHandleContext({
|
|
702
702
|
providerId: options.provider.id,
|
|
703
|
-
|
|
704
|
-
request: { headers: {} },
|
|
705
|
-
credential,
|
|
703
|
+
request: { headers: {}, connectionId: "local-perf" },
|
|
706
704
|
state,
|
|
707
705
|
}),
|
|
708
706
|
};
|
package/bin/apifuse-record.ts
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
createHttpClient,
|
|
12
12
|
createInProcessProviderEngine,
|
|
13
13
|
createOcrClientFromEnv,
|
|
14
|
-
|
|
14
|
+
createHandleContext,
|
|
15
15
|
createProviderEnvironment,
|
|
16
16
|
createSttClientFromEnv,
|
|
17
17
|
createUnsupportedResolverClient,
|
|
@@ -521,7 +521,6 @@ export function createCaptureContext(
|
|
|
521
521
|
provider.secrets?.map((secret) => secret.name) ?? [],
|
|
522
522
|
);
|
|
523
523
|
const env = { get: (key: string) => readDiagnosticEnv(key, providerEnvironment) };
|
|
524
|
-
const engineEnv = { get: (key: string) => readDiagnosticEnv(key) };
|
|
525
524
|
const engineCredentials = readEngineProxyCredentials();
|
|
526
525
|
const credential = {
|
|
527
526
|
mode: "none" as const,
|
|
@@ -537,7 +536,7 @@ export function createCaptureContext(
|
|
|
537
536
|
const candidates: ProviderEngineBindingCandidates = {
|
|
538
537
|
env,
|
|
539
538
|
credential,
|
|
540
|
-
request: { headers: {} },
|
|
539
|
+
request: { headers: {}, connectionId: "local-record" },
|
|
541
540
|
http,
|
|
542
541
|
cache,
|
|
543
542
|
state,
|
|
@@ -583,11 +582,9 @@ export function createCaptureContext(
|
|
|
583
582
|
: {}),
|
|
584
583
|
})
|
|
585
584
|
: createUnsupportedResolverClient("Provider does not declare resolver capability"),
|
|
586
|
-
|
|
585
|
+
handle: createHandleContext({
|
|
587
586
|
providerId: provider.id,
|
|
588
|
-
|
|
589
|
-
request: { headers: {} },
|
|
590
|
-
credential,
|
|
587
|
+
request: { headers: {}, connectionId: "local-record" },
|
|
591
588
|
state,
|
|
592
589
|
}),
|
|
593
590
|
};
|
|
@@ -949,11 +949,11 @@ function scoreNoDynamicCode(providerRoot: string): SubmitCheck {
|
|
|
949
949
|
}
|
|
950
950
|
|
|
951
951
|
const REDUNDANT_RUNTIME_GUARD_PATTERNS: readonly RegExp[] = [
|
|
952
|
-
/\bctx\.(?:stealth|http|cache|state|browser|trace|auth|stt|
|
|
952
|
+
/\bctx\.(?:stealth|http|cache|state|browser|trace|auth|stt|handle)\?\./,
|
|
953
953
|
];
|
|
954
954
|
|
|
955
955
|
const SDK_CONTEXT_METHOD_ALIAS_PATTERN =
|
|
956
|
-
/\bconst\s+(\w+)\s*=\s*ctx\.(?:stealth|http|cache|state|browser|trace|auth|stt|
|
|
956
|
+
/\bconst\s+(\w+)\s*=\s*ctx\.(?:stealth|http|cache|state|browser|trace|auth|stt|handle)\.(?:\w+)/;
|
|
957
957
|
|
|
958
958
|
function hasRedundantRuntimeGuard(line: string, remainingLines: readonly string[]): boolean {
|
|
959
959
|
if (REDUNDANT_RUNTIME_GUARD_PATTERNS.some((pattern) => pattern.test(line))) {
|
package/dist/define.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { HandleKind } from "./handle.js";
|
|
1
2
|
import type { AuthConfig, BrowserEngine, ContextDeclaration, CredentialDeclaration, HealthJourneyDefinition, HealthJourneySchedule, HealthScheduleRandomization, InferSchemaOutput, NativeProviderConfig, OperationDefinition, OperationHandlerResult, OperationHttpStreamTransport, OperationSseTransport, OperationWebSocketTransport, ProviderAccessConfig, ProviderContext, ProviderDefinition, ProviderDeploymentOverrides, ProviderHealthMonitorConfig, ProviderOcrConfig, ProviderProxyConfig, ProviderPublicProfile, ProviderResolverConfig, ProviderReviewed, ProviderRuntimeTarget, ProviderSecretDeclaration, ProviderStreamEvent, ProviderSttConfig, ProviderStealthConfig, SchemaLike, SmsOtpMatcherDefinition } from "./types.js";
|
|
2
3
|
type ProviderImplementationSourceAccess = "official_api" | "private_api" | "browser_flow" | "hybrid";
|
|
3
4
|
type ProviderImplementationCredentialStrategy = "apifuse_managed" | "workspace_secret" | "user_oauth" | "user_session" | "none";
|
|
@@ -70,8 +71,8 @@ export interface ProviderDeclaration {
|
|
|
70
71
|
engine: BrowserEngine;
|
|
71
72
|
};
|
|
72
73
|
auth?: AuthConfig;
|
|
73
|
-
/** Declares the
|
|
74
|
-
|
|
74
|
+
/** Declares the handle kinds this provider issues and accepts (ADR-0012). */
|
|
75
|
+
handle?: readonly HandleKind[];
|
|
75
76
|
reviewed?: ProviderReviewed;
|
|
76
77
|
access?: ProviderAccessConfig;
|
|
77
78
|
/** Declares secret requirements; this does not add a `ctx.secrets` member. */
|
package/dist/define.js
CHANGED
|
@@ -2040,7 +2040,7 @@ function finalizeProvider(config) {
|
|
|
2040
2040
|
resolver: config.resolver,
|
|
2041
2041
|
browser: config.browser,
|
|
2042
2042
|
auth: config.auth,
|
|
2043
|
-
|
|
2043
|
+
handle: config.handle,
|
|
2044
2044
|
reviewed: config.reviewed,
|
|
2045
2045
|
access: config.access,
|
|
2046
2046
|
secrets: config.secrets,
|
package/dist/dev.js
CHANGED
|
@@ -3,7 +3,9 @@ export function createDevServer(provider, options) {
|
|
|
3
3
|
const port = options?.port ?? 3900;
|
|
4
4
|
return {
|
|
5
5
|
start: () => {
|
|
6
|
-
|
|
6
|
+
// Local development has no Redis by default; handles (ADR-0012) need a
|
|
7
|
+
// state backend, so fall back to memory state when none is configured.
|
|
8
|
+
void serve(provider, { port, engine: options?.engine, allowMemoryStateFallback: true });
|
|
7
9
|
console.log(`[apifuse dev] ${provider.id}@${provider.version} running at http://localhost:${port}`);
|
|
8
10
|
console.log(`[apifuse dev] Operations: ${Object.keys(provider.operations).join(", ")}`);
|
|
9
11
|
console.log(`[apifuse dev] Health: http://localhost:${port}/health`);
|
package/dist/engine.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { HandleContext } from "./handle.js";
|
|
2
|
+
import type { AuthContext, BrowserClient, CredentialContext, EnvContext, HttpClient, NativeContext, OcrContext, ProviderCache, ProviderContext, ProviderDefinition, ProviderFilesContext, ProviderRequestContext, ProviderRuntimeState, ResolverContext, StealthClient, SttContext, TraceContext } from "./types.js";
|
|
2
3
|
/** Versioned envelope protocol used by out-of-process engine transports. */
|
|
3
4
|
export declare const PROVIDER_ENGINE_PROTOCOL_VERSION: "provider-engine.v1";
|
|
4
5
|
/** Credential names owned by the engine and forbidden in provider declarations. */
|
|
@@ -21,11 +22,11 @@ export declare function isEngineOwnedResolverCredentialName(name: string): boole
|
|
|
21
22
|
export declare const ENGINE_OWNED_TELEMETRY_ENV_NAMES: readonly ["OTEL_EXPORTER_OTLP_TRACES_ENDPOINT", "OTEL_EXPORTER_OTLP_ENDPOINT", "OTEL_EXPORTER_OTLP_TRACES_HEADERS", "OTEL_EXPORTER_OTLP_HEADERS", "OTEL_SERVICE_NAME", "OTEL_RESOURCE_ATTRIBUTES"];
|
|
22
23
|
export declare function isEngineOwnedTelemetryEnvName(name: string): boolean;
|
|
23
24
|
/**
|
|
24
|
-
* Hosted runtime settings the engine reads for OCR, STT, cache keying,
|
|
25
|
-
*
|
|
25
|
+
* Hosted runtime settings the engine reads for OCR, STT, cache keying, and
|
|
26
|
+
* the CDP pool. Providers never own them: declarations are rejected
|
|
26
27
|
* and projections drop them, like the vendor credentials above.
|
|
27
28
|
*/
|
|
28
|
-
export declare const ENGINE_OWNED_RUNTIME_ENV_NAMES: readonly ["APIFUSE__STT__CLOUDFLARE_API_TOKEN", "APIFUSE__OCR__CLOUDFLARE_API_TOKEN", "APIFUSE__OCR__API_KEY", "APIFUSE__CLOUDFLARE__ACCOUNT_ID", "APIFUSE__CACHE__KEY_PEPPER"
|
|
29
|
+
export declare const ENGINE_OWNED_RUNTIME_ENV_NAMES: readonly ["APIFUSE__STT__CLOUDFLARE_API_TOKEN", "APIFUSE__OCR__CLOUDFLARE_API_TOKEN", "APIFUSE__OCR__API_KEY", "APIFUSE__CLOUDFLARE__ACCOUNT_ID", "APIFUSE__CACHE__KEY_PEPPER"];
|
|
29
30
|
export declare function isEngineOwnedRuntimeEnvName(name: string): boolean;
|
|
30
31
|
/** Every environment name the engine owns: rejected in declarations and filtered from provider projections. */
|
|
31
32
|
export declare function isEngineOwnedEnvName(name: string): boolean;
|
|
@@ -70,14 +71,14 @@ export interface ProviderEngineCapabilitySurface {
|
|
|
70
71
|
export interface ProviderEngineResidentSurface {
|
|
71
72
|
readonly native: NativeContext;
|
|
72
73
|
}
|
|
73
|
-
export declare const PROVIDER_CAPABILITY_KEYS: readonly ["env", "credential", "http", "files", "native", "cache", "state", "stealth", "browser", "auth", "ocr", "stt", "resolver", "
|
|
74
|
+
export declare const PROVIDER_CAPABILITY_KEYS: readonly ["env", "credential", "http", "files", "native", "cache", "state", "stealth", "browser", "auth", "ocr", "stt", "resolver", "handle"];
|
|
74
75
|
export type ProviderCapabilityKey = (typeof PROVIDER_CAPABILITY_KEYS)[number];
|
|
75
76
|
export type ProviderEngineBindingCandidates = Partial<ProviderEngineCapabilitySurface & ProviderEngineResidentSurface & {
|
|
76
77
|
readonly env: EnvContext;
|
|
77
78
|
readonly credential: CredentialContext;
|
|
78
79
|
readonly files: ProviderFilesContext;
|
|
79
80
|
readonly auth: AuthContext;
|
|
80
|
-
readonly
|
|
81
|
+
readonly handle: HandleContext;
|
|
81
82
|
}> & {
|
|
82
83
|
readonly request?: ProviderRequestContext;
|
|
83
84
|
readonly trace: TraceContext;
|
package/dist/engine.js
CHANGED
|
@@ -69,8 +69,8 @@ function isEngineOwnedEngineEnvName(name) {
|
|
|
69
69
|
return canonicalEnvName(name).startsWith("APIFUSE__ENGINE__");
|
|
70
70
|
}
|
|
71
71
|
/**
|
|
72
|
-
* Hosted runtime settings the engine reads for OCR, STT, cache keying,
|
|
73
|
-
*
|
|
72
|
+
* Hosted runtime settings the engine reads for OCR, STT, cache keying, and
|
|
73
|
+
* the CDP pool. Providers never own them: declarations are rejected
|
|
74
74
|
* and projections drop them, like the vendor credentials above.
|
|
75
75
|
*/
|
|
76
76
|
export const ENGINE_OWNED_RUNTIME_ENV_NAMES = [
|
|
@@ -79,7 +79,6 @@ export const ENGINE_OWNED_RUNTIME_ENV_NAMES = [
|
|
|
79
79
|
"APIFUSE__OCR__API_KEY",
|
|
80
80
|
"APIFUSE__CLOUDFLARE__ACCOUNT_ID",
|
|
81
81
|
"APIFUSE__CACHE__KEY_PEPPER",
|
|
82
|
-
"APIFUSE__PROVIDER_RUNTIME__CHOICE_TOKEN_MASTER_SECRET",
|
|
83
82
|
];
|
|
84
83
|
const ENGINE_OWNED_RUNTIME_ENV_NAME_SET = new Set(ENGINE_OWNED_RUNTIME_ENV_NAMES);
|
|
85
84
|
/** CDP pool settings form a prefix family (`APIFUSE__CDP_POOL__URL` today); the whole prefix is engine-owned. */
|
|
@@ -127,7 +126,7 @@ export const PROVIDER_CAPABILITY_KEYS = [
|
|
|
127
126
|
"ocr",
|
|
128
127
|
"stt",
|
|
129
128
|
"resolver",
|
|
130
|
-
"
|
|
129
|
+
"handle",
|
|
131
130
|
];
|
|
132
131
|
const CAPABILITY_KEY_SET = new Set(PROVIDER_CAPABILITY_KEYS);
|
|
133
132
|
function declaresCapability(provider, capability) {
|
package/dist/error-resolution.js
CHANGED
|
@@ -18,10 +18,17 @@ export const SDK_OWNED_PROVIDER_ERROR_CODES = new Set([
|
|
|
18
18
|
"refresh_not_supported",
|
|
19
19
|
"RUNTIME_UNSUPPORTED",
|
|
20
20
|
"PROVIDER_STATE_UNSUPPORTED",
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
21
|
+
"HANDLE_INVALID",
|
|
22
|
+
"HANDLE_NOT_FOUND",
|
|
23
|
+
"HANDLE_EXPIRED",
|
|
24
|
+
"HANDLE_KIND_MISMATCH",
|
|
25
|
+
"HANDLE_BUSY",
|
|
26
|
+
"HANDLE_COMMITTED",
|
|
27
|
+
"HANDLE_CONNECTION_REQUIRED",
|
|
28
|
+
"HANDLE_STORAGE_UNAVAILABLE",
|
|
29
|
+
"HANDLE_TOO_LARGE",
|
|
30
|
+
"HANDLE_INVALID_DATA",
|
|
31
|
+
"PICK_NOT_OFFERED",
|
|
25
32
|
"unsupported_stealth_cookie_store_version",
|
|
26
33
|
"provider_secret_error",
|
|
27
34
|
"credential_key_error",
|
|
@@ -125,6 +132,16 @@ export const SDK_STATUS_MAPPED_PROVIDER_ERROR_CODES = new Map([
|
|
|
125
132
|
["EGRESS_LEASE_INVALID", 409],
|
|
126
133
|
["REPLAY_SESSION_MISMATCH", 409],
|
|
127
134
|
["REPLAY_ALREADY_ATTEMPTED", 409],
|
|
135
|
+
// Handles (ADR-0012): only caller-side faults are mapped. Storage,
|
|
136
|
+
// connection-scope, size, and schema faults stay 500 because the provider
|
|
137
|
+
// or host owns them.
|
|
138
|
+
["HANDLE_INVALID", 400],
|
|
139
|
+
["HANDLE_KIND_MISMATCH", 400],
|
|
140
|
+
["PICK_NOT_OFFERED", 400],
|
|
141
|
+
["HANDLE_NOT_FOUND", 404],
|
|
142
|
+
["HANDLE_EXPIRED", 410],
|
|
143
|
+
["HANDLE_COMMITTED", 409],
|
|
144
|
+
["HANDLE_BUSY", 409],
|
|
128
145
|
["UPSTREAM_ERROR", 502],
|
|
129
146
|
["BLOCKED", 502],
|
|
130
147
|
["OCR_UNAVAILABLE", 503],
|
package/dist/errors.d.ts
CHANGED
|
@@ -74,6 +74,9 @@ export declare class HttpRedirectError extends TransportError {
|
|
|
74
74
|
export declare function isProviderError(value: unknown): value is ProviderError;
|
|
75
75
|
export declare function isSessionExpiredError(value: unknown): value is SessionExpiredError;
|
|
76
76
|
export declare function isTransportError(value: unknown): value is TransportError;
|
|
77
|
+
/** Brands a handle error (ADR-0012) so `isHandleError` works across duplicate SDK module copies. */
|
|
78
|
+
export declare function brandHandleError(target: object): void;
|
|
79
|
+
export declare function hasHandleErrorBrand(value: unknown): boolean;
|
|
77
80
|
export declare function isValidationError(value: unknown): value is ValidationError;
|
|
78
81
|
export declare class ProviderSecretError extends ProviderError {
|
|
79
82
|
constructor(message: string, options?: ProviderErrorOptions);
|
package/dist/errors.js
CHANGED
|
@@ -9,6 +9,7 @@ const PROVIDER_ERROR_BRAND_VALUE = 1;
|
|
|
9
9
|
const SESSION_EXPIRED_BRAND = Symbol.for("@apifuse/provider-sdk/error-kind/session-expired@1");
|
|
10
10
|
const TRANSPORT_BRAND = Symbol.for("@apifuse/provider-sdk/error-kind/transport@1");
|
|
11
11
|
const VALIDATION_BRAND = Symbol.for("@apifuse/provider-sdk/error-kind/validation@1");
|
|
12
|
+
const HANDLE_BRAND = Symbol.for("@apifuse/provider-sdk/error-kind/handle@1");
|
|
12
13
|
// Defines a non-enumerable, non-writable, non-configurable own data property.
|
|
13
14
|
// Immutable + own means a guard can trust it via a single descriptor read
|
|
14
15
|
// without invoking attacker-controlled getters or accepting inherited brands.
|
|
@@ -143,6 +144,14 @@ export function isSessionExpiredError(value) {
|
|
|
143
144
|
export function isTransportError(value) {
|
|
144
145
|
return isProviderError(value) && hasOwnBrand(value, TRANSPORT_BRAND, true);
|
|
145
146
|
}
|
|
147
|
+
/** Brands a handle error (ADR-0012) so `isHandleError` works across duplicate SDK module copies. */
|
|
148
|
+
export function brandHandleError(target) {
|
|
149
|
+
defineErrorBrand(target, HANDLE_BRAND, true);
|
|
150
|
+
}
|
|
151
|
+
export function hasHandleErrorBrand(value) {
|
|
152
|
+
return (hasOwnBrand(value, PROVIDER_ERROR_BRAND, PROVIDER_ERROR_BRAND_VALUE) &&
|
|
153
|
+
hasOwnBrand(value, HANDLE_BRAND, true));
|
|
154
|
+
}
|
|
146
155
|
export function isValidationError(value) {
|
|
147
156
|
return (isProviderError(value) &&
|
|
148
157
|
(hasOwnBrand(value, VALIDATION_BRAND, true) || value.name === "ValidationError"));
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared, dependency-free definitions for LLM-friendly handles (ADR-0012).
|
|
3
|
+
*
|
|
4
|
+
* Kept separate from the runtime so schema helpers, lint, and the runtime can
|
|
5
|
+
* agree on the JSON Schema meta key and the declaration shape without importing
|
|
6
|
+
* each other.
|
|
7
|
+
*/
|
|
8
|
+
/** JSON Schema meta key placed on every schema field created by `kind.field()`. */
|
|
9
|
+
export declare const APIFUSE_HANDLE_META_KEY: "x-apifuse-handle";
|
|
10
|
+
export type HandleKindType = "cursor" | "draft";
|
|
11
|
+
export type HandleAccess = "bound" | "public";
|
|
12
|
+
/** Value stored under {@link APIFUSE_HANDLE_META_KEY} on a handle field. */
|
|
13
|
+
export interface HandleFieldMeta {
|
|
14
|
+
readonly kind: string;
|
|
15
|
+
readonly type: HandleKindType;
|
|
16
|
+
readonly fieldName: string;
|
|
17
|
+
readonly issuedBy?: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Structural view of a handle kind as seen by the provider declaration
|
|
21
|
+
* (`handle: [WaitingDraft, PageCursor]`), lint, and the registry. The runtime
|
|
22
|
+
* kinds returned by `defineCursor` / `defineDraft` satisfy this shape.
|
|
23
|
+
*/
|
|
24
|
+
export interface HandleKindDeclaration {
|
|
25
|
+
readonly name: string;
|
|
26
|
+
readonly type: HandleKindType;
|
|
27
|
+
readonly fieldName: string;
|
|
28
|
+
readonly access: HandleAccess;
|
|
29
|
+
readonly issuedBy?: string;
|
|
30
|
+
}
|
|
31
|
+
/** Kind names are lowercase letters only so they never fuse with the word body. */
|
|
32
|
+
export declare const HANDLE_KIND_NAME_PATTERN: RegExp;
|
|
33
|
+
export declare function isHandleFieldMeta(value: unknown): value is HandleFieldMeta;
|
|
34
|
+
/**
|
|
35
|
+
* The SDK-owned description `kind.field()` emits. Lint exempts a handle field
|
|
36
|
+
* from `describeKey` only while its description is exactly this text; a
|
|
37
|
+
* provider override re-enters the localization rules.
|
|
38
|
+
*/
|
|
39
|
+
export declare function handleFieldDescription(meta: Pick<HandleFieldMeta, "issuedBy">): string;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared, dependency-free definitions for LLM-friendly handles (ADR-0012).
|
|
3
|
+
*
|
|
4
|
+
* Kept separate from the runtime so schema helpers, lint, and the runtime can
|
|
5
|
+
* agree on the JSON Schema meta key and the declaration shape without importing
|
|
6
|
+
* each other.
|
|
7
|
+
*/
|
|
8
|
+
/** JSON Schema meta key placed on every schema field created by `kind.field()`. */
|
|
9
|
+
export const APIFUSE_HANDLE_META_KEY = "x-apifuse-handle";
|
|
10
|
+
/** Kind names are lowercase letters only so they never fuse with the word body. */
|
|
11
|
+
export const HANDLE_KIND_NAME_PATTERN = /^[a-z]{2,12}$/;
|
|
12
|
+
export function isHandleFieldMeta(value) {
|
|
13
|
+
if (typeof value !== "object" || value === null)
|
|
14
|
+
return false;
|
|
15
|
+
const record = value;
|
|
16
|
+
return (typeof record.kind === "string" &&
|
|
17
|
+
(record.type === "cursor" || record.type === "draft") &&
|
|
18
|
+
typeof record.fieldName === "string" &&
|
|
19
|
+
(record.issuedBy === undefined || typeof record.issuedBy === "string"));
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The SDK-owned description `kind.field()` emits. Lint exempts a handle field
|
|
23
|
+
* from `describeKey` only while its description is exactly this text; a
|
|
24
|
+
* provider override re-enters the localization rules.
|
|
25
|
+
*/
|
|
26
|
+
export function handleFieldDescription(meta) {
|
|
27
|
+
const origin = meta.issuedBy ? ` issued by \`${meta.issuedBy}\`` : "";
|
|
28
|
+
return `Opaque handle${origin}. Copy it exactly as returned; do not edit or shorten it.`;
|
|
29
|
+
}
|