会话
创建一个连接真实模型的会话,在运行中插话、排队、撤回和压缩。
一个真实模型会话
从仓库根目录运行。先设置 RNA_MODEL_BASE_URL、RNA_MODEL_ID、RNA_MODEL_API_KEY,RNA_MODEL_PROTOCOL 可选 openai(基址包含 /v1)或 anthropic。执行会产生真实的模型请求。
import { resolve } from 'node:path';
import { createSession, createWorkspaceTools } from './packages/sdk/src/index.mjs';
const model = {
providerId: 'configured',
protocol: process.env.RNA_MODEL_PROTOCOL || 'openai',
baseUrl: process.env.RNA_MODEL_BASE_URL,
modelId: process.env.RNA_MODEL_ID,
contextWindow: Number(process.env.RNA_CONTEXT_WINDOW || 32000),
maxOutputTokens: Number(process.env.RNA_MAX_OUTPUT_TOKENS || 2048),
reasoning: 'off',
cacheRetention: 'short',
};
const cwd = process.cwd();
const stateDir = resolve('.rna-sdk-state');
const session = await createSession({
sessionId: 'example-conversation',
projectId: 'example-project',
cwd,
stateDir,
model,
apiKey: process.env.RNA_MODEL_API_KEY,
tools: await createWorkspaceTools(cwd, { deniedPaths: [stateDir] }),
systemPrompt: '你是 Rna Agent,结合可读取的项目内容协助用户;不要声称执行了没有执行的操作。',
onEvent(event) {
if (event.type === 'message_update' && event.assistantMessageEvent?.type === 'text_delta') {
process.stdout.write(event.assistantMessageEvent.delta);
}
},
});
try {
await session.prompt('查看项目顶层目录,并简要说明下一步值得关注的事项。');
console.log('\n', session.snapshot().usage);
} finally {
await session.dispose();
}代码中的容量只是这个例子的请求预算,不是供应商规格。用相同的 stateDir + sessionId + projectId 重新创建会话,会读取同一份 JSONL 日志。
运行中的输入
const running = session.prompt('开始检查');
const queued = await session.followUp('检查结束后补充测试建议');
await session.steer('先关注兼容性,再处理其他问题');
await session.withdraw(queued.id); // 仍在队列中时可以撤回
await running;steer在模型或工具的安全边界进入上下文,不能改变已经发出的 HTTP 请求,也不撤销已执行的副作用。followUp在当前工作收束后接续。abort()请求停止,resume()继续持久会话。yieldAtBoundary({ requestId })保存一个合作式暂停请求,在当前请求或工具批次结算后暂停。
压缩
默认开启自动压缩:接近配置上下文的 80% 时,在完整的工具批次之后逐段摘要较早的历史,保留最近两组回应和工具结果。摘要失败或没有减少上下文时,保留原样并停止这一轮。
压缩的做法与 pi 一致,换到上下文更小的模型也能接上:交给摘要的是纯文本记录,工具结果、调用参数、宿主上下文和推理各保留前 2000 字符;服务端报告某一批太长时减半重试(最多三次);回答请求因超长被拒时,压缩到发送量的一半后重试一次。超长错误从各家的报错文字、Codex 的 detail 字段和流里的 response.failed 识别。
手动压缩保留原始日志,并要求你提供真实的摘要:
await session.compact({
keepLastTurns: 2,
summarize: async ({ messages, previousSummary, instructions }) => {
return await yourSummarizer({ messages, previousSummary, instructions });
},
});yourSummarizer 需要宿主实现,不是包导出的函数。
请求边界的钩子
| 钩子 | 时机 | 用途 |
|---|---|---|
beforeRequest | 上一批工具全部完成、下一次请求之前 | 返回新的完整工具集合或带来源的上下文更新 |
beforeCompletion | 每个实际模型请求之前,包括压缩 | 预算预留 |
beforeFinish / afterRun | 收束前后 | 记录结果;不是新的权限来源 |
beforeTool / afterTool | 每个工具调用的参数校验之后、执行之前,和执行之后 | 宿主的工具策略:可以拒绝调用,或把说明附在工具结果后;不是新的权限来源 |
时限与重试
streamCompletion 默认空闲时限 120 秒、单次请求总时限 15 分钟,SSE 心跳只刷新空闲计时。只有明确的 HTTP 429 或 5xx、并且尚未接受 SSE 响应时才自动重试,最多 2 次,尊重不超过 60 秒的 Retry-After。连接错误、半条流和已执行的工具都不会被隐式重放。
Anthropic 提示缓存
用 Anthropic 协议时,历史只追加(appendOnlyActive),思考内容绑定对话。cacheKeepAlive(input) 把上一个请求以 max_tokens: 0、非流式重发一次,只做预填,不产生输出,却会刷新缓存计时;带 thinking.type: "enabled" 或结构化输出的请求不能这样保温,会被拒绝。它从不重试,由宿主决定何时值得调用。
图片
图片以 { type: 'image', data, mimeType } 通过 session.prompt(text, { images }) 传入。模型必须显式声明 input: ['text', 'image'];不支持时返回明确诊断,不会自动换模型或假装读过图片。