qlogicagent 0.2.1 → 0.4.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.
Files changed (226) hide show
  1. package/README.md +45 -45
  2. package/package.json +56 -42
  3. package/dist/agent/agent.d.ts +0 -43
  4. package/dist/agent/agent.js +0 -113
  5. package/dist/agent/tool-loop.d.ts +0 -64
  6. package/dist/agent/tool-loop.js +0 -575
  7. package/dist/agent/types.d.ts +0 -175
  8. package/dist/agent/types.js +0 -14
  9. package/dist/cli/main.d.ts +0 -11
  10. package/dist/cli/main.js +0 -23
  11. package/dist/cli/stdio-server.d.ts +0 -45
  12. package/dist/cli/stdio-server.js +0 -463
  13. package/dist/config/config.d.ts +0 -17
  14. package/dist/config/config.js +0 -21
  15. package/dist/contracts/hooks.d.ts +0 -120
  16. package/dist/contracts/hooks.js +0 -7
  17. package/dist/contracts/index.d.ts +0 -10
  18. package/dist/contracts/index.js +0 -10
  19. package/dist/contracts/planner.d.ts +0 -35
  20. package/dist/contracts/planner.js +0 -2
  21. package/dist/contracts/skill-candidate.d.ts +0 -63
  22. package/dist/contracts/skill-candidate.js +0 -195
  23. package/dist/contracts/todo.d.ts +0 -14
  24. package/dist/contracts/todo.js +0 -9
  25. package/dist/index.d.ts +0 -13
  26. package/dist/index.js +0 -15
  27. package/dist/llm/builtin-providers.d.ts +0 -10
  28. package/dist/llm/builtin-providers.js +0 -531
  29. package/dist/llm/index.d.ts +0 -15
  30. package/dist/llm/index.js +0 -14
  31. package/dist/llm/llm-client.d.ts +0 -43
  32. package/dist/llm/llm-client.js +0 -67
  33. package/dist/llm/model-catalog.d.ts +0 -53
  34. package/dist/llm/model-catalog.js +0 -191
  35. package/dist/llm/provider-def.d.ts +0 -59
  36. package/dist/llm/provider-def.js +0 -12
  37. package/dist/llm/provider-registry.d.ts +0 -54
  38. package/dist/llm/provider-registry.js +0 -147
  39. package/dist/llm/transport.d.ts +0 -62
  40. package/dist/llm/transport.js +0 -27
  41. package/dist/llm/transports/anthropic-messages.d.ts +0 -31
  42. package/dist/llm/transports/anthropic-messages.js +0 -293
  43. package/dist/llm/transports/openai-chat.d.ts +0 -36
  44. package/dist/llm/transports/openai-chat.js +0 -165
  45. package/dist/orchestration/agent-registry.d.ts +0 -41
  46. package/dist/orchestration/agent-registry.js +0 -116
  47. package/dist/orchestration/approval-aware-tool-plan.d.ts +0 -32
  48. package/dist/orchestration/approval-aware-tool-plan.js +0 -87
  49. package/dist/orchestration/context-compression.d.ts +0 -220
  50. package/dist/orchestration/context-compression.js +0 -583
  51. package/dist/orchestration/conversation-repair.d.ts +0 -61
  52. package/dist/orchestration/conversation-repair.js +0 -429
  53. package/dist/orchestration/curator-scheduler.d.ts +0 -119
  54. package/dist/orchestration/curator-scheduler.js +0 -135
  55. package/dist/orchestration/embedded-failover-policy.d.ts +0 -110
  56. package/dist/orchestration/embedded-failover-policy.js +0 -168
  57. package/dist/orchestration/error-classification.d.ts +0 -12
  58. package/dist/orchestration/error-classification.js +0 -77
  59. package/dist/orchestration/failover-classification.d.ts +0 -8
  60. package/dist/orchestration/failover-classification.js +0 -381
  61. package/dist/orchestration/failover-error.d.ts +0 -33
  62. package/dist/orchestration/failover-error.js +0 -198
  63. package/dist/orchestration/fork-subagent.d.ts +0 -100
  64. package/dist/orchestration/fork-subagent.js +0 -98
  65. package/dist/orchestration/index.d.ts +0 -120
  66. package/dist/orchestration/index.js +0 -267
  67. package/dist/orchestration/memory-flush-policy.d.ts +0 -57
  68. package/dist/orchestration/memory-flush-policy.js +0 -85
  69. package/dist/orchestration/memory-provider.d.ts +0 -14
  70. package/dist/orchestration/memory-provider.js +0 -2
  71. package/dist/orchestration/parallel-tool-calls.d.ts +0 -41
  72. package/dist/orchestration/parallel-tool-calls.js +0 -59
  73. package/dist/orchestration/prompt-cache-strategy.d.ts +0 -126
  74. package/dist/orchestration/prompt-cache-strategy.js +0 -228
  75. package/dist/orchestration/reactive-compact.d.ts +0 -73
  76. package/dist/orchestration/reactive-compact.js +0 -78
  77. package/dist/orchestration/retry-loop.d.ts +0 -22
  78. package/dist/orchestration/retry-loop.js +0 -24
  79. package/dist/orchestration/skill-candidate.d.ts +0 -52
  80. package/dist/orchestration/skill-candidate.js +0 -141
  81. package/dist/orchestration/skill-consolidation.d.ts +0 -123
  82. package/dist/orchestration/skill-consolidation.js +0 -220
  83. package/dist/orchestration/skill-improvement.d.ts +0 -59
  84. package/dist/orchestration/skill-improvement.js +0 -66
  85. package/dist/orchestration/skill-similarity.d.ts +0 -98
  86. package/dist/orchestration/skill-similarity.js +0 -131
  87. package/dist/orchestration/streaming-tool-executor.d.ts +0 -73
  88. package/dist/orchestration/streaming-tool-executor.js +0 -96
  89. package/dist/orchestration/team-orchestration.d.ts +0 -195
  90. package/dist/orchestration/team-orchestration.js +0 -369
  91. package/dist/orchestration/team-tool-loop-wiring.d.ts +0 -92
  92. package/dist/orchestration/team-tool-loop-wiring.js +0 -147
  93. package/dist/orchestration/tool-choice-policy.d.ts +0 -54
  94. package/dist/orchestration/tool-choice-policy.js +0 -164
  95. package/dist/orchestration/tool-loop-state.d.ts +0 -50
  96. package/dist/orchestration/tool-loop-state.js +0 -133
  97. package/dist/orchestration/tool-schema.d.ts +0 -39
  98. package/dist/orchestration/tool-schema.js +0 -297
  99. package/dist/orchestration/transcript-repair.d.ts +0 -42
  100. package/dist/orchestration/transcript-repair.js +0 -426
  101. package/dist/orchestration/turn-loop-guard.d.ts +0 -86
  102. package/dist/orchestration/turn-loop-guard.js +0 -92
  103. package/dist/orchestration/web-browser-policy.d.ts +0 -17
  104. package/dist/orchestration/web-browser-policy.js +0 -39
  105. package/dist/runtime/context-compression.d.ts +0 -61
  106. package/dist/runtime/context-compression.js +0 -274
  107. package/dist/runtime/hook-registry.d.ts +0 -12
  108. package/dist/runtime/hook-registry.js +0 -53
  109. package/dist/runtime/memory-hooks.d.ts +0 -23
  110. package/dist/runtime/memory-hooks.js +0 -65
  111. package/dist/runtime/tool-eligibility.d.ts +0 -59
  112. package/dist/runtime/tool-eligibility.js +0 -111
  113. package/dist/skills/index.d.ts +0 -108
  114. package/dist/skills/index.js +0 -82
  115. package/dist/skills/memory-extractor.d.ts +0 -64
  116. package/dist/skills/memory-extractor.js +0 -173
  117. package/dist/skills/memory-query-tool.d.ts +0 -43
  118. package/dist/skills/memory-query-tool.js +0 -127
  119. package/dist/skills/memory-store.d.ts +0 -66
  120. package/dist/skills/memory-store.js +0 -228
  121. package/dist/skills/memory-tool.d.ts +0 -67
  122. package/dist/skills/memory-tool.js +0 -192
  123. package/dist/skills/portable-tool.d.ts +0 -71
  124. package/dist/skills/portable-tool.js +0 -14
  125. package/dist/skills/qmemory-adapter.d.ts +0 -52
  126. package/dist/skills/qmemory-adapter.js +0 -165
  127. package/dist/skills/skill-frontmatter.d.ts +0 -19
  128. package/dist/skills/skill-frontmatter.js +0 -344
  129. package/dist/skills/skill-guard.d.ts +0 -23
  130. package/dist/skills/skill-guard.js +0 -229
  131. package/dist/skills/skill-loader.d.ts +0 -16
  132. package/dist/skills/skill-loader.js +0 -303
  133. package/dist/skills/skill-source.d.ts +0 -119
  134. package/dist/skills/skill-source.js +0 -126
  135. package/dist/skills/skill-types.d.ts +0 -199
  136. package/dist/skills/skill-types.js +0 -6
  137. package/dist/skills/think-tool.d.ts +0 -16
  138. package/dist/skills/think-tool.js +0 -59
  139. package/dist/skills/todo-tool.d.ts +0 -63
  140. package/dist/skills/todo-tool.js +0 -114
  141. package/dist/skills/tools/agent-tool.d.ts +0 -91
  142. package/dist/skills/tools/agent-tool.js +0 -142
  143. package/dist/skills/tools/apply-patch-tool.d.ts +0 -29
  144. package/dist/skills/tools/apply-patch-tool.js +0 -184
  145. package/dist/skills/tools/ask-user-tool.d.ts +0 -80
  146. package/dist/skills/tools/ask-user-tool.js +0 -121
  147. package/dist/skills/tools/brief-tool.d.ts +0 -74
  148. package/dist/skills/tools/brief-tool.js +0 -95
  149. package/dist/skills/tools/browser-tool.d.ts +0 -114
  150. package/dist/skills/tools/browser-tool.js +0 -155
  151. package/dist/skills/tools/checkpoint-tool.d.ts +0 -66
  152. package/dist/skills/tools/checkpoint-tool.js +0 -102
  153. package/dist/skills/tools/config-tool.d.ts +0 -63
  154. package/dist/skills/tools/config-tool.js +0 -143
  155. package/dist/skills/tools/cron-tool.d.ts +0 -116
  156. package/dist/skills/tools/cron-tool.js +0 -175
  157. package/dist/skills/tools/edit-tool.d.ts +0 -43
  158. package/dist/skills/tools/edit-tool.js +0 -70
  159. package/dist/skills/tools/exec-tool.d.ts +0 -102
  160. package/dist/skills/tools/exec-tool.js +0 -133
  161. package/dist/skills/tools/image-generate-tool.d.ts +0 -62
  162. package/dist/skills/tools/image-generate-tool.js +0 -67
  163. package/dist/skills/tools/instructions-tool.d.ts +0 -103
  164. package/dist/skills/tools/instructions-tool.js +0 -187
  165. package/dist/skills/tools/lsp-tool.d.ts +0 -153
  166. package/dist/skills/tools/lsp-tool.js +0 -227
  167. package/dist/skills/tools/mcp-client-types.d.ts +0 -269
  168. package/dist/skills/tools/mcp-client-types.js +0 -53
  169. package/dist/skills/tools/mcp-tool.d.ts +0 -249
  170. package/dist/skills/tools/mcp-tool.js +0 -503
  171. package/dist/skills/tools/memory-tool.d.ts +0 -74
  172. package/dist/skills/tools/memory-tool.js +0 -88
  173. package/dist/skills/tools/monitor-tool.d.ts +0 -113
  174. package/dist/skills/tools/monitor-tool.js +0 -131
  175. package/dist/skills/tools/music-generate-tool.d.ts +0 -55
  176. package/dist/skills/tools/music-generate-tool.js +0 -62
  177. package/dist/skills/tools/notify-tool.d.ts +0 -53
  178. package/dist/skills/tools/notify-tool.js +0 -62
  179. package/dist/skills/tools/patch-tool.d.ts +0 -45
  180. package/dist/skills/tools/patch-tool.js +0 -505
  181. package/dist/skills/tools/pdf-tool.d.ts +0 -66
  182. package/dist/skills/tools/pdf-tool.js +0 -88
  183. package/dist/skills/tools/plan-mode-tool.d.ts +0 -59
  184. package/dist/skills/tools/plan-mode-tool.js +0 -122
  185. package/dist/skills/tools/read-tool.d.ts +0 -51
  186. package/dist/skills/tools/read-tool.js +0 -84
  187. package/dist/skills/tools/repl-tool.d.ts +0 -70
  188. package/dist/skills/tools/repl-tool.js +0 -69
  189. package/dist/skills/tools/search-tool.d.ts +0 -112
  190. package/dist/skills/tools/search-tool.js +0 -225
  191. package/dist/skills/tools/send-message-tool.d.ts +0 -51
  192. package/dist/skills/tools/send-message-tool.js +0 -76
  193. package/dist/skills/tools/skill-list-tool.d.ts +0 -33
  194. package/dist/skills/tools/skill-list-tool.js +0 -54
  195. package/dist/skills/tools/skill-manage-tool.d.ts +0 -73
  196. package/dist/skills/tools/skill-manage-tool.js +0 -153
  197. package/dist/skills/tools/skill-view-tool.d.ts +0 -37
  198. package/dist/skills/tools/skill-view-tool.js +0 -72
  199. package/dist/skills/tools/sleep-tool.d.ts +0 -49
  200. package/dist/skills/tools/sleep-tool.js +0 -81
  201. package/dist/skills/tools/structured-output-tool.d.ts +0 -116
  202. package/dist/skills/tools/structured-output-tool.js +0 -176
  203. package/dist/skills/tools/task-tool.d.ts +0 -104
  204. package/dist/skills/tools/task-tool.js +0 -161
  205. package/dist/skills/tools/team-tool.d.ts +0 -89
  206. package/dist/skills/tools/team-tool.js +0 -105
  207. package/dist/skills/tools/tool-search-tool.d.ts +0 -51
  208. package/dist/skills/tools/tool-search-tool.js +0 -110
  209. package/dist/skills/tools/tts-tool.d.ts +0 -38
  210. package/dist/skills/tools/tts-tool.js +0 -45
  211. package/dist/skills/tools/video-edit-tool.d.ts +0 -69
  212. package/dist/skills/tools/video-edit-tool.js +0 -74
  213. package/dist/skills/tools/video-generate-tool.d.ts +0 -62
  214. package/dist/skills/tools/video-generate-tool.js +0 -66
  215. package/dist/skills/tools/video-merge-tool.d.ts +0 -105
  216. package/dist/skills/tools/video-merge-tool.js +0 -92
  217. package/dist/skills/tools/video-upscale-tool.d.ts +0 -45
  218. package/dist/skills/tools/video-upscale-tool.js +0 -52
  219. package/dist/skills/tools/web-fetch-tool.d.ts +0 -78
  220. package/dist/skills/tools/web-fetch-tool.js +0 -92
  221. package/dist/skills/tools/web-search-tool.d.ts +0 -57
  222. package/dist/skills/tools/web-search-tool.js +0 -86
  223. package/dist/skills/tools/worktree-tool.d.ts +0 -69
  224. package/dist/skills/tools/worktree-tool.js +0 -147
  225. package/dist/skills/tools/write-tool.d.ts +0 -45
  226. package/dist/skills/tools/write-tool.js +0 -81
