diff --git a/src/lib/aws-comprehend/aws-comprehend.test.ts b/src/lib/aws-comprehend/aws-comprehend.test.ts new file mode 100644 index 00000000..246d9644 --- /dev/null +++ b/src/lib/aws-comprehend/aws-comprehend.test.ts @@ -0,0 +1,30 @@ +import { describe, it, expect, vi } from "vitest"; +import { AWSComprehendPIIRedactor } from "./aws-comprehend"; + +// Mocking AWS SDK secara total dengan factory function +vi.mock("@aws-sdk/client-comprehend", () => { + return { + DetectPiiEntitiesCommand: vi.fn(), + ComprehendClient: class { + send = vi.fn().mockResolvedValue({ + Entities: [ + { Type: "NAME", BeginOffset: 10, EndOffset: 13 }, + { Type: "PHONE", BeginOffset: 27, EndOffset: 38 } + ] + }); + } + }; +}); + +describe("AWSComprehendPIIRedactor", () => { + it("harus menyunting teks dengan placeholder yang benar", async () => { + const redactor = new AWSComprehendPIIRedactor("us-east-1", "fake-key", "fake-secret"); + const input = "Nama saya Sky dan nomor hp 08123456789"; + const result = await redactor.redact(input); + + expect(result).toContain("[REDACTED_NAME]"); + expect(result).toContain("[REDACTED_PHONE]"); + console.log("SUCCESS! HASIL REDACT:", result); + }); +}); + diff --git a/src/lib/aws-comprehend/aws-comprehend.ts b/src/lib/aws-comprehend/aws-comprehend.ts new file mode 100644 index 00000000..9a253c5b --- /dev/null +++ b/src/lib/aws-comprehend/aws-comprehend.ts @@ -0,0 +1,60 @@ +import { ComprehendClient, DetectPiiEntitiesCommand } from "@aws-sdk/client-comprehend"; + +/** + * AWSComprehendPIIRedactor + * Utilitas untuk mendeteksi dan menyunting data sensitif (PII) menggunakan AWS Comprehend. + */ +export class AWSComprehendPIIRedactor { + private client: ComprehendClient; + + constructor(region: string, accessKeyId: string, secretAccessKey: string) { + this.client = new ComprehendClient({ + region, + credentials: { + accessKeyId, + secretAccessKey, + }, + }); + } + + /** + * Menyunting teks dengan mengganti entitas PII yang terdeteksi dengan placeholder. + */ + async redact(text: string, languageCode: string = "en"): Promise { + const entities = await this.detectPii(text, languageCode); + + // Kita urutkan entitas dari offset TERBESAR ke terkecil agar index tidak rusak saat editing + const sortedEntities = [...entities].sort((a, b) => b.BeginOffset - a.BeginOffset); + + let redactedText = text; + + for (const entity of sortedEntities) { + const placeholder = `[REDACTED_${entity.Type}]`; + redactedText = + redactedText.slice(0, entity.BeginOffset) + + placeholder + + redactedText.slice(entity.EndOffset); + } + + return redactedText; + } + + /** + * Memanggil AWS Comprehend untuk mendeteksi entitas PII. + */ + async detectPii(text: string, languageCode: string) { + const command = new DetectPiiEntitiesCommand({ + Text: text, + LanguageCode: languageCode, + }); + + try { + const response = await this.client.send(command); + return response.Entities || []; + } catch (error) { + console.error("AWS Comprehend Error:", error); + throw error; + } + } +} + diff --git a/src/lib/smart-router.ts b/src/lib/smart-router.ts new file mode 100644 index 00000000..b9e4a74f --- /dev/null +++ b/src/lib/smart-router.ts @@ -0,0 +1,11 @@ +import { bandit } from '@oraclaw/bandit'; + +const provider = bandit({ + arms: [ + { id: 'openai', name: 'OpenAI', pulls: tokensUsed.openai, totalReward: success.openai }, + { id: 'palm', name: 'Google Palm', pulls: tokensUsed.palm, totalReward: success.palm }, + { id: 'cohere', name: 'Cohere', pulls: tokensUsed.cohere, totalReward: success.cohere }, + ], + strategy: 'ucb1' // Ini kunci buat dapetin $200! +}); +