feat(consultation): unify server-distributed agents without fixed roles

This commit is contained in:
2026-09-24 11:56:42 +08:00
parent 248776d855
commit 8a157be4f6
35 changed files with 625 additions and 668 deletions

View File

@@ -20,12 +20,12 @@ interface TeacherSession {
type Json = Record<string, unknown>;
function object(value: unknown): Json {
if (!value || typeof value !== 'object' || Array.isArray(value))
throw new TeacherError(502, 'teacher_protocol_invalid', '老师服务返回的数据无效。');
throw new TeacherError(502, 'teacher_protocol_invalid', '智能体服务返回的数据无效。');
return value as Json;
}
function identifier(value: unknown): string {
if (typeof value !== 'string' || !value)
throw new TeacherError(502, 'teacher_protocol_invalid', '老师服务未返回有效的运行标识。');
throw new TeacherError(502, 'teacher_protocol_invalid', '智能体服务未返回有效的运行标识。');
return value;
}
export interface TeacherCloudTransport {
@@ -60,7 +60,7 @@ export function teacherCloudTransport(account: TeacherAccount): TeacherCloudTran
typeof session.api_base_url !== 'string' ||
!/^https?:\/\//.test(session.api_base_url)
) {
throw new TeacherError(502, 'teacher_session_invalid', '老师接入凭据无效,请重新连接。');
throw new TeacherError(502, 'teacher_session_invalid', '智能体接入凭据无效,请重新连接。');
}
}
if (!stopping) assertTeacherAccount(account);
@@ -100,7 +100,7 @@ export function teacherCloudTransport(account: TeacherAccount): TeacherCloudTran
throw new TeacherError(
response.status,
'teacher_cloud_failed',
typeof message === 'string' ? message : '老师服务暂不可用,请稍后重试。'
typeof message === 'string' ? message : '智能体服务暂不可用,请稍后重试。'
);
}
return response;
@@ -110,7 +110,7 @@ export function teacherCloudTransport(account: TeacherAccount): TeacherCloudTran
await delay(500, undefined, { signal });
}
}
throw new TeacherError(502, 'teacher_connection_failed', '连接老师失败。');
throw new TeacherError(502, 'teacher_connection_failed', '连接智能体失败。');
};
return {
async json(path, body, signal) {
@@ -122,7 +122,7 @@ export function teacherCloudTransport(account: TeacherAccount): TeacherCloudTran
async events(path, signal, accept) {
const response = await fetchCloud(path, undefined, signal);
if (!response.body)
throw new TeacherError(502, 'teacher_stream_missing', '老师回复连接不可用。');
throw new TeacherError(502, 'teacher_stream_missing', '智能体回复连接不可用。');
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
@@ -165,9 +165,7 @@ export function prepareCloudTeacher(
saveRequestId: (id: string) => Promise<void>,
transport: TeacherCloudTransport = teacherCloudTransport(account)
) {
const tools = topic.role === 'friend' || topic.definition.teacher_id === 'coding-friend'
? undefined
: createTeacherReadTools(access);
const tools = createTeacherReadTools(access);
return {
// The cloud query contract is byte-bounded. Compile against the exact JSON
// envelope instead of reserving a fixed amount for unpredictable escaping.
@@ -237,9 +235,9 @@ export function prepareCloudTeacher(
throw new TeacherError(
409,
'teacher_request_stopped',
'老师提问未能启动,请重新提问。'
'智能体提问未能启动,请重新提问。'
);
onProgress('正在等待老师…');
onProgress('正在等待智能体…');
await delay(800, undefined, { signal: bounded });
queued = await transport.json(
'/requests/' + encodeURIComponent(questionId),
@@ -273,7 +271,7 @@ export function prepareCloudTeacher(
throw new TeacherError(
409,
'teacher_interaction_unsupported',
'老师请求了当前面板不支持的交互,请联系运营调整该智能体。'
'智能体请求了当前面板不支持的交互,请联系运营调整该智能体。'
);
if (
pending.context_id !== requestId ||
@@ -285,14 +283,14 @@ export function prepareCloudTeacher(
throw new TeacherError(
409,
'teacher_context_expired',
'老师读取请求已失效,请重新提问。'
'智能体读取请求已失效,请重新提问。'
);
onProgress('正在读取项目与会话…');
const results = [];
for (const raw of pending.calls) {
const call = object(raw);
if (typeof call.tool_call_id !== 'string' || typeof call.name !== 'string')
throw new TeacherError(502, 'teacher_protocol_invalid', '老师读取请求无效。');
throw new TeacherError(502, 'teacher_protocol_invalid', '智能体读取请求无效。');
results.push({
tool_call_id: call.tool_call_id,
...(await tools.executeResult(call.name, JSON.stringify(call.arguments), bounded)),
@@ -308,7 +306,7 @@ export function prepareCloudTeacher(
cursor = '0-0';
runText = '';
messageId = '';
onProgress('老师正在继续思考…');
onProgress('智能体正在继续思考…');
continue;
}
if (view.status === 'completed') {
@@ -328,11 +326,11 @@ export function prepareCloudTeacher(
throw new TeacherError(
409,
'teacher_run_failed',
typeof detail === 'string' ? detail : '老师回复已停止。'
typeof detail === 'string' ? detail : '智能体回复已停止。'
);
}
const threadId = identifier(view.thread_id);
onProgress('老师正在思考…');
onProgress('智能体正在思考…');
try {
await transport.events(
'/runs/' +
@@ -342,7 +340,7 @@ export function prepareCloudTeacher(
bounded,
(_event, envelope, id) => {
if (id) cursor = id;
// Yuxi 的父 Run 也包含子线程事件,老师正文只接收云端主线程文本。
// Yuxi 的父 Run 也包含子线程事件,智能体正文只接收云端主线程文本。
if (envelope.thread_id !== threadId) return;
const payload = envelope.payload ? object(envelope.payload) : {};
for (const item of Array.isArray(payload.items)
@@ -368,11 +366,11 @@ export function prepareCloudTeacher(
bounded.throwIfAborted();
access.assertCurrent();
if (error instanceof TeacherError && error.status < 500) throw error;
onProgress('连接中断,正在恢复老师回复…');
onProgress('连接中断,正在恢复智能体回复…');
}
await delay(400, undefined, { signal: bounded });
}
throw new TeacherError(408, 'teacher_question_expired', '本次老师提问已超时,请重新提问。');
throw new TeacherError(408, 'teacher_question_expired', '本次智能体提问已超时,请重新提问。');
} finally {
if (!completed) {
await transport