@jsonstudio/llms 0.6.568 → 0.6.626
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/compat/profiles/chat-gemini.json +15 -15
- package/dist/conversion/compat/profiles/chat-glm.json +194 -194
- package/dist/conversion/compat/profiles/chat-iflow.json +199 -199
- package/dist/conversion/compat/profiles/chat-lmstudio.json +43 -43
- package/dist/conversion/compat/profiles/chat-qwen.json +20 -20
- package/dist/conversion/compat/profiles/responses-c4m.json +42 -42
- package/dist/conversion/compat/profiles/responses-output2choices-test.json +9 -10
- package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +0 -1
- package/dist/conversion/hub/pipeline/hub-pipeline.js +68 -69
- package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.js +0 -34
- package/dist/conversion/hub/process/chat-process.js +37 -16
- package/dist/conversion/hub/response/provider-response.js +0 -8
- package/dist/conversion/hub/response/response-runtime.js +47 -1
- package/dist/conversion/hub/semantic-mappers/anthropic-mapper.js +59 -4
- package/dist/conversion/hub/semantic-mappers/chat-mapper.d.ts +8 -0
- package/dist/conversion/hub/semantic-mappers/chat-mapper.js +93 -12
- package/dist/conversion/hub/semantic-mappers/gemini-mapper.js +208 -31
- package/dist/conversion/hub/semantic-mappers/responses-mapper.js +280 -14
- package/dist/conversion/hub/standardized-bridge.js +11 -2
- package/dist/conversion/hub/types/chat-envelope.d.ts +10 -0
- package/dist/conversion/hub/types/standardized.d.ts +2 -1
- package/dist/conversion/responses/responses-openai-bridge.d.ts +3 -2
- package/dist/conversion/responses/responses-openai-bridge.js +1 -13
- package/dist/conversion/shared/text-markup-normalizer.d.ts +20 -0
- package/dist/conversion/shared/text-markup-normalizer.js +84 -5
- package/dist/conversion/shared/tool-filter-pipeline.d.ts +1 -1
- package/dist/conversion/shared/tool-filter-pipeline.js +54 -29
- package/dist/filters/index.d.ts +1 -0
- package/dist/filters/special/response-apply-patch-toon-decode.js +15 -7
- package/dist/filters/special/response-tool-arguments-toon-decode.js +108 -22
- package/dist/guidance/index.js +2 -0
- package/dist/router/virtual-router/classifier.js +16 -12
- package/dist/router/virtual-router/engine.js +45 -4
- package/dist/router/virtual-router/tool-signals.d.ts +2 -1
- package/dist/router/virtual-router/tool-signals.js +293 -134
- package/dist/router/virtual-router/types.d.ts +1 -1
- package/dist/router/virtual-router/types.js +1 -1
- package/dist/servertool/handlers/gemini-empty-reply-continue.js +28 -4
- package/dist/sse/json-to-sse/event-generators/responses.js +9 -2
- package/dist/sse/sse-to-json/builders/anthropic-response-builder.js +7 -3
- package/dist/tools/apply-patch-structured.js +4 -3
- package/package.json +2 -2
|
@@ -2,6 +2,7 @@ import { isJsonObject, jsonClone } from '../types/json.js';
|
|
|
2
2
|
import { createBridgeActionState, runBridgeActionPipeline } from '../../shared/bridge-actions.js';
|
|
3
3
|
import { resolveBridgePolicy, resolvePolicyActions } from '../../shared/bridge-policies.js';
|
|
4
4
|
import { captureResponsesContext, buildChatRequestFromResponses, buildResponsesRequestFromChat } from '../../responses/responses-openai-bridge.js';
|
|
5
|
+
import { maybeAugmentApplyPatchErrorContent } from './chat-mapper.js';
|
|
5
6
|
const RESPONSES_PARAMETER_KEYS = [
|
|
6
7
|
'model',
|
|
7
8
|
'temperature',
|
|
@@ -19,6 +20,7 @@ const RESPONSES_PARAMETER_KEYS = [
|
|
|
19
20
|
'stop_sequences',
|
|
20
21
|
'modalities'
|
|
21
22
|
];
|
|
23
|
+
const RESPONSES_SUBMIT_ENDPOINT = '/v1/responses.submit_tool_outputs';
|
|
22
24
|
function mapToolOutputs(entries, missing) {
|
|
23
25
|
if (!entries || !entries.length)
|
|
24
26
|
return undefined;
|
|
@@ -45,10 +47,12 @@ function mapToolOutputs(entries, missing) {
|
|
|
45
47
|
content = String(entry.output);
|
|
46
48
|
}
|
|
47
49
|
}
|
|
50
|
+
const nameValue = typeof entry.name === 'string' ? entry.name : undefined;
|
|
51
|
+
const augmented = maybeAugmentApplyPatchErrorContent(content, nameValue);
|
|
48
52
|
outputs.push({
|
|
49
53
|
tool_call_id: String(callId),
|
|
50
|
-
content,
|
|
51
|
-
name:
|
|
54
|
+
content: augmented,
|
|
55
|
+
name: nameValue
|
|
52
56
|
});
|
|
53
57
|
});
|
|
54
58
|
return outputs.length ? outputs : undefined;
|
|
@@ -197,6 +201,259 @@ function mergeMetadata(a, b) {
|
|
|
197
201
|
const right = jsonClone(b);
|
|
198
202
|
return { ...left, ...right };
|
|
199
203
|
}
|
|
204
|
+
function isSubmitToolOutputsEndpoint(ctx) {
|
|
205
|
+
if (!ctx || typeof ctx !== 'object') {
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
const entry = typeof ctx.entryEndpoint === 'string' ? ctx.entryEndpoint.trim().toLowerCase() : '';
|
|
209
|
+
return entry === RESPONSES_SUBMIT_ENDPOINT;
|
|
210
|
+
}
|
|
211
|
+
function attachResponsesSemantics(existing, context, resume) {
|
|
212
|
+
if (!context && !resume) {
|
|
213
|
+
return existing;
|
|
214
|
+
}
|
|
215
|
+
const next = existing ? { ...existing } : {};
|
|
216
|
+
const currentNode = next.responses && isJsonObject(next.responses) ? { ...next.responses } : {};
|
|
217
|
+
if (context) {
|
|
218
|
+
currentNode.context = jsonClone(context);
|
|
219
|
+
}
|
|
220
|
+
if (resume) {
|
|
221
|
+
currentNode.resume = jsonClone(resume);
|
|
222
|
+
}
|
|
223
|
+
next.responses = currentNode;
|
|
224
|
+
return next;
|
|
225
|
+
}
|
|
226
|
+
function extractResponsesSemanticsNode(chat) {
|
|
227
|
+
if (!chat?.semantics || typeof chat.semantics !== 'object') {
|
|
228
|
+
return undefined;
|
|
229
|
+
}
|
|
230
|
+
const node = chat.semantics.responses;
|
|
231
|
+
return node && isJsonObject(node) ? node : undefined;
|
|
232
|
+
}
|
|
233
|
+
function readResponsesContextFromSemantics(chat) {
|
|
234
|
+
const node = extractResponsesSemanticsNode(chat);
|
|
235
|
+
if (!node) {
|
|
236
|
+
return undefined;
|
|
237
|
+
}
|
|
238
|
+
const contextNode = node.context;
|
|
239
|
+
if (!contextNode || !isJsonObject(contextNode)) {
|
|
240
|
+
return undefined;
|
|
241
|
+
}
|
|
242
|
+
return jsonClone(contextNode);
|
|
243
|
+
}
|
|
244
|
+
function readResponsesResumeFromSemantics(chat) {
|
|
245
|
+
const node = extractResponsesSemanticsNode(chat);
|
|
246
|
+
if (!node) {
|
|
247
|
+
return undefined;
|
|
248
|
+
}
|
|
249
|
+
const resumeNode = node.resume;
|
|
250
|
+
if (!resumeNode || !isJsonObject(resumeNode)) {
|
|
251
|
+
return undefined;
|
|
252
|
+
}
|
|
253
|
+
return jsonClone(resumeNode);
|
|
254
|
+
}
|
|
255
|
+
function selectResponsesContextSnapshot(chat, envelopeMetadata) {
|
|
256
|
+
const semanticsContext = readResponsesContextFromSemantics(chat);
|
|
257
|
+
const metadataContextCandidate = chat.metadata?.responsesContext;
|
|
258
|
+
const metadataContext = metadataContextCandidate && isJsonObject(metadataContextCandidate)
|
|
259
|
+
? jsonClone(metadataContextCandidate)
|
|
260
|
+
: undefined;
|
|
261
|
+
const context = semanticsContext ??
|
|
262
|
+
metadataContext ??
|
|
263
|
+
{
|
|
264
|
+
metadata: envelopeMetadata
|
|
265
|
+
};
|
|
266
|
+
const mergedMetadata = mergeMetadata(context.metadata ?? undefined, envelopeMetadata);
|
|
267
|
+
if (mergedMetadata) {
|
|
268
|
+
context.metadata = mergedMetadata;
|
|
269
|
+
}
|
|
270
|
+
return context;
|
|
271
|
+
}
|
|
272
|
+
function resolveSubmitResponseId(ctx, responsesContext) {
|
|
273
|
+
const resumeMeta = ctx.responsesResume && typeof ctx.responsesResume === 'object'
|
|
274
|
+
? ctx.responsesResume
|
|
275
|
+
: undefined;
|
|
276
|
+
const resumeId = typeof resumeMeta?.restoredFromResponseId === 'string'
|
|
277
|
+
? resumeMeta.restoredFromResponseId.trim()
|
|
278
|
+
: undefined;
|
|
279
|
+
if (resumeId) {
|
|
280
|
+
return resumeId;
|
|
281
|
+
}
|
|
282
|
+
const contextRecord = responsesContext && typeof responsesContext === 'object'
|
|
283
|
+
? responsesContext
|
|
284
|
+
: undefined;
|
|
285
|
+
const previousId = typeof contextRecord?.previous_response_id === 'string'
|
|
286
|
+
? contextRecord.previous_response_id.trim()
|
|
287
|
+
: undefined;
|
|
288
|
+
if (previousId) {
|
|
289
|
+
return previousId;
|
|
290
|
+
}
|
|
291
|
+
return undefined;
|
|
292
|
+
}
|
|
293
|
+
function extractSubmitMetadata(responsesContext, resumeMeta) {
|
|
294
|
+
if (responsesContext && responsesContext.metadata && isJsonObject(responsesContext.metadata)) {
|
|
295
|
+
return jsonClone(responsesContext.metadata);
|
|
296
|
+
}
|
|
297
|
+
if (resumeMeta && resumeMeta.metadata && isJsonObject(resumeMeta.metadata)) {
|
|
298
|
+
return jsonClone(resumeMeta.metadata);
|
|
299
|
+
}
|
|
300
|
+
return undefined;
|
|
301
|
+
}
|
|
302
|
+
function coerceOutputText(value) {
|
|
303
|
+
if (typeof value === 'string') {
|
|
304
|
+
return value;
|
|
305
|
+
}
|
|
306
|
+
if (value === undefined || value === null) {
|
|
307
|
+
return '';
|
|
308
|
+
}
|
|
309
|
+
try {
|
|
310
|
+
return JSON.stringify(value);
|
|
311
|
+
}
|
|
312
|
+
catch {
|
|
313
|
+
return String(value);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
function extractCapturedToolOutputs(responsesContext) {
|
|
317
|
+
if (!responsesContext || typeof responsesContext !== 'object') {
|
|
318
|
+
return [];
|
|
319
|
+
}
|
|
320
|
+
const snapshot = responsesContext.__captured_tool_results;
|
|
321
|
+
if (!Array.isArray(snapshot) || !snapshot.length) {
|
|
322
|
+
return [];
|
|
323
|
+
}
|
|
324
|
+
const entries = [];
|
|
325
|
+
snapshot.forEach((entry) => {
|
|
326
|
+
if (!entry || typeof entry !== 'object') {
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
const record = entry;
|
|
330
|
+
const toolId = typeof record.tool_call_id === 'string' && record.tool_call_id.trim().length
|
|
331
|
+
? record.tool_call_id.trim()
|
|
332
|
+
: typeof record.call_id === 'string' && record.call_id.trim().length
|
|
333
|
+
? record.call_id.trim()
|
|
334
|
+
: undefined;
|
|
335
|
+
if (!toolId) {
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
entries.push({
|
|
339
|
+
tool_call_id: toolId,
|
|
340
|
+
id: toolId,
|
|
341
|
+
output: typeof record.output === 'string' ? record.output : coerceOutputText(record.output),
|
|
342
|
+
name: typeof record.name === 'string' ? record.name : undefined
|
|
343
|
+
});
|
|
344
|
+
});
|
|
345
|
+
return entries;
|
|
346
|
+
}
|
|
347
|
+
function collectSubmitToolOutputs(chat, ctx, responsesContext) {
|
|
348
|
+
const outputs = [];
|
|
349
|
+
const seen = new Set();
|
|
350
|
+
const append = (idSeed, outputSeed, name) => {
|
|
351
|
+
const trimmed = typeof idSeed === 'string' && idSeed.trim().length ? idSeed.trim() : '';
|
|
352
|
+
const fallbackId = trimmed || `submit_tool_${outputs.length + 1}`;
|
|
353
|
+
if (seen.has(fallbackId)) {
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
seen.add(fallbackId);
|
|
357
|
+
outputs.push({
|
|
358
|
+
tool_call_id: fallbackId,
|
|
359
|
+
id: fallbackId,
|
|
360
|
+
output: coerceOutputText(outputSeed),
|
|
361
|
+
name
|
|
362
|
+
});
|
|
363
|
+
};
|
|
364
|
+
if (Array.isArray(chat.toolOutputs) && chat.toolOutputs.length) {
|
|
365
|
+
chat.toolOutputs.forEach((entry) => {
|
|
366
|
+
append(entry.tool_call_id ?? entry.call_id, entry.content, entry.name);
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
if (!outputs.length) {
|
|
370
|
+
const captured = extractCapturedToolOutputs(responsesContext);
|
|
371
|
+
captured.forEach((entry) => append(entry.tool_call_id ?? entry.id, entry.output, entry.name));
|
|
372
|
+
}
|
|
373
|
+
if (!outputs.length) {
|
|
374
|
+
const resume = ctx.responsesResume && typeof ctx.responsesResume === 'object'
|
|
375
|
+
? ctx.responsesResume
|
|
376
|
+
: undefined;
|
|
377
|
+
const detailed = Array.isArray(resume?.toolOutputsDetailed)
|
|
378
|
+
? resume?.toolOutputsDetailed
|
|
379
|
+
: undefined;
|
|
380
|
+
if (detailed) {
|
|
381
|
+
detailed.forEach((entry, index) => {
|
|
382
|
+
if (!entry || typeof entry !== 'object') {
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
const callId = typeof entry.callId === 'string' && entry.callId.trim().length
|
|
386
|
+
? entry.callId.trim()
|
|
387
|
+
: typeof entry.originalId === 'string' && entry.originalId.trim().length
|
|
388
|
+
? entry.originalId.trim()
|
|
389
|
+
: `resume_tool_${index + 1}`;
|
|
390
|
+
append(callId, typeof entry.outputText === 'string' ? entry.outputText : entry.outputText ?? '');
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return outputs;
|
|
395
|
+
}
|
|
396
|
+
function resolveSubmitStreamFlag(chat, ctx, responsesContext) {
|
|
397
|
+
if (chat.parameters && typeof chat.parameters.stream === 'boolean') {
|
|
398
|
+
return chat.parameters.stream;
|
|
399
|
+
}
|
|
400
|
+
if (responsesContext && typeof responsesContext.stream === 'boolean') {
|
|
401
|
+
return responsesContext.stream;
|
|
402
|
+
}
|
|
403
|
+
if (ctx.streamingHint === 'force') {
|
|
404
|
+
return true;
|
|
405
|
+
}
|
|
406
|
+
if (ctx.streamingHint === 'disable') {
|
|
407
|
+
return false;
|
|
408
|
+
}
|
|
409
|
+
return undefined;
|
|
410
|
+
}
|
|
411
|
+
function resolveSubmitModel(chat, responsesContext) {
|
|
412
|
+
const direct = chat.parameters && typeof chat.parameters.model === 'string'
|
|
413
|
+
? chat.parameters.model.trim()
|
|
414
|
+
: undefined;
|
|
415
|
+
if (direct) {
|
|
416
|
+
return direct;
|
|
417
|
+
}
|
|
418
|
+
if (responsesContext && typeof responsesContext.parameters === 'object') {
|
|
419
|
+
const params = responsesContext.parameters;
|
|
420
|
+
const model = typeof params.model === 'string' ? params.model.trim() : undefined;
|
|
421
|
+
if (model) {
|
|
422
|
+
return model;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
return undefined;
|
|
426
|
+
}
|
|
427
|
+
function buildSubmitToolOutputsPayload(chat, ctx, responsesContext) {
|
|
428
|
+
const responseId = resolveSubmitResponseId(ctx, responsesContext);
|
|
429
|
+
if (!responseId) {
|
|
430
|
+
throw new Error('Submit tool outputs requires response_id from Responses resume context');
|
|
431
|
+
}
|
|
432
|
+
const toolOutputs = collectSubmitToolOutputs(chat, ctx, responsesContext);
|
|
433
|
+
if (!toolOutputs.length) {
|
|
434
|
+
throw new Error('Submit tool outputs requires at least one tool output entry');
|
|
435
|
+
}
|
|
436
|
+
const resumeMeta = ctx.responsesResume && typeof ctx.responsesResume === 'object'
|
|
437
|
+
? ctx.responsesResume
|
|
438
|
+
: undefined;
|
|
439
|
+
const payload = {
|
|
440
|
+
response_id: responseId,
|
|
441
|
+
tool_outputs: toolOutputs
|
|
442
|
+
};
|
|
443
|
+
const modelValue = resolveSubmitModel(chat, responsesContext);
|
|
444
|
+
if (modelValue) {
|
|
445
|
+
payload.model = modelValue;
|
|
446
|
+
}
|
|
447
|
+
const streamValue = resolveSubmitStreamFlag(chat, ctx, responsesContext);
|
|
448
|
+
if (typeof streamValue === 'boolean') {
|
|
449
|
+
payload.stream = streamValue;
|
|
450
|
+
}
|
|
451
|
+
const metadata = extractSubmitMetadata(responsesContext, resumeMeta);
|
|
452
|
+
if (metadata) {
|
|
453
|
+
payload.metadata = metadata;
|
|
454
|
+
}
|
|
455
|
+
return payload;
|
|
456
|
+
}
|
|
200
457
|
export class ResponsesSemanticMapper {
|
|
201
458
|
async toChat(format, ctx) {
|
|
202
459
|
const payload = format.payload || {};
|
|
@@ -248,15 +505,35 @@ export class ResponsesSemanticMapper {
|
|
|
248
505
|
if (responsesContext.responseFormat) {
|
|
249
506
|
metadata.responseFormat = jsonClone(responsesContext.responseFormat);
|
|
250
507
|
}
|
|
508
|
+
metadata.responsesContext = jsonClone(responsesContext);
|
|
509
|
+
const resumeNode = ctx.responsesResume && isJsonObject(ctx.responsesResume)
|
|
510
|
+
? ctx.responsesResume
|
|
511
|
+
: undefined;
|
|
512
|
+
const semantics = attachResponsesSemantics(undefined, responsesContext, resumeNode);
|
|
251
513
|
return {
|
|
252
514
|
messages,
|
|
253
515
|
tools: normalizeTools(toolsNormalized, missingFields),
|
|
254
516
|
toolOutputs,
|
|
255
517
|
parameters,
|
|
518
|
+
semantics,
|
|
256
519
|
metadata
|
|
257
520
|
};
|
|
258
521
|
}
|
|
259
522
|
async fromChat(chat, ctx) {
|
|
523
|
+
const envelopeMetadata = chat.metadata && isJsonObject(chat.metadata) ? chat.metadata : undefined;
|
|
524
|
+
const responsesContext = selectResponsesContextSnapshot(chat, envelopeMetadata);
|
|
525
|
+
if (isSubmitToolOutputsEndpoint(ctx)) {
|
|
526
|
+
const submitPayload = buildSubmitToolOutputsPayload(chat, ctx, responsesContext);
|
|
527
|
+
return {
|
|
528
|
+
protocol: 'openai-responses',
|
|
529
|
+
direction: 'response',
|
|
530
|
+
payload: submitPayload,
|
|
531
|
+
meta: {
|
|
532
|
+
context: ctx,
|
|
533
|
+
submitToolOutputs: true
|
|
534
|
+
}
|
|
535
|
+
};
|
|
536
|
+
}
|
|
260
537
|
const modelValue = chat.parameters?.model;
|
|
261
538
|
if (typeof modelValue !== 'string' || !modelValue.trim()) {
|
|
262
539
|
throw new Error('ChatEnvelope.parameters.model is required for openai-responses outbound conversion');
|
|
@@ -271,18 +548,7 @@ export class ResponsesSemanticMapper {
|
|
|
271
548
|
.filter((message) => Boolean(message && typeof message === 'object' && message.role === 'system'))
|
|
272
549
|
.map(message => serializeSystemContent(message))
|
|
273
550
|
.filter((content) => typeof content === 'string' && content.length > 0);
|
|
274
|
-
|
|
275
|
-
const envelopeMetadata = chat.metadata && isJsonObject(chat.metadata) ? chat.metadata : undefined;
|
|
276
|
-
const responsesContext = isJsonObject(capturedContext)
|
|
277
|
-
? {
|
|
278
|
-
...capturedContext,
|
|
279
|
-
metadata: mergeMetadata(capturedContext.metadata, envelopeMetadata),
|
|
280
|
-
originalSystemMessages
|
|
281
|
-
}
|
|
282
|
-
: {
|
|
283
|
-
metadata: envelopeMetadata,
|
|
284
|
-
originalSystemMessages
|
|
285
|
-
};
|
|
551
|
+
responsesContext.originalSystemMessages = originalSystemMessages;
|
|
286
552
|
const responsesResult = buildResponsesRequestFromChat(requestShape, responsesContext);
|
|
287
553
|
const responses = responsesResult.request;
|
|
288
554
|
if (chat.parameters && chat.parameters.stream !== undefined) {
|
|
@@ -5,6 +5,7 @@ export function chatEnvelopeToStandardized(chat, options) {
|
|
|
5
5
|
const model = extractModel(parameters);
|
|
6
6
|
const messages = chat.messages.map((message) => normalizeChatMessage(message));
|
|
7
7
|
const tools = normalizeTools(chat.tools);
|
|
8
|
+
const semantics = cloneSemantics(chat.semantics);
|
|
8
9
|
const metadataCaptured = {};
|
|
9
10
|
const hubState = {};
|
|
10
11
|
if (Array.isArray(chat.metadata?.missingFields) && chat.metadata?.missingFields.length) {
|
|
@@ -35,7 +36,8 @@ export function chatEnvelopeToStandardized(chat, options) {
|
|
|
35
36
|
capturedContext: metadataCaptured,
|
|
36
37
|
requestId: options.requestId,
|
|
37
38
|
stream: parameters.stream === true
|
|
38
|
-
}
|
|
39
|
+
},
|
|
40
|
+
semantics
|
|
39
41
|
};
|
|
40
42
|
return standardized;
|
|
41
43
|
}
|
|
@@ -93,7 +95,8 @@ export function standardizedToChatEnvelope(request, options) {
|
|
|
93
95
|
messages,
|
|
94
96
|
tools,
|
|
95
97
|
parameters,
|
|
96
|
-
metadata
|
|
98
|
+
metadata,
|
|
99
|
+
semantics: cloneSemantics(request.semantics)
|
|
97
100
|
};
|
|
98
101
|
}
|
|
99
102
|
function extractModel(parameters) {
|
|
@@ -103,6 +106,12 @@ function extractModel(parameters) {
|
|
|
103
106
|
}
|
|
104
107
|
throw new Error('ChatEnvelope parameters must include model string');
|
|
105
108
|
}
|
|
109
|
+
function cloneSemantics(value) {
|
|
110
|
+
if (!value) {
|
|
111
|
+
return value;
|
|
112
|
+
}
|
|
113
|
+
return jsonClone(value);
|
|
114
|
+
}
|
|
106
115
|
function normalizeChatMessage(message) {
|
|
107
116
|
const normalized = {
|
|
108
117
|
role: message.role,
|
|
@@ -56,11 +56,21 @@ export interface AdapterContext {
|
|
|
56
56
|
responsesResume?: JsonObject;
|
|
57
57
|
[key: string]: JsonValue;
|
|
58
58
|
}
|
|
59
|
+
export interface ChatSemantics extends JsonObject {
|
|
60
|
+
session?: JsonObject;
|
|
61
|
+
system?: JsonObject;
|
|
62
|
+
tools?: JsonObject;
|
|
63
|
+
responses?: JsonObject;
|
|
64
|
+
anthropic?: JsonObject;
|
|
65
|
+
gemini?: JsonObject;
|
|
66
|
+
providerExtras?: JsonObject;
|
|
67
|
+
}
|
|
59
68
|
export interface ChatEnvelope {
|
|
60
69
|
messages: ChatMessage[];
|
|
61
70
|
tools?: ChatToolDefinition[];
|
|
62
71
|
toolOutputs?: ChatToolOutput[];
|
|
63
72
|
parameters?: JsonObject;
|
|
73
|
+
semantics?: ChatSemantics;
|
|
64
74
|
metadata: {
|
|
65
75
|
context: AdapterContext;
|
|
66
76
|
missingFields?: MissingField[];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ChatMessageContentPart } from './chat-envelope.js';
|
|
1
|
+
import type { ChatMessageContentPart, ChatSemantics } from './chat-envelope.js';
|
|
2
2
|
import type { JsonObject } from './json.js';
|
|
3
3
|
export type ToolChoice = 'none' | 'auto' | 'required' | {
|
|
4
4
|
type: 'function';
|
|
@@ -74,6 +74,7 @@ export interface StandardizedRequest {
|
|
|
74
74
|
tools?: StandardizedTool[];
|
|
75
75
|
parameters: StandardizedParameters;
|
|
76
76
|
metadata: StandardizedMetadata;
|
|
77
|
+
semantics?: ChatSemantics;
|
|
77
78
|
}
|
|
78
79
|
export interface ProcessedRequest extends StandardizedRequest {
|
|
79
80
|
processed: {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { BridgeInputItem, BridgeToolDefinition } from '../shared/bridge-message-types.js';
|
|
2
2
|
import type { ChatToolDefinition } from '../hub/types/chat-envelope.js';
|
|
3
|
+
import type { JsonObject, JsonValue } from '../hub/types/json.js';
|
|
3
4
|
import type { BridgeInputBuildResult } from '../shared/bridge-message-utils.js';
|
|
4
5
|
import type { ToolCallIdStyle } from '../shared/responses-tool-utils.js';
|
|
5
6
|
type Unknown = Record<string, unknown>;
|
|
@@ -11,8 +12,8 @@ export interface ResponsesRequestContext extends Unknown {
|
|
|
11
12
|
store?: unknown;
|
|
12
13
|
toolChoice?: unknown;
|
|
13
14
|
parallelToolCalls?: boolean;
|
|
14
|
-
metadata?:
|
|
15
|
-
responseFormat?:
|
|
15
|
+
metadata?: JsonObject;
|
|
16
|
+
responseFormat?: JsonValue;
|
|
16
17
|
stream?: boolean;
|
|
17
18
|
isChatPayload?: boolean;
|
|
18
19
|
isResponsesPayload?: boolean;
|
|
@@ -10,7 +10,6 @@ import { normalizeMessageReasoningTools } from '../shared/reasoning-tool-normali
|
|
|
10
10
|
import { createBridgeActionState, runBridgeActionPipeline } from '../shared/bridge-actions.js';
|
|
11
11
|
import { resolveBridgePolicy, resolvePolicyActions } from '../shared/bridge-policies.js';
|
|
12
12
|
import { buildResponsesOutputFromChat } from '../shared/responses-output-builder.js';
|
|
13
|
-
import { consumeResponsesPayloadSnapshot, consumeResponsesPassthrough } from '../shared/responses-reasoning-registry.js';
|
|
14
13
|
function isObject(v) {
|
|
15
14
|
return !!v && typeof v === 'object' && !Array.isArray(v);
|
|
16
15
|
}
|
|
@@ -29,7 +28,7 @@ export function captureResponsesContext(payload, dto) {
|
|
|
29
28
|
store: payload.store,
|
|
30
29
|
toolChoice: payload.tool_choice,
|
|
31
30
|
parallelToolCalls: typeof payload.parallel_tool_calls === 'boolean' ? payload.parallel_tool_calls : undefined,
|
|
32
|
-
metadata: (payload.metadata && typeof payload.metadata === 'object') ?
|
|
31
|
+
metadata: (payload.metadata && typeof payload.metadata === 'object') ? payload.metadata : undefined,
|
|
33
32
|
responseFormat: payload.response_format,
|
|
34
33
|
stream: typeof payload.stream === 'boolean' ? payload.stream : undefined,
|
|
35
34
|
isChatPayload: Array.isArray(payload.messages)
|
|
@@ -461,17 +460,6 @@ export function buildResponsesPayloadFromChat(payload, context) {
|
|
|
461
460
|
const response = unwrapData(payload);
|
|
462
461
|
if (!response || typeof response !== 'object')
|
|
463
462
|
return payload;
|
|
464
|
-
const snapshotKey = resolveSnapshotLookupKey(response, context);
|
|
465
|
-
if (snapshotKey) {
|
|
466
|
-
const passthrough = consumeResponsesPassthrough(snapshotKey);
|
|
467
|
-
if (passthrough) {
|
|
468
|
-
return passthrough;
|
|
469
|
-
}
|
|
470
|
-
const snapshot = consumeResponsesPayloadSnapshot(snapshotKey);
|
|
471
|
-
if (snapshot) {
|
|
472
|
-
return snapshot;
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
463
|
if (response.object === 'response' && Array.isArray(response.output)) {
|
|
476
464
|
return response;
|
|
477
465
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type ToolCallLite = {
|
|
2
|
+
id?: string;
|
|
3
|
+
name: string;
|
|
4
|
+
args: string;
|
|
5
|
+
};
|
|
6
|
+
export declare function extractApplyPatchCallsFromText(text: string): ToolCallLite[] | null;
|
|
7
|
+
export declare function extractExecuteBlocksFromText(text: string): ToolCallLite[] | null;
|
|
8
|
+
export declare function extractXMLToolCallsFromText(text: string): ToolCallLite[] | null;
|
|
9
|
+
/**
|
|
10
|
+
* 提取简单 XML 形式的工具调用块,例如:
|
|
11
|
+
*
|
|
12
|
+
* <list_directory>
|
|
13
|
+
* <path>/path/to/dir</path>
|
|
14
|
+
* <recursive>false</recursive>
|
|
15
|
+
* </list_directory>
|
|
16
|
+
*
|
|
17
|
+
* 仅针对已知工具名(目前为 list_directory),避免误伤普通 XML 文本。
|
|
18
|
+
*/
|
|
19
|
+
export declare function extractSimpleXmlToolsFromText(text: string): ToolCallLite[] | null;
|
|
20
|
+
export declare function normalizeAssistantTextToToolCalls(message: Record<string, any>): Record<string, any>;
|
|
@@ -11,7 +11,9 @@ const KNOWN_TOOLS = new Set([
|
|
|
11
11
|
'view_image',
|
|
12
12
|
'list_mcp_resources',
|
|
13
13
|
'read_mcp_resource',
|
|
14
|
-
'list_mcp_resource_templates'
|
|
14
|
+
'list_mcp_resource_templates',
|
|
15
|
+
// 文件/目录类工具(CLI 侧已有约定;此处只做文本→tool_calls 收割)
|
|
16
|
+
'list_directory'
|
|
15
17
|
]);
|
|
16
18
|
const ALLOWED_KEYS = {
|
|
17
19
|
shell: new Set(['command', 'justification', 'timeout_ms', 'with_escalated_permissions', 'workdir']),
|
|
@@ -20,7 +22,8 @@ const ALLOWED_KEYS = {
|
|
|
20
22
|
view_image: new Set(['path']),
|
|
21
23
|
list_mcp_resources: new Set(['server', 'cursor', 'filter', 'root']),
|
|
22
24
|
read_mcp_resource: new Set(['server', 'uri', 'cursor']),
|
|
23
|
-
list_mcp_resource_templates: new Set(['server', 'cursor'])
|
|
25
|
+
list_mcp_resource_templates: new Set(['server', 'cursor']),
|
|
26
|
+
list_directory: new Set(['path', 'recursive'])
|
|
24
27
|
};
|
|
25
28
|
function normalizeKey(raw) {
|
|
26
29
|
try {
|
|
@@ -259,9 +262,16 @@ export function extractXMLToolCallsFromText(text) {
|
|
|
259
262
|
name = cand;
|
|
260
263
|
}
|
|
261
264
|
catch { /* ignore */ }
|
|
265
|
+
// 如果前一行抽不到合法函数名,但 arg_key 明显是 toon/command,
|
|
266
|
+
// 视为 CLI 通路下的 exec_command 工具,避免把半截 <tool_call> 当成纯文本丢弃。
|
|
267
|
+
const rawKey = (pm[1] || '').trim();
|
|
268
|
+
const normalizedKey = normalizeKey(rawKey).toLowerCase();
|
|
269
|
+
if (!name && (normalizedKey === 'toon' || normalizedKey === 'command')) {
|
|
270
|
+
name = 'exec_command';
|
|
271
|
+
}
|
|
262
272
|
if (!name)
|
|
263
273
|
continue;
|
|
264
|
-
const k = normalizeKey(
|
|
274
|
+
const k = normalizeKey(rawKey);
|
|
265
275
|
let vRaw = (pm[2] || '').trim();
|
|
266
276
|
const argsObj = {};
|
|
267
277
|
if (k) {
|
|
@@ -305,6 +315,74 @@ export function extractXMLToolCallsFromText(text) {
|
|
|
305
315
|
return null;
|
|
306
316
|
}
|
|
307
317
|
}
|
|
318
|
+
/**
|
|
319
|
+
* 提取简单 XML 形式的工具调用块,例如:
|
|
320
|
+
*
|
|
321
|
+
* <list_directory>
|
|
322
|
+
* <path>/path/to/dir</path>
|
|
323
|
+
* <recursive>false</recursive>
|
|
324
|
+
* </list_directory>
|
|
325
|
+
*
|
|
326
|
+
* 仅针对已知工具名(目前为 list_directory),避免误伤普通 XML 文本。
|
|
327
|
+
*/
|
|
328
|
+
export function extractSimpleXmlToolsFromText(text) {
|
|
329
|
+
try {
|
|
330
|
+
if (typeof text !== 'string' || !text)
|
|
331
|
+
return null;
|
|
332
|
+
const out = [];
|
|
333
|
+
const blockRe = /<\s*([A-Za-z0-9_.-]+)\s*>([\s\S]*?)<\/\s*\1\s*>/gi;
|
|
334
|
+
let bm;
|
|
335
|
+
while ((bm = blockRe.exec(text)) !== null) {
|
|
336
|
+
const rawName = (bm[1] || '').trim();
|
|
337
|
+
const lname = rawName.toLowerCase();
|
|
338
|
+
if (!lname || lname === 'tool_call')
|
|
339
|
+
continue;
|
|
340
|
+
// 目前仅支持 list_directory,后续按需扩展
|
|
341
|
+
if (lname !== 'list_directory')
|
|
342
|
+
continue;
|
|
343
|
+
const inner = bm[2] || '';
|
|
344
|
+
const args = {};
|
|
345
|
+
const argRe = /<\s*([A-Za-z0-9_]+)\s*>([\s\S]*?)<\/\s*\1\s*>/gi;
|
|
346
|
+
let am;
|
|
347
|
+
while ((am = argRe.exec(inner)) !== null) {
|
|
348
|
+
const key = normalizeKey((am[1] || '').trim());
|
|
349
|
+
if (!key)
|
|
350
|
+
continue;
|
|
351
|
+
let rawVal = (am[2] || '').trim();
|
|
352
|
+
let v = rawVal;
|
|
353
|
+
if ((rawVal.startsWith('[') && rawVal.endsWith(']')) || (rawVal.startsWith('{') && rawVal.endsWith('}'))) {
|
|
354
|
+
try {
|
|
355
|
+
v = JSON.parse(rawVal);
|
|
356
|
+
}
|
|
357
|
+
catch {
|
|
358
|
+
v = rawVal;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
else if (rawVal === 'true' || rawVal === 'false') {
|
|
362
|
+
v = rawVal === 'true';
|
|
363
|
+
}
|
|
364
|
+
args[key] = v;
|
|
365
|
+
}
|
|
366
|
+
const filtered = filterArgsForTool(lname, args);
|
|
367
|
+
let argsStr = '{}';
|
|
368
|
+
try {
|
|
369
|
+
argsStr = JSON.stringify(filtered);
|
|
370
|
+
}
|
|
371
|
+
catch {
|
|
372
|
+
argsStr = '{}';
|
|
373
|
+
}
|
|
374
|
+
out.push({
|
|
375
|
+
id: `call_${Math.random().toString(36).slice(2, 10)}`,
|
|
376
|
+
name: lname,
|
|
377
|
+
args: argsStr
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
return out.length ? out : null;
|
|
381
|
+
}
|
|
382
|
+
catch {
|
|
383
|
+
return null;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
308
386
|
export function normalizeAssistantTextToToolCalls(message) {
|
|
309
387
|
if (!enabled())
|
|
310
388
|
return message;
|
|
@@ -317,10 +395,11 @@ export function normalizeAssistantTextToToolCalls(message) {
|
|
|
317
395
|
const text = typeof content === 'string' ? content : null;
|
|
318
396
|
if (!text)
|
|
319
397
|
return message;
|
|
320
|
-
// Order: xml-like tool_call → apply_patch → execute blocks
|
|
398
|
+
// Order: xml-like <tool_call> → apply_patch → execute blocks → 简单 XML 工具(list_directory 等)
|
|
321
399
|
const calls = (extractXMLToolCallsFromText(text) ||
|
|
322
400
|
extractApplyPatchCallsFromText(text) ||
|
|
323
|
-
extractExecuteBlocksFromText(text)
|
|
401
|
+
extractExecuteBlocksFromText(text) ||
|
|
402
|
+
extractSimpleXmlToolsFromText(text));
|
|
324
403
|
if (calls && calls.length) {
|
|
325
404
|
const toolCalls = calls.map((c) => ({ id: c.id, type: 'function', function: { name: c.name, arguments: c.args } }));
|
|
326
405
|
const copy = { ...message };
|