This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
sttp-ai is a Scala library providing a non-official client wrapper for OpenAI, Claude (Anthropic), and OpenAI-compatible APIs. Built on sttp HTTP client with support for sync/async operations and various effect systems (cats-effect, ZIO, Akka/Pekko Streams, Ox).
Key Features:
- Native OpenAI API support (Chat, Completions, Embeddings, Audio, Images, etc.)
- Native Claude (Anthropic) API support with dedicated module
- Native Gemini (Google) API support via the Interactions API with dedicated module
- OpenAI-compatible API support (Ollama, Grok, OpenRouter, etc.)
- Streaming support for all major effect systems
- Cross-platform: Scala 2.13.18 and Scala 3.3.8
- Agent loop tools loadable from MCP servers (
mcpmodule, Scala 3 only, via chimp), in addition to manually definedAgentTools
# Compile
sbt compile # All modules
sbt openai/compile # OpenAI module
sbt claude/compile # Claude module
sbt gemini/compile # Gemini module
sbt mcp3/compile # MCP module (Scala 3 only; sbt-projectmatrix suffixes the Scala 3 row with "3")
# Test
sbt test # Unit tests (excludes integration)
sbt "testOnly *OpenAIIntegrationSpec" # OpenAI integration (requires OPENAI_API_KEY)
sbt "testOnly *ClaudeIntegrationSpec" # Claude integration (requires ANTHROPIC_API_KEY)
sbt "testOnly *GeminiIntegrationSpec" # Gemini integration (requires GEMINI_API_KEY)
./run-integration-tests.sh # All integration tests
# Format (CRITICAL - always run after changes!)
sbt scalafmtAll # Format all code
sbt scalafmtCheck # Verify formatting
sbt Test/scalafmtCheck # Verify test formatting
# Documentation
sbt compileDocumentation # Compile mdoc documentationIf jetbrains MCP is available, USE mcp__jetbrains__reformat_file tool instead of running sbt scalafmtAll command.
# Update OpenAI model definitions (automated workflow)
scala-cli model_update_scripts/scrape_models.scala # 1. Scrape models
scala-cli model_update_scripts/update_code_with_new_models.scala --apply # 2. Update code
sbt scalafmtAll # 3. FormatCore Differences:
| Aspect | OpenAI (openai/) |
Claude (claude/) |
Gemini (gemini/) |
|---|---|---|---|
| Client | OpenAI / OpenAISyncClient |
ClaudeClient / ClaudeSyncClient |
GeminiClient / GeminiSyncClient |
| Return Type | Either[OpenAIException, A] |
Either[ClaudeException, A] |
Either[GeminiException, A] |
| Message Content | Simple strings | ContentBlock arrays (rich content) |
Step/Content arrays |
| System Messages | Role-based in messages array | Separate system parameter |
Separate system_instruction parameter |
| Authentication | Authorization: Bearer <key> |
x-api-key: <key> + anthropic-version |
x-goog-api-key: <key> |
| Package Structure | sttp.ai.openai.* |
sttp.ai.claude.* |
sttp.ai.gemini.* |
Shared Patterns:
- All use circe with snake_case configuration for JSON
- All have comprehensive exception hierarchies for API errors
- All support streaming via SSE with same effect systems
Each streaming module (streaming/{effect-system}/) provides extensions for all three APIs:
| Effect System | Module Location | OpenAI Extension | Claude Extension | Gemini Extension | Scala Version |
|---|---|---|---|---|---|
| fs2 | streaming/fs2/ |
sttp.ai.openai.streaming.fs2.* |
sttp.ai.claude.streaming.fs2.* |
sttp.ai.gemini.streaming.fs2.* |
2.13, 3 |
| zio | streaming/zio/ |
sttp.ai.openai.streaming.zio.* |
sttp.ai.claude.streaming.zio.* |
sttp.ai.gemini.streaming.zio.* |
2.13, 3 |
| akka | streaming/akka/ |
sttp.ai.openai.streaming.akka.* |
sttp.ai.claude.streaming.akka.* |
sttp.ai.gemini.streaming.akka.* |
2.13 only |
| pekko | streaming/pekko/ |
sttp.ai.openai.streaming.pekko.* |
sttp.ai.claude.streaming.pekko.* |
sttp.ai.gemini.streaming.pekko.* |
2.13, 3 |
| ox | streaming/ox/ |
sttp.ai.openai.streaming.ox.* |
sttp.ai.claude.streaming.ox.* |
sttp.ai.gemini.streaming.ox.* |
3 only |
Pattern: Extension methods add createStreamedChatCompletion (OpenAI) / createStreamedMessage (Claude) / createStreamedInteraction (Gemini), each returning a stream of parsed SSE events for the given effect system.
- OpenAI API endpoints:
openai/src/main/scala/sttp/ai/openai/requests/{api-category}/ - Claude API code:
claude/src/main/scala/sttp/ai/claude/ - Gemini API code:
gemini/src/main/scala/sttp/ai/gemini/ - OpenAI models: Search for
ChatCompletionModel,EmbeddingModelinopenai/request bodies - Claude models:
claude/src/main/scala/sttp/ai/claude/models/ClaudeModel.scala - Gemini models:
gemini/src/main/scala/sttp/ai/gemini/models/GeminiModel.scala - Streaming implementations:
streaming/{effect-system}/src/main/scala/ - MCP tool loading:
mcp/src/main/scala/sttp/ai/core/agent/mcp/McpTools.scala(Scala 3 only; depends oncoreand chimp'schimp-client, not onopenai/claude) - Examples:
examples/src/main/scala/examples/(runnable with scala-cli) - Tests: Each module has
{module}/src/test/following same package structure
Request package mirrors OpenAI API structure: requests/{api-category}/ contains endpoint-specific request/response models.
sbt scalafmtAll or mcp__jetbrains__reformat_file after EVERY code change!
When to format:
- After creating/modifying files
- After implementing features
- After writing/modifying tests
- Before committing changes
Why this is critical:
- CI/CD fails without proper formatting
- Prevents merge conflicts
- Required before PR merge
- Formatting: Scalafmt with max column 140, Scala 3 dialect
- Imports:
- AVOID
import _root_.xxxx.yyyy, USEimport xxxx.yyyy - Scala 3 syntax preferred:
import package.*(notimport package._) - SortImports rule applied, RedundantBraces/Parens removed
- AVOID
- Naming: Snake_case for JSON fields (handled by the shared circe snake_case configuration)
- Models: Case objects extending sealed traits, companion values for easy access
- Documentation: Always use Scala 3 syntax (
@main,given,import package.*)
- Unit tests:
*/src/test/for all modules - Integration tests: Hit real APIs, cost-efficient (minimal inputs)
- OpenAI: Requires
OPENAI_API_KEY, 30s timeouts, rate limiting handled - Claude: Requires
ANTHROPIC_API_KEY, minimal token usage - Gemini: Requires
GEMINI_API_KEY, minimal token usage - Auto-skip if API key not set
- OpenAI: Requires
- Cross-building: sbt-projectmatrix for Scala 2.13.18 & 3.3.8
- Sync Clients:
OpenAISyncClient/ClaudeSyncClient/GeminiSyncClient- UseDefaultSyncBackend, block on responses, may throw exceptions - Async Clients:
OpenAI/ClaudeClient/GeminiClient- Raw sttp requests, choose backend (cats-effect, ZIO, etc.) - Custom Backends: Pass backend to
.send(backend) - OpenAI-Compatible: Use
OpenAIclient with custom base URL for Ollama, Grok, OpenRouter, etc.
Scratch files are powerful debugging tools using scala-cli for rapid prototyping without full sbt builds.
Ideal for:
- JSON serialization debugging (test circe behavior)
- API request validation (verify structures before integration tests)
- Library behavior testing (test specific features/edge cases)
- Hypothesis validation (confirm assumptions)
- Cost-effective debugging (test locally before hitting paid APIs)
// debug_serialization.sc - Test JSON output
//> using dep com.softwaremill.sttp.ai::claude:0.3.10+SNAPSHOT
import sttp.ai.claude.json.ClaudeDerivedCodecs.*
import io.circe.syntax.*
val obj = MyModel(...)
println(obj.asJson.noSpaces) // Check JSON structureNaming:
debug_*.sc- Debugging specific issuestest_*.sc- Testing functionalityvalidate_*.sc- Validation/verification
Dependencies:
- Use explicit versions:
//> using dep group::artifact:version - Use SNAPSHOT for unreleased:
//> using repository ivy2Local
Cleanup:
- Remove scratch files after debugging
- Never commit .sc files to repository
- Document findings in code comments/issues
Benefits:
- Compile/run in seconds vs. minutes for sbt
- Test JSON locally before hitting paid APIs
- Isolate issues without dependency complexity
For every implementation phase:
- Write/modify code
- Run
sbt scalafmtAll(CRITICAL - never skip!) - Run
sbt scalafmtCheckandsbt Test/scalafmtCheck - Run
sbt compile - Run relevant tests
- Commit changes
- Do what has been asked; nothing more, nothing less
- ALWAYS prefer editing existing files to creating new ones
- NEVER proactively create documentation (*.md) or README files unless explicitly requested
- ALWAYS use Scala 3 syntax in docs, examples, and README
- Format code after EVERY change - think of it as part of "save"
- GitHub Actions: SoftwareMill shared workflows
- Java 21: Build/test environment
- Scala Steward: Automated dependency updates
- Auto-merge: For dependency PRs from softwaremill-ci
- Publishing: Automatic releases on version tags
- Examples: See
examples/directory for runnable scala-cli examples (OpenAI and Claude) - Integration Testing: See
INTEGRATION_TESTING.mdfor detailed API setup - MCP tools: See
docs/agents/mcp.mdfor loading agent tools from MCP servers - API Documentation: