Close DDD refactor and remove generated asset proxy
This commit is contained in:
@@ -10,6 +10,7 @@ import type {
|
||||
StoryOption,
|
||||
} from '../types';
|
||||
import { AnimationState, WorldType } from '../types';
|
||||
import type { StoryRuntimeProjectionResponse } from '../../packages/shared/src/contracts/story';
|
||||
|
||||
const {
|
||||
connectivityError,
|
||||
@@ -101,6 +102,7 @@ function createContext(
|
||||
): StoryGenerationContext {
|
||||
return {
|
||||
runtimeSessionId: 'runtime-main',
|
||||
storySessionId: 'storysess-main',
|
||||
runtimeActionVersion: 3,
|
||||
playerHp: 30,
|
||||
playerMaxHp: 40,
|
||||
@@ -115,6 +117,8 @@ function createContext(
|
||||
sceneName: 'Forest Trail',
|
||||
sceneDescription: 'A quiet mountain path.',
|
||||
pendingSceneEncounter: false,
|
||||
observeSignsRequested: false,
|
||||
recentActionResult: null,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
@@ -424,6 +428,71 @@ function createApiEnvelopeResponse(data: unknown) {
|
||||
} as Response;
|
||||
}
|
||||
|
||||
type RuntimeProjectionOverrides = Omit<
|
||||
Partial<StoryRuntimeProjectionResponse>,
|
||||
'storySession'
|
||||
> & {
|
||||
storySession?: Partial<StoryRuntimeProjectionResponse['storySession']>;
|
||||
};
|
||||
|
||||
function createRuntimeProjection(
|
||||
overrides: RuntimeProjectionOverrides = {},
|
||||
): StoryRuntimeProjectionResponse {
|
||||
const storySession = {
|
||||
storySessionId: 'storysess-main',
|
||||
runtimeSessionId: 'runtime-main',
|
||||
actorUserId: 'user-main',
|
||||
worldProfileId: 'profile-main',
|
||||
initialPrompt: '进入山路',
|
||||
openingSummary: null,
|
||||
latestNarrativeText: '山路尽头传来新的动静。',
|
||||
latestChoiceFunctionId: null,
|
||||
status: 'active',
|
||||
version: 3,
|
||||
createdAt: '2026-04-08T00:00:00.000Z',
|
||||
updatedAt: '2026-04-08T00:00:01.000Z',
|
||||
...(overrides.storySession ?? {}),
|
||||
} satisfies StoryRuntimeProjectionResponse['storySession'];
|
||||
|
||||
return {
|
||||
storySession,
|
||||
storyEvents: overrides.storyEvents ?? [],
|
||||
serverVersion: overrides.serverVersion ?? storySession.version,
|
||||
gameState: {
|
||||
runtimeSessionId: storySession.runtimeSessionId,
|
||||
storySessionId: storySession.storySessionId,
|
||||
runtimeActionVersion: overrides.serverVersion ?? storySession.version,
|
||||
currentScene: 'Story',
|
||||
playerEquipment: { weapon: null, armor: null, relic: null },
|
||||
...(overrides.gameState ?? {}),
|
||||
},
|
||||
actor: overrides.actor ?? {
|
||||
hp: 30,
|
||||
maxHp: 40,
|
||||
mana: 12,
|
||||
maxMana: 20,
|
||||
currency: 0,
|
||||
currencyText: '0 铜钱',
|
||||
},
|
||||
inventory: overrides.inventory ?? {
|
||||
backpackItems: [],
|
||||
equipmentSlots: [],
|
||||
forgeRecipes: [],
|
||||
},
|
||||
options: overrides.options ?? [],
|
||||
status: overrides.status ?? {
|
||||
inBattle: false,
|
||||
npcInteractionActive: false,
|
||||
currentNpcBattleMode: null,
|
||||
currentNpcBattleOutcome: null,
|
||||
},
|
||||
currentNarrativeText:
|
||||
overrides.currentNarrativeText ?? storySession.latestNarrativeText,
|
||||
actionResultText: overrides.actionResultText ?? null,
|
||||
toast: overrides.toast ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
function createSseResponse(text: string) {
|
||||
const encoder = new TextEncoder();
|
||||
const chunks = [
|
||||
@@ -478,14 +547,26 @@ describe('ai runtime client orchestration', () => {
|
||||
streamPlainTextCompletionMock.mockReset();
|
||||
});
|
||||
|
||||
it('requests initial story from the runtime api server', async () => {
|
||||
it('requests initial story from the story session projection', async () => {
|
||||
const availableOptions = [createStoryOption()];
|
||||
fetchMock.mockResolvedValue(
|
||||
createApiEnvelopeResponse({
|
||||
storyText: '山路尽头传来新的动静。',
|
||||
options: availableOptions,
|
||||
encounter: null,
|
||||
}),
|
||||
createApiEnvelopeResponse(
|
||||
createRuntimeProjection({
|
||||
options: availableOptions.map((option) => ({
|
||||
functionId: option.functionId,
|
||||
actionText: option.actionText,
|
||||
detailText: null,
|
||||
scope: 'story',
|
||||
payload: null,
|
||||
enabled: true,
|
||||
reason: null,
|
||||
})),
|
||||
currentNarrativeText: '山路尽头传来新的动静。',
|
||||
storySession: {
|
||||
latestNarrativeText: '山路尽头传来新的动静。',
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
const response = await generateInitialStory(
|
||||
@@ -497,21 +578,16 @@ describe('ai runtime client orchestration', () => {
|
||||
);
|
||||
|
||||
expect(fetchMock).toHaveBeenCalledWith(
|
||||
'/api/runtime/story/initial',
|
||||
'/api/story/sessions/storysess-main/runtime-projection',
|
||||
expect.objectContaining({
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
sessionId: 'runtime-main',
|
||||
clientVersion: 3,
|
||||
requestOptions: { availableOptions },
|
||||
}),
|
||||
method: 'GET',
|
||||
}),
|
||||
);
|
||||
expect(response.storyText).toBe('山路尽头传来新的动静。');
|
||||
expect(response.options).toEqual(availableOptions);
|
||||
});
|
||||
|
||||
it('requests next story step from the runtime api server', async () => {
|
||||
it('requests next story step from the story session action endpoint', async () => {
|
||||
const availableOptions = [
|
||||
createStoryOption({
|
||||
functionId: 'idle_explore_forward',
|
||||
@@ -521,9 +597,24 @@ describe('ai runtime client orchestration', () => {
|
||||
];
|
||||
fetchMock.mockResolvedValue(
|
||||
createApiEnvelopeResponse({
|
||||
storyText: '林间重新安静下来,你听见远处的风声。',
|
||||
encounter: null,
|
||||
options: availableOptions,
|
||||
projection: createRuntimeProjection({
|
||||
serverVersion: 4,
|
||||
currentNarrativeText: '林间重新安静下来,你听见远处的风声。',
|
||||
storySession: {
|
||||
latestNarrativeText: '林间重新安静下来,你听见远处的风声。',
|
||||
latestChoiceFunctionId: 'idle_explore_forward',
|
||||
version: 4,
|
||||
},
|
||||
options: availableOptions.map((option) => ({
|
||||
functionId: option.functionId,
|
||||
actionText: option.actionText,
|
||||
detailText: null,
|
||||
scope: 'story',
|
||||
payload: null,
|
||||
enabled: true,
|
||||
reason: null,
|
||||
})),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -533,19 +624,27 @@ describe('ai runtime client orchestration', () => {
|
||||
monsters,
|
||||
storyHistory,
|
||||
'继续向前',
|
||||
context,
|
||||
{
|
||||
...context,
|
||||
lastFunctionId: 'idle_explore_forward',
|
||||
},
|
||||
{ availableOptions },
|
||||
);
|
||||
|
||||
expect(fetchMock).toHaveBeenCalledWith(
|
||||
'/api/runtime/story/continue',
|
||||
'/api/story/sessions/storysess-main/actions/resolve',
|
||||
expect.objectContaining({
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
sessionId: 'runtime-main',
|
||||
storySessionId: 'storysess-main',
|
||||
clientVersion: 3,
|
||||
choice: '继续向前',
|
||||
requestOptions: { availableOptions },
|
||||
functionId: 'idle_explore_forward',
|
||||
actionText: '继续向前',
|
||||
payload: {
|
||||
optionText: '继续向前',
|
||||
observeSignsRequested: false,
|
||||
recentActionResult: null,
|
||||
},
|
||||
}),
|
||||
}),
|
||||
);
|
||||
@@ -972,11 +1071,11 @@ describe('ai runtime client orchestration', () => {
|
||||
});
|
||||
|
||||
const sceneImageCalls = fetchMock.mock.calls.filter(
|
||||
([url]) => url === '/api/custom-world/scene-image',
|
||||
([url]) => url === '/api/runtime/custom-world/scene-image',
|
||||
);
|
||||
expect(sceneImageCalls).toHaveLength(1);
|
||||
expect(sceneImageCalls[0]).toEqual([
|
||||
'/api/custom-world/scene-image',
|
||||
'/api/runtime/custom-world/scene-image',
|
||||
expect.objectContaining({
|
||||
method: 'POST',
|
||||
headers: expect.objectContaining({
|
||||
|
||||
Reference in New Issue
Block a user