mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-16 13:03:31 +08:00
feat(adapters): add attachment type and limit check module
This commit is contained in:
parent
63e5fb3a41
commit
e2ea346754
@ -0,0 +1,46 @@
|
||||
import { describe, it, expect } from 'bun:test'
|
||||
import {
|
||||
checkAttachmentLimit,
|
||||
IMAGE_MAX_BYTES,
|
||||
FILE_MAX_BYTES,
|
||||
IMAGE_MIME_WHITELIST,
|
||||
} from '../attachment-limits.js'
|
||||
|
||||
describe('checkAttachmentLimit', () => {
|
||||
it('accepts a 1 MB PNG image', () => {
|
||||
const result = checkAttachmentLimit('image', 1024 * 1024, 'image/png')
|
||||
expect(result.ok).toBe(true)
|
||||
})
|
||||
|
||||
it('rejects an 11 MB image as too_large', () => {
|
||||
const result = checkAttachmentLimit('image', 11 * 1024 * 1024, 'image/png')
|
||||
expect(result.ok).toBe(false)
|
||||
if (!result.ok) {
|
||||
expect(result.reason).toBe('too_large')
|
||||
expect(result.hint).toContain('10')
|
||||
}
|
||||
})
|
||||
|
||||
it('rejects an unsupported image mime', () => {
|
||||
const result = checkAttachmentLimit('image', 500_000, 'image/svg+xml')
|
||||
expect(result.ok).toBe(false)
|
||||
if (!result.ok) expect(result.reason).toBe('unsupported_mime')
|
||||
})
|
||||
|
||||
it('accepts a 10 MB PDF file', () => {
|
||||
const result = checkAttachmentLimit('file', 10 * 1024 * 1024, 'application/pdf')
|
||||
expect(result.ok).toBe(true)
|
||||
})
|
||||
|
||||
it('rejects a 31 MB file as too_large', () => {
|
||||
const result = checkAttachmentLimit('file', 31 * 1024 * 1024, 'application/pdf')
|
||||
expect(result.ok).toBe(false)
|
||||
if (!result.ok) expect(result.reason).toBe('too_large')
|
||||
})
|
||||
|
||||
it('exposes the limits as exports', () => {
|
||||
expect(IMAGE_MAX_BYTES).toBe(10 * 1024 * 1024)
|
||||
expect(FILE_MAX_BYTES).toBe(30 * 1024 * 1024)
|
||||
expect(IMAGE_MIME_WHITELIST).toContain('image/png')
|
||||
})
|
||||
})
|
||||
59
adapters/common/attachment/attachment-limits.ts
Normal file
59
adapters/common/attachment/attachment-limits.ts
Normal file
@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Size and MIME restrictions for IM attachments.
|
||||
*
|
||||
* Limits chosen to sit safely under both Feishu (10 MB image / 30 MB file)
|
||||
* and Telegram Bot API (10 MB image / 50 MB file), and under Claude API's
|
||||
* own image size bounds.
|
||||
*/
|
||||
|
||||
export const IMAGE_MAX_BYTES = 10 * 1024 * 1024 // 10 MB
|
||||
export const FILE_MAX_BYTES = 30 * 1024 * 1024 // 30 MB
|
||||
|
||||
export const IMAGE_MIME_WHITELIST = [
|
||||
'image/jpeg',
|
||||
'image/png',
|
||||
'image/gif',
|
||||
'image/webp',
|
||||
'image/heic',
|
||||
] as const
|
||||
|
||||
export type LimitCheckResult =
|
||||
| { ok: true }
|
||||
| { ok: false; reason: 'too_large' | 'unsupported_mime'; hint: string }
|
||||
|
||||
function formatMb(bytes: number): string {
|
||||
return (bytes / (1024 * 1024)).toFixed(1)
|
||||
}
|
||||
|
||||
export function checkAttachmentLimit(
|
||||
kind: 'image' | 'file',
|
||||
size: number,
|
||||
mime?: string,
|
||||
): LimitCheckResult {
|
||||
if (kind === 'image') {
|
||||
if (size > IMAGE_MAX_BYTES) {
|
||||
return {
|
||||
ok: false,
|
||||
reason: 'too_large',
|
||||
hint: `📎 图片过大(${formatMb(size)} MB),请控制在 10 MB 以内`,
|
||||
}
|
||||
}
|
||||
if (mime && !IMAGE_MIME_WHITELIST.includes(mime as (typeof IMAGE_MIME_WHITELIST)[number])) {
|
||||
return {
|
||||
ok: false,
|
||||
reason: 'unsupported_mime',
|
||||
hint: `📎 暂不支持此图片格式(${mime})`,
|
||||
}
|
||||
}
|
||||
return { ok: true }
|
||||
}
|
||||
// kind === 'file'
|
||||
if (size > FILE_MAX_BYTES) {
|
||||
return {
|
||||
ok: false,
|
||||
reason: 'too_large',
|
||||
hint: `📎 文件过大(${formatMb(size)} MB),请控制在 30 MB 以内`,
|
||||
}
|
||||
}
|
||||
return { ok: true }
|
||||
}
|
||||
29
adapters/common/attachment/attachment-types.ts
Normal file
29
adapters/common/attachment/attachment-types.ts
Normal file
@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Shared attachment types for IM adapters.
|
||||
*/
|
||||
|
||||
import type { AttachmentRef } from '../ws-bridge.js'
|
||||
export type { AttachmentRef }
|
||||
|
||||
/** Platform tag — used for local staging subdir and telemetry. */
|
||||
export type ImPlatform = 'feishu' | 'telegram'
|
||||
|
||||
/** Result of downloading an IM resource into the local stage dir. */
|
||||
export interface LocalAttachment {
|
||||
kind: 'image' | 'file'
|
||||
name: string // original filename, or synthesized if none
|
||||
path: string // absolute path on disk (under ~/.claude/im-downloads)
|
||||
size: number // bytes
|
||||
mimeType: string // detected or provided
|
||||
buffer: Buffer // raw bytes (kept so caller can choose base64 vs path)
|
||||
}
|
||||
|
||||
/** Pending outbound media found in Agent stream output. */
|
||||
export interface PendingUpload {
|
||||
id: string // fingerprint, used for dedup
|
||||
source:
|
||||
| { kind: 'base64'; data: string; mime: string }
|
||||
| { kind: 'path'; path: string; mime?: string }
|
||||
| { kind: 'url'; url: string; mime?: string }
|
||||
alt?: string
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user