clanka 0.0.21 → 0.0.22
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/Agent.d.ts.map +1 -1
- package/dist/Agent.js +5 -1
- package/dist/Agent.js.map +1 -1
- package/dist/AgentTools.d.ts +3 -1
- package/dist/AgentTools.d.ts.map +1 -1
- package/dist/AgentTools.js +7 -1
- package/dist/AgentTools.js.map +1 -1
- package/dist/AgentTools.test.js +549 -1
- package/dist/AgentTools.test.js.map +1 -1
- package/dist/ApplyPatch.test.js +352 -0
- package/dist/ApplyPatch.test.js.map +1 -1
- package/dist/Codex.d.ts +1 -1
- package/dist/CodexAuth.d.ts +4 -4
- package/dist/Copilot.d.ts +1 -1
- package/dist/CopilotAuth.d.ts +3 -3
- package/package.json +2 -2
- package/src/Agent.ts +5 -4
- package/src/AgentTools.test.ts +721 -1
- package/src/AgentTools.ts +7 -1
- package/src/ApplyPatch.test.ts +369 -0
package/src/AgentTools.test.ts
CHANGED
|
@@ -36,8 +36,9 @@ describe("AgentTools", () => {
|
|
|
36
36
|
expect(output).toContain("readonly path: string;")
|
|
37
37
|
expect(output).toContain("readonly startLine?: number | undefined;")
|
|
38
38
|
expect(output).toContain("readonly endLine?: number | undefined;")
|
|
39
|
+
expect(output).toContain("readonly noIgnore?: boolean | undefined;")
|
|
39
40
|
expect(output).toContain(
|
|
40
|
-
"/** Apply a git diff / unified diff patch across one or more files. */",
|
|
41
|
+
"/** Apply a git diff / unified diff patch, or a wrapped apply_patch patch, across one or more files. */",
|
|
41
42
|
)
|
|
42
43
|
expect(output).toContain(
|
|
43
44
|
"declare function applyPatch(patch: string): Promise<string>",
|
|
@@ -192,6 +193,621 @@ describe("AgentTools", () => {
|
|
|
192
193
|
),
|
|
193
194
|
)
|
|
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
|
+
|
|
195
811
|
it.effect("renames a file", () =>
|
|
196
812
|
Effect.gen(function* () {
|
|
197
813
|
const fs = yield* FileSystem.FileSystem
|
|
@@ -231,4 +847,108 @@ describe("AgentTools", () => {
|
|
|
231
847
|
Effect.provide(NodeServices.layer),
|
|
232
848
|
),
|
|
233
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
|
+
)
|
|
234
954
|
})
|