@makefinks/daemon 0.1.4 → 0.2.0

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.
@@ -0,0 +1,155 @@
1
+ import type { ContentBlock, ConversationMessage, GroundingMap, UrlMenuItem } from "../types";
2
+
3
+ function normalizeUrlKey(rawUrl: string): string {
4
+ try {
5
+ const parsed = new URL(rawUrl);
6
+ parsed.hash = "";
7
+ return parsed.toString();
8
+ } catch {
9
+ return rawUrl;
10
+ }
11
+ }
12
+
13
+ function computeCoveragePercent(intervals: Array<[number, number]>, totalLines: number): number | undefined {
14
+ if (!Number.isFinite(totalLines) || totalLines <= 0) return undefined;
15
+ if (intervals.length === 0) return undefined;
16
+
17
+ const sorted = [...intervals].sort((a, b) => a[0] - b[0]);
18
+ let covered = 0;
19
+ let curStart = sorted[0]?.[0] ?? 0;
20
+ let curEnd = sorted[0]?.[1] ?? 0;
21
+
22
+ for (const [start, end] of sorted.slice(1)) {
23
+ if (start <= curEnd) {
24
+ curEnd = Math.max(curEnd, end);
25
+ continue;
26
+ }
27
+ covered += Math.max(0, curEnd - curStart);
28
+ curStart = start;
29
+ curEnd = end;
30
+ }
31
+ covered += Math.max(0, curEnd - curStart);
32
+
33
+ const percent = Math.round((covered / totalLines) * 100);
34
+ return Math.max(0, Math.min(100, percent));
35
+ }
36
+
37
+ export function deriveUrlMenuItems(params: {
38
+ conversationHistory: ConversationMessage[];
39
+ currentContentBlocks: ContentBlock[];
40
+ latestGroundingMap: GroundingMap | null;
41
+ }): UrlMenuItem[] {
42
+ const { conversationHistory, currentContentBlocks, latestGroundingMap } = params;
43
+
44
+ const intervalsByUrl = new Map<string, Array<[number, number]>>();
45
+ const totalLinesByUrl = new Map<string, number>();
46
+ const highlightsCountByUrl = new Map<string, number>();
47
+ const lastSeenIndexByUrl = new Map<string, number>();
48
+ const statusByUrl = new Map<string, "ok" | "error">();
49
+ const errorByUrl = new Map<string, string>();
50
+
51
+ const allBlocks = [
52
+ ...conversationHistory.flatMap((msg) => msg.contentBlocks ?? []),
53
+ ...currentContentBlocks,
54
+ ];
55
+
56
+ for (const [blockIndex, block] of allBlocks.entries()) {
57
+ if (block.type !== "tool") continue;
58
+ if (block.call.name !== "fetchUrls" && block.call.name !== "renderUrl") continue;
59
+
60
+ const input = block.call.input as { url?: string } | undefined;
61
+ const url = input?.url;
62
+ if (!url) continue;
63
+
64
+ lastSeenIndexByUrl.set(url, blockIndex);
65
+
66
+ const result = block.result as
67
+ | {
68
+ lineOffset?: number;
69
+ lineLimit?: number;
70
+ totalLines?: number;
71
+ highlights?: unknown[];
72
+ success?: boolean;
73
+ error?: string;
74
+ }
75
+ | undefined;
76
+
77
+ if (!result || typeof result !== "object") continue;
78
+
79
+ if (result.success === false && typeof result.error === "string" && result.error.trim().length > 0) {
80
+ statusByUrl.set(url, "error");
81
+ errorByUrl.set(url, result.error.trim());
82
+ } else if (result.success === true) {
83
+ statusByUrl.set(url, "ok");
84
+ }
85
+
86
+ if (Array.isArray(result.highlights)) {
87
+ highlightsCountByUrl.set(url, result.highlights.length);
88
+ }
89
+
90
+ if (
91
+ typeof result.totalLines === "number" &&
92
+ Number.isFinite(result.totalLines) &&
93
+ result.totalLines > 0
94
+ ) {
95
+ const prev = totalLinesByUrl.get(url) ?? 0;
96
+ totalLinesByUrl.set(url, Math.max(prev, result.totalLines));
97
+ }
98
+
99
+ if (
100
+ typeof result.lineOffset === "number" &&
101
+ typeof result.lineLimit === "number" &&
102
+ Number.isFinite(result.lineOffset) &&
103
+ Number.isFinite(result.lineLimit) &&
104
+ result.lineLimit > 0 &&
105
+ result.lineOffset >= 0
106
+ ) {
107
+ const start = result.lineOffset;
108
+ const end = result.lineOffset + result.lineLimit;
109
+ const list = intervalsByUrl.get(url) ?? [];
110
+ list.push([start, end]);
111
+ intervalsByUrl.set(url, list);
112
+ }
113
+ }
114
+
115
+ const groundedCountByUrl = new Map<string, number>();
116
+ for (const groundedItem of latestGroundingMap?.items ?? []) {
117
+ const groundedUrl = groundedItem.source?.url;
118
+ if (!groundedUrl) continue;
119
+ const next = (groundedCountByUrl.get(groundedUrl) ?? 0) + 1;
120
+ groundedCountByUrl.set(groundedUrl, next);
121
+ }
122
+
123
+ function lookupGroundedCount(url: string): number {
124
+ const direct = groundedCountByUrl.get(url);
125
+ if (direct !== undefined) return direct;
126
+
127
+ const key = normalizeUrlKey(url);
128
+ for (const [gUrl, count] of groundedCountByUrl.entries()) {
129
+ if (normalizeUrlKey(gUrl) === key) return count;
130
+ }
131
+ return 0;
132
+ }
133
+
134
+ const urls = [...lastSeenIndexByUrl.keys()];
135
+ return urls.map((url) => {
136
+ const groundedCount = lookupGroundedCount(url);
137
+ const highlightsCount = highlightsCountByUrl.get(url);
138
+ const totalLines = totalLinesByUrl.get(url);
139
+ const intervals = intervalsByUrl.get(url) ?? [];
140
+ const readPercent = totalLines !== undefined ? computeCoveragePercent(intervals, totalLines) : undefined;
141
+ const error = errorByUrl.get(url);
142
+ const status = statusByUrl.get(url) ?? (error ? "error" : "ok");
143
+ const lastSeenIndex = lastSeenIndexByUrl.get(url) ?? 0;
144
+
145
+ return {
146
+ url,
147
+ groundedCount,
148
+ readPercent,
149
+ highlightsCount,
150
+ status,
151
+ error,
152
+ lastSeenIndex,
153
+ };
154
+ });
155
+ }
@@ -209,14 +209,8 @@ export function isTodoInput(input: unknown): input is TodoInput {
209
209
  );
210
210
  }
211
211
 
212
- /**
213
- * Format token count with K suffix for thousands
214
- */
215
212
  export function formatTokenCount(count: number): string {
216
- // if (count >= 1000) {
217
- // return `${(count / 1000).toFixed(1)}k`;
218
- // }
219
- return String(count);
213
+ return count.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ".");
220
214
  }
221
215
 
222
216
  export function formatContextWindowK(contextLength: number): string {