@jsonstudio/llms 0.6.467 → 0.6.567
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 +15 -0
- package/dist/conversion/compat/actions/claude-thinking-tools.js +72 -0
- package/dist/conversion/compat/profiles/chat-gemini.json +1 -1
- package/dist/conversion/compat/profiles/responses-output2choices-test.json +12 -0
- package/dist/conversion/hub/pipeline/compat/compat-pipeline-executor.js +6 -0
- package/dist/conversion/hub/pipeline/compat/compat-types.d.ts +2 -0
- package/dist/conversion/hub/pipeline/hub-pipeline.js +15 -0
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/index.js +15 -0
- package/dist/conversion/hub/process/chat-process.js +44 -17
- package/dist/conversion/hub/semantic-mappers/anthropic-mapper.js +8 -0
- package/dist/conversion/hub/semantic-mappers/gemini-mapper.js +13 -8
- package/dist/conversion/hub/tool-session-compat.d.ts +26 -0
- package/dist/conversion/hub/tool-session-compat.js +299 -0
- package/dist/conversion/responses/responses-openai-bridge.d.ts +0 -1
- package/dist/conversion/responses/responses-openai-bridge.js +0 -71
- package/dist/conversion/shared/gemini-tool-utils.js +8 -0
- package/dist/conversion/shared/responses-output-builder.js +6 -68
- package/dist/conversion/shared/tool-governor.js +75 -4
- package/dist/conversion/shared/tool-mapping.js +14 -8
- package/dist/filters/special/request-toolcalls-stringify.js +5 -55
- package/dist/filters/special/request-tools-normalize.js +0 -19
- package/dist/guidance/index.js +25 -9
- package/dist/router/virtual-router/engine-health.d.ts +11 -0
- package/dist/router/virtual-router/engine-health.js +210 -0
- package/dist/router/virtual-router/engine-logging.d.ts +19 -0
- package/dist/router/virtual-router/engine-logging.js +165 -0
- package/dist/router/virtual-router/engine-selection.d.ts +32 -0
- package/dist/router/virtual-router/engine-selection.js +649 -0
- package/dist/router/virtual-router/engine.d.ts +4 -13
- package/dist/router/virtual-router/engine.js +64 -517
- package/dist/router/virtual-router/health-manager.d.ts +23 -0
- package/dist/router/virtual-router/health-manager.js +14 -0
- package/dist/router/virtual-router/message-utils.js +22 -0
- package/dist/router/virtual-router/routing-instructions.d.ts +6 -1
- package/dist/router/virtual-router/routing-instructions.js +129 -3
- package/dist/router/virtual-router/types.d.ts +6 -0
- package/dist/servertool/handlers/gemini-empty-reply-continue.d.ts +1 -0
- package/dist/servertool/handlers/gemini-empty-reply-continue.js +120 -0
- package/dist/servertool/handlers/stop-message-auto.d.ts +1 -0
- package/dist/servertool/handlers/stop-message-auto.js +147 -0
- package/dist/servertool/handlers/vision.js +105 -7
- package/dist/servertool/server-side-tools.d.ts +2 -0
- package/dist/servertool/server-side-tools.js +2 -0
- package/dist/tools/tool-registry.js +195 -4
- package/package.json +1 -1
|
@@ -60,7 +60,26 @@ function shouldRunVisionFlow(ctx) {
|
|
|
60
60
|
if (followupFlag) {
|
|
61
61
|
return false;
|
|
62
62
|
}
|
|
63
|
-
|
|
63
|
+
const hasImageAttachment = record.hasImageAttachment === true || record.hasImageAttachment === 'true';
|
|
64
|
+
if (!hasImageAttachment) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
// 若当前已经使用具备内建多模态能力的 Provider(例如 Gemini/Claude/ChatGPT 路径),
|
|
68
|
+
// 且未显式 forceVision,则不再触发额外的 vision 二跳,避免同一轮请求跑两次。
|
|
69
|
+
const forceVision = record.forceVision === true || record.forceVision === 'true';
|
|
70
|
+
if (forceVision) {
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
const providerType = typeof record.providerType === 'string' ? record.providerType.toLowerCase() : '';
|
|
74
|
+
const providerProtocol = typeof record.providerProtocol === 'string' ? record.providerProtocol.toLowerCase() : '';
|
|
75
|
+
const inlineMultimodal = providerType === 'gemini' ||
|
|
76
|
+
providerType === 'responses' ||
|
|
77
|
+
providerProtocol === 'gemini-chat' ||
|
|
78
|
+
providerProtocol === 'openai-responses';
|
|
79
|
+
if (inlineMultimodal) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
return true;
|
|
64
83
|
}
|
|
65
84
|
function getCapturedRequest(adapterContext) {
|
|
66
85
|
if (!adapterContext || typeof adapterContext !== 'object') {
|
|
@@ -80,15 +99,15 @@ function buildVisionAnalysisPayload(source) {
|
|
|
80
99
|
if (typeof source.model === 'string' && source.model.trim()) {
|
|
81
100
|
payload.model = source.model.trim();
|
|
82
101
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
else {
|
|
102
|
+
const rawMessages = source.messages;
|
|
103
|
+
if (!Array.isArray(rawMessages) || !rawMessages.length) {
|
|
87
104
|
return null;
|
|
88
105
|
}
|
|
89
|
-
|
|
90
|
-
|
|
106
|
+
const visionMessages = buildVisionAnalysisMessages(rawMessages);
|
|
107
|
+
if (!visionMessages.length) {
|
|
108
|
+
return null;
|
|
91
109
|
}
|
|
110
|
+
payload.messages = visionMessages;
|
|
92
111
|
const parameters = source.parameters;
|
|
93
112
|
if (parameters && typeof parameters === 'object' && !Array.isArray(parameters)) {
|
|
94
113
|
const params = cloneJson(parameters);
|
|
@@ -96,6 +115,85 @@ function buildVisionAnalysisPayload(source) {
|
|
|
96
115
|
}
|
|
97
116
|
return payload;
|
|
98
117
|
}
|
|
118
|
+
function buildVisionAnalysisMessages(sourceMessages) {
|
|
119
|
+
const latestUser = extractLatestUserMessageForVision(sourceMessages);
|
|
120
|
+
if (!latestUser) {
|
|
121
|
+
return [];
|
|
122
|
+
}
|
|
123
|
+
const userMessage = buildVisionUserMessage(latestUser);
|
|
124
|
+
if (!userMessage) {
|
|
125
|
+
return [];
|
|
126
|
+
}
|
|
127
|
+
const messages = [];
|
|
128
|
+
const systemMessage = buildVisionSystemMessage();
|
|
129
|
+
if (systemMessage) {
|
|
130
|
+
messages.push(systemMessage);
|
|
131
|
+
}
|
|
132
|
+
messages.push(userMessage);
|
|
133
|
+
return messages;
|
|
134
|
+
}
|
|
135
|
+
function extractLatestUserMessageForVision(sourceMessages) {
|
|
136
|
+
for (let idx = sourceMessages.length - 1; idx >= 0; idx -= 1) {
|
|
137
|
+
const msg = sourceMessages[idx];
|
|
138
|
+
if (!msg || typeof msg !== 'object' || Array.isArray(msg)) {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
const role = msg.role;
|
|
142
|
+
if (typeof role === 'string' && role.trim().toLowerCase() === 'user') {
|
|
143
|
+
return cloneJson(msg);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
function buildVisionSystemMessage() {
|
|
149
|
+
const content = '你是一名专业的图像分析助手。无论输入是界面截图、文档、图表、代码编辑器、应用窗口还是普通照片,都需要先用结构化、详细的自然语言完整描述画面内容(关键区域、文字信息、布局层次、颜色与对比度、元素之间的关系等),然后总结出与用户任务最相关的关键信息和潜在问题,最后给出具体、可执行的改进建议或结论,避免泛泛而谈。';
|
|
150
|
+
return {
|
|
151
|
+
role: 'system',
|
|
152
|
+
content
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function buildVisionUserMessage(source) {
|
|
156
|
+
const roleRaw = source.role;
|
|
157
|
+
const role = typeof roleRaw === 'string' && roleRaw.trim().length
|
|
158
|
+
? roleRaw.trim()
|
|
159
|
+
: 'user';
|
|
160
|
+
const rawContent = source.content;
|
|
161
|
+
const message = { role };
|
|
162
|
+
if (Array.isArray(rawContent)) {
|
|
163
|
+
const textParts = [];
|
|
164
|
+
const imageParts = [];
|
|
165
|
+
for (const part of rawContent) {
|
|
166
|
+
if (!part || typeof part !== 'object' || Array.isArray(part)) {
|
|
167
|
+
textParts.push(part);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
const record = part;
|
|
171
|
+
const typeValue = typeof record.type === 'string' ? record.type.toLowerCase() : '';
|
|
172
|
+
if (typeValue.includes('image')) {
|
|
173
|
+
imageParts.push(part);
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
textParts.push(part);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
const combined = [];
|
|
180
|
+
if (textParts.length)
|
|
181
|
+
combined.push(...textParts);
|
|
182
|
+
if (imageParts.length)
|
|
183
|
+
combined.push(...imageParts);
|
|
184
|
+
if (!combined.length) {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
message.content = combined;
|
|
188
|
+
}
|
|
189
|
+
else if (typeof rawContent === 'string' && rawContent.trim().length) {
|
|
190
|
+
message.content = rawContent.trim();
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
return message;
|
|
196
|
+
}
|
|
99
197
|
function buildVisionFollowupPayload(source, summary) {
|
|
100
198
|
if (!source || typeof source !== 'object') {
|
|
101
199
|
return null;
|
|
@@ -2,6 +2,8 @@ import type { JsonObject } from '../conversion/hub/types/json.js';
|
|
|
2
2
|
import type { ServerSideToolEngineOptions, ServerSideToolEngineResult, ToolCall } from './types.js';
|
|
3
3
|
import './handlers/web-search.js';
|
|
4
4
|
import './handlers/vision.js';
|
|
5
|
+
import './handlers/gemini-empty-reply-continue.js';
|
|
6
|
+
import './handlers/stop-message-auto.js';
|
|
5
7
|
export declare function runServerSideToolEngine(options: ServerSideToolEngineOptions): Promise<ServerSideToolEngineResult>;
|
|
6
8
|
export declare function extractToolCalls(chatResponse: JsonObject): ToolCall[];
|
|
7
9
|
export declare function cloneJson<T>(value: T): T;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { getServerToolHandler, listAutoServerToolHandlers } from './registry.js';
|
|
2
2
|
import './handlers/web-search.js';
|
|
3
3
|
import './handlers/vision.js';
|
|
4
|
+
import './handlers/gemini-empty-reply-continue.js';
|
|
5
|
+
import './handlers/stop-message-auto.js';
|
|
4
6
|
export async function runServerSideToolEngine(options) {
|
|
5
7
|
const base = asObject(options.chatResponse);
|
|
6
8
|
if (!base) {
|
|
@@ -43,6 +43,164 @@ const toJson = (value) => {
|
|
|
43
43
|
return '{}';
|
|
44
44
|
}
|
|
45
45
|
};
|
|
46
|
+
const APPLY_PATCH_SECTION_RE = /^\*\*\*\s+(Add|Update|Delete|Copy|Move)\s+File:\s*(.+)$/;
|
|
47
|
+
const sanitizeRelativePath = (value) => {
|
|
48
|
+
if (!value)
|
|
49
|
+
return null;
|
|
50
|
+
const trimmed = value.trim().replace(/\\/g, '/');
|
|
51
|
+
if (!trimmed)
|
|
52
|
+
return null;
|
|
53
|
+
if (trimmed.startsWith('/'))
|
|
54
|
+
return null;
|
|
55
|
+
if (/^[a-zA-Z]:/.test(trimmed))
|
|
56
|
+
return null;
|
|
57
|
+
if (trimmed.includes('\0'))
|
|
58
|
+
return null;
|
|
59
|
+
if (trimmed.includes('..')) {
|
|
60
|
+
const segments = trimmed.split('/');
|
|
61
|
+
if (segments.some((seg) => seg === '..')) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return trimmed.replace(/\/{2,}/g, '/');
|
|
66
|
+
};
|
|
67
|
+
const collectExplicitPaths = (record) => {
|
|
68
|
+
const bucket = [];
|
|
69
|
+
const invalid = [];
|
|
70
|
+
const pushPath = (candidate) => {
|
|
71
|
+
const str = asString(candidate);
|
|
72
|
+
const normalized = sanitizeRelativePath(str);
|
|
73
|
+
if (normalized)
|
|
74
|
+
bucket.push(normalized);
|
|
75
|
+
else if (typeof candidate === 'string')
|
|
76
|
+
invalid.push(candidate);
|
|
77
|
+
};
|
|
78
|
+
const entries = [
|
|
79
|
+
record.paths,
|
|
80
|
+
record.path,
|
|
81
|
+
record.files,
|
|
82
|
+
record.file,
|
|
83
|
+
record.targets,
|
|
84
|
+
record.target_path
|
|
85
|
+
];
|
|
86
|
+
for (const entry of entries) {
|
|
87
|
+
if (!entry)
|
|
88
|
+
continue;
|
|
89
|
+
if (typeof entry === 'string') {
|
|
90
|
+
pushPath(entry);
|
|
91
|
+
}
|
|
92
|
+
else if (Array.isArray(entry)) {
|
|
93
|
+
entry.forEach((item) => {
|
|
94
|
+
if (typeof item === 'string')
|
|
95
|
+
pushPath(item);
|
|
96
|
+
else if (isRecord(item))
|
|
97
|
+
pushPath(item.path);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
else if (isRecord(entry)) {
|
|
101
|
+
pushPath(entry.path ?? entry.value);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return { paths: bucket, invalid };
|
|
105
|
+
};
|
|
106
|
+
const looksLikePatchBody = (text) => {
|
|
107
|
+
const trimmed = text.trim();
|
|
108
|
+
if (!trimmed)
|
|
109
|
+
return false;
|
|
110
|
+
const hunkRe = /^@@/m;
|
|
111
|
+
const diffLineRe = /^[ +-]/m;
|
|
112
|
+
return hunkRe.test(trimmed) || diffLineRe.test(trimmed);
|
|
113
|
+
};
|
|
114
|
+
const applyPathOverrides = (lines, overridePaths) => {
|
|
115
|
+
if (!overridePaths.length)
|
|
116
|
+
return;
|
|
117
|
+
const sections = [];
|
|
118
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
119
|
+
const match = lines[i].match(APPLY_PATCH_SECTION_RE);
|
|
120
|
+
if (match) {
|
|
121
|
+
sections.push({ index: i, action: match[1] });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (!sections.length) {
|
|
125
|
+
const beginIdx = lines.findIndex((line) => line.trim() === '*** Begin Patch');
|
|
126
|
+
if (beginIdx >= 0) {
|
|
127
|
+
lines.splice(beginIdx + 1, 0, `*** Update File: ${overridePaths[0]}`);
|
|
128
|
+
}
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const limit = Math.min(sections.length, overridePaths.length);
|
|
132
|
+
for (let i = 0; i < limit; i += 1) {
|
|
133
|
+
lines[sections[i].index] = `*** ${sections[i].action} File: ${overridePaths[i]}`;
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
const normalizeApplyPatchInput = (rawPatch, options) => {
|
|
137
|
+
if (typeof rawPatch !== 'string') {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
const text = rawPatch.replace(/\r\n/g, '\n');
|
|
141
|
+
const beginIdx = text.toLowerCase().indexOf('*** begin patch');
|
|
142
|
+
const endIdx = text.toLowerCase().lastIndexOf('*** end patch');
|
|
143
|
+
let candidate = '';
|
|
144
|
+
if (beginIdx >= 0 && endIdx >= 0 && endIdx > beginIdx) {
|
|
145
|
+
candidate = text.slice(beginIdx, endIdx + '*** End Patch'.length);
|
|
146
|
+
}
|
|
147
|
+
else if (options?.overridePaths && options.overridePaths.length) {
|
|
148
|
+
if (!looksLikePatchBody(text)) {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
const headerPath = options.overridePaths[0];
|
|
152
|
+
candidate = ['*** Begin Patch', `*** Update File: ${headerPath}`, text.trim(), '*** End Patch'].join('\n');
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
const lines = candidate.split(/\r?\n/);
|
|
158
|
+
if (!lines.length) {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
let firstIdx = 0;
|
|
162
|
+
while (firstIdx < lines.length && lines[firstIdx].trim().length === 0) {
|
|
163
|
+
firstIdx += 1;
|
|
164
|
+
}
|
|
165
|
+
if (firstIdx >= lines.length) {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
const firstLineRaw = lines[firstIdx];
|
|
169
|
+
const firstLineTrimmed = firstLineRaw.trim();
|
|
170
|
+
if (!firstLineTrimmed.toLowerCase().startsWith('*** begin patch')) {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
if (firstLineTrimmed !== '*** Begin Patch') {
|
|
174
|
+
lines[firstIdx] = '*** Begin Patch';
|
|
175
|
+
}
|
|
176
|
+
let lastIdx = lines.length - 1;
|
|
177
|
+
while (lastIdx > firstIdx && lines[lastIdx].trim().length === 0) {
|
|
178
|
+
lastIdx -= 1;
|
|
179
|
+
}
|
|
180
|
+
if (lastIdx > firstIdx) {
|
|
181
|
+
const lastLineTrimmed = lines[lastIdx].trim();
|
|
182
|
+
if (lastLineTrimmed.toLowerCase().startsWith('*** end patch') && lastLineTrimmed !== '*** End Patch') {
|
|
183
|
+
lines[lastIdx] = '*** End Patch';
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if (options?.overridePaths && options.overridePaths.length) {
|
|
187
|
+
applyPathOverrides(lines, options.overridePaths);
|
|
188
|
+
}
|
|
189
|
+
const normalized = lines.join('\n');
|
|
190
|
+
if (!normalized.includes('*** Begin Patch') || !normalized.includes('*** End Patch')) {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
return normalized;
|
|
194
|
+
};
|
|
195
|
+
const logApplyPatchValidatorError = (message) => {
|
|
196
|
+
try {
|
|
197
|
+
// eslint-disable-next-line no-console
|
|
198
|
+
console.error(`\x1b[31m[apply_patch][validator] ${message}\x1b[0m`);
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
/* ignore */
|
|
202
|
+
}
|
|
203
|
+
};
|
|
46
204
|
const detectForbiddenWrite = (script) => {
|
|
47
205
|
const normalized = script.toLowerCase();
|
|
48
206
|
if (!normalized) {
|
|
@@ -95,12 +253,45 @@ export function validateToolCall(name, argsString) {
|
|
|
95
253
|
const rawArgs = tryParseJson(typeof argsString === 'string' ? argsString : '{}');
|
|
96
254
|
switch (normalizedName) {
|
|
97
255
|
case 'apply_patch': {
|
|
98
|
-
const
|
|
99
|
-
const
|
|
100
|
-
|
|
256
|
+
const record = rawArgs;
|
|
257
|
+
const inputField = asString(record.input);
|
|
258
|
+
const patchField = asString(record.patch);
|
|
259
|
+
const rawStr = typeof argsString === 'string' ? argsString : '';
|
|
260
|
+
let patchRaw = null;
|
|
261
|
+
if (patchField) {
|
|
262
|
+
patchRaw = patchField;
|
|
263
|
+
}
|
|
264
|
+
else if (inputField) {
|
|
265
|
+
patchRaw = inputField;
|
|
266
|
+
}
|
|
267
|
+
else if (rawStr && /\*\*\*\s+Begin Patch/i.test(rawStr)) {
|
|
268
|
+
patchRaw = rawStr;
|
|
269
|
+
}
|
|
270
|
+
if (!patchRaw) {
|
|
271
|
+
logApplyPatchValidatorError('missing apply_patch patch/input payload');
|
|
101
272
|
return { ok: false, reason: 'missing_input' };
|
|
102
273
|
}
|
|
103
|
-
|
|
274
|
+
const { paths: explicitPaths, invalid } = collectExplicitPaths(record);
|
|
275
|
+
if (invalid.length) {
|
|
276
|
+
invalid.forEach((value) => logApplyPatchValidatorError(`invalid path argument: ${value}`));
|
|
277
|
+
}
|
|
278
|
+
const normalizedPatch = normalizeApplyPatchInput(patchRaw, { overridePaths: explicitPaths });
|
|
279
|
+
if (!normalizedPatch) {
|
|
280
|
+
logApplyPatchValidatorError('patch text missing *** Begin Patch/*** End Patch or failed to auto-wrap with provided paths');
|
|
281
|
+
return { ok: false, reason: 'invalid_patch_header' };
|
|
282
|
+
}
|
|
283
|
+
// Canonical, parameterized shape; keep input for backwards compatibility.
|
|
284
|
+
const payload = {
|
|
285
|
+
patch: normalizedPatch,
|
|
286
|
+
input: normalizedPatch
|
|
287
|
+
};
|
|
288
|
+
if (explicitPaths.length) {
|
|
289
|
+
payload.paths = explicitPaths;
|
|
290
|
+
}
|
|
291
|
+
return {
|
|
292
|
+
ok: true,
|
|
293
|
+
normalizedArgs: toJson(payload)
|
|
294
|
+
};
|
|
104
295
|
}
|
|
105
296
|
case 'shell': {
|
|
106
297
|
const rawCommand = rawArgs.command;
|