Skip to content

Commit c200747

Browse files
authored
Merge pull request #42 from Deep-CodeAI/feat/1016-typed-skill-tools-refs
feat(#1016): Skill.tools(...) accepts typed Tool<*, *> handles
2 parents 93022b7 + 85266af commit c200747

3 files changed

Lines changed: 92 additions & 5 deletions

File tree

src/main/kotlin/agents_engine/core/Skill.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,21 @@ class Skill<IN, OUT>(
7474
implementation = null
7575
}
7676

77+
/**
78+
* Typed overload accepting `Tool<*, *>` handles returned by `tool(...)` builders
79+
* (#1015). Catches typos and stale references at compile time instead of at agent
80+
* `validate()` (`Agent.kt:404`). See `docs/ksp-design.md` for the broader plan.
81+
*
82+
* Requires at least one ref to disambiguate from `tools()` (empty), which resolves
83+
* to the legacy string-vararg form.
84+
*/
85+
fun tools(first: agents_engine.model.Tool<*, *>, vararg rest: agents_engine.model.Tool<*, *>) {
86+
checkNotFrozen()
87+
isAgentic = true
88+
toolNames = listOf(first.name) + rest.map { it.name }
89+
implementation = null
90+
}
91+
7792
var outputTransformer: ((String) -> OUT)? = null
7893
private set
7994

src/main/kotlin/agents_engine/model/ToolDef.kt

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,21 @@ class ToolDef(
3030

3131
/**
3232
* Typed handle returned by every `tool(...)` builder overload. Wraps a
33-
* [ToolDef] with phantom type parameters that let `Skill.tools(...)` and
34-
* `+autoTool(...)` accept compile-time-checked references instead of
35-
* stringly-typed lookups (#1015 — KSP P1.1).
33+
* [ToolDef] with phantom type parameters that let `Skill.tools(...)` accept
34+
* compile-time-checked references instead of stringly-typed lookups
35+
* (#1015 — KSP P1.1).
3636
*
3737
* `Args` is the deserialized input type for typed tools (the `@Generable`
3838
* data class), `Map<String, Any?>` for untyped tools. `Result` is the lambda's
3939
* return type. Both type parameters are erased at runtime — the [def]
4040
* underneath is the canonical runtime representation.
41+
*
42+
* Not `@JvmInline value` because Kotlin prohibits vararg of value-class types,
43+
* and `Skill.tools(vararg refs: Tool<*, *>)` (#1016) is the primary use site.
44+
* Tool handles are constructed once per agent build, never on the hot path —
45+
* the per-handle allocation is negligible.
4146
*/
42-
@JvmInline
43-
value class Tool<Args, Result> @PublishedApi internal constructor(
47+
class Tool<Args, Result> @PublishedApi internal constructor(
4448
@PublishedApi internal val def: ToolDef,
4549
) {
4650
val name: String get() = def.name
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package agents_engine.model
2+
3+
import agents_engine.core.agent
4+
import kotlin.test.Test
5+
import kotlin.test.assertEquals
6+
7+
/**
8+
* #1016 — `Skill.tools(...)` accepts typed `Tool<*, *>` handles in addition to
9+
* the legacy stringly-typed form. The two forms produce identical
10+
* `Skill.toolNames` and dispatch identically through the agentic loop.
11+
*
12+
* The string form stays — typed and string overloads coexist; #1017 will
13+
* deprecate the string form (warning level), but not yet.
14+
*/
15+
class TypedToolRefsTest {
16+
17+
@Test
18+
fun `typed tool refs produce same toolNames as string form`() {
19+
val typedAgent = agent<String, String>("typed-form") {
20+
lateinit var fetch: Tool<Map<String, Any?>, Any?>
21+
lateinit var compile: Tool<Map<String, Any?>, Any?>
22+
tools {
23+
fetch = tool("fetch", "Fetch") { _ -> "fetched" }
24+
compile = tool("compile", "Compile") { _ -> "compiled" }
25+
}
26+
skills {
27+
skill<String, String>("build") {
28+
tools(fetch, compile)
29+
}
30+
}
31+
}
32+
33+
val stringAgent = agent<String, String>("string-form") {
34+
tools {
35+
tool("fetch", "Fetch") { _ -> "fetched" }
36+
tool("compile", "Compile") { _ -> "compiled" }
37+
}
38+
skills {
39+
skill<String, String>("build") {
40+
tools("fetch", "compile")
41+
}
42+
}
43+
}
44+
45+
val typedSkill = typedAgent.skills["build"]!!
46+
val stringSkill = stringAgent.skills["build"]!!
47+
48+
assertEquals(stringSkill.toolNames, typedSkill.toolNames)
49+
assertEquals(true, typedSkill.isAgentic)
50+
assertEquals(listOf("fetch", "compile"), typedSkill.toolNames)
51+
}
52+
53+
@Test
54+
fun `typed refs survive validate() — agent constructs without unknown-tool error`() {
55+
val a = agent<String, String>("typed-validate") {
56+
lateinit var ping: Tool<Map<String, Any?>, Any?>
57+
tools {
58+
ping = tool("ping", "Ping") { _ -> "pong" }
59+
}
60+
skills {
61+
skill<String, String>("respond") {
62+
tools(ping)
63+
}
64+
}
65+
}
66+
assertEquals(listOf("ping"), a.skills["respond"]!!.toolNames)
67+
}
68+
}

0 commit comments

Comments
 (0)