import { parseApiErrorMessage } from '../../../packages/shared/src/http'; import type { TextStreamOptions } from '../aiTypes'; import { type ApiRetryOptions, fetchWithApiAuth, requestJson, } from '../apiClient'; import { readCreationAgentSessionFromSse } from './creationAgentSse'; type CreationAgentClientMessages = { createSession: string; getSession: string; sendMessage: string; streamIncomplete: string; executeAction: string; }; type CreationAgentClientOptions = { apiBase: string; messages: CreationAgentClientMessages; createSessionTimeoutMs?: number; executeActionTimeoutMs?: number; readRetry?: ApiRetryOptions; writeRetry?: ApiRetryOptions; }; const DEFAULT_CREATION_AGENT_READ_RETRY: ApiRetryOptions = { maxRetries: 1, baseDelayMs: 180, maxDelayMs: 480, }; const DEFAULT_CREATION_AGENT_WRITE_RETRY: ApiRetryOptions = { maxRetries: 1, baseDelayMs: 240, maxDelayMs: 640, retryUnsafeMethods: true, }; function buildJsonPostInit(payload: unknown): RequestInit { return { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }; } async function openCreationAgentSsePost( url: string, payload: unknown, fallbackMessage: string, signal?: AbortSignal, ) { const response = await fetchWithApiAuth(url, { ...buildJsonPostInit(payload), signal, }); 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; } /** * 三类作品创作 Agent 都遵循同一组 HTTP/SSE 端点形状。 * 这里统一请求骨架,玩法 client 只保留路径、类型与中文错误文案差异。 */ export function createCreationAgentClient< TCreateSessionPayload, TCreateSessionResponse, TGetSessionResponse, TSession, TSendMessagePayload, TSendMessageResponse, TExecuteActionPayload, TExecuteActionResponse, >({ apiBase, messages, createSessionTimeoutMs = 15000, executeActionTimeoutMs, readRetry = DEFAULT_CREATION_AGENT_READ_RETRY, writeRetry = DEFAULT_CREATION_AGENT_WRITE_RETRY, }: CreationAgentClientOptions) { const createSession = ( payload: TCreateSessionPayload, ): Promise => requestJson( apiBase, buildJsonPostInit(payload), messages.createSession, { retry: writeRetry, timeoutMs: createSessionTimeoutMs, }, ); const getSession = (sessionId: string): Promise => requestJson( `${apiBase}/${encodeURIComponent(sessionId)}`, { method: 'GET' }, messages.getSession, { retry: readRetry, }, ); const sendMessage = ( sessionId: string, payload: TSendMessagePayload, ): Promise => requestJson( `${apiBase}/${encodeURIComponent(sessionId)}/messages`, buildJsonPostInit(payload), messages.sendMessage, { retry: writeRetry, }, ); const streamMessage = async ( sessionId: string, payload: TSendMessagePayload, options: TextStreamOptions = {}, ): Promise => { const response = await openCreationAgentSsePost( `${apiBase}/${encodeURIComponent(sessionId)}/messages/stream`, payload, messages.sendMessage, options.signal, ); return readCreationAgentSessionFromSse(response, { ...options, fallbackMessage: messages.sendMessage, incompleteMessage: messages.streamIncomplete, }); }; const executeAction = ( sessionId: string, payload: TExecuteActionPayload, ): Promise => requestJson( `${apiBase}/${encodeURIComponent(sessionId)}/actions`, buildJsonPostInit(payload), messages.executeAction, { retry: writeRetry, timeoutMs: executeActionTimeoutMs, }, ); return { createSession, getSession, sendMessage, streamMessage, executeAction, }; }