@jsonstudio/llms 0.6.567 → 0.6.568
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/codecs/gemini-openai-codec.js +33 -4
- package/dist/conversion/codecs/openai-openai-codec.js +2 -1
- package/dist/conversion/codecs/responses-openai-codec.js +3 -2
- package/dist/conversion/compat/actions/glm-history-image-trim.d.ts +2 -0
- package/dist/conversion/compat/actions/glm-history-image-trim.js +88 -0
- package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +6 -1
- package/dist/conversion/hub/pipeline/hub-pipeline.js +25 -13
- package/dist/conversion/hub/process/chat-process.js +65 -11
- package/dist/conversion/hub/semantic-mappers/gemini-mapper.js +16 -3
- package/dist/conversion/hub/semantic-mappers/responses-mapper.js +51 -2
- package/dist/conversion/hub/types/chat-envelope.d.ts +1 -0
- package/dist/conversion/shared/anthropic-message-utils.js +54 -0
- package/dist/conversion/shared/args-mapping.js +11 -3
- package/dist/conversion/shared/responses-output-builder.js +42 -21
- package/dist/conversion/shared/streaming-text-extractor.d.ts +25 -0
- package/dist/conversion/shared/streaming-text-extractor.js +31 -38
- package/dist/conversion/shared/text-markup-normalizer.js +42 -27
- package/dist/conversion/shared/tool-filter-pipeline.js +2 -1
- package/dist/conversion/shared/tool-harvester.js +43 -12
- package/dist/conversion/shared/tool-mapping.d.ts +1 -0
- package/dist/conversion/shared/tool-mapping.js +33 -19
- package/dist/filters/index.d.ts +1 -0
- package/dist/filters/index.js +1 -0
- package/dist/filters/special/request-tools-normalize.js +14 -4
- package/dist/filters/special/response-apply-patch-toon-decode.d.ts +23 -0
- package/dist/filters/special/response-apply-patch-toon-decode.js +109 -0
- package/dist/filters/special/response-tool-arguments-toon-decode.d.ts +10 -0
- package/dist/filters/special/response-tool-arguments-toon-decode.js +55 -13
- package/dist/guidance/index.js +69 -42
- package/dist/router/virtual-router/bootstrap.js +10 -5
- package/dist/router/virtual-router/classifier.js +9 -4
- package/dist/router/virtual-router/engine-health.d.ts +11 -0
- package/dist/router/virtual-router/engine-health.js +217 -4
- package/dist/router/virtual-router/engine-logging.d.ts +2 -1
- package/dist/router/virtual-router/engine-logging.js +35 -3
- package/dist/router/virtual-router/engine.d.ts +17 -1
- package/dist/router/virtual-router/engine.js +154 -6
- package/dist/router/virtual-router/routing-instructions.d.ts +2 -0
- package/dist/router/virtual-router/routing-instructions.js +19 -1
- package/dist/router/virtual-router/tool-signals.js +57 -11
- package/dist/router/virtual-router/types.d.ts +30 -0
- package/dist/router/virtual-router/types.js +1 -1
- package/dist/servertool/engine.js +3 -0
- package/dist/servertool/handlers/iflow-model-error-retry.d.ts +1 -0
- package/dist/servertool/handlers/iflow-model-error-retry.js +93 -0
- package/dist/servertool/handlers/stop-message-auto.js +61 -4
- package/dist/servertool/server-side-tools.d.ts +1 -0
- package/dist/servertool/server-side-tools.js +27 -0
- package/dist/sse/sse-to-json/builders/anthropic-response-builder.js +16 -0
- package/dist/tools/apply-patch-structured.d.ts +20 -0
- package/dist/tools/apply-patch-structured.js +239 -0
- package/dist/tools/tool-description-utils.d.ts +5 -0
- package/dist/tools/tool-description-utils.js +50 -0
- package/dist/tools/tool-registry.js +11 -193
- package/package.json +2 -2
|
@@ -101,7 +101,25 @@ const SHELL_READ_PATTERNS = [
|
|
|
101
101
|
'python - <<',
|
|
102
102
|
'python -c',
|
|
103
103
|
'node - <<',
|
|
104
|
-
'node -e'
|
|
104
|
+
'node -e',
|
|
105
|
+
'sed -n',
|
|
106
|
+
'sed --quiet',
|
|
107
|
+
'sed ',
|
|
108
|
+
'rg ',
|
|
109
|
+
' ripgrep',
|
|
110
|
+
'grep ',
|
|
111
|
+
'egrep ',
|
|
112
|
+
'fgrep ',
|
|
113
|
+
'ag ',
|
|
114
|
+
'ack ',
|
|
115
|
+
'find ',
|
|
116
|
+
'nl ',
|
|
117
|
+
'less',
|
|
118
|
+
'more',
|
|
119
|
+
'awk ',
|
|
120
|
+
'perl -ne',
|
|
121
|
+
'perl -pe',
|
|
122
|
+
'strings '
|
|
105
123
|
];
|
|
106
124
|
export function detectVisionTool(request) {
|
|
107
125
|
if (!Array.isArray(request.tools)) {
|
|
@@ -197,21 +215,49 @@ function classifyToolCall(call) {
|
|
|
197
215
|
}
|
|
198
216
|
const argsObject = parseToolArguments(call?.function?.arguments);
|
|
199
217
|
const commandText = extractCommandText(argsObject);
|
|
200
|
-
const nameCategory = categorizeToolName(functionName);
|
|
201
218
|
const snippet = buildCommandSnippet(commandText);
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
219
|
+
const normalizedName = functionName.toLowerCase();
|
|
220
|
+
const normalizedCmd = commandText.toLowerCase();
|
|
221
|
+
// 1) Web search 优先:函数名或命令文本中命中 web 搜索关键字时,一律归类为 search,优先级最高。
|
|
222
|
+
const isWebSearch = WEB_TOOL_KEYWORDS.some((keyword) => normalizedName.includes(keyword)) ||
|
|
223
|
+
WEB_TOOL_KEYWORDS.some((keyword) => normalizedCmd.includes(keyword));
|
|
224
|
+
// 2) 基于工具名的初步分类(read / write / search / other)
|
|
225
|
+
const nameCategory = categorizeToolName(functionName);
|
|
226
|
+
// 3) shell_command / exec_command 根据内部命令判断读写性质
|
|
227
|
+
let shellCategory = 'other';
|
|
228
|
+
if (SHELL_TOOL_NAMES.has(functionName) || functionName === 'exec_command') {
|
|
229
|
+
shellCategory = classifyShellCommand(commandText);
|
|
230
|
+
}
|
|
231
|
+
// 按优先级合并分类结果:
|
|
232
|
+
// 1. web search
|
|
233
|
+
// 2. 写文件(任一维度命中写)
|
|
234
|
+
// 3. 读文件(任一维度命中读)
|
|
235
|
+
// 4. 其他搜索(非 web search)
|
|
236
|
+
// 5. 其它工具
|
|
237
|
+
// Priority 1: Web search
|
|
238
|
+
if (isWebSearch) {
|
|
239
|
+
return { category: 'search', name: functionName, commandSnippet: snippet };
|
|
240
|
+
}
|
|
241
|
+
// Priority 2: Write (写文件) — 名称或内部命令任一判断为写,都按写处理
|
|
242
|
+
if (nameCategory === 'write' || shellCategory === 'write') {
|
|
243
|
+
return { category: 'write', name: functionName, commandSnippet: snippet };
|
|
244
|
+
}
|
|
245
|
+
// Priority 3: Read (读文件) — 仅在没有写的情况下,再看读
|
|
246
|
+
if (nameCategory === 'read' || shellCategory === 'read') {
|
|
247
|
+
return { category: 'read', name: functionName, commandSnippet: snippet };
|
|
248
|
+
}
|
|
249
|
+
// Priority 4: 其他 search 类工具(非 web search)
|
|
250
|
+
if (nameCategory === 'search') {
|
|
251
|
+
return { category: 'search', name: functionName, commandSnippet: snippet };
|
|
252
|
+
}
|
|
253
|
+
// Priority 5: 兜底用命令文本再判断一次 shell 风格读写(非 shell/exec_command 的工具)
|
|
254
|
+
if (!SHELL_TOOL_NAMES.has(functionName) && functionName !== 'exec_command' && commandText) {
|
|
210
255
|
const derivedCategory = classifyShellCommand(commandText);
|
|
211
|
-
if (derivedCategory
|
|
256
|
+
if (derivedCategory === 'write' || derivedCategory === 'read') {
|
|
212
257
|
return { category: derivedCategory, name: functionName, commandSnippet: snippet };
|
|
213
258
|
}
|
|
214
259
|
}
|
|
260
|
+
// 最终兜底:other
|
|
215
261
|
return { category: 'other', name: functionName, commandSnippet: snippet };
|
|
216
262
|
}
|
|
217
263
|
function extractToolName(tool) {
|
|
@@ -172,6 +172,11 @@ export interface RouterMetadataInput {
|
|
|
172
172
|
* 强制路由模式,从消息中的 <**...**> 指令解析得出
|
|
173
173
|
*/
|
|
174
174
|
routingMode?: RoutingInstructionMode;
|
|
175
|
+
/**
|
|
176
|
+
* 当 disableStickyRoutes=true 时,本次请求仍使用 sticky session 状态,
|
|
177
|
+
* 但不继承 sticky target,允许后续路由重新选择 provider。
|
|
178
|
+
*/
|
|
179
|
+
disableStickyRoutes?: boolean;
|
|
175
180
|
/**
|
|
176
181
|
* 允许的 provider 白名单
|
|
177
182
|
*/
|
|
@@ -343,3 +348,28 @@ export interface ProviderErrorEvent {
|
|
|
343
348
|
export interface FeatureBuilder {
|
|
344
349
|
build(request: StandardizedRequest, metadata: RouterMetadataInput): RoutingFeatures;
|
|
345
350
|
}
|
|
351
|
+
export interface ProviderCooldownState {
|
|
352
|
+
providerKey: string;
|
|
353
|
+
cooldownExpiresAt: number;
|
|
354
|
+
reason?: string;
|
|
355
|
+
}
|
|
356
|
+
export interface VirtualRouterHealthSnapshot {
|
|
357
|
+
providers: ProviderHealthState[];
|
|
358
|
+
cooldowns: ProviderCooldownState[];
|
|
359
|
+
}
|
|
360
|
+
export interface VirtualRouterHealthStore {
|
|
361
|
+
/**
|
|
362
|
+
* 在 VirtualRouterEngine 初始化时提供上一次持久化的健康快照。
|
|
363
|
+
* 调用方应仅返回仍在有效期内的 cooldown/熔断信息,或返回 null 表示无可恢复状态。
|
|
364
|
+
*/
|
|
365
|
+
loadInitialSnapshot(): VirtualRouterHealthSnapshot | null;
|
|
366
|
+
/**
|
|
367
|
+
* 当 VirtualRouterEngine 更新 provider 健康状态或 cooldown 时,可选地持久化最新快照。
|
|
368
|
+
* 实现应保证内部吞掉 I/O 错误,不影响路由主流程。
|
|
369
|
+
*/
|
|
370
|
+
persistSnapshot?(snapshot: VirtualRouterHealthSnapshot): void;
|
|
371
|
+
/**
|
|
372
|
+
* 可选:记录原始 ProviderErrorEvent,便于后续离线统计与诊断。
|
|
373
|
+
*/
|
|
374
|
+
recordProviderError?(event: ProviderErrorEvent): void;
|
|
375
|
+
}
|
|
@@ -94,6 +94,9 @@ function resolveRouteHint(adapterContext, flowId) {
|
|
|
94
94
|
if (!routeId) {
|
|
95
95
|
return undefined;
|
|
96
96
|
}
|
|
97
|
+
if (routeId.toLowerCase() === 'default') {
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
97
100
|
if (flowId && routeId.toLowerCase() === flowId.toLowerCase()) {
|
|
98
101
|
return undefined;
|
|
99
102
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { registerServerToolHandler } from '../registry.js';
|
|
2
|
+
import { cloneJson } from '../server-side-tools.js';
|
|
3
|
+
const FLOW_ID = 'iflow_model_error_retry';
|
|
4
|
+
const handler = async (ctx) => {
|
|
5
|
+
if (!ctx.options.reenterPipeline) {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
const adapterRecord = ctx.adapterContext;
|
|
9
|
+
// 避免在 followup 请求里再次触发,防止循环。
|
|
10
|
+
const followupRaw = adapterRecord.serverToolFollowup;
|
|
11
|
+
if (followupRaw === true || (typeof followupRaw === 'string' && followupRaw.trim().toLowerCase() === 'true')) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
// 仅针对 openai-chat 协议 + iflow.* providerKey 的 /v1/responses 路径启用。
|
|
15
|
+
if (ctx.options.providerProtocol !== 'openai-chat') {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
const entryEndpoint = (ctx.options.entryEndpoint || '').toLowerCase();
|
|
19
|
+
if (!entryEndpoint.includes('/v1/responses')) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
const providerKey = typeof adapterRecord.providerKey === 'string' && adapterRecord.providerKey.trim()
|
|
23
|
+
? adapterRecord.providerKey.trim().toLowerCase()
|
|
24
|
+
: '';
|
|
25
|
+
if (!providerKey.startsWith('iflow.')) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
// 仅在上游返回 error_code(HTTP 200 + 业务错误)时触发一次自动重试。
|
|
29
|
+
const base = ctx.base;
|
|
30
|
+
const errorCode = base.error_code;
|
|
31
|
+
const msg = base.msg;
|
|
32
|
+
if (typeof errorCode !== 'number' || errorCode === 0) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
if (typeof msg !== 'string' || !msg.trim().length) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
const captured = getCapturedRequest(ctx.adapterContext);
|
|
39
|
+
if (!captured) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
const followupPayload = buildRetryFollowupPayload(captured);
|
|
43
|
+
if (!followupPayload) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
chatResponse: ctx.base,
|
|
48
|
+
execution: {
|
|
49
|
+
flowId: FLOW_ID,
|
|
50
|
+
followup: {
|
|
51
|
+
requestIdSuffix: ':retry',
|
|
52
|
+
payload: followupPayload,
|
|
53
|
+
metadata: {
|
|
54
|
+
serverToolFollowup: true
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
registerServerToolHandler('iflow_model_error_retry', handler, { trigger: 'auto' });
|
|
61
|
+
function getCapturedRequest(adapterContext) {
|
|
62
|
+
if (!adapterContext || typeof adapterContext !== 'object') {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
const captured = adapterContext.capturedChatRequest;
|
|
66
|
+
if (!captured || typeof captured !== 'object' || Array.isArray(captured)) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
return captured;
|
|
70
|
+
}
|
|
71
|
+
function buildRetryFollowupPayload(source) {
|
|
72
|
+
if (!source || typeof source !== 'object') {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
const payload = {};
|
|
76
|
+
if (typeof source.model === 'string' && source.model.trim()) {
|
|
77
|
+
payload.model = source.model.trim();
|
|
78
|
+
}
|
|
79
|
+
const rawMessages = source.messages;
|
|
80
|
+
if (Array.isArray(rawMessages)) {
|
|
81
|
+
payload.messages = cloneJson(rawMessages);
|
|
82
|
+
}
|
|
83
|
+
const rawTools = source.tools;
|
|
84
|
+
if (Array.isArray(rawTools) && rawTools.length) {
|
|
85
|
+
payload.tools = cloneJson(rawTools);
|
|
86
|
+
}
|
|
87
|
+
const parameters = source.parameters;
|
|
88
|
+
if (parameters && typeof parameters === 'object' && !Array.isArray(parameters)) {
|
|
89
|
+
const params = cloneJson(parameters);
|
|
90
|
+
Object.assign(payload, params);
|
|
91
|
+
}
|
|
92
|
+
return payload;
|
|
93
|
+
}
|
|
@@ -1,24 +1,52 @@
|
|
|
1
1
|
import { registerServerToolHandler } from '../registry.js';
|
|
2
2
|
import { cloneJson } from '../server-side-tools.js';
|
|
3
3
|
import { loadRoutingInstructionStateSync, saveRoutingInstructionStateAsync } from '../../router/virtual-router/sticky-session-store.js';
|
|
4
|
+
const STOPMESSAGE_DEBUG = (process.env.ROUTECODEX_STOPMESSAGE_DEBUG || '').trim() === '1';
|
|
5
|
+
function debugLog(message, extra) {
|
|
6
|
+
if (!STOPMESSAGE_DEBUG) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
try {
|
|
10
|
+
// eslint-disable-next-line no-console
|
|
11
|
+
console.log(`\x1b[38;5;33m[stopMessage][debug] ${message}` +
|
|
12
|
+
(extra ? ` ${JSON.stringify(extra)}` : '') +
|
|
13
|
+
'\x1b[0m');
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
/* ignore logging failures */
|
|
17
|
+
}
|
|
18
|
+
}
|
|
4
19
|
const FLOW_ID = 'stop_message_flow';
|
|
5
20
|
const handler = async (ctx) => {
|
|
6
21
|
const record = ctx.adapterContext;
|
|
22
|
+
debugLog('handler_start', {
|
|
23
|
+
requestId: record.requestId,
|
|
24
|
+
providerProtocol: record.providerProtocol
|
|
25
|
+
});
|
|
7
26
|
const followupRaw = record.serverToolFollowup;
|
|
8
27
|
if (followupRaw === true || (typeof followupRaw === 'string' && followupRaw.trim().toLowerCase() === 'true')) {
|
|
28
|
+
debugLog('skip_servertool_followup_flag');
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
const connectionState = resolveClientConnectionState(record.clientConnectionState);
|
|
32
|
+
if (connectionState?.disconnected === true) {
|
|
33
|
+
debugLog('skip_client_disconnected');
|
|
9
34
|
return null;
|
|
10
35
|
}
|
|
11
36
|
const clientDisconnectedRaw = record.clientDisconnected;
|
|
12
37
|
if (clientDisconnectedRaw === true ||
|
|
13
38
|
(typeof clientDisconnectedRaw === 'string' && clientDisconnectedRaw.trim().toLowerCase() === 'true')) {
|
|
39
|
+
debugLog('skip_client_disconnected_flag');
|
|
14
40
|
return null;
|
|
15
41
|
}
|
|
16
42
|
const stickyKey = resolveStickyKey(record);
|
|
17
43
|
if (!stickyKey) {
|
|
44
|
+
debugLog('skip_no_sticky_key');
|
|
18
45
|
return null;
|
|
19
46
|
}
|
|
20
47
|
const state = loadRoutingInstructionStateSync(stickyKey);
|
|
21
48
|
if (!state || !state.stopMessageText || !state.stopMessageMaxRepeats) {
|
|
49
|
+
debugLog('skip_no_state', { stickyKey });
|
|
22
50
|
return null;
|
|
23
51
|
}
|
|
24
52
|
const text = typeof state.stopMessageText === 'string' ? state.stopMessageText.trim() : '';
|
|
@@ -26,25 +54,45 @@ const handler = async (ctx) => {
|
|
|
26
54
|
? Math.max(1, Math.floor(state.stopMessageMaxRepeats))
|
|
27
55
|
: 0;
|
|
28
56
|
if (!text || maxRepeats <= 0) {
|
|
57
|
+
debugLog('skip_invalid_text_or_maxRepeats', {
|
|
58
|
+
stickyKey,
|
|
59
|
+
textLength: text.length,
|
|
60
|
+
maxRepeats
|
|
61
|
+
});
|
|
29
62
|
return null;
|
|
30
63
|
}
|
|
31
64
|
const used = typeof state.stopMessageUsed === 'number' && Number.isFinite(state.stopMessageUsed)
|
|
32
65
|
? Math.max(0, Math.floor(state.stopMessageUsed))
|
|
33
66
|
: 0;
|
|
34
67
|
if (used >= maxRepeats) {
|
|
68
|
+
debugLog('skip_reached_max_repeats', {
|
|
69
|
+
stickyKey,
|
|
70
|
+
used,
|
|
71
|
+
maxRepeats
|
|
72
|
+
});
|
|
35
73
|
return null;
|
|
36
74
|
}
|
|
37
75
|
if (!isStopFinishReason(ctx.base)) {
|
|
76
|
+
debugLog('skip_not_stop_finish_reason', {
|
|
77
|
+
stickyKey
|
|
78
|
+
});
|
|
38
79
|
return null;
|
|
39
80
|
}
|
|
40
81
|
const captured = getCapturedRequest(ctx.adapterContext);
|
|
41
82
|
if (!captured) {
|
|
83
|
+
debugLog('skip_no_captured_request', {
|
|
84
|
+
stickyKey
|
|
85
|
+
});
|
|
42
86
|
return null;
|
|
43
87
|
}
|
|
44
88
|
state.stopMessageUsed = used + 1;
|
|
89
|
+
state.stopMessageLastUsedAt = Date.now();
|
|
45
90
|
saveRoutingInstructionStateAsync(stickyKey, state);
|
|
46
91
|
const followupPayload = buildStopMessageFollowupPayload(captured, text);
|
|
47
92
|
if (!followupPayload) {
|
|
93
|
+
debugLog('skip_failed_build_followup', {
|
|
94
|
+
stickyKey
|
|
95
|
+
});
|
|
48
96
|
return null;
|
|
49
97
|
}
|
|
50
98
|
return {
|
|
@@ -56,7 +104,10 @@ const handler = async (ctx) => {
|
|
|
56
104
|
payload: followupPayload,
|
|
57
105
|
metadata: {
|
|
58
106
|
serverToolFollowup: true,
|
|
59
|
-
stream: false
|
|
107
|
+
stream: false,
|
|
108
|
+
preserveRouteHint: false,
|
|
109
|
+
disableStickyRoutes: true,
|
|
110
|
+
...(connectionState ? { clientConnectionState: connectionState } : {})
|
|
60
111
|
}
|
|
61
112
|
}
|
|
62
113
|
}
|
|
@@ -65,12 +116,12 @@ const handler = async (ctx) => {
|
|
|
65
116
|
registerServerToolHandler('stop_message_auto', handler, { trigger: 'auto' });
|
|
66
117
|
function resolveStickyKey(record) {
|
|
67
118
|
const sessionId = typeof record.sessionId === 'string' && record.sessionId.trim() ? record.sessionId.trim() : '';
|
|
68
|
-
if (sessionId) {
|
|
69
|
-
return `session:${sessionId}`;
|
|
70
|
-
}
|
|
71
119
|
const conversationId = typeof record.conversationId === 'string' && record.conversationId.trim()
|
|
72
120
|
? record.conversationId.trim()
|
|
73
121
|
: '';
|
|
122
|
+
if (sessionId) {
|
|
123
|
+
return `session:${sessionId}`;
|
|
124
|
+
}
|
|
74
125
|
if (conversationId) {
|
|
75
126
|
return `conversation:${conversationId}`;
|
|
76
127
|
}
|
|
@@ -145,3 +196,9 @@ function buildStopMessageFollowupPayload(source, text) {
|
|
|
145
196
|
}
|
|
146
197
|
return payload;
|
|
147
198
|
}
|
|
199
|
+
function resolveClientConnectionState(value) {
|
|
200
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
return value;
|
|
204
|
+
}
|
|
@@ -3,6 +3,7 @@ import type { ServerSideToolEngineOptions, ServerSideToolEngineResult, ToolCall
|
|
|
3
3
|
import './handlers/web-search.js';
|
|
4
4
|
import './handlers/vision.js';
|
|
5
5
|
import './handlers/gemini-empty-reply-continue.js';
|
|
6
|
+
import './handlers/iflow-model-error-retry.js';
|
|
6
7
|
import './handlers/stop-message-auto.js';
|
|
7
8
|
export declare function runServerSideToolEngine(options: ServerSideToolEngineOptions): Promise<ServerSideToolEngineResult>;
|
|
8
9
|
export declare function extractToolCalls(chatResponse: JsonObject): ToolCall[];
|
|
@@ -2,12 +2,16 @@ import { getServerToolHandler, listAutoServerToolHandlers } from './registry.js'
|
|
|
2
2
|
import './handlers/web-search.js';
|
|
3
3
|
import './handlers/vision.js';
|
|
4
4
|
import './handlers/gemini-empty-reply-continue.js';
|
|
5
|
+
import './handlers/iflow-model-error-retry.js';
|
|
5
6
|
import './handlers/stop-message-auto.js';
|
|
6
7
|
export async function runServerSideToolEngine(options) {
|
|
7
8
|
const base = asObject(options.chatResponse);
|
|
8
9
|
if (!base) {
|
|
9
10
|
return { mode: 'passthrough', finalChatResponse: options.chatResponse };
|
|
10
11
|
}
|
|
12
|
+
if (isClientDisconnected(options.adapterContext)) {
|
|
13
|
+
return { mode: 'passthrough', finalChatResponse: base };
|
|
14
|
+
}
|
|
11
15
|
const toolCalls = extractToolCalls(base);
|
|
12
16
|
const contextBase = {
|
|
13
17
|
base,
|
|
@@ -91,6 +95,29 @@ function getArray(value) {
|
|
|
91
95
|
export function cloneJson(value) {
|
|
92
96
|
return JSON.parse(JSON.stringify(value));
|
|
93
97
|
}
|
|
98
|
+
function isClientDisconnected(adapterContext) {
|
|
99
|
+
if (!adapterContext || typeof adapterContext !== 'object') {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
const state = adapterContext.clientConnectionState;
|
|
103
|
+
if (state && typeof state === 'object' && !Array.isArray(state)) {
|
|
104
|
+
const disconnected = state.disconnected;
|
|
105
|
+
if (disconnected === true) {
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
if (typeof disconnected === 'string' && disconnected.trim().toLowerCase() === 'true') {
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const raw = adapterContext.clientDisconnected;
|
|
113
|
+
if (raw === true) {
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
if (typeof raw === 'string' && raw.trim().toLowerCase() === 'true') {
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
94
121
|
export function extractTextFromChatLike(payload) {
|
|
95
122
|
let current = payload;
|
|
96
123
|
const visited = new Set();
|
|
@@ -132,6 +132,22 @@ export function createAnthropicResponseBuilder(options) {
|
|
|
132
132
|
},
|
|
133
133
|
getResult() {
|
|
134
134
|
if (!state.completed) {
|
|
135
|
+
// 对部分实现(或网络提前关闭)导致缺失 message_stop 的 SSE 流,
|
|
136
|
+
// 只要已经累计到可用内容,就以最佳努力方式返回结果,而不是直接抛错。
|
|
137
|
+
if (state.content.length > 0) {
|
|
138
|
+
return {
|
|
139
|
+
success: true,
|
|
140
|
+
response: {
|
|
141
|
+
id: state.id || `msg_${Date.now()}`,
|
|
142
|
+
type: 'message',
|
|
143
|
+
role: state.role || 'assistant',
|
|
144
|
+
model: state.model || 'unknown',
|
|
145
|
+
content: state.content,
|
|
146
|
+
usage: state.usage,
|
|
147
|
+
stop_reason: state.stopReason ?? 'end_turn'
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
}
|
|
135
151
|
return { success: false, error: new Error('Anthropic SSE stream incomplete') };
|
|
136
152
|
}
|
|
137
153
|
return {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type StructuredApplyPatchKind = 'insert_after' | 'insert_before' | 'replace' | 'delete' | 'create_file' | 'delete_file';
|
|
2
|
+
export interface StructuredApplyPatchChange {
|
|
3
|
+
file?: string;
|
|
4
|
+
kind: StructuredApplyPatchKind | string;
|
|
5
|
+
anchor?: string;
|
|
6
|
+
target?: string;
|
|
7
|
+
lines?: string[] | string;
|
|
8
|
+
use_anchor_indent?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface StructuredApplyPatchPayload extends Record<string, unknown> {
|
|
11
|
+
instructions?: string;
|
|
12
|
+
file?: string;
|
|
13
|
+
changes: StructuredApplyPatchChange[];
|
|
14
|
+
}
|
|
15
|
+
export declare class StructuredApplyPatchError extends Error {
|
|
16
|
+
reason: string;
|
|
17
|
+
constructor(reason: string, message: string);
|
|
18
|
+
}
|
|
19
|
+
export declare function buildStructuredPatch(payload: StructuredApplyPatchPayload): string;
|
|
20
|
+
export declare function isStructuredApplyPatchPayload(candidate: unknown): candidate is StructuredApplyPatchPayload;
|