movie-agent 1.1.0 → 1.1.2
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/README.md +15 -6
- package/bin/movie-agent +6 -6
- package/dist/agent.d.ts +6 -4
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +34 -17
- package/dist/agent.js.map +1 -1
- package/dist/cache.d.ts +40 -5
- package/dist/cache.d.ts.map +1 -1
- package/dist/cache.js +95 -11
- package/dist/cache.js.map +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -1
- package/dist/config.js.map +1 -1
- package/dist/factory.d.ts +4 -4
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +11 -11
- package/dist/factory.js.map +1 -1
- package/dist/format.d.ts +14 -1
- package/dist/format.d.ts.map +1 -1
- package/dist/format.js +22 -1
- package/dist/format.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/llm.d.ts.map +1 -1
- package/dist/llm.js +15 -4
- package/dist/llm.js.map +1 -1
- package/dist/providers.d.ts +9 -1
- package/dist/providers.d.ts.map +1 -1
- package/dist/providers.js +27 -0
- package/dist/providers.js.map +1 -1
- package/dist/rateLimiter.d.ts +59 -0
- package/dist/rateLimiter.d.ts.map +1 -0
- package/dist/rateLimiter.js +99 -0
- package/dist/rateLimiter.js.map +1 -0
- package/dist/sanitize.d.ts +24 -0
- package/dist/sanitize.d.ts.map +1 -0
- package/dist/sanitize.js +137 -0
- package/dist/sanitize.js.map +1 -0
- package/dist/tmdbApi.d.ts +37 -1
- package/dist/tmdbApi.d.ts.map +1 -1
- package/dist/tmdbApi.js +71 -23
- package/dist/tmdbApi.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/validate.d.ts +5 -0
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +38 -1
- package/dist/validate.js.map +1 -1
- package/package.json +6 -3
package/dist/sanitize.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Sanitization utilities for LLM prompt injection prevention
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.sanitizeForLLMPrompt = sanitizeForLLMPrompt;
|
|
5
|
+
exports.sanitizeString = sanitizeString;
|
|
6
|
+
exports.detectPromptInjection = detectPromptInjection;
|
|
7
|
+
/**
|
|
8
|
+
* Common prompt injection patterns to detect and mitigate
|
|
9
|
+
* Using consolidated patterns for better performance and maintainability
|
|
10
|
+
*/
|
|
11
|
+
const PROMPT_INJECTION_PATTERNS = [
|
|
12
|
+
// Action-based injections: "ignore/disregard/forget/override [all] [previous/the] instructions/prompts/commands"
|
|
13
|
+
/(?:ignore|disregard|forget|override)\s+(?:all\s+)?(?:previous|the)\s+(?:instructions?|prompts?|commands?)/gi,
|
|
14
|
+
/(?:ignore|disregard|forget|override)\s+(?:previous|all|the)\s+(?:instructions?|prompts?|commands?)/gi,
|
|
15
|
+
// New instruction attempts
|
|
16
|
+
/new\s+(?:instructions?|prompts?|commands?)[\s:]/gi,
|
|
17
|
+
// Role-based injections
|
|
18
|
+
/(?:system|assistant|user)\s*:/gi,
|
|
19
|
+
// Model-specific control tokens
|
|
20
|
+
/\[INST\]|\[\/INST\]/gi,
|
|
21
|
+
/<\|im_start\|>|<\|im_end\|>/gi,
|
|
22
|
+
// Code blocks (limited to reasonable size to prevent ReDoS)
|
|
23
|
+
/```[^`]{0,1000}```/g,
|
|
24
|
+
// Role injection in JSON format
|
|
25
|
+
/\{[^}]{0,500}"role"\s*:\s*"(?:system|assistant)"[^}]{0,500}\}/gi,
|
|
26
|
+
];
|
|
27
|
+
/**
|
|
28
|
+
* Maximum lengths for different input types (defense in depth with existing validation)
|
|
29
|
+
*/
|
|
30
|
+
const MAX_SANITIZED_LENGTH = 500;
|
|
31
|
+
/**
|
|
32
|
+
* Sanitizes user input to prevent prompt injection attacks
|
|
33
|
+
* This is specifically for content that will be passed to LLM prompts
|
|
34
|
+
*
|
|
35
|
+
* @param input - Raw user input object
|
|
36
|
+
* @returns Sanitized version safe for LLM prompts
|
|
37
|
+
*/
|
|
38
|
+
function sanitizeForLLMPrompt(input) {
|
|
39
|
+
if (input === null || input === undefined) {
|
|
40
|
+
return input;
|
|
41
|
+
}
|
|
42
|
+
// Handle primitive types
|
|
43
|
+
if (typeof input === 'string') {
|
|
44
|
+
return sanitizeString(input);
|
|
45
|
+
}
|
|
46
|
+
if (typeof input === 'number' || typeof input === 'boolean') {
|
|
47
|
+
return input;
|
|
48
|
+
}
|
|
49
|
+
// Handle arrays
|
|
50
|
+
if (Array.isArray(input)) {
|
|
51
|
+
return input.map(item => sanitizeForLLMPrompt(item));
|
|
52
|
+
}
|
|
53
|
+
// Handle objects recursively
|
|
54
|
+
if (typeof input === 'object') {
|
|
55
|
+
const sanitized = {};
|
|
56
|
+
for (const [key, value] of Object.entries(input)) {
|
|
57
|
+
// Sanitize both key and value
|
|
58
|
+
const sanitizedKey = sanitizeString(String(key));
|
|
59
|
+
sanitized[sanitizedKey] = sanitizeForLLMPrompt(value);
|
|
60
|
+
}
|
|
61
|
+
return sanitized;
|
|
62
|
+
}
|
|
63
|
+
return input;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Sanitizes a string to remove potential prompt injection attacks
|
|
67
|
+
*
|
|
68
|
+
* @param str - Input string to sanitize
|
|
69
|
+
* @returns Sanitized string
|
|
70
|
+
*/
|
|
71
|
+
function sanitizeString(str) {
|
|
72
|
+
if (!str || typeof str !== 'string') {
|
|
73
|
+
return '';
|
|
74
|
+
}
|
|
75
|
+
let sanitized = str;
|
|
76
|
+
// Truncate to maximum length early to prevent expensive operations on large strings
|
|
77
|
+
if (sanitized.length > MAX_SANITIZED_LENGTH) {
|
|
78
|
+
sanitized = sanitized.substring(0, MAX_SANITIZED_LENGTH);
|
|
79
|
+
}
|
|
80
|
+
// Remove common prompt injection patterns - replace with safe placeholder
|
|
81
|
+
// Use a marker that won't be affected by later cleanup
|
|
82
|
+
const FILTERED_MARKER = '___FILTERED___';
|
|
83
|
+
for (const pattern of PROMPT_INJECTION_PATTERNS) {
|
|
84
|
+
// Reset regex state to ensure consistent behavior with global flag
|
|
85
|
+
pattern.lastIndex = 0;
|
|
86
|
+
sanitized = sanitized.replace(pattern, FILTERED_MARKER);
|
|
87
|
+
}
|
|
88
|
+
// Remove control characters except common whitespace (space, tab, newline)
|
|
89
|
+
// eslint-disable-next-line no-control-regex
|
|
90
|
+
sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '');
|
|
91
|
+
// Limit consecutive special characters (more than 3 in a row) - more efficient than nested quantifiers
|
|
92
|
+
// Only process if the string contains special characters
|
|
93
|
+
if (/[^\w\s]/.test(sanitized)) {
|
|
94
|
+
sanitized = sanitized.replace(/([^\w\s])\1{3,}/g, '$1$1$1');
|
|
95
|
+
}
|
|
96
|
+
// Remove any remaining potential instruction delimiters (but preserve our marker)
|
|
97
|
+
sanitized = sanitized.replace(/[<>{}[\]]/g, ' ');
|
|
98
|
+
// Clean up multiple spaces created by replacements
|
|
99
|
+
sanitized = sanitized.replace(/\s+/g, ' ');
|
|
100
|
+
// Replace marker with user-friendly text
|
|
101
|
+
sanitized = sanitized.replace(new RegExp(FILTERED_MARKER, 'g'), '[filtered]');
|
|
102
|
+
// Trim whitespace
|
|
103
|
+
sanitized = sanitized.trim();
|
|
104
|
+
return sanitized;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Detects if input contains potential prompt injection attempts
|
|
108
|
+
* This is for logging/monitoring purposes
|
|
109
|
+
*
|
|
110
|
+
* @param input - Input to check
|
|
111
|
+
* @returns True if potential injection detected
|
|
112
|
+
*/
|
|
113
|
+
function detectPromptInjection(input) {
|
|
114
|
+
if (!input) {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
const inputStr = typeof input === 'string' ? input : JSON.stringify(input);
|
|
118
|
+
// Early termination for very large inputs to prevent performance issues
|
|
119
|
+
const CHECK_LENGTH_LIMIT = 10000;
|
|
120
|
+
const truncatedInput = inputStr.length > CHECK_LENGTH_LIMIT
|
|
121
|
+
? inputStr.substring(0, CHECK_LENGTH_LIMIT)
|
|
122
|
+
: inputStr;
|
|
123
|
+
// Reuse sanitization patterns for consistency
|
|
124
|
+
for (const pattern of PROMPT_INJECTION_PATTERNS) {
|
|
125
|
+
// Reset regex state for reuse
|
|
126
|
+
pattern.lastIndex = 0;
|
|
127
|
+
if (pattern.test(truncatedInput)) {
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// Additional checks for suspicious character sequences
|
|
132
|
+
if (/[<>{}[\]]{3,}/.test(truncatedInput)) {
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=sanitize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sanitize.js","sourceRoot":"","sources":["../src/sanitize.ts"],"names":[],"mappings":"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"}
|
package/dist/tmdbApi.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { RateLimiterConfig } from './rateLimiter';
|
|
1
2
|
export interface MovieSummary {
|
|
2
3
|
id: number;
|
|
3
4
|
title: string;
|
|
@@ -6,6 +7,8 @@ export interface MovieSummary {
|
|
|
6
7
|
genre_ids?: number[];
|
|
7
8
|
vote_average?: number;
|
|
8
9
|
popularity?: number;
|
|
10
|
+
/** Path to poster image (e.g., "/abc123.jpg") */
|
|
11
|
+
poster_path?: string | null;
|
|
9
12
|
}
|
|
10
13
|
export interface DiscoverMoviesParams {
|
|
11
14
|
sort_by?: string;
|
|
@@ -33,6 +36,12 @@ export interface MovieDetails extends MovieSummary {
|
|
|
33
36
|
tagline?: string;
|
|
34
37
|
homepage?: string;
|
|
35
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Movie details with embedded watch providers (from append_to_response)
|
|
41
|
+
*/
|
|
42
|
+
export interface MovieDetailsWithProviders extends MovieDetails {
|
|
43
|
+
'watch/providers'?: WatchProvidersResponse;
|
|
44
|
+
}
|
|
36
45
|
export interface SearchMoviesResponse extends DiscoverMoviesResponse {
|
|
37
46
|
}
|
|
38
47
|
export interface Genre {
|
|
@@ -62,15 +71,42 @@ export interface WatchProvidersResponse {
|
|
|
62
71
|
};
|
|
63
72
|
};
|
|
64
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Configuration options for TmdbApiClient
|
|
76
|
+
*/
|
|
77
|
+
export interface TmdbApiClientConfig {
|
|
78
|
+
baseUrl?: string;
|
|
79
|
+
apiKey?: string;
|
|
80
|
+
region?: string;
|
|
81
|
+
/** Rate limiter configuration for throttling requests */
|
|
82
|
+
rateLimiterConfig?: Partial<RateLimiterConfig>;
|
|
83
|
+
/** Request timeout in milliseconds (default: 10000) */
|
|
84
|
+
timeoutMs?: number;
|
|
85
|
+
}
|
|
65
86
|
export declare class TmdbApiClient {
|
|
66
87
|
private readonly baseUrl;
|
|
67
88
|
private readonly apiKey;
|
|
68
89
|
private readonly region;
|
|
69
|
-
|
|
90
|
+
private readonly rateLimiter;
|
|
91
|
+
private readonly rateLimiterConfig;
|
|
92
|
+
private readonly timeoutMs;
|
|
93
|
+
constructor(baseUrl?: string, apiKey?: string, region?: string, rateLimiterConfig?: Partial<RateLimiterConfig>, timeoutMs?: number);
|
|
70
94
|
private buildUrl;
|
|
95
|
+
/**
|
|
96
|
+
* Internal fetch method with rate limiting and exponential backoff retry
|
|
97
|
+
* @param url - The URL to fetch
|
|
98
|
+
* @returns Promise resolving to the parsed JSON response
|
|
99
|
+
*/
|
|
71
100
|
private doFetch;
|
|
72
101
|
discoverMovies(params?: DiscoverMoviesParams): Promise<DiscoverMoviesResponse>;
|
|
73
102
|
getMovieDetails(movieId: number): Promise<MovieDetails>;
|
|
103
|
+
/**
|
|
104
|
+
* Fetches movie details with watch providers in a single API call
|
|
105
|
+
* Uses append_to_response to reduce API calls
|
|
106
|
+
* @param movieId - TMDb movie ID
|
|
107
|
+
* @returns Movie details with embedded watch providers
|
|
108
|
+
*/
|
|
109
|
+
getMovieDetailsWithProviders(movieId: number): Promise<MovieDetailsWithProviders>;
|
|
74
110
|
searchMovies(query: string, page?: number): Promise<SearchMoviesResponse>;
|
|
75
111
|
getGenres(): Promise<GenresResponse>;
|
|
76
112
|
getWatchProviders(movieId: number): Promise<WatchProvidersResponse>;
|
package/dist/tmdbApi.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tmdbApi.d.ts","sourceRoot":"","sources":["../src/tmdbApi.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"tmdbApi.d.ts","sourceRoot":"","sources":["../src/tmdbApi.ts"],"names":[],"mappings":"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"}
|
package/dist/tmdbApi.js
CHANGED
|
@@ -5,19 +5,33 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.TmdbApiClient = void 0;
|
|
7
7
|
const config_1 = __importDefault(require("./config"));
|
|
8
|
+
const rateLimiter_1 = require("./rateLimiter");
|
|
9
|
+
/**
|
|
10
|
+
* Default timeout for API requests in milliseconds (10 seconds)
|
|
11
|
+
*/
|
|
12
|
+
const DEFAULT_TIMEOUT_MS = 10000;
|
|
8
13
|
class TmdbApiClient {
|
|
9
|
-
constructor(baseUrl, apiKey, region) {
|
|
14
|
+
constructor(baseUrl, apiKey, region, rateLimiterConfig, timeoutMs) {
|
|
10
15
|
this.baseUrl = baseUrl ?? config_1.default.TMDB_BASE_URL;
|
|
11
|
-
this.apiKey = apiKey ?? config_1.default.
|
|
16
|
+
this.apiKey = (apiKey ?? config_1.default.TMDB_ACCESS_TOKEN).trim();
|
|
12
17
|
this.region = region ?? config_1.default.TMDB_REGION;
|
|
18
|
+
this.timeoutMs = timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
19
|
+
// Enforce HTTPS-only for security
|
|
20
|
+
if (!this.baseUrl.toLowerCase().startsWith('https://')) {
|
|
21
|
+
throw new Error('Base URL must use HTTPS protocol for secure API communication');
|
|
22
|
+
}
|
|
23
|
+
this.rateLimiterConfig = {
|
|
24
|
+
...rateLimiter_1.DEFAULT_RATE_LIMITER_CONFIG,
|
|
25
|
+
...rateLimiterConfig,
|
|
26
|
+
};
|
|
27
|
+
this.rateLimiter = (0, rateLimiter_1.createRateLimiter)(this.rateLimiterConfig.concurrency);
|
|
13
28
|
}
|
|
14
29
|
buildUrl(path, params) {
|
|
15
30
|
const normalizedBase = this.baseUrl.endsWith('/')
|
|
16
31
|
? this.baseUrl
|
|
17
32
|
: this.baseUrl + '/';
|
|
18
33
|
const url = new URL(path, normalizedBase);
|
|
19
|
-
//
|
|
20
|
-
url.searchParams.set('api_key', this.apiKey);
|
|
34
|
+
// API key is passed via Authorization header for security
|
|
21
35
|
if (params) {
|
|
22
36
|
for (const [k, v] of Object.entries(params)) {
|
|
23
37
|
if (v !== undefined && v !== null) {
|
|
@@ -27,26 +41,47 @@ class TmdbApiClient {
|
|
|
27
41
|
}
|
|
28
42
|
return url.toString();
|
|
29
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* Internal fetch method with rate limiting and exponential backoff retry
|
|
46
|
+
* @param url - The URL to fetch
|
|
47
|
+
* @returns Promise resolving to the parsed JSON response
|
|
48
|
+
*/
|
|
30
49
|
async doFetch(url) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
+
return this.rateLimiter(() => (0, rateLimiter_1.withRetry)(async () => {
|
|
51
|
+
let resp;
|
|
52
|
+
const abortController = new AbortController();
|
|
53
|
+
const timeoutId = setTimeout(() => {
|
|
54
|
+
abortController.abort();
|
|
55
|
+
}, this.timeoutMs);
|
|
56
|
+
try {
|
|
57
|
+
resp = await fetch(url, {
|
|
58
|
+
headers: {
|
|
59
|
+
Accept: 'application/json',
|
|
60
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
61
|
+
},
|
|
62
|
+
signal: abortController.signal,
|
|
63
|
+
});
|
|
64
|
+
clearTimeout(timeoutId);
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
clearTimeout(timeoutId);
|
|
68
|
+
// Check if the error is due to abort (timeout)
|
|
69
|
+
if (err.name === 'AbortError') {
|
|
70
|
+
throw new Error(`Network error calling TMDb API: Request timeout after ${this.timeoutMs}ms`);
|
|
71
|
+
}
|
|
72
|
+
throw new Error(`Network error calling TMDb API: ${err?.message ?? String(err)}`);
|
|
73
|
+
}
|
|
74
|
+
if (!resp.ok) {
|
|
75
|
+
const text = await resp.text().catch(() => '');
|
|
76
|
+
throw new Error(`TMDb API error ${resp.status}: ${text || resp.statusText}`);
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
return (await resp.json());
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
throw new Error(`Invalid JSON from TMDb API: ${err?.message ?? String(err)}`);
|
|
83
|
+
}
|
|
84
|
+
}, this.rateLimiterConfig));
|
|
50
85
|
}
|
|
51
86
|
async discoverMovies(params = {}) {
|
|
52
87
|
const url = this.buildUrl('discover/movie', {
|
|
@@ -66,6 +101,19 @@ class TmdbApiClient {
|
|
|
66
101
|
const url = this.buildUrl(`movie/${movieId}`, { region: this.region });
|
|
67
102
|
return this.doFetch(url);
|
|
68
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Fetches movie details with watch providers in a single API call
|
|
106
|
+
* Uses append_to_response to reduce API calls
|
|
107
|
+
* @param movieId - TMDb movie ID
|
|
108
|
+
* @returns Movie details with embedded watch providers
|
|
109
|
+
*/
|
|
110
|
+
async getMovieDetailsWithProviders(movieId) {
|
|
111
|
+
const url = this.buildUrl(`movie/${movieId}`, {
|
|
112
|
+
region: this.region,
|
|
113
|
+
append_to_response: 'watch/providers',
|
|
114
|
+
});
|
|
115
|
+
return this.doFetch(url);
|
|
116
|
+
}
|
|
69
117
|
async searchMovies(query, page) {
|
|
70
118
|
const url = this.buildUrl('search/movie', {
|
|
71
119
|
query,
|
package/dist/tmdbApi.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tmdbApi.js","sourceRoot":"","sources":["../src/tmdbApi.ts"],"names":[],"mappings":";;;;;;AAAA,sDAA8B;
|
|
1
|
+
{"version":3,"file":"tmdbApi.js","sourceRoot":"","sources":["../src/tmdbApi.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -60,6 +60,8 @@ export interface MovieRecommendation {
|
|
|
60
60
|
streamingPlatforms: StreamingPlatform[];
|
|
61
61
|
/** Explanation of why this movie was recommended */
|
|
62
62
|
matchReason: string;
|
|
63
|
+
/** URL to movie poster image (w500 size) */
|
|
64
|
+
posterUrl: string | null;
|
|
63
65
|
}
|
|
64
66
|
/**
|
|
65
67
|
* Metadata about the recommendation request
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,qDAAqD;IACrD,IAAI,EAAE,MAAM,CAAC;IACb,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAC;IACb,gEAAgE;IAChE,SAAS,EAAE,OAAO,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,iCAAiC;IACjC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,iCAAiC;IACjC,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,6BAA6B;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,2BAA2B;IAC3B,EAAE,EAAE,MAAM,CAAC;CACZ;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,gDAAgD;IAChD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6CAA6C;IAC7C,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,yBAAyB;IACzB,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC1B,qCAAqC;IACrC,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAC7B,iCAAiC;IACjC,WAAW,CAAC,EAAE,MAAM,GAAG,gBAAgB,CAAC;CACzC;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,kBAAkB;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,sBAAsB;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,yBAAyB;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,6CAA6C;IAC7C,WAAW,EAAE,MAAM,CAAC;IACpB,qBAAqB;IACrB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,oCAAoC;IACpC,kBAAkB,EAAE,iBAAiB,EAAE,CAAC;IACxC,oDAAoD;IACpD,WAAW,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,qDAAqD;IACrD,IAAI,EAAE,MAAM,CAAC;IACb,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAC;IACb,gEAAgE;IAChE,SAAS,EAAE,OAAO,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,iCAAiC;IACjC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,iCAAiC;IACjC,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,6BAA6B;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,2BAA2B;IAC3B,EAAE,EAAE,MAAM,CAAC;CACZ;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,gDAAgD;IAChD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6CAA6C;IAC7C,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,yBAAyB;IACzB,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC1B,qCAAqC;IACrC,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAC7B,iCAAiC;IACjC,WAAW,CAAC,EAAE,MAAM,GAAG,gBAAgB,CAAC;CACzC;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,kBAAkB;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,sBAAsB;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,yBAAyB;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,6CAA6C;IAC7C,WAAW,EAAE,MAAM,CAAC;IACpB,qBAAqB;IACrB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,oCAAoC;IACpC,kBAAkB,EAAE,iBAAiB,EAAE,CAAC;IACxC,oDAAoD;IACpD,WAAW,EAAE,MAAM,CAAC;IACpB,4CAA4C;IAC5C,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,iDAAiD;IACjD,gBAAgB,EAAE,MAAM,CAAC;IACzB,+CAA+C;IAC/C,YAAY,EAAE,MAAM,CAAC;IACrB,4CAA4C;IAC5C,eAAe,EAAE,SAAS,CAAC;CAC5B;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,wCAAwC;IACxC,eAAe,EAAE,mBAAmB,EAAE,CAAC;IACvC,uBAAuB;IACvB,QAAQ,EAAE,gBAAgB,CAAC;CAC5B;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,sBAAsB;IACtB,KAAK,EAAE,IAAI,CAAC;IACZ,0BAA0B;IAC1B,SAAS,EACL,iBAAiB,GACjB,iBAAiB,GACjB,qBAAqB,GACrB,kBAAkB,GAClB,YAAY,GACZ,eAAe,CAAC;IACpB,mCAAmC;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,+CAA+C;IAC/C,SAAS,EAAE,MAAM,CAAC;IAClB,qCAAqC;IACrC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB"}
|
package/dist/validate.d.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
export declare const MAX_MOOD_LENGTH = 100;
|
|
2
|
+
export declare const MAX_GENRE_LENGTH = 50;
|
|
3
|
+
export declare const MAX_ARRAY_LENGTH = 10;
|
|
1
4
|
export declare const ALLOWED_PLATFORMS: string[];
|
|
2
5
|
export declare function isValidPlatform(name: string): boolean;
|
|
3
6
|
export declare function validatePlatforms(platforms: string[]): void;
|
|
@@ -10,4 +13,6 @@ export declare function validateYearRange({ from, to, }: {
|
|
|
10
13
|
from: number;
|
|
11
14
|
to: number;
|
|
12
15
|
}): void;
|
|
16
|
+
export declare function validateMood(mood: string): void;
|
|
17
|
+
export declare function validateGenre(genre: string | string[]): void;
|
|
13
18
|
//# sourceMappingURL=validate.d.ts.map
|
package/dist/validate.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,eAAe,MAAM,CAAC;AACnC,eAAO,MAAM,gBAAgB,KAAK,CAAC;AACnC,eAAO,MAAM,gBAAgB,KAAK,CAAC;AAEnC,eAAO,MAAM,iBAAiB,UAU7B,CAAC;AAEF,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAErD;AAED,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,IAAI,CAU3D;AAED,wBAAgB,eAAe,CAAC,EAC9B,GAAG,EACH,GAAG,GACJ,EAAE;IACD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,GAAG,IAAI,CAMP;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAI/C;AAED,wBAAgB,iBAAiB,CAAC,EAChC,IAAI,EACJ,EAAE,GACH,EAAE;IACD,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;CACZ,GAAG,IAAI,CAMP;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAY/C;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,CAsB5D"}
|
package/dist/validate.js
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// Input validation utilities for movie-agent
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.ALLOWED_PLATFORMS = void 0;
|
|
4
|
+
exports.ALLOWED_PLATFORMS = exports.MAX_ARRAY_LENGTH = exports.MAX_GENRE_LENGTH = exports.MAX_MOOD_LENGTH = void 0;
|
|
5
5
|
exports.isValidPlatform = isValidPlatform;
|
|
6
6
|
exports.validatePlatforms = validatePlatforms;
|
|
7
7
|
exports.validateRuntime = validateRuntime;
|
|
8
8
|
exports.validateYear = validateYear;
|
|
9
9
|
exports.validateYearRange = validateYearRange;
|
|
10
|
+
exports.validateMood = validateMood;
|
|
11
|
+
exports.validateGenre = validateGenre;
|
|
12
|
+
// Maximum length constants to prevent DoS attacks
|
|
13
|
+
exports.MAX_MOOD_LENGTH = 100;
|
|
14
|
+
exports.MAX_GENRE_LENGTH = 50;
|
|
15
|
+
exports.MAX_ARRAY_LENGTH = 10;
|
|
10
16
|
exports.ALLOWED_PLATFORMS = [
|
|
11
17
|
'Netflix',
|
|
12
18
|
'Prime Video',
|
|
@@ -22,6 +28,9 @@ function isValidPlatform(name) {
|
|
|
22
28
|
return exports.ALLOWED_PLATFORMS.includes(name);
|
|
23
29
|
}
|
|
24
30
|
function validatePlatforms(platforms) {
|
|
31
|
+
if (platforms.length > exports.MAX_ARRAY_LENGTH) {
|
|
32
|
+
throw new Error(`Too many platforms: maximum ${exports.MAX_ARRAY_LENGTH} allowed, got ${platforms.length}`);
|
|
33
|
+
}
|
|
25
34
|
const invalid = platforms.filter(p => !isValidPlatform(p));
|
|
26
35
|
if (invalid.length > 0) {
|
|
27
36
|
throw new Error(`Invalid platform(s): ${invalid.join(', ')}`);
|
|
@@ -44,4 +53,32 @@ function validateYearRange({ from, to, }) {
|
|
|
44
53
|
throw new Error(`Year range invalid: from (${from}) > to (${to})`);
|
|
45
54
|
}
|
|
46
55
|
}
|
|
56
|
+
function validateMood(mood) {
|
|
57
|
+
if (typeof mood !== 'string') {
|
|
58
|
+
throw new Error('Mood must be a string');
|
|
59
|
+
}
|
|
60
|
+
if (mood.trim().length === 0) {
|
|
61
|
+
throw new Error('Mood cannot be empty');
|
|
62
|
+
}
|
|
63
|
+
if (mood.length > exports.MAX_MOOD_LENGTH) {
|
|
64
|
+
throw new Error(`Mood must be ${exports.MAX_MOOD_LENGTH} characters or less, got ${mood.length}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function validateGenre(genre) {
|
|
68
|
+
const genres = Array.isArray(genre) ? genre : [genre];
|
|
69
|
+
if (genres.length > exports.MAX_ARRAY_LENGTH) {
|
|
70
|
+
throw new Error(`Too many genres: maximum ${exports.MAX_ARRAY_LENGTH} allowed, got ${genres.length}`);
|
|
71
|
+
}
|
|
72
|
+
for (const g of genres) {
|
|
73
|
+
if (typeof g !== 'string') {
|
|
74
|
+
throw new Error('Genre must be a string');
|
|
75
|
+
}
|
|
76
|
+
if (g.trim().length === 0) {
|
|
77
|
+
throw new Error('Genre cannot be empty');
|
|
78
|
+
}
|
|
79
|
+
if (g.length > exports.MAX_GENRE_LENGTH) {
|
|
80
|
+
throw new Error(`Genre must be ${exports.MAX_GENRE_LENGTH} characters or less, got ${g.length}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
47
84
|
//# sourceMappingURL=validate.js.map
|
package/dist/validate.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":";AAAA,6CAA6C;;;
|
|
1
|
+
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "movie-agent",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "Movie Agent - A TypeScript-based movie recommendation agent",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -30,8 +30,9 @@
|
|
|
30
30
|
"format": "prettier --write \"src/**/*.ts\"",
|
|
31
31
|
"format:check": "prettier --check \"src/**/*.ts\"",
|
|
32
32
|
"type-check": "tsc --noEmit",
|
|
33
|
+
"audit": "npm audit --audit-level=moderate",
|
|
33
34
|
"validate": "npm run type-check && npm run lint && npm run test:coverage",
|
|
34
|
-
"validate:ci": "npm run type-check && npm run lint && npm run format:check && npm run test:ci && npm run build",
|
|
35
|
+
"validate:ci": "npm run type-check && npm run audit && npm run lint && npm run format:check && npm run test:ci && npm run build",
|
|
35
36
|
"clean": "rm -rf dist coverage",
|
|
36
37
|
"prebuild": "npm run clean",
|
|
37
38
|
"prepublishOnly": "npm run validate:ci",
|
|
@@ -55,6 +56,7 @@
|
|
|
55
56
|
"devDependencies": {
|
|
56
57
|
"@types/jest": "^29.5.11",
|
|
57
58
|
"@types/node": "^20.10.6",
|
|
59
|
+
"@types/p-limit": "^2.1.0",
|
|
58
60
|
"@typescript-eslint/eslint-plugin": "^6.17.0",
|
|
59
61
|
"@typescript-eslint/parser": "^6.17.0",
|
|
60
62
|
"dotenv-cli": "^7.1.0",
|
|
@@ -70,6 +72,7 @@
|
|
|
70
72
|
"@langchain/core": "^1.1.7",
|
|
71
73
|
"@langchain/google-genai": "^2.1.2",
|
|
72
74
|
"@langchain/openai": "^1.2.0",
|
|
73
|
-
"dotenv": "^16.4.5"
|
|
75
|
+
"dotenv": "^16.4.5",
|
|
76
|
+
"p-limit": "^3.1.0"
|
|
74
77
|
}
|
|
75
78
|
}
|