import type { PuzzleAgentActionRequest, PuzzleAgentActionResponse, } from '../../../packages/shared/src/contracts/puzzleAgentActions'; import type { CreatePuzzleAgentSessionRequest, CreatePuzzleAgentSessionResponse, PuzzleAgentSessionSnapshot, SendPuzzleAgentMessageRequest, } from '../../../packages/shared/src/contracts/puzzleAgentSession'; import { parseApiErrorMessage } from '../../../packages/shared/src/http'; import type { TextStreamOptions } from '../aiTypes'; import { type ApiRetryOptions, fetchWithApiAuth, requestJson, } from '../apiClient'; import { readCreationAgentSessionFromSse } from '../creation-agent'; const PUZZLE_AGENT_API_BASE = '/api/runtime/puzzle/agent/sessions'; const PUZZLE_AGENT_SESSION_START_TIMEOUT_MS = 15000; const PUZZLE_AGENT_READ_RETRY: ApiRetryOptions = { maxRetries: 1, baseDelayMs: 180, maxDelayMs: 480, }; const PUZZLE_AGENT_WRITE_RETRY: ApiRetryOptions = { maxRetries: 1, baseDelayMs: 240, maxDelayMs: 640, retryUnsafeMethods: true, }; /** * 创建拼图 Agent 共创会话。 * 首版继续走 Axum facade,前端不直连 SpacetimeDB。 */ export async function createPuzzleAgentSession( payload: CreatePuzzleAgentSessionRequest = {}, ) { return requestJson( PUZZLE_AGENT_API_BASE, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }, '创建拼图共创会话失败', { retry: PUZZLE_AGENT_WRITE_RETRY, timeoutMs: PUZZLE_AGENT_SESSION_START_TIMEOUT_MS, }, ); } /** * 读取拼图 Agent 会话快照。 */ export async function getPuzzleAgentSession(sessionId: string) { return requestJson( `${PUZZLE_AGENT_API_BASE}/${encodeURIComponent(sessionId)}`, { method: 'GET', }, '读取拼图共创会话失败', { retry: PUZZLE_AGENT_READ_RETRY, }, ); } /** * 非流式发送拼图 Agent 消息。 * 当前 UI 主链使用 SSE,但保留普通接口便于后续降级。 */ export async function sendPuzzleAgentMessage( sessionId: string, payload: SendPuzzleAgentMessageRequest, ) { return requestJson<{ session: PuzzleAgentSessionSnapshot }>( `${PUZZLE_AGENT_API_BASE}/${encodeURIComponent(sessionId)}/messages`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }, '发送拼图共创消息失败', { retry: PUZZLE_AGENT_WRITE_RETRY, }, ); } /** * 流式发送拼图 Agent 消息。 * 后端当前会先回传一段 assistant 文本,再附上最新 session 快照。 */ export async function streamPuzzleAgentMessage( sessionId: string, payload: SendPuzzleAgentMessageRequest, options: TextStreamOptions = {}, ) { const response = await openPuzzleAgentSsePost( `${PUZZLE_AGENT_API_BASE}/${encodeURIComponent(sessionId)}/messages/stream`, payload, '发送拼图共创消息失败', ); return readCreationAgentSessionFromSse( response, { ...options, fallbackMessage: '发送拼图共创消息失败', incompleteMessage: '拼图共创消息流式结果不完整', }, ); } /** * 执行拼图结果页相关操作。 * 后端会返回 operation 记录,前端再按需刷新 session 或 works/gallery。 */ export async function executePuzzleAgentAction( sessionId: string, payload: PuzzleAgentActionRequest, ) { return requestJson( `${PUZZLE_AGENT_API_BASE}/${encodeURIComponent(sessionId)}/actions`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }, '执行拼图共创操作失败', { retry: PUZZLE_AGENT_WRITE_RETRY, }, ); } async function openPuzzleAgentSsePost( url: string, payload: unknown, fallbackMessage: string, ) { const response = await fetchWithApiAuth(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); if (!response.ok) { const responseText = await response.text(); throw new Error(parseApiErrorMessage(responseText, fallbackMessage)); } if (!response.body) { throw new Error('streaming response body is unavailable'); } return response; } export const puzzleAgentClient = { createSession: createPuzzleAgentSession, getSession: getPuzzleAgentSession, sendMessage: sendPuzzleAgentMessage, streamMessage: streamPuzzleAgentMessage, executeAction: executePuzzleAgentAction, };