Skip to main content
Conversations & Sessions

Multi-turn Conversation

For one-off, stateless queries just use query(); see Quick Start. To send multiple user messages within the same session, the two SDKs differ:
  • TypeScript: pass query() an async message stream that yields user messages in order; the session closes automatically when the input stream ends.
  • Python: use QoderSDKClient—it maintains a long-lived connection and lets you decide the next message based on the model's replies.

Multi-message session

import { qodercliAuth, query, type SDKUserMessage } from '@qodercn-ai/qodercn-agent-sdk';

async function* messages(): AsyncGenerator<SDKUserMessage> {
  yield {
    type: 'user',
    message: { role: 'user', content: [{ type: 'text', text: 'Analyze this codebase for security issues.' }] },
    parent_tool_use_id: null,
  };

  // Wait for any external condition before sending the next message.
  await new Promise((resolve) => setTimeout(resolve, 2000));

  yield {
    type: 'user',
    message: { role: 'user', content: [{ type: 'text', text: 'Now write a brief report.' }] },
    parent_tool_use_id: null,
  };
}

for await (const msg of query({
  prompt: messages(),
  options: {
    auth: qodercliAuth(),
    allowedTools: ['Read', 'Grep'],
  },
})) {
  if (msg.type === 'result' && msg.subtype === 'success') {
    console.log(msg.result);
  }
}
Normally, the model starts responding after you send a user message. New messages sent before the response finishes are handled at the time specified by priority. In TypeScript, the session closes automatically when the input message stream ends; message field definitions are in SDKUserMessage. In Python, each client.query(...) call appends one turn of input, then consume client.receive_response() until the turn's response completes.

Steering a response

While the model is responding, you can keep sending new messages into the same session:
yield {
  type: 'user',
  message: { role: 'user', content: [{ type: 'text', text: 'Stop the current direction and analyze only the failing tests.' }] },
  parent_tool_use_id: null,
  priority: 'now',
};
priority controls when a message is delivered:
ValueBehavior
nowStop the current response and handle this message immediately
nextDefault; handle this message at the next suitable point
laterWait until the current response finishes
Messages with equal priority are processed in send order. priority now suits changing direction immediately; to stop the current response without sending a new message, use Interrupting the current response.

Add context without starting a response

Setting shouldQuery: false (TypeScript) / should_query=False (Python) adds the message to the conversation without triggering a response on its own. Its processing time is still governed by priority.
yield {
  type: 'user',
  message: { role: 'user', content: [{ type: 'text', text: 'All subsequent suggestions must be compatible with Python 3.10.' }] },
  parent_tool_use_id: null,
  shouldQuery: false,
};

Interrupting the current response

Calling interrupt() stops the current response without closing the session, so the conversation can continue afterwards. In TypeScript, call it on the object returned by query(); in Python, only QoderSDKClient offers runtime interruption—the one-shot query() iterator does not.
import { qodercliAuth, query } from '@qodercn-ai/qodercn-agent-sdk';

const q = query({
  prompt: 'Inspect all files in the repository.',
  options: { auth: qodercliAuth() },
});

const interruptTimer = setTimeout(() => {
  void q.interrupt().catch(console.error);
}, 5_000);

try {
  for await (const msg of q) {
    console.dir(msg, { depth: null });
  }
} finally {
  clearTimeout(interruptTimer);
}
interrupt() does not clear queued messages; you can still send the next turn afterwards. If a queued message should not proceed, cancel it instead. For ending the whole session, see Managing the session lifecycle.

Cancel a queued message

Give messages you need to track a session-unique UUID (TypeScript: the uuid field on the message; Python: the message_uuid parameter), then call the cancel method to cancel messages that have not started executing:
const cancelled = await q.cancelAsyncMessage(uuid);
Returns true on success; returns false if the message does not exist or can no longer be cancelled. Messages without a UUID cannot be cancelled this way. Do not reuse UUIDs within a session.

Managing the session lifecycle

  • TypeScript: after a single string input finishes or the input message stream ends, the SDK closes the session automatically; to end early, use an AbortController or call q.close() directly.
  • Python: the QoderSDKClient connection lifecycle is owned by the caller; prefer async with for automatic management, or use connect() / disconnect() manually.

Automatic cleanup bound to a code block

Suited to sessions whose lifecycle is tied to a function or code block. TypeScript puts close() in finally to guarantee resources are released; Python disconnects automatically when the async with block exits:
import { qodercliAuth, query } from '@qodercn-ai/qodercn-agent-sdk';

const q = query({
  prompt: 'Inspect all files in the repository.',
  options: { auth: qodercliAuth() },
});

try {
  for await (const msg of q) {
    console.dir(msg, { depth: null });
  }
} finally {
  await q.close();
}

Ending on an external condition

Suited to closing the session on external conditions (timeout, user cancellation, app exit, etc.). TypeScript creates an AbortController, passes it via options.abortController, and calls abort() when the condition fires—closing the whole session and ending message iteration; Python holds the client manually and calls disconnect() in finally:
import { qodercliAuth, query } from '@qodercn-ai/qodercn-agent-sdk';

const abortController = new AbortController();
const q = query({
  prompt: 'Inspect all files in the repository.',
  options: { auth: qodercliAuth(), abortController },
});

const taskTimeout = setTimeout(() => abortController.abort(), 5_000);
try {
  for await (const msg of q) {
    console.dir(msg, { depth: null });
  }
} finally {
  clearTimeout(taskTimeout);
}
After the session is closed, no more messages can be sent.