Conversation
JavaScript · package toolnexus · SPEC §8 · js/src/client.ts
client.conversation(ctx: { toolkit: Toolkit; signal?: AbortSignal }): Conversation
class Conversation { messages: any[] send(prompt: string): Promise<RunResult> reset(): void}An explicit handle for a multi-turn exchange. conversation() returns a Conversation holding a
running transcript (messages); each send() runs one turn via run with that transcript as
history, then updates messages with the result — so the next send() continues where the last
one left off, in-process, with no id or store involved.
When to use it
Section titled “When to use it”Use Conversation when the caller already owns the object across turns — a chat session object, a
REPL loop, a single request that does several back-and-forth exchanges with the model in one
process lifetime. You hold the Conversation; it holds the transcript.
Why this and not the alternative
Section titled “Why this and not the alternative”Examples
Section titled “Examples”1. The smallest useful call — two turns, one Conversation
Section titled “1. The smallest useful call — two turns, one Conversation”import assert from "node:assert"import http from "node:http"import { createClient, createToolkit } from "toolnexus"
// Replies with the number of messages it received — proves history accumulates.const server = http.createServer((req, res) => { let raw = "" req.on("data", (c) => (raw += c)) req.on("end", () => { const body = JSON.parse(raw) res.writeHead(200, { "content-type": "application/json" }) res.end(JSON.stringify({ choices: [{ message: { content: String(body.messages.length) }, finish_reason: "stop" }], usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 }, })) })})await new Promise<void>((r) => server.listen(0, "127.0.0.1", r))const port = (server.address() as any).port
const tk = await createToolkit({})const client = createClient({ baseUrl: `http://127.0.0.1:${port}`, style: "openai", model: "stub", apiKey: "test-key" })
const convo = client.conversation({ toolkit: tk })const first = await convo.send("My name is Muthu.")assert.equal(first.text, "1", "first turn is just the one user message")const second = await convo.send("What's my name?")assert.equal(second.text, "3", "second turn carries the prior user+assistant turns plus this one")
await tk.close()server.close()console.log("ok:", first.text, second.text)2. A realistic case — a REPL-style loop over several prompts
Section titled “2. A realistic case — a REPL-style loop over several prompts”import assert from "node:assert"import http from "node:http"import { createClient, createToolkit } from "toolnexus"
let n = 0const server = http.createServer((req, res) => { n++ res.writeHead(200, { "content-type": "application/json" }) res.end(JSON.stringify({ choices: [{ message: { content: `reply ${n}` }, finish_reason: "stop" }], usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 } }))})await new Promise<void>((r) => server.listen(0, "127.0.0.1", r))const port = (server.address() as any).port
const tk = await createToolkit({})const client = createClient({ baseUrl: `http://127.0.0.1:${port}`, style: "openai", model: "stub", apiKey: "test-key" })const convo = client.conversation({ toolkit: tk })
const prompts = ["step 1", "step 2", "step 3"]const answers: string[] = []for (const p of prompts) { const res = await convo.send(p) answers.push(res.text)}assert.deepEqual(answers, ["reply 1", "reply 2", "reply 3"])// the transcript grew by one user + one assistant turn per send()assert.equal(convo.messages.length, prompts.length * 2)
await tk.close()server.close()console.log("ok:", answers.join(","))3. The full surface — inspect messages directly, then reset
Section titled “3. The full surface — inspect messages directly, then reset”Conversation.messages is public — read it for debugging/logging — and reset() drops it back to
an empty transcript so the same handle can start a fresh thread without a new client.conversation() call.
import assert from "node:assert"import http from "node:http"import { createClient, createToolkit } from "toolnexus"
const server = http.createServer((req, res) => { let raw = "" req.on("data", (c) => (raw += c)) req.on("end", () => { const body = JSON.parse(raw) res.writeHead(200, { "content-type": "application/json" }) res.end(JSON.stringify({ choices: [{ message: { content: String(body.messages.length) }, finish_reason: "stop" }], usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 } })) })})await new Promise<void>((r) => server.listen(0, "127.0.0.1", r))const port = (server.address() as any).port
const tk = await createToolkit({})const client = createClient({ baseUrl: `http://127.0.0.1:${port}`, style: "openai", model: "stub", apiKey: "test-key", systemPrompt: "Be terse." })const convo = client.conversation({ toolkit: tk, signal: AbortSignal.timeout(5_000) })
await convo.send("first")assert.ok(convo.messages.some((m) => m.role === "system"), "system prompt not present in the transcript")assert.ok(convo.messages.length > 0)
convo.reset()assert.deepEqual(convo.messages, [], "reset() did not clear the transcript")
const afterReset = await convo.send("fresh start")assert.equal(afterReset.text, "2", "post-reset turn should look like a first turn again (system + user, no leftover history)")
await tk.close()server.close()console.log("ok:", afterReset.text)Fields
Section titled “Fields”| Member | Type | What it does |
|---|---|---|
messages |
any[] |
The running transcript — system + user + assistant + tool messages. Read directly. |
send(prompt) |
(prompt: string) => Promise<RunResult> |
Run the next turn; prior history is retained automatically. |
reset() |
() => void |
Drop the transcript back to empty — the next send() starts fresh. |
See also
Section titled “See also”createClient— The unified client: system prompt, skills injection, parallel and chained tool calls, retries, memory.Client.run— Send a prompt, let the loop call tools until the model stops, get a RunResult.Client.stream— The streaming loop: text deltas, tool-call events, and suspension events as they happen.Hooks— Intercept before/after model calls and tool calls: audit, redact, veto, or rewrite.