Liking cljdoc? Tell your friends :D

toolnexus.client


add-usageclj/s

(add-usage acc client raw)

Sum token usage across turns. OpenAI prompt/completion/total_tokens; Anthropic input_tokens -> prompt, output_tokens -> completion. Public because §11 translation reports the same Usage from the same provider payloads.

Sum token usage across turns. OpenAI prompt/completion/total_tokens;
Anthropic input_tokens -> prompt, output_tokens -> completion. Public because
§11 translation reports the same Usage from the same provider payloads.
sourceraw docstring

after-llm!clj/s

(after-llm! client response turn)

§8 afterLLM — observe only (logging, cost, tracing). :response is the provider's decoded payload, so it carries usage. Returns nil; a return value from an observer would be a silent contract nobody could rely on.

§8 `afterLLM` — observe only (logging, cost, tracing). `:response` is the
provider's decoded payload, so it carries `usage`. Returns nil; a return value
from an observer would be a silent contract nobody could rely on.
sourceraw docstring

askclj/s

(ask client prompt {:keys [toolkit id on-event] :as ctx})

run, remembering the conversation (§conversation-store) — the id-keyed sugar every other port ships as ask.

(ask client "and the second one?" {:toolkit tk :id "muthu"})

With an :id, the store's history for that id is loaded before the call and the full transcript saved after it, so the next ask with the same id continues the conversation. WITHOUT an id it is a stateless one-shot, identical to run — the same rule as js/src/client.ts, where ask with no id delegates straight to run.

This is deliberately nothing more than run with :conversation-id filled in: the memory mechanics live in ONE place, and a second copy of the load-then-save rule here is exactly the drift the ports exist to prevent.

`run`, remembering the conversation (§conversation-store) — the id-keyed
sugar every other port ships as `ask`.

    (ask client "and the second one?" {:toolkit tk :id "muthu"})

With an `:id`, the store's history for that id is loaded before the call and
the full transcript saved after it, so the next `ask` with the same id
continues the conversation. WITHOUT an id it is a stateless one-shot,
identical to `run` — the same rule as js/src/client.ts, where `ask` with no
id delegates straight to `run`.

This is deliberately nothing more than `run` with `:conversation-id` filled
in: the memory mechanics live in ONE place, and a second copy of the
load-then-save rule here is exactly the drift the ports exist to prevent.
sourceraw docstring

auth-requiredclj/s

(auth-required url)
(auth-required url prompt)

§10 sugar: a kind:"authorization" suspension at url.

§10 sugar: a `kind:"authorization"` suspension at `url`.
sourceraw docstring

before-llm!clj/s

(before-llm! client messages tools turn)

§8 beforeLLM, applied to ONE round trip. Returns [messages tools] — the hook's replacements when it returned them, the originals otherwise.

Public because §11 toolnexus.translate must fire this hook exactly once for its single call, and a second copy of the rule in that namespace is exactly the drift the ports exist to prevent.

Ordering (SPEC.md §8 Gap 1, and it IS the spec):

base body -> beforeLLM hook -> :request-params merge -> :body-transform -> marshal -> wire

so this runs BEFORE the body is assembled, and its tools replacement feeds the same empty-list omission (Gap 5) the toolkit's own list does.

§8 `beforeLLM`, applied to ONE round trip. Returns `[messages tools]` — the
hook's replacements when it returned them, the originals otherwise.

Public because §11 `toolnexus.translate` must fire this hook exactly once for
its single call, and a second copy of the rule in that namespace is exactly
the drift the ports exist to prevent.

Ordering (SPEC.md §8 Gap 1, and it IS the spec):

  base body -> beforeLLM hook -> :request-params merge -> :body-transform
            -> marshal -> wire

so this runs BEFORE the body is assembled, and its `tools` replacement feeds
the same empty-list omission (Gap 5) the toolkit's own list does.
sourceraw docstring

call-providerclj/s

(call-provider client body)

ONE provider round trip for a CALLER-BUILT body map, through exactly the endpoint, headers, §resilience-policy retry/backoff and llm metric the agent loop uses. §8's :request-params merge and :body-transform apply unchanged.

This is the seam §11 toolnexus.translate sits on: single-turn translation must lose neither resilience nor metrics, and the only way to guarantee that is to share the code rather than copy it.

ONE provider round trip for a CALLER-BUILT body map, through exactly the
endpoint, headers, §resilience-policy retry/backoff and `llm` metric the agent
loop uses. §8's `:request-params` merge and `:body-transform` apply unchanged.

This is the seam §11 `toolnexus.translate` sits on: single-turn translation
must lose neither resilience nor metrics, and the only way to guarantee that
is to share the code rather than copy it.
sourceraw docstring

create-clientclj/s

(create-client opts)

Build a client. Options (idiomatic kebab-case — these never hit the wire):

:base-url required, e.g. "https://api.anthropic.com" :style "openai" | "anthropic" (default "openai") :model required :api-key optional; falls back to OPENAI_API_KEY / ANTHROPIC_API_KEY / OPENROUTER_API_KEY :headers extra request headers :system-prompt prepended to the toolkit's skills prompt :max-turns default 10 :hooks §8 lifecycle middleware — a map of any of {:before-llm :after-llm :before-tool :after-tool}; see before-llm! / execute-tool. Absent => nothing changes. :wait-for (fn [request] answer) — the ONE host slot of §10.

