feat: unify recommend anonymous runtime guest auth

- 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
This commit is contained in:
kdletters
2026-05-25 14:03:38 +08:00
parent 9a0bc6b129
commit c1dcf074bb
23 changed files with 820 additions and 236 deletions

View File

@@ -19,12 +19,16 @@ import type {
import { parseApiErrorMessage } from '../../../packages/shared/src/http';
import type { TextStreamOptions } from '../aiTypes';
import {
type ApiRequestOptions,
type ApiRetryOptions,
fetchWithApiAuth,
requestJson,
} from '../apiClient';
import { readVisualNovelRuntimeRunFromSse } from './visualNovelRuntimeSse';
import {
buildRuntimeGuestAuthOptions,
buildRuntimeGuestHeaders,
type RuntimeGuestRequestOptions,
} from '../runtimeGuestAuth';
const VISUAL_NOVEL_RUNTIME_API_BASE = '/api/runtime/visual-novel';
const VISUAL_NOVEL_RUNTIME_READ_RETRY: ApiRetryOptions = {
@@ -39,16 +43,11 @@ const VISUAL_NOVEL_RUNTIME_WRITE_RETRY: ApiRetryOptions = {
retryUnsafeMethods: true,
};
export type VisualNovelRuntimeStreamOptions = TextStreamOptions & {
onEvent?: (event: VisualNovelRuntimeStreamEvent) => void;
};
type VisualNovelRuntimeRequestOptions = Pick<
ApiRequestOptions,
| 'authImpact'
| 'skipRefresh'
| 'notifyAuthStateChange'
| 'clearAuthOnUnauthorized'
>;
export type VisualNovelRuntimeStreamOptions = TextStreamOptions &
RuntimeGuestRequestOptions & {
onEvent?: (event: VisualNovelRuntimeStreamEvent) => void;
};
type VisualNovelRuntimeRequestOptions = RuntimeGuestRequestOptions;
export type VisualNovelSaveArchiveResumeResponse =
ProfileSaveArchiveResumeResponse<
@@ -84,11 +83,20 @@ async function openVisualNovelRuntimeSsePost(
payload: unknown,
fallbackMessage: string,
signal?: AbortSignal,
options: RuntimeGuestRequestOptions = {},
) {
const response = await fetchWithApiAuth(url, {
...buildJsonInit('POST', payload),
signal,
});
const requestOptions = buildRuntimeGuestAuthOptions(options);
const response = await fetchWithApiAuth(
url,
{
...buildJsonInit('POST', payload),
headers: buildRuntimeGuestHeaders(options, {
'Content-Type': 'application/json',
}),
signal,
},
requestOptions,
);
if (!response.ok) {
const responseText = await response.text();
@@ -107,17 +115,20 @@ export async function startVisualNovelRun(
payload: VisualNovelStartRunRequest,
options: VisualNovelRuntimeRequestOptions = {},
) {
const requestOptions = buildRuntimeGuestAuthOptions(options);
return requestJson<VisualNovelRunResponse>(
`${VISUAL_NOVEL_RUNTIME_API_BASE}/works/${encodeURIComponent(profileId)}/runs`,
buildJsonInit('POST', payload),
{
...buildJsonInit('POST', payload),
headers: buildRuntimeGuestHeaders(options, {
'Content-Type': 'application/json',
}),
},
'启动视觉小说运行失败',
{
retry: VISUAL_NOVEL_RUNTIME_WRITE_RETRY,
timeoutMs: 15000,
authImpact: options.authImpact,
skipRefresh: options.skipRefresh,
notifyAuthStateChange: options.notifyAuthStateChange,
clearAuthOnUnauthorized: options.clearAuthOnUnauthorized,
...requestOptions,
},
);
}
@@ -154,6 +165,7 @@ export async function streamVisualNovelRuntimeAction(
payload,
'推进视觉小说失败',
options.signal,
options,
);
return readVisualNovelRuntimeRunFromSse(response, {