Installation
TypeCast runs on the JVM, JDK 17+, for Scala 3 (3.7+) and Java. The published artifacts are
ordinary JVM jars: Scala consumers depend on them through sbt's %% operator; Java, Kotlin, and
other JVM languages spell out the same coordinates from Gradle or Maven.
Pre-1.0 coordinates
Not yet on Maven Central. The coordinates below are the planned shape — organization
io.typecast, version 0.1.0-SNAPSHOT until the first tag. Both the coordinates and the
TypeCast name may change before launch (see the README status section).
Requirements
- JDK 17 or newer. The build targets
-java-output-version 17, and the Java facade relies on Java 17 records and sealed interfaces (it picks up Java 21 sealed pattern switches when present — see java.md). - Scala 3.7+ for Scala consumers. There is no Scala 2 support, by design.
- A provider to extract against: an Anthropic or OpenAI API key, an OpenAI-compatible endpoint, or a local Ollama install. See Provider setup below.
Dependency coordinates
The core is zero-dependency (Scala stdlib only). You always add typecast-core, then one
provider transport, then any optional modules you need (see the module table).
sbt
sbt's %% operator appends the Scala binary-compatibility suffix for you, so you write the bare
module name:
libraryDependencies ++= Seq(
"io.typecast" %% "typecast-core" % "0.1.0-SNAPSHOT",
"io.typecast" %% "typecast-anthropic" % "0.1.0-SNAPSHOT", // or typecast-openai
"io.typecast" %% "typecast-iron" % "0.1.0-SNAPSHOT" // optional: refinement constraints
)
Gradle (Kotlin DSL)
dependencies {
implementation("io.typecast:typecast-core_3:0.1.0-SNAPSHOT")
implementation("io.typecast:typecast-anthropic_3:0.1.0-SNAPSHOT")
implementation("io.typecast:typecast-java_3:0.1.0-SNAPSHOT") // Java/Kotlin facade
}
Gradle (Groovy DSL)
dependencies {
implementation 'io.typecast:typecast-core_3:0.1.0-SNAPSHOT'
implementation 'io.typecast:typecast-anthropic_3:0.1.0-SNAPSHOT'
implementation 'io.typecast:typecast-java_3:0.1.0-SNAPSHOT' // Java/Kotlin facade
}
Maven
<dependency>
<groupId>io.typecast</groupId>
<artifactId>typecast-core_3</artifactId>
<version>0.1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.typecast</groupId>
<artifactId>typecast-anthropic_3</artifactId>
<version>0.1.0-SNAPSHOT</version>
</dependency>
The _3 suffix for non-sbt build tools
Outside sbt you write the _3 suffix explicitly: typecast-core_3, typecast-java_3, and so on.
_3 is Scala's binary-compatibility suffix (Scala 3), baked into the artifactId by the publisher —
the same convention as cats-core_3 or munit_3. sbt appends it automatically through %%; Gradle
and Maven do not, so you spell it out.
The suffix says nothing about consumability. The jars are ordinary JVM jars, and the Java/Kotlin
facade keeps Scala types out of its public signatures; the Scala standard library rides along as a
regular transitive dependency, like any other jar. The standalone Kotlin/Gradle app in
examples-realworld/kotlin-triage
consumes typecast-java_3 and typecast-langchain4j_3 from a Gradle build to prove exactly this.
Modules
Add typecast-core plus one transport; reach for the rest as the feature you need requires.
| Artifact | Provides | Add when |
|---|---|---|
typecast-core |
The whole pipeline: JSON AST, tolerant parser, schema derivation, alignment, validation, the extraction state machine, report rendering. Zero external dependencies. | Always. |
typecast-derivation |
Additional derives-based derivation support that builds on core. |
Pulled in transitively by typecast-tools; add directly only if you need its surface without tools. |
typecast-iron |
The typecast.iron.given bridge so Iron refinements (Int :| Positive, String :| ValidEmail) act as schema constraints, validators, and targeted-retry sources. Adds the iron dependency. |
You want compile-time refinement types inside derives LlmSchema. |
typecast-anthropic |
The Anthropic transport (Anthropic.fromEnv(), AnthropicTransport(apiKey)), native structured outputs. Adds an HTTP client. |
Extracting against Anthropic / Claude. |
typecast-openai |
The OpenAI transport and OpenAI-compatible factories (OpenAi(apiKey), Ollama(), OpenAiCompatible.jsonMode/promptOnly). Adds an HTTP client. |
Extracting against OpenAI, Ollama, vLLM, Groq, or any OpenAI-compatible endpoint. |
typecast-runner-sync |
The synchronous runner. | You want blocking, effect-free extraction (the simplest default). |
typecast-runner-ce |
The cats-effect runner; carries fs2 streaming, so this is the module that provides StreamingExtract.stream[IO, A] (streaming.md). Adds cats-effect and fs2. |
You run on cats-effect / want streaming partial decode. |
typecast-runner-zio |
The ZIO runner. Adds zio and zio-streams. | You run on ZIO. |
typecast-tools |
Typed tool dispatch: derives LlmTool, Tools(...), tc.withTools(...).run(...) (tools-and-mcp.md). Depends on typecast-derivation. |
You want the model to call typed tools. |
typecast-mcp |
McpServer.from(tools).serveStdio() — export a tool registry as an MCP server. Depends on typecast-tools. |
You want to serve your tools over MCP. |
typecast-java |
The Java/Kotlin facade: TypeCastClient, records, sealed interfaces, Jakarta Bean Validation annotations (java.md). Adds jakarta.validation-api and jackson-databind. |
You call TypeCast from Java, Kotlin, or another JVM language. |
typecast-langchain4j |
Adapter that drives any LangChain4j ChatModel (20+ providers) through TypeCast. Depends on the Java facade plus both transports. Adds langchain4j. |
You want a provider only LangChain4j reaches. |
Provider setup
The same user code runs on every provider; only the factory you pass to TypeCast(...) changes.
The model identifier is required at construction — model names are provider-scoped and there is no
sensible cross-provider default.
Anthropic
import typecast.anthropic.Anthropic
val tc = TypeCast(Anthropic.fromEnv(), model = "claude-haiku-4-5")
Anthropic.fromEnv() reads ANTHROPIC_API_KEY from the environment and returns an Either — a
missing key is not thrown but deferred and reported as ExtractionError.TransportFailed on each
extraction. Set it before running:
You can also construct the transport directly with AnthropicTransport(apiKey).
OpenAI
import typecast.openai.OpenAi
val tc = TypeCast(OpenAi(sys.env("OPENAI_API_KEY")), model = "gpt-4o-mini")
Ollama (local, no key)
Ollama() targets the OpenAI-compatible endpoint at localhost:11434/v1 and needs no credentials.
Install Ollama, then pull the default model used throughout the docs:
Other OpenAI-compatible endpoints
vLLM, Groq, and any other OpenAI-compatible server go through the compatible factories:
import typecast.openai.OpenAiCompatible
val groq = TypeCast(
OpenAiCompatible.jsonMode("https://api.groq.com/openai/v1", apiKey = sys.env.get("GROQ_API_KEY")),
model = "llama-3.3-70b-versatile"
)
For the per-provider enforcement model (native vs JSON mode vs prompt-only, and what gets validated post-hoc), see providers-and-lowering.md.
Smoke test
A minimal end-to-end check against a local Ollama (no API key needed):
import typecast.*
import typecast.openai.Ollama
case class City(name: String, country: String, population: Int) derives LlmSchema
@main def smokeTest(): Unit =
val tc = TypeCast(Ollama(), model = "llama3.2")
tc.extract[City]("Paris is the capital of France, home to about 2.1 million people.") match
case Right(city) => println(s"${city.name}, ${city.country}: ${city.population}")
case Left(err) => println(err.render)
extract[A] returns Either[ExtractionError, A] — a validated value or a typed failure that
renders human-readable. Once this runs, continue with getting-started.md for
reports, retries, budgets, and provider swaps.