On the name of that slot: §10 says "never await" because await is reserved in JS/Python/C#. In Clojure it is not reserved — it is clojure.core/await, and it exists on BOTH hosts, so (defn await ...) would shadow a core name and (per the spike brief) can make cljgo's static interop scan reject the WHOLE namespace. Same answer, sharper teeth.

Build a client. Options (idiomatic kebab-case — these never hit the wire):

  :base-url       required, e.g. "https://api.anthropic.com"
  :style          "openai" | "anthropic"   (default "openai")
  :model          required
  :api-key        optional; falls back to OPENAI_API_KEY / ANTHROPIC_API_KEY
                  / OPENROUTER_API_KEY
  :headers        extra request headers
  :system-prompt  prepended to the toolkit's skills prompt
  :max-turns      default 10
  :hooks          §8 lifecycle middleware — a map of any of
                  {:before-llm :after-llm :before-tool :after-tool}; see
                  `before-llm!` / `execute-tool`. Absent => nothing changes.
  :wait-for       (fn [request] answer) — the ONE host slot of §10.

On the name of that slot: §10 says "never `await`" because `await` is
reserved in JS/Python/C#. In Clojure it is not reserved — it is
`clojure.core/await`, and it exists on BOTH hosts, so `(defn await ...)`
would shadow a core name and (per the spike brief) can make cljgo's static
interop scan reject the WHOLE namespace. Same answer, sharper teeth.
sourceraw docstring

in-memory-storeclj/s

(in-memory-store)

§conversation-store — the shipped default: per-client, process lifetime. A store is exactly two operations, :get and :save, so a host can swap in a file or a database without this namespace knowing anything about it.

§conversation-store — the shipped default: per-client, process lifetime.
A store is exactly two operations, `:get` and `:save`, so a host can swap in
a file or a database without this namespace knowing anything about it.
sourceraw docstring

make-answerclj/s

(make-answer id ok)
(make-answer id ok data)
(make-answer id ok data reason)

§10 Answer. R1: reason is populated ONLY when ok == false; the loop rule branches on ok alone and never reads it.

§10 `Answer`. R1: `reason` is populated ONLY when ok == false; the loop rule
branches on `ok` alone and never reads it.
sourceraw docstring

make-requestclj/s

(make-request kind prompt)
(make-request kind prompt opts)

§10 Request. opts may carry :url, :data (incl. R2's data.schema) and :expiresAt (RFC3339).

§10 `Request`. `opts` may carry :url, :data (incl. R2's `data.schema`) and
:expiresAt (RFC3339).
sourceraw docstring

new-request-idclj/s

(new-request-id)

A unique correlation key for one suspension.

A unique correlation key for one suspension.
sourceraw docstring

pending-ofclj/s

(pending-of result)

The Request iff this ToolResult is a suspension, else nil.

The `Request` iff this ToolResult is a suspension, else nil.
sourceraw docstring

runclj/s

(run client prompt {:keys [toolkit history on-event conversation-id]})

Run the agent loop. ctx = {:toolkit tk :history [...] :on-event f}.

Returns a RunResult: {:text :messages :tool-calls :tool-call-count :turns :usage {:prompt-tokens :completion-tokens :total-tokens} :model :status ("done"|"pending"|"incomplete") :limit? :pending?}

:on-event is an optional synchronous sink for the §8 event vocabulary this non-streaming loop can honestly produce — tool_call, tool_result, pending (emitted BEFORE wait-for runs, so a channel handler can push the link in real time), usage, done. There are no text deltas here: this loop buffers the whole response, and faking deltas out of a buffered body would be a lie. Real SSE streaming is a separate seam (koine.stream/sse-post exists and is proven on both hosts) and is NOT implemented in this namespace.

Run the agent loop. `ctx` = {:toolkit tk :history [...] :on-event f}.

Returns a RunResult:
  {:text :messages :tool-calls :tool-call-count :turns
   :usage {:prompt-tokens :completion-tokens :total-tokens}
   :model :status ("done"|"pending"|"incomplete") :limit? :pending?}

`:on-event` is an optional synchronous sink for the §8 event vocabulary this
non-streaming loop can honestly produce — `tool_call`, `tool_result`,
`pending` (emitted BEFORE `wait-for` runs, so a channel handler can push the
link in real time), `usage`, `done`. There are no `text` deltas here: this
loop buffers the whole response, and faking deltas out of a buffered body
would be a lie. Real SSE streaming is a separate seam (`koine.stream/sse-post`
exists and is proven on both hosts) and is NOT implemented in this namespace.
sourceraw docstring

suspendclj/s

(suspend request)
(suspend request output)

A ToolResult whose metadata.pending is a Request IS a suspension (§0.12). execute's signature is untouched — suspension is data on the existing result, not a new return type.

A `ToolResult` whose `metadata.pending` is a `Request` IS a suspension
(§0.12). `execute`'s signature is untouched — suspension is data on the
existing result, not a new return type.
sourceraw docstring

zero-usageclj/s

The empty §8 Usage. Idiomatic kebab-case — Usage is a RETURN value, it never crosses the wire.

The empty §8 Usage. Idiomatic kebab-case — Usage is a RETURN value, it never
crosses the wire.
sourceraw docstring

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close