@jsonstudio/llms 0.6.141 → 0.6.187

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.
Files changed (61) hide show
  1. package/dist/conversion/codecs/gemini-openai-codec.js +15 -1
  2. package/dist/conversion/compat/actions/auto-thinking.d.ts +6 -0
  3. package/dist/conversion/compat/actions/auto-thinking.js +25 -0
  4. package/dist/conversion/compat/actions/field-mapping.d.ts +14 -0
  5. package/dist/conversion/compat/actions/field-mapping.js +155 -0
  6. package/dist/conversion/compat/actions/qwen-transform.d.ts +3 -0
  7. package/dist/conversion/compat/actions/qwen-transform.js +209 -0
  8. package/dist/conversion/compat/actions/request-rules.d.ts +24 -0
  9. package/dist/conversion/compat/actions/request-rules.js +63 -0
  10. package/dist/conversion/compat/actions/response-blacklist.d.ts +14 -0
  11. package/dist/conversion/compat/actions/response-blacklist.js +85 -0
  12. package/dist/conversion/compat/actions/response-normalize.d.ts +5 -0
  13. package/dist/conversion/compat/actions/response-normalize.js +121 -0
  14. package/dist/conversion/compat/actions/response-validate.d.ts +5 -0
  15. package/dist/conversion/compat/actions/response-validate.js +76 -0
  16. package/dist/conversion/compat/actions/snapshot.d.ts +8 -0
  17. package/dist/conversion/compat/actions/snapshot.js +21 -0
  18. package/dist/conversion/compat/actions/tool-schema.d.ts +6 -0
  19. package/dist/conversion/compat/actions/tool-schema.js +91 -0
  20. package/dist/conversion/compat/actions/universal-shape-filter.d.ts +74 -0
  21. package/dist/conversion/compat/actions/universal-shape-filter.js +382 -0
  22. package/dist/conversion/compat/profiles/chat-glm.json +187 -13
  23. package/dist/conversion/compat/profiles/chat-iflow.json +177 -9
  24. package/dist/conversion/compat/profiles/chat-lmstudio.json +10 -2
  25. package/dist/conversion/compat/profiles/chat-qwen.json +14 -10
  26. package/dist/conversion/hub/pipeline/compat/compat-engine.d.ts +7 -2
  27. package/dist/conversion/hub/pipeline/compat/compat-engine.js +409 -5
  28. package/dist/conversion/hub/pipeline/compat/compat-types.d.ts +47 -0
  29. package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +2 -0
  30. package/dist/conversion/hub/pipeline/hub-pipeline.js +35 -1
  31. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage3_compat/index.js +2 -2
  32. package/dist/conversion/hub/pipeline/target-utils.js +3 -0
  33. package/dist/conversion/hub/response/response-runtime.js +19 -2
  34. package/dist/conversion/responses/responses-host-policy.d.ts +6 -0
  35. package/dist/conversion/responses/responses-host-policy.js +14 -0
  36. package/dist/conversion/responses/responses-openai-bridge.js +51 -2
  37. package/dist/conversion/shared/anthropic-message-utils.js +6 -0
  38. package/dist/conversion/shared/responses-conversation-store.js +3 -26
  39. package/dist/conversion/shared/responses-reasoning-registry.d.ts +4 -0
  40. package/dist/conversion/shared/responses-reasoning-registry.js +62 -1
  41. package/dist/conversion/shared/responses-response-utils.js +23 -1
  42. package/dist/conversion/shared/tool-canonicalizer.d.ts +2 -0
  43. package/dist/conversion/shared/tool-filter-pipeline.js +11 -0
  44. package/dist/router/virtual-router/bootstrap.js +218 -39
  45. package/dist/router/virtual-router/classifier.js +19 -52
  46. package/dist/router/virtual-router/context-advisor.d.ts +21 -0
  47. package/dist/router/virtual-router/context-advisor.js +76 -0
  48. package/dist/router/virtual-router/engine.d.ts +11 -26
  49. package/dist/router/virtual-router/engine.js +191 -386
  50. package/dist/router/virtual-router/features.js +24 -621
  51. package/dist/router/virtual-router/health-manager.js +2 -7
  52. package/dist/router/virtual-router/message-utils.d.ts +7 -0
  53. package/dist/router/virtual-router/message-utils.js +66 -0
  54. package/dist/router/virtual-router/provider-registry.js +6 -2
  55. package/dist/router/virtual-router/token-estimator.d.ts +2 -0
  56. package/dist/router/virtual-router/token-estimator.js +16 -0
  57. package/dist/router/virtual-router/tool-signals.d.ts +13 -0
  58. package/dist/router/virtual-router/tool-signals.js +403 -0
  59. package/dist/router/virtual-router/types.d.ts +21 -7
  60. package/dist/router/virtual-router/types.js +1 -0
  61. package/package.json +2 -2
