@jsonstudio/llms 0.4.5 → 0.6.0

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 (92) hide show
  1. package/dist/conversion/codecs/anthropic-openai-codec.js +28 -2
  2. package/dist/conversion/codecs/gemini-openai-codec.js +23 -0
  3. package/dist/conversion/codecs/responses-openai-codec.js +8 -1
  4. package/dist/conversion/hub/node-support.js +14 -1
  5. package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +66 -0
  6. package/dist/conversion/hub/pipeline/hub-pipeline.js +284 -193
  7. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage1_format_parse/index.d.ts +11 -0
  8. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage1_format_parse/index.js +6 -0
  9. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.d.ts +16 -0
  10. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.js +17 -0
  11. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/context-factories.d.ts +5 -0
  12. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/context-factories.js +17 -0
  13. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/index.d.ts +19 -0
  14. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/index.js +269 -0
  15. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/index.d.ts +18 -0
  16. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/index.js +141 -0
  17. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage2_format_build/index.d.ts +11 -0
  18. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage2_format_build/index.js +29 -0
  19. package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage1_tool_governance/index.d.ts +16 -0
  20. package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage1_tool_governance/index.js +15 -0
  21. package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage2_route_select/index.d.ts +17 -0
  22. package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage2_route_select/index.js +18 -0
  23. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage1_sse_decode/index.d.ts +17 -0
  24. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage1_sse_decode/index.js +63 -0
  25. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage2_format_parse/index.d.ts +11 -0
  26. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage2_format_parse/index.js +6 -0
  27. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage3_semantic_map/index.d.ts +12 -0
  28. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage3_semantic_map/index.js +6 -0
  29. package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.d.ts +13 -0
  30. package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.js +43 -0
  31. package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage2_sse_stream/index.d.ts +17 -0
  32. package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage2_sse_stream/index.js +22 -0
  33. package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage1_tool_governance/index.d.ts +16 -0
  34. package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage1_tool_governance/index.js +19 -0
  35. package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage2_finalize/index.d.ts +17 -0
  36. package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage2_finalize/index.js +19 -0
  37. package/dist/conversion/hub/pipeline/stages/utils.d.ts +2 -0
  38. package/dist/conversion/hub/pipeline/stages/utils.js +11 -0
  39. package/dist/conversion/hub/pipeline/target-utils.d.ts +5 -0
  40. package/dist/conversion/hub/pipeline/target-utils.js +87 -0
  41. package/dist/conversion/hub/process/chat-process.js +11 -11
  42. package/dist/conversion/hub/response/provider-response.js +69 -122
  43. package/dist/conversion/hub/response/response-mappers.d.ts +19 -0
  44. package/dist/conversion/hub/response/response-mappers.js +22 -2
  45. package/dist/conversion/hub/response/response-runtime.d.ts +8 -0
  46. package/dist/conversion/hub/response/response-runtime.js +239 -6
  47. package/dist/conversion/hub/semantic-mappers/anthropic-mapper.d.ts +8 -0
  48. package/dist/conversion/hub/semantic-mappers/anthropic-mapper.js +119 -59
  49. package/dist/conversion/hub/semantic-mappers/chat-mapper.js +74 -13
  50. package/dist/conversion/hub/semantic-mappers/gemini-mapper.js +0 -9
  51. package/dist/conversion/hub/semantic-mappers/responses-mapper.js +16 -13
  52. package/dist/conversion/hub/snapshot-recorder.d.ts +13 -0
  53. package/dist/conversion/hub/snapshot-recorder.js +90 -50
  54. package/dist/conversion/hub/standardized-bridge.js +44 -30
  55. package/dist/conversion/hub/types/chat-envelope.d.ts +68 -0
  56. package/dist/conversion/hub/types/standardized.d.ts +97 -0
  57. package/dist/conversion/pipeline/codecs/v2/anthropic-openai-pipeline.js +29 -2
  58. package/dist/conversion/pipeline/codecs/v2/responses-openai-pipeline.js +68 -1
  59. package/dist/conversion/responses/responses-openai-bridge.d.ts +6 -1
  60. package/dist/conversion/responses/responses-openai-bridge.js +132 -6
  61. package/dist/conversion/shared/anthropic-message-utils.d.ts +9 -1
  62. package/dist/conversion/shared/anthropic-message-utils.js +334 -14
  63. package/dist/conversion/shared/bridge-actions.js +267 -40
  64. package/dist/conversion/shared/bridge-message-utils.js +54 -8
  65. package/dist/conversion/shared/bridge-policies.js +29 -4
  66. package/dist/conversion/shared/chat-envelope-validator.d.ts +8 -0
  67. package/dist/conversion/shared/chat-envelope-validator.js +128 -0
  68. package/dist/conversion/shared/chat-request-filters.js +108 -25
  69. package/dist/conversion/shared/mcp-injection.js +41 -20
  70. package/dist/conversion/shared/openai-finalizer.d.ts +11 -0
  71. package/dist/conversion/shared/openai-finalizer.js +73 -0
  72. package/dist/conversion/shared/openai-message-normalize.js +32 -31
  73. package/dist/conversion/shared/reasoning-normalizer.d.ts +1 -0
  74. package/dist/conversion/shared/reasoning-normalizer.js +50 -18
  75. package/dist/conversion/shared/responses-output-builder.d.ts +1 -1
  76. package/dist/conversion/shared/responses-output-builder.js +76 -25
  77. package/dist/conversion/shared/responses-reasoning-registry.d.ts +8 -0
  78. package/dist/conversion/shared/responses-reasoning-registry.js +61 -0
  79. package/dist/conversion/shared/responses-response-utils.js +32 -2
  80. package/dist/conversion/shared/responses-tool-utils.js +28 -2
  81. package/dist/conversion/shared/snapshot-hooks.d.ts +9 -0
  82. package/dist/conversion/shared/snapshot-hooks.js +60 -6
  83. package/dist/conversion/shared/snapshot-utils.d.ts +16 -0
  84. package/dist/conversion/shared/snapshot-utils.js +84 -0
  85. package/dist/conversion/shared/tool-filter-pipeline.js +45 -5
  86. package/dist/conversion/shared/tool-governor.js +5 -0
  87. package/dist/conversion/shared/tool-mapping.js +13 -2
  88. package/dist/filters/special/request-tool-choice-policy.js +3 -1
  89. package/dist/filters/special/request-tool-list-filter.d.ts +11 -0
  90. package/dist/filters/special/request-tool-list-filter.js +20 -7
  91. package/dist/sse/shared/responses-output-normalizer.js +5 -4
  92. package/package.json +1 -1
