turbollm 1.8.1 → 1.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +783 -112
- package/dist/webdist/assets/{AgentEditPage-DRzR4iGs.js → AgentEditPage-DrcwQcJE.js} +1 -1
- package/dist/webdist/assets/{ChatScreen-cMenh5vH.js → ChatScreen-BkpwUk-O.js} +15 -15
- package/dist/webdist/assets/{CodeComposer-WSBYcLfh.js → CodeComposer-DG4lgSoA.js} +1 -1
- package/dist/webdist/assets/{CodeHomeScreen-DLYhNrPE.js → CodeHomeScreen-Cqv5c0nM.js} +1 -1
- package/dist/webdist/assets/{CodeSessionScreen-DiOU2TWi.js → CodeSessionScreen-aQesvCPO.js} +1 -1
- package/dist/webdist/assets/{CustomizeScreen-lEyan0c2.js → CustomizeScreen-DRrw2hs7.js} +3 -3
- package/dist/webdist/assets/{DeveloperScreen-BYWhhQGb.js → DeveloperScreen-pTaZZ6r5.js} +1 -1
- package/dist/webdist/assets/{EnginesScreen-C5nINY73.js → EnginesScreen-BvZtUKd0.js} +2 -2
- package/dist/webdist/assets/{FsBrowser-BCP84FiV.js → FsBrowser-D0rDep29.js} +1 -1
- package/dist/webdist/assets/{ModelLoadMenu-DGZLiX2S.js → ModelLoadMenu-K1Qolm9B.js} +1 -1
- package/dist/webdist/assets/{ModelsScreen-D-FCPIeG.js → ModelsScreen-HfLjpGmf.js} +1 -1
- package/dist/webdist/assets/{SettingsScreen-X5-09oZy.js → SettingsScreen-z-UuH8MI.js} +1 -1
- package/dist/webdist/assets/{SkillEditPage-CgXB_6Nl.js → SkillEditPage-W3ofhEQn.js} +1 -1
- package/dist/webdist/assets/{TokensScreen-NrRIm4cC.js → TokensScreen-dLb-UDmL.js} +1 -1
- package/dist/webdist/assets/{ToolApprovalBar-tOnZJ73i.js → ToolApprovalBar-BGRW2hFN.js} +15 -15
- package/dist/webdist/assets/WorkspaceScreen-C5R7gzUi.js +1 -0
- package/dist/webdist/assets/{agent-api-CUKcfOzd.js → agent-api-C48x10WZ.js} +1 -1
- package/dist/webdist/assets/{arc-CvnQcBmG.js → arc-B6W63qO_.js} +1 -1
- package/dist/webdist/assets/{architectureDiagram-3BPJPVTR-Pm9wMCDM.js → architectureDiagram-3BPJPVTR-BNxjHCm7.js} +1 -1
- package/dist/webdist/assets/{arrow-left-OByItXF5.js → arrow-left-DmGW0W4g.js} +1 -1
- package/dist/webdist/assets/{badge-DJWQwk9P.js → badge-IsOHcYvf.js} +1 -1
- package/dist/webdist/assets/{blockDiagram-GPEHLZMM-DzLgLYNm.js → blockDiagram-GPEHLZMM-DrPlDIMe.js} +1 -1
- package/dist/webdist/assets/{bot-BNdnf7ne.js → bot-DAa4xIIF.js} +1 -1
- package/dist/webdist/assets/{c4Diagram-AAUBKEIU-qsqnYbgY.js → c4Diagram-AAUBKEIU-BKtn6SbN.js} +1 -1
- package/dist/webdist/assets/channel-CDs60xNM.js +1 -0
- package/dist/webdist/assets/{chat-api-BPIB2oYY.js → chat-api-CtbwBsSY.js} +1 -1
- package/dist/webdist/assets/{chat-queries-C37A8E8t.js → chat-queries-BANbggZ1.js} +1 -1
- package/dist/webdist/assets/check-DeJoIInG.js +1 -0
- package/dist/webdist/assets/{chevron-left-vAm2-1DH.js → chevron-left-DScqk7lr.js} +1 -1
- package/dist/webdist/assets/{chevron-right-A1qanud_.js → chevron-right-DNc_yz-y.js} +1 -1
- package/dist/webdist/assets/{chunk-2J33WTMH-C2ZqERw1.js → chunk-2J33WTMH-B97vCEvd.js} +1 -1
- package/dist/webdist/assets/{chunk-4BX2VUAB-C59qC9gw.js → chunk-4BX2VUAB-B5mAgaq1.js} +1 -1
- package/dist/webdist/assets/{chunk-55IACEB6-Bj5dZNjh.js → chunk-55IACEB6-CCZRTh0d.js} +1 -1
- package/dist/webdist/assets/{chunk-727SXJPM-DDibr_98.js → chunk-727SXJPM-tZT9XNMs.js} +1 -1
- package/dist/webdist/assets/{chunk-AQP2D5EJ-DR5l9iQa.js → chunk-AQP2D5EJ-CfIdLt8G.js} +1 -1
- package/dist/webdist/assets/{chunk-FMBD7UC4-Br0rQBb-.js → chunk-FMBD7UC4-BRdScA0W.js} +1 -1
- package/dist/webdist/assets/{chunk-ND2GUHAM-BLhxelYC.js → chunk-ND2GUHAM-BedHAIQl.js} +1 -1
- package/dist/webdist/assets/{chunk-QZHKN3VN-C2hJC_sd.js → chunk-QZHKN3VN-ByAw17Nt.js} +1 -1
- package/dist/webdist/assets/{circle-check-BFahLD_z.js → circle-check-BNIKoJDg.js} +1 -1
- package/dist/webdist/assets/{circle-x-B34XrT4_.js → circle-x-DfIf4ChY.js} +1 -1
- package/dist/webdist/assets/classDiagram-4FO5ZUOK-BLIfDCvc.js +1 -0
- package/dist/webdist/assets/classDiagram-v2-Q7XG4LA2-BLIfDCvc.js +1 -0
- package/dist/webdist/assets/{common-Cd9MCcBN.js → common-BwXsHduN.js} +1 -1
- package/dist/webdist/assets/{copy-button-Ctl1GqEo.js → copy-button-BcFHHH9b.js} +1 -1
- package/dist/webdist/assets/{cose-bilkent-S5V4N54A-lgljnskt.js → cose-bilkent-S5V4N54A--x5WjVCv.js} +1 -1
- package/dist/webdist/assets/{dagre-BM42HDAG-CieWJzRR.js → dagre-BM42HDAG-DWQy_ulr.js} +1 -1
- package/dist/webdist/assets/{diagram-2AECGRRQ-CbASwXb9.js → diagram-2AECGRRQ-DQo4yJeJ.js} +1 -1
- package/dist/webdist/assets/{diagram-5GNKFQAL-dH7h3v4f.js → diagram-5GNKFQAL-DwbynoQ2.js} +1 -1
- package/dist/webdist/assets/{diagram-KO2AKTUF-CshsiuQd.js → diagram-KO2AKTUF-CjKV0fFm.js} +1 -1
- package/dist/webdist/assets/{diagram-LMA3HP47-ZjRHTqGM.js → diagram-LMA3HP47-CXfjg5Fe.js} +1 -1
- package/dist/webdist/assets/{diagram-OG6HWLK6-BiqwWdGR.js → diagram-OG6HWLK6-CKEjI4NC.js} +1 -1
- package/dist/webdist/assets/{dialog-CmUNO7nv.js → dialog-uY3tk3E9.js} +1 -1
- package/dist/webdist/assets/{erDiagram-TEJ5UH35-57UMKMnk.js → erDiagram-TEJ5UH35-D43kYg84.js} +1 -1
- package/dist/webdist/assets/{external-link-BYRaM4A8.js → external-link-jnEQCJGd.js} +1 -1
- package/dist/webdist/assets/{flowDiagram-I6XJVG4X-yjW2Ak0W.js → flowDiagram-I6XJVG4X-qGS0J-De.js} +1 -1
- package/dist/webdist/assets/{ganttDiagram-6RSMTGT7-XCpB83hN.js → ganttDiagram-6RSMTGT7-3Ud_YPtk.js} +1 -1
- package/dist/webdist/assets/{git-branch-Dch9ecuo.js → git-branch-BZW2_OwW.js} +1 -1
- package/dist/webdist/assets/{gitGraphDiagram-PVQCEYII-CR2LNCc5.js → gitGraphDiagram-PVQCEYII-BhOiYdR2.js} +1 -1
- package/dist/webdist/assets/index-DffknE3S.css +1 -0
- package/dist/webdist/assets/{index-DNrwllG_.js → index-rezSzJo3.js} +2 -2
- package/dist/webdist/assets/{infoDiagram-5YYISTIA-BcOPgHgK.js → infoDiagram-5YYISTIA-D2Yo845I.js} +1 -1
- package/dist/webdist/assets/{ishikawaDiagram-YF4QCWOH-C4eg0Z0x.js → ishikawaDiagram-YF4QCWOH-BoTTMzTX.js} +1 -1
- package/dist/webdist/assets/{journeyDiagram-JHISSGLW-DZYiwT4f.js → journeyDiagram-JHISSGLW-B30FNQqG.js} +1 -1
- package/dist/webdist/assets/{kanban-definition-UN3LZRKU-pOnJphWm.js → kanban-definition-UN3LZRKU-DwuHMcyp.js} +1 -1
- package/dist/webdist/assets/{layers-cWfyYdPT.js → layers-BbMBWfEB.js} +1 -1
- package/dist/webdist/assets/{linear-VlBvT_0X.js → linear-BI-xSqy2.js} +1 -1
- package/dist/webdist/assets/{mermaid.core-BI2Q_Wx8.js → mermaid.core-DjBtB3Ob.js} +4 -4
- package/dist/webdist/assets/{mindmap-definition-RKZ34NQL-BC5CLMVT.js → mindmap-definition-RKZ34NQL-P0Blz3ba.js} +1 -1
- package/dist/webdist/assets/{pencil-CLmcV9uS.js → pencil-pd4qdmIY.js} +1 -1
- package/dist/webdist/assets/{personas-CqrWwpby.js → personas-DGNsw4JH.js} +36 -12
- package/dist/webdist/assets/{pieDiagram-4H26LBE5-DAknqfMh.js → pieDiagram-4H26LBE5-D_RWWNYB.js} +1 -1
- package/dist/webdist/assets/{plus-CvK7zibA.js → plus-Co-23h9C.js} +1 -1
- package/dist/webdist/assets/{quadrantDiagram-W4KKPZXB-9LBpUvz1.js → quadrantDiagram-W4KKPZXB-0UW-qXoo.js} +1 -1
- package/dist/webdist/assets/{refresh-cw-BMXeVLNx.js → refresh-cw-DBHReAx0.js} +1 -1
- package/dist/webdist/assets/{requirementDiagram-4Y6WPE33-CIg6A5rC.js → requirementDiagram-4Y6WPE33-DQPViUOU.js} +1 -1
- package/dist/webdist/assets/{rotate-ccw-B_2DOZJc.js → rotate-ccw-L6hm18l-.js} +1 -1
- package/dist/webdist/assets/{sankeyDiagram-5OEKKPKP-BX3yi4pm.js → sankeyDiagram-5OEKKPKP-CoFKXFxN.js} +1 -1
- package/dist/webdist/assets/{sequenceDiagram-3UESZ5HK-Do0XLaou.js → sequenceDiagram-3UESZ5HK-CTKbZzaz.js} +1 -1
- package/dist/webdist/assets/{skeleton-BN6_VQVb.js → skeleton-CTqhU4nE.js} +1 -1
- package/dist/webdist/assets/{sliders-horizontal-BO-2FJXO.js → sliders-horizontal-CqA4xQwh.js} +1 -1
- package/dist/webdist/assets/{star-gNSRckrW.js → star-C4xvUEne.js} +1 -1
- package/dist/webdist/assets/{stateDiagram-AJRCARHV-644YDLF8.js → stateDiagram-AJRCARHV-Bbgyioxb.js} +1 -1
- package/dist/webdist/assets/stateDiagram-v2-BHNVJYJU-DBMc27o_.js +1 -0
- package/dist/webdist/assets/{switch-B_Lxu9_l.js → switch-vVu6UIJB.js} +1 -1
- package/dist/webdist/assets/{terminal-coo1NYsn.js → terminal-BqY0B6tG.js} +1 -1
- package/dist/webdist/assets/{timeline-definition-PNZ67QCA-C4ocqXD4.js → timeline-definition-PNZ67QCA-CNuDMLf0.js} +1 -1
- package/dist/webdist/assets/{trash-2-CaTm58vg.js → trash-2-CI-4vgjV.js} +1 -1
- package/dist/webdist/assets/{useIsDesktop--92FD_Wr.js → useIsDesktop-B-UPxY57.js} +1 -1
- package/dist/webdist/assets/{vennDiagram-CIIHVFJN-D1j0WATg.js → vennDiagram-CIIHVFJN-5KfL6f8c.js} +1 -1
- package/dist/webdist/assets/{wardley-L42UT6IY-CVMZBlcB.js → wardley-L42UT6IY-KSQpq69w.js} +1 -1
- package/dist/webdist/assets/{wardleyDiagram-YWT4CUSO-BXbaN4Ls.js → wardleyDiagram-YWT4CUSO-CRDC9-52.js} +1 -1
- package/dist/webdist/assets/{wrench-cQ16tDks.js → wrench-B6O2XFxJ.js} +1 -1
- package/dist/webdist/assets/{xychartDiagram-2RQKCTM6-DWd8i6R6.js → xychartDiagram-2RQKCTM6-CFJUnPty.js} +1 -1
- package/dist/webdist/index.html +2 -2
- package/package.json +1 -1
- package/dist/webdist/assets/WorkspaceScreen-CpTRJunA.js +0 -1
- package/dist/webdist/assets/channel-D1GgHB0B.js +0 -1
- package/dist/webdist/assets/check-Bv_bnX7C.js +0 -1
- package/dist/webdist/assets/classDiagram-4FO5ZUOK-BiLDame3.js +0 -1
- package/dist/webdist/assets/classDiagram-v2-Q7XG4LA2-BiLDame3.js +0 -1
- package/dist/webdist/assets/index-D3nTAqcF.css +0 -1
- package/dist/webdist/assets/stateDiagram-v2-BHNVJYJU-CqPYkk1X.js +0 -1
package/dist/cli.js
CHANGED
|
@@ -3677,7 +3677,7 @@ function enumWindowsGpus() {
|
|
|
3677
3677
|
var DISPLAY_CLASS_GUID = "{4d36e968-e325-11ce-bfc1-08002be10318}";
|
|
3678
3678
|
function readWindowsVramRegistry() {
|
|
3679
3679
|
try {
|
|
3680
|
-
const ps = `Get-ChildItem 'HKLM:\\SYSTEM\\CurrentControlSet\\Control\\Class\\${DISPLAY_CLASS_GUID}' -ErrorAction SilentlyContinue | ForEach-Object { $p = Get-ItemProperty -Path $_.PSPath -ErrorAction
|
|
3680
|
+
const ps = `Get-ChildItem 'HKLM:\\SYSTEM\\CurrentControlSet\\Control\\Class\\${DISPLAY_CLASS_GUID}' -ErrorAction SilentlyContinue | Where-Object { $_.PSChildName -match '^\\d{4}$' } | ForEach-Object { try { $p = Get-ItemProperty -Path $_.PSPath -ErrorAction Stop; if ($p.'HardwareInformation.qwMemorySize') { "$($p.DriverDesc)|$($p.'HardwareInformation.qwMemorySize')" } } catch { } }; exit 0`;
|
|
3681
3681
|
const out = execFileSync2("powershell", ["-NoProfile", "-NonInteractive", "-Command", ps], {
|
|
3682
3682
|
timeout: 8e3,
|
|
3683
3683
|
windowsHide: true
|
|
@@ -4134,18 +4134,37 @@ function skipValue(r, t) {
|
|
|
4134
4134
|
}
|
|
4135
4135
|
r.skip(scalarSize(t));
|
|
4136
4136
|
}
|
|
4137
|
-
function
|
|
4137
|
+
function readNumberList(r, t) {
|
|
4138
4138
|
if (t === T_ARRAY) {
|
|
4139
4139
|
const elemType = r.u32();
|
|
4140
4140
|
const count = r.u64();
|
|
4141
|
-
|
|
4142
|
-
for (let i = 0; i < count; i++)
|
|
4143
|
-
|
|
4144
|
-
if (v > max) max = v;
|
|
4145
|
-
}
|
|
4146
|
-
return max;
|
|
4141
|
+
const values = [];
|
|
4142
|
+
for (let i = 0; i < count; i++) values.push(Number(readScalar(r, elemType)));
|
|
4143
|
+
return { values, isArray: true };
|
|
4147
4144
|
}
|
|
4148
|
-
return Number(readScalar(r, t));
|
|
4145
|
+
return { values: [Number(readScalar(r, t))], isArray: false };
|
|
4146
|
+
}
|
|
4147
|
+
function readNumberOrMax(r, t) {
|
|
4148
|
+
return maxOf(readNumberList(r, t).values);
|
|
4149
|
+
}
|
|
4150
|
+
function maxOf(values) {
|
|
4151
|
+
let max = 0;
|
|
4152
|
+
for (const v of values) if (v > max) max = v;
|
|
4153
|
+
return max;
|
|
4154
|
+
}
|
|
4155
|
+
function readBoolArray(r, t) {
|
|
4156
|
+
if (t !== T_ARRAY) {
|
|
4157
|
+
skipValue(r, t);
|
|
4158
|
+
return [];
|
|
4159
|
+
}
|
|
4160
|
+
const elemType = r.u32();
|
|
4161
|
+
const count = r.u64();
|
|
4162
|
+
const out = [];
|
|
4163
|
+
for (let i = 0; i < count; i++) {
|
|
4164
|
+
const v = readScalar(r, elemType);
|
|
4165
|
+
out.push(typeof v === "string" ? v.toLowerCase() === "true" : Boolean(v));
|
|
4166
|
+
}
|
|
4167
|
+
return out;
|
|
4149
4168
|
}
|
|
4150
4169
|
function parseGguf(path) {
|
|
4151
4170
|
const fd = openSync2(path, "r");
|
|
@@ -4159,6 +4178,8 @@ function parseGguf(path) {
|
|
|
4159
4178
|
const m = { hasChatTemplate: false };
|
|
4160
4179
|
let keyLength;
|
|
4161
4180
|
let valueLength;
|
|
4181
|
+
let keyLengthSwa;
|
|
4182
|
+
let valueLengthSwa;
|
|
4162
4183
|
for (let i = 0; i < kvCount; i++) {
|
|
4163
4184
|
const key = r.str();
|
|
4164
4185
|
const t = r.u32();
|
|
@@ -4169,9 +4190,20 @@ function parseGguf(path) {
|
|
|
4169
4190
|
else if (key.endsWith(".context_length")) m.nativeCtx = readNumberOrMax(r, t);
|
|
4170
4191
|
else if (key.endsWith(".block_count")) m.blockCount = readNumberOrMax(r, t);
|
|
4171
4192
|
else if (key.endsWith(".embedding_length")) m.embedLen = readNumberOrMax(r, t);
|
|
4172
|
-
else if (key.endsWith(".attention.head_count_kv"))
|
|
4173
|
-
|
|
4193
|
+
else if (key.endsWith(".attention.head_count_kv")) {
|
|
4194
|
+
const kv = readNumberList(r, t);
|
|
4195
|
+
m.headCountKv = maxOf(kv.values);
|
|
4196
|
+
if (kv.isArray) m.headCountKvPerLayer = kv.values;
|
|
4197
|
+
} else if (key.endsWith(".attention.key_length")) keyLength = readNumberOrMax(r, t);
|
|
4174
4198
|
else if (key.endsWith(".attention.value_length")) valueLength = readNumberOrMax(r, t);
|
|
4199
|
+
else if (key.endsWith(".attention.key_length_swa")) keyLengthSwa = readNumberOrMax(r, t);
|
|
4200
|
+
else if (key.endsWith(".attention.value_length_swa")) valueLengthSwa = readNumberOrMax(r, t);
|
|
4201
|
+
else if (key.endsWith(".attention.sliding_window")) m.slidingWindow = readNumberOrMax(r, t);
|
|
4202
|
+
else if (key.endsWith(".attention.sliding_window_pattern")) m.slidingWindowPattern = readBoolArray(r, t);
|
|
4203
|
+
else if (key.endsWith(".full_attention_interval")) m.fullAttentionInterval = readNumberOrMax(r, t);
|
|
4204
|
+
else if (key.endsWith(".ssm.inner_size")) m.ssmInnerSize = readNumberOrMax(r, t);
|
|
4205
|
+
else if (key.endsWith(".ssm.state_size")) m.ssmStateSize = readNumberOrMax(r, t);
|
|
4206
|
+
else if (key.endsWith(".ssm.conv_kernel")) m.ssmConvKernel = readNumberOrMax(r, t);
|
|
4175
4207
|
else if (key.endsWith(".expert_count")) m.expertCount = readNumberOrMax(r, t);
|
|
4176
4208
|
else if (key.endsWith(".nextn_predict_layers")) m.nextnLayers = readNumberOrMax(r, t);
|
|
4177
4209
|
else if (key === "tokenizer.chat_template") {
|
|
@@ -4193,7 +4225,15 @@ function parseGguf(path) {
|
|
|
4193
4225
|
headDim: keyLength ?? valueLength ?? 0,
|
|
4194
4226
|
expertCount: m.expertCount ?? 0,
|
|
4195
4227
|
nextnLayers: m.nextnLayers ?? 0,
|
|
4196
|
-
hasChatTemplate: m.hasChatTemplate ?? false
|
|
4228
|
+
hasChatTemplate: m.hasChatTemplate ?? false,
|
|
4229
|
+
slidingWindow: m.slidingWindow ?? 0,
|
|
4230
|
+
slidingWindowPattern: m.slidingWindowPattern ?? [],
|
|
4231
|
+
headDimSwa: keyLengthSwa ?? valueLengthSwa ?? 0,
|
|
4232
|
+
headCountKvPerLayer: m.headCountKvPerLayer ?? [],
|
|
4233
|
+
fullAttentionInterval: m.fullAttentionInterval ?? 0,
|
|
4234
|
+
ssmInnerSize: m.ssmInnerSize ?? 0,
|
|
4235
|
+
ssmStateSize: m.ssmStateSize ?? 0,
|
|
4236
|
+
ssmConvKernel: m.ssmConvKernel ?? 0
|
|
4197
4237
|
};
|
|
4198
4238
|
} finally {
|
|
4199
4239
|
closeSync2(fd);
|
|
@@ -4205,7 +4245,20 @@ function quantFromName(filename) {
|
|
|
4205
4245
|
}
|
|
4206
4246
|
|
|
4207
4247
|
// src/models/scanner.ts
|
|
4208
|
-
var CACHE_VERSION =
|
|
4248
|
+
var CACHE_VERSION = 4;
|
|
4249
|
+
function attentionLayoutOf(meta) {
|
|
4250
|
+
const a = meta ?? {};
|
|
4251
|
+
const out = {};
|
|
4252
|
+
if (a.slidingWindow) out.slidingWindow = a.slidingWindow;
|
|
4253
|
+
if (a.slidingWindowPattern?.length) out.slidingWindowPattern = a.slidingWindowPattern;
|
|
4254
|
+
if (a.headDimSwa) out.headDimSwa = a.headDimSwa;
|
|
4255
|
+
if (a.headCountKvPerLayer?.length) out.headCountKvPerLayer = a.headCountKvPerLayer;
|
|
4256
|
+
if (a.fullAttentionInterval) out.fullAttentionInterval = a.fullAttentionInterval;
|
|
4257
|
+
if (a.ssmInnerSize) out.ssmInnerSize = a.ssmInnerSize;
|
|
4258
|
+
if (a.ssmStateSize) out.ssmStateSize = a.ssmStateSize;
|
|
4259
|
+
if (a.ssmConvKernel) out.ssmConvKernel = a.ssmConvKernel;
|
|
4260
|
+
return out;
|
|
4261
|
+
}
|
|
4209
4262
|
var SPLIT_RE = /^(.*)-(\d{5})-of-(\d{5})\.gguf$/i;
|
|
4210
4263
|
var EMBED_ARCHS = /* @__PURE__ */ new Set([
|
|
4211
4264
|
"bert",
|
|
@@ -4392,6 +4445,9 @@ var Scanner = class {
|
|
|
4392
4445
|
blockCount: meta?.blockCount ?? 0,
|
|
4393
4446
|
headCountKv: meta?.headCountKv ?? 0,
|
|
4394
4447
|
headDim: meta?.headDim ?? 0,
|
|
4448
|
+
// Only present on the handful of models that declare a non-uniform attention
|
|
4449
|
+
// layout; everything else keeps the shape (and the KV estimate) it had before.
|
|
4450
|
+
...attentionLayoutOf(meta),
|
|
4395
4451
|
moe: (meta?.expertCount ?? 0) > 0,
|
|
4396
4452
|
expertCount: meta?.expertCount ?? 0,
|
|
4397
4453
|
nextnLayers: meta?.nextnLayers ?? 0,
|
|
@@ -4689,6 +4745,52 @@ function kvBytesPerElem(t) {
|
|
|
4689
4745
|
function defaultSampling() {
|
|
4690
4746
|
return { temp: 0.8, topP: 0.95, topK: 40, minP: 0.05, repeatPenalty: 1, presencePenalty: 0, frequencyPenalty: 0, stop: [] };
|
|
4691
4747
|
}
|
|
4748
|
+
function isFullAttentionLayer(i, interval) {
|
|
4749
|
+
return (i + 1) % interval === 0;
|
|
4750
|
+
}
|
|
4751
|
+
function hybridInterval(m, blocks) {
|
|
4752
|
+
const interval = m.fullAttentionInterval ?? 0;
|
|
4753
|
+
if (!Number.isInteger(interval) || interval <= 1 || interval > blocks) return 0;
|
|
4754
|
+
return interval;
|
|
4755
|
+
}
|
|
4756
|
+
function linearLayerCount(m) {
|
|
4757
|
+
const blocks = m.blockCount || 1;
|
|
4758
|
+
const interval = hybridInterval(m, blocks);
|
|
4759
|
+
if (interval === 0) return 0;
|
|
4760
|
+
let n = 0;
|
|
4761
|
+
for (let i = 0; i < blocks; i++) if (!isFullAttentionLayer(i, interval)) n++;
|
|
4762
|
+
return n;
|
|
4763
|
+
}
|
|
4764
|
+
function kvCacheElems(m, ctx) {
|
|
4765
|
+
const blocks = m.blockCount || 1;
|
|
4766
|
+
const kvHeads = m.headCountKv || 8;
|
|
4767
|
+
const headDim = m.headDim || HEAD_DIM;
|
|
4768
|
+
const perLayerHeads = m.headCountKvPerLayer?.length === blocks ? m.headCountKvPerLayer : void 0;
|
|
4769
|
+
const window = m.slidingWindow ?? 0;
|
|
4770
|
+
const swaPattern = window > 0 && m.slidingWindowPattern?.length === blocks ? m.slidingWindowPattern : void 0;
|
|
4771
|
+
const swaHeadDim = (m.headDimSwa ?? 0) > 0 ? m.headDimSwa : headDim;
|
|
4772
|
+
const interval = hybridInterval(m, blocks);
|
|
4773
|
+
const hybrid = interval > 0 && swaPattern === void 0;
|
|
4774
|
+
if (swaPattern === void 0 && !hybrid) return 2 * blocks * ctx * kvHeads * headDim;
|
|
4775
|
+
let elems = 0;
|
|
4776
|
+
for (let i = 0; i < blocks; i++) {
|
|
4777
|
+
if (hybrid && !isFullAttentionLayer(i, interval)) continue;
|
|
4778
|
+
const sliding = swaPattern?.[i] === true;
|
|
4779
|
+
const tokens = sliding ? Math.min(ctx, window) : ctx;
|
|
4780
|
+
const heads = perLayerHeads?.[i] || kvHeads;
|
|
4781
|
+
const dim = sliding ? swaHeadDim : headDim;
|
|
4782
|
+
elems += 2 * tokens * heads * dim;
|
|
4783
|
+
}
|
|
4784
|
+
return elems;
|
|
4785
|
+
}
|
|
4786
|
+
function ssmStateElems(m) {
|
|
4787
|
+
const layers = linearLayerCount(m);
|
|
4788
|
+
const inner = m.ssmInnerSize ?? 0;
|
|
4789
|
+
const state = m.ssmStateSize ?? 0;
|
|
4790
|
+
if (layers === 0 || inner <= 0 || state <= 0) return 0;
|
|
4791
|
+
const conv = Math.max(1, m.ssmConvKernel ?? 1);
|
|
4792
|
+
return layers * inner * (state + conv - 1);
|
|
4793
|
+
}
|
|
4692
4794
|
function gpuBudgetMb(sys, p) {
|
|
4693
4795
|
if (sys.gpus.length === 0) return 0;
|
|
4694
4796
|
if (p?.gpu?.splitMode === "none") {
|
|
@@ -4704,10 +4806,9 @@ function estimateVram(p, m, sys) {
|
|
|
4704
4806
|
const blocks = m.blockCount || 1;
|
|
4705
4807
|
const gpuFrac = m.moe ? 1 - 0.85 * (p.nCpuMoe / blocks) : Math.min(p.ngl, blocks) / blocks;
|
|
4706
4808
|
const weightsMb = sizeMb * Math.max(0, Math.min(1, gpuFrac));
|
|
4707
|
-
const
|
|
4708
|
-
const
|
|
4709
|
-
const
|
|
4710
|
-
const kvMb = p.kvOffload === false ? 0 : kvElems * kvBytesPerElem(p.kvTypeK) / 1e6 * (p.kvUnified ? 1 : Math.max(1, p.parallel));
|
|
4809
|
+
const kvElems = kvCacheElems(m, p.ctx);
|
|
4810
|
+
const ssmMb = ssmStateElems(m) * 4 / 1e6;
|
|
4811
|
+
const kvMb = p.kvOffload === false ? 0 : (kvElems * kvBytesPerElem(p.kvTypeK) / 1e6 + ssmMb) * (p.kvUnified ? 1 : Math.max(1, p.parallel));
|
|
4711
4812
|
const mmprojMb = p.useMmproj && p.mmprojGpu && m.mmprojPath ? m.mmprojSizeBytes / 1e6 * 1.15 : 0;
|
|
4712
4813
|
const estMb = Math.round(weightsMb + kvMb + 800 + mmprojMb);
|
|
4713
4814
|
const pct = estMb / totalVramMb;
|
|
@@ -8291,6 +8392,18 @@ async function checkSsrf(url) {
|
|
|
8291
8392
|
|
|
8292
8393
|
// src/tools/search-providers.ts
|
|
8293
8394
|
var SEARCH_TIMEOUT_MS = 2e4;
|
|
8395
|
+
var FUTURE_SKEW_MS = 2 * 24 * 60 * 60 * 1e3;
|
|
8396
|
+
function normalizePublishedDate(raw) {
|
|
8397
|
+
if (typeof raw !== "string") return void 0;
|
|
8398
|
+
const trimmed = raw.trim();
|
|
8399
|
+
if (!trimmed) return void 0;
|
|
8400
|
+
const ms = Date.parse(trimmed);
|
|
8401
|
+
if (!Number.isFinite(ms)) return void 0;
|
|
8402
|
+
if (ms > Date.now() + FUTURE_SKEW_MS) return void 0;
|
|
8403
|
+
const iso = new Date(ms).toISOString();
|
|
8404
|
+
if (Number(iso.slice(0, 4)) < 1990) return void 0;
|
|
8405
|
+
return iso.slice(0, 10);
|
|
8406
|
+
}
|
|
8294
8407
|
function searchConfigured(cfg2) {
|
|
8295
8408
|
if (!cfg2) return false;
|
|
8296
8409
|
switch (cfg2.provider) {
|
|
@@ -8325,7 +8438,11 @@ var TavilyClient = class {
|
|
|
8325
8438
|
key;
|
|
8326
8439
|
fetchImpl;
|
|
8327
8440
|
provider = "tavily";
|
|
8328
|
-
|
|
8441
|
+
// `time_range` is honoured on the default (general) topic and meaningfully shifts the
|
|
8442
|
+
// mix toward recent pages. It does NOT make Tavily return `published_date` — only
|
|
8443
|
+
// `topic:'news'` does that, and that collapses results to news articles, so we don't
|
|
8444
|
+
// send it. Tavily therefore filters by recency but rarely supplies a date here.
|
|
8445
|
+
async search(query, maxResults, opts) {
|
|
8329
8446
|
const resp = await this.fetchImpl("https://api.tavily.com/search", {
|
|
8330
8447
|
method: "POST",
|
|
8331
8448
|
headers: { "Content-Type": "application/json" },
|
|
@@ -8333,18 +8450,23 @@ var TavilyClient = class {
|
|
|
8333
8450
|
api_key: this.key,
|
|
8334
8451
|
query,
|
|
8335
8452
|
max_results: maxResults,
|
|
8336
|
-
search_depth: "advanced"
|
|
8453
|
+
search_depth: "advanced",
|
|
8454
|
+
...opts?.recent ? { time_range: "month" } : {}
|
|
8337
8455
|
}),
|
|
8338
8456
|
signal: AbortSignal.timeout(SEARCH_TIMEOUT_MS)
|
|
8339
8457
|
});
|
|
8340
8458
|
if (!resp.ok) throw new Error(await httpError("Tavily", resp));
|
|
8341
8459
|
const data = await resp.json();
|
|
8342
|
-
return (data.results ?? []).map((r) =>
|
|
8343
|
-
|
|
8344
|
-
|
|
8345
|
-
|
|
8346
|
-
|
|
8347
|
-
|
|
8460
|
+
return (data.results ?? []).map((r) => {
|
|
8461
|
+
const publishedDate = normalizePublishedDate(r.published_date);
|
|
8462
|
+
return {
|
|
8463
|
+
title: r.title ?? "",
|
|
8464
|
+
url: r.url ?? "",
|
|
8465
|
+
content: r.content ?? "",
|
|
8466
|
+
score: r.score,
|
|
8467
|
+
...publishedDate ? { publishedDate } : {}
|
|
8468
|
+
};
|
|
8469
|
+
});
|
|
8348
8470
|
}
|
|
8349
8471
|
};
|
|
8350
8472
|
var KagiClient = class {
|
|
@@ -8355,6 +8477,10 @@ var KagiClient = class {
|
|
|
8355
8477
|
key;
|
|
8356
8478
|
fetchImpl;
|
|
8357
8479
|
provider = "kagi";
|
|
8480
|
+
// No `opts` arg: the v0 search endpoint this adapter targets documents only `q` and
|
|
8481
|
+
// `limit` — it has no recency parameter, so `recent` cannot be honoured here.
|
|
8482
|
+
// (Kagi's newer POST /search endpoint does support recency via lens/filters, but it
|
|
8483
|
+
// has a different request and response shape and would need a full adapter rewrite.)
|
|
8358
8484
|
async search(query, maxResults) {
|
|
8359
8485
|
const url = `https://kagi.com/api/v0/search?q=${encodeURIComponent(query)}&limit=${maxResults}`;
|
|
8360
8486
|
const resp = await this.fetchImpl(url, {
|
|
@@ -8364,11 +8490,15 @@ var KagiClient = class {
|
|
|
8364
8490
|
});
|
|
8365
8491
|
if (!resp.ok) throw new Error(await httpError("Kagi", resp));
|
|
8366
8492
|
const data = await resp.json();
|
|
8367
|
-
return (data.data ?? []).filter((r) => r.t === 0 && r.url).map((r) =>
|
|
8368
|
-
|
|
8369
|
-
|
|
8370
|
-
|
|
8371
|
-
|
|
8493
|
+
return (data.data ?? []).filter((r) => r.t === 0 && r.url).map((r) => {
|
|
8494
|
+
const publishedDate = normalizePublishedDate(r.published);
|
|
8495
|
+
return {
|
|
8496
|
+
title: r.title ?? "",
|
|
8497
|
+
url: r.url ?? "",
|
|
8498
|
+
content: r.snippet ?? "",
|
|
8499
|
+
...publishedDate ? { publishedDate } : {}
|
|
8500
|
+
};
|
|
8501
|
+
});
|
|
8372
8502
|
}
|
|
8373
8503
|
};
|
|
8374
8504
|
var SearxngClient = class {
|
|
@@ -8379,21 +8509,26 @@ var SearxngClient = class {
|
|
|
8379
8509
|
baseUrl;
|
|
8380
8510
|
fetchImpl;
|
|
8381
8511
|
provider = "searxng";
|
|
8382
|
-
async search(query, maxResults) {
|
|
8512
|
+
async search(query, maxResults, opts) {
|
|
8383
8513
|
const base2 = this.baseUrl.replace(/\/+$/, "");
|
|
8384
|
-
|
|
8514
|
+
let url = `${base2}/search?q=${encodeURIComponent(query)}&format=json&categories=general`;
|
|
8515
|
+
if (opts?.recent) url += "&time_range=month";
|
|
8385
8516
|
const resp = await this.fetchImpl(url, {
|
|
8386
8517
|
method: "GET",
|
|
8387
8518
|
signal: AbortSignal.timeout(SEARCH_TIMEOUT_MS)
|
|
8388
8519
|
});
|
|
8389
8520
|
if (!resp.ok) throw new Error(await httpError("SearXNG", resp));
|
|
8390
8521
|
const data = await resp.json();
|
|
8391
|
-
return (data.results ?? []).slice(0, maxResults).map((r) =>
|
|
8392
|
-
|
|
8393
|
-
|
|
8394
|
-
|
|
8395
|
-
|
|
8396
|
-
|
|
8522
|
+
return (data.results ?? []).slice(0, maxResults).map((r) => {
|
|
8523
|
+
const publishedDate = normalizePublishedDate(r.publishedDate);
|
|
8524
|
+
return {
|
|
8525
|
+
title: r.title ?? "",
|
|
8526
|
+
url: r.url ?? "",
|
|
8527
|
+
content: r.content ?? "",
|
|
8528
|
+
score: r.score,
|
|
8529
|
+
...publishedDate ? { publishedDate } : {}
|
|
8530
|
+
};
|
|
8531
|
+
});
|
|
8397
8532
|
}
|
|
8398
8533
|
};
|
|
8399
8534
|
async function httpError(name, resp) {
|
|
@@ -8403,8 +8538,10 @@ async function httpError(name, resp) {
|
|
|
8403
8538
|
|
|
8404
8539
|
// src/tools/research-service.ts
|
|
8405
8540
|
var MIN_RELEVANCE_SCORE = 0.4;
|
|
8406
|
-
var MAX_RESULTS =
|
|
8541
|
+
var MAX_RESULTS = 8;
|
|
8407
8542
|
var MAX_SEARCH_RESULTS = 10;
|
|
8543
|
+
var FRESH_MAX_AGE_DAYS = 90;
|
|
8544
|
+
var DATED_MIN_AGE_DAYS = 3 * 365;
|
|
8408
8545
|
var TRUSTED_DOMAINS = [
|
|
8409
8546
|
[/\.(edu|ac\.[a-z]{2})$/, 0.9],
|
|
8410
8547
|
[/\.gov(\.[a-z]{2})?$/, 0.9],
|
|
@@ -8511,58 +8648,385 @@ function keywordOverlap(query, text) {
|
|
|
8511
8648
|
const bigramScore = bigramTotal > 0 ? bigramHits / bigramTotal : 0;
|
|
8512
8649
|
return Math.min(1, unigramScore * 0.7 + bigramScore * 0.3);
|
|
8513
8650
|
}
|
|
8651
|
+
function clamp(n, lo, hi) {
|
|
8652
|
+
return Math.min(hi, Math.max(lo, n));
|
|
8653
|
+
}
|
|
8654
|
+
var GENERIC_URL_LABELS = /* @__PURE__ */ new Set([
|
|
8655
|
+
"best",
|
|
8656
|
+
"top",
|
|
8657
|
+
"guide",
|
|
8658
|
+
"guides",
|
|
8659
|
+
"review",
|
|
8660
|
+
"reviews",
|
|
8661
|
+
"news",
|
|
8662
|
+
"blog",
|
|
8663
|
+
"blogs",
|
|
8664
|
+
"shop",
|
|
8665
|
+
"store",
|
|
8666
|
+
"buy",
|
|
8667
|
+
"price",
|
|
8668
|
+
"prices",
|
|
8669
|
+
"deal",
|
|
8670
|
+
"deals",
|
|
8671
|
+
"compare",
|
|
8672
|
+
"ranking",
|
|
8673
|
+
"rankings",
|
|
8674
|
+
"list",
|
|
8675
|
+
"lists",
|
|
8676
|
+
"info",
|
|
8677
|
+
"online",
|
|
8678
|
+
"free",
|
|
8679
|
+
"home",
|
|
8680
|
+
"site",
|
|
8681
|
+
"daily",
|
|
8682
|
+
"today",
|
|
8683
|
+
"live",
|
|
8684
|
+
"tips",
|
|
8685
|
+
"world",
|
|
8686
|
+
"expert",
|
|
8687
|
+
"experts",
|
|
8688
|
+
"hub",
|
|
8689
|
+
"zone",
|
|
8690
|
+
"central",
|
|
8691
|
+
"article",
|
|
8692
|
+
"articles",
|
|
8693
|
+
"post",
|
|
8694
|
+
"posts",
|
|
8695
|
+
"page",
|
|
8696
|
+
"pages",
|
|
8697
|
+
"category",
|
|
8698
|
+
"tag",
|
|
8699
|
+
"tags",
|
|
8700
|
+
"search",
|
|
8701
|
+
"wiki",
|
|
8702
|
+
"forum",
|
|
8703
|
+
"forums",
|
|
8704
|
+
"topic",
|
|
8705
|
+
"topics"
|
|
8706
|
+
]);
|
|
8707
|
+
var SUFFIX_LABELS = /* @__PURE__ */ new Set(["www", "co", "com", "org", "net", "ac", "gov", "edu"]);
|
|
8708
|
+
function entityStem(s) {
|
|
8709
|
+
return s.replace(/\d+$/, "");
|
|
8710
|
+
}
|
|
8711
|
+
function entityTerms(query) {
|
|
8712
|
+
const terms = new Set(
|
|
8713
|
+
tokenize(query).filter((t) => t.length >= 4 && !GENERIC_URL_LABELS.has(t))
|
|
8714
|
+
);
|
|
8715
|
+
if (query !== query.toUpperCase()) {
|
|
8716
|
+
for (const m of query.match(/\b[A-Z]{3,6}\b/g) ?? []) {
|
|
8717
|
+
const lower = m.toLowerCase();
|
|
8718
|
+
if (!GENERIC_URL_LABELS.has(lower)) terms.add(lower);
|
|
8719
|
+
}
|
|
8720
|
+
}
|
|
8721
|
+
return terms;
|
|
8722
|
+
}
|
|
8723
|
+
function officialSourceBoost(query, url) {
|
|
8724
|
+
let host2;
|
|
8725
|
+
let segments;
|
|
8726
|
+
try {
|
|
8727
|
+
const u = new URL(url.startsWith("http") ? url : `https://${url}`);
|
|
8728
|
+
host2 = u.hostname.toLowerCase();
|
|
8729
|
+
segments = u.pathname.split("/").filter(Boolean).map((s) => s.toLowerCase().replace(/[^a-z0-9]/g, ""));
|
|
8730
|
+
} catch {
|
|
8731
|
+
return 0;
|
|
8732
|
+
}
|
|
8733
|
+
const terms = entityTerms(query);
|
|
8734
|
+
if (terms.size === 0) return 0;
|
|
8735
|
+
const matches = (candidate) => {
|
|
8736
|
+
if (candidate.length < 3 || GENERIC_URL_LABELS.has(candidate)) return false;
|
|
8737
|
+
const stem = entityStem(candidate);
|
|
8738
|
+
for (const t of terms) {
|
|
8739
|
+
if (t === candidate) return true;
|
|
8740
|
+
if (stem.length >= 4 && entityStem(t) === stem) return true;
|
|
8741
|
+
}
|
|
8742
|
+
return false;
|
|
8743
|
+
};
|
|
8744
|
+
const isConcatenatedName = (candidate) => {
|
|
8745
|
+
if (candidate.length < 8) return false;
|
|
8746
|
+
let hits = 0;
|
|
8747
|
+
let covered = 0;
|
|
8748
|
+
for (const t of terms) {
|
|
8749
|
+
if (t.length >= 4 && candidate.includes(t)) {
|
|
8750
|
+
hits++;
|
|
8751
|
+
covered += t.length;
|
|
8752
|
+
}
|
|
8753
|
+
}
|
|
8754
|
+
return hits >= 2 && covered >= candidate.length * 0.6;
|
|
8755
|
+
};
|
|
8756
|
+
const labels = host2.split(".");
|
|
8757
|
+
for (let i = 0; i < labels.length - 1; i++) {
|
|
8758
|
+
if (SUFFIX_LABELS.has(labels[i])) continue;
|
|
8759
|
+
if (matches(labels[i])) return 0.25;
|
|
8760
|
+
if (isConcatenatedName(labels[i])) return 0.22;
|
|
8761
|
+
}
|
|
8762
|
+
if (segments.length !== 1) return 0;
|
|
8763
|
+
if (labels.some((l) => GENERIC_URL_LABELS.has(l))) return 0;
|
|
8764
|
+
return matches(segments[0]) ? 0.15 : 0;
|
|
8765
|
+
}
|
|
8766
|
+
function officialDocsBoost(url) {
|
|
8767
|
+
try {
|
|
8768
|
+
const host2 = new URL(url.startsWith("http") ? url : `https://${url}`).hostname.toLowerCase();
|
|
8769
|
+
return /^(docs?|developers?|support|help)\./.test(host2) ? 0.1 : 0;
|
|
8770
|
+
} catch {
|
|
8771
|
+
return 0;
|
|
8772
|
+
}
|
|
8773
|
+
}
|
|
8774
|
+
var AGGREGATOR_BAIT = [
|
|
8775
|
+
/\bbest\b/,
|
|
8776
|
+
/\btop\s+\d+\b/,
|
|
8777
|
+
/\b\d+\s+(best|top|ways|reasons|tips|things|ideas|alternatives)\b/,
|
|
8778
|
+
/\bultimate\s+guide\b/,
|
|
8779
|
+
/\branked\b/,
|
|
8780
|
+
/\breviews?\b|\breviewed\b/,
|
|
8781
|
+
/\bround\s?up\b/,
|
|
8782
|
+
/\b(vs|versus)\b/
|
|
8783
|
+
];
|
|
8784
|
+
var AGGREGATOR_PATTERNS = [
|
|
8785
|
+
...AGGREGATOR_BAIT,
|
|
8786
|
+
/\bwhich\b[\s\S]*\bshould\s+you\b/
|
|
8787
|
+
];
|
|
8788
|
+
var AGGREGATOR_EXEMPTIONS = [
|
|
8789
|
+
/\bbest\s+practices?\b/,
|
|
8790
|
+
/\b(systematic|literature|scoping|narrative|meta)\b/,
|
|
8791
|
+
/\bpeer\s+reviewed\b/
|
|
8792
|
+
];
|
|
8793
|
+
function aggregatorPenalty(title, url, intent) {
|
|
8794
|
+
if (intent === "comparison") return 0;
|
|
8795
|
+
let slug = "";
|
|
8796
|
+
try {
|
|
8797
|
+
slug = new URL(url.startsWith("http") ? url : `https://${url}`).pathname;
|
|
8798
|
+
} catch {
|
|
8799
|
+
slug = "";
|
|
8800
|
+
}
|
|
8801
|
+
const text = `${title} ${slug.replace(/[^a-zA-Z0-9]+/g, " ")}`.toLowerCase();
|
|
8802
|
+
if (AGGREGATOR_EXEMPTIONS.some((re) => re.test(text))) return 0;
|
|
8803
|
+
if (!AGGREGATOR_PATTERNS.some((re) => re.test(text))) return 0;
|
|
8804
|
+
return /\b(19|20)\d{2}\b/.test(text) ? 0.2 : 0.15;
|
|
8805
|
+
}
|
|
8806
|
+
function substantiveTermCount(text) {
|
|
8807
|
+
return text.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((t) => t.length >= 2 && !STOP_WORDS.has(t)).length;
|
|
8808
|
+
}
|
|
8809
|
+
var QUERY_ONLY_BAIT = [
|
|
8810
|
+
/\bwhich\b/gi,
|
|
8811
|
+
/\bshould\s+(you|i|we)\b/gi,
|
|
8812
|
+
/\b(19|20)\d{2}\b/g
|
|
8813
|
+
];
|
|
8814
|
+
function debaitQuery(query) {
|
|
8815
|
+
if (typeof query !== "string" || !query.trim()) return null;
|
|
8816
|
+
const lower = query.toLowerCase();
|
|
8817
|
+
if (AGGREGATOR_EXEMPTIONS.some((re) => re.test(lower))) return null;
|
|
8818
|
+
if (!AGGREGATOR_PATTERNS.some((re) => re.test(lower))) return null;
|
|
8819
|
+
let out = query;
|
|
8820
|
+
for (const re of AGGREGATOR_BAIT) {
|
|
8821
|
+
out = out.replace(new RegExp(re.source, "gi"), " ");
|
|
8822
|
+
}
|
|
8823
|
+
for (const re of QUERY_ONLY_BAIT) out = out.replace(re, " ");
|
|
8824
|
+
out = out.replace(/\s+/g, " ").trim().replace(/^[^\p{L}\p{N}]+|[^\p{L}\p{N}]+$/gu, "").trim();
|
|
8825
|
+
if (!out || out.toLowerCase() === query.trim().toLowerCase()) return null;
|
|
8826
|
+
if (substantiveTermCount(out) < 2) return null;
|
|
8827
|
+
return out;
|
|
8828
|
+
}
|
|
8829
|
+
function sourceQuality(query, title, url, intent) {
|
|
8830
|
+
let base2;
|
|
8831
|
+
try {
|
|
8832
|
+
base2 = domainScore(url);
|
|
8833
|
+
} catch {
|
|
8834
|
+
base2 = 0.5;
|
|
8835
|
+
}
|
|
8836
|
+
return clamp(
|
|
8837
|
+
base2 + officialSourceBoost(query, url) + officialDocsBoost(url) - aggregatorPenalty(title, url, intent),
|
|
8838
|
+
0.1,
|
|
8839
|
+
1
|
|
8840
|
+
);
|
|
8841
|
+
}
|
|
8842
|
+
function passagePriority(sentence) {
|
|
8843
|
+
const s = sentence.trim();
|
|
8844
|
+
let rank = 0;
|
|
8845
|
+
if (/^#{1,6}\s/.test(s)) rank -= 2;
|
|
8846
|
+
if (/\b(19|20)\d{2}\b/.test(s)) rank += 1;
|
|
8847
|
+
return rank;
|
|
8848
|
+
}
|
|
8849
|
+
var MAX_PASSAGE_CHARS = 1200;
|
|
8850
|
+
var REFERENTIAL_OPENER = /^(in addition|as a result|for example|for instance|it|this|that|these|those|they|he|she|its|their|his|her|there|such|however|but|and|so|then|also|additionally|moreover|furthermore|instead|meanwhile|therefore|thus|yet|still)\b/i;
|
|
8851
|
+
function needsPrecedingContext(sentence) {
|
|
8852
|
+
const s = sentence.trim();
|
|
8853
|
+
return s.length < 120 || REFERENTIAL_OPENER.test(s) || /^[a-z]/.test(s);
|
|
8854
|
+
}
|
|
8514
8855
|
function extractPassage(query, content) {
|
|
8515
8856
|
if (!content) return "";
|
|
8516
8857
|
const sentences = content.split(/(?<=[.?!])\s+/).filter((s) => s.trim().length > 0);
|
|
8517
8858
|
if (sentences.length <= 1) {
|
|
8518
|
-
return content.slice(0,
|
|
8859
|
+
return content.slice(0, MAX_PASSAGE_CHARS);
|
|
8519
8860
|
}
|
|
8520
|
-
let
|
|
8861
|
+
let bestIdx = 0;
|
|
8521
8862
|
let bestScore = keywordOverlap(query, sentences[0]);
|
|
8522
8863
|
for (let i = 1; i < sentences.length; i++) {
|
|
8523
8864
|
const s = keywordOverlap(query, sentences[i]);
|
|
8524
|
-
if (s > bestScore) {
|
|
8865
|
+
if (s > bestScore || s === bestScore && passagePriority(sentences[i]) > passagePriority(sentences[bestIdx])) {
|
|
8525
8866
|
bestScore = s;
|
|
8526
|
-
|
|
8527
|
-
}
|
|
8867
|
+
bestIdx = i;
|
|
8868
|
+
}
|
|
8869
|
+
}
|
|
8870
|
+
const best = sentences[bestIdx].trim();
|
|
8871
|
+
if (best.length >= MAX_PASSAGE_CHARS) return best.slice(0, MAX_PASSAGE_CHARS);
|
|
8872
|
+
let start = bestIdx;
|
|
8873
|
+
let end = bestIdx;
|
|
8874
|
+
let total = best.length;
|
|
8875
|
+
const fits = (s) => total + s.length + 1 <= MAX_PASSAGE_CHARS;
|
|
8876
|
+
if (bestIdx > 0 && needsPrecedingContext(best)) {
|
|
8877
|
+
const prev = sentences[bestIdx - 1].trim();
|
|
8878
|
+
if (fits(prev)) {
|
|
8879
|
+
start = bestIdx - 1;
|
|
8880
|
+
total += prev.length + 1;
|
|
8881
|
+
}
|
|
8882
|
+
}
|
|
8883
|
+
while (end + 1 < sentences.length) {
|
|
8884
|
+
const next = sentences[end + 1].trim();
|
|
8885
|
+
if (!fits(next)) break;
|
|
8886
|
+
end++;
|
|
8887
|
+
total += next.length + 1;
|
|
8888
|
+
}
|
|
8889
|
+
while (start > 0) {
|
|
8890
|
+
const prev = sentences[start - 1].trim();
|
|
8891
|
+
if (!fits(prev)) break;
|
|
8892
|
+
start--;
|
|
8893
|
+
total += prev.length + 1;
|
|
8894
|
+
}
|
|
8895
|
+
return sentences.slice(start, end + 1).map((s) => s.trim()).join(" ");
|
|
8896
|
+
}
|
|
8897
|
+
function normalizePublishedDate2(raw) {
|
|
8898
|
+
if (typeof raw !== "string") return void 0;
|
|
8899
|
+
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(raw.trim());
|
|
8900
|
+
if (!m) return void 0;
|
|
8901
|
+
const [, y, mo, d] = m;
|
|
8902
|
+
const ms = Date.UTC(Number(y), Number(mo) - 1, Number(d));
|
|
8903
|
+
const dt = new Date(ms);
|
|
8904
|
+
if (dt.getUTCFullYear() !== Number(y) || dt.getUTCMonth() !== Number(mo) - 1 || dt.getUTCDate() !== Number(d)) {
|
|
8905
|
+
return void 0;
|
|
8528
8906
|
}
|
|
8529
|
-
return
|
|
8907
|
+
return `${y}-${mo}-${d}`;
|
|
8530
8908
|
}
|
|
8531
|
-
function
|
|
8532
|
-
|
|
8533
|
-
|
|
8909
|
+
function ageInDays(isoDate, now = Date.now()) {
|
|
8910
|
+
const normalized = normalizePublishedDate2(isoDate);
|
|
8911
|
+
if (!normalized) return void 0;
|
|
8912
|
+
const published = Date.parse(`${normalized}T00:00:00Z`);
|
|
8913
|
+
if (Number.isNaN(published)) return void 0;
|
|
8914
|
+
return Math.max(0, Math.floor((now - published) / 864e5));
|
|
8915
|
+
}
|
|
8916
|
+
function yearMentionHint(content) {
|
|
8534
8917
|
const yearMatches = content.match(/\b(20\d{2})\b/g);
|
|
8535
8918
|
if (!yearMatches) return "unknown";
|
|
8536
|
-
const
|
|
8537
|
-
const maxYear = Math.max(...
|
|
8919
|
+
const currentYear = (/* @__PURE__ */ new Date()).getFullYear();
|
|
8920
|
+
const maxYear = Math.max(...yearMatches.map(Number));
|
|
8538
8921
|
if (maxYear >= currentYear - 1) return "recent";
|
|
8539
8922
|
if (maxYear <= currentYear - 3) return "dated";
|
|
8540
8923
|
return "unknown";
|
|
8541
8924
|
}
|
|
8542
|
-
function
|
|
8543
|
-
if (
|
|
8544
|
-
if (
|
|
8545
|
-
if (
|
|
8925
|
+
function currentnessMultiplier(ageDays) {
|
|
8926
|
+
if (ageDays <= FRESH_MAX_AGE_DAYS) return 1;
|
|
8927
|
+
if (ageDays <= 365) return 0.85;
|
|
8928
|
+
if (ageDays <= DATED_MIN_AGE_DAYS) return 0.65;
|
|
8929
|
+
return 0.45;
|
|
8930
|
+
}
|
|
8931
|
+
function freshnessSignal(ageDays) {
|
|
8932
|
+
if (ageDays === void 0) return "unknown";
|
|
8933
|
+
if (ageDays <= FRESH_MAX_AGE_DAYS) return "recent";
|
|
8934
|
+
if (ageDays >= DATED_MIN_AGE_DAYS) return "dated";
|
|
8935
|
+
return "unknown";
|
|
8936
|
+
}
|
|
8937
|
+
function freshnessScore(ageDays, content, freshness) {
|
|
8938
|
+
if (freshness !== "current") return 0.5;
|
|
8939
|
+
if (ageDays !== void 0) {
|
|
8940
|
+
if (ageDays <= FRESH_MAX_AGE_DAYS) return 1;
|
|
8941
|
+
if (ageDays <= 180) return 0.6;
|
|
8942
|
+
if (ageDays <= 365) return 0.4;
|
|
8943
|
+
if (ageDays < DATED_MIN_AGE_DAYS) return 0.2;
|
|
8944
|
+
return 0.1;
|
|
8945
|
+
}
|
|
8946
|
+
const hint = yearMentionHint(content);
|
|
8947
|
+
if (hint === "recent") return 0.6;
|
|
8948
|
+
if (hint === "dated") return 0.45;
|
|
8546
8949
|
return 0.5;
|
|
8547
8950
|
}
|
|
8951
|
+
var TRACKING_PARAMS = /^(utm_[a-z0-9_]+|fbclid|gclid|dclid|msclkid|mc_cid|mc_eid|igshid|ref|referrer|source)$/i;
|
|
8952
|
+
function normalizeUrlIdentity(url) {
|
|
8953
|
+
const raw = String(url ?? "").trim();
|
|
8954
|
+
try {
|
|
8955
|
+
const u = new URL(raw.startsWith("http") ? raw : `https://${raw}`);
|
|
8956
|
+
for (const key of [...u.searchParams.keys()]) {
|
|
8957
|
+
if (TRACKING_PARAMS.test(key)) u.searchParams.delete(key);
|
|
8958
|
+
}
|
|
8959
|
+
u.searchParams.sort();
|
|
8960
|
+
const host2 = u.hostname.toLowerCase().replace(/^www\./, "");
|
|
8961
|
+
const path = u.pathname.replace(/\/+$/, "");
|
|
8962
|
+
const qs = u.searchParams.toString();
|
|
8963
|
+
return `${host2}${path}${qs ? `?${qs}` : ""}`;
|
|
8964
|
+
} catch {
|
|
8965
|
+
return raw.toLowerCase();
|
|
8966
|
+
}
|
|
8967
|
+
}
|
|
8968
|
+
function normalizeTitleIdentity(title) {
|
|
8969
|
+
return String(title ?? "").trim().replace(/\s+/g, " ").toLowerCase();
|
|
8970
|
+
}
|
|
8971
|
+
function isBetterDuplicate(candidate, incumbent) {
|
|
8972
|
+
const candidateDated = !!candidate.publishedDate;
|
|
8973
|
+
const incumbentDated = !!incumbent.publishedDate;
|
|
8974
|
+
if (candidateDated !== incumbentDated) return candidateDated;
|
|
8975
|
+
return (candidate.content?.length ?? 0) > (incumbent.content?.length ?? 0);
|
|
8976
|
+
}
|
|
8977
|
+
function collapseBy(results, keyOf) {
|
|
8978
|
+
const seenAt = /* @__PURE__ */ new Map();
|
|
8979
|
+
const out = [];
|
|
8980
|
+
for (const r of results) {
|
|
8981
|
+
const key = keyOf(r);
|
|
8982
|
+
if (key === null) {
|
|
8983
|
+
out.push(r);
|
|
8984
|
+
continue;
|
|
8985
|
+
}
|
|
8986
|
+
const at = seenAt.get(key);
|
|
8987
|
+
if (at === void 0) {
|
|
8988
|
+
seenAt.set(key, out.length);
|
|
8989
|
+
out.push(r);
|
|
8990
|
+
} else if (isBetterDuplicate(r, out[at])) {
|
|
8991
|
+
out[at] = r;
|
|
8992
|
+
}
|
|
8993
|
+
}
|
|
8994
|
+
return out;
|
|
8995
|
+
}
|
|
8996
|
+
function dedupeSearchResults(results) {
|
|
8997
|
+
const byUrl = collapseBy(results, (r) => r?.url ? normalizeUrlIdentity(r.url) : null);
|
|
8998
|
+
return collapseBy(byUrl, (r) => {
|
|
8999
|
+
const title = normalizeTitleIdentity(r?.title ?? "");
|
|
9000
|
+
if (!title) return null;
|
|
9001
|
+
return `${extractDomain(r.url ?? "")}\0${title}`;
|
|
9002
|
+
});
|
|
9003
|
+
}
|
|
8548
9004
|
async function research(q, cfg2, fetchImpl) {
|
|
8549
9005
|
if (!searchConfigured(cfg2)) return [];
|
|
8550
9006
|
const client = searchProviderClient(cfg2, fetchImpl);
|
|
8551
9007
|
if (!client) return [];
|
|
8552
|
-
|
|
8553
|
-
|
|
8554
|
-
|
|
8555
|
-
|
|
8556
|
-
|
|
8557
|
-
|
|
9008
|
+
const opts = q.freshness === "current" ? { recent: true } : void 0;
|
|
9009
|
+
const debaited = debaitQuery(q.query);
|
|
9010
|
+
const primaryCall = client.search(q.query, MAX_SEARCH_RESULTS, opts).then(
|
|
9011
|
+
(r) => r,
|
|
9012
|
+
() => null
|
|
9013
|
+
// null distinguishes "the search failed" from "the search found nothing".
|
|
9014
|
+
);
|
|
9015
|
+
const extraCall = debaited ? client.search(debaited, MAX_SEARCH_RESULTS, opts).catch(() => []) : Promise.resolve([]);
|
|
9016
|
+
const [primaryRaw, extraRaw] = await Promise.all([primaryCall, extraCall]);
|
|
9017
|
+
if (primaryRaw === null) return [];
|
|
9018
|
+
const raw = dedupeSearchResults([...primaryRaw, ...extraRaw]);
|
|
9019
|
+
const now = Date.now();
|
|
8558
9020
|
const scored = raw.map((r) => {
|
|
8559
9021
|
const domain = extractDomain(r.url);
|
|
8560
9022
|
const text = `${r.title} ${r.content}`;
|
|
8561
9023
|
const kwScore = keywordOverlap(q.query, text);
|
|
8562
|
-
const
|
|
8563
|
-
const
|
|
8564
|
-
const
|
|
8565
|
-
const
|
|
9024
|
+
const sq = sourceQuality(q.query, r.title, r.url, q.intent);
|
|
9025
|
+
const publishedDate = normalizePublishedDate2(r.publishedDate);
|
|
9026
|
+
const ageDays = publishedDate ? ageInDays(publishedDate, now) : void 0;
|
|
9027
|
+
const signal = freshnessSignal(ageDays);
|
|
9028
|
+
const fs2 = freshnessScore(ageDays, r.content, q.freshness);
|
|
9029
|
+
const relevanceScore = 0.35 * kwScore + 0.45 * sq + 0.2 * fs2;
|
|
8566
9030
|
const passage = extractPassage(q.query, r.content);
|
|
8567
9031
|
return {
|
|
8568
9032
|
url: r.url,
|
|
@@ -8570,40 +9034,45 @@ async function research(q, cfg2, fetchImpl) {
|
|
|
8570
9034
|
passage,
|
|
8571
9035
|
relevanceScore: Math.round(relevanceScore * 1e3) / 1e3,
|
|
8572
9036
|
freshnessSignal: signal,
|
|
8573
|
-
domain
|
|
9037
|
+
domain,
|
|
9038
|
+
publishedDate,
|
|
9039
|
+
ageDays
|
|
8574
9040
|
};
|
|
8575
9041
|
});
|
|
8576
9042
|
return scored.filter((r) => r.relevanceScore >= MIN_RELEVANCE_SCORE).sort((a, b) => b.relevanceScore - a.relevanceScore).slice(0, MAX_RESULTS);
|
|
8577
9043
|
}
|
|
8578
9044
|
|
|
8579
9045
|
// src/tools/builtin.ts
|
|
8580
|
-
|
|
8581
|
-
|
|
8582
|
-
|
|
8583
|
-
|
|
8584
|
-
|
|
8585
|
-
|
|
8586
|
-
|
|
8587
|
-
|
|
8588
|
-
|
|
8589
|
-
|
|
8590
|
-
|
|
8591
|
-
|
|
8592
|
-
|
|
8593
|
-
|
|
8594
|
-
|
|
8595
|
-
|
|
9046
|
+
function webSearchTool(today = todayIso()) {
|
|
9047
|
+
const year = today.slice(0, 4);
|
|
9048
|
+
return {
|
|
9049
|
+
type: "function",
|
|
9050
|
+
function: {
|
|
9051
|
+
name: "web_search",
|
|
9052
|
+
description: `Search the web for real-time information. TODAY IS ${today}. Call this BEFORE answering any question that depends on current events, recent data, prices, specific facts, or anything your training data may not cover accurately. DISCOVERY BEFORE RECALL: when you need what is best/current/latest in some category, your FIRST search must NOT name a specific product, model, version, brand or person you are recalling from memory \u2014 what you remember may have been replaced, and a search only returns what you ask about. Search the category itself plus the current year (e.g. "<category> ${year}", "latest <category> released"), read which names actually exist now, and only THEN search those names. Run multiple focused searches rather than one broad one for complex questions. Returns up to 8 pre-ranked results. Each entry carries a substantial excerpt from the page \u2014 not a one-line snippet \u2014 plus a relevanceScore (0\u20131), a source URL, and the publication date when the source reports one. Judge sources by authority, not only by how closely their wording echoes the question: prefer primary and official sources \u2014 the organisation's own site, official documentation, the original announcement, report, filing, or paper \u2014 over roundups, listicles and "best of" articles, which are often re-titled stale content dressed up as current. When an excerpt reads as authoritative but is too thin to answer from, call fetch_url on that result to read the full page rather than filling the gap from memory.`,
|
|
9053
|
+
parameters: {
|
|
9054
|
+
type: "object",
|
|
9055
|
+
properties: {
|
|
9056
|
+
query: {
|
|
9057
|
+
type: "string",
|
|
9058
|
+
description: `A precise, specific search query. Use key terms and identifiers. When recency matters use ${year} \u2014 never a year you recall from training, which is how a search silently returns correct answers about superseded things. Good: "Python 3.13 release date new features". Bad: "Python new stuff". Good: "NVIDIA RTX 5090 benchmark ${year}". Bad: "new GPU benchmarks".`
|
|
9059
|
+
},
|
|
9060
|
+
intent: {
|
|
9061
|
+
type: "string",
|
|
9062
|
+
enum: ["factual", "recent_news", "comparison", "how_to"],
|
|
9063
|
+
description: "Optional: the type of answer needed. Helps the retrieval service weight results appropriately."
|
|
9064
|
+
},
|
|
9065
|
+
freshness: {
|
|
9066
|
+
type: "string",
|
|
9067
|
+
enum: ["current", "any"],
|
|
9068
|
+
description: 'Optional: "current" asks the search provider for recent results and penalises sources published more than 90 days ago. Pass "current" whenever the answer can go stale: questions using latest/newest/current/right now/this year, software releases and version numbers, prices, benchmarks, rankings, ongoing events, and news. Omit it (or pass "any") for timeless background facts such as definitions, history, or how something works.'
|
|
9069
|
+
}
|
|
8596
9070
|
},
|
|
8597
|
-
|
|
8598
|
-
|
|
8599
|
-
enum: ["current", "any"],
|
|
8600
|
-
description: 'Optional: "current" penalises results older than 90 days. Use for breaking news or time-sensitive queries.'
|
|
8601
|
-
}
|
|
8602
|
-
},
|
|
8603
|
-
required: ["query"]
|
|
9071
|
+
required: ["query"]
|
|
9072
|
+
}
|
|
8604
9073
|
}
|
|
8605
|
-
}
|
|
8606
|
-
}
|
|
9074
|
+
};
|
|
9075
|
+
}
|
|
8607
9076
|
var FETCH_URL_TOOL = {
|
|
8608
9077
|
type: "function",
|
|
8609
9078
|
function: {
|
|
@@ -8632,11 +9101,92 @@ var RUN_CODE_TOOL = {
|
|
|
8632
9101
|
}
|
|
8633
9102
|
}
|
|
8634
9103
|
};
|
|
9104
|
+
function todayIso() {
|
|
9105
|
+
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
9106
|
+
}
|
|
9107
|
+
function normalizeIsoDate(raw) {
|
|
9108
|
+
if (!raw) return void 0;
|
|
9109
|
+
const match = /\d{4}-\d{2}-\d{2}/.exec(raw);
|
|
9110
|
+
if (!match) return void 0;
|
|
9111
|
+
const iso = match[0];
|
|
9112
|
+
const ms = Date.parse(`${iso}T00:00:00Z`);
|
|
9113
|
+
if (Number.isNaN(ms)) return void 0;
|
|
9114
|
+
if (new Date(ms).toISOString().slice(0, 10) !== iso) return void 0;
|
|
9115
|
+
const year = Number(iso.slice(0, 4));
|
|
9116
|
+
if (year < 1990 || year > (/* @__PURE__ */ new Date()).getUTCFullYear() + 1) return void 0;
|
|
9117
|
+
return iso;
|
|
9118
|
+
}
|
|
9119
|
+
function oneLine(s) {
|
|
9120
|
+
return s.replace(/[\u200b\u200e\u200f\u202a-\u202e\u2066-\u2069]/g, "").replace(/[\s\u0085]+/g, " ").trim();
|
|
9121
|
+
}
|
|
9122
|
+
function publishedLabel(r) {
|
|
9123
|
+
const iso = normalizeIsoDate(r.publishedDate);
|
|
9124
|
+
if (!iso) return "unknown";
|
|
9125
|
+
const age = r.ageDays;
|
|
9126
|
+
if (typeof age !== "number" || !Number.isFinite(age) || age < 0) return iso;
|
|
9127
|
+
if (age === 0) return `${iso} (today)`;
|
|
9128
|
+
return `${iso} (${age === 1 ? "1 day" : `${age} days`} ago)`;
|
|
9129
|
+
}
|
|
8635
9130
|
function resolveSearchQuery(args) {
|
|
8636
9131
|
if (typeof args.query === "string") return args.query;
|
|
8637
9132
|
if (Array.isArray(args.queries) && typeof args.queries[0] === "string") return args.queries[0];
|
|
8638
9133
|
return "";
|
|
8639
9134
|
}
|
|
9135
|
+
var DATE_PROBE_MAX_BYTES = 96e3;
|
|
9136
|
+
var DATE_PROBE_TIMEOUT_MS = 4e3;
|
|
9137
|
+
function sourceAdvisory(query, results) {
|
|
9138
|
+
if (results.length === 0) return null;
|
|
9139
|
+
const roundups = results.filter((r) => aggregatorPenalty(r.title, r.url) > 0).length;
|
|
9140
|
+
const primaries = results.filter(
|
|
9141
|
+
(r) => officialSourceBoost(query, r.url) > 0 || officialDocsBoost(r.url) > 0
|
|
9142
|
+
).length;
|
|
9143
|
+
if (primaries > 0 || roundups < Math.ceil(results.length / 2)) return null;
|
|
9144
|
+
return `CAUTION: ${roundups} of these ${results.length} results are roundup/"best of" pages and none is a primary or official source. Pages like these are frequently re-published with a new date and stale contents, so they establish which names EXIST, not which is current or correct. Treat every name below as a CANDIDATE, not an answer: before recommending one, search that name against its own official source (the maker/organisation/authority behind it) and confirm the specifics there. If you cannot confirm a candidate that way, say so rather than repeating the roundup.`;
|
|
9145
|
+
}
|
|
9146
|
+
async function backfillPublishedDates(results) {
|
|
9147
|
+
const missing = results.filter((r) => !r.publishedDate);
|
|
9148
|
+
if (missing.length === 0) return;
|
|
9149
|
+
await Promise.allSettled(
|
|
9150
|
+
missing.map(async (r) => {
|
|
9151
|
+
if (await checkSsrf(r.url)) return;
|
|
9152
|
+
let resp;
|
|
9153
|
+
try {
|
|
9154
|
+
resp = await fetch(r.url, {
|
|
9155
|
+
headers: { "User-Agent": "TurboLLM/0.7 (tool-fetch)" },
|
|
9156
|
+
signal: AbortSignal.timeout(DATE_PROBE_TIMEOUT_MS)
|
|
9157
|
+
});
|
|
9158
|
+
} catch {
|
|
9159
|
+
return;
|
|
9160
|
+
}
|
|
9161
|
+
if (!resp.ok || !resp.body) return;
|
|
9162
|
+
let html = "";
|
|
9163
|
+
try {
|
|
9164
|
+
const reader = resp.body.getReader();
|
|
9165
|
+
const decoder = new TextDecoder();
|
|
9166
|
+
let read = 0;
|
|
9167
|
+
while (read < DATE_PROBE_MAX_BYTES) {
|
|
9168
|
+
const { done, value } = await reader.read();
|
|
9169
|
+
if (done) break;
|
|
9170
|
+
read += value.byteLength;
|
|
9171
|
+
html += decoder.decode(value, { stream: true });
|
|
9172
|
+
}
|
|
9173
|
+
await reader.cancel().catch(() => {
|
|
9174
|
+
});
|
|
9175
|
+
} catch {
|
|
9176
|
+
return;
|
|
9177
|
+
}
|
|
9178
|
+
const iso = extractPublishedDate(html);
|
|
9179
|
+
if (!iso) return;
|
|
9180
|
+
r.publishedDate = iso;
|
|
9181
|
+
r.ageDays = ageInDays(iso);
|
|
9182
|
+
r.freshnessSignal = freshnessSignal(r.ageDays);
|
|
9183
|
+
})
|
|
9184
|
+
);
|
|
9185
|
+
}
|
|
9186
|
+
function webSearchUnavailableMessage(reason) {
|
|
9187
|
+
const cause = reason === "not_configured" ? "No web-search provider is configured (Settings \u2192 Tools)." : "Web search is not available in this conversation \u2014 the active engine did not receive the tool list.";
|
|
9188
|
+
return `${cause} You CANNOT search the web on this turn, and no results will arrive however many times you ask. Answer from your own knowledge only, and say so plainly at the top of your reply: state that you could not search, that what follows is from training data which may be out of date, and flag anything that is likely to have changed since. Do not claim or imply that you searched, do not invent sources, citations, URLs or dates, and do not emit a confidence line.`;
|
|
9189
|
+
}
|
|
8640
9190
|
async function execWebSearch(args, searchCfg) {
|
|
8641
9191
|
const query = resolveSearchQuery(args);
|
|
8642
9192
|
if (!query.trim()) return "Error: query is required.";
|
|
@@ -8649,16 +9199,75 @@ async function execWebSearch(args, searchCfg) {
|
|
|
8649
9199
|
return `Error: could not reach the ${searchCfg.provider} search provider \u2014 ${e.message}`;
|
|
8650
9200
|
}
|
|
8651
9201
|
if (results.length === 0) return "No results found.";
|
|
8652
|
-
|
|
9202
|
+
await backfillPublishedDates(results);
|
|
9203
|
+
if (freshness === "current") {
|
|
9204
|
+
for (const r of results) {
|
|
9205
|
+
if (typeof r.ageDays === "number") {
|
|
9206
|
+
r.relevanceScore = Math.round(r.relevanceScore * currentnessMultiplier(r.ageDays) * 1e3) / 1e3;
|
|
9207
|
+
}
|
|
9208
|
+
}
|
|
9209
|
+
results.sort((a, b) => b.relevanceScore - a.relevanceScore);
|
|
9210
|
+
}
|
|
9211
|
+
const retrieved = todayIso();
|
|
9212
|
+
const lines = [
|
|
9213
|
+
`RESEARCH RESULTS (${results.length} ranked results for "${query.replace(/\s+/g, " ")}"):`,
|
|
9214
|
+
`Retrieved: ${retrieved} \u2014 this search ran today, so treat ${retrieved} as the current date. Weigh each result against its Published date; "Published: unknown" means the source reported no date, not that it is current.`
|
|
9215
|
+
];
|
|
9216
|
+
const advisory = sourceAdvisory(query, results);
|
|
9217
|
+
if (advisory) lines.push(advisory);
|
|
8653
9218
|
for (const [i, r] of results.entries()) {
|
|
8654
9219
|
lines.push(`
|
|
8655
|
-
[${i + 1}] ${r.title}`);
|
|
9220
|
+
[${i + 1}] ${oneLine(r.title)}`);
|
|
8656
9221
|
lines.push(`Source: ${r.url}`);
|
|
8657
|
-
lines.push(`Domain: ${r.domain} | Relevance: ${r.relevanceScore.toFixed(2)} | Freshness: ${r.freshnessSignal}`);
|
|
8658
|
-
lines.push(`Key passage: ${r.passage}`);
|
|
9222
|
+
lines.push(`Domain: ${r.domain} | Relevance: ${r.relevanceScore.toFixed(2)} | Freshness: ${r.freshnessSignal} | Published: ${publishedLabel(r)} | Retrieved: ${retrieved}`);
|
|
9223
|
+
lines.push(`Key passage: ${oneLine(r.passage)}`);
|
|
8659
9224
|
}
|
|
8660
9225
|
return lines.join("\n").trim();
|
|
8661
9226
|
}
|
|
9227
|
+
var DATE_META_KEYS = /* @__PURE__ */ new Set([
|
|
9228
|
+
"article:published_time",
|
|
9229
|
+
"og:article:published_time",
|
|
9230
|
+
"og:published_time",
|
|
9231
|
+
"datepublished",
|
|
9232
|
+
"date",
|
|
9233
|
+
"dc.date",
|
|
9234
|
+
"dc.date.issued",
|
|
9235
|
+
"pubdate",
|
|
9236
|
+
"publish-date"
|
|
9237
|
+
]);
|
|
9238
|
+
function extractPublishedDate(html) {
|
|
9239
|
+
for (const block of html.matchAll(/<script[^>]*type=["']application\/ld\+json["'][^>]*>([\s\S]*?)<\/script>/gi)) {
|
|
9240
|
+
const iso = normalizeIsoDate(/"datePublished"\s*:\s*"([^"]{1,64})"/i.exec(block[1])?.[1]);
|
|
9241
|
+
if (iso) return iso;
|
|
9242
|
+
}
|
|
9243
|
+
for (const tag of html.matchAll(/<meta\s[^>]*>/gi)) {
|
|
9244
|
+
const key = /\b(?:property|name|itemprop)\s*=\s*["']([^"']{1,64})["']/i.exec(tag[0])?.[1];
|
|
9245
|
+
if (!key || !DATE_META_KEYS.has(key.trim().toLowerCase())) continue;
|
|
9246
|
+
const iso = normalizeIsoDate(/\bcontent\s*=\s*["']([^"']{1,64})["']/i.exec(tag[0])?.[1]);
|
|
9247
|
+
if (iso) return iso;
|
|
9248
|
+
}
|
|
9249
|
+
for (const tag of html.matchAll(/<time\s[^>]*>/gi)) {
|
|
9250
|
+
const iso = normalizeIsoDate(/\bdatetime\s*=\s*["']([^"']{1,64})["']/i.exec(tag[0])?.[1]);
|
|
9251
|
+
if (iso) return iso;
|
|
9252
|
+
}
|
|
9253
|
+
return void 0;
|
|
9254
|
+
}
|
|
9255
|
+
var MODIFIED_META_KEYS = /* @__PURE__ */ new Set(["article:modified_time", "og:updated_time", "datemodified", "last-modified", "dc.date.modified"]);
|
|
9256
|
+
function extractModifiedDate(html) {
|
|
9257
|
+
for (const block of html.matchAll(/<script[^>]*type=["']application\/ld\+json["'][^>]*>([\s\S]*?)<\/script>/gi)) {
|
|
9258
|
+
const iso = normalizeIsoDate(/"dateModified"\s*:\s*"([^"]{1,64})"/i.exec(block[1])?.[1]);
|
|
9259
|
+
if (iso) return iso;
|
|
9260
|
+
}
|
|
9261
|
+
for (const tag of html.matchAll(/<meta\s[^>]*>/gi)) {
|
|
9262
|
+
const key = /\b(?:property|name|itemprop)\s*=\s*["']([^"']{1,64})["']/i.exec(tag[0])?.[1];
|
|
9263
|
+
if (!key || !MODIFIED_META_KEYS.has(key.trim().toLowerCase())) continue;
|
|
9264
|
+
const iso = normalizeIsoDate(/\bcontent\s*=\s*["']([^"']{1,64})["']/i.exec(tag[0])?.[1]);
|
|
9265
|
+
if (iso) return iso;
|
|
9266
|
+
}
|
|
9267
|
+
return void 0;
|
|
9268
|
+
}
|
|
9269
|
+
var FETCH_URL_MAX_CHARS = 2e4;
|
|
9270
|
+
var FETCH_URL_TRUNCATION_MARKER = "\n[truncated: this page was longer than the fetch limit and is cut off here. If the answer is not above, fetch a more specific URL on this site rather than assuming the page lacks it.]";
|
|
8662
9271
|
async function execFetchUrl(args) {
|
|
8663
9272
|
const url = String(args.url ?? "").trim();
|
|
8664
9273
|
if (!url) return "Error: url is required.";
|
|
@@ -8677,13 +9286,20 @@ async function execFetchUrl(args) {
|
|
|
8677
9286
|
const contentType2 = resp.headers.get("content-type") ?? "";
|
|
8678
9287
|
const text = await resp.text().catch(() => "");
|
|
8679
9288
|
let content;
|
|
9289
|
+
let published;
|
|
9290
|
+
let modified;
|
|
8680
9291
|
if (contentType2.includes("text/html")) {
|
|
9292
|
+
published = extractPublishedDate(text);
|
|
9293
|
+
modified = extractModifiedDate(text);
|
|
8681
9294
|
content = text.replace(/<script[\s\S]*?<\/script>/gi, "").replace(/<style[\s\S]*?<\/style>/gi, "").replace(/<[^>]+>/g, " ").replace(/ /g, " ").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/\s{2,}/g, " ").trim();
|
|
8682
9295
|
} else {
|
|
8683
9296
|
content = text.trim();
|
|
8684
9297
|
}
|
|
8685
|
-
|
|
8686
|
-
|
|
9298
|
+
const header = modified && modified > (published ?? "") ? `Published: ${published ?? "unknown"} | Updated: ${modified} | Retrieved: ${todayIso()}` : `Published: ${published ?? "unknown"} | Retrieved: ${todayIso()}`;
|
|
9299
|
+
const budget = FETCH_URL_MAX_CHARS - header.length - 1 - FETCH_URL_TRUNCATION_MARKER.length;
|
|
9300
|
+
if (content.length > budget) content = content.slice(0, budget) + FETCH_URL_TRUNCATION_MARKER;
|
|
9301
|
+
return `${header}
|
|
9302
|
+
${content || "(empty response)"}`;
|
|
8687
9303
|
}
|
|
8688
9304
|
function execRunCode(args) {
|
|
8689
9305
|
const code = String(args.code ?? "").trim();
|
|
@@ -9007,7 +9623,7 @@ var ToolRegistry = class {
|
|
|
9007
9623
|
/** Build the full tools array to send to the engine. Returns [] when no tools are available. */
|
|
9008
9624
|
async buildToolDefinitions() {
|
|
9009
9625
|
const defs = [];
|
|
9010
|
-
if (searchConfigured(this.toolsCfg.search)) defs.push(
|
|
9626
|
+
if (searchConfigured(this.toolsCfg.search)) defs.push(webSearchTool());
|
|
9011
9627
|
defs.push(FETCH_URL_TOOL);
|
|
9012
9628
|
defs.push(RUN_CODE_TOOL);
|
|
9013
9629
|
for (const client of this.mcpClients.values()) {
|
|
@@ -9034,7 +9650,7 @@ var ToolRegistry = class {
|
|
|
9034
9650
|
const args = call.args;
|
|
9035
9651
|
if (name === "web_search") {
|
|
9036
9652
|
if (!searchConfigured(this.toolsCfg.search)) {
|
|
9037
|
-
return
|
|
9653
|
+
return `Error: ${webSearchUnavailableMessage("not_configured")}`;
|
|
9038
9654
|
}
|
|
9039
9655
|
return execWebSearch(args, this.toolsCfg.search);
|
|
9040
9656
|
}
|
|
@@ -9058,6 +9674,13 @@ var ToolRegistry = class {
|
|
|
9058
9674
|
}
|
|
9059
9675
|
return `Error: unknown tool "${name}"`;
|
|
9060
9676
|
}
|
|
9677
|
+
/** Whether web_search can actually execute (a provider is configured). Distinct from whether
|
|
9678
|
+
* the model can REACH it — the tool list may still be suppressed downstream (see the
|
|
9679
|
+
* engine-kind gate in chat-routes.ts). Callers that need to explain a failed research turn
|
|
9680
|
+
* use this to pick between the 'not_configured' and 'tools_unreachable' reasons. */
|
|
9681
|
+
searchAvailable() {
|
|
9682
|
+
return searchConfigured(this.toolsCfg.search);
|
|
9683
|
+
}
|
|
9061
9684
|
/** Whether any tools are currently available (determines if tools should be sent to engine). */
|
|
9062
9685
|
hasTools() {
|
|
9063
9686
|
if (searchConfigured(this.toolsCfg.search)) return true;
|
|
@@ -10317,7 +10940,7 @@ var BUILTIN_SKILLS = [
|
|
|
10317
10940
|
}
|
|
10318
10941
|
];
|
|
10319
10942
|
function toSkillMd(skill) {
|
|
10320
|
-
const fm = [`name: ${skill.name}`, `description: ${
|
|
10943
|
+
const fm = [`name: ${skill.name}`, `description: ${oneLine2(skill.description)}`];
|
|
10321
10944
|
if (skill.tools.length) fm.push(`tools: ${skill.tools.join(", ")}`);
|
|
10322
10945
|
return `---
|
|
10323
10946
|
${fm.join("\n")}
|
|
@@ -10345,7 +10968,7 @@ function fromSkillMd(id, raw) {
|
|
|
10345
10968
|
function unquote(s) {
|
|
10346
10969
|
return s.replace(/^["']|["']$/g, "").trim();
|
|
10347
10970
|
}
|
|
10348
|
-
function
|
|
10971
|
+
function oneLine2(s) {
|
|
10349
10972
|
return s.replace(/\s*\n\s*/g, " ").trim();
|
|
10350
10973
|
}
|
|
10351
10974
|
var SkillStore = class {
|
|
@@ -10628,6 +11251,19 @@ async function body(c) {
|
|
|
10628
11251
|
return {};
|
|
10629
11252
|
}
|
|
10630
11253
|
}
|
|
11254
|
+
var BAKED_DATE_LINE = /^Today['’]s date is [^\n]{0,140}$\n?/gm;
|
|
11255
|
+
function currentDateLine(now) {
|
|
11256
|
+
const y = now.getFullYear();
|
|
11257
|
+
const m = String(now.getMonth() + 1).padStart(2, "0");
|
|
11258
|
+
const d = String(now.getDate()).padStart(2, "0");
|
|
11259
|
+
return `Today's date is ${y}-${m}-${d}. Use it only when the request depends on the current date; otherwise ignore it.`;
|
|
11260
|
+
}
|
|
11261
|
+
function withCurrentDate(systemPrompt, now = /* @__PURE__ */ new Date()) {
|
|
11262
|
+
const base2 = systemPrompt.replace(BAKED_DATE_LINE, "").replace(/\n{3,}/g, "\n\n").trim();
|
|
11263
|
+
return base2 ? `${base2}
|
|
11264
|
+
|
|
11265
|
+
${currentDateLine(now)}` : currentDateLine(now);
|
|
11266
|
+
}
|
|
10631
11267
|
function registerChatRoutes(app2, d) {
|
|
10632
11268
|
const { db: db2 } = d;
|
|
10633
11269
|
app2.get("/api/v1/folders", (c) => {
|
|
@@ -10775,7 +11411,7 @@ function registerChatRoutes(app2, d) {
|
|
|
10775
11411
|
await stream.writeSSE({ event: "meta", data: JSON.stringify({ userMessageId: userMsg.id, assistantMessageId: assistantMsg.id }) });
|
|
10776
11412
|
const allMsgs = (conv.messages ?? []).filter((m) => m.id !== assistantMsg.id);
|
|
10777
11413
|
const engineMessages = [];
|
|
10778
|
-
if (conv.systemPrompt) engineMessages.push({ role: "system", content: conv.systemPrompt });
|
|
11414
|
+
if (conv.systemPrompt) engineMessages.push({ role: "system", content: withCurrentDate(conv.systemPrompt) });
|
|
10779
11415
|
for (const m of allMsgs) {
|
|
10780
11416
|
const content2 = conv.preserveThinking && m.role === "assistant" && m.reasoning?.trim() ? `<think>
|
|
10781
11417
|
${m.reasoning}
|
|
@@ -10821,7 +11457,7 @@ ${content}` : b.docContext : content;
|
|
|
10821
11457
|
await stream.writeSSE({ event: "meta", data: JSON.stringify({ userMessageId: lastUser.id, assistantMessageId: assistantMsg.id }) });
|
|
10822
11458
|
const allMsgs = (conv.messages ?? []).filter((m) => m.id !== assistantMsg.id);
|
|
10823
11459
|
const engineMessages = [];
|
|
10824
|
-
if (conv.systemPrompt) engineMessages.push({ role: "system", content: conv.systemPrompt });
|
|
11460
|
+
if (conv.systemPrompt) engineMessages.push({ role: "system", content: withCurrentDate(conv.systemPrompt) });
|
|
10825
11461
|
for (const m of allMsgs) {
|
|
10826
11462
|
const content = conv.preserveThinking && m.role === "assistant" && m.reasoning?.trim() ? `<think>
|
|
10827
11463
|
${m.reasoning}
|
|
@@ -11065,7 +11701,11 @@ ${existing}`.trim() };
|
|
|
11065
11701
|
}
|
|
11066
11702
|
}
|
|
11067
11703
|
}
|
|
11068
|
-
const
|
|
11704
|
+
const MAX_SEARCH_CALLS = 6;
|
|
11705
|
+
const MAX_CONFIDENCE_RETRIES = 2;
|
|
11706
|
+
const CONFIDENCE_INSTRUCTION = `
|
|
11707
|
+
|
|
11708
|
+
After reviewing the search results, include a confidence assessment on a line by itself before your final answer: \`[confidence: 0.XX]\` where XX is your confidence (0.0\u20131.0) that your answer is accurate and current. If your confidence is below 0.8, call web_search again with a more specific query first. Maximum ${MAX_SEARCH_CALLS} search calls per response.`;
|
|
11069
11709
|
if (conv.toolPolicy === "force_web_search") {
|
|
11070
11710
|
const sysIdx = iterMessages.findIndex((m) => m.role === "system");
|
|
11071
11711
|
if (sysIdx >= 0) {
|
|
@@ -11075,9 +11715,10 @@ ${existing}`.trim() };
|
|
|
11075
11715
|
iterMessages.unshift({ role: "system", content: CONFIDENCE_INSTRUCTION.trim() });
|
|
11076
11716
|
}
|
|
11077
11717
|
}
|
|
11078
|
-
const MAX_TOOL_ITER =
|
|
11718
|
+
const MAX_TOOL_ITER = 16;
|
|
11079
11719
|
let toolIter = 0;
|
|
11080
11720
|
let searchCallCount = 0;
|
|
11721
|
+
let confidenceRetries = 0;
|
|
11081
11722
|
const allResearchSources = [];
|
|
11082
11723
|
let parsedConfidence;
|
|
11083
11724
|
let fullContent = "";
|
|
@@ -11295,7 +11936,7 @@ ${existing}`.trim() };
|
|
|
11295
11936
|
if (tc.name === "web_search" && !callError) {
|
|
11296
11937
|
searchCallCount++;
|
|
11297
11938
|
try {
|
|
11298
|
-
const sourceMatches = [...result.matchAll(/\[(\d+)\] (.+?)\nSource: (\S+)\nDomain: (\S+) \| Relevance: ([\d.]+) \| Freshness: (\w+)\nKey passage: ([\s\S]+?)(?=\n\[|\s*$)/g)];
|
|
11939
|
+
const sourceMatches = [...result.matchAll(/\[(\d+)\] (.+?)\nSource: (\S+)\nDomain: (\S+) \| Relevance: ([\d.]+) \| Freshness: (\w+)(?: \| Published: ([^|\n]*?))?(?: \| Retrieved: ([\d-]+))?\nKey passage: ([\s\S]+?)(?=\n\[|\s*$)/g)];
|
|
11299
11940
|
for (const m of sourceMatches) {
|
|
11300
11941
|
allResearchSources.push({
|
|
11301
11942
|
title: m[2].trim(),
|
|
@@ -11303,7 +11944,7 @@ ${existing}`.trim() };
|
|
|
11303
11944
|
domain: m[4].trim(),
|
|
11304
11945
|
relevanceScore: parseFloat(m[5]),
|
|
11305
11946
|
freshnessSignal: m[6].trim(),
|
|
11306
|
-
passage: m[
|
|
11947
|
+
passage: m[9].trim()
|
|
11307
11948
|
});
|
|
11308
11949
|
}
|
|
11309
11950
|
} catch {
|
|
@@ -11320,8 +11961,9 @@ ${existing}`.trim() };
|
|
|
11320
11961
|
const conf = parseFloat(confMatch[1]);
|
|
11321
11962
|
parsedConfidence = conf;
|
|
11322
11963
|
fullContent = fullContent.replace(/\[confidence:\s*[\d.]+\]\s*/gi, "").trim();
|
|
11323
|
-
if (conf < 0.8 && searchCallCount <
|
|
11324
|
-
|
|
11964
|
+
if (conf < 0.8 && searchCallCount < MAX_SEARCH_CALLS && confidenceRetries < MAX_CONFIDENCE_RETRIES) {
|
|
11965
|
+
confidenceRetries++;
|
|
11966
|
+
console.log(`[chat] F-021: confidence ${conf} < 0.8 (searches: ${searchCallCount}/${MAX_SEARCH_CALLS}, retry ${confidenceRetries}/${MAX_CONFIDENCE_RETRIES}) \u2014 re-entering search loop`);
|
|
11325
11967
|
const toolDefs2 = await d.tools.buildToolDefinitions();
|
|
11326
11968
|
if (toolDefs2.some((t) => t.function.name === "web_search")) {
|
|
11327
11969
|
iterMessages.push({ role: "assistant", content: fullContent });
|
|
@@ -15208,6 +15850,33 @@ function keepRecentTokensFor(messages, baseKeepRecentTokens) {
|
|
|
15208
15850
|
}
|
|
15209
15851
|
var MUTATING_TOOLS = /* @__PURE__ */ new Set(["edit", "write", "bash"]);
|
|
15210
15852
|
var PATH_TOOLS = /* @__PURE__ */ new Set(["read", "edit", "write", "grep", "find", "ls"]);
|
|
15853
|
+
var LOOP_BREAK_AFTER = 3;
|
|
15854
|
+
function toolCallSignature(name, input) {
|
|
15855
|
+
const stable = (v) => {
|
|
15856
|
+
if (v === null || typeof v !== "object") return JSON.stringify(v) ?? "undefined";
|
|
15857
|
+
if (Array.isArray(v)) return `[${v.map(stable).join(",")}]`;
|
|
15858
|
+
const o = v;
|
|
15859
|
+
return `{${Object.keys(o).sort().map((k) => `${JSON.stringify(k)}:${stable(o[k])}`).join(",")}}`;
|
|
15860
|
+
};
|
|
15861
|
+
return `${name}\0${stable(input)}`;
|
|
15862
|
+
}
|
|
15863
|
+
var ToolLoopTracker = class {
|
|
15864
|
+
lastSig = null;
|
|
15865
|
+
count = 0;
|
|
15866
|
+
record(name, input) {
|
|
15867
|
+
const sig = toolCallSignature(name, input);
|
|
15868
|
+
if (sig === this.lastSig) this.count += 1;
|
|
15869
|
+
else {
|
|
15870
|
+
this.lastSig = sig;
|
|
15871
|
+
this.count = 1;
|
|
15872
|
+
}
|
|
15873
|
+
return this.count;
|
|
15874
|
+
}
|
|
15875
|
+
reset() {
|
|
15876
|
+
this.lastSig = null;
|
|
15877
|
+
this.count = 0;
|
|
15878
|
+
}
|
|
15879
|
+
};
|
|
15211
15880
|
var DEPENDENCY_ADD_PATTERNS = [
|
|
15212
15881
|
/\bnpm\s+(i|install|add)\s+\S/,
|
|
15213
15882
|
/\byarn\s+add\s+\S/,
|
|
@@ -15291,6 +15960,7 @@ async function runCodeSession(params) {
|
|
|
15291
15960
|
};
|
|
15292
15961
|
let consecutiveToolFailures = 0;
|
|
15293
15962
|
let hasSearchedWebThisTurn = false;
|
|
15963
|
+
const toolLoop = new ToolLoopTracker();
|
|
15294
15964
|
const getLspClient = (spec) => {
|
|
15295
15965
|
let clients = lspClientsByConv.get(convId);
|
|
15296
15966
|
if (!clients) {
|
|
@@ -15452,15 +16122,16 @@ async function runCodeSession(params) {
|
|
|
15452
16122
|
pi.on("after_provider_response", () => {
|
|
15453
16123
|
releaseGate();
|
|
15454
16124
|
});
|
|
15455
|
-
pi.on("turn_start", () => {
|
|
15456
|
-
consecutiveToolFailures = 0;
|
|
15457
|
-
hasSearchedWebThisTurn = false;
|
|
15458
|
-
});
|
|
15459
16125
|
pi.on("tool_call", async (event) => {
|
|
15460
16126
|
const toolName = event.toolName;
|
|
15461
16127
|
const toolCallId = event.toolCallId;
|
|
15462
16128
|
const input = event.input;
|
|
15463
16129
|
await sink({ event: "tool_call", data: { id: toolCallId, name: toolName, args: input, status: "pending" } });
|
|
16130
|
+
if (toolLoop.record(toolName, input) > LOOP_BREAK_AFTER) {
|
|
16131
|
+
const reason = `[SYSTEM: you have called \`${toolName}\` with identical arguments too many times in a row and it is not making progress \u2014 this call was NOT executed. Stop repeating it: take a different action, call it with different arguments, or, if you already have what you need, give your final answer now.]`;
|
|
16132
|
+
await sink({ event: "tool_call", data: { id: toolCallId, name: toolName, args: input, status: "error", result: reason } });
|
|
16133
|
+
return { block: true, reason };
|
|
16134
|
+
}
|
|
15464
16135
|
if (PATH_TOOLS.has(toolName)) {
|
|
15465
16136
|
const p = input.path;
|
|
15466
16137
|
const pathRequired = toolName === "read" || toolName === "edit" || toolName === "write";
|