@@ -0,0 +1,121 @@
1
+ const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
2
+ export function normalizeResponsePayload(payload, config) {
3
+ const normalized = { ...payload };
4
+ if (isRecord(normalized.usage)) {
5
+ normalized.usage = normalizeUsageFields(normalized.usage);
6
+ }
7
+ if (typeof normalized.created_at === 'number') {
8
+ normalized.created = normalized.created_at;
9
+ delete normalized.created_at;
10
+ }
11
+ if (Array.isArray(normalized.choices)) {
12
+ normalized.choices = normalizeChoices(normalized.choices, config);
13
+ }
14
+ return normalized;
15
+ }
16
+ function normalizeUsageFields(usage) {
17
+ const normalizedUsage = { ...usage };
18
+ const fieldMappings = {
19
+ input_tokens: 'prompt_tokens',
20
+ output_tokens: 'completion_tokens',
21
+ total_input_tokens: 'prompt_tokens',
22
+ total_output_tokens: 'completion_tokens'
23
+ };
24
+ for (const [glmField, standardField] of Object.entries(fieldMappings)) {
25
+ const value = normalizedUsage[glmField];
26
+ if (typeof value === 'number') {
27
+ normalizedUsage[standardField] = value;
28
+ delete normalizedUsage[glmField];
29
+ }
30
+ }
31
+ const promptTokens = typeof normalizedUsage.prompt_tokens === 'number' ? normalizedUsage.prompt_tokens : 0;
32
+ const completionTokens = typeof normalizedUsage.completion_tokens === 'number'
33
+ ? normalizedUsage.completion_tokens
34
+ : 0;
35
+ normalizedUsage.prompt_tokens = promptTokens;
36
+ normalizedUsage.completion_tokens = completionTokens;
37
+ if (typeof normalizedUsage.total_tokens !== 'number') {
38
+ normalizedUsage.total_tokens = promptTokens + completionTokens;
39
+ }
40
+ return normalizedUsage;
41
+ }
42
+ function normalizeChoices(choices, config) {
43
+ const map = config?.finishReasonMap || {};
44
+ return choices.map((choice, idx) => {
45
+ if (!isRecord(choice)) {
46
+ return choice;
47
+ }
48
+ const normalizedChoice = {
49
+ index: typeof choice.index === 'number' ? choice.index : idx,
50
+ ...choice
51
+ };
52
+ if (typeof normalizedChoice.finish_reason === 'string') {
53
+ normalizedChoice.finish_reason = map[normalizedChoice.finish_reason] ?? normalizedChoice.finish_reason;
54
+ }
55
+ if (isRecord(normalizedChoice.message)) {
56
+ normalizedChoice.message = normalizeChoiceMessage(normalizedChoice.message);
57
+ }
58
+ return normalizedChoice;
59
+ });
60
+ }
61
+ function normalizeChoiceMessage(message) {
62
+ const normalizedMessage = { ...message };
63
+ if (normalizedMessage.content !== undefined && typeof normalizedMessage.content !== 'string') {
64
+ normalizedMessage.content = normalizeMessageContent(normalizedMessage.content);
65
+ }
66
+ if (Array.isArray(normalizedMessage.tool_calls)) {
67
+ normalizedMessage.tool_calls = normalizeToolCalls(normalizedMessage.tool_calls);
68
+ }
69
+ return normalizedMessage;
70
+ }
71
+ function normalizeMessageContent(content) {
72
+ if (typeof content === 'string') {
73
+ return content;
74
+ }
75
+ if (Array.isArray(content)) {
76
+ return content.map(item => (typeof item === 'string' ? item : JSON.stringify(item))).join('');
77
+ }
78
+ if (isRecord(content)) {
79
+ return JSON.stringify(content);
80
+ }
81
+ return String(content ?? '');
82
+ }
83
+ function normalizeToolCalls(toolCalls) {
84
+ return toolCalls.map(toolCall => {
85
+ if (!isRecord(toolCall)) {
86
+ return toolCall;
87
+ }
88
+ const normalizedToolCall = { ...toolCall };
89
+ const func = normalizedToolCall.function;
90
+ if (isRecord(func) && func.arguments !== undefined) {
91
+ func.arguments = normalizeToolArguments(func.arguments);
92
+ normalizedToolCall.function = func;
93
+ }
94
+ return normalizedToolCall;
95
+ });
96
+ }
97
+ function normalizeToolArguments(args) {
98
+ let normalized = '';
99
+ if (typeof args === 'string') {
100
+ normalized = args;
101
+ }
102
+ else {
103
+ try {
104
+ normalized = JSON.stringify(args ?? {});
105
+ }
106
+ catch {
107
+ normalized = String(args ?? '');
108
+ }
109
+ }
110
+ const trimmed = normalized.trim();
111
+ if (!trimmed.length) {
112
+ return '';
113
+ }
114
+ try {
115
+ const parsed = JSON.parse(trimmed);
116
+ return JSON.stringify(parsed);
117
+ }
118
+ catch {
119
+ return trimmed;
120
+ }
121
+ }
@@ -0,0 +1,5 @@
1
+ import type { JsonObject } from '../../hub/types/json.js';
2
+ export interface ResponseValidateConfig {
3
+ strict?: boolean;
4
+ }
5
+ export declare function validateResponsePayload(payload: JsonObject, _config?: ResponseValidateConfig): void;
@@ -0,0 +1,76 @@
1
+ const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
2
+ export function validateResponsePayload(payload, _config) {
3
+ const errors = [];
4
+ if (!payload.id || typeof payload.id !== 'string') {
5
+ errors.push('响应缺少有效的id字段');
6
+ }
7
+ if (!payload.created || typeof payload.created !== 'number') {
8
+ errors.push('响应缺少有效的created字段');
9
+ }
10
+ if (!payload.model || typeof payload.model !== 'string') {
11
+ errors.push('响应缺少有效的model字段');
12
+ }
13
+ if (!Array.isArray(payload.choices) || payload.choices.length === 0) {
14
+ errors.push('choices数组不能为空');
15
+ }
16
+ else {
17
+ payload.choices.forEach((choice, idx) => {
18
+ if (!isRecord(choice)) {
19
+ errors.push(`choices[${idx}]必须是对象`);
20
+ return;
21
+ }
22
+ if (!choice.message || typeof choice.message !== 'object') {
23
+ errors.push(`choices[${idx}].message字段必须是对象`);
24
+ }
25
+ else if (Array.isArray(choice.message.tool_calls)) {
26
+ choice.message.tool_calls.forEach((toolCall, tIdx) => {
27
+ if (!isRecord(toolCall)) {
28
+ errors.push(`choices[${idx}].message.tool_calls[${tIdx}]必须是对象`);
29
+ return;
30
+ }
31
+ if (!toolCall.function || typeof toolCall.function !== 'object') {
32
+ errors.push(`choices[${idx}].message.tool_calls[${tIdx}].function字段必须是对象`);
33
+ return;
34
+ }
35
+ const fn = toolCall.function;
36
+ if (typeof fn.name !== 'string') {
37
+ errors.push(`choices[${idx}].message.tool_calls[${tIdx}].function.name字段必须是字符串`);
38
+ }
39
+ if (typeof fn.arguments !== 'string') {
40
+ errors.push(`choices[${idx}].message.tool_calls[${tIdx}].function.arguments字段必须是字符串`);
41
+ }
42
+ else {
43
+ try {
44
+ JSON.parse(fn.arguments);
45
+ }
46
+ catch {
47
+ errors.push(`choices[${idx}].message.tool_calls[${tIdx}].function.arguments必须是有效JSON`);
48
+ }
49
+ }
50
+ });
51
+ }
52
+ });
53
+ }
54
+ if (payload.usage && typeof payload.usage === 'object') {
55
+ const usage = payload.usage;
56
+ const promptTokens = usage.prompt_tokens;
57
+ const completionTokens = usage.completion_tokens;
58
+ const totalTokens = usage.total_tokens;
59
+ if (!isNonNegativeNumber(promptTokens) ||
60
+ !isNonNegativeNumber(completionTokens) ||
61
+ !isNonNegativeNumber(totalTokens)) {
62
+ errors.push('usage字段的token必须是非负数');
63
+ }
64
+ else if (promptTokens + completionTokens !== totalTokens) {
65
+ errors.push('usage.total_tokens 应等于 prompt_tokens 与 completion_tokens 之和');
66
+ }
67
+ }
68
+ if (errors.length) {
69
+ const error = new Error(`GLM响应校验失败:\n${errors.join('\n')}`);
70
+ error.code = 'compat_response_validation_failed';
71
+ throw error;
72
+ }
73
+ }
74
+ function isNonNegativeNumber(value) {
75
+ return typeof value === 'number' && Number.isFinite(value) && value >= 0;
76
+ }
@@ -0,0 +1,8 @@
1
+ type Phase = 'compat-pre' | 'compat-post';
2
+ export declare function writeCompatSnapshot(options: {
3
+ phase: Phase;
4
+ requestId?: string;
5
+ entryEndpoint?: string;
6
+ data: unknown;
7
+ }): Promise<void>;
8
+ export {};
@@ -0,0 +1,21 @@
1
+ import { writeSnapshotViaHooks } from '../../shared/snapshot-hooks.js';
2
+ const SNAPSHOT_FLAG = String(process.env.ROUTECODEX_SNAPSHOT ?? '').toLowerCase();
3
+ const SNAPSHOT_ENABLED = SNAPSHOT_FLAG === '1' || SNAPSHOT_FLAG === 'true';
4
+ export async function writeCompatSnapshot(options) {
5
+ if (!SNAPSHOT_ENABLED) {
6
+ return;
7
+ }
8
+ try {
9
+ const endpoint = options.entryEndpoint || '/v1/chat/completions';
10
+ await writeSnapshotViaHooks({
11
+ endpoint,
12
+ stage: options.phase,
13
+ requestId: options.requestId || 'unknown',
14
+ data: options.data,
15
+ verbosity: 'verbose'
16
+ });
17
+ }
18
+ catch {
19
+ // snapshots are best-effort
20
+ }
21
+ }
@@ -0,0 +1,6 @@
1
+ import type { JsonObject } from '../../hub/types/json.js';
2
+ type UnknownRecord = Record<string, unknown>;
3
+ export declare const sanitizeGLMToolsSchema: (data: UnknownRecord) => UnknownRecord;
4
+ export declare const sanitizeGLMToolsSchemaInPlace: (data: UnknownRecord) => void;
5
+ export declare function sanitizeToolSchema(payload: JsonObject, mode?: 'glm_shell'): JsonObject;
6
+ export {};
@@ -0,0 +1,91 @@
1
+ const SHELL_COMMAND_DESCRIPTION = 'Shell command argv tokens. Use ["bash","-lc","<cmd>"] form.';
2
+ const isRecord = (value) => typeof value === 'object' && value !== null;
3
+ const ensureStringArray = (value) => {
4
+ if (Array.isArray(value) && value.every(item => typeof item === 'string')) {
5
+ return [...value];
6
+ }
7
+ return [];
8
+ };
9
+ const sanitizeShellCommandProperty = (properties) => {
10
+ const candidate = properties.command;
11
+ if (isRecord(candidate)) {
12
+ const command = { ...candidate };
13
+ delete command.oneOf;
14
+ command.type = 'array';
15
+ command.items = { type: 'string' };
16
+ if (typeof command.description !== 'string' || command.description.length === 0) {
17
+ command.description = SHELL_COMMAND_DESCRIPTION;
18
+ }
19
+ properties.command = command;
20
+ return;
21
+ }
22
+ properties.command = {
23
+ description: SHELL_COMMAND_DESCRIPTION,
24
+ type: 'array',
25
+ items: { type: 'string' }
26
+ };
27
+ };
28
+ const sanitizeToolFunction = (toolFn, isShell) => {
29
+ const fn = { ...toolFn };
30
+ if ('strict' in fn) {
31
+ delete fn.strict;
32
+ }
33
+ if (!fn.parameters || !isRecord(fn.parameters)) {
34
+ return fn;
35
+ }
36
+ const params = { ...fn.parameters };
37
+ if (isShell) {
38
+ if (params.properties && isRecord(params.properties)) {
39
+ params.properties = { ...params.properties };
40
+ }
41
+ else {
42
+ params.properties = {};
43
+ }
44
+ sanitizeShellCommandProperty(params.properties);
45
+ const required = ensureStringArray(params.required);
46
+ if (!required.includes('command')) {
47
+ required.push('command');
48
+ }
49
+ params.required = required;
50
+ params.type = typeof params.type === 'string' ? params.type : 'object';
51
+ params.additionalProperties = typeof params.additionalProperties === 'boolean'
52
+ ? params.additionalProperties
53
+ : false;
54
+ }
55
+ fn.parameters = params;
56
+ return fn;
57
+ };
58
+ const sanitizeToolDefinition = (tool) => {
59
+ const sanitized = { ...tool };
60
+ if (!sanitized.function || !isRecord(sanitized.function)) {
61
+ return sanitized;
62
+ }
63
+ const fn = sanitizeToolFunction(sanitized.function, sanitized.function.name === 'shell');
64
+ sanitized.function = fn;
65
+ return sanitized;
66
+ };
67
+ export const sanitizeGLMToolsSchema = (data) => {
68
+ const toolsValue = data.tools;
69
+ if (!Array.isArray(toolsValue)) {
70
+ return data;
71
+ }
72
+ const sanitizedTools = toolsValue.map(tool => (isRecord(tool) ? sanitizeToolDefinition(tool) : tool));
73
+ return {
74
+ ...data,
75
+ tools: sanitizedTools
76
+ };
77
+ };
78
+ export const sanitizeGLMToolsSchemaInPlace = (data) => {
79
+ const toolsValue = data.tools;
80
+ if (!Array.isArray(toolsValue)) {
81
+ return;
82
+ }
83
+ const sanitized = sanitizeGLMToolsSchema(data);
84
+ data.tools = sanitized.tools;
85
+ };
86
+ export function sanitizeToolSchema(payload, mode = 'glm_shell') {
87
+ if (mode === 'glm_shell') {
88
+ return sanitizeGLMToolsSchema(payload);
89
+ }
90
+ return payload;
91
+ }
@@ -0,0 +1,74 @@
1
+ import type { AdapterContext } from '../../hub/types/chat-envelope.js';
2
+ import type { JsonObject } from '../../hub/types/json.js';
3
+ type RequestMessagesRule = {
4
+ when?: {
5
+ role?: 'system' | 'user' | 'assistant' | 'tool';
6
+ hasToolCalls?: boolean;
7
+ };
8
+ action: 'drop' | 'keep' | 'set';
9
+ set?: Record<string, unknown>;
10
+ };
11
+ export interface FilterConfig {
12
+ request: {
13
+ allowTopLevel: string[];
14
+ messages: {
15
+ allowedRoles: string[];
16
+ assistantWithToolCallsContentNull?: boolean;
17
+ toolContentStringify?: boolean;
18
+ suppressAssistantToolCalls?: boolean;
19
+ };
20
+ tools?: {
21
+ normalize?: boolean;
22
+ forceToolChoiceAuto?: boolean;
23
+ };
24
+ assistantToolCalls?: {
25
+ functionArgumentsType?: 'object' | 'string';
26
+ };
27
+ messagesRules?: RequestMessagesRule[];
28
+ };
29
+ response: {
30
+ allowTopLevel: string[];
31
+ choices: {
32
+ required?: boolean;
33
+ message: {
34
+ allow: string[];
35
+ roleDefault?: string;
36
+ contentNullWhenToolCalls?: boolean;
37
+ tool_calls?: {
38
+ function?: {
39
+ nameRequired?: boolean;
40
+ argumentsType?: 'object' | 'string';
41
+ };
42
+ };
43
+ };
44
+ finish_reason?: string[];
45
+ };
46
+ usage?: {
47
+ allow: string[];
48
+ };
49
+ };
50
+ }
51
+ export declare class UniversalShapeFilter {
52
+ private readonly cfg;
53
+ constructor(config: FilterConfig);
54
+ applyRequestFilter(payload: JsonObject): JsonObject;
55
+ applyResponseFilter(payload: JsonObject, ctx?: AdapterContext): JsonObject;
56
+ private shallowPick;
57
+ private toObjectArgs;
58
+ private toStringArgs;
59
+ private normalizeToolContent;
60
+ private normalizeRequestMessages;
61
+ private normalizeSingleMessage;
62
+ private normalizeAssistantToolCalls;
63
+ private applyMessageRules;
64
+ private pairToolResults;
65
+ private normalizeTools;
66
+ private normalizeSingleTool;
67
+ private normalizeToolParameters;
68
+ private enforceShellSchema;
69
+ private cleanupToolChoice;
70
+ private normalizeResponseChoice;
71
+ private normalizeResponseMessage;
72
+ private normalizeResponseToolCalls;
73
+ }
74
+ export {};