@vellumai/assistant 0.11.4-dev.202608192308.82015f7 → 0.11.4-dev.202608200019.f77fe6a
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -145,7 +145,7 @@
|
|
|
145
145
|
"scope": "client",
|
|
146
146
|
"key": "fork-from-message",
|
|
147
147
|
"label": "Fork from Message",
|
|
148
|
-
"description": "Show the 'Fork from here'
|
|
148
|
+
"description": "Show the fork affordance: 'Fork from here' on messages and 'Fork Conversation' in the thread menu. Staff-only: the client also requires the viewer to be Vellum staff, so enabling this for a non-staff account still shows nothing.",
|
|
149
149
|
"defaultEnabled": false
|
|
150
150
|
},
|
|
151
151
|
{
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests that an unclassified 4xx provider rejection logs the upstream
|
|
3
|
+
* response body (bounded) plus the offending request content-part coordinates,
|
|
4
|
+
* so the failing message can be identified from the daemon log alone.
|
|
5
|
+
*/
|
|
6
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
7
|
+
|
|
8
|
+
import type { AssistantEvent } from "../../api/index.js";
|
|
9
|
+
import { ProviderError } from "../../util/errors.js";
|
|
10
|
+
import * as loggerModule from "../../util/logger.js";
|
|
11
|
+
import type {
|
|
12
|
+
EventHandlerDeps,
|
|
13
|
+
EventHandlerState,
|
|
14
|
+
} from "../conversation-agent-loop-handlers.js";
|
|
15
|
+
import {
|
|
16
|
+
buildProviderRejectionLogFields,
|
|
17
|
+
MAX_LOGGED_UPSTREAM_BODY_CHARS,
|
|
18
|
+
} from "../provider-rejection-log-fields.js";
|
|
19
|
+
|
|
20
|
+
const logRecords: Record<string, unknown>[] = [];
|
|
21
|
+
|
|
22
|
+
mock.module("../../util/logger.js", () => ({
|
|
23
|
+
...loggerModule,
|
|
24
|
+
getLogger: () => ({
|
|
25
|
+
error: (record: Record<string, unknown>) => {
|
|
26
|
+
logRecords.push(record);
|
|
27
|
+
},
|
|
28
|
+
warn: () => {},
|
|
29
|
+
info: () => {},
|
|
30
|
+
debug: () => {},
|
|
31
|
+
trace: () => {},
|
|
32
|
+
}),
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
// The handlers module binds its logger at import time, so it is loaded
|
|
36
|
+
// dynamically once the logger stub is installed above.
|
|
37
|
+
const { createEventHandlerState, dispatchAgentEvent } =
|
|
38
|
+
await import("../conversation-agent-loop-handlers.js");
|
|
39
|
+
|
|
40
|
+
function createDeps(): EventHandlerDeps {
|
|
41
|
+
return {
|
|
42
|
+
ctx: {
|
|
43
|
+
conversationId: "conv-provider-rejection",
|
|
44
|
+
provider: { name: "openai" },
|
|
45
|
+
streamThinking: false,
|
|
46
|
+
emitActivityState: () => {},
|
|
47
|
+
markWorkspaceTopLevelDirty: () => {},
|
|
48
|
+
currentTurnSurfaces: [],
|
|
49
|
+
} as unknown as EventHandlerDeps["ctx"],
|
|
50
|
+
onEvent: (_msg: AssistantEvent) => {},
|
|
51
|
+
reqId: "req-provider-rejection",
|
|
52
|
+
isFirstMessage: false,
|
|
53
|
+
shouldGenerateTitle: false,
|
|
54
|
+
rlog: new Proxy({} as Record<string, unknown>, {
|
|
55
|
+
get: () => () => {},
|
|
56
|
+
}) as unknown as EventHandlerDeps["rlog"],
|
|
57
|
+
turnChannelContext: {
|
|
58
|
+
userMessageChannel: "vellum",
|
|
59
|
+
assistantMessageChannel: "vellum",
|
|
60
|
+
} as EventHandlerDeps["turnChannelContext"],
|
|
61
|
+
turnInterfaceContext: {
|
|
62
|
+
userMessageInterface: "macos",
|
|
63
|
+
assistantMessageInterface: "macos",
|
|
64
|
+
} as EventHandlerDeps["turnInterfaceContext"],
|
|
65
|
+
} as EventHandlerDeps;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
describe("buildProviderRejectionLogFields", () => {
|
|
69
|
+
test("carries the verbatim upstream body", () => {
|
|
70
|
+
// GIVEN a provider rejection that captured the upstream 400 body
|
|
71
|
+
const rawBody = JSON.stringify({
|
|
72
|
+
error: {
|
|
73
|
+
message: "Invalid body: failed to parse JSON value from the request.",
|
|
74
|
+
type: "invalid_request_error",
|
|
75
|
+
code: "invalid_body",
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
const error = new ProviderError(
|
|
79
|
+
"API error (400): Invalid body",
|
|
80
|
+
"openai",
|
|
81
|
+
400,
|
|
82
|
+
{
|
|
83
|
+
rawBody,
|
|
84
|
+
apiErrorCode: "invalid_body",
|
|
85
|
+
apiErrorType: "invalid_request_error",
|
|
86
|
+
requestId: "req_abc123",
|
|
87
|
+
},
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
// WHEN diagnostic log fields are built for it
|
|
91
|
+
const fields = buildProviderRejectionLogFields(error);
|
|
92
|
+
|
|
93
|
+
// THEN the body and the upstream metadata ride the record
|
|
94
|
+
expect(fields.upstreamErrorBody).toBe(rawBody);
|
|
95
|
+
expect(fields.upstreamErrorBodyTruncated).toBeUndefined();
|
|
96
|
+
expect(fields.apiErrorCode).toBe("invalid_body");
|
|
97
|
+
expect(fields.apiErrorType).toBe("invalid_request_error");
|
|
98
|
+
expect(fields.requestId).toBe("req_abc123");
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("bounds the body to 4 KB and flags the truncation", () => {
|
|
102
|
+
// GIVEN an upstream body larger than the logged ceiling
|
|
103
|
+
const rawBody = "x".repeat(MAX_LOGGED_UPSTREAM_BODY_CHARS + 500);
|
|
104
|
+
const error = new ProviderError("API error (400): huge", "openai", 400, {
|
|
105
|
+
rawBody,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// WHEN diagnostic log fields are built for it
|
|
109
|
+
const fields = buildProviderRejectionLogFields(error);
|
|
110
|
+
|
|
111
|
+
// THEN the logged body stops at the ceiling
|
|
112
|
+
expect(fields.upstreamErrorBody).toHaveLength(
|
|
113
|
+
MAX_LOGGED_UPSTREAM_BODY_CHARS,
|
|
114
|
+
);
|
|
115
|
+
// AND the record says the body was cut
|
|
116
|
+
expect(fields.upstreamErrorBodyTruncated).toBe(true);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("parses the offending content-part coordinates for oversized strings", () => {
|
|
120
|
+
// GIVEN an oversized-content-part rejection naming the request position
|
|
121
|
+
const error = new ProviderError(
|
|
122
|
+
"API error (400): Invalid 'input[191].content[1].text': string too long. Expected a string with maximum length 10485760, but got a string with length 11436754 instead.",
|
|
123
|
+
"openai",
|
|
124
|
+
400,
|
|
125
|
+
{ apiErrorCode: "string_above_max_length" },
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
// WHEN diagnostic log fields are built for it
|
|
129
|
+
const fields = buildProviderRejectionLogFields(error);
|
|
130
|
+
|
|
131
|
+
// THEN the message and content indices are logged for a follow-up query
|
|
132
|
+
expect(fields.offendingMessageIndex).toBe(191);
|
|
133
|
+
expect(fields.offendingContentIndex).toBe(1);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("parses coordinates out of the Chat Completions `messages[N]` shape", () => {
|
|
137
|
+
// GIVEN a rejection using the Chat Completions request shape
|
|
138
|
+
const error = new ProviderError(
|
|
139
|
+
"API error (400): Invalid 'messages[7].content[0].text': string too long.",
|
|
140
|
+
"openai",
|
|
141
|
+
400,
|
|
142
|
+
{ apiErrorCode: "string_above_max_length" },
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
// WHEN diagnostic log fields are built for it
|
|
146
|
+
const fields = buildProviderRejectionLogFields(error);
|
|
147
|
+
|
|
148
|
+
// THEN the same coordinates are extracted
|
|
149
|
+
expect(fields.offendingMessageIndex).toBe(7);
|
|
150
|
+
expect(fields.offendingContentIndex).toBe(0);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("leaves coordinates absent for codes that carry no content-part pointer", () => {
|
|
154
|
+
// GIVEN a rejection whose code is outside the narrow tap
|
|
155
|
+
const error = new ProviderError(
|
|
156
|
+
"API error (400): Invalid 'input[3].content[0].text': something else.",
|
|
157
|
+
"openai",
|
|
158
|
+
400,
|
|
159
|
+
{ apiErrorCode: "model_not_found" },
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
// WHEN diagnostic log fields are built for it
|
|
163
|
+
const fields = buildProviderRejectionLogFields(error);
|
|
164
|
+
|
|
165
|
+
// THEN no coordinates are inferred from a coincidental match
|
|
166
|
+
expect(fields.offendingMessageIndex).toBeUndefined();
|
|
167
|
+
expect(fields.offendingContentIndex).toBeUndefined();
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test("scrubs secrets out of the upstream body", () => {
|
|
171
|
+
// GIVEN an upstream body that echoed back a credential
|
|
172
|
+
const projectKey = `sk-proj-${"A".repeat(48)}`;
|
|
173
|
+
const error = new ProviderError("API error (400): bad key", "openai", 400, {
|
|
174
|
+
rawBody: JSON.stringify({
|
|
175
|
+
error: { message: `Incorrect API key provided: ${projectKey}` },
|
|
176
|
+
headers: { authorization: "Bearer some-token-value" },
|
|
177
|
+
}),
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// WHEN diagnostic log fields are built for it
|
|
181
|
+
const fields = buildProviderRejectionLogFields(error);
|
|
182
|
+
|
|
183
|
+
// THEN the credential never reaches the log record
|
|
184
|
+
expect(fields.upstreamErrorBody).not.toContain(projectKey);
|
|
185
|
+
expect(fields.upstreamErrorBody).not.toContain("some-token-value");
|
|
186
|
+
expect(fields.upstreamErrorBody).toContain("[REDACTED]");
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test("reports a null body for errors that are not provider errors", () => {
|
|
190
|
+
// GIVEN a non-provider error
|
|
191
|
+
const error = new Error("socket hang up");
|
|
192
|
+
|
|
193
|
+
// WHEN diagnostic log fields are built for it
|
|
194
|
+
const fields = buildProviderRejectionLogFields(error);
|
|
195
|
+
|
|
196
|
+
// THEN the body is explicitly null rather than missing
|
|
197
|
+
expect(fields.upstreamErrorBody).toBeNull();
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
describe("unclassified 4xx log line", () => {
|
|
202
|
+
let state: EventHandlerState;
|
|
203
|
+
|
|
204
|
+
beforeEach(() => {
|
|
205
|
+
logRecords.length = 0;
|
|
206
|
+
state = createEventHandlerState();
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test("includes the upstream response body", async () => {
|
|
210
|
+
// GIVEN an unclassified 4xx provider rejection carrying the upstream body
|
|
211
|
+
const rawBody = JSON.stringify({
|
|
212
|
+
error: {
|
|
213
|
+
message:
|
|
214
|
+
"Invalid body: failed to parse JSON value from the request. Common errors include trailing commas.",
|
|
215
|
+
type: "invalid_request_error",
|
|
216
|
+
code: "invalid_body",
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
const error = new ProviderError(
|
|
220
|
+
"API error (400): Invalid body: failed to parse JSON value from the request.",
|
|
221
|
+
"openai",
|
|
222
|
+
400,
|
|
223
|
+
{ rawBody, apiErrorCode: "invalid_body" },
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
// WHEN the agent loop dispatches the error event
|
|
227
|
+
await dispatchAgentEvent(state, createDeps(), { type: "error", error });
|
|
228
|
+
|
|
229
|
+
// THEN the log record carries the body alongside the existing fields
|
|
230
|
+
const record = logRecords.find((r) => r.upstreamErrorBody !== undefined);
|
|
231
|
+
expect(record).toBeDefined();
|
|
232
|
+
expect(record?.upstreamErrorBody).toBe(rawBody);
|
|
233
|
+
expect(record?.conversationId).toBe("conv-provider-rejection");
|
|
234
|
+
expect(record?.statusCode).toBe(400);
|
|
235
|
+
expect(record?.provider).toBe("openai");
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test("scrubs secrets out of the error message it logs", async () => {
|
|
239
|
+
// GIVEN a rejection whose normalized message echoes a credential
|
|
240
|
+
const error = new ProviderError(
|
|
241
|
+
"API error (400): rejected request with Authorization: Bearer some-token-value",
|
|
242
|
+
"openai",
|
|
243
|
+
400,
|
|
244
|
+
{ rawBody: "{}" },
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
// WHEN the agent loop dispatches the error event
|
|
248
|
+
await dispatchAgentEvent(state, createDeps(), { type: "error", error });
|
|
249
|
+
|
|
250
|
+
// THEN the credential is redacted out of the logged message
|
|
251
|
+
const record = logRecords.find((r) => r.upstreamErrorBody !== undefined);
|
|
252
|
+
expect(record?.errorMessage).not.toContain("some-token-value");
|
|
253
|
+
expect(record?.errorMessage).toContain("Bearer [REDACTED]");
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test("bounds a huge upstream body to 4 KB", async () => {
|
|
257
|
+
// GIVEN an unclassified 4xx rejection whose body exceeds the ceiling
|
|
258
|
+
const error = new ProviderError("API error (400): huge", "openai", 400, {
|
|
259
|
+
rawBody: "y".repeat(MAX_LOGGED_UPSTREAM_BODY_CHARS * 3),
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
// WHEN the agent loop dispatches the error event
|
|
263
|
+
await dispatchAgentEvent(state, createDeps(), { type: "error", error });
|
|
264
|
+
|
|
265
|
+
// THEN the logged body is capped
|
|
266
|
+
const record = logRecords.find((r) => r.upstreamErrorBody !== undefined);
|
|
267
|
+
expect(record?.upstreamErrorBody).toHaveLength(
|
|
268
|
+
MAX_LOGGED_UPSTREAM_BODY_CHARS,
|
|
269
|
+
);
|
|
270
|
+
expect(record?.upstreamErrorBodyTruncated).toBe(true);
|
|
271
|
+
});
|
|
272
|
+
});
|
|
@@ -89,6 +89,7 @@ import {
|
|
|
89
89
|
} from "../usage/pricing.js";
|
|
90
90
|
import { ProviderError } from "../util/errors.js";
|
|
91
91
|
import { faviconUrlForDomain } from "../util/favicon.js";
|
|
92
|
+
import { redactLogString } from "../util/log-redact.js";
|
|
92
93
|
import { getLogger } from "../util/logger.js";
|
|
93
94
|
import { withSqliteRetry } from "../util/sqlite-retry.js";
|
|
94
95
|
import type { DirectiveRequest } from "./assistant-attachments.js";
|
|
@@ -143,6 +144,7 @@ import type {
|
|
|
143
144
|
WebSearchResultItem,
|
|
144
145
|
} from "./message-types/web-activity.js";
|
|
145
146
|
import { referenceMediaBlocksForPersist } from "./persist-media-references.js";
|
|
147
|
+
import { buildProviderRejectionLogFields } from "./provider-rejection-log-fields.js";
|
|
146
148
|
import { turnOrRestingTrust } from "./trust-context-types.js";
|
|
147
149
|
import type { TurnLatencyTracker } from "./turn-latency-tracker.js";
|
|
148
150
|
|
|
@@ -2708,7 +2710,8 @@ function handleError(
|
|
|
2708
2710
|
event.error instanceof ProviderError
|
|
2709
2711
|
? event.error.provider
|
|
2710
2712
|
: undefined,
|
|
2711
|
-
errorMessage: event.error.message,
|
|
2713
|
+
errorMessage: redactLogString(event.error.message),
|
|
2714
|
+
...buildProviderRejectionLogFields(event.error),
|
|
2712
2715
|
},
|
|
2713
2716
|
"Provider rejected request with unclassified 4xx error",
|
|
2714
2717
|
);
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Log-field builder for provider rejections that carry no actionable
|
|
3
|
+
* classification of their own (unclassified 4xx).
|
|
4
|
+
*
|
|
5
|
+
* The extracted fields on `ProviderError` (`apiErrorCode`, `message`) drop the
|
|
6
|
+
* detail an operator needs to find the offending request: which request field,
|
|
7
|
+
* which message, which byte pattern. The verbatim upstream body carries it, so
|
|
8
|
+
* it rides the log record, bounded so a large provider error page cannot flood
|
|
9
|
+
* the log stream.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { ProviderError } from "../util/errors.js";
|
|
13
|
+
import { redactLogString } from "../util/log-redact.js";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Ceiling for the logged upstream body. Bodies are already capped at 16 KiB
|
|
17
|
+
* when captured (`providers/openai/api-error-normalization.ts`); this second,
|
|
18
|
+
* tighter bound is what a log record carries.
|
|
19
|
+
*/
|
|
20
|
+
export const MAX_LOGGED_UPSTREAM_BODY_CHARS = 4096;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Upstream error codes whose message names the offending request content part,
|
|
24
|
+
* e.g. `Invalid 'input[191].content[1].text': string too long`. Kept narrow:
|
|
25
|
+
* codes outside this set have no such pointer, so parsing their message would
|
|
26
|
+
* only produce coincidental matches.
|
|
27
|
+
*/
|
|
28
|
+
const CONTENT_PART_POINTER_CODES = new Set([
|
|
29
|
+
"string_above_max_length",
|
|
30
|
+
"invalid_body",
|
|
31
|
+
]);
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Positional pointer into the request payload. `input[N]` is the Responses API
|
|
35
|
+
* shape, `messages[N]` the Chat Completions shape; both index the request's
|
|
36
|
+
* message array in send order, so `N` is an offset into the conversation
|
|
37
|
+
* history that produced the request.
|
|
38
|
+
*/
|
|
39
|
+
const CONTENT_PART_POINTER = /\b(?:input|messages)\[(\d+)\]\.content\[(\d+)\]/i;
|
|
40
|
+
|
|
41
|
+
export interface ProviderRejectionLogFields {
|
|
42
|
+
/** Verbatim upstream non-2xx body, truncated to the logged ceiling. */
|
|
43
|
+
upstreamErrorBody: string | null;
|
|
44
|
+
/** True when the body was cut to fit the ceiling. */
|
|
45
|
+
upstreamErrorBodyTruncated?: boolean;
|
|
46
|
+
apiErrorCode?: string;
|
|
47
|
+
apiErrorType?: string;
|
|
48
|
+
apiErrorParam?: string;
|
|
49
|
+
requestId?: string;
|
|
50
|
+
/** Index of the offending message within the request's message array. */
|
|
51
|
+
offendingMessageIndex?: number;
|
|
52
|
+
/** Index of the offending content part within that message. */
|
|
53
|
+
offendingContentIndex?: number;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Extract the offending `input[N].content[M]` coordinates from an upstream
|
|
58
|
+
* error message, for the codes that carry such a pointer.
|
|
59
|
+
*/
|
|
60
|
+
function parseContentPartPointer(
|
|
61
|
+
error: ProviderError,
|
|
62
|
+
): Pick<
|
|
63
|
+
ProviderRejectionLogFields,
|
|
64
|
+
"offendingMessageIndex" | "offendingContentIndex"
|
|
65
|
+
> {
|
|
66
|
+
const code = error.apiErrorCode?.toLowerCase();
|
|
67
|
+
if (!code || !CONTENT_PART_POINTER_CODES.has(code)) {
|
|
68
|
+
return {};
|
|
69
|
+
}
|
|
70
|
+
const match = CONTENT_PART_POINTER.exec(
|
|
71
|
+
`${error.message} ${error.rawBody ?? ""}`,
|
|
72
|
+
);
|
|
73
|
+
if (!match) {
|
|
74
|
+
return {};
|
|
75
|
+
}
|
|
76
|
+
const messageIndex = Number(match[1]);
|
|
77
|
+
const contentIndex = Number(match[2]);
|
|
78
|
+
if (
|
|
79
|
+
!Number.isSafeInteger(messageIndex) ||
|
|
80
|
+
!Number.isSafeInteger(contentIndex)
|
|
81
|
+
) {
|
|
82
|
+
return {};
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
offendingMessageIndex: messageIndex,
|
|
86
|
+
offendingContentIndex: contentIndex,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Diagnostic fields describing what the provider actually rejected. */
|
|
91
|
+
export function buildProviderRejectionLogFields(
|
|
92
|
+
error: Error,
|
|
93
|
+
): ProviderRejectionLogFields {
|
|
94
|
+
if (!(error instanceof ProviderError)) {
|
|
95
|
+
return { upstreamErrorBody: null };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// The body is arbitrary upstream text under a custom field name, so it is
|
|
99
|
+
// outside the reach of the pino `err`/`req`/`res` serializers and is scrubbed
|
|
100
|
+
// here before it reaches the record.
|
|
101
|
+
const rawBody =
|
|
102
|
+
error.rawBody === undefined ? undefined : redactLogString(error.rawBody);
|
|
103
|
+
const truncated =
|
|
104
|
+
rawBody !== undefined && rawBody.length > MAX_LOGGED_UPSTREAM_BODY_CHARS;
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
upstreamErrorBody:
|
|
108
|
+
rawBody === undefined
|
|
109
|
+
? null
|
|
110
|
+
: rawBody.slice(0, MAX_LOGGED_UPSTREAM_BODY_CHARS),
|
|
111
|
+
...(truncated ? { upstreamErrorBodyTruncated: true } : {}),
|
|
112
|
+
...(error.apiErrorCode !== undefined
|
|
113
|
+
? { apiErrorCode: error.apiErrorCode }
|
|
114
|
+
: {}),
|
|
115
|
+
...(error.apiErrorType !== undefined
|
|
116
|
+
? { apiErrorType: error.apiErrorType }
|
|
117
|
+
: {}),
|
|
118
|
+
...(error.apiErrorParam !== undefined
|
|
119
|
+
? { apiErrorParam: error.apiErrorParam }
|
|
120
|
+
: {}),
|
|
121
|
+
...(error.requestId !== undefined ? { requestId: error.requestId } : {}),
|
|
122
|
+
...parseContentPartPointer(error),
|
|
123
|
+
};
|
|
124
|
+
}
|