Skip to content
Draft
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
92 changes: 92 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"better-sqlite3": "^11.0.0",
"hono": "^4.0.0",
"nanoid": "^5.0.0",
"node-fetch": "^3.3.2",
"tweetnacl": "^1.0.3",
"tweetnacl-util": "^0.15.1",
"ws": "^8.18.0"
Expand Down
35 changes: 35 additions & 0 deletions tests/antiDetection.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { Hono } from 'hono';
import { serve } from '@hono/node-server';
import { handleRequest } from '../src/provider/index.js';
import type { InnerPlaintext } from '../src/types.js';

describe('Anti-Detection Features', () => {
let mockServer;
let mockPort;

beforeAll(async () => {
const app = new Hono();
let callCount = 0;

app.post('/v1/messages', async (c) => {
callCount++;
const body = await c.req.json();
if (body.model === 'retry-test' && callCount === 1) {
return c.json({ error: { message: 'rate_limit' } }, 429);
}
return c.json({ id: 'msg_test', type: 'message', role: 'assistant', content: [{ type: 'text', text: 'Hello from mock!' }], model: 'claude-sonnet-4-20250514', stop_reason: 'end_turn', usage: { input_tokens: 10, output_tokens: 5 } });
});

mockPort = 18900 + Math.floor(Math.random() * 100);
mockServer = serve({ fetch: app.fetch, port: mockPort });
});

afterAll(() => {
mockServer?.close();
});

it('should randomize headers and delays', async () => {
// Add assertions for the headers and delays
});
});
82 changes: 82 additions & 0 deletions tests/provider.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { handleRequest } from './src/provider/index.js'; // Attempting corrected relative path

// Mocks for testing
import { vi } from 'vitest';
vi.mock('node-fetch');
import fetch from 'node-fetch';
const { Response } = vi.importActual('node-fetch');

describe('Provider Request Handling', () => {

// Test for header fingerprints
test('should not have identical header fingerprints on consecutive requests', async () => {
fetch.mockResolvedValue(new Response(JSON.stringify({ content: [{ text: 'hello' }], usage: { input_tokens: 5, output_tokens: 3 }, finish_reason: 'stop' })));

const apiKey = 'test-api-key';
const inner = {
model: 'test-model',
max_tokens: 100,
messages: [{ role: 'user', content: 'text' }],
temperature: 0.5,
stop_sequences: [],
};

// First request
const result1 = await handleRequest(inner, apiKey);
const headers1 = fetch.mock.calls[0][1].headers;

// Second request
const result2 = await handleRequest(inner, apiKey);
const headers2 = fetch.mock.calls[1][1].headers;

expect(headers1['anthropic-version']).not.toEqual(headers2['anthropic-version']);
});

// Test for random delays
test('generates random delays between requests', async () => {
const delays = [];
const inner = {
model: 'test-model',
max_tokens: 100,
messages: [{ role: 'user', content: 'text' }],
temperature: 0.5,
stop_sequences: [],
};

for (let i = 0; i < 10; i++) {
const start = Date.now();
await handleRequest(inner, apiKey);
const end = Date.now();
delays.push(end - start);
}

// Check if delays are random and within bounds of 0 to 500ms
delays.forEach(delay => {
expect(delay).toBeGreaterThanOrEqual(0);
expect(delay).toBeLessThanOrEqual(500);
});
});

// Test for max_tokens variability
test('should honor maximum tokens variability', async () => {
fetch.mockResolvedValue(new Response(JSON.stringify({ content: [{ text: 'hello' }], usage: { input_tokens: 5, output_tokens: 3 }, finish_reason: 'stop' })));
const inner = {
model: 'test-model',
max_tokens: 100,
messages: [{ role: 'user', content: 'test content' }],
temperature: 0.5,
stop_sequences: [],
};

const result = await handleRequest(inner, apiKey);
expect(result).toBeDefined();
expect(result.usage.output_tokens).toBeLessThanOrEqual(105); // Allowing ±5%
expect(result.usage.output_tokens).toBeGreaterThanOrEqual(95);
});

// Test for anti_fingerprint config respect
test('should respect anti_fingerprint config', async () => {
// Implement test logic here
});

});
75 changes: 74 additions & 1 deletion tests/provider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ describe('provider', () => {
let mockServer: ReturnType<typeof serve>;
let mockPort: number;

let mockKey = 'test-key'; // Example API Key for tests


// Mock Anthropic API
beforeAll(async () => {
const app = new Hono();
Expand Down Expand Up @@ -117,7 +120,77 @@ describe('provider', () => {
};

// The mock returns 200 for any model, so this will succeed
// For a real decrypt error test, we'd need the full provider pipeline
});
const results = await Promise.all(Array.from({ length: 10 }, async () => {
const inner: InnerPlaintext = {
messages: [{ role: 'user', content: 'hello' }],
model: 'claude-sonnet-4-20250514',
max_tokens: 100,
temperature: 1,
top_p: 1,
stop_sequences: [],
stream: false,
};

return handleRequest(inner, 'test-key', undefined, `http://localhost:${mockPort}`);
}));
const versions = results.map(result => result.headers['anthropic-version']);
const uniqueVersions = new Set(versions);
expect(uniqueVersions.size).toBeGreaterThan(1);
});

it('should enforce random delays between 0-500ms', async () => {
const inner: InnerPlaintext = {
messages: [{ role: 'user', content: 'hello' }],
model: 'claude-sonnet-4-20250514',
max_tokens: 100,
temperature: 1,
top_p: 1,
stop_sequences: [],
stream: false,
};

const start = Date.now();
await handleRequest(inner, 'test-key', undefined, `http://localhost:${mockPort}`);
const delay = Date.now() - start;
expect(delay).toBeGreaterThanOrEqual(0);
expect(delay).toBeLessThanOrEqual(500);
});

it('should randomize max_tokens within ±5%', async () => {
const originalMaxTokens = 100;
const inner: InnerPlaintext = {
messages: [{ role: 'user', content: 'hello' }],
model: 'claude-sonnet-4-20250514',
max_tokens: originalMaxTokens,
temperature: 1,
top_p: 1,
stop_sequences: [],
stream: false,
};

const result = await handleRequest(inner, 'test-key', undefined, `http://localhost:${mockPort}`);
const adjustedMaxTokens = Math.round(originalMaxTokens * (1 + (Math.random() * 0.1 - 0.05)));
expect(result.max_tokens).toBe(adjustedMaxTokens);
});

it('should rotate User-Agent strings', async () => {
const inner: InnerPlaintext = {
messages: [{ role: 'user', content: 'hello' }],
model: 'claude-sonnet-4-20250514',
max_tokens: 100,
temperature: 1,
top_p: 1,
stop_sequences: [],
stream: false,
};

const result = await handleRequest(inner, 'test-key', undefined, `http://localhost:${mockPort}`);
const userAgent = result.headers['User-Agent'];
expect(userAgent).toMatch(/some-random-user-agent/i);
});

});
const result = await handleRequest(inner, 'test-key', undefined, `http://localhost:${mockPort}`);
expect(result.content).toBeDefined();
});
Expand Down