Skip to main content
对话与会话

输入模式

输入模式决定的是:任务开始后,你的应用还能不能继续给 Agent 发送消息。 它不区分文本、图片等消息内容,也不决定回复是否逐块显示。无论选择哪种输入模式,SDK 都会以消息流返回 Agent 的回复。如何处理回复,见流式输出。 Qoder Agent SDK 提供两类输入方式:
输入方式TypeScriptPythonJava什么时候使用
单消息输入query({ prompt: string })query(prompt=str)client.query(OneShotQueryRequest)一次性任务、批处理、CI 脚本
流式输入query({ prompt: AsyncIterable<SDKUserMessage> })推荐使用 QoderSDKClient;预先确定的消息流也可传给 query()client.openSession(options) 返回的 QoderSession聊天界面、多消息会话、运行中追加或调整任务
选择时只需要考虑一个问题:任务开始后,是否还要继续给 Agent 发消息?
  • 不需要。使用单消息输入。
  • 需要。例如用户还会追问,或者应用要根据 Agent 的回复决定下一步。使用流式输入。

单消息输入

把字符串传给 query() 会启动一个独立任务。任务开始后,不能再向这次调用追加用户消息。任务完成后,SDK 自动结束会话。
import { accessTokenFromEnv, query } from '@qoder-ai/qoder-agent-sdk';

for await (const message of query({
  prompt: '检查当前项目中缺少测试覆盖的函数,并给出报告。',
  options: {
    auth: accessTokenFromEnv(),
    allowedTools: ['Read', 'Glob', 'Grep'],
  },
})) {
  if (message.type === 'result' && message.subtype === 'success') {
    console.log(message.result);
  }
}
Java 的一次性查询在 CLI 返回本轮 ResultMessage 后结束。仍应把消息流读到结束,再关闭游标或 QoderQuery;ResultOutcome.ERROR 表示本轮失败,没有抛异常不等于成功。 单消息输入不影响工具审批。如果 Agent 需要执行尚未获得许可的工具,canUseTool / can_use_tool 仍然可以询问用户。你不需要为此把 prompt 改成消息流。详见审批与用户输入。

流式输入

流式输入允许你的应用在任务开始后继续发送消息。除了聊天中的多轮对话,它还可以在任务进行中补充信息、改变执行方向,或安排稍后处理的消息。 三个 SDK 的推荐接口不同:
  • TypeScript:把 AsyncIterable<SDKUserMessage> 传给 query()。它是一个可以持续 yield 用户消息的异步迭代器。聊天 UI、消息队列或其他事件都可以向它提供消息。
  • Python:使用 QoderSDKClient 保持会话。调用 client.query(...) 发送一条新消息,再用 client.receive_response() 接收这一轮的回复。
  • Java:用 client.openSession(options) 打开 QoderSession。调用 session.send(...) 发送一条新消息,再从同一个消息游标读取回复,读到 ResultMessage 表示这一轮结束。

多消息会话

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: '检查这个代码库中的安全问题。' }],
    },
    parent_tool_use_id: null,
  };

  // 实际应用中,这里可以等待 UI、消息队列或其他外部事件。
  await new Promise((resolve) => setTimeout(resolve, 2_000));

  yield {
    type: 'user',
    message: {
      role: 'user',
      content: [{ type: 'text', text: '完成当前分析后,再生成一份简短报告。' }],
    },
    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);
  }
}
TypeScript 的消息字段见 SDKUserMessage。在 Python 中,每次 receive_response() 接收一轮回复。收到 ResultMessage 后,可以继续发送下一条消息。 在 Java 中,每条输入是一个 UserInput。send() 完成只表示输入已写入 CLI,不表示回复结束。同一个会话的消息流只能消费一次,应跨轮复用同一个游标;关闭游标会同时关闭会话。

Python 异步消息流的边界

Python 的 query() 也接受 AsyncIterable[dict[str, Any]]。如果多条消息在任务开始前就已经确定,可以用这种方式依次发送。下面只展示 Python 异步消息流的核心写法,query 和 options 的创建方式见前面的完整示例:
Python
async def prompts():
    yield {
        "type": "user",
        "message": {"role": "user", "content": "检查认证模块。"},
        "parent_tool_use_id": None,
    }
    yield {
        "type": "user",
        "message": {"role": "user", "content": "然后汇总发现的问题。"},
        "parent_tool_use_id": None,
    }


async for message in query(prompt=prompts(), options=options):
    print(message)
这种写法只适合预先准备好的消息。它不能根据 Agent 刚刚返回的内容决定下一条消息,也不能调用 interrupt() 或取消排队消息。聊天界面等交互式应用应使用 QoderSDKClient。

预先启动 CLI(Java)

Java 还可以在拿到用户输入之前启动 CLI。client.prepare(options) 完成启动、认证和初始化,但不发送输入,用来消除首次请求的启动延迟:
Java
// 应用启动时预热:启动 CLI 并完成认证和初始化,不发送输入。
PreparedQuery prepared = client.prepare(options).toCompletableFuture().join();