@@ -1,5 +1,6 @@
1
1
  import { FilterEngine } from '../../filters/index.js';
2
2
  import { loadFieldMapConfig } from '../../filters/utils/fieldmap-loader.js';
3
+ import { createSnapshotWriter } from './snapshot-utils.js';
3
4
  export async function runChatRequestToolFilters(chatRequest, options = {}) {
4
5
  const reqCtxBase = {
5
6
  requestId: options.requestId ?? `req_${Date.now()}`,
@@ -10,15 +11,35 @@ export async function runChatRequestToolFilters(chatRequest, options = {}) {
10
11
  toolFilterHints: options.toolFilterHints,
11
12
  debug: { emit: () => { } }
12
13
  };
14
+ const snapshot = createSnapshotWriter({
15
+ requestId: reqCtxBase.requestId,
16
+ endpoint: reqCtxBase.endpoint,
17
+ folderHint: 'openai-chat'
18
+ });
19
+ const recordStage = (stage, payload) => {
20
+ if (!snapshot)
21
+ return;
22
+ snapshot(stage, payload);
23
+ };
24
+ recordStage('req_process_tool_filters_input', chatRequest);
13
25
  const engine = new FilterEngine();
14
- try {
15
- const { RequestToolListFilter } = await import('../../filters/index.js');
16
- engine.registerFilter(new RequestToolListFilter());
26
+ const profile = (reqCtxBase.profile || '').toLowerCase();
27
+ const endpoint = (reqCtxBase.endpoint || '').toLowerCase();
28
+ const isAnthropic = profile === 'anthropic-messages' || endpoint.includes('/v1/messages');
29
+ if (!isAnthropic) {
30
+ try {
31
+ const { RequestToolListFilter } = await import('../../filters/index.js');
32
+ engine.registerFilter(new RequestToolListFilter());
33
+ }
34
+ catch {
35
+ /* optional */
36
+ }
17
37
  }
18
- catch { /* optional */ }
19
38
  const { RequestToolCallsStringifyFilter, RequestToolChoicePolicyFilter, RequestStreamingToNonStreamingFilter } = await import('../../filters/index.js');
20
39
  engine.registerFilter(new RequestToolCallsStringifyFilter());
21
- engine.registerFilter(new RequestToolChoicePolicyFilter());
40
+ if (!isAnthropic) {
41
+ engine.registerFilter(new RequestToolChoicePolicyFilter());
42
+ }
22
43
  engine.registerFilter(new RequestStreamingToNonStreamingFilter());
23
44
  try {
24
45
  const cfg = await loadFieldMapConfig('openai-openai.fieldmap.json');
@@ -33,8 +54,12 @@ export async function runChatRequestToolFilters(chatRequest, options = {}) {
33
54
  }
34
55
  catch { /* ignore */ }
35
56
  let staged = await engine.run('request_pre', chatRequest, reqCtxBase);
57
+ recordStage('req_process_tool_filters_request_pre', staged);
36
58
  staged = await engine.run('request_map', staged, reqCtxBase);
59
+ recordStage('req_process_tool_filters_request_map', staged);
37
60
  staged = await engine.run('request_post', staged, reqCtxBase);
61
+ recordStage('req_process_tool_filters_request_post', staged);
62
+ recordStage('req_process_tool_filters_output', staged);
38
63
  return staged;
39
64
  }
40
65
  export async function runChatResponseToolFilters(chatJson, options = {}) {
@@ -45,6 +70,17 @@ export async function runChatResponseToolFilters(chatJson, options = {}) {
45
70
  profile: options.profile || 'openai-chat',
46
71
  debug: { emit: () => { } }
47
72
  };
73
+ const snapshot = createSnapshotWriter({
74
+ requestId: resCtxBase.requestId,
75
+ endpoint: resCtxBase.endpoint,
76
+ folderHint: 'openai-chat'
77
+ });
78
+ const recordStage = (stage, payload) => {
79
+ if (!snapshot)
80
+ return;
81
+ snapshot(stage, payload);
82
+ };
83
+ recordStage('resp_process_tool_filters_input', chatJson);
48
84
  const engine = new FilterEngine();
49
85
  const { ResponseToolTextCanonicalizeFilter, ResponseToolArgumentsStringifyFilter, ResponseFinishInvariantsFilter } = await import('../../filters/index.js');
50
86
  engine.registerFilter(new ResponseToolTextCanonicalizeFilter());
@@ -73,7 +109,11 @@ export async function runChatResponseToolFilters(chatJson, options = {}) {
73
109
  }
74
110
  catch { /* ignore */ }
75
111
  let staged = await engine.run('response_pre', chatJson, resCtxBase);
112
+ recordStage('resp_process_tool_filters_response_pre', staged);
76
113
  staged = await engine.run('response_map', staged, resCtxBase);
114
+ recordStage('resp_process_tool_filters_response_map', staged);
77
115
  staged = await engine.run('response_post', staged, resCtxBase);
116
+ recordStage('resp_process_tool_filters_response_post', staged);
117
+ recordStage('resp_process_tool_filters_output', staged);
78
118
  return staged;
79
119
  }
@@ -1,5 +1,8 @@
1
1
  // Unified tool governance API (标准)
2
2
  // Centralizes tool augmentation, guidance injection/refinement, and textual→tool_calls canonicalization
3
+ // canonicalizer 按需加载(避免在请求侧仅注入时引入不必要的模块)
4
+ // enforceChatBudget: 为避免在请求侧引入多余依赖,这里提供最小实现(保留形状,不裁剪)。
5
+ import { augmentOpenAITools } from '../../guidance/index.js';
3
6
  function isObject(v) { return !!v && typeof v === 'object' && !Array.isArray(v); }
4
7
  // Note: tool schema strict augmentation removed per alignment
5
8
  function enforceChatBudget(chat, _modelId) { return chat; }
@@ -63,6 +66,8 @@ export function processChatRequestTools(request, opts) {
63
66
  t.function = { ...fn, parameters: {} };
64
67
  }
65
68
  }
69
+ // 严格化工具 schema(apply_patch/shell/MCP 等)保持在唯一治理点,避免重复注入
70
+ out.tools = augmentOpenAITools(tools);
66
71
  }
67
72
  }
