- Route recommended runtime launches through shared runtime guest token handling - Extend recommend-page anonymous play beyond jump-hop - Add regression coverage for runtime guest launch clients - Update docs to reflect the full anonymous-play matrix
312 lines
9.2 KiB
TypeScript
312 lines
9.2 KiB
TypeScript
import type {
|
|
WoodenFishActionRequest,
|
|
WoodenFishActionResponse,
|
|
WoodenFishCheckpointRunRequest,
|
|
WoodenFishFinishRunRequest,
|
|
WoodenFishGalleryCardResponse,
|
|
WoodenFishGalleryDetailResponse,
|
|
WoodenFishGalleryResponse,
|
|
WoodenFishRunResponse,
|
|
WoodenFishRuntimeRunSnapshotResponse,
|
|
WoodenFishSessionResponse,
|
|
WoodenFishSessionSnapshotResponse,
|
|
WoodenFishWorkDetailResponse,
|
|
WoodenFishWorkMutationResponse,
|
|
WoodenFishWorkProfileResponse,
|
|
WoodenFishWorkspaceCreateRequest,
|
|
WoodenFishWorkSummaryResponse,
|
|
} from '../../../packages/shared/src/contracts/woodenFish';
|
|
import { type ApiRetryOptions, requestJson } from '../apiClient';
|
|
import { createCreationAgentClient } from '../creation-agent';
|
|
import {
|
|
buildRuntimeGuestAuthOptions,
|
|
buildRuntimeGuestHeaders,
|
|
type RuntimeGuestRequestOptions,
|
|
} from '../runtimeGuestAuth';
|
|
|
|
const WOODEN_FISH_API_BASE = '/api/creation/wooden-fish/sessions';
|
|
const WOODEN_FISH_WORKS_API_BASE = '/api/creation/wooden-fish/works';
|
|
const WOODEN_FISH_RUNTIME_API_BASE = '/api/runtime/wooden-fish';
|
|
const WOODEN_FISH_RUNTIME_READ_RETRY: ApiRetryOptions = {
|
|
maxRetries: 1,
|
|
baseDelayMs: 120,
|
|
maxDelayMs: 360,
|
|
};
|
|
const WOODEN_FISH_RUNTIME_WRITE_RETRY: ApiRetryOptions = {
|
|
maxRetries: 1,
|
|
baseDelayMs: 120,
|
|
maxDelayMs: 360,
|
|
retryUnsafeMethods: true,
|
|
};
|
|
type WoodenFishRuntimeRequestOptions = RuntimeGuestRequestOptions;
|
|
|
|
export type {
|
|
WoodenFishActionRequest,
|
|
WoodenFishActionResponse,
|
|
WoodenFishCheckpointRunRequest,
|
|
WoodenFishFinishRunRequest,
|
|
WoodenFishGalleryCardResponse,
|
|
WoodenFishGalleryDetailResponse,
|
|
WoodenFishGalleryResponse,
|
|
WoodenFishRunResponse,
|
|
WoodenFishRuntimeRunSnapshotResponse,
|
|
WoodenFishSessionResponse,
|
|
WoodenFishSessionSnapshotResponse,
|
|
WoodenFishWorkDetailResponse,
|
|
WoodenFishWorkMutationResponse,
|
|
WoodenFishWorkProfileResponse,
|
|
WoodenFishWorkspaceCreateRequest,
|
|
};
|
|
export type CreateWoodenFishSessionRequest = WoodenFishWorkspaceCreateRequest;
|
|
export type WoodenFishSessionSnapshot = WoodenFishSessionSnapshotResponse;
|
|
|
|
const woodenFishCreationClient = createCreationAgentClient<
|
|
WoodenFishWorkspaceCreateRequest,
|
|
WoodenFishSessionResponse,
|
|
WoodenFishSessionResponse,
|
|
WoodenFishSessionSnapshotResponse,
|
|
never,
|
|
never,
|
|
WoodenFishActionRequest,
|
|
WoodenFishActionResponse
|
|
>({
|
|
apiBase: WOODEN_FISH_API_BASE,
|
|
messages: {
|
|
createSession: '创建敲木鱼共创会话失败',
|
|
getSession: '读取敲木鱼共创会话失败',
|
|
sendMessage: '发送敲木鱼共创消息失败',
|
|
streamIncomplete: '敲木鱼共创消息流式结果不完整',
|
|
executeAction: '执行敲木鱼共创操作失败',
|
|
},
|
|
});
|
|
|
|
type FlattenedWoodenFishWorkProfileResponse = Omit<
|
|
WoodenFishWorkProfileResponse,
|
|
'summary'
|
|
> &
|
|
WoodenFishWorkSummaryResponse;
|
|
|
|
function normalizeWoodenFishWorkProfile(
|
|
work:
|
|
| WoodenFishWorkProfileResponse
|
|
| FlattenedWoodenFishWorkProfileResponse,
|
|
): WoodenFishWorkProfileResponse {
|
|
if ('summary' in work && work.summary) {
|
|
return work;
|
|
}
|
|
|
|
const flattened = work as FlattenedWoodenFishWorkProfileResponse;
|
|
const summary: WoodenFishWorkProfileResponse['summary'] = {
|
|
runtimeKind: flattened.runtimeKind,
|
|
workId: flattened.workId,
|
|
profileId: flattened.profileId,
|
|
ownerUserId: flattened.ownerUserId,
|
|
sourceSessionId: flattened.sourceSessionId ?? null,
|
|
workTitle: flattened.workTitle,
|
|
workDescription: flattened.workDescription,
|
|
themeTags: flattened.themeTags,
|
|
coverImageSrc: flattened.coverImageSrc ?? null,
|
|
publicationStatus: flattened.publicationStatus,
|
|
playCount: flattened.playCount,
|
|
updatedAt: flattened.updatedAt,
|
|
publishedAt: flattened.publishedAt ?? null,
|
|
publishReady: flattened.publishReady,
|
|
generationStatus: flattened.generationStatus,
|
|
};
|
|
|
|
return {
|
|
summary,
|
|
draft: flattened.draft,
|
|
hitObjectAsset: flattened.hitObjectAsset,
|
|
backgroundAsset:
|
|
flattened.backgroundAsset ?? flattened.draft?.backgroundAsset ?? null,
|
|
hitSoundAsset: flattened.hitSoundAsset,
|
|
floatingWords: flattened.floatingWords,
|
|
};
|
|
}
|
|
|
|
function normalizeWoodenFishActionResponse(
|
|
response: WoodenFishActionResponse,
|
|
): WoodenFishActionResponse {
|
|
return {
|
|
...response,
|
|
work: response.work ? normalizeWoodenFishWorkProfile(response.work) : null,
|
|
};
|
|
}
|
|
|
|
function normalizeWoodenFishWorkDetailResponse(
|
|
response: WoodenFishWorkDetailResponse,
|
|
): WoodenFishWorkDetailResponse {
|
|
return {
|
|
...response,
|
|
item: normalizeWoodenFishWorkProfile(response.item),
|
|
};
|
|
}
|
|
|
|
function normalizeWoodenFishWorkMutationResponse(
|
|
response: WoodenFishWorkMutationResponse,
|
|
): WoodenFishWorkMutationResponse {
|
|
return {
|
|
...response,
|
|
item: normalizeWoodenFishWorkProfile(response.item),
|
|
};
|
|
}
|
|
|
|
export function createWoodenFishCreationSession(
|
|
payload: WoodenFishWorkspaceCreateRequest,
|
|
) {
|
|
return woodenFishCreationClient.createSession(payload);
|
|
}
|
|
|
|
export function getWoodenFishCreationSession(sessionId: string) {
|
|
return woodenFishCreationClient.getSession(sessionId);
|
|
}
|
|
|
|
export function executeWoodenFishCreationAction(
|
|
sessionId: string,
|
|
payload: WoodenFishActionRequest,
|
|
) {
|
|
return woodenFishCreationClient
|
|
.executeAction(sessionId, payload)
|
|
.then(normalizeWoodenFishActionResponse);
|
|
}
|
|
|
|
export async function getWoodenFishWorkDetail(profileId: string) {
|
|
const response = await requestJson<WoodenFishWorkDetailResponse>(
|
|
`${WOODEN_FISH_RUNTIME_API_BASE}/works/${encodeURIComponent(profileId)}`,
|
|
{ method: 'GET' },
|
|
'读取敲木鱼作品详情失败',
|
|
);
|
|
return normalizeWoodenFishWorkDetailResponse(response);
|
|
}
|
|
|
|
export async function listWoodenFishGallery() {
|
|
return requestJson<WoodenFishGalleryResponse>(
|
|
`${WOODEN_FISH_RUNTIME_API_BASE}/gallery`,
|
|
{ method: 'GET' },
|
|
'读取敲木鱼广场失败',
|
|
{
|
|
retry: WOODEN_FISH_RUNTIME_READ_RETRY,
|
|
skipAuth: true,
|
|
skipRefresh: true,
|
|
},
|
|
);
|
|
}
|
|
|
|
export async function getWoodenFishGalleryDetail(publicWorkCode: string) {
|
|
const response = await requestJson<WoodenFishGalleryDetailResponse>(
|
|
`${WOODEN_FISH_RUNTIME_API_BASE}/gallery/${encodeURIComponent(publicWorkCode)}`,
|
|
{ method: 'GET' },
|
|
'读取敲木鱼广场详情失败',
|
|
{
|
|
retry: WOODEN_FISH_RUNTIME_READ_RETRY,
|
|
skipAuth: true,
|
|
skipRefresh: true,
|
|
},
|
|
);
|
|
return normalizeWoodenFishWorkDetailResponse(response);
|
|
}
|
|
|
|
export async function publishWoodenFishWork(profileId: string) {
|
|
const response = await requestJson<WoodenFishWorkMutationResponse>(
|
|
`${WOODEN_FISH_WORKS_API_BASE}/${encodeURIComponent(profileId)}/publish`,
|
|
{ method: 'POST' },
|
|
'发布敲木鱼作品失败',
|
|
);
|
|
return normalizeWoodenFishWorkMutationResponse(response);
|
|
}
|
|
|
|
export async function startWoodenFishRuntimeRun(
|
|
profileId: string,
|
|
options: WoodenFishRuntimeRequestOptions = {},
|
|
) {
|
|
const requestOptions = buildRuntimeGuestAuthOptions(options);
|
|
return requestJson<WoodenFishRunResponse>(
|
|
`${WOODEN_FISH_RUNTIME_API_BASE}/runs`,
|
|
{
|
|
method: 'POST',
|
|
headers: {
|
|
'content-type': 'application/json',
|
|
...buildRuntimeGuestHeaders(options),
|
|
},
|
|
body: JSON.stringify({ profileId }),
|
|
},
|
|
'启动敲木鱼运行态失败',
|
|
{
|
|
retry: WOODEN_FISH_RUNTIME_WRITE_RETRY,
|
|
...requestOptions,
|
|
},
|
|
);
|
|
}
|
|
|
|
export async function checkpointWoodenFishRun(
|
|
runId: string,
|
|
payload: Omit<WoodenFishCheckpointRunRequest, 'clientEventId'>,
|
|
options: WoodenFishRuntimeRequestOptions = {},
|
|
) {
|
|
const requestOptions = buildRuntimeGuestAuthOptions(options);
|
|
const requestPayload: WoodenFishCheckpointRunRequest = {
|
|
...payload,
|
|
clientEventId: `checkpoint-${runId}-${Date.now()}`,
|
|
};
|
|
|
|
return requestJson<WoodenFishRunResponse>(
|
|
`${WOODEN_FISH_RUNTIME_API_BASE}/runs/${encodeURIComponent(runId)}/checkpoint`,
|
|
{
|
|
method: 'POST',
|
|
headers: {
|
|
'content-type': 'application/json',
|
|
...buildRuntimeGuestHeaders(options),
|
|
},
|
|
body: JSON.stringify(requestPayload),
|
|
},
|
|
'保存敲木鱼进度失败',
|
|
{
|
|
retry: WOODEN_FISH_RUNTIME_WRITE_RETRY,
|
|
...requestOptions,
|
|
},
|
|
);
|
|
}
|
|
|
|
export async function finishWoodenFishRun(
|
|
runId: string,
|
|
payload: Omit<WoodenFishFinishRunRequest, 'clientEventId'>,
|
|
options: WoodenFishRuntimeRequestOptions = {},
|
|
) {
|
|
const requestOptions = buildRuntimeGuestAuthOptions(options);
|
|
const requestPayload: WoodenFishFinishRunRequest = {
|
|
...payload,
|
|
clientEventId: `finish-${runId}-${Date.now()}`,
|
|
};
|
|
|
|
return requestJson<WoodenFishRunResponse>(
|
|
`${WOODEN_FISH_RUNTIME_API_BASE}/runs/${encodeURIComponent(runId)}/finish`,
|
|
{
|
|
method: 'POST',
|
|
headers: {
|
|
'content-type': 'application/json',
|
|
...buildRuntimeGuestHeaders(options),
|
|
},
|
|
body: JSON.stringify(requestPayload),
|
|
},
|
|
'结束敲木鱼运行失败',
|
|
{
|
|
retry: WOODEN_FISH_RUNTIME_WRITE_RETRY,
|
|
...requestOptions,
|
|
},
|
|
);
|
|
}
|
|
|
|
export const woodenFishClient = {
|
|
checkpointRun: checkpointWoodenFishRun,
|
|
createSession: createWoodenFishCreationSession,
|
|
executeAction: executeWoodenFishCreationAction,
|
|
finishRun: finishWoodenFishRun,
|
|
getGalleryDetail: getWoodenFishGalleryDetail,
|
|
getSession: getWoodenFishCreationSession,
|
|
getWorkDetail: getWoodenFishWorkDetail,
|
|
listGallery: listWoodenFishGallery,
|
|
publishWork: publishWoodenFishWork,
|
|
startRun: startWoodenFishRuntimeRun,
|
|
};
|