@@ -1,505 +0,0 @@
1
- // ============================================================
2
- // Patch Tool — fuzzy-match file editing with 9-strategy degradation.
3
- // Reference: hermes-agent-main/tools/fuzzy_match.py (9-strategy chain)
4
- // hermes-agent-main/tools/patch_parser.py (V4A format)
5
- // Category: coding
6
- // ============================================================
7
- export const PATCH_TOOL_NAME = "patch";
8
- export const PATCH_TOOL_SCHEMA = {
9
- type: "object",
10
- properties: {
11
- input: {
12
- type: "string",
13
- description: "Patch content. Supports two formats:\n" +
14
- "1) V4A unified diff (*** Begin Patch / *** End Patch markers)\n" +
15
- "2) Simple find-replace: first line is file path, then <<<< SEARCH / ==== / >>>> REPLACE blocks.\n" +
16
- "Fuzzy matching automatically handles whitespace/indent/unicode drift in the search text.",
17
- },
18
- replaceAll: {
19
- type: "boolean",
20
- description: "Replace all occurrences instead of first only (default: false).",
21
- },
22
- },
23
- required: ["input"],
24
- };
25
- function escapeNormalize(s) {
26
- return s.replace(/\\n/g, "\n").replace(/\\t/g, "\t").replace(/\\'/g, "'").replace(/\\"/g, '"');
27
- }
28
- function unicodeNormalize(s) {
29
- return s
30
- .replace(/[\u201c\u201d\u201e\u201f]/g, '"')
31
- .replace(/[\u2018\u2019\u201a\u201b]/g, "'")
32
- .replace(/\u2014/g, "--")
33
- .replace(/\u2013/g, "-")
34
- .replace(/\u2026/g, "...")
35
- .replace(/\u00a0/g, " ");
36
- }
37
- function lineSimilarity(a, b) {
38
- if (a === b)
39
- return 1;
40
- const longer = Math.max(a.length, b.length);
41
- if (longer === 0)
42
- return 1;
43
- let dist = 0;
44
- const la = a.length;
45
- const lb = b.length;
46
- const maxLen = Math.max(la, lb);
47
- // Simplified: character-level distance ratio
48
- for (let i = 0; i < Math.min(la, lb); i++) {
49
- if (a[i] !== b[i])
50
- dist++;
51
- }
52
- dist += Math.abs(la - lb);
53
- return 1 - dist / maxLen;
54
- }
55
- function fuzzyFind(content, search, replaceAll) {
56
- const results = [];
57
- // Strategy 1: exact
58
- let idx = content.indexOf(search);
59
- if (idx !== -1) {
60
- if (replaceAll) {
61
- let pos = 0;
62
- while ((idx = content.indexOf(search, pos)) !== -1) {
63
- results.push({ start: idx, end: idx + search.length, strategy: "exact" });
64
- pos = idx + search.length;
65
- }
66
- }
67
- else {
68
- results.push({ start: idx, end: idx + search.length, strategy: "exact" });
69
- }
70
- return results;
71
- }
72
- // Strategy 2: line_trimmed
73
- const searchLines = search.split("\n").map((l) => l.trim());
74
- const contentLines = content.split("\n");
75
- const trimmedMatch = findLineBlock(contentLines, searchLines, (a, b) => a.trim() === b);
76
- if (trimmedMatch) {
77
- return [{ ...lineBlockToRange(content, contentLines, trimmedMatch), strategy: "line_trimmed" }];
78
- }
79
- // Strategy 3: whitespace_normalized
80
- const normSearch = search.replace(/[ \t]+/g, " ");
81
- const normContent = content.replace(/[ \t]+/g, " ");
82
- idx = normContent.indexOf(normSearch);
83
- if (idx !== -1) {
84
- const mapped = mapNormalizedPos(content, normContent, idx, normSearch.length);
85
- if (mapped)
86
- return [{ ...mapped, strategy: "whitespace_normalized" }];
87
- }
88
- // Strategy 4: indentation_flexible
89
- const flexMatch = findLineBlock(contentLines, searchLines, (a, b) => a.trimStart() === b.trimStart());
90
- if (flexMatch) {
91
- return [{ ...lineBlockToRange(content, contentLines, flexMatch), strategy: "indentation_flexible" }];
92
- }
93
- // Strategy 5: escape_normalized
94
- const escSearch = escapeNormalize(search);
95
- if (escSearch !== search) {
96
- idx = content.indexOf(escSearch);
97
- if (idx !== -1) {
98
- return [{ start: idx, end: idx + escSearch.length, strategy: "escape_normalized" }];
99
- }
100
- }
101
- // Strategy 6: trimmed_boundary
102
- const sLines = search.split("\n");
103
- if (sLines.length >= 3) {
104
- const innerSearch = sLines.slice(1, -1).join("\n");
105
- const firstTrimmed = sLines[0].trim();
106
- const lastTrimmed = sLines[sLines.length - 1].trim();
107
- for (let i = 0; i < contentLines.length; i++) {
108
- if (contentLines[i].trim() === firstTrimmed) {
109
- const innerStart = contentLines.slice(0, i + 1).join("\n").length + 1;
110
- const innerIdx = content.indexOf(innerSearch, innerStart);
111
- if (innerIdx !== -1) {
112
- const afterInner = innerIdx + innerSearch.length;
113
- const nextNewline = content.indexOf("\n", afterInner);
114
- const lastLine = nextNewline === -1 ? content.slice(afterInner) : content.slice(afterInner, nextNewline);
115
- if (lastLine.trim() === lastTrimmed || (nextNewline !== -1 && content.slice(afterInner + 1, content.indexOf("\n", afterInner + 1) === -1 ? undefined : content.indexOf("\n", afterInner + 1)).trim() === lastTrimmed)) {
116
- const rangeStart = contentLines.slice(0, i).join("\n").length + (i > 0 ? 1 : 0);
117
- // Find end after last line
118
- let endI = i;
119
- const needed = sLines.length;
120
- if (i + needed <= contentLines.length) {
121
- endI = i + needed;
122
- const rangeEnd = contentLines.slice(0, endI).join("\n").length;
123
- return [{ start: rangeStart, end: rangeEnd, strategy: "trimmed_boundary" }];
124
- }
125
- }
126
- }
127
- }
128
- }
129
- }
130
- // Strategy 7: unicode_normalized
131
- const uniSearch = unicodeNormalize(search);
132
- const uniContent = unicodeNormalize(content);
133
- if (uniSearch !== search || uniContent !== content) {
134
- idx = uniContent.indexOf(uniSearch);
135
- if (idx !== -1) {
136
- const mapped = mapNormalizedPos(content, uniContent, idx, uniSearch.length);
137
- if (mapped)
138
- return [{ ...mapped, strategy: "unicode_normalized" }];
139
- }
140
- }
141
- // Strategy 8: block_anchor
142
- if (sLines.length >= 3) {
143
- const firstLine = sLines[0];
144
- const lastLine = sLines[sLines.length - 1];
145
- for (let i = 0; i < contentLines.length; i++) {
146
- if (contentLines[i] === firstLine) {
147
- for (let j = i + sLines.length - 1; j < Math.min(i + sLines.length + 2, contentLines.length); j++) {
148
- if (contentLines[j] === lastLine) {
149
- const blockLen = j - i + 1;
150
- const middleLines = contentLines.slice(i + 1, j);
151
- const searchMiddle = sLines.slice(1, -1);
152
- const matchCount = searchMiddle.filter((sl) => middleLines.some((cl) => lineSimilarity(cl, sl) >= 0.7)).length;
153
- const ratio = searchMiddle.length > 0 ? matchCount / searchMiddle.length : 1;
154
- if (ratio >= 0.5 && blockLen <= sLines.length + 2) {
155
- const rangeStart = contentLines.slice(0, i).join("\n").length + (i > 0 ? 1 : 0);
156
- const rangeEnd = contentLines.slice(0, j + 1).join("\n").length;
157
- return [{ start: rangeStart, end: rangeEnd, strategy: "block_anchor" }];
158
- }
159
- }
160
- }
161
- }
162
- }
163
- }
164
- // Strategy 9: context_aware
165
- if (sLines.length >= 2) {
166
- for (let i = 0; i <= contentLines.length - sLines.length; i++) {
167
- const candidateLines = contentLines.slice(i, i + sLines.length);
168
- const similarities = sLines.map((sl, k) => lineSimilarity(sl, candidateLines[k]));
169
- const goodLines = similarities.filter((s) => s >= 0.8).length;
170
- if (goodLines / sLines.length >= 0.5) {
171
- const rangeStart = contentLines.slice(0, i).join("\n").length + (i > 0 ? 1 : 0);
172
- const rangeEnd = contentLines.slice(0, i + sLines.length).join("\n").length;
173
- return [{ start: rangeStart, end: rangeEnd, strategy: "context_aware" }];
174
- }
175
- }
176
- }
177
- return [];
178
- }
179
- function findLineBlock(contentLines, searchLines, compareFn) {
180
- for (let i = 0; i <= contentLines.length - searchLines.length; i++) {
181
- let match = true;
182
- for (let j = 0; j < searchLines.length; j++) {
183
- if (!compareFn(contentLines[i + j], searchLines[j])) {
184
- match = false;
185
- break;
186
- }
187
- }
188
- if (match)
189
- return { startIdx: i, endIdx: i + searchLines.length };
190
- }
191
- return null;
192
- }
193
- function lineBlockToRange(content, contentLines, block) {
194
- const start = contentLines.slice(0, block.startIdx).join("\n").length + (block.startIdx > 0 ? 1 : 0);
195
- const end = contentLines.slice(0, block.endIdx).join("\n").length;
196
- return { start, end };
197
- }
198
- function mapNormalizedPos(original, normalized, normStart, normLen) {
199
- // Simple character-length mapping (works for whitespace collapse and unicode transforms)
200
- // Walk both strings in sync to find positional correspondence
201
- let oi = 0;
202
- let ni = 0;
203
- let origStart = -1;
204
- let origEnd = -1;
205
- while (oi <= original.length && ni <= normalized.length) {
206
- if (ni === normStart && origStart === -1)
207
- origStart = oi;
208
- if (ni === normStart + normLen) {
209
- origEnd = oi;
210
- break;
211
- }
212
- if (ni >= normalized.length)
213
- break;
214
- if (oi >= original.length)
215
- break;
216
- ni++;
217
- oi++;
218
- // Skip extra chars in original that were collapsed
219
- while (oi < original.length && ni < normalized.length && original[oi] !== normalized[ni]) {
220
- oi++;
221
- }
222
- }
223
- if (origStart !== -1 && origEnd === -1)
224
- origEnd = original.length;
225
- if (origStart === -1)
226
- return null;
227
- return { start: origStart, end: origEnd };
228
- }
229
- function parseV4A(input) {
230
- const lines = input.split("\n");
231
- const ops = [];
232
- let current = null;
233
- let currentHunk = null;
234
- let inPatch = false;
235
- for (const line of lines) {
236
- if (line.startsWith("*** Begin Patch")) {
237
- inPatch = true;
238
- continue;
239
- }
240
- if (line.startsWith("*** End Patch"))
241
- break;
242
- if (!inPatch)
243
- continue;
244
- if (line.startsWith("*** Add File: ")) {
245
- if (current)
246
- ops.push(current);
247
- current = { type: "add", path: line.slice(14).trim(), hunks: [] };
248
- currentHunk = { lines: [] };
249
- current.hunks.push(currentHunk);
250
- }
251
- else if (line.startsWith("*** Update File: ")) {
252
- if (current)
253
- ops.push(current);
254
- current = { type: "update", path: line.slice(17).trim(), hunks: [] };
255
- currentHunk = null;
256
- }
257
- else if (line.startsWith("*** Delete File: ")) {
258
- if (current)
259
- ops.push(current);
260
- current = { type: "delete", path: line.slice(17).trim(), hunks: [] };
261
- currentHunk = null;
262
- }
263
- else if (line.startsWith("*** Move File: ")) {
264
- if (current)
265
- ops.push(current);
266
- const parts = line.slice(15).trim().split(" -> ");
267
- current = { type: "move", path: parts[0].trim(), newPath: parts[1]?.trim(), hunks: [] };
268
- currentHunk = null;
269
- }
270
- else if (line.startsWith("@@ ") && current) {
271
- currentHunk = { contextHint: line.slice(3).replace(/ @@$/, "").trim() || undefined, lines: [] };
272
- current.hunks.push(currentHunk);
273
- }
274
- else if (currentHunk) {
275
- if (line.startsWith("+")) {
276
- currentHunk.lines.push({ prefix: "+", content: line.slice(1) });
277
- }
278
- else if (line.startsWith("-")) {
279
- currentHunk.lines.push({ prefix: "-", content: line.slice(1) });
280
- }
281
- else {
282
- currentHunk.lines.push({ prefix: " ", content: line.startsWith(" ") ? line.slice(1) : line });
283
- }
284
- }
285
- }
286
- if (current)
287
- ops.push(current);
288
- return ops;
289
- }
290
- function parseSimpleFormat(input) {
291
- const ops = [];
292
- const blocks = input.split(/^(<<<< SEARCH)$/m);
293
- if (blocks.length < 2)
294
- return [];
295
- // First element before first <<<< SEARCH is the file path
296
- const pathLine = blocks[0].trim().split("\n").pop()?.trim();
297
- if (!pathLine)
298
- return [];
299
- for (let i = 1; i < blocks.length; i += 2) {
300
- const rest = blocks[i + 1] || "";
301
- const sepIdx = rest.indexOf("\n====\n");
302
- if (sepIdx === -1)
303
- continue;
304
- const search = rest.slice(0, sepIdx).replace(/^\n/, "");
305
- const afterSep = rest.slice(sepIdx + 6);
306
- const replaceEnd = afterSep.indexOf("\n>>>> REPLACE");
307
- const replace = replaceEnd === -1 ? afterSep : afterSep.slice(0, replaceEnd);
308
- ops.push({ path: pathLine, search, replace });
309
- }
310
- return ops;
311
- }
312
- // ── Main tool function ──────────────────────────────────────
313
- function applyV4AHunk(content, hunk, replaceAll) {
314
- const contextLines = hunk.lines.filter((l) => l.prefix === " ").map((l) => l.content);
315
- const removeLines = hunk.lines.filter((l) => l.prefix === "-").map((l) => l.content);
316
- const addLines = hunk.lines.filter((l) => l.prefix === "+").map((l) => l.content);
317
- // Build the search text from context + removals (what should exist in file)
318
- const searchParts = [];
319
- let phase = "context";
320
- for (const line of hunk.lines) {
321
- if (line.prefix === " " || line.prefix === "-") {
322
- searchParts.push(line.content);
323
- }
324
- // Skip additions in search
325
- }
326
- const searchText = searchParts.join("\n");
327
- if (!searchText) {
328
- // Pure addition — append at end or after context hint
329
- return { content: content + "\n" + addLines.join("\n"), strategy: "exact" };
330
- }
331
- const matches = fuzzyFind(content, searchText, replaceAll);
332
- if (matches.length === 0)
333
- return null;
334
- // Build replacement: context lines + additions (removals excluded)
335
- const replaceParts = [];
336
- for (const line of hunk.lines) {
337
- if (line.prefix === " " || line.prefix === "+") {
338
- replaceParts.push(line.content);
339
- }
340
- }
341
- const replaceText = replaceParts.join("\n");
342
- // Apply first match
343
- const m = matches[0];
344
- const newContent = content.slice(0, m.start) + replaceText + content.slice(m.end);
345
- return { content: newContent, strategy: m.strategy };
346
- }
347
- export function createPatchTool(deps) {
348
- return {
349
- name: PATCH_TOOL_NAME,
350
- label: "Patch",
351
- description: "Apply edits to files using fuzzy matching. Supports V4A unified diff format " +
352
- "(*** Begin Patch / *** End Patch) for multi-file operations, and simple search/replace " +
353
- "blocks. The fuzzy matcher handles whitespace, indentation, unicode, and escape drift " +
354
- "— LLM output with minor formatting differences will still match correctly.",
355
- parameters: PATCH_TOOL_SCHEMA,
356
- execute: async (_toolCallId, params) => {
357
- const replaceAll = params.replaceAll ?? false;
358
- const result = { filesModified: [], filesAdded: [], filesDeleted: [], strategies: {}, errors: [] };
359
- // Detect format: V4A or simple
360
- const isV4A = params.input.includes("*** Begin Patch");
361
- const isSimple = params.input.includes("<<<< SEARCH");
362
- if (isV4A) {
363
- const ops = parseV4A(params.input);
364
- if (ops.length === 0) {
365
- return {
366
- content: [{ type: "text", text: "Error: No valid V4A operations found. Ensure *** Begin Patch / *** End Patch markers are present." }],
367
- details: { type: "patch", error: "parse_failed" },
368
- };
369
- }
370
- for (const op of ops) {
371
- const resolved = deps.resolvePath(op.path);
372
- try {
373
- switch (op.type) {
374
- case "add": {
375
- const addContent = op.hunks.flatMap((h) => h.lines.filter((l) => l.prefix === "+").map((l) => l.content)).join("\n");
376
- await deps.writeFile(resolved, addContent);
377
- result.filesAdded.push(op.path);
378
- result.strategies[op.path] = "exact";
379
- break;
380
- }
381
- case "delete": {
382
- await deps.deleteFile(resolved);
383
- result.filesDeleted.push(op.path);
384
- break;
385
- }
386
- case "move": {
387
- const existing = await deps.readFile(resolved);
388
- if (op.newPath) {
389
- const dest = deps.resolvePath(op.newPath);
390
- let content = existing;
391
- for (const hunk of op.hunks) {
392
- const hr = applyV4AHunk(content, hunk, replaceAll);
393
- if (hr) {
394
- content = hr.content;
395
- result.strategies[op.path] = hr.strategy;
396
- }
397
- }
398
- await deps.writeFile(dest, content);
399
- await deps.deleteFile(resolved);
400
- result.filesModified.push(`${op.path} → ${op.newPath}`);
401
- }
402
- break;
403
- }
404
- case "update": {
405
- let content = await deps.readFile(resolved);
406
- let lastStrategy = "exact";
407
- let allSuccess = true;
408
- for (const hunk of op.hunks) {
409
- const hr = applyV4AHunk(content, hunk, replaceAll);
410
- if (hr) {
411
- content = hr.content;
412
- lastStrategy = hr.strategy;
413
- }
414
- else {
415
- allSuccess = false;
416
- // Build readable error
417
- const searchSnippet = hunk.lines
418
- .filter((l) => l.prefix === " " || l.prefix === "-")
419
- .map((l) => l.content)
420
- .slice(0, 5)
421
- .join("\n");
422
- result.errors.push(`${op.path}: hunk not matched. Search begins with:\n${searchSnippet}`);
423
- }
424
- }
425
- if (allSuccess || content !== await deps.readFile(resolved)) {
426
- await deps.writeFile(resolved, content);
427
- result.filesModified.push(op.path);
428
- result.strategies[op.path] = lastStrategy;
429
- }
430
- break;
431
- }
432
- }
433
- }
434
- catch (err) {
435
- result.errors.push(`${op.type} ${op.path}: ${err instanceof Error ? err.message : String(err)}`);
436
- }
437
- }
438
- }
439
- else if (isSimple) {
440
- const ops = parseSimpleFormat(params.input);
441
- if (ops.length === 0) {
442
- return {
443
- content: [{ type: "text", text: "Error: Invalid search/replace format. Use <<<< SEARCH / ==== / >>>> REPLACE blocks." }],
444
- details: { type: "patch", error: "parse_failed" },
445
- };
446
- }
447
- for (const op of ops) {
448
- const resolved = deps.resolvePath(op.path);
449
- try {
450
- let content = await deps.readFile(resolved);
451
- const matches = fuzzyFind(content, op.search, replaceAll);
452
- if (matches.length === 0) {
453
- result.errors.push(`${op.path}: search text not matched (tried all 9 strategies)`);
454
- continue;
455
- }
456
- // Apply in reverse order to preserve positions
457
- const sorted = [...matches].sort((a, b) => b.start - a.start);
458
- for (const m of sorted) {
459
- content = content.slice(0, m.start) + op.replace + content.slice(m.end);
460
- }
461
- await deps.writeFile(resolved, content);
462
- result.filesModified.push(op.path);
463
- result.strategies[op.path] = matches[0].strategy;
464
- }
465
- catch (err) {
466
- result.errors.push(`${op.path}: ${err instanceof Error ? err.message : String(err)}`);
467
- }
468
- }
469
- }
470
- else {
471
- // Try parsing as a plain old/new pair in V4A-like format (backwards compat with apply_patch)
472
- const ops = parseV4A("*** Begin Patch\n" + params.input + "\n*** End Patch");
473
- if (ops.length > 0) {
474
- // Re-run through V4A path
475
- return createPatchTool(deps).execute(_toolCallId, { input: "*** Begin Patch\n" + params.input + "\n*** End Patch", replaceAll });
476
- }
477
- return {
478
- content: [{ type: "text", text: "Error: Unrecognized patch format. Use V4A (*** Begin Patch) or search/replace (<<<< SEARCH / ==== / >>>> REPLACE)." }],
479
- details: { type: "patch", error: "unknown_format" },
480
- };
481
- }
482
- // Format output
483
- const parts = [];
484
- if (result.filesAdded.length)
485
- parts.push(`Added: ${result.filesAdded.join(", ")}`);
486
- if (result.filesModified.length) {
487
- const strategyInfo = result.filesModified
488
- .map((f) => `${f} (${result.strategies[f] || "exact"})`)
489
- .join(", ");
490
- parts.push(`Modified: ${strategyInfo}`);
491
- }
492
- if (result.filesDeleted.length)
493
- parts.push(`Deleted: ${result.filesDeleted.join(", ")}`);
494
- if (result.errors.length)
495
- parts.push(`\nErrors:\n${result.errors.join("\n")}`);
496
- const success = result.errors.length === 0;
497
- return {
498
- content: [{ type: "text", text: parts.join("\n") || "No changes applied." }],
499
- details: { type: "patch", ...result, success },
500
- };
501
- },
502
- };
503
- }
504
- // Export the fuzzy engine for unit testing
505
- export { fuzzyFind };
@@ -1,66 +0,0 @@
1
- import type { PortableTool } from "../portable-tool.js";
2
- export declare const PDF_TOOL_NAME: "pdf";
3
- export interface PdfToolParams {
4
- prompt?: string;
5
- pdf?: string;
6
- pdfs?: string[];
7
- pages?: string;
8
- }
9
- export declare const PDF_TOOL_SCHEMA: {
10
- readonly type: "object";
11
- readonly properties: {
12
- readonly prompt: {
13
- readonly type: "string";
14
- readonly description: "Describe what to analyze or extract from the PDF(s).";
15
- };
16
- readonly pdf: {
17
- readonly type: "string";
18
- readonly description: "Single PDF path or URL.";
19
- };
20
- readonly pdfs: {
21
- readonly type: "array";
22
- readonly items: {
23
- readonly type: "string";
24
- };
25
- readonly description: "Multiple PDF paths or URLs (up to 10).";
26
- };
27
- readonly pages: {
28
- readonly type: "string";
29
- readonly description: "Page range to process, e.g. \"1-5\", \"1,3,5-7\". Defaults to all pages.";
30
- };
31
- };
32
- readonly required: readonly [];
33
- };
34
- /** Deps injected by the host runtime for PDF analysis. */
35
- export interface PdfToolDeps {
36
- /**
37
- * Extract text and/or page images from a PDF.
38
- * The host decides how to load (local FS, HTTP, etc.).
39
- */
40
- extractPdf(source: string, options?: {
41
- pages?: string;
42
- }): Promise<{
43
- text: string;
44
- pageImages?: Array<{
45
- page: number;
46
- base64: string;
47
- mimeType: string;
48
- }>;
49
- }>;
50
- /**
51
- * Optional: analyze extracted content via LLM.
52
- * If not provided, the tool returns raw extracted text.
53
- */
54
- analyzePdfContent?(prompt: string, content: {
55
- text: string;
56
- images?: Array<{
57
- base64: string;
58
- mimeType: string;
59
- }>;
60
- }): Promise<{
61
- text: string;
62
- model?: string;
63
- provider?: string;
64
- }>;
65
- }
66
- export declare function createPdfTool(deps: PdfToolDeps): PortableTool<PdfToolParams>;
@@ -1,88 +0,0 @@
1
- // ============================================================
2
- // PDF Tool — analyze PDF documents via relay/backend pipeline.
3
- // Category: document
4
- // ============================================================
5
- export const PDF_TOOL_NAME = "pdf";
6
- export const PDF_TOOL_SCHEMA = {
7
- type: "object",
8
- properties: {
9
- prompt: {
10
- type: "string",
11
- description: "Describe what to analyze or extract from the PDF(s).",
12
- },
13
- pdf: {
14
- type: "string",
15
- description: "Single PDF path or URL.",
16
- },
17
- pdfs: {
18
- type: "array",
19
- items: { type: "string" },
20
- description: "Multiple PDF paths or URLs (up to 10).",
21
- },
22
- pages: {
23
- type: "string",
24
- description: 'Page range to process, e.g. "1-5", "1,3,5-7". Defaults to all pages.',
25
- },
26
- },
27
- required: [],
28
- };
29
- const MAX_PDF_COUNT = 10;
30
- export function createPdfTool(deps) {
31
- return {
32
- name: PDF_TOOL_NAME,
33
- label: "PDF",
34
- description: "Analyze one or more PDF documents. Use `pdf` for a single path/URL, or `pdfs` for multiple (up to 10). " +
35
- "Provide a `prompt` describing what to analyze. Use `pages` to limit which pages to process.",
36
- parameters: PDF_TOOL_SCHEMA,
37
- execute: async (_toolCallId, params) => {
38
- // Normalize sources
39
- const sources = [];
40
- if (params.pdf)
41
- sources.push(params.pdf);
42
- if (params.pdfs)
43
- sources.push(...params.pdfs);
44
- // Deduplicate
45
- const unique = [...new Set(sources)];
46
- if (unique.length === 0) {
47
- return { content: [{ type: "text", text: "Error: provide at least one PDF via `pdf` or `pdfs`." }] };
48
- }
49
- if (unique.length > MAX_PDF_COUNT) {
50
- return { content: [{ type: "text", text: `Error: maximum ${MAX_PDF_COUNT} PDFs per call.` }] };
51
- }
52
- const allTexts = [];
53
- const allImages = [];
54
- const errors = [];
55
- for (const src of unique) {
56
- try {
57
- const result = await deps.extractPdf(src, { pages: params.pages });
58
- allTexts.push(result.text);
59
- if (result.pageImages) {
60
- for (const img of result.pageImages) {
61
- allImages.push({ base64: img.base64, mimeType: img.mimeType });
62
- }
63
- }
64
- }
65
- catch (err) {
66
- errors.push(`${src}: ${err instanceof Error ? err.message : String(err)}`);
67
- }
68
- }
69
- if (allTexts.length === 0) {
70
- return { content: [{ type: "text", text: `Failed to extract any PDFs: ${errors.join("; ")}` }] };
71
- }
72
- const combinedText = allTexts.join("\n\n---\n\n");
73
- // If LLM analysis is available and prompt given, use it
74
- if (deps.analyzePdfContent && params.prompt) {
75
- const analysis = await deps.analyzePdfContent(params.prompt, { text: combinedText, images: allImages });
76
- return {
77
- content: [{ type: "text", text: analysis.text }],
78
- details: { type: "pdf", model: analysis.model, provider: analysis.provider, pdfCount: unique.length, errors },
79
- };
80
- }
81
- // Otherwise return raw extracted text
82
- return {
83
- content: [{ type: "text", text: combinedText }],
84
- details: { type: "pdf", pdfCount: unique.length, errors },
85
- };
86
- },
87
- };
88
- }