Skip to main content
Input & Output

Multi-turn Conversation

query() supports two input modes:
  • Single-message query mode: submit one user message; the SDK closes the session after the current reply completes. See the Quickstart.
  • Multi-message session mode: keep the session open and carry on a multi-turn conversation with the model.

Multi-message session

Define a sequence that yields user messages in order:
import { qodercliAuth, query, type SDKUserMessage } from '@qoder-ai/qoder-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);
  }
}
A user message normally starts a model response. Messages sent before that response finishes are handled at the time selected by priority. The session closes automatically when the input message stream ends. For message field definitions see SDKUserMessage.

Steering a response

The async input stream can continue sending SDKUserMessage objects while the model is responding.
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 the same priority run in send order. Use priority: 'now' to change direction immediately. To stop the current response without sending another message, use q.interrupt().

Add context without starting a response

shouldQuery: false adds the message to the conversation without starting a response by itself. Its processing time still follows priority.

Interrupting the current response

Call await q.interrupt() to stop the current response without closing the session. You can continue the conversation afterward.
import { qodercliAuth, query } from '@qoder-ai/qoder-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. Use cancelAsyncMessage() when a specific queued message must not run. See Managing the session lifecycle to end the entire session.

Cancel a queued message

Give each tracked message a session-unique uuid, then call q.cancelAsyncMessage(uuid) to cancel it before execution starts:
const cancelled = await q.cancelAsyncMessage(uuid);
The method returns true when the message is cancelled and false when the message is not found 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

The SDK closes the session automatically after a string input is handled or the input message stream ends. To end the session earlier, use an AbortController to define when it should end, or call q.close() directly.

Define when the session ends

To end the session when a custom business condition is met, create an AbortController and pass it through options.abortController when calling query(). Call abort() on the same controller when that condition is met. The condition can come from a user action, upstream request, task timeout, application shutdown, or other application logic:
import { qodercliAuth, query } from '@qoder-ai/qoder-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);
}
Calling abort() closes the entire session and ends message iteration. It cannot accept more messages afterward.

Close a session explicitly

When the current code no longer needs the session, call await q.close() directly. This works for normal completion, early exit, and error cleanup; placing it in finally ensures related resources finish closing:
import { qodercliAuth, query } from '@qoder-ai/qoder-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();
}
Using Qoder CLI
Configuration & Security
Reference
SDK