@jsonstudio/llms 0.6.3214 → 0.6.3271
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/bridge-actions.js +37 -322
- package/dist/conversion/bridge-instructions.js +12 -109
- package/dist/conversion/codecs/anthropic-openai-codec.js +1 -1
- package/dist/conversion/compat/actions/deepseek-web-request.js +43 -110
- package/dist/conversion/compat/actions/deepseek-web-response.d.ts +3 -0
- package/dist/conversion/compat/actions/deepseek-web-response.js +150 -11
- package/dist/conversion/hub/operation-table/semantic-mappers/archive/chat-mapper.archive.d.ts +8 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/archive/chat-mapper.archive.js +404 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/chat-mapper.js +5 -384
- package/dist/conversion/hub/response/response-runtime.d.ts +1 -0
- package/dist/conversion/hub/response/response-runtime.js +26 -0
- package/dist/conversion/hub/snapshot-recorder.js +2 -91
- package/dist/conversion/hub/tool-governance/engine.d.ts +1 -1
- package/dist/conversion/hub/tool-governance/engine.js +17 -127
- package/dist/conversion/shared/anthropic-message-utils.d.ts +3 -1
- package/dist/conversion/shared/anthropic-message-utils.js +23 -15
- package/dist/conversion/shared/openai-finalizer.d.ts +0 -3
- package/dist/conversion/shared/openai-finalizer.js +11 -169
- package/dist/conversion/shared/openai-message-normalize.js +11 -72
- package/dist/conversion/shared/tool-mapping.js +5 -0
- package/dist/native/router_hotpath_napi.node +0 -0
- package/dist/router/virtual-router/bootstrap/provider-normalization.js +11 -3
- package/dist/router/virtual-router/engine-selection/native-hub-bridge-action-semantics.d.ts +20 -0
- package/dist/router/virtual-router/engine-selection/native-hub-bridge-action-semantics.js +71 -0
- package/dist/router/virtual-router/engine-selection/native-hub-pipeline-governance-semantics.d.ts +8 -0
- package/dist/router/virtual-router/engine-selection/native-hub-pipeline-governance-semantics.js +48 -0
- package/dist/router/virtual-router/engine-selection/native-hub-pipeline-req-inbound-semantics.d.ts +1 -0
- package/dist/router/virtual-router/engine-selection/native-hub-pipeline-req-inbound-semantics.js +30 -0
- package/dist/router/virtual-router/engine-selection/native-hub-pipeline-semantic-mappers.d.ts +2 -0
- package/dist/router/virtual-router/engine-selection/native-hub-pipeline-semantic-mappers.js +83 -0
- package/dist/router/virtual-router/engine-selection/native-router-hotpath-loader.js +11 -0
- package/dist/router/virtual-router/engine-selection/native-shared-conversion-semantics.d.ts +2 -0
- package/dist/router/virtual-router/engine-selection/native-shared-conversion-semantics.js +61 -0
- package/dist/router/virtual-router/engine-selection/native-snapshot-hooks.d.ts +1 -0
- package/dist/router/virtual-router/engine-selection/native-snapshot-hooks.js +40 -0
- package/dist/router/virtual-router/engine.js +58 -1
- package/dist/router/virtual-router/types.d.ts +1 -1
- package/package.json +1 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { DEFAULT_TOOL_GOVERNANCE_RULES } from './rules.js';
|
|
2
|
-
import { governRequestWithNative } from '../../../router/virtual-router/engine-selection/native-hub-pipeline-governance-semantics.js';
|
|
2
|
+
import { governRequestWithNative, governResponseWithNative } from '../../../router/virtual-router/engine-selection/native-hub-pipeline-governance-semantics.js';
|
|
3
3
|
/**
|
|
4
4
|
* Hybrid governance engine:
|
|
5
5
|
* - request path: native-primary
|
|
6
|
-
* - response path:
|
|
6
|
+
* - response path: native-primary
|
|
7
7
|
*
|
|
8
8
|
* Legacy-only snapshot retained at:
|
|
9
9
|
* - src/conversion/hub/tool-governance/archive/engine.legacy.ts
|
|
@@ -79,18 +79,24 @@ export class ToolGovernanceEngine {
|
|
|
79
79
|
summary: buildSummary(protocol, 'response', false)
|
|
80
80
|
};
|
|
81
81
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
82
|
+
let governed;
|
|
83
|
+
try {
|
|
84
|
+
governed = governResponseWithNative({
|
|
85
|
+
payload: payload,
|
|
86
|
+
protocol,
|
|
87
|
+
registry: this.registry
|
|
88
|
+
});
|
|
87
89
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
+
catch (error) {
|
|
91
|
+
const message = error instanceof Error ? error.message : String(error ?? 'unknown error');
|
|
92
|
+
if (message.includes('Tool name exceeds max length')) {
|
|
93
|
+
throw new ToolGovernanceError(message, protocol, 'response', 'tool.function.name');
|
|
94
|
+
}
|
|
95
|
+
throw error;
|
|
90
96
|
}
|
|
91
97
|
return {
|
|
92
|
-
payload:
|
|
93
|
-
summary:
|
|
98
|
+
payload: governed.payload,
|
|
99
|
+
summary: governed.summary
|
|
94
100
|
};
|
|
95
101
|
}
|
|
96
102
|
resolveRules(protocol, direction) {
|
|
@@ -99,122 +105,6 @@ export class ToolGovernanceEngine {
|
|
|
99
105
|
return entry?.[direction];
|
|
100
106
|
}
|
|
101
107
|
}
|
|
102
|
-
function sanitizeChatCompletionChoice(choice, rules, stats) {
|
|
103
|
-
const message = choice?.message;
|
|
104
|
-
if (!message || typeof message !== 'object') {
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
const msg = message;
|
|
108
|
-
if (Array.isArray(msg.tool_calls)) {
|
|
109
|
-
msg.tool_calls = msg.tool_calls.map((tc, index) => sanitizeChatCompletionToolCall(tc, rules, stats, `choices[].message.tool_calls[${index}].function.name`));
|
|
110
|
-
}
|
|
111
|
-
if (msg.function_call && typeof msg.function_call === 'object') {
|
|
112
|
-
const orig = msg.function_call.name;
|
|
113
|
-
const sanitizedName = sanitizeName(orig, rules, stats, 'choices[].message.function_call.name');
|
|
114
|
-
msg.function_call.name = sanitizedName;
|
|
115
|
-
}
|
|
116
|
-
if (typeof msg.name === 'string' || msg.role === 'tool') {
|
|
117
|
-
msg.name = sanitizeName(msg.name, rules, stats, 'choices[].message.name');
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
function sanitizeChatCompletionToolCall(tc, rules, stats, context = 'choices[].message.tool_calls[].function.name') {
|
|
121
|
-
if (!tc || typeof tc !== 'object') {
|
|
122
|
-
return tc;
|
|
123
|
-
}
|
|
124
|
-
const fn = tc.function;
|
|
125
|
-
if (!fn || typeof fn !== 'object') {
|
|
126
|
-
return tc;
|
|
127
|
-
}
|
|
128
|
-
const sanitizedName = sanitizeName(fn.name, rules, stats, context);
|
|
129
|
-
if (sanitizedName === fn.name) {
|
|
130
|
-
return tc;
|
|
131
|
-
}
|
|
132
|
-
return {
|
|
133
|
-
...tc,
|
|
134
|
-
function: {
|
|
135
|
-
...fn,
|
|
136
|
-
name: sanitizedName
|
|
137
|
-
}
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
function createStats(protocol, direction) {
|
|
141
|
-
return {
|
|
142
|
-
protocol,
|
|
143
|
-
direction,
|
|
144
|
-
applied: false,
|
|
145
|
-
sanitizedNames: 0,
|
|
146
|
-
truncatedNames: 0,
|
|
147
|
-
defaultedNames: 0
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
function sanitizeName(rawName, rules, stats, field) {
|
|
151
|
-
const defaultName = rules.defaultName ?? 'tool';
|
|
152
|
-
let next = typeof rawName === 'string' ? rawName : '';
|
|
153
|
-
let changed = false;
|
|
154
|
-
if (rules.trimWhitespace !== false) {
|
|
155
|
-
next = next.trim();
|
|
156
|
-
}
|
|
157
|
-
if (!next) {
|
|
158
|
-
next = defaultName;
|
|
159
|
-
stats.defaultedNames += 1;
|
|
160
|
-
changed = true;
|
|
161
|
-
}
|
|
162
|
-
if (rules.forceCase === 'lower') {
|
|
163
|
-
const forced = next.toLowerCase();
|
|
164
|
-
if (forced !== next) {
|
|
165
|
-
next = forced;
|
|
166
|
-
changed = true;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
else if (rules.forceCase === 'upper') {
|
|
170
|
-
const forced = next.toUpperCase();
|
|
171
|
-
if (forced !== next) {
|
|
172
|
-
next = forced;
|
|
173
|
-
changed = true;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
if (rules.allowedCharacters) {
|
|
177
|
-
const matcher = new RegExp(rules.allowedCharacters.source);
|
|
178
|
-
const filtered = next
|
|
179
|
-
.split('')
|
|
180
|
-
.filter((ch) => matcher.test(ch))
|
|
181
|
-
.join('');
|
|
182
|
-
matcher.lastIndex = 0;
|
|
183
|
-
if (filtered.length === 0) {
|
|
184
|
-
next = defaultName;
|
|
185
|
-
stats.defaultedNames += 1;
|
|
186
|
-
changed = true;
|
|
187
|
-
}
|
|
188
|
-
else if (filtered !== next) {
|
|
189
|
-
next = filtered;
|
|
190
|
-
changed = true;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
if (rules.maxNameLength && next.length > rules.maxNameLength) {
|
|
194
|
-
if (rules.onViolation === 'reject') {
|
|
195
|
-
throw new ToolGovernanceError(`Tool name exceeds max length of ${rules.maxNameLength}`, stats.protocol, stats.direction, field);
|
|
196
|
-
}
|
|
197
|
-
next = next.slice(0, rules.maxNameLength);
|
|
198
|
-
stats.truncatedNames += 1;
|
|
199
|
-
changed = true;
|
|
200
|
-
}
|
|
201
|
-
if (changed || (typeof rawName === 'string' && rawName !== next)) {
|
|
202
|
-
stats.sanitizedNames += 1;
|
|
203
|
-
}
|
|
204
|
-
stats.applied = true;
|
|
205
|
-
return next || defaultName;
|
|
206
|
-
}
|
|
207
|
-
function finalizeSummary(stats) {
|
|
208
|
-
return {
|
|
209
|
-
protocol: stats.protocol,
|
|
210
|
-
direction: stats.direction,
|
|
211
|
-
applied: stats.applied,
|
|
212
|
-
sanitizedNames: stats.sanitizedNames,
|
|
213
|
-
truncatedNames: stats.truncatedNames,
|
|
214
|
-
defaultedNames: stats.defaultedNames,
|
|
215
|
-
timestamp: Date.now()
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
108
|
function buildSummary(protocol, direction, applied) {
|
|
219
109
|
return {
|
|
220
110
|
protocol,
|
|
@@ -7,7 +7,9 @@ interface OpenAIChatPayload extends Unknown {
|
|
|
7
7
|
}
|
|
8
8
|
export declare function normalizeAnthropicToolName(value: unknown): string | undefined;
|
|
9
9
|
export declare function denormalizeAnthropicToolName(value: unknown): string | undefined;
|
|
10
|
-
export declare function buildOpenAIChatFromAnthropic(payload: unknown
|
|
10
|
+
export declare function buildOpenAIChatFromAnthropic(payload: unknown, options?: {
|
|
11
|
+
includeToolCallIds?: boolean;
|
|
12
|
+
}): OpenAIChatPayload;
|
|
11
13
|
export interface BuildAnthropicFromOpenAIOptions {
|
|
12
14
|
toolNameMap?: Record<string, string>;
|
|
13
15
|
requestId?: string;
|
|
@@ -17,23 +17,24 @@ function safeJson(v) {
|
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
function stripOpenAIChatToolAliasFields(messages) {
|
|
20
|
+
// No-op: preserve tool_call_id/call_id for downstream consumers and regression parity.
|
|
21
|
+
void messages;
|
|
22
|
+
}
|
|
23
|
+
function stripToolCallIdFieldsFromAssistant(messages) {
|
|
20
24
|
for (const message of messages) {
|
|
21
|
-
if (!message || typeof message !== 'object')
|
|
25
|
+
if (!message || typeof message !== 'object')
|
|
22
26
|
continue;
|
|
23
|
-
}
|
|
24
27
|
const role = String(message.role || '').toLowerCase();
|
|
25
|
-
if (role
|
|
26
|
-
for (const call of message.tool_calls) {
|
|
27
|
-
if (!call || typeof call !== 'object') {
|
|
28
|
-
continue;
|
|
29
|
-
}
|
|
30
|
-
delete call.tool_call_id;
|
|
31
|
-
delete call.call_id;
|
|
32
|
-
}
|
|
28
|
+
if (role !== 'assistant')
|
|
33
29
|
continue;
|
|
34
|
-
|
|
35
|
-
if (
|
|
36
|
-
|
|
30
|
+
const calls = message.tool_calls;
|
|
31
|
+
if (!Array.isArray(calls))
|
|
32
|
+
continue;
|
|
33
|
+
for (const call of calls) {
|
|
34
|
+
if (!call || typeof call !== 'object')
|
|
35
|
+
continue;
|
|
36
|
+
delete call.call_id;
|
|
37
|
+
delete call.tool_call_id;
|
|
37
38
|
}
|
|
38
39
|
}
|
|
39
40
|
}
|
|
@@ -352,7 +353,7 @@ function invertAnthropicAliasMap(source) {
|
|
|
352
353
|
}
|
|
353
354
|
return Object.keys(inverted).length ? inverted : undefined;
|
|
354
355
|
}
|
|
355
|
-
export function buildOpenAIChatFromAnthropic(payload) {
|
|
356
|
+
export function buildOpenAIChatFromAnthropic(payload, options) {
|
|
356
357
|
const newMessages = [];
|
|
357
358
|
const body = isObject(payload) ? payload : {};
|
|
358
359
|
const canonicalAliasMap = coerceAliasRecord(buildAnthropicToolAliasMap(body.tools));
|
|
@@ -458,7 +459,13 @@ export function buildOpenAIChatFromAnthropic(payload) {
|
|
|
458
459
|
const input = block.input ?? {};
|
|
459
460
|
const args = safeJson(input);
|
|
460
461
|
const canonicalName = resolveToolName(name) || name;
|
|
461
|
-
|
|
462
|
+
const includeIds = options?.includeToolCallIds === true;
|
|
463
|
+
toolCalls.push({
|
|
464
|
+
id,
|
|
465
|
+
...(includeIds ? { call_id: id, tool_call_id: id } : {}),
|
|
466
|
+
type: 'function',
|
|
467
|
+
function: { name: canonicalName, arguments: args }
|
|
468
|
+
});
|
|
462
469
|
}
|
|
463
470
|
else if (t === 'tool_result') {
|
|
464
471
|
const callId = requireTrimmedString(block.tool_call_id ??
|
|
@@ -549,6 +556,7 @@ export function buildOpenAIChatFromAnthropic(payload) {
|
|
|
549
556
|
catch {
|
|
550
557
|
// ignore policy failures
|
|
551
558
|
}
|
|
559
|
+
stripToolCallIdFieldsFromAssistant(newMessages);
|
|
552
560
|
stripOpenAIChatToolAliasFields(newMessages);
|
|
553
561
|
return request;
|
|
554
562
|
}
|
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
type ProcessingMode = 'streaming' | 'non-streaming' | 'auto';
|
|
2
1
|
export type ChatReasoningMode = 'keep' | 'drop' | 'append_to_content';
|
|
3
2
|
export interface FinalizeOptions {
|
|
4
3
|
requestId?: string;
|
|
5
4
|
endpoint?: string;
|
|
6
|
-
processingMode?: ProcessingMode;
|
|
7
5
|
stream?: boolean;
|
|
8
6
|
reasoningMode?: ChatReasoningMode;
|
|
9
7
|
}
|
|
10
8
|
export declare function finalizeOpenAIChatResponse(chatLike: unknown, opts?: FinalizeOptions): Promise<unknown>;
|
|
11
|
-
export {};
|
|
@@ -1,180 +1,22 @@
|
|
|
1
1
|
// OpenAI Chat finalizer: enforce canonical shapes without the legacy hook system.
|
|
2
2
|
// - Ensures tool_calls use stringified JSON arguments and sets finish_reason='tool_calls' when applicable
|
|
3
3
|
// - Normalizes potential tool messages (role:'tool') content to strict strings (JSON-stringify for objects)
|
|
4
|
-
import {
|
|
4
|
+
import { finalizeRespProcessChatResponseWithNative } from '../../router/virtual-router/engine-selection/native-chat-process-governance-semantics.js';
|
|
5
5
|
function asObject(v) {
|
|
6
6
|
return v && typeof v === 'object' && !Array.isArray(v) ? v : null;
|
|
7
7
|
}
|
|
8
|
-
|
|
9
|
-
if (typeof argVal === 'string')
|
|
10
|
-
return argVal;
|
|
11
|
-
try {
|
|
12
|
-
return JSON.stringify(argVal ?? {});
|
|
13
|
-
}
|
|
14
|
-
catch {
|
|
15
|
-
return '{}';
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
function toStringContent(val, emptyFallback = 'Command succeeded (no output).') {
|
|
19
|
-
if (val == null)
|
|
20
|
-
return emptyFallback;
|
|
21
|
-
if (typeof val === 'string') {
|
|
22
|
-
const s = val.trim();
|
|
23
|
-
return s.length ? s : emptyFallback;
|
|
24
|
-
}
|
|
25
|
-
if (Array.isArray(val) || typeof val === 'object') {
|
|
26
|
-
try {
|
|
27
|
-
const s = JSON.stringify(val);
|
|
28
|
-
return s && s.trim().length ? s : emptyFallback;
|
|
29
|
-
}
|
|
30
|
-
catch {
|
|
31
|
-
return emptyFallback;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
try {
|
|
35
|
-
const s = String(val);
|
|
36
|
-
return s.trim().length ? s : emptyFallback;
|
|
37
|
-
}
|
|
38
|
-
catch {
|
|
39
|
-
return emptyFallback;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
8
|
+
// Canonicalization handled by native finalizeChatResponseJson.
|
|
42
9
|
export async function finalizeOpenAIChatResponse(chatLike, opts) {
|
|
43
|
-
const streamingIntent = opts?.stream === true;
|
|
44
10
|
const obj = asObject(chatLike);
|
|
45
11
|
if (!obj)
|
|
46
12
|
return chatLike;
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
try {
|
|
56
|
-
const fn = tc?.function || {};
|
|
57
|
-
if (fn && typeof fn === 'object') {
|
|
58
|
-
fn.arguments = stringifyArgs(fn.arguments);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
catch { /* ignore */ }
|
|
62
|
-
}
|
|
63
|
-
if (String(first.finish_reason || '').toLowerCase() !== 'tool_calls') {
|
|
64
|
-
first.finish_reason = 'tool_calls';
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
// nothing more to do for Chat completion response
|
|
69
|
-
}
|
|
70
|
-
// Case B: Chat-style messages array ({ messages: [...] })
|
|
71
|
-
if (Array.isArray(obj.messages)) {
|
|
72
|
-
const msgs = obj.messages;
|
|
73
|
-
// Build latest assistant tool_call id->name map
|
|
74
|
-
const idToName = new Map();
|
|
75
|
-
for (let i = msgs.length - 1; i >= 0; i--) {
|
|
76
|
-
const m = msgs[i];
|
|
77
|
-
if (m && m.role === 'assistant' && Array.isArray(m.tool_calls) && m.tool_calls.length) {
|
|
78
|
-
for (const tc of m.tool_calls) {
|
|
79
|
-
const id = typeof tc?.id === 'string' ? tc.id : undefined;
|
|
80
|
-
const nm = typeof tc?.function?.name === 'string' ? tc.function.name : undefined;
|
|
81
|
-
if (id && nm)
|
|
82
|
-
idToName.set(id, nm);
|
|
83
|
-
if (tc?.function)
|
|
84
|
-
tc.function.arguments = stringifyArgs(tc.function.arguments);
|
|
85
|
-
}
|
|
86
|
-
break;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
// Normalize tool role messages: ensure string content + name (if known)
|
|
90
|
-
for (const m of msgs) {
|
|
91
|
-
if (!m || typeof m !== 'object')
|
|
92
|
-
continue;
|
|
93
|
-
if (String(m.role || '').toLowerCase() !== 'tool')
|
|
94
|
-
continue;
|
|
95
|
-
const cid = typeof m.tool_call_id === 'string' ? m.tool_call_id : undefined;
|
|
96
|
-
// add name when available
|
|
97
|
-
const nm = cid ? idToName.get(cid) : undefined;
|
|
98
|
-
if (nm && typeof m.name !== 'string')
|
|
99
|
-
m.name = nm;
|
|
100
|
-
m.content = toStringContent(m.content);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
catch { /* ignore normalization errors */ }
|
|
105
|
-
applyChatReasoningPolicy(obj, opts?.reasoningMode);
|
|
106
|
-
return obj;
|
|
107
|
-
}
|
|
108
|
-
function applyChatReasoningPolicy(target, mode) {
|
|
109
|
-
if (!mode || mode === 'keep') {
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
const containers = [];
|
|
113
|
-
if (Array.isArray(target.messages)) {
|
|
114
|
-
target.messages.forEach(entry => {
|
|
115
|
-
if (entry && typeof entry === 'object') {
|
|
116
|
-
containers.push(entry);
|
|
117
|
-
}
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
if (Array.isArray(target.choices)) {
|
|
121
|
-
target.choices.forEach(choice => {
|
|
122
|
-
if (!choice || typeof choice !== 'object') {
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
const record = choice;
|
|
126
|
-
if (record.message && typeof record.message === 'object') {
|
|
127
|
-
containers.push(record.message);
|
|
128
|
-
}
|
|
129
|
-
if (record.delta && typeof record.delta === 'object') {
|
|
130
|
-
containers.push(record.delta);
|
|
131
|
-
}
|
|
132
|
-
});
|
|
133
|
-
}
|
|
134
|
-
containers.forEach(container => normalizeReasoningField(container, mode));
|
|
135
|
-
}
|
|
136
|
-
function normalizeReasoningField(container, mode) {
|
|
137
|
-
const bag = container;
|
|
138
|
-
const rawValue = typeof bag.reasoning_content === 'string' ? bag.reasoning_content : '';
|
|
139
|
-
const trimmed = sanitizeReasoningTaggedTextWithNative(rawValue).trim();
|
|
140
|
-
if (!trimmed && Object.prototype.hasOwnProperty.call(bag, 'reasoning_content')) {
|
|
141
|
-
delete bag.reasoning_content;
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
if (!trimmed) {
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
if (mode === 'drop') {
|
|
148
|
-
delete bag.reasoning_content;
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
if (mode === 'append_to_content') {
|
|
152
|
-
appendReasoningToContent(bag, trimmed);
|
|
153
|
-
delete bag.reasoning_content;
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
function appendReasoningToContent(container, reasoning) {
|
|
158
|
-
const separator = reasoning.startsWith('\n') ? '' : '\n';
|
|
159
|
-
if (typeof container.content === 'string') {
|
|
160
|
-
const base = container.content.trim().length ? container.content : '';
|
|
161
|
-
container.content = base ? `${base}${separator}${reasoning}` : reasoning;
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
if (Array.isArray(container.content)) {
|
|
165
|
-
const list = container.content;
|
|
166
|
-
list.push({ type: 'text', text: reasoning });
|
|
167
|
-
return;
|
|
168
|
-
}
|
|
169
|
-
if (container.content && typeof container.content === 'object') {
|
|
170
|
-
try {
|
|
171
|
-
const serialized = JSON.stringify(container.content);
|
|
172
|
-
container.content = serialized ? `${serialized}${separator}${reasoning}` : reasoning;
|
|
173
|
-
}
|
|
174
|
-
catch {
|
|
175
|
-
container.content = reasoning;
|
|
176
|
-
}
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
container.content = reasoning;
|
|
13
|
+
const finalized = await finalizeRespProcessChatResponseWithNative({
|
|
14
|
+
payload: obj,
|
|
15
|
+
stream: opts?.stream === true,
|
|
16
|
+
reasoningMode: opts?.reasoningMode,
|
|
17
|
+
endpoint: opts?.endpoint,
|
|
18
|
+
requestId: opts?.requestId
|
|
19
|
+
});
|
|
20
|
+
return finalized;
|
|
180
21
|
}
|
|
22
|
+
// All canonicalization + reasoning policy handled by native finalizeChatResponseJson.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { injectMcpToolsForChat } from '../mcp-injection.js';
|
|
2
2
|
import { normalizeOpenaiChatMessagesWithNative, normalizeOpenaiMessageWithNative, normalizeOpenaiToolCallWithNative, normalizeOpenaiToolWithNative } from '../../router/virtual-router/engine-selection/native-shared-conversion-semantics.js';
|
|
3
|
+
import { enforceChatBudgetWithNative } from '../../router/virtual-router/engine-selection/native-shared-conversion-semantics.js';
|
|
3
4
|
// Message normalization utilities for OpenAI chat payloads (renamed to avoid confusion
|
|
4
5
|
// with the deprecated "openai-normalizer" module entry). This file contains the
|
|
5
6
|
// previously-implemented logic from openai-normalize.ts.
|
|
@@ -7,47 +8,14 @@ import { normalizeOpenaiChatMessagesWithNative, normalizeOpenaiMessageWithNative
|
|
|
7
8
|
export function normalizeChatRequest(request) {
|
|
8
9
|
if (!request || typeof request !== 'object')
|
|
9
10
|
return request;
|
|
10
|
-
|
|
11
|
+
let normalized = { ...request };
|
|
11
12
|
if (Array.isArray(normalized.messages)) {
|
|
12
13
|
normalized.messages = normalized.messages.map((msg) => normalizeMessage(msg));
|
|
13
14
|
}
|
|
14
15
|
if (Array.isArray(normalized.tools)) {
|
|
15
16
|
normalized.tools = normalized.tools.map((tool) => normalizeTool(tool));
|
|
16
17
|
}
|
|
17
|
-
//
|
|
18
|
-
// - Do not remove messages; avoid truncating tool output content.
|
|
19
|
-
try {
|
|
20
|
-
const msgs = Array.isArray(normalized.messages) ? normalized.messages : [];
|
|
21
|
-
if (msgs.length) {
|
|
22
|
-
const asstLimitRaw = process?.env?.RCC_ASSISTANT_TEXT_LIMIT;
|
|
23
|
-
const ASST_LIMIT = (asstLimitRaw === '0') ? 0 : Math.max(0, Number(asstLimitRaw ?? 8192));
|
|
24
|
-
const clamp = (s, n) => {
|
|
25
|
-
try {
|
|
26
|
-
const t = String(s);
|
|
27
|
-
return (n > 0 && t.length > n) ? (t.slice(0, n) + '...(truncated)') : t;
|
|
28
|
-
}
|
|
29
|
-
catch {
|
|
30
|
-
return s;
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
|
-
for (let i = 0; i < msgs.length; i++) {
|
|
34
|
-
const m = msgs[i];
|
|
35
|
-
if (!m || typeof m !== 'object')
|
|
36
|
-
continue;
|
|
37
|
-
const role = String(m.role || '').toLowerCase();
|
|
38
|
-
if (role === 'assistant') {
|
|
39
|
-
if (ASST_LIMIT > 0) {
|
|
40
|
-
if (typeof m.content === 'string')
|
|
41
|
-
m.content = clamp(m.content, ASST_LIMIT);
|
|
42
|
-
else if (Array.isArray(m.content)) {
|
|
43
|
-
m.content = m.content.map((p) => (p && typeof p.text === 'string') ? { ...p, text: clamp(p.text, ASST_LIMIT) } : p);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
catch { /* ignore limit errors */ }
|
|
18
|
+
// Assistant text limit handling has been moved into native budget enforcement.
|
|
51
19
|
// MCP 注入(两步法)统一走共享实现,避免路径分叉
|
|
52
20
|
const disableMcpTools = Boolean(normalized.__rcc_disable_mcp_tools);
|
|
53
21
|
if (!disableMcpTools) {
|
|
@@ -141,48 +109,19 @@ export function normalizeChatRequest(request) {
|
|
|
141
109
|
}
|
|
142
110
|
catch { /* ignore message normalization */ }
|
|
143
111
|
// 注意:不合并/删除多条 system(与 统一标准,避免高风险修改)。
|
|
144
|
-
//
|
|
112
|
+
// 基于“载荷预算”(配置驱动)进行裁剪,统一走 native。
|
|
145
113
|
try {
|
|
146
114
|
const msgs = Array.isArray(normalized.messages) ? normalized.messages : [];
|
|
147
115
|
if (msgs.length) {
|
|
148
116
|
const modelId = String(normalized?.model || '').trim();
|
|
149
117
|
const budget = resolveBudgetForModelSync(modelId);
|
|
150
|
-
const allowed = Math.max(32 * 1024, Math.floor(budget.allowedBytes));
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
let total = sizeBytes(normalized.messages);
|
|
158
|
-
if (total > allowed) {
|
|
159
|
-
// 分层裁剪:最近 3 条工具消息额度更大,其余更严格;若仍超限,再统一降到最小额度。
|
|
160
|
-
const toolIdx = [];
|
|
161
|
-
for (let i = 0; i < msgs.length; i++) {
|
|
162
|
-
const m = msgs[i];
|
|
163
|
-
if (m && m.role === 'tool' && typeof m.content === 'string')
|
|
164
|
-
toolIdx.push(i);
|
|
165
|
-
}
|
|
166
|
-
if (toolIdx.length) {
|
|
167
|
-
// Disable shrinking/truncation of tool outputs to avoid altering content
|
|
168
|
-
const shrinkTo = (_idx, _limit) => { };
|
|
169
|
-
const recentLimitChars = Number(process?.env?.RCC_TOOL_TEXT_RECENT || 1024);
|
|
170
|
-
const olderLimitChars = Number(process?.env?.RCC_TOOL_TEXT_OLDER || 256);
|
|
171
|
-
const minimalLimitChars = Number(process?.env?.RCC_TOOL_TEXT_MIN || 128);
|
|
172
|
-
const recent = toolIdx.slice(-3);
|
|
173
|
-
const older = toolIdx.slice(0, Math.max(0, toolIdx.length - recent.length));
|
|
174
|
-
// No-op shrink to keep content intact
|
|
175
|
-
for (const i of older)
|
|
176
|
-
shrinkTo(i, olderLimitChars);
|
|
177
|
-
for (const i of recent)
|
|
178
|
-
shrinkTo(i, recentLimitChars);
|
|
179
|
-
total = sizeBytes(normalized.messages);
|
|
180
|
-
if (total > allowed) {
|
|
181
|
-
for (const i of toolIdx)
|
|
182
|
-
shrinkTo(i, minimalLimitChars);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
}
|
|
118
|
+
const allowed = Math.max(32 * 1024, Math.floor(budget.allowedBytes));
|
|
119
|
+
const sysLimit = (() => {
|
|
120
|
+
const raw = process?.env?.RCC_SYSTEM_TEXT_LIMIT;
|
|
121
|
+
const n = Number(raw);
|
|
122
|
+
return Number.isFinite(n) && n >= 0 ? n : 8192;
|
|
123
|
+
})();
|
|
124
|
+
normalized = enforceChatBudgetWithNative(normalized, allowed, sysLimit);
|
|
186
125
|
}
|
|
187
126
|
}
|
|
188
127
|
catch { /* ignore budget enforcement */ }
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { sanitizeResponsesFunctionNameWithNative } from '../../router/virtual-router/engine-selection/native-hub-pipeline-resp-semantics.js';
|
|
2
|
+
import { mapChatToolsToBridgeWithNative } from '../../router/virtual-router/engine-selection/native-shared-conversion-semantics.js';
|
|
2
3
|
export function stringifyArgs(args) {
|
|
3
4
|
if (typeof args === 'string')
|
|
4
5
|
return args;
|
|
@@ -209,6 +210,10 @@ export function mapChatToolsToBridge(rawTools, options) {
|
|
|
209
210
|
if (!Array.isArray(rawTools) || rawTools.length === 0) {
|
|
210
211
|
return undefined;
|
|
211
212
|
}
|
|
213
|
+
if (!options?.sanitizeName) {
|
|
214
|
+
const mapped = mapChatToolsToBridgeWithNative(rawTools);
|
|
215
|
+
return mapped && mapped.length ? mapped : undefined;
|
|
216
|
+
}
|
|
212
217
|
const mapped = rawTools
|
|
213
218
|
.map((entry) => chatToolToBridgeDefinition(entry, options))
|
|
214
219
|
.filter((entry) => !!entry);
|
|
Binary file
|
|
@@ -116,17 +116,25 @@ function normalizeDeepSeekOptions(provider) {
|
|
|
116
116
|
: typeof source.strictToolRequired === 'string'
|
|
117
117
|
? source.strictToolRequired.trim().toLowerCase() === 'true'
|
|
118
118
|
: undefined;
|
|
119
|
-
const
|
|
119
|
+
const toolProtocolRaw = typeof source.toolProtocol === 'string' ? source.toolProtocol.trim().toLowerCase() : '';
|
|
120
|
+
let toolProtocol;
|
|
121
|
+
if (toolProtocolRaw === 'text' || toolProtocolRaw === 'native') {
|
|
122
|
+
toolProtocol = toolProtocolRaw;
|
|
123
|
+
}
|
|
124
|
+
const legacyTextToolFallback = typeof source.textToolFallback === 'boolean'
|
|
120
125
|
? source.textToolFallback
|
|
121
126
|
: typeof source.textToolFallback === 'string'
|
|
122
127
|
? source.textToolFallback.trim().toLowerCase() === 'true'
|
|
123
128
|
: undefined;
|
|
124
|
-
if (
|
|
129
|
+
if (toolProtocol === undefined && legacyTextToolFallback !== undefined) {
|
|
130
|
+
toolProtocol = legacyTextToolFallback ? 'text' : 'native';
|
|
131
|
+
}
|
|
132
|
+
if (strictToolRequired === undefined && toolProtocol === undefined) {
|
|
125
133
|
return undefined;
|
|
126
134
|
}
|
|
127
135
|
return {
|
|
128
136
|
...(strictToolRequired !== undefined ? { strictToolRequired } : {}),
|
|
129
|
-
...(
|
|
137
|
+
...(toolProtocol !== undefined ? { toolProtocol } : {})
|
|
130
138
|
};
|
|
131
139
|
}
|
|
132
140
|
function resolveCompatibilityProfile(providerId, provider) {
|
|
@@ -113,6 +113,25 @@ export interface NativeApplyBridgeEnsureSystemInstructionOutput {
|
|
|
113
113
|
messages: unknown[];
|
|
114
114
|
metadata?: Record<string, unknown>;
|
|
115
115
|
}
|
|
116
|
+
export interface NativeBridgeActionPipelineInput {
|
|
117
|
+
stage: 'request_inbound' | 'request_outbound' | 'response_inbound' | 'response_outbound';
|
|
118
|
+
actions?: Array<{
|
|
119
|
+
name: string;
|
|
120
|
+
options?: Record<string, unknown>;
|
|
121
|
+
}>;
|
|
122
|
+
protocol?: string;
|
|
123
|
+
moduleType?: string;
|
|
124
|
+
requestId?: string;
|
|
125
|
+
state: NativeBridgeActionState;
|
|
126
|
+
}
|
|
127
|
+
export interface NativeBridgeActionState {
|
|
128
|
+
messages: unknown[];
|
|
129
|
+
requiredAction?: Record<string, unknown>;
|
|
130
|
+
capturedToolResults?: Array<Record<string, unknown>>;
|
|
131
|
+
rawRequest?: Record<string, unknown>;
|
|
132
|
+
rawResponse?: Record<string, unknown>;
|
|
133
|
+
metadata?: Record<string, unknown>;
|
|
134
|
+
}
|
|
116
135
|
export interface NativeNormalizeMessageReasoningToolsOutput {
|
|
117
136
|
message: Record<string, unknown>;
|
|
118
137
|
toolCallsAdded: number;
|
|
@@ -130,5 +149,6 @@ export declare function applyBridgeReasoningExtractWithNative(input: NativeApply
|
|
|
130
149
|
export declare function applyBridgeResponsesOutputReasoningWithNative(input: NativeApplyBridgeResponsesOutputReasoningInput): NativeApplyBridgeResponsesOutputReasoningOutput;
|
|
131
150
|
export declare function applyBridgeInjectSystemInstructionWithNative(input: NativeApplyBridgeInjectSystemInstructionInput): NativeApplyBridgeInjectSystemInstructionOutput;
|
|
132
151
|
export declare function applyBridgeEnsureSystemInstructionWithNative(input: NativeApplyBridgeEnsureSystemInstructionInput): NativeApplyBridgeEnsureSystemInstructionOutput;
|
|
152
|
+
export declare function runBridgeActionPipelineWithNative(input: NativeBridgeActionPipelineInput): NativeBridgeActionState | null;
|
|
133
153
|
export declare function normalizeMessageReasoningToolsWithNative(message: Record<string, unknown>, idPrefix?: string): NativeNormalizeMessageReasoningToolsOutput;
|
|
134
154
|
export declare function normalizeChatResponseReasoningToolsWithNative(response: Record<string, unknown>, idPrefixBase?: string): Record<string, unknown>;
|