@jsonstudio/llms 0.6.2125 → 0.6.2172

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 (37) hide show
  1. package/dist/conversion/compat/actions/deepseek-web-response.js +27 -3
  2. package/dist/conversion/compat/actions/strip-orphan-function-calls-tag.js +1 -1
  3. package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage1_tool_governance/index.js +9 -3
  4. package/dist/conversion/hub/process/chat-process.js +15 -18
  5. package/dist/conversion/responses/responses-openai-bridge.js +13 -12
  6. package/dist/conversion/shared/bridge-message-utils.js +92 -39
  7. package/dist/router/virtual-router/classifier.js +29 -5
  8. package/dist/router/virtual-router/engine/routing-pools/index.js +111 -5
  9. package/dist/router/virtual-router/engine-selection/multimodal-capability.d.ts +3 -0
  10. package/dist/router/virtual-router/engine-selection/multimodal-capability.js +26 -0
  11. package/dist/router/virtual-router/engine-selection/route-utils.js +6 -2
  12. package/dist/router/virtual-router/engine-selection/selection-deps.d.ts +1 -0
  13. package/dist/router/virtual-router/engine-selection/tier-selection.js +2 -0
  14. package/dist/router/virtual-router/engine.d.ts +2 -0
  15. package/dist/router/virtual-router/engine.js +57 -14
  16. package/dist/router/virtual-router/features.js +12 -4
  17. package/dist/router/virtual-router/message-utils.d.ts +8 -0
  18. package/dist/router/virtual-router/message-utils.js +170 -45
  19. package/dist/router/virtual-router/token-counter.js +51 -10
  20. package/dist/router/virtual-router/types.d.ts +3 -0
  21. package/dist/servertool/clock/session-scope.d.ts +3 -0
  22. package/dist/servertool/clock/session-scope.js +52 -0
  23. package/dist/servertool/engine.js +68 -8
  24. package/dist/servertool/handlers/clock-auto.js +2 -8
  25. package/dist/servertool/handlers/clock.js +3 -9
  26. package/dist/servertool/handlers/stop-message-auto/blocked-report.d.ts +16 -0
  27. package/dist/servertool/handlers/stop-message-auto/blocked-report.js +349 -0
  28. package/dist/servertool/handlers/stop-message-auto/iflow-followup.d.ts +23 -0
  29. package/dist/servertool/handlers/stop-message-auto/iflow-followup.js +503 -0
  30. package/dist/servertool/handlers/stop-message-auto/routing-state.d.ts +38 -0
  31. package/dist/servertool/handlers/stop-message-auto/routing-state.js +149 -0
  32. package/dist/servertool/handlers/stop-message-auto/runtime-utils.d.ts +67 -0
  33. package/dist/servertool/handlers/stop-message-auto/runtime-utils.js +387 -0
  34. package/dist/servertool/handlers/stop-message-auto.d.ts +1 -7
  35. package/dist/servertool/handlers/stop-message-auto.js +69 -971
  36. package/dist/servertool/handlers/web-search.js +117 -0
  37. package/package.json +1 -1
