merge master into codex/wechat-mini-program-virtual-payment

This commit is contained in:
kdletters
2026-05-30 16:46:11 +08:00
119 changed files with 3345 additions and 428 deletions

View File

@@ -14,7 +14,10 @@ import type {
GenerateCustomWorldProfileInput,
GenerateCustomWorldProfileOptions,
} from '../../packages/shared/src/contracts/runtime';
import { parseApiErrorMessage } from '../../packages/shared/src/http';
import {
appendApiErrorRequestId,
parseApiErrorMessage,
} from '../../packages/shared/src/http';
import type {
AIResponse,
Character,
@@ -93,7 +96,12 @@ async function requestPlainTextStream(
if (!response.ok) {
const responseText = await response.text();
throw new Error(parseApiErrorMessage(responseText, '流式请求失败'));
throw new Error(
appendApiErrorRequestId(
parseApiErrorMessage(responseText, '流式请求失败'),
response.headers.get('x-request-id'),
),
);
}
if (!response.body) {
@@ -488,7 +496,12 @@ export async function streamNpcChatTurn(
if (!response.ok) {
const responseText = await response.text();
throw new Error(parseApiErrorMessage(responseText, 'NPC 聊天续写失败'));
throw new Error(
appendApiErrorRequestId(
parseApiErrorMessage(responseText, 'NPC 聊天续写失败'),
response.headers.get('x-request-id'),
),
);
}
if (!response.body) {

View File

@@ -547,6 +547,7 @@ describe('apiClient', () => {
routeVersion: 'runtime.v2',
},
});
expect((capturedError as Error).message).toContain('requestId: req-body');
});
it('uses api error details.message as ApiClientError message', async () => {

View File

@@ -671,9 +671,14 @@ async function buildApiClientError(
) {
const responseText = await response.text();
const parsedError = parseApiErrorShape(responseText);
const requestId =
parsedError?.meta.requestId ??
response.headers.get(REQUEST_ID_HEADER) ??
undefined;
const baseMessage = parseApiErrorMessage(responseText, fallbackMessage);
return new ApiClientError({
message: parseApiErrorMessage(responseText, fallbackMessage),
message: requestId ? `${baseMessage}requestId: ${requestId}` : baseMessage,
status: response.status,
code: parsedError?.code ?? `HTTP_${response.status || 0}`,
details: parsedError?.details ?? null,
@@ -682,10 +687,7 @@ async function buildApiClientError(
parsedError?.meta.apiVersion ??
response.headers.get(API_VERSION_HEADER) ??
API_VERSION,
requestId:
parsedError?.meta.requestId ??
response.headers.get(REQUEST_ID_HEADER) ??
undefined,
requestId,
routeVersion:
parsedError?.meta.routeVersion ??
response.headers.get(ROUTE_VERSION_HEADER) ??

View File

@@ -1,4 +1,7 @@
import { parseApiErrorMessage } from '../../packages/shared/src/http';
import {
appendApiErrorRequestId,
parseApiErrorMessage,
} from '../../packages/shared/src/http';
import {
ApiClientError,
BACKGROUND_AUTH_REQUEST_OPTIONS,
@@ -338,7 +341,12 @@ export async function readAssetBytes(
if (!response.ok) {
const message = await response
.text()
.then((text) => parseApiErrorMessage(text, '读取资源内容失败'))
.then((text) =>
appendApiErrorRequestId(
parseApiErrorMessage(text, '读取资源内容失败'),
response.headers.get('x-request-id'),
),
)
.catch(() => '');
throw new Error(message || '读取资源内容失败');
}

View File

@@ -91,14 +91,12 @@ describe('authService', () => {
user: {
id: 'user_1',
publicUserCode: 'SY-00000001',
username: 'phone_00000001',
displayName: '138****8000',
avatarUrl: null,
phoneNumberMasked: '138****8000',
loginMethod: 'password',
bindingStatus: 'active',
wechatBound: false,
createdAt: '2026-05-01T00:00:00.000Z',
},
});
@@ -128,14 +126,12 @@ describe('authService', () => {
user: {
id: 'user_1',
publicUserCode: 'SY-00000001',
username: 'phone_00000001',
displayName: '旅人甲',
avatarUrl: 'data:image/png;base64,AAAA',
phoneNumberMasked: '138****8000',
loginMethod: 'password',
bindingStatus: 'active',
wechatBound: false,
createdAt: '2026-05-01T00:00:00.000Z',
},
});
@@ -167,14 +163,12 @@ describe('authService', () => {
user: {
id: 'user_1',
publicUserCode: 'SY-00000001',
username: 'phone_00000001',
displayName: '旅人甲',
avatarUrl: null,
phoneNumberMasked: '138****8000',
loginMethod: 'password',
bindingStatus: 'active',
wechatBound: false,
createdAt: '2026-05-01T00:00:00.000Z',
},
});
@@ -254,14 +248,12 @@ describe('authService', () => {
user: {
id: 'user_phone',
publicUserCode: 'SY-00000004',
username: '138****8000',
displayName: '138****8000',
avatarUrl: null,
phoneNumberMasked: '138****8000',
loginMethod: 'phone',
bindingStatus: 'active',
wechatBound: false,
createdAt: '2026-05-01T00:00:00.000Z',
},
});
@@ -271,7 +263,7 @@ describe('authService', () => {
'spring-2026',
);
expect(response.user.username).toBe('138****8000');
expect(response.user.displayName).toBe('138****8000');
expect(apiClientMocks.requestJson).toHaveBeenCalledWith(
'/api/auth/phone/login',
expect.objectContaining({
@@ -333,14 +325,12 @@ describe('authService', () => {
user: {
id: 'user_wechat',
publicUserCode: 'SY-00000005',
username: '138****8000',
displayName: '138****8000',
avatarUrl: null,
phoneNumberMasked: '138****8000',
loginMethod: 'wechat',
bindingStatus: 'active',
wechatBound: true,
createdAt: '2026-05-01T00:00:00.000Z',
},
});
@@ -356,14 +346,12 @@ describe('authService', () => {
user: {
id: 'user_phone',
publicUserCode: 'SY-00000006',
username: '139****9000',
displayName: '139****9000',
avatarUrl: null,
phoneNumberMasked: '139****9000',
loginMethod: 'phone',
bindingStatus: 'active',
wechatBound: false,
createdAt: '2026-05-01T00:00:00.000Z',
},
});

View File

@@ -1,4 +1,4 @@
import { parseApiErrorMessage } from '../../../packages/shared/src/http';
import { appendApiErrorRequestId, parseApiErrorMessage } from '../../../packages/shared/src/http';
import type { TextStreamOptions } from '../aiTypes';
import {
type ApiRetryOptions,
@@ -64,7 +64,7 @@ async function openCreationAgentSsePost(
if (!response.ok) {
const responseText = await response.text();
throw new Error(parseApiErrorMessage(responseText, fallbackMessage));
throw new Error(appendApiErrorRequestId(parseApiErrorMessage(responseText, fallbackMessage), response.headers.get('x-request-id')));
}
if (!response.body) {

View File

@@ -7,7 +7,7 @@ import type {
CreativeDraftEditStreamRequest,
StreamCreativeAgentMessageRequest,
} from '../../../packages/shared/src/contracts/creativeAgent';
import { parseApiErrorMessage } from '../../../packages/shared/src/http';
import { appendApiErrorRequestId, parseApiErrorMessage } from '../../../packages/shared/src/http';
import type { TextStreamOptions } from '../aiTypes';
import { fetchWithApiAuth, requestJson } from '../apiClient';
import {
@@ -42,7 +42,7 @@ async function openCreativeAgentSsePost(
if (!response.ok) {
const responseText = await response.text();
throw new Error(parseApiErrorMessage(responseText, fallbackMessage));
throw new Error(appendApiErrorRequestId(parseApiErrorMessage(responseText, fallbackMessage), response.headers.get('x-request-id')));
}
if (!response.body) {

View File

@@ -1,4 +1,4 @@
import { parseApiErrorMessage } from '../../../packages/shared/src/http';
import { appendApiErrorRequestId, parseApiErrorMessage } from '../../../packages/shared/src/http';
import { fetchWithApiAuth, requestJson } from '../apiClient';
export async function requestRpgCreationPostJson<T>(
@@ -32,7 +32,7 @@ export async function openRpgCreationSsePost(
if (!response.ok) {
const responseText = await response.text();
throw new Error(parseApiErrorMessage(responseText, fallbackMessage));
throw new Error(appendApiErrorRequestId(parseApiErrorMessage(responseText, fallbackMessage), response.headers.get('x-request-id')));
}
if (!response.body) {

View File

@@ -16,7 +16,7 @@ import type {
VisualNovelStartRunRequest,
VisualNovelWorksResponse,
} from '../../../packages/shared/src/contracts/visualNovel';
import { parseApiErrorMessage } from '../../../packages/shared/src/http';
import { appendApiErrorRequestId, parseApiErrorMessage } from '../../../packages/shared/src/http';
import type { TextStreamOptions } from '../aiTypes';
import {
type ApiRetryOptions,
@@ -100,7 +100,7 @@ async function openVisualNovelRuntimeSsePost(
if (!response.ok) {
const responseText = await response.text();
throw new Error(parseApiErrorMessage(responseText, fallbackMessage));
throw new Error(appendApiErrorRequestId(parseApiErrorMessage(responseText, fallbackMessage), response.headers.get('x-request-id')));
}
if (!response.body) {