@apifuse/provider-sdk 2.2.0-beta.3 → 2.2.0-beta.4
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 +14 -0
- package/CHANGELOG.md +4 -0
- package/bin/apifuse-check.ts +18 -58
- package/bin/apifuse-create.ts +1 -1
- package/bin/apifuse-dev.ts +27 -52
- package/bin/apifuse-pack-smoke.ts +22 -81
- package/bin/apifuse-pack-types.ts +266 -0
- package/bin/apifuse-perf.ts +45 -127
- package/bin/apifuse-record.ts +23 -61
- package/bin/apifuse-submit-check.ts +26 -21
- package/bin/apifuse.ts +1 -1
- package/bin/submit-check-xml.ts +1 -1
- package/dist/auth-turn/index.d.ts +1 -1
- package/dist/auth.d.ts +2 -2
- package/dist/auth.js +9 -18
- package/dist/ceremonies/index.d.ts +1 -1
- package/dist/ceremonies/index.js +8 -18
- package/dist/config/loader.d.ts +2 -2
- package/dist/config/loader.js +19 -42
- package/dist/contract-serialization.d.ts +2 -2
- package/dist/contract-serialization.js +3 -6
- package/dist/contract-types.d.ts +2 -2
- package/dist/contract.d.ts +3 -3
- package/dist/contract.js +4 -6
- package/dist/define.d.ts +8 -1
- package/dist/define.js +57 -115
- package/dist/dev.d.ts +1 -1
- package/dist/dev.js +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/errors.js +1 -3
- package/dist/i18n/catalog.d.ts +2 -2
- package/dist/i18n/catalog.js +4 -10
- package/dist/i18n/index.d.ts +2 -2
- package/dist/i18n/index.js +2 -2
- package/dist/i18n/keys.d.ts +2 -2
- package/dist/index.d.ts +43 -42
- package/dist/index.js +38 -37
- package/dist/lint.js +8 -15
- package/dist/provider.d.ts +11 -11
- package/dist/provider.js +9 -9
- package/dist/public-schema-field-lint.d.ts +1 -1
- package/dist/recipes/gov-api.js +1 -1
- package/dist/runtime/auth-flow.d.ts +1 -1
- package/dist/runtime/auth-flow.js +3 -3
- package/dist/runtime/browser.d.ts +1 -1
- package/dist/runtime/browser.js +15 -29
- package/dist/runtime/cache.d.ts +1 -1
- package/dist/runtime/cache.js +4 -8
- package/dist/runtime/choice.d.ts +1 -1
- package/dist/runtime/choice.js +12 -34
- package/dist/runtime/credential.d.ts +1 -1
- package/dist/runtime/credential.js +1 -1
- package/dist/runtime/env.d.ts +1 -1
- package/dist/runtime/executor.d.ts +1 -1
- package/dist/runtime/executor.js +2 -2
- package/dist/runtime/http.d.ts +2 -2
- package/dist/runtime/http.js +5 -5
- package/dist/runtime/insights.d.ts +1 -1
- package/dist/runtime/insights.js +6 -13
- package/dist/runtime/instrumentation.d.ts +2 -2
- package/dist/runtime/instrumentation.js +5 -15
- package/dist/runtime/keyring.js +1 -1
- package/dist/runtime/namespace.js +1 -1
- package/dist/runtime/otlp.d.ts +1 -1
- package/dist/runtime/perf.d.ts +1 -1
- package/dist/runtime/provider.d.ts +1 -1
- package/dist/runtime/provider.js +1 -2
- package/dist/runtime/proxy-errors.d.ts +1 -1
- package/dist/runtime/proxy-errors.js +3 -5
- package/dist/runtime/proxy-retry-policy.d.ts +2 -2
- package/dist/runtime/proxy-retry-policy.js +2 -2
- package/dist/runtime/proxy-telemetry.d.ts +1 -1
- package/dist/runtime/proxy-telemetry.js +16 -48
- package/dist/runtime/redis.d.ts +1 -1
- package/dist/runtime/redis.js +2 -4
- package/dist/runtime/request-options.d.ts +1 -1
- package/dist/runtime/state.d.ts +2 -2
- package/dist/runtime/state.js +3 -3
- package/dist/runtime/stealth.d.ts +2 -2
- package/dist/runtime/stealth.js +6 -6
- package/dist/runtime/stt.d.ts +1 -1
- package/dist/runtime/stt.js +11 -15
- package/dist/runtime/trace.d.ts +2 -2
- package/dist/runtime/trace.js +2 -4
- package/dist/runtime/waterfall.d.ts +1 -1
- package/dist/schema.d.ts +1 -1
- package/dist/schema.js +7 -15
- package/dist/serve.d.ts +1 -1
- package/dist/serve.js +1 -1
- package/dist/server/index.d.ts +7 -7
- package/dist/server/index.js +6 -6
- package/dist/server/self-test-redaction.d.ts +1 -1
- package/dist/server/self-test-redaction.js +1 -1
- package/dist/server/self-test.d.ts +3 -3
- package/dist/server/self-test.js +3 -3
- package/dist/server/serve.d.ts +3 -3
- package/dist/server/serve.js +36 -49
- package/dist/server/types.js +2 -7
- package/dist/stealth/profiles.d.ts +1 -1
- package/dist/stealth/profiles.js +5 -14
- package/dist/stream.d.ts +1 -1
- package/dist/testing/index.d.ts +2 -2
- package/dist/testing/index.js +2 -2
- package/dist/testing/run.d.ts +1 -1
- package/dist/testing/run.js +7 -14
- package/dist/types.d.ts +57 -0
- package/dist/user-input.d.ts +30 -0
- package/dist/user-input.js +66 -0
- package/package.json +6 -4
- package/src/auth-turn/index.ts +1 -1
- package/src/auth.ts +28 -86
- package/src/ceremonies/index.ts +24 -78
- package/src/config/loader.ts +58 -176
- package/src/contract-serialization.ts +5 -11
- package/src/contract-types.ts +2 -2
- package/src/contract.ts +12 -28
- package/src/define.ts +174 -495
- package/src/dev.ts +4 -9
- package/src/errors.ts +4 -11
- package/src/i18n/catalog.ts +10 -32
- package/src/i18n/index.ts +2 -2
- package/src/i18n/keys.ts +5 -11
- package/src/index.ts +44 -42
- package/src/lint.ts +89 -159
- package/src/provider.ts +12 -11
- package/src/public-schema-field-lint.ts +7 -33
- package/src/recipes/gov-api.ts +2 -5
- package/src/runtime/auth-flow.ts +5 -7
- package/src/runtime/browser.ts +64 -187
- package/src/runtime/cache.ts +20 -67
- package/src/runtime/choice.ts +55 -129
- package/src/runtime/credential.ts +2 -2
- package/src/runtime/env.ts +1 -1
- package/src/runtime/executor.ts +9 -19
- package/src/runtime/http.ts +7 -7
- package/src/runtime/insights.ts +15 -53
- package/src/runtime/instrumentation.ts +19 -58
- package/src/runtime/keyring.ts +7 -19
- package/src/runtime/namespace.ts +2 -7
- package/src/runtime/otlp.ts +12 -23
- package/src/runtime/perf.ts +1 -1
- package/src/runtime/provider.ts +4 -9
- package/src/runtime/proxy-errors.ts +17 -38
- package/src/runtime/proxy-retry-policy.ts +3 -3
- package/src/runtime/proxy-telemetry.ts +23 -72
- package/src/runtime/redis.ts +4 -12
- package/src/runtime/request-options.ts +4 -13
- package/src/runtime/state.ts +30 -109
- package/src/runtime/stealth.ts +8 -8
- package/src/runtime/stt.ts +38 -94
- package/src/runtime/trace.ts +14 -44
- package/src/runtime/waterfall.ts +5 -18
- package/src/schema.ts +23 -84
- package/src/serve.ts +1 -1
- package/src/server/index.ts +7 -7
- package/src/server/self-test-redaction.ts +2 -2
- package/src/server/self-test.ts +5 -5
- package/src/server/serve.ts +87 -242
- package/src/server/types.ts +6 -19
- package/src/stealth/profiles.ts +10 -26
- package/src/stream.ts +8 -19
- package/src/testing/index.ts +2 -2
- package/src/testing/run.ts +19 -67
- package/src/types.ts +58 -0
- package/src/user-input.ts +118 -0
package/src/stealth/profiles.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { SDKError } from "../errors";
|
|
2
|
-
import type { StealthPlatform, StealthProfile } from "../types";
|
|
1
|
+
import { SDKError } from "../errors.js";
|
|
2
|
+
import type { StealthPlatform, StealthProfile } from "../types.js";
|
|
3
3
|
|
|
4
4
|
type StealthProfileDefinition = Omit<StealthProfile, "name" | "platform"> & {
|
|
5
5
|
platform: StealthPlatform;
|
|
@@ -81,10 +81,7 @@ const FIREFOX_JA3 =
|
|
|
81
81
|
const SAFARI_JA3 =
|
|
82
82
|
"771,4865-4866-4867-49196-49195-52393-49200-49199-49188-49192-159-158-107-103-57-51-157-156-61-60-53-47-255,0-23-65281-10-11-16-5-13-18-51-45-43-27,29-23-24-25,0";
|
|
83
83
|
|
|
84
|
-
function createProfile(
|
|
85
|
-
name: string,
|
|
86
|
-
definition: StealthProfileDefinition,
|
|
87
|
-
): StealthProfile {
|
|
84
|
+
function createProfile(name: string, definition: StealthProfileDefinition): StealthProfile {
|
|
88
85
|
return {
|
|
89
86
|
name,
|
|
90
87
|
platform: definition.platform,
|
|
@@ -93,12 +90,8 @@ function createProfile(
|
|
|
93
90
|
tlsClientIdentifier: definition.tlsClientIdentifier,
|
|
94
91
|
ja3: definition.ja3,
|
|
95
92
|
ja4: definition.ja4,
|
|
96
|
-
h2Settings: definition.h2Settings
|
|
97
|
-
|
|
98
|
-
: undefined,
|
|
99
|
-
headerOrder: definition.headerOrder
|
|
100
|
-
? [...definition.headerOrder]
|
|
101
|
-
: undefined,
|
|
93
|
+
h2Settings: definition.h2Settings ? { ...definition.h2Settings } : undefined,
|
|
94
|
+
headerOrder: definition.headerOrder ? [...definition.headerOrder] : undefined,
|
|
102
95
|
};
|
|
103
96
|
}
|
|
104
97
|
|
|
@@ -108,8 +101,7 @@ function extractBrowserMajorVersion(profile: StealthProfile): string {
|
|
|
108
101
|
return chromeVersion;
|
|
109
102
|
}
|
|
110
103
|
|
|
111
|
-
const identifierVersion =
|
|
112
|
-
profile.tlsClientIdentifier?.match(/(\d+)(?!.*\d)/)?.[1];
|
|
104
|
+
const identifierVersion = profile.tlsClientIdentifier?.match(/(\d+)(?!.*\d)/)?.[1];
|
|
113
105
|
if (identifierVersion) {
|
|
114
106
|
return identifierVersion;
|
|
115
107
|
}
|
|
@@ -132,9 +124,7 @@ function toPlatformHeaderValue(platform: StealthPlatform): string {
|
|
|
132
124
|
}
|
|
133
125
|
}
|
|
134
126
|
|
|
135
|
-
export function generateLayer2Headers(
|
|
136
|
-
profile: StealthProfile,
|
|
137
|
-
): Record<string, string> {
|
|
127
|
+
export function generateLayer2Headers(profile: StealthProfile): Record<string, string> {
|
|
138
128
|
const headers: Record<string, string> = {
|
|
139
129
|
"Accept-Language": "ko-KR,ko;q=0.9,en-US;q=0.8",
|
|
140
130
|
};
|
|
@@ -143,16 +133,13 @@ export function generateLayer2Headers(
|
|
|
143
133
|
const majorVersion = extractBrowserMajorVersion(profile);
|
|
144
134
|
|
|
145
135
|
if (identifier.startsWith("chrome_") || identifier.startsWith("edge_")) {
|
|
146
|
-
const isEdge =
|
|
147
|
-
identifier.startsWith("edge_") || /\bEdg\//.test(profile.userAgent);
|
|
136
|
+
const isEdge = identifier.startsWith("edge_") || /\bEdg\//.test(profile.userAgent);
|
|
148
137
|
headers["Sec-Ch-Ua"] = isEdge
|
|
149
138
|
? `"Chromium";v="${majorVersion}", "Microsoft Edge";v="${majorVersion}", "Not)A;Brand";v="99"`
|
|
150
139
|
: `"Chromium";v="${majorVersion}", "Google Chrome";v="${majorVersion}", "Not)A;Brand";v="99"`;
|
|
151
140
|
headers["Sec-Ch-Ua-Platform"] = toPlatformHeaderValue(profile.platform);
|
|
152
141
|
headers["Sec-Ch-Ua-Mobile"] =
|
|
153
|
-
profile.platform === "android" || profile.platform === "ios"
|
|
154
|
-
? "?1"
|
|
155
|
-
: "?0";
|
|
142
|
+
profile.platform === "android" || profile.platform === "ios" ? "?1" : "?0";
|
|
156
143
|
}
|
|
157
144
|
|
|
158
145
|
return headers;
|
|
@@ -301,8 +288,5 @@ export function getStealthProfile(name: string): StealthProfile {
|
|
|
301
288
|
}
|
|
302
289
|
|
|
303
290
|
export function listStealthProfiles(): string[] {
|
|
304
|
-
return [
|
|
305
|
-
...Object.keys(STEALTH_PROFILES),
|
|
306
|
-
...Object.keys(STEALTH_PROFILE_ALIASES),
|
|
307
|
-
];
|
|
291
|
+
return [...Object.keys(STEALTH_PROFILES), ...Object.keys(STEALTH_PROFILE_ALIASES)];
|
|
308
292
|
}
|
package/src/stream.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SseMessage } from "./types";
|
|
1
|
+
import type { SseMessage } from "./types.js";
|
|
2
2
|
|
|
3
3
|
export interface SseEvent<TData = unknown> {
|
|
4
4
|
event: string;
|
|
@@ -61,8 +61,7 @@ export function done<TData>(
|
|
|
61
61
|
|
|
62
62
|
export function encodeSseEvent(input: SseEvent): string {
|
|
63
63
|
const lines: string[] = [];
|
|
64
|
-
if (input.id !== undefined)
|
|
65
|
-
lines.push(`id: ${sseFieldValue(input.id, "id")}`);
|
|
64
|
+
if (input.id !== undefined) lines.push(`id: ${sseFieldValue(input.id, "id")}`);
|
|
66
65
|
if (input.event) lines.push(`event: ${sseFieldValue(input.event, "event")}`);
|
|
67
66
|
if (input.retry !== undefined) {
|
|
68
67
|
if (!Number.isInteger(input.retry) || input.retry < 0) {
|
|
@@ -70,8 +69,7 @@ export function encodeSseEvent(input: SseEvent): string {
|
|
|
70
69
|
}
|
|
71
70
|
lines.push(`retry: ${input.retry}`);
|
|
72
71
|
}
|
|
73
|
-
const data =
|
|
74
|
-
typeof input.data === "string" ? input.data : JSON.stringify(input.data);
|
|
72
|
+
const data = typeof input.data === "string" ? input.data : JSON.stringify(input.data);
|
|
75
73
|
for (const line of data.split(/\r?\n/)) {
|
|
76
74
|
lines.push(`data: ${line}`);
|
|
77
75
|
}
|
|
@@ -85,9 +83,7 @@ function sseFieldValue(value: string, field: "event" | "id"): string {
|
|
|
85
83
|
return value;
|
|
86
84
|
}
|
|
87
85
|
|
|
88
|
-
export async function* readableBytes(
|
|
89
|
-
body: ReadableStream<Uint8Array>,
|
|
90
|
-
): AsyncIterable<Uint8Array> {
|
|
86
|
+
export async function* readableBytes(body: ReadableStream<Uint8Array>): AsyncIterable<Uint8Array> {
|
|
91
87
|
const reader = body.getReader();
|
|
92
88
|
try {
|
|
93
89
|
for (;;) {
|
|
@@ -100,9 +96,7 @@ export async function* readableBytes(
|
|
|
100
96
|
}
|
|
101
97
|
}
|
|
102
98
|
|
|
103
|
-
export async function* readableTextChunks(
|
|
104
|
-
body: ReadableStream<Uint8Array>,
|
|
105
|
-
): AsyncIterable<string> {
|
|
99
|
+
export async function* readableTextChunks(body: ReadableStream<Uint8Array>): AsyncIterable<string> {
|
|
106
100
|
const decoder = new TextDecoder();
|
|
107
101
|
for await (const chunk of readableBytes(body)) {
|
|
108
102
|
yield decoder.decode(chunk, { stream: true });
|
|
@@ -111,9 +105,7 @@ export async function* readableTextChunks(
|
|
|
111
105
|
if (tail) yield tail;
|
|
112
106
|
}
|
|
113
107
|
|
|
114
|
-
export async function* readableLines(
|
|
115
|
-
body: ReadableStream<Uint8Array>,
|
|
116
|
-
): AsyncIterable<string> {
|
|
108
|
+
export async function* readableLines(body: ReadableStream<Uint8Array>): AsyncIterable<string> {
|
|
117
109
|
let buffer = "";
|
|
118
110
|
for await (const chunk of readableTextChunks(body)) {
|
|
119
111
|
buffer += chunk;
|
|
@@ -121,8 +113,7 @@ export async function* readableLines(
|
|
|
121
113
|
const index = buffer.search(/\r?\n/);
|
|
122
114
|
if (index < 0) break;
|
|
123
115
|
const line = buffer.slice(0, index);
|
|
124
|
-
const newlineLength =
|
|
125
|
-
buffer[index] === "\r" && buffer[index + 1] === "\n" ? 2 : 1;
|
|
116
|
+
const newlineLength = buffer[index] === "\r" && buffer[index + 1] === "\n" ? 2 : 1;
|
|
126
117
|
buffer = buffer.slice(index + newlineLength);
|
|
127
118
|
yield line;
|
|
128
119
|
}
|
|
@@ -150,9 +141,7 @@ function parseJson<T>(data: string): T {
|
|
|
150
141
|
return JSON.parse(data);
|
|
151
142
|
}
|
|
152
143
|
|
|
153
|
-
export async function* parseSseStream(
|
|
154
|
-
body: ReadableStream<Uint8Array>,
|
|
155
|
-
): AsyncIterable<SseMessage> {
|
|
144
|
+
export async function* parseSseStream(body: ReadableStream<Uint8Array>): AsyncIterable<SseMessage> {
|
|
156
145
|
let eventName = "message";
|
|
157
146
|
let dataLines: string[] = [];
|
|
158
147
|
let id: string | undefined;
|
package/src/testing/index.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { describeTransform, snapshotTransform, toMatchShape } from "./helpers";
|
|
2
|
-
export { runStandardTests } from "./run";
|
|
1
|
+
export { describeTransform, snapshotTransform, toMatchShape } from "./helpers.js";
|
|
2
|
+
export { runStandardTests } from "./run.js";
|
package/src/testing/run.ts
CHANGED
|
@@ -1,28 +1,23 @@
|
|
|
1
1
|
import { describe, expect, it } from "bun:test";
|
|
2
2
|
|
|
3
|
-
import { createProviderCache } from "../runtime/cache";
|
|
4
|
-
import { createTestProviderChoiceContext } from "../runtime/choice";
|
|
5
|
-
import { createMemoryProviderRuntimeState } from "../runtime/state";
|
|
6
|
-
import { createUnsupportedSttClient } from "../runtime/stt";
|
|
7
|
-
import { safeParseSchemaSync } from "../schema";
|
|
3
|
+
import { createProviderCache } from "../runtime/cache.js";
|
|
4
|
+
import { createTestProviderChoiceContext } from "../runtime/choice.js";
|
|
5
|
+
import { createMemoryProviderRuntimeState } from "../runtime/state.js";
|
|
6
|
+
import { createUnsupportedSttClient } from "../runtime/stt.js";
|
|
7
|
+
import { safeParseSchemaSync } from "../schema.js";
|
|
8
8
|
import type {
|
|
9
9
|
AuthMode,
|
|
10
10
|
CredentialContext,
|
|
11
11
|
HttpResponse,
|
|
12
12
|
ProviderContext,
|
|
13
13
|
ProviderDefinition,
|
|
14
|
-
} from "../types";
|
|
14
|
+
} from "../types.js";
|
|
15
15
|
|
|
16
16
|
// Mirrors CONNECTOR_ID_REGEX in ../define.ts, which defineProvider() enforces.
|
|
17
17
|
// A single lowercase segment (no hyphen) is a valid id, so the trailing group
|
|
18
18
|
// is optional (`*`), matching providers like `kakaomap`, `kstartup`, `triple`.
|
|
19
19
|
const CONNECTOR_ID_REGEX = /^[a-z][a-z0-9]*(-[a-z][a-z0-9]*)*$/;
|
|
20
|
-
const VALID_AUTH_MODES = [
|
|
21
|
-
"none",
|
|
22
|
-
"platform-managed",
|
|
23
|
-
"credentials",
|
|
24
|
-
"oauth2",
|
|
25
|
-
] as const;
|
|
20
|
+
const VALID_AUTH_MODES = ["none", "platform-managed", "credentials", "oauth2"] as const;
|
|
26
21
|
const UPDATE_SNAPSHOT_ARGS = new Set(["-u", "--update-snapshots"]);
|
|
27
22
|
|
|
28
23
|
export interface StandardTestsManifest {
|
|
@@ -118,9 +113,7 @@ function inferFixtureDir(providerId: string): string {
|
|
|
118
113
|
.find((file) => file !== undefined);
|
|
119
114
|
|
|
120
115
|
if (testFile) {
|
|
121
|
-
const pathname = testFile.startsWith("file://")
|
|
122
|
-
? new URL(testFile).pathname
|
|
123
|
-
: testFile;
|
|
116
|
+
const pathname = testFile.startsWith("file://") ? new URL(testFile).pathname : testFile;
|
|
124
117
|
return `${pathname.replace(/\/[^/]+$/, "")}/../__fixtures__`;
|
|
125
118
|
}
|
|
126
119
|
|
|
@@ -138,10 +131,7 @@ function jsonResponse(data: unknown): HttpResponse {
|
|
|
138
131
|
json: async <_T = unknown>() => JSON.parse(JSON.stringify(data)),
|
|
139
132
|
text: async () => body,
|
|
140
133
|
arrayBuffer: async () =>
|
|
141
|
-
bodyBytes.buffer.slice(
|
|
142
|
-
bodyBytes.byteOffset,
|
|
143
|
-
bodyBytes.byteOffset + bodyBytes.byteLength,
|
|
144
|
-
),
|
|
134
|
+
bodyBytes.buffer.slice(bodyBytes.byteOffset, bodyBytes.byteOffset + bodyBytes.byteLength),
|
|
145
135
|
bytes: async () => bodyBytes.slice(0),
|
|
146
136
|
};
|
|
147
137
|
}
|
|
@@ -184,16 +174,14 @@ function createSnapshotContext(rawFixture: unknown): ProviderContext {
|
|
|
184
174
|
engine: "playwright-stealth",
|
|
185
175
|
newPage: async () => unsupported("ctx.browser.newPage"),
|
|
186
176
|
rawPage: async () => unsupported("ctx.browser.rawPage"),
|
|
187
|
-
withIsolatedContext: async () =>
|
|
188
|
-
unsupported("ctx.browser.withIsolatedContext"),
|
|
177
|
+
withIsolatedContext: async () => unsupported("ctx.browser.withIsolatedContext"),
|
|
189
178
|
solveChallenge: async () => unsupported("ctx.browser.solveChallenge"),
|
|
190
179
|
},
|
|
191
180
|
trace: {
|
|
192
181
|
span: async (_name, fn) => fn(),
|
|
193
182
|
},
|
|
194
183
|
auth: {
|
|
195
|
-
requestField: async (name) =>
|
|
196
|
-
unsupported(`ctx.auth.requestField(${name})`),
|
|
184
|
+
requestField: async (name) => unsupported(`ctx.auth.requestField(${name})`),
|
|
197
185
|
},
|
|
198
186
|
stt: createUnsupportedSttClient(
|
|
199
187
|
"Standard test snapshot context does not support ctx.stt.transcribe",
|
|
@@ -292,11 +280,7 @@ function parseSchemaFixture(
|
|
|
292
280
|
operationName,
|
|
293
281
|
fieldName,
|
|
294
282
|
fixture,
|
|
295
|
-
safeParseSchemaSync(
|
|
296
|
-
schema,
|
|
297
|
-
fixture,
|
|
298
|
-
`operations.${operationName}.fixtures.${fieldName}`,
|
|
299
|
-
),
|
|
283
|
+
safeParseSchemaSync(schema, fixture, `operations.${operationName}.fixtures.${fieldName}`),
|
|
300
284
|
);
|
|
301
285
|
}
|
|
302
286
|
|
|
@@ -322,40 +306,20 @@ export function runStandardTests(
|
|
|
322
306
|
|
|
323
307
|
for (const [operationName, op] of operations) {
|
|
324
308
|
if (op.fixtures?.request !== undefined) {
|
|
325
|
-
parseSchemaFixture(
|
|
326
|
-
operationName,
|
|
327
|
-
"request",
|
|
328
|
-
op.input,
|
|
329
|
-
op.fixtures.request,
|
|
330
|
-
);
|
|
309
|
+
parseSchemaFixture(operationName, "request", op.input, op.fixtures.request);
|
|
331
310
|
}
|
|
332
311
|
|
|
333
312
|
if (op.fixtures?.response !== undefined) {
|
|
334
|
-
parseSchemaFixture(
|
|
335
|
-
operationName,
|
|
336
|
-
"response",
|
|
337
|
-
op.output,
|
|
338
|
-
op.fixtures.response,
|
|
339
|
-
);
|
|
313
|
+
parseSchemaFixture(operationName, "response", op.output, op.fixtures.response);
|
|
340
314
|
}
|
|
341
315
|
|
|
342
316
|
if (isFixtureEnvelope(rawFixture)) {
|
|
343
317
|
if (rawFixture.request !== undefined) {
|
|
344
|
-
parseSchemaFixture(
|
|
345
|
-
operationName,
|
|
346
|
-
"request",
|
|
347
|
-
op.input,
|
|
348
|
-
rawFixture.request,
|
|
349
|
-
);
|
|
318
|
+
parseSchemaFixture(operationName, "request", op.input, rawFixture.request);
|
|
350
319
|
}
|
|
351
320
|
|
|
352
321
|
if (rawFixture.response !== undefined) {
|
|
353
|
-
parseSchemaFixture(
|
|
354
|
-
operationName,
|
|
355
|
-
"response",
|
|
356
|
-
op.output,
|
|
357
|
-
rawFixture.response,
|
|
358
|
-
);
|
|
322
|
+
parseSchemaFixture(operationName, "response", op.output, rawFixture.response);
|
|
359
323
|
}
|
|
360
324
|
}
|
|
361
325
|
}
|
|
@@ -411,21 +375,11 @@ export function runStandardTests(
|
|
|
411
375
|
it("operation schemas can parse fixture data", () => {
|
|
412
376
|
for (const [operationName, op] of operations) {
|
|
413
377
|
if (op.fixtures?.request !== undefined && op.input) {
|
|
414
|
-
parseSchemaFixture(
|
|
415
|
-
operationName,
|
|
416
|
-
"request",
|
|
417
|
-
op.input,
|
|
418
|
-
op.fixtures.request,
|
|
419
|
-
);
|
|
378
|
+
parseSchemaFixture(operationName, "request", op.input, op.fixtures.request);
|
|
420
379
|
}
|
|
421
380
|
|
|
422
381
|
if (op.fixtures?.response !== undefined && op.output) {
|
|
423
|
-
parseSchemaFixture(
|
|
424
|
-
operationName,
|
|
425
|
-
"response",
|
|
426
|
-
op.output,
|
|
427
|
-
op.fixtures.response,
|
|
428
|
-
);
|
|
382
|
+
parseSchemaFixture(operationName, "response", op.output, op.fixtures.response);
|
|
429
383
|
}
|
|
430
384
|
|
|
431
385
|
expect(operationName).toBeTruthy();
|
|
@@ -471,9 +425,7 @@ export function runStandardTests(
|
|
|
471
425
|
await Bun.write(snapshotPath, serialized);
|
|
472
426
|
}
|
|
473
427
|
|
|
474
|
-
const expected: unknown = JSON.parse(
|
|
475
|
-
await Bun.file(snapshotPath).text(),
|
|
476
|
-
);
|
|
428
|
+
const expected: unknown = JSON.parse(await Bun.file(snapshotPath).text());
|
|
477
429
|
expect(actual).toEqual(expected);
|
|
478
430
|
});
|
|
479
431
|
}
|
package/src/types.ts
CHANGED
|
@@ -1759,6 +1759,19 @@ export interface OperationDefinition<
|
|
|
1759
1759
|
TInput extends SchemaLike = SchemaLike,
|
|
1760
1760
|
TOutput extends SchemaLike = SchemaLike,
|
|
1761
1761
|
> {
|
|
1762
|
+
/**
|
|
1763
|
+
* Short English display title for the operation. The SDK passes it through
|
|
1764
|
+
* verbatim; the APIFuse registry derives the operation's en locale title
|
|
1765
|
+
* baseline from it (`docs.titleKey` remains available as an explicit key
|
|
1766
|
+
* override).
|
|
1767
|
+
*/
|
|
1768
|
+
title?: string;
|
|
1769
|
+
/**
|
|
1770
|
+
* Raw English operation description. The SDK passes it through verbatim;
|
|
1771
|
+
* keyed descriptions via `descriptionKey` remain the primary flow, and
|
|
1772
|
+
* provider lint rules for raw prose descriptions still apply.
|
|
1773
|
+
*/
|
|
1774
|
+
description?: string;
|
|
1762
1775
|
descriptionKey?: ProviderLocaleKeyInput;
|
|
1763
1776
|
docs?: OperationDocMeta;
|
|
1764
1777
|
whenToUseKeys?: readonly ProviderLocaleKeyInput[];
|
|
@@ -1797,10 +1810,55 @@ export interface OperationDefinition<
|
|
|
1797
1810
|
healthCheckUnsupported?: HealthCheckUnsupported;
|
|
1798
1811
|
}
|
|
1799
1812
|
|
|
1813
|
+
/**
|
|
1814
|
+
* Author-declared deployment overrides accepted as the optional top-level
|
|
1815
|
+
* `deployment` key on `defineProvider()`.
|
|
1816
|
+
*
|
|
1817
|
+
* The SDK passes this object through VERBATIM onto the returned provider
|
|
1818
|
+
* definition — it is typed here but deliberately not deep-validated: the
|
|
1819
|
+
* APIFuse registry builder owns deployment validation and resolves omitted
|
|
1820
|
+
* fields against the runtime deployment profiles. The shape mirrors the
|
|
1821
|
+
* registry's `ProviderDeploymentOverrides` contract; every field is
|
|
1822
|
+
* optional. Note that the deployment `runtime` axis
|
|
1823
|
+
* (`shared`/`dedicated`/`browser`) is distinct from the provider execution
|
|
1824
|
+
* `runtime` (`standard`/`shared`/`browser`).
|
|
1825
|
+
*/
|
|
1826
|
+
export interface ProviderDeploymentOverrides {
|
|
1827
|
+
runtime?: "shared" | "dedicated" | "browser";
|
|
1828
|
+
language?: "typescript" | "python";
|
|
1829
|
+
replicas?: number;
|
|
1830
|
+
hpa?: {
|
|
1831
|
+
enabled: boolean;
|
|
1832
|
+
minReplicas?: number;
|
|
1833
|
+
maxReplicas?: number;
|
|
1834
|
+
targetCPUUtilizationPercentage?: number;
|
|
1835
|
+
};
|
|
1836
|
+
resources?: {
|
|
1837
|
+
cpu: string;
|
|
1838
|
+
memory: string;
|
|
1839
|
+
};
|
|
1840
|
+
cache?: {
|
|
1841
|
+
redis?: {
|
|
1842
|
+
enabled: boolean;
|
|
1843
|
+
url?: string;
|
|
1844
|
+
};
|
|
1845
|
+
};
|
|
1846
|
+
network?: {
|
|
1847
|
+
additionalTcpPorts?: number[];
|
|
1848
|
+
};
|
|
1849
|
+
buildContext?: string;
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1800
1852
|
export interface ProviderDefinition {
|
|
1801
1853
|
id: string;
|
|
1802
1854
|
version: string;
|
|
1803
1855
|
runtime: "standard" | "shared" | "browser";
|
|
1856
|
+
/**
|
|
1857
|
+
* Optional deployment overrides, passed through verbatim from
|
|
1858
|
+
* `defineProvider({ deployment })`. Validation and profile resolution are
|
|
1859
|
+
* owned by the APIFuse registry builder, not the SDK.
|
|
1860
|
+
*/
|
|
1861
|
+
deployment?: ProviderDeploymentOverrides;
|
|
1804
1862
|
allowedHosts?: string[];
|
|
1805
1863
|
stealth?: {
|
|
1806
1864
|
profile: string;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// Official cross-provider contract for user-input round-trips.
|
|
2
|
+
//
|
|
3
|
+
// Doctrine (born from the 2026-07-20 CatchTable reserve incident): an
|
|
4
|
+
// operation MUST NOT dead-end on a problem the end user can resolve by
|
|
5
|
+
// choosing among live options. Instead of throwing, the provider returns a
|
|
6
|
+
// SUCCESSFUL payload with `status: "needs_input"` that carries everything a
|
|
7
|
+
// consumer-side agent needs to relay the choice verbatim and retry:
|
|
8
|
+
//
|
|
9
|
+
// - `required_selections`: only the still-pending questions, each with
|
|
10
|
+
// human-readable `label`s and `valid_options` the agent shows the user
|
|
11
|
+
// as-is. The agent never chooses on the user's behalf.
|
|
12
|
+
// - `selected_options`: selections already settled, echoed so the follow-up
|
|
13
|
+
// call keeps them without the agent reconstructing anything.
|
|
14
|
+
// - a fresh provider-specific state token (e.g. `reservation_state`) minted
|
|
15
|
+
// at response time, so the retry never races an expired token.
|
|
16
|
+
//
|
|
17
|
+
// Keeping this success-shaped (instead of error `details`) is deliberate:
|
|
18
|
+
// consumer error-shaping layers routinely strip error metadata, and a model
|
|
19
|
+
// that only sees "error" narrates failure to the user. Complex recovery
|
|
20
|
+
// logic belongs to the system, not the model.
|
|
21
|
+
//
|
|
22
|
+
// Deliberately ABSENT: retry templates, next-action routing, or any other
|
|
23
|
+
// agent choreography. Provider payloads carry upstream-backed data only; how
|
|
24
|
+
// to phrase the ask and shape the retry call is the consumer's contract
|
|
25
|
+
// (apifuse#1030). The retry is fully determined by data: copy the echoed
|
|
26
|
+
// selected_options, add the user's new answers, resend with the fresh state.
|
|
27
|
+
|
|
28
|
+
export const NEEDS_INPUT_STATUS = "needs_input" as const;
|
|
29
|
+
|
|
30
|
+
export interface ProviderSelectionOption {
|
|
31
|
+
readonly selection_value: string;
|
|
32
|
+
readonly label: string;
|
|
33
|
+
readonly [extra: string]: unknown;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface ProviderRequiredSelection {
|
|
37
|
+
readonly selection_key: string;
|
|
38
|
+
readonly label: string;
|
|
39
|
+
readonly required: boolean;
|
|
40
|
+
readonly selection_type?: string;
|
|
41
|
+
readonly valid_options: readonly ProviderSelectionOption[];
|
|
42
|
+
readonly [extra: string]: unknown;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface ProviderSelectedOption {
|
|
46
|
+
readonly selection_key: string;
|
|
47
|
+
readonly selection_value: string;
|
|
48
|
+
/** Free-text answer for text-kind selections. */
|
|
49
|
+
readonly text?: string;
|
|
50
|
+
/** Quantity for quantity-kind selections. */
|
|
51
|
+
readonly quantity?: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface ProviderNeedsInputPayload {
|
|
55
|
+
readonly status: typeof NEEDS_INPUT_STATUS;
|
|
56
|
+
readonly required_selections: readonly ProviderRequiredSelection[];
|
|
57
|
+
readonly selected_options?: readonly ProviderSelectedOption[];
|
|
58
|
+
/** Provider-specific fresh state token(s), e.g. `reservation_state`. */
|
|
59
|
+
readonly [extra: string]: unknown;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
63
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function isSelectionOption(value: unknown): value is ProviderSelectionOption {
|
|
67
|
+
return (
|
|
68
|
+
isRecord(value) &&
|
|
69
|
+
typeof value.selection_value === "string" &&
|
|
70
|
+
typeof value.label === "string"
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function isRequiredSelection(
|
|
75
|
+
value: unknown,
|
|
76
|
+
): value is ProviderRequiredSelection {
|
|
77
|
+
return (
|
|
78
|
+
isRecord(value) &&
|
|
79
|
+
typeof value.selection_key === "string" &&
|
|
80
|
+
typeof value.label === "string" &&
|
|
81
|
+
typeof value.required === "boolean" &&
|
|
82
|
+
Array.isArray(value.valid_options) &&
|
|
83
|
+
value.valid_options.every(isSelectionOption)
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function isSelectedOption(value: unknown): value is ProviderSelectedOption {
|
|
88
|
+
return (
|
|
89
|
+
isRecord(value) &&
|
|
90
|
+
typeof value.selection_key === "string" &&
|
|
91
|
+
typeof value.selection_value === "string"
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function isProviderNeedsInputPayload(
|
|
96
|
+
value: unknown,
|
|
97
|
+
): value is ProviderNeedsInputPayload {
|
|
98
|
+
if (!isRecord(value)) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
if (
|
|
102
|
+
value.status !== NEEDS_INPUT_STATUS ||
|
|
103
|
+
!Array.isArray(value.required_selections) ||
|
|
104
|
+
!value.required_selections.every(isRequiredSelection)
|
|
105
|
+
) {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
// A needs_input with nothing to ask AND nothing settled to echo is a
|
|
109
|
+
// no-op dead end — reject it so providers cannot ship it accidentally.
|
|
110
|
+
if (value.required_selections.length > 0) {
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
return (
|
|
114
|
+
Array.isArray(value.selected_options) &&
|
|
115
|
+
value.selected_options.length > 0 &&
|
|
116
|
+
value.selected_options.every(isSelectedOption)
|
|
117
|
+
);
|
|
118
|
+
}
|