@jsonstudio/llms 0.6.3688 → 0.6.3689

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.
@@ -1,4 +1,303 @@
1
+ import { ensureProtocolState } from '../../../protocol-state.js';
2
+ import { isJsonObject, jsonClone } from '../../types/json.js';
1
3
  import { mapOpenaiChatFromChatWithNative, mapOpenaiChatToChatWithNative } from '../../../../router/virtual-router/engine-selection/native-hub-pipeline-semantic-mappers.js';
4
+ import { mapReqInboundBridgeToolsToChatWithNative } from '../../../../router/virtual-router/engine-selection/native-hub-pipeline-req-inbound-semantics.js';
5
+ const CHAT_PARAMETER_KEYS = [
6
+ 'model',
7
+ 'temperature',
8
+ 'top_p',
9
+ 'top_k',
10
+ 'max_tokens',
11
+ 'frequency_penalty',
12
+ 'presence_penalty',
13
+ 'logit_bias',
14
+ 'response_format',
15
+ 'parallel_tool_calls',
16
+ 'tool_choice',
17
+ 'seed',
18
+ 'user',
19
+ 'metadata',
20
+ 'stop',
21
+ 'stop_sequences',
22
+ 'stream'
23
+ ];
24
+ const KNOWN_TOP_LEVEL_FIELDS = new Set([
25
+ 'messages',
26
+ 'tools',
27
+ 'tool_outputs',
28
+ ...CHAT_PARAMETER_KEYS,
29
+ 'stageExpectations',
30
+ 'stages'
31
+ ]);
32
+ function flattenSystemContent(content) {
33
+ if (typeof content === 'string') {
34
+ return content;
35
+ }
36
+ if (Array.isArray(content)) {
37
+ return content.map(flattenSystemContent).filter(Boolean).join('\n');
38
+ }
39
+ if (content && typeof content === 'object') {
40
+ const row = content;
41
+ if (typeof row.text === 'string') {
42
+ return row.text;
43
+ }
44
+ if (typeof row.content === 'string') {
45
+ return row.content;
46
+ }
47
+ if (Array.isArray(row.content)) {
48
+ return row.content.map(flattenSystemContent).filter(Boolean).join('\n');
49
+ }
50
+ }
51
+ return '';
52
+ }
53
+ function normalizeToolContent(content) {
54
+ if (content === null || content === undefined) {
55
+ return '执行成功(无输出)';
56
+ }
57
+ if (typeof content === 'string') {
58
+ return content.trim().length ? content : '执行成功(无输出)';
59
+ }
60
+ if (typeof content === 'object') {
61
+ try {
62
+ return JSON.stringify(content);
63
+ }
64
+ catch {
65
+ return String(content);
66
+ }
67
+ }
68
+ return String(content);
69
+ }
70
+ function isEmptyAssistantContent(content) {
71
+ if (content === null || content === undefined) {
72
+ return true;
73
+ }
74
+ if (typeof content === 'string') {
75
+ return content.trim().length === 0;
76
+ }
77
+ if (Array.isArray(content)) {
78
+ const joined = content
79
+ .filter((entry) => Boolean(entry) && typeof entry === 'object' && !Array.isArray(entry))
80
+ .map((entry) => (typeof entry.text === 'string' ? entry.text : ''))
81
+ .join('');
82
+ return joined.trim().length === 0;
83
+ }
84
+ return false;
85
+ }
86
+ function collectSystemRawBlocks(raw) {
87
+ if (!Array.isArray(raw)) {
88
+ return undefined;
89
+ }
90
+ const blocks = [];
91
+ for (const entry of raw) {
92
+ if (!isJsonObject(entry)) {
93
+ continue;
94
+ }
95
+ if (String(entry.role ?? '').toLowerCase() !== 'system') {
96
+ continue;
97
+ }
98
+ blocks.push(jsonClone(entry));
99
+ }
100
+ return blocks.length ? blocks : undefined;
101
+ }
102
+ function collectExtraFields(payload) {
103
+ const extras = {};
104
+ for (const [key, value] of Object.entries(payload)) {
105
+ if (KNOWN_TOP_LEVEL_FIELDS.has(key) || value === undefined) {
106
+ continue;
107
+ }
108
+ extras[key] = jsonClone(value);
109
+ }
110
+ return Object.keys(extras).length ? extras : undefined;
111
+ }
112
+ function extractParameters(payload) {
113
+ const parameters = {};
114
+ for (const key of CHAT_PARAMETER_KEYS) {
115
+ if (payload[key] !== undefined) {
116
+ parameters[key] = payload[key];
117
+ }
118
+ }
119
+ return Object.keys(parameters).length ? parameters : undefined;
120
+ }
121
+ function buildOpenaiSemantics(systemSegments, extraFields, explicitEmptyTools) {
122
+ const semantics = {};
123
+ if (systemSegments.length > 0) {
124
+ semantics.system = {
125
+ textBlocks: systemSegments
126
+ };
127
+ }
128
+ if (extraFields && Object.keys(extraFields).length > 0) {
129
+ semantics.providerExtras = {
130
+ openaiChat: {
131
+ extraFields
132
+ }
133
+ };
134
+ }
135
+ if (explicitEmptyTools) {
136
+ semantics.tools = {
137
+ explicitEmpty: true
138
+ };
139
+ }
140
+ return Object.keys(semantics).length > 0 ? semantics : undefined;
141
+ }
142
+ function normalizeAssistantToolCallsFast(message) {
143
+ if (message.tool_calls === undefined) {
144
+ return undefined;
145
+ }
146
+ if (!Array.isArray(message.tool_calls)) {
147
+ return null;
148
+ }
149
+ const normalized = [];
150
+ for (const entry of message.tool_calls) {
151
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
152
+ return null;
153
+ }
154
+ const toolCall = entry;
155
+ const functionNode = toolCall.function && typeof toolCall.function === 'object' && !Array.isArray(toolCall.function)
156
+ ? toolCall.function
157
+ : undefined;
158
+ if (!functionNode) {
159
+ return null;
160
+ }
161
+ const rawName = typeof functionNode.name === 'string' ? functionNode.name.trim() : '';
162
+ if (!rawName) {
163
+ return null;
164
+ }
165
+ const rawArguments = functionNode.arguments;
166
+ if (rawArguments !== undefined && rawArguments !== null && typeof rawArguments !== 'string') {
167
+ return null;
168
+ }
169
+ const dot = rawName.indexOf('.');
170
+ const normalizedName = dot >= 0 ? rawName.slice(dot + 1).trim() : rawName;
171
+ if (!normalizedName) {
172
+ return null;
173
+ }
174
+ normalized.push({
175
+ ...toolCall,
176
+ function: {
177
+ ...functionNode,
178
+ name: normalizedName,
179
+ arguments: typeof rawArguments === 'string' ? rawArguments : '{}'
180
+ }
181
+ });
182
+ }
183
+ return normalized;
184
+ }
185
+ function normalizeToolDefinitionsFast(rawTools) {
186
+ if (rawTools === undefined) {
187
+ return undefined;
188
+ }
189
+ if (!Array.isArray(rawTools)) {
190
+ return null;
191
+ }
192
+ if (rawTools.length === 0) {
193
+ return undefined;
194
+ }
195
+ const mapped = mapReqInboundBridgeToolsToChatWithNative(rawTools);
196
+ return mapped.length > 0 ? mapped : null;
197
+ }
198
+ function tryMapOpenaiChatToChatFast(payload, ctx) {
199
+ if (!Array.isArray(payload.messages) || payload.tool_outputs !== undefined) {
200
+ return undefined;
201
+ }
202
+ const normalizedMessages = [];
203
+ const toolOutputs = [];
204
+ const seenToolOutputIds = new Set();
205
+ const systemSegments = [];
206
+ for (const entry of payload.messages) {
207
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
208
+ return undefined;
209
+ }
210
+ const message = entry;
211
+ const role = typeof message.role === 'string' ? message.role.trim().toLowerCase() : '';
212
+ if (role !== 'system' && role !== 'user' && role !== 'assistant' && role !== 'tool') {
213
+ return undefined;
214
+ }
215
+ const nextMessage = {
216
+ ...jsonClone(message),
217
+ role: role
218
+ };
219
+ if (role === 'assistant') {
220
+ const normalizedToolCalls = normalizeAssistantToolCallsFast(message);
221
+ if (normalizedToolCalls === null) {
222
+ return undefined;
223
+ }
224
+ if (normalizedToolCalls !== undefined) {
225
+ nextMessage.tool_calls = normalizedToolCalls;
226
+ }
227
+ const content = nextMessage.content;
228
+ if (content !== undefined &&
229
+ content !== null &&
230
+ typeof content !== 'string' &&
231
+ !Array.isArray(content) &&
232
+ typeof content !== 'object') {
233
+ nextMessage.content = String(content);
234
+ }
235
+ if ((nextMessage.tool_calls?.length ?? 0) === 0 && isEmptyAssistantContent(nextMessage.content)) {
236
+ continue;
237
+ }
238
+ normalizedMessages.push(nextMessage);
239
+ continue;
240
+ }
241
+ if (role === 'tool') {
242
+ const rawToolCallId = message.tool_call_id ?? message.call_id ?? message.id;
243
+ const toolCallId = typeof rawToolCallId === 'string' ? rawToolCallId.trim() : '';
244
+ if (!toolCallId) {
245
+ return undefined;
246
+ }
247
+ nextMessage.tool_call_id = toolCallId;
248
+ nextMessage.content = normalizeToolContent(message.content ?? message.output);
249
+ const name = typeof message.name === 'string' && message.name.trim().length ? message.name.trim() : undefined;
250
+ if (!seenToolOutputIds.has(toolCallId)) {
251
+ seenToolOutputIds.add(toolCallId);
252
+ toolOutputs.push({
253
+ tool_call_id: toolCallId,
254
+ content: maybeAugmentApplyPatchErrorContent(nextMessage.content, name),
255
+ ...(name ? { name } : {})
256
+ });
257
+ }
258
+ normalizedMessages.push(nextMessage);
259
+ continue;
260
+ }
261
+ if (role === 'system') {
262
+ const segment = flattenSystemContent(message.content);
263
+ if (segment.trim().length > 0) {
264
+ systemSegments.push(segment);
265
+ }
266
+ }
267
+ else {
268
+ const content = nextMessage.content;
269
+ if (content !== undefined &&
270
+ content !== null &&
271
+ typeof content !== 'string' &&
272
+ !Array.isArray(content) &&
273
+ typeof content !== 'object') {
274
+ nextMessage.content = String(content);
275
+ }
276
+ }
277
+ normalizedMessages.push(nextMessage);
278
+ }
279
+ const tools = normalizeToolDefinitionsFast(payload.tools);
280
+ if (tools === null) {
281
+ return undefined;
282
+ }
283
+ const metadata = { context: ctx };
284
+ const rawSystemBlocks = collectSystemRawBlocks(payload.messages);
285
+ if (rawSystemBlocks) {
286
+ const protocolState = ensureProtocolState(metadata, 'openai');
287
+ protocolState.systemMessages = jsonClone(rawSystemBlocks);
288
+ }
289
+ const parameters = extractParameters(payload);
290
+ const extraFields = collectExtraFields(payload);
291
+ const semantics = buildOpenaiSemantics(systemSegments, extraFields, Array.isArray(payload.tools) && payload.tools.length === 0);
292
+ return {
293
+ messages: normalizedMessages,
294
+ ...(tools ? { tools } : {}),
295
+ ...(toolOutputs.length > 0 ? { toolOutputs } : {}),
296
+ ...(parameters ? { parameters } : {}),
297
+ ...(semantics ? { semantics } : {}),
298
+ metadata
299
+ };
300
+ }
2
301
  export function maybeAugmentApplyPatchErrorContent(content, toolName) {
3
302
  if (!content)
4
303
  return content;
@@ -28,7 +327,12 @@ export function maybeAugmentApplyPatchErrorContent(content, toolName) {
28
327
  }
29
328
  export class ChatSemanticMapper {
30
329
  async toChat(format, ctx) {
31
- return mapOpenaiChatToChatWithNative((format.payload ?? {}), ctx);
330
+ const payload = (format.payload ?? {});
331
+ const fastMapped = tryMapOpenaiChatToChatFast(payload, ctx);
332
+ if (fastMapped) {
333
+ return fastMapped;
334
+ }
335
+ return mapOpenaiChatToChatWithNative(payload, ctx);
32
336
  }
33
337
  async fromChat(chat, ctx) {
34
338
  return mapOpenaiChatFromChatWithNative(chat, ctx);
@@ -35,7 +35,7 @@ import { applyHubProviderOutboundPolicy, recordHubPolicyObservation, setHubPolic
35
35
  import { applyProviderOutboundToolSurface, } from "../tool-surface/tool-surface-engine.js";
36
36
  import { cloneRuntimeMetadata, ensureRuntimeMetadata, readRuntimeMetadata, } from "../../runtime-metadata.js";
37
37
  import { containsImageAttachment, stripHistoricalImageAttachments, stripHistoricalVisualToolOutputs, repairIncompleteToolCalls, } from "../process/chat-process-media.js";
38
- import { measureHubStage, logHubStageTiming } from "./hub-stage-timing.js";
38
+ import { measureHubStage, logHubStageTiming, clearHubStageTiming, } from "./hub-stage-timing.js";
39
39
  function isTruthyEnv(value) {
40
40
  const v = typeof value === "string" ? value.trim().toLowerCase() : "";
41
41
  return v === "1" || v === "true" || v === "yes" || v === "on";
@@ -1341,15 +1341,21 @@ export class HubPipeline {
1341
1341
  }
1342
1342
  async execute(request) {
1343
1343
  const normalized = await this.normalizeRequest(request);
1344
- if (normalized.direction === "request" &&
1345
- normalized.hubEntryMode === "chat_process") {
1346
- return await this.executeChatProcessEntryPipeline(normalized);
1344
+ clearHubStageTiming(normalized.id);
1345
+ try {
1346
+ if (normalized.direction === "request" &&
1347
+ normalized.hubEntryMode === "chat_process") {
1348
+ return await this.executeChatProcessEntryPipeline(normalized);
1349
+ }
1350
+ const hooks = this.resolveProtocolHooks(normalized.providerProtocol);
1351
+ if (!hooks) {
1352
+ throw new Error(`Unsupported provider protocol for hub pipeline: ${normalized.providerProtocol}`);
1353
+ }
1354
+ return await this.executeRequestStagePipeline(normalized, hooks);
1347
1355
  }
1348
- const hooks = this.resolveProtocolHooks(normalized.providerProtocol);
1349
- if (!hooks) {
1350
- throw new Error(`Unsupported provider protocol for hub pipeline: ${normalized.providerProtocol}`);
1356
+ finally {
1357
+ clearHubStageTiming(normalized.id);
1351
1358
  }
1352
- return await this.executeRequestStagePipeline(normalized, hooks);
1353
1359
  }
1354
1360
  captureAnthropicAliasMap(normalized, adapterContext, chatEnvelope) {
1355
1361
  if (!this.shouldCaptureAnthropicAlias(normalized.entryEndpoint)) {
@@ -1,4 +1,5 @@
1
1
  export declare function isHubStageTimingDetailEnabled(): boolean;
2
+ export declare function clearHubStageTiming(requestId: string | undefined | null): void;
2
3
  export declare function logHubStageTiming(requestId: string, stage: string, phase: 'start' | 'completed' | 'error', details?: Record<string, unknown>): void;
3
4
  export declare function measureHubStage<T>(requestId: string, stage: string, fn: () => Promise<T> | T, options?: {
4
5
  startDetails?: Record<string, unknown>;
@@ -120,6 +120,12 @@ function renderDetails(details) {
120
120
  return '';
121
121
  }
122
122
  }
123
+ export function clearHubStageTiming(requestId) {
124
+ if (!requestId) {
125
+ return;
126
+ }
127
+ REQUEST_TIMELINES.delete(requestId);
128
+ }
123
129
  export function logHubStageTiming(requestId, stage, phase, details) {
124
130
  if (!isHubStageTimingEnabled() || !requestId || !stage) {
125
131
  return;
@@ -45,6 +45,58 @@ function filterRedundantResponsesReasoningAction(actions) {
45
45
  return name !== 'reasoning.extract';
46
46
  });
47
47
  }
48
+ const RESPONSES_TOOL_PASSTHROUGH_KEYS = [
49
+ 'temperature',
50
+ 'tool_choice',
51
+ 'parallel_tool_calls',
52
+ 'response_format',
53
+ 'user',
54
+ 'top_p',
55
+ 'prompt_cache_key',
56
+ 'reasoning',
57
+ 'logit_bias',
58
+ 'seed'
59
+ ];
60
+ function pickObjectFields(value, keys) {
61
+ if (!value) {
62
+ return undefined;
63
+ }
64
+ const picked = {};
65
+ for (const key of keys) {
66
+ if (value[key] !== undefined) {
67
+ picked[key] = value[key];
68
+ }
69
+ }
70
+ return Object.keys(picked).length ? picked : undefined;
71
+ }
72
+ function buildSlimResponsesBridgeContext(context) {
73
+ if (!context || typeof context !== 'object') {
74
+ return undefined;
75
+ }
76
+ const slim = {};
77
+ if (Array.isArray(context.input) && context.input.length) {
78
+ slim.input = context.input;
79
+ }
80
+ if (Array.isArray(context.originalSystemMessages) && context.originalSystemMessages.length) {
81
+ slim.originalSystemMessages = context.originalSystemMessages;
82
+ }
83
+ if (typeof context.systemInstruction === 'string' && context.systemInstruction.trim().length) {
84
+ slim.systemInstruction = context.systemInstruction;
85
+ }
86
+ if (typeof context.toolCallIdStyle === 'string' && context.toolCallIdStyle.trim().length) {
87
+ slim.toolCallIdStyle = context.toolCallIdStyle;
88
+ }
89
+ if (context.metadata && typeof context.metadata === 'object' && !Array.isArray(context.metadata)) {
90
+ slim.metadata = context.metadata;
91
+ }
92
+ return Object.keys(slim).length ? slim : undefined;
93
+ }
94
+ function buildSlimBridgeDecisionMetadata(metadata) {
95
+ if (!metadata) {
96
+ return undefined;
97
+ }
98
+ return pickObjectFields(metadata, ['toolCallIdStyle', 'bridgeHistory']);
99
+ }
48
100
  // normalizeTools unified in ../args-mapping.ts
49
101
  // NOTE: 自修复提示已移除(统一标准:不做模糊兜底)。
50
102
  // --- Public bridge functions ---
@@ -222,10 +274,10 @@ export function buildResponsesRequestFromChat(payload, ctx, extras) {
222
274
  }
223
275
  const metadataExtraFields = extractMetadataExtraFields(envelopeMetadata);
224
276
  const bridgeDecisions = resolveResponsesRequestBridgeDecisionsWithNative({
225
- context: ctx && typeof ctx === 'object' ? ctx : undefined,
226
- requestMetadata,
227
- envelopeMetadata,
228
- bridgeMetadata,
277
+ context: buildSlimResponsesBridgeContext(ctx),
278
+ requestMetadata: buildSlimBridgeDecisionMetadata(requestMetadata),
279
+ envelopeMetadata: buildSlimBridgeDecisionMetadata(envelopeMetadata),
280
+ bridgeMetadata: buildSlimBridgeDecisionMetadata(bridgeMetadata),
229
281
  extraBridgeHistory: extras?.bridgeHistory
230
282
  });
231
283
  const forceWebSearch = bridgeDecisions.forceWebSearch === true;
@@ -241,19 +293,8 @@ export function buildResponsesRequestFromChat(payload, ctx, extras) {
241
293
  originalTools: Array.isArray(originalTools) ? originalTools : undefined,
242
294
  chatTools: Array.isArray(responsesToolsFromChat) ? responsesToolsFromChat : undefined,
243
295
  hasServerSideWebSearch: !forceWebSearch,
244
- passthroughKeys: [
245
- 'temperature',
246
- 'tool_choice',
247
- 'parallel_tool_calls',
248
- 'response_format',
249
- 'user',
250
- 'top_p',
251
- 'prompt_cache_key',
252
- 'reasoning',
253
- 'logit_bias',
254
- 'seed'
255
- ],
256
- request: chat
296
+ passthroughKeys: [...RESPONSES_TOOL_PASSTHROUGH_KEYS],
297
+ request: pickObjectFields(chat, RESPONSES_TOOL_PASSTHROUGH_KEYS)
257
298
  });
258
299
  const mergedTools = resolvedBridgeTools.mergedTools;
259
300
  if (mergedTools?.length) {
@@ -48,6 +48,11 @@ export function normalizeReasoningInChatPayload(payload) {
48
48
  assertReasoningNormalizerNativeAvailable();
49
49
  if (!payload)
50
50
  return;
51
+ const shouldNormalize = valueMayContainReasoningMarkup(payload.messages) ||
52
+ valueMayContainReasoningMarkup(payload.choices);
53
+ if (!shouldNormalize) {
54
+ return;
55
+ }
51
56
  const normalized = normalizeReasoningInChatPayloadWithNative(payload);
52
57
  if (normalized && typeof normalized === 'object') {
53
58
  Object.assign(payload, normalized);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsonstudio/llms",
3
- "version": "0.6.3688",
3
+ "version": "0.6.3689",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",