@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.
Files changed (66) hide show
  1. package/dist/conversion/compat/actions/antigravity-thought-signature-cache.js +4 -115
  2. package/dist/conversion/compat/actions/auto-thinking.js +3 -2
  3. package/dist/conversion/compat/actions/deepseek-web-response.js +15 -49
  4. package/dist/conversion/compat/actions/gemini-cli-request.d.ts +2 -0
  5. package/dist/conversion/compat/actions/gemini-cli-request.js +1 -1
  6. package/dist/conversion/compat/actions/glm-history-image-trim.js +3 -37
  7. package/dist/conversion/compat/actions/glm-image-content.js +3 -32
  8. package/dist/conversion/compat/actions/glm-native-compat.d.ts +6 -0
  9. package/dist/conversion/compat/actions/glm-native-compat.js +34 -0
  10. package/dist/conversion/compat/actions/glm-vision-prompt.js +3 -76
  11. package/dist/conversion/compat/actions/glm-web-search.js +10 -43
  12. package/dist/conversion/compat/actions/iflow-kimi-cli-defaults.js +4 -53
  13. package/dist/conversion/compat/actions/iflow-kimi-history-media-placeholder.js +5 -141
  14. package/dist/conversion/compat/actions/iflow-kimi-thinking-reasoning-fill.js +7 -28
  15. package/dist/conversion/compat/actions/iflow-native-compat.d.ts +6 -0
  16. package/dist/conversion/compat/actions/iflow-native-compat.js +36 -0
  17. package/dist/conversion/compat/actions/iflow-response-body-unwrap.js +4 -119
  18. package/dist/conversion/compat/actions/iflow-web-search.js +14 -55
  19. package/dist/conversion/hub/operation-table/semantic-mappers/archive/chat-mapper.archive.js +5 -0
  20. package/dist/conversion/hub/operation-table/semantic-mappers/responses-mapper.js +31 -18
  21. package/dist/conversion/hub/pipeline/hub-pipeline.js +163 -163
  22. package/dist/conversion/hub/pipeline/hub-stage-timing.d.ts +6 -0
  23. package/dist/conversion/hub/pipeline/hub-stage-timing.js +178 -0
  24. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage1_format_parse/index.d.ts +4 -4
  25. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage1_format_parse/index.js +33 -14
  26. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.d.ts +7 -6
  27. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.js +41 -23
  28. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/index.js +44 -1
  29. package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage1_tool_governance/index.js +4 -1
  30. package/dist/conversion/hub/process/chat-process-continue-execution.js +5 -4
  31. package/dist/conversion/hub/process/chat-process-governance-orchestration.js +3 -1
  32. package/dist/conversion/hub/process/chat-process-media.d.ts +3 -1
  33. package/dist/conversion/hub/process/chat-process-media.js +92 -2
  34. package/dist/conversion/hub/process/chat-process-session-usage.d.ts +7 -0
  35. package/dist/conversion/hub/process/chat-process-session-usage.js +147 -0
  36. package/dist/conversion/hub/response/provider-response.js +13 -0
  37. package/dist/conversion/responses/responses-openai-bridge/response-payload.js +0 -12
  38. package/dist/conversion/responses/responses-openai-bridge/types.d.ts +1 -9
  39. package/dist/conversion/responses/responses-openai-bridge.js +77 -44
  40. package/dist/conversion/shared/reasoning-normalizer.js +42 -0
  41. package/dist/conversion/shared/responses-tool-utils.js +2 -3
  42. package/dist/native/router_hotpath_napi.node +0 -0
  43. package/dist/router/virtual-router/bootstrap/profile-builder.js +1 -0
  44. package/dist/router/virtual-router/bootstrap/provider-normalization.d.ts +1 -0
  45. package/dist/router/virtual-router/bootstrap/provider-normalization.js +6 -0
  46. package/dist/router/virtual-router/bootstrap.js +1 -6
  47. package/dist/router/virtual-router/engine-legacy.js +43 -0
  48. package/dist/router/virtual-router/engine-logging.d.ts +3 -0
  49. package/dist/router/virtual-router/engine-logging.js +29 -3
  50. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-edge-stage-semantics.d.ts +2 -2
  51. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-edge-stage-semantics.js +96 -80
  52. package/dist/router/virtual-router/engine-selection/native-hub-pipeline-req-process-semantics.d.ts +1 -0
  53. package/dist/router/virtual-router/engine.js +34 -22
  54. package/dist/router/virtual-router/provider-registry.js +1 -0
  55. package/dist/router/virtual-router/routing-instructions/state.js +35 -3
  56. package/dist/router/virtual-router/routing-instructions/types.d.ts +4 -0
  57. package/dist/router/virtual-router/types.d.ts +7 -0
  58. package/dist/servertool/engine.js +3 -34
  59. package/dist/servertool/handlers/followup-request-builder.js +0 -6
  60. package/dist/servertool/handlers/gemini-empty-reply-continue.js +3 -274
  61. package/dist/servertool/handlers/stop-message-auto/runtime-utils.d.ts +0 -3
  62. package/dist/servertool/handlers/stop-message-auto/runtime-utils.js +0 -29
  63. package/dist/servertool/handlers/stop-message-auto.js +2 -10
  64. package/dist/servertool/handlers/vision.js +4 -1
  65. package/dist/servertool/server-side-tools.js +66 -3
  66. 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 (stop_message_flow / empty_reply_continue)
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 || isEmptyReplyContinue || isApplyPatchGuard || isExecCommandGuard;
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 || isEmptyReplyContinue;
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
- import { registerServerToolHandler } from '../registry.js';
2
- import { isCompactionRequest } from './compaction-detect.js';
3
- import { extractCapturedChatSeed } from './followup-request-builder.js';
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 runtimeMode = readStopMessageStageMode(rt?.stopMessageState);
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 ?? rt?.stopMessageState;
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 hasImageAttachment = record.hasImageAttachment === true || record.hasImageAttachment === 'true';
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 = runtimeMetadata && typeof runtimeMetadata === 'object'
313
- ? runtimeMetadata.preCommandState
314
- : undefined;
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsonstudio/llms",
3
- "version": "0.6.3551",
3
+ "version": "0.6.3686",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",