clanka 0.0.26 → 0.0.27

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 (44) hide show
  1. package/dist/Agent.d.ts +5 -4
  2. package/dist/Agent.d.ts.map +1 -1
  3. package/dist/Agent.js.map +1 -1
  4. package/dist/Agent.test.js +7 -8
  5. package/dist/Agent.test.js.map +1 -1
  6. package/dist/AgentTools.d.ts +274 -2
  7. package/dist/AgentTools.d.ts.map +1 -1
  8. package/dist/AgentTools.js +37 -2
  9. package/dist/AgentTools.js.map +1 -1
  10. package/dist/ApplyPatch.d.ts.map +1 -1
  11. package/dist/ApplyPatch.js +6 -2
  12. package/dist/ApplyPatch.js.map +1 -1
  13. package/dist/ApplyPatch.test.js +39 -0
  14. package/dist/ApplyPatch.test.js.map +1 -1
  15. package/dist/Codex.d.ts +1 -1
  16. package/dist/CodexAuth.d.ts +4 -4
  17. package/dist/Copilot.d.ts +1 -1
  18. package/dist/CopilotAuth.d.ts +3 -3
  19. package/dist/ExaSearch.d.ts +37 -0
  20. package/dist/ExaSearch.d.ts.map +1 -0
  21. package/dist/ExaSearch.js +56 -0
  22. package/dist/ExaSearch.js.map +1 -0
  23. package/dist/McpClient.d.ts +35 -0
  24. package/dist/McpClient.d.ts.map +1 -0
  25. package/dist/McpClient.js +51 -0
  26. package/dist/McpClient.js.map +1 -0
  27. package/dist/WebToMarkdown.d.ts +22 -0
  28. package/dist/WebToMarkdown.d.ts.map +1 -0
  29. package/dist/WebToMarkdown.js +66 -0
  30. package/dist/WebToMarkdown.js.map +1 -0
  31. package/package.json +13 -10
  32. package/src/Agent.test.ts +15 -9
  33. package/src/Agent.ts +11 -5
  34. package/src/AgentTools.ts +49 -1
  35. package/src/ApplyPatch.test.ts +44 -0
  36. package/src/ApplyPatch.ts +6 -2
  37. package/src/ExaSearch.ts +78 -0
  38. package/src/McpClient.ts +81 -0
  39. package/src/WebToMarkdown.ts +87 -0
  40. package/dist/AgentTools.test.d.ts +0 -2
  41. package/dist/AgentTools.test.d.ts.map +0 -1
  42. package/dist/AgentTools.test.js +0 -714
  43. package/dist/AgentTools.test.js.map +0 -1
  44. package/src/AgentTools.test.ts +0 -954
