@bowenqt/qiniu-ai-sdk 0.19.0 → 0.20.0
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/dist/ai/memory/index.d.ts.map +1 -1
- package/dist/ai/memory/index.js +9 -2
- package/dist/ai/memory/index.js.map +1 -1
- package/dist/ai/memory/index.mjs +9 -2
- package/dist/browser/index.d.ts +6 -2
- package/dist/browser/index.d.ts.map +1 -1
- package/dist/browser/index.js +29 -20
- package/dist/browser/index.js.map +1 -1
- package/dist/browser/index.mjs +15 -6
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +6 -0
- package/dist/lib/asset-cost.d.ts +56 -0
- package/dist/lib/asset-cost.d.ts.map +1 -0
- package/dist/lib/asset-cost.js +138 -0
- package/dist/lib/asset-cost.js.map +1 -0
- package/dist/lib/asset-cost.mjs +134 -0
- package/dist/lib/asset-resolver.d.ts +70 -0
- package/dist/lib/asset-resolver.d.ts.map +1 -0
- package/dist/lib/asset-resolver.js +145 -0
- package/dist/lib/asset-resolver.js.map +1 -0
- package/dist/lib/asset-resolver.mjs +138 -0
- package/dist/lib/signer.d.ts +0 -6
- package/dist/lib/signer.d.ts.map +1 -1
- package/dist/lib/signer.js +14 -8
- package/dist/lib/signer.js.map +1 -1
- package/dist/lib/signer.mjs +14 -8
- package/dist/lib/vframe.d.ts +94 -0
- package/dist/lib/vframe.d.ts.map +1 -0
- package/dist/lib/vframe.js +189 -0
- package/dist/lib/vframe.js.map +1 -0
- package/dist/lib/vframe.mjs +181 -0
- package/package.json +1 -1
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Asset Cost Estimation for Qiniu Cloud resources.
|
|
4
|
+
* Helps agents understand processing costs before committing.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.estimateAssetCost = estimateAssetCost;
|
|
8
|
+
exports.detectAssetType = detectAssetType;
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// Token Estimation Constants
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// Approximate token costs per asset type
|
|
13
|
+
const TOKENS_PER_IMAGE_BASE = 500; // Base tokens for an image
|
|
14
|
+
const TOKENS_PER_IMAGE_MEGAPIXEL = 200; // Additional tokens per megapixel
|
|
15
|
+
const TOKENS_PER_AUDIO_MINUTE = 100; // Tokens per minute of audio transcript
|
|
16
|
+
const TOKENS_PER_VIDEO_FRAME = 700; // Tokens per video frame (image + context)
|
|
17
|
+
const TOKENS_PER_DOCUMENT_PAGE = 500; // Tokens per document page
|
|
18
|
+
// Thresholds for cost levels
|
|
19
|
+
const HIGH_COST_THRESHOLD = 5000;
|
|
20
|
+
const MEDIUM_COST_THRESHOLD = 2000;
|
|
21
|
+
// ============================================================================
|
|
22
|
+
// Cost Estimation
|
|
23
|
+
// ============================================================================
|
|
24
|
+
/**
|
|
25
|
+
* Estimate the cost of processing an asset.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```typescript
|
|
29
|
+
* const cost = estimateAssetCost({
|
|
30
|
+
* type: 'video',
|
|
31
|
+
* duration: 120,
|
|
32
|
+
* frameCount: 5,
|
|
33
|
+
* });
|
|
34
|
+
* // { tokensEstimate: 3500, level: 'medium', confidence: 'calculated', ... }
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
function estimateAssetCost(info) {
|
|
38
|
+
let tokens = 0;
|
|
39
|
+
let confidence = 'estimate';
|
|
40
|
+
const recommendations = [];
|
|
41
|
+
switch (info.type) {
|
|
42
|
+
case 'video': {
|
|
43
|
+
if (info.frameCount !== undefined) {
|
|
44
|
+
tokens = info.frameCount * TOKENS_PER_VIDEO_FRAME;
|
|
45
|
+
confidence = 'calculated';
|
|
46
|
+
}
|
|
47
|
+
else if (info.duration !== undefined) {
|
|
48
|
+
// Assume 5 frames default
|
|
49
|
+
const estimatedFrames = 5;
|
|
50
|
+
tokens = estimatedFrames * TOKENS_PER_VIDEO_FRAME;
|
|
51
|
+
recommendations.push(`Consider extracting ${estimatedFrames} frames for ${info.duration}s video`);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
tokens = 5 * TOKENS_PER_VIDEO_FRAME;
|
|
55
|
+
recommendations.push('Provide duration or frameCount for accurate estimate');
|
|
56
|
+
}
|
|
57
|
+
if (info.duration && info.duration > 300) {
|
|
58
|
+
recommendations.push('Long video: consider smart sampling or key-frame extraction');
|
|
59
|
+
}
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
case 'image': {
|
|
63
|
+
tokens = TOKENS_PER_IMAGE_BASE;
|
|
64
|
+
if (info.resolution) {
|
|
65
|
+
const megapixels = (info.resolution.width * info.resolution.height) / 1000000;
|
|
66
|
+
tokens += Math.round(megapixels * TOKENS_PER_IMAGE_MEGAPIXEL);
|
|
67
|
+
confidence = 'calculated';
|
|
68
|
+
}
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
case 'audio': {
|
|
72
|
+
if (info.duration !== undefined) {
|
|
73
|
+
const minutes = info.duration / 60;
|
|
74
|
+
tokens = Math.round(minutes * TOKENS_PER_AUDIO_MINUTE);
|
|
75
|
+
confidence = 'calculated';
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
tokens = TOKENS_PER_AUDIO_MINUTE * 5; // Assume 5 minutes
|
|
79
|
+
recommendations.push('Provide duration for accurate estimate');
|
|
80
|
+
}
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
case 'document': {
|
|
84
|
+
if (info.sizeBytes) {
|
|
85
|
+
// Rough estimate: 10KB per page
|
|
86
|
+
const pages = Math.ceil(info.sizeBytes / 10000);
|
|
87
|
+
tokens = pages * TOKENS_PER_DOCUMENT_PAGE;
|
|
88
|
+
confidence = 'calculated';
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
tokens = TOKENS_PER_DOCUMENT_PAGE * 5; // Assume 5 pages
|
|
92
|
+
recommendations.push('Provide file size for accurate estimate');
|
|
93
|
+
}
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
default:
|
|
97
|
+
tokens = 1000; // Unknown type
|
|
98
|
+
recommendations.push('Unknown asset type: estimate may be inaccurate');
|
|
99
|
+
}
|
|
100
|
+
// Determine cost level
|
|
101
|
+
let level;
|
|
102
|
+
if (tokens >= HIGH_COST_THRESHOLD) {
|
|
103
|
+
level = 'high';
|
|
104
|
+
recommendations.push('High token cost: consider chunking or summarization');
|
|
105
|
+
}
|
|
106
|
+
else if (tokens >= MEDIUM_COST_THRESHOLD) {
|
|
107
|
+
level = 'medium';
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
level = 'low';
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
tokensEstimate: tokens,
|
|
114
|
+
level,
|
|
115
|
+
confidence,
|
|
116
|
+
recommendations,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Detect asset type from file extension.
|
|
121
|
+
*/
|
|
122
|
+
function detectAssetType(key) {
|
|
123
|
+
const ext = key.split('.').pop()?.toLowerCase() ?? '';
|
|
124
|
+
const videoExts = ['mp4', 'webm', 'avi', 'mov', 'mkv', 'flv', 'm4v'];
|
|
125
|
+
const imageExts = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'svg'];
|
|
126
|
+
const audioExts = ['mp3', 'wav', 'ogg', 'aac', 'flac', 'm4a'];
|
|
127
|
+
const documentExts = ['pdf', 'doc', 'docx', 'ppt', 'pptx', 'xls', 'xlsx', 'txt'];
|
|
128
|
+
if (videoExts.includes(ext))
|
|
129
|
+
return 'video';
|
|
130
|
+
if (imageExts.includes(ext))
|
|
131
|
+
return 'image';
|
|
132
|
+
if (audioExts.includes(ext))
|
|
133
|
+
return 'audio';
|
|
134
|
+
if (documentExts.includes(ext))
|
|
135
|
+
return 'document';
|
|
136
|
+
return 'unknown';
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=asset-cost.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"asset-cost.js","sourceRoot":"","sources":["../../src/lib/asset-cost.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Asset Cost Estimation for Qiniu Cloud resources.
|
|
3
|
+
* Helps agents understand processing costs before committing.
|
|
4
|
+
*/
|
|
5
|
+
// ============================================================================
|
|
6
|
+
// Token Estimation Constants
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// Approximate token costs per asset type
|
|
9
|
+
const TOKENS_PER_IMAGE_BASE = 500; // Base tokens for an image
|
|
10
|
+
const TOKENS_PER_IMAGE_MEGAPIXEL = 200; // Additional tokens per megapixel
|
|
11
|
+
const TOKENS_PER_AUDIO_MINUTE = 100; // Tokens per minute of audio transcript
|
|
12
|
+
const TOKENS_PER_VIDEO_FRAME = 700; // Tokens per video frame (image + context)
|
|
13
|
+
const TOKENS_PER_DOCUMENT_PAGE = 500; // Tokens per document page
|
|
14
|
+
// Thresholds for cost levels
|
|
15
|
+
const HIGH_COST_THRESHOLD = 5000;
|
|
16
|
+
const MEDIUM_COST_THRESHOLD = 2000;
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// Cost Estimation
|
|
19
|
+
// ============================================================================
|
|
20
|
+
/**
|
|
21
|
+
* Estimate the cost of processing an asset.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* const cost = estimateAssetCost({
|
|
26
|
+
* type: 'video',
|
|
27
|
+
* duration: 120,
|
|
28
|
+
* frameCount: 5,
|
|
29
|
+
* });
|
|
30
|
+
* // { tokensEstimate: 3500, level: 'medium', confidence: 'calculated', ... }
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export function estimateAssetCost(info) {
|
|
34
|
+
let tokens = 0;
|
|
35
|
+
let confidence = 'estimate';
|
|
36
|
+
const recommendations = [];
|
|
37
|
+
switch (info.type) {
|
|
38
|
+
case 'video': {
|
|
39
|
+
if (info.frameCount !== undefined) {
|
|
40
|
+
tokens = info.frameCount * TOKENS_PER_VIDEO_FRAME;
|
|
41
|
+
confidence = 'calculated';
|
|
42
|
+
}
|
|
43
|
+
else if (info.duration !== undefined) {
|
|
44
|
+
// Assume 5 frames default
|
|
45
|
+
const estimatedFrames = 5;
|
|
46
|
+
tokens = estimatedFrames * TOKENS_PER_VIDEO_FRAME;
|
|
47
|
+
recommendations.push(`Consider extracting ${estimatedFrames} frames for ${info.duration}s video`);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
tokens = 5 * TOKENS_PER_VIDEO_FRAME;
|
|
51
|
+
recommendations.push('Provide duration or frameCount for accurate estimate');
|
|
52
|
+
}
|
|
53
|
+
if (info.duration && info.duration > 300) {
|
|
54
|
+
recommendations.push('Long video: consider smart sampling or key-frame extraction');
|
|
55
|
+
}
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
case 'image': {
|
|
59
|
+
tokens = TOKENS_PER_IMAGE_BASE;
|
|
60
|
+
if (info.resolution) {
|
|
61
|
+
const megapixels = (info.resolution.width * info.resolution.height) / 1000000;
|
|
62
|
+
tokens += Math.round(megapixels * TOKENS_PER_IMAGE_MEGAPIXEL);
|
|
63
|
+
confidence = 'calculated';
|
|
64
|
+
}
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
case 'audio': {
|
|
68
|
+
if (info.duration !== undefined) {
|
|
69
|
+
const minutes = info.duration / 60;
|
|
70
|
+
tokens = Math.round(minutes * TOKENS_PER_AUDIO_MINUTE);
|
|
71
|
+
confidence = 'calculated';
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
tokens = TOKENS_PER_AUDIO_MINUTE * 5; // Assume 5 minutes
|
|
75
|
+
recommendations.push('Provide duration for accurate estimate');
|
|
76
|
+
}
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
case 'document': {
|
|
80
|
+
if (info.sizeBytes) {
|
|
81
|
+
// Rough estimate: 10KB per page
|
|
82
|
+
const pages = Math.ceil(info.sizeBytes / 10000);
|
|
83
|
+
tokens = pages * TOKENS_PER_DOCUMENT_PAGE;
|
|
84
|
+
confidence = 'calculated';
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
tokens = TOKENS_PER_DOCUMENT_PAGE * 5; // Assume 5 pages
|
|
88
|
+
recommendations.push('Provide file size for accurate estimate');
|
|
89
|
+
}
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
default:
|
|
93
|
+
tokens = 1000; // Unknown type
|
|
94
|
+
recommendations.push('Unknown asset type: estimate may be inaccurate');
|
|
95
|
+
}
|
|
96
|
+
// Determine cost level
|
|
97
|
+
let level;
|
|
98
|
+
if (tokens >= HIGH_COST_THRESHOLD) {
|
|
99
|
+
level = 'high';
|
|
100
|
+
recommendations.push('High token cost: consider chunking or summarization');
|
|
101
|
+
}
|
|
102
|
+
else if (tokens >= MEDIUM_COST_THRESHOLD) {
|
|
103
|
+
level = 'medium';
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
level = 'low';
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
tokensEstimate: tokens,
|
|
110
|
+
level,
|
|
111
|
+
confidence,
|
|
112
|
+
recommendations,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Detect asset type from file extension.
|
|
117
|
+
*/
|
|
118
|
+
export function detectAssetType(key) {
|
|
119
|
+
const ext = key.split('.').pop()?.toLowerCase() ?? '';
|
|
120
|
+
const videoExts = ['mp4', 'webm', 'avi', 'mov', 'mkv', 'flv', 'm4v'];
|
|
121
|
+
const imageExts = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'svg'];
|
|
122
|
+
const audioExts = ['mp3', 'wav', 'ogg', 'aac', 'flac', 'm4a'];
|
|
123
|
+
const documentExts = ['pdf', 'doc', 'docx', 'ppt', 'pptx', 'xls', 'xlsx', 'txt'];
|
|
124
|
+
if (videoExts.includes(ext))
|
|
125
|
+
return 'video';
|
|
126
|
+
if (imageExts.includes(ext))
|
|
127
|
+
return 'image';
|
|
128
|
+
if (audioExts.includes(ext))
|
|
129
|
+
return 'audio';
|
|
130
|
+
if (documentExts.includes(ext))
|
|
131
|
+
return 'document';
|
|
132
|
+
return 'unknown';
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=asset-cost.js.map
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Asset Resolution for Qiniu Cloud resources.
|
|
3
|
+
* Provides parsing and resolution of qiniu:// URIs with security validation.
|
|
4
|
+
*/
|
|
5
|
+
import type { QiniuSigner } from './signer';
|
|
6
|
+
import { CachedSigner } from './signer';
|
|
7
|
+
/** Parsed Qiniu asset */
|
|
8
|
+
export interface QiniuAsset {
|
|
9
|
+
/** Bucket name */
|
|
10
|
+
bucket: string;
|
|
11
|
+
/** Object key (path) */
|
|
12
|
+
key: string;
|
|
13
|
+
}
|
|
14
|
+
/** Asset resolution options */
|
|
15
|
+
export interface ResolveOptions {
|
|
16
|
+
/** Processing fop command (e.g., 'vframe/jpg/offset/1') */
|
|
17
|
+
fop?: string;
|
|
18
|
+
/** Allowed buckets whitelist (if empty, all buckets allowed) */
|
|
19
|
+
allowedBuckets?: string[];
|
|
20
|
+
/** URL expiry in seconds (default: 3600) */
|
|
21
|
+
expiry?: number;
|
|
22
|
+
}
|
|
23
|
+
/** Resolved asset with signed URL */
|
|
24
|
+
export interface ResolvedAsset {
|
|
25
|
+
/** Signed URL */
|
|
26
|
+
url: string;
|
|
27
|
+
/** URL expiration timestamp (ms) */
|
|
28
|
+
expiresAt: number;
|
|
29
|
+
/** Bucket name */
|
|
30
|
+
bucket: string;
|
|
31
|
+
/** Object key */
|
|
32
|
+
key: string;
|
|
33
|
+
}
|
|
34
|
+
/** Asset resolution error */
|
|
35
|
+
export declare class AssetResolutionError extends Error {
|
|
36
|
+
readonly code: 'INVALID_URI' | 'SECURITY_VIOLATION' | 'BUCKET_NOT_ALLOWED';
|
|
37
|
+
constructor(message: string, code: 'INVALID_URI' | 'SECURITY_VIOLATION' | 'BUCKET_NOT_ALLOWED');
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Parse a qiniu:// URI into bucket and key.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```typescript
|
|
44
|
+
* const asset = parseQiniuUri('qiniu://my-bucket/path/to/video.mp4');
|
|
45
|
+
* // { bucket: 'my-bucket', key: 'path/to/video.mp4' }
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* @returns Parsed asset or null if invalid URI
|
|
49
|
+
* @throws AssetResolutionError for security violations
|
|
50
|
+
*/
|
|
51
|
+
export declare function parseQiniuUri(uri: string): QiniuAsset | null;
|
|
52
|
+
/**
|
|
53
|
+
* Resolve a Qiniu asset to a signed URL.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```typescript
|
|
57
|
+
* const asset = parseQiniuUri('qiniu://my-bucket/video.mp4');
|
|
58
|
+
* const resolved = await resolveAsset(asset, signer, {
|
|
59
|
+
* allowedBuckets: ['my-bucket'],
|
|
60
|
+
* });
|
|
61
|
+
* console.log(resolved.url); // Signed URL
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
export declare function resolveAsset(asset: QiniuAsset, signer: QiniuSigner | CachedSigner, options?: ResolveOptions): Promise<ResolvedAsset>;
|
|
65
|
+
/**
|
|
66
|
+
* Resolve multiple assets in parallel.
|
|
67
|
+
* Uses CachedSigner for efficiency.
|
|
68
|
+
*/
|
|
69
|
+
export declare function resolveAssets(assets: QiniuAsset[], signer: QiniuSigner | CachedSigner, options?: ResolveOptions): Promise<ResolvedAsset[]>;
|
|
70
|
+
//# sourceMappingURL=asset-resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"asset-resolver.d.ts","sourceRoot":"","sources":["../../src/lib/asset-resolver.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,WAAW,EAA0B,MAAM,UAAU,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAMxC,yBAAyB;AACzB,MAAM,WAAW,UAAU;IACvB,kBAAkB;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,wBAAwB;IACxB,GAAG,EAAE,MAAM,CAAC;CACf;AAED,+BAA+B;AAC/B,MAAM,WAAW,cAAc;IAC3B,2DAA2D;IAC3D,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,gEAAgE;IAChE,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,4CAA4C;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,qCAAqC;AACrC,MAAM,WAAW,aAAa;IAC1B,iBAAiB;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,oCAAoC;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,kBAAkB;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,iBAAiB;IACjB,GAAG,EAAE,MAAM,CAAC;CACf;AAqBD,6BAA6B;AAC7B,qBAAa,oBAAqB,SAAQ,KAAK;aAGvB,IAAI,EAAE,aAAa,GAAG,oBAAoB,GAAG,oBAAoB;gBADjF,OAAO,EAAE,MAAM,EACC,IAAI,EAAE,aAAa,GAAG,oBAAoB,GAAG,oBAAoB;CAKxF;AAMD;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAiE5D;AAMD;;;;;;;;;;;GAWG;AACH,wBAAsB,YAAY,CAC9B,KAAK,EAAE,UAAU,EACjB,MAAM,EAAE,WAAW,GAAG,YAAY,EAClC,OAAO,GAAE,cAAmB,GAC7B,OAAO,CAAC,aAAa,CAAC,CA0BxB;AAMD;;;GAGG;AACH,wBAAsB,aAAa,CAC/B,MAAM,EAAE,UAAU,EAAE,EACpB,MAAM,EAAE,WAAW,GAAG,YAAY,EAClC,OAAO,GAAE,cAAmB,GAC7B,OAAO,CAAC,aAAa,EAAE,CAAC,CAI1B"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Asset Resolution for Qiniu Cloud resources.
|
|
4
|
+
* Provides parsing and resolution of qiniu:// URIs with security validation.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.AssetResolutionError = void 0;
|
|
8
|
+
exports.parseQiniuUri = parseQiniuUri;
|
|
9
|
+
exports.resolveAsset = resolveAsset;
|
|
10
|
+
exports.resolveAssets = resolveAssets;
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// Constants
|
|
13
|
+
// ============================================================================
|
|
14
|
+
const QINIU_URI_SCHEME = 'qiniu://';
|
|
15
|
+
// Characters not allowed in bucket/key names (security)
|
|
16
|
+
const DANGEROUS_PATTERNS = [
|
|
17
|
+
/\.\./, // Path traversal
|
|
18
|
+
/\\/, // Backslash
|
|
19
|
+
/[\x00-\x1f]/, // Control characters
|
|
20
|
+
/%2e%2e/i, // URL-encoded ../
|
|
21
|
+
/%5c/i, // URL-encoded \
|
|
22
|
+
];
|
|
23
|
+
// ============================================================================
|
|
24
|
+
// Errors
|
|
25
|
+
// ============================================================================
|
|
26
|
+
/** Asset resolution error */
|
|
27
|
+
class AssetResolutionError extends Error {
|
|
28
|
+
constructor(message, code) {
|
|
29
|
+
super(message);
|
|
30
|
+
this.code = code;
|
|
31
|
+
this.name = 'AssetResolutionError';
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
exports.AssetResolutionError = AssetResolutionError;
|
|
35
|
+
// ============================================================================
|
|
36
|
+
// URI Parsing
|
|
37
|
+
// ============================================================================
|
|
38
|
+
/**
|
|
39
|
+
* Parse a qiniu:// URI into bucket and key.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```typescript
|
|
43
|
+
* const asset = parseQiniuUri('qiniu://my-bucket/path/to/video.mp4');
|
|
44
|
+
* // { bucket: 'my-bucket', key: 'path/to/video.mp4' }
|
|
45
|
+
* ```
|
|
46
|
+
*
|
|
47
|
+
* @returns Parsed asset or null if invalid URI
|
|
48
|
+
* @throws AssetResolutionError for security violations
|
|
49
|
+
*/
|
|
50
|
+
function parseQiniuUri(uri) {
|
|
51
|
+
if (!uri || typeof uri !== 'string') {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
// Normalize: trim and check scheme
|
|
55
|
+
const normalized = uri.trim();
|
|
56
|
+
if (!normalized.startsWith(QINIU_URI_SCHEME)) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
// Extract path after scheme
|
|
60
|
+
const path = normalized.slice(QINIU_URI_SCHEME.length);
|
|
61
|
+
// Remove fragment (#...) and query string (?...)
|
|
62
|
+
const cleanPath = path.split('#')[0].split('?')[0];
|
|
63
|
+
// Security check before decoding
|
|
64
|
+
for (const pattern of DANGEROUS_PATTERNS) {
|
|
65
|
+
if (pattern.test(cleanPath)) {
|
|
66
|
+
throw new AssetResolutionError(`Security violation: dangerous pattern in URI`, 'SECURITY_VIOLATION');
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// URL decode
|
|
70
|
+
let decodedPath;
|
|
71
|
+
try {
|
|
72
|
+
decodedPath = decodeURIComponent(cleanPath);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
throw new AssetResolutionError(`Invalid URI encoding`, 'INVALID_URI');
|
|
76
|
+
}
|
|
77
|
+
// Security check after decoding
|
|
78
|
+
for (const pattern of DANGEROUS_PATTERNS) {
|
|
79
|
+
if (pattern.test(decodedPath)) {
|
|
80
|
+
throw new AssetResolutionError(`Security violation: dangerous pattern in decoded URI`, 'SECURITY_VIOLATION');
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// Remove leading slash
|
|
84
|
+
const trimmedPath = decodedPath.startsWith('/') ? decodedPath.slice(1) : decodedPath;
|
|
85
|
+
// Split into bucket/key
|
|
86
|
+
const slashIndex = trimmedPath.indexOf('/');
|
|
87
|
+
if (slashIndex === -1) {
|
|
88
|
+
// Only bucket, no key
|
|
89
|
+
if (!trimmedPath)
|
|
90
|
+
return null;
|
|
91
|
+
return { bucket: trimmedPath, key: '' };
|
|
92
|
+
}
|
|
93
|
+
const bucket = trimmedPath.slice(0, slashIndex);
|
|
94
|
+
const key = trimmedPath.slice(slashIndex + 1);
|
|
95
|
+
if (!bucket)
|
|
96
|
+
return null;
|
|
97
|
+
return { bucket, key };
|
|
98
|
+
}
|
|
99
|
+
// ============================================================================
|
|
100
|
+
// Asset Resolution
|
|
101
|
+
// ============================================================================
|
|
102
|
+
/**
|
|
103
|
+
* Resolve a Qiniu asset to a signed URL.
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```typescript
|
|
107
|
+
* const asset = parseQiniuUri('qiniu://my-bucket/video.mp4');
|
|
108
|
+
* const resolved = await resolveAsset(asset, signer, {
|
|
109
|
+
* allowedBuckets: ['my-bucket'],
|
|
110
|
+
* });
|
|
111
|
+
* console.log(resolved.url); // Signed URL
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
114
|
+
async function resolveAsset(asset, signer, options = {}) {
|
|
115
|
+
// Bucket whitelist validation
|
|
116
|
+
if (options.allowedBuckets?.length) {
|
|
117
|
+
if (!options.allowedBuckets.includes(asset.bucket)) {
|
|
118
|
+
throw new AssetResolutionError(`Bucket '${asset.bucket}' not in allowed list`, 'BUCKET_NOT_ALLOWED');
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// Build sign options
|
|
122
|
+
const signOptions = {
|
|
123
|
+
fop: options.fop,
|
|
124
|
+
expiry: options.expiry,
|
|
125
|
+
};
|
|
126
|
+
// Sign the URL
|
|
127
|
+
const signed = await signer.sign(asset.bucket, asset.key, signOptions);
|
|
128
|
+
return {
|
|
129
|
+
url: signed.url,
|
|
130
|
+
expiresAt: signed.expiresAt,
|
|
131
|
+
bucket: asset.bucket,
|
|
132
|
+
key: asset.key,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
// ============================================================================
|
|
136
|
+
// Batch Resolution
|
|
137
|
+
// ============================================================================
|
|
138
|
+
/**
|
|
139
|
+
* Resolve multiple assets in parallel.
|
|
140
|
+
* Uses CachedSigner for efficiency.
|
|
141
|
+
*/
|
|
142
|
+
async function resolveAssets(assets, signer, options = {}) {
|
|
143
|
+
return Promise.all(assets.map(asset => resolveAsset(asset, signer, options)));
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=asset-resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"asset-resolver.js","sourceRoot":"","sources":["../../src/lib/asset-resolver.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Asset Resolution for Qiniu Cloud resources.
|
|
3
|
+
* Provides parsing and resolution of qiniu:// URIs with security validation.
|
|
4
|
+
*/
|
|
5
|
+
// ============================================================================
|
|
6
|
+
// Constants
|
|
7
|
+
// ============================================================================
|
|
8
|
+
const QINIU_URI_SCHEME = 'qiniu://';
|
|
9
|
+
// Characters not allowed in bucket/key names (security)
|
|
10
|
+
const DANGEROUS_PATTERNS = [
|
|
11
|
+
/\.\./, // Path traversal
|
|
12
|
+
/\\/, // Backslash
|
|
13
|
+
/[\x00-\x1f]/, // Control characters
|
|
14
|
+
/%2e%2e/i, // URL-encoded ../
|
|
15
|
+
/%5c/i, // URL-encoded \
|
|
16
|
+
];
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// Errors
|
|
19
|
+
// ============================================================================
|
|
20
|
+
/** Asset resolution error */
|
|
21
|
+
export class AssetResolutionError extends Error {
|
|
22
|
+
constructor(message, code) {
|
|
23
|
+
super(message);
|
|
24
|
+
this.code = code;
|
|
25
|
+
this.name = 'AssetResolutionError';
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
// ============================================================================
|
|
29
|
+
// URI Parsing
|
|
30
|
+
// ============================================================================
|
|
31
|
+
/**
|
|
32
|
+
* Parse a qiniu:// URI into bucket and key.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```typescript
|
|
36
|
+
* const asset = parseQiniuUri('qiniu://my-bucket/path/to/video.mp4');
|
|
37
|
+
* // { bucket: 'my-bucket', key: 'path/to/video.mp4' }
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* @returns Parsed asset or null if invalid URI
|
|
41
|
+
* @throws AssetResolutionError for security violations
|
|
42
|
+
*/
|
|
43
|
+
export function parseQiniuUri(uri) {
|
|
44
|
+
if (!uri || typeof uri !== 'string') {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
// Normalize: trim and check scheme
|
|
48
|
+
const normalized = uri.trim();
|
|
49
|
+
if (!normalized.startsWith(QINIU_URI_SCHEME)) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
// Extract path after scheme
|
|
53
|
+
const path = normalized.slice(QINIU_URI_SCHEME.length);
|
|
54
|
+
// Remove fragment (#...) and query string (?...)
|
|
55
|
+
const cleanPath = path.split('#')[0].split('?')[0];
|
|
56
|
+
// Security check before decoding
|
|
57
|
+
for (const pattern of DANGEROUS_PATTERNS) {
|
|
58
|
+
if (pattern.test(cleanPath)) {
|
|
59
|
+
throw new AssetResolutionError(`Security violation: dangerous pattern in URI`, 'SECURITY_VIOLATION');
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// URL decode
|
|
63
|
+
let decodedPath;
|
|
64
|
+
try {
|
|
65
|
+
decodedPath = decodeURIComponent(cleanPath);
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
throw new AssetResolutionError(`Invalid URI encoding`, 'INVALID_URI');
|
|
69
|
+
}
|
|
70
|
+
// Security check after decoding
|
|
71
|
+
for (const pattern of DANGEROUS_PATTERNS) {
|
|
72
|
+
if (pattern.test(decodedPath)) {
|
|
73
|
+
throw new AssetResolutionError(`Security violation: dangerous pattern in decoded URI`, 'SECURITY_VIOLATION');
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// Remove leading slash
|
|
77
|
+
const trimmedPath = decodedPath.startsWith('/') ? decodedPath.slice(1) : decodedPath;
|
|
78
|
+
// Split into bucket/key
|
|
79
|
+
const slashIndex = trimmedPath.indexOf('/');
|
|
80
|
+
if (slashIndex === -1) {
|
|
81
|
+
// Only bucket, no key
|
|
82
|
+
if (!trimmedPath)
|
|
83
|
+
return null;
|
|
84
|
+
return { bucket: trimmedPath, key: '' };
|
|
85
|
+
}
|
|
86
|
+
const bucket = trimmedPath.slice(0, slashIndex);
|
|
87
|
+
const key = trimmedPath.slice(slashIndex + 1);
|
|
88
|
+
if (!bucket)
|
|
89
|
+
return null;
|
|
90
|
+
return { bucket, key };
|
|
91
|
+
}
|
|
92
|
+
// ============================================================================
|
|
93
|
+
// Asset Resolution
|
|
94
|
+
// ============================================================================
|
|
95
|
+
/**
|
|
96
|
+
* Resolve a Qiniu asset to a signed URL.
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```typescript
|
|
100
|
+
* const asset = parseQiniuUri('qiniu://my-bucket/video.mp4');
|
|
101
|
+
* const resolved = await resolveAsset(asset, signer, {
|
|
102
|
+
* allowedBuckets: ['my-bucket'],
|
|
103
|
+
* });
|
|
104
|
+
* console.log(resolved.url); // Signed URL
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
export async function resolveAsset(asset, signer, options = {}) {
|
|
108
|
+
// Bucket whitelist validation
|
|
109
|
+
if (options.allowedBuckets?.length) {
|
|
110
|
+
if (!options.allowedBuckets.includes(asset.bucket)) {
|
|
111
|
+
throw new AssetResolutionError(`Bucket '${asset.bucket}' not in allowed list`, 'BUCKET_NOT_ALLOWED');
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// Build sign options
|
|
115
|
+
const signOptions = {
|
|
116
|
+
fop: options.fop,
|
|
117
|
+
expiry: options.expiry,
|
|
118
|
+
};
|
|
119
|
+
// Sign the URL
|
|
120
|
+
const signed = await signer.sign(asset.bucket, asset.key, signOptions);
|
|
121
|
+
return {
|
|
122
|
+
url: signed.url,
|
|
123
|
+
expiresAt: signed.expiresAt,
|
|
124
|
+
bucket: asset.bucket,
|
|
125
|
+
key: asset.key,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
// ============================================================================
|
|
129
|
+
// Batch Resolution
|
|
130
|
+
// ============================================================================
|
|
131
|
+
/**
|
|
132
|
+
* Resolve multiple assets in parallel.
|
|
133
|
+
* Uses CachedSigner for efficiency.
|
|
134
|
+
*/
|
|
135
|
+
export async function resolveAssets(assets, signer, options = {}) {
|
|
136
|
+
return Promise.all(assets.map(asset => resolveAsset(asset, signer, options)));
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=asset-resolver.js.map
|
package/dist/lib/signer.d.ts
CHANGED
|
@@ -11,12 +11,6 @@
|
|
|
11
11
|
* }
|
|
12
12
|
* };
|
|
13
13
|
* ```
|
|
14
|
-
*
|
|
15
|
-
* @example Node.js usage (direct signing):
|
|
16
|
-
* ```typescript
|
|
17
|
-
* import { createSigner } from '@bowenqt/qiniu-ai-sdk/node';
|
|
18
|
-
* const signer = createSigner({ accessKey, secretKey });
|
|
19
|
-
* ```
|
|
20
14
|
*/
|
|
21
15
|
/** Signed URL result */
|
|
22
16
|
export interface SignedUrl {
|
package/dist/lib/signer.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"signer.d.ts","sourceRoot":"","sources":["../../src/lib/signer.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"signer.d.ts","sourceRoot":"","sources":["../../src/lib/signer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAMH,wBAAwB;AACxB,MAAM,WAAW,SAAS;IACtB,sBAAsB;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,oCAAoC;IACpC,SAAS,EAAE,MAAM,CAAC;CACrB;AAED,mBAAmB;AACnB,MAAM,WAAW,WAAW;IACxB,gDAAgD;IAChD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2DAA2D;IAC3D,GAAG,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,iDAAiD;AACjD,MAAM,WAAW,WAAW;IACxB,0BAA0B;IAC1B,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IAE7E,oCAAoC;IACpC,cAAc,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAClE;AAMD,0BAA0B;AAC1B,MAAM,WAAW,cAAc;IAC3B,yCAAyC;IACzC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2FAA2F;IAC3F,eAAe,CAAC,EAAE,MAAM,CAAC;CAC5B;AAQD;;;GAGG;AACH,qBAAa,QAAQ;IACjB,OAAO,CAAC,KAAK,CAAiC;IAC9C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA2B;gBAEtC,MAAM,GAAE,cAAmB;IAOvC,oBAAoB;IACpB,OAAO,CAAC,MAAM;IAId,8BAA8B;IAC9B,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS;IAkBrE,yBAAyB;IACzB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI;IAuB1E,4BAA4B;IAC5B,KAAK,IAAI,IAAI;IAIb,6BAA6B;IAC7B,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,uCAAuC;IACvC,OAAO,CAAC,YAAY;IAMpB,sCAAsC;IACtC,OAAO,CAAC,QAAQ;CAenB;AAMD;;;;;;;;GAQG;AACH,qBAAa,YAAa,YAAW,WAAW;IAC5C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAW;IACjC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAc;gBAE7B,UAAU,EAAE,WAAW,EAAE,WAAW,CAAC,EAAE,cAAc;IAK3D,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,SAAS,CAAC;IAgB5E,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAOnE,0BAA0B;IAC1B,UAAU,IAAI,IAAI;IAIlB,2BAA2B;IAC3B,IAAI,SAAS,IAAI,MAAM,CAEtB;CACJ"}
|