{"id":51572059,"url":"https://github.com/jsavyasachi/anthropic-clj","last_synced_at":"2026-07-10T20:04:15.869Z","repository":{"id":364734123,"uuid":"1269017818","full_name":"jsavyasachi/anthropic-clj","owner":"jsavyasachi","description":"Idiomatic Clojure wrapper over the official Anthropic Java SDK - messages, streaming, tools, batches, files, structured output","archived":false,"fork":false,"pushed_at":"2026-07-02T17:17:38.000Z","size":122,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-02T19:17:24.445Z","etag":null,"topics":["ai","anthropic","anthropic-api","claude","claude-api","clojure","clojure-library","llm","sdk"],"latest_commit_sha":null,"homepage":"https://cljdoc.org/d/net.clojars.savya/anthropic-clj","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"epl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jsavyasachi.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-14T07:54:01.000Z","updated_at":"2026-07-02T17:17:56.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/jsavyasachi/anthropic-clj","commit_stats":null,"previous_names":["jsavyasachi/anthropic-clj"],"tags_count":14,"template":false,"template_full_name":null,"purl":"pkg:github/jsavyasachi/anthropic-clj","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jsavyasachi%2Fanthropic-clj","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jsavyasachi%2Fanthropic-clj/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jsavyasachi%2Fanthropic-clj/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jsavyasachi%2Fanthropic-clj/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jsavyasachi","download_url":"https://codeload.github.com/jsavyasachi/anthropic-clj/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jsavyasachi%2Fanthropic-clj/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35341775,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-10T02:00:06.465Z","response_time":60,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai","anthropic","anthropic-api","claude","claude-api","clojure","clojure-library","llm","sdk"],"created_at":"2026-07-10T20:04:15.023Z","updated_at":"2026-07-10T20:04:15.862Z","avatar_url":"https://github.com/jsavyasachi.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# anthropic-clj\n\n[![Clojars Project](https://img.shields.io/clojars/v/net.clojars.savya/anthropic-clj.svg)](https://clojars.org/net.clojars.savya/anthropic-clj)\n[![cljdoc](https://cljdoc.org/badge/net.clojars.savya/anthropic-clj)](https://cljdoc.org/d/net.clojars.savya/anthropic-clj)\n[![test](https://github.com/jsavyasachi/anthropic-clj/actions/workflows/test.yml/badge.svg)](https://github.com/jsavyasachi/anthropic-clj/actions/workflows/test.yml)\n\nAn idiomatic Clojure wrapper over the **official** Anthropic Java SDK\n([`com.anthropic/anthropic-java`](https://github.com/anthropics/anthropic-sdk-java)).\nBuild a request as a Clojure map, get a Clojure map back.\n\n\u003e **Unofficial.** A community library, not affiliated with or endorsed by\n\u003e Anthropic. It wraps Anthropic's official Java SDK; it is not itself an official\n\u003e Anthropic SDK.\n\n## Stack\n\n\u003ca href=\"https://clojure.org\"\u003e\u003cimg src=\"https://img.shields.io/badge/Clojure-5881D8?style=flat\u0026logo=clojure\u0026logoColor=fff\" alt=\"Clojure\" /\u003e\u003c/a\u003e\n\u003ca href=\"https://docs.anthropic.com\"\u003e\u003cimg src=\"https://img.shields.io/badge/Anthropic-D97757?style=flat\u0026logo=anthropic\u0026logoColor=fff\" alt=\"Anthropic\" /\u003e\u003c/a\u003e\n\u003ca href=\"https://github.com/metosin/jsonista\"\u003e\u003cimg src=\"https://img.shields.io/badge/jsonista-2D3748?style=flat\u0026logo=clojure\u0026logoColor=fff\" alt=\"jsonista\" /\u003e\u003c/a\u003e\n\n## Why\n\nEvery other Clojure Anthropic library hand-rolls HTTP against the REST API, which\nmeans each one is perpetually chasing Anthropic's surface and tends to fall\nbehind. This one wraps Anthropic's own actively-maintained Java SDK instead, so\nstreaming, tool use, retries, and new model ids stay close to Anthropic's Java\nsurface. The wrapper intentionally exposes a Clojure-shaped subset: maps in,\nmaps out, keywords for roles and block types.\n\n## Installation\n\nLeiningen (`project.clj`):\n\n```clojure\n[net.clojars.savya/anthropic-clj \"0.11.1\"]\n```\n\ntools.deps (`deps.edn`):\n\n```clojure\nnet.clojars.savya/anthropic-clj {:mvn/version \"0.11.1\"}\n```\n\nSet `ANTHROPIC_API_KEY` in your environment, or pass client options:\n`:api-key`, `:auth-token`, `:base-url`, `:timeout-ms`, `:max-retries`.\n\nTracks [`com.anthropic/anthropic-java` 2.48.0](https://github.com/anthropics/anthropic-sdk-java/releases/tag/v2.48.0) - see `CHANGELOG.md` for the bump history.\n\n## Usage\n\n```clojure\n(require '[anthropic.core :as anthropic])\n\n(def client (anthropic/client))   ; reads ANTHROPIC_API_KEY\n\n;; A single message. :model defaults to \"claude-opus-4-8\", :max-tokens to 1024.\n(anthropic/create-message\n  client\n  {:model \"claude-opus-4-8\"\n   :max-tokens 1024\n   :system \"You are concise.\"\n   :messages [{:role :user :content \"Name three primary colors.\"}]})\n;; =\u003e {:id \"msg_...\" :model \"claude-opus-4-8\" :role :assistant\n;;     :stop-reason :end-turn\n;;     :content [{:type :text :text \"Red, blue, yellow.\"}]\n;;     :usage {:input-tokens 18 :output-tokens 9}}\n```\n\n`create-message` also accepts the optional controls `:temperature`, `:top-p`,\n`:top-k`, `:stop-sequences`, `:tool-choice` (`:auto`/`:any`/`:none` or\n`{:type :tool :name \"x\"}`), `:thinking` (`{:type :enabled :budget-tokens N}`,\n`{:type :adaptive}`, or `{:type :disabled}`), `:metadata` (`{:user-id \"...\"}`),\n`:service-tier` (`:auto`/`:standard-only`), `:container`, `:inference-geo`,\n`:user-profile-id`, and top-level `:cache-control`. For structured output, pass\n`:response-format` and/or `:effort`. Responses include newer `:usage` fields\nwhen present, including cache creation/read tokens, server-tool usage,\nservice-tier, inference geo, cache creation details, and output-token details.\n\n### Images, PDFs, and prompt caching\n\nMessage content can be a vector of blocks. Beyond `:text`, `:tool-use`, and\n`:tool-result`, you can send `:image`, `:document`, `:search-result`,\n`:thinking`, `:redacted-thinking`, and `:container-upload` blocks. Blocks that\nsupport prompt caching accept `:cache-control`.\n\n```clojure\n(anthropic/create-message\n  client\n  {:max-tokens 256\n   :messages [{:role :user\n               :content [{:type :image\n                          :source {:type :base64 :media-type \"image/png\" :data \"\u003cbase64\u003e\"}}\n                         ;; or {:type :url :url \"https://…/photo.jpg\"}\n                         {:type :document\n                          :source {:type :url :url \"https://…/paper.pdf\"}\n                          :title \"Paper\"}\n                         {:type :search-result\n                          :source \"https://example.com/result\"\n                          :title \"Result\"\n                          :citations true\n                          :content [{:type :text :text \"Relevant excerpt\"}]}\n                         {:type :text :text \"Summarize the paper and the image.\"\n                          :cache-control true}]}]})  ; :cache-control {:ttl :1h} for 1-hour\n```\n\nAssistant turns that contained thinking can be round-tripped with\n`{:type :thinking :thinking \"...\" :signature \"...\"}` or\n`{:type :redacted-thinking :data \"...\"}`. Container uploads use\n`{:type :container-upload :file-id \"file_...\"}`.\n\n### Server-side tools\n\nEnable Anthropic-hosted tools by `:type` (latest version of each is used). The\nmodel runs them server-side; the response content carries `:server-tool-use`\nblocks and typed result blocks (`:web-search-result`, `:code-execution-result`,\n…).\n\n```clojure\n(anthropic/create-message\n  client\n  {:max-tokens 1024\n   :tools [{:type :web-search :max-uses 3\n            :allowed-domains [\"clojure.org\"]        ; or :blocked-domains\n            :user-location {:city \"Paris\" :country \"FR\"}\n            :allowed-callers [:direct]}             ; some models need :direct\n           {:type :web-fetch :max-content-tokens 4096}\n           {:type :code-execution}\n           {:type :bash}\n           {:type :text-editor :max-characters 2000}\n           {:type :memory}\n           {:type :tool-search :variant :bm25}   ; or :regex\n           {:type :tool-search :variant :regex\n            :defer-loading true :strict true\n            :allowed-callers [:direct]}]\n   :messages [{:role :user :content \"Search the web for today's Clojure news.\"}]})\n```\n\n### Structured output\n\nPass `:response-format` (a JSON Schema map) to get a `:parsed` Clojure map back.\nObject schemas must set `\"additionalProperties\": false` (an API requirement).\n`:effort` (`:low`…`:max`) is accepted alongside or on its own.\n\n```clojure\n(anthropic/create-message\n  client\n  {:max-tokens 256\n   :response-format {:type \"object\"\n                     :properties {:capital {:type \"string\"}}\n                     :required [\"capital\"]\n                     :additionalProperties false}\n   :messages [{:role :user :content \"What is the capital of France?\"}]})\n;; =\u003e {... :content [{:type :text :text \"{\\\"capital\\\":\\\"Paris\\\"}\"}]\n;;     :parsed {:capital \"Paris\"}}\n```\n\n### Counting tokens\n\n`count-tokens` takes the same request map and returns the input-token count\nwithout sending the message (`:max-tokens` and sampling params are ignored).\n\n```clojure\n(anthropic/count-tokens\n  client\n  {:messages [{:role :user :content \"How many tokens is this?\"}]})\n;; =\u003e {:input-tokens 13}\n```\n\n### Models\n\n```clojure\n(anthropic/list-models client)\n;; =\u003e [{:id \"claude-opus-4-8\" :display-name \"Claude Opus 4.8\"\n;;      :created-at \"2026-...\" :max-tokens 64000} ...]\n\n(anthropic/get-model client \"claude-opus-4-8\")\n;; =\u003e {:id \"claude-opus-4-8\" :display-name \"Claude Opus 4.8\" ...}\n```\n\n### Files (beta)\n\n```clojure\n(def f (anthropic/upload-file client \"paper.pdf\"))   ; path/File/Path/InputStream/bytes\n;; =\u003e {:id \"file_...\" :filename \"paper.pdf\" :mime-type \"application/pdf\"\n;;     :size-bytes 12345 :created-at \"2026-...\"}\n\n(anthropic/get-file client (:id f))\n(anthropic/list-files client)\n(anthropic/download-file client some-id)   ; bytes; only API-generated downloadable files\n(anthropic/delete-file client (:id f))\n```\n\n### Message Batches\n\nSubmit many requests at the 50%-cost batch tier. Each request is\n`{:custom-id \"...\" :params \u003csame map as create-message\u003e}`.\n\n```clojure\n(def batch\n  (anthropic/create-batch\n    client\n    [{:custom-id \"a\" :params {:max-tokens 64 :messages [{:role :user :content \"Hi\"}]}}\n     {:custom-id \"b\" :params {:max-tokens 64 :messages [{:role :user :content \"Bye\"}]}}]))\n;; =\u003e {:id \"msgbatch_...\" :processing-status :in-progress\n;;     :request-counts {:processing 2 :succeeded 0 ...} ...}\n\n(anthropic/get-batch client (:id batch))     ; poll until :processing-status :ended\n(anthropic/list-batches client)\n(anthropic/cancel-batch client (:id batch))\n\n;; Once ended, pull results (succeeded entries carry the parsed :message):\n(anthropic/batch-results client (:id batch))\n;; =\u003e [{:custom-id \"a\" :result {:type :succeeded :message {...}}} ...]\n\n;; Streaming reduction for large result sets:\n(anthropic/reduce-batch-results client (:id batch)\n  (fn [acc result] (conj acc (:custom-id result)))\n  [])\n```\n\n### Streaming\n\n`stream-text` calls your callback with each text delta and returns the full text.\n\n```clojure\n(anthropic/stream-text\n  client\n  {:model \"claude-opus-4-8\" :max-tokens 256\n   :messages [{:role :user :content \"Write a haiku about parentheses.\"}]}\n  #(print %))   ; prints each delta as it arrives\n;; =\u003e returns the complete string when the stream ends\n```\n\nFor thinking or tool-use streams, `stream` surfaces every normalized event. Each\nevent is a map keyed by `:type`: `:message-start`, `:content-block-start`\n(`:index`, `:block`), `:text-delta`/`:thinking-delta`/`:input-json-delta`/\n`:signature-delta` (`:index` plus payload), `:content-block-stop` (`:index`),\n`:message-delta` (`:stop-reason`), and `:message-stop`. It still returns the full\ntext. To rebuild a streamed tool call, accumulate `:input-json-delta`\n`:partial-json` per `:index` - the matching `:content-block-start` carries the\ntool `:id`/`:name`.\n\n```clojure\n(anthropic/stream\n  client\n  {:max-tokens 256 :messages [{:role :user :content \"Think, then answer.\"}]}\n  (fn [ev]\n    (case (:type ev)\n      :thinking-delta (print \"[thinking]\" (:thinking ev))\n      :text-delta     (print (:text ev))\n      nil)))\n```\n\n### Tool use\n\nDeclare tools as maps; `tool_use` blocks come back parsed, and you complete the\nloop by echoing the assistant turn and sending a `:tool-result` block.\n\n```clojure\n(def weather-tool\n  {:name \"get_weather\"\n   :description \"Get the current weather for a city\"\n   :input-schema {:type \"object\"\n                  :properties {:city {:type \"string\"}}\n                  :required [\"city\"]}})\n\n(def ask {:role :user :content \"What's the weather in Paris?\"})\n\n(def r1 (anthropic/create-message\n          client {:tools [weather-tool] :messages [ask]}))\n\n;; r1 :stop-reason is :tool-use; find the call:\n(def call (first (filter #(= :tool-use (:type %)) (:content r1))))\n;; =\u003e {:type :tool-use :id \"toolu_...\" :name \"get_weather\" :input {:city \"Paris\"}}\n\n;; Run the tool yourself, then send the result back:\n(anthropic/create-message\n  client\n  {:tools [weather-tool]\n   :messages [ask\n              {:role :assistant :content (:content r1)}\n              {:role :user :content [{:type :tool-result\n                                      :tool-use-id (:id call)\n                                      :content \"18°C and sunny\"}]}]})\n;; =\u003e {... :content [{:type :text :text \"It's 18°C and sunny in Paris.\"}]}\n```\n\nOr let `run-tools` drive that loop: give each tool a `:fn` (a function of the\nparsed `:input` map) and it keeps calling `create-message`, executing every\nrequested tool call (parallel calls included) and feeding results back, until\nthe model stops asking for tools.\n\n```clojure\n(anthropic/run-tools\n  client\n  {:messages [ask]\n   :tools [(assoc weather-tool\n                  :fn (fn [{:keys [city]}] (fetch-weather city)))]}\n  {:max-iterations 5              ; create-message calls; default 10, exceeding throws\n   :on-message println})          ; optional: observe each API response\n;; =\u003e the final response map, plus :messages - the full accumulated\n;;    conversation, ready to continue from\n```\n\nA tool `:fn` that throws sends the exception message back as an `:is-error`\ntool result instead of aborting, so the model can recover. String returns are\nsent as-is; any other value is JSON-encoded. `:fn` is stripped before every\nAPI call.\n\n## What's covered\n\n- `create-message` - request map ↔ response map, with the full set of request\n  controls (sampling, stop sequences, tool-choice, thinking, metadata,\n  service-tier), cache-token usage, and **structured output** (`:response-format`\n  → `:parsed`, plus `:effort`)\n- **Content blocks** - text, `tool_use`/`tool_result`, images (base64/url),\n  documents/PDFs (base64/url/text), search results, thinking/redacted-thinking\n  round-trips, container uploads, and `:cache-control` breakpoints where supported\n- **Tools** - custom tools, plus **server-side tools** (web search, web fetch,\n  code execution, bash, text editor, memory, tool-search bm25/regex), with\n  `:server-tool-use` and typed result blocks parsed back out\n- **Citations** - text blocks carry `:citations` (char/page/content-block/\n  web-search/search-result locations) when present\n- `count-tokens` - input-token count without sending\n- `stream-text` - incremental text deltas\n- `stream` - every normalized stream event (message + content-block lifecycle,\n  text/thinking/tool-use/signature deltas)\n- `list-models` / `get-model` - Models API\n- Message Batches - `create-batch`, `get-batch`, `list-batches`, `cancel-batch`,\n  `delete-batch`, `batch-results`, `reduce-batch-results`\n- Files (beta) - `upload-file`, `get-file`, `list-files`, `download-file`, `delete-file`\n\nWrapped surfaces: Messages, streaming, tool use including server tools, Message\nBatches, Files beta, Models, and count-tokens - plus the beta agents platform\nin `anthropic.beta` (see below). For anything not wrapped, reach for the\n[Java SDK](https://github.com/anthropics/anthropic-sdk-java) directly.\n\n## Errors\n\nAll failures throw `ex-info` keyed `:anthropic/error` in `ex-data`:\n\n- Request-shaping errors (bad tool spec, missing key) throw before any network\n  call, with an error keyword describing the problem.\n- API failures carry `{:anthropic/error :api-error :status \u003chttp status\u003e\n  :error-type \u003ckw\u003e}` where `:error-type` is one of `:bad-request`,\n  `:unauthorized`, `:permission-denied`, `:not-found`,\n  `:unprocessable-entity`, `:rate-limit`, `:internal-server`, or\n  `:unexpected-status`. The original SDK exception is preserved as\n  `(ex-cause e)`.\n- Network/IO failures carry `{:anthropic/error :io-error}`, original exception\n  as cause.\n\nOther SDK exceptions (e.g. `AnthropicInvalidDataException`) propagate\nunchanged.\n\n## Beta agents platform\n\n`anthropic.beta` wraps the beta agents-platform APIs - skills (and skill\nversions), memory stores (and memories), agents, sessions (events and\nthreads), deployments (and runs), environments, vaults, user profiles, and\nwebhook payload parsing - with the same maps-in/maps-out shape and the same\nerror contract as `anthropic.core`:\n\n```clojure\n(require '[anthropic.beta :as beta])\n\n(beta/create-skill client {:display-title \"Summarizer\" :files [\"SKILL.md\"]})\n(beta/list-skill-versions client \"skill_123\")\n(beta/download-skill-version client \"skill_123\" \"2\") ;; =\u003e byte[]\n\n(beta/create-memory-store client {:name \"notes\" :description \"team notes\"})\n(beta/create-memory client \"ms_123\" {:path \"/notes/prefs\"\n                                     :content \"prefers tables\"})\n\n(def agent (beta/create-agent\n            client\n            {:name \"helper\"\n             :model \"claude-opus-4-8\"\n             :system \"be helpful\"\n             :skills [{:type :anthropic :skill-id \"skill_123\" :version \"2\"}]\n             :mcp-servers [{:name \"github\" :url \"https://mcp.example.test\"}]\n             :tools [{:type :mcp-toolset :mcp-server-name \"github\"}]}))\n(beta/update-agent client (:id agent) {:version (:version agent) :system \"new\"})\n\n(def session (beta/create-session client {:agent (:id agent) :title \"run 1\"}))\n(beta/send-session-events client (:id session)\n                          [{:type :user-message :content \"hello\"}])\n(beta/list-session-events client (:id session))\n(beta/list-session-threads client (:id session))\n\n(beta/create-environment client {:name \"prod\"})\n(beta/create-vault client {:display-name \"secrets\"})\n(beta/create-user-profile client {:name \"Ada\" :external-id \"u-1\"})\n(beta/create-enrollment-url client \"up_123\")\n\n(beta/create-deployment client {:name \"nightly\"\n                                :agent (:id agent)\n                                :environment-id \"env_123\"\n                                :initial-events [{:type :user-message\n                                                  :content \"run the report\"}]})\n(beta/run-deployment client \"deploy_123\")\n(beta/list-deployment-runs client)\n\n(beta/unwrap-webhook client payload) ;; parse a webhook payload string\n(beta/unwrap-webhook client payload {:headers headers :secret secret}) ;; verify\n```\n\nEach service also has `get-*`, `list-*`, and `archive-*`/`delete-*` variants.\nNot wrapped yet: vault credentials, environment work, memory versions, thread\nevents, session event streaming, session resources, and agent versions. These\nare beta endpoints - Anthropic may change them.\n\n## Bedrock and Vertex\n\nThe SDK ships separate backend artifacts,\n[`com.anthropic/anthropic-java-bedrock` and\n`com.anthropic/anthropic-java-vertex`](https://github.com/anthropics/anthropic-sdk-java#amazon-bedrock-and-google-vertex-ai),\nfor Amazon Bedrock and Google Vertex AI. `client` here builds the direct-API\nclient only, but every function takes the client as its first argument, so an\n`AnthropicClient` constructed from either backend artifact works with all of\nthem.\n\n## Tests\n\nUnit tests (the request/response translation) run with no network:\n\n```\nlein test\n```\n\nThe `:integration` suite hits the live API and is billed - it needs\n`ANTHROPIC_API_KEY` and is run explicitly:\n\n```\nANTHROPIC_API_KEY=sk-... lein test :integration\n```\n\n## License\n\nCopyright © 2026 Savyasachi\n\nDistributed under the [Eclipse Public License 2.0](https://www.eclipse.org/legal/epl-2.0/).\nThe wrapped `com.anthropic/anthropic-java` SDK is MIT-licensed and remains the\nproperty of Anthropic.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjsavyasachi%2Fanthropic-clj","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjsavyasachi%2Fanthropic-clj","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjsavyasachi%2Fanthropic-clj/lists"}