nolo-cli 0.32.0 → 0.33.0-alpha.10
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/README.md +6 -12
- package/{_executeModel-LTMSIBS4.js → _executeModel-YXWAXPWQ.js} +77 -67
- package/{action-HI3VRQLP.js → action-OQWPNKAH.js} +17 -15
- package/{addContentAction-NCURJ6PK.js → addContentAction-YVINZQTF.js} +14 -13
- package/{addDialogAgentAction-RCY4ECF4.js → addDialogAgentAction-6IOCIEPI.js} +14 -13
- package/{agentDeleteCommand-D3MYZK6E.js → agentDeleteCommand-VGHWJN5T.js} +44 -60
- package/{agentEmailCommands-6BXS574I.js → agentEmailCommands-ZLRF35I3.js} +91 -81
- package/{agentGrantCommands-E7ZZFRHK.js → agentGrantCommands-2LEZDADN.js} +2 -2
- package/{agentListCommands-SF3HEUTT.js → agentListCommands-OHZJ7AFP.js} +75 -79
- package/{agentMachineCommands-YARVXRR6.js → agentMachineCommands-62PHPGTK.js} +43 -59
- package/{agentPullCommand-ARSIDNVC.js → agentPullCommand-TSWAJPS2.js} +5 -5
- package/agentRecordCommands-CW2JWERD.js +113 -0
- package/agentRunCommand-O4HV7YWA.js +131 -0
- package/{agentRuntimeLocal-ATGJY6WC.js → agentRuntimeLocal-4PSBNUOE.js} +37 -51
- package/{agentSlice-4XWW344Y.js → agentSlice-OUTQ5O72.js} +16 -14
- package/{appDeleteCommands-63JO7RXS.js → appDeleteCommands-VTDBVHYR.js} +40 -56
- package/{appDeployCommands-Y7EWYUGM.js → appDeployCommands-WOUP7MDA.js} +40 -56
- package/{appDeployStatusCommands-ZBEY3DWB.js → appDeployStatusCommands-N2NWZKEN.js} +40 -56
- package/{appGetCommands-QSSOSEYL.js → appGetCommands-GYKLWWCA.js} +40 -56
- package/{appListCommands-PH3XWNDX.js → appListCommands-RUWBSEJK.js} +40 -56
- package/{authCommand-BBDHMSO6.js → authCommand-YVXVH25I.js} +21 -20
- package/{authCommands-4UDEKRE6.js → authCommands-WTW6MEYS.js} +10 -10
- package/chatQueueReduxAdapter-2QJAD4KQ.js +181 -0
- package/{chunk-EEF23OOK.js → chunk-266VUBLK.js} +7 -7
- package/{chunk-TEYN42N3.js → chunk-2IVEXF7P.js} +11 -4
- package/{chunk-2HXKAPH7.js → chunk-2QLAVHVK.js} +6 -6
- package/{chunk-WTGCTVJG.js → chunk-2QTEYCR7.js} +3 -6
- package/{chunk-DOISARTV.js → chunk-2UECQ7RE.js} +1 -1
- package/{chunk-E3APEOIM.js → chunk-3GZXL6FM.js} +4 -19
- package/{chunk-KGEVMUMI.js → chunk-3RUW3ONU.js} +11 -11
- package/{chunk-RXVX5LFR.js → chunk-3UG2IIZ3.js} +1 -1
- package/{chunk-O5OMJPPF.js → chunk-3WXRAHCG.js} +6 -5
- package/{chunk-V54H24CQ.js → chunk-4F5M2MW5.js} +3 -3
- package/{chunk-2ZRP5RUG.js → chunk-4OV7DXFJ.js} +12 -11
- package/{chunk-W367V5CH.js → chunk-54BAYVCY.js} +404 -117
- package/{chunk-GAF5X6B7.js → chunk-5QW3O3ET.js} +284 -310
- package/{chunk-DLWCWUNW.js → chunk-64OVNZXE.js} +1197 -757
- package/{chunk-ODS2MVMU.js → chunk-66CWPJK2.js} +19 -303
- package/{chunk-PTHDLIRB.js → chunk-6KOS7ZBZ.js} +1 -1
- package/{chunk-MXFQHRSS.js → chunk-6XIE5ZGI.js} +1 -1
- package/{chunk-ATK5WJOA.js → chunk-6YXDOOSH.js} +4 -4
- package/{chunk-SWYMCHM7.js → chunk-75U775RZ.js} +69 -1
- package/{chunk-UDA3ZYLW.js → chunk-7YYHKBJP.js} +31 -72
- package/chunk-ADLYMQB6.js +642 -0
- package/{chunk-ZWJOGMKH.js → chunk-AETZ7AD6.js} +1 -1
- package/{chunk-43EPBPZ2.js → chunk-ASDXPOJH.js} +2 -2
- package/{chunk-44HACRCK.js → chunk-BANYWEYY.js} +4 -4
- package/chunk-BFPSVRCQ.js +1799 -0
- package/{chunk-TOW4E763.js → chunk-BJIRLHJR.js} +2 -0
- package/{chunk-ZJ2WITOO.js → chunk-BQXOGBFN.js} +2 -2
- package/{chunk-4PNU7W7K.js → chunk-C7H5GMXT.js} +107 -125
- package/chunk-CPYYM4RJ.js +98 -0
- package/{chunk-3GZT3L6F.js → chunk-DDVIBKIB.js} +326 -481
- package/{chunk-2K4NDPT5.js → chunk-DFUPZE6V.js} +20 -12
- package/{chunk-BGTUGS3T.js → chunk-DKMOY3QI.js} +4 -30
- package/chunk-EGRJC4ZN.js +91 -0
- package/{chunk-K6QEHQZ3.js → chunk-EKLRRZIN.js} +1 -1
- package/{chunk-6HYVJV7B.js → chunk-ESHYE7WU.js} +2 -1
- package/{chunk-XFG3BAKN.js → chunk-FMEVI5TG.js} +65 -0
- package/{chunk-U4TRT5V4.js → chunk-GEPHJJW4.js} +1 -1
- package/chunk-GJXRIOLS.js +241 -0
- package/{chunk-VP4WJWKW.js → chunk-GM6PU7B6.js} +431 -345
- package/chunk-HS7MLR74.js +38 -0
- package/{chunk-3AUWC6EM.js → chunk-HUCT7HID.js} +5 -84
- package/chunk-IDAU4UFS.js +437 -0
- package/{chunk-Q76I4SXI.js → chunk-IHEC7VBF.js} +1 -1
- package/{chunk-OWHNFZNW.js → chunk-IKWTTKWG.js} +4 -4
- package/{chunk-3LY4F7SV.js → chunk-INZZJSQF.js} +348 -331
- package/{chunk-ZJH3LKEW.js → chunk-IR6W62Q6.js} +102 -15
- package/{chunk-RH53LMEJ.js → chunk-J44TVX2O.js} +2 -4
- package/{chunk-CF2WZF4N.js → chunk-J6WH4PYZ.js} +0 -8
- package/chunk-KHCLMKZZ.js +3062 -0
- package/{chunk-WHLVPK32.js → chunk-KMSZPTM6.js} +27 -3
- package/{chunk-L3GCUVOH.js → chunk-KSRFVP36.js} +1 -1
- package/{chunk-XDHHF73Q.js → chunk-LUIHOUZC.js} +95 -95
- package/{chunk-UOJRKY5J.js → chunk-M2YMQOLC.js} +44 -53
- package/chunk-M3JEW6N6.js +193 -0
- package/{chunk-MITD7CON.js → chunk-MDIAECHA.js} +1461 -301
- package/{chunk-IXZX5CJK.js → chunk-MQRZ5QUH.js} +1 -1
- package/{chunk-3ASDTTEI.js → chunk-MRWGHF56.js} +2 -0
- package/{chunk-Q3CSL43H.js → chunk-N4XRDM26.js} +1 -1
- package/{chunk-D34RI6NI.js → chunk-NNWHHCVP.js} +75 -1
- package/{chunk-CAENRKNJ.js → chunk-OR2HNLVO.js} +126 -88
- package/chunk-OW765XBU.js +10 -0
- package/{chunk-VMWR7VNI.js → chunk-PRMMCFIN.js} +13 -9
- package/{chunk-JZPBVBIY.js → chunk-QTZD6HBK.js} +18 -8
- package/chunk-RCFIB45Q.js +28 -0
- package/{chunk-7S54ZGF6.js → chunk-RJKIB55Y.js} +256 -12
- package/chunk-RPRRIFXV.js +231 -0
- package/chunk-RQFO3CAF.js +300 -0
- package/chunk-RY57GLGW.js +188 -0
- package/{chunk-YHGHADBQ.js → chunk-SCUETSRP.js} +4 -4
- package/{chunk-TA6EA2A3.js → chunk-TFK3HUNQ.js} +182 -20
- package/{chunk-LAXMH7JY.js → chunk-UM4H3URE.js} +18 -15
- package/{chunk-6YABCOFU.js → chunk-UNHKVD2N.js} +1 -1
- package/{chunk-G5GVZAXF.js → chunk-UNOTBWZZ.js} +15 -19
- package/{chunk-GE6IWA5N.js → chunk-UNZDCI3O.js} +126 -1
- package/chunk-URMS4XY2.js +10 -0
- package/{chunk-SFWZUTCI.js → chunk-VBUQYOX7.js} +2 -2
- package/{chunk-FXFGNOT2.js → chunk-VFVCYIRQ.js} +1 -1
- package/chunk-VIDPVFP3.js +20 -0
- package/{chunk-5RDLGLAP.js → chunk-WJSW43QZ.js} +4 -7
- package/{chunk-CF7S2RYZ.js → chunk-WLH5BAKX.js} +6 -6
- package/{chunk-VVEH6XUE.js → chunk-X2DAEZ3X.js} +1 -1
- package/{chunk-2IJAKBK2.js → chunk-X664KVEV.js} +426 -246
- package/{chunk-3FCNXC2J.js → chunk-XRO5VGQ6.js} +3735 -1394
- package/chunk-Y5GCAMGP.js +9 -0
- package/{chunk-O3JR45Q2.js → chunk-YBMI5JAH.js} +44 -44
- package/{chunk-V42OE5HT.js → chunk-YDTYDA5W.js} +3 -3
- package/chunk-YYLHWIW6.js +48 -0
- package/{chunk-AOS5IN2I.js → chunk-ZHYK3N56.js} +1 -1
- package/{chunk-PJ2DDQQM.js → chunk-ZURFOV5J.js} +2 -2
- package/{chunk-3UGKATZQ.js → chunk-ZWS6DUHX.js} +26 -435
- package/chunk-ZZ2MH7LE.js +84 -0
- package/{cliExecutor-BBUVQU4L.js → cliExecutor-DW3PHGEL.js} +2 -2
- package/{clipboardy-BVE3LVEH.js → clipboardy-MMBYVRLF.js} +166 -372
- package/{codingSkills-V35TRR4Y.js → codingSkills-DY5MINPG.js} +5 -4
- package/{createAgentAutomationAction-WLRNE7C4.js → createAgentAutomationAction-H6ZRPXBR.js} +41 -31
- package/createDialogAction-UJ6LFJYM.js +61 -0
- package/{createPageAction-7YROVZ3W.js → createPageAction-KVPT3IRK.js} +57 -47
- package/{db-6DPSJ6WT.js → db-3H3JECGY.js} +45 -5
- package/{dbSlice-BBL3PEVW.js → dbSlice-UI2VCBLG.js} +13 -12
- package/{deleteFile-RJTEPOT4.js → deleteFile-XQXPBFFB.js} +7 -6
- package/dialogCommands-6ASXJP3H.js +82 -0
- package/{dialogSlice-N4HPG6HM.js → dialogSlice-B7KCGHMY.js} +37 -29
- package/{docCreateCommands-GRA5BHSA.js → docCreateCommands-S4E4MKVC.js} +42 -56
- package/{docDeleteCommands-ONIJM5MD.js → docDeleteCommands-M7XLKWRM.js} +44 -58
- package/{docReadCommands-H55VQ2US.js → docReadCommands-LSCMW2FR.js} +42 -56
- package/{docStore-B5AHDNW5.js → docStore-ZYWZWIRB.js} +40 -30
- package/{docUpdateCommands-ZQIQVGC2.js → docUpdateCommands-OMRBAFOB.js} +45 -59
- package/{fetchMessages-VPPQDDZ4.js → fetchMessages-7QRCCRYK.js} +3 -3
- package/{getPricing-QO5TYNCK.js → getPricing-L2ENXWHV.js} +7 -6
- package/{handleSendMessageAction-IN5XX5U7.js → handleSendMessageAction-Y2XKODRT.js} +39 -31
- package/index.js +74 -74
- package/{lib-FYYBQIXS.js → lib-2ZTNRXR4.js} +432 -183
- package/{localDialogRead-3DBXVYXS.js → localDialogRead-SLMPV5SK.js} +2 -2
- package/{localRuntimeAuthority-NDUA2D7Y.js → localRuntimeAuthority-S77B5QVB.js} +10 -4
- package/{localRuntimeDb-GKYRBWLQ.js → localRuntimeDb-N3H3HQ7G.js} +1 -1
- package/{machineCommands-H3UMKE76.js → machineCommands-62RUZVSW.js} +95 -85
- package/{machineInfo-M6KB6HZN.js → machineInfo-66ZFUMLE.js} +1 -1
- package/machineWsRunDispatch-L3HD3FSF.js +129 -0
- package/{models-WDUTTYFS.js → models-OX2S5C6Q.js} +2 -2
- package/{offlineMarxistsAgentCommand-E6LUG63A.js → offlineMarxistsAgentCommand-GEH6SNNA.js} +1 -1
- package/package.json +1 -4
- package/{read-64OQDO3I.js → read-2VHEHCAN.js} +8 -7
- package/{readDocTool-MXM3T6PE.js → readDocTool-CSWD6NPC.js} +1 -2
- package/{readlineWorkspace-XVJZDCSC.js → readlineWorkspace-3MOJWFM7.js} +3358 -3409
- package/{removeDialogAgentAction-NEKVAMSH.js → removeDialogAgentAction-2T7BPM3T.js} +19 -17
- package/{runAgentClientLoop-H7U53QB6.js → runAgentClientLoop-Y5UW544I.js} +73 -64
- package/{runtimeDoctorCommands-WI2YZUSJ.js → runtimeDoctorCommands-KHGQKT3B.js} +6 -6
- package/{setDialogExtraReferencesAction-WTOHQDIV.js → setDialogExtraReferencesAction-GPT37HTW.js} +15 -14
- package/{setPrimaryDialogAgentAction-LDJOYC4F.js → setPrimaryDialogAgentAction-3746P7IW.js} +19 -17
- package/{settingSlice-4M37TN7V.js → settingSlice-FIFOQ6OR.js} +16 -14
- package/{skillDiscovery-5U6RYAHN.js → skillDiscovery-KLLKXPUO.js} +4 -4
- package/{slateUtils-2M7MUTWX.js → slateUtils-VO2FU36K.js} +1 -1
- package/{spaceCommands-7VVKJBDV.js → spaceCommands-NKSBD5G3.js} +45 -241
- package/{spaceSlice-T6YK6D34.js → spaceSlice-L44CI2QR.js} +39 -61
- package/{streamAgentChatTurn-RQ63QFQ7.js → streamAgentChatTurn-7DU4Z2VJ.js} +1262 -150
- package/{switchDialogAgentAction-XVWGKR2Z.js → switchDialogAgentAction-MQTUTQHL.js} +14 -13
- package/syncMapping-RK2MANUX.js +43 -0
- package/{tableCommands-RJZ3V4TF.js → tableCommands-64FZBAI3.js} +9 -8
- package/{toolApiClient-ARGFDJAG.js → toolApiClient-B2MFXMEG.js} +3 -3
- package/tools-2JI52WJP.js +99 -0
- package/{tuiSummaryLlmCaller-SRZ4QQUP.js → tuiSummaryLlmCaller-EAWQ3IZX.js} +25 -21
- package/{updateCommands-AK2RDOFR.js → updateCommands-MNDUIKN7.js} +2 -2
- package/{updateDialogTitleAction-BBDSWHNG.js → updateDialogTitleAction-HW33OIJI.js} +49 -38
- package/agentRecordCommands-JFZRQJQ4.js +0 -103
- package/agentRunCommand-NOX5FLKO.js +0 -121
- package/chunk-AYMOSAHE.js +0 -3158
- package/chunk-D2BPX6DQ.js +0 -1707
- package/chunk-DCDL3GC4.js +0 -171
- package/chunk-F3MP7CP6.js +0 -73
- package/chunk-WZSRQNQG.js +0 -2587
- package/createDialogAction-Y6YV64QB.js +0 -52
- package/dialogCommands-XLO5CAHZ.js +0 -95
- package/machineWsRunDispatch-HONRUK3R.js +0 -119
- package/tools-KYZX3BGO.js +0 -90
- package/{chunk-Z5MXSEOJ.js → chunk-RQSOAMPJ.js} +2 -2
- package/{chunk-JDSUTNYY.js → chunk-SAQQLJZ5.js} +3 -3
|
@@ -0,0 +1,1799 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildCurrentTimeBlock,
|
|
3
|
+
buildIdentityBlock,
|
|
4
|
+
buildRuntimeGuidanceBlocks,
|
|
5
|
+
resolveToolGuidedSections
|
|
6
|
+
} from "./chunk-ZWS6DUHX.js";
|
|
7
|
+
import {
|
|
8
|
+
getModelContextWindow,
|
|
9
|
+
resolveBuiltinPlatformAgentConfig
|
|
10
|
+
} from "./chunk-GJXRIOLS.js";
|
|
11
|
+
import {
|
|
12
|
+
COMPACTION_SUMMARY_SYSTEM_PROMPT,
|
|
13
|
+
buildCompactionMetricsFromPlan,
|
|
14
|
+
buildCompactionUserContent,
|
|
15
|
+
clipCompactText,
|
|
16
|
+
estimateTokenCount,
|
|
17
|
+
formatCompactionMetricsLog,
|
|
18
|
+
formatFileOperationsFromMessages,
|
|
19
|
+
formatMessagesForSummaryWithTruncation,
|
|
20
|
+
planCompression,
|
|
21
|
+
planContextUsage
|
|
22
|
+
} from "./chunk-DKMOY3QI.js";
|
|
23
|
+
import {
|
|
24
|
+
findModelConfig,
|
|
25
|
+
getProviderByModelName
|
|
26
|
+
} from "./chunk-DFUPZE6V.js";
|
|
27
|
+
import {
|
|
28
|
+
buildDialogSummaryLayer
|
|
29
|
+
} from "./chunk-2UECQ7RE.js";
|
|
30
|
+
import {
|
|
31
|
+
buildUserResponseLanguageContext
|
|
32
|
+
} from "./chunk-FMEVI5TG.js";
|
|
33
|
+
import {
|
|
34
|
+
canonicalizeToolName,
|
|
35
|
+
canonicalizeToolNames
|
|
36
|
+
} from "./chunk-ZZ2MH7LE.js";
|
|
37
|
+
import {
|
|
38
|
+
toErrorMessage
|
|
39
|
+
} from "./chunk-HNMXYYJR.js";
|
|
40
|
+
import {
|
|
41
|
+
PLATFORM_HOSTED_QWEN_37_FLASH_MODEL
|
|
42
|
+
} from "./chunk-OR2HNLVO.js";
|
|
43
|
+
import {
|
|
44
|
+
asRecordOrEmpty
|
|
45
|
+
} from "./chunk-KX6YBIHG.js";
|
|
46
|
+
import {
|
|
47
|
+
asTrimmedLowercaseString
|
|
48
|
+
} from "./chunk-QSJ3HFEF.js";
|
|
49
|
+
import {
|
|
50
|
+
PUBLIC_QWEN_37_FLASH_AGENT_KEY
|
|
51
|
+
} from "./chunk-IR6W62Q6.js";
|
|
52
|
+
import {
|
|
53
|
+
isRecord
|
|
54
|
+
} from "./chunk-KUG52IAP.js";
|
|
55
|
+
import {
|
|
56
|
+
asOptionalTrimmedString
|
|
57
|
+
} from "./chunk-G3SUCT4W.js";
|
|
58
|
+
|
|
59
|
+
// packages/agent-runtime/actionGate.ts
|
|
60
|
+
function readCommandActionGatePayload(payload) {
|
|
61
|
+
if (!isRecord(payload)) return null;
|
|
62
|
+
if (!Array.isArray(payload.command)) return null;
|
|
63
|
+
const command = payload.command.flatMap((item) => {
|
|
64
|
+
const value = asOptionalTrimmedString(item);
|
|
65
|
+
return value ? [value] : [];
|
|
66
|
+
});
|
|
67
|
+
if (command.length === 0) return null;
|
|
68
|
+
const displayCommand = asOptionalTrimmedString(payload.displayCommand);
|
|
69
|
+
return {
|
|
70
|
+
command,
|
|
71
|
+
...displayCommand ? { displayCommand } : {}
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function readActionGate(value) {
|
|
75
|
+
if (!isRecord(value)) return null;
|
|
76
|
+
if (typeof value.id !== "string" || !value.id.trim()) return null;
|
|
77
|
+
if (value.kind !== "confirm" && value.kind !== "handoff" && value.kind !== "input") return null;
|
|
78
|
+
if (typeof value.title !== "string" || !value.title.trim()) return null;
|
|
79
|
+
const titleParams = readTitleParams(value.titleParams);
|
|
80
|
+
return {
|
|
81
|
+
id: value.id.trim(),
|
|
82
|
+
kind: value.kind,
|
|
83
|
+
title: value.title.trim(),
|
|
84
|
+
...titleParams ? { titleParams } : {},
|
|
85
|
+
...asOptionalTrimmedString(value.body) ? { body: asOptionalTrimmedString(value.body) } : {},
|
|
86
|
+
...Object.prototype.hasOwnProperty.call(value, "payload") ? { payload: value.payload } : {}
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function readTitleParams(value) {
|
|
90
|
+
if (!isRecord(value)) return void 0;
|
|
91
|
+
const entries = Object.entries(value).flatMap(([key, raw]) => {
|
|
92
|
+
if (typeof raw === "string" || typeof raw === "number") {
|
|
93
|
+
return [[key, raw]];
|
|
94
|
+
}
|
|
95
|
+
return [];
|
|
96
|
+
});
|
|
97
|
+
if (entries.length === 0) return void 0;
|
|
98
|
+
return Object.fromEntries(entries);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// packages/ai/llm/agentCapabilities.ts
|
|
102
|
+
var ANTIGRAVITY_EFFORT_SUFFIXES = [
|
|
103
|
+
"-tiered",
|
|
104
|
+
"-extra-low",
|
|
105
|
+
"-low",
|
|
106
|
+
"-medium",
|
|
107
|
+
"-high"
|
|
108
|
+
];
|
|
109
|
+
function stripEffortSuffix(model) {
|
|
110
|
+
for (const suffix of ANTIGRAVITY_EFFORT_SUFFIXES) {
|
|
111
|
+
if (model.endsWith(suffix) && model.length > suffix.length) {
|
|
112
|
+
return model.slice(0, -suffix.length);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return model;
|
|
116
|
+
}
|
|
117
|
+
function stripPreviewSuffix(model) {
|
|
118
|
+
const suffix = "-preview";
|
|
119
|
+
if (model.endsWith(suffix) && model.length > suffix.length) {
|
|
120
|
+
return model.slice(0, -suffix.length);
|
|
121
|
+
}
|
|
122
|
+
return model;
|
|
123
|
+
}
|
|
124
|
+
var lookupKnownModelVision = (provider, model) => {
|
|
125
|
+
if (!model) return void 0;
|
|
126
|
+
const candidates = [model];
|
|
127
|
+
const strippedEffort = stripEffortSuffix(model);
|
|
128
|
+
if (strippedEffort !== model) candidates.push(strippedEffort);
|
|
129
|
+
const strippedPreview = stripPreviewSuffix(model);
|
|
130
|
+
if (strippedPreview !== model) candidates.push(strippedPreview);
|
|
131
|
+
for (const candidate of candidates) {
|
|
132
|
+
if (provider && provider !== "custom") {
|
|
133
|
+
const direct = findModelConfig(provider, candidate);
|
|
134
|
+
if (direct && typeof direct.hasVision === "boolean") return direct.hasVision;
|
|
135
|
+
}
|
|
136
|
+
const detected = getProviderByModelName(candidate);
|
|
137
|
+
if (detected) {
|
|
138
|
+
const found = findModelConfig(detected, candidate)?.hasVision;
|
|
139
|
+
if (found !== void 0) return found;
|
|
140
|
+
}
|
|
141
|
+
if (candidate.includes("/")) {
|
|
142
|
+
const slash = candidate.indexOf("/");
|
|
143
|
+
const modelProvider = candidate.slice(0, slash).toLowerCase();
|
|
144
|
+
const modelName = candidate.slice(slash + 1);
|
|
145
|
+
const nested = findModelConfig(modelProvider, modelName);
|
|
146
|
+
if (nested && typeof nested.hasVision === "boolean") return nested.hasVision;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return void 0;
|
|
150
|
+
};
|
|
151
|
+
var resolveAgentImageInputSupport = (agent) => {
|
|
152
|
+
if (!agent) return true;
|
|
153
|
+
const provider = asTrimmedLowercaseString(agent.provider) || null;
|
|
154
|
+
const model = asOptionalTrimmedString(agent.model) ?? "";
|
|
155
|
+
const catalogHasVision = lookupKnownModelVision(
|
|
156
|
+
provider,
|
|
157
|
+
model
|
|
158
|
+
);
|
|
159
|
+
if (catalogHasVision !== void 0) {
|
|
160
|
+
return catalogHasVision;
|
|
161
|
+
}
|
|
162
|
+
if (typeof agent.hasVision === "boolean") {
|
|
163
|
+
return agent.hasVision;
|
|
164
|
+
}
|
|
165
|
+
return true;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
// packages/ai/agent/imagePreprocessing.ts
|
|
169
|
+
var DEFAULT_IMAGE_PREPROCESSOR_AGENT_KEY = PUBLIC_QWEN_37_FLASH_AGENT_KEY;
|
|
170
|
+
var DESCRIBE_IMAGE_PROMPT = `\u4F60\u662F\u4E00\u4E2A\u4E13\u4E1A\u7684\u56FE\u7247\u63CF\u8FF0\u52A9\u624B\u3002\u8BF7\u6781\u5176\u8BE6\u7EC6\u5730\u63CF\u8FF0\u8FD9\u5F20\u56FE\u7247\uFF0C\u76EE\u6807\u662F\u8BA9\u4E00\u4E2A\u770B\u4E0D\u5230\u56FE\u7247\u7684 AI \u52A9\u624B\u80FD\u5B8C\u5168\u7406\u89E3\u56FE\u7247\u5185\u5BB9\u3002
|
|
171
|
+
|
|
172
|
+
\u8BF7\u6309\u4EE5\u4E0B\u7ED3\u6784\u63CF\u8FF0\uFF1A
|
|
173
|
+
|
|
174
|
+
1. **\u56FE\u7247\u7C7B\u578B**\uFF1A\u8FD9\u662F\u754C\u9762\u622A\u56FE\u3001\u7167\u7247\u3001\u56FE\u8868\u3001\u6587\u6863\u3001\u8FD8\u662F\u5176\u4ED6\u7C7B\u578B\uFF1F
|
|
175
|
+
|
|
176
|
+
2. **\u5982\u679C\u662F\u754C\u9762\u622A\u56FE**\uFF1A
|
|
177
|
+
- \u5217\u51FA\u6240\u6709\u53EF\u89C1\u7684 UI \u5143\u7D20\uFF1A\u6807\u9898\u680F\u3001\u5BFC\u822A\u83DC\u5355\u3001\u6309\u94AE\u3001\u8F93\u5165\u6846\u3001\u5217\u8868\u9879\u7B49
|
|
178
|
+
- \u9010\u4E2A\u63CF\u8FF0\u6BCF\u4E2A\u5143\u7D20\u7684\u6587\u5B57\u5185\u5BB9\u548C\u4F4D\u7F6E\uFF08\u5982"\u5DE6\u4E0A\u89D2"\u3001"\u53F3\u4FA7\u680F"\uFF09
|
|
179
|
+
- \u63CF\u8FF0\u989C\u8272\u4E3B\u9898\u3001\u5E03\u5C40\u7ED3\u6784\u3001\u5F53\u524D\u72B6\u6001\uFF08\u5982\u9009\u4E2D\u3001hover\u3001loading\uFF09
|
|
180
|
+
- \u5982\u679C\u6709\u5F39\u7A97/\u5BF9\u8BDD\u6846/\u901A\u77E5\uFF0C\u5B8C\u6574\u8BB0\u5F55\u5176\u6587\u5B57\u5185\u5BB9
|
|
181
|
+
|
|
182
|
+
3. **\u5982\u679C\u662F\u751F\u6D3B\u7167\u7247**\uFF1A
|
|
183
|
+
- \u573A\u666F/\u73AF\u5883\uFF1A\u5BA4\u5185/\u5BA4\u5916\u3001\u5730\u70B9\u7C7B\u578B\u3001\u65F6\u95F4\u611F\uFF08\u767D\u5929/\u591C\u665A\uFF09
|
|
184
|
+
- \u4EBA\u7269\uFF1A\u4EBA\u6570\u3001\u59FF\u52BF\u3001\u8868\u60C5\u3001\u7A7F\u7740\u3001\u52A8\u4F5C
|
|
185
|
+
- \u7269\u4F53\uFF1A\u524D\u666F/\u80CC\u666F\u4E2D\u7684\u6240\u6709\u663E\u8457\u7269\u4F53
|
|
186
|
+
- \u5149\u7EBF\u3001\u5929\u6C14\u3001\u6C1B\u56F4
|
|
187
|
+
|
|
188
|
+
4. **\u5982\u679C\u662F\u56FE\u8868/\u6570\u636E\u56FE**\uFF1A
|
|
189
|
+
- \u56FE\u8868\u7C7B\u578B\uFF08\u67F1\u72B6\u56FE/\u6298\u7EBF\u56FE/\u997C\u56FE\u7B49\uFF09
|
|
190
|
+
- \u5750\u6807\u8F74\u6807\u7B7E\u3001\u6570\u503C\u8303\u56F4\u3001\u5355\u4F4D
|
|
191
|
+
- \u5173\u952E\u6570\u636E\u70B9\u548C\u8D8B\u52BF
|
|
192
|
+
- \u56FE\u4F8B\u3001\u6807\u9898
|
|
193
|
+
|
|
194
|
+
5. **\u6240\u6709\u53EF\u89C1\u6587\u5B57**\uFF1A\u9010\u5B57\u8BB0\u5F55\u56FE\u7247\u4E2D\u51FA\u73B0\u7684\u6240\u6709\u6587\u5B57\uFF08UI \u6587\u5B57\u3001\u8DEF\u724C\u3001\u6807\u7B7E\u3001\u6C34\u5370\u7B49\uFF09\uFF0C\u4FDD\u7559\u539F\u6587\u3002
|
|
195
|
+
|
|
196
|
+
6. **\u989C\u8272\u548C\u5E03\u5C40**\uFF1A\u4E3B\u8272\u8C03\u3001\u5E03\u5C40\u7ED3\u6784\u3001\u7A7A\u95F4\u5173\u7CFB\u3002
|
|
197
|
+
|
|
198
|
+
\u8BF7\u5C3D\u53EF\u80FD\u8BE6\u7EC6\uFF0C\u4E0D\u8981\u9057\u6F0F\u4EFB\u4F55\u53EF\u89C1\u4FE1\u606F\u3002`;
|
|
199
|
+
function extractImageUrlsFromMessages(messages) {
|
|
200
|
+
const urls = [];
|
|
201
|
+
for (const msg of messages) {
|
|
202
|
+
if (!Array.isArray(msg.content)) continue;
|
|
203
|
+
for (const part of msg.content) {
|
|
204
|
+
if (part && typeof part === "object" && part.type === "image_url" && part.image_url && typeof part.image_url.url === "string" && part.image_url.url.trim()) {
|
|
205
|
+
urls.push(part.image_url.url);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return urls;
|
|
210
|
+
}
|
|
211
|
+
function hasImageInRuntimeMessages(messages) {
|
|
212
|
+
return extractImageUrlsFromMessages(messages).length > 0;
|
|
213
|
+
}
|
|
214
|
+
var IMAGE_DESCRIPTION_CACHE = /* @__PURE__ */ new Map();
|
|
215
|
+
function cacheKeyForUrls(urls) {
|
|
216
|
+
return urls.slice().sort().join("||");
|
|
217
|
+
}
|
|
218
|
+
async function preprocessImagesForTextOnlyAgent(messages, describeImage) {
|
|
219
|
+
const imageUrls = extractImageUrlsFromMessages(messages);
|
|
220
|
+
if (imageUrls.length === 0) return messages;
|
|
221
|
+
const key = cacheKeyForUrls(imageUrls);
|
|
222
|
+
let description = IMAGE_DESCRIPTION_CACHE.get(key);
|
|
223
|
+
if (!description) {
|
|
224
|
+
const rawDesc = await describeImage(imageUrls);
|
|
225
|
+
if (rawDesc === null || !rawDesc.trim()) return messages;
|
|
226
|
+
description = rawDesc.trim();
|
|
227
|
+
IMAGE_DESCRIPTION_CACHE.set(key, description);
|
|
228
|
+
}
|
|
229
|
+
const descriptionText = `[\u56FE\u7247\u63CF\u8FF0] ${description}`;
|
|
230
|
+
return messages.map((msg) => {
|
|
231
|
+
if (!Array.isArray(msg.content)) return msg;
|
|
232
|
+
const hasImage = msg.content.some((p) => p?.type === "image_url");
|
|
233
|
+
if (!hasImage) return msg;
|
|
234
|
+
const newContent = msg.content.map((part) => {
|
|
235
|
+
if (part && typeof part === "object" && part.type === "image_url") {
|
|
236
|
+
return { type: "text", text: descriptionText };
|
|
237
|
+
}
|
|
238
|
+
return part;
|
|
239
|
+
}).filter((p) => p !== null && p !== void 0);
|
|
240
|
+
return { ...msg, content: newContent };
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
async function describeImageWithLocalProvider(adapter, imageUrls, visionAgentConfig) {
|
|
244
|
+
if (imageUrls.length === 0) return null;
|
|
245
|
+
try {
|
|
246
|
+
const config = visionAgentConfig ?? resolveBuiltinPlatformAgentConfig(DEFAULT_IMAGE_PREPROCESSOR_AGENT_KEY);
|
|
247
|
+
if (!config) {
|
|
248
|
+
console.warn("[imagePreprocessing] vision agent config not found, skipping");
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
const visionProvider = await adapter.resolveProvider(config);
|
|
252
|
+
const result = await visionProvider.complete(
|
|
253
|
+
[{ role: "user", content: buildDescribeContentParts(imageUrls) }],
|
|
254
|
+
{ timeoutMs: 3e4 }
|
|
255
|
+
);
|
|
256
|
+
const text = result.content?.trim();
|
|
257
|
+
if (!text) return null;
|
|
258
|
+
return text;
|
|
259
|
+
} catch (error) {
|
|
260
|
+
console.warn("[imagePreprocessing] vision describe failed, will fallback:", error);
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
async function describeImageViaServerProxy(serverUrl, authToken, imageUrls, visionAgentKey) {
|
|
265
|
+
if (imageUrls.length === 0) return null;
|
|
266
|
+
const agentKey = visionAgentKey ?? DEFAULT_IMAGE_PREPROCESSOR_AGENT_KEY;
|
|
267
|
+
const { model: visionModel, provider: visionProvider } = resolveDefaultVisionModelConfig(agentKey);
|
|
268
|
+
try {
|
|
269
|
+
const payload = {
|
|
270
|
+
model: visionModel,
|
|
271
|
+
messages: [{ role: "user", content: buildDescribeContentParts(imageUrls) }],
|
|
272
|
+
stream: false,
|
|
273
|
+
provider: visionProvider,
|
|
274
|
+
agentKey,
|
|
275
|
+
// server proxy 需要 url 字段,但 nolo 平台 agent 的上游由 server 端路由,
|
|
276
|
+
// 这里放一个占位 url,server 端会根据 provider/model 覆盖。
|
|
277
|
+
url: "https://api.ollama.com/v1/chat/completions"
|
|
278
|
+
};
|
|
279
|
+
const response = await fetch(`${serverUrl}/api/chat`, {
|
|
280
|
+
method: "POST",
|
|
281
|
+
headers: {
|
|
282
|
+
"Content-Type": "application/json",
|
|
283
|
+
Authorization: `Bearer ${authToken}`
|
|
284
|
+
},
|
|
285
|
+
body: JSON.stringify(payload),
|
|
286
|
+
signal: AbortSignal.timeout(3e4)
|
|
287
|
+
});
|
|
288
|
+
if (!response.ok) {
|
|
289
|
+
console.warn(`[imagePreprocessing] server proxy vision call failed: ${response.status}`);
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
const data = await response.json();
|
|
293
|
+
const text = data?.choices?.[0]?.message?.content;
|
|
294
|
+
if (typeof text === "string" && text.trim()) {
|
|
295
|
+
return text.trim();
|
|
296
|
+
}
|
|
297
|
+
return null;
|
|
298
|
+
} catch (error) {
|
|
299
|
+
console.warn("[imagePreprocessing] server proxy vision describe failed:", error);
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
function buildDescribeContentParts(imageUrls) {
|
|
304
|
+
const parts = [{ type: "text", text: DESCRIBE_IMAGE_PROMPT }];
|
|
305
|
+
for (const url of imageUrls) {
|
|
306
|
+
parts.push({ type: "image_url", image_url: { url } });
|
|
307
|
+
}
|
|
308
|
+
return parts;
|
|
309
|
+
}
|
|
310
|
+
function resolveDefaultVisionModelConfig(agentKey) {
|
|
311
|
+
const key = agentKey ?? DEFAULT_IMAGE_PREPROCESSOR_AGENT_KEY;
|
|
312
|
+
const config = resolveBuiltinPlatformAgentConfig(key);
|
|
313
|
+
return {
|
|
314
|
+
model: config?.model ?? PLATFORM_HOSTED_QWEN_37_FLASH_MODEL,
|
|
315
|
+
provider: config?.provider ?? "nolo",
|
|
316
|
+
config
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
var IMAGE_OMITTED_PLACEHOLDER = "[Image content omitted: model does not support image input]";
|
|
320
|
+
function stripImagePartsFromContent(content) {
|
|
321
|
+
if (!Array.isArray(content)) return content;
|
|
322
|
+
const filtered = content.filter((part) => part?.type !== "image_url");
|
|
323
|
+
if (filtered.length === 0) return IMAGE_OMITTED_PLACEHOLDER;
|
|
324
|
+
return filtered;
|
|
325
|
+
}
|
|
326
|
+
function stripImagePartsFromMessages(messages) {
|
|
327
|
+
return messages.map((msg) => ({
|
|
328
|
+
...msg,
|
|
329
|
+
content: stripImagePartsFromContent(msg.content)
|
|
330
|
+
}));
|
|
331
|
+
}
|
|
332
|
+
async function tryPreprocessWebImageOrReject(messages, agentConfig, initialHistoryIds, serverUrl, authToken) {
|
|
333
|
+
const supportsVision = resolveAgentImageInputSupport(agentConfig);
|
|
334
|
+
const hasImages = hasImageInRuntimeMessages(messages);
|
|
335
|
+
const needsPreprocessing = !supportsVision && hasImages;
|
|
336
|
+
if (!needsPreprocessing) {
|
|
337
|
+
const firstDynamicIdx = messages.findIndex((m) => m.id && !initialHistoryIds.has(m.id));
|
|
338
|
+
const splitIdx2 = firstDynamicIdx === -1 ? messages.length : firstDynamicIdx;
|
|
339
|
+
return {
|
|
340
|
+
kind: "ok",
|
|
341
|
+
messages,
|
|
342
|
+
stableMessages: messages.slice(0, splitIdx2),
|
|
343
|
+
dynamicMessages: messages.slice(splitIdx2)
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
let finalMessages = messages;
|
|
347
|
+
if (serverUrl && authToken) {
|
|
348
|
+
try {
|
|
349
|
+
const preprocessed = await preprocessImagesForTextOnlyAgent(
|
|
350
|
+
messages,
|
|
351
|
+
(urls) => describeImageViaServerProxy(serverUrl, authToken, urls)
|
|
352
|
+
);
|
|
353
|
+
if (preprocessed !== messages) {
|
|
354
|
+
finalMessages = preprocessed;
|
|
355
|
+
}
|
|
356
|
+
} catch (e) {
|
|
357
|
+
console.warn("[imagePreprocessing] web vision describe error:", e);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
if (finalMessages === messages) {
|
|
361
|
+
finalMessages = stripImagePartsFromMessages(messages);
|
|
362
|
+
}
|
|
363
|
+
const newFirstDynamicIdx = finalMessages.findIndex((m) => m.id && !initialHistoryIds.has(m.id));
|
|
364
|
+
const splitIdx = newFirstDynamicIdx === -1 ? finalMessages.length : newFirstDynamicIdx;
|
|
365
|
+
return {
|
|
366
|
+
kind: "ok",
|
|
367
|
+
messages: finalMessages,
|
|
368
|
+
stableMessages: finalMessages.slice(0, splitIdx),
|
|
369
|
+
dynamicMessages: finalMessages.slice(splitIdx)
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// packages/agent-runtime/toolCallPairing.ts
|
|
374
|
+
function readToolCallId(message) {
|
|
375
|
+
if (typeof message.tool_call_id === "string" && message.tool_call_id) {
|
|
376
|
+
return message.tool_call_id;
|
|
377
|
+
}
|
|
378
|
+
if (typeof message.toolCallId === "string" && message.toolCallId) {
|
|
379
|
+
return message.toolCallId;
|
|
380
|
+
}
|
|
381
|
+
return void 0;
|
|
382
|
+
}
|
|
383
|
+
function isContentBlank(content) {
|
|
384
|
+
if (content == null) return true;
|
|
385
|
+
if (typeof content === "string") return content.trim().length === 0;
|
|
386
|
+
if (Array.isArray(content)) {
|
|
387
|
+
if (content.length === 0) return true;
|
|
388
|
+
return !content.some((part) => {
|
|
389
|
+
if (part == null || typeof part !== "object") return false;
|
|
390
|
+
const p = part;
|
|
391
|
+
if (p.type === "text") return typeof p.text === "string" && p.text.trim().length > 0;
|
|
392
|
+
if (p.type === "image_url") {
|
|
393
|
+
const url = p.image_url?.url;
|
|
394
|
+
return typeof url === "string" && url.trim().length > 0;
|
|
395
|
+
}
|
|
396
|
+
return true;
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
return false;
|
|
400
|
+
}
|
|
401
|
+
function sanitizeToolCallPairing(messages) {
|
|
402
|
+
const assistantIndexByCallId = /* @__PURE__ */ new Map();
|
|
403
|
+
for (let i = 0; i < messages.length; i += 1) {
|
|
404
|
+
const m = messages[i];
|
|
405
|
+
if (m == null) continue;
|
|
406
|
+
if (m.role !== "assistant") continue;
|
|
407
|
+
const toolCalls = m.tool_calls;
|
|
408
|
+
if (!Array.isArray(toolCalls)) continue;
|
|
409
|
+
for (const call of toolCalls) {
|
|
410
|
+
const id = call?.id;
|
|
411
|
+
if (typeof id !== "string" || !id) continue;
|
|
412
|
+
if (!assistantIndexByCallId.has(id)) {
|
|
413
|
+
assistantIndexByCallId.set(id, i);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
const toolByCallId = /* @__PURE__ */ new Map();
|
|
418
|
+
for (let i = 0; i < messages.length; i += 1) {
|
|
419
|
+
const m = messages[i];
|
|
420
|
+
if (m == null) continue;
|
|
421
|
+
if (m.role !== "tool") continue;
|
|
422
|
+
const id = readToolCallId(m);
|
|
423
|
+
if (typeof id !== "string" || !id) continue;
|
|
424
|
+
if (!toolByCallId.has(id)) {
|
|
425
|
+
toolByCallId.set(id, m);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
const output = [];
|
|
429
|
+
const consumedToolCallIds = /* @__PURE__ */ new Set();
|
|
430
|
+
for (let i = 0; i < messages.length; i += 1) {
|
|
431
|
+
const m = messages[i];
|
|
432
|
+
if (m == null) continue;
|
|
433
|
+
if (m.role === "tool") continue;
|
|
434
|
+
if (m.role !== "assistant") {
|
|
435
|
+
output.push(m);
|
|
436
|
+
continue;
|
|
437
|
+
}
|
|
438
|
+
const toolCalls = m.tool_calls;
|
|
439
|
+
if (!Array.isArray(toolCalls) || toolCalls.length === 0) {
|
|
440
|
+
output.push(m);
|
|
441
|
+
continue;
|
|
442
|
+
}
|
|
443
|
+
const matchedToolCalls = [];
|
|
444
|
+
const matchedToolMessages = [];
|
|
445
|
+
for (const call of toolCalls) {
|
|
446
|
+
const id = call?.id;
|
|
447
|
+
if (typeof id !== "string" || !id) continue;
|
|
448
|
+
const toolMessage = toolByCallId.get(id);
|
|
449
|
+
if (!toolMessage) continue;
|
|
450
|
+
if (assistantIndexByCallId.get(id) !== i) continue;
|
|
451
|
+
if (consumedToolCallIds.has(id)) continue;
|
|
452
|
+
matchedToolCalls.push(call);
|
|
453
|
+
matchedToolMessages.push(toolMessage);
|
|
454
|
+
consumedToolCallIds.add(id);
|
|
455
|
+
}
|
|
456
|
+
if (matchedToolCalls.length === 0) {
|
|
457
|
+
const { tool_calls: _drop, ...rest } = m;
|
|
458
|
+
const stripped = rest;
|
|
459
|
+
if (isContentBlank(stripped.content)) {
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
462
|
+
output.push(stripped);
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
const normalizedAssistant = { ...m, tool_calls: matchedToolCalls };
|
|
466
|
+
output.push(normalizedAssistant);
|
|
467
|
+
for (const toolMessage of matchedToolMessages) {
|
|
468
|
+
output.push(toolMessage);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
return output;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// packages/agent-runtime/parseToolArguments.ts
|
|
475
|
+
function parseToolArgumentsJson(raw) {
|
|
476
|
+
if (!raw?.trim()) return {};
|
|
477
|
+
try {
|
|
478
|
+
return asRecordOrEmpty(JSON.parse(raw));
|
|
479
|
+
} catch {
|
|
480
|
+
return {};
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// packages/agent-runtime/summarizeToolArguments.ts
|
|
485
|
+
function clip(value, max = 240) {
|
|
486
|
+
return clipCompactText(value, max);
|
|
487
|
+
}
|
|
488
|
+
function summarizeToolArguments(toolName, rawArgs) {
|
|
489
|
+
const args = parseToolArgumentsJson(rawArgs);
|
|
490
|
+
const pick = (...keys2) => {
|
|
491
|
+
for (const key of keys2) {
|
|
492
|
+
const value = asOptionalTrimmedString(args[key]);
|
|
493
|
+
if (value) return value;
|
|
494
|
+
}
|
|
495
|
+
return "";
|
|
496
|
+
};
|
|
497
|
+
const command = pick("command", "cmd", "runCommand", "executeCommand", "bash");
|
|
498
|
+
if (command) return clip(command);
|
|
499
|
+
const query = pick("query", "pattern", "search", "q");
|
|
500
|
+
if (query) return clip(query);
|
|
501
|
+
const filePath = pick("filePath", "file_path", "path", "filename", "file");
|
|
502
|
+
if (filePath) return clip(filePath);
|
|
503
|
+
const keys = Object.keys(args);
|
|
504
|
+
if (keys.length === 0) return "";
|
|
505
|
+
return clip(
|
|
506
|
+
keys.slice(0, 6).map((key) => {
|
|
507
|
+
const value = args[key];
|
|
508
|
+
if (typeof value === "string") return `${key}=${clip(value, 80)}`;
|
|
509
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
510
|
+
return `${key}=${String(value)}`;
|
|
511
|
+
}
|
|
512
|
+
if (Array.isArray(value)) return `${key}[${value.length}]`;
|
|
513
|
+
return `${key}=${value === null ? "null" : typeof value}`;
|
|
514
|
+
}).join(" ")
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// packages/agent-runtime/contextBlockScope.ts
|
|
519
|
+
var isContextBlockScope = (value) => {
|
|
520
|
+
if (typeof value !== "object" || value === null) return false;
|
|
521
|
+
const candidate = value;
|
|
522
|
+
return typeof candidate.content === "string" && (candidate.cacheScope === "session" || candidate.cacheScope === "turn");
|
|
523
|
+
};
|
|
524
|
+
var normalizeContextBlockScopes = (legacyBlocks = void 0, scopes = void 0) => {
|
|
525
|
+
const trimmedScopes = (Array.isArray(scopes) ? scopes : []).filter(isContextBlockScope).map((scope) => ({ content: scope.content.trim(), cacheScope: scope.cacheScope })).filter((scope) => scope.content.length > 0);
|
|
526
|
+
if (trimmedScopes.length > 0) {
|
|
527
|
+
return trimmedScopes;
|
|
528
|
+
}
|
|
529
|
+
return (Array.isArray(legacyBlocks) ? legacyBlocks : []).filter((block) => typeof block === "string").map((block) => block.trim()).filter((block) => block.length > 0).map((block) => ({ content: block, cacheScope: "turn" }));
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
// packages/ai/agent/toolOutputPolicy.ts
|
|
533
|
+
var MAX_HISTORICAL_TOOL_CONTENT_CHARS = 1600;
|
|
534
|
+
var MAX_IN_TURN_TOOL_CONTENT_CHARS = 4e3;
|
|
535
|
+
var FRESH_TOOL_OUTPUT_MAX_CHARS = 32e3;
|
|
536
|
+
var DEFAULT_TOOL_OUTPUT_PROFILE = {
|
|
537
|
+
maxChars: MAX_IN_TURN_TOOL_CONTENT_CHARS,
|
|
538
|
+
headRatio: 0.5
|
|
539
|
+
};
|
|
540
|
+
var TOOL_OUTPUT_PROFILES = {
|
|
541
|
+
readFile: { maxChars: 4800, headRatio: 0.68, historicalMaxChars: 4800 },
|
|
542
|
+
read_file: { maxChars: 4800, headRatio: 0.68, historicalMaxChars: 4800 },
|
|
543
|
+
readWorkspaceFile: { maxChars: 4800, headRatio: 0.68, historicalMaxChars: 4800 },
|
|
544
|
+
searchFiles: { maxChars: 3600, headRatio: 0.78 },
|
|
545
|
+
search_files: { maxChars: 3600, headRatio: 0.78 },
|
|
546
|
+
listFiles: { maxChars: 2800, headRatio: 0.85 },
|
|
547
|
+
globFiles: { maxChars: 2800, headRatio: 0.85 },
|
|
548
|
+
execShell: { maxChars: 4e3, headRatio: 0.35 },
|
|
549
|
+
runCommand: { maxChars: 4e3, headRatio: 0.35 },
|
|
550
|
+
launchProcess: { maxChars: 2800, headRatio: 0.35 },
|
|
551
|
+
editFile: { maxChars: 2800, headRatio: 0.62 },
|
|
552
|
+
writeFile: { maxChars: 2400, headRatio: 0.62 },
|
|
553
|
+
readPastedText: { maxChars: 4800, headRatio: 0.5, historicalMaxChars: 4800 }
|
|
554
|
+
};
|
|
555
|
+
function resolveToolOutputProfile(toolName) {
|
|
556
|
+
return (toolName ? TOOL_OUTPUT_PROFILES[toolName] : void 0) ?? DEFAULT_TOOL_OUTPUT_PROFILE;
|
|
557
|
+
}
|
|
558
|
+
function resolveHistoricalToolContentCap(toolName, historicalMaxChars) {
|
|
559
|
+
const profile = toolName ? TOOL_OUTPUT_PROFILES[toolName] : void 0;
|
|
560
|
+
return profile?.historicalMaxChars !== void 0 ? Math.max(historicalMaxChars, profile.historicalMaxChars) : historicalMaxChars;
|
|
561
|
+
}
|
|
562
|
+
function projectToolMessageContent(input) {
|
|
563
|
+
const { content, isFresh, toolName, historicalMaxChars } = input;
|
|
564
|
+
if (isFresh) {
|
|
565
|
+
if (content.length <= FRESH_TOOL_OUTPUT_MAX_CHARS) return content;
|
|
566
|
+
return clipToolText(
|
|
567
|
+
content,
|
|
568
|
+
FRESH_TOOL_OUTPUT_MAX_CHARS,
|
|
569
|
+
resolveToolOutputProfile(toolName).headRatio,
|
|
570
|
+
`
|
|
571
|
+
\u2026[\u622A\u65AD\uFF0C\u539F\u59CB\u957F\u5EA6 ${content.length} \u5B57\u7B26]`
|
|
572
|
+
);
|
|
573
|
+
}
|
|
574
|
+
const cap = resolveHistoricalToolContentCap(toolName, historicalMaxChars);
|
|
575
|
+
if (content.length <= cap) return content;
|
|
576
|
+
return content.slice(0, cap) + `
|
|
577
|
+
\u2026[\u622A\u65AD\uFF0C\u539F\u59CB\u957F\u5EA6 ${content.length} \u5B57\u7B26]`;
|
|
578
|
+
}
|
|
579
|
+
function clipToolText(content, maxChars, headRatio, marker = "\n\n[... tool output middle omitted; head/tail preserved ...]\n\n") {
|
|
580
|
+
const normalized = content.replace(/\r\n/g, "\n").trim();
|
|
581
|
+
if (normalized.length <= maxChars) return normalized;
|
|
582
|
+
if (maxChars <= marker.length + 2) return normalized.slice(0, maxChars);
|
|
583
|
+
const available = maxChars - marker.length;
|
|
584
|
+
const headChars = Math.max(1, Math.floor(available * headRatio));
|
|
585
|
+
const tailChars = Math.max(1, available - headChars);
|
|
586
|
+
return `${normalized.slice(0, headChars)}${marker}${normalized.slice(-tailChars)}`;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// packages/agent-runtime/localAutoCompaction.ts
|
|
590
|
+
function toPlanCompressionMessages(history) {
|
|
591
|
+
return history.map((message, index) => ({
|
|
592
|
+
id: `local-${index}`,
|
|
593
|
+
role: message.role,
|
|
594
|
+
content: message.content,
|
|
595
|
+
...Array.isArray(message.tool_calls) ? { tool_calls: message.tool_calls } : {}
|
|
596
|
+
}));
|
|
597
|
+
}
|
|
598
|
+
function buildLocalSummaryHistoryMessage(summary) {
|
|
599
|
+
const layer = buildDialogSummaryLayer({ summary });
|
|
600
|
+
return {
|
|
601
|
+
role: "user",
|
|
602
|
+
content: layer?.content ?? `--- \u5386\u53F2\u5BF9\u8BDD\u6458\u8981 ---
|
|
603
|
+
${summary.trim()}`
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
function projectHistoryWithSummary(args) {
|
|
607
|
+
const bridged = toPlanCompressionMessages(args.history);
|
|
608
|
+
let startIndex = 0;
|
|
609
|
+
if (args.summarizedBeforeId) {
|
|
610
|
+
const found = bridged.findIndex((m) => m.id === args.summarizedBeforeId);
|
|
611
|
+
if (found !== -1) startIndex = found + 1;
|
|
612
|
+
}
|
|
613
|
+
return [
|
|
614
|
+
buildLocalSummaryHistoryMessage(args.summary),
|
|
615
|
+
...args.history.slice(startIndex)
|
|
616
|
+
];
|
|
617
|
+
}
|
|
618
|
+
var COLD_RESUME_IDLE_MS = 60 * 60 * 1e3;
|
|
619
|
+
function isColdResume(history, nowMs) {
|
|
620
|
+
for (let i = history.length - 1; i >= 0; i -= 1) {
|
|
621
|
+
const at = history[i]?.createdAt;
|
|
622
|
+
if (typeof at === "number" && Number.isFinite(at)) {
|
|
623
|
+
return nowMs - at > COLD_RESUME_IDLE_MS;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
return false;
|
|
627
|
+
}
|
|
628
|
+
async function maybeAutoCompactLocalHistory(args) {
|
|
629
|
+
const { adapter, dialogId, history } = args;
|
|
630
|
+
const unchanged = () => ({
|
|
631
|
+
history,
|
|
632
|
+
compressed: false,
|
|
633
|
+
summaryGenerated: false
|
|
634
|
+
});
|
|
635
|
+
if (!dialogId || history.length === 0 || typeof adapter.loadDialogSummary !== "function" || typeof adapter.saveDialogSummary !== "function") {
|
|
636
|
+
return unchanged();
|
|
637
|
+
}
|
|
638
|
+
let stored = null;
|
|
639
|
+
try {
|
|
640
|
+
stored = await adapter.loadDialogSummary(dialogId);
|
|
641
|
+
} catch (error) {
|
|
642
|
+
console.warn("[localLoop] loadDialogSummary failed:", error);
|
|
643
|
+
return unchanged();
|
|
644
|
+
}
|
|
645
|
+
const existingSummary = typeof stored?.summary === "string" ? stored.summary : "";
|
|
646
|
+
const summarizedBeforeId = typeof stored?.summarizedBeforeId === "string" ? stored.summarizedBeforeId : void 0;
|
|
647
|
+
const contextWindow = typeof args.contextWindow === "number" && Number.isFinite(args.contextWindow) && args.contextWindow > 0 ? args.contextWindow : getModelContextWindow(args.model ?? "");
|
|
648
|
+
const allMsgs = toPlanCompressionMessages(history);
|
|
649
|
+
const coldResume = isColdResume(history, args.now?.() ?? Date.now());
|
|
650
|
+
const plan = planCompression({
|
|
651
|
+
allMsgs,
|
|
652
|
+
summarizedBeforeId,
|
|
653
|
+
summary: existingSummary,
|
|
654
|
+
contextWindow,
|
|
655
|
+
// 防死亡螺旋:用 summary 长度作为上次压缩后的基线。如果新内容没让
|
|
656
|
+
// totalUsed 比 summary 本身增长超过 minNewTokens,不重复触发。
|
|
657
|
+
lastCompactedTokenCount: existingSummary ? estimateTokenCount(existingSummary) : void 0,
|
|
658
|
+
...coldResume ? { force: true, reason: "context_budget" } : {}
|
|
659
|
+
});
|
|
660
|
+
const projectExisting = () => {
|
|
661
|
+
if (!existingSummary.trim()) return unchanged();
|
|
662
|
+
return {
|
|
663
|
+
history: projectHistoryWithSummary({
|
|
664
|
+
history,
|
|
665
|
+
summary: existingSummary,
|
|
666
|
+
summarizedBeforeId
|
|
667
|
+
}),
|
|
668
|
+
compressed: true,
|
|
669
|
+
summaryGenerated: false
|
|
670
|
+
};
|
|
671
|
+
};
|
|
672
|
+
if (!plan.shouldCompress) {
|
|
673
|
+
return projectExisting();
|
|
674
|
+
}
|
|
675
|
+
try {
|
|
676
|
+
const provider = await args.resolveProvider();
|
|
677
|
+
const msgsToCompress = plan.msgsToCompress;
|
|
678
|
+
const messagesText = formatMessagesForSummaryWithTruncation(msgsToCompress);
|
|
679
|
+
const fileOpsText = formatFileOperationsFromMessages(
|
|
680
|
+
msgsToCompress,
|
|
681
|
+
canonicalizeToolName
|
|
682
|
+
);
|
|
683
|
+
const promptContent = buildCompactionUserContent({
|
|
684
|
+
previousSummary: existingSummary,
|
|
685
|
+
messagesText,
|
|
686
|
+
fileOpsText
|
|
687
|
+
});
|
|
688
|
+
const result = await provider.complete([
|
|
689
|
+
{ role: "system", content: COMPACTION_SUMMARY_SYSTEM_PROMPT },
|
|
690
|
+
{ role: "user", content: promptContent }
|
|
691
|
+
]);
|
|
692
|
+
const newSummary = typeof result.content === "string" ? result.content.trim() : "";
|
|
693
|
+
if (!newSummary) {
|
|
694
|
+
console.warn(
|
|
695
|
+
"[localLoop] auto-compaction produced empty summary; keeping prior projection"
|
|
696
|
+
);
|
|
697
|
+
return projectExisting();
|
|
698
|
+
}
|
|
699
|
+
await adapter.saveDialogSummary({
|
|
700
|
+
dialogId,
|
|
701
|
+
summary: newSummary,
|
|
702
|
+
summarizedBeforeId: plan.newSummarizedBeforeId
|
|
703
|
+
});
|
|
704
|
+
const metrics = buildCompactionMetricsFromPlan({
|
|
705
|
+
reason: coldResume ? "cold_resume" : "context_budget",
|
|
706
|
+
previousSummary: existingSummary,
|
|
707
|
+
plan,
|
|
708
|
+
newSummary,
|
|
709
|
+
summaryUsage: result.usage
|
|
710
|
+
});
|
|
711
|
+
console.log(formatCompactionMetricsLog(metrics));
|
|
712
|
+
return {
|
|
713
|
+
history: projectHistoryWithSummary({
|
|
714
|
+
history,
|
|
715
|
+
summary: newSummary,
|
|
716
|
+
summarizedBeforeId: plan.newSummarizedBeforeId
|
|
717
|
+
}),
|
|
718
|
+
compressed: true,
|
|
719
|
+
summaryGenerated: true,
|
|
720
|
+
...result.usage ? { usage: result.usage } : {},
|
|
721
|
+
metrics
|
|
722
|
+
};
|
|
723
|
+
} catch (error) {
|
|
724
|
+
console.warn("[localLoop] auto-compaction failed:", error);
|
|
725
|
+
return projectExisting();
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
// packages/agent-runtime/localLoop.ts
|
|
730
|
+
var LOCAL_AGENT_CONFIG_MISSING_CODE = "LOCAL_AGENT_CONFIG_MISSING";
|
|
731
|
+
var EMPTY_ASSISTANT_REPAIR_PROMPT = "\u8BF7\u7ED9\u51FA\u660E\u786E\u7684\u6587\u5B57\u56DE\u7B54\u6216\u6267\u884C\u4E0B\u4E00\u6B65\uFF1A\u5982\u679C\u4EFB\u52A1\u5DF2\u5B8C\u6210\uFF0C\u8BF7\u76F4\u63A5\u603B\u7ED3\u7ED3\u679C\uFF1B\u5982\u679C\u9700\u8981\u8C03\u7528\u5DE5\u5177\uFF0C\u8BF7\u76F4\u63A5\u8F93\u51FA tool_calls\u3002\u8BF7\u5207\u52FF\u8FD4\u56DE\u7A7A\u5185\u5BB9\u3002";
|
|
732
|
+
var EMPTY_ASSISTANT_FALLBACK_MESSAGE = "\u6A21\u578B\u8FDE\u7EED\u8FD4\u56DE\u7A7A\u6D88\u606F\uFF0C\u5F53\u524D\u4EFB\u52A1\u672A\u5B8C\u6210\u3002\u8BF7\u91CD\u8BD5\u5F53\u524D\u6B65\u9AA4\uFF0C\u6216\u7ED9\u51FA\u66F4\u5177\u4F53\u7684\u4FEE\u6539\u8303\u56F4\u3002";
|
|
733
|
+
var LENGTH_TRUNCATED_FALLBACK_MESSAGE = "\u8F93\u51FA\u8FBE\u5230\u957F\u5EA6\u4E0A\u9650\u88AB\u622A\u65AD\uFF0C\u5EFA\u8BAE\u7F29\u77ED\u4EFB\u52A1\u6216\u63D0\u9AD8\u8F93\u51FA\u4E0A\u9650\u3002";
|
|
734
|
+
var STREAM_TRUNCATED_FALLBACK_MESSAGE = "\u4E0A\u6E38\u54CD\u5E94\u6D41\u5728\u6536\u5C3E\u524D\u88AB\u4E2D\u65AD\uFF08\u672A\u6536\u5230\u7ED3\u675F\u6807\u8BB0\uFF09\uFF0C\u672C\u8F6E\u8F93\u51FA\u4E0D\u5B8C\u6574\u3002\u8BF7\u91CD\u8BD5\u5F53\u524D\u6B65\u9AA4\u3002";
|
|
735
|
+
function resolveEmptyAssistantOutcome(args) {
|
|
736
|
+
if (args.hasToolCalls || args.hasVisibleOutput) return { kind: "ok" };
|
|
737
|
+
if (args.finishReason === "length") return { kind: "fallback", reason: "length_truncated" };
|
|
738
|
+
if (!args.repairUsed) return { kind: "repair" };
|
|
739
|
+
if (!args.finishReason && !args.streamComplete) {
|
|
740
|
+
return { kind: "fallback", reason: "stream_truncated" };
|
|
741
|
+
}
|
|
742
|
+
return { kind: "fallback", reason: "empty_completion" };
|
|
743
|
+
}
|
|
744
|
+
function resolveEmptyAssistantFallbackMessage(reason) {
|
|
745
|
+
if (reason === "length_truncated") return LENGTH_TRUNCATED_FALLBACK_MESSAGE;
|
|
746
|
+
if (reason === "stream_truncated") return STREAM_TRUNCATED_FALLBACK_MESSAGE;
|
|
747
|
+
return EMPTY_ASSISTANT_FALLBACK_MESSAGE;
|
|
748
|
+
}
|
|
749
|
+
function hasAssistantVisibleOutput(content) {
|
|
750
|
+
if (typeof content === "string") return content.trim().length > 0;
|
|
751
|
+
if (!Array.isArray(content)) return false;
|
|
752
|
+
return content.some((part) => {
|
|
753
|
+
if (part?.type === "text" && String(part.text ?? "").trim()) return true;
|
|
754
|
+
if (part?.type === "image_url") {
|
|
755
|
+
const url = part?.image_url?.url;
|
|
756
|
+
return typeof url === "string" && url.trim().length > 0;
|
|
757
|
+
}
|
|
758
|
+
return false;
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
function formatToolExecutionError(args) {
|
|
762
|
+
const message = toErrorMessage(args.error);
|
|
763
|
+
return `${args.toolName} failed: ${message}`;
|
|
764
|
+
}
|
|
765
|
+
function formatStructuredToolExecutionError(args) {
|
|
766
|
+
if (!args.error || typeof args.error !== "object") return null;
|
|
767
|
+
const error = args.error;
|
|
768
|
+
if (typeof error.code !== "string") return null;
|
|
769
|
+
return JSON.stringify({
|
|
770
|
+
error: error.code,
|
|
771
|
+
message: typeof error.message === "string" ? error.message : formatToolExecutionError(args),
|
|
772
|
+
...error.policy && typeof error.policy === "object" ? { policy: error.policy } : {},
|
|
773
|
+
...error.permissionRequest && typeof error.permissionRequest === "object" ? { permissionRequest: error.permissionRequest } : {}
|
|
774
|
+
});
|
|
775
|
+
}
|
|
776
|
+
function shouldReturnToolExecutionErrors(adapter) {
|
|
777
|
+
return adapter.capabilities.includes("local-tools");
|
|
778
|
+
}
|
|
779
|
+
function emitToolEvent(input, event) {
|
|
780
|
+
input.onToolEvent?.(event);
|
|
781
|
+
}
|
|
782
|
+
function emitLoopEvent(input, event) {
|
|
783
|
+
if (!input.onLoopEvent) return;
|
|
784
|
+
try {
|
|
785
|
+
input.onLoopEvent(event);
|
|
786
|
+
} catch {
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
var LLM_REQUEST_TIMEOUT = "LLM_REQUEST_TIMEOUT";
|
|
790
|
+
var LOCAL_TURN_ABORTED_CODE = "LOCAL_TURN_ABORTED";
|
|
791
|
+
function buildAbortedError() {
|
|
792
|
+
const error = new Error("local agent turn aborted by user");
|
|
793
|
+
error.code = LOCAL_TURN_ABORTED_CODE;
|
|
794
|
+
return error;
|
|
795
|
+
}
|
|
796
|
+
function throwIfAborted(input) {
|
|
797
|
+
if (input.abortSignal?.aborted) throw buildAbortedError();
|
|
798
|
+
}
|
|
799
|
+
async function raceWithAbort(input, promise, pendingToolName) {
|
|
800
|
+
const signal = input.abortSignal;
|
|
801
|
+
if (!signal) return promise;
|
|
802
|
+
let abortListener;
|
|
803
|
+
const abortPromise = new Promise((_resolve, reject) => {
|
|
804
|
+
abortListener = () => {
|
|
805
|
+
const error = buildAbortedError();
|
|
806
|
+
if (pendingToolName) error.pendingToolName = pendingToolName;
|
|
807
|
+
promise.catch(() => {
|
|
808
|
+
});
|
|
809
|
+
reject(error);
|
|
810
|
+
};
|
|
811
|
+
if (signal.aborted) {
|
|
812
|
+
abortListener();
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
signal.addEventListener("abort", abortListener, { once: true });
|
|
816
|
+
});
|
|
817
|
+
try {
|
|
818
|
+
return await Promise.race([promise, abortPromise]);
|
|
819
|
+
} finally {
|
|
820
|
+
if (signal && abortListener) signal.removeEventListener("abort", abortListener);
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
function resolveLlmRequestTimeoutMs(input) {
|
|
824
|
+
const raw = input.llmRequestTimeoutMs ?? input.timeoutMs;
|
|
825
|
+
if (typeof raw !== "number" || !Number.isFinite(raw) || raw <= 0) return void 0;
|
|
826
|
+
return raw;
|
|
827
|
+
}
|
|
828
|
+
async function runCompleteWithTimeout(args) {
|
|
829
|
+
const { provider, messages, options, timeoutMs, round, input, context } = args;
|
|
830
|
+
emitLoopEvent(input, {
|
|
831
|
+
kind: "llm-start",
|
|
832
|
+
round,
|
|
833
|
+
atMs: Date.now(),
|
|
834
|
+
...context ? { context } : {}
|
|
835
|
+
});
|
|
836
|
+
const complete = provider.complete(messages, options);
|
|
837
|
+
let ok = false;
|
|
838
|
+
const signal = input.abortSignal;
|
|
839
|
+
let abortListener;
|
|
840
|
+
const racers = [];
|
|
841
|
+
if (signal) {
|
|
842
|
+
racers.push(
|
|
843
|
+
new Promise((_resolve, reject) => {
|
|
844
|
+
abortListener = () => reject(buildAbortedError());
|
|
845
|
+
if (signal.aborted) {
|
|
846
|
+
abortListener();
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
signal.addEventListener("abort", abortListener, { once: true });
|
|
850
|
+
})
|
|
851
|
+
);
|
|
852
|
+
}
|
|
853
|
+
let timer;
|
|
854
|
+
if (typeof timeoutMs === "number") {
|
|
855
|
+
racers.push(
|
|
856
|
+
new Promise((_resolve, reject) => {
|
|
857
|
+
timer = setTimeout(() => {
|
|
858
|
+
reject(new Error(`__llm_timeout__:${timeoutMs}`));
|
|
859
|
+
}, timeoutMs);
|
|
860
|
+
})
|
|
861
|
+
);
|
|
862
|
+
}
|
|
863
|
+
let result;
|
|
864
|
+
try {
|
|
865
|
+
result = racers.length === 0 ? await complete : await Promise.race([complete, ...racers]);
|
|
866
|
+
ok = true;
|
|
867
|
+
return result;
|
|
868
|
+
} catch (error) {
|
|
869
|
+
const isTimeout = error instanceof Error && error.message.startsWith("__llm_timeout__:");
|
|
870
|
+
if (!isTimeout) throw error;
|
|
871
|
+
const timeoutError = new Error(
|
|
872
|
+
`LLM request timed out after ${timeoutMs}ms (round ${round})`
|
|
873
|
+
);
|
|
874
|
+
timeoutError.code = LLM_REQUEST_TIMEOUT;
|
|
875
|
+
throw timeoutError;
|
|
876
|
+
} finally {
|
|
877
|
+
if (timer) clearTimeout(timer);
|
|
878
|
+
if (signal && abortListener) signal.removeEventListener("abort", abortListener);
|
|
879
|
+
const usage = result?.usage;
|
|
880
|
+
const cacheHit = Number(usage?.cache_read_input_tokens ?? usage?.prompt_cache_hit_tokens ?? 0);
|
|
881
|
+
const cacheMiss = Number(usage?.cache_creation_input_tokens ?? usage?.prompt_cache_miss_tokens ?? 0);
|
|
882
|
+
const inputTokens = Number(usage?.input_tokens ?? usage?.prompt_tokens ?? 0);
|
|
883
|
+
const outputTokens = Number(usage?.output_tokens ?? usage?.completion_tokens ?? 0);
|
|
884
|
+
emitLoopEvent(input, {
|
|
885
|
+
kind: "llm-end",
|
|
886
|
+
round,
|
|
887
|
+
atMs: Date.now(),
|
|
888
|
+
ok,
|
|
889
|
+
...inputTokens > 0 || cacheHit > 0 || cacheMiss > 0 ? {
|
|
890
|
+
cache: {
|
|
891
|
+
inputTokens,
|
|
892
|
+
outputTokens,
|
|
893
|
+
cacheHitTokens: cacheHit,
|
|
894
|
+
cacheMissTokens: cacheMiss,
|
|
895
|
+
hitRatio: inputTokens > 0 ? Math.round(cacheHit / inputTokens * 1e4) / 1e4 : 0
|
|
896
|
+
}
|
|
897
|
+
} : {}
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
function clip2(value, max = 240) {
|
|
902
|
+
return clipCompactText(value, max);
|
|
903
|
+
}
|
|
904
|
+
function summarizeToolResult(content, metadata) {
|
|
905
|
+
const parts = [];
|
|
906
|
+
const exitCode = metadata?.exitCode;
|
|
907
|
+
if (typeof exitCode === "number") parts.push(`exit=${exitCode}`);
|
|
908
|
+
if (typeof content === "string") {
|
|
909
|
+
const trimmed = content.trim();
|
|
910
|
+
if (trimmed) {
|
|
911
|
+
const lines = trimmed.split(/\r?\n/).length;
|
|
912
|
+
parts.push(`${lines} line${lines === 1 ? "" : "s"}`);
|
|
913
|
+
parts.push(`${trimmed.length} chars`);
|
|
914
|
+
const tail = clip2(trimmed.slice(-160), 160);
|
|
915
|
+
if (tail) parts.push(`tail="${tail}"`);
|
|
916
|
+
} else {
|
|
917
|
+
parts.push("empty");
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
return parts.join(" ");
|
|
921
|
+
}
|
|
922
|
+
function formatToolMessageContent(args) {
|
|
923
|
+
if (args.toolName !== "listFiles" && args.toolName !== "globFiles" && args.toolName !== "searchFiles" && args.toolName !== "readFile" || !args.metadata || Object.keys(args.metadata).length === 0) {
|
|
924
|
+
return args.content;
|
|
925
|
+
}
|
|
926
|
+
return `${args.content}
|
|
927
|
+
|
|
928
|
+
[tool metadata]
|
|
929
|
+
${JSON.stringify(args.metadata)}`;
|
|
930
|
+
}
|
|
931
|
+
function blocksToOpenAiMessages(blocks) {
|
|
932
|
+
const out = [];
|
|
933
|
+
let text = "";
|
|
934
|
+
let reasoning = "";
|
|
935
|
+
let pendingToolCalls = [];
|
|
936
|
+
let pendingToolResults = [];
|
|
937
|
+
const flushSegment = () => {
|
|
938
|
+
if (text === "" && pendingToolCalls.length === 0 && reasoning === "") return;
|
|
939
|
+
out.push({
|
|
940
|
+
role: "assistant",
|
|
941
|
+
content: text || null,
|
|
942
|
+
...reasoning ? { reasoning_content: reasoning } : {},
|
|
943
|
+
...pendingToolCalls.length > 0 ? { tool_calls: pendingToolCalls } : {}
|
|
944
|
+
});
|
|
945
|
+
for (let i = 0; i < pendingToolCalls.length; i += 1) {
|
|
946
|
+
const tc = pendingToolCalls[i];
|
|
947
|
+
const res = pendingToolResults[i];
|
|
948
|
+
out.push({
|
|
949
|
+
role: "tool",
|
|
950
|
+
content: formatToolMessageContent({
|
|
951
|
+
toolName: tc.function.name,
|
|
952
|
+
content: res?.content ?? "",
|
|
953
|
+
...res?.metadata ? { metadata: res.metadata } : {}
|
|
954
|
+
}),
|
|
955
|
+
tool_call_id: tc.id,
|
|
956
|
+
toolName: tc.function.name,
|
|
957
|
+
...res?.metadata ? { tool_result_metadata: res.metadata } : {}
|
|
958
|
+
});
|
|
959
|
+
}
|
|
960
|
+
text = "";
|
|
961
|
+
reasoning = "";
|
|
962
|
+
pendingToolCalls = [];
|
|
963
|
+
pendingToolResults = [];
|
|
964
|
+
};
|
|
965
|
+
for (const block of blocks) {
|
|
966
|
+
if (block.type === "text") {
|
|
967
|
+
if (pendingToolCalls.length > 0) {
|
|
968
|
+
flushSegment();
|
|
969
|
+
}
|
|
970
|
+
text += block.text;
|
|
971
|
+
continue;
|
|
972
|
+
}
|
|
973
|
+
if (block.type === "thinking") {
|
|
974
|
+
reasoning += block.thinking;
|
|
975
|
+
continue;
|
|
976
|
+
}
|
|
977
|
+
if (block.type === "toolCall") {
|
|
978
|
+
pendingToolCalls.push(block.toolCall);
|
|
979
|
+
pendingToolResults.push({
|
|
980
|
+
content: block.result?.content ?? "",
|
|
981
|
+
...block.result?.metadata ? { metadata: block.result.metadata } : {}
|
|
982
|
+
});
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
flushSegment();
|
|
986
|
+
return out;
|
|
987
|
+
}
|
|
988
|
+
function buildActionGate(args) {
|
|
989
|
+
const gate = readActionGate(args.metadata?.actionGate);
|
|
990
|
+
if (!gate) return null;
|
|
991
|
+
if (gate.kind === "handoff" && !readCommandActionGatePayload(gate.payload)) return null;
|
|
992
|
+
return {
|
|
993
|
+
...gate,
|
|
994
|
+
toolName: args.toolName,
|
|
995
|
+
toolCallId: args.toolCallId
|
|
996
|
+
};
|
|
997
|
+
}
|
|
998
|
+
var TOOL_METADATA_KEYS = [
|
|
999
|
+
"path",
|
|
1000
|
+
"query",
|
|
1001
|
+
"effectivePattern",
|
|
1002
|
+
"startLine",
|
|
1003
|
+
"endLine",
|
|
1004
|
+
"totalLines",
|
|
1005
|
+
"totalBytes",
|
|
1006
|
+
"bytes",
|
|
1007
|
+
"totalChars",
|
|
1008
|
+
"count",
|
|
1009
|
+
"matchCount",
|
|
1010
|
+
"matchedFiles",
|
|
1011
|
+
"truncated",
|
|
1012
|
+
"limitedByMaxResults",
|
|
1013
|
+
"limitedByMaxDepth",
|
|
1014
|
+
"visitedEntries",
|
|
1015
|
+
"maxResults",
|
|
1016
|
+
"exitCode",
|
|
1017
|
+
"status",
|
|
1018
|
+
"timedOut",
|
|
1019
|
+
"aborted",
|
|
1020
|
+
"replacements",
|
|
1021
|
+
"code",
|
|
1022
|
+
"error",
|
|
1023
|
+
"message",
|
|
1024
|
+
"warnings",
|
|
1025
|
+
"pasteId",
|
|
1026
|
+
"source"
|
|
1027
|
+
];
|
|
1028
|
+
function trimHistoryToContextBudget(history, model) {
|
|
1029
|
+
if (history.length === 0) return { history, droppedCount: 0 };
|
|
1030
|
+
const { rawMessageBudget } = planContextUsage({
|
|
1031
|
+
contextWindow: getModelContextWindow(model ?? ""),
|
|
1032
|
+
summaryTokens: 0,
|
|
1033
|
+
// localLoop 没有 web 端的负载分档器;medium 是中性默认值,
|
|
1034
|
+
// 不为了省几个 token 在这里复制一份分类逻辑。
|
|
1035
|
+
recentLoad: "medium"
|
|
1036
|
+
});
|
|
1037
|
+
const messageTokens = (message) => {
|
|
1038
|
+
const toolCalls = message.tool_calls;
|
|
1039
|
+
return estimateTokenCount(contentAsText(message.content)) + (Array.isArray(toolCalls) ? estimateTokenCount(JSON.stringify(toolCalls)) : 0);
|
|
1040
|
+
};
|
|
1041
|
+
let used = 0;
|
|
1042
|
+
let start = history.length;
|
|
1043
|
+
for (let i = history.length - 1; i >= 0; i -= 1) {
|
|
1044
|
+
const cost = messageTokens(history[i]);
|
|
1045
|
+
if (used + cost > rawMessageBudget && start < history.length) break;
|
|
1046
|
+
used += cost;
|
|
1047
|
+
start = i;
|
|
1048
|
+
}
|
|
1049
|
+
if (start === 0) return { history, droppedCount: 0 };
|
|
1050
|
+
return {
|
|
1051
|
+
history: sanitizeToolCallPairing(history.slice(start)),
|
|
1052
|
+
droppedCount: start
|
|
1053
|
+
};
|
|
1054
|
+
}
|
|
1055
|
+
function contentAsText(content) {
|
|
1056
|
+
if (typeof content === "string") return content;
|
|
1057
|
+
if (!Array.isArray(content)) return "";
|
|
1058
|
+
return content.map((part) => {
|
|
1059
|
+
if (part?.type === "text") return part.text;
|
|
1060
|
+
if (part?.type === "image_url") return part.image_url.url;
|
|
1061
|
+
return "";
|
|
1062
|
+
}).join("\n");
|
|
1063
|
+
}
|
|
1064
|
+
function contentCharCount(content) {
|
|
1065
|
+
if (typeof content === "string") return content.length;
|
|
1066
|
+
if (!Array.isArray(content)) return 0;
|
|
1067
|
+
return content.reduce((total, part) => {
|
|
1068
|
+
if (part?.type === "text") return total + part.text.length;
|
|
1069
|
+
if (part?.type === "image_url") return total + part.image_url.url.length;
|
|
1070
|
+
return total;
|
|
1071
|
+
}, 0);
|
|
1072
|
+
}
|
|
1073
|
+
function compactToolMetadata(metadata) {
|
|
1074
|
+
if (!metadata) return "";
|
|
1075
|
+
const selected = {};
|
|
1076
|
+
for (const key of TOOL_METADATA_KEYS) {
|
|
1077
|
+
const value = metadata[key];
|
|
1078
|
+
if (value === void 0) continue;
|
|
1079
|
+
if (typeof value === "string") {
|
|
1080
|
+
selected[key] = clipCompactText(value, 240);
|
|
1081
|
+
continue;
|
|
1082
|
+
}
|
|
1083
|
+
if (Array.isArray(value)) {
|
|
1084
|
+
selected[key] = value.slice(0, 20).map(
|
|
1085
|
+
(item) => typeof item === "string" ? clipCompactText(item, 180) : clipCompactText(JSON.stringify(item), 180)
|
|
1086
|
+
);
|
|
1087
|
+
continue;
|
|
1088
|
+
}
|
|
1089
|
+
selected[key] = value;
|
|
1090
|
+
}
|
|
1091
|
+
return Object.keys(selected).length > 0 ? clipCompactText(JSON.stringify(selected), 1200) : "";
|
|
1092
|
+
}
|
|
1093
|
+
function projectToolContentForProvider(args) {
|
|
1094
|
+
const content = args.content;
|
|
1095
|
+
if (typeof content !== "string") return content;
|
|
1096
|
+
const metadataText = compactToolMetadata(args.metadata);
|
|
1097
|
+
const embeddedMetadataIndex = metadataText ? content.indexOf("\n\n[tool metadata]\n") : -1;
|
|
1098
|
+
const contentForProjection = embeddedMetadataIndex >= 0 ? content.slice(0, embeddedMetadataIndex) : content;
|
|
1099
|
+
const metadataSuffix = metadataText ? `
|
|
1100
|
+
|
|
1101
|
+
[tool metadata]
|
|
1102
|
+
${metadataText}` : "";
|
|
1103
|
+
if (embeddedMetadataIndex >= 0 && content.length <= args.maxChars) {
|
|
1104
|
+
return content;
|
|
1105
|
+
}
|
|
1106
|
+
const headRatio = resolveToolOutputProfile(args.toolName).headRatio;
|
|
1107
|
+
const diagnostic = (clippedLength) => `[${args.label}; originalChars=${content.length}; omittedChars=${Math.max(
|
|
1108
|
+
0,
|
|
1109
|
+
content.length - clippedLength
|
|
1110
|
+
)}]`;
|
|
1111
|
+
const suffix = (clippedLength) => [diagnostic(clippedLength), metadataSuffix.trimStart()].filter(Boolean).join("\n\n");
|
|
1112
|
+
const initialBudget = Math.max(
|
|
1113
|
+
1,
|
|
1114
|
+
args.maxChars - suffix(contentForProjection.length).length - 2
|
|
1115
|
+
);
|
|
1116
|
+
let clipped = clipToolText(contentForProjection, initialBudget, headRatio);
|
|
1117
|
+
const wasClipped = clipped.length < contentForProjection.trim().length;
|
|
1118
|
+
const needsProjection = wasClipped || Boolean(metadataSuffix) || embeddedMetadataIndex >= 0;
|
|
1119
|
+
if (!needsProjection) return args.content;
|
|
1120
|
+
let projected = wasClipped || metadataSuffix ? `${clipped}
|
|
1121
|
+
|
|
1122
|
+
${suffix(clipped.length)}` : clipped;
|
|
1123
|
+
if (projected.length > args.maxChars) {
|
|
1124
|
+
const boundedBudget = Math.max(
|
|
1125
|
+
1,
|
|
1126
|
+
args.maxChars - suffix(clipped.length).length - 2
|
|
1127
|
+
);
|
|
1128
|
+
clipped = clipToolText(contentForProjection, boundedBudget, headRatio);
|
|
1129
|
+
projected = `${clipped}
|
|
1130
|
+
|
|
1131
|
+
${suffix(clipped.length)}`;
|
|
1132
|
+
}
|
|
1133
|
+
return projected.length <= args.maxChars ? projected : projected.slice(0, args.maxChars);
|
|
1134
|
+
}
|
|
1135
|
+
function summarizeHistoricalToolContent(content, toolName, metadata) {
|
|
1136
|
+
return projectToolContentForProvider({
|
|
1137
|
+
content,
|
|
1138
|
+
toolName,
|
|
1139
|
+
metadata,
|
|
1140
|
+
// Same single source of truth as the server path and the read ledgers:
|
|
1141
|
+
// read-family tools keep their profile historical cap (4800) cross-turn,
|
|
1142
|
+
// unprofiled tools fall back to the flat historical budget.
|
|
1143
|
+
maxChars: resolveHistoricalToolContentCap(toolName, MAX_HISTORICAL_TOOL_CONTENT_CHARS),
|
|
1144
|
+
label: "historical tool result truncated for the next turn"
|
|
1145
|
+
});
|
|
1146
|
+
}
|
|
1147
|
+
function prepareMessagesForProviderCall(messages) {
|
|
1148
|
+
const paired = sanitizeToolCallPairing(messages);
|
|
1149
|
+
let freshRunStart = paired.length;
|
|
1150
|
+
while (freshRunStart > 0 && paired[freshRunStart - 1].role === "tool") {
|
|
1151
|
+
freshRunStart -= 1;
|
|
1152
|
+
}
|
|
1153
|
+
let toolMessageCount = 0;
|
|
1154
|
+
let rawToolContentChars = 0;
|
|
1155
|
+
let projectedToolContentChars = 0;
|
|
1156
|
+
let truncatedToolResults = 0;
|
|
1157
|
+
const projected = paired.map((message, index) => {
|
|
1158
|
+
const { context_reference: _contextReference, ...providerMessage } = message;
|
|
1159
|
+
const sanitizedContent = providerMessage.content == null ? "" : typeof providerMessage.content === "string" ? providerMessage.content : providerMessage.content;
|
|
1160
|
+
if (providerMessage.role !== "tool") {
|
|
1161
|
+
return {
|
|
1162
|
+
...providerMessage,
|
|
1163
|
+
content: sanitizedContent
|
|
1164
|
+
};
|
|
1165
|
+
}
|
|
1166
|
+
toolMessageCount += 1;
|
|
1167
|
+
rawToolContentChars += contentCharCount(sanitizedContent);
|
|
1168
|
+
const isFresh = index >= freshRunStart;
|
|
1169
|
+
const maxChars = isFresh ? FRESH_TOOL_OUTPUT_MAX_CHARS : resolveToolOutputProfile(providerMessage.toolName).maxChars;
|
|
1170
|
+
const projectedContent = projectToolContentForProvider({
|
|
1171
|
+
content: sanitizedContent,
|
|
1172
|
+
toolName: providerMessage.toolName,
|
|
1173
|
+
metadata: providerMessage.tool_result_metadata,
|
|
1174
|
+
maxChars,
|
|
1175
|
+
label: "in-turn tool result truncated/projected before next provider call"
|
|
1176
|
+
});
|
|
1177
|
+
projectedToolContentChars += contentCharCount(projectedContent);
|
|
1178
|
+
if (contentCharCount(projectedContent) < contentCharCount(sanitizedContent)) {
|
|
1179
|
+
truncatedToolResults += 1;
|
|
1180
|
+
}
|
|
1181
|
+
return {
|
|
1182
|
+
...providerMessage,
|
|
1183
|
+
content: projectedContent
|
|
1184
|
+
};
|
|
1185
|
+
});
|
|
1186
|
+
return {
|
|
1187
|
+
messages: projected,
|
|
1188
|
+
metrics: {
|
|
1189
|
+
messageCount: projected.length,
|
|
1190
|
+
contentChars: projected.reduce((total, message) => total + contentCharCount(message.content), 0),
|
|
1191
|
+
toolMessageCount,
|
|
1192
|
+
rawToolContentChars,
|
|
1193
|
+
projectedToolContentChars,
|
|
1194
|
+
truncatedToolResults,
|
|
1195
|
+
stableContextChars: 0,
|
|
1196
|
+
dynamicContextChars: 0
|
|
1197
|
+
}
|
|
1198
|
+
};
|
|
1199
|
+
}
|
|
1200
|
+
function prepareHistoryForNextTurn(history, contextReferenceResolver) {
|
|
1201
|
+
return history.map((message) => {
|
|
1202
|
+
if (message.role === "user" && message.context_reference !== void 0 && contextReferenceResolver?.(message.context_reference)) {
|
|
1203
|
+
return { ...message, content: message.context_reference };
|
|
1204
|
+
}
|
|
1205
|
+
if (message.role !== "tool") return message;
|
|
1206
|
+
return {
|
|
1207
|
+
...message,
|
|
1208
|
+
content: summarizeHistoricalToolContent(
|
|
1209
|
+
message.content,
|
|
1210
|
+
message.toolName,
|
|
1211
|
+
message.tool_result_metadata
|
|
1212
|
+
)
|
|
1213
|
+
};
|
|
1214
|
+
});
|
|
1215
|
+
}
|
|
1216
|
+
var IMAGE_OMITTED_PLACEHOLDER2 = "[Image content omitted: model does not support image input]";
|
|
1217
|
+
function stripImagePartsFromContent2(content) {
|
|
1218
|
+
if (!Array.isArray(content)) return content;
|
|
1219
|
+
const filtered = content.filter((part) => part?.type !== "image_url");
|
|
1220
|
+
if (filtered.length === 0) return IMAGE_OMITTED_PLACEHOLDER2;
|
|
1221
|
+
return filtered;
|
|
1222
|
+
}
|
|
1223
|
+
function filterImagePartsFromMessages(messages, supportsImages) {
|
|
1224
|
+
if (supportsImages) return messages;
|
|
1225
|
+
return messages.map((msg) => ({
|
|
1226
|
+
...msg,
|
|
1227
|
+
content: stripImagePartsFromContent2(msg.content)
|
|
1228
|
+
}));
|
|
1229
|
+
}
|
|
1230
|
+
function buildMessages(args) {
|
|
1231
|
+
if (args.contextBlockScopes?.length) {
|
|
1232
|
+
const blocks2 = args.contextBlockScopes.filter((b) => b.content.trim());
|
|
1233
|
+
const stableParts = [args.prompt?.trim(), ...blocks2.filter((b) => b.cacheScope === "session").map((b) => b.content)].filter(Boolean);
|
|
1234
|
+
const dynamicParts = blocks2.filter((b) => b.cacheScope === "turn").map((b) => b.content).map((block) => block.trim()).filter(Boolean);
|
|
1235
|
+
const stableContent = stableParts.join("\n\n");
|
|
1236
|
+
const dynamicContent = dynamicParts.join("\n\n");
|
|
1237
|
+
const systemContent2 = dynamicContent ? `${stableContent}
|
|
1238
|
+
|
|
1239
|
+
${dynamicContent}` : stableContent;
|
|
1240
|
+
return {
|
|
1241
|
+
messages: [
|
|
1242
|
+
...systemContent2 ? [{
|
|
1243
|
+
role: "system",
|
|
1244
|
+
content: systemContent2,
|
|
1245
|
+
...stableContent ? { stable_prefix_chars: stableContent.length } : {}
|
|
1246
|
+
}] : [],
|
|
1247
|
+
...prepareHistoryForNextTurn(args.history, args.contextReferenceResolver),
|
|
1248
|
+
{ role: "user", content: args.input }
|
|
1249
|
+
],
|
|
1250
|
+
stableContextChars: stableContent.length,
|
|
1251
|
+
dynamicContextChars: dynamicContent.length
|
|
1252
|
+
};
|
|
1253
|
+
}
|
|
1254
|
+
const blocks = (args.contextBlocks ?? []).map((block) => block.trim()).filter(Boolean);
|
|
1255
|
+
const systemContent = [args.prompt?.trim(), ...blocks].filter(Boolean).join("\n\n");
|
|
1256
|
+
return {
|
|
1257
|
+
messages: [
|
|
1258
|
+
...systemContent ? [{ role: "system", content: systemContent }] : [],
|
|
1259
|
+
...prepareHistoryForNextTurn(args.history, args.contextReferenceResolver),
|
|
1260
|
+
{ role: "user", content: args.input }
|
|
1261
|
+
],
|
|
1262
|
+
stableContextChars: (args.prompt?.trim() ?? "").length,
|
|
1263
|
+
dynamicContextChars: blocks.join("\n\n").length
|
|
1264
|
+
};
|
|
1265
|
+
}
|
|
1266
|
+
function mergeTurnUsage(current, next) {
|
|
1267
|
+
if (!next) return current;
|
|
1268
|
+
const read = (usage) => ({
|
|
1269
|
+
input: Number(usage.input_tokens ?? usage.prompt_tokens ?? 0),
|
|
1270
|
+
output: Number(usage.output_tokens ?? usage.completion_tokens ?? 0),
|
|
1271
|
+
cacheHit: Number(
|
|
1272
|
+
usage.cache_read_input_tokens ?? usage.prompt_cache_hit_tokens ?? 0
|
|
1273
|
+
),
|
|
1274
|
+
cacheMiss: Number(
|
|
1275
|
+
usage.cache_creation_input_tokens ?? usage.prompt_cache_miss_tokens ?? 0
|
|
1276
|
+
)
|
|
1277
|
+
});
|
|
1278
|
+
const right = read(next);
|
|
1279
|
+
const left = current ? read(current) : { input: 0, output: 0, cacheHit: 0, cacheMiss: 0 };
|
|
1280
|
+
return {
|
|
1281
|
+
input_tokens: left.input + right.input,
|
|
1282
|
+
output_tokens: left.output + right.output,
|
|
1283
|
+
cache_read_input_tokens: left.cacheHit + right.cacheHit,
|
|
1284
|
+
cache_creation_input_tokens: left.cacheMiss + right.cacheMiss
|
|
1285
|
+
};
|
|
1286
|
+
}
|
|
1287
|
+
function addOutOfBandUsage(turn, extra) {
|
|
1288
|
+
if (!extra) return turn;
|
|
1289
|
+
const num = (u, ...keys) => {
|
|
1290
|
+
if (!u) return 0;
|
|
1291
|
+
for (const k of keys) {
|
|
1292
|
+
const v = Number(u[k]);
|
|
1293
|
+
if (Number.isFinite(v) && v !== 0) return v;
|
|
1294
|
+
}
|
|
1295
|
+
return 0;
|
|
1296
|
+
};
|
|
1297
|
+
return {
|
|
1298
|
+
...turn ?? {},
|
|
1299
|
+
input_tokens: num(turn, "input_tokens", "prompt_tokens") + num(extra, "input_tokens", "prompt_tokens"),
|
|
1300
|
+
output_tokens: num(turn, "output_tokens", "completion_tokens") + num(extra, "output_tokens", "completion_tokens"),
|
|
1301
|
+
cache_read_input_tokens: num(turn, "cache_read_input_tokens", "prompt_cache_hit_tokens") + num(extra, "cache_read_input_tokens", "prompt_cache_hit_tokens"),
|
|
1302
|
+
cache_creation_input_tokens: num(turn, "cache_creation_input_tokens", "prompt_cache_miss_tokens") + num(extra, "cache_creation_input_tokens", "prompt_cache_miss_tokens")
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
1305
|
+
function extractUserInputText(content) {
|
|
1306
|
+
if (typeof content === "string") return content;
|
|
1307
|
+
if (!Array.isArray(content)) return "";
|
|
1308
|
+
return content.flatMap((part) => part?.type === "text" && part.text ? [part.text] : []).join("\n").trim();
|
|
1309
|
+
}
|
|
1310
|
+
function attachDialogIdToError(error, dialogId) {
|
|
1311
|
+
if (!dialogId) return;
|
|
1312
|
+
if (typeof error === "object" && error !== null) {
|
|
1313
|
+
error.dialogId = dialogId;
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
async function persistFailedLocalTurn(args) {
|
|
1317
|
+
const errorMessage = toErrorMessage(args.error);
|
|
1318
|
+
try {
|
|
1319
|
+
const saved = await args.adapter.saveTurn({
|
|
1320
|
+
agentKey: args.agentKey,
|
|
1321
|
+
messages: args.messages,
|
|
1322
|
+
result: {
|
|
1323
|
+
content: args.partialContent || `[nolo] Agent run failed: ${errorMessage}`,
|
|
1324
|
+
model: args.model ?? "unknown",
|
|
1325
|
+
toolCallCount: args.toolCallCount ?? 0,
|
|
1326
|
+
error: true,
|
|
1327
|
+
errorMessage,
|
|
1328
|
+
...args.usage ? { usage: args.usage } : {}
|
|
1329
|
+
},
|
|
1330
|
+
...args.accountingUsage ? { accountingUsage: args.accountingUsage } : {},
|
|
1331
|
+
...args.usageRecords?.length ? { usageRecords: args.usageRecords } : {},
|
|
1332
|
+
...args.billingConfig ? { billingConfig: args.billingConfig } : {},
|
|
1333
|
+
...args.input.runtimeContext ? { runtimeContext: args.input.runtimeContext } : {},
|
|
1334
|
+
...args.input.continueDialogId ? { continueDialogId: args.input.continueDialogId } : {},
|
|
1335
|
+
...args.input.spaceId ? { spaceId: args.input.spaceId } : {},
|
|
1336
|
+
...args.input.category ? { category: args.input.category } : {},
|
|
1337
|
+
...args.input.inheritedFromDialogKey ? { inheritedFromDialogKey: args.input.inheritedFromDialogKey } : {},
|
|
1338
|
+
...args.input.parentDialogId ? { parentDialogId: args.input.parentDialogId } : {}
|
|
1339
|
+
});
|
|
1340
|
+
return saved?.dialogId;
|
|
1341
|
+
} catch {
|
|
1342
|
+
return args.input.continueDialogId;
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
function applyPersistedTurnInput(messages, persistedInput, persistedInputReference) {
|
|
1346
|
+
if (persistedInput === void 0 && persistedInputReference === void 0) {
|
|
1347
|
+
return messages;
|
|
1348
|
+
}
|
|
1349
|
+
let replaced = false;
|
|
1350
|
+
return messages.map((message) => {
|
|
1351
|
+
if (replaced || message.role !== "user") return message;
|
|
1352
|
+
replaced = true;
|
|
1353
|
+
return {
|
|
1354
|
+
...message,
|
|
1355
|
+
...persistedInput !== void 0 ? { content: persistedInput } : {},
|
|
1356
|
+
...persistedInputReference !== void 0 ? { context_reference: persistedInputReference } : {}
|
|
1357
|
+
};
|
|
1358
|
+
});
|
|
1359
|
+
}
|
|
1360
|
+
async function runLocalAgentTurn(input) {
|
|
1361
|
+
const agentConfig = await input.adapter.loadAgentConfig(input.agentRef);
|
|
1362
|
+
if (!agentConfig) {
|
|
1363
|
+
const error = new Error(
|
|
1364
|
+
`agentRef "${input.agentRef}" \u672A\u5339\u914D\u5230\u672C\u5730 agent\u3002\u8FD9\u4E0D\u662F\u914D\u7F6E\u7F3A\u5931\uFF0C\u8BF7\u7528 listAgents \u67E5\u770B\u53EF\u7528 agent\uFF0C\u518D\u7528 readAgent \u89E3\u6790 agentKey\uFF0C\u52FF\u624B\u5DE5\u62FC key\u3002`
|
|
1365
|
+
);
|
|
1366
|
+
error.code = LOCAL_AGENT_CONFIG_MISSING_CODE;
|
|
1367
|
+
error.agentRef = input.agentRef;
|
|
1368
|
+
throw error;
|
|
1369
|
+
}
|
|
1370
|
+
const rawBillingConfig = agentConfig.rawRecord ?? {};
|
|
1371
|
+
const billingConfig = {
|
|
1372
|
+
model: agentConfig.model || "unknown",
|
|
1373
|
+
...agentConfig.provider ? { provider: agentConfig.provider } : {},
|
|
1374
|
+
...agentConfig.apiSource ? { apiSource: agentConfig.apiSource } : {},
|
|
1375
|
+
...agentConfig.apiKeyRef !== void 0 ? { apiKeyRef: agentConfig.apiKeyRef } : {},
|
|
1376
|
+
...typeof rawBillingConfig.inputPrice === "number" ? { inputPrice: rawBillingConfig.inputPrice } : {},
|
|
1377
|
+
...typeof rawBillingConfig.outputPrice === "number" ? { outputPrice: rawBillingConfig.outputPrice } : {},
|
|
1378
|
+
...rawBillingConfig.sharingLevel ? { sharingLevel: rawBillingConfig.sharingLevel } : {},
|
|
1379
|
+
id: agentConfig.key,
|
|
1380
|
+
...typeof rawBillingConfig.userId === "string" ? { userId: rawBillingConfig.userId } : {}
|
|
1381
|
+
};
|
|
1382
|
+
let history = [];
|
|
1383
|
+
try {
|
|
1384
|
+
history = input.continueDialogId ? await input.adapter.loadDialogHistory(input.continueDialogId) : [];
|
|
1385
|
+
} catch (error) {
|
|
1386
|
+
const dialogId = await persistFailedLocalTurn({
|
|
1387
|
+
adapter: input.adapter,
|
|
1388
|
+
agentKey: agentConfig.key,
|
|
1389
|
+
messages: [{ role: "user", content: input.input }],
|
|
1390
|
+
error,
|
|
1391
|
+
model: agentConfig.model,
|
|
1392
|
+
input
|
|
1393
|
+
});
|
|
1394
|
+
attachDialogIdToError(error, dialogId);
|
|
1395
|
+
throw error;
|
|
1396
|
+
}
|
|
1397
|
+
const identityBlock = buildIdentityBlock({
|
|
1398
|
+
agentName: agentConfig.name,
|
|
1399
|
+
agentId: agentConfig.key,
|
|
1400
|
+
model: agentConfig.model
|
|
1401
|
+
});
|
|
1402
|
+
const agentTools = canonicalizeToolNames(
|
|
1403
|
+
agentConfig.exposedToolNames ?? agentConfig.toolNames ?? []
|
|
1404
|
+
);
|
|
1405
|
+
const guidanceBlocks = buildRuntimeGuidanceBlocks(agentTools);
|
|
1406
|
+
const toolGuidedSections = resolveToolGuidedSections(agentTools);
|
|
1407
|
+
const guidanceScopes = [
|
|
1408
|
+
...input.userLanguage?.trim() ? [buildUserResponseLanguageContext({ language: input.userLanguage })] : [],
|
|
1409
|
+
guidanceBlocks.startupProtocol,
|
|
1410
|
+
guidanceBlocks.contextLayerContract,
|
|
1411
|
+
guidanceBlocks.emailRegistrationWorkflow,
|
|
1412
|
+
guidanceBlocks.webResearchToolPolicy,
|
|
1413
|
+
...Object.values(toolGuidedSections)
|
|
1414
|
+
].map((content) => content.trim()).filter((content) => content.length > 0).map((content) => ({ content, cacheScope: "session" }));
|
|
1415
|
+
const currentTimeScope = [
|
|
1416
|
+
{ content: buildCurrentTimeBlock(/* @__PURE__ */ new Date(), void 0), cacheScope: "turn" }
|
|
1417
|
+
];
|
|
1418
|
+
const callerScopes = normalizeContextBlockScopes(
|
|
1419
|
+
input.contextBlocks,
|
|
1420
|
+
input.contextBlockScopes
|
|
1421
|
+
);
|
|
1422
|
+
const mergedContextBlockScopes = [
|
|
1423
|
+
{ content: identityBlock, cacheScope: "session" },
|
|
1424
|
+
...guidanceScopes,
|
|
1425
|
+
...currentTimeScope,
|
|
1426
|
+
...callerScopes
|
|
1427
|
+
];
|
|
1428
|
+
let resolvedProvider;
|
|
1429
|
+
const resolveProviderOnce = async () => {
|
|
1430
|
+
if (!resolvedProvider) {
|
|
1431
|
+
resolvedProvider = await input.adapter.resolveProvider(agentConfig);
|
|
1432
|
+
}
|
|
1433
|
+
return resolvedProvider;
|
|
1434
|
+
};
|
|
1435
|
+
let compactionUsage;
|
|
1436
|
+
try {
|
|
1437
|
+
const compacted = await maybeAutoCompactLocalHistory({
|
|
1438
|
+
adapter: input.adapter,
|
|
1439
|
+
dialogId: input.continueDialogId,
|
|
1440
|
+
history,
|
|
1441
|
+
model: agentConfig.model,
|
|
1442
|
+
resolveProvider: resolveProviderOnce
|
|
1443
|
+
});
|
|
1444
|
+
history = compacted.history;
|
|
1445
|
+
compactionUsage = compacted.usage;
|
|
1446
|
+
} catch (error) {
|
|
1447
|
+
console.warn("[localLoop] auto-compaction unexpected error:", error);
|
|
1448
|
+
}
|
|
1449
|
+
const trimmedHistory = trimHistoryToContextBudget(history, agentConfig.model);
|
|
1450
|
+
if (trimmedHistory.droppedCount > 0) {
|
|
1451
|
+
history = trimmedHistory.history;
|
|
1452
|
+
}
|
|
1453
|
+
const hasContextBlocks = callerScopes.some((block) => block.content.trim()) || mergedContextBlockScopes.some((block) => block.content.trim());
|
|
1454
|
+
const promptMessageCount = agentConfig.prompt?.trim() || hasContextBlocks ? 1 : 0;
|
|
1455
|
+
const turnStartIndex = promptMessageCount + history.length;
|
|
1456
|
+
const builtMessages = buildMessages({
|
|
1457
|
+
prompt: agentConfig.prompt,
|
|
1458
|
+
contextBlockScopes: mergedContextBlockScopes,
|
|
1459
|
+
history,
|
|
1460
|
+
input: input.input,
|
|
1461
|
+
contextReferenceResolver: input.adapter.host === "cli" ? input.contextReferenceResolver : void 0
|
|
1462
|
+
});
|
|
1463
|
+
let messages = builtMessages.messages;
|
|
1464
|
+
const supportsImages = resolveAgentImageInputSupport({
|
|
1465
|
+
apiSource: agentConfig.apiSource,
|
|
1466
|
+
provider: agentConfig.provider,
|
|
1467
|
+
model: agentConfig.model,
|
|
1468
|
+
hasVision: agentConfig.hasVision
|
|
1469
|
+
});
|
|
1470
|
+
if (!supportsImages && hasImageInRuntimeMessages(messages)) {
|
|
1471
|
+
const imageUrls = extractImageUrlsFromMessages(messages);
|
|
1472
|
+
const preprocessed = await preprocessImagesForTextOnlyAgent(
|
|
1473
|
+
messages,
|
|
1474
|
+
(urls) => describeImageWithLocalProvider(input.adapter, urls)
|
|
1475
|
+
);
|
|
1476
|
+
if (preprocessed !== messages) {
|
|
1477
|
+
messages = preprocessed;
|
|
1478
|
+
emitLoopEvent(input, {
|
|
1479
|
+
kind: "image-preprocessed",
|
|
1480
|
+
atMs: Date.now(),
|
|
1481
|
+
imageCount: imageUrls.length
|
|
1482
|
+
});
|
|
1483
|
+
} else {
|
|
1484
|
+
messages = filterImagePartsFromMessages(messages, false);
|
|
1485
|
+
emitLoopEvent(input, {
|
|
1486
|
+
kind: "image-downgraded",
|
|
1487
|
+
reason: "no-vision",
|
|
1488
|
+
atMs: Date.now()
|
|
1489
|
+
});
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
const userInputText = extractUserInputText(input.input);
|
|
1493
|
+
let toolCallCount = 0;
|
|
1494
|
+
let result;
|
|
1495
|
+
let turnUsage;
|
|
1496
|
+
let contextUsage;
|
|
1497
|
+
const usageRecords = [];
|
|
1498
|
+
let loopError;
|
|
1499
|
+
let round = 0;
|
|
1500
|
+
let emptyAssistantRepairPending = false;
|
|
1501
|
+
let emptyAssistantRepairUsed = false;
|
|
1502
|
+
let skipFinalAppend = false;
|
|
1503
|
+
let partialContent = "";
|
|
1504
|
+
try {
|
|
1505
|
+
const provider = await resolveProviderOnce();
|
|
1506
|
+
while (true) {
|
|
1507
|
+
partialContent = "";
|
|
1508
|
+
throwIfAborted(input);
|
|
1509
|
+
const preparedMessages = prepareMessagesForProviderCall(messages);
|
|
1510
|
+
const baseRequestMessages = filterImagePartsFromMessages(
|
|
1511
|
+
preparedMessages.messages,
|
|
1512
|
+
supportsImages
|
|
1513
|
+
);
|
|
1514
|
+
const requestMessages = emptyAssistantRepairPending ? [...baseRequestMessages, { role: "user", content: EMPTY_ASSISTANT_REPAIR_PROMPT }] : baseRequestMessages;
|
|
1515
|
+
const contextMetrics = {
|
|
1516
|
+
...preparedMessages.metrics,
|
|
1517
|
+
messageCount: requestMessages.length,
|
|
1518
|
+
contentChars: requestMessages.reduce(
|
|
1519
|
+
(total, message) => total + contentCharCount(message.content),
|
|
1520
|
+
0
|
|
1521
|
+
),
|
|
1522
|
+
stableContextChars: builtMessages.stableContextChars,
|
|
1523
|
+
dynamicContextChars: builtMessages.dynamicContextChars
|
|
1524
|
+
};
|
|
1525
|
+
emptyAssistantRepairPending = false;
|
|
1526
|
+
result = await runCompleteWithTimeout({
|
|
1527
|
+
provider,
|
|
1528
|
+
messages: requestMessages,
|
|
1529
|
+
options: {
|
|
1530
|
+
...typeof input.timeoutMs === "number" ? { timeoutMs: input.timeoutMs } : {},
|
|
1531
|
+
// 计费归因:续聊轮次带上 dialogId,platform proxy 才能把 token 记录
|
|
1532
|
+
// 归到具体对话而不是 chat-proxy 兜底桶。新对话首轮 id 尚未分配。
|
|
1533
|
+
...input.continueDialogId ? { dialogId: input.continueDialogId } : {},
|
|
1534
|
+
...input.onTextDelta ? { onTextDelta: (chunk) => {
|
|
1535
|
+
partialContent += chunk;
|
|
1536
|
+
input.onTextDelta(chunk);
|
|
1537
|
+
} } : {},
|
|
1538
|
+
...input.onReasoningDelta ? { onReasoningDelta: input.onReasoningDelta } : {},
|
|
1539
|
+
...input.onToolEvent ? { onToolEvent: input.onToolEvent } : {},
|
|
1540
|
+
...input.onToolEvent ? { toolEventRound: round } : {}
|
|
1541
|
+
},
|
|
1542
|
+
timeoutMs: resolveLlmRequestTimeoutMs(input),
|
|
1543
|
+
round,
|
|
1544
|
+
input,
|
|
1545
|
+
context: contextMetrics
|
|
1546
|
+
});
|
|
1547
|
+
turnUsage = mergeTurnUsage(turnUsage, result.usage);
|
|
1548
|
+
contextUsage = result.usage;
|
|
1549
|
+
if (result.usage && Object.keys(result.usage).length > 0) {
|
|
1550
|
+
usageRecords.push({
|
|
1551
|
+
callId: typeof result.usage.provider_call_id === "string" && result.usage.provider_call_id.trim() ? result.usage.provider_call_id.trim() : crypto.randomUUID(),
|
|
1552
|
+
usage: result.usage,
|
|
1553
|
+
model: result.model || agentConfig.model || "unknown",
|
|
1554
|
+
...result.provider || agentConfig.provider ? { provider: result.provider || agentConfig.provider } : {}
|
|
1555
|
+
});
|
|
1556
|
+
}
|
|
1557
|
+
const toolCalls = result.tool_calls ?? [];
|
|
1558
|
+
const rawToolCallsCount = (result.tool_calls?.length ?? 0) || (Array.isArray(result.raw_tool_calls) ? result.raw_tool_calls.length : 0);
|
|
1559
|
+
if (toolCalls.length === 0 && rawToolCallsCount === 0) {
|
|
1560
|
+
const outcome = resolveEmptyAssistantOutcome({
|
|
1561
|
+
hasToolCalls: rawToolCallsCount > 0,
|
|
1562
|
+
hasVisibleOutput: hasAssistantVisibleOutput(result.content),
|
|
1563
|
+
repairUsed: emptyAssistantRepairUsed,
|
|
1564
|
+
finishReason: result.finish_reason,
|
|
1565
|
+
streamComplete: result.stream_complete
|
|
1566
|
+
});
|
|
1567
|
+
if (outcome.kind === "repair") {
|
|
1568
|
+
emptyAssistantRepairPending = true;
|
|
1569
|
+
emptyAssistantRepairUsed = true;
|
|
1570
|
+
continue;
|
|
1571
|
+
}
|
|
1572
|
+
if (outcome.kind === "fallback") {
|
|
1573
|
+
result = {
|
|
1574
|
+
...result,
|
|
1575
|
+
content: resolveEmptyAssistantFallbackMessage(outcome.reason)
|
|
1576
|
+
};
|
|
1577
|
+
break;
|
|
1578
|
+
}
|
|
1579
|
+
break;
|
|
1580
|
+
}
|
|
1581
|
+
const outputBlocks = result.output ?? [];
|
|
1582
|
+
if (outputBlocks.length > 0) {
|
|
1583
|
+
let hasInlineExecutedTools = false;
|
|
1584
|
+
for (const block of outputBlocks) {
|
|
1585
|
+
if (block.type !== "toolCall") continue;
|
|
1586
|
+
toolCallCount += 1;
|
|
1587
|
+
const toolName = block.toolCall.function.name;
|
|
1588
|
+
if (block.result) {
|
|
1589
|
+
hasInlineExecutedTools = true;
|
|
1590
|
+
} else {
|
|
1591
|
+
throw new Error(
|
|
1592
|
+
`provider returned output block with unexecuted toolCall "${toolName}" (id=${block.toolCall.id}); no provider currently emits this shape. Either the provider must fill block.result (like Cursor's exec channel) or it must not set result.output at all.`
|
|
1593
|
+
);
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
if (hasInlineExecutedTools) {
|
|
1597
|
+
messages.push(...blocksToOpenAiMessages(outputBlocks));
|
|
1598
|
+
skipFinalAppend = true;
|
|
1599
|
+
break;
|
|
1600
|
+
}
|
|
1601
|
+
round += 1;
|
|
1602
|
+
continue;
|
|
1603
|
+
}
|
|
1604
|
+
toolCallCount += toolCalls.length;
|
|
1605
|
+
messages.push({
|
|
1606
|
+
role: "assistant",
|
|
1607
|
+
content: result.content || null,
|
|
1608
|
+
...result.reasoning_content ? { reasoning_content: result.reasoning_content } : {},
|
|
1609
|
+
tool_calls: toolCalls
|
|
1610
|
+
});
|
|
1611
|
+
for (const toolCall of toolCalls) {
|
|
1612
|
+
throwIfAborted(input);
|
|
1613
|
+
const toolName = toolCall.function.name;
|
|
1614
|
+
let toolResult;
|
|
1615
|
+
const startedAt = Date.now();
|
|
1616
|
+
emitLoopEvent(input, { kind: "tool-start", name: toolName, atMs: Date.now() });
|
|
1617
|
+
emitToolEvent(input, {
|
|
1618
|
+
type: "tool-call",
|
|
1619
|
+
round,
|
|
1620
|
+
toolCallId: toolCall.id,
|
|
1621
|
+
toolName,
|
|
1622
|
+
argumentsPreview: summarizeToolArguments(toolName, toolCall.function.arguments)
|
|
1623
|
+
});
|
|
1624
|
+
try {
|
|
1625
|
+
const executePromise = input.adapter.executeTool({
|
|
1626
|
+
id: toolCall.id,
|
|
1627
|
+
name: toolName,
|
|
1628
|
+
arguments: toolCall.function.arguments,
|
|
1629
|
+
...userInputText ? { userInput: userInputText } : {},
|
|
1630
|
+
...input.runtimeContext ? { runtimeContext: input.runtimeContext } : {},
|
|
1631
|
+
...input.abortSignal ? { abortSignal: input.abortSignal } : {}
|
|
1632
|
+
}, {
|
|
1633
|
+
...input.abortSignal ? { abortSignal: input.abortSignal } : {},
|
|
1634
|
+
...input.runtimeContext ? { runtimeContext: input.runtimeContext } : {}
|
|
1635
|
+
});
|
|
1636
|
+
toolResult = await raceWithAbort(input, executePromise, toolName);
|
|
1637
|
+
const actionGate = buildActionGate({
|
|
1638
|
+
toolName,
|
|
1639
|
+
toolCallId: toolCall.id,
|
|
1640
|
+
metadata: toolResult.metadata
|
|
1641
|
+
});
|
|
1642
|
+
if (actionGate && input.onActionGate) {
|
|
1643
|
+
const replacement = await input.onActionGate(actionGate);
|
|
1644
|
+
if (replacement) {
|
|
1645
|
+
toolResult = replacement;
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
emitLoopEvent(input, { kind: "tool-end", name: toolName, atMs: Date.now(), ok: true });
|
|
1649
|
+
emitToolEvent(input, {
|
|
1650
|
+
type: "tool-result",
|
|
1651
|
+
round,
|
|
1652
|
+
toolCallId: toolCall.id,
|
|
1653
|
+
toolName,
|
|
1654
|
+
elapsedMs: Math.max(0, Date.now() - startedAt),
|
|
1655
|
+
summary: summarizeToolResult(toolResult.content, toolResult.metadata),
|
|
1656
|
+
...typeof toolResult.content === "string" ? { content: toolResult.content } : {},
|
|
1657
|
+
metadata: toolResult.metadata
|
|
1658
|
+
});
|
|
1659
|
+
} catch (error) {
|
|
1660
|
+
emitLoopEvent(input, { kind: "tool-end", name: toolName, atMs: Date.now(), ok: false });
|
|
1661
|
+
if (error && typeof error === "object" && error.code === LOCAL_TURN_ABORTED_CODE) {
|
|
1662
|
+
throw error;
|
|
1663
|
+
}
|
|
1664
|
+
if (!shouldReturnToolExecutionErrors(input.adapter)) throw error;
|
|
1665
|
+
emitToolEvent(input, {
|
|
1666
|
+
type: "tool-error",
|
|
1667
|
+
round,
|
|
1668
|
+
toolCallId: toolCall.id,
|
|
1669
|
+
toolName,
|
|
1670
|
+
elapsedMs: Math.max(0, Date.now() - startedAt),
|
|
1671
|
+
message: toErrorMessage(error)
|
|
1672
|
+
});
|
|
1673
|
+
toolResult = {
|
|
1674
|
+
content: formatStructuredToolExecutionError({ toolName, error }) ?? formatToolExecutionError({ toolName, error }),
|
|
1675
|
+
metadata: {
|
|
1676
|
+
error: true,
|
|
1677
|
+
toolName,
|
|
1678
|
+
message: toErrorMessage(error),
|
|
1679
|
+
...error && typeof error === "object" && typeof error.code === "string" ? { code: error.code } : {}
|
|
1680
|
+
}
|
|
1681
|
+
};
|
|
1682
|
+
}
|
|
1683
|
+
messages.push({
|
|
1684
|
+
role: "tool",
|
|
1685
|
+
content: formatToolMessageContent({
|
|
1686
|
+
toolName,
|
|
1687
|
+
content: toolResult.content,
|
|
1688
|
+
metadata: toolResult.metadata
|
|
1689
|
+
}),
|
|
1690
|
+
tool_call_id: toolCall.id,
|
|
1691
|
+
toolName,
|
|
1692
|
+
...toolResult.metadata ? { tool_result_metadata: toolResult.metadata } : {}
|
|
1693
|
+
});
|
|
1694
|
+
}
|
|
1695
|
+
round += 1;
|
|
1696
|
+
}
|
|
1697
|
+
} catch (error) {
|
|
1698
|
+
loopError = error;
|
|
1699
|
+
}
|
|
1700
|
+
if (loopError) {
|
|
1701
|
+
const turnMessages2 = applyPersistedTurnInput(
|
|
1702
|
+
messages.slice(turnStartIndex),
|
|
1703
|
+
input.persistedInput,
|
|
1704
|
+
input.persistedInputReference
|
|
1705
|
+
);
|
|
1706
|
+
const dialogId = await persistFailedLocalTurn({
|
|
1707
|
+
adapter: input.adapter,
|
|
1708
|
+
agentKey: agentConfig.key,
|
|
1709
|
+
messages: turnMessages2,
|
|
1710
|
+
error: loopError,
|
|
1711
|
+
model: agentConfig.model,
|
|
1712
|
+
toolCallCount,
|
|
1713
|
+
partialContent,
|
|
1714
|
+
usage: contextUsage,
|
|
1715
|
+
accountingUsage: addOutOfBandUsage(turnUsage, compactionUsage),
|
|
1716
|
+
usageRecords: [
|
|
1717
|
+
...compactionUsage ? [{
|
|
1718
|
+
callId: typeof compactionUsage.provider_call_id === "string" && compactionUsage.provider_call_id.trim() ? compactionUsage.provider_call_id.trim() : crypto.randomUUID(),
|
|
1719
|
+
usage: compactionUsage,
|
|
1720
|
+
model: agentConfig.model || "unknown",
|
|
1721
|
+
...agentConfig.provider ? { provider: agentConfig.provider } : {}
|
|
1722
|
+
}] : [],
|
|
1723
|
+
...usageRecords
|
|
1724
|
+
],
|
|
1725
|
+
billingConfig,
|
|
1726
|
+
input
|
|
1727
|
+
});
|
|
1728
|
+
attachDialogIdToError(loopError, dialogId);
|
|
1729
|
+
throw loopError;
|
|
1730
|
+
}
|
|
1731
|
+
result = result;
|
|
1732
|
+
if (!skipFinalAppend) {
|
|
1733
|
+
messages.push({
|
|
1734
|
+
role: "assistant",
|
|
1735
|
+
content: result.content,
|
|
1736
|
+
// 与中间轮(:561)一致带上 reasoning_content,让 saveTurn 持久化思维链,
|
|
1737
|
+
// 空轮/异常排查时能回看模型实际想了什么。
|
|
1738
|
+
...result.reasoning_content ? { reasoning_content: result.reasoning_content } : {}
|
|
1739
|
+
});
|
|
1740
|
+
}
|
|
1741
|
+
const turnMessages = applyPersistedTurnInput(
|
|
1742
|
+
messages.slice(turnStartIndex),
|
|
1743
|
+
input.persistedInput,
|
|
1744
|
+
input.persistedInputReference
|
|
1745
|
+
);
|
|
1746
|
+
if (compactionUsage && Object.keys(compactionUsage).length > 0) {
|
|
1747
|
+
usageRecords.unshift({
|
|
1748
|
+
callId: typeof compactionUsage.provider_call_id === "string" && compactionUsage.provider_call_id.trim() ? compactionUsage.provider_call_id.trim() : crypto.randomUUID(),
|
|
1749
|
+
usage: compactionUsage,
|
|
1750
|
+
model: agentConfig.model || result.model || "unknown",
|
|
1751
|
+
...agentConfig.provider ? { provider: agentConfig.provider } : {}
|
|
1752
|
+
});
|
|
1753
|
+
}
|
|
1754
|
+
const accountingUsage = addOutOfBandUsage(turnUsage, compactionUsage);
|
|
1755
|
+
const saved = await input.adapter.saveTurn({
|
|
1756
|
+
agentKey: agentConfig.key,
|
|
1757
|
+
messages: turnMessages,
|
|
1758
|
+
result: {
|
|
1759
|
+
...result,
|
|
1760
|
+
...toolCallCount > 0 ? { toolCallCount } : {},
|
|
1761
|
+
...agentConfig.toolSurface ? { runtimeToolSurface: agentConfig.toolSurface } : {}
|
|
1762
|
+
},
|
|
1763
|
+
...usageRecords.length > 0 ? { usageRecords } : {},
|
|
1764
|
+
...accountingUsage ? { accountingUsage } : {},
|
|
1765
|
+
billingConfig,
|
|
1766
|
+
...input.runtimeContext ? { runtimeContext: input.runtimeContext } : {},
|
|
1767
|
+
...input.continueDialogId ? { continueDialogId: input.continueDialogId } : {},
|
|
1768
|
+
...input.spaceId ? { spaceId: input.spaceId } : {},
|
|
1769
|
+
...input.category ? { category: input.category } : {},
|
|
1770
|
+
...input.inheritedFromDialogKey ? { inheritedFromDialogKey: input.inheritedFromDialogKey } : {},
|
|
1771
|
+
...input.parentDialogId ? { parentDialogId: input.parentDialogId } : {}
|
|
1772
|
+
});
|
|
1773
|
+
return {
|
|
1774
|
+
...result,
|
|
1775
|
+
...usageRecords.length > 0 ? { usageRecords } : {},
|
|
1776
|
+
...accountingUsage ? { accountingUsage } : {},
|
|
1777
|
+
...toolCallCount > 0 ? { toolCallCount } : {},
|
|
1778
|
+
...agentConfig.toolSurface ? { runtimeToolSurface: agentConfig.toolSurface } : {},
|
|
1779
|
+
// 透出最后一轮 provider 调用的 finish_reason;多轮工具循环里只有最后一轮收尾状态有意义。
|
|
1780
|
+
...result.finish_reason ? { finish_reason: result.finish_reason } : {},
|
|
1781
|
+
dialogId: saved.dialogId,
|
|
1782
|
+
title: saved.title,
|
|
1783
|
+
...saved.titlePatchPromise ? { titlePatchPromise: saved.titlePatchPromise } : {},
|
|
1784
|
+
turnMessages
|
|
1785
|
+
};
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
export {
|
|
1789
|
+
readCommandActionGatePayload,
|
|
1790
|
+
readActionGate,
|
|
1791
|
+
summarizeToolArguments,
|
|
1792
|
+
tryPreprocessWebImageOrReject,
|
|
1793
|
+
resolveHistoricalToolContentCap,
|
|
1794
|
+
projectToolMessageContent,
|
|
1795
|
+
LOCAL_AGENT_CONFIG_MISSING_CODE,
|
|
1796
|
+
EMPTY_ASSISTANT_REPAIR_PROMPT,
|
|
1797
|
+
LOCAL_TURN_ABORTED_CODE,
|
|
1798
|
+
runLocalAgentTurn
|
|
1799
|
+
};
|