@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.
Files changed (44) hide show
  1. package/dist/conversion/codecs/gemini-openai-codec.js +1 -2
  2. package/dist/conversion/codecs/responses-openai-codec.js +16 -1
  3. package/dist/conversion/compat/profiles/chat-glm.json +17 -0
  4. package/dist/conversion/compat/profiles/chat-iflow.json +36 -0
  5. package/dist/conversion/compat/profiles/chat-lmstudio.json +37 -0
  6. package/dist/conversion/compat/profiles/chat-qwen.json +18 -0
  7. package/dist/conversion/compat/profiles/responses-c4m.json +45 -0
  8. package/dist/conversion/config/compat-profiles.json +38 -0
  9. package/dist/conversion/config/sample-config.json +314 -0
  10. package/dist/conversion/config/version-switch.json +150 -0
  11. package/dist/conversion/hub/pipeline/compat/compat-engine.d.ts +4 -0
  12. package/dist/conversion/hub/pipeline/compat/compat-engine.js +667 -0
  13. package/dist/conversion/hub/pipeline/compat/compat-profile-store.d.ts +2 -0
  14. package/dist/conversion/hub/pipeline/compat/compat-profile-store.js +76 -0
  15. package/dist/conversion/hub/pipeline/compat/compat-types.d.ts +62 -0
  16. package/dist/conversion/hub/pipeline/compat/compat-types.js +1 -0
  17. package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +1 -0
  18. package/dist/conversion/hub/pipeline/hub-pipeline.js +76 -28
  19. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/index.js +0 -13
  20. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage3_compat/index.d.ts +14 -0
  21. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage3_compat/index.js +23 -0
  22. package/dist/conversion/hub/response/provider-response.js +18 -0
  23. package/dist/conversion/hub/response/response-mappers.d.ts +1 -1
  24. package/dist/conversion/hub/response/response-mappers.js +2 -12
  25. package/dist/conversion/responses/responses-openai-bridge.d.ts +1 -0
  26. package/dist/conversion/responses/responses-openai-bridge.js +71 -0
  27. package/dist/conversion/shared/responses-output-builder.js +22 -43
  28. package/dist/conversion/shared/responses-response-utils.js +1 -47
  29. package/dist/conversion/shared/text-markup-normalizer.js +2 -2
  30. package/dist/conversion/shared/tool-canonicalizer.js +16 -118
  31. package/dist/conversion/shared/tool-filter-pipeline.js +63 -21
  32. package/dist/conversion/shared/tool-mapping.js +52 -32
  33. package/dist/filters/config/openai-openai.fieldmap.json +18 -0
  34. package/dist/filters/special/request-tools-normalize.js +20 -1
  35. package/dist/index.d.ts +0 -1
  36. package/dist/index.js +0 -1
  37. package/dist/router/virtual-router/bootstrap.js +18 -33
  38. package/dist/router/virtual-router/classifier.js +51 -77
  39. package/dist/router/virtual-router/features.js +338 -111
  40. package/dist/router/virtual-router/types.d.ts +2 -4
  41. package/dist/router/virtual-router/types.js +2 -2
  42. package/dist/sse/sse-to-json/builders/response-builder.js +1 -0
  43. package/dist/tools/tool-registry.js +4 -3
  44. 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({ input: patch });
98
+ argsStr = JSON.stringify({ patch });
99
99
  }
100
100
  catch {
101
- argsStr = '{"input":""}';
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 toolCalls = Array.isArray(msg?.tool_calls) ? msg.tool_calls : undefined;
84
- const hasToolCalls = Array.isArray(toolCalls) && toolCalls.length > 0;
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
- if (harvestedPatch) {
129
- const id = createToolCallId();
130
- const toolCall = {
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
- ch.finish_reason = 'tool_calls';
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
- engine.registerFilter(new RequestToolListFilter());
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
- engine.registerFilter(new RequestToolCallsStringifyFilter());
68
+ register(new RequestToolCallsStringifyFilter());
40
69
  if (!isAnthropic) {
41
- engine.registerFilter(new RequestToolChoicePolicyFilter());
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
- let staged = await engine.run('request_pre', chatRequest, reqCtxBase);
56
- recordStage('req_process_tool_filters_request_pre', staged);
57
- staged = await engine.run('request_map', staged, reqCtxBase);
58
- recordStage('req_process_tool_filters_request_map', staged);
59
- staged = await engine.run('request_post', staged, reqCtxBase);
60
- recordStage('req_process_tool_filters_request_post', staged);
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
- engine.registerFilter(new ResponseToolTextCanonicalizeFilter());
127
+ register(new ResponseToolTextCanonicalizeFilter());
86
128
  try {
87
129
  const { ResponseToolArgumentsToonDecodeFilter, ResponseToolArgumentsBlacklistFilter, ResponseToolArgumentsSchemaConvergeFilter } = await import('../../filters/index.js');
88
- engine.registerFilter(new ResponseToolArgumentsToonDecodeFilter());
130
+ register(new ResponseToolArgumentsToonDecodeFilter());
89
131
  try {
90
- engine.registerFilter(new ResponseToolArgumentsSchemaConvergeFilter());
132
+ register(new ResponseToolArgumentsSchemaConvergeFilter());
91
133
  }
92
134
  catch { /* optional */ }
93
- engine.registerFilter(new ResponseToolArgumentsBlacklistFilter());
135
+ register(new ResponseToolArgumentsBlacklistFilter());
94
136
  }
95
137
  catch { /* optional */ }
96
- engine.registerFilter(new ResponseToolArgumentsStringifyFilter());
97
- engine.registerFilter(new ResponseFinishInvariantsFilter());
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
- let staged = await engine.run('response_pre', chatJson, resCtxBase);
111
- recordStage('resp_process_tool_filters_response_pre', staged);
112
- staged = await engine.run('response_map', staged, resCtxBase);
113
- recordStage('resp_process_tool_filters_response_map', staged);
114
- staged = await engine.run('response_post', staged, resCtxBase);
115
- recordStage('resp_process_tool_filters_response_post', staged);
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 'shell' at unified shaping point
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: DEFAULT_THINKING_KEYWORDS,
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
- const profile = readOptionalString(provider.compatibilityProfile);
262
- if (profile) {
263
- return profile;
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 'default';
250
+ return 'compat:passthrough';
266
251
  }
267
252
  function normalizeProcessMode(value) {
268
253
  if (typeof value !== 'string') {