Enrich external API failure audit metadata

This commit is contained in:
kdletters
2026-05-28 15:42:46 +08:00
parent 2cd2b9704b
commit f1fb92aa29
40 changed files with 315 additions and 152 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

@@ -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) {