@jsonstudio/llms 0.6.802 → 0.6.954
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/dist/bridge/routecodex-adapter.d.ts +74 -0
- package/dist/config-unified/enhanced-path-resolver.d.ts +5 -0
- package/dist/config-unified/unified-config.d.ts +26 -0
- package/dist/conversion/codec-registry.d.ts +10 -0
- package/dist/conversion/codecs/gemini-openai-codec.d.ts +16 -0
- package/dist/conversion/codecs/openai-openai-codec.d.ts +12 -0
- package/dist/conversion/codecs/responses-openai-codec.d.ts +12 -0
- package/dist/conversion/compat/profiles/chat-gemini.json +12 -0
- package/dist/conversion/config/config-manager.d.ts +212 -0
- package/dist/conversion/hub/config/types.d.ts +26 -0
- package/dist/conversion/hub/core/detour-registry.d.ts +9 -0
- package/dist/conversion/hub/core/hub-context.d.ts +21 -0
- package/dist/conversion/hub/core/index.d.ts +3 -0
- package/dist/conversion/hub/core/stage-driver.d.ts +30 -0
- package/dist/conversion/hub/format-adapters/anthropic-format-adapter.d.ts +16 -0
- package/dist/conversion/hub/format-adapters/chat-format-adapter.d.ts +17 -0
- package/dist/conversion/hub/format-adapters/gemini-format-adapter.d.ts +16 -0
- package/dist/conversion/hub/format-adapters/index.d.ts +21 -0
- package/dist/conversion/hub/hub-feature.d.ts +1 -0
- package/dist/conversion/hub/node-support.d.ts +19 -0
- package/dist/conversion/hub/pipeline/compat/compat-pipeline-executor.js +11 -0
- package/dist/conversion/hub/pipeline/compat/compat-types.d.ts +3 -0
- package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +7 -0
- package/dist/conversion/hub/pipeline/hub-pipeline.js +113 -17
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/index.js +6 -3
- package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage1_tool_governance/index.js +4 -0
- package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.js +23 -1
- package/dist/conversion/hub/pipelines/inbound.d.ts +22 -0
- package/dist/conversion/hub/pipelines/outbound.d.ts +22 -0
- package/dist/conversion/hub/policy/policy-engine.d.ts +46 -0
- package/dist/conversion/hub/policy/policy-engine.js +176 -0
- package/dist/conversion/hub/policy/protocol-spec.d.ts +50 -0
- package/dist/conversion/hub/policy/protocol-spec.js +105 -0
- package/dist/conversion/hub/process/chat-process.d.ts +32 -0
- package/dist/conversion/hub/registry.d.ts +28 -0
- package/dist/conversion/hub/response/chat-response-utils.d.ts +6 -0
- package/dist/conversion/hub/response/provider-response.js +31 -0
- package/dist/conversion/hub/semantic-mappers/chat-mapper.js +32 -1
- package/dist/conversion/hub/semantic-mappers/gemini-mapper.d.ts +7 -0
- package/dist/conversion/hub/semantic-mappers/gemini-mapper.js +96 -1
- package/dist/conversion/hub/semantic-mappers/index.d.ts +4 -0
- package/dist/conversion/hub/semantic-mappers/responses-mapper.d.ts +21 -0
- package/dist/conversion/hub/standardized-bridge.d.ts +12 -0
- package/dist/conversion/hub/types/chat-schema.d.ts +112 -0
- package/dist/conversion/hub/types/errors.d.ts +5 -0
- package/dist/conversion/hub/types/format-envelope.d.ts +7 -0
- package/dist/conversion/hub/types/index.d.ts +6 -0
- package/dist/conversion/hub/types/json.d.ts +9 -0
- package/dist/conversion/hub/types/node.d.ts +31 -0
- package/dist/conversion/responses/responses-openai-bridge.js +263 -10
- package/dist/conversion/schema-validator.d.ts +7 -0
- package/dist/conversion/shared/args-mapping.d.ts +18 -0
- package/dist/conversion/shared/chat-request-filters.d.ts +9 -0
- package/dist/conversion/shared/errors.d.ts +1 -1
- package/dist/conversion/shared/gemini-tool-utils.js +105 -1
- package/dist/conversion/shared/jsonish.d.ts +3 -0
- package/dist/conversion/shared/mcp-injection.d.ts +2 -0
- package/dist/conversion/shared/media.d.ts +1 -0
- package/dist/conversion/shared/openai-message-normalize.d.ts +1 -0
- package/dist/conversion/shared/payload-budget.d.ts +13 -0
- package/dist/conversion/shared/reasoning-mapping.d.ts +5 -0
- package/dist/conversion/shared/responses-request-adapter.d.ts +1 -28
- package/dist/conversion/shared/responses-request-adapter.js +1 -430
- package/dist/conversion/shared/snapshot-hooks.js +58 -3
- package/dist/conversion/shared/tool-governor.js +8 -2
- package/dist/conversion/shared/tool-harvester.d.ts +31 -0
- package/dist/conversion/shared/tool-mapping.js +10 -29
- package/dist/conversion/types.d.ts +33 -0
- package/dist/filters/builtin/add-fields-filter.d.ts +8 -0
- package/dist/filters/builtin/blacklist-filter.d.ts +8 -0
- package/dist/filters/builtin/whitelist-filter.d.ts +8 -0
- package/dist/filters/engine.d.ts +16 -0
- package/dist/filters/special/request-tool-choice-policy.d.ts +11 -0
- package/dist/filters/special/response-finish-invariants.d.ts +11 -0
- package/dist/filters/special/response-openai-to-responses-bridge.d.ts +13 -0
- package/dist/filters/special/response-tool-arguments-blacklist.d.ts +12 -0
- package/dist/filters/special/response-tool-arguments-schema-converge.d.ts +13 -0
- package/dist/filters/special/response-tool-arguments-stringify.d.ts +9 -0
- package/dist/filters/special/response-tool-arguments-whitelist.d.ts +11 -0
- package/dist/filters/special/tool-filter-hooks.d.ts +19 -0
- package/dist/filters/special/tool-post-constraints.d.ts +31 -0
- package/dist/filters/types.d.ts +68 -0
- package/dist/filters/utils/fieldmap-loader.d.ts +2 -0
- package/dist/filters/utils/snapshot-writer.d.ts +10 -0
- package/dist/guidance/index.d.ts +3 -0
- package/dist/guidance/index.js +78 -83
- package/dist/http/sse-response.d.ts +22 -0
- package/dist/router/virtual-router/bootstrap.d.ts +6 -0
- package/dist/router/virtual-router/bootstrap.js +49 -5
- package/dist/router/virtual-router/classifier.d.ts +10 -0
- package/dist/router/virtual-router/engine-selection.js +98 -11
- package/dist/router/virtual-router/engine.js +177 -31
- package/dist/router/virtual-router/error-center.d.ts +10 -0
- package/dist/router/virtual-router/features.d.ts +3 -0
- package/dist/router/virtual-router/routing-instructions.d.ts +23 -1
- package/dist/router/virtual-router/routing-instructions.js +120 -30
- package/dist/router/virtual-router/types.d.ts +11 -0
- package/dist/servertool/engine.js +192 -17
- package/dist/servertool/handlers/apply-patch-guard.js +269 -0
- package/dist/servertool/handlers/exec-command-guard.js +558 -0
- package/dist/servertool/handlers/followup-message-trimmer.d.ts +16 -0
- package/dist/servertool/handlers/followup-message-trimmer.js +198 -0
- package/dist/servertool/handlers/followup-request-builder.d.ts +17 -0
- package/dist/servertool/handlers/followup-request-builder.js +122 -0
- package/dist/servertool/handlers/gemini-empty-reply-continue.js +252 -51
- package/dist/servertool/handlers/iflow-model-error-retry.js +12 -22
- package/dist/servertool/handlers/stop-message-auto.js +237 -75
- package/dist/servertool/handlers/vision.js +15 -27
- package/dist/servertool/handlers/web-search.js +17 -43
- package/dist/servertool/server-side-tools.d.ts +3 -0
- package/dist/servertool/server-side-tools.js +3 -0
- package/dist/sse/json-to-sse/anthropic-json-to-sse-converter.d.ts +2 -1
- package/dist/sse/json-to-sse/chat-json-to-sse-converter.d.ts +80 -0
- package/dist/sse/json-to-sse/event-generators/chat.d.ts +55 -0
- package/dist/sse/json-to-sse/event-generators/responses.d.ts +99 -0
- package/dist/sse/json-to-sse/gemini-json-to-sse-converter.d.ts +2 -1
- package/dist/sse/json-to-sse/responses-json-to-sse-converter.d.ts +80 -0
- package/dist/sse/json-to-sse/sequencers/anthropic-sequencer.d.ts +1 -1
- package/dist/sse/json-to-sse/sequencers/chat-sequencer.d.ts +2 -2
- package/dist/sse/json-to-sse/sequencers/gemini-sequencer.d.ts +1 -1
- package/dist/sse/json-to-sse/sequencers/responses-sequencer.d.ts +40 -0
- package/dist/sse/shared/chat-serializer.d.ts +4 -0
- package/dist/sse/shared/constants.d.ts +272 -0
- package/dist/sse/shared/serializers/anthropic-event-serializer.d.ts +1 -1
- package/dist/sse/shared/serializers/base-serializer.d.ts +158 -0
- package/dist/sse/shared/serializers/chat-event-serializer.d.ts +82 -0
- package/dist/sse/shared/serializers/gemini-event-serializer.d.ts +1 -1
- package/dist/sse/shared/serializers/index.d.ts +2 -1
- package/dist/sse/shared/serializers/responses-event-serializer.d.ts +123 -0
- package/dist/sse/shared/serializers/types.d.ts +51 -0
- package/dist/sse/shared/utils.d.ts +254 -0
- package/dist/sse/shared/writer.d.ts +2 -2
- package/dist/sse/sse-to-json/anthropic-sse-to-json-converter.d.ts +1 -1
- package/dist/sse/sse-to-json/builders/anthropic-response-builder.d.ts +1 -1
- package/dist/sse/sse-to-json/builders/response-builder.d.ts +1 -1
- package/dist/sse/sse-to-json/chat-sse-to-json-converter.d.ts +2 -1
- package/dist/sse/sse-to-json/gemini-sse-to-json-converter.d.ts +2 -1
- package/dist/sse/sse-to-json/parsers/sse-parser.d.ts +73 -0
- package/dist/sse/sse-to-json/responses-sse-to-json-converter.d.ts +1 -1
- package/dist/sse/types/chat-types.d.ts +1 -1
- package/dist/sse/types/responses-types.d.ts +1 -1
- package/dist/tools/apply-patch/execution-capturer.d.ts +13 -0
- package/dist/tools/apply-patch/execution-capturer.js +158 -0
- package/dist/tools/apply-patch/regression-capturer.d.ts +1 -0
- package/dist/tools/apply-patch/regression-capturer.js +5 -4
- package/dist/tools/apply-patch/structured.js +109 -13
- package/dist/tools/apply-patch/validator.js +112 -18
- package/dist/tools/tool-registry.d.ts +8 -0
- package/dist/tools/tool-registry.js +2 -1
- package/package.json +4 -4
- package/dist/conversion/compat/actions/apply-patch-format-fixer.js +0 -233
- package/dist/conversion/config/compat-profiles.json +0 -38
- package/dist/conversion/hub/pipeline/context-limit.d.ts +0 -13
- package/dist/conversion/hub/pipeline/context-limit.js +0 -55
- package/dist/conversion/hub/response/server-side-tools.d.ts +0 -26
- package/dist/conversion/hub/response/server-side-tools.js +0 -383
- package/dist/conversion/shared/bridge-conversation-store.d.ts +0 -41
- package/dist/conversion/shared/bridge-conversation-store.js +0 -279
- package/dist/conversion/shared/bridge-request-adapter.d.ts +0 -28
- package/dist/conversion/shared/bridge-request-adapter.js +0 -430
- package/dist/conversion/shared/responses-id-utils.js +0 -42
- package/dist/conversion/shared/responses-instructions.js +0 -113
- package/dist/conversion/shared/responses-message-utils.d.ts +0 -15
- package/dist/conversion/shared/responses-message-utils.js +0 -206
- package/dist/conversion/shared/responses-metadata.js +0 -1
- package/dist/conversion/shared/responses-output-utils.d.ts +0 -7
- package/dist/conversion/shared/responses-output-utils.js +0 -108
- package/dist/conversion/shared/responses-types.d.ts +0 -33
- package/dist/conversion/shared/tool-normalizers.d.ts +0 -4
- package/dist/conversion/shared/tool-normalizers.js +0 -84
- package/dist/filters/special/request-streaming-to-nonstreaming.d.ts +0 -13
- package/dist/filters/special/request-streaming-to-nonstreaming.js +0 -39
- package/dist/filters/special/response-apply-patch-toon-decode.d.ts +0 -23
- package/dist/filters/special/response-apply-patch-toon-decode.js +0 -460
- package/dist/filters/special/response-tool-arguments-toon-decode.d.ts +0 -10
- package/dist/filters/special/response-tool-arguments-toon-decode.js +0 -154
- package/dist/servertool/flow-types.d.ts +0 -40
- package/dist/servertool/flow-types.js +0 -1
- package/dist/servertool/orchestration-types.d.ts +0 -33
- package/dist/servertool/orchestration-types.js +0 -1
- package/dist/servertool/vision-tool.d.ts +0 -2
- package/dist/servertool/vision-tool.js +0 -185
- package/dist/tools/patch-args-normalizer.d.ts +0 -15
- package/dist/tools/patch-args-normalizer.js +0 -472
- package/dist/utils/toon.d.ts +0 -4
- package/dist/utils/toon.js +0 -75
- /package/dist/{conversion/compat/actions/apply-patch-format-fixer.d.ts → servertool/handlers/apply-patch-guard.d.ts} +0 -0
- /package/dist/{conversion/shared/responses-types.js → servertool/handlers/exec-command-guard.d.ts} +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Readable } from 'node:stream';
|
|
2
|
-
import { isJsonObject } from '../types/json.js';
|
|
2
|
+
import { isJsonObject, jsonClone } from '../types/json.js';
|
|
3
3
|
import { VirtualRouterEngine } from '../../../router/virtual-router/engine.js';
|
|
4
4
|
import { providerErrorCenter } from '../../../router/virtual-router/error-center.js';
|
|
5
5
|
import { defaultSseCodecRegistry } from '../../../sse/index.js';
|
|
@@ -25,6 +25,23 @@ import { runReqOutboundStage3Compat } from './stages/req_outbound/req_outbound_s
|
|
|
25
25
|
import { extractSessionIdentifiersFromMetadata } from './session-identifiers.js';
|
|
26
26
|
import { computeRequestTokens } from '../../../router/virtual-router/token-estimator.js';
|
|
27
27
|
import { isCompactionRequest } from '../../shared/compaction-detect.js';
|
|
28
|
+
import { applyHubProviderOutboundPolicy, recordHubPolicyObservation, setHubPolicyRuntimePolicy } from '../policy/policy-engine.js';
|
|
29
|
+
function extractHubPolicyOverride(metadata) {
|
|
30
|
+
const raw = metadata ? metadata.__hubPolicyOverride : undefined;
|
|
31
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
const obj = raw;
|
|
35
|
+
const mode = typeof obj.mode === 'string' ? obj.mode.trim().toLowerCase() : '';
|
|
36
|
+
const sampleRate = typeof obj.sampleRate === 'number' && Number.isFinite(obj.sampleRate) ? obj.sampleRate : undefined;
|
|
37
|
+
if (mode !== 'off' && mode !== 'observe' && mode !== 'enforce') {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
mode: mode,
|
|
42
|
+
...(sampleRate !== undefined ? { sampleRate } : {})
|
|
43
|
+
};
|
|
44
|
+
}
|
|
28
45
|
export class HubPipeline {
|
|
29
46
|
routerEngine;
|
|
30
47
|
config;
|
|
@@ -37,6 +54,7 @@ export class HubPipeline {
|
|
|
37
54
|
quotaView: config.quotaView
|
|
38
55
|
});
|
|
39
56
|
this.routerEngine.initialize(config.virtualRouter);
|
|
57
|
+
setHubPolicyRuntimePolicy(config.policy);
|
|
40
58
|
try {
|
|
41
59
|
this.unsubscribeProviderErrors = providerErrorCenter.subscribe((event) => {
|
|
42
60
|
try {
|
|
@@ -97,13 +115,38 @@ export class HubPipeline {
|
|
|
97
115
|
const formatAdapter = hooks.createFormatAdapter();
|
|
98
116
|
const semanticMapper = hooks.createSemanticMapper();
|
|
99
117
|
const rawRequest = this.asJsonObject(normalized.payload);
|
|
118
|
+
// Preserve the client-provided raw tool definitions (schema included) for response-side validation
|
|
119
|
+
// and protocol-correct outbound tool-call formatting. This must reflect the inbound request shape,
|
|
120
|
+
// not the governed/augmented tools.
|
|
121
|
+
try {
|
|
122
|
+
const toolsRaw = Array.isArray(rawRequest?.tools) ? rawRequest.tools : null;
|
|
123
|
+
if (toolsRaw && toolsRaw.length > 0) {
|
|
124
|
+
normalized.metadata = normalized.metadata || {};
|
|
125
|
+
normalized.metadata.clientToolsRaw = jsonClone(toolsRaw);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
// best-effort: do not block request handling due to tool snapshot failures
|
|
130
|
+
}
|
|
100
131
|
if (isCompactionRequest(rawRequest)) {
|
|
101
132
|
normalized.metadata = normalized.metadata || {};
|
|
102
133
|
normalized.metadata.compactionRequest = true;
|
|
103
134
|
}
|
|
135
|
+
const effectivePolicy = normalized.policyOverride ?? this.config.policy;
|
|
104
136
|
const inboundAdapterContext = this.buildAdapterContext(normalized);
|
|
105
|
-
const inboundRecorder = this.maybeCreateStageRecorder(inboundAdapterContext, normalized.entryEndpoint
|
|
137
|
+
const inboundRecorder = this.maybeCreateStageRecorder(inboundAdapterContext, normalized.entryEndpoint, {
|
|
138
|
+
disableSnapshots: normalized.disableSnapshots === true
|
|
139
|
+
});
|
|
106
140
|
const inboundStart = Date.now();
|
|
141
|
+
// Phase 0: observe client inbound payload violations (best-effort; no rewrites).
|
|
142
|
+
recordHubPolicyObservation({
|
|
143
|
+
policy: effectivePolicy,
|
|
144
|
+
providerProtocol: this.resolveClientProtocol(normalized.entryEndpoint),
|
|
145
|
+
payload: rawRequest,
|
|
146
|
+
phase: 'client_inbound',
|
|
147
|
+
stageRecorder: inboundRecorder,
|
|
148
|
+
requestId: normalized.id
|
|
149
|
+
});
|
|
107
150
|
const formatEnvelope = await runReqInboundStage1FormatParse({
|
|
108
151
|
rawRequest,
|
|
109
152
|
adapterContext: inboundAdapterContext,
|
|
@@ -139,20 +182,26 @@ export class HubPipeline {
|
|
|
139
182
|
}
|
|
140
183
|
}
|
|
141
184
|
});
|
|
185
|
+
// 将 VirtualRouter 层的 servertool 相关配置注入到 metadata,保证响应侧
|
|
186
|
+
// servertool(第三跳 reenter)也能访问到相同配置,即使当前 route 标记为 passthrough。
|
|
187
|
+
const metaBase = {
|
|
188
|
+
...(normalized.metadata ?? {})
|
|
189
|
+
};
|
|
190
|
+
const webSearchConfig = this.config.virtualRouter?.webSearch;
|
|
191
|
+
if (webSearchConfig) {
|
|
192
|
+
metaBase.webSearch = webSearchConfig;
|
|
193
|
+
}
|
|
194
|
+
const execCommandGuard = this.config.virtualRouter?.execCommandGuard;
|
|
195
|
+
if (execCommandGuard) {
|
|
196
|
+
metaBase.execCommandGuard = execCommandGuard;
|
|
197
|
+
}
|
|
198
|
+
normalized.metadata = metaBase;
|
|
142
199
|
let processedRequest;
|
|
143
200
|
if (normalized.processMode !== 'passthrough') {
|
|
144
|
-
const processMetadata = {
|
|
145
|
-
...(normalized.metadata ?? {})
|
|
146
|
-
};
|
|
147
|
-
const webSearchConfig = this.config.virtualRouter?.webSearch;
|
|
148
|
-
if (webSearchConfig) {
|
|
149
|
-
processMetadata.webSearch = webSearchConfig;
|
|
150
|
-
}
|
|
151
|
-
normalized.metadata = processMetadata;
|
|
152
201
|
const processResult = await runReqProcessStage1ToolGovernance({
|
|
153
202
|
request: standardizedRequest,
|
|
154
203
|
rawPayload: rawRequest,
|
|
155
|
-
metadata:
|
|
204
|
+
metadata: metaBase,
|
|
156
205
|
entryEndpoint: normalized.entryEndpoint,
|
|
157
206
|
requestId: normalized.id,
|
|
158
207
|
stageRecorder: inboundRecorder
|
|
@@ -251,7 +300,9 @@ export class HubPipeline {
|
|
|
251
300
|
// Snapshots must be grouped by entry endpoint (client-facing protocol), not by provider protocol.
|
|
252
301
|
// Otherwise one request would be split across multiple folders (e.g. openai-responses + anthropic-messages),
|
|
253
302
|
// which breaks codex-samples correlation.
|
|
254
|
-
const outboundRecorder = this.maybeCreateStageRecorder(outboundAdapterContext, normalized.entryEndpoint
|
|
303
|
+
const outboundRecorder = this.maybeCreateStageRecorder(outboundAdapterContext, normalized.entryEndpoint, {
|
|
304
|
+
disableSnapshots: normalized.disableSnapshots === true
|
|
305
|
+
});
|
|
255
306
|
const outboundStart = Date.now();
|
|
256
307
|
let providerPayload;
|
|
257
308
|
const outboundStage1 = await runReqOutboundStage1SemanticMap({
|
|
@@ -273,7 +324,20 @@ export class HubPipeline {
|
|
|
273
324
|
adapterContext: outboundAdapterContext,
|
|
274
325
|
stageRecorder: outboundRecorder
|
|
275
326
|
});
|
|
276
|
-
providerPayload =
|
|
327
|
+
providerPayload = applyHubProviderOutboundPolicy({
|
|
328
|
+
policy: effectivePolicy,
|
|
329
|
+
providerProtocol: outboundProtocol,
|
|
330
|
+
payload: formattedPayload,
|
|
331
|
+
stageRecorder: outboundRecorder,
|
|
332
|
+
requestId: normalized.id
|
|
333
|
+
});
|
|
334
|
+
recordHubPolicyObservation({
|
|
335
|
+
policy: effectivePolicy,
|
|
336
|
+
providerProtocol: outboundProtocol,
|
|
337
|
+
payload: providerPayload,
|
|
338
|
+
stageRecorder: outboundRecorder,
|
|
339
|
+
requestId: normalized.id
|
|
340
|
+
});
|
|
277
341
|
const outboundEnd = Date.now();
|
|
278
342
|
nodeResults.push({
|
|
279
343
|
id: 'req_outbound',
|
|
@@ -305,11 +369,13 @@ export class HubPipeline {
|
|
|
305
369
|
// 对于 capturedChatRequest,我们只需要一个“可读快照”,不会在后续流程中
|
|
306
370
|
// 对其做就地修改,因此可以直接使用浅拷贝结构,避免序列化失败导致整段
|
|
307
371
|
// 逻辑失效。
|
|
372
|
+
// Deep-clone a JSON-safe snapshot for servertool followups.
|
|
373
|
+
// Only capture the canonical Chat payload fields (model/messages/tools/parameters) to keep it serializable.
|
|
308
374
|
const capturedChatRequest = {
|
|
309
375
|
model: workingRequest.model,
|
|
310
|
-
messages:
|
|
311
|
-
tools: workingRequest.tools,
|
|
312
|
-
parameters: workingRequest.parameters
|
|
376
|
+
messages: jsonClone(workingRequest.messages),
|
|
377
|
+
tools: workingRequest.tools ? jsonClone(workingRequest.tools) : workingRequest.tools,
|
|
378
|
+
parameters: workingRequest.parameters ? jsonClone(workingRequest.parameters) : workingRequest.parameters
|
|
313
379
|
};
|
|
314
380
|
const metadata = {
|
|
315
381
|
...normalized.metadata,
|
|
@@ -335,6 +401,14 @@ export class HubPipeline {
|
|
|
335
401
|
nodeResults
|
|
336
402
|
};
|
|
337
403
|
}
|
|
404
|
+
resolveClientProtocol(entryEndpoint) {
|
|
405
|
+
const lowered = String(entryEndpoint || '').toLowerCase();
|
|
406
|
+
if (lowered.includes('/v1/responses'))
|
|
407
|
+
return 'openai-responses';
|
|
408
|
+
if (lowered.includes('/v1/messages'))
|
|
409
|
+
return 'anthropic-messages';
|
|
410
|
+
return 'openai-chat';
|
|
411
|
+
}
|
|
338
412
|
async execute(request) {
|
|
339
413
|
const normalized = await this.normalizeRequest(request);
|
|
340
414
|
const hooks = this.resolveProtocolHooks(normalized.providerProtocol);
|
|
@@ -429,6 +503,10 @@ export class HubPipeline {
|
|
|
429
503
|
streamingHint,
|
|
430
504
|
toolCallIdStyle
|
|
431
505
|
};
|
|
506
|
+
const runtime = metadata.runtime;
|
|
507
|
+
if (runtime && typeof runtime === 'object' && !Array.isArray(runtime)) {
|
|
508
|
+
adapterContext.runtime = jsonClone(runtime);
|
|
509
|
+
}
|
|
432
510
|
const clientRequestId = typeof metadata.clientRequestId === 'string'
|
|
433
511
|
? metadata.clientRequestId.trim()
|
|
434
512
|
: '';
|
|
@@ -457,6 +535,11 @@ export class HubPipeline {
|
|
|
457
535
|
adapterContext.serverToolFollowup = metadata
|
|
458
536
|
.serverToolFollowup;
|
|
459
537
|
}
|
|
538
|
+
// Preserve raw client tools (schemas) for response-side validation/formatting.
|
|
539
|
+
if (Object.prototype.hasOwnProperty.call(metadata, 'clientToolsRaw')) {
|
|
540
|
+
adapterContext.clientToolsRaw = metadata
|
|
541
|
+
.clientToolsRaw;
|
|
542
|
+
}
|
|
460
543
|
const sessionId = typeof metadata.sessionId === 'string'
|
|
461
544
|
? metadata.sessionId.trim()
|
|
462
545
|
: '';
|
|
@@ -512,7 +595,10 @@ export class HubPipeline {
|
|
|
512
595
|
}
|
|
513
596
|
return adapterContext;
|
|
514
597
|
}
|
|
515
|
-
maybeCreateStageRecorder(context, endpoint) {
|
|
598
|
+
maybeCreateStageRecorder(context, endpoint, options) {
|
|
599
|
+
if (options?.disableSnapshots === true) {
|
|
600
|
+
return undefined;
|
|
601
|
+
}
|
|
516
602
|
if (!shouldRecordSnapshots()) {
|
|
517
603
|
return undefined;
|
|
518
604
|
}
|
|
@@ -539,6 +625,14 @@ export class HubPipeline {
|
|
|
539
625
|
const metadataRecord = {
|
|
540
626
|
...(request.metadata ?? {})
|
|
541
627
|
};
|
|
628
|
+
const policyOverride = extractHubPolicyOverride(metadataRecord);
|
|
629
|
+
if (Object.prototype.hasOwnProperty.call(metadataRecord, '__hubPolicyOverride')) {
|
|
630
|
+
delete metadataRecord.__hubPolicyOverride;
|
|
631
|
+
}
|
|
632
|
+
const disableSnapshots = metadataRecord.__disableHubSnapshots === true;
|
|
633
|
+
if (Object.prototype.hasOwnProperty.call(metadataRecord, '__disableHubSnapshots')) {
|
|
634
|
+
delete metadataRecord.__disableHubSnapshots;
|
|
635
|
+
}
|
|
542
636
|
const entryEndpoint = typeof metadataRecord.entryEndpoint === 'string'
|
|
543
637
|
? normalizeEndpoint(metadataRecord.entryEndpoint)
|
|
544
638
|
: endpoint;
|
|
@@ -576,6 +670,8 @@ export class HubPipeline {
|
|
|
576
670
|
providerProtocol,
|
|
577
671
|
payload,
|
|
578
672
|
metadata: normalizedMetadata,
|
|
673
|
+
policyOverride: policyOverride ?? undefined,
|
|
674
|
+
disableSnapshots,
|
|
579
675
|
processMode,
|
|
580
676
|
direction,
|
|
581
677
|
stage,
|
package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/index.js
CHANGED
|
@@ -312,9 +312,6 @@ function buildApplyPatchDiagnostics(output) {
|
|
|
312
312
|
}
|
|
313
313
|
function appendDiagnosticsToRecord(record) {
|
|
314
314
|
const name = typeof record.name === 'string' ? record.name.trim() : undefined;
|
|
315
|
-
if (name !== 'apply_patch') {
|
|
316
|
-
return;
|
|
317
|
-
}
|
|
318
315
|
let text;
|
|
319
316
|
if (typeof record.output === 'string') {
|
|
320
317
|
text = record.output;
|
|
@@ -329,6 +326,12 @@ function appendDiagnosticsToRecord(record) {
|
|
|
329
326
|
if (!diag) {
|
|
330
327
|
return;
|
|
331
328
|
}
|
|
329
|
+
// Some providers / compatibility layers omit `name` on tool outputs.
|
|
330
|
+
// When the output text matches apply_patch argument-parse failures, still inject the diagnostics
|
|
331
|
+
// to keep user-visible behavior stable across modes.
|
|
332
|
+
if (name && name !== 'apply_patch') {
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
332
335
|
const merged = `${text}${diag}`;
|
|
333
336
|
if (typeof record.output === 'string') {
|
|
334
337
|
record.output = merged;
|
package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage1_tool_governance/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { runHubChatProcess } from '../../../../process/chat-process.js';
|
|
2
2
|
import { recordStage } from '../../../stages/utils.js';
|
|
3
|
+
import { captureApplyPatchExecutionFailuresFromProcessedRequest } from '../../../../../../tools/apply-patch/execution-capturer.js';
|
|
3
4
|
export async function runReqProcessStage1ToolGovernance(options) {
|
|
4
5
|
const result = await runHubChatProcess({
|
|
5
6
|
request: options.request,
|
|
@@ -10,6 +11,9 @@ export async function runReqProcessStage1ToolGovernance(options) {
|
|
|
10
11
|
});
|
|
11
12
|
if (result.processedRequest) {
|
|
12
13
|
recordStage(options.stageRecorder, 'req_process_stage1_tool_governance', result.processedRequest);
|
|
14
|
+
// Best-effort: capture apply_patch execution failures reported by tool role messages.
|
|
15
|
+
// This is for errorsamples collection only and must not affect runtime behavior.
|
|
16
|
+
captureApplyPatchExecutionFailuresFromProcessedRequest(result.processedRequest);
|
|
13
17
|
}
|
|
14
18
|
return result;
|
|
15
19
|
}
|
package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.js
CHANGED
|
@@ -12,8 +12,10 @@ export function runRespOutboundStage1ClientRemap(options) {
|
|
|
12
12
|
});
|
|
13
13
|
}
|
|
14
14
|
else {
|
|
15
|
+
const toolsRaw = resolveClientToolsRawFromContext(options.adapterContext);
|
|
15
16
|
clientPayload = buildResponsesPayloadFromChat(options.payload, {
|
|
16
|
-
requestId: options.requestId
|
|
17
|
+
requestId: options.requestId,
|
|
18
|
+
...(toolsRaw ? { toolsRaw } : {})
|
|
17
19
|
});
|
|
18
20
|
}
|
|
19
21
|
recordStage(options.stageRecorder, 'resp_outbound_stage1_client_remap', clientPayload);
|
|
@@ -41,3 +43,23 @@ function resolveAliasMapFromContext(adapterContext) {
|
|
|
41
43
|
}
|
|
42
44
|
return Object.keys(map).length ? map : undefined;
|
|
43
45
|
}
|
|
46
|
+
function resolveClientToolsRawFromContext(adapterContext) {
|
|
47
|
+
if (!adapterContext) {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
const candidate = adapterContext.clientToolsRaw;
|
|
51
|
+
if (!candidate || !Array.isArray(candidate)) {
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
const filtered = [];
|
|
55
|
+
for (const entry of candidate) {
|
|
56
|
+
if (entry && typeof entry === 'object' && !Array.isArray(entry)) {
|
|
57
|
+
const type = entry.type;
|
|
58
|
+
if (typeof type !== 'string' || !type.trim()) {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
filtered.push(entry);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return filtered.length ? filtered : undefined;
|
|
65
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ChatEnvelope, AdapterContext } from '../types/chat-envelope.js';
|
|
2
|
+
import type { FormatAdapter, SemanticMapper, StageRecorder } from '../format-adapters/index.js';
|
|
3
|
+
import type { JsonObject } from '../types/json.js';
|
|
4
|
+
export type InboundStage = 'format_parse' | 'semantic_map_to_chat' | 'inbound_passthrough';
|
|
5
|
+
export interface InboundPassthroughConfig {
|
|
6
|
+
mode: 'chat';
|
|
7
|
+
factory: (raw: JsonObject, ctx: AdapterContext) => Promise<ChatEnvelope> | ChatEnvelope;
|
|
8
|
+
}
|
|
9
|
+
export interface InboundPlan {
|
|
10
|
+
protocol?: string;
|
|
11
|
+
stages: InboundStage[];
|
|
12
|
+
formatAdapter?: Pick<FormatAdapter, 'parseRequest'>;
|
|
13
|
+
semanticMapper?: Pick<SemanticMapper, 'toChat'>;
|
|
14
|
+
passthrough?: InboundPassthroughConfig;
|
|
15
|
+
}
|
|
16
|
+
export interface InboundPipelineOptions {
|
|
17
|
+
rawRequest: JsonObject;
|
|
18
|
+
context: AdapterContext;
|
|
19
|
+
plan: InboundPlan;
|
|
20
|
+
stageRecorder?: StageRecorder;
|
|
21
|
+
}
|
|
22
|
+
export declare function runInboundPipeline(options: InboundPipelineOptions): Promise<ChatEnvelope>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ChatEnvelope, AdapterContext } from '../types/chat-envelope.js';
|
|
2
|
+
import type { FormatAdapter, SemanticMapper, StageRecorder } from '../format-adapters/index.js';
|
|
3
|
+
import type { JsonObject } from '../types/json.js';
|
|
4
|
+
export type OutboundStage = 'semantic_map_from_chat' | 'format_build' | 'outbound_passthrough';
|
|
5
|
+
export interface OutboundPassthroughConfig {
|
|
6
|
+
mode: 'protocol';
|
|
7
|
+
factory: (chat: ChatEnvelope, ctx: AdapterContext) => Promise<JsonObject> | JsonObject;
|
|
8
|
+
}
|
|
9
|
+
export interface OutboundPlan {
|
|
10
|
+
protocol?: string;
|
|
11
|
+
stages: OutboundStage[];
|
|
12
|
+
formatAdapter?: Pick<FormatAdapter, 'buildResponse'>;
|
|
13
|
+
semanticMapper?: Pick<SemanticMapper, 'fromChat'>;
|
|
14
|
+
passthrough?: OutboundPassthroughConfig;
|
|
15
|
+
}
|
|
16
|
+
export interface OutboundPipelineOptions {
|
|
17
|
+
chat: ChatEnvelope;
|
|
18
|
+
context: AdapterContext;
|
|
19
|
+
plan: OutboundPlan;
|
|
20
|
+
stageRecorder?: StageRecorder;
|
|
21
|
+
}
|
|
22
|
+
export declare function runOutboundPipeline(options: OutboundPipelineOptions): Promise<JsonObject>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { JsonObject } from '../types/json.js';
|
|
2
|
+
import type { StageRecorder } from '../format-adapters/index.js';
|
|
3
|
+
export type HubPolicyMode = 'off' | 'observe' | 'enforce';
|
|
4
|
+
export interface HubPolicyConfig {
|
|
5
|
+
mode?: HubPolicyMode;
|
|
6
|
+
/**
|
|
7
|
+
* Optional: sampling rate in [0, 1]. When provided, observation is best-effort
|
|
8
|
+
* and may skip recording for some requests.
|
|
9
|
+
*/
|
|
10
|
+
sampleRate?: number;
|
|
11
|
+
}
|
|
12
|
+
export interface PolicyObservation {
|
|
13
|
+
phase: 'client_inbound' | 'provider_outbound' | 'provider_inbound' | 'client_outbound';
|
|
14
|
+
providerProtocol: string;
|
|
15
|
+
violations: Array<{
|
|
16
|
+
code: 'unexpected_wrapper' | 'unexpected_field';
|
|
17
|
+
path: string;
|
|
18
|
+
detail?: string;
|
|
19
|
+
}>;
|
|
20
|
+
summary: {
|
|
21
|
+
totalViolations: number;
|
|
22
|
+
unexpectedFieldCount: number;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export interface ProviderOutboundPolicyApplyResult {
|
|
26
|
+
payload: JsonObject;
|
|
27
|
+
changed: boolean;
|
|
28
|
+
removedTopLevelKeys: string[];
|
|
29
|
+
flattenedWrappers: string[];
|
|
30
|
+
}
|
|
31
|
+
export declare function setHubPolicyRuntimePolicy(policy?: HubPolicyConfig): void;
|
|
32
|
+
export declare function recordHubPolicyObservation(options: {
|
|
33
|
+
policy?: HubPolicyConfig;
|
|
34
|
+
phase?: PolicyObservation['phase'];
|
|
35
|
+
providerProtocol: string;
|
|
36
|
+
payload: JsonObject;
|
|
37
|
+
stageRecorder?: StageRecorder;
|
|
38
|
+
requestId?: string;
|
|
39
|
+
}): void;
|
|
40
|
+
export declare function applyHubProviderOutboundPolicy(options: {
|
|
41
|
+
policy?: HubPolicyConfig;
|
|
42
|
+
providerProtocol: string;
|
|
43
|
+
payload: JsonObject;
|
|
44
|
+
stageRecorder?: StageRecorder;
|
|
45
|
+
requestId?: string;
|
|
46
|
+
}): JsonObject;
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { resolveHubProtocolSpec } from './protocol-spec.js';
|
|
2
|
+
let hubPolicyRuntime = undefined;
|
|
3
|
+
export function setHubPolicyRuntimePolicy(policy) {
|
|
4
|
+
hubPolicyRuntime = policy;
|
|
5
|
+
}
|
|
6
|
+
function resolveEffectivePolicy(policy) {
|
|
7
|
+
return policy ?? hubPolicyRuntime;
|
|
8
|
+
}
|
|
9
|
+
function shouldSample(rate) {
|
|
10
|
+
if (rate === undefined)
|
|
11
|
+
return true;
|
|
12
|
+
if (!Number.isFinite(rate))
|
|
13
|
+
return true;
|
|
14
|
+
if (rate <= 0)
|
|
15
|
+
return false;
|
|
16
|
+
if (rate >= 1)
|
|
17
|
+
return true;
|
|
18
|
+
return Math.random() < rate;
|
|
19
|
+
}
|
|
20
|
+
function isJsonRecord(value) {
|
|
21
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
22
|
+
}
|
|
23
|
+
function applyProviderOutboundPolicy(providerProtocol, payload) {
|
|
24
|
+
const removedTopLevelKeys = [];
|
|
25
|
+
const flattenedWrappers = [];
|
|
26
|
+
const spec = resolveHubProtocolSpec(providerProtocol);
|
|
27
|
+
if (!spec.providerOutbound.enforceEnabled) {
|
|
28
|
+
return {
|
|
29
|
+
payload,
|
|
30
|
+
changed: false,
|
|
31
|
+
removedTopLevelKeys,
|
|
32
|
+
flattenedWrappers
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
let out = payload;
|
|
36
|
+
const ensureOutClone = () => {
|
|
37
|
+
if (out === payload) {
|
|
38
|
+
out = { ...payload };
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
// Reserved/private keys must never be sent upstream.
|
|
42
|
+
for (const key of Object.keys(payload)) {
|
|
43
|
+
if (spec.providerOutbound.reservedKeyPrefixes.some((prefix) => key.startsWith(prefix))) {
|
|
44
|
+
ensureOutClone();
|
|
45
|
+
delete out[key];
|
|
46
|
+
removedTopLevelKeys.push(key);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// Flatten accidental wrappers that have caused upstream 400s.
|
|
50
|
+
for (const rule of spec.providerOutbound.flattenWrappers) {
|
|
51
|
+
const wrapperKey = rule.wrapperKey;
|
|
52
|
+
if (!wrapperKey || typeof wrapperKey !== 'string') {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (!isJsonRecord(out[wrapperKey])) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
ensureOutClone();
|
|
59
|
+
const inner = { ...out[wrapperKey] };
|
|
60
|
+
const alias = rule.aliasKeys || {};
|
|
61
|
+
for (const [from, to] of Object.entries(alias)) {
|
|
62
|
+
if (inner[to] === undefined && inner[from] !== undefined) {
|
|
63
|
+
inner[to] = inner[from];
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const allow = Array.isArray(rule.allowKeys) ? new Set(rule.allowKeys) : null;
|
|
67
|
+
const onlyIfMissing = rule.onlyIfTargetMissing !== false;
|
|
68
|
+
for (const [key, value] of Object.entries(inner)) {
|
|
69
|
+
if (allow && !allow.has(key)) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (!onlyIfMissing || out[key] === undefined) {
|
|
73
|
+
out[key] = value;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
delete out[wrapperKey];
|
|
77
|
+
flattenedWrappers.push(wrapperKey);
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
payload: out,
|
|
81
|
+
changed: out !== payload || removedTopLevelKeys.length > 0 || flattenedWrappers.length > 0,
|
|
82
|
+
removedTopLevelKeys,
|
|
83
|
+
flattenedWrappers
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function observeProviderOutboundPayload(providerProtocol, payload) {
|
|
87
|
+
const violations = [];
|
|
88
|
+
// V0 (observe-only): detect known "layout anti-patterns" and reserved keys.
|
|
89
|
+
// Do NOT modify payload here.
|
|
90
|
+
const spec = resolveHubProtocolSpec(providerProtocol);
|
|
91
|
+
for (const rule of spec.providerOutbound.forbidWrappers) {
|
|
92
|
+
if (rule.code !== 'forbid_wrapper') {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (rule.path in payload && isJsonRecord(payload[rule.path])) {
|
|
96
|
+
violations.push({
|
|
97
|
+
code: 'unexpected_wrapper',
|
|
98
|
+
path: rule.path,
|
|
99
|
+
detail: rule.detail
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// Always record unknown private wrapper keys (best-effort, conservative).
|
|
104
|
+
for (const key of Object.keys(payload)) {
|
|
105
|
+
if (spec.providerOutbound.reservedKeyPrefixes.some((prefix) => key.startsWith(prefix))) {
|
|
106
|
+
violations.push({
|
|
107
|
+
code: 'unexpected_field',
|
|
108
|
+
path: key
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const unexpectedFieldCount = violations.filter((v) => v.code === 'unexpected_field').length;
|
|
113
|
+
return {
|
|
114
|
+
phase: 'provider_outbound',
|
|
115
|
+
providerProtocol,
|
|
116
|
+
violations,
|
|
117
|
+
summary: {
|
|
118
|
+
totalViolations: violations.length,
|
|
119
|
+
unexpectedFieldCount
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
export function recordHubPolicyObservation(options) {
|
|
124
|
+
if (!options.stageRecorder) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const effectivePolicy = resolveEffectivePolicy(options.policy);
|
|
128
|
+
const mode = effectivePolicy?.mode ?? 'off';
|
|
129
|
+
// Keep observing in enforce mode (best-effort) so operators can monitor
|
|
130
|
+
// violations while gradually turning enforcement on.
|
|
131
|
+
if (mode !== 'observe' && mode !== 'enforce') {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (!shouldSample(effectivePolicy?.sampleRate)) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
const phase = options.phase ?? 'provider_outbound';
|
|
139
|
+
const observation = observeProviderOutboundPayload(options.providerProtocol, options.payload);
|
|
140
|
+
observation.phase = phase;
|
|
141
|
+
if (observation.summary.totalViolations <= 0) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const stage = `hub_policy.observe.${phase}`;
|
|
145
|
+
options.stageRecorder.record(stage, {
|
|
146
|
+
requestId: options.requestId,
|
|
147
|
+
...observation
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
// observe-only must never break the pipeline
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
export function applyHubProviderOutboundPolicy(options) {
|
|
155
|
+
const effectivePolicy = resolveEffectivePolicy(options.policy);
|
|
156
|
+
const mode = effectivePolicy?.mode ?? 'off';
|
|
157
|
+
if (mode !== 'enforce') {
|
|
158
|
+
return options.payload;
|
|
159
|
+
}
|
|
160
|
+
const result = applyProviderOutboundPolicy(options.providerProtocol, options.payload);
|
|
161
|
+
if (!result.changed) {
|
|
162
|
+
return options.payload;
|
|
163
|
+
}
|
|
164
|
+
try {
|
|
165
|
+
options.stageRecorder?.record('hub_policy.enforce.provider_outbound', {
|
|
166
|
+
requestId: options.requestId,
|
|
167
|
+
providerProtocol: options.providerProtocol,
|
|
168
|
+
removedTopLevelKeys: result.removedTopLevelKeys,
|
|
169
|
+
flattenedWrappers: result.flattenedWrappers
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
// policy enforcement recording must not break the pipeline
|
|
174
|
+
}
|
|
175
|
+
return result.payload;
|
|
176
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export type HubProviderProtocol = 'openai-chat' | 'openai-responses' | 'anthropic-messages' | 'gemini-chat';
|
|
2
|
+
export interface ProviderOutboundLayoutRule {
|
|
3
|
+
code: 'forbid_wrapper';
|
|
4
|
+
path: string;
|
|
5
|
+
detail: string;
|
|
6
|
+
}
|
|
7
|
+
export interface ProviderOutboundWrapperFlattenRule {
|
|
8
|
+
wrapperKey: string;
|
|
9
|
+
/**
|
|
10
|
+
* When provided, only these keys from the wrapper are merged into top-level.
|
|
11
|
+
* When omitted, all keys are eligible.
|
|
12
|
+
*/
|
|
13
|
+
allowKeys?: string[];
|
|
14
|
+
/**
|
|
15
|
+
* Key remap inside wrapper prior to merging (e.g. max_tokens -> max_output_tokens).
|
|
16
|
+
*/
|
|
17
|
+
aliasKeys?: Record<string, string>;
|
|
18
|
+
/**
|
|
19
|
+
* Merge strategy: only write to target when top-level key is undefined.
|
|
20
|
+
*/
|
|
21
|
+
onlyIfTargetMissing?: boolean;
|
|
22
|
+
}
|
|
23
|
+
export interface ProviderOutboundPolicySpec {
|
|
24
|
+
/**
|
|
25
|
+
* Whether provider outbound enforcement is enabled for this protocol.
|
|
26
|
+
* Keep this false for protocols not yet migrated, to avoid behavior changes.
|
|
27
|
+
*/
|
|
28
|
+
enforceEnabled: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Reserved/private key prefixes that must not be sent upstream.
|
|
31
|
+
* (Enforced only when enforceEnabled=true.)
|
|
32
|
+
*/
|
|
33
|
+
reservedKeyPrefixes: string[];
|
|
34
|
+
/**
|
|
35
|
+
* Wrappers that should not exist in provider outbound payload.
|
|
36
|
+
* observe: treat as violations; enforce: flatten/remove when configured.
|
|
37
|
+
*/
|
|
38
|
+
forbidWrappers: ProviderOutboundLayoutRule[];
|
|
39
|
+
/**
|
|
40
|
+
* Best-effort fix for known wrapper anti-patterns by flattening into top-level.
|
|
41
|
+
* (Applied only when enforceEnabled=true.)
|
|
42
|
+
*/
|
|
43
|
+
flattenWrappers: ProviderOutboundWrapperFlattenRule[];
|
|
44
|
+
}
|
|
45
|
+
export interface ProtocolSpec {
|
|
46
|
+
id: HubProviderProtocol;
|
|
47
|
+
providerOutbound: ProviderOutboundPolicySpec;
|
|
48
|
+
}
|
|
49
|
+
export declare const HUB_PROTOCOL_SPECS: Record<HubProviderProtocol, ProtocolSpec>;
|
|
50
|
+
export declare function resolveHubProtocolSpec(protocol: string): ProtocolSpec;
|