@jsonstudio/llms 0.6.568 → 0.6.586
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/hub/pipeline/hub-pipeline.d.ts +0 -1
- package/dist/conversion/hub/pipeline/hub-pipeline.js +47 -68
- 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 +3 -13
- package/dist/conversion/hub/response/provider-response.js +0 -8
- package/dist/conversion/hub/semantic-mappers/gemini-mapper.js +7 -1
- package/dist/conversion/hub/semantic-mappers/responses-mapper.js +216 -12
- 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 +76 -4
- package/dist/conversion/shared/tool-filter-pipeline.js +54 -29
- 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 +30 -0
- 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/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
|
@@ -22,6 +22,7 @@ import { runReqOutboundStage1SemanticMap } from './stages/req_outbound/req_outbo
|
|
|
22
22
|
import { runReqOutboundStage2FormatBuild } from './stages/req_outbound/req_outbound_stage2_format_build/index.js';
|
|
23
23
|
import { runReqOutboundStage3Compat } from './stages/req_outbound/req_outbound_stage3_compat/index.js';
|
|
24
24
|
import { extractSessionIdentifiersFromMetadata } from './session-identifiers.js';
|
|
25
|
+
import { computeRequestTokens } from '../../../router/virtual-router/token-estimator.js';
|
|
25
26
|
export class HubPipeline {
|
|
26
27
|
routerEngine;
|
|
27
28
|
config;
|
|
@@ -119,6 +120,18 @@ export class HubPipeline {
|
|
|
119
120
|
}
|
|
120
121
|
}
|
|
121
122
|
const workingRequest = processedRequest ?? standardizedRequest;
|
|
123
|
+
// 使用与 VirtualRouter 一致的 tiktoken 计数逻辑,对标准化请求进行一次
|
|
124
|
+
// 上下文 token 估算,供后续 usage 归一化与统计使用。
|
|
125
|
+
try {
|
|
126
|
+
const estimatedTokens = computeRequestTokens(workingRequest, '');
|
|
127
|
+
if (typeof estimatedTokens === 'number' && Number.isFinite(estimatedTokens) && estimatedTokens > 0) {
|
|
128
|
+
normalized.metadata = normalized.metadata || {};
|
|
129
|
+
normalized.metadata.estimatedInputTokens = estimatedTokens;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
// 估算失败不应影响主流程
|
|
134
|
+
}
|
|
122
135
|
const normalizedMeta = normalized.metadata;
|
|
123
136
|
const responsesResume = normalizedMeta && typeof normalizedMeta.responsesResume === 'object'
|
|
124
137
|
? normalizedMeta.responsesResume
|
|
@@ -182,66 +195,42 @@ export class HubPipeline {
|
|
|
182
195
|
const outboundEndpoint = resolveEndpointForProviderProtocol(outboundAdapterContext.providerProtocol);
|
|
183
196
|
const outboundRecorder = this.maybeCreateStageRecorder(outboundAdapterContext, outboundEndpoint);
|
|
184
197
|
const outboundStart = Date.now();
|
|
185
|
-
const isResponsesSubmit = normalized.entryEndpoint === '/v1/responses.submit_tool_outputs' &&
|
|
186
|
-
outboundProtocol === 'openai-responses';
|
|
187
198
|
let providerPayload;
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
formatAdapter: outboundFormatAdapter,
|
|
221
|
-
stageRecorder: outboundRecorder
|
|
222
|
-
});
|
|
223
|
-
formattedPayload = await runReqOutboundStage3Compat({
|
|
224
|
-
payload: formattedPayload,
|
|
225
|
-
adapterContext: outboundAdapterContext,
|
|
226
|
-
stageRecorder: outboundRecorder
|
|
227
|
-
});
|
|
228
|
-
providerPayload = formattedPayload;
|
|
229
|
-
const outboundEnd = Date.now();
|
|
230
|
-
nodeResults.push({
|
|
231
|
-
id: 'req_outbound',
|
|
232
|
-
success: true,
|
|
233
|
-
metadata: {
|
|
234
|
-
node: 'req_outbound',
|
|
235
|
-
executionTime: outboundEnd - outboundStart,
|
|
236
|
-
startTime: outboundStart,
|
|
237
|
-
endTime: outboundEnd,
|
|
238
|
-
dataProcessed: {
|
|
239
|
-
messages: workingRequest.messages.length,
|
|
240
|
-
tools: workingRequest.tools?.length ?? 0
|
|
241
|
-
}
|
|
199
|
+
const outboundStage1 = await runReqOutboundStage1SemanticMap({
|
|
200
|
+
request: workingRequest,
|
|
201
|
+
adapterContext: outboundAdapterContext,
|
|
202
|
+
semanticMapper: outboundSemanticMapper,
|
|
203
|
+
contextSnapshot: outboundContextSnapshot,
|
|
204
|
+
contextMetadataKey: outboundContextMetadataKey,
|
|
205
|
+
stageRecorder: outboundRecorder
|
|
206
|
+
});
|
|
207
|
+
let formattedPayload = await runReqOutboundStage2FormatBuild({
|
|
208
|
+
formatEnvelope: outboundStage1.formatEnvelope,
|
|
209
|
+
adapterContext: outboundAdapterContext,
|
|
210
|
+
formatAdapter: outboundFormatAdapter,
|
|
211
|
+
stageRecorder: outboundRecorder
|
|
212
|
+
});
|
|
213
|
+
formattedPayload = await runReqOutboundStage3Compat({
|
|
214
|
+
payload: formattedPayload,
|
|
215
|
+
adapterContext: outboundAdapterContext,
|
|
216
|
+
stageRecorder: outboundRecorder
|
|
217
|
+
});
|
|
218
|
+
providerPayload = formattedPayload;
|
|
219
|
+
const outboundEnd = Date.now();
|
|
220
|
+
nodeResults.push({
|
|
221
|
+
id: 'req_outbound',
|
|
222
|
+
success: true,
|
|
223
|
+
metadata: {
|
|
224
|
+
node: 'req_outbound',
|
|
225
|
+
executionTime: outboundEnd - outboundStart,
|
|
226
|
+
startTime: outboundStart,
|
|
227
|
+
endTime: outboundEnd,
|
|
228
|
+
dataProcessed: {
|
|
229
|
+
messages: workingRequest.messages.length,
|
|
230
|
+
tools: workingRequest.tools?.length ?? 0
|
|
242
231
|
}
|
|
243
|
-
}
|
|
244
|
-
}
|
|
232
|
+
}
|
|
233
|
+
});
|
|
245
234
|
// 为响应侧 servertool/web_search 提供一次性 Chat 请求快照,便于在 Hub 内部实现
|
|
246
235
|
// 第三跳(将工具结果注入消息历史后重新调用主模型)。
|
|
247
236
|
//
|
|
@@ -432,16 +421,6 @@ export class HubPipeline {
|
|
|
432
421
|
}
|
|
433
422
|
return value;
|
|
434
423
|
}
|
|
435
|
-
pickRawRequestBody(metadata) {
|
|
436
|
-
if (!metadata || typeof metadata !== 'object') {
|
|
437
|
-
return undefined;
|
|
438
|
-
}
|
|
439
|
-
const raw = metadata.__raw_request_body;
|
|
440
|
-
if (!raw || typeof raw !== 'object') {
|
|
441
|
-
return undefined;
|
|
442
|
-
}
|
|
443
|
-
return raw;
|
|
444
|
-
}
|
|
445
424
|
async normalizeRequest(request) {
|
|
446
425
|
if (!request || typeof request !== 'object') {
|
|
447
426
|
throw new Error('HubPipeline requires request payload');
|
package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.js
CHANGED
|
@@ -15,7 +15,6 @@ export function runRespOutboundStage1ClientRemap(options) {
|
|
|
15
15
|
clientPayload = buildResponsesPayloadFromChat(options.payload, {
|
|
16
16
|
requestId: options.requestId
|
|
17
17
|
});
|
|
18
|
-
mergeOriginalResponsesPayload(clientPayload, options.adapterContext);
|
|
19
18
|
}
|
|
20
19
|
recordStage(options.stageRecorder, 'resp_outbound_stage1_client_remap', clientPayload);
|
|
21
20
|
return clientPayload;
|
|
@@ -42,36 +41,3 @@ function resolveAliasMapFromContext(adapterContext) {
|
|
|
42
41
|
}
|
|
43
42
|
return Object.keys(map).length ? map : undefined;
|
|
44
43
|
}
|
|
45
|
-
function mergeOriginalResponsesPayload(payload, adapterContext) {
|
|
46
|
-
if (!adapterContext) {
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
const raw = adapterContext.__raw_responses_payload;
|
|
50
|
-
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
try {
|
|
54
|
-
if (payload.required_action == null && raw.required_action != null) {
|
|
55
|
-
payload.required_action = JSON.parse(JSON.stringify(raw.required_action));
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
catch {
|
|
59
|
-
/* ignore clone errors */
|
|
60
|
-
}
|
|
61
|
-
const rawStatus = typeof raw.status === 'string' ? raw.status : undefined;
|
|
62
|
-
if (rawStatus === 'requires_action') {
|
|
63
|
-
payload.status = 'requires_action';
|
|
64
|
-
}
|
|
65
|
-
// 如果桥接后的 payload 没有 usage,而原始 Responses 载荷带有 usage,则回填原始 usage,
|
|
66
|
-
// 确保 token usage 不在工具/桥接路径中丢失。
|
|
67
|
-
const payloadUsage = payload.usage;
|
|
68
|
-
const rawUsage = raw.usage;
|
|
69
|
-
if ((payloadUsage == null || typeof payloadUsage !== 'object') && rawUsage && typeof rawUsage === 'object') {
|
|
70
|
-
try {
|
|
71
|
-
payload.usage = JSON.parse(JSON.stringify(rawUsage));
|
|
72
|
-
}
|
|
73
|
-
catch {
|
|
74
|
-
payload.usage = rawUsage;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { runChatRequestToolFilters } from '../../shared/tool-filter-pipeline.js';
|
|
2
2
|
import { ToolGovernanceEngine } from '../tool-governance/index.js';
|
|
3
|
-
import { detectLastAssistantToolCategory } from '../../../router/virtual-router/tool-signals.js';
|
|
4
3
|
import { ensureApplyPatchSchema } from '../../shared/tool-mapping.js';
|
|
5
4
|
const toolGovernanceEngine = new ToolGovernanceEngine();
|
|
6
5
|
export async function runHubChatProcess(options) {
|
|
@@ -409,19 +408,10 @@ function maybeInjectWebSearchTool(request, metadata) {
|
|
|
409
408
|
: 'selective';
|
|
410
409
|
const intent = detectWebSearchIntent(request);
|
|
411
410
|
if (injectPolicy === 'selective') {
|
|
411
|
+
// 仅当当前这一轮用户输入明确表达“联网搜索”意图时才注入 web_search。
|
|
412
|
+
// 不再依赖上一轮工具分类(read/search/websearch),避免形成隐式续写语义。
|
|
412
413
|
if (!intent.hasIntent) {
|
|
413
|
-
|
|
414
|
-
// 如果上一轮 assistant 的工具调用已经属于搜索类(如 web_search),
|
|
415
|
-
// 则仍然视为 web_search 续写场景,强制注入 web_search 工具,
|
|
416
|
-
// 以便在后续路由中按 servertool 逻辑跳过不适配的 Provider(例如 serverToolsDisabled 的 crs)。
|
|
417
|
-
const assistantMessages = Array.isArray(request.messages)
|
|
418
|
-
? request.messages.filter((msg) => msg && msg.role === 'assistant')
|
|
419
|
-
: [];
|
|
420
|
-
const lastTool = detectLastAssistantToolCategory(assistantMessages);
|
|
421
|
-
const hasSearchToolContext = lastTool?.category === 'search';
|
|
422
|
-
if (!hasSearchToolContext) {
|
|
423
|
-
return request;
|
|
424
|
-
}
|
|
414
|
+
return request;
|
|
425
415
|
}
|
|
426
416
|
}
|
|
427
417
|
const existingTools = Array.isArray(request.tools) ? request.tools : [];
|
|
@@ -130,14 +130,6 @@ export async function convertProviderResponse(options) {
|
|
|
130
130
|
catch {
|
|
131
131
|
// ignore conversation capture errors
|
|
132
132
|
}
|
|
133
|
-
if (formatEnvelope.payload && typeof formatEnvelope.payload === 'object') {
|
|
134
|
-
try {
|
|
135
|
-
options.context.__raw_responses_payload = JSON.parse(JSON.stringify(formatEnvelope.payload));
|
|
136
|
-
}
|
|
137
|
-
catch {
|
|
138
|
-
/* best-effort clone */
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
133
|
}
|
|
142
134
|
formatEnvelope.payload = runRespInboundStageCompatResponse({
|
|
143
135
|
payload: formatEnvelope.payload,
|
|
@@ -337,7 +337,13 @@ function buildGeminiRequestFromChat(chat, metadata) {
|
|
|
337
337
|
else {
|
|
338
338
|
argsStruct = fn.arguments ?? {};
|
|
339
339
|
}
|
|
340
|
-
|
|
340
|
+
let argsJson = cloneAsJsonValue(argsStruct);
|
|
341
|
+
// Gemini / Antigravity 期望 functionCall.args 为对象(Struct),
|
|
342
|
+
// 若顶层为数组或原始类型,则包装到 value 字段下,避免产生非法的 list 形状。
|
|
343
|
+
if (!argsJson || typeof argsJson !== 'object' || Array.isArray(argsJson)) {
|
|
344
|
+
argsJson = { value: argsJson };
|
|
345
|
+
}
|
|
346
|
+
const functionCall = { name, args: argsJson };
|
|
341
347
|
const part = { functionCall };
|
|
342
348
|
if (typeof tc.id === 'string') {
|
|
343
349
|
part.functionCall.id = tc.id;
|
|
@@ -19,6 +19,7 @@ const RESPONSES_PARAMETER_KEYS = [
|
|
|
19
19
|
'stop_sequences',
|
|
20
20
|
'modalities'
|
|
21
21
|
];
|
|
22
|
+
const RESPONSES_SUBMIT_ENDPOINT = '/v1/responses.submit_tool_outputs';
|
|
22
23
|
function mapToolOutputs(entries, missing) {
|
|
23
24
|
if (!entries || !entries.length)
|
|
24
25
|
return undefined;
|
|
@@ -197,6 +198,198 @@ function mergeMetadata(a, b) {
|
|
|
197
198
|
const right = jsonClone(b);
|
|
198
199
|
return { ...left, ...right };
|
|
199
200
|
}
|
|
201
|
+
function isSubmitToolOutputsEndpoint(ctx) {
|
|
202
|
+
if (!ctx || typeof ctx !== 'object') {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
const entry = typeof ctx.entryEndpoint === 'string' ? ctx.entryEndpoint.trim().toLowerCase() : '';
|
|
206
|
+
return entry === RESPONSES_SUBMIT_ENDPOINT;
|
|
207
|
+
}
|
|
208
|
+
function resolveSubmitResponseId(ctx, responsesContext) {
|
|
209
|
+
const resumeMeta = ctx.responsesResume && typeof ctx.responsesResume === 'object'
|
|
210
|
+
? ctx.responsesResume
|
|
211
|
+
: undefined;
|
|
212
|
+
const resumeId = typeof resumeMeta?.restoredFromResponseId === 'string'
|
|
213
|
+
? resumeMeta.restoredFromResponseId.trim()
|
|
214
|
+
: undefined;
|
|
215
|
+
if (resumeId) {
|
|
216
|
+
return resumeId;
|
|
217
|
+
}
|
|
218
|
+
const contextRecord = responsesContext && typeof responsesContext === 'object'
|
|
219
|
+
? responsesContext
|
|
220
|
+
: undefined;
|
|
221
|
+
const previousId = typeof contextRecord?.previous_response_id === 'string'
|
|
222
|
+
? contextRecord.previous_response_id.trim()
|
|
223
|
+
: undefined;
|
|
224
|
+
if (previousId) {
|
|
225
|
+
return previousId;
|
|
226
|
+
}
|
|
227
|
+
return undefined;
|
|
228
|
+
}
|
|
229
|
+
function extractSubmitMetadata(responsesContext, resumeMeta) {
|
|
230
|
+
if (responsesContext && responsesContext.metadata && isJsonObject(responsesContext.metadata)) {
|
|
231
|
+
return jsonClone(responsesContext.metadata);
|
|
232
|
+
}
|
|
233
|
+
if (resumeMeta && resumeMeta.metadata && isJsonObject(resumeMeta.metadata)) {
|
|
234
|
+
return jsonClone(resumeMeta.metadata);
|
|
235
|
+
}
|
|
236
|
+
return undefined;
|
|
237
|
+
}
|
|
238
|
+
function coerceOutputText(value) {
|
|
239
|
+
if (typeof value === 'string') {
|
|
240
|
+
return value;
|
|
241
|
+
}
|
|
242
|
+
if (value === undefined || value === null) {
|
|
243
|
+
return '';
|
|
244
|
+
}
|
|
245
|
+
try {
|
|
246
|
+
return JSON.stringify(value);
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
return String(value);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
function extractCapturedToolOutputs(responsesContext) {
|
|
253
|
+
if (!responsesContext || typeof responsesContext !== 'object') {
|
|
254
|
+
return [];
|
|
255
|
+
}
|
|
256
|
+
const snapshot = responsesContext.__captured_tool_results;
|
|
257
|
+
if (!Array.isArray(snapshot) || !snapshot.length) {
|
|
258
|
+
return [];
|
|
259
|
+
}
|
|
260
|
+
const entries = [];
|
|
261
|
+
snapshot.forEach((entry) => {
|
|
262
|
+
if (!entry || typeof entry !== 'object') {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
const record = entry;
|
|
266
|
+
const toolId = typeof record.tool_call_id === 'string' && record.tool_call_id.trim().length
|
|
267
|
+
? record.tool_call_id.trim()
|
|
268
|
+
: typeof record.call_id === 'string' && record.call_id.trim().length
|
|
269
|
+
? record.call_id.trim()
|
|
270
|
+
: undefined;
|
|
271
|
+
if (!toolId) {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
entries.push({
|
|
275
|
+
tool_call_id: toolId,
|
|
276
|
+
id: toolId,
|
|
277
|
+
output: typeof record.output === 'string' ? record.output : coerceOutputText(record.output),
|
|
278
|
+
name: typeof record.name === 'string' ? record.name : undefined
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
return entries;
|
|
282
|
+
}
|
|
283
|
+
function collectSubmitToolOutputs(chat, ctx, responsesContext) {
|
|
284
|
+
const outputs = [];
|
|
285
|
+
const seen = new Set();
|
|
286
|
+
const append = (idSeed, outputSeed, name) => {
|
|
287
|
+
const trimmed = typeof idSeed === 'string' && idSeed.trim().length ? idSeed.trim() : '';
|
|
288
|
+
const fallbackId = trimmed || `submit_tool_${outputs.length + 1}`;
|
|
289
|
+
if (seen.has(fallbackId)) {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
seen.add(fallbackId);
|
|
293
|
+
outputs.push({
|
|
294
|
+
tool_call_id: fallbackId,
|
|
295
|
+
id: fallbackId,
|
|
296
|
+
output: coerceOutputText(outputSeed),
|
|
297
|
+
name
|
|
298
|
+
});
|
|
299
|
+
};
|
|
300
|
+
if (Array.isArray(chat.toolOutputs) && chat.toolOutputs.length) {
|
|
301
|
+
chat.toolOutputs.forEach((entry) => {
|
|
302
|
+
append(entry.tool_call_id ?? entry.call_id, entry.content, entry.name);
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
if (!outputs.length) {
|
|
306
|
+
const captured = extractCapturedToolOutputs(responsesContext);
|
|
307
|
+
captured.forEach((entry) => append(entry.tool_call_id ?? entry.id, entry.output, entry.name));
|
|
308
|
+
}
|
|
309
|
+
if (!outputs.length) {
|
|
310
|
+
const resume = ctx.responsesResume && typeof ctx.responsesResume === 'object'
|
|
311
|
+
? ctx.responsesResume
|
|
312
|
+
: undefined;
|
|
313
|
+
const detailed = Array.isArray(resume?.toolOutputsDetailed)
|
|
314
|
+
? resume?.toolOutputsDetailed
|
|
315
|
+
: undefined;
|
|
316
|
+
if (detailed) {
|
|
317
|
+
detailed.forEach((entry, index) => {
|
|
318
|
+
if (!entry || typeof entry !== 'object') {
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
const callId = typeof entry.callId === 'string' && entry.callId.trim().length
|
|
322
|
+
? entry.callId.trim()
|
|
323
|
+
: typeof entry.originalId === 'string' && entry.originalId.trim().length
|
|
324
|
+
? entry.originalId.trim()
|
|
325
|
+
: `resume_tool_${index + 1}`;
|
|
326
|
+
append(callId, typeof entry.outputText === 'string' ? entry.outputText : entry.outputText ?? '');
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return outputs;
|
|
331
|
+
}
|
|
332
|
+
function resolveSubmitStreamFlag(chat, ctx, responsesContext) {
|
|
333
|
+
if (chat.parameters && typeof chat.parameters.stream === 'boolean') {
|
|
334
|
+
return chat.parameters.stream;
|
|
335
|
+
}
|
|
336
|
+
if (responsesContext && typeof responsesContext.stream === 'boolean') {
|
|
337
|
+
return responsesContext.stream;
|
|
338
|
+
}
|
|
339
|
+
if (ctx.streamingHint === 'force') {
|
|
340
|
+
return true;
|
|
341
|
+
}
|
|
342
|
+
if (ctx.streamingHint === 'disable') {
|
|
343
|
+
return false;
|
|
344
|
+
}
|
|
345
|
+
return undefined;
|
|
346
|
+
}
|
|
347
|
+
function resolveSubmitModel(chat, responsesContext) {
|
|
348
|
+
const direct = chat.parameters && typeof chat.parameters.model === 'string'
|
|
349
|
+
? chat.parameters.model.trim()
|
|
350
|
+
: undefined;
|
|
351
|
+
if (direct) {
|
|
352
|
+
return direct;
|
|
353
|
+
}
|
|
354
|
+
if (responsesContext && typeof responsesContext.parameters === 'object') {
|
|
355
|
+
const params = responsesContext.parameters;
|
|
356
|
+
const model = typeof params.model === 'string' ? params.model.trim() : undefined;
|
|
357
|
+
if (model) {
|
|
358
|
+
return model;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return undefined;
|
|
362
|
+
}
|
|
363
|
+
function buildSubmitToolOutputsPayload(chat, ctx, responsesContext) {
|
|
364
|
+
const responseId = resolveSubmitResponseId(ctx, responsesContext);
|
|
365
|
+
if (!responseId) {
|
|
366
|
+
throw new Error('Submit tool outputs requires response_id from Responses resume context');
|
|
367
|
+
}
|
|
368
|
+
const toolOutputs = collectSubmitToolOutputs(chat, ctx, responsesContext);
|
|
369
|
+
if (!toolOutputs.length) {
|
|
370
|
+
throw new Error('Submit tool outputs requires at least one tool output entry');
|
|
371
|
+
}
|
|
372
|
+
const resumeMeta = ctx.responsesResume && typeof ctx.responsesResume === 'object'
|
|
373
|
+
? ctx.responsesResume
|
|
374
|
+
: undefined;
|
|
375
|
+
const payload = {
|
|
376
|
+
response_id: responseId,
|
|
377
|
+
tool_outputs: toolOutputs
|
|
378
|
+
};
|
|
379
|
+
const modelValue = resolveSubmitModel(chat, responsesContext);
|
|
380
|
+
if (modelValue) {
|
|
381
|
+
payload.model = modelValue;
|
|
382
|
+
}
|
|
383
|
+
const streamValue = resolveSubmitStreamFlag(chat, ctx, responsesContext);
|
|
384
|
+
if (typeof streamValue === 'boolean') {
|
|
385
|
+
payload.stream = streamValue;
|
|
386
|
+
}
|
|
387
|
+
const metadata = extractSubmitMetadata(responsesContext, resumeMeta);
|
|
388
|
+
if (metadata) {
|
|
389
|
+
payload.metadata = metadata;
|
|
390
|
+
}
|
|
391
|
+
return payload;
|
|
392
|
+
}
|
|
200
393
|
export class ResponsesSemanticMapper {
|
|
201
394
|
async toChat(format, ctx) {
|
|
202
395
|
const payload = format.payload || {};
|
|
@@ -257,6 +450,28 @@ export class ResponsesSemanticMapper {
|
|
|
257
450
|
};
|
|
258
451
|
}
|
|
259
452
|
async fromChat(chat, ctx) {
|
|
453
|
+
const capturedContext = chat.metadata?.responsesContext;
|
|
454
|
+
const envelopeMetadata = chat.metadata && isJsonObject(chat.metadata) ? chat.metadata : undefined;
|
|
455
|
+
const responsesContext = isJsonObject(capturedContext)
|
|
456
|
+
? {
|
|
457
|
+
...capturedContext,
|
|
458
|
+
metadata: mergeMetadata(capturedContext.metadata, envelopeMetadata)
|
|
459
|
+
}
|
|
460
|
+
: {
|
|
461
|
+
metadata: envelopeMetadata
|
|
462
|
+
};
|
|
463
|
+
if (isSubmitToolOutputsEndpoint(ctx)) {
|
|
464
|
+
const submitPayload = buildSubmitToolOutputsPayload(chat, ctx, responsesContext);
|
|
465
|
+
return {
|
|
466
|
+
protocol: 'openai-responses',
|
|
467
|
+
direction: 'response',
|
|
468
|
+
payload: submitPayload,
|
|
469
|
+
meta: {
|
|
470
|
+
context: ctx,
|
|
471
|
+
submitToolOutputs: true
|
|
472
|
+
}
|
|
473
|
+
};
|
|
474
|
+
}
|
|
260
475
|
const modelValue = chat.parameters?.model;
|
|
261
476
|
if (typeof modelValue !== 'string' || !modelValue.trim()) {
|
|
262
477
|
throw new Error('ChatEnvelope.parameters.model is required for openai-responses outbound conversion');
|
|
@@ -271,18 +486,7 @@ export class ResponsesSemanticMapper {
|
|
|
271
486
|
.filter((message) => Boolean(message && typeof message === 'object' && message.role === 'system'))
|
|
272
487
|
.map(message => serializeSystemContent(message))
|
|
273
488
|
.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
|
-
};
|
|
489
|
+
responsesContext.originalSystemMessages = originalSystemMessages;
|
|
286
490
|
const responsesResult = buildResponsesRequestFromChat(requestShape, responsesContext);
|
|
287
491
|
const responses = responsesResult.request;
|
|
288
492
|
if (chat.parameters && chat.parameters.stream !== undefined) {
|
|
@@ -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>;
|