fix(chat): recover session list from snapshots
This commit is contained in:
+8
-8
@@ -2187,14 +2187,6 @@ export function createTkmindProxy({
|
||||
}
|
||||
}
|
||||
|
||||
if (healthyTargets === 0) {
|
||||
res.status(502).json({ message: lastFailure ?? '后端连接失败,请稍后重试' });
|
||||
return;
|
||||
}
|
||||
if (healthyTargets < targets.length) {
|
||||
res.setHeader('X-TKMind-Degraded', '1');
|
||||
}
|
||||
|
||||
const directSessionIds = [...owned].filter(isDirectChatSessionId);
|
||||
const ownedMissingFromGoose = [...owned].filter((sessionId) => !sessionsById.has(sessionId));
|
||||
const snapshotFallbackIds = [...new Set([...directSessionIds, ...ownedMissingFromGoose])];
|
||||
@@ -2231,6 +2223,14 @@ export function createTkmindProxy({
|
||||
}
|
||||
}
|
||||
|
||||
if (healthyTargets === 0 && owned.size > 0 && sessionsById.size === 0) {
|
||||
res.status(502).json({ message: lastFailure ?? '后端连接失败,请稍后重试' });
|
||||
return;
|
||||
}
|
||||
if (healthyTargets < targets.length || healthyTargets === 0) {
|
||||
res.setHeader('X-TKMind-Degraded', '1');
|
||||
}
|
||||
|
||||
const sessions = [...sessionsById.values()].sort(sortSessionsByRecent);
|
||||
await enrichSessionHistory(sessions, req.currentUser.id);
|
||||
if (typeof userAuth.getSessionOrigins === 'function' && sessions.length > 0) {
|
||||
|
||||
@@ -452,6 +452,134 @@ test('buildVisionPayload injects public standard image urls for page generation'
|
||||
assert.match(text, /不得改写图片里人物的年龄、性别、人数或主体关系/);
|
||||
});
|
||||
|
||||
test('GET /sessions serves snapshot summaries when every upstream is unavailable', async () => {
|
||||
let upstream;
|
||||
try {
|
||||
upstream = createServer((_req, res) => {
|
||||
res.writeHead(503, { 'Content-Type': 'text/plain' });
|
||||
res.end('temporarily unavailable');
|
||||
});
|
||||
const upstreamPort = await listen(upstream);
|
||||
const proxy = createTkmindProxy({
|
||||
apiTarget: `http://127.0.0.1:${upstreamPort}`,
|
||||
apiSecret: 'test-secret',
|
||||
userAuth: {
|
||||
async listOwnedSessionIds() {
|
||||
return new Set(['session-1']);
|
||||
},
|
||||
async getSessionOrigins() {
|
||||
return new Map([['session-1', 'h5']]);
|
||||
},
|
||||
},
|
||||
sessionSnapshotService: {
|
||||
isEnabled() {
|
||||
return true;
|
||||
},
|
||||
async get(sessionId) {
|
||||
assert.equal(sessionId, 'session-1');
|
||||
return {
|
||||
session: {
|
||||
id: sessionId,
|
||||
name: 'Recovered session',
|
||||
updated_at: '2026-07-27T00:00:00.000Z',
|
||||
},
|
||||
messages: [{ role: 'user', content: [{ type: 'text', text: 'hello' }] }],
|
||||
meta: {
|
||||
synced_msg_count: 1,
|
||||
source_updated_at: '2026-07-27T00:00:00.000Z',
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const req = {
|
||||
currentUser: { id: 'user-1', username: 'john' },
|
||||
query: {},
|
||||
};
|
||||
let payload;
|
||||
const headers = new Map();
|
||||
const res = {
|
||||
statusCode: 200,
|
||||
status(code) {
|
||||
this.statusCode = code;
|
||||
return this;
|
||||
},
|
||||
setHeader(name, value) {
|
||||
headers.set(name, value);
|
||||
},
|
||||
json(value) {
|
||||
payload = value;
|
||||
return this;
|
||||
},
|
||||
};
|
||||
|
||||
await proxy.handlers['GET /sessions'][1](req, res);
|
||||
|
||||
assert.equal(res.statusCode, 200);
|
||||
assert.equal(headers.get('X-TKMind-Degraded'), '1');
|
||||
assert.equal(payload.sessions.length, 1);
|
||||
assert.equal(payload.sessions[0].id, 'session-1');
|
||||
assert.equal(payload.sessions[0].name, 'Recovered session');
|
||||
assert.equal(payload.sessions[0].message_count, 1);
|
||||
} finally {
|
||||
await closeServer(upstream);
|
||||
}
|
||||
});
|
||||
|
||||
test('GET /sessions keeps the upstream error when no fallback session can be recovered', async () => {
|
||||
let upstream;
|
||||
try {
|
||||
upstream = createServer((_req, res) => {
|
||||
res.writeHead(503, { 'Content-Type': 'text/plain' });
|
||||
res.end('temporarily unavailable');
|
||||
});
|
||||
const upstreamPort = await listen(upstream);
|
||||
const proxy = createTkmindProxy({
|
||||
apiTarget: `http://127.0.0.1:${upstreamPort}`,
|
||||
apiSecret: 'test-secret',
|
||||
userAuth: {
|
||||
async listOwnedSessionIds() {
|
||||
return new Set(['session-1']);
|
||||
},
|
||||
},
|
||||
sessionSnapshotService: {
|
||||
isEnabled() {
|
||||
return true;
|
||||
},
|
||||
async get() {
|
||||
return null;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const req = {
|
||||
currentUser: { id: 'user-1', username: 'john' },
|
||||
query: {},
|
||||
};
|
||||
let payload;
|
||||
const res = {
|
||||
statusCode: 200,
|
||||
status(code) {
|
||||
this.statusCode = code;
|
||||
return this;
|
||||
},
|
||||
setHeader() {},
|
||||
json(value) {
|
||||
payload = value;
|
||||
return this;
|
||||
},
|
||||
};
|
||||
|
||||
await proxy.handlers['GET /sessions'][1](req, res);
|
||||
|
||||
assert.equal(res.statusCode, 502);
|
||||
assert.equal(payload.message, 'temporarily unavailable');
|
||||
} finally {
|
||||
await closeServer(upstream);
|
||||
}
|
||||
});
|
||||
|
||||
test('proxySessionEvents does not send JSON after SSE headers were sent', async () => {
|
||||
let upstream;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user