@@ -1,954 +0,0 @@
1
- import { tmpdir } from "node:os"
2
- import { join } from "node:path"
3
- import { NodeFileSystem, NodeServices } from "@effect/platform-node"
4
- import { Deferred, Effect, FileSystem, Stream } from "effect"
5
- import { describe, it } from "@effect/vitest"
6
- import { expect } from "vitest"
7
- import {
8
- AgentToolHandlers,
9
- AgentTools,
10
- CurrentDirectory,
11
- makeContextNoop,
12
- TaskCompleteDeferred,
13
- } from "./AgentTools.ts"
14
- import { Executor } from "./Executor.ts"
15
- import { ToolkitRenderer } from "./ToolkitRenderer.ts"
16
-
17
- const makeTempRoot = (prefix: string) =>
18
- Effect.gen(function* () {
19
- const fs = yield* FileSystem.FileSystem
20
- return yield* fs.makeTempDirectoryScoped({
21
- directory: tmpdir(),
22
- prefix,
23
- })
24
- })
25
-
26
- describe("AgentTools", () => {
27
- it.effect("renders the tool signatures", () =>
28
- Effect.gen(function* () {
29
- const renderer = yield* ToolkitRenderer
30
- const output = renderer.render(AgentTools)
31
-
32
- expect(output).toContain(
33
- "/** Read a file and optionally filter the lines to return. Returns null if the file doesn't exist. */",
34
- )
35
- expect(output).toContain("declare function readFile(options: {")
36
- expect(output).toContain("readonly path: string;")
37
- expect(output).toContain("readonly startLine?: number | undefined;")
38
- expect(output).toContain("readonly endLine?: number | undefined;")
39
- expect(output).toContain("readonly noIgnore?: boolean | undefined;")
40
- expect(output).toContain(
41
- "/** Apply a git diff / unified diff patch, or a wrapped apply_patch patch, across one or more files. */",
42
- )
43
- expect(output).toContain(
44
- "declare function applyPatch(patch: string): Promise<string>",
45
- )
46
- expect(output).not.toContain("declare function python(")
47
- }).pipe(
48
- Effect.provide([
49
- AgentToolHandlers,
50
- Executor.layer,
51
- ToolkitRenderer.layer,
52
- ]),
53
- Effect.provide(NodeServices.layer),
54
- Effect.provideService(CurrentDirectory, process.cwd()),
55
- Effect.provideServiceEffect(
56
- TaskCompleteDeferred,
57
- Deferred.make<string>(),
58
- ),
59
- ),
60
- )
61
-
62
- it.effect("applies multi-file patches with add, move, and delete", () =>
63
- Effect.gen(function* () {
64
- const fs = yield* FileSystem.FileSystem
65
- const tempRoot = yield* makeTempRoot("clanka-apply-patch-")
66
- yield* fs.makeDirectory(join(tempRoot, "src"), { recursive: true })
67
- yield* fs.writeFileString(join(tempRoot, "src", "app.txt"), "old\n")
68
- yield* fs.writeFileString(join(tempRoot, "obsolete.txt"), "remove me\n")
69
-
70
- const executor = yield* Executor
71
- const tools = yield* AgentTools
72
- const output = yield* executor
73
- .execute({
74
- tools,
75
- script: [
76
- "const output = await applyPatch(`",
77
- "diff --git a/src/app.txt b/src/main.txt",
78
- "similarity index 100%",
79
- "rename from src/app.txt",
80
- "rename to src/main.txt",
81
- "--- a/src/app.txt",
82
- "+++ b/src/main.txt",
83
- "@@ -1 +1 @@",
84
- "-old",
85
- "+new",
86
- "diff --git a/obsolete.txt b/obsolete.txt",
87
- "deleted file mode 100644",
88
- "--- a/obsolete.txt",
89
- "+++ /dev/null",
90
- "diff --git a/dev/null b/notes/hello.txt",
91
- "new file mode 100644",
92
- "--- /dev/null",
93
- "+++ b/notes/hello.txt",
94
- "@@ -0,0 +1 @@",
95
- "+hello",
96
- "`)",
97
- "console.log(output)",
98
- ].join("\n"),
99
- })
100
- .pipe(
101
- Stream.mkString,
102
- Effect.provideServices(makeContextNoop(tempRoot)),
103
- )
104
-
105
- expect(output).toContain("A notes/hello.txt")
106
- expect(output).toContain("M src/main.txt")
107
- expect(output).toContain("D obsolete.txt")
108
- expect(
109
- yield* fs.readFileString(join(tempRoot, "notes", "hello.txt")),
110
- ).toBe("hello\n")
111
- expect(yield* fs.readFileString(join(tempRoot, "src", "main.txt"))).toBe(
112
- "new\n",
113
- )
114
- yield* Effect.flip(fs.readFileString(join(tempRoot, "obsolete.txt")))
115
- yield* Effect.flip(fs.readFileString(join(tempRoot, "src", "app.txt")))
116
- }).pipe(
117
- Effect.provide([
118
- AgentToolHandlers,
119
- Executor.layer,
120
- ToolkitRenderer.layer,
121
- ]),
122
- Effect.provide(NodeServices.layer),
123
- ),
124
- )
125
-
126
- it.effect("plans later hunks against in-memory file state", () =>
127
- Effect.gen(function* () {
128
- const fs = yield* FileSystem.FileSystem
129
- const tempRoot = yield* makeTempRoot("clanka-apply-patch-state-")
130
- yield* fs.makeDirectory(join(tempRoot, "src"), { recursive: true })
131
- yield* fs.writeFileString(join(tempRoot, "src", "app.txt"), "old\n")
132
-
133
- const executor = yield* Executor
134
- const tools = yield* AgentTools
135
- const output = yield* executor
136
- .execute({
137
- tools,
138
- script: [
139
- "const output = await applyPatch(`",
140
- "diff --git a/dev/null b/notes/hello.txt",
141
- "new file mode 100644",
142
- "--- /dev/null",
143
- "+++ b/notes/hello.txt",
144
- "@@ -0,0 +1 @@",
145
- "+hello",
146
- "diff --git a/notes/hello.txt b/notes/hello.txt",
147
- "--- a/notes/hello.txt",
148
- "+++ b/notes/hello.txt",
149
- "@@ -1 +1 @@",
150
- "-hello",
151
- "+hello again",
152
- "diff --git a/src/app.txt b/src/main.txt",
153
- "similarity index 100%",
154
- "rename from src/app.txt",
155
- "rename to src/main.txt",
156
- "--- a/src/app.txt",
157
- "+++ b/src/main.txt",
158
- "@@ -1 +1 @@",
159
- "-old",
160
- "+new",
161
- "diff --git a/src/main.txt b/src/main.txt",
162
- "--- a/src/main.txt",
163
- "+++ b/src/main.txt",
164
- "@@ -1 +1 @@",
165
- "-new",
166
- "+newer",
167
- "`)",
168
- "console.log(output)",
169
- ].join("\n"),
170
- })
171
- .pipe(
172
- Stream.mkString,
173
- Effect.provideServices(makeContextNoop(tempRoot)),
174
- )
175
-
176
- expect(output).toContain("A notes/hello.txt")
177
- expect(output).toContain("M notes/hello.txt")
178
- expect(output).toContain("M src/main.txt")
179
- expect(
180
- yield* fs.readFileString(join(tempRoot, "notes", "hello.txt")),
181
- ).toBe("hello again\n")
182
- expect(yield* fs.readFileString(join(tempRoot, "src", "main.txt"))).toBe(
183
- "newer\n",
184
- )
185
- yield* Effect.flip(fs.readFileString(join(tempRoot, "src", "app.txt")))
186
- }).pipe(
187
- Effect.provide([
188
- AgentToolHandlers,
189
- Executor.layer,
190
- ToolkitRenderer.layer,
191
- ]),
192
- Effect.provide(NodeServices.layer),
193
- ),
194
- )
195
-
196
- it.effect("applies wrapped apply_patch patches", () =>
197
- Effect.gen(function* () {
198
- const fs = yield* FileSystem.FileSystem
199
- const tempRoot = yield* makeTempRoot("clanka-apply-patch-wrapped-")
200
- yield* fs.makeDirectory(join(tempRoot, "src"), { recursive: true })
201
- yield* fs.writeFileString(join(tempRoot, "src", "app.txt"), "old\n")
202
- yield* fs.writeFileString(join(tempRoot, "obsolete.txt"), "remove me\n")
203
-
204
- const executor = yield* Executor
205
- const tools = yield* AgentTools
206
- const output = yield* executor
207
- .execute({
208
- tools,
209
- script: [
210
- "const output = await applyPatch(`",
211
- "*** Begin Patch",
212
- "*** Update File: src/app.txt",
213
- "*** Move to: src/main.txt",
214
- "@@",
215
- "-old",
216
- "+new",
217
- "*** Delete File: obsolete.txt",
218
- "*** Add File: notes/hello.txt",
219
- "+hello",
220
- "*** End Patch",
221
- "`)",
222
- "console.log(output)",
223
- ].join("\n"),
224
- })
225
- .pipe(
226
- Stream.mkString,
227
- Effect.provideServices(makeContextNoop(tempRoot)),
228
- )
229
-
230
- expect(output).toContain("A notes/hello.txt")
231
- expect(output).toContain("M src/main.txt")
232
- expect(output).toContain("D obsolete.txt")
233
- expect(
234
- yield* fs.readFileString(join(tempRoot, "notes", "hello.txt")),
235
- ).toBe("hello\n")
236
- expect(yield* fs.readFileString(join(tempRoot, "src", "main.txt"))).toBe(
237
- "new\n",
238
- )
239
- yield* Effect.flip(fs.readFileString(join(tempRoot, "obsolete.txt")))
240
- yield* Effect.flip(fs.readFileString(join(tempRoot, "src", "app.txt")))
241
- }).pipe(
242
- Effect.provide([
243
- AgentToolHandlers,
244
- Executor.layer,
245
- ToolkitRenderer.layer,
246
- ]),
247
- Effect.provide(NodeServices.layer),
248
- ),
249
- )
250
-
251
- it.effect(
252
- "applies larger wrapped apply_patch patches across multiple files",
253
- () =>
254
- Effect.gen(function* () {
255
- const fs = yield* FileSystem.FileSystem
256
- const tempRoot = yield* makeTempRoot(
257
- "clanka-apply-patch-wrapped-large-",
258
- )
259
- yield* fs.makeDirectory(join(tempRoot, "src"), { recursive: true })
260
- yield* fs.makeDirectory(join(tempRoot, "docs"), { recursive: true })
261
- yield* fs.writeFileString(
262
- join(tempRoot, "src", "app.txt"),
263
- "alpha\nbeta\n",
264
- )
265
- yield* fs.writeFileString(
266
- join(tempRoot, "src", "config.json"),
267
- '{"enabled":false}\n',
268
- )
269
- yield* fs.writeFileString(join(tempRoot, "docs", "old.md"), "legacy\n")
270
- yield* fs.writeFileString(
271
- join(tempRoot, "README.md"),
272
- "# Title\nOld intro\n",
273
- )
274
-
275
- const executor = yield* Executor
276
- const tools = yield* AgentTools
277
- const output = yield* executor
278
- .execute({
279
- tools,
280
- script: [
281
- "const output = await applyPatch(`",
282
- "*** Begin Patch",
283
- "",
284
- "*** Update File: src/app.txt",
285
- "*** Move to: src/main.txt",
286
- "@@",
287
- " alpha",
288
- "-beta",
289
- "+gamma",
290
- "",
291
- "*** Update File: src/config.json",
292
- "@@",
293
- '-{"enabled":false}',
294
- '+{"enabled":true}',
295
- "",
296
- "*** Update File: README.md",
297
- "@@",
298
- " # Title",
299
- "-Old intro",
300
- "+New intro",
301
- "+More details",
302
- "",
303
- "*** Delete File: docs/old.md",
304
- "",
305
- "*** Add File: docs/new.md",
306
- "+# Docs",
307
- "+",
308
- "+Updated",
309
- "",
310
- "*** Add File: notes/todo.txt",
311
- "+one",
312
- "+two",
313
- "*** End Patch",
314
- "`)",
315
- "console.log(output)",
316
- ].join("\n"),
317
- })
318
- .pipe(
319
- Stream.mkString,
320
- Effect.provideServices(makeContextNoop(tempRoot)),
321
- )
322
-
323
- expect(output).toContain("M src/main.txt")
324
- expect(output).toContain("M src/config.json")
325
- expect(output).toContain("M README.md")
326
- expect(output).toContain("D docs/old.md")
327
- expect(output).toContain("A docs/new.md")
328
- expect(output).toContain("A notes/todo.txt")
329
- expect(
330
- yield* fs.readFileString(join(tempRoot, "src", "main.txt")),
331
- ).toBe("alpha\ngamma\n")
332
- expect(
333
- yield* fs.readFileString(join(tempRoot, "src", "config.json")),
334
- ).toBe('{"enabled":true}\n')
335
- expect(yield* fs.readFileString(join(tempRoot, "README.md"))).toBe(
336
- "# Title\nNew intro\nMore details\n",
337
- )
338
- expect(yield* fs.readFileString(join(tempRoot, "docs", "new.md"))).toBe(
339
- "# Docs\n\nUpdated\n",
340
- )
341
- expect(
342
- yield* fs.readFileString(join(tempRoot, "notes", "todo.txt")),
343
- ).toBe("one\ntwo\n")
344
- yield* Effect.flip(fs.readFileString(join(tempRoot, "docs", "old.md")))
345
- yield* Effect.flip(fs.readFileString(join(tempRoot, "src", "app.txt")))
346
- }).pipe(
347
- Effect.provide([
348
- AgentToolHandlers,
349
- Executor.layer,
350
- ToolkitRenderer.layer,
351
- ]),
352
- Effect.provide(NodeServices.layer),
353
- ),
354
- )
355
-
356
- it.effect(
357
- "chains wrapped apply_patch updates through in-memory renamed state",
358
- () =>
359
- Effect.gen(function* () {
360
- const fs = yield* FileSystem.FileSystem
361
- const tempRoot = yield* makeTempRoot(
362
- "clanka-apply-patch-wrapped-state-",
363
- )
364
- yield* fs.makeDirectory(join(tempRoot, "src"), { recursive: true })
365
- yield* fs.writeFileString(join(tempRoot, "src", "app.txt"), "old\n")
366
-
367
- const executor = yield* Executor
368
- const tools = yield* AgentTools
369
- const output = yield* executor
370
- .execute({
371
- tools,
372
- script: [
373
- "const output = await applyPatch(`",
374
- "*** Begin Patch",
375
- "*** Update File: src/app.txt",
376
- "*** Move to: src/main.txt",
377
- "@@",
378
- "-old",
379
- "+new",
380
- "*** Update File: src/main.txt",
381
- "@@",
382
- "-new",
383
- "+newer",
384
- "*** Add File: notes/hello.txt",
385
- "+hello",
386
- "*** Update File: notes/hello.txt",
387
- "@@",
388
- "-hello",
389
- "+hello again",
390
- "*** End Patch",
391
- "`)",
392
- "console.log(output)",
393
- ].join("\n"),
394
- })
395
- .pipe(
396
- Stream.mkString,
397
- Effect.provideServices(makeContextNoop(tempRoot)),
398
- )
399
-
400
- expect(output).toContain("M src/main.txt")
401
- expect(output).toContain("A notes/hello.txt")
402
- expect(output).toContain("M notes/hello.txt")
403
- expect(
404
- yield* fs.readFileString(join(tempRoot, "src", "main.txt")),
405
- ).toBe("newer\n")
406
- expect(
407
- yield* fs.readFileString(join(tempRoot, "notes", "hello.txt")),
408
- ).toBe("hello again\n")
409
- yield* Effect.flip(fs.readFileString(join(tempRoot, "src", "app.txt")))
410
- }).pipe(
411
- Effect.provide([
412
- AgentToolHandlers,
413
- Executor.layer,
414
- ToolkitRenderer.layer,
415
- ]),
416
- Effect.provide(NodeServices.layer),
417
- ),
418
- )
419
-
420
- it.effect("applies wrapped apply_patch patches with multiple hunks", () =>
421
- Effect.gen(function* () {
422
- const fs = yield* FileSystem.FileSystem
423
- const tempRoot = yield* makeTempRoot("clanka-apply-patch-wrapped-hunks-")
424
- yield* fs.writeFileString(
425
- join(tempRoot, "multi.txt"),
426
- "line1\nline2\nline3\nline4\n",
427
- )
428
-
429
- const executor = yield* Executor
430
- const tools = yield* AgentTools
431
- const output = yield* executor
432
- .execute({
433
- tools,
434
- script: [
435
- "const output = await applyPatch(`",
436
- "*** Begin Patch",
437
- "*** Update File: multi.txt",
438
- "@@",
439
- "-line2",
440
- "+changed2",
441
- "@@",
442
- "-line4",
443
- "+changed4",
444
- "*** End Patch",
445
- "`)",
446
- "console.log(output)",
447
- ].join("\n"),
448
- })
449
- .pipe(
450
- Stream.mkString,
451
- Effect.provideServices(makeContextNoop(tempRoot)),
452
- )
453
-
454
- expect(output).toContain("M multi.txt")
455
- expect(yield* fs.readFileString(join(tempRoot, "multi.txt"))).toBe(
456
- "line1\nchanged2\nline3\nchanged4\n",
457
- )
458
- }).pipe(
459
- Effect.provide([
460
- AgentToolHandlers,
461
- Executor.layer,
462
- ToolkitRenderer.layer,
463
- ]),
464
- Effect.provide(NodeServices.layer),
465
- ),
466
- )
467
-
468
- it.effect(
469
- "applies realistic multi-file git patches with repeated multi-hunk updates",
470
- () =>
471
- Effect.gen(function* () {
472
- const fs = yield* FileSystem.FileSystem
473
- const tempRoot = yield* makeTempRoot("clanka-apply-patch-realistic-")
474
- yield* fs.makeDirectory(join(tempRoot, "dist", "internal"), {
475
- recursive: true,
476
- })
477
-
478
- const initial = [
479
- "if (reasoningStarted && !textStarted) {",
480
- " controller.enqueue({",
481
- ' type: "reasoning-end",',
482
- " id: reasoningId || generateId()",
483
- " });",
484
- "}",
485
- "",
486
- "separator",
487
- "",
488
- "if (reasoningStarted) {",
489
- " controller.enqueue({",
490
- ' type: "reasoning-end",',
491
- " id: reasoningId || generateId()",
492
- " });",
493
- "}",
494
- "",
495
- ].join("\n")
496
-
497
- for (const path of [
498
- join(tempRoot, "dist", "index.js"),
499
- join(tempRoot, "dist", "index.mjs"),
500
- join(tempRoot, "dist", "internal", "index.js"),
501
- join(tempRoot, "dist", "internal", "index.mjs"),
502
- ]) {
503
- yield* fs.writeFileString(path, initial)
504
- }
505
-
506
- const executor = yield* Executor
507
- const tools = yield* AgentTools
508
- const output = yield* executor
509
- .execute({
510
- tools,
511
- script: [
512
- "const output = await applyPatch(`",
513
- "diff --git a/dist/index.js b/dist/index.js",
514
- "index f33510a..e887a60 100644",
515
- "--- a/dist/index.js",
516
- "+++ b/dist/index.js",
517
- "@@ -1,7 +1,12 @@",
518
- " if (reasoningStarted && !textStarted) {",
519
- " controller.enqueue({",
520
- ' type: "reasoning-end",',
521
- "- id: reasoningId || generateId()",
522
- "+ id: reasoningId || generateId(),",
523
- "+ providerMetadata: accumulatedReasoningDetails.length > 0 ? {",
524
- "+ openrouter: {",
525
- "+ reasoning_details: accumulatedReasoningDetails",
526
- "+ }",
527
- "+ } : undefined",
528
- " });",
529
- " }",
530
- "@@ -10,7 +15,12 @@",
531
- " if (reasoningStarted) {",
532
- " controller.enqueue({",
533
- ' type: "reasoning-end",',
534
- "- id: reasoningId || generateId()",
535
- "+ id: reasoningId || generateId(),",
536
- "+ providerMetadata: accumulatedReasoningDetails.length > 0 ? {",
537
- "+ openrouter: {",
538
- "+ reasoning_details: accumulatedReasoningDetails",
539
- "+ }",
540
- "+ } : undefined",
541
- " });",
542
- " }",
543
- "diff --git a/dist/index.mjs b/dist/index.mjs",
544
- "index 8a68833..6310cb8 100644",
545
- "--- a/dist/index.mjs",
546
- "+++ b/dist/index.mjs",
547
- "@@ -1,7 +1,12 @@",
548
- " if (reasoningStarted && !textStarted) {",
549
- " controller.enqueue({",
550
- ' type: "reasoning-end",',
551
- "- id: reasoningId || generateId()",
552
- "+ id: reasoningId || generateId(),",
553
- "+ providerMetadata: accumulatedReasoningDetails.length > 0 ? {",
554
- "+ openrouter: {",
555
- "+ reasoning_details: accumulatedReasoningDetails",
556
- "+ }",
557
- "+ } : undefined",
558
- " });",
559
- " }",
560
- "@@ -10,7 +15,12 @@",
561
- " if (reasoningStarted) {",
562
- " controller.enqueue({",
563
- ' type: "reasoning-end",',
564
- "- id: reasoningId || generateId()",
565
- "+ id: reasoningId || generateId(),",
566
- "+ providerMetadata: accumulatedReasoningDetails.length > 0 ? {",
567
- "+ openrouter: {",
568
- "+ reasoning_details: accumulatedReasoningDetails",
569
- "+ }",
570
- "+ } : undefined",
571
- " });",
572
- " }",
573
- "diff --git a/dist/internal/index.js b/dist/internal/index.js",
574
- "index d40fa66..8dd86d1 100644",
575
- "--- a/dist/internal/index.js",
576
- "+++ b/dist/internal/index.js",
577
- "@@ -1,7 +1,12 @@",
578
- " if (reasoningStarted && !textStarted) {",
579
- " controller.enqueue({",
580
- ' type: "reasoning-end",',
581
- "- id: reasoningId || generateId()",
582
- "+ id: reasoningId || generateId(),",
583
- "+ providerMetadata: accumulatedReasoningDetails.length > 0 ? {",
584
- "+ openrouter: {",
585
- "+ reasoning_details: accumulatedReasoningDetails",
586
- "+ }",
587
- "+ } : undefined",
588
- " });",
589
- " }",
590
- "@@ -10,7 +15,12 @@",
591
- " if (reasoningStarted) {",
592
- " controller.enqueue({",
593
- ' type: "reasoning-end",',
594
- "- id: reasoningId || generateId()",
595
- "+ id: reasoningId || generateId(),",
596
- "+ providerMetadata: accumulatedReasoningDetails.length > 0 ? {",
597
- "+ openrouter: {",
598
- "+ reasoning_details: accumulatedReasoningDetails",
599
- "+ }",
600
- "+ } : undefined",
601
- " });",
602
- " }",
603
- "diff --git a/dist/internal/index.mjs b/dist/internal/index.mjs",
604
- "index b0ed9d1..5695930 100644",
605
- "--- a/dist/internal/index.mjs",
606
- "+++ b/dist/internal/index.mjs",
607
- "@@ -1,7 +1,12 @@",
608
- " if (reasoningStarted && !textStarted) {",
609
- " controller.enqueue({",
610
- ' type: "reasoning-end",',
611
- "- id: reasoningId || generateId()",
612
- "+ id: reasoningId || generateId(),",
613
- "+ providerMetadata: accumulatedReasoningDetails.length > 0 ? {",
614
- "+ openrouter: {",
615
- "+ reasoning_details: accumulatedReasoningDetails",
616
- "+ }",
617
- "+ } : undefined",
618
- " });",
619
- " }",
620
- "@@ -10,7 +15,12 @@",
621
- " if (reasoningStarted) {",
622
- " controller.enqueue({",
623
- ' type: "reasoning-end",',
624
- "- id: reasoningId || generateId()",
625
- "+ id: reasoningId || generateId(),",
626
- "+ providerMetadata: accumulatedReasoningDetails.length > 0 ? {",
627
- "+ openrouter: {",
628
- "+ reasoning_details: accumulatedReasoningDetails",
629
- "+ }",
630
- "+ } : undefined",
631
- " });",
632
- " }",
633
- "`)",
634
- "console.log(output)",
635
- ].join("\n"),
636
- })
637
- .pipe(
638
- Stream.mkString,
639
- Effect.provideServices(makeContextNoop(tempRoot)),
640
- )
641
-
642
- expect(output).toContain("M dist/index.js")
643
- expect(output).toContain("M dist/index.mjs")
644
- expect(output).toContain("M dist/internal/index.js")
645
- expect(output).toContain("M dist/internal/index.mjs")
646
-
647
- const expected = [
648
- "if (reasoningStarted && !textStarted) {",
649
- " controller.enqueue({",
650
- ' type: "reasoning-end",',
651
- " id: reasoningId || generateId(),",
652
- " providerMetadata: accumulatedReasoningDetails.length > 0 ? {",
653
- " openrouter: {",
654
- " reasoning_details: accumulatedReasoningDetails",
655
- " }",
656
- " } : undefined",
657
- " });",
658
- "}",
659
- "",
660
- "separator",
661
- "",
662
- "if (reasoningStarted) {",
663
- " controller.enqueue({",
664
- ' type: "reasoning-end",',
665
- " id: reasoningId || generateId(),",
666
- " providerMetadata: accumulatedReasoningDetails.length > 0 ? {",
667
- " openrouter: {",
668
- " reasoning_details: accumulatedReasoningDetails",
669
- " }",
670
- " } : undefined",
671
- " });",
672
- "}",
673
- "",
674
- ].join("\n")
675
-
676
- for (const path of [
677
- join(tempRoot, "dist", "index.js"),
678
- join(tempRoot, "dist", "index.mjs"),
679
- join(tempRoot, "dist", "internal", "index.js"),
680
- join(tempRoot, "dist", "internal", "index.mjs"),
681
- ]) {
682
- expect(yield* fs.readFileString(path)).toBe(expected)
683
- }
684
- }).pipe(
685
- Effect.provide([
686
- AgentToolHandlers,
687
- Executor.layer,
688
- ToolkitRenderer.layer,
689
- ]),
690
- Effect.provide(NodeServices.layer),
691
- ),
692
- )
693
-
694
- it.effect("fails multi-file git patches atomically", () =>
695
- Effect.gen(function* () {
696
- const fs = yield* FileSystem.FileSystem
697
- const tempRoot = yield* makeTempRoot("clanka-apply-patch-git-fail-")
698
- yield* fs.makeDirectory(join(tempRoot, "src"), { recursive: true })
699
- yield* fs.writeFileString(join(tempRoot, "src", "app.txt"), "old\n")
700
- yield* fs.writeFileString(join(tempRoot, "keep.txt"), "keep\n")
701
-
702
- const executor = yield* Executor
703
- const tools = yield* AgentTools
704
- const output = yield* executor
705
- .execute({
706
- tools,
707
- script: [
708
- "await applyPatch(`",
709
- "diff --git a/src/app.txt b/src/main.txt",
710
- "similarity index 100%",
711
- "rename from src/app.txt",
712
- "rename to src/main.txt",
713
- "--- a/src/app.txt",
714
- "+++ b/src/main.txt",
715
- "@@ -1 +1 @@",
716
- "-missing",
717
- "+new",
718
- "diff --git a/keep.txt b/keep.txt",
719
- "deleted file mode 100644",
720
- "--- a/keep.txt",
721
- "+++ /dev/null",
722
- "diff --git a/dev/null b/notes/hello.txt",
723
- "new file mode 100644",
724
- "--- /dev/null",
725
- "+++ b/notes/hello.txt",
726
- "@@ -0,0 +1 @@",
727
- "+hello",
728
- "`)",
729
- ].join("\n"),
730
- })
731
- .pipe(
732
- Stream.mkString,
733
- Effect.provideServices(makeContextNoop(tempRoot)),
734
- )
735
-
736
- expect(output).toContain("applyPatch verification failed")
737
- expect(output).toContain("Failed to find expected lines")
738
- expect(yield* fs.readFileString(join(tempRoot, "src", "app.txt"))).toBe(
739
- "old\n",
740
- )
741
- expect(yield* fs.readFileString(join(tempRoot, "keep.txt"))).toBe(
742
- "keep\n",
743
- )
744
- yield* Effect.flip(fs.readFileString(join(tempRoot, "src", "main.txt")))
745
- yield* Effect.flip(
746
- fs.readFileString(join(tempRoot, "notes", "hello.txt")),
747
- )
748
- }).pipe(
749
- Effect.provide([
750
- AgentToolHandlers,
751
- Executor.layer,
752
- ToolkitRenderer.layer,
753
- ]),
754
- Effect.provide(NodeServices.layer),
755
- ),
756
- )
757
-
758
- it.effect("fails wrapped apply_patch patches atomically", () =>
759
- Effect.gen(function* () {
760
- const fs = yield* FileSystem.FileSystem
761
- const tempRoot = yield* makeTempRoot("clanka-apply-patch-wrapped-fail-")
762
- yield* fs.makeDirectory(join(tempRoot, "src"), { recursive: true })
763
- yield* fs.writeFileString(join(tempRoot, "src", "app.txt"), "old\n")
764
- yield* fs.writeFileString(join(tempRoot, "keep.txt"), "keep\n")
765
-
766
- const executor = yield* Executor
767
- const tools = yield* AgentTools
768
- const output = yield* executor
769
- .execute({
770
- tools,
771
- script: [
772
- "await applyPatch(`",
773
- "*** Begin Patch",
774
- "*** Update File: src/app.txt",
775
- "@@",
776
- "-missing",
777
- "+new",
778
- "*** Delete File: keep.txt",
779
- "*** Add File: notes/hello.txt",
780
- "+hello",
781
- "*** End Patch",
782
- "`)",
783
- ].join("\n"),
784
- })
785
- .pipe(
786
- Stream.mkString,
787
- Effect.provideServices(makeContextNoop(tempRoot)),
788
- )
789
-
790
- expect(output).toContain("applyPatch verification failed")
791
- expect(output).toContain("Failed to find expected lines")
792
- expect(yield* fs.readFileString(join(tempRoot, "src", "app.txt"))).toBe(
793
- "old\n",
794
- )
795
- expect(yield* fs.readFileString(join(tempRoot, "keep.txt"))).toBe(
796
- "keep\n",
797
- )
798
- yield* Effect.flip(
799
- fs.readFileString(join(tempRoot, "notes", "hello.txt")),
800
- )
801
- }).pipe(
802
- Effect.provide([
803
- AgentToolHandlers,
804
- Executor.layer,
805
- ToolkitRenderer.layer,
806
- ]),
807
- Effect.provide(NodeServices.layer),
808
- ),
809
- )
810
-
811
- it.effect("renames a file", () =>
812
- Effect.gen(function* () {
813
- const fs = yield* FileSystem.FileSystem
814
- const tempRoot = yield* makeTempRoot("clanka-rename-file-")
815
- yield* fs.makeDirectory(join(tempRoot, "src"), { recursive: true })
816
- yield* fs.writeFileString(join(tempRoot, "src", "app.txt"), "hello\n")
817
-
818
- const executor = yield* Executor
819
- const tools = yield* AgentTools
820
- yield* executor
821
- .execute({
822
- tools,
823
- script: [
824
- "await renameFile({",
825
- ' from: "src/app.txt",',
826
- ' to: "src/main.txt",',
827
- "})",
828
- 'console.log("renamed")',
829
- ].join("\n"),
830
- })
831
- .pipe(
832
- Stream.mkString,
833
- Effect.provideServices(makeContextNoop(tempRoot)),
834
- )
835
-
836
- expect(yield* fs.readFileString(join(tempRoot, "src", "main.txt"))).toBe(
837
- "hello\n",
838
- )
839
- yield* Effect.flip(fs.readFileString(join(tempRoot, "src", "app.txt")))
840
- }).pipe(
841
- Effect.provide([
842
- AgentToolHandlers,
843
- Executor.layer,
844
- ToolkitRenderer.layer,
845
- NodeFileSystem.layer,
846
- ]),
847
- Effect.provide(NodeServices.layer),
848
- ),
849
- )
850
-
851
- it.effect("rg respects ignore files by default and can disable them", () =>
852
- Effect.gen(function* () {
853
- const fs = yield* FileSystem.FileSystem
854
- const tempRoot = yield* makeTempRoot("clanka-rg-ignore-")
855
- yield* fs.writeFileString(join(tempRoot, ".ignore"), "ignored.txt\n")
856
- yield* fs.writeFileString(
857
- join(tempRoot, "visible.txt"),
858
- "match visible\n",
859
- )
860
- yield* fs.writeFileString(
861
- join(tempRoot, "ignored.txt"),
862
- "match ignored\n",
863
- )
864
-
865
- const executor = yield* Executor
866
- const tools = yield* AgentTools
867
-
868
- const defaultOutput = yield* executor
869
- .execute({
870
- tools,
871
- script: [
872
- 'const output = await rg({ pattern: "match" })',
873
- "console.log(output)",
874
- ].join("\n"),
875
- })
876
- .pipe(
877
- Stream.mkString,
878
- Effect.provideServices(makeContextNoop(tempRoot)),
879
- )
880
-
881
- expect(defaultOutput).toContain("visible.txt:1:match visible")
882
- expect(defaultOutput).not.toContain("ignored.txt:1:match ignored")
883
-
884
- const noIgnoreOutput = yield* executor
885
- .execute({
886
- tools,
887
- script: [
888
- 'const output = await rg({ pattern: "match", noIgnore: true })',
889
- "console.log(output)",
890
- ].join("\n"),
891
- })
892
- .pipe(
893
- Stream.mkString,
894
- Effect.provideServices(makeContextNoop(tempRoot)),
895
- )
896
-
897
- expect(noIgnoreOutput).toContain("visible.txt:1:match visible")
898
- expect(noIgnoreOutput).toContain("ignored.txt:1:match ignored")
899
- }).pipe(
900
- Effect.provide([
901
- AgentToolHandlers,
902
- Executor.layer,
903
- ToolkitRenderer.layer,
904
- ]),
905
- Effect.provide(NodeServices.layer),
906
- ),
907
- )
908
-
909
- it.effect("rg combines noIgnore with glob and maxLines", () =>
910
- Effect.gen(function* () {
911
- const fs = yield* FileSystem.FileSystem
912
- const tempRoot = yield* makeTempRoot("clanka-rg-no-ignore-glob-")
913
- yield* fs.writeFileString(join(tempRoot, ".ignore"), "ignored-*.txt\n")
914
- yield* fs.writeFileString(join(tempRoot, "ignored-a.txt"), "needle one\n")
915
- yield* fs.writeFileString(join(tempRoot, "ignored-b.txt"), "needle two\n")
916
- yield* fs.writeFileString(
917
- join(tempRoot, "visible.txt"),
918
- "needle visible\n",
919
- )
920
-
921
- const executor = yield* Executor
922
- const tools = yield* AgentTools
923
- const output = yield* executor
924
- .execute({
925
- tools,
926
- script: [
927
- "const output = await rg({",
928
- ' pattern: "needle",',
929
- ' glob: "ignored-*.txt",',
930
- " noIgnore: true,",
931
- " maxLines: 1,",
932
- "})",
933
- "console.log(output)",
934
- ].join("\n"),
935
- })
936
- .pipe(
937
- Stream.mkString,
938
- Effect.provideServices(makeContextNoop(tempRoot)),
939
- )
940
-
941
- const lines = output.trimEnd().split("\n")
942
- expect(lines).toHaveLength(1)
943
- expect(lines[0]).toMatch(/ignored-[ab]\.txt:1:needle (one|two)/)
944
- expect(output).not.toContain("visible.txt:1:needle visible")
945
- }).pipe(
946
- Effect.provide([
947
- AgentToolHandlers,
948
- Executor.layer,
949
- ToolkitRenderer.layer,
950
- ]),
951
- Effect.provide(NodeServices.layer),
952
- ),
953
- )
954
- })