Merge codex/sse-stream-architecture into architecture adjustment
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { beforeEach, expect, test, vi } from 'vitest';
|
||||
import { afterEach, beforeEach, expect, test, vi } from 'vitest';
|
||||
|
||||
const requestJsonMock = vi.hoisted(() => vi.fn());
|
||||
|
||||
@@ -27,6 +27,10 @@ beforeEach(() => {
|
||||
requestJsonMock.mockReset();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
test('wooden fish creation keeps image2 generation requests alive long enough', async () => {
|
||||
await import('./woodenFishClient');
|
||||
|
||||
@@ -51,15 +55,85 @@ test('wooden fish list works uses creation works endpoint', async () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('wooden fish delete work uses creation works endpoint', async () => {
|
||||
test('wooden fish start run uses runtime guest json skeleton', async () => {
|
||||
const { woodenFishClient } = await import('./woodenFishClient');
|
||||
requestJsonMock.mockResolvedValueOnce({ items: [] });
|
||||
requestJsonMock.mockResolvedValueOnce({ run: { runId: 'run-1' } });
|
||||
|
||||
await woodenFishClient.deleteWork('wooden-fish-profile-1');
|
||||
await woodenFishClient.startRun('profile/1', {
|
||||
runtimeGuestToken: 'runtime-guest-token',
|
||||
});
|
||||
|
||||
expect(requestJsonMock).toHaveBeenCalledWith(
|
||||
'/api/creation/wooden-fish/works/wooden-fish-profile-1',
|
||||
{ method: 'DELETE' },
|
||||
'删除敲木鱼作品失败',
|
||||
'/api/runtime/wooden-fish/runs',
|
||||
expect.objectContaining({
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: 'Bearer runtime-guest-token',
|
||||
},
|
||||
body: JSON.stringify({ profileId: 'profile/1' }),
|
||||
}),
|
||||
'启动敲木鱼运行态失败',
|
||||
expect.objectContaining({
|
||||
retry: expect.objectContaining({ retryUnsafeMethods: true }),
|
||||
skipAuth: true,
|
||||
skipRefresh: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('wooden fish checkpoint run keeps client event id local to the client', async () => {
|
||||
const { woodenFishClient } = await import('./woodenFishClient');
|
||||
vi.spyOn(Date, 'now').mockReturnValue(1780000000000);
|
||||
requestJsonMock.mockResolvedValueOnce({ run: { runId: 'run-1' } });
|
||||
|
||||
await woodenFishClient.checkpointRun(
|
||||
'run/1',
|
||||
{
|
||||
totalTapCount: 12,
|
||||
wordCounters: [{ text: '功德', count: 3 }],
|
||||
},
|
||||
{ runtimeGuestToken: 'runtime-guest-token' },
|
||||
);
|
||||
|
||||
const [, init] = requestJsonMock.mock.calls[0];
|
||||
const body = JSON.parse(init.body);
|
||||
expect(requestJsonMock.mock.calls[0][0]).toBe(
|
||||
'/api/runtime/wooden-fish/runs/run%2F1/checkpoint',
|
||||
);
|
||||
expect(body).toEqual({
|
||||
totalTapCount: 12,
|
||||
wordCounters: [{ text: '功德', count: 3 }],
|
||||
clientEventId: 'checkpoint-run/1-1780000000000',
|
||||
});
|
||||
expect(body).not.toHaveProperty('runId');
|
||||
expect(body).not.toHaveProperty('checkpointAtMs');
|
||||
});
|
||||
|
||||
test('wooden fish finish run keeps finish event id local to the client', async () => {
|
||||
const { woodenFishClient } = await import('./woodenFishClient');
|
||||
vi.spyOn(Date, 'now').mockReturnValue(1780000000001);
|
||||
requestJsonMock.mockResolvedValueOnce({ run: { runId: 'run-1' } });
|
||||
|
||||
await woodenFishClient.finishRun(
|
||||
'run/1',
|
||||
{
|
||||
totalTapCount: 18,
|
||||
wordCounters: [{ text: '清净', count: 2 }],
|
||||
},
|
||||
{ runtimeGuestToken: 'runtime-guest-token' },
|
||||
);
|
||||
|
||||
const [, init] = requestJsonMock.mock.calls[0];
|
||||
const body = JSON.parse(init.body);
|
||||
expect(requestJsonMock.mock.calls[0][0]).toBe(
|
||||
'/api/runtime/wooden-fish/runs/run%2F1/finish',
|
||||
);
|
||||
expect(body).toEqual({
|
||||
totalTapCount: 18,
|
||||
wordCounters: [{ text: '清净', count: 2 }],
|
||||
clientEventId: 'finish-run/1-1780000000001',
|
||||
});
|
||||
expect(body).not.toHaveProperty('runId');
|
||||
expect(body).not.toHaveProperty('finishedAtMs');
|
||||
});
|
||||
|
||||
@@ -13,17 +13,14 @@ import type {
|
||||
WoodenFishWorkDetailResponse,
|
||||
WoodenFishWorkMutationResponse,
|
||||
WoodenFishWorkProfileResponse,
|
||||
WoodenFishWorksResponse,
|
||||
WoodenFishWorkspaceCreateRequest,
|
||||
WoodenFishWorksResponse,
|
||||
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';
|
||||
import { type RuntimeGuestRequestOptions } from '../runtimeGuestAuth';
|
||||
import { buildRuntimeApiPath, requestRuntimeJson } from '../runtimeRequest';
|
||||
|
||||
const WOODEN_FISH_API_BASE = '/api/creation/wooden-fish/sessions';
|
||||
const WOODEN_FISH_WORKS_API_BASE = '/api/creation/wooden-fish/works';
|
||||
@@ -58,8 +55,8 @@ export type {
|
||||
WoodenFishWorkDetailResponse,
|
||||
WoodenFishWorkMutationResponse,
|
||||
WoodenFishWorkProfileResponse,
|
||||
WoodenFishWorksResponse,
|
||||
WoodenFishWorkspaceCreateRequest,
|
||||
WoodenFishWorksResponse,
|
||||
};
|
||||
export type CreateWoodenFishSessionRequest = WoodenFishWorkspaceCreateRequest;
|
||||
export type WoodenFishSessionSnapshot = WoodenFishSessionSnapshotResponse;
|
||||
@@ -245,23 +242,14 @@ 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,
|
||||
},
|
||||
);
|
||||
return requestRuntimeJson<WoodenFishRunResponse>({
|
||||
url: buildRuntimeApiPath(WOODEN_FISH_RUNTIME_API_BASE, 'runs'),
|
||||
method: 'POST',
|
||||
jsonBody: { profileId },
|
||||
fallbackMessage: '启动敲木鱼运行态失败',
|
||||
retry: WOODEN_FISH_RUNTIME_WRITE_RETRY,
|
||||
requestOptions: options,
|
||||
});
|
||||
}
|
||||
|
||||
export async function checkpointWoodenFishRun(
|
||||
@@ -269,28 +257,24 @@ export async function checkpointWoodenFishRun(
|
||||
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,
|
||||
},
|
||||
);
|
||||
return requestRuntimeJson<WoodenFishRunResponse>({
|
||||
url: buildRuntimeApiPath(
|
||||
WOODEN_FISH_RUNTIME_API_BASE,
|
||||
'runs',
|
||||
runId,
|
||||
'checkpoint',
|
||||
),
|
||||
method: 'POST',
|
||||
jsonBody: requestPayload,
|
||||
fallbackMessage: '保存敲木鱼进度失败',
|
||||
retry: WOODEN_FISH_RUNTIME_WRITE_RETRY,
|
||||
requestOptions: options,
|
||||
});
|
||||
}
|
||||
|
||||
export async function finishWoodenFishRun(
|
||||
@@ -298,28 +282,24 @@ export async function finishWoodenFishRun(
|
||||
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,
|
||||
},
|
||||
);
|
||||
return requestRuntimeJson<WoodenFishRunResponse>({
|
||||
url: buildRuntimeApiPath(
|
||||
WOODEN_FISH_RUNTIME_API_BASE,
|
||||
'runs',
|
||||
runId,
|
||||
'finish',
|
||||
),
|
||||
method: 'POST',
|
||||
jsonBody: requestPayload,
|
||||
fallbackMessage: '结束敲木鱼运行失败',
|
||||
retry: WOODEN_FISH_RUNTIME_WRITE_RETRY,
|
||||
requestOptions: options,
|
||||
});
|
||||
}
|
||||
|
||||
export const woodenFishClient = {
|
||||
|
||||
Reference in New Issue
Block a user