@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
package/dist/runtime/trace.js
CHANGED
|
@@ -52,6 +52,17 @@ function insertCompletedSpan(completed, entry, maxSpans) {
|
|
|
52
52
|
completed.splice(0, completed.length - maxSpans);
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
|
+
/** Hands the hook a copy whose attributes are detached from the recorded span; a hook that returns nothing fails closed. */
|
|
56
|
+
function prepareSpanForExport(span, sanitize) {
|
|
57
|
+
const copy = { ...span, attributes: { ...span.attributes } };
|
|
58
|
+
if (!sanitize)
|
|
59
|
+
return copy;
|
|
60
|
+
const sanitized = sanitize(copy);
|
|
61
|
+
if (!sanitized || typeof sanitized !== "object") {
|
|
62
|
+
throw new Error("sanitizeSpanForExport returned no span");
|
|
63
|
+
}
|
|
64
|
+
return sanitized;
|
|
65
|
+
}
|
|
55
66
|
export function getTraceRecorder(trace) {
|
|
56
67
|
return trace[TRACE_RECORDER] ?? null;
|
|
57
68
|
}
|
|
@@ -60,14 +71,40 @@ export function createTraceContext(options = {}) {
|
|
|
60
71
|
const completed = [];
|
|
61
72
|
const activeSpanStorage = new AsyncLocalStorage();
|
|
62
73
|
let sequence = 0;
|
|
63
|
-
|
|
74
|
+
// Export configuration (which can carry collector credentials) stays in this closure;
|
|
75
|
+
// the context object handed to provider code never exposes it.
|
|
76
|
+
const exportOptions = options.exportOptions;
|
|
77
|
+
const exportResourceAttributes = options.resourceAttributes
|
|
78
|
+
? { ...options.resourceAttributes }
|
|
79
|
+
: undefined;
|
|
80
|
+
// One trace id per context so every export batch of this request shares it and
|
|
81
|
+
// two processes can never mint the same id.
|
|
82
|
+
const exportTraceId = crypto.randomUUID().replace(/-/g, "");
|
|
83
|
+
let exportScheduled = false;
|
|
84
|
+
// One pending batch per context: roots completing before the flush share it, and a span is
|
|
85
|
+
// handed to the exporter exactly once, so later roots never re-send earlier spans.
|
|
64
86
|
const scheduleExport = () => {
|
|
65
|
-
if (!
|
|
87
|
+
if (!exportOptions || exportScheduled) {
|
|
66
88
|
return;
|
|
67
89
|
}
|
|
68
|
-
|
|
90
|
+
exportScheduled = true;
|
|
69
91
|
setImmediate(() => {
|
|
70
|
-
|
|
92
|
+
exportScheduled = false;
|
|
93
|
+
const pending = completed.filter((entry) => !entry.exported);
|
|
94
|
+
for (const entry of pending)
|
|
95
|
+
entry.exported = true;
|
|
96
|
+
if (pending.length === 0)
|
|
97
|
+
return;
|
|
98
|
+
// Sanitization runs off the request path; a faulty sanitizer drops the batch, never the request.
|
|
99
|
+
let spans;
|
|
100
|
+
try {
|
|
101
|
+
spans = pending.map((entry) => prepareSpanForExport(entry.span, options.sanitizeSpanForExport));
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
console.warn("[apifuse] OTLP export skipped; span sanitization failed.");
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
void exportSpansOTLP(spans, exportOptions, exportResourceAttributes, exportTraceId);
|
|
71
108
|
});
|
|
72
109
|
};
|
|
73
110
|
const recorder = {
|
|
@@ -101,7 +138,7 @@ export function createTraceContext(options = {}) {
|
|
|
101
138
|
...(error ? { error } : {}),
|
|
102
139
|
...(pendingSpan.parentId ? { parentId: pendingSpan.parentId } : {}),
|
|
103
140
|
};
|
|
104
|
-
insertCompletedSpan(completed, { sequence: pendingSpan.sequence, span }, maxSpans);
|
|
141
|
+
insertCompletedSpan(completed, { sequence: pendingSpan.sequence, span, exported: false }, maxSpans);
|
|
105
142
|
options.onSpan?.(span);
|
|
106
143
|
if (!pendingSpan.parentId) {
|
|
107
144
|
scheduleExport();
|
|
@@ -123,17 +160,13 @@ export function createTraceContext(options = {}) {
|
|
|
123
160
|
});
|
|
124
161
|
},
|
|
125
162
|
};
|
|
126
|
-
traceContext = {
|
|
163
|
+
const traceContext = {
|
|
127
164
|
span(name, fn) {
|
|
128
165
|
return recorder.runSpan(name, fn);
|
|
129
166
|
},
|
|
130
167
|
getSpans() {
|
|
131
168
|
return completed.map((entry) => ({ ...entry.span }));
|
|
132
169
|
},
|
|
133
|
-
...(options.exportOptions ? { _exportOptions: options.exportOptions } : {}),
|
|
134
|
-
...(options.resourceAttributes
|
|
135
|
-
? { _resourceAttributes: { ...options.resourceAttributes } }
|
|
136
|
-
: {}),
|
|
137
170
|
[TRACE_RECORDER]: recorder,
|
|
138
171
|
};
|
|
139
172
|
return traceContext;
|
|
@@ -134,9 +134,7 @@ export interface SelfTestAppOptions {
|
|
|
134
134
|
export declare function computeSelfTestPlanDigest(provider: ProviderDefinition): string;
|
|
135
135
|
/**
|
|
136
136
|
* Fail-closed read-only classification: an operation may self-test only when
|
|
137
|
-
* its
|
|
138
|
-
* toolRouter.riskClass "read") and nothing marks it side-effecting. Unclassified
|
|
139
|
-
* operations are refused so the endpoint cannot become a mutation oracle.
|
|
137
|
+
* its authored safety source of truth classifies it as read-only.
|
|
140
138
|
*/
|
|
141
139
|
export declare function isSelfTestReadOnlyOperation(operation: OperationDefinition): boolean;
|
|
142
140
|
/** Binds the self-test executor to a tenant app's /v1 pipeline in-process. */
|
package/dist/server/self-test.js
CHANGED
|
@@ -115,20 +115,10 @@ export function computeSelfTestPlanDigest(provider) {
|
|
|
115
115
|
}
|
|
116
116
|
/**
|
|
117
117
|
* Fail-closed read-only classification: an operation may self-test only when
|
|
118
|
-
* its
|
|
119
|
-
* toolRouter.riskClass "read") and nothing marks it side-effecting. Unclassified
|
|
120
|
-
* operations are refused so the endpoint cannot become a mutation oracle.
|
|
118
|
+
* its authored safety source of truth classifies it as read-only.
|
|
121
119
|
*/
|
|
122
120
|
export function isSelfTestReadOnlyOperation(operation) {
|
|
123
|
-
|
|
124
|
-
if (annotations?.readOnly === false)
|
|
125
|
-
return false;
|
|
126
|
-
if (annotations?.destructive === true)
|
|
127
|
-
return false;
|
|
128
|
-
const riskClass = operation.toolRouter?.riskClass;
|
|
129
|
-
if (riskClass !== undefined && riskClass !== "read")
|
|
130
|
-
return false;
|
|
131
|
-
return annotations?.readOnly === true || riskClass === "read";
|
|
121
|
+
return operation.riskClass === "read";
|
|
132
122
|
}
|
|
133
123
|
/** Binds the self-test executor to a tenant app's /v1 pipeline in-process. */
|
|
134
124
|
export function createSelfTestInvoke(app) {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { Hono } from "hono";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
+
import { type ProviderEngine } from "../engine.js";
|
|
3
4
|
import { type ProviderErrorObservability } from "../errors.js";
|
|
4
5
|
import { type ProviderErrorCategory } from "../observability.js";
|
|
5
6
|
import { type ProxyTelemetryLogPayload } from "../runtime/proxy-telemetry.js";
|
|
6
|
-
import type { OcrContext, ProviderContext, ProviderDefinition, ProviderRuntimeState, ResolverContext, SttContext } from "../types.js";
|
|
7
|
+
import type { OcrContext, ProviderContext, ProviderDefinition, ProviderFilesContext, ProviderRuntimeState, ResolverContext, SttContext } from "../types.js";
|
|
7
8
|
import type { SelfTestCancellationLogEvent } from "./self-test.js";
|
|
8
9
|
import { type AuthFlowRequest, type OperationRequest } from "./types.js";
|
|
9
10
|
/** Compact SDK-owned error classification emitted separately from the public response body. */
|
|
@@ -140,6 +141,10 @@ export type ProviderServerLogEvent = (ProviderServerLogEventBase & {
|
|
|
140
141
|
export type ProviderServerLogger = (event: ProviderServerLogEvent) => void;
|
|
141
142
|
export type ProviderServerOptions<TContext extends Partial<ProviderContext> = ProviderContext> = {
|
|
142
143
|
logger?: ProviderServerLogger;
|
|
144
|
+
/** Capability attachment boundary. Defaults to the local in-process engine. */
|
|
145
|
+
engine?: ProviderEngine;
|
|
146
|
+
/** Request-file resolver supplied by the engine host when `files` is declared. */
|
|
147
|
+
files?: ProviderFilesContext;
|
|
143
148
|
/** Optional provider-specific operation executor. Stateful providers use this to preserve provider-local runtime semantics. */
|
|
144
149
|
operationExecutor?: ProviderServerOperationExecutor<TContext>;
|
|
145
150
|
/** Optional signed internal executor for stateful owner forwarding. */
|
|
@@ -7,6 +7,7 @@ import { z } from "zod";
|
|
|
7
7
|
import { AuthAbortError, createAuthFlowHelpers } from "../auth.js";
|
|
8
8
|
import { validateFailClosedDeclaration } from "../declaration-validation.js";
|
|
9
9
|
import { safeProviderErrorObservability } from "../error-observability.js";
|
|
10
|
+
import { createInProcessProviderEngine, ENGINE_OWNED_PROXY_CREDENTIAL_ENV_NAMES, isEngineOwnedEnvName, readEngineProxyCredentials, } from "../engine.js";
|
|
10
11
|
import { SDK_OWNED_PROVIDER_ERROR_CODES, SDK_RUNTIME_OWNED_ERROR_CODES, SDK_STATUS_MAPPED_PROVIDER_ERROR_CODES, } from "../error-resolution.js";
|
|
11
12
|
import { AuthError, isProviderError, isSessionExpiredError, isTransportError, isValidationError, ProviderError, } from "../errors.js";
|
|
12
13
|
import { sanitizeDiagnosticText } from "../fixture-sanitization.js";
|
|
@@ -307,7 +308,7 @@ function getProviderStealthBaseUrl(provider) {
|
|
|
307
308
|
return firstHost ? `https://${firstHost}` : undefined;
|
|
308
309
|
}
|
|
309
310
|
function getProviderStealthProfile(provider) {
|
|
310
|
-
return provider.stealth
|
|
311
|
+
return provider.stealth ? getStealthProfile(provider.stealth) : undefined;
|
|
311
312
|
}
|
|
312
313
|
function isProductionProviderBrowserMode(provider, env = process.env) {
|
|
313
314
|
if (provider.runtime !== "browser") {
|
|
@@ -394,16 +395,21 @@ function resolveNativeProxyPolicy(provider) {
|
|
|
394
395
|
return { mode: "disabled" };
|
|
395
396
|
return undefined;
|
|
396
397
|
}
|
|
398
|
+
function providerSecretNames(provider) {
|
|
399
|
+
return (provider.secrets?.map((secret) => secret.name) ?? []).filter((name) => !isEngineOwnedEnvName(name));
|
|
400
|
+
}
|
|
397
401
|
function createProviderContext(provider, request, operationId, options, state = createUnsupportedProviderRuntimeState(), proxyTelemetry, signal) {
|
|
398
402
|
const traceConfig = resolveTraceConfigFromEnv();
|
|
399
403
|
const baseUrl = getProviderBaseUrl(provider);
|
|
400
404
|
const stealthBaseUrl = getProviderStealthBaseUrl(provider);
|
|
401
405
|
const stealthProfile = getProviderStealthProfile(provider);
|
|
402
406
|
const proxyPolicy = resolveNativeProxyPolicy(provider);
|
|
407
|
+
const engineProxyCredentials = readEngineProxyCredentials();
|
|
403
408
|
const proxyClientOptions = {
|
|
404
409
|
upstream: { proxy: provider.proxy },
|
|
405
410
|
affinityKey: resolveProviderProxyAffinityKey(provider, request, operationId),
|
|
406
411
|
telemetry: proxyTelemetry,
|
|
412
|
+
engineCredentials: engineProxyCredentials,
|
|
407
413
|
};
|
|
408
414
|
const resolverIdentityScope = resolveProviderResolverIdentityScope(provider, proxyClientOptions.affinityKey, request.requestId);
|
|
409
415
|
let wrappedContext;
|
|
@@ -411,12 +417,14 @@ function createProviderContext(provider, request, operationId, options, state =
|
|
|
411
417
|
upstream: proxyClientOptions.upstream,
|
|
412
418
|
affinityKey: proxyClientOptions.affinityKey,
|
|
413
419
|
telemetry: proxyTelemetry,
|
|
420
|
+
engineCredentials: engineProxyCredentials,
|
|
414
421
|
...(signal ? { signal } : {}),
|
|
422
|
+
...(provider.stealth ? { stealth: provider.stealth } : {}),
|
|
415
423
|
};
|
|
416
424
|
const { capabilityModules } = options;
|
|
417
425
|
const logStealthCleanupError = (error) => logProviderCleanupError(options.logger, provider, "operation", operationId, request.requestId, "stealth", error);
|
|
418
426
|
const env = createEnvContext([
|
|
419
|
-
...(provider
|
|
427
|
+
...providerSecretNames(provider),
|
|
420
428
|
PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV,
|
|
421
429
|
]);
|
|
422
430
|
const credential = createCredentialContext({
|
|
@@ -431,9 +439,10 @@ function createProviderContext(provider, request, operationId, options, state =
|
|
|
431
439
|
};
|
|
432
440
|
const requestState = state.forConnection(requestContext.connectionId);
|
|
433
441
|
const cache = createProviderCache({ providerId: provider.id });
|
|
434
|
-
const
|
|
442
|
+
const bindings = {
|
|
435
443
|
env,
|
|
436
444
|
credential,
|
|
445
|
+
...(options.files ? { files: options.files } : {}),
|
|
437
446
|
request: requestContext,
|
|
438
447
|
http: createHttpClient(baseUrl, {
|
|
439
448
|
...proxyClientOptions,
|
|
@@ -448,12 +457,8 @@ function createProviderContext(provider, request, operationId, options, state =
|
|
|
448
457
|
state: requestState,
|
|
449
458
|
stealth: stealthBaseUrl
|
|
450
459
|
? capabilityModules.stealth
|
|
451
|
-
?
|
|
452
|
-
|
|
453
|
-
: capabilityModules.stealth.createStealthClient(stealthBaseUrl, stealthClientOptions)
|
|
454
|
-
: stealthProfile
|
|
455
|
-
? createLazyStealthClient(logStealthCleanupError, stealthBaseUrl, provider.stealth.profile, stealthClientOptions)
|
|
456
|
-
: createLazyStealthClient(logStealthCleanupError, stealthBaseUrl, stealthClientOptions)
|
|
460
|
+
? capabilityModules.stealth.createStealthClient(stealthBaseUrl, stealthClientOptions)
|
|
461
|
+
: createLazyStealthClient(logStealthCleanupError, stealthBaseUrl, stealthClientOptions)
|
|
457
462
|
: createStealthStub(),
|
|
458
463
|
browser: provider.runtime === "browser"
|
|
459
464
|
? capabilityModules.browser.createBrowserClient({
|
|
@@ -472,7 +477,7 @@ function createProviderContext(provider, request, operationId, options, state =
|
|
|
472
477
|
egress: provider.native.network,
|
|
473
478
|
proxyPolicy: resolveNativeProxyPolicy(provider),
|
|
474
479
|
affinityKey: proxyClientOptions.affinityKey,
|
|
475
|
-
credentials: capabilityModules.nativeNetwork.createEnvVendorCredentialResolver(
|
|
480
|
+
credentials: capabilityModules.nativeNetwork.createEnvVendorCredentialResolver(createEnvContext([...ENGINE_OWNED_PROXY_CREDENTIAL_ENV_NAMES])),
|
|
476
481
|
}),
|
|
477
482
|
},
|
|
478
483
|
}
|
|
@@ -517,7 +522,8 @@ function createProviderContext(provider, request, operationId, options, state =
|
|
|
517
522
|
...event,
|
|
518
523
|
}),
|
|
519
524
|
}),
|
|
520
|
-
}
|
|
525
|
+
};
|
|
526
|
+
const context = wrapWithInstrumentation(options.engine.attach({ provider, bindings }));
|
|
521
527
|
wrappedContext = context;
|
|
522
528
|
return context;
|
|
523
529
|
}
|
|
@@ -557,19 +563,23 @@ function createAuthFlowContext(provider, request, options, state, proxyTelemetry
|
|
|
557
563
|
const stealthBaseUrl = getProviderStealthBaseUrl(provider);
|
|
558
564
|
const stealthProfile = getProviderStealthProfile(provider);
|
|
559
565
|
const proxyPolicy = resolveNativeProxyPolicy(provider);
|
|
566
|
+
const engineProxyCredentials = readEngineProxyCredentials();
|
|
560
567
|
const contextData = request.context ?? {};
|
|
561
568
|
const flowContextStore = createFlowContextStore(provider.context?.keys ?? Object.keys(contextData), contextData);
|
|
562
569
|
const proxyClientOptions = {
|
|
563
570
|
upstream: { proxy: provider.proxy },
|
|
564
571
|
affinityKey: resolveAuthFlowProxyAffinityKey(provider, request),
|
|
565
572
|
telemetry: proxyTelemetry,
|
|
573
|
+
engineCredentials: engineProxyCredentials,
|
|
566
574
|
};
|
|
567
575
|
const resolverIdentityScope = resolveProviderResolverIdentityScope(provider, proxyClientOptions.affinityKey, request.requestId);
|
|
568
576
|
const stealthClientOptions = {
|
|
569
577
|
upstream: proxyClientOptions.upstream,
|
|
570
578
|
affinityKey: proxyClientOptions.affinityKey,
|
|
571
579
|
telemetry: proxyTelemetry,
|
|
580
|
+
engineCredentials: engineProxyCredentials,
|
|
572
581
|
...(signal ? { signal } : {}),
|
|
582
|
+
...(provider.stealth ? { stealth: provider.stealth } : {}),
|
|
573
583
|
};
|
|
574
584
|
const { capabilityModules } = options;
|
|
575
585
|
const logStealthCleanupError = (error) => logProviderCleanupError(options.logger, provider, "auth", "flow", request.requestId, "stealth", error);
|
|
@@ -596,12 +606,8 @@ function createAuthFlowContext(provider, request, options, state, proxyTelemetry
|
|
|
596
606
|
state: state.forConnection(resolveOperationConnectionId(request)),
|
|
597
607
|
stealth: stealthBaseUrl
|
|
598
608
|
? capabilityModules.stealth
|
|
599
|
-
?
|
|
600
|
-
|
|
601
|
-
: capabilityModules.stealth.createStealthClient(stealthBaseUrl, stealthClientOptions)
|
|
602
|
-
: stealthProfile
|
|
603
|
-
? createLazyStealthClient(logStealthCleanupError, stealthBaseUrl, provider.stealth.profile, stealthClientOptions)
|
|
604
|
-
: createLazyStealthClient(logStealthCleanupError, stealthBaseUrl, stealthClientOptions)
|
|
609
|
+
? capabilityModules.stealth.createStealthClient(stealthBaseUrl, stealthClientOptions)
|
|
610
|
+
: createLazyStealthClient(logStealthCleanupError, stealthBaseUrl, stealthClientOptions)
|
|
605
611
|
: createStealthStub(),
|
|
606
612
|
...(provider.native
|
|
607
613
|
? {
|
|
@@ -610,13 +616,13 @@ function createAuthFlowContext(provider, request, options, state, proxyTelemetry
|
|
|
610
616
|
egress: provider.native.network,
|
|
611
617
|
proxyPolicy: resolveNativeProxyPolicy(provider),
|
|
612
618
|
affinityKey: proxyClientOptions.affinityKey,
|
|
613
|
-
credentials: capabilityModules.nativeNetwork.createEnvVendorCredentialResolver(createEnvContext(
|
|
619
|
+
credentials: capabilityModules.nativeNetwork.createEnvVendorCredentialResolver(createEnvContext([...ENGINE_OWNED_PROXY_CREDENTIAL_ENV_NAMES])),
|
|
614
620
|
}),
|
|
615
621
|
},
|
|
616
622
|
}
|
|
617
623
|
: {}),
|
|
618
624
|
env: createEnvContext([
|
|
619
|
-
...(provider
|
|
625
|
+
...providerSecretNames(provider),
|
|
620
626
|
...(provider.auth?.mode === "oauth2_proxied" ? ["APIFUSE__AUTH_PROXY__URL"] : []),
|
|
621
627
|
]),
|
|
622
628
|
credential,
|
|
@@ -766,11 +772,8 @@ function providerObservabilityDetails(error, declaredErrorCode) {
|
|
|
766
772
|
const declaredRetryable = sdkOwnsErrorResolution(error)
|
|
767
773
|
? undefined
|
|
768
774
|
: declaredErrorCode?.retryable;
|
|
769
|
-
// Session-expiry surfaces the credential_expired category
|
|
770
|
-
//
|
|
771
|
-
// operation (see design.md §4.3 D3). Without this branch the auth error would
|
|
772
|
-
// serialize as a bare 401 with no retryable/category, losing the refresh
|
|
773
|
-
// signal for exactly the retryOnAuthRefresh operations it is meant to enable.
|
|
775
|
+
// Session-expiry surfaces the credential_expired category so Gateway and
|
|
776
|
+
// Credential Service can distinguish an expired credential from other 401s.
|
|
774
777
|
if (isSessionExpiredError(error)) {
|
|
775
778
|
return {
|
|
776
779
|
category: providerErrorOption(error, "category") ?? "credential_expired",
|
|
@@ -962,7 +965,7 @@ function sdkOwnsErrorResolution(error) {
|
|
|
962
965
|
}
|
|
963
966
|
function buildOperationErrorCodeLookup(provider) {
|
|
964
967
|
return new Map(Object.entries(provider.operations).flatMap(([operationId, operation]) => {
|
|
965
|
-
const errorCodes = operation.
|
|
968
|
+
const errorCodes = operation.errorCodes;
|
|
966
969
|
return errorCodes?.length
|
|
967
970
|
? [[operationId, new Map(errorCodes.map((entry) => [entry.code, entry]))]]
|
|
968
971
|
: [];
|
|
@@ -1063,7 +1066,7 @@ function logProviderError(logger, provider, kind, route, requestId, error, statu
|
|
|
1063
1066
|
...(isUnregisteredProviderErrorCode
|
|
1064
1067
|
? {
|
|
1065
1068
|
signal: "unregistered_provider_error_code",
|
|
1066
|
-
signalFix: "Declare this code (with status and retryable) in the operation's
|
|
1069
|
+
signalFix: "Declare this code (with status and retryable) in the operation's errorCodes so it serves its intended status instead of 500.",
|
|
1067
1070
|
}
|
|
1068
1071
|
: {}),
|
|
1069
1072
|
...(error instanceof z.ZodError ? { issues: zodDetails(error) } : {}),
|
|
@@ -1107,7 +1110,7 @@ function toJsonSuccessResponse(result, ctx) {
|
|
|
1107
1110
|
if (result instanceof ReadableStream) {
|
|
1108
1111
|
return new Response(result);
|
|
1109
1112
|
}
|
|
1110
|
-
const cacheMeta = ctx
|
|
1113
|
+
const cacheMeta = ctx && "cache" in ctx ? ctx.cache.responseMeta() : undefined;
|
|
1111
1114
|
const retryMeta = ctx ? retryResponseMeta.get(ctx) : undefined;
|
|
1112
1115
|
const meta = cacheMeta || retryMeta
|
|
1113
1116
|
? {
|
|
@@ -1425,17 +1428,21 @@ async function handleOperation(provider, request, operationId, options, state =
|
|
|
1425
1428
|
if (cleanupCalled)
|
|
1426
1429
|
return;
|
|
1427
1430
|
cleanupCalled = true;
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
await ctx.browser.close?.();
|
|
1431
|
+
if ("stealth" in ctx) {
|
|
1432
|
+
try {
|
|
1433
|
+
ctx.stealth.close?.();
|
|
1434
|
+
}
|
|
1435
|
+
catch (error) {
|
|
1436
|
+
logProviderCleanupError(options.logger, provider, "operation", operationId, request.requestId, "stealth", error);
|
|
1437
|
+
}
|
|
1436
1438
|
}
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
+
if ("browser" in ctx) {
|
|
1440
|
+
try {
|
|
1441
|
+
await ctx.browser.close?.();
|
|
1442
|
+
}
|
|
1443
|
+
catch (error) {
|
|
1444
|
+
logProviderCleanupError(options.logger, provider, "operation", operationId, request.requestId, "browser", error);
|
|
1445
|
+
}
|
|
1439
1446
|
}
|
|
1440
1447
|
};
|
|
1441
1448
|
try {
|
|
@@ -1447,7 +1454,9 @@ async function handleOperation(provider, request, operationId, options, state =
|
|
|
1447
1454
|
request,
|
|
1448
1455
|
signal,
|
|
1449
1456
|
})
|
|
1450
|
-
: await executeOperation(provider, operationId, ctx, request.input
|
|
1457
|
+
: await executeOperation(provider, operationId, ctx, request.input, {
|
|
1458
|
+
env: createEnvContext(providerSecretNames(provider)),
|
|
1459
|
+
});
|
|
1451
1460
|
if (streaming && operation) {
|
|
1452
1461
|
return toStreamingResponse(operation, result, cleanup, request.requestId);
|
|
1453
1462
|
}
|
|
@@ -1670,7 +1679,11 @@ export function createServerApp(provider, options = {}) {
|
|
|
1670
1679
|
return createServerAppWithCapabilityModules(runtimeProvider, runtimeOptions, loadProviderCapabilityModulesSync(runtimeProvider));
|
|
1671
1680
|
}
|
|
1672
1681
|
function createServerAppWithCapabilityModules(provider, serverOptions, capabilityModules) {
|
|
1673
|
-
const options = {
|
|
1682
|
+
const options = {
|
|
1683
|
+
...serverOptions,
|
|
1684
|
+
capabilityModules,
|
|
1685
|
+
engine: serverOptions.engine ?? createInProcessProviderEngine(),
|
|
1686
|
+
};
|
|
1674
1687
|
const app = new Hono();
|
|
1675
1688
|
const logger = options.logger ?? defaultProviderServerLogger;
|
|
1676
1689
|
const operationErrorCodes = buildOperationErrorCodeLookup(provider);
|
|
@@ -1686,7 +1699,7 @@ function createServerAppWithCapabilityModules(provider, serverOptions, capabilit
|
|
|
1686
1699
|
// instead of discovering it request-by-request. Deliberately log-only — a
|
|
1687
1700
|
// boot crash would trade a structured MISSING_SECRET signal for
|
|
1688
1701
|
// CrashLoopBackOff. Requests still fail closed via the executeOperation gate.
|
|
1689
|
-
const missingSecretsAtBoot = listMissingRequiredSecrets(provider, createEnvContext(provider
|
|
1702
|
+
const missingSecretsAtBoot = listMissingRequiredSecrets(provider, createEnvContext(providerSecretNames(provider)));
|
|
1690
1703
|
if (missingSecretsAtBoot.length > 0) {
|
|
1691
1704
|
logger({
|
|
1692
1705
|
level: "warn",
|
|
@@ -2064,6 +2077,8 @@ export async function serve(provider, options = {}) {
|
|
|
2064
2077
|
const selfTestSecrets = resolveSelfTestMasterSecrets();
|
|
2065
2078
|
const serverAppOptions = {
|
|
2066
2079
|
logger: options.logger,
|
|
2080
|
+
engine: options.engine,
|
|
2081
|
+
files: options.files,
|
|
2067
2082
|
ocr: options.ocr,
|
|
2068
2083
|
stt: options.stt,
|
|
2069
2084
|
resolver: options.resolver,
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { type CreateTraceContextOptions } from "../runtime/trace.js";
|
|
2
2
|
import type { TraceConfig } from "../types.js";
|
|
3
|
+
type EnvLike = Record<string, string | undefined>;
|
|
3
4
|
/** Server-only trace output policy. Shared programmatic trace callers stay in-memory. */
|
|
4
|
-
export declare function resolveServerTraceContextOptions(config: TraceConfig, resourceAttributes: Record<string, string
|
|
5
|
+
export declare function resolveServerTraceContextOptions(config: TraceConfig, resourceAttributes: Record<string, string>, env?: EnvLike): CreateTraceContextOptions;
|
|
6
|
+
export {};
|
|
@@ -1,7 +1,56 @@
|
|
|
1
|
+
import { isSensitiveFixtureKey, REDACTED_FIXTURE_VALUE } from "../fixture-sanitization.js";
|
|
2
|
+
import { OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, resolveOTLPExportOptions, resolveOTLPResourceAttributes, } from "../runtime/otlp.js";
|
|
1
3
|
import { resolveTraceContextOptions, } from "../runtime/trace.js";
|
|
2
|
-
import { sanitizeSpanForOutput } from "../trace-sanitization.js";
|
|
4
|
+
import { sanitizeSpanForOutput, sanitizeSpanNameForOutput, sanitizeTraceAttributes, } from "../trace-sanitization.js";
|
|
5
|
+
// Warn once per environment object: process.env in production, each injected env in tests.
|
|
6
|
+
const warnedEnvironments = new WeakSet();
|
|
7
|
+
const warnedResourceEnvironments = new WeakSet();
|
|
8
|
+
function warnDiscardedResourceAttributes(env, resolution) {
|
|
9
|
+
if (resolution.discarded.length === 0 || warnedResourceEnvironments.has(env))
|
|
10
|
+
return;
|
|
11
|
+
warnedResourceEnvironments.add(env);
|
|
12
|
+
console.warn(`[apifuse] ${resolution.discarded.join(", ")} could not be parsed as an OTel key=value list and was ignored.`);
|
|
13
|
+
}
|
|
14
|
+
function warnExportDisabled(env, resolution) {
|
|
15
|
+
if (warnedEnvironments.has(env))
|
|
16
|
+
return;
|
|
17
|
+
warnedEnvironments.add(env);
|
|
18
|
+
console.warn(resolution.status === "unconfigured"
|
|
19
|
+
? `[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.`
|
|
20
|
+
: `[apifuse] OTLP trace export is enabled but ${resolution.source} ${resolution.reason}. Trace export is disabled.`);
|
|
21
|
+
}
|
|
22
|
+
function resolveServerOTLPExportOptions(config, env) {
|
|
23
|
+
const resolution = resolveOTLPExportOptions({
|
|
24
|
+
endpoint: config.otlp?.endpoint ?? config.endpoint,
|
|
25
|
+
headers: config.otlp?.headers,
|
|
26
|
+
timeout: config.otlp?.timeout,
|
|
27
|
+
}, env);
|
|
28
|
+
if (resolution.status === "resolved")
|
|
29
|
+
return resolution.options;
|
|
30
|
+
warnExportDisabled(env, resolution);
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Per-request attributes carry client-supplied values (request_id) and get the full attribute
|
|
35
|
+
* sanitizer, exactly as on the console path. Operator-configured attributes from the process
|
|
36
|
+
* environment are identifiers rather than request data: their values are kept, with control
|
|
37
|
+
* characters neutralized, length bounded, and secret-named keys redacted.
|
|
38
|
+
*/
|
|
39
|
+
function resolveExportResourceAttributes(requestAttributes, env) {
|
|
40
|
+
const resolution = resolveOTLPResourceAttributes({}, env);
|
|
41
|
+
warnDiscardedResourceAttributes(env, resolution);
|
|
42
|
+
const operatorAttributes = Object.fromEntries(Object.entries(resolution.attributes).map(([key, value]) => [
|
|
43
|
+
sanitizeSpanNameForOutput(key),
|
|
44
|
+
isSensitiveFixtureKey(key) ? REDACTED_FIXTURE_VALUE : sanitizeSpanNameForOutput(value),
|
|
45
|
+
]));
|
|
46
|
+
const sanitizedRequestAttributes = Object.fromEntries(Object.entries(sanitizeTraceAttributes(requestAttributes)).map(([key, value]) => [
|
|
47
|
+
key,
|
|
48
|
+
String(value),
|
|
49
|
+
]));
|
|
50
|
+
return { ...operatorAttributes, ...sanitizedRequestAttributes };
|
|
51
|
+
}
|
|
3
52
|
/** Server-only trace output policy. Shared programmatic trace callers stay in-memory. */
|
|
4
|
-
export function resolveServerTraceContextOptions(config, resourceAttributes) {
|
|
53
|
+
export function resolveServerTraceContextOptions(config, resourceAttributes, env = process.env) {
|
|
5
54
|
const resolved = resolveTraceContextOptions(config);
|
|
6
55
|
const outputEnabled = config.enabled !== false && config.exporter !== "none";
|
|
7
56
|
const consoleHook = outputEnabled && (config.exporter === "console" || config.exporter === "json")
|
|
@@ -13,8 +62,18 @@ export function resolveServerTraceContextOptions(config, resourceAttributes) {
|
|
|
13
62
|
resolved.onSpan?.(span);
|
|
14
63
|
}
|
|
15
64
|
: (consoleHook ?? resolved.onSpan);
|
|
65
|
+
const exportOptions = outputEnabled && config.exporter === "otlp"
|
|
66
|
+
? resolveServerOTLPExportOptions(config, env)
|
|
67
|
+
: undefined;
|
|
16
68
|
return {
|
|
17
69
|
maxSpans: resolved.maxSpans,
|
|
18
70
|
onSpan,
|
|
71
|
+
...(exportOptions
|
|
72
|
+
? {
|
|
73
|
+
exportOptions,
|
|
74
|
+
resourceAttributes: resolveExportResourceAttributes(resourceAttributes, env),
|
|
75
|
+
sanitizeSpanForExport: (span) => sanitizeSpanForOutput(span),
|
|
76
|
+
}
|
|
77
|
+
: {}),
|
|
19
78
|
};
|
|
20
79
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import type { StealthProfile } from "../types.js";
|
|
2
|
-
export declare
|
|
3
|
-
export declare
|
|
4
|
-
/**
|
|
5
|
-
export declare function
|
|
6
|
-
/**
|
|
7
|
-
export declare function
|
|
8
|
-
export declare function
|
|
1
|
+
import type { StealthProfile, StealthProfileDescriptor, StealthProfileSelection } from "../types.js";
|
|
2
|
+
export declare const DEFAULT_STEALTH_BROWSER: "chrome";
|
|
3
|
+
export declare const DEFAULT_STEALTH_OS: "macos";
|
|
4
|
+
/** Internal detector used to keep version-pinned string failures actionable. */
|
|
5
|
+
export declare function getVersionPinnedStealthReplacement(name: string): string | undefined;
|
|
6
|
+
/** Resolve omitted axes and validate that the resulting pair is supported. */
|
|
7
|
+
export declare function resolveStealthProfileSelection(selection: StealthProfileSelection | undefined, base?: StealthProfileDescriptor): StealthProfileDescriptor;
|
|
8
|
+
export declare function getStealthProfile(selection?: StealthProfileSelection): StealthProfile;
|
|
9
|
+
export declare function listStealthProfiles(): StealthProfileDescriptor[];
|