@jsonstudio/llms 0.6.1462 → 0.6.1733
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 +6 -1
- package/dist/conversion/compat/actions/anthropic-claude-code-system-prompt.d.ts +4 -7
- package/dist/conversion/compat/actions/anthropic-claude-code-system-prompt.js +140 -21
- package/dist/conversion/compat/actions/antigravity-thought-signature-cache.js +68 -10
- package/dist/conversion/compat/actions/antigravity-thought-signature-prepare.js +151 -23
- package/dist/conversion/compat/actions/gemini-cli-request.js +72 -13
- package/dist/conversion/compat/actions/harvest-tool-calls-from-text.d.ts +10 -0
- package/dist/conversion/compat/actions/harvest-tool-calls-from-text.js +121 -0
- package/dist/conversion/compat/actions/iflow-kimi-cli-defaults.d.ts +10 -0
- package/dist/conversion/compat/actions/iflow-kimi-cli-defaults.js +80 -0
- package/dist/conversion/compat/actions/iflow-kimi-history-media-placeholder.d.ts +7 -0
- package/dist/conversion/compat/actions/iflow-kimi-history-media-placeholder.js +161 -0
- package/dist/conversion/compat/actions/iflow-kimi-thinking-reasoning-fill.d.ts +12 -0
- package/dist/conversion/compat/actions/iflow-kimi-thinking-reasoning-fill.js +67 -0
- package/dist/conversion/compat/actions/iflow-response-body-unwrap.d.ts +9 -0
- package/dist/conversion/compat/actions/iflow-response-body-unwrap.js +140 -0
- package/dist/conversion/compat/actions/lmstudio-responses-fc-ids.d.ts +10 -0
- package/dist/conversion/compat/actions/lmstudio-responses-fc-ids.js +59 -0
- package/dist/conversion/compat/actions/lmstudio-responses-input-stringify.d.ts +14 -0
- package/dist/conversion/compat/actions/lmstudio-responses-input-stringify.js +125 -0
- package/dist/conversion/compat/actions/normalize-tool-call-ids.d.ts +11 -0
- package/dist/conversion/compat/actions/normalize-tool-call-ids.js +140 -0
- package/dist/conversion/compat/actions/strip-orphan-function-calls-tag.d.ts +2 -0
- package/dist/conversion/compat/actions/strip-orphan-function-calls-tag.js +152 -0
- package/dist/conversion/compat/antigravity-session-signature.d.ts +57 -3
- package/dist/conversion/compat/antigravity-session-signature.js +821 -27
- package/dist/conversion/compat/profiles/anthropic-claude-code.json +0 -9
- package/dist/conversion/compat/profiles/chat-gemini-cli.json +1 -0
- package/dist/conversion/compat/profiles/chat-iflow.json +6 -0
- package/dist/conversion/compat/profiles/chat-lmstudio.json +7 -1
- package/dist/conversion/hub/operation-table/operation-table-runner.js +1 -1
- package/dist/conversion/hub/operation-table/semantic-mappers/gemini-mapper.js +52 -10
- package/dist/conversion/hub/pipeline/compat/compat-pipeline-executor.js +102 -6
- package/dist/conversion/hub/pipeline/compat/compat-profile-resolver.d.ts +2 -0
- package/dist/conversion/hub/pipeline/compat/compat-profile-resolver.js +63 -0
- package/dist/conversion/hub/pipeline/compat/compat-profile-store.js +12 -3
- package/dist/conversion/hub/pipeline/compat/compat-types.d.ts +18 -0
- package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +1 -0
- package/dist/conversion/hub/pipeline/hub-pipeline.js +25 -1
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.js +20 -0
- package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/index.js +8 -5
- package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage3_compat/index.js +5 -1
- package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage1_sse_decode/index.js +113 -0
- package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.js +26 -1
- package/dist/conversion/hub/pipeline/target-utils.js +3 -0
- package/dist/conversion/hub/process/chat-process.js +300 -67
- package/dist/conversion/hub/response/provider-response.js +31 -4
- package/dist/conversion/responses/responses-openai-bridge.js +32 -6
- package/dist/conversion/shared/anthropic-message-utils.js +20 -5
- package/dist/conversion/shared/bridge-id-utils.d.ts +2 -0
- package/dist/conversion/shared/bridge-id-utils.js +52 -15
- package/dist/conversion/shared/gemini-tool-utils.js +134 -9
- package/dist/conversion/shared/responses-conversation-store.js +40 -5
- package/dist/conversion/shared/responses-output-builder.js +23 -7
- package/dist/conversion/shared/responses-tool-utils.d.ts +1 -0
- package/dist/conversion/shared/responses-tool-utils.js +30 -13
- package/dist/conversion/shared/text-markup-normalizer.d.ts +1 -0
- package/dist/conversion/shared/text-markup-normalizer.js +359 -2
- package/dist/conversion/shared/thought-signature-validator.d.ts +1 -1
- package/dist/conversion/shared/thought-signature-validator.js +2 -1
- package/dist/quota/apikey-reset.d.ts +17 -0
- package/dist/quota/apikey-reset.js +43 -0
- package/dist/quota/index.d.ts +2 -0
- package/dist/quota/index.js +1 -0
- package/dist/quota/quota-manager.d.ts +44 -0
- package/dist/quota/quota-manager.js +491 -0
- package/dist/quota/quota-state.d.ts +6 -0
- package/dist/quota/quota-state.js +167 -0
- package/dist/quota/types.d.ts +61 -0
- package/dist/quota/types.js +1 -0
- package/dist/router/virtual-router/bootstrap.js +134 -13
- package/dist/router/virtual-router/classifier.js +1 -1
- package/dist/router/virtual-router/engine/antigravity/alias-lease.d.ts +33 -0
- package/dist/router/virtual-router/engine/antigravity/alias-lease.js +247 -0
- package/dist/router/virtual-router/engine/health/index.d.ts +23 -0
- package/dist/router/virtual-router/engine/health/index.js +720 -0
- package/dist/router/virtual-router/engine/provider-key/parse.d.ts +6 -0
- package/dist/router/virtual-router/engine/provider-key/parse.js +43 -0
- package/dist/router/virtual-router/engine/routing-pools/index.d.ts +13 -0
- package/dist/router/virtual-router/engine/routing-pools/index.js +225 -0
- package/dist/router/virtual-router/engine/routing-state/keys.d.ts +3 -0
- package/dist/router/virtual-router/engine/routing-state/keys.js +30 -0
- package/dist/router/virtual-router/engine/routing-state/metadata.d.ts +6 -0
- package/dist/router/virtual-router/engine/routing-state/metadata.js +132 -0
- package/dist/router/virtual-router/engine/routing-state/store.d.ts +11 -0
- package/dist/router/virtual-router/engine/routing-state/store.js +107 -0
- package/dist/router/virtual-router/engine-health.d.ts +1 -23
- package/dist/router/virtual-router/engine-health.js +1 -616
- package/dist/router/virtual-router/engine-selection/route-utils.js +57 -0
- package/dist/router/virtual-router/engine-selection/selection-deps.d.ts +18 -0
- package/dist/router/virtual-router/engine-selection/tier-priority.d.ts +1 -2
- package/dist/router/virtual-router/engine-selection/tier-priority.js +2 -2
- package/dist/router/virtual-router/engine-selection/tier-selection-select.js +39 -55
- package/dist/router/virtual-router/engine-selection/tier-selection.js +284 -23
- package/dist/router/virtual-router/engine-selection.d.ts +1 -13
- package/dist/router/virtual-router/engine-selection.js +1 -225
- package/dist/router/virtual-router/engine.d.ts +8 -14
- package/dist/router/virtual-router/engine.js +187 -382
- package/dist/router/virtual-router/features.js +20 -2
- package/dist/router/virtual-router/message-utils.js +15 -5
- package/dist/router/virtual-router/success-center.d.ts +10 -0
- package/dist/router/virtual-router/success-center.js +32 -0
- package/dist/router/virtual-router/types.d.ts +48 -0
- package/dist/servertool/clock/config.d.ts +2 -0
- package/dist/servertool/clock/config.js +10 -2
- package/dist/servertool/clock/daemon.js +3 -0
- package/dist/servertool/clock/ntp.d.ts +18 -0
- package/dist/servertool/clock/ntp.js +318 -0
- package/dist/servertool/clock/paths.d.ts +1 -0
- package/dist/servertool/clock/paths.js +3 -0
- package/dist/servertool/clock/state.d.ts +2 -0
- package/dist/servertool/clock/state.js +15 -2
- package/dist/servertool/clock/tasks.d.ts +1 -0
- package/dist/servertool/clock/tasks.js +24 -1
- package/dist/servertool/clock/types.d.ts +21 -0
- package/dist/servertool/engine.js +109 -5
- package/dist/servertool/handlers/antigravity-thought-signature-bootstrap.d.ts +1 -0
- package/dist/servertool/handlers/antigravity-thought-signature-bootstrap.js +201 -0
- package/dist/servertool/handlers/clock-auto.js +39 -4
- package/dist/servertool/handlers/clock.js +145 -16
- package/dist/servertool/handlers/followup-request-builder.js +84 -0
- package/dist/servertool/handlers/gemini-empty-reply-continue.js +48 -47
- package/dist/servertool/handlers/stop-message-auto.js +3 -3
- package/dist/servertool/handlers/vision.js +10 -0
- package/dist/servertool/server-side-tools.d.ts +1 -0
- package/dist/servertool/server-side-tools.js +1 -0
- package/dist/servertool/types.d.ts +2 -0
- package/dist/sse/sse-to-json/builders/response-builder.js +6 -0
- package/dist/sse/sse-to-json/chat-sse-to-json-converter.js +32 -2
- package/dist/sse/sse-to-json/parsers/sse-parser.js +34 -0
- package/dist/sse/sse-to-json/responses-sse-to-json-converter.d.ts +1 -0
- package/dist/sse/sse-to-json/responses-sse-to-json-converter.js +33 -1
- package/dist/tools/apply-patch/args-normalizer/default-actions.d.ts +2 -0
- package/dist/tools/apply-patch/args-normalizer/default-actions.js +12 -0
- package/dist/tools/apply-patch/args-normalizer/extract-patch.d.ts +2 -0
- package/dist/tools/apply-patch/args-normalizer/extract-patch.js +15 -0
- package/dist/tools/apply-patch/args-normalizer/index.d.ts +2 -0
- package/dist/tools/apply-patch/args-normalizer/index.js +164 -0
- package/dist/tools/apply-patch/args-normalizer/structured-builders.d.ts +7 -0
- package/dist/tools/apply-patch/args-normalizer/structured-builders.js +85 -0
- package/dist/tools/apply-patch/args-normalizer/types.d.ts +54 -0
- package/dist/tools/apply-patch/args-normalizer/types.js +1 -0
- package/dist/tools/apply-patch/execution-capturer.js +24 -3
- package/dist/tools/apply-patch/patch-text/looks-like-patch.js +1 -0
- package/dist/tools/apply-patch/patch-text/normalize.js +104 -5
- package/dist/tools/apply-patch/structured/coercion.js +28 -4
- package/dist/tools/apply-patch/validator.js +7 -146
- package/package.json +3 -2
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
2
|
+
function looksLikeKnownProviderResponseShape(value) {
|
|
3
|
+
if (!isRecord(value))
|
|
4
|
+
return false;
|
|
5
|
+
if (Array.isArray(value.choices))
|
|
6
|
+
return true; // openai-chat
|
|
7
|
+
if (Array.isArray(value.output) || value.object === 'response')
|
|
8
|
+
return true; // openai-responses
|
|
9
|
+
if (typeof value.type === 'string' && String(value.type).toLowerCase() === 'message' && Array.isArray(value.content)) {
|
|
10
|
+
return true; // anthropic-messages
|
|
11
|
+
}
|
|
12
|
+
if (Array.isArray(value.candidates))
|
|
13
|
+
return true; // gemini-chat
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
function stripJsonTextPrefix(text) {
|
|
17
|
+
let out = String(text || '').trimStart();
|
|
18
|
+
// anti-XSSI prefix: ")]}',\n{...}"
|
|
19
|
+
out = out.replace(/^\)\]\}',?\s*/u, '');
|
|
20
|
+
// single-line SSE-like wrapper sometimes returned as plain text
|
|
21
|
+
out = out.replace(/^data:\s*/iu, '');
|
|
22
|
+
return out;
|
|
23
|
+
}
|
|
24
|
+
function tryParseJsonRecord(text) {
|
|
25
|
+
try {
|
|
26
|
+
const trimmed = stripJsonTextPrefix(text).trim();
|
|
27
|
+
if (!trimmed)
|
|
28
|
+
return null;
|
|
29
|
+
if (trimmed.length > 10 * 1024 * 1024)
|
|
30
|
+
return null;
|
|
31
|
+
const first = trimmed.charAt(0);
|
|
32
|
+
const last = trimmed.charAt(trimmed.length - 1);
|
|
33
|
+
if ((first !== '{' && first !== '[') || (last !== '}' && last !== ']')) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
const parsed = JSON.parse(trimmed);
|
|
37
|
+
return isRecord(parsed) ? parsed : null;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function tryParseJsonRecordFromMaybeSseText(text) {
|
|
44
|
+
if (typeof text !== 'string') {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
const raw = stripJsonTextPrefix(text);
|
|
48
|
+
const direct = tryParseJsonRecord(raw);
|
|
49
|
+
if (direct) {
|
|
50
|
+
return direct;
|
|
51
|
+
}
|
|
52
|
+
// Multi-line SSE text occasionally shows up as a plain string inside iFlow envelopes.
|
|
53
|
+
// Best-effort: parse the last valid `data:` JSON object chunk.
|
|
54
|
+
if (raw.length > 10 * 1024 * 1024) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
const lines = raw.split(/\r?\n/u);
|
|
58
|
+
for (let i = lines.length - 1; i >= 0; i -= 1) {
|
|
59
|
+
const line = lines[i]?.trim();
|
|
60
|
+
if (!line)
|
|
61
|
+
continue;
|
|
62
|
+
const lower = line.toLowerCase();
|
|
63
|
+
if (lower === 'data: [done]' || lower === '[done]')
|
|
64
|
+
continue;
|
|
65
|
+
const candidate = lower.startsWith('data:') ? line.slice(5).trim() : line;
|
|
66
|
+
const parsed = tryParseJsonRecord(candidate);
|
|
67
|
+
if (parsed) {
|
|
68
|
+
return parsed;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
function tryUnwrapKnownShapeFromBody(value, depth) {
|
|
74
|
+
if (depth < 0)
|
|
75
|
+
return null;
|
|
76
|
+
if (looksLikeKnownProviderResponseShape(value)) {
|
|
77
|
+
return value;
|
|
78
|
+
}
|
|
79
|
+
if (isRecord(value)) {
|
|
80
|
+
// nested body/data wrappers
|
|
81
|
+
for (const key of ['data', 'body', 'response', 'payload', 'result']) {
|
|
82
|
+
const nested = value[key];
|
|
83
|
+
const unwrapped = tryUnwrapKnownShapeFromBody(nested, depth - 1);
|
|
84
|
+
if (unwrapped)
|
|
85
|
+
return unwrapped;
|
|
86
|
+
}
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
if (typeof value === 'string') {
|
|
90
|
+
const parsed = tryParseJsonRecord(value) ?? tryParseJsonRecordFromMaybeSseText(value);
|
|
91
|
+
if (!parsed)
|
|
92
|
+
return null;
|
|
93
|
+
return tryUnwrapKnownShapeFromBody(parsed, depth - 1);
|
|
94
|
+
}
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* iFlow compatibility: some iFlow backends wrap OpenAI-compatible payloads inside:
|
|
99
|
+
* { status, msg, body, request_id }
|
|
100
|
+
* where `body` is the actual OpenAI-chat/Responses JSON (or a JSON string).
|
|
101
|
+
*
|
|
102
|
+
* This action unwraps `body` so hub semantic mapping and tool harvesting can proceed.
|
|
103
|
+
*/
|
|
104
|
+
export function unwrapIflowResponseBodyEnvelope(payload) {
|
|
105
|
+
try {
|
|
106
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
|
|
107
|
+
return payload;
|
|
108
|
+
}
|
|
109
|
+
const root = structuredClone(payload);
|
|
110
|
+
// Only unwrap when it looks like the iFlow envelope.
|
|
111
|
+
if (!('body' in root) || !('status' in root) || !('msg' in root)) {
|
|
112
|
+
return root;
|
|
113
|
+
}
|
|
114
|
+
// If the root is already a known provider response, don't touch it.
|
|
115
|
+
if (looksLikeKnownProviderResponseShape(root)) {
|
|
116
|
+
return root;
|
|
117
|
+
}
|
|
118
|
+
const body = root.body;
|
|
119
|
+
const unwrapped = tryUnwrapKnownShapeFromBody(body, 6);
|
|
120
|
+
if (unwrapped) {
|
|
121
|
+
return unwrapped;
|
|
122
|
+
}
|
|
123
|
+
// Fallback: unwrap to `body` even when we can't yet recognize the nested shape.
|
|
124
|
+
// This avoids treating the iFlow envelope as the provider payload and improves
|
|
125
|
+
// downstream diagnostics / error handling.
|
|
126
|
+
if (isRecord(body)) {
|
|
127
|
+
return body;
|
|
128
|
+
}
|
|
129
|
+
if (typeof body === 'string') {
|
|
130
|
+
const parsed = tryParseJsonRecordFromMaybeSseText(body);
|
|
131
|
+
if (parsed) {
|
|
132
|
+
return parsed;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return root;
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return payload;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
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
|
+
export declare function enforceLmstudioResponsesFcToolCallIds(payload: JsonObject): JsonObject;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { normalizeFunctionCallId, normalizeFunctionCallOutputId, normalizeResponsesCallId } from '../../shared/bridge-id-utils.js';
|
|
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
|
+
*/
|
|
17
|
+
export function enforceLmstudioResponsesFcToolCallIds(payload) {
|
|
18
|
+
try {
|
|
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
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { JsonObject } from '../../hub/types/json.js';
|
|
2
|
+
import type { AdapterContext } from '../../hub/types/chat-envelope.js';
|
|
3
|
+
/**
|
|
4
|
+
* Legacy compatibility shim:
|
|
5
|
+
* Some older LM Studio builds rejected the array form of `input` ("Invalid type for 'input'").
|
|
6
|
+
* Convert canonical Responses input items into a single `input` string.
|
|
7
|
+
*
|
|
8
|
+
* ⚠️ Default is OFF (modern LM Studio accepts array input). Enable only if you hit that legacy error:
|
|
9
|
+
* - `LLMSWITCH_LMSTUDIO_STRINGIFY_INPUT=1`
|
|
10
|
+
* - or `ROUTECODEX_LMSTUDIO_STRINGIFY_INPUT=1`
|
|
11
|
+
*
|
|
12
|
+
* This is applied via compat profile `chat:lmstudio` and only when `providerProtocol === 'openai-responses'`.
|
|
13
|
+
*/
|
|
14
|
+
export declare function stringifyLmstudioResponsesInput(payload: JsonObject, adapterContext?: AdapterContext): JsonObject;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
function isRecord(value) {
|
|
2
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
3
|
+
}
|
|
4
|
+
function extractTextParts(content) {
|
|
5
|
+
const out = [];
|
|
6
|
+
if (typeof content === 'string' && content.trim().length) {
|
|
7
|
+
out.push(content.trim());
|
|
8
|
+
return out;
|
|
9
|
+
}
|
|
10
|
+
if (!Array.isArray(content)) {
|
|
11
|
+
return out;
|
|
12
|
+
}
|
|
13
|
+
for (const part of content) {
|
|
14
|
+
if (!isRecord(part))
|
|
15
|
+
continue;
|
|
16
|
+
const type = typeof part.type === 'string' ? String(part.type).trim().toLowerCase() : '';
|
|
17
|
+
const text = typeof part.text === 'string'
|
|
18
|
+
? part.text
|
|
19
|
+
: typeof part.content === 'string'
|
|
20
|
+
? String(part.content)
|
|
21
|
+
: undefined;
|
|
22
|
+
if (typeof text === 'string' && text.trim().length) {
|
|
23
|
+
out.push(text.trim());
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
// OpenAI Responses content parts often use { type: 'input_text'|'output_text', text: '...' }.
|
|
27
|
+
if ((type === 'input_text' || type === 'output_text') && typeof part.text === 'string') {
|
|
28
|
+
const t = String(part.text).trim();
|
|
29
|
+
if (t.length)
|
|
30
|
+
out.push(t);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
function stringifyInputItems(input) {
|
|
36
|
+
if (!Array.isArray(input))
|
|
37
|
+
return null;
|
|
38
|
+
const chunks = [];
|
|
39
|
+
for (const item of input) {
|
|
40
|
+
if (!isRecord(item))
|
|
41
|
+
continue;
|
|
42
|
+
const type = typeof item.type === 'string' ? String(item.type).trim().toLowerCase() : '';
|
|
43
|
+
const roleCandidate = typeof item.role === 'string' ? String(item.role).trim() : '';
|
|
44
|
+
const messageNode = isRecord(item.message) ? item.message : undefined;
|
|
45
|
+
const nestedRoleCandidate = messageNode && typeof messageNode.role === 'string' ? String(messageNode.role).trim() : '';
|
|
46
|
+
// OpenAI Responses supports message-like items without an explicit `type` field:
|
|
47
|
+
// { role: 'user'|'assistant'|'system', content: [...] }
|
|
48
|
+
if (type === 'message' || (!type && (roleCandidate || nestedRoleCandidate))) {
|
|
49
|
+
const role = roleCandidate ||
|
|
50
|
+
nestedRoleCandidate ||
|
|
51
|
+
'user';
|
|
52
|
+
const contentNode = item.content !== undefined ? item.content : messageNode?.content;
|
|
53
|
+
const parts = extractTextParts(contentNode);
|
|
54
|
+
if (parts.length) {
|
|
55
|
+
chunks.push(`${role}: ${parts.join('\n')}`);
|
|
56
|
+
}
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (type === 'function_call') {
|
|
60
|
+
const name = typeof item.name === 'string' ? String(item.name).trim() : 'tool';
|
|
61
|
+
const args = typeof item.arguments === 'string'
|
|
62
|
+
? String(item.arguments)
|
|
63
|
+
: (() => {
|
|
64
|
+
try {
|
|
65
|
+
return JSON.stringify(item.arguments ?? null);
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return String(item.arguments ?? '');
|
|
69
|
+
}
|
|
70
|
+
})();
|
|
71
|
+
chunks.push(`assistant tool_call ${name}: ${args}`);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (type === 'function_call_output') {
|
|
75
|
+
const output = typeof item.output === 'string'
|
|
76
|
+
? String(item.output)
|
|
77
|
+
: (() => {
|
|
78
|
+
try {
|
|
79
|
+
return JSON.stringify(item.output ?? null);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return String(item.output ?? '');
|
|
83
|
+
}
|
|
84
|
+
})();
|
|
85
|
+
chunks.push(`tool_output: ${output}`);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (!chunks.length)
|
|
90
|
+
return '';
|
|
91
|
+
return chunks.join('\n\n');
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Legacy compatibility shim:
|
|
95
|
+
* Some older LM Studio builds rejected the array form of `input` ("Invalid type for 'input'").
|
|
96
|
+
* Convert canonical Responses input items into a single `input` string.
|
|
97
|
+
*
|
|
98
|
+
* ⚠️ Default is OFF (modern LM Studio accepts array input). Enable only if you hit that legacy error:
|
|
99
|
+
* - `LLMSWITCH_LMSTUDIO_STRINGIFY_INPUT=1`
|
|
100
|
+
* - or `ROUTECODEX_LMSTUDIO_STRINGIFY_INPUT=1`
|
|
101
|
+
*
|
|
102
|
+
* This is applied via compat profile `chat:lmstudio` and only when `providerProtocol === 'openai-responses'`.
|
|
103
|
+
*/
|
|
104
|
+
export function stringifyLmstudioResponsesInput(payload, adapterContext) {
|
|
105
|
+
const enabled = process.env.LLMSWITCH_LMSTUDIO_STRINGIFY_INPUT === '1' ||
|
|
106
|
+
process.env.ROUTECODEX_LMSTUDIO_STRINGIFY_INPUT === '1';
|
|
107
|
+
if (!enabled) {
|
|
108
|
+
return payload;
|
|
109
|
+
}
|
|
110
|
+
if (!adapterContext || adapterContext.providerProtocol !== 'openai-responses') {
|
|
111
|
+
return payload;
|
|
112
|
+
}
|
|
113
|
+
const record = payload;
|
|
114
|
+
const input = record.input;
|
|
115
|
+
if (!Array.isArray(input)) {
|
|
116
|
+
return payload;
|
|
117
|
+
}
|
|
118
|
+
const flattened = stringifyInputItems(input);
|
|
119
|
+
if (flattened === null) {
|
|
120
|
+
return payload;
|
|
121
|
+
}
|
|
122
|
+
const instructions = typeof record.instructions === 'string' ? record.instructions.trim() : '';
|
|
123
|
+
record.input = instructions.length ? `${instructions}\n\n${flattened}`.trim() : flattened;
|
|
124
|
+
return payload;
|
|
125
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
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
|
+
export declare function normalizeToolCallIdsInPlace(root: JsonObject): void;
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
function isRecord(value) {
|
|
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
|
+
*/
|
|
117
|
+
export function normalizeToolCallIdsInPlace(root) {
|
|
118
|
+
const record = root;
|
|
119
|
+
// Responses request
|
|
120
|
+
if (Array.isArray(record.input)) {
|
|
121
|
+
for (const item of record.input) {
|
|
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
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// Chat request/response
|
|
139
|
+
normalizeChatMessages(record.messages);
|
|
140
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
function isRecord(value) {
|
|
2
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
3
|
+
}
|
|
4
|
+
const ORPHAN_TAG_RE = /^\s*(?:[•*+-]\s*)?<\/?\s*function_calls\s*\/?\s*>\s*$/i;
|
|
5
|
+
function stripOrphanTagLines(text) {
|
|
6
|
+
const raw = String(text ?? '');
|
|
7
|
+
if (!raw)
|
|
8
|
+
return { text: raw, changed: false };
|
|
9
|
+
const lines = raw.split(/\r?\n/);
|
|
10
|
+
const kept = [];
|
|
11
|
+
let changed = false;
|
|
12
|
+
for (const line of lines) {
|
|
13
|
+
if (ORPHAN_TAG_RE.test(line)) {
|
|
14
|
+
changed = true;
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
kept.push(line);
|
|
18
|
+
}
|
|
19
|
+
return { text: kept.join('\n'), changed };
|
|
20
|
+
}
|
|
21
|
+
function stripInMessageInPlace(message) {
|
|
22
|
+
let changed = false;
|
|
23
|
+
const content = message.content;
|
|
24
|
+
if (typeof content === 'string') {
|
|
25
|
+
const res = stripOrphanTagLines(content);
|
|
26
|
+
if (res.changed) {
|
|
27
|
+
message.content = res.text;
|
|
28
|
+
changed = true;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
else if (Array.isArray(content)) {
|
|
32
|
+
const next = content.map((part) => {
|
|
33
|
+
if (typeof part === 'string') {
|
|
34
|
+
const res = stripOrphanTagLines(part);
|
|
35
|
+
if (res.changed)
|
|
36
|
+
changed = true;
|
|
37
|
+
return res.text;
|
|
38
|
+
}
|
|
39
|
+
if (!isRecord(part))
|
|
40
|
+
return part;
|
|
41
|
+
const p = { ...part };
|
|
42
|
+
if (typeof p.text === 'string') {
|
|
43
|
+
const res = stripOrphanTagLines(p.text);
|
|
44
|
+
if (res.changed) {
|
|
45
|
+
p.text = res.text;
|
|
46
|
+
changed = true;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (typeof p.content === 'string') {
|
|
50
|
+
const res = stripOrphanTagLines(p.content);
|
|
51
|
+
if (res.changed) {
|
|
52
|
+
p.content = res.text;
|
|
53
|
+
changed = true;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return p;
|
|
57
|
+
});
|
|
58
|
+
if (changed) {
|
|
59
|
+
message.content = next;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
for (const key of ['reasoning', 'thinking', 'reasoning_content']) {
|
|
63
|
+
const raw = message[key];
|
|
64
|
+
if (typeof raw === 'string' && raw.trim().length) {
|
|
65
|
+
const res = stripOrphanTagLines(raw);
|
|
66
|
+
if (res.changed) {
|
|
67
|
+
message[key] = res.text;
|
|
68
|
+
changed = true;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return changed;
|
|
73
|
+
}
|
|
74
|
+
function stripInChatPayloadInPlace(root) {
|
|
75
|
+
const choices = Array.isArray(root.choices) ? root.choices : [];
|
|
76
|
+
if (!choices.length)
|
|
77
|
+
return false;
|
|
78
|
+
let changed = false;
|
|
79
|
+
for (const choice of choices) {
|
|
80
|
+
if (!isRecord(choice))
|
|
81
|
+
continue;
|
|
82
|
+
const message = choice.message;
|
|
83
|
+
if (!isRecord(message))
|
|
84
|
+
continue;
|
|
85
|
+
const role = typeof message.role === 'string' ? String(message.role).trim().toLowerCase() : 'assistant';
|
|
86
|
+
if (role !== 'assistant')
|
|
87
|
+
continue;
|
|
88
|
+
if (stripInMessageInPlace(message)) {
|
|
89
|
+
changed = true;
|
|
90
|
+
choice.message = message;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return changed;
|
|
94
|
+
}
|
|
95
|
+
function stripInResponsesPayloadInPlace(root) {
|
|
96
|
+
const output = Array.isArray(root.output) ? root.output : [];
|
|
97
|
+
if (!output.length)
|
|
98
|
+
return false;
|
|
99
|
+
let changed = false;
|
|
100
|
+
for (const item of output) {
|
|
101
|
+
if (!isRecord(item))
|
|
102
|
+
continue;
|
|
103
|
+
const type = typeof item.type === 'string' ? String(item.type).trim().toLowerCase() : '';
|
|
104
|
+
if (type !== 'message')
|
|
105
|
+
continue;
|
|
106
|
+
const role = typeof item.role === 'string' ? String(item.role).trim().toLowerCase() : 'assistant';
|
|
107
|
+
if (role !== 'assistant')
|
|
108
|
+
continue;
|
|
109
|
+
const content = Array.isArray(item.content) ? item.content : [];
|
|
110
|
+
if (content.length) {
|
|
111
|
+
const next = content.map((part) => {
|
|
112
|
+
if (!isRecord(part))
|
|
113
|
+
return part;
|
|
114
|
+
const p = { ...part };
|
|
115
|
+
for (const key of ['text', 'content', 'value']) {
|
|
116
|
+
if (typeof p[key] === 'string' && String(p[key]).trim().length) {
|
|
117
|
+
const res = stripOrphanTagLines(String(p[key]));
|
|
118
|
+
if (res.changed) {
|
|
119
|
+
p[key] = res.text;
|
|
120
|
+
changed = true;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return p;
|
|
125
|
+
});
|
|
126
|
+
if (changed) {
|
|
127
|
+
item.content = next;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (typeof item.output_text === 'string' && String(item.output_text).trim().length) {
|
|
131
|
+
const res = stripOrphanTagLines(String(item.output_text));
|
|
132
|
+
if (res.changed) {
|
|
133
|
+
item.output_text = res.text;
|
|
134
|
+
changed = true;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return changed;
|
|
139
|
+
}
|
|
140
|
+
export function stripOrphanFunctionCallsTag(payload) {
|
|
141
|
+
try {
|
|
142
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
|
|
143
|
+
return payload;
|
|
144
|
+
}
|
|
145
|
+
const root = structuredClone(payload);
|
|
146
|
+
const changed = stripInChatPayloadInPlace(root) || stripInResponsesPayloadInPlace(root);
|
|
147
|
+
return (changed ? root : payload);
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
return payload;
|
|
151
|
+
}
|
|
152
|
+
}
|