| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import { historyResponseSchema, type HistoryMessage } from '@moonshot-ai/kap-server/protocol'; |
|
|
| export const HISTORY_PAGE_SIZE = 500; |
|
|
| export interface HistoryPage { |
| readonly messages: readonly HistoryMessage[]; |
| |
| readonly inFlight?: { turn_id: string; step_id: string }; |
| } |
|
|
| export interface FetchHistoryPageOptions { |
| readonly baseUrl: string; |
| readonly token?: string; |
| readonly sessionId: string; |
| readonly agentId: string; |
| |
| readonly beforeTurn?: string; |
| |
| readonly afterStep?: string; |
| readonly pageSize?: number; |
| |
| readonly fetchImpl?: typeof fetch; |
| } |
|
|
| export async function fetchHistoryPage(opts: FetchHistoryPageOptions): Promise<HistoryPage> { |
| const params = new URLSearchParams({ |
| agent_id: opts.agentId, |
| page_size: String(opts.pageSize ?? HISTORY_PAGE_SIZE), |
| }); |
| if (opts.beforeTurn !== undefined) params.set('before_turn', opts.beforeTurn); |
| if (opts.afterStep !== undefined) params.set('after_step', opts.afterStep); |
| const headers: Record<string, string> = {}; |
| if (opts.token !== undefined && opts.token !== '') { |
| headers['authorization'] = `Bearer ${opts.token}`; |
| } |
| const doFetch = opts.fetchImpl ?? fetch; |
| const res = await doFetch( |
| `${opts.baseUrl}/api/v1/sessions/${encodeURIComponent(opts.sessionId)}/history?${params.toString()}`, |
| { headers }, |
| ); |
| const envelope = (await res.json()) as { code: number; msg: string; data: unknown }; |
| if (envelope.code !== 0) { |
| throw new Error(`history page failed (${envelope.code}): ${envelope.msg}`); |
| } |
| const parsed = historyResponseSchema.safeParse(envelope.data); |
| if (!parsed.success) { |
| throw new Error('history page: unexpected response shape'); |
| } |
| return { messages: parsed.data.messages, inFlight: parsed.data.in_flight }; |
| } |
|
|
| |
| |
| |
| |
| |
| export async function fetchFullHistory(opts: { |
| readonly baseUrl: string; |
| readonly token?: string; |
| readonly sessionId: string; |
| readonly agentId: string; |
| readonly pageSize?: number; |
| readonly fetchImpl?: typeof fetch; |
| }): Promise<readonly HistoryMessage[]> { |
| const pageSize = opts.pageSize ?? HISTORY_PAGE_SIZE; |
| const messages: HistoryMessage[] = []; |
| const seen = new Set<string>(); |
| let beforeTurn: string | undefined; |
| for (;;) { |
| const page = await fetchHistoryPage({ ...opts, beforeTurn, pageSize }); |
| if (page.messages.length === 0) break; |
| const fresh: HistoryMessage[] = []; |
| for (const message of page.messages) { |
| const key = historyEntityKey(message); |
| if (seen.has(key)) continue; |
| seen.add(key); |
| fresh.push(message); |
| } |
| messages.unshift(...fresh); |
| if (page.messages.length < pageSize) break; |
| const oldest = page.messages |
| .map((message) => ('turn_id' in message ? message.turn_id : undefined)) |
| .find((turnId) => turnId !== undefined); |
| if (oldest === undefined || oldest === beforeTurn) break; |
| beforeTurn = oldest; |
| } |
| return messages; |
| } |
|
|
| function historyEntityKey(message: HistoryMessage): string { |
| switch (message.type) { |
| case 'turn': |
| return `turn:${message.turn_id}`; |
| case 'step': |
| return `step:${message.step_id}`; |
| case 'user': |
| case 'assistant': |
| case 'thinking': |
| return `${message.type}:${message.message_id}`; |
| case 'tool_call': |
| return `tool_call:${message.tool_call_id}`; |
| case 'system': |
| return `system:${message.system_id}`; |
| case 'interaction': |
| return `interaction:${message.interaction_id}`; |
| case 'task': |
| return `task:${message.task_id}`; |
| case 'todo': |
| return `todo:${message.todo_id}`; |
| } |
| } |
|
|