@apifuse/provider-sdk 2.2.0-beta.47 → 2.2.0-beta.49
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 +91 -36
- package/CHANGELOG.md +8 -0
- package/README.md +11 -9
- package/SUBMISSION.md +1 -1
- package/bin/apifuse-dev.ts +24 -13
- package/bin/apifuse-migrate-operation-declaration.ts +55 -0
- package/bin/apifuse-pack-smoke.ts +1 -1
- package/bin/apifuse-pack-types.ts +2 -1
- package/bin/apifuse-record.ts +30 -16
- package/bin/apifuse-submit-check.ts +20 -35
- package/dist/cli/commands.d.ts +1 -1
- package/dist/cli/commands.js +11 -0
- package/dist/cli/migrate-operation-declaration.d.ts +59 -0
- package/dist/cli/migrate-operation-declaration.js +1178 -0
- package/dist/cli/templates/provider/README.md.tpl +3 -3
- package/dist/cli/templates/provider/operations/ping.ts.tpl +2 -0
- package/dist/config/loader.d.ts +2 -0
- package/dist/config/loader.js +18 -7
- package/dist/contract-types.d.ts +11 -5
- package/dist/contract.js +21 -10
- package/dist/define.d.ts +25 -22
- package/dist/define.js +49 -75
- package/dist/dev.d.ts +3 -0
- package/dist/dev.js +1 -1
- package/dist/engine.d.ts +78 -0
- package/dist/engine.js +133 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -1
- package/dist/lint.d.ts +7 -15
- package/dist/lint.js +45 -70
- package/dist/provider.d.ts +3 -1
- package/dist/provider.js +1 -0
- package/dist/runtime/chrome149-header-order.d.ts +58 -0
- package/dist/runtime/chrome149-header-order.js +289 -0
- package/dist/runtime/env.js +12 -0
- package/dist/runtime/executor.d.ts +2 -1
- package/dist/runtime/executor.js +3 -36
- package/dist/runtime/insights.js +2 -2
- package/dist/runtime/otlp.d.ts +71 -2
- package/dist/runtime/otlp.js +397 -16
- package/dist/runtime/resolver-public.d.ts +1 -1
- package/dist/runtime/resolver-public.js +1 -1
- package/dist/runtime/resolver-vendors/capsolver.js +9 -3
- package/dist/runtime/resolver-vendors/twocaptcha.js +1 -0
- package/dist/runtime/resolver.d.ts +12 -0
- package/dist/runtime/resolver.js +45 -11
- package/dist/runtime/stealth.d.ts +13 -4
- package/dist/runtime/stealth.js +362 -85
- package/dist/runtime/trace-config.js +2 -1
- package/dist/runtime/trace.d.ts +5 -0
- package/dist/runtime/trace.js +43 -10
- package/dist/server/self-test.d.ts +1 -3
- package/dist/server/self-test.js +2 -12
- package/dist/server/serve-implementation.d.ts +6 -1
- package/dist/server/serve-implementation.js +55 -40
- package/dist/server/trace-output.d.ts +3 -1
- package/dist/server/trace-output.js +61 -2
- package/dist/stealth/profiles.d.ts +9 -8
- package/dist/stealth/profiles.js +123 -286
- package/dist/types.d.ts +116 -108
- package/package.json +2 -1
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/approval-override.ts.txt +6 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/codemod-syntax.ts.txt +3 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/connection-precedence.ts.txt +10 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/docs-conflict.ts.txt +8 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-map.ts.txt +5 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-operation.ts.txt +16 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-map.ts.txt +3 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/hoist-all.ts.txt +31 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/hoisted-const.ts.txt +11 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/imported-spread.ts.txt +11 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-map.ts.txt +11 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-cast-tail.ts.txt +21 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-ekitan.ts.txt +11 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-override.ts.txt +14 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/missing-english-locale.ts.txt +7 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/no-safety.ts.txt +6 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/non-literal.ts.txt +7 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/redundant-approval.ts.txt +6 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/safety-conflict.ts.txt +7 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/stream.ts.txt +7 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/tool-router-spread.ts.txt +15 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/unparseable.ts.txt +4 -0
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/verbatim-template.ts.txt +12 -0
- package/src/cli/commands.ts +13 -0
- package/src/cli/migrate-operation-declaration.ts +1654 -0
- package/src/cli/templates/provider/README.md.tpl +3 -3
- package/src/cli/templates/provider/operations/ping.ts.tpl +2 -0
- package/src/config/loader.ts +31 -6
- package/src/contract-types.ts +11 -5
- package/src/contract.ts +21 -10
- package/src/define.ts +107 -119
- package/src/dev.ts +4 -1
- package/src/engine.ts +279 -0
- package/src/index.ts +13 -5
- package/src/lint.ts +58 -92
- package/src/provider.ts +25 -3
- package/src/runtime/chrome149-header-order.ts +330 -0
- package/src/runtime/env.ts +13 -0
- package/src/runtime/executor.ts +7 -40
- package/src/runtime/insights.ts +2 -2
- package/src/runtime/otlp.ts +467 -21
- package/src/runtime/resolver-public.ts +3 -0
- package/src/runtime/resolver-vendors/capsolver.ts +12 -4
- package/src/runtime/resolver-vendors/twocaptcha.ts +1 -0
- package/src/runtime/resolver.ts +68 -19
- package/src/runtime/stealth.ts +435 -103
- package/src/runtime/trace-config.ts +3 -2
- package/src/runtime/trace.ts +57 -17
- package/src/server/self-test.ts +2 -9
- package/src/server/serve-implementation.ts +89 -72
- package/src/server/trace-output.ts +99 -2
- package/src/stealth/profiles.ts +169 -327
- package/src/types.ts +114 -137
|
@@ -6,9 +6,10 @@ export const APIFUSE__TRACE__EXPORTER = "APIFUSE__TRACE__EXPORTER";
|
|
|
6
6
|
|
|
7
7
|
type EnvLike = Record<string, string | undefined>;
|
|
8
8
|
|
|
9
|
-
const TRACE_EXPORTER_LOOKUP: Record<
|
|
9
|
+
const TRACE_EXPORTER_LOOKUP: Record<NonNullable<TraceConfig["exporter"]>, true> = {
|
|
10
10
|
console: true,
|
|
11
11
|
json: true,
|
|
12
|
+
otlp: true,
|
|
12
13
|
none: true,
|
|
13
14
|
};
|
|
14
15
|
const TRACE_EXPORTERS = new Set(Object.keys(TRACE_EXPORTER_LOOKUP));
|
|
@@ -66,7 +67,7 @@ export function resolveTraceConfigFromEnv(env: EnvLike = process.env): TraceConf
|
|
|
66
67
|
if (exporterRaw !== undefined && exporter === "none" && normalizedExporter !== "none") {
|
|
67
68
|
warnOnce(
|
|
68
69
|
APIFUSE__TRACE__EXPORTER,
|
|
69
|
-
`[apifuse] Invalid APIFUSE__TRACE__EXPORTER value "${normalizedExporter ?? exporterRaw}"
|
|
70
|
+
`[apifuse] Invalid APIFUSE__TRACE__EXPORTER value "${normalizedExporter ?? exporterRaw}"; supported exporters are "console", "json", "otlp", and "none"; falling back to exporter "none".`,
|
|
70
71
|
);
|
|
71
72
|
}
|
|
72
73
|
|
package/src/runtime/trace.ts
CHANGED
|
@@ -21,6 +21,11 @@ export interface CreateTraceContextOptions {
|
|
|
21
21
|
onSpan?: (span: Span) => void;
|
|
22
22
|
exportOptions?: OTLPExportOptions;
|
|
23
23
|
resourceAttributes?: Record<string, string>;
|
|
24
|
+
/**
|
|
25
|
+
* Applied to a detached copy of each span immediately before OTLP export; never touches
|
|
26
|
+
* getSpans() or onSpan. Returning nothing (or throwing) drops that export batch.
|
|
27
|
+
*/
|
|
28
|
+
sanitizeSpanForExport?: (span: Span) => Span | undefined;
|
|
24
29
|
}
|
|
25
30
|
|
|
26
31
|
type SpanHookOptions<T> = {
|
|
@@ -41,6 +46,8 @@ type PendingSpan = {
|
|
|
41
46
|
type CompletedSpanEntry = {
|
|
42
47
|
sequence: number;
|
|
43
48
|
span: Span;
|
|
49
|
+
/** Set once the span has been handed to the exporter so no batch re-sends it. */
|
|
50
|
+
exported: boolean;
|
|
44
51
|
};
|
|
45
52
|
|
|
46
53
|
export interface TraceRecorder {
|
|
@@ -51,8 +58,6 @@ export const TRACE_RECORDER = Symbol.for("@apifuse/provider-sdk/runtime/trace-re
|
|
|
51
58
|
|
|
52
59
|
type InternalTraceContext = TraceContext & {
|
|
53
60
|
[TRACE_RECORDER]: TraceRecorder;
|
|
54
|
-
_exportOptions?: OTLPExportOptions;
|
|
55
|
-
_resourceAttributes?: Record<string, string>;
|
|
56
61
|
};
|
|
57
62
|
|
|
58
63
|
function buildOTLPExportOptions(config?: TraceConfig): OTLPExportOptions | undefined {
|
|
@@ -124,6 +129,20 @@ function insertCompletedSpan(
|
|
|
124
129
|
}
|
|
125
130
|
}
|
|
126
131
|
|
|
132
|
+
/** Hands the hook a copy whose attributes are detached from the recorded span; a hook that returns nothing fails closed. */
|
|
133
|
+
function prepareSpanForExport(
|
|
134
|
+
span: Span,
|
|
135
|
+
sanitize: CreateTraceContextOptions["sanitizeSpanForExport"],
|
|
136
|
+
): Span {
|
|
137
|
+
const copy: Span = { ...span, attributes: { ...span.attributes } };
|
|
138
|
+
if (!sanitize) return copy;
|
|
139
|
+
const sanitized = sanitize(copy);
|
|
140
|
+
if (!sanitized || typeof sanitized !== "object") {
|
|
141
|
+
throw new Error("sanitizeSpanForExport returned no span");
|
|
142
|
+
}
|
|
143
|
+
return sanitized;
|
|
144
|
+
}
|
|
145
|
+
|
|
127
146
|
export function getTraceRecorder(trace: BaseTraceContext): TraceRecorder | null {
|
|
128
147
|
return (trace as Partial<InternalTraceContext>)[TRACE_RECORDER] ?? null;
|
|
129
148
|
}
|
|
@@ -133,20 +152,41 @@ export function createTraceContext(options: CreateTraceContextOptions = {}): Tra
|
|
|
133
152
|
const completed: CompletedSpanEntry[] = [];
|
|
134
153
|
const activeSpanStorage = new AsyncLocalStorage<PendingSpan | undefined>();
|
|
135
154
|
let sequence = 0;
|
|
136
|
-
|
|
137
|
-
|
|
155
|
+
// Export configuration (which can carry collector credentials) stays in this closure;
|
|
156
|
+
// the context object handed to provider code never exposes it.
|
|
157
|
+
const exportOptions = options.exportOptions;
|
|
158
|
+
const exportResourceAttributes = options.resourceAttributes
|
|
159
|
+
? { ...options.resourceAttributes }
|
|
160
|
+
: undefined;
|
|
161
|
+
// One trace id per context so every export batch of this request shares it and
|
|
162
|
+
// two processes can never mint the same id.
|
|
163
|
+
const exportTraceId = crypto.randomUUID().replace(/-/g, "");
|
|
164
|
+
let exportScheduled = false;
|
|
165
|
+
|
|
166
|
+
// One pending batch per context: roots completing before the flush share it, and a span is
|
|
167
|
+
// handed to the exporter exactly once, so later roots never re-send earlier spans.
|
|
138
168
|
const scheduleExport = () => {
|
|
139
|
-
if (!
|
|
169
|
+
if (!exportOptions || exportScheduled) {
|
|
140
170
|
return;
|
|
141
171
|
}
|
|
172
|
+
exportScheduled = true;
|
|
142
173
|
|
|
143
|
-
const spans = completed.map((entry) => ({ ...entry.span }));
|
|
144
174
|
setImmediate(() => {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
175
|
+
exportScheduled = false;
|
|
176
|
+
const pending = completed.filter((entry) => !entry.exported);
|
|
177
|
+
for (const entry of pending) entry.exported = true;
|
|
178
|
+
if (pending.length === 0) return;
|
|
179
|
+
// Sanitization runs off the request path; a faulty sanitizer drops the batch, never the request.
|
|
180
|
+
let spans: Span[];
|
|
181
|
+
try {
|
|
182
|
+
spans = pending.map((entry) =>
|
|
183
|
+
prepareSpanForExport(entry.span, options.sanitizeSpanForExport),
|
|
184
|
+
);
|
|
185
|
+
} catch {
|
|
186
|
+
console.warn("[apifuse] OTLP export skipped; span sanitization failed.");
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
void exportSpansOTLP(spans, exportOptions, exportResourceAttributes, exportTraceId);
|
|
150
190
|
});
|
|
151
191
|
};
|
|
152
192
|
|
|
@@ -189,7 +229,11 @@ export function createTraceContext(options: CreateTraceContextOptions = {}): Tra
|
|
|
189
229
|
...(pendingSpan.parentId ? { parentId: pendingSpan.parentId } : {}),
|
|
190
230
|
};
|
|
191
231
|
|
|
192
|
-
insertCompletedSpan(
|
|
232
|
+
insertCompletedSpan(
|
|
233
|
+
completed,
|
|
234
|
+
{ sequence: pendingSpan.sequence, span, exported: false },
|
|
235
|
+
maxSpans,
|
|
236
|
+
);
|
|
193
237
|
options.onSpan?.(span);
|
|
194
238
|
|
|
195
239
|
if (!pendingSpan.parentId) {
|
|
@@ -213,17 +257,13 @@ export function createTraceContext(options: CreateTraceContextOptions = {}): Tra
|
|
|
213
257
|
},
|
|
214
258
|
};
|
|
215
259
|
|
|
216
|
-
traceContext = {
|
|
260
|
+
const traceContext: InternalTraceContext = {
|
|
217
261
|
span(name, fn) {
|
|
218
262
|
return recorder.runSpan(name, fn);
|
|
219
263
|
},
|
|
220
264
|
getSpans() {
|
|
221
265
|
return completed.map((entry) => ({ ...entry.span }));
|
|
222
266
|
},
|
|
223
|
-
...(options.exportOptions ? { _exportOptions: options.exportOptions } : {}),
|
|
224
|
-
...(options.resourceAttributes
|
|
225
|
-
? { _resourceAttributes: { ...options.resourceAttributes } }
|
|
226
|
-
: {}),
|
|
227
267
|
[TRACE_RECORDER]: recorder,
|
|
228
268
|
};
|
|
229
269
|
|
package/src/server/self-test.ts
CHANGED
|
@@ -245,17 +245,10 @@ export function computeSelfTestPlanDigest(provider: ProviderDefinition): string
|
|
|
245
245
|
|
|
246
246
|
/**
|
|
247
247
|
* Fail-closed read-only classification: an operation may self-test only when
|
|
248
|
-
* its
|
|
249
|
-
* toolRouter.riskClass "read") and nothing marks it side-effecting. Unclassified
|
|
250
|
-
* operations are refused so the endpoint cannot become a mutation oracle.
|
|
248
|
+
* its authored safety source of truth classifies it as read-only.
|
|
251
249
|
*/
|
|
252
250
|
export function isSelfTestReadOnlyOperation(operation: OperationDefinition): boolean {
|
|
253
|
-
|
|
254
|
-
if (annotations?.readOnly === false) return false;
|
|
255
|
-
if (annotations?.destructive === true) return false;
|
|
256
|
-
const riskClass = operation.toolRouter?.riskClass;
|
|
257
|
-
if (riskClass !== undefined && riskClass !== "read") return false;
|
|
258
|
-
return annotations?.readOnly === true || riskClass === "read";
|
|
251
|
+
return operation.riskClass === "read";
|
|
259
252
|
}
|
|
260
253
|
|
|
261
254
|
/** Binds the self-test executor to a tenant app's /v1 pipeline in-process. */
|
|
@@ -8,6 +8,14 @@ import { z } from "zod";
|
|
|
8
8
|
import { AuthAbortError, createAuthFlowHelpers } from "../auth.js";
|
|
9
9
|
import { validateFailClosedDeclaration } from "../declaration-validation.js";
|
|
10
10
|
import { safeProviderErrorObservability } from "../error-observability.js";
|
|
11
|
+
import {
|
|
12
|
+
createInProcessProviderEngine,
|
|
13
|
+
ENGINE_OWNED_PROXY_CREDENTIAL_ENV_NAMES,
|
|
14
|
+
isEngineOwnedEnvName,
|
|
15
|
+
readEngineProxyCredentials,
|
|
16
|
+
type ProviderEngine,
|
|
17
|
+
type ProviderEngineBindingCandidates,
|
|
18
|
+
} from "../engine.js";
|
|
11
19
|
import {
|
|
12
20
|
SDK_OWNED_PROVIDER_ERROR_CODES,
|
|
13
21
|
SDK_RUNTIME_OWNED_ERROR_CODES,
|
|
@@ -112,6 +120,7 @@ import type {
|
|
|
112
120
|
ProviderErrorStatus,
|
|
113
121
|
ProviderContext,
|
|
114
122
|
ProviderDefinition,
|
|
123
|
+
ProviderFilesContext,
|
|
115
124
|
ProviderProxyPolicy,
|
|
116
125
|
ProviderRuntimeState,
|
|
117
126
|
ProviderStreamEvent,
|
|
@@ -262,6 +271,7 @@ type ProviderCapabilityModules = {
|
|
|
262
271
|
|
|
263
272
|
type ProviderServerRuntimeOptions = ProviderServerOptions & {
|
|
264
273
|
readonly capabilityModules: ProviderCapabilityModules;
|
|
274
|
+
readonly engine: ProviderEngine;
|
|
265
275
|
};
|
|
266
276
|
|
|
267
277
|
const require = createRequire(import.meta.url);
|
|
@@ -518,7 +528,7 @@ function getProviderStealthBaseUrl(provider: ProviderDefinition): string | undef
|
|
|
518
528
|
}
|
|
519
529
|
|
|
520
530
|
function getProviderStealthProfile(provider: ProviderDefinition) {
|
|
521
|
-
return provider.stealth
|
|
531
|
+
return provider.stealth ? getStealthProfile(provider.stealth) : undefined;
|
|
522
532
|
}
|
|
523
533
|
|
|
524
534
|
function isProductionProviderBrowserMode(provider: ProviderDefinition, env = process.env): boolean {
|
|
@@ -647,6 +657,12 @@ function resolveNativeProxyPolicy(provider: ProviderDefinition): ProviderProxyPo
|
|
|
647
657
|
return undefined;
|
|
648
658
|
}
|
|
649
659
|
|
|
660
|
+
function providerSecretNames(provider: ProviderDefinition): string[] {
|
|
661
|
+
return (provider.secrets?.map((secret) => secret.name) ?? []).filter(
|
|
662
|
+
(name) => !isEngineOwnedEnvName(name),
|
|
663
|
+
);
|
|
664
|
+
}
|
|
665
|
+
|
|
650
666
|
function createProviderContext(
|
|
651
667
|
provider: ProviderDefinition,
|
|
652
668
|
request: OperationRequest,
|
|
@@ -661,10 +677,12 @@ function createProviderContext(
|
|
|
661
677
|
const stealthBaseUrl = getProviderStealthBaseUrl(provider);
|
|
662
678
|
const stealthProfile = getProviderStealthProfile(provider);
|
|
663
679
|
const proxyPolicy = resolveNativeProxyPolicy(provider);
|
|
680
|
+
const engineProxyCredentials = readEngineProxyCredentials();
|
|
664
681
|
const proxyClientOptions = {
|
|
665
682
|
upstream: { proxy: provider.proxy },
|
|
666
683
|
affinityKey: resolveProviderProxyAffinityKey(provider, request, operationId),
|
|
667
684
|
telemetry: proxyTelemetry,
|
|
685
|
+
engineCredentials: engineProxyCredentials,
|
|
668
686
|
};
|
|
669
687
|
const resolverIdentityScope = resolveProviderResolverIdentityScope(
|
|
670
688
|
provider,
|
|
@@ -676,7 +694,9 @@ function createProviderContext(
|
|
|
676
694
|
upstream: proxyClientOptions.upstream,
|
|
677
695
|
affinityKey: proxyClientOptions.affinityKey,
|
|
678
696
|
telemetry: proxyTelemetry,
|
|
697
|
+
engineCredentials: engineProxyCredentials,
|
|
679
698
|
...(signal ? { signal } : {}),
|
|
699
|
+
...(provider.stealth ? { stealth: provider.stealth } : {}),
|
|
680
700
|
};
|
|
681
701
|
const { capabilityModules } = options;
|
|
682
702
|
const logStealthCleanupError = (error: unknown) =>
|
|
@@ -691,7 +711,7 @@ function createProviderContext(
|
|
|
691
711
|
);
|
|
692
712
|
|
|
693
713
|
const env = createEnvContext([
|
|
694
|
-
...(provider
|
|
714
|
+
...providerSecretNames(provider),
|
|
695
715
|
PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV,
|
|
696
716
|
]);
|
|
697
717
|
const credential = createCredentialContext({
|
|
@@ -706,9 +726,10 @@ function createProviderContext(
|
|
|
706
726
|
};
|
|
707
727
|
const requestState = state.forConnection(requestContext.connectionId);
|
|
708
728
|
const cache = createProviderCache({ providerId: provider.id });
|
|
709
|
-
const
|
|
729
|
+
const bindings: ProviderEngineBindingCandidates = {
|
|
710
730
|
env,
|
|
711
731
|
credential,
|
|
732
|
+
...(options.files ? { files: options.files } : {}),
|
|
712
733
|
request: requestContext,
|
|
713
734
|
http: createHttpClient(baseUrl, {
|
|
714
735
|
...proxyClientOptions,
|
|
@@ -722,21 +743,8 @@ function createProviderContext(
|
|
|
722
743
|
state: requestState,
|
|
723
744
|
stealth: stealthBaseUrl
|
|
724
745
|
? capabilityModules.stealth
|
|
725
|
-
?
|
|
726
|
-
|
|
727
|
-
stealthBaseUrl,
|
|
728
|
-
provider.stealth!.profile,
|
|
729
|
-
stealthClientOptions,
|
|
730
|
-
)
|
|
731
|
-
: capabilityModules.stealth.createStealthClient(stealthBaseUrl, stealthClientOptions)
|
|
732
|
-
: stealthProfile
|
|
733
|
-
? createLazyStealthClient(
|
|
734
|
-
logStealthCleanupError,
|
|
735
|
-
stealthBaseUrl,
|
|
736
|
-
provider.stealth!.profile,
|
|
737
|
-
stealthClientOptions,
|
|
738
|
-
)
|
|
739
|
-
: createLazyStealthClient(logStealthCleanupError, stealthBaseUrl, stealthClientOptions)
|
|
746
|
+
? capabilityModules.stealth.createStealthClient(stealthBaseUrl, stealthClientOptions)
|
|
747
|
+
: createLazyStealthClient(logStealthCleanupError, stealthBaseUrl, stealthClientOptions)
|
|
740
748
|
: createStealthStub(),
|
|
741
749
|
browser:
|
|
742
750
|
provider.runtime === "browser"
|
|
@@ -756,7 +764,9 @@ function createProviderContext(
|
|
|
756
764
|
egress: provider.native.network,
|
|
757
765
|
proxyPolicy: resolveNativeProxyPolicy(provider),
|
|
758
766
|
affinityKey: proxyClientOptions.affinityKey,
|
|
759
|
-
credentials: capabilityModules.nativeNetwork!.createEnvVendorCredentialResolver(
|
|
767
|
+
credentials: capabilityModules.nativeNetwork!.createEnvVendorCredentialResolver(
|
|
768
|
+
createEnvContext([...ENGINE_OWNED_PROXY_CREDENTIAL_ENV_NAMES]),
|
|
769
|
+
),
|
|
760
770
|
}),
|
|
761
771
|
},
|
|
762
772
|
}
|
|
@@ -810,7 +820,10 @@ function createProviderContext(
|
|
|
810
820
|
...event,
|
|
811
821
|
}),
|
|
812
822
|
}),
|
|
813
|
-
}
|
|
823
|
+
};
|
|
824
|
+
const context = wrapWithInstrumentation(
|
|
825
|
+
options.engine.attach({ provider, bindings }) as ProviderContext,
|
|
826
|
+
);
|
|
814
827
|
wrappedContext = context;
|
|
815
828
|
return context;
|
|
816
829
|
}
|
|
@@ -882,6 +895,7 @@ function createAuthFlowContext(
|
|
|
882
895
|
const stealthBaseUrl = getProviderStealthBaseUrl(provider);
|
|
883
896
|
const stealthProfile = getProviderStealthProfile(provider);
|
|
884
897
|
const proxyPolicy = resolveNativeProxyPolicy(provider);
|
|
898
|
+
const engineProxyCredentials = readEngineProxyCredentials();
|
|
885
899
|
const contextData = request.context ?? {};
|
|
886
900
|
const flowContextStore = createFlowContextStore(
|
|
887
901
|
provider.context?.keys ?? Object.keys(contextData),
|
|
@@ -891,6 +905,7 @@ function createAuthFlowContext(
|
|
|
891
905
|
upstream: { proxy: provider.proxy },
|
|
892
906
|
affinityKey: resolveAuthFlowProxyAffinityKey(provider, request),
|
|
893
907
|
telemetry: proxyTelemetry,
|
|
908
|
+
engineCredentials: engineProxyCredentials,
|
|
894
909
|
};
|
|
895
910
|
const resolverIdentityScope = resolveProviderResolverIdentityScope(
|
|
896
911
|
provider,
|
|
@@ -901,7 +916,9 @@ function createAuthFlowContext(
|
|
|
901
916
|
upstream: proxyClientOptions.upstream,
|
|
902
917
|
affinityKey: proxyClientOptions.affinityKey,
|
|
903
918
|
telemetry: proxyTelemetry,
|
|
919
|
+
engineCredentials: engineProxyCredentials,
|
|
904
920
|
...(signal ? { signal } : {}),
|
|
921
|
+
...(provider.stealth ? { stealth: provider.stealth } : {}),
|
|
905
922
|
};
|
|
906
923
|
const { capabilityModules } = options;
|
|
907
924
|
const logStealthCleanupError = (error: unknown) =>
|
|
@@ -938,21 +955,8 @@ function createAuthFlowContext(
|
|
|
938
955
|
state: state.forConnection(resolveOperationConnectionId(request)),
|
|
939
956
|
stealth: stealthBaseUrl
|
|
940
957
|
? capabilityModules.stealth
|
|
941
|
-
?
|
|
942
|
-
|
|
943
|
-
stealthBaseUrl,
|
|
944
|
-
provider.stealth!.profile,
|
|
945
|
-
stealthClientOptions,
|
|
946
|
-
)
|
|
947
|
-
: capabilityModules.stealth.createStealthClient(stealthBaseUrl, stealthClientOptions)
|
|
948
|
-
: stealthProfile
|
|
949
|
-
? createLazyStealthClient(
|
|
950
|
-
logStealthCleanupError,
|
|
951
|
-
stealthBaseUrl,
|
|
952
|
-
provider.stealth!.profile,
|
|
953
|
-
stealthClientOptions,
|
|
954
|
-
)
|
|
955
|
-
: createLazyStealthClient(logStealthCleanupError, stealthBaseUrl, stealthClientOptions)
|
|
958
|
+
? capabilityModules.stealth.createStealthClient(stealthBaseUrl, stealthClientOptions)
|
|
959
|
+
: createLazyStealthClient(logStealthCleanupError, stealthBaseUrl, stealthClientOptions)
|
|
956
960
|
: createStealthStub(),
|
|
957
961
|
...(provider.native
|
|
958
962
|
? {
|
|
@@ -962,14 +966,14 @@ function createAuthFlowContext(
|
|
|
962
966
|
proxyPolicy: resolveNativeProxyPolicy(provider),
|
|
963
967
|
affinityKey: proxyClientOptions.affinityKey,
|
|
964
968
|
credentials: capabilityModules.nativeNetwork!.createEnvVendorCredentialResolver(
|
|
965
|
-
createEnvContext(
|
|
969
|
+
createEnvContext([...ENGINE_OWNED_PROXY_CREDENTIAL_ENV_NAMES]),
|
|
966
970
|
),
|
|
967
971
|
}),
|
|
968
972
|
},
|
|
969
973
|
}
|
|
970
974
|
: {}),
|
|
971
975
|
env: createEnvContext([
|
|
972
|
-
...(provider
|
|
976
|
+
...providerSecretNames(provider),
|
|
973
977
|
...(provider.auth?.mode === "oauth2_proxied" ? ["APIFUSE__AUTH_PROXY__URL"] : []),
|
|
974
978
|
]),
|
|
975
979
|
credential,
|
|
@@ -1086,6 +1090,10 @@ export type ProviderServerLogger = (event: ProviderServerLogEvent) => void;
|
|
|
1086
1090
|
|
|
1087
1091
|
export type ProviderServerOptions<TContext extends Partial<ProviderContext> = ProviderContext> = {
|
|
1088
1092
|
logger?: ProviderServerLogger;
|
|
1093
|
+
/** Capability attachment boundary. Defaults to the local in-process engine. */
|
|
1094
|
+
engine?: ProviderEngine;
|
|
1095
|
+
/** Request-file resolver supplied by the engine host when `files` is declared. */
|
|
1096
|
+
files?: ProviderFilesContext;
|
|
1089
1097
|
/** Optional provider-specific operation executor. Stateful providers use this to preserve provider-local runtime semantics. */
|
|
1090
1098
|
operationExecutor?: ProviderServerOperationExecutor<TContext>;
|
|
1091
1099
|
/** Optional signed internal executor for stateful owner forwarding. */
|
|
@@ -1275,11 +1283,8 @@ function providerObservabilityDetails(
|
|
|
1275
1283
|
const declaredRetryable = sdkOwnsErrorResolution(error)
|
|
1276
1284
|
? undefined
|
|
1277
1285
|
: declaredErrorCode?.retryable;
|
|
1278
|
-
// Session-expiry surfaces the credential_expired category
|
|
1279
|
-
//
|
|
1280
|
-
// operation (see design.md §4.3 D3). Without this branch the auth error would
|
|
1281
|
-
// serialize as a bare 401 with no retryable/category, losing the refresh
|
|
1282
|
-
// signal for exactly the retryOnAuthRefresh operations it is meant to enable.
|
|
1286
|
+
// Session-expiry surfaces the credential_expired category so Gateway and
|
|
1287
|
+
// Credential Service can distinguish an expired credential from other 401s.
|
|
1283
1288
|
if (isSessionExpiredError(error)) {
|
|
1284
1289
|
return {
|
|
1285
1290
|
category: providerErrorOption(error, "category") ?? "credential_expired",
|
|
@@ -1496,7 +1501,7 @@ type OperationErrorCodeLookup = ReadonlyMap<string, ReadonlyMap<string, Operatio
|
|
|
1496
1501
|
function buildOperationErrorCodeLookup(provider: ProviderDefinition): OperationErrorCodeLookup {
|
|
1497
1502
|
return new Map(
|
|
1498
1503
|
Object.entries(provider.operations).flatMap(([operationId, operation]) => {
|
|
1499
|
-
const errorCodes = operation.
|
|
1504
|
+
const errorCodes = operation.errorCodes;
|
|
1500
1505
|
return errorCodes?.length
|
|
1501
1506
|
? [[operationId, new Map(errorCodes.map((entry) => [entry.code, entry]))] as const]
|
|
1502
1507
|
: [];
|
|
@@ -1635,7 +1640,7 @@ function logProviderError(
|
|
|
1635
1640
|
? {
|
|
1636
1641
|
signal: "unregistered_provider_error_code" as const,
|
|
1637
1642
|
signalFix:
|
|
1638
|
-
"Declare this code (with status and retryable) in the operation's
|
|
1643
|
+
"Declare this code (with status and retryable) in the operation's errorCodes so it serves its intended status instead of 500.",
|
|
1639
1644
|
}
|
|
1640
1645
|
: {}),
|
|
1641
1646
|
...(error instanceof z.ZodError ? { issues: zodDetails(error) } : {}),
|
|
@@ -1704,7 +1709,7 @@ function toJsonSuccessResponse(
|
|
|
1704
1709
|
return new Response(result);
|
|
1705
1710
|
}
|
|
1706
1711
|
|
|
1707
|
-
const cacheMeta = ctx
|
|
1712
|
+
const cacheMeta = ctx && "cache" in ctx ? ctx.cache.responseMeta() : undefined;
|
|
1708
1713
|
const retryMeta = ctx ? retryResponseMeta.get(ctx) : undefined;
|
|
1709
1714
|
const meta =
|
|
1710
1715
|
cacheMeta || retryMeta
|
|
@@ -2105,31 +2110,35 @@ async function handleOperation(
|
|
|
2105
2110
|
const cleanup = async () => {
|
|
2106
2111
|
if (cleanupCalled) return;
|
|
2107
2112
|
cleanupCalled = true;
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2113
|
+
if ("stealth" in ctx) {
|
|
2114
|
+
try {
|
|
2115
|
+
ctx.stealth.close?.();
|
|
2116
|
+
} catch (error) {
|
|
2117
|
+
logProviderCleanupError(
|
|
2118
|
+
options.logger,
|
|
2119
|
+
provider,
|
|
2120
|
+
"operation",
|
|
2121
|
+
operationId,
|
|
2122
|
+
request.requestId,
|
|
2123
|
+
"stealth",
|
|
2124
|
+
error,
|
|
2125
|
+
);
|
|
2126
|
+
}
|
|
2120
2127
|
}
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2128
|
+
if ("browser" in ctx) {
|
|
2129
|
+
try {
|
|
2130
|
+
await ctx.browser.close?.();
|
|
2131
|
+
} catch (error) {
|
|
2132
|
+
logProviderCleanupError(
|
|
2133
|
+
options.logger,
|
|
2134
|
+
provider,
|
|
2135
|
+
"operation",
|
|
2136
|
+
operationId,
|
|
2137
|
+
request.requestId,
|
|
2138
|
+
"browser",
|
|
2139
|
+
error,
|
|
2140
|
+
);
|
|
2141
|
+
}
|
|
2133
2142
|
}
|
|
2134
2143
|
};
|
|
2135
2144
|
try {
|
|
@@ -2141,7 +2150,9 @@ async function handleOperation(
|
|
|
2141
2150
|
request,
|
|
2142
2151
|
signal,
|
|
2143
2152
|
})
|
|
2144
|
-
: await executeOperation(provider, operationId, ctx, request.input
|
|
2153
|
+
: await executeOperation(provider, operationId, ctx, request.input, {
|
|
2154
|
+
env: createEnvContext(providerSecretNames(provider)),
|
|
2155
|
+
});
|
|
2145
2156
|
if (streaming && operation) {
|
|
2146
2157
|
return toStreamingResponse(operation, result, cleanup, request.requestId);
|
|
2147
2158
|
}
|
|
@@ -2427,7 +2438,11 @@ function createServerAppWithCapabilityModules(
|
|
|
2427
2438
|
serverOptions: ProviderServerOptions,
|
|
2428
2439
|
capabilityModules: ProviderCapabilityModules,
|
|
2429
2440
|
): Hono {
|
|
2430
|
-
const options: ProviderServerRuntimeOptions = {
|
|
2441
|
+
const options: ProviderServerRuntimeOptions = {
|
|
2442
|
+
...serverOptions,
|
|
2443
|
+
capabilityModules,
|
|
2444
|
+
engine: serverOptions.engine ?? createInProcessProviderEngine(),
|
|
2445
|
+
};
|
|
2431
2446
|
const app = new Hono();
|
|
2432
2447
|
const logger = options.logger ?? defaultProviderServerLogger;
|
|
2433
2448
|
const operationErrorCodes = buildOperationErrorCodeLookup(provider);
|
|
@@ -2449,7 +2464,7 @@ function createServerAppWithCapabilityModules(
|
|
|
2449
2464
|
// CrashLoopBackOff. Requests still fail closed via the executeOperation gate.
|
|
2450
2465
|
const missingSecretsAtBoot = listMissingRequiredSecrets(
|
|
2451
2466
|
provider,
|
|
2452
|
-
createEnvContext(provider
|
|
2467
|
+
createEnvContext(providerSecretNames(provider)),
|
|
2453
2468
|
);
|
|
2454
2469
|
if (missingSecretsAtBoot.length > 0) {
|
|
2455
2470
|
logger({
|
|
@@ -3155,6 +3170,8 @@ export async function serve<TContext extends Partial<ProviderContext> = Provider
|
|
|
3155
3170
|
const selfTestSecrets = resolveSelfTestMasterSecrets();
|
|
3156
3171
|
const serverAppOptions: ProviderServerOptions<TContext> = {
|
|
3157
3172
|
logger: options.logger,
|
|
3173
|
+
engine: options.engine,
|
|
3174
|
+
files: options.files,
|
|
3158
3175
|
ocr: options.ocr,
|
|
3159
3176
|
stt: options.stt,
|
|
3160
3177
|
resolver: options.resolver,
|
|
@@ -1,15 +1,101 @@
|
|
|
1
|
+
import { isSensitiveFixtureKey, REDACTED_FIXTURE_VALUE } from "../fixture-sanitization.js";
|
|
2
|
+
import {
|
|
3
|
+
OTEL_EXPORTER_OTLP_ENDPOINT,
|
|
4
|
+
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,
|
|
5
|
+
type OTLPExportOptions,
|
|
6
|
+
type OTLPExportResolution,
|
|
7
|
+
type OTLPResourceResolution,
|
|
8
|
+
resolveOTLPExportOptions,
|
|
9
|
+
resolveOTLPResourceAttributes,
|
|
10
|
+
} from "../runtime/otlp.js";
|
|
1
11
|
import {
|
|
2
|
-
resolveTraceContextOptions,
|
|
3
12
|
type CreateTraceContextOptions,
|
|
13
|
+
resolveTraceContextOptions,
|
|
4
14
|
type Span,
|
|
5
15
|
} from "../runtime/trace.js";
|
|
6
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
sanitizeSpanForOutput,
|
|
18
|
+
sanitizeSpanNameForOutput,
|
|
19
|
+
sanitizeTraceAttributes,
|
|
20
|
+
} from "../trace-sanitization.js";
|
|
7
21
|
import type { TraceConfig } from "../types.js";
|
|
8
22
|
|
|
23
|
+
type EnvLike = Record<string, string | undefined>;
|
|
24
|
+
|
|
25
|
+
// Warn once per environment object: process.env in production, each injected env in tests.
|
|
26
|
+
const warnedEnvironments = new WeakSet<EnvLike>();
|
|
27
|
+
const warnedResourceEnvironments = new WeakSet<EnvLike>();
|
|
28
|
+
|
|
29
|
+
function warnDiscardedResourceAttributes(env: EnvLike, resolution: OTLPResourceResolution): void {
|
|
30
|
+
if (resolution.discarded.length === 0 || warnedResourceEnvironments.has(env)) return;
|
|
31
|
+
warnedResourceEnvironments.add(env);
|
|
32
|
+
console.warn(
|
|
33
|
+
`[apifuse] ${resolution.discarded.join(", ")} could not be parsed as an OTel key=value list and was ignored.`,
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function warnExportDisabled(
|
|
38
|
+
env: EnvLike,
|
|
39
|
+
resolution: Exclude<OTLPExportResolution, { status: "resolved" }>,
|
|
40
|
+
): void {
|
|
41
|
+
if (warnedEnvironments.has(env)) return;
|
|
42
|
+
warnedEnvironments.add(env);
|
|
43
|
+
console.warn(
|
|
44
|
+
resolution.status === "unconfigured"
|
|
45
|
+
? `[apifuse] OTLP trace export is enabled but no endpoint is configured; set ${OTEL_EXPORTER_OTLP_TRACES_ENDPOINT} or ${OTEL_EXPORTER_OTLP_ENDPOINT}. Trace export is disabled.`
|
|
46
|
+
: `[apifuse] OTLP trace export is enabled but ${resolution.source} ${resolution.reason}. Trace export is disabled.`,
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function resolveServerOTLPExportOptions(
|
|
51
|
+
config: TraceConfig,
|
|
52
|
+
env: EnvLike,
|
|
53
|
+
): OTLPExportOptions | undefined {
|
|
54
|
+
const resolution = resolveOTLPExportOptions(
|
|
55
|
+
{
|
|
56
|
+
endpoint: config.otlp?.endpoint ?? config.endpoint,
|
|
57
|
+
headers: config.otlp?.headers,
|
|
58
|
+
timeout: config.otlp?.timeout,
|
|
59
|
+
},
|
|
60
|
+
env,
|
|
61
|
+
);
|
|
62
|
+
if (resolution.status === "resolved") return resolution.options;
|
|
63
|
+
warnExportDisabled(env, resolution);
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Per-request attributes carry client-supplied values (request_id) and get the full attribute
|
|
69
|
+
* sanitizer, exactly as on the console path. Operator-configured attributes from the process
|
|
70
|
+
* environment are identifiers rather than request data: their values are kept, with control
|
|
71
|
+
* characters neutralized, length bounded, and secret-named keys redacted.
|
|
72
|
+
*/
|
|
73
|
+
function resolveExportResourceAttributes(
|
|
74
|
+
requestAttributes: Record<string, string>,
|
|
75
|
+
env: EnvLike,
|
|
76
|
+
): Record<string, string> {
|
|
77
|
+
const resolution = resolveOTLPResourceAttributes({}, env);
|
|
78
|
+
warnDiscardedResourceAttributes(env, resolution);
|
|
79
|
+
const operatorAttributes = Object.fromEntries(
|
|
80
|
+
Object.entries(resolution.attributes).map(([key, value]) => [
|
|
81
|
+
sanitizeSpanNameForOutput(key),
|
|
82
|
+
isSensitiveFixtureKey(key) ? REDACTED_FIXTURE_VALUE : sanitizeSpanNameForOutput(value),
|
|
83
|
+
]),
|
|
84
|
+
);
|
|
85
|
+
const sanitizedRequestAttributes = Object.fromEntries(
|
|
86
|
+
Object.entries(sanitizeTraceAttributes(requestAttributes)).map(([key, value]) => [
|
|
87
|
+
key,
|
|
88
|
+
String(value),
|
|
89
|
+
]),
|
|
90
|
+
);
|
|
91
|
+
return { ...operatorAttributes, ...sanitizedRequestAttributes };
|
|
92
|
+
}
|
|
93
|
+
|
|
9
94
|
/** Server-only trace output policy. Shared programmatic trace callers stay in-memory. */
|
|
10
95
|
export function resolveServerTraceContextOptions(
|
|
11
96
|
config: TraceConfig,
|
|
12
97
|
resourceAttributes: Record<string, string>,
|
|
98
|
+
env: EnvLike = process.env,
|
|
13
99
|
): CreateTraceContextOptions {
|
|
14
100
|
const resolved = resolveTraceContextOptions(config);
|
|
15
101
|
const outputEnabled = config.enabled !== false && config.exporter !== "none";
|
|
@@ -24,9 +110,20 @@ export function resolveServerTraceContextOptions(
|
|
|
24
110
|
resolved.onSpan?.(span);
|
|
25
111
|
}
|
|
26
112
|
: (consoleHook ?? resolved.onSpan);
|
|
113
|
+
const exportOptions =
|
|
114
|
+
outputEnabled && config.exporter === "otlp"
|
|
115
|
+
? resolveServerOTLPExportOptions(config, env)
|
|
116
|
+
: undefined;
|
|
27
117
|
|
|
28
118
|
return {
|
|
29
119
|
maxSpans: resolved.maxSpans,
|
|
30
120
|
onSpan,
|
|
121
|
+
...(exportOptions
|
|
122
|
+
? {
|
|
123
|
+
exportOptions,
|
|
124
|
+
resourceAttributes: resolveExportResourceAttributes(resourceAttributes, env),
|
|
125
|
+
sanitizeSpanForExport: (span: Span) => sanitizeSpanForOutput(span),
|
|
126
|
+
}
|
|
127
|
+
: {}),
|
|
31
128
|
};
|
|
32
129
|
}
|