68
73
  catch { /* best-effort: 保持原样 */ }
@@ -32,8 +32,8 @@ function pickToolName(candidates, options) {
32
32
  return undefined;
33
33
  }
34
34
  function resolveToolDescription(candidate) {
35
- if (typeof candidate === 'string' && candidate.trim().length) {
36
- return candidate.trim();
35
+ if (typeof candidate === 'string') {
36
+ return candidate;
37
37
  }
38
38
  return undefined;
39
39
  }
@@ -121,6 +121,17 @@ export function chatToolToBridgeDefinition(rawTool, options) {
121
121
  if (strict !== undefined) {
122
122
  responseShape.strict = strict;
123
123
  }
124
+ const fnOut = { name };
125
+ if (description !== undefined) {
126
+ fnOut.description = description;
127
+ }
128
+ if (parameters !== undefined) {
129
+ fnOut.parameters = parameters;
130
+ }
131
+ if (strict !== undefined) {
132
+ fnOut.strict = strict;
133
+ }
134
+ responseShape.function = fnOut;
124
135
  return responseShape;
125
136
  }
126
137
  export function mapChatToolsToBridge(rawTools, options) {
@@ -11,8 +11,10 @@ export class RequestToolChoicePolicyFilter {
11
11
  const out = JSON.parse(JSON.stringify(input || {}));
12
12
  const tools = Array.isArray(out.tools) ? out.tools : [];
13
13
  if (tools.length > 0) {
14
- if (out.tool_choice == null)
14
+ const hasOwn = Object.prototype.hasOwnProperty.call(out, 'tool_choice');
15
+ if (!hasOwn || out.tool_choice === undefined) {
15
16
  out.tool_choice = 'auto';
17
+ }
16
18
  }
17
19
  else {
18
20
  if (Object.prototype.hasOwnProperty.call(out, 'tool_choice'))
@@ -0,0 +1,11 @@
1
+ import type { Filter, FilterContext, FilterResult, JsonObject } from '../types.js';
2
+ /**
3
+ * RequestToolListFilter (request_pre)
4
+ * - Generic tool-list filter/augment hook
5
+ * - Currently only handles MCP tools with phase-based exposure
6
+ */
7
+ export declare class RequestToolListFilter implements Filter<JsonObject> {
8
+ readonly name = "request_tool_list_filter";
9
+ readonly stage: FilterContext['stage'];
10
+ apply(input: JsonObject, ctx: FilterContext): FilterResult<JsonObject>;
11
+ }
@@ -94,15 +94,21 @@ function removeToolByName(tools, name) {
94
94
  export class RequestToolListFilter {
95
95
  name = 'request_tool_list_filter';
96
96
  stage = 'request_pre';
97
- apply(input) {
97
+ apply(input, ctx) {
98
98
  try {
99
99
  const out = JSON.parse(JSON.stringify(input || {}));
100
- const tools = Array.isArray(out.tools) ? out.tools : [];
101
- if (!tools.length)
102
- return { ok: true, data: out };
100
+ const hadIncomingTools = Array.isArray(out.tools);
101
+ const tools = hadIncomingTools ? out.tools : [];
102
+ if (!hadIncomingTools) {
103
+ out.tools = tools;
104
+ }
103
105
  const mode = envMode();
104
- if (mode === 'off')
106
+ if (mode === 'off') {
107
+ if (!hadIncomingTools && tools.length === 0) {
108
+ delete out.tools;
109
+ }
105
110
  return { ok: true, data: out };
111
+ }
106
112
  const messages = Array.isArray(out.messages) ? out.messages : [];
107
113
  const servers = new Set();
108
114
  for (const s of getEnvServers())
@@ -137,14 +143,21 @@ export class RequestToolListFilter {
137
143
  required: ['server', 'uri'],
138
144
  additionalProperties: false
139
145
  };
146
+ const profileKey = String(ctx?.profile || '').toLowerCase();
147
+ const endpointKey = String(ctx?.endpoint || '').toLowerCase();
148
+ const prefersLegacyListDescription = profileKey.includes('anthropic') ||
149
+ endpointKey.includes('/v1/messages');
150
+ const listDescription = prefersLegacyListDescription
151
+ ? 'List resources from an MCP server.'
152
+ : 'List resources from a given MCP server (arguments.server = server label).';
140
153
  if (mode === 'all') {
141
- ensureFunctionTool(tools, 'list_mcp_resources', 'List resources from an MCP server.', listResParams);
154
+ ensureFunctionTool(tools, 'list_mcp_resources', listDescription, listResParams);
142
155
  ensureFunctionTool(tools, 'list_mcp_resource_templates', 'List resource templates from MCP servers.', listTplParams);
143
156
  ensureFunctionTool(tools, 'read_mcp_resource', 'Read a specific MCP resource.', readResParamsBase);
144
157
  }
145
158
  else {
146
159
  // phase
147
- ensureFunctionTool(tools, 'list_mcp_resources', 'List resources from an MCP server.', listResParams);
160
+ ensureFunctionTool(tools, 'list_mcp_resources', listDescription, listResParams);
148
161
  ensureFunctionTool(tools, 'list_mcp_resource_templates', 'List resource templates from MCP servers.', listTplParams);
149
162
  // read is only exposed when we have known servers
150
163
  if (knownServers.length > 0) {
@@ -13,9 +13,8 @@ export function normalizeResponsesMessageItem(item, options) {
13
13
  const extras = Array.isArray(additionalReasoning) ? additionalReasoning : [additionalReasoning];
14
14
  for (const entry of extras) {
15
15
  if (typeof entry === 'string') {
16
- const trimmed = entry.trim();
17
- if (trimmed.length) {
18
- reasoningChunks.push(trimmed);
16
+ if (entry.length) {
17
+ reasoningChunks.push(entry);
19
18
  }
20
19
  }
21
20
  }
@@ -41,5 +40,7 @@ export function normalizeResponsesMessageItem(item, options) {
41
40
  }
42
41
  export function expandResponsesMessageItem(item, options) {
43
42
  const { message, reasoning } = normalizeResponsesMessageItem(item, options);
44
- return reasoning ? [message, reasoning] : [message];
43
+ // Responses upstream (legacy + CCR) emitted reasoning segments before the final assistant message.
44
+ // Preserve that ordering so roundtrip comparisons against provider snapshots remain identical.
45
+ return reasoning ? [reasoning, message] : [message];
45
46
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsonstudio/llms",
3
- "version": "0.4.5",
3
+ "version": "0.6.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",