clanka 0.7.2 → 0.7.3
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.test.js +78 -0
- package/dist/Agent.test.js.map +1 -1
- package/dist/AgentExecutor.d.ts +1 -1
- package/dist/AgentTools.d.ts +4 -4
- package/dist/AgentTools.d.ts.map +1 -1
- package/dist/AgentTools.js +4 -0
- package/dist/AgentTools.js.map +1 -1
- package/dist/bin/cli.mjs +4 -2
- package/dist/bin/cli.mjs.map +1 -1
- package/package.json +1 -1
- package/src/Agent.test.ts +118 -0
- package/src/AgentTools.ts +9 -1
package/package.json
CHANGED
package/src/Agent.test.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { assert, describe, it } from "@effect/vitest"
|
|
2
|
+
import * as NodeServices from "@effect/platform-node/NodeServices"
|
|
3
|
+
import * as FileSystem from "effect/FileSystem"
|
|
4
|
+
import * as FetchHttpClient from "effect/unstable/http/FetchHttpClient"
|
|
2
5
|
import * as Effect from "effect/Effect"
|
|
3
6
|
import * as Duration from "effect/Duration"
|
|
4
7
|
import * as Exit from "effect/Exit"
|
|
@@ -404,6 +407,121 @@ const assertCompactedShape = (
|
|
|
404
407
|
return prompt.content.slice(2)
|
|
405
408
|
}
|
|
406
409
|
|
|
410
|
+
const localExecutorLayer = Layer.unwrap(
|
|
411
|
+
Effect.gen(function* () {
|
|
412
|
+
const fs = yield* FileSystem.FileSystem
|
|
413
|
+
const directory = yield* fs.makeTempDirectoryScoped()
|
|
414
|
+
return AgentExecutor.layerLocal({ directory })
|
|
415
|
+
}),
|
|
416
|
+
).pipe(Layer.provide(Layer.mergeAll(NodeServices.layer, FetchHttpClient.layer)))
|
|
417
|
+
|
|
418
|
+
const invalidCompletions = [
|
|
419
|
+
'taskComplete({ output: "done" })',
|
|
420
|
+
"taskComplete()",
|
|
421
|
+
"taskComplete(undefined)",
|
|
422
|
+
"taskComplete(null)",
|
|
423
|
+
"taskComplete(42)",
|
|
424
|
+
"taskComplete(true)",
|
|
425
|
+
'taskComplete(["done"])',
|
|
426
|
+
'taskComplete({ summary: "done" })',
|
|
427
|
+
]
|
|
428
|
+
|
|
429
|
+
it.layer(localExecutorLayer, { excludeTestServices: true })(
|
|
430
|
+
"taskComplete through the real VM executor",
|
|
431
|
+
(it) => {
|
|
432
|
+
for (const summary of ["done", "", "First line\nSecond line"]) {
|
|
433
|
+
it.effect(`finishes with string summary ${JSON.stringify(summary)}`, () =>
|
|
434
|
+
AgentExecutor.AgentExecutor.use((executor) =>
|
|
435
|
+
runAgentCollect({
|
|
436
|
+
conversationMode: false,
|
|
437
|
+
executor,
|
|
438
|
+
respond: (_call, index) =>
|
|
439
|
+
index === 0
|
|
440
|
+
? Stream.fromIterable([
|
|
441
|
+
toolCall(
|
|
442
|
+
"complete",
|
|
443
|
+
`await taskComplete(${JSON.stringify(summary)})`,
|
|
444
|
+
),
|
|
445
|
+
])
|
|
446
|
+
: Stream.die("completion must not require another model call"),
|
|
447
|
+
}).pipe(
|
|
448
|
+
Effect.map(({ exit, calls }) => {
|
|
449
|
+
assert.deepStrictEqual(exit, Exit.succeed(summary))
|
|
450
|
+
assert.strictEqual(calls.length, 1)
|
|
451
|
+
}),
|
|
452
|
+
),
|
|
453
|
+
),
|
|
454
|
+
)
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
for (const invocation of invalidCompletions) {
|
|
458
|
+
it.effect(`rejects ${invocation} without completing`, () =>
|
|
459
|
+
AgentExecutor.AgentExecutor.use((executor) =>
|
|
460
|
+
Effect.gen(function* () {
|
|
461
|
+
const summaries: Array<string> = []
|
|
462
|
+
const output = yield* executor
|
|
463
|
+
.execute({
|
|
464
|
+
script: `try { await ${invocation}; console.log("ACCEPTED") } catch (error) { console.log("REJECTED:", String(error)) } console.log("SCRIPT_SURVIVED")`,
|
|
465
|
+
onTaskComplete: (summary) =>
|
|
466
|
+
Effect.sync(() => {
|
|
467
|
+
summaries.push(summary)
|
|
468
|
+
}),
|
|
469
|
+
onSubagent: () => Effect.succeed(""),
|
|
470
|
+
onImage: () => Effect.void,
|
|
471
|
+
})
|
|
472
|
+
.pipe(Stream.mkString)
|
|
473
|
+
assert.deepStrictEqual(
|
|
474
|
+
summaries,
|
|
475
|
+
[],
|
|
476
|
+
"invalid input must not reach TaskCompleter",
|
|
477
|
+
)
|
|
478
|
+
assert.include(output, "REJECTED:")
|
|
479
|
+
assert.match(output, /string/i)
|
|
480
|
+
assert.include(output, "SCRIPT_SURVIVED")
|
|
481
|
+
assert.notInclude(output, "ACCEPTED")
|
|
482
|
+
}),
|
|
483
|
+
),
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
it.effect(
|
|
487
|
+
`survives ${invocation} and completes on the next model call`,
|
|
488
|
+
() =>
|
|
489
|
+
AgentExecutor.AgentExecutor.use((executor) =>
|
|
490
|
+
runAgentCollect({
|
|
491
|
+
conversationMode: false,
|
|
492
|
+
executor,
|
|
493
|
+
respond: (_call, index) => {
|
|
494
|
+
switch (index) {
|
|
495
|
+
case 0:
|
|
496
|
+
return Stream.fromIterable([
|
|
497
|
+
toolCall("invalid", `await ${invocation}`),
|
|
498
|
+
])
|
|
499
|
+
case 1:
|
|
500
|
+
return Stream.fromIterable([
|
|
501
|
+
toolCall("retry", 'await taskComplete("recovered")'),
|
|
502
|
+
])
|
|
503
|
+
default:
|
|
504
|
+
return Stream.die("unexpected model call after completion")
|
|
505
|
+
}
|
|
506
|
+
},
|
|
507
|
+
}).pipe(
|
|
508
|
+
Effect.map(({ exit, calls }) => {
|
|
509
|
+
assert.deepStrictEqual(exit, Exit.succeed("recovered"))
|
|
510
|
+
assert.strictEqual(calls.length, 2)
|
|
511
|
+
const [result] = toolResults(calls[1]!.prompt)
|
|
512
|
+
assert.isDefined(
|
|
513
|
+
result,
|
|
514
|
+
"the model must receive the recoverable tool error",
|
|
515
|
+
)
|
|
516
|
+
assert.match(String(result!.result), /string/i)
|
|
517
|
+
}),
|
|
518
|
+
),
|
|
519
|
+
),
|
|
520
|
+
)
|
|
521
|
+
}
|
|
522
|
+
},
|
|
523
|
+
)
|
|
524
|
+
|
|
407
525
|
// --- tests -------------------------------------------------------------------
|
|
408
526
|
|
|
409
527
|
describe("Agent execute output cap", () => {
|
package/src/AgentTools.ts
CHANGED
|
@@ -273,6 +273,7 @@ export const AgentTools = Toolkit.make(
|
|
|
273
273
|
identifier: "output",
|
|
274
274
|
}),
|
|
275
275
|
dependencies: [TaskCompleter],
|
|
276
|
+
failure: Schema.String,
|
|
276
277
|
}),
|
|
277
278
|
)
|
|
278
279
|
|
|
@@ -722,7 +723,14 @@ export const AgentToolHandlersNoDeps = AgentToolsWithSearch.toLayer(
|
|
|
722
723
|
|
|
723
724
|
${prompt}`)
|
|
724
725
|
}, Effect.orDie),
|
|
725
|
-
taskComplete: Effect.fn("AgentTools.taskComplete")(function* (
|
|
726
|
+
taskComplete: Effect.fn("AgentTools.taskComplete")(function* (
|
|
727
|
+
message: unknown,
|
|
728
|
+
) {
|
|
729
|
+
if (typeof message !== "string") {
|
|
730
|
+
return yield* Effect.fail(
|
|
731
|
+
'taskComplete requires a string argument. Pass your final output directly, for example: taskComplete("done").',
|
|
732
|
+
)
|
|
733
|
+
}
|
|
726
734
|
const deferred = yield* TaskCompleter
|
|
727
735
|
yield* deferred(message)
|
|
728
736
|
}),
|