// 用户输入到达后转为会话;也可以用 toQuery(input) 转为一次性查询。
QoderSession session = prepared.toSession();
session.send(UserInput.text("检查这个代码库中的安全问题。"));
PreparedQuery 只能转换一次。转换后由新的 QoderSession 或 QoderQuery 负责释放资源;一直没有用上时,调用 close() 丢弃它。

运行中追加输入

Agent 回复期间,也可以继续发送消息。priority 决定 Agent 什么时候处理这条消息:
值行为
now停止当前回复,立即处理这条消息
next默认值;在下一个合适的时机处理
later等当前回复结束后处理
相同优先级的消息按发送顺序处理。要立即改变当前方向,使用 priority: 'now'。如果只想停止当前回复,不想发送新消息,请使用中断当前回复。 Java 用 UserInput.Builder 的 priority(SendPriority.NOW / NEXT / LATER) 设置优先级。Java 的 UserInput 默认优先级是 NOW,不是 next;运行中追加消息时,按需要显式设置。 下面的代码需要放入前面的 TypeScript 消息生成器、已连接的 Python client 会话或 Java 的 QoderSession 中:
yield {
  type: 'user',
  message: {
    role: 'user',
    content: [{ type: 'text', text: '停止当前方向,只分析失败的测试。' }],
  },
  parent_tool_use_id: null,
  priority: 'now',
};

添加上下文但不触发回复

有时你只想补充背景信息,不希望 Agent 立即回复。此时设置 shouldQuery: false(TypeScript)、should_query=False(Python)或 shouldQuery(false)(Java)。这条消息仍会加入对话,何时生效由 priority 决定。
yield {
  type: 'user',
  message: {
    role: 'user',
    content: [{ type: 'text', text: '后续建议都必须兼容 Python 3.10。' }],
  },
  parent_tool_use_id: null,
  shouldQuery: false,
};

中断当前回复

调用 interrupt() 可以停止 Agent 当前的回复,但不会结束会话。中断后仍可继续发送消息。TypeScript 在 query() 返回的对象上调用;Python 需要使用 QoderSDKClient;Java 在 QoderQuery 或 QoderSession 上调用。
import { accessTokenFromEnv, query } from '@qoder-ai/qoder-agent-sdk';

const q = query({
  prompt: '检查项目中的所有文件。',
  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);
}
Java 的 interrupt() 返回 InterruptResult,成功只表示 CLI 接受了中断请求,本轮何时结束仍以 ResultMessage 为准。 interrupt() 不会删除等待处理的消息。如果其中某条消息已经不需要,请单独取消。要结束整个会话,请调用关闭方法或退出 async with 代码块。

取消排队消息

要取消一条尚未开始处理的消息,先给它设置会话内唯一的 UUID。TypeScript 使用消息的 uuid 字段,Python 使用 message_uuid 参数。Java 不需要自己生成 ID:用 session.submit(input) 代替 send(input) 发送,返回的 MessageSubmission 立即带有 SDK 分配的 MessageId: 下面只展示取消操作本身,假设 q / client 和对应的消息 UUID 已经创建:
const cancelled = await q.cancelAsyncMessage(uuid);
取消成功返回 true / True。如果消息不存在或已经开始处理,则返回 false / False。没有 UUID 的消息不能单独取消;同一会话中不要重复使用 UUID。 Java 中,还没写入 CLI 的消息由 SDK 在本地取消,这条消息的 written() 以 CancellationException 结束,会话不受影响。已经开始写入的消息,只有 CLI 支持对应能力时才会尝试远端取消,否则返回 false。只需要写入确认时用 send(),需要在写入前拿到 ID 时用 submit()。

结束输入和关闭会话

  • TypeScript:字符串任务完成后,SDK 自动结束会话。使用异步消息流时,迭代器结束表示不再发送消息。要提前关闭整个会话,可以使用 AbortController 或调用 q.close()。
  • Python:一次性 query() 会自动结束。使用 QoderSDKClient 时,推荐用 async with 自动连接和断开;也可以手动调用 connect() / disconnect()。
  • Java:一次性查询读到消息流结束后,关闭游标或 QoderQuery。QoderSession 用完后关闭;需要确保已接受的输入都写入 CLI 时,先等待 completeInput(),它会拒绝后续输入,但不等待回复。关闭 QoderClient 会级联关闭它创建的全部查询和会话。
下面的代码只展示会话如何结束,并沿用前面示例中的导入、messages() 和 options:

自动关闭

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

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

根据外部条件提前关闭

const abortController = new AbortController();
const q = query({
  prompt: '检查项目中的所有文件。',
  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);
}
Java 中从其他线程关闭会话后,正在读取的游标以 SessionClosedException 结束。不要在 SDK 回调或推送处理器里调用阻塞式 close(),在这些位置使用 closeAsync()。 会话关闭后,不能再向它发送消息。