Skip to main content
Conversations & Sessions

Input Modes

Input mode determines one thing: whether your application can keep sending messages to the Agent after a task starts. It does not distinguish between text, images, or other message content, and it does not determine whether responses are displayed incrementally. With either input mode, the SDK returns the Agent's responses as a message stream. To process those responses, see Streaming Output. Qoder Agent SDK provides two input modes:
Input modeTypeScriptPythonWhen to use it
Single-message inputquery({ prompt: string })query(prompt=str)One-off tasks, batch jobs, and CI scripts
Streaming inputquery({ prompt: AsyncIterable<SDKUserMessage> })QoderSDKClient is recommended; a predetermined message stream can also be passed to query()Chat interfaces, multi-message sessions, and tasks that need additional or revised instructions while running
Choose between them by answering one question: will your application need to send more messages after the task starts?
  • No. Use single-message input.
  • Yes. For example, the user may follow up, or the application may choose the next step based on the Agent's response. Use streaming input.

Single-message input

Passing a string to query() starts an independent task. After the task starts, you cannot append another user message to that call. The SDK ends the session when the task completes.
import { accessTokenFromEnv, query } from '@qoder-ai/qoder-agent-sdk';

for await (const message of query({
  prompt: 'Find functions in the current project that lack test coverage and report them.',
  options: {
    auth: accessTokenFromEnv(),
    allowedTools: ['Read', 'Glob', 'Grep'],
  },
})) {
  if (message.type === 'result' && message.subtype === 'success') {
    console.log(message.result);
  }
}
Single-message input does not disable tool approval. If the Agent needs to run a tool that has not been authorized, canUseTool / can_use_tool can still ask the user. You do not need to turn prompt into a message stream for that purpose. See Approval and User Input.

Streaming input

Streaming input lets your application keep sending messages after a task starts. In addition to multi-turn chat, it can supply new information, redirect a running task, or queue a message for later. The recommended interface differs by SDK:
  • TypeScript: pass an AsyncIterable<SDKUserMessage> to query(). The async iterator can continue to yield user messages supplied by a chat UI, message queue, or another event source.
  • Python: use QoderSDKClient to keep the session open. Call client.query(...) to send a new message, then use client.receive_response() to receive that turn's response.

Multi-message session

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

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

  // A real application can wait here for a UI, message queue, or another event.
  await new Promise((resolve) => setTimeout(resolve, 2_000));

  yield {
    type: 'user',
    message: {
      role: 'user',
      content: [{ type: 'text', text: 'After the analysis, produce a brief report.' }],
    },
    parent_tool_use_id: null,
    priority: 'later',
  };
}

for await (const message of query({
  prompt: messages(),
  options: {
    auth: accessTokenFromEnv(),
    allowedTools: ['Read', 'Glob', 'Grep'],
  },
})) {
  if (message.type === 'result' && message.subtype === 'success') {
    console.log(message.result);
  }
}
For TypeScript message fields, see SDKUserMessage. In Python, each receive_response() call receives one turn. After receiving a ResultMessage, you can send the next message.

Python async message stream boundary

Python query() also accepts AsyncIterable[dict[str, Any]]. If every message is known before the task starts, you can send them sequentially this way. The following fragment only shows the core async-message-stream pattern; see the complete example above for the creation of query and options:
Python
async def prompts():
    yield {
        "type": "user",
        "message": {"role": "user", "content": "Inspect the authentication module."},
        "parent_tool_use_id": None,
    }
    yield {
        "type": "user",
        "message": {"role": "user", "content": "Then summarize the findings."},
        "parent_tool_use_id": None,
    }


async for message in query(prompt=prompts(), options=options):
    print(message)
Use this form only for messages prepared in advance. It cannot choose the next message from content the Agent has just returned, and it cannot call interrupt() or cancel a queued message. Interactive applications such as chat interfaces should use QoderSDKClient.

Add input while the Agent is running

You can send another message while the Agent is responding. priority determines when the Agent processes it:
ValueBehavior
nowStop the current response and process this message immediately
nextDefault; process the message at the next suitable opportunity
laterWait until the current response finishes
Messages with the same priority are processed in send order. Use priority: 'now' to redirect the current work immediately. If you only want to stop the current response without sending a new message, use Interrupt the current response. Place the following snippets inside the TypeScript message generator or connected Python client session shown above:
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',
};

Add context without triggering a response

Sometimes you only want to provide background information without asking the Agent to respond immediately. Set shouldQuery: false in TypeScript or should_query=False in Python. The message is still added to the conversation, and priority determines when it takes effect.
yield {
  type: 'user',
  message: {
    role: 'user',
    content: [{ type: 'text', text: 'All subsequent suggestions must support Python 3.10.' }],
  },
  parent_tool_use_id: null,
  shouldQuery: false,
};

Interrupt the current response

Call interrupt() to stop the Agent's current response without ending the session. You can continue sending messages afterward. In TypeScript, call it on the object returned by query(); Python requires QoderSDKClient.
import { accessTokenFromEnv, query } from '@qoder-ai/qoder-agent-sdk';

const q = query({
  prompt: 'Inspect every file in the project.',
  options: { auth: accessTokenFromEnv() },
});

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

try {
  for await (const message of q) {
    console.dir(message, { depth: null });
  }
} finally {
  clearTimeout(interruptTimer);
}
interrupt() does not remove messages waiting in the queue. Cancel any queued message that is no longer needed. To end the entire session, call a close method or leave the async with block.

Cancel a queued message

To cancel a message that has not started, first give it a UUID that is unique within the session. TypeScript uses the message's uuid field; Python uses the message_uuid argument: The following fragments only show the cancellation operation and assume that q / client and the corresponding message UUID have already been created:
const cancelled = await q.cancelAsyncMessage(uuid);
Cancellation returns true / True when successful and false / False when the message does not exist or has already started. A message without a UUID cannot be cancelled individually. Do not reuse a UUID within the same session.

End input and close the session

  • TypeScript: the SDK ends a string-prompt session when the task finishes. With an async message stream, completion of the iterator means no more messages will be sent. To close the entire session early, use AbortController or call q.close().
  • Python: one-off query() calls end automatically. With QoderSDKClient, use async with to connect and disconnect automatically, or call connect() / disconnect() manually.
The following code only shows how a session ends and reuses the imports, messages(), and options definitions from the earlier examples:

Close automatically

const q = query({
  prompt: messages(),
  options: { auth: accessTokenFromEnv() },
});

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

Close early in response to an external condition

const abortController = new AbortController();
const q = query({
  prompt: 'Inspect every file in the project.',
  options: { auth: accessTokenFromEnv(), abortController },
});

const taskTimeout = setTimeout(() => abortController.abort(), 5_000);
try {
  for await (const message of q) {
    console.dir(message, { depth: null });
  }
} finally {
  clearTimeout(taskTimeout);
}
You cannot send another message after the session closes.