@@ -0,0 +1,503 @@
1
+ import * as childProcess from 'node:child_process';
2
+ import { readRuntimeMetadata } from '../../../conversion/shared/runtime-metadata.js';
3
+ import { extractTextFromMessageContent } from './blocked-report.js';
4
+ const STOP_MESSAGE_AUTOMESSAGE_IFLOW_TIMEOUT_MS = 300_000;
5
+ const STOP_MESSAGE_AUTOMESSAGE_IFLOW_TIMEOUT_MAX_MS = 300_000;
6
+ const STOP_MESSAGE_AUTOMESSAGE_IFLOW_TIMEOUT_TEST_CAP_MS = 800;
7
+ const STOP_MESSAGE_AUTOMESSAGE_PROMPT_MAX_CHARS = 18_000;
8
+ const STOP_MESSAGE_AUTOMESSAGE_OUTPUT_MAX_CHARS = 1_600;
9
+ const STOP_MESSAGE_AUTOMESSAGE_LOG_SUMMARY_MAX_CHARS = 200;
10
+ const ANSI_ESCAPE_PATTERN = /\x1B\[[0-?]*[ -/]*[@-~]/g;
11
+ export function renderStopMessageAutoFollowupViaIflow(args) {
12
+ if (!isStopMessageAutoMessageIflowEnabled()) {
13
+ return null;
14
+ }
15
+ const command = resolveStopMessageAutoMessageIflowCommand();
16
+ if (!command) {
17
+ return null;
18
+ }
19
+ const prompt = buildStopMessageAutoMessageIflowPrompt(args);
20
+ if (!prompt) {
21
+ return null;
22
+ }
23
+ const timeoutMs = resolveStopMessageAutoMessageIflowTimeoutMs();
24
+ const maxOutputChars = resolveStopMessageAutoMessageIflowOutputMaxChars();
25
+ const requestSummary = summarizeStopMessageAutoMessageLog([
26
+ `base=${args.baseStopMessageText || ''}`,
27
+ `candidate=${args.candidateFollowupText || ''}`,
28
+ `assistant=${args.responseSnapshot.assistantText || ''}`,
29
+ `reasoning=${args.responseSnapshot.reasoningText || ''}`
30
+ ].join(' | '), STOP_MESSAGE_AUTOMESSAGE_LOG_SUMMARY_MAX_CHARS);
31
+ logStopMessageAutoMessageIflow({
32
+ requestId: args.requestId,
33
+ stage: 'request',
34
+ requestSummary
35
+ });
36
+ try {
37
+ const result = childProcess.spawnSync(command, ['-p', prompt], {
38
+ encoding: 'utf8',
39
+ timeout: timeoutMs,
40
+ maxBuffer: 1024 * 1024
41
+ });
42
+ if (result.error || result.status !== 0) {
43
+ const responseSummary = summarizeStopMessageAutoMessageLog(sanitizeStopMessageAutoMessageOutput(result.stderr || result.stdout, STOP_MESSAGE_AUTOMESSAGE_OUTPUT_MAX_CHARS), STOP_MESSAGE_AUTOMESSAGE_LOG_SUMMARY_MAX_CHARS);
44
+ logStopMessageAutoMessageIflow({
45
+ requestId: args.requestId,
46
+ stage: 'response',
47
+ status: result.status ?? -1,
48
+ requestSummary,
49
+ responseSummary,
50
+ error: result.error ? String(result.error) : 'non_zero_exit'
51
+ });
52
+ return null;
53
+ }
54
+ const stdout = sanitizeStopMessageAutoMessageOutput(result.stdout, maxOutputChars);
55
+ if (stdout) {
56
+ logStopMessageAutoMessageIflow({
57
+ requestId: args.requestId,
58
+ stage: 'response',
59
+ status: result.status ?? 0,
60
+ requestSummary,
61
+ responseSummary: summarizeStopMessageAutoMessageLog(stdout, STOP_MESSAGE_AUTOMESSAGE_LOG_SUMMARY_MAX_CHARS)
62
+ });
63
+ return stdout;
64
+ }
65
+ const stderr = sanitizeStopMessageAutoMessageOutput(result.stderr, maxOutputChars);
66
+ logStopMessageAutoMessageIflow({
67
+ requestId: args.requestId,
68
+ stage: 'response',
69
+ status: result.status ?? 0,
70
+ requestSummary,
71
+ responseSummary: summarizeStopMessageAutoMessageLog(stderr, STOP_MESSAGE_AUTOMESSAGE_LOG_SUMMARY_MAX_CHARS)
72
+ });
73
+ return stderr || null;
74
+ }
75
+ catch (error) {
76
+ logStopMessageAutoMessageIflow({
77
+ requestId: args.requestId,
78
+ stage: 'response',
79
+ status: -1,
80
+ requestSummary,
81
+ error: error instanceof Error ? error.message : String(error ?? 'unknown_error')
82
+ });
83
+ return null;
84
+ }
85
+ }
86
+ export function extractStopMessageAutoResponseSnapshot(base, adapterContext) {
87
+ const providerProtocol = extractStopMessageProviderProtocol(adapterContext);
88
+ if (!base || typeof base !== 'object' || Array.isArray(base)) {
89
+ return { ...(providerProtocol ? { providerProtocol } : {}) };
90
+ }
91
+ const payload = base;
92
+ const choices = Array.isArray(payload.choices) ? payload.choices : [];
93
+ if (choices.length > 0) {
94
+ const targetChoice = choices.find((choice) => toNonEmptyText(asRecord(choice)?.finish_reason).toLowerCase() === 'stop') || choices[0];
95
+ const choiceRecord = asRecord(targetChoice);
96
+ const message = asRecord(choiceRecord?.message);
97
+ const finishReason = toNonEmptyText(choiceRecord?.finish_reason).toLowerCase() || undefined;
98
+ const assistantText = message ? extractStopMessageAssistantText(message) : '';
99
+ const reasoningText = message ? extractStopMessageReasoningText(message) : '';
100
+ return {
101
+ ...(providerProtocol ? { providerProtocol } : {}),
102
+ ...(finishReason ? { finishReason } : {}),
103
+ ...(assistantText ? { assistantText } : {}),
104
+ ...(reasoningText ? { reasoningText } : {}),
105
+ responseExcerpt: buildStopMessageResponseExcerpt(choiceRecord || payload)
106
+ };
107
+ }
108
+ const anthropicContent = extractTextFromMessageContent(payload.content);
109
+ const anthropicReasoning = extractStopMessageReasoningFromContent(payload.content);
110
+ const anthropicFinishReason = toNonEmptyText(payload.stop_reason).toLowerCase() || undefined;
111
+ const responsesText = extractResponsesOutputText(payload);
112
+ const responsesReasoning = extractResponsesReasoningText(payload);
113
+ const responseFinishReason = anthropicFinishReason ||
114
+ toNonEmptyText(payload.finish_reason).toLowerCase() ||
115
+ toNonEmptyText(payload.status).toLowerCase() ||
116
+ undefined;
117
+ const assistantText = responsesText || anthropicContent;
118
+ const reasoningText = responsesReasoning || anthropicReasoning;
119
+ return {
120
+ ...(providerProtocol ? { providerProtocol } : {}),
121
+ ...(responseFinishReason ? { finishReason: responseFinishReason } : {}),
122
+ ...(assistantText ? { assistantText } : {}),
123
+ ...(reasoningText ? { reasoningText } : {}),
124
+ responseExcerpt: buildStopMessageResponseExcerpt(payload)
125
+ };
126
+ }
127
+ export function extractResponsesOutputText(base) {
128
+ const raw = base.output_text;
129
+ if (typeof raw === 'string') {
130
+ return raw.trim();
131
+ }
132
+ if (Array.isArray(raw)) {
133
+ const texts = raw
134
+ .map((entry) => (typeof entry === 'string' ? entry : ''))
135
+ .filter((entry) => entry.trim().length > 0);
136
+ if (texts.length > 0) {
137
+ return texts.join('\n').trim();
138
+ }
139
+ }
140
+ const output = Array.isArray(base.output) ? (base.output) : [];
141
+ const chunks = [];
142
+ for (const item of output) {
143
+ if (!item || typeof item !== 'object' || Array.isArray(item))
144
+ continue;
145
+ if (typeof item.type !== 'string')
146
+ continue;
147
+ const type = String(item.type).trim().toLowerCase();
148
+ if (type !== 'message')
149
+ continue;
150
+ const content = Array.isArray(item.content) ? (item.content) : [];
151
+ for (const part of content) {
152
+ if (!part || typeof part !== 'object' || Array.isArray(part))
153
+ continue;
154
+ const pType = typeof part.type === 'string'
155
+ ? String(part.type).trim().toLowerCase()
156
+ : '';
157
+ if (pType === 'output_text') {
158
+ const text = typeof part.text === 'string' ? String(part.text) : '';
159
+ if (text.trim().length)
160
+ chunks.push(text.trim());
161
+ }
162
+ }
163
+ }
164
+ return chunks.join('\n').trim();
165
+ }
166
+ export function hasToolLikeOutput(value) {
167
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
168
+ return false;
169
+ }
170
+ const typeRaw = value.type;
171
+ const type = typeof typeRaw === 'string' ? typeRaw.trim().toLowerCase() : '';
172
+ if (!type) {
173
+ return false;
174
+ }
175
+ return (type === 'tool_call' ||
176
+ type === 'tool_use' ||
177
+ type === 'function_call' ||
178
+ type.includes('tool'));
179
+ }
180
+ function isStopMessageAutoMessageIflowEnabled() {
181
+ const raw = String(process.env.ROUTECODEX_STOPMESSAGE_AUTOMESSAGE_IFLOW || '')
182
+ .trim()
183
+ .toLowerCase();
184
+ if (!raw) {
185
+ return true;
186
+ }
187
+ if (raw === '0' || raw === 'false' || raw === 'no' || raw === 'off') {
188
+ return false;
189
+ }
190
+ return true;
191
+ }
192
+ function resolveStopMessageAutoMessageIflowCommand() {
193
+ const raw = String(process.env.ROUTECODEX_STOPMESSAGE_AUTOMESSAGE_IFLOW_BIN || '').trim();
194
+ return raw || 'iflow';
195
+ }
196
+ function resolveStopMessageAutoMessageIflowTimeoutMs() {
197
+ const raw = String(process.env.ROUTECODEX_STOPMESSAGE_AUTOMESSAGE_TIMEOUT_MS || '').trim();
198
+ const parsed = Number(raw);
199
+ const explicitTimeout = Number.isFinite(parsed) && parsed > 0
200
+ ? Math.max(200, Math.min(STOP_MESSAGE_AUTOMESSAGE_IFLOW_TIMEOUT_MAX_MS, Math.floor(parsed)))
201
+ : STOP_MESSAGE_AUTOMESSAGE_IFLOW_TIMEOUT_MS;
202
+ let resolvedTimeout = explicitTimeout;
203
+ const explicitCommand = String(process.env.ROUTECODEX_STOPMESSAGE_AUTOMESSAGE_IFLOW_BIN || '').trim();
204
+ const usingDefaultIflowCommand = !explicitCommand || explicitCommand === 'iflow';
205
+ if (process.env.JEST_WORKER_ID && usingDefaultIflowCommand) {
206
+ resolvedTimeout = Math.min(resolvedTimeout, STOP_MESSAGE_AUTOMESSAGE_IFLOW_TIMEOUT_TEST_CAP_MS);
207
+ }
208
+ const followupTimeoutRaw = String(process.env.ROUTECODEX_SERVERTOOL_FOLLOWUP_TIMEOUT_MS || '').trim();
209
+ const followupTimeoutParsed = Number(followupTimeoutRaw);
210
+ if (Number.isFinite(followupTimeoutParsed) && followupTimeoutParsed > 0) {
211
+ const boundedFollowupTimeout = Math.max(100, Math.min(STOP_MESSAGE_AUTOMESSAGE_IFLOW_TIMEOUT_MAX_MS, Math.floor(followupTimeoutParsed)));
212
+ resolvedTimeout = Math.min(resolvedTimeout, boundedFollowupTimeout);
213
+ }
214
+ return resolvedTimeout;
215
+ }
216
+ function resolveStopMessageAutoMessageIflowOutputMaxChars() {
217
+ const raw = String(process.env.ROUTECODEX_STOPMESSAGE_AUTOMESSAGE_OUTPUT_MAX_CHARS || '').trim();
218
+ const parsed = Number(raw);
219
+ if (!Number.isFinite(parsed) || parsed <= 0) {
220
+ return STOP_MESSAGE_AUTOMESSAGE_OUTPUT_MAX_CHARS;
221
+ }
222
+ return Math.max(128, Math.min(8_000, Math.floor(parsed)));
223
+ }
224
+ function sanitizeStopMessageAutoMessageOutput(raw, maxChars) {
225
+ const text = typeof raw === 'string' ? raw : '';
226
+ if (!text.trim()) {
227
+ return '';
228
+ }
229
+ const withoutAnsi = text.replace(ANSI_ESCAPE_PATTERN, '');
230
+ const withoutCodeFence = withoutAnsi
231
+ .replace(/^```[a-zA-Z0-9_-]*\s*/g, '')
232
+ .replace(/\s*```$/g, '');
233
+ const cleaned = withoutCodeFence.trim();
234
+ if (!cleaned) {
235
+ return '';
236
+ }
237
+ return cleaned.length > maxChars ? cleaned.slice(0, maxChars).trim() : cleaned;
238
+ }
239
+ function truncateStopMessageAutoMessagePrompt(value, maxChars) {
240
+ const text = typeof value === 'string' ? value.trim() : '';
241
+ if (!text) {
242
+ return '';
243
+ }
244
+ if (text.length <= maxChars) {
245
+ return text;
246
+ }
247
+ return `${text.slice(0, maxChars)}...`;
248
+ }
249
+ function buildStopMessageAutoMessageIflowPrompt(args) {
250
+ const lines = [
251
+ '你是 RouteCodex 的 stopMessage followup 文案生成器。',
252
+ '目标:基于本轮 finish_reason=stop 的模型原始响应,生成“下一步用户 followup 消息文本”。',
253
+ '输出要求(严格):',
254
+ '1) 只输出一段可直接注入的用户消息文本;不要解释、不要 JSON、不要代码块。',
255
+ '2) 必须基于 assistantText / reasoningText / responseExcerpt 的实际内容给出具体下一步动作。',
256
+ '3) 必须包含至少一个可执行动作(如具体文件、命令、检查点、验证目标)。',
257
+ '4) 禁止输出空泛短答:如“继续”“继续执行”“好的”“收到”“ok”。',
258
+ '5) 语气直接,不要复述流程,不要问选择题。',
259
+ '',
260
+ `baseStopMessage: ${args.baseStopMessageText || 'n/a'}`,
261
+ `candidateFollowup: ${args.candidateFollowupText || 'n/a'}`,
262
+ `repeat: ${Math.max(0, args.usedRepeats) + 1}/${Math.max(0, args.maxRepeats)}`,
263
+ `requestId: ${args.requestId || 'n/a'}`,
264
+ `sessionId: ${args.sessionId || 'n/a'}`,
265
+ `providerKey: ${args.providerKey || 'n/a'}`,
266
+ `model: ${args.model || 'n/a'}`,
267
+ '',
268
+ '本轮模型响应(结构化摘录):',
269
+ `responseProtocol: ${args.responseSnapshot.providerProtocol || 'n/a'}`,
270
+ `finishReason: ${args.responseSnapshot.finishReason || 'n/a'}`,
271
+ '',
272
+ 'assistantText:',
273
+ truncateStopMessageAutoMessagePrompt(args.responseSnapshot.assistantText || 'n/a', 3_600),
274
+ '',
275
+ 'reasoningText:',
276
+ truncateStopMessageAutoMessagePrompt(args.responseSnapshot.reasoningText || 'n/a', 3_600),
277
+ '',
278
+ 'responseExcerpt:',
279
+ truncateStopMessageAutoMessagePrompt(args.responseSnapshot.responseExcerpt || 'n/a', 6_000)
280
+ ];
281
+ lines.push('', '现在直接输出下一步 followup 消息文本。');
282
+ const prompt = lines.join('\n').trim();
283
+ return truncateStopMessageAutoMessagePrompt(prompt, STOP_MESSAGE_AUTOMESSAGE_PROMPT_MAX_CHARS);
284
+ }
285
+ function summarizeStopMessageAutoMessageLog(value, maxChars) {
286
+ const text = typeof value === 'string' ? value : '';
287
+ if (!text.trim()) {
288
+ return '';
289
+ }
290
+ const singleLine = text
291
+ .replace(ANSI_ESCAPE_PATTERN, '')
292
+ .replace(/\s+/g, ' ')
293
+ .trim();
294
+ if (singleLine.length <= maxChars) {
295
+ return singleLine;
296
+ }
297
+ return `${singleLine.slice(0, maxChars)}...`;
298
+ }
299
+ function isStopMessageAutoMessageIflowTraceEnabled() {
300
+ const raw = String(process.env.ROUTECODEX_STOPMESSAGE_IFLOW_TRACE || '').trim().toLowerCase();
301
+ if (!raw) {
302
+ return true;
303
+ }
304
+ if (raw === '0' || raw === 'false' || raw === 'no' || raw === 'off') {
305
+ return false;
306
+ }
307
+ return true;
308
+ }
309
+ function logStopMessageAutoMessageIflow(args) {
310
+ if (!isStopMessageAutoMessageIflowTraceEnabled()) {
311
+ return;
312
+ }
313
+ const requestIdPart = args.requestId ? ` requestId=${args.requestId}` : '';
314
+ try {
315
+ if (args.stage === 'request') {
316
+ const request = args.requestSummary || '';
317
+ // cyan: outbound request summary
318
+ // eslint-disable-next-line no-console
319
+ console.log(`\x1b[38;5;45m[servertool][iflow-automessage] SEND${requestIdPart} ${request}\x1b[0m`);
320
+ return;
321
+ }
322
+ const response = args.responseSummary || '';
323
+ if (args.error || (typeof args.status === 'number' && args.status !== 0)) {
324
+ const statusPart = typeof args.status === 'number' ? ` status=${args.status}` : '';
325
+ const errorPart = args.error ? ` error=${args.error}` : '';
326
+ // red: failed response summary
327
+ // eslint-disable-next-line no-console
328
+ console.log(`\x1b[38;5;196m[servertool][iflow-automessage] RECV${requestIdPart}${statusPart}${errorPart} ${response}\x1b[0m`);
329
+ return;
330
+ }
331
+ // green: successful response summary
332
+ // eslint-disable-next-line no-console
333
+ console.log(`\x1b[38;5;46m[servertool][iflow-automessage] RECV${requestIdPart} ${response}\x1b[0m`);
334
+ }
335
+ catch {
336
+ // ignore logging failures
337
+ }
338
+ }
339
+ function extractStopMessageProviderProtocol(adapterContext) {
340
+ const direct = toNonEmptyText(asRecord(adapterContext)?.providerProtocol);
341
+ if (direct) {
342
+ return direct;
343
+ }
344
+ const runtime = readRuntimeMetadata(asRecord(adapterContext) || {});
345
+ const fromRuntime = toNonEmptyText(asRecord(runtime)?.providerProtocol);
346
+ return fromRuntime || undefined;
347
+ }
348
+ function extractStopMessageAssistantText(message) {
349
+ const contentText = extractTextFromMessageContent(message.content);
350
+ if (contentText) {
351
+ return contentText;
352
+ }
353
+ return extractUnknownText(message.content);
354
+ }
355
+ function extractStopMessageReasoningText(message) {
356
+ const explicitKeys = [
357
+ 'reasoning_content',
358
+ 'reasoning',
359
+ 'reasoning_text',
360
+ 'thinking',
361
+ 'thought',
362
+ 'analysis'
363
+ ];
364
+ const chunks = [];
365
+ for (const key of explicitKeys) {
366
+ const text = extractUnknownText(message[key]);
367
+ if (text) {
368
+ chunks.push(text);
369
+ }
370
+ }
371
+ const contentReasoning = extractStopMessageReasoningFromContent(message.content);
372
+ if (contentReasoning) {
373
+ chunks.push(contentReasoning);
374
+ }
375
+ return dedupeAndJoinTexts(chunks);
376
+ }
377
+ function extractResponsesReasoningText(payload) {
378
+ const chunks = [];
379
+ const directReasoning = extractUnknownText(payload.reasoning);
380
+ if (directReasoning) {
381
+ chunks.push(directReasoning);
382
+ }
383
+ const output = Array.isArray(payload.output) ? payload.output : [];
384
+ for (const item of output) {
385
+ const record = asRecord(item);
386
+ if (!record) {
387
+ continue;
388
+ }
389
+ const type = toNonEmptyText(record.type).toLowerCase();
390
+ if (type.includes('reason') || type.includes('think') || type.includes('analysis')) {
391
+ const text = extractUnknownText(record.summary) || extractUnknownText(record.content) || extractUnknownText(record.text);
392
+ if (text) {
393
+ chunks.push(text);
394
+ }
395
+ continue;
396
+ }
397
+ if (type === 'message') {
398
+ const contentReasoning = extractStopMessageReasoningFromContent(record.content);
399
+ if (contentReasoning) {
400
+ chunks.push(contentReasoning);
401
+ }
402
+ }
403
+ }
404
+ return dedupeAndJoinTexts(chunks);
405
+ }
406
+ function extractStopMessageReasoningFromContent(content) {
407
+ if (!Array.isArray(content)) {
408
+ return '';
409
+ }
410
+ const chunks = [];
411
+ for (const item of content) {
412
+ const record = asRecord(item);
413
+ if (!record) {
414
+ continue;
415
+ }
416
+ const type = toNonEmptyText(record.type).toLowerCase();
417
+ if (!type.includes('reason') && !type.includes('think') && !type.includes('analysis')) {
418
+ continue;
419
+ }
420
+ const text = extractUnknownText(record.text) ||
421
+ extractUnknownText(record.summary) ||
422
+ extractUnknownText(record.content) ||
423
+ extractUnknownText(record.value);
424
+ if (text) {
425
+ chunks.push(text);
426
+ }
427
+ }
428
+ return dedupeAndJoinTexts(chunks);
429
+ }
430
+ function extractUnknownText(value, depth = 0) {
431
+ if (depth > 4 || value === null || value === undefined) {
432
+ return '';
433
+ }
434
+ if (typeof value === 'string') {
435
+ return value.trim();
436
+ }
437
+ if (typeof value === 'number' || typeof value === 'boolean') {
438
+ return String(value);
439
+ }
440
+ if (Array.isArray(value)) {
441
+ const parts = value
442
+ .map((entry) => extractUnknownText(entry, depth + 1))
443
+ .filter((entry) => entry.length > 0);
444
+ return dedupeAndJoinTexts(parts);
445
+ }
446
+ if (typeof value !== 'object') {
447
+ return '';
448
+ }
449
+ const record = value;
450
+ const priorityKeys = ['text', 'content', 'value', 'summary', 'reasoning', 'thinking', 'analysis'];
451
+ const parts = [];
452
+ for (const key of priorityKeys) {
453
+ if (!(key in record)) {
454
+ continue;
455
+ }
456
+ const text = extractUnknownText(record[key], depth + 1);
457
+ if (text) {
458
+ parts.push(text);
459
+ }
460
+ }
461
+ if (parts.length === 0) {
462
+ for (const raw of Object.values(record)) {
463
+ if (typeof raw !== 'string') {
464
+ continue;
465
+ }
466
+ const text = raw.trim();
467
+ if (text) {
468
+ parts.push(text);
469
+ }
470
+ }
471
+ }
472
+ return dedupeAndJoinTexts(parts);
473
+ }
474
+ function dedupeAndJoinTexts(parts) {
475
+ const unique = Array.from(new Set(parts
476
+ .map((entry) => (typeof entry === 'string' ? entry.trim() : ''))
477
+ .filter((entry) => entry.length > 0)));
478
+ return unique.join('\n').trim();
479
+ }
480
+ function buildStopMessageResponseExcerpt(value) {
481
+ try {
482
+ const raw = JSON.stringify(value);
483
+ if (!raw) {
484
+ return '';
485
+ }
486
+ if (raw.length <= 3_000) {
487
+ return raw;
488
+ }
489
+ return `${raw.slice(0, 3_000)}...`;
490
+ }
491
+ catch {
492
+ return '';
493
+ }
494
+ }
495
+ function asRecord(value) {
496
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
497
+ return null;
498
+ }
499
+ return value;
500
+ }
501
+ function toNonEmptyText(value) {
502
+ return typeof value === 'string' && value.trim() ? value.trim() : '';
503
+ }
@@ -0,0 +1,38 @@
1
+ import type { RoutingInstructionState } from '../../../router/virtual-router/routing-instructions.js';
2
+ export declare function hasArmedStopMessageState(state: RoutingInstructionState): boolean;
3
+ export declare function normalizeStopMessageModeValue(value: unknown): 'on' | 'off' | 'auto' | undefined;
4
+ export declare function normalizeStopMessageStageMode(value: unknown): 'on' | 'off' | 'auto' | undefined;
5
+ export declare function resolveStopMessageMaxRepeats(value: unknown, stageMode: 'on' | 'off' | 'auto' | undefined): number;
6
+ export declare function resolveStopMessageSnapshot(raw: unknown): {
7
+ text: string;
8
+ maxRepeats: number;
9
+ used: number;
10
+ source?: string;
11
+ updatedAt?: number;
12
+ lastUsedAt?: number;
13
+ stage?: string;
14
+ stageMode?: 'on' | 'off' | 'auto';
15
+ observationHash?: string;
16
+ observationStableCount?: number;
17
+ bdWorkState?: string;
18
+ assignedIssueId?: string;
19
+ assignedIssueSource?: 'in_progress' | 'ready' | 'open';
20
+ noTaskSummaryUsed?: boolean;
21
+ } | null;
22
+ export declare function createStopMessageState(snapshot: {
23
+ text: string;
24
+ maxRepeats: number;
25
+ used: number;
26
+ source?: string;
27
+ updatedAt?: number;
28
+ lastUsedAt?: number;
29
+ stage?: string;
30
+ stageMode?: 'on' | 'off' | 'auto';
31
+ observationHash?: string;
32
+ observationStableCount?: number;
33
+ bdWorkState?: string;
34
+ assignedIssueId?: string;
35
+ assignedIssueSource?: 'in_progress' | 'ready' | 'open';
36
+ noTaskSummaryUsed?: boolean;
37
+ }): RoutingInstructionState;
38
+ export declare function clearStopMessageState(state: RoutingInstructionState, now: number): void;
@@ -0,0 +1,149 @@
1
+ import { DEFAULT_STOP_MESSAGE_MAX_REPEATS } from '../../../router/virtual-router/routing-stop-message-state-codec.js';
2
+ export function hasArmedStopMessageState(state) {
3
+ const text = typeof state.stopMessageText === 'string' ? state.stopMessageText.trim() : '';
4
+ const stageMode = normalizeStopMessageModeValue(state.stopMessageStageMode);
5
+ const maxRepeats = resolveStopMessageMaxRepeats(state.stopMessageMaxRepeats, stageMode);
6
+ const allowModeOnlyState = !text &&
7
+ maxRepeats > 0 &&
8
+ (stageMode === 'on' || stageMode === 'auto');
9
+ return (text.length > 0 || allowModeOnlyState) && maxRepeats > 0;
10
+ }
11
+ export function normalizeStopMessageModeValue(value) {
12
+ if (typeof value !== 'string') {
13
+ return undefined;
14
+ }
15
+ const normalized = value.trim().toLowerCase();
16
+ if (normalized === 'on' || normalized === 'off' || normalized === 'auto') {
17
+ return normalized;
18
+ }
19
+ return undefined;
20
+ }
21
+ export function normalizeStopMessageStageMode(value) {
22
+ if (typeof value !== 'string') {
23
+ return undefined;
24
+ }
25
+ const normalized = value.trim().toLowerCase();
26
+ if (normalized === 'on' || normalized === 'off' || normalized === 'auto') {
27
+ return normalized;
28
+ }
29
+ return undefined;
30
+ }
31
+ export function resolveStopMessageMaxRepeats(value, stageMode) {
32
+ const parsed = typeof value === 'number' && Number.isFinite(value)
33
+ ? Math.floor(value)
34
+ : 0;
35
+ if (parsed > 0) {
36
+ return parsed;
37
+ }
38
+ if (stageMode === 'on' || stageMode === 'auto') {
39
+ return DEFAULT_STOP_MESSAGE_MAX_REPEATS;
40
+ }
41
+ return 0;
42
+ }
43
+ export function resolveStopMessageSnapshot(raw) {
44
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
45
+ return null;
46
+ }
47
+ const record = raw;
48
+ const text = typeof record.stopMessageText === 'string' ? record.stopMessageText.trim() : '';
49
+ const stageMode = normalizeStopMessageStageMode(record.stopMessageStageMode);
50
+ const maxRepeats = resolveStopMessageMaxRepeats(record.stopMessageMaxRepeats, stageMode);
51
+ const allowModeOnlyState = !text &&
52
+ maxRepeats > 0 &&
53
+ (stageMode === 'on' || stageMode === 'auto');
54
+ if ((!text && !allowModeOnlyState) || maxRepeats <= 0) {
55
+ return null;
56
+ }
57
+ const used = typeof record.stopMessageUsed === 'number' && Number.isFinite(record.stopMessageUsed)
58
+ ? Math.max(0, Math.floor(record.stopMessageUsed))
59
+ : 0;
60
+ const updatedAt = typeof record.stopMessageUpdatedAt === 'number' && Number.isFinite(record.stopMessageUpdatedAt)
61
+ ? record.stopMessageUpdatedAt
62
+ : undefined;
63
+ const lastUsedAt = typeof record.stopMessageLastUsedAt === 'number' && Number.isFinite(record.stopMessageLastUsedAt)
64
+ ? record.stopMessageLastUsedAt
65
+ : undefined;
66
+ const source = typeof record.stopMessageSource === 'string' && record.stopMessageSource.trim()
67
+ ? record.stopMessageSource.trim()
68
+ : undefined;
69
+ const stage = typeof record.stopMessageStage === 'string' && record.stopMessageStage.trim()
70
+ ? record.stopMessageStage.trim()
71
+ : undefined;
72
+ const observationHash = typeof record.stopMessageObservationHash === 'string' && record.stopMessageObservationHash.trim()
73
+ ? record.stopMessageObservationHash.trim()
74
+ : undefined;
75
+ const observationStableCount = typeof record.stopMessageObservationStableCount === 'number' && Number.isFinite(record.stopMessageObservationStableCount)
76
+ ? Math.max(0, Math.floor(record.stopMessageObservationStableCount))
77
+ : undefined;
78
+ const bdWorkState = typeof record.stopMessageBdWorkState === 'string' && record.stopMessageBdWorkState.trim()
79
+ ? record.stopMessageBdWorkState.trim()
80
+ : undefined;
81
+ const assignedIssueId = typeof record.stopMessageAssignedIssueId === 'string' && record.stopMessageAssignedIssueId.trim()
82
+ ? record.stopMessageAssignedIssueId.trim()
83
+ : undefined;
84
+ const sourceRaw = typeof record.stopMessageAssignedIssueSource === 'string'
85
+ ? record.stopMessageAssignedIssueSource.trim().toLowerCase()
86
+ : '';
87
+ const assignedIssueSource = sourceRaw === 'in_progress' || sourceRaw === 'ready' || sourceRaw === 'open'
88
+ ? sourceRaw
89
+ : undefined;
90
+ const noTaskSummaryUsed = typeof record.stopMessageNoTaskSummaryUsed === 'boolean'
91
+ ? record.stopMessageNoTaskSummaryUsed
92
+ : undefined;
93
+ return {
94
+ text,
95
+ maxRepeats,
96
+ used,
97
+ ...(source ? { source } : {}),
98
+ ...(updatedAt ? { updatedAt } : {}),
99
+ ...(lastUsedAt ? { lastUsedAt } : {}),
100
+ ...(stage ? { stage } : {}),
101
+ ...(stageMode ? { stageMode } : {}),
102
+ ...(observationHash ? { observationHash } : {}),
103
+ ...(typeof observationStableCount === 'number' ? { observationStableCount } : {}),
104
+ ...(bdWorkState ? { bdWorkState } : {}),
105
+ ...(assignedIssueId ? { assignedIssueId } : {}),
106
+ ...(assignedIssueSource ? { assignedIssueSource } : {}),
107
+ ...(typeof noTaskSummaryUsed === 'boolean' ? { noTaskSummaryUsed } : {})
108
+ };
109
+ }
110
+ export function createStopMessageState(snapshot) {
111
+ return {
112
+ forcedTarget: undefined,
113
+ stickyTarget: undefined,
114
+ allowedProviders: new Set(),
115
+ disabledProviders: new Set(),
116
+ disabledKeys: new Map(),
117
+ disabledModels: new Map(),
118
+ stopMessageSource: snapshot.source && snapshot.source.trim() ? snapshot.source.trim() : 'explicit',
119
+ stopMessageText: snapshot.text,
120
+ stopMessageMaxRepeats: snapshot.maxRepeats,
121
+ stopMessageUsed: snapshot.used,
122
+ stopMessageUpdatedAt: snapshot.updatedAt,
123
+ stopMessageLastUsedAt: snapshot.lastUsedAt,
124
+ stopMessageStage: snapshot.stage,
125
+ stopMessageStageMode: snapshot.stageMode,
126
+ stopMessageObservationHash: snapshot.observationHash,
127
+ stopMessageObservationStableCount: snapshot.observationStableCount,
128
+ stopMessageBdWorkState: snapshot.bdWorkState,
129
+ stopMessageAssignedIssueId: snapshot.assignedIssueId,
130
+ stopMessageAssignedIssueSource: snapshot.assignedIssueSource,
131
+ stopMessageNoTaskSummaryUsed: snapshot.noTaskSummaryUsed
132
+ };
133
+ }
134
+ export function clearStopMessageState(state, now) {
135
+ state.stopMessageText = undefined;
136
+ state.stopMessageMaxRepeats = undefined;
137
+ state.stopMessageUsed = undefined;
138
+ state.stopMessageSource = undefined;
139
+ state.stopMessageStage = undefined;
140
+ state.stopMessageStageMode = undefined;
141
+ state.stopMessageObservationHash = undefined;
142
+ state.stopMessageObservationStableCount = undefined;
143
+ state.stopMessageBdWorkState = undefined;
144
+ state.stopMessageAssignedIssueId = undefined;
145
+ state.stopMessageAssignedIssueSource = undefined;
146
+ state.stopMessageNoTaskSummaryUsed = undefined;
147
+ state.stopMessageUpdatedAt = now;
148
+ state.stopMessageLastUsedAt = now;
149
+ }