@jsonstudio/llms 0.6.3379 → 0.6.3405
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/claude-thinking-tools.d.ts +1 -14
- package/dist/conversion/compat/actions/claude-thinking-tools.js +3 -71
- package/dist/conversion/compat/actions/lmstudio-responses-fc-ids.d.ts +0 -8
- package/dist/conversion/compat/actions/lmstudio-responses-fc-ids.js +2 -57
- package/dist/conversion/compat/actions/normalize-tool-call-ids.d.ts +0 -9
- package/dist/conversion/compat/actions/normalize-tool-call-ids.js +6 -136
- package/dist/conversion/compat/actions/request-rules.js +2 -61
- package/dist/conversion/compat/actions/response-blacklist.d.ts +0 -4
- package/dist/conversion/compat/actions/response-blacklist.js +2 -77
- package/dist/conversion/compat/actions/response-normalize.js +2 -119
- package/dist/conversion/compat/actions/response-validate.js +2 -74
- package/dist/conversion/compat/actions/strip-orphan-function-calls-tag.js +2 -150
- package/dist/conversion/hub/operation-table/semantic-mappers/anthropic-mapper.js +24 -1
- package/dist/conversion/hub/pipeline/hub-pipeline.js +91 -0
- package/dist/conversion/shared/reasoning-tool-parser.js +7 -8
- package/dist/conversion/shared/responses-response-utils.js +3 -48
- package/dist/conversion/shared/responses-tool-utils.js +22 -126
- package/dist/native/router_hotpath_napi.node +0 -0
- package/dist/router/virtual-router/bootstrap/web-search-config.js +25 -0
- package/dist/router/virtual-router/bootstrap.js +21 -16
- package/dist/router/virtual-router/engine-selection/native-compat-action-semantics.d.ts +6 -0
- package/dist/router/virtual-router/engine-selection/native-compat-action-semantics.js +171 -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 +5 -0
- package/dist/router/virtual-router/engine-selection/native-shared-conversion-semantics.js +137 -0
- package/dist/router/virtual-router/types.d.ts +23 -0
- package/dist/servertool/handlers/web-search.js +26 -1
- package/dist/servertool/server-side-tools.js +11 -2
- package/dist/servertool/types.d.ts +4 -0
- package/package.json +1 -1
|
@@ -1,15 +1,2 @@
|
|
|
1
1
|
import type { JsonObject } from '../../hub/types/json.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Compat for Claude models routed via antigravity on gemini-chat.
|
|
5
|
-
*
|
|
6
|
-
* Anthropic requires tools[*].custom.input_schema to be valid JSON Schema draft 2020-12.
|
|
7
|
-
* We currently send OpenAI-style parameters which may not fully conform, causing upstream
|
|
8
|
-
* invalid_request_error on tools.N.custom.input_schema.
|
|
9
|
-
*
|
|
10
|
-
* For safety, when we detect the antigravity.*.claude-* path over gemini-chat,
|
|
11
|
-
* we aggressively simplify Gemini functionDeclarations[*].parameters to a minimal
|
|
12
|
-
* but valid object schema, letting RouteCodex govern tool semantics while keeping
|
|
13
|
-
* Anthropic's schema validator happy.
|
|
14
|
-
*/
|
|
15
|
-
export declare function applyClaudeThinkingToolSchemaCompat(payload: JsonObject, adapterContext?: AdapterContext): JsonObject;
|
|
2
|
+
export declare function applyClaudeThinkingToolSchemaCompat(payload: JsonObject): JsonObject;
|
|
@@ -1,72 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
*
|
|
5
|
-
* Anthropic requires tools[*].custom.input_schema to be valid JSON Schema draft 2020-12.
|
|
6
|
-
* We currently send OpenAI-style parameters which may not fully conform, causing upstream
|
|
7
|
-
* invalid_request_error on tools.N.custom.input_schema.
|
|
8
|
-
*
|
|
9
|
-
* For safety, when we detect the antigravity.*.claude-* path over gemini-chat,
|
|
10
|
-
* we aggressively simplify Gemini functionDeclarations[*].parameters to a minimal
|
|
11
|
-
* but valid object schema, letting RouteCodex govern tool semantics while keeping
|
|
12
|
-
* Anthropic's schema validator happy.
|
|
13
|
-
*/
|
|
14
|
-
export function applyClaudeThinkingToolSchemaCompat(payload, adapterContext) {
|
|
15
|
-
const modelRaw = payload.model;
|
|
16
|
-
const modelId = typeof modelRaw === 'string' ? modelRaw.trim() : '';
|
|
17
|
-
// Only apply on Claude models.
|
|
18
|
-
// Upstream Anthropic enforces strict JSON Schema 2020-12 on custom.input_schema for these models.
|
|
19
|
-
if (!modelId.startsWith('claude-')) {
|
|
20
|
-
return payload;
|
|
21
|
-
}
|
|
22
|
-
const root = structuredClone(payload);
|
|
23
|
-
// Support both shapes:
|
|
24
|
-
// - Provider envelope: { model, request: { tools, ... } }
|
|
25
|
-
// - Gemini mapper request: { model, tools, ... }
|
|
26
|
-
const requestNode = isRecord(root.request)
|
|
27
|
-
? root.request
|
|
28
|
-
: root;
|
|
29
|
-
const toolsRaw = requestNode.tools;
|
|
30
|
-
if (!Array.isArray(toolsRaw)) {
|
|
31
|
-
return root;
|
|
32
|
-
}
|
|
33
|
-
const nextTools = [];
|
|
34
|
-
for (const entry of toolsRaw) {
|
|
35
|
-
if (!isRecord(entry)) {
|
|
36
|
-
nextTools.push(entry);
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
39
|
-
const decls = Array.isArray(entry.functionDeclarations)
|
|
40
|
-
? entry.functionDeclarations
|
|
41
|
-
: undefined;
|
|
42
|
-
if (!decls || !decls.length) {
|
|
43
|
-
// Non functionDeclarations-based tools (e.g. googleSearch) are left as-is.
|
|
44
|
-
nextTools.push(entry);
|
|
45
|
-
continue;
|
|
46
|
-
}
|
|
47
|
-
const nextDecls = [];
|
|
48
|
-
for (const fn of decls) {
|
|
49
|
-
if (!isRecord(fn)) {
|
|
50
|
-
nextDecls.push(fn);
|
|
51
|
-
continue;
|
|
52
|
-
}
|
|
53
|
-
const fnCopy = { ...fn };
|
|
54
|
-
// Replace parameters with a minimal, always-valid object schema.
|
|
55
|
-
fnCopy.parameters = {
|
|
56
|
-
type: 'object',
|
|
57
|
-
properties: {},
|
|
58
|
-
additionalProperties: true
|
|
59
|
-
};
|
|
60
|
-
// Drop strict flag to avoid upstream schema incompatibilities.
|
|
61
|
-
if (Object.prototype.hasOwnProperty.call(fnCopy, 'strict')) {
|
|
62
|
-
delete fnCopy.strict;
|
|
63
|
-
}
|
|
64
|
-
nextDecls.push(fnCopy);
|
|
65
|
-
}
|
|
66
|
-
nextTools.push({
|
|
67
|
-
functionDeclarations: nextDecls
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
requestNode.tools = nextTools;
|
|
71
|
-
return root;
|
|
1
|
+
import { applyClaudeThinkingToolSchemaCompatWithNative } from '../../../router/virtual-router/engine-selection/native-hub-pipeline-req-outbound-semantics.js';
|
|
2
|
+
export function applyClaudeThinkingToolSchemaCompat(payload) {
|
|
3
|
+
return applyClaudeThinkingToolSchemaCompatWithNative(payload);
|
|
72
4
|
}
|
|
@@ -1,10 +1,2 @@
|
|
|
1
1
|
import type { JsonObject } from '../../hub/types/json.js';
|
|
2
|
-
/**
|
|
3
|
-
* LM Studio compat:
|
|
4
|
-
* Some LM Studio OpenAI-Responses endpoints accept tool-call items but are picky about ids:
|
|
5
|
-
* - `call_id` should remain `call_*` (used to link tool outputs)
|
|
6
|
-
* - `id` for `function_call` / `function_call_output` should be `fc_*` (OpenAI Responses convention)
|
|
7
|
-
*
|
|
8
|
-
* We apply this in compat (profile: chat:lmstudio) to keep provider layer transport-only.
|
|
9
|
-
*/
|
|
10
2
|
export declare function enforceLmstudioResponsesFcToolCallIds(payload: JsonObject): JsonObject;
|
|
@@ -1,59 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
function isRecord(value) {
|
|
3
|
-
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
4
|
-
}
|
|
5
|
-
function normalizeToCallId(raw, fallback) {
|
|
6
|
-
const rawStr = typeof raw === 'string' ? raw.trim() : '';
|
|
7
|
-
return normalizeResponsesCallId({ callId: rawStr || undefined, fallback });
|
|
8
|
-
}
|
|
9
|
-
/**
|
|
10
|
-
* LM Studio compat:
|
|
11
|
-
* Some LM Studio OpenAI-Responses endpoints accept tool-call items but are picky about ids:
|
|
12
|
-
* - `call_id` should remain `call_*` (used to link tool outputs)
|
|
13
|
-
* - `id` for `function_call` / `function_call_output` should be `fc_*` (OpenAI Responses convention)
|
|
14
|
-
*
|
|
15
|
-
* We apply this in compat (profile: chat:lmstudio) to keep provider layer transport-only.
|
|
16
|
-
*/
|
|
1
|
+
import { enforceLmstudioResponsesFcToolCallIdsWithNative } from '../../../router/virtual-router/engine-selection/native-compat-action-semantics.js';
|
|
17
2
|
export function enforceLmstudioResponsesFcToolCallIds(payload) {
|
|
18
|
-
|
|
19
|
-
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
|
|
20
|
-
return payload;
|
|
21
|
-
}
|
|
22
|
-
const root = structuredClone(payload);
|
|
23
|
-
const input = root.input;
|
|
24
|
-
if (!Array.isArray(input) || input.length === 0) {
|
|
25
|
-
return root;
|
|
26
|
-
}
|
|
27
|
-
let callCounter = 0;
|
|
28
|
-
for (const item of input) {
|
|
29
|
-
if (!isRecord(item))
|
|
30
|
-
continue;
|
|
31
|
-
const type = typeof item.type === 'string' ? item.type.trim().toLowerCase() : '';
|
|
32
|
-
if (type !== 'function_call' && type !== 'function_call_output') {
|
|
33
|
-
continue;
|
|
34
|
-
}
|
|
35
|
-
const rawCallId = (item.call_id ?? item.tool_call_id ?? item.id);
|
|
36
|
-
const callId = normalizeToCallId(rawCallId, `call_${++callCounter}`);
|
|
37
|
-
item.call_id = callId;
|
|
38
|
-
if (type === 'function_call') {
|
|
39
|
-
const normalizedItemId = normalizeFunctionCallId({
|
|
40
|
-
callId,
|
|
41
|
-
fallback: `fc_${callId}`
|
|
42
|
-
});
|
|
43
|
-
item.id = normalizedItemId;
|
|
44
|
-
continue;
|
|
45
|
-
}
|
|
46
|
-
// function_call_output
|
|
47
|
-
const normalizedOutputId = normalizeFunctionCallOutputId({
|
|
48
|
-
callId,
|
|
49
|
-
fallback: typeof item.id === 'string' && item.id.trim().length ? item.id.trim() : `fc_tool_${callCounter}`
|
|
50
|
-
});
|
|
51
|
-
item.id = normalizedOutputId;
|
|
52
|
-
}
|
|
53
|
-
root.input = input;
|
|
54
|
-
return root;
|
|
55
|
-
}
|
|
56
|
-
catch {
|
|
57
|
-
return payload;
|
|
58
|
-
}
|
|
3
|
+
return enforceLmstudioResponsesFcToolCallIdsWithNative(payload);
|
|
59
4
|
}
|
|
@@ -1,11 +1,2 @@
|
|
|
1
1
|
import type { JsonObject } from '../../hub/types/json.js';
|
|
2
|
-
/**
|
|
3
|
-
* Normalize tool-call identifiers across OpenAI-compatible payload variants.
|
|
4
|
-
*
|
|
5
|
-
* Goal: field-level normalization only (no rewriting/rehashing IDs):
|
|
6
|
-
* - Mirror `call_id` ↔ `tool_call_id` when one side is missing.
|
|
7
|
-
* - For Responses `input` / `output` function_call items, ensure `id` and `call_id` are aligned.
|
|
8
|
-
*
|
|
9
|
-
* This is intended to live in compatibility profiles (per-provider), not in host/provider code.
|
|
10
|
-
*/
|
|
11
2
|
export declare function normalizeToolCallIdsInPlace(root: JsonObject): void;
|
|
@@ -1,140 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
3
|
-
}
|
|
4
|
-
function ensureRecord(parent, key) {
|
|
5
|
-
const existing = parent[key];
|
|
6
|
-
if (isRecord(existing)) {
|
|
7
|
-
return existing;
|
|
8
|
-
}
|
|
9
|
-
const next = {};
|
|
10
|
-
parent[key] = next;
|
|
11
|
-
return next;
|
|
12
|
-
}
|
|
13
|
-
function pickString(...candidates) {
|
|
14
|
-
for (const c of candidates) {
|
|
15
|
-
if (typeof c === 'string' && c.trim().length) {
|
|
16
|
-
return c.trim();
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
return undefined;
|
|
20
|
-
}
|
|
21
|
-
function normalizeResponsesInputItem(item) {
|
|
22
|
-
const type = typeof item.type === 'string' ? item.type : '';
|
|
23
|
-
if (type === 'function_call') {
|
|
24
|
-
const id = pickString(item.id);
|
|
25
|
-
const callId = pickString(item.call_id);
|
|
26
|
-
if (callId && !id) {
|
|
27
|
-
item.id = callId;
|
|
28
|
-
}
|
|
29
|
-
else if (id && !callId) {
|
|
30
|
-
item.call_id = id;
|
|
31
|
-
}
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
if (type === 'function_call_output' || type === 'tool_result' || type === 'tool_message') {
|
|
35
|
-
const id = pickString(item.id);
|
|
36
|
-
const callId = pickString(item.call_id);
|
|
37
|
-
const toolCallId = pickString(item.tool_call_id);
|
|
38
|
-
const resolvedCall = pickString(callId, toolCallId);
|
|
39
|
-
if (resolvedCall && !callId) {
|
|
40
|
-
item.call_id = resolvedCall;
|
|
41
|
-
}
|
|
42
|
-
if (id && !callId && !toolCallId) {
|
|
43
|
-
// Some upstreams only provide an id for tool output items; mirror it to both fields.
|
|
44
|
-
item.call_id = id;
|
|
45
|
-
item.tool_call_id = id;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
function normalizeResponsesOutputItem(item) {
|
|
50
|
-
const type = typeof item.type === 'string' ? item.type : '';
|
|
51
|
-
if (type === 'function_call') {
|
|
52
|
-
const id = pickString(item.id, item.item_id);
|
|
53
|
-
const callId = pickString(item.call_id);
|
|
54
|
-
if (callId && !id) {
|
|
55
|
-
item.id = callId;
|
|
56
|
-
}
|
|
57
|
-
else if (id && !callId) {
|
|
58
|
-
item.call_id = id;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
function normalizeToolOutputsArray(toolOutputs) {
|
|
63
|
-
if (!Array.isArray(toolOutputs))
|
|
64
|
-
return;
|
|
65
|
-
for (const entry of toolOutputs) {
|
|
66
|
-
if (!isRecord(entry))
|
|
67
|
-
continue;
|
|
68
|
-
const toolCallId = pickString(entry.tool_call_id);
|
|
69
|
-
const callId = pickString(entry.call_id);
|
|
70
|
-
const resolved = pickString(toolCallId, callId, entry.id);
|
|
71
|
-
if (resolved) {
|
|
72
|
-
entry.tool_call_id = resolved;
|
|
73
|
-
entry.call_id = resolved;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
function normalizeChatMessages(messages) {
|
|
78
|
-
if (!Array.isArray(messages))
|
|
79
|
-
return;
|
|
80
|
-
for (const msg of messages) {
|
|
81
|
-
if (!isRecord(msg))
|
|
82
|
-
continue;
|
|
83
|
-
// chat tool message uses tool_call_id; mirror to call_id for downstream consumers that only look at call_id.
|
|
84
|
-
const toolCallId = pickString(msg.tool_call_id);
|
|
85
|
-
const callId = pickString(msg.call_id);
|
|
86
|
-
const resolved = pickString(toolCallId, callId);
|
|
87
|
-
if (resolved) {
|
|
88
|
-
msg.tool_call_id = resolved;
|
|
89
|
-
msg.call_id = resolved;
|
|
90
|
-
}
|
|
91
|
-
const toolCalls = msg.tool_calls;
|
|
92
|
-
if (Array.isArray(toolCalls)) {
|
|
93
|
-
for (const call of toolCalls) {
|
|
94
|
-
if (!isRecord(call))
|
|
95
|
-
continue;
|
|
96
|
-
const id = pickString(call.id);
|
|
97
|
-
const callId2 = pickString(call.call_id);
|
|
98
|
-
if (id && !callId2) {
|
|
99
|
-
call.call_id = id;
|
|
100
|
-
}
|
|
101
|
-
else if (callId2 && !id) {
|
|
102
|
-
call.id = callId2;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
/**
|
|
109
|
-
* Normalize tool-call identifiers across OpenAI-compatible payload variants.
|
|
110
|
-
*
|
|
111
|
-
* Goal: field-level normalization only (no rewriting/rehashing IDs):
|
|
112
|
-
* - Mirror `call_id` ↔ `tool_call_id` when one side is missing.
|
|
113
|
-
* - For Responses `input` / `output` function_call items, ensure `id` and `call_id` are aligned.
|
|
114
|
-
*
|
|
115
|
-
* This is intended to live in compatibility profiles (per-provider), not in host/provider code.
|
|
116
|
-
*/
|
|
1
|
+
import { normalizeToolCallIdsWithNative } from '../../../router/virtual-router/engine-selection/native-compat-action-semantics.js';
|
|
117
2
|
export function normalizeToolCallIdsInPlace(root) {
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
if (isRecord(item)) {
|
|
123
|
-
normalizeResponsesInputItem(item);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
// Responses submit_tool_outputs request (common alias)
|
|
128
|
-
normalizeToolOutputsArray(record.tool_outputs);
|
|
129
|
-
normalizeToolOutputsArray(record.toolOutputs);
|
|
130
|
-
// Responses response
|
|
131
|
-
if (Array.isArray(record.output)) {
|
|
132
|
-
for (const item of record.output) {
|
|
133
|
-
if (isRecord(item)) {
|
|
134
|
-
normalizeResponsesOutputItem(item);
|
|
135
|
-
}
|
|
3
|
+
const normalized = normalizeToolCallIdsWithNative(root);
|
|
4
|
+
for (const key of Object.keys(root)) {
|
|
5
|
+
if (!(key in normalized)) {
|
|
6
|
+
delete root[key];
|
|
136
7
|
}
|
|
137
8
|
}
|
|
138
|
-
|
|
139
|
-
normalizeChatMessages(record.messages);
|
|
9
|
+
Object.assign(root, normalized);
|
|
140
10
|
}
|
|
@@ -1,63 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import { applyRequestRulesWithNative } from '../../../router/virtual-router/engine-selection/native-compat-action-semantics.js';
|
|
2
2
|
export function applyRequestRules(payload, config) {
|
|
3
|
-
|
|
4
|
-
return payload;
|
|
5
|
-
}
|
|
6
|
-
const cloned = { ...payload };
|
|
7
|
-
const toolRule = config.tools?.['function'];
|
|
8
|
-
if (toolRule?.removeKeys && Array.isArray(cloned.tools)) {
|
|
9
|
-
for (const toolEntry of cloned.tools) {
|
|
10
|
-
if (!isRecord(toolEntry)) {
|
|
11
|
-
continue;
|
|
12
|
-
}
|
|
13
|
-
const fnNode = toolEntry.function;
|
|
14
|
-
if (!isRecord(fnNode)) {
|
|
15
|
-
continue;
|
|
16
|
-
}
|
|
17
|
-
for (const key of toolRule.removeKeys) {
|
|
18
|
-
delete fnNode[key];
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
const assistantRule = config.messages?.assistantToolCalls?.['function'];
|
|
23
|
-
if (Array.isArray(cloned.messages) && assistantRule?.removeKeys) {
|
|
24
|
-
for (const message of cloned.messages) {
|
|
25
|
-
if (!isRecord(message) || !Array.isArray(message.tool_calls)) {
|
|
26
|
-
continue;
|
|
27
|
-
}
|
|
28
|
-
for (const callEntry of message.tool_calls) {
|
|
29
|
-
if (!isRecord(callEntry)) {
|
|
30
|
-
continue;
|
|
31
|
-
}
|
|
32
|
-
const fnNode = callEntry.function;
|
|
33
|
-
if (!isRecord(fnNode)) {
|
|
34
|
-
continue;
|
|
35
|
-
}
|
|
36
|
-
for (const key of assistantRule.removeKeys) {
|
|
37
|
-
delete fnNode[key];
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
if (Array.isArray(config.topLevel?.conditional)) {
|
|
43
|
-
for (const rule of config.topLevel.conditional) {
|
|
44
|
-
if (!rule)
|
|
45
|
-
continue;
|
|
46
|
-
if (rule.when?.tools === 'empty') {
|
|
47
|
-
if (Array.isArray(cloned.tools) && cloned.tools.length === 0) {
|
|
48
|
-
for (const field of rule.remove || []) {
|
|
49
|
-
delete cloned[field];
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
if (rule.when?.tools === 'present') {
|
|
54
|
-
if (Array.isArray(cloned.tools) && cloned.tools.length > 0) {
|
|
55
|
-
for (const field of rule.remove || []) {
|
|
56
|
-
delete cloned[field];
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
return cloned;
|
|
3
|
+
return applyRequestRulesWithNative(payload, (config ?? {}));
|
|
63
4
|
}
|
|
@@ -5,10 +5,6 @@ export interface ResponseBlacklistConfig {
|
|
|
5
5
|
}
|
|
6
6
|
export declare class ResponseBlacklistSanitizer {
|
|
7
7
|
private readonly cfg;
|
|
8
|
-
private readonly criticalPaths;
|
|
9
8
|
constructor(config: ResponseBlacklistConfig);
|
|
10
|
-
private isCritical;
|
|
11
|
-
private unwrapPayload;
|
|
12
|
-
private deleteByPath;
|
|
13
9
|
apply(payload: JsonObject): JsonObject;
|
|
14
10
|
}
|
|
@@ -1,85 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
3
|
-
}
|
|
4
|
-
function isTraversable(value) {
|
|
5
|
-
return Array.isArray(value) || isRecord(value);
|
|
6
|
-
}
|
|
1
|
+
import { applyResponseBlacklistWithNative } from '../../../router/virtual-router/engine-selection/native-compat-action-semantics.js';
|
|
7
2
|
export class ResponseBlacklistSanitizer {
|
|
8
3
|
cfg;
|
|
9
|
-
criticalPaths = new Set([
|
|
10
|
-
'status',
|
|
11
|
-
'output',
|
|
12
|
-
'output_text',
|
|
13
|
-
'required_action',
|
|
14
|
-
'choices[].message.content',
|
|
15
|
-
'choices[].message.tool_calls',
|
|
16
|
-
'choices[].finish_reason'
|
|
17
|
-
]);
|
|
18
4
|
constructor(config) {
|
|
19
5
|
this.cfg = config;
|
|
20
6
|
}
|
|
21
|
-
isCritical(pathStr) {
|
|
22
|
-
if (!this.cfg.keepCritical) {
|
|
23
|
-
return false;
|
|
24
|
-
}
|
|
25
|
-
return this.criticalPaths.has(pathStr);
|
|
26
|
-
}
|
|
27
|
-
unwrapPayload(payload) {
|
|
28
|
-
if (!isRecord(payload)) {
|
|
29
|
-
return {};
|
|
30
|
-
}
|
|
31
|
-
const rootCandidate = payload.data;
|
|
32
|
-
return isRecord(rootCandidate) ? rootCandidate : payload;
|
|
33
|
-
}
|
|
34
|
-
deleteByPath(obj, pathStr) {
|
|
35
|
-
const tokens = pathStr.split('.');
|
|
36
|
-
const recurse = (current, idx) => {
|
|
37
|
-
if (!isTraversable(current) || idx >= tokens.length) {
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
const token = tokens[idx];
|
|
41
|
-
const isArrayWildcard = token.endsWith('[]');
|
|
42
|
-
const key = isArrayWildcard ? token.slice(0, -2) : token;
|
|
43
|
-
if (idx === tokens.length - 1) {
|
|
44
|
-
if (!isArrayWildcard && isRecord(current) && Object.prototype.hasOwnProperty.call(current, key)) {
|
|
45
|
-
delete current[key];
|
|
46
|
-
}
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
if (isArrayWildcard) {
|
|
50
|
-
if (isRecord(current)) {
|
|
51
|
-
const arr = current[key];
|
|
52
|
-
if (Array.isArray(arr)) {
|
|
53
|
-
for (const item of arr) {
|
|
54
|
-
recurse(item, idx + 1);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
else if (isRecord(current)) {
|
|
60
|
-
recurse(current[key], idx + 1);
|
|
61
|
-
}
|
|
62
|
-
};
|
|
63
|
-
recurse(obj, 0);
|
|
64
|
-
}
|
|
65
7
|
apply(payload) {
|
|
66
|
-
|
|
67
|
-
try {
|
|
68
|
-
const root = this.unwrapPayload(out);
|
|
69
|
-
const configPaths = Array.isArray(this.cfg?.paths) ? this.cfg.paths ?? [] : [];
|
|
70
|
-
for (const p of configPaths) {
|
|
71
|
-
if (typeof p !== 'string' || !p) {
|
|
72
|
-
continue;
|
|
73
|
-
}
|
|
74
|
-
if (this.isCritical(p)) {
|
|
75
|
-
continue;
|
|
76
|
-
}
|
|
77
|
-
this.deleteByPath(root, p);
|
|
78
|
-
}
|
|
79
|
-
return out;
|
|
80
|
-
}
|
|
81
|
-
catch {
|
|
82
|
-
return out;
|
|
83
|
-
}
|
|
8
|
+
return applyResponseBlacklistWithNative(payload, this.cfg);
|
|
84
9
|
}
|
|
85
10
|
}
|
|
@@ -1,121 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import { normalizeResponsePayloadWithNative } from '../../../router/virtual-router/engine-selection/native-compat-action-semantics.js';
|
|
2
2
|
export function normalizeResponsePayload(payload, config) {
|
|
3
|
-
|
|
4
|
-
if (isRecord(normalized.usage)) {
|
|
5
|
-
normalized.usage = normalizeUsageFields(normalized.usage);
|
|
6
|
-
}
|
|
7
|
-
if (typeof normalized.created_at === 'number') {
|
|
8
|
-
normalized.created = normalized.created_at;
|
|
9
|
-
delete normalized.created_at;
|
|
10
|
-
}
|
|
11
|
-
if (Array.isArray(normalized.choices)) {
|
|
12
|
-
normalized.choices = normalizeChoices(normalized.choices, config);
|
|
13
|
-
}
|
|
14
|
-
return normalized;
|
|
15
|
-
}
|
|
16
|
-
function normalizeUsageFields(usage) {
|
|
17
|
-
const normalizedUsage = { ...usage };
|
|
18
|
-
const fieldMappings = {
|
|
19
|
-
input_tokens: 'prompt_tokens',
|
|
20
|
-
output_tokens: 'completion_tokens',
|
|
21
|
-
total_input_tokens: 'prompt_tokens',
|
|
22
|
-
total_output_tokens: 'completion_tokens'
|
|
23
|
-
};
|
|
24
|
-
for (const [glmField, standardField] of Object.entries(fieldMappings)) {
|
|
25
|
-
const value = normalizedUsage[glmField];
|
|
26
|
-
if (typeof value === 'number') {
|
|
27
|
-
normalizedUsage[standardField] = value;
|
|
28
|
-
delete normalizedUsage[glmField];
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
const promptTokens = typeof normalizedUsage.prompt_tokens === 'number' ? normalizedUsage.prompt_tokens : 0;
|
|
32
|
-
const completionTokens = typeof normalizedUsage.completion_tokens === 'number'
|
|
33
|
-
? normalizedUsage.completion_tokens
|
|
34
|
-
: 0;
|
|
35
|
-
normalizedUsage.prompt_tokens = promptTokens;
|
|
36
|
-
normalizedUsage.completion_tokens = completionTokens;
|
|
37
|
-
if (typeof normalizedUsage.total_tokens !== 'number') {
|
|
38
|
-
normalizedUsage.total_tokens = promptTokens + completionTokens;
|
|
39
|
-
}
|
|
40
|
-
return normalizedUsage;
|
|
41
|
-
}
|
|
42
|
-
function normalizeChoices(choices, config) {
|
|
43
|
-
const map = config?.finishReasonMap || {};
|
|
44
|
-
return choices.map((choice, idx) => {
|
|
45
|
-
if (!isRecord(choice)) {
|
|
46
|
-
return choice;
|
|
47
|
-
}
|
|
48
|
-
const normalizedChoice = {
|
|
49
|
-
index: typeof choice.index === 'number' ? choice.index : idx,
|
|
50
|
-
...choice
|
|
51
|
-
};
|
|
52
|
-
if (typeof normalizedChoice.finish_reason === 'string') {
|
|
53
|
-
normalizedChoice.finish_reason = map[normalizedChoice.finish_reason] ?? normalizedChoice.finish_reason;
|
|
54
|
-
}
|
|
55
|
-
if (isRecord(normalizedChoice.message)) {
|
|
56
|
-
normalizedChoice.message = normalizeChoiceMessage(normalizedChoice.message);
|
|
57
|
-
}
|
|
58
|
-
return normalizedChoice;
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
function normalizeChoiceMessage(message) {
|
|
62
|
-
const normalizedMessage = { ...message };
|
|
63
|
-
if (normalizedMessage.content !== undefined && typeof normalizedMessage.content !== 'string') {
|
|
64
|
-
normalizedMessage.content = normalizeMessageContent(normalizedMessage.content);
|
|
65
|
-
}
|
|
66
|
-
if (Array.isArray(normalizedMessage.tool_calls)) {
|
|
67
|
-
normalizedMessage.tool_calls = normalizeToolCalls(normalizedMessage.tool_calls);
|
|
68
|
-
}
|
|
69
|
-
return normalizedMessage;
|
|
70
|
-
}
|
|
71
|
-
function normalizeMessageContent(content) {
|
|
72
|
-
if (typeof content === 'string') {
|
|
73
|
-
return content;
|
|
74
|
-
}
|
|
75
|
-
if (Array.isArray(content)) {
|
|
76
|
-
return content.map(item => (typeof item === 'string' ? item : JSON.stringify(item))).join('');
|
|
77
|
-
}
|
|
78
|
-
if (isRecord(content)) {
|
|
79
|
-
return JSON.stringify(content);
|
|
80
|
-
}
|
|
81
|
-
return String(content ?? '');
|
|
82
|
-
}
|
|
83
|
-
function normalizeToolCalls(toolCalls) {
|
|
84
|
-
return toolCalls.map(toolCall => {
|
|
85
|
-
if (!isRecord(toolCall)) {
|
|
86
|
-
return toolCall;
|
|
87
|
-
}
|
|
88
|
-
const normalizedToolCall = { ...toolCall };
|
|
89
|
-
const func = normalizedToolCall.function;
|
|
90
|
-
if (isRecord(func) && func.arguments !== undefined) {
|
|
91
|
-
func.arguments = normalizeToolArguments(func.arguments);
|
|
92
|
-
normalizedToolCall.function = func;
|
|
93
|
-
}
|
|
94
|
-
return normalizedToolCall;
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
function normalizeToolArguments(args) {
|
|
98
|
-
let normalized = '';
|
|
99
|
-
if (typeof args === 'string') {
|
|
100
|
-
normalized = args;
|
|
101
|
-
}
|
|
102
|
-
else {
|
|
103
|
-
try {
|
|
104
|
-
normalized = JSON.stringify(args ?? {});
|
|
105
|
-
}
|
|
106
|
-
catch {
|
|
107
|
-
normalized = String(args ?? '');
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
const trimmed = normalized.trim();
|
|
111
|
-
if (!trimmed.length) {
|
|
112
|
-
return '';
|
|
113
|
-
}
|
|
114
|
-
try {
|
|
115
|
-
const parsed = JSON.parse(trimmed);
|
|
116
|
-
return JSON.stringify(parsed);
|
|
117
|
-
}
|
|
118
|
-
catch {
|
|
119
|
-
return trimmed;
|
|
120
|
-
}
|
|
3
|
+
return normalizeResponsePayloadWithNative(payload, (config ?? {}));
|
|
121
4
|
}
|