Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ERRORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ No error scenarios — prints a message directing users to run `npm update -g mm
| HTTP 401 / 403 | `API key rejected (HTTP ${status}).` |
| HTTP 429 | `Rate limit or quota exceeded. ${apiMsg}` |
| `status_code` 1002 / 1039 (content filter) | `Input content flagged by sensitivity filter (${filterType}).` |
| `status_msg` contains "new_sensitive" / output sensitivity (HTTP 200) | `Output withheld by content moderation (${apiMsg}).` |
| `status_code` 1028 / 1030 (quota exhausted) | `Quota exhausted. ${apiMsg}` |
| `status_code` 2061 (model not on plan) | `This model is not available on your current Token Plan. ${apiMsg}` |
| Other API errors | `API error: ${apiMsg} (HTTP ${status})` |
Expand Down
11 changes: 10 additions & 1 deletion src/errors/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export function mapApiError(status: number, body: ApiErrorBody, url?: string): C
(/sensitive content/i.test(apiMsg) || /(?:^|\D)1026(?:\D|$)/.test(apiMsg));

// MiniMax content sensitivity filter
if (apiCode === 1002 || apiCode === 1039 || isV2ContentFilter) {
if (apiCode === 1002 || apiCode === 1039 || apiCode === 1026 || isV2ContentFilter) {
const filterType =
body.base_resp?.status_msg ||
body.error?.message ||
Expand Down Expand Up @@ -106,6 +106,15 @@ export function mapApiError(status: number, body: ApiErrorBody, url?: string): C
);
}

// output sensitivity (status_code 1027, e.g. "output new_sensitive")
if (apiCode === 1027 || /output.*sensitive/i.test(apiMsg)) {
return new CLIError(
`Output withheld by content moderation (${apiMsg}).`,
ExitCode.CONTENT_FILTER,
'Refine your query and try again.',
);
}

return new CLIError(
`API error: ${apiMsg} (HTTP ${status})`,
ExitCode.GENERAL,
Expand Down
26 changes: 26 additions & 0 deletions test/errors/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,32 @@ describe('mapApiError', () => {
expect(err.exitCode).toBe(ExitCode.QUOTA);
});

it('maps output sensitivity (HTTP 200) to CONTENT_FILTER', () => {
const err = mapApiError(200, {
base_resp: { status_code: 2001, status_msg: 'output new_sensitive' },
});
expect(err.exitCode).toBe(ExitCode.CONTENT_FILTER);
expect(err.message).toContain('content moderation');
expect(err.message).toContain('output new_sensitive');
expect(err.hint).toBeDefined();
});

it('maps output sensitivity code 1027 to CONTENT_FILTER', () => {
const err = mapApiError(200, {
base_resp: { status_code: 1027, status_msg: '输出内容涉敏' },
});
expect(err.exitCode).toBe(ExitCode.CONTENT_FILTER);
});

it('maps input sensitivity code 1026 to CONTENT_FILTER (not output)', () => {
const err = mapApiError(200, {
base_resp: { status_code: 1026, status_msg: 'input new_sensitive' },
});
expect(err.exitCode).toBe(ExitCode.CONTENT_FILTER);
expect(err.message).toContain('Input');
expect(err.message).not.toContain('Output withheld');
});

it('maps unknown errors to GENERAL', () => {
const err = mapApiError(500, { base_resp: { status_code: 0, status_msg: 'internal error' } });
expect(err.exitCode).toBe(ExitCode.GENERAL);
Expand Down