The Tools plugin for speech-to-speech-mobile.
speech-to-speech-mobile
│
│ Tools contract
▼
s2s-tools
│
└── core — ToolRegistry (the dispatcher) + CalculatorTool (example)
core — ToolRegistry, moved unchanged from speech-to-speech-mobile
(behavior preserved exactly: flat-JSON tool-call parsing, hand-written
parser kept testable off-device without org.json). Registers/dispatches
ToolDefinition/ToolFunction pairs, extracts a ToolCall from model
output, executes it and returns a ToolResult.
CalculatorTool — one worked stateless example (pure function of its
arguments, ignores ToolContext entirely).
fetch and webdroid adapters are not wired in. Both are separate,
existing, generic repos (loyality7/fetch, loyality7/webdroid) that stay
completely S2S-unaware — this repo's job is to adapt their APIs into
ToolDefinition/ToolFunction, never the other way around. As of this
writing neither repo publishes a JitPack-resolvable artifact: fetch's
Gradle modules have no maven-publish plugin configured, and webdroid's
own README states Maven Central deployment will accompany a future v1.1.0
release. Once either fixes that, add it as its own module here
(s2s-tools/fetch, s2s-tools/webdroid) — same one-module-per-dependency
isolation pattern s2s-llm uses (llama-cpp/remote never share a
dependency), so installing one adapter never pulls the other's transitive
deps. Do not modify fetch or webdroid themselves to make this work.
For the worked example of what a stateful, session-isolated tool adapter
looks like (the pattern a real webdroid adapter should follow), see
speech-to-speech-mobile/templates/s2s-tools-plugin-template's
BrowserTool — it demonstrates exactly the ToolContext.sessionId-keyed
isolation a real browsing tool needs, using a fake in-memory page instead
of a real WebDroid session.
dependencyResolutionManagement {
repositories {
maven { url = uri("https://jitpack.io") }
}
}
dependencies {
// Single-module repo today — JitPack publishes under the plain repo
// coordinate (com.github.loyality7:s2s-tools), not the ".repo:module"
// multi-module convention s2s-llm/s2s-context use. That convention only
// kicks in once a second module (e.g. fetch/webdroid, once they're
// JitPack-resolvable) exists here.
implementation("com.github.loyality7:s2s-tools:0.1.0")
}import com.s2s.tools.core.CalculatorTool
import com.s2s.tools.core.ToolRegistry
val tools = ToolRegistry()
CalculatorTool.registerOn(tools)
val engine = S2SEngine(
context = androidContext,
config = config,
languageModel = languageModel,
history = history,
tools = tools,
)Core's default (when no tools arg is passed) is NoopTools — a genuine
no-op, not a hidden concrete registry. A host that wants real tool calling
must supply a Tools implementation, whether ToolRegistry from here or
its own.
ToolContext(sessionId, turnId, callId) is engine-generated —
S2SEngine creates it and passes it into every Tools.execute() /
ToolFunction.invoke() call. No tool, including anything built on top of
ToolRegistry here, generates its own session identity. See
ToolRegistrySessionIsolationTest.kt for the proof that keying tool state
on ToolContext.sessionId keeps two concurrent conversations' tool state
fully isolated.
ToolRegistryTest and ToolRegistrySessionIsolationTest run on the JVM
with plain JUnit — no Android instrumentation needed for the pure-Kotlin
dispatch/parsing logic. A real fetch/webdroid adapter touching actual
network/WebView APIs will need instrumented tests in its own module for
that part; the session-isolation logic itself should stay testable this way.
Same JitPack mechanism as speech-to-speech-mobile and s2s-llm — push,
tag, JitPack builds on first resolution. No custom groupId/artifactId
override in core/build.gradle.kts.
With only one module (:core), JitPack publishes under the plain repo
coordinate com.github.loyality7:s2s-tools, NOT com.github.loyality7. s2s-tools:core — the .repo:module multi-module convention (what
s2s-llm/s2s-context use) only applies once a repo has more than one
Gradle module. Confirmed by directly listing the published artifact
directory after the first tag build — do not assume the multi-module
pattern applies here until fetch/webdroid modules actually exist.