@jsonstudio/llms 0.6.473 → 0.6.568
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 +33 -4
- package/dist/conversion/codecs/openai-openai-codec.js +2 -1
- package/dist/conversion/codecs/responses-openai-codec.js +3 -2
- 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/actions/glm-history-image-trim.d.ts +2 -0
- package/dist/conversion/compat/actions/glm-history-image-trim.js +88 -0
- package/dist/conversion/compat/profiles/chat-gemini.json +15 -14
- package/dist/conversion/compat/profiles/chat-glm.json +194 -194
- package/dist/conversion/compat/profiles/chat-iflow.json +199 -199
- package/dist/conversion/compat/profiles/chat-lmstudio.json +43 -43
- package/dist/conversion/compat/profiles/chat-qwen.json +20 -20
- package/dist/conversion/compat/profiles/responses-c4m.json +42 -42
- 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.d.ts +6 -1
- package/dist/conversion/hub/pipeline/hub-pipeline.js +40 -13
- 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 +107 -26
- package/dist/conversion/hub/semantic-mappers/anthropic-mapper.js +8 -0
- package/dist/conversion/hub/semantic-mappers/gemini-mapper.js +28 -10
- package/dist/conversion/hub/semantic-mappers/responses-mapper.js +51 -2
- package/dist/conversion/hub/tool-session-compat.d.ts +26 -0
- package/dist/conversion/hub/tool-session-compat.js +299 -0
- package/dist/conversion/hub/types/chat-envelope.d.ts +1 -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/anthropic-message-utils.js +54 -0
- package/dist/conversion/shared/args-mapping.js +11 -3
- package/dist/conversion/shared/gemini-tool-utils.js +8 -0
- package/dist/conversion/shared/responses-output-builder.js +47 -88
- package/dist/conversion/shared/streaming-text-extractor.d.ts +25 -0
- package/dist/conversion/shared/streaming-text-extractor.js +31 -38
- package/dist/conversion/shared/text-markup-normalizer.js +42 -27
- package/dist/conversion/shared/tool-filter-pipeline.js +2 -1
- package/dist/conversion/shared/tool-governor.js +75 -4
- package/dist/conversion/shared/tool-harvester.js +43 -12
- package/dist/conversion/shared/tool-mapping.d.ts +1 -0
- package/dist/conversion/shared/tool-mapping.js +33 -13
- package/dist/filters/index.d.ts +1 -0
- package/dist/filters/index.js +1 -0
- package/dist/filters/special/request-toolcalls-stringify.js +5 -55
- package/dist/filters/special/request-tools-normalize.js +14 -23
- package/dist/filters/special/response-apply-patch-toon-decode.d.ts +23 -0
- package/dist/filters/special/response-apply-patch-toon-decode.js +109 -0
- package/dist/filters/special/response-tool-arguments-toon-decode.d.ts +10 -0
- package/dist/filters/special/response-tool-arguments-toon-decode.js +55 -13
- package/dist/guidance/index.js +70 -27
- package/dist/router/virtual-router/bootstrap.js +10 -5
- package/dist/router/virtual-router/classifier.js +9 -4
- package/dist/router/virtual-router/engine-health.d.ts +22 -0
- package/dist/router/virtual-router/engine-health.js +423 -0
- package/dist/router/virtual-router/engine-logging.d.ts +20 -0
- package/dist/router/virtual-router/engine-logging.js +197 -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 +21 -14
- package/dist/router/virtual-router/engine.js +200 -523
- package/dist/router/virtual-router/message-utils.js +22 -0
- package/dist/router/virtual-router/routing-instructions.d.ts +8 -1
- package/dist/router/virtual-router/routing-instructions.js +137 -3
- package/dist/router/virtual-router/tool-signals.js +57 -11
- package/dist/router/virtual-router/types.d.ts +30 -0
- package/dist/router/virtual-router/types.js +1 -1
- package/dist/servertool/engine.js +3 -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/iflow-model-error-retry.d.ts +1 -0
- package/dist/servertool/handlers/iflow-model-error-retry.js +93 -0
- package/dist/servertool/handlers/stop-message-auto.d.ts +1 -0
- package/dist/servertool/handlers/stop-message-auto.js +204 -0
- package/dist/servertool/handlers/vision.js +105 -7
- package/dist/servertool/server-side-tools.d.ts +3 -0
- package/dist/servertool/server-side-tools.js +29 -0
- package/dist/sse/sse-to-json/builders/anthropic-response-builder.js +16 -0
- package/dist/tools/apply-patch-structured.d.ts +20 -0
- package/dist/tools/apply-patch-structured.js +239 -0
- package/dist/tools/tool-description-utils.d.ts +5 -0
- package/dist/tools/tool-description-utils.js +50 -0
- package/dist/tools/tool-registry.js +14 -5
- package/package.json +2 -2
|
@@ -360,7 +360,9 @@ export function buildOpenAIChatFromGeminiResponse(payload) {
|
|
|
360
360
|
if (Number.isFinite(totalTokens))
|
|
361
361
|
usage.total_tokens = totalTokens;
|
|
362
362
|
const combinedText = textParts.join('\n');
|
|
363
|
-
const normalized = combinedText.length
|
|
363
|
+
const normalized = combinedText.length
|
|
364
|
+
? normalizeChatMessageContent(combinedText)
|
|
365
|
+
: { contentText: undefined, reasoningText: undefined };
|
|
364
366
|
const baseContent = normalized.contentText ?? combinedText ?? '';
|
|
365
367
|
const toolResultBlock = toolResultTexts.length ? toolResultTexts.join('\n') : '';
|
|
366
368
|
const finalContent = toolResultBlock && baseContent
|
|
@@ -525,16 +527,43 @@ export function buildGeminiFromOpenAIChat(chatResp) {
|
|
|
525
527
|
let argsStruct;
|
|
526
528
|
const rawArgs = fn.arguments;
|
|
527
529
|
if (typeof rawArgs === 'string') {
|
|
530
|
+
const trimmed = rawArgs.trim();
|
|
531
|
+
if (trimmed.startsWith('{')) {
|
|
532
|
+
try {
|
|
533
|
+
const parsed = JSON.parse(rawArgs);
|
|
534
|
+
if (isObject(parsed)) {
|
|
535
|
+
argsStruct = parsed;
|
|
536
|
+
}
|
|
537
|
+
else {
|
|
538
|
+
argsStruct = { _raw: rawArgs };
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
catch {
|
|
542
|
+
argsStruct = { _raw: rawArgs };
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
else {
|
|
546
|
+
argsStruct = { _raw: rawArgs };
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
else if (isObject(rawArgs)) {
|
|
550
|
+
argsStruct = rawArgs;
|
|
551
|
+
}
|
|
552
|
+
else if (Array.isArray(rawArgs)) {
|
|
528
553
|
try {
|
|
529
|
-
argsStruct = JSON.
|
|
554
|
+
argsStruct = { _raw: JSON.stringify(rawArgs) };
|
|
530
555
|
}
|
|
531
556
|
catch {
|
|
532
|
-
argsStruct = { _raw: rawArgs };
|
|
557
|
+
argsStruct = { _raw: String(rawArgs) };
|
|
533
558
|
}
|
|
534
559
|
}
|
|
560
|
+
else if (rawArgs != null) {
|
|
561
|
+
argsStruct = { _raw: String(rawArgs) };
|
|
562
|
+
}
|
|
535
563
|
else {
|
|
536
|
-
argsStruct =
|
|
564
|
+
argsStruct = {};
|
|
537
565
|
}
|
|
566
|
+
// Gemini request/response wire uses `args` for functionCall payload.
|
|
538
567
|
const functionCall = { name, args: argsStruct };
|
|
539
568
|
const id = typeof tc.id === 'string' ? String(tc.id) : undefined;
|
|
540
569
|
if (id)
|
|
@@ -89,8 +89,9 @@ export class OpenAIOpenAIConversionCodec {
|
|
|
89
89
|
// Response-side filters (idempotent w.r.t existing logic)
|
|
90
90
|
engine.registerFilter(new ResponseToolTextCanonicalizeFilter()); // response_pre
|
|
91
91
|
try {
|
|
92
|
-
const { ResponseToolArgumentsToonDecodeFilter } = await import('../../filters/index.js');
|
|
92
|
+
const { ResponseToolArgumentsToonDecodeFilter, ResponseApplyPatchToonDecodeFilter } = await import('../../filters/index.js');
|
|
93
93
|
engine.registerFilter(new ResponseToolArgumentsToonDecodeFilter()); // response_pre, runs before stringify
|
|
94
|
+
engine.registerFilter(new ResponseApplyPatchToonDecodeFilter()); // response_pre, runs before stringify
|
|
94
95
|
}
|
|
95
96
|
catch { /* optional */ }
|
|
96
97
|
engine.registerFilter(new ResponseToolArgumentsStringifyFilter()); // response_post
|
|
@@ -147,11 +147,12 @@ export class ResponsesOpenAIConversionCodec {
|
|
|
147
147
|
debug: { emit: () => { } }
|
|
148
148
|
};
|
|
149
149
|
const engine = new FilterEngine();
|
|
150
|
-
// Response-side filters:文本标准化 → TOON decode(可选)→ shell/basics → finish_reason 不变式
|
|
150
|
+
// Response-side filters:文本标准化 → TOON decode(可选)→ apply_patch 结构化补丁规范化 → shell/basics → finish_reason 不变式
|
|
151
151
|
engine.registerFilter(new ResponseToolTextCanonicalizeFilter()); // response_pre
|
|
152
152
|
try {
|
|
153
|
-
const { ResponseToolArgumentsToonDecodeFilter } = await import('../../filters/index.js');
|
|
153
|
+
const { ResponseToolArgumentsToonDecodeFilter, ResponseApplyPatchToonDecodeFilter } = await import('../../filters/index.js');
|
|
154
154
|
engine.registerFilter(new ResponseToolArgumentsToonDecodeFilter()); // response_pre, runs before stringify
|
|
155
|
+
engine.registerFilter(new ResponseApplyPatchToonDecodeFilter()); // response_pre, runs before stringify
|
|
155
156
|
}
|
|
156
157
|
catch { /* optional */ }
|
|
157
158
|
engine.registerFilter(new ResponseToolArgumentsStringifyFilter()); // response_post
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { JsonObject } from '../../hub/types/json.js';
|
|
2
|
+
import type { AdapterContext } from '../../hub/types/chat-envelope.js';
|
|
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;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
2
|
+
/**
|
|
3
|
+
* Compat for Claude models routed via antigravity on gemini-chat.
|
|
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;
|
|
72
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
2
|
+
function shouldDropInlineImagePart(part) {
|
|
3
|
+
const rawType = typeof part.type === 'string' ? part.type.toLowerCase() : '';
|
|
4
|
+
if (rawType !== 'image' && rawType !== 'image_url' && rawType !== 'input_image') {
|
|
5
|
+
return false;
|
|
6
|
+
}
|
|
7
|
+
const imageUrlBlock = isRecord(part.image_url)
|
|
8
|
+
? part.image_url
|
|
9
|
+
: part;
|
|
10
|
+
const urlRaw = typeof imageUrlBlock.url === 'string'
|
|
11
|
+
? imageUrlBlock.url
|
|
12
|
+
: typeof imageUrlBlock.data === 'string'
|
|
13
|
+
? imageUrlBlock.data
|
|
14
|
+
: '';
|
|
15
|
+
const url = urlRaw.trim();
|
|
16
|
+
if (!url) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
// GLM 4.7 在历史消息中携带 data:image/base64 时会返回 1210,
|
|
20
|
+
// 因此仅在历史中丢弃这类 inline image 片段。
|
|
21
|
+
return url.startsWith('data:image');
|
|
22
|
+
}
|
|
23
|
+
export function applyGlmHistoryImageTrim(payload) {
|
|
24
|
+
const root = structuredClone(payload);
|
|
25
|
+
const modelRaw = root.model;
|
|
26
|
+
const modelId = typeof modelRaw === 'string' ? modelRaw.trim().toLowerCase() : '';
|
|
27
|
+
if (!modelId || !modelId.startsWith('glm-4.7')) {
|
|
28
|
+
return root;
|
|
29
|
+
}
|
|
30
|
+
const messagesValue = root.messages;
|
|
31
|
+
if (!Array.isArray(messagesValue)) {
|
|
32
|
+
return root;
|
|
33
|
+
}
|
|
34
|
+
const messages = messagesValue.filter(msg => isRecord(msg));
|
|
35
|
+
if (!messages.length) {
|
|
36
|
+
return root;
|
|
37
|
+
}
|
|
38
|
+
// 仅在历史消息中进行裁剪:保留最后一条 user 完整内容。
|
|
39
|
+
let lastUserIdx = -1;
|
|
40
|
+
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
41
|
+
const msg = messages[i];
|
|
42
|
+
const role = typeof msg.role === 'string' ? msg.role.toLowerCase() : '';
|
|
43
|
+
if (role === 'user') {
|
|
44
|
+
lastUserIdx = i;
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (lastUserIdx === -1) {
|
|
49
|
+
return root;
|
|
50
|
+
}
|
|
51
|
+
const nextMessages = [];
|
|
52
|
+
for (let i = 0; i < messages.length; i += 1) {
|
|
53
|
+
const msg = messages[i];
|
|
54
|
+
const role = typeof msg.role === 'string' ? msg.role.toLowerCase() : '';
|
|
55
|
+
if (i < lastUserIdx && role === 'user') {
|
|
56
|
+
const contentValue = msg.content;
|
|
57
|
+
if (!Array.isArray(contentValue)) {
|
|
58
|
+
nextMessages.push(msg);
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
const newContent = [];
|
|
62
|
+
for (const part of contentValue) {
|
|
63
|
+
if (!isRecord(part)) {
|
|
64
|
+
newContent.push(part);
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (shouldDropInlineImagePart(part)) {
|
|
68
|
+
// 丢弃历史中的 data:image/* 片段
|
|
69
|
+
// eslint-disable-next-line no-continue
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
newContent.push(part);
|
|
73
|
+
}
|
|
74
|
+
if (!newContent.length) {
|
|
75
|
+
// 历史消息只剩下 inline image 时,直接移除整条消息。
|
|
76
|
+
// 避免向 GLM 发送纯图片历史导致 1210。
|
|
77
|
+
// eslint-disable-next-line no-continue
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const cloned = { ...msg, content: newContent };
|
|
81
|
+
nextMessages.push(cloned);
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
nextMessages.push(msg);
|
|
85
|
+
}
|
|
86
|
+
root.messages = nextMessages;
|
|
87
|
+
return root;
|
|
88
|
+
}
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
2
|
+
"id": "chat:gemini",
|
|
3
|
+
"protocol": "gemini-chat",
|
|
4
|
+
"request": {
|
|
5
|
+
"mappings": [
|
|
6
|
+
{ "action": "snapshot", "phase": "compat-pre" },
|
|
7
|
+
{ "action": "claude_thinking_tool_schema" },
|
|
8
|
+
{
|
|
9
|
+
"action": "gemini_web_search_request"
|
|
10
|
+
},
|
|
11
|
+
{ "action": "snapshot", "phase": "compat-post" }
|
|
12
|
+
]
|
|
13
|
+
},
|
|
14
|
+
"response": {
|
|
15
|
+
"mappings": []
|
|
16
|
+
}
|
|
16
17
|
}
|