@jsonstudio/llms 0.6.3551 → 0.6.3686
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/actions/antigravity-thought-signature-cache.js +4 -115
- package/dist/conversion/compat/actions/auto-thinking.js +3 -2
- package/dist/conversion/compat/actions/deepseek-web-response.js +15 -49
- package/dist/conversion/compat/actions/gemini-cli-request.d.ts +2 -0
- package/dist/conversion/compat/actions/gemini-cli-request.js +1 -1
- package/dist/conversion/compat/actions/glm-history-image-trim.js +3 -37
- package/dist/conversion/compat/actions/glm-image-content.js +3 -32
- package/dist/conversion/compat/actions/glm-native-compat.d.ts +6 -0
- package/dist/conversion/compat/actions/glm-native-compat.js +34 -0
- package/dist/conversion/compat/actions/glm-vision-prompt.js +3 -76
- package/dist/conversion/compat/actions/glm-web-search.js +10 -43
- package/dist/conversion/compat/actions/iflow-kimi-cli-defaults.js +4 -53
- package/dist/conversion/compat/actions/iflow-kimi-history-media-placeholder.js +5 -141
- package/dist/conversion/compat/actions/iflow-kimi-thinking-reasoning-fill.js +7 -28
- package/dist/conversion/compat/actions/iflow-native-compat.d.ts +6 -0
- package/dist/conversion/compat/actions/iflow-native-compat.js +36 -0
- package/dist/conversion/compat/actions/iflow-response-body-unwrap.js +4 -119
- package/dist/conversion/compat/actions/iflow-web-search.js +14 -55
- package/dist/conversion/hub/operation-table/semantic-mappers/archive/chat-mapper.archive.js +5 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/responses-mapper.js +31 -18
- package/dist/conversion/hub/pipeline/hub-pipeline.js +163 -163
- package/dist/conversion/hub/pipeline/hub-stage-timing.d.ts +6 -0
- package/dist/conversion/hub/pipeline/hub-stage-timing.js +178 -0
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage1_format_parse/index.d.ts +4 -4
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage1_format_parse/index.js +33 -14
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.d.ts +7 -6
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.js +41 -23
- package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/index.js +44 -1
- package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage1_tool_governance/index.js +4 -1
- package/dist/conversion/hub/process/chat-process-continue-execution.js +5 -4
- package/dist/conversion/hub/process/chat-process-governance-orchestration.js +3 -1
- package/dist/conversion/hub/process/chat-process-media.d.ts +3 -1
- package/dist/conversion/hub/process/chat-process-media.js +92 -2
- package/dist/conversion/hub/process/chat-process-session-usage.d.ts +7 -0
- package/dist/conversion/hub/process/chat-process-session-usage.js +147 -0
- package/dist/conversion/hub/response/provider-response.js +13 -0
- package/dist/conversion/responses/responses-openai-bridge/response-payload.js +0 -12
- package/dist/conversion/responses/responses-openai-bridge/types.d.ts +1 -9
- package/dist/conversion/responses/responses-openai-bridge.js +77 -44
- package/dist/conversion/shared/reasoning-normalizer.js +42 -0
- package/dist/conversion/shared/responses-tool-utils.js +2 -3
- package/dist/native/router_hotpath_napi.node +0 -0
- package/dist/router/virtual-router/bootstrap/profile-builder.js +1 -0
- package/dist/router/virtual-router/bootstrap/provider-normalization.d.ts +1 -0
- package/dist/router/virtual-router/bootstrap/provider-normalization.js +6 -0
- package/dist/router/virtual-router/bootstrap.js +1 -6
- package/dist/router/virtual-router/engine-legacy.js +43 -0
- package/dist/router/virtual-router/engine-logging.d.ts +3 -0
- package/dist/router/virtual-router/engine-logging.js +29 -3
- package/dist/router/virtual-router/engine-selection/native-hub-pipeline-edge-stage-semantics.d.ts +2 -2
- package/dist/router/virtual-router/engine-selection/native-hub-pipeline-edge-stage-semantics.js +96 -80
- package/dist/router/virtual-router/engine-selection/native-hub-pipeline-req-process-semantics.d.ts +1 -0
- package/dist/router/virtual-router/engine.js +34 -22
- package/dist/router/virtual-router/provider-registry.js +1 -0
- package/dist/router/virtual-router/routing-instructions/state.js +35 -3
- package/dist/router/virtual-router/routing-instructions/types.d.ts +4 -0
- package/dist/router/virtual-router/types.d.ts +7 -0
- package/dist/servertool/engine.js +3 -34
- package/dist/servertool/handlers/followup-request-builder.js +0 -6
- package/dist/servertool/handlers/gemini-empty-reply-continue.js +3 -274
- package/dist/servertool/handlers/stop-message-auto/runtime-utils.d.ts +0 -3
- package/dist/servertool/handlers/stop-message-auto/runtime-utils.js +0 -29
- package/dist/servertool/handlers/stop-message-auto.js +2 -10
- package/dist/servertool/handlers/vision.js +4 -1
- package/dist/servertool/server-side-tools.js +66 -3
- package/package.json +1 -1
|
@@ -189,7 +189,7 @@ function isEmptyClientResponsePayload(payload) {
|
|
|
189
189
|
return false;
|
|
190
190
|
}
|
|
191
191
|
// OpenAI Responses: requires_action (function_call output) is a meaningful response and must not be
|
|
192
|
-
// treated as "empty". Some auto-followup servertools (
|
|
192
|
+
// treated as "empty". Some auto-followup servertools (for example stop_message_flow)
|
|
193
193
|
// previously misclassified this as empty because there is no output_text/content yet.
|
|
194
194
|
const requiredAction = payload.required_action;
|
|
195
195
|
if (requiredAction && typeof requiredAction === 'object') {
|
|
@@ -314,7 +314,6 @@ export async function runServerToolOrchestration(options) {
|
|
|
314
314
|
continue_execution_flow: 'continue_execution',
|
|
315
315
|
review_flow: 'review',
|
|
316
316
|
stop_message_flow: 'stop_message_auto',
|
|
317
|
-
empty_reply_continue: 'empty_reply_continue',
|
|
318
317
|
apply_patch_guard: 'apply_patch_guard',
|
|
319
318
|
exec_command_guard: 'exec_command_guard',
|
|
320
319
|
iflow_model_error_retry: 'iflow_model_error_retry',
|
|
@@ -636,11 +635,10 @@ export async function runServerToolOrchestration(options) {
|
|
|
636
635
|
const isClockHoldFlow = engineResult.execution.flowId === 'clock_hold_flow';
|
|
637
636
|
const isContinueExecutionFlow = engineResult.execution.flowId === 'continue_execution_flow';
|
|
638
637
|
const isReviewFlow = engineResult.execution.flowId === 'review_flow';
|
|
639
|
-
const isEmptyReplyContinue = engineResult.execution.flowId === 'empty_reply_continue';
|
|
640
638
|
const isApplyPatchGuard = engineResult.execution.flowId === 'apply_patch_guard';
|
|
641
639
|
const isExecCommandGuard = engineResult.execution.flowId === 'exec_command_guard';
|
|
642
640
|
const isErrorAutoFlow = engineResult.execution.flowId === 'iflow_model_error_retry';
|
|
643
|
-
const applyAutoLimit = isErrorAutoFlow ||
|
|
641
|
+
const applyAutoLimit = isErrorAutoFlow || isApplyPatchGuard || isExecCommandGuard;
|
|
644
642
|
// ServerTool followups must not inherit or inject any routeHint; always route fresh.
|
|
645
643
|
const preserveRouteHint = false;
|
|
646
644
|
const followupPlan = engineResult.execution.followup;
|
|
@@ -757,8 +755,6 @@ export async function runServerToolOrchestration(options) {
|
|
|
757
755
|
// - do not inherit sticky target
|
|
758
756
|
// - record original entry endpoint for downstream formatting/debug
|
|
759
757
|
rt.preserveRouteHint = preserveRouteHint;
|
|
760
|
-
// Use empty string (falsy) to avoid VirtualRouter calling `.trim()` on non-string values.
|
|
761
|
-
metadata.routeHint = '';
|
|
762
758
|
rt.disableStickyRoutes = true;
|
|
763
759
|
rt.serverToolOriginalEntryEndpoint =
|
|
764
760
|
(typeof options.entryEndpoint === 'string' && options.entryEndpoint.trim().length
|
|
@@ -774,7 +770,7 @@ export async function runServerToolOrchestration(options) {
|
|
|
774
770
|
metadata.__shadowCompareForcedProviderKey = providerKey;
|
|
775
771
|
}
|
|
776
772
|
}
|
|
777
|
-
const retryEmptyFollowupOnce = isStopMessageFlow
|
|
773
|
+
const retryEmptyFollowupOnce = isStopMessageFlow;
|
|
778
774
|
const maxAttempts = retryEmptyFollowupOnce ? 2 : 1;
|
|
779
775
|
const followupRequestId = buildFollowupRequestId(options.requestId, engineResult.execution.followup.requestIdSuffix);
|
|
780
776
|
const clientInjectOnlyRaw = metadata.clientInjectOnly;
|
|
@@ -1072,7 +1068,6 @@ export async function runServerToolOrchestration(options) {
|
|
|
1072
1068
|
replayRt.serverToolLoopState = replayLoopState;
|
|
1073
1069
|
}
|
|
1074
1070
|
replayMetadata.__hubEntry = 'chat_process';
|
|
1075
|
-
replayMetadata.routeHint = '';
|
|
1076
1071
|
replayRt.preserveRouteHint = false;
|
|
1077
1072
|
replayRt.disableStickyRoutes = true;
|
|
1078
1073
|
replayRt.serverToolOriginalEntryEndpoint =
|
|
@@ -1374,17 +1369,6 @@ function resolveCapturedChatRequest(adapterContext) {
|
|
|
1374
1369
|
if (direct && typeof direct === 'object' && !Array.isArray(direct)) {
|
|
1375
1370
|
return direct;
|
|
1376
1371
|
}
|
|
1377
|
-
const rt = readRuntimeMetadata(record);
|
|
1378
|
-
const runtimeCaptured = rt && typeof rt === 'object' && !Array.isArray(rt)
|
|
1379
|
-
? rt.capturedChatRequest
|
|
1380
|
-
: undefined;
|
|
1381
|
-
if (runtimeCaptured && typeof runtimeCaptured === 'object' && !Array.isArray(runtimeCaptured)) {
|
|
1382
|
-
return runtimeCaptured;
|
|
1383
|
-
}
|
|
1384
|
-
const originalRequest = record.originalRequest;
|
|
1385
|
-
if (originalRequest && typeof originalRequest === 'object' && !Array.isArray(originalRequest)) {
|
|
1386
|
-
return originalRequest;
|
|
1387
|
-
}
|
|
1388
1372
|
return null;
|
|
1389
1373
|
}
|
|
1390
1374
|
function buildStopMessageLoopPayload(adapterContext) {
|
|
@@ -1546,21 +1530,6 @@ function buildFollowupRequestId(baseRequestId, suffix) {
|
|
|
1546
1530
|
}
|
|
1547
1531
|
return normalized.endsWith(suffixText) ? normalized : `${normalized}${suffixText}`;
|
|
1548
1532
|
}
|
|
1549
|
-
function getStopMessageSource(adapterContext) {
|
|
1550
|
-
if (!adapterContext || typeof adapterContext !== 'object') {
|
|
1551
|
-
return undefined;
|
|
1552
|
-
}
|
|
1553
|
-
const rt = readRuntimeMetadata(adapterContext);
|
|
1554
|
-
const raw = rt?.stopMessageState;
|
|
1555
|
-
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
1556
|
-
return undefined;
|
|
1557
|
-
}
|
|
1558
|
-
const record = raw;
|
|
1559
|
-
const source = typeof record.stopMessageSource === 'string' && record.stopMessageSource.trim()
|
|
1560
|
-
? record.stopMessageSource.trim()
|
|
1561
|
-
: '';
|
|
1562
|
-
return source || undefined;
|
|
1563
|
-
}
|
|
1564
1533
|
function isAdapterClientDisconnected(adapterContext) {
|
|
1565
1534
|
if (!adapterContext || typeof adapterContext !== 'object') {
|
|
1566
1535
|
return false;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { buildChatRequestFromResponses, captureResponsesContext } from '../../conversion/responses/responses-openai-bridge.js';
|
|
2
|
-
import { stripHistoricalImageAttachments } from '../../conversion/hub/process/chat-process-media.js';
|
|
3
2
|
import { cloneJson } from '../server-side-tools.js';
|
|
4
3
|
import { trimOpenAiMessagesForFollowup } from './followup-message-trimmer.js';
|
|
5
4
|
function extractResponsesTopLevelParameters(record) {
|
|
@@ -423,11 +422,6 @@ export function buildServerToolFollowupChatPayloadFromInjection(args) {
|
|
|
423
422
|
return null;
|
|
424
423
|
}
|
|
425
424
|
let messages = Array.isArray(seed.messages) ? cloneJson(seed.messages) : [];
|
|
426
|
-
// ServerTool followups must enter marker/routing/chat-process analysis with the same
|
|
427
|
-
// historical-media invariants as normal chat-process requests:
|
|
428
|
-
// only the latest live user turn may keep inline image payloads; earlier user turns
|
|
429
|
-
// are scrubbed to placeholders before any followup ops append new assistant/user items.
|
|
430
|
-
messages = stripHistoricalImageAttachments(messages);
|
|
431
425
|
const ops = Array.isArray(args.injection?.ops) ? args.injection.ops : [];
|
|
432
426
|
// Followup is a normal request hop: inherit tool schema from the captured request and
|
|
433
427
|
// let compat/tool-governance apply standard sanitization rules.
|
|
@@ -1,274 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import { ensureRuntimeMetadata, readRuntimeMetadata } from '../../conversion/runtime-metadata.js';
|
|
5
|
-
const FLOW_ID = 'empty_reply_continue';
|
|
6
|
-
const MAX_TOOL_HINTS = 24;
|
|
7
|
-
const handler = async (ctx) => {
|
|
8
|
-
if (!ctx.capabilities.reenterPipeline) {
|
|
9
|
-
return null;
|
|
10
|
-
}
|
|
11
|
-
// 避免在 followup 请求里再次触发,防止循环。
|
|
12
|
-
const rt = readRuntimeMetadata(ctx.adapterContext);
|
|
13
|
-
const followupRaw = rt?.serverToolFollowup;
|
|
14
|
-
if (followupRaw === true || (typeof followupRaw === 'string' && followupRaw.trim().toLowerCase() === 'true')) {
|
|
15
|
-
return null;
|
|
16
|
-
}
|
|
17
|
-
if (hasCompactionFlag(rt)) {
|
|
18
|
-
return null;
|
|
19
|
-
}
|
|
20
|
-
// 通用空回复续跑:只要是 /v1/responses 链路且满足空回复判定,不区分 provider family / protocol。
|
|
21
|
-
const entryEndpoint = (ctx.entryEndpoint || '').toLowerCase();
|
|
22
|
-
if (!entryEndpoint.includes('/v1/responses')) {
|
|
23
|
-
return null;
|
|
24
|
-
}
|
|
25
|
-
// 支持两种客户端协议形状:
|
|
26
|
-
// - OpenAI Chat: choices[0].message.content
|
|
27
|
-
// - OpenAI Responses: output/output_text/status
|
|
28
|
-
const base = ctx.base;
|
|
29
|
-
const emptyDecision = decideEmptyReply(base);
|
|
30
|
-
if (!emptyDecision.shouldTrigger) {
|
|
31
|
-
return null;
|
|
32
|
-
}
|
|
33
|
-
// 统计连续空回复次数,超过上限后不再自动续写,而是返回一个可重试错误。
|
|
34
|
-
const previousCountRaw = rt?.emptyReplyContinueCount ?? rt?.geminiEmptyReplyCount;
|
|
35
|
-
const previousCount = typeof previousCountRaw === 'number' && Number.isFinite(previousCountRaw) && previousCountRaw >= 0
|
|
36
|
-
? previousCountRaw
|
|
37
|
-
: 0;
|
|
38
|
-
const nextCount = previousCount + 1;
|
|
39
|
-
const captured = getCapturedRequest(ctx.adapterContext);
|
|
40
|
-
if (!captured) {
|
|
41
|
-
return null;
|
|
42
|
-
}
|
|
43
|
-
if (isCompactionRequest(captured)) {
|
|
44
|
-
return null;
|
|
45
|
-
}
|
|
46
|
-
const seed = extractCapturedChatSeed(captured);
|
|
47
|
-
if (!seed) {
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
const continueText = buildContinueUserText(seed);
|
|
51
|
-
// 超过最多 3 次空回复:返回一个 HTTP_HANDLER_ERROR 形状的错误,交由上层错误中心处理。
|
|
52
|
-
if (nextCount > 3) {
|
|
53
|
-
const errorChat = {
|
|
54
|
-
id: base.id,
|
|
55
|
-
object: base.object,
|
|
56
|
-
model: base.model,
|
|
57
|
-
error: {
|
|
58
|
-
message: 'fetch failed: empty_reply_continue exceeded max empty replies',
|
|
59
|
-
code: 'HTTP_HANDLER_ERROR',
|
|
60
|
-
type: 'servertool_empty_reply'
|
|
61
|
-
}
|
|
62
|
-
};
|
|
63
|
-
return {
|
|
64
|
-
flowId: FLOW_ID,
|
|
65
|
-
finalize: async () => ({
|
|
66
|
-
chatResponse: errorChat,
|
|
67
|
-
execution: {
|
|
68
|
-
flowId: FLOW_ID
|
|
69
|
-
}
|
|
70
|
-
})
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
return {
|
|
74
|
-
flowId: FLOW_ID,
|
|
75
|
-
finalize: async () => ({
|
|
76
|
-
chatResponse: ctx.base,
|
|
77
|
-
execution: {
|
|
78
|
-
flowId: FLOW_ID,
|
|
79
|
-
followup: {
|
|
80
|
-
requestIdSuffix: ':continue',
|
|
81
|
-
entryEndpoint: ctx.entryEndpoint,
|
|
82
|
-
injection: {
|
|
83
|
-
ops: [
|
|
84
|
-
{ op: 'preserve_tools' },
|
|
85
|
-
{ op: 'append_assistant_message', required: false },
|
|
86
|
-
{ op: 'append_user_text', text: continueText }
|
|
87
|
-
]
|
|
88
|
-
},
|
|
89
|
-
metadata: (() => {
|
|
90
|
-
const meta = {};
|
|
91
|
-
const runtime = ensureRuntimeMetadata(meta);
|
|
92
|
-
runtime.emptyReplyContinueCount = nextCount;
|
|
93
|
-
// Backward compatibility for old snapshots/guards.
|
|
94
|
-
runtime.geminiEmptyReplyCount = nextCount;
|
|
95
|
-
return meta;
|
|
96
|
-
})()
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
})
|
|
100
|
-
};
|
|
101
|
-
};
|
|
102
|
-
registerServerToolHandler('empty_reply_continue', handler, { trigger: 'auto', hook: { phase: 'default', priority: 20 } });
|
|
103
|
-
function decideEmptyReply(base) {
|
|
104
|
-
// 1) OpenAI Chat shape
|
|
105
|
-
const choices = Array.isArray(base.choices) ? base.choices : [];
|
|
106
|
-
if (choices.length > 0) {
|
|
107
|
-
const firstRaw = choices[0];
|
|
108
|
-
if (!firstRaw || typeof firstRaw !== 'object') {
|
|
109
|
-
return { shouldTrigger: false };
|
|
110
|
-
}
|
|
111
|
-
const first = firstRaw;
|
|
112
|
-
const finishReasonRaw = typeof first.finish_reason === 'string' && first.finish_reason.trim()
|
|
113
|
-
? first.finish_reason.trim()
|
|
114
|
-
: '';
|
|
115
|
-
const finishReason = finishReasonRaw.toLowerCase();
|
|
116
|
-
const isStop = finishReason === 'stop';
|
|
117
|
-
const isMaxTokens = finishReason === 'length'; // 映射自 Gemini 的 MAX_TOKENS
|
|
118
|
-
if (!isStop && !isMaxTokens) {
|
|
119
|
-
return { shouldTrigger: false };
|
|
120
|
-
}
|
|
121
|
-
const message = first.message && typeof first.message === 'object' && !Array.isArray(first.message)
|
|
122
|
-
? first.message
|
|
123
|
-
: null;
|
|
124
|
-
if (!message) {
|
|
125
|
-
return { shouldTrigger: false };
|
|
126
|
-
}
|
|
127
|
-
const contentRaw = message.content;
|
|
128
|
-
const contentText = typeof contentRaw === 'string' ? contentRaw.trim() : '';
|
|
129
|
-
// 对于 finish_reason=stop,仅在真正“空回复”时触发;
|
|
130
|
-
// 对于 finish_reason=length(MAX_TOKENS 截断),允许已有内容,视为需要自动续写。
|
|
131
|
-
if (isStop && contentText.length > 0) {
|
|
132
|
-
return { shouldTrigger: false };
|
|
133
|
-
}
|
|
134
|
-
const toolCalls = Array.isArray(message.tool_calls) ? message.tool_calls : [];
|
|
135
|
-
if (toolCalls.length > 0) {
|
|
136
|
-
return { shouldTrigger: false };
|
|
137
|
-
}
|
|
138
|
-
return { shouldTrigger: true };
|
|
139
|
-
}
|
|
140
|
-
// 2) OpenAI Responses shape
|
|
141
|
-
const statusRaw = typeof base.status === 'string' ? base.status.trim().toLowerCase() : '';
|
|
142
|
-
if (statusRaw && statusRaw !== 'completed') {
|
|
143
|
-
return { shouldTrigger: false };
|
|
144
|
-
}
|
|
145
|
-
if (base.required_action && typeof base.required_action === 'object') {
|
|
146
|
-
return { shouldTrigger: false };
|
|
147
|
-
}
|
|
148
|
-
const outputText = extractResponsesOutputText(base);
|
|
149
|
-
if (outputText.length > 0) {
|
|
150
|
-
return { shouldTrigger: false };
|
|
151
|
-
}
|
|
152
|
-
const outputRaw = Array.isArray(base.output) ? base.output : [];
|
|
153
|
-
if (outputRaw.some((item) => hasToolLikeOutput(item))) {
|
|
154
|
-
return { shouldTrigger: false };
|
|
155
|
-
}
|
|
156
|
-
// 允许 output 为空或仅包含空消息:视作空回复,触发自动续写。
|
|
157
|
-
return { shouldTrigger: true };
|
|
158
|
-
}
|
|
159
|
-
function extractResponsesOutputText(base) {
|
|
160
|
-
const raw = base.output_text;
|
|
161
|
-
if (typeof raw === 'string') {
|
|
162
|
-
return raw.trim();
|
|
163
|
-
}
|
|
164
|
-
if (Array.isArray(raw)) {
|
|
165
|
-
const texts = raw
|
|
166
|
-
.map((entry) => (typeof entry === 'string' ? entry : ''))
|
|
167
|
-
.filter((entry) => entry.trim().length > 0);
|
|
168
|
-
if (texts.length > 0) {
|
|
169
|
-
return texts.join('\n').trim();
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
const output = Array.isArray(base.output) ? (base.output) : [];
|
|
173
|
-
const chunks = [];
|
|
174
|
-
for (const item of output) {
|
|
175
|
-
if (!item || typeof item !== 'object' || Array.isArray(item))
|
|
176
|
-
continue;
|
|
177
|
-
if (typeof item.type !== 'string')
|
|
178
|
-
continue;
|
|
179
|
-
const type = String(item.type).trim().toLowerCase();
|
|
180
|
-
if (type !== 'message')
|
|
181
|
-
continue;
|
|
182
|
-
const content = Array.isArray(item.content) ? (item.content) : [];
|
|
183
|
-
for (const part of content) {
|
|
184
|
-
if (!part || typeof part !== 'object' || Array.isArray(part))
|
|
185
|
-
continue;
|
|
186
|
-
const pType = typeof part.type === 'string'
|
|
187
|
-
? String(part.type).trim().toLowerCase()
|
|
188
|
-
: '';
|
|
189
|
-
if (pType === 'output_text' || pType === 'text' || pType === 'input_text') {
|
|
190
|
-
const text = typeof part.text === 'string' ? String(part.text) : '';
|
|
191
|
-
if (text.trim().length)
|
|
192
|
-
chunks.push(text.trim());
|
|
193
|
-
continue;
|
|
194
|
-
}
|
|
195
|
-
const fallback = typeof part.content === 'string'
|
|
196
|
-
? String(part.content).trim()
|
|
197
|
-
: '';
|
|
198
|
-
if (fallback.length)
|
|
199
|
-
chunks.push(fallback);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
return chunks.join('\n').trim();
|
|
203
|
-
}
|
|
204
|
-
function hasToolLikeOutput(value) {
|
|
205
|
-
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
206
|
-
return false;
|
|
207
|
-
}
|
|
208
|
-
const typeRaw = value.type;
|
|
209
|
-
const type = typeof typeRaw === 'string' ? typeRaw.trim().toLowerCase() : '';
|
|
210
|
-
if (!type) {
|
|
211
|
-
return false;
|
|
212
|
-
}
|
|
213
|
-
return (type === 'tool_call' ||
|
|
214
|
-
type === 'tool_use' ||
|
|
215
|
-
type === 'function_call' ||
|
|
216
|
-
type.includes('tool'));
|
|
217
|
-
}
|
|
218
|
-
function getCapturedRequest(adapterContext) {
|
|
219
|
-
if (!adapterContext || typeof adapterContext !== 'object') {
|
|
220
|
-
return null;
|
|
221
|
-
}
|
|
222
|
-
const captured = adapterContext.capturedChatRequest;
|
|
223
|
-
if (!captured || typeof captured !== 'object' || Array.isArray(captured)) {
|
|
224
|
-
return null;
|
|
225
|
-
}
|
|
226
|
-
return captured;
|
|
227
|
-
}
|
|
228
|
-
function hasCompactionFlag(rt) {
|
|
229
|
-
const flag = rt && typeof rt === 'object' && !Array.isArray(rt) ? rt.compactionRequest : undefined;
|
|
230
|
-
if (flag === true) {
|
|
231
|
-
return true;
|
|
232
|
-
}
|
|
233
|
-
if (typeof flag === 'string' && flag.trim().toLowerCase() === 'true') {
|
|
234
|
-
return true;
|
|
235
|
-
}
|
|
236
|
-
return false;
|
|
237
|
-
}
|
|
238
|
-
function buildContinueUserText(seed) {
|
|
239
|
-
const toolNames = collectToolNames(seed);
|
|
240
|
-
if (!toolNames.length) {
|
|
241
|
-
return '继续执行';
|
|
242
|
-
}
|
|
243
|
-
const shown = toolNames.slice(0, MAX_TOOL_HINTS);
|
|
244
|
-
const omitted = toolNames.length - shown.length;
|
|
245
|
-
const listText = shown.join(', ');
|
|
246
|
-
const tail = omitted > 0 ? `(其余 ${omitted} 个省略)` : '';
|
|
247
|
-
return `继续执行。可用工具列表:${listText}${tail}。请优先调用这些工具,不要返回空回复。`;
|
|
248
|
-
}
|
|
249
|
-
function collectToolNames(seed) {
|
|
250
|
-
const tools = Array.isArray(seed.tools) ? seed.tools : [];
|
|
251
|
-
if (!tools.length) {
|
|
252
|
-
return [];
|
|
253
|
-
}
|
|
254
|
-
const names = [];
|
|
255
|
-
const seen = new Set();
|
|
256
|
-
for (const tool of tools) {
|
|
257
|
-
if (!tool || typeof tool !== 'object' || Array.isArray(tool))
|
|
258
|
-
continue;
|
|
259
|
-
const fnNode = tool.function;
|
|
260
|
-
const fnName = fnNode && typeof fnNode === 'object' && !Array.isArray(fnNode)
|
|
261
|
-
? normalizeToolName(fnNode.name)
|
|
262
|
-
: '';
|
|
263
|
-
const fallbackName = normalizeToolName(tool.name);
|
|
264
|
-
const resolvedName = fnName || fallbackName;
|
|
265
|
-
if (!resolvedName || seen.has(resolvedName))
|
|
266
|
-
continue;
|
|
267
|
-
seen.add(resolvedName);
|
|
268
|
-
names.push(resolvedName);
|
|
269
|
-
}
|
|
270
|
-
return names;
|
|
271
|
-
}
|
|
272
|
-
function normalizeToolName(value) {
|
|
273
|
-
return typeof value === 'string' && value.trim().length > 0 ? value.trim() : '';
|
|
274
|
-
}
|
|
1
|
+
// empty_reply_continue has been retired intentionally.
|
|
2
|
+
// This file remains as a tombstone so stale imports fail closed.
|
|
3
|
+
export {};
|
|
@@ -7,19 +7,16 @@ export declare function resolveStickyKey(record: {
|
|
|
7
7
|
sessionId?: unknown;
|
|
8
8
|
conversationId?: unknown;
|
|
9
9
|
metadata?: unknown;
|
|
10
|
-
originalRequest?: unknown;
|
|
11
10
|
[key: string]: unknown;
|
|
12
11
|
}, runtimeMetadata?: unknown): string | undefined;
|
|
13
12
|
export declare function persistStopMessageState(stickyKey: string | undefined, state: RoutingInstructionState): void;
|
|
14
13
|
export declare function resolveStopMessageSessionScope(record: {
|
|
15
14
|
sessionId?: unknown;
|
|
16
15
|
metadata?: unknown;
|
|
17
|
-
originalRequest?: unknown;
|
|
18
16
|
[key: string]: unknown;
|
|
19
17
|
}, runtimeMetadata?: unknown): string | undefined;
|
|
20
18
|
export declare function resolveBdWorkingDirectoryForRecord(record: {
|
|
21
19
|
metadata?: unknown;
|
|
22
|
-
originalRequest?: unknown;
|
|
23
20
|
[key: string]: unknown;
|
|
24
21
|
}, runtimeMetadata: unknown): string | undefined;
|
|
25
22
|
export declare function readServerToolFollowupFlowId(runtimeMetadata: unknown): string;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { readRuntimeMetadata } from '../../../conversion/runtime-metadata.js';
|
|
2
1
|
import { saveRoutingInstructionStateSync } from '../../../router/virtual-router/sticky-session-store.js';
|
|
3
2
|
import { isStopEligibleForServerTool } from '../../stop-gateway-context.js';
|
|
4
3
|
import { extractResponsesOutputText, hasToolLikeOutput } from './iflow-followup.js';
|
|
@@ -101,17 +100,6 @@ export function getCapturedRequest(adapterContext) {
|
|
|
101
100
|
if (direct && typeof direct === 'object' && !Array.isArray(direct)) {
|
|
102
101
|
return direct;
|
|
103
102
|
}
|
|
104
|
-
const runtime = readRuntimeMetadata(contextRecord);
|
|
105
|
-
const runtimeCaptured = runtime && typeof runtime === 'object' && !Array.isArray(runtime)
|
|
106
|
-
? runtime.capturedChatRequest
|
|
107
|
-
: undefined;
|
|
108
|
-
if (runtimeCaptured && typeof runtimeCaptured === 'object' && !Array.isArray(runtimeCaptured)) {
|
|
109
|
-
return runtimeCaptured;
|
|
110
|
-
}
|
|
111
|
-
const originalRequest = contextRecord.originalRequest;
|
|
112
|
-
if (originalRequest && typeof originalRequest === 'object' && !Array.isArray(originalRequest)) {
|
|
113
|
-
return originalRequest;
|
|
114
|
-
}
|
|
115
103
|
return null;
|
|
116
104
|
}
|
|
117
105
|
export function resolveClientConnectionState(value) {
|
|
@@ -276,23 +264,6 @@ function readSessionScopeValue(record, runtimeMetadata, key) {
|
|
|
276
264
|
if (fromMetadataCapture) {
|
|
277
265
|
return fromMetadataCapture;
|
|
278
266
|
}
|
|
279
|
-
const originalRequest = asRecord(record.originalRequest);
|
|
280
|
-
const fromOriginalMetadata = originalRequest
|
|
281
|
-
? toNonEmptyText(asRecord(originalRequest.metadata)?.[key])
|
|
282
|
-
: '';
|
|
283
|
-
if (fromOriginalMetadata) {
|
|
284
|
-
return fromOriginalMetadata;
|
|
285
|
-
}
|
|
286
|
-
const fromOriginalCapture = originalRequest ? readHubCaptureContextValue(originalRequest, key) : '';
|
|
287
|
-
if (fromOriginalCapture) {
|
|
288
|
-
return fromOriginalCapture;
|
|
289
|
-
}
|
|
290
|
-
const fromOriginalMetadataCapture = originalRequest
|
|
291
|
-
? readHubCaptureContextValue(asRecord(originalRequest.metadata), key)
|
|
292
|
-
: '';
|
|
293
|
-
if (fromOriginalMetadataCapture) {
|
|
294
|
-
return fromOriginalMetadataCapture;
|
|
295
|
-
}
|
|
296
267
|
const runtime = asRecord(runtimeMetadata);
|
|
297
268
|
const fromRuntime = runtime ? toNonEmptyText(runtime[key]) : '';
|
|
298
269
|
if (fromRuntime) {
|
|
@@ -214,16 +214,8 @@ const handler = async (ctx) => {
|
|
|
214
214
|
const stickyKey = strictSessionScope || resolveStickyKey(record, rt);
|
|
215
215
|
let stickyState = stickyKey ? loadRoutingInstructionStateSync(stickyKey) : null;
|
|
216
216
|
let snapshot = resolveStopMessageSnapshot(stickyState);
|
|
217
|
-
if (!snapshot) {
|
|
218
|
-
snapshot = resolveStopMessageSnapshot(rt?.stopMessageState);
|
|
219
|
-
if (snapshot && stickyKey) {
|
|
220
|
-
stickyState = createStopMessageState(snapshot);
|
|
221
|
-
persistStopMessageState(stickyKey, stickyState);
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
217
|
const stickyMode = readStopMessageStageMode(stickyState);
|
|
225
|
-
const
|
|
226
|
-
const explicitMode = stickyMode ?? runtimeMode;
|
|
218
|
+
const explicitMode = stickyMode;
|
|
227
219
|
if (!snapshot && explicitMode === 'off') {
|
|
228
220
|
debugLog('skip_explicit_mode_off', { stickyKey });
|
|
229
221
|
return markSkip('skip_explicit_mode_off', {
|
|
@@ -379,7 +371,7 @@ const handler = async (ctx) => {
|
|
|
379
371
|
debugLog('skip_failed_build_followup');
|
|
380
372
|
return markSkip('skip_failed_build_followup');
|
|
381
373
|
}
|
|
382
|
-
const historyStateCandidate = stickyState
|
|
374
|
+
const historyStateCandidate = stickyState;
|
|
383
375
|
const existingSeedPrompt = readStopMessageAiSeedPrompt(historyStateCandidate);
|
|
384
376
|
const existingHistory = readStopMessageAiHistory(historyStateCandidate);
|
|
385
377
|
const fallbackCandidateFollowupText = existingSeedPrompt || text || '继续执行';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { registerServerToolHandler } from '../registry.js';
|
|
2
2
|
import { cloneJson, extractTextFromChatLike } from '../server-side-tools.js';
|
|
3
3
|
import { extractCapturedChatSeed } from './followup-request-builder.js';
|
|
4
|
+
import { containsImageAttachment } from '../../conversion/hub/process/chat-process-media.js';
|
|
4
5
|
import { reenterServerToolBackend } from '../reenter-backend.js';
|
|
5
6
|
import { readRuntimeMetadata } from '../../conversion/runtime-metadata.js';
|
|
6
7
|
const FLOW_ID = 'vision_flow';
|
|
@@ -91,7 +92,9 @@ function shouldRunVisionFlow(ctx) {
|
|
|
91
92
|
if (followupFlag) {
|
|
92
93
|
return false;
|
|
93
94
|
}
|
|
94
|
-
const
|
|
95
|
+
const captured = getCapturedRequest(ctx.adapterContext);
|
|
96
|
+
const seed = captured ? extractCapturedChatSeed(captured) : null;
|
|
97
|
+
const hasImageAttachment = Boolean(seed && Array.isArray(seed.messages) && containsImageAttachment(seed.messages));
|
|
95
98
|
if (!hasImageAttachment) {
|
|
96
99
|
return false;
|
|
97
100
|
}
|
|
@@ -13,6 +13,7 @@ import './handlers/continue-execution.js';
|
|
|
13
13
|
import './handlers/review.js';
|
|
14
14
|
import { runPreCommandHooks } from './pre-command-hooks.js';
|
|
15
15
|
import { readRuntimeMetadata } from '../conversion/runtime-metadata.js';
|
|
16
|
+
import { loadRoutingInstructionStateSync } from '../router/virtual-router/sticky-session-store.js';
|
|
16
17
|
function traceAutoHook(options, event) {
|
|
17
18
|
try {
|
|
18
19
|
options.onAutoHookTrace?.(event);
|
|
@@ -115,6 +116,59 @@ function normalizeServerToolCallName(name) {
|
|
|
115
116
|
}
|
|
116
117
|
return normalized;
|
|
117
118
|
}
|
|
119
|
+
function readText(value) {
|
|
120
|
+
return typeof value === 'string' && value.trim() ? value.trim() : '';
|
|
121
|
+
}
|
|
122
|
+
function resolveStickyKeyFromAdapterContext(record) {
|
|
123
|
+
const runtime = readRuntimeMetadata(record);
|
|
124
|
+
const metadata = record.metadata && typeof record.metadata === 'object' && !Array.isArray(record.metadata)
|
|
125
|
+
? record.metadata
|
|
126
|
+
: undefined;
|
|
127
|
+
const explicitScope = readText(runtime?.stopMessageClientInjectSessionScope) ||
|
|
128
|
+
readText(runtime?.stopMessageClientInjectScope) ||
|
|
129
|
+
readText(record.stopMessageClientInjectSessionScope) ||
|
|
130
|
+
readText(record.stopMessageClientInjectScope);
|
|
131
|
+
if (explicitScope &&
|
|
132
|
+
(explicitScope.startsWith('tmux:') ||
|
|
133
|
+
explicitScope.startsWith('session:') ||
|
|
134
|
+
explicitScope.startsWith('conversation:'))) {
|
|
135
|
+
return explicitScope;
|
|
136
|
+
}
|
|
137
|
+
const tmuxSessionId = readText(record.clientTmuxSessionId) ||
|
|
138
|
+
readText(record.client_tmux_session_id) ||
|
|
139
|
+
readText(record.tmuxSessionId) ||
|
|
140
|
+
readText(record.tmux_session_id) ||
|
|
141
|
+
readText(runtime?.clientTmuxSessionId) ||
|
|
142
|
+
readText(runtime?.client_tmux_session_id) ||
|
|
143
|
+
readText(runtime?.tmuxSessionId) ||
|
|
144
|
+
readText(runtime?.tmux_session_id) ||
|
|
145
|
+
readText(metadata?.clientTmuxSessionId) ||
|
|
146
|
+
readText(metadata?.client_tmux_session_id) ||
|
|
147
|
+
readText(metadata?.tmuxSessionId) ||
|
|
148
|
+
readText(metadata?.tmux_session_id);
|
|
149
|
+
if (tmuxSessionId) {
|
|
150
|
+
return `tmux:${tmuxSessionId}`;
|
|
151
|
+
}
|
|
152
|
+
const sessionId = readText(record.sessionId) ||
|
|
153
|
+
readText(record.session_id) ||
|
|
154
|
+
readText(runtime?.sessionId) ||
|
|
155
|
+
readText(runtime?.session_id) ||
|
|
156
|
+
readText(metadata?.sessionId) ||
|
|
157
|
+
readText(metadata?.session_id);
|
|
158
|
+
if (sessionId) {
|
|
159
|
+
return `session:${sessionId}`;
|
|
160
|
+
}
|
|
161
|
+
const conversationId = readText(record.conversationId) ||
|
|
162
|
+
readText(record.conversation_id) ||
|
|
163
|
+
readText(runtime?.conversationId) ||
|
|
164
|
+
readText(runtime?.conversation_id) ||
|
|
165
|
+
readText(metadata?.conversationId) ||
|
|
166
|
+
readText(metadata?.conversation_id);
|
|
167
|
+
if (conversationId) {
|
|
168
|
+
return `conversation:${conversationId}`;
|
|
169
|
+
}
|
|
170
|
+
return undefined;
|
|
171
|
+
}
|
|
118
172
|
function extractToolCallsFromMessage(message) {
|
|
119
173
|
const toolCalls = getArray(message.tool_calls);
|
|
120
174
|
const out = [];
|
|
@@ -309,9 +363,18 @@ export async function runServerSideToolEngine(options) {
|
|
|
309
363
|
let lastExecution;
|
|
310
364
|
const attemptedToolCallsByMessage = [];
|
|
311
365
|
const runtimeMetadata = readRuntimeMetadata(options.adapterContext);
|
|
312
|
-
const runtimePreCommandState =
|
|
313
|
-
|
|
314
|
-
|
|
366
|
+
const runtimePreCommandState = (() => {
|
|
367
|
+
const stickyKey = resolveStickyKeyFromAdapterContext(options.adapterContext);
|
|
368
|
+
if (!stickyKey) {
|
|
369
|
+
return undefined;
|
|
370
|
+
}
|
|
371
|
+
try {
|
|
372
|
+
return loadRoutingInstructionStateSync(stickyKey) ?? undefined;
|
|
373
|
+
}
|
|
374
|
+
catch {
|
|
375
|
+
return undefined;
|
|
376
|
+
}
|
|
377
|
+
})();
|
|
315
378
|
const choices = getArray(base.choices);
|
|
316
379
|
for (const choice of choices) {
|
|
317
380
|
const choiceObj = asObject(choice);
|