@jsonstudio/llms 0.6.34 → 0.6.74
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/codecs/gemini-openai-codec.js +1 -2
- package/dist/conversion/codecs/responses-openai-codec.js +16 -1
- package/dist/conversion/compat/profiles/chat-glm.json +17 -0
- package/dist/conversion/compat/profiles/chat-iflow.json +36 -0
- package/dist/conversion/compat/profiles/chat-lmstudio.json +37 -0
- package/dist/conversion/compat/profiles/chat-qwen.json +18 -0
- package/dist/conversion/compat/profiles/responses-c4m.json +45 -0
- package/dist/conversion/config/compat-profiles.json +38 -0
- package/dist/conversion/config/sample-config.json +314 -0
- package/dist/conversion/config/version-switch.json +150 -0
- package/dist/conversion/hub/pipeline/compat/compat-engine.d.ts +4 -0
- package/dist/conversion/hub/pipeline/compat/compat-engine.js +667 -0
- package/dist/conversion/hub/pipeline/compat/compat-profile-store.d.ts +2 -0
- package/dist/conversion/hub/pipeline/compat/compat-profile-store.js +76 -0
- package/dist/conversion/hub/pipeline/compat/compat-types.d.ts +62 -0
- package/dist/conversion/hub/pipeline/compat/compat-types.js +1 -0
- package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +1 -0
- package/dist/conversion/hub/pipeline/hub-pipeline.js +76 -28
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/index.js +0 -13
- package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage3_compat/index.d.ts +14 -0
- package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage3_compat/index.js +23 -0
- package/dist/conversion/hub/response/provider-response.js +18 -0
- package/dist/conversion/hub/response/response-mappers.d.ts +1 -1
- package/dist/conversion/hub/response/response-mappers.js +2 -12
- package/dist/conversion/responses/responses-openai-bridge.d.ts +1 -0
- package/dist/conversion/responses/responses-openai-bridge.js +71 -0
- package/dist/conversion/shared/responses-output-builder.js +22 -43
- package/dist/conversion/shared/responses-response-utils.js +1 -47
- package/dist/conversion/shared/text-markup-normalizer.js +2 -2
- package/dist/conversion/shared/tool-canonicalizer.js +16 -118
- package/dist/conversion/shared/tool-filter-pipeline.js +63 -21
- package/dist/conversion/shared/tool-mapping.js +52 -32
- package/dist/filters/config/openai-openai.fieldmap.json +18 -0
- package/dist/filters/special/request-tools-normalize.js +20 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/router/virtual-router/bootstrap.js +18 -33
- package/dist/router/virtual-router/classifier.js +51 -77
- package/dist/router/virtual-router/features.js +338 -111
- package/dist/router/virtual-router/types.d.ts +2 -4
- package/dist/router/virtual-router/types.js +2 -2
- package/dist/sse/sse-to-json/builders/response-builder.js +1 -0
- package/dist/tools/tool-registry.js +4 -3
- package/package.json +3 -3
|
@@ -95,10 +95,10 @@ export function extractApplyPatchCallsFromText(text) {
|
|
|
95
95
|
continue;
|
|
96
96
|
let argsStr = '{}';
|
|
97
97
|
try {
|
|
98
|
-
argsStr = JSON.stringify({
|
|
98
|
+
argsStr = JSON.stringify({ patch });
|
|
99
99
|
}
|
|
100
100
|
catch {
|
|
101
|
-
argsStr = '{"
|
|
101
|
+
argsStr = '{"patch":""}';
|
|
102
102
|
}
|
|
103
103
|
out.push({ id: genId(), name: 'apply_patch', args: argsStr });
|
|
104
104
|
}
|
|
@@ -12,133 +12,31 @@ function repairArgumentsToString(args) {
|
|
|
12
12
|
return String(args);
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
|
-
function extractStringContent(value) {
|
|
16
|
-
if (typeof value === 'string') {
|
|
17
|
-
return value;
|
|
18
|
-
}
|
|
19
|
-
if (Array.isArray(value)) {
|
|
20
|
-
const parts = [];
|
|
21
|
-
for (const entry of value) {
|
|
22
|
-
if (typeof entry === 'string') {
|
|
23
|
-
parts.push(entry);
|
|
24
|
-
}
|
|
25
|
-
else if (entry && typeof entry === 'object') {
|
|
26
|
-
const text = entry.text;
|
|
27
|
-
if (typeof text === 'string') {
|
|
28
|
-
parts.push(text);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
if (parts.length) {
|
|
33
|
-
return parts.join('\n');
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
return null;
|
|
37
|
-
}
|
|
38
|
-
function readApplyPatchArgument(args) {
|
|
39
|
-
if (!args || typeof args !== 'object')
|
|
40
|
-
return undefined;
|
|
41
|
-
const input = args.input;
|
|
42
|
-
if (typeof input === 'string' && input.trim().length > 0) {
|
|
43
|
-
return input;
|
|
44
|
-
}
|
|
45
|
-
const legacy = args.patch;
|
|
46
|
-
if (typeof legacy === 'string' && legacy.trim().length > 0) {
|
|
47
|
-
return legacy;
|
|
48
|
-
}
|
|
49
|
-
return undefined;
|
|
50
|
-
}
|
|
51
|
-
function hasApplyPatchInput(argText) {
|
|
52
|
-
if (!argText) {
|
|
53
|
-
return false;
|
|
54
|
-
}
|
|
55
|
-
try {
|
|
56
|
-
const parsed = JSON.parse(argText);
|
|
57
|
-
const content = readApplyPatchArgument(parsed);
|
|
58
|
-
return typeof content === 'string' && content.trim().length > 0;
|
|
59
|
-
}
|
|
60
|
-
catch {
|
|
61
|
-
return false;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
function extractUnifiedDiff(content) {
|
|
65
|
-
if (!content)
|
|
66
|
-
return undefined;
|
|
67
|
-
const begin = content.indexOf('*** Begin Patch');
|
|
68
|
-
const end = content.indexOf('*** End Patch');
|
|
69
|
-
if (begin >= 0 && end > begin) {
|
|
70
|
-
return content.slice(begin, end + '*** End Patch'.length).trim();
|
|
71
|
-
}
|
|
72
|
-
return undefined;
|
|
73
|
-
}
|
|
74
|
-
function createToolCallId() {
|
|
75
|
-
return `call_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
76
|
-
}
|
|
77
15
|
export function canonicalizeChatResponseTools(payload) {
|
|
78
16
|
try {
|
|
79
17
|
const out = isObject(payload) ? JSON.parse(JSON.stringify(payload)) : payload;
|
|
80
18
|
const choices = Array.isArray(out?.choices) ? out.choices : [];
|
|
81
19
|
for (const ch of choices) {
|
|
82
20
|
const msg = ch && ch.message ? ch.message : undefined;
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
const originalContentText = extractStringContent(msg?.content);
|
|
86
|
-
const harvestedPatch = typeof originalContentText === 'string' ? extractUnifiedDiff(originalContentText) : undefined;
|
|
87
|
-
if (hasToolCalls) {
|
|
88
|
-
// ensure arguments is string and content is null when tool_calls present
|
|
89
|
-
try {
|
|
90
|
-
if (!ch.finish_reason)
|
|
91
|
-
ch.finish_reason = 'tool_calls';
|
|
92
|
-
}
|
|
93
|
-
catch { /* ignore */ }
|
|
94
|
-
try {
|
|
95
|
-
if (msg && typeof msg === 'object')
|
|
96
|
-
msg.content = null;
|
|
97
|
-
}
|
|
98
|
-
catch { /* ignore */ }
|
|
99
|
-
for (const tc of toolCalls) {
|
|
100
|
-
try {
|
|
101
|
-
const fn = tc && tc.function ? tc.function : undefined;
|
|
102
|
-
if (fn) {
|
|
103
|
-
const repaired = repairArgumentsToString(fn.arguments);
|
|
104
|
-
let nextArgs = repaired;
|
|
105
|
-
try {
|
|
106
|
-
const parsed = JSON.parse(repaired);
|
|
107
|
-
const diffText = readApplyPatchArgument(parsed);
|
|
108
|
-
if (typeof diffText === 'string' && diffText.trim().length > 0) {
|
|
109
|
-
nextArgs = JSON.stringify({ input: diffText });
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
catch {
|
|
113
|
-
// fallback to repaired string
|
|
114
|
-
}
|
|
115
|
-
fn.arguments = nextArgs;
|
|
116
|
-
if (typeof fn.name === 'string' &&
|
|
117
|
-
fn.name === 'apply_patch' &&
|
|
118
|
-
!hasApplyPatchInput(fn.arguments) &&
|
|
119
|
-
harvestedPatch) {
|
|
120
|
-
fn.arguments = JSON.stringify({ input: harvestedPatch });
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
catch { /* ignore */ }
|
|
125
|
-
}
|
|
21
|
+
const tcs = Array.isArray(msg?.tool_calls) ? msg.tool_calls : [];
|
|
22
|
+
if (!tcs || !tcs.length)
|
|
126
23
|
continue;
|
|
24
|
+
// ensure arguments is string and content is null when tool_calls present
|
|
25
|
+
try {
|
|
26
|
+
if (!ch.finish_reason)
|
|
27
|
+
ch.finish_reason = 'tool_calls';
|
|
127
28
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
id,
|
|
132
|
-
type: 'function',
|
|
133
|
-
function: {
|
|
134
|
-
name: 'apply_patch',
|
|
135
|
-
arguments: JSON.stringify({ input: harvestedPatch })
|
|
136
|
-
}
|
|
137
|
-
};
|
|
138
|
-
try {
|
|
139
|
-
msg.tool_calls = [toolCall];
|
|
29
|
+
catch { /* ignore */ }
|
|
30
|
+
try {
|
|
31
|
+
if (msg && typeof msg === 'object')
|
|
140
32
|
msg.content = null;
|
|
141
|
-
|
|
33
|
+
}
|
|
34
|
+
catch { /* ignore */ }
|
|
35
|
+
for (const tc of tcs) {
|
|
36
|
+
try {
|
|
37
|
+
const fn = tc && tc.function ? tc.function : undefined;
|
|
38
|
+
if (fn)
|
|
39
|
+
fn.arguments = repairArgumentsToString(fn.arguments);
|
|
142
40
|
}
|
|
143
41
|
catch { /* ignore */ }
|
|
144
42
|
}
|
|
@@ -1,6 +1,30 @@
|
|
|
1
1
|
import { FilterEngine } from '../../filters/index.js';
|
|
2
2
|
import { loadFieldMapConfig } from '../../filters/utils/fieldmap-loader.js';
|
|
3
3
|
import { createSnapshotWriter } from './snapshot-utils.js';
|
|
4
|
+
const REQUEST_FILTER_STAGES = [
|
|
5
|
+
'request_pre',
|
|
6
|
+
'request_map',
|
|
7
|
+
'request_post',
|
|
8
|
+
'request_finalize'
|
|
9
|
+
];
|
|
10
|
+
const RESPONSE_FILTER_STAGES = [
|
|
11
|
+
'response_pre',
|
|
12
|
+
'response_map',
|
|
13
|
+
'response_post',
|
|
14
|
+
'response_finalize'
|
|
15
|
+
];
|
|
16
|
+
function assertStageCoverage(label, registeredStages, skeletonStages) {
|
|
17
|
+
const allowed = new Set(skeletonStages);
|
|
18
|
+
const uncovered = [];
|
|
19
|
+
for (const stage of registeredStages) {
|
|
20
|
+
if (!allowed.has(stage)) {
|
|
21
|
+
uncovered.push(stage);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
if (uncovered.length) {
|
|
25
|
+
throw new Error(`[tool-filter-pipeline] ${label}: registered filter stage(s) not covered by skeleton: ${uncovered.join(', ')}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
4
28
|
export async function runChatRequestToolFilters(chatRequest, options = {}) {
|
|
5
29
|
const reqCtxBase = {
|
|
6
30
|
requestId: options.requestId ?? `req_${Date.now()}`,
|
|
@@ -23,22 +47,27 @@ export async function runChatRequestToolFilters(chatRequest, options = {}) {
|
|
|
23
47
|
};
|
|
24
48
|
recordStage('req_process_tool_filters_input', chatRequest);
|
|
25
49
|
const engine = new FilterEngine();
|
|
50
|
+
const registeredStages = new Set();
|
|
51
|
+
const register = (filter) => {
|
|
52
|
+
registeredStages.add(filter.stage);
|
|
53
|
+
engine.registerFilter(filter);
|
|
54
|
+
};
|
|
26
55
|
const profile = (reqCtxBase.profile || '').toLowerCase();
|
|
27
56
|
const endpoint = (reqCtxBase.endpoint || '').toLowerCase();
|
|
28
57
|
const isAnthropic = profile === 'anthropic-messages' || endpoint.includes('/v1/messages');
|
|
29
58
|
if (!isAnthropic) {
|
|
30
59
|
try {
|
|
31
60
|
const { RequestToolListFilter } = await import('../../filters/index.js');
|
|
32
|
-
|
|
61
|
+
register(new RequestToolListFilter());
|
|
33
62
|
}
|
|
34
63
|
catch {
|
|
35
64
|
/* optional */
|
|
36
65
|
}
|
|
37
66
|
}
|
|
38
67
|
const { RequestToolCallsStringifyFilter, RequestToolChoicePolicyFilter } = await import('../../filters/index.js');
|
|
39
|
-
|
|
68
|
+
register(new RequestToolCallsStringifyFilter());
|
|
40
69
|
if (!isAnthropic) {
|
|
41
|
-
|
|
70
|
+
register(new RequestToolChoicePolicyFilter());
|
|
42
71
|
}
|
|
43
72
|
try {
|
|
44
73
|
const cfg = await loadFieldMapConfig('openai-openai.fieldmap.json');
|
|
@@ -52,12 +81,20 @@ export async function runChatRequestToolFilters(chatRequest, options = {}) {
|
|
|
52
81
|
} })());
|
|
53
82
|
}
|
|
54
83
|
catch { /* ignore */ }
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
84
|
+
try {
|
|
85
|
+
const { RequestOpenAIToolsNormalizeFilter, ToolPostConstraintsFilter } = await import('../../filters/index.js');
|
|
86
|
+
register(new RequestOpenAIToolsNormalizeFilter());
|
|
87
|
+
register(new ToolPostConstraintsFilter('request_finalize'));
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
// optional; keep prior behavior when filter not available
|
|
91
|
+
}
|
|
92
|
+
assertStageCoverage('request', registeredStages, REQUEST_FILTER_STAGES);
|
|
93
|
+
let staged = chatRequest;
|
|
94
|
+
for (const stage of REQUEST_FILTER_STAGES) {
|
|
95
|
+
staged = await engine.run(stage, staged, reqCtxBase);
|
|
96
|
+
recordStage(`req_process_tool_filters_${stage}`, staged);
|
|
97
|
+
}
|
|
61
98
|
recordStage('req_process_tool_filters_output', staged);
|
|
62
99
|
return staged;
|
|
63
100
|
}
|
|
@@ -81,20 +118,25 @@ export async function runChatResponseToolFilters(chatJson, options = {}) {
|
|
|
81
118
|
};
|
|
82
119
|
recordStage('resp_process_tool_filters_input', chatJson);
|
|
83
120
|
const engine = new FilterEngine();
|
|
121
|
+
const registeredStages = new Set();
|
|
122
|
+
const register = (filter) => {
|
|
123
|
+
registeredStages.add(filter.stage);
|
|
124
|
+
engine.registerFilter(filter);
|
|
125
|
+
};
|
|
84
126
|
const { ResponseToolTextCanonicalizeFilter, ResponseToolArgumentsStringifyFilter, ResponseFinishInvariantsFilter } = await import('../../filters/index.js');
|
|
85
|
-
|
|
127
|
+
register(new ResponseToolTextCanonicalizeFilter());
|
|
86
128
|
try {
|
|
87
129
|
const { ResponseToolArgumentsToonDecodeFilter, ResponseToolArgumentsBlacklistFilter, ResponseToolArgumentsSchemaConvergeFilter } = await import('../../filters/index.js');
|
|
88
|
-
|
|
130
|
+
register(new ResponseToolArgumentsToonDecodeFilter());
|
|
89
131
|
try {
|
|
90
|
-
|
|
132
|
+
register(new ResponseToolArgumentsSchemaConvergeFilter());
|
|
91
133
|
}
|
|
92
134
|
catch { /* optional */ }
|
|
93
|
-
|
|
135
|
+
register(new ResponseToolArgumentsBlacklistFilter());
|
|
94
136
|
}
|
|
95
137
|
catch { /* optional */ }
|
|
96
|
-
|
|
97
|
-
|
|
138
|
+
register(new ResponseToolArgumentsStringifyFilter());
|
|
139
|
+
register(new ResponseFinishInvariantsFilter());
|
|
98
140
|
try {
|
|
99
141
|
const cfg = await loadFieldMapConfig('openai-openai.fieldmap.json');
|
|
100
142
|
if (cfg)
|
|
@@ -107,12 +149,12 @@ export async function runChatResponseToolFilters(chatJson, options = {}) {
|
|
|
107
149
|
} })());
|
|
108
150
|
}
|
|
109
151
|
catch { /* ignore */ }
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
152
|
+
assertStageCoverage('response', registeredStages, RESPONSE_FILTER_STAGES);
|
|
153
|
+
let staged = chatJson;
|
|
154
|
+
for (const stage of RESPONSE_FILTER_STAGES) {
|
|
155
|
+
staged = await engine.run(stage, staged, resCtxBase);
|
|
156
|
+
recordStage(`resp_process_tool_filters_${stage}`, staged);
|
|
157
|
+
}
|
|
116
158
|
recordStage('resp_process_tool_filters_output', staged);
|
|
117
159
|
return staged;
|
|
118
160
|
}
|
|
@@ -8,6 +8,56 @@ export function stringifyArgs(args) {
|
|
|
8
8
|
return String(args);
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
|
+
function isPlainObject(value) {
|
|
12
|
+
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
13
|
+
}
|
|
14
|
+
function clonePlainObject(value) {
|
|
15
|
+
try {
|
|
16
|
+
return JSON.parse(JSON.stringify(value));
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return { ...value };
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function asSchema(value) {
|
|
23
|
+
if (isPlainObject(value)) {
|
|
24
|
+
return clonePlainObject(value);
|
|
25
|
+
}
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
function ensureApplyPatchSchema(seed) {
|
|
29
|
+
const schema = seed ? { ...seed } : {};
|
|
30
|
+
schema.type = typeof schema.type === 'string' ? schema.type : 'object';
|
|
31
|
+
const properties = isPlainObject(schema.properties) ? { ...schema.properties } : {};
|
|
32
|
+
properties.input = {
|
|
33
|
+
type: 'string',
|
|
34
|
+
description: 'Unified diff patch body between *** Begin Patch/*** End Patch.'
|
|
35
|
+
};
|
|
36
|
+
if (!properties.patch || typeof properties.patch !== 'object') {
|
|
37
|
+
properties.patch = {
|
|
38
|
+
type: 'string',
|
|
39
|
+
description: 'Alias of input for backwards compatibility.'
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
schema.properties = properties;
|
|
43
|
+
const requiredList = Array.isArray(schema.required) ? schema.required.filter((entry) => typeof entry === 'string') : [];
|
|
44
|
+
if (!requiredList.includes('input')) {
|
|
45
|
+
requiredList.push('input');
|
|
46
|
+
}
|
|
47
|
+
schema.required = requiredList;
|
|
48
|
+
if (typeof schema.additionalProperties !== 'boolean') {
|
|
49
|
+
schema.additionalProperties = false;
|
|
50
|
+
}
|
|
51
|
+
return schema;
|
|
52
|
+
}
|
|
53
|
+
function enforceBuiltinToolSchema(name, candidate) {
|
|
54
|
+
const normalizedName = typeof name === 'string' ? name.trim().toLowerCase() : '';
|
|
55
|
+
if (normalizedName === 'apply_patch') {
|
|
56
|
+
const base = asSchema(candidate);
|
|
57
|
+
return ensureApplyPatchSchema(base);
|
|
58
|
+
}
|
|
59
|
+
return asSchema(candidate);
|
|
60
|
+
}
|
|
11
61
|
const DEFAULT_SANITIZER = (value) => {
|
|
12
62
|
if (typeof value === 'string') {
|
|
13
63
|
const trimmed = value.trim();
|
|
@@ -15,28 +65,6 @@ const DEFAULT_SANITIZER = (value) => {
|
|
|
15
65
|
}
|
|
16
66
|
return undefined;
|
|
17
67
|
};
|
|
18
|
-
const APPLY_PATCH_TOOL_NAME = 'apply_patch';
|
|
19
|
-
const APPLY_PATCH_ARG_KEY = 'input';
|
|
20
|
-
function isApplyPatchTool(name) {
|
|
21
|
-
return typeof name === 'string' && name.trim() === APPLY_PATCH_TOOL_NAME;
|
|
22
|
-
}
|
|
23
|
-
function buildApplyPatchParameters() {
|
|
24
|
-
return {
|
|
25
|
-
type: 'object',
|
|
26
|
-
properties: {
|
|
27
|
-
[APPLY_PATCH_ARG_KEY]: {
|
|
28
|
-
type: 'string',
|
|
29
|
-
description: 'Unified diff patch content (*** Begin Patch ... *** End Patch)'
|
|
30
|
-
}
|
|
31
|
-
},
|
|
32
|
-
required: [APPLY_PATCH_ARG_KEY],
|
|
33
|
-
additionalProperties: false
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
function applyPatchSchemaToFunction(fnNode) {
|
|
37
|
-
fnNode.parameters = buildApplyPatchParameters();
|
|
38
|
-
fnNode.strict = true;
|
|
39
|
-
}
|
|
40
68
|
function resolveToolName(candidate, options) {
|
|
41
69
|
const sanitized = options?.sanitizeName?.(candidate);
|
|
42
70
|
if (typeof sanitized === 'string' && sanitized.trim().length) {
|
|
@@ -88,7 +116,7 @@ export function bridgeToolToChatDefinition(rawTool, options) {
|
|
|
88
116
|
return null;
|
|
89
117
|
}
|
|
90
118
|
const description = resolveToolDescription(fnNode?.description ?? tool.description);
|
|
91
|
-
const parameters = resolveToolParameters(fnNode, tool);
|
|
119
|
+
const parameters = enforceBuiltinToolSchema(name, resolveToolParameters(fnNode, tool));
|
|
92
120
|
const strict = resolveToolStrict(fnNode, tool);
|
|
93
121
|
const rawType = typeof tool.type === 'string' && tool.type.trim().length ? tool.type.trim() : 'function';
|
|
94
122
|
const normalizedType = rawType.toLowerCase() === 'custom' ? 'function' : rawType;
|
|
@@ -102,9 +130,6 @@ export function bridgeToolToChatDefinition(rawTool, options) {
|
|
|
102
130
|
if (strict !== undefined) {
|
|
103
131
|
fnOut.strict = strict;
|
|
104
132
|
}
|
|
105
|
-
if (isApplyPatchTool(name)) {
|
|
106
|
-
applyPatchSchemaToFunction(fnOut);
|
|
107
|
-
}
|
|
108
133
|
return {
|
|
109
134
|
type: normalizedType,
|
|
110
135
|
function: fnOut
|
|
@@ -130,7 +155,7 @@ export function chatToolToBridgeDefinition(rawTool, options) {
|
|
|
130
155
|
return null;
|
|
131
156
|
}
|
|
132
157
|
const description = resolveToolDescription(fnNode?.description);
|
|
133
|
-
const parameters = resolveToolParameters(fnNode, undefined);
|
|
158
|
+
const parameters = enforceBuiltinToolSchema(name, resolveToolParameters(fnNode, undefined));
|
|
134
159
|
const strict = resolveToolStrict(fnNode, undefined);
|
|
135
160
|
const normalizedType = typeof tool.type === 'string' && tool.type.trim().length ? tool.type.trim() : 'function';
|
|
136
161
|
const responseShape = {
|
|
@@ -156,11 +181,6 @@ export function chatToolToBridgeDefinition(rawTool, options) {
|
|
|
156
181
|
if (strict !== undefined) {
|
|
157
182
|
fnOut.strict = strict;
|
|
158
183
|
}
|
|
159
|
-
if (isApplyPatchTool(name)) {
|
|
160
|
-
applyPatchSchemaToFunction(fnOut);
|
|
161
|
-
responseShape.parameters = buildApplyPatchParameters();
|
|
162
|
-
responseShape.strict = true;
|
|
163
|
-
}
|
|
164
184
|
responseShape.function = fnOut;
|
|
165
185
|
return responseShape;
|
|
166
186
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"request": [
|
|
3
|
+
{
|
|
4
|
+
"sourcePath": "messages[*].tool_calls[*].function.arguments",
|
|
5
|
+
"targetPath": "messages[*].tool_calls[*].function.arguments",
|
|
6
|
+
"type": "string",
|
|
7
|
+
"transform": "stringifyJson"
|
|
8
|
+
}
|
|
9
|
+
],
|
|
10
|
+
"response": [
|
|
11
|
+
{
|
|
12
|
+
"sourcePath": "choices[*].message.tool_calls[*].function.arguments",
|
|
13
|
+
"targetPath": "choices[*].message.tool_calls[*].function.arguments",
|
|
14
|
+
"type": "string",
|
|
15
|
+
"transform": "stringifyJson"
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -73,7 +73,7 @@ export class RequestOpenAIToolsNormalizeFilter {
|
|
|
73
73
|
delete dst.function.strict;
|
|
74
74
|
}
|
|
75
75
|
catch { /* ignore */ }
|
|
76
|
-
// Switch schema for
|
|
76
|
+
// Switch schema for specific built-in tools at unified shaping point
|
|
77
77
|
try {
|
|
78
78
|
const name = String(dst.function.name || '').toLowerCase();
|
|
79
79
|
if (name === 'shell') {
|
|
@@ -101,6 +101,25 @@ export class RequestOpenAIToolsNormalizeFilter {
|
|
|
101
101
|
};
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
|
+
else if (name === 'apply_patch') {
|
|
105
|
+
dst.function.parameters = {
|
|
106
|
+
type: 'object',
|
|
107
|
+
properties: {
|
|
108
|
+
input: {
|
|
109
|
+
type: 'string',
|
|
110
|
+
description: 'Unified diff patch body between *** Begin Patch/*** End Patch.'
|
|
111
|
+
},
|
|
112
|
+
patch: {
|
|
113
|
+
type: 'string',
|
|
114
|
+
description: 'Alias of input for backwards compatibility.'
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
required: ['input'],
|
|
118
|
+
additionalProperties: false
|
|
119
|
+
};
|
|
120
|
+
dst.function.description =
|
|
121
|
+
'Use apply_patch to edit files. Provide the diff via the input field (*** Begin Patch ... *** End Patch).';
|
|
122
|
+
}
|
|
104
123
|
}
|
|
105
124
|
catch { /* ignore */ }
|
|
106
125
|
finalTools.push(dst);
|
package/dist/index.d.ts
CHANGED
|
@@ -7,5 +7,4 @@
|
|
|
7
7
|
export * from './conversion/index.js';
|
|
8
8
|
export * from './router/virtual-router/bootstrap.js';
|
|
9
9
|
export * from './router/virtual-router/types.js';
|
|
10
|
-
export { DEFAULT_THINKING_KEYWORDS } from './router/virtual-router/default-thinking-keywords.js';
|
|
11
10
|
export declare const VERSION = "0.4.0";
|
package/dist/index.js
CHANGED
|
@@ -7,5 +7,4 @@
|
|
|
7
7
|
export * from './conversion/index.js';
|
|
8
8
|
export * from './router/virtual-router/bootstrap.js';
|
|
9
9
|
export * from './router/virtual-router/types.js';
|
|
10
|
-
export { DEFAULT_THINKING_KEYWORDS } from './router/virtual-router/default-thinking-keywords.js';
|
|
11
10
|
export const VERSION = '0.4.0';
|
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
import { VirtualRouterError, VirtualRouterErrorCode } from './types.js';
|
|
2
|
-
import { DEFAULT_THINKING_KEYWORDS } from './default-thinking-keywords.js';
|
|
3
|
-
const KEYWORD_INJECTION_KEYS = ['thinking', 'background', 'vision', 'coding'];
|
|
4
2
|
const DEFAULT_CLASSIFIER = {
|
|
5
3
|
longContextThresholdTokens: 180000,
|
|
6
|
-
thinkingKeywords:
|
|
4
|
+
thinkingKeywords: ['think step', 'analysis', 'reasoning', '仔细分析', '深度思考'],
|
|
7
5
|
codingKeywords: ['apply_patch', 'write_file', 'create_file', 'shell', '修改文件', '写入文件'],
|
|
8
6
|
backgroundKeywords: ['background', 'context dump', '上下文'],
|
|
9
|
-
visionKeywords: ['vision', 'image', 'picture', 'photo']
|
|
10
|
-
keywordInjections: {}
|
|
7
|
+
visionKeywords: ['vision', 'image', 'picture', 'photo']
|
|
11
8
|
};
|
|
12
9
|
const DEFAULT_LOAD_BALANCING = { strategy: 'round-robin' };
|
|
13
10
|
const DEFAULT_HEALTH = { failureThreshold: 3, cooldownMs: 30_000, fatalCooldownMs: 300_000 };
|
|
@@ -189,8 +186,7 @@ function normalizeClassifier(input) {
|
|
|
189
186
|
thinkingKeywords: normalizeStringArray(normalized.thinkingKeywords, DEFAULT_CLASSIFIER.thinkingKeywords),
|
|
190
187
|
codingKeywords: normalizeStringArray(normalized.codingKeywords, DEFAULT_CLASSIFIER.codingKeywords),
|
|
191
188
|
backgroundKeywords: normalizeStringArray(normalized.backgroundKeywords, DEFAULT_CLASSIFIER.backgroundKeywords),
|
|
192
|
-
visionKeywords: normalizeStringArray(normalized.visionKeywords, DEFAULT_CLASSIFIER.visionKeywords)
|
|
193
|
-
keywordInjections: normalizeKeywordInjectionMap(normalized.keywordInjections)
|
|
189
|
+
visionKeywords: normalizeStringArray(normalized.visionKeywords, DEFAULT_CLASSIFIER.visionKeywords)
|
|
194
190
|
};
|
|
195
191
|
return result;
|
|
196
192
|
}
|
|
@@ -201,26 +197,6 @@ function normalizeStringArray(value, fallback) {
|
|
|
201
197
|
const normalized = value.map((item) => (typeof item === 'string' ? item.trim() : '')).filter(Boolean);
|
|
202
198
|
return normalized.length ? normalized : [...fallback];
|
|
203
199
|
}
|
|
204
|
-
function normalizeKeywordInjectionMap(value) {
|
|
205
|
-
const map = {};
|
|
206
|
-
const record = asRecord(value);
|
|
207
|
-
if (!record) {
|
|
208
|
-
return map;
|
|
209
|
-
}
|
|
210
|
-
for (const key of KEYWORD_INJECTION_KEYS) {
|
|
211
|
-
const rawList = record[key];
|
|
212
|
-
if (!Array.isArray(rawList)) {
|
|
213
|
-
continue;
|
|
214
|
-
}
|
|
215
|
-
const normalized = rawList
|
|
216
|
-
.map((entry) => (typeof entry === 'string' ? entry.trim() : ''))
|
|
217
|
-
.filter(Boolean);
|
|
218
|
-
if (normalized.length) {
|
|
219
|
-
map[key] = normalized;
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
return map;
|
|
223
|
-
}
|
|
224
200
|
function normalizeProvider(providerId, raw) {
|
|
225
201
|
const provider = asRecord(raw);
|
|
226
202
|
const providerType = detectProviderType(provider);
|
|
@@ -232,7 +208,7 @@ function normalizeProvider(providerId, raw) {
|
|
|
232
208
|
? provider.baseUrl.trim()
|
|
233
209
|
: '';
|
|
234
210
|
const headers = normalizeHeaders(provider.headers);
|
|
235
|
-
const compatibilityProfile = resolveCompatibilityProfile(provider);
|
|
211
|
+
const compatibilityProfile = resolveCompatibilityProfile(providerId, provider);
|
|
236
212
|
const responsesConfig = normalizeResponsesConfig(provider);
|
|
237
213
|
const processMode = normalizeProcessMode(provider.process);
|
|
238
214
|
return {
|
|
@@ -257,12 +233,21 @@ function normalizeResponsesConfig(provider) {
|
|
|
257
233
|
}
|
|
258
234
|
return undefined;
|
|
259
235
|
}
|
|
260
|
-
function resolveCompatibilityProfile(provider) {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
236
|
+
function resolveCompatibilityProfile(providerId, provider) {
|
|
237
|
+
if (typeof provider.compatibilityProfile === 'string' && provider.compatibilityProfile.trim()) {
|
|
238
|
+
return provider.compatibilityProfile.trim();
|
|
239
|
+
}
|
|
240
|
+
const legacyFields = [];
|
|
241
|
+
if (typeof provider.compat === 'string') {
|
|
242
|
+
legacyFields.push('compat');
|
|
243
|
+
}
|
|
244
|
+
if (typeof provider.compatibility_profile === 'string') {
|
|
245
|
+
legacyFields.push('compatibility_profile');
|
|
246
|
+
}
|
|
247
|
+
if (legacyFields.length > 0) {
|
|
248
|
+
throw new VirtualRouterError(`Provider "${providerId}" uses legacy compatibility field(s): ${legacyFields.join(', ')}. Rename to "compatibilityProfile".`, VirtualRouterErrorCode.CONFIG_ERROR);
|
|
264
249
|
}
|
|
265
|
-
return '
|
|
250
|
+
return 'compat:passthrough';
|
|
266
251
|
}
|
|
267
252
|
function normalizeProcessMode(value) {
|
|
268
253
|
if (typeof value !== 'string') {
|