A type-safe Scala.js SDK for the @anthropic-ai/claude-agent-sdk, providing idiomatic ZIO-based access to Claude's agentic capabilities.
- Single import convenience —
import com.tjclp.scalagent.*brings all types into scope - ZIO + ZStream integration for purely functional streaming
- Structured outputs with compile-time JSON Schema generation
- Type-safe message ADT mirroring the SDK's discriminated unions
- Fluent configuration builders with Scala-native API
- Multi-turn conversations via session management
- Tool definition DSL for custom MCP tools
def ivyDeps = Seq(
mvn"com.tjclp::scalagent::0.2.4"
)libraryDependencies += "com.tjclp" %%% "scalagent" % "0.2.4"<dependency>
<groupId>com.tjclp</groupId>
<artifactId>scalagent_sjs1_3</artifactId>
<version>0.2.4</version>
</dependency>- Mill build tool
- Bun runtime (or Node.js 18+)
- Scala 3.3.x
ANTHROPIC_API_KEYenvironment variable
import com.tjclp.scalagent.*
import zio.*
object MyApp extends ZIOAppDefault:
val run =
for
answer <- Claude.ask("What is 2 + 2?")
_ <- Console.printLine(s"Answer: $answer")
yield ()import com.tjclp.scalagent.*
import zio.*
object StreamingApp extends ZIOAppDefault:
val run =
for
_ <- Console.printLine("Counting to 5...")
_ <- Claude.query("Count from 1 to 5, one number per line")
.textOnly
.foreach(text => Console.print(text).orDie)
yield ()import com.tjclp.scalagent.*
import zio.*
object ConversationApp extends ZIOAppDefault:
val run =
for
session <- ClaudeSession.create(AgentOptions.default.withModel(Model.Sonnet4_5))
_ <- session.send("Remember the number 42").runDrain
answer <- session.ask("What number did I ask you to remember?")
_ <- Console.printLine(s"Claude remembered: $answer")
yield ()Get type-safe responses with compile-time JSON Schema generation:
import com.tjclp.scalagent.*
import zio.*
import zio.json.*
// Define your output type with optional field descriptions
case class Analysis(
@description("Brief summary of findings") summary: String,
@description("Quality score from 0-100") score: Int,
suggestions: List[String]
) derives JsonDecoder
// Single-line schema derivation
given StructuredOutput[Analysis] = StructuredOutput.derive[Analysis]
object AnalysisApp extends ZIOAppDefault:
val run =
val options = AgentOptions.default
.withModel(Model.Sonnet4_5)
.withStructuredOutput[Analysis]
for
result <- Claude.queryComplete("Analyze this code...", options)
analysis = result.outcome match
case s: ResultOutcome.Success => s.parseAs[Analysis]
case e: ResultOutcome.Error => Left(e.errors.mkString(", "))
_ <- Console.printLine(s"Analysis: $analysis")
yield ()# Install dependencies with Bun
bun install
# Compile the project
./mill agent.compile
# Run the example (compiles and runs with Bun)
bun run run
# Or manually:
./mill examples.fastLinkJS
bun run out/examples/fastLinkJS.dest/main.jsUse AgentOptions to configure queries:
val options = AgentOptions.default
.withModel(Model.Sonnet4_5)
.withMaxTurns(10)
.withMaxBudgetUsd(0.50)
.withPermissionMode(PermissionMode.AcceptEdits)
.withMcpServer("myserver", McpServerConfig.stdio("node", "server.js"))| Option | Description |
|---|---|
withModel(m) |
Set the model to use |
withModelId(id) |
Set a custom/new model ID |
withMaxTurns(n) |
Limit number of conversation turns |
withMaxBudgetUsd(b) |
Set maximum cost budget |
withPermissionMode(pm) |
Control permission handling |
withMcpServer(name, config) |
Add an MCP server |
withBypassPermissions |
Bypass all permission checks (dangerous!) |
withIncludePartialMessages |
Include streaming partial messages |
withStructuredOutput[T] |
Enable structured output with type-safe parsing |
withMainAgent(name) |
Set agent for main conversation thread |
withFileCheckpointing |
Enable file rewind capability |
withFallbackModel(m) |
Set fallback model if primary fails |
Default- Prompt user for each tool useAcceptEdits- Auto-accept file editsBypassPermissions- Skip all permission checksPlan- Plan mode without executionDontAsk- Deny unpermitted tools without promptingDelegate- Delegated permission handling
The AgentMessage enum represents all message types from the SDK:
enum AgentMessage:
case Assistant(message, parentToolUseId, error, uuid, sessionId)
case User(message, parentToolUseId, isSynthetic, toolUseResult, uuid, sessionId)
case Result(outcome, uuid, sessionId)
case System(event, uuid, sessionId)
case StreamEvent(event, parentToolUseId, uuid, sessionId)
case ToolProgress(toolUseId, toolName, parentToolUseId, elapsedTimeSeconds, uuid, sessionId)
case TaskNotification(taskId, status, outputFile, summary, uuid, sessionId)
case ToolUseSummary(summary, precedingToolUseIds, uuid, sessionId)enum TaskStatus:
case Completed, Failed, Stopped
case Custom(value: String)enum ResultOutcome:
case Success(durationMs, durationApiMs, numTurns, result, totalCostUsd, usage, ...)
case Error(reason, durationMs, durationApiMs, numTurns, totalCostUsd, usage, errors, ...)For advanced control (interruption, permission mode changes):
for
queryStream <- ClaudeAgent.queryRaw("Complex task...")
fiber <- queryStream.messages.foreach(handleMessage).fork
_ <- ZIO.sleep(30.seconds)
_ <- queryStream.interrupt // Cancel the query
_ <- fiber.join
yield ()The QueryStream provides methods for runtime control:
| Method | Description |
|---|---|
close() |
Abort running query and terminate process |
reconnectMcpServer(name) |
Reconnect a specific MCP server |
toggleMcpServer(name, enabled) |
Enable/disable an MCP server |
rewindFiles(messageId, dryRun) |
Restore files to previous state (requires withFileCheckpointing) |
setMcpServers(servers) |
Dynamically configure MCP servers |
mcpServerStatus() |
Get MCP server connection status |
supportedModels() |
Get list of supported models |
accountInfo() |
Get account information |
import com.tjclp.scalagent.*
import zio.json.*
case class WeatherInput(location: String, unit: Option[String]) derives JsonDecoder
object WeatherInput:
given ToolInput[WeatherInput] = ToolInput.derive[WeatherInput]
val weatherTool = ToolDef.fromInput[WeatherInput](
name = "get_weather",
description = "Get current weather for a location"
) { input =>
fetchWeather(input.location, input.unit.getOrElse("celsius")).map(ToolResult.Success(_))
}Tools can return arrays of content blocks (text, image, audio, resources), and you can emit custom error content when something fails:
val richTool = ToolDef.fromInput[WeatherInput](
name = "rich_content_demo",
description = "Return rich MCP content blocks"
) { _ =>
val pngBase64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/wIAAgMBAp9W8Z8AAAAASUVORK5CYII="
ToolResult.multi
.text("Here is rich content.")
.image(pngBase64, mime = "image/png")
.resourceLink("https://example.com/spec", description = Some("Spec link"))
.build
}
val errorTool = ToolDef.fromInput[WeatherInput](
name = "rich_error_demo",
description = "Return rich error content"
) { _ =>
ToolResult.errorContents(
ToolContent.Text("Something went wrong."),
ToolContent.ResourceLink("https://example.com/help")
)
}Prefer the macro-based style when you want minimal boilerplate and inline parameter docs.
Return types can be ToolResult, String, Task[ToolResult], or Task[String] (strings are wrapped as ToolResult.text).
import com.tjclp.scalagent.*
import zio.*
object MyTools:
enum Unit:
case Celsius, Fahrenheit
@Tool("get_weather", "Get the current weather for a location")
def getWeather(
@Param("City or location name") location: String,
@Param("Temperature unit") unit: Option[Unit] = None
): String =
val u = unit.getOrElse(Unit.Celsius)
s"Weather in $location: 22°${u.toString.take(1)}"
// One-liner server creation from annotated object
val server = ToolMacros.createServer[MyTools.type]("macro-tools", runtime)┌─────────────────────────────────────────┐
│ User Application │
├─────────────────────────────────────────┤
│ Idiomatic Scala API (ZIO) │
│ - ClaudeAgent service │
│ - ZStream[AgentMessage] streaming │
│ - Sealed trait message ADT │
│ - Type-safe config builders │
├─────────────────────────────────────────┤
│ ScalablyTyped Raw Facades │
│ - js.Promise, js.UndefOr, native types │
├─────────────────────────────────────────┤
│ @anthropic-ai/claude-agent-sdk │
└─────────────────────────────────────────┘
scalagent/
├── build.mill # Mill build configuration
├── package.json # NPM dependencies
├── src/
│ └── com/tjclp/scalagent/
│ ├── messages/ # Message ADT
│ ├── config/ # Configuration types
│ ├── macros/ # Compile-time schema derivation
│ ├── streaming/ # AsyncGenerator → ZStream
│ ├── tools/ # Tool DSL skeleton
│ └── ClaudeAgent.scala # Main ZIO service
└── examples/
└── SimpleQuery.scala # Example applications
MIT