@jsonstudio/llms 0.6.3551 → 0.6.3686
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/conversion/compat/actions/antigravity-thought-signature-cache.js +4 -115
- package/dist/conversion/compat/actions/auto-thinking.js +3 -2
- package/dist/conversion/compat/actions/deepseek-web-response.js +15 -49
- package/dist/conversion/compat/actions/gemini-cli-request.d.ts +2 -0
- package/dist/conversion/compat/actions/gemini-cli-request.js +1 -1
- package/dist/conversion/compat/actions/glm-history-image-trim.js +3 -37
- package/dist/conversion/compat/actions/glm-image-content.js +3 -32
- package/dist/conversion/compat/actions/glm-native-compat.d.ts +6 -0
- package/dist/conversion/compat/actions/glm-native-compat.js +34 -0
- package/dist/conversion/compat/actions/glm-vision-prompt.js +3 -76
- package/dist/conversion/compat/actions/glm-web-search.js +10 -43
- package/dist/conversion/compat/actions/iflow-kimi-cli-defaults.js +4 -53
- package/dist/conversion/compat/actions/iflow-kimi-history-media-placeholder.js +5 -141
- package/dist/conversion/compat/actions/iflow-kimi-thinking-reasoning-fill.js +7 -28
- package/dist/conversion/compat/actions/iflow-native-compat.d.ts +6 -0
- package/dist/conversion/compat/actions/iflow-native-compat.js +36 -0
- package/dist/conversion/compat/actions/iflow-response-body-unwrap.js +4 -119
- package/dist/conversion/compat/actions/iflow-web-search.js +14 -55
- package/dist/conversion/hub/operation-table/semantic-mappers/archive/chat-mapper.archive.js +5 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/responses-mapper.js +31 -18
- package/dist/conversion/hub/pipeline/hub-pipeline.js +163 -163
- package/dist/conversion/hub/pipeline/hub-stage-timing.d.ts +6 -0
- package/dist/conversion/hub/pipeline/hub-stage-timing.js +178 -0
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage1_format_parse/index.d.ts +4 -4
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage1_format_parse/index.js +33 -14
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.d.ts +7 -6
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.js +41 -23
- package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/index.js +44 -1
- package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage1_tool_governance/index.js +4 -1
- package/dist/conversion/hub/process/chat-process-continue-execution.js +5 -4
- package/dist/conversion/hub/process/chat-process-governance-orchestration.js +3 -1
- package/dist/conversion/hub/process/chat-process-media.d.ts +3 -1
- package/dist/conversion/hub/process/chat-process-media.js +92 -2
- package/dist/conversion/hub/process/chat-process-session-usage.d.ts +7 -0
- package/dist/conversion/hub/process/chat-process-session-usage.js +147 -0
- package/dist/conversion/hub/response/provider-response.js +13 -0
- package/dist/conversion/responses/responses-openai-bridge/response-payload.js +0 -12
- package/dist/conversion/responses/responses-openai-bridge/types.d.ts +1 -9
- package/dist/conversion/responses/responses-openai-bridge.js +77 -44
- package/dist/conversion/shared/reasoning-normalizer.js +42 -0
- package/dist/conversion/shared/responses-tool-utils.js +2 -3
- package/dist/native/router_hotpath_napi.node +0 -0
- package/dist/router/virtual-router/bootstrap/profile-builder.js +1 -0
- package/dist/router/virtual-router/bootstrap/provider-normalization.d.ts +1 -0
- package/dist/router/virtual-router/bootstrap/provider-normalization.js +6 -0
- package/dist/router/virtual-router/bootstrap.js +1 -6
- package/dist/router/virtual-router/engine-legacy.js +43 -0
- package/dist/router/virtual-router/engine-logging.d.ts +3 -0
- package/dist/router/virtual-router/engine-logging.js +29 -3
- package/dist/router/virtual-router/engine-selection/native-hub-pipeline-edge-stage-semantics.d.ts +2 -2
- package/dist/router/virtual-router/engine-selection/native-hub-pipeline-edge-stage-semantics.js +96 -80
- package/dist/router/virtual-router/engine-selection/native-hub-pipeline-req-process-semantics.d.ts +1 -0
- package/dist/router/virtual-router/engine.js +34 -22
- package/dist/router/virtual-router/provider-registry.js +1 -0
- package/dist/router/virtual-router/routing-instructions/state.js +35 -3
- package/dist/router/virtual-router/routing-instructions/types.d.ts +4 -0
- package/dist/router/virtual-router/types.d.ts +7 -0
- package/dist/servertool/engine.js +3 -34
- package/dist/servertool/handlers/followup-request-builder.js +0 -6
- package/dist/servertool/handlers/gemini-empty-reply-continue.js +3 -274
- package/dist/servertool/handlers/stop-message-auto/runtime-utils.d.ts +0 -3
- package/dist/servertool/handlers/stop-message-auto/runtime-utils.js +0 -29
- package/dist/servertool/handlers/stop-message-auto.js +2 -10
- package/dist/servertool/handlers/vision.js +4 -1
- package/dist/servertool/server-side-tools.js +66 -3
- package/package.json +1 -1
|
@@ -1,18 +1,108 @@
|
|
|
1
|
-
import { analyzeChatProcessMedia, stripChatProcessHistoricalImages } from
|
|
1
|
+
import { analyzeChatProcessMedia, stripChatProcessHistoricalImages, } from "../../../router/virtual-router/engine-selection/native-router-hotpath.js";
|
|
2
2
|
export function stripHistoricalImageAttachments(messages) {
|
|
3
3
|
if (!Array.isArray(messages) || !messages.length) {
|
|
4
4
|
return messages;
|
|
5
5
|
}
|
|
6
|
-
const placeholderText =
|
|
6
|
+
const placeholderText = "[Image omitted]";
|
|
7
7
|
const stripped = stripChatProcessHistoricalImages(messages, placeholderText);
|
|
8
8
|
if (stripped.changed !== true || !Array.isArray(stripped.messages)) {
|
|
9
9
|
return messages;
|
|
10
10
|
}
|
|
11
11
|
return stripped.messages;
|
|
12
12
|
}
|
|
13
|
+
const INLINE_MEDIA_DATA_RE = /data:(image|video)\/[a-z0-9.+-]+;base64,[a-z0-9+/=\s]+/i;
|
|
14
|
+
function isVisualToolMessage(message) {
|
|
15
|
+
if (!message || typeof message !== "object") {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
if (message.role !== "tool") {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
const name = typeof message.name === "string" ? message.name.trim().toLowerCase() : "";
|
|
22
|
+
if (name === "view_image") {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
const content = typeof message.content === "string" ? message.content : "";
|
|
26
|
+
return INLINE_MEDIA_DATA_RE.test(content);
|
|
27
|
+
}
|
|
28
|
+
export function stripHistoricalVisualToolOutputs(messages) {
|
|
29
|
+
if (!Array.isArray(messages) || messages.length === 0) {
|
|
30
|
+
return messages;
|
|
31
|
+
}
|
|
32
|
+
let changed = false;
|
|
33
|
+
const next = messages.map((message) => {
|
|
34
|
+
if (!isVisualToolMessage(message)) {
|
|
35
|
+
return message;
|
|
36
|
+
}
|
|
37
|
+
const content = typeof message.content === "string" ? message.content : "";
|
|
38
|
+
if (!INLINE_MEDIA_DATA_RE.test(content)) {
|
|
39
|
+
return message;
|
|
40
|
+
}
|
|
41
|
+
changed = true;
|
|
42
|
+
return {
|
|
43
|
+
...message,
|
|
44
|
+
content: "[Image omitted]",
|
|
45
|
+
};
|
|
46
|
+
});
|
|
47
|
+
return changed ? next : messages;
|
|
48
|
+
}
|
|
13
49
|
export function containsImageAttachment(messages) {
|
|
14
50
|
if (!Array.isArray(messages) || !messages.length) {
|
|
15
51
|
return false;
|
|
16
52
|
}
|
|
17
53
|
return analyzeChatProcessMedia(messages).containsCurrentTurnImage === true;
|
|
18
54
|
}
|
|
55
|
+
export function repairIncompleteToolCalls(messages) {
|
|
56
|
+
if (!Array.isArray(messages) || messages.length === 0) {
|
|
57
|
+
return messages;
|
|
58
|
+
}
|
|
59
|
+
const toolCallIdsWithResponse = new Set();
|
|
60
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
61
|
+
const msg = messages[i];
|
|
62
|
+
if (msg?.role === "tool" && typeof msg.tool_call_id === "string") {
|
|
63
|
+
toolCallIdsWithResponse.add(msg.tool_call_id);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const result = [];
|
|
67
|
+
let changed = false;
|
|
68
|
+
for (const msg of messages) {
|
|
69
|
+
if (msg?.role === "assistant" && Array.isArray(msg.tool_calls)) {
|
|
70
|
+
const completeToolCalls = [];
|
|
71
|
+
const missingToolCallIds = [];
|
|
72
|
+
for (const tc of msg.tool_calls) {
|
|
73
|
+
const tcId = typeof tc?.id === "string" ? tc.id : "";
|
|
74
|
+
if (tcId && toolCallIdsWithResponse.has(tcId)) {
|
|
75
|
+
completeToolCalls.push(tc);
|
|
76
|
+
}
|
|
77
|
+
else if (tcId) {
|
|
78
|
+
missingToolCallIds.push(tcId);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (missingToolCallIds.length > 0) {
|
|
82
|
+
changed = true;
|
|
83
|
+
const repaired = {
|
|
84
|
+
...msg,
|
|
85
|
+
tool_calls: completeToolCalls.length > 0 ? completeToolCalls : undefined,
|
|
86
|
+
};
|
|
87
|
+
if (!repaired.tool_calls) {
|
|
88
|
+
delete repaired.tool_calls;
|
|
89
|
+
}
|
|
90
|
+
result.push(repaired);
|
|
91
|
+
for (const missingId of missingToolCallIds) {
|
|
92
|
+
result.push({
|
|
93
|
+
role: "tool",
|
|
94
|
+
tool_call_id: missingId,
|
|
95
|
+
content: '{"status":"tool_call_repaired_orphaned_tool_call"}',
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
result.push(msg);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
result.push(msg);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return changed ? result : messages;
|
|
108
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { AdapterContext } from '../types/chat-envelope.js';
|
|
2
|
+
import type { ProcessedRequest, StandardizedRequest } from '../types/standardized.js';
|
|
3
|
+
export declare function estimateSessionBoundTokens(request: StandardizedRequest | ProcessedRequest, metadata: Record<string, unknown> | undefined): number | undefined;
|
|
4
|
+
export declare function saveChatProcessSessionActualUsage(options: {
|
|
5
|
+
context: AdapterContext;
|
|
6
|
+
usage: Record<string, unknown> | undefined;
|
|
7
|
+
}): void;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { loadRoutingInstructionStateSync, saveRoutingInstructionStateSync } from '../../../router/virtual-router/sticky-session-store.js';
|
|
2
|
+
import { countRequestTokens } from '../../../router/virtual-router/token-counter.js';
|
|
3
|
+
function createEmptyRoutingInstructionState() {
|
|
4
|
+
return {
|
|
5
|
+
allowedProviders: new Set(),
|
|
6
|
+
disabledProviders: new Set(),
|
|
7
|
+
disabledKeys: new Map(),
|
|
8
|
+
disabledModels: new Map()
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
function resolveSessionUsageScope(record) {
|
|
12
|
+
const sessionId = typeof record?.sessionId === 'string' ? record.sessionId.trim() : '';
|
|
13
|
+
if (sessionId) {
|
|
14
|
+
return `session:${sessionId}`;
|
|
15
|
+
}
|
|
16
|
+
const conversationId = typeof record?.conversationId === 'string' ? record.conversationId.trim() : '';
|
|
17
|
+
if (conversationId) {
|
|
18
|
+
return `conversation:${conversationId}`;
|
|
19
|
+
}
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
function loadState(scope) {
|
|
23
|
+
try {
|
|
24
|
+
return loadRoutingInstructionStateSync(scope);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function readRoundedToken(value) {
|
|
31
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
const rounded = Math.round(value);
|
|
35
|
+
return rounded > 0 ? rounded : undefined;
|
|
36
|
+
}
|
|
37
|
+
function buildSnapshot(scope, state) {
|
|
38
|
+
if (!state) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
const totalTokens = readRoundedToken(state.chatProcessLastTotalTokens);
|
|
42
|
+
const inputTokens = readRoundedToken(state.chatProcessLastInputTokens);
|
|
43
|
+
const messageCount = readRoundedToken(state.chatProcessLastMessageCount);
|
|
44
|
+
const updatedAtMs = readRoundedToken(state.chatProcessLastUpdatedAt);
|
|
45
|
+
if (totalTokens === undefined && inputTokens === undefined) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
scope,
|
|
50
|
+
...(totalTokens !== undefined ? { totalTokens } : {}),
|
|
51
|
+
...(inputTokens !== undefined ? { inputTokens } : {}),
|
|
52
|
+
...(messageCount !== undefined ? { messageCount } : {}),
|
|
53
|
+
...(updatedAtMs !== undefined ? { updatedAtMs } : {})
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function normalizeUsage(usage) {
|
|
57
|
+
if (!usage || typeof usage !== 'object') {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
const inputTokens = readRoundedToken(usage.input_tokens ??
|
|
61
|
+
usage.prompt_tokens ??
|
|
62
|
+
usage.inputTokens ??
|
|
63
|
+
usage.promptTokens ??
|
|
64
|
+
usage.request_tokens ??
|
|
65
|
+
usage.requestTokens);
|
|
66
|
+
const outputTokens = readRoundedToken(usage.output_tokens ??
|
|
67
|
+
usage.completion_tokens ??
|
|
68
|
+
usage.outputTokens ??
|
|
69
|
+
usage.completionTokens ??
|
|
70
|
+
usage.response_tokens ??
|
|
71
|
+
usage.responseTokens);
|
|
72
|
+
const totalTokens = readRoundedToken(usage.total_tokens ??
|
|
73
|
+
usage.totalTokens ??
|
|
74
|
+
((inputTokens ?? 0) + (outputTokens ?? 0) > 0
|
|
75
|
+
? (inputTokens ?? 0) + (outputTokens ?? 0)
|
|
76
|
+
: undefined));
|
|
77
|
+
if (totalTokens === undefined && inputTokens === undefined) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
...(inputTokens !== undefined ? { inputTokens } : {}),
|
|
82
|
+
...(outputTokens !== undefined ? { outputTokens } : {}),
|
|
83
|
+
...(totalTokens !== undefined ? { totalTokens } : {})
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function estimateDeltaTokens(request, previousMessageCount) {
|
|
87
|
+
const messages = Array.isArray(request.messages) ? request.messages : [];
|
|
88
|
+
if (previousMessageCount < 0 || previousMessageCount > messages.length) {
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
const appendedMessages = messages.slice(previousMessageCount);
|
|
92
|
+
if (appendedMessages.length === 0) {
|
|
93
|
+
return 0;
|
|
94
|
+
}
|
|
95
|
+
return countRequestTokens({
|
|
96
|
+
model: request.model,
|
|
97
|
+
messages: appendedMessages,
|
|
98
|
+
parameters: {},
|
|
99
|
+
metadata: { originalEndpoint: request.metadata?.originalEndpoint ?? '/v1/chat/completions' }
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
export function estimateSessionBoundTokens(request, metadata) {
|
|
103
|
+
const scope = resolveSessionUsageScope(metadata);
|
|
104
|
+
if (!scope) {
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
const snapshot = buildSnapshot(scope, loadState(scope));
|
|
108
|
+
if (!snapshot) {
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
const previousTotal = snapshot.totalTokens ?? snapshot.inputTokens;
|
|
112
|
+
const previousMessageCount = snapshot.messageCount;
|
|
113
|
+
if (previousTotal === undefined || previousMessageCount === undefined) {
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
const deltaTokens = estimateDeltaTokens(request, previousMessageCount);
|
|
117
|
+
if (deltaTokens === undefined) {
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
return Math.max(1, Math.round(previousTotal + deltaTokens));
|
|
121
|
+
}
|
|
122
|
+
export function saveChatProcessSessionActualUsage(options) {
|
|
123
|
+
const scope = resolveSessionUsageScope(options.context);
|
|
124
|
+
if (!scope) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const normalizedUsage = normalizeUsage(options.usage);
|
|
128
|
+
if (!normalizedUsage) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const capturedChatRequest = options.context.capturedChatRequest;
|
|
132
|
+
const messageCount = Array.isArray(capturedChatRequest?.messages)
|
|
133
|
+
? (capturedChatRequest.messages ?? []).length
|
|
134
|
+
: undefined;
|
|
135
|
+
const state = loadState(scope) ?? createEmptyRoutingInstructionState();
|
|
136
|
+
if (normalizedUsage.totalTokens !== undefined) {
|
|
137
|
+
state.chatProcessLastTotalTokens = normalizedUsage.totalTokens;
|
|
138
|
+
}
|
|
139
|
+
if (normalizedUsage.inputTokens !== undefined) {
|
|
140
|
+
state.chatProcessLastInputTokens = normalizedUsage.inputTokens;
|
|
141
|
+
}
|
|
142
|
+
if (typeof messageCount === 'number' && Number.isFinite(messageCount)) {
|
|
143
|
+
state.chatProcessLastMessageCount = Math.max(0, Math.round(messageCount));
|
|
144
|
+
}
|
|
145
|
+
state.chatProcessLastUpdatedAt = Date.now();
|
|
146
|
+
saveRoutingInstructionStateSync(scope, state);
|
|
147
|
+
}
|
|
@@ -19,6 +19,7 @@ import { ProviderProtocolError } from '../../provider-protocol-error.js';
|
|
|
19
19
|
import { readRuntimeMetadata } from '../../runtime-metadata.js';
|
|
20
20
|
import { commitClockReservation, resolveClockConfig } from '../../../servertool/clock/task-store.js';
|
|
21
21
|
import { detectProviderResponseShapeWithNative } from '../../../router/virtual-router/engine-selection/native-chat-process-servertool-orchestration-semantics.js';
|
|
22
|
+
import { saveChatProcessSessionActualUsage } from '../process/chat-process-session-usage.js';
|
|
22
23
|
const PROVIDER_RESPONSE_REGISTRY = {
|
|
23
24
|
'openai-chat': {
|
|
24
25
|
createFormatAdapter: () => new ChatFormatAdapter(),
|
|
@@ -451,6 +452,18 @@ export async function convertProviderResponse(options) {
|
|
|
451
452
|
});
|
|
452
453
|
// Commit scheduled-task delivery only after a successful client payload/stream is prepared.
|
|
453
454
|
await maybeCommitClockReservationFromContext(options.context);
|
|
455
|
+
try {
|
|
456
|
+
const usage = clientPayload && typeof clientPayload === 'object' && !Array.isArray(clientPayload)
|
|
457
|
+
? clientPayload.usage
|
|
458
|
+
: undefined;
|
|
459
|
+
saveChatProcessSessionActualUsage({
|
|
460
|
+
context: options.context,
|
|
461
|
+
usage
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
catch {
|
|
465
|
+
// best-effort: usage persistence must not break response delivery
|
|
466
|
+
}
|
|
454
467
|
if (outbound.stream) {
|
|
455
468
|
const usage = clientPayload && typeof clientPayload === 'object' && !Array.isArray(clientPayload)
|
|
456
469
|
? clientPayload.usage
|
|
@@ -55,10 +55,6 @@ function collectRetentionContext(context) {
|
|
|
55
55
|
const stripHostManagedFields = shouldStripHostManagedFields(context);
|
|
56
56
|
return {
|
|
57
57
|
metadata: context?.metadata,
|
|
58
|
-
parallelToolCalls: context?.parallel_tool_calls,
|
|
59
|
-
toolChoice: context?.tool_choice,
|
|
60
|
-
include: context?.include,
|
|
61
|
-
store: context?.store,
|
|
62
58
|
stripHostManagedFields
|
|
63
59
|
};
|
|
64
60
|
}
|
|
@@ -103,10 +99,6 @@ export function buildResponsesPayloadFromChat(payload, context) {
|
|
|
103
99
|
requestId: context?.requestId,
|
|
104
100
|
toolsRaw: Array.isArray(context?.toolsRaw) ? context?.toolsRaw : [],
|
|
105
101
|
metadata: retentionContext.metadata,
|
|
106
|
-
parallelToolCalls: retentionContext.parallelToolCalls,
|
|
107
|
-
toolChoice: retentionContext.toolChoice,
|
|
108
|
-
include: retentionContext.include,
|
|
109
|
-
store: retentionContext.store,
|
|
110
102
|
stripHostManagedFields: retentionContext.stripHostManagedFields,
|
|
111
103
|
sourceForRetention: sourceForRetention
|
|
112
104
|
});
|
|
@@ -176,10 +168,6 @@ export function buildResponsesPayloadFromChat(payload, context) {
|
|
|
176
168
|
requestId: context?.requestId,
|
|
177
169
|
toolsRaw: Array.isArray(context?.toolsRaw) ? context?.toolsRaw : [],
|
|
178
170
|
metadata: retentionContext.metadata,
|
|
179
|
-
parallelToolCalls: retentionContext.parallelToolCalls,
|
|
180
|
-
toolChoice: retentionContext.toolChoice,
|
|
181
|
-
include: retentionContext.include,
|
|
182
|
-
store: retentionContext.store,
|
|
183
171
|
stripHostManagedFields: retentionContext.stripHostManagedFields,
|
|
184
172
|
sourceForRetention: sourceForRetention
|
|
185
173
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { BridgeInputItem, BridgeToolDefinition } from '../../types/bridge-message-types.js';
|
|
2
2
|
import type { ChatToolDefinition } from '../../hub/types/chat-envelope.js';
|
|
3
|
-
import type { JsonObject
|
|
3
|
+
import type { JsonObject } from '../../hub/types/json.js';
|
|
4
4
|
import type { ToolCallIdStyle } from '../../shared/responses-tool-utils.js';
|
|
5
5
|
export type Unknown = Record<string, unknown>;
|
|
6
6
|
export interface ResponsesRequestContext extends Unknown {
|
|
@@ -8,15 +8,7 @@ export interface ResponsesRequestContext extends Unknown {
|
|
|
8
8
|
targetProtocol?: string;
|
|
9
9
|
originalSystemMessages?: string[];
|
|
10
10
|
input?: BridgeInputItem[];
|
|
11
|
-
include?: unknown;
|
|
12
|
-
store?: unknown;
|
|
13
|
-
serviceTier?: unknown;
|
|
14
|
-
truncation?: unknown;
|
|
15
|
-
toolChoice?: unknown;
|
|
16
|
-
parallelToolCalls?: boolean;
|
|
17
11
|
metadata?: JsonObject;
|
|
18
|
-
responseFormat?: JsonValue;
|
|
19
|
-
stream?: boolean;
|
|
20
12
|
isChatPayload?: boolean;
|
|
21
13
|
isResponsesPayload?: boolean;
|
|
22
14
|
historyMessages?: Array<{
|
|
@@ -4,10 +4,12 @@ import { convertBridgeInputToChatMessages } from '../bridge-message-utils.js';
|
|
|
4
4
|
import { createToolCallIdTransformer, enforceToolCallIdStyle, sanitizeResponsesFunctionName } from '../shared/responses-tool-utils.js';
|
|
5
5
|
import { mapChatToolsToBridge } from '../shared/tool-mapping.js';
|
|
6
6
|
import { ProviderProtocolError } from '../provider-protocol-error.js';
|
|
7
|
+
import { isJsonObject, jsonClone } from '../hub/types/json.js';
|
|
7
8
|
import { captureReqInboundResponsesContextSnapshotWithNative, mapReqInboundBridgeToolsToChatWithNative } from '../../router/virtual-router/engine-selection/native-hub-pipeline-req-inbound-semantics.js';
|
|
8
9
|
import { appendLocalImageBlockOnLatestUserInputWithNative, buildBridgeHistoryWithNative, filterBridgeInputForUpstreamWithNative, normalizeBridgeHistorySeedWithNative, prepareResponsesRequestEnvelopeWithNative, resolveResponsesRequestBridgeDecisionsWithNative, resolveResponsesBridgeToolsWithNative, runBridgeActionPipelineWithNative } from '../../router/virtual-router/engine-selection/native-hub-bridge-action-semantics.js';
|
|
9
10
|
// --- Utilities (ported strictly) ---
|
|
10
11
|
import { resolveBridgePolicy, resolvePolicyActions } from '../bridge-policies.js';
|
|
12
|
+
import { logHubStageTiming } from '../hub/pipeline/hub-stage-timing.js';
|
|
11
13
|
function isObject(v) {
|
|
12
14
|
return !!v && typeof v === 'object' && !Array.isArray(v);
|
|
13
15
|
}
|
|
@@ -37,18 +39,22 @@ function runNativeResponsesBridgePipeline(input) {
|
|
|
37
39
|
: undefined
|
|
38
40
|
};
|
|
39
41
|
}
|
|
42
|
+
function filterRedundantResponsesReasoningAction(actions) {
|
|
43
|
+
return actions?.filter((action) => {
|
|
44
|
+
const name = typeof action?.name === 'string' ? action.name.trim().toLowerCase() : '';
|
|
45
|
+
return name !== 'reasoning.extract';
|
|
46
|
+
});
|
|
47
|
+
}
|
|
40
48
|
// normalizeTools unified in ../args-mapping.ts
|
|
41
49
|
// NOTE: 自修复提示已移除(统一标准:不做模糊兜底)。
|
|
42
50
|
// --- Public bridge functions ---
|
|
43
51
|
export function captureResponsesContext(payload, dto) {
|
|
44
|
-
const preservedInput =
|
|
52
|
+
const preservedInput = Array.isArray(payload.input)
|
|
53
|
+
? payload.input
|
|
54
|
+
: undefined;
|
|
45
55
|
ensureBridgeInstructions(payload);
|
|
46
|
-
const requestForCapture = {
|
|
47
|
-
...payload,
|
|
48
|
-
...(preservedInput ? { input: preservedInput } : {})
|
|
49
|
-
};
|
|
50
56
|
const captured = captureReqInboundResponsesContextSnapshotWithNative({
|
|
51
|
-
rawRequest:
|
|
57
|
+
rawRequest: payload,
|
|
52
58
|
requestId: dto?.route?.requestId,
|
|
53
59
|
toolCallIdStyle: payload?.toolCallIdStyle ?? payload?.metadata?.toolCallIdStyle
|
|
54
60
|
});
|
|
@@ -68,26 +74,42 @@ export function captureResponsesContext(payload, dto) {
|
|
|
68
74
|
if (!captured.systemInstruction && typeof payload.instructions === 'string' && payload.instructions.trim().length) {
|
|
69
75
|
captured.systemInstruction = payload.instructions;
|
|
70
76
|
}
|
|
77
|
+
if (captured.metadata && isJsonObject(captured.metadata)) {
|
|
78
|
+
const cloned = jsonClone(captured.metadata);
|
|
79
|
+
delete cloned.extraFields;
|
|
80
|
+
captured.metadata = cloned;
|
|
81
|
+
}
|
|
71
82
|
return captured;
|
|
72
83
|
}
|
|
73
84
|
export function buildChatRequestFromResponses(payload, context) {
|
|
85
|
+
const requestId = typeof context.requestId === 'string' && context.requestId.trim().length
|
|
86
|
+
? context.requestId
|
|
87
|
+
: 'unknown';
|
|
74
88
|
// V3: 对 Responses 路径仅做“形状转换”,不做参数解析/修复。
|
|
75
89
|
// 将顶层 { type,name,description,parameters,strict } 归一为 OpenAI Chat tools 形状:
|
|
76
90
|
// { type:'function', function:{ name,description,parameters,strict? } }
|
|
77
91
|
const toolsNormalized = Array.isArray(context.toolsNormalized) && context.toolsNormalized.length
|
|
78
|
-
?
|
|
92
|
+
? context.toolsNormalized
|
|
79
93
|
: mapReqInboundBridgeToolsToChatWithNative(payload.tools);
|
|
80
94
|
// 不在 Responses 路径进行 MCP 工具注入;统一由 Chat 后半段治理注入
|
|
95
|
+
logHubStageTiming(requestId, 'req_inbound.responses.convert_input_to_messages', 'start');
|
|
96
|
+
const convertStart = Date.now();
|
|
81
97
|
let messages = convertBridgeInputToChatMessages({
|
|
82
98
|
input: context.input,
|
|
83
99
|
tools: toolsNormalized,
|
|
84
100
|
normalizeFunctionName: 'responses',
|
|
85
101
|
toolResultFallbackText: 'Command succeeded (no output).'
|
|
86
102
|
});
|
|
103
|
+
logHubStageTiming(requestId, 'req_inbound.responses.convert_input_to_messages', 'completed', {
|
|
104
|
+
elapsedMs: Date.now() - convertStart,
|
|
105
|
+
forceLog: true
|
|
106
|
+
});
|
|
87
107
|
try {
|
|
88
108
|
const bridgePolicy = resolveBridgePolicy({ protocol: 'openai-responses', moduleType: 'openai-responses' });
|
|
89
|
-
const policyActions = resolvePolicyActions(bridgePolicy, 'request_inbound');
|
|
109
|
+
const policyActions = filterRedundantResponsesReasoningAction(resolvePolicyActions(bridgePolicy, 'request_inbound'));
|
|
90
110
|
if (policyActions?.length) {
|
|
111
|
+
logHubStageTiming(requestId, 'req_inbound.responses.inbound_policy', 'start');
|
|
112
|
+
const policyStart = Date.now();
|
|
91
113
|
const actionState = runNativeResponsesBridgePipeline({
|
|
92
114
|
stage: 'request_inbound',
|
|
93
115
|
actions: policyActions,
|
|
@@ -99,6 +121,10 @@ export function buildChatRequestFromResponses(payload, context) {
|
|
|
99
121
|
rawRequest: payload
|
|
100
122
|
});
|
|
101
123
|
messages = actionState.messages;
|
|
124
|
+
logHubStageTiming(requestId, 'req_inbound.responses.inbound_policy', 'completed', {
|
|
125
|
+
elapsedMs: Date.now() - policyStart,
|
|
126
|
+
forceLog: true
|
|
127
|
+
});
|
|
102
128
|
}
|
|
103
129
|
}
|
|
104
130
|
catch {
|
|
@@ -174,7 +200,7 @@ export function buildResponsesRequestFromChat(payload, ctx, extras) {
|
|
|
174
200
|
let bridgeMetadata;
|
|
175
201
|
try {
|
|
176
202
|
const bridgePolicy = resolveBridgePolicy({ protocol: 'openai-responses', moduleType: 'openai-responses' });
|
|
177
|
-
const policyActions = resolvePolicyActions(bridgePolicy, 'request_outbound');
|
|
203
|
+
const policyActions = filterRedundantResponsesReasoningAction(resolvePolicyActions(bridgePolicy, 'request_outbound'));
|
|
178
204
|
if (policyActions?.length) {
|
|
179
205
|
const actionState = runNativeResponsesBridgePipeline({
|
|
180
206
|
stage: 'request_outbound',
|
|
@@ -270,29 +296,27 @@ export function buildResponsesRequestFromChat(payload, ctx, extras) {
|
|
|
270
296
|
metadataSystemInstruction: envelopeMetadata?.systemInstruction,
|
|
271
297
|
combinedSystemInstruction,
|
|
272
298
|
reasoningInstructionSegments: ctx?.__rcc_reasoning_instructions_segments,
|
|
273
|
-
contextParameters:
|
|
299
|
+
contextParameters: undefined,
|
|
274
300
|
chatParameters: chat.parameters,
|
|
275
|
-
metadataParameters: metadataExtraFields?.parameters,
|
|
276
|
-
contextStream:
|
|
277
|
-
metadataStream:
|
|
301
|
+
metadataParameters: stripToolControlFieldsFromParameterObject(metadataExtraFields?.parameters),
|
|
302
|
+
contextStream: undefined,
|
|
303
|
+
metadataStream: undefined,
|
|
278
304
|
chatStream: streamFromChat,
|
|
279
305
|
chatParametersStream: streamFromParameters,
|
|
280
|
-
contextInclude:
|
|
281
|
-
metadataInclude:
|
|
282
|
-
contextStore:
|
|
283
|
-
metadataStore:
|
|
306
|
+
contextInclude: undefined,
|
|
307
|
+
metadataInclude: undefined,
|
|
308
|
+
contextStore: undefined,
|
|
309
|
+
metadataStore: undefined,
|
|
284
310
|
stripHostFields,
|
|
285
|
-
contextToolChoice:
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
metadataTruncation: metadataExtraFields?.truncation,
|
|
295
|
-
contextMetadata: ctx?.metadata,
|
|
311
|
+
contextToolChoice: undefined,
|
|
312
|
+
contextParallelToolCalls: undefined,
|
|
313
|
+
contextResponseFormat: undefined,
|
|
314
|
+
metadataResponseFormat: undefined,
|
|
315
|
+
contextServiceTier: undefined,
|
|
316
|
+
metadataServiceTier: undefined,
|
|
317
|
+
contextTruncation: undefined,
|
|
318
|
+
metadataTruncation: undefined,
|
|
319
|
+
contextMetadata: stripToolControlFieldsFromContextMetadata(ctx?.metadata),
|
|
296
320
|
metadataMetadata: metadataExtraFields?.metadata
|
|
297
321
|
});
|
|
298
322
|
Object.assign(out, preparedEnvelope.request);
|
|
@@ -300,22 +324,6 @@ export function buildResponsesRequestFromChat(payload, ctx, extras) {
|
|
|
300
324
|
ensureBridgeInstructions(out);
|
|
301
325
|
return { request: out, originalSystemMessages };
|
|
302
326
|
}
|
|
303
|
-
function cloneBridgeEntries(entries) {
|
|
304
|
-
if (!Array.isArray(entries)) {
|
|
305
|
-
return undefined;
|
|
306
|
-
}
|
|
307
|
-
return entries.map((entry) => {
|
|
308
|
-
if (!entry || typeof entry !== 'object') {
|
|
309
|
-
return entry;
|
|
310
|
-
}
|
|
311
|
-
try {
|
|
312
|
-
return JSON.parse(JSON.stringify(entry));
|
|
313
|
-
}
|
|
314
|
-
catch {
|
|
315
|
-
return entry;
|
|
316
|
-
}
|
|
317
|
-
});
|
|
318
|
-
}
|
|
319
327
|
function extractMetadataExtraFields(metadata) {
|
|
320
328
|
if (!metadata) {
|
|
321
329
|
return undefined;
|
|
@@ -326,6 +334,31 @@ function extractMetadataExtraFields(metadata) {
|
|
|
326
334
|
}
|
|
327
335
|
return undefined;
|
|
328
336
|
}
|
|
337
|
+
function stripToolControlFieldsFromContextMetadata(metadata) {
|
|
338
|
+
if (!metadata) {
|
|
339
|
+
return undefined;
|
|
340
|
+
}
|
|
341
|
+
const cloned = jsonClone(metadata);
|
|
342
|
+
const extras = cloned.extraFields;
|
|
343
|
+
if (!extras || !isPlainObject(extras)) {
|
|
344
|
+
return cloned;
|
|
345
|
+
}
|
|
346
|
+
delete extras.tool_choice;
|
|
347
|
+
delete extras.parallel_tool_calls;
|
|
348
|
+
if (Object.keys(extras).length === 0) {
|
|
349
|
+
delete cloned.extraFields;
|
|
350
|
+
}
|
|
351
|
+
return cloned;
|
|
352
|
+
}
|
|
353
|
+
function stripToolControlFieldsFromParameterObject(value) {
|
|
354
|
+
if (!value) {
|
|
355
|
+
return undefined;
|
|
356
|
+
}
|
|
357
|
+
const cloned = jsonClone(value);
|
|
358
|
+
delete cloned.tool_choice;
|
|
359
|
+
delete cloned.parallel_tool_calls;
|
|
360
|
+
return Object.keys(cloned).length ? cloned : undefined;
|
|
361
|
+
}
|
|
329
362
|
function isPlainObject(value) {
|
|
330
363
|
return Boolean(value && typeof value === 'object' && !Array.isArray(value));
|
|
331
364
|
}
|
|
@@ -1,5 +1,40 @@
|
|
|
1
1
|
import { normalizeReasoningInAnthropicPayloadWithNative, normalizeReasoningInChatPayloadWithNative, normalizeReasoningInGeminiPayloadWithNative, normalizeReasoningInOpenAIPayloadWithNative, normalizeReasoningInResponsesPayloadWithNative } from '../../router/virtual-router/engine-selection/native-shared-conversion-semantics.js';
|
|
2
2
|
export const RESPONSES_INSTRUCTIONS_REASONING_FIELD = '__rcc_reasoning_instructions';
|
|
3
|
+
const REASONING_TEXT_MARKERS = [
|
|
4
|
+
'<think',
|
|
5
|
+
'</think',
|
|
6
|
+
'<reflection',
|
|
7
|
+
'</reflection',
|
|
8
|
+
'```think',
|
|
9
|
+
'```reflection'
|
|
10
|
+
];
|
|
11
|
+
function stringHasReasoningMarker(value) {
|
|
12
|
+
const lower = value.toLowerCase();
|
|
13
|
+
return REASONING_TEXT_MARKERS.some((marker) => lower.includes(marker));
|
|
14
|
+
}
|
|
15
|
+
function valueMayContainReasoningMarkup(value) {
|
|
16
|
+
if (typeof value === 'string') {
|
|
17
|
+
return stringHasReasoningMarker(value);
|
|
18
|
+
}
|
|
19
|
+
if (!value || typeof value !== 'object') {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
if (Array.isArray(value)) {
|
|
23
|
+
for (const entry of value) {
|
|
24
|
+
if (valueMayContainReasoningMarkup(entry)) {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
const record = value;
|
|
31
|
+
for (const entry of Object.values(record)) {
|
|
32
|
+
if (valueMayContainReasoningMarkup(entry)) {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
3
38
|
function assertReasoningNormalizerNativeAvailable() {
|
|
4
39
|
if (typeof normalizeReasoningInChatPayloadWithNative !== 'function' ||
|
|
5
40
|
typeof normalizeReasoningInResponsesPayloadWithNative !== 'function' ||
|
|
@@ -22,6 +57,13 @@ export function normalizeReasoningInResponsesPayload(payload, options = { includ
|
|
|
22
57
|
assertReasoningNormalizerNativeAvailable();
|
|
23
58
|
if (!payload)
|
|
24
59
|
return;
|
|
60
|
+
const shouldNormalize = (options.includeInput === true && valueMayContainReasoningMarkup(payload.input)) ||
|
|
61
|
+
(options.includeOutput === true && valueMayContainReasoningMarkup(payload.output)) ||
|
|
62
|
+
(options.includeInstructions === true && valueMayContainReasoningMarkup(payload.instructions)) ||
|
|
63
|
+
(options.includeRequiredAction === true && valueMayContainReasoningMarkup(payload.required_action));
|
|
64
|
+
if (!shouldNormalize) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
25
67
|
const normalized = normalizeReasoningInResponsesPayloadWithNative(payload, options);
|
|
26
68
|
if (normalized && typeof normalized === 'object') {
|
|
27
69
|
Object.assign(payload, normalized);
|
|
@@ -94,9 +94,8 @@ export function enforceToolCallIdStyle(input, transformer) {
|
|
|
94
94
|
if (type === 'function_call_output' || type === 'tool_result' || type === 'tool_message') {
|
|
95
95
|
const normalizedCallId = transformer.normalizeCallId(entry.call_id ?? entry.tool_call_id ?? entry.id);
|
|
96
96
|
entry.call_id = normalizedCallId;
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}
|
|
97
|
+
// Keep tool_call_id for providers that expect it (e.g., Qwen)
|
|
98
|
+
entry.tool_call_id = normalizedCallId;
|
|
100
99
|
entry.id = transformer.normalizeOutputId(normalizedCallId, entry.id);
|
|
101
100
|
}
|
|
102
101
|
}
|
|
Binary file
|
|
@@ -22,6 +22,7 @@ export function buildProviderProfiles(targetKeys, runtimeEntries) {
|
|
|
22
22
|
providerType: runtime.providerType,
|
|
23
23
|
endpoint: runtime.endpoint,
|
|
24
24
|
auth: { ...runtime.auth },
|
|
25
|
+
...(runtime.enabled !== undefined ? { enabled: runtime.enabled } : {}),
|
|
25
26
|
outboundProfile: runtime.outboundProfile,
|
|
26
27
|
compatibilityProfile: runtime.compatibilityProfile,
|
|
27
28
|
runtimeKey,
|
|
@@ -11,6 +11,11 @@ import { normalizeModelStreaming, normalizeModelContextTokens, normalizeModelOut
|
|
|
11
11
|
*/
|
|
12
12
|
export function normalizeProvider(providerId, raw) {
|
|
13
13
|
const provider = asRecord(raw);
|
|
14
|
+
const enabled = typeof provider.enabled === 'boolean'
|
|
15
|
+
? provider.enabled
|
|
16
|
+
: typeof provider.enabled === 'string'
|
|
17
|
+
? provider.enabled.trim().toLowerCase() !== 'false'
|
|
18
|
+
: undefined;
|
|
14
19
|
const providerType = detectProviderType(provider);
|
|
15
20
|
const endpoint = typeof provider.endpoint === 'string' && provider.endpoint.trim()
|
|
16
21
|
? provider.endpoint.trim()
|
|
@@ -46,6 +51,7 @@ export function normalizeProvider(providerId, raw) {
|
|
|
46
51
|
providerType,
|
|
47
52
|
endpoint,
|
|
48
53
|
headers,
|
|
54
|
+
...(enabled !== undefined ? { enabled } : {}),
|
|
49
55
|
outboundProfile: mapOutboundProfile(providerType),
|
|
50
56
|
compatibilityProfile,
|
|
51
57
|
processMode,
|