anthropic-gateway 1.0.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/LICENSE +25 -0
- package/README.md +75 -0
- package/cli.js +261 -0
- package/config.example.json +20 -0
- package/lib/server.js +132 -0
- package/package.json +39 -0
- package/vendor/LICENSE.claude-adapter +21 -0
- package/vendor/dist/cli.js +288 -0
- package/vendor/dist/converters/index.js +22 -0
- package/vendor/dist/converters/request.js +349 -0
- package/vendor/dist/converters/response.js +125 -0
- package/vendor/dist/converters/streaming.js +308 -0
- package/vendor/dist/converters/tools.js +51 -0
- package/vendor/dist/converters/xmlPrompt.js +87 -0
- package/vendor/dist/converters/xmlStreaming.js +258 -0
- package/vendor/dist/index.js +32 -0
- package/vendor/dist/server/handlers.js +184 -0
- package/vendor/dist/server/index.js +116 -0
- package/vendor/dist/types/anthropic.js +4 -0
- package/vendor/dist/types/config.js +4 -0
- package/vendor/dist/types/index.js +21 -0
- package/vendor/dist/types/openai.js +4 -0
- package/vendor/dist/utils/config.js +169 -0
- package/vendor/dist/utils/errorLog.js +61 -0
- package/vendor/dist/utils/fileStorage.js +114 -0
- package/vendor/dist/utils/index.js +19 -0
- package/vendor/dist/utils/logger.js +138 -0
- package/vendor/dist/utils/metadata.js +128 -0
- package/vendor/dist/utils/provider.js +14 -0
- package/vendor/dist/utils/tokenUsage.js +32 -0
- package/vendor/dist/utils/ui.js +107 -0
- package/vendor/dist/utils/update.js +112 -0
- package/vendor/dist/utils/validation.js +130 -0
- package/vendor/package.json +6 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.UI = void 0;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const logger_1 = require("./logger");
|
|
9
|
+
// Claude Code Inspired Palette
|
|
10
|
+
const Palette = {
|
|
11
|
+
Brand: '#D97757', // Warm Terracotta (Main Brand)
|
|
12
|
+
Error: '#D95858', // Soft Red
|
|
13
|
+
Warning: '#D9A458', // Mustard Yellow
|
|
14
|
+
Dim: '#6B6B6B', // Dark Gray
|
|
15
|
+
Text: '#E6E6E6', // Off-White
|
|
16
|
+
Border: '#3F3F3F', // Subtle Border
|
|
17
|
+
Highlight: '#A78BFA', // Soft Purple (Files/Links)
|
|
18
|
+
};
|
|
19
|
+
class UI {
|
|
20
|
+
static log(message) {
|
|
21
|
+
logger_1.logger.print(message);
|
|
22
|
+
}
|
|
23
|
+
static info(message) {
|
|
24
|
+
this.log(`${chalk_1.default.hex(Palette.Dim).bold('•')} ${chalk_1.default.hex(Palette.Text)(message)}`);
|
|
25
|
+
}
|
|
26
|
+
static success(message) {
|
|
27
|
+
this.log(`${chalk_1.default.hex(Palette.Brand)('✔')} ${chalk_1.default.hex(Palette.Brand)(message)}`);
|
|
28
|
+
}
|
|
29
|
+
static warning(message) {
|
|
30
|
+
this.log(`${chalk_1.default.hex(Palette.Dim)('⚠')} ${message}`);
|
|
31
|
+
}
|
|
32
|
+
static error(message, error) {
|
|
33
|
+
this.log(`${chalk_1.default.hex(Palette.Dim)('✖')} ${message}`);
|
|
34
|
+
if (error && error.message) {
|
|
35
|
+
this.log(chalk_1.default.hex(Palette.Error)(` ${error.message}`));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
static header(subtitle) {
|
|
39
|
+
this.log('');
|
|
40
|
+
this.log(chalk_1.default.hex(Palette.Dim)(` ${subtitle}`));
|
|
41
|
+
this.log('');
|
|
42
|
+
}
|
|
43
|
+
static status(text) {
|
|
44
|
+
this.log(`${chalk_1.default.hex(Palette.Dim)('•')} ${chalk_1.default.hex(Palette.Text)(text)}`);
|
|
45
|
+
}
|
|
46
|
+
static statusDone(success = true, text) {
|
|
47
|
+
if (success) {
|
|
48
|
+
this.log(`${chalk_1.default.hex(Palette.Dim)('✔')} ${text || ''}`);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
this.log(`${chalk_1.default.hex(Palette.Dim)('✖')} ${text || ''}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
static box(title, content) {
|
|
55
|
+
const border = chalk_1.default.hex(Palette.Border)('──────────────────────────────────────────────────');
|
|
56
|
+
this.log('');
|
|
57
|
+
this.log(border);
|
|
58
|
+
this.log(chalk_1.default.hex(Palette.Brand).bold(` ${title}`));
|
|
59
|
+
this.log(border);
|
|
60
|
+
content.forEach((line) => this.log(` ${line}`));
|
|
61
|
+
this.log(border);
|
|
62
|
+
this.log('');
|
|
63
|
+
}
|
|
64
|
+
static newUrl(url) {
|
|
65
|
+
return chalk_1.default.hex(Palette.Highlight).bold.underline(url);
|
|
66
|
+
}
|
|
67
|
+
static dim(text) {
|
|
68
|
+
return chalk_1.default.hex(Palette.Dim)(text);
|
|
69
|
+
}
|
|
70
|
+
static highlight(text) {
|
|
71
|
+
return chalk_1.default.hex(Palette.Highlight)(text);
|
|
72
|
+
}
|
|
73
|
+
static hint(text) {
|
|
74
|
+
this.log(` ${chalk_1.default.hex(Palette.Dim)(text)}`);
|
|
75
|
+
}
|
|
76
|
+
static banner() {
|
|
77
|
+
const brand = chalk_1.default.hex(Palette.Brand);
|
|
78
|
+
const dim = chalk_1.default.hex(Palette.Dim);
|
|
79
|
+
// USB adapter with CLAUDE text inside
|
|
80
|
+
const art = [
|
|
81
|
+
'',
|
|
82
|
+
dim(' ┌────────────────────┐'),
|
|
83
|
+
dim(' │ ') + brand('┌─┐┬ ┌─┐┬ ┬┌┬┐┌─┐') + dim(' ├──┐'),
|
|
84
|
+
dim(' │ ') + brand('│ │ ├─┤│ │ ││├┤ ') + dim(' │▓▓│'),
|
|
85
|
+
dim(' │ ') + brand('└─┘┴─┘┴ ┴└─┘─┴┘└─┘') + dim(' ├──┘'),
|
|
86
|
+
dim(' └──────•ADAPTER──────┘'),
|
|
87
|
+
'',
|
|
88
|
+
];
|
|
89
|
+
art.forEach((line) => this.log(line));
|
|
90
|
+
}
|
|
91
|
+
static table(rows) {
|
|
92
|
+
const maxLabelWidth = Math.max(...rows.map((r) => r.label.length));
|
|
93
|
+
this.log('');
|
|
94
|
+
rows.forEach((row) => {
|
|
95
|
+
const paddedLabel = row.label.padEnd(maxLabelWidth);
|
|
96
|
+
this.log(` ${chalk_1.default.hex(Palette.Dim)(paddedLabel)} ${chalk_1.default.hex(Palette.Highlight)(row.value)}`);
|
|
97
|
+
});
|
|
98
|
+
this.log('');
|
|
99
|
+
}
|
|
100
|
+
static updateNotify(current, latest) {
|
|
101
|
+
this.log('');
|
|
102
|
+
this.log(`${chalk_1.default.hex(Palette.Dim)('•')} ${chalk_1.default.hex(Palette.Text)('Update available:')} ${chalk_1.default.hex(Palette.Dim)(current)} ${chalk_1.default.hex(Palette.Dim)('→')} ${chalk_1.default.hex(Palette.Highlight)(latest)}`);
|
|
103
|
+
this.hint('Run "npm i -g claude-adapter" to update');
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
exports.UI = UI;
|
|
107
|
+
//# sourceMappingURL=ui.js.map
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.checkForUpdates = checkForUpdates;
|
|
7
|
+
exports.getCachedUpdateInfo = getCachedUpdateInfo;
|
|
8
|
+
const https_1 = __importDefault(require("https"));
|
|
9
|
+
const package_json_1 = require("../../package.json");
|
|
10
|
+
const metadata_1 = require("./metadata");
|
|
11
|
+
const CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours in milliseconds
|
|
12
|
+
/**
|
|
13
|
+
* Compare two semantic versions
|
|
14
|
+
* Returns true if latest is greater than current
|
|
15
|
+
*/
|
|
16
|
+
function isNewerVersion(latest, current) {
|
|
17
|
+
const parseVersion = (v) => v.split('.').map(n => parseInt(n, 10) || 0);
|
|
18
|
+
const [latestParts, currentParts] = [parseVersion(latest), parseVersion(current)];
|
|
19
|
+
for (let i = 0; i < Math.max(latestParts.length, currentParts.length); i++) {
|
|
20
|
+
const l = latestParts[i] || 0;
|
|
21
|
+
const c = currentParts[i] || 0;
|
|
22
|
+
if (l > c)
|
|
23
|
+
return true;
|
|
24
|
+
if (l < c)
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Check if cached version is still valid (within 24 hours)
|
|
31
|
+
*/
|
|
32
|
+
function isCacheValid(timestamp) {
|
|
33
|
+
return Date.now() - timestamp < CACHE_TTL;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Fetch latest version from npm registry
|
|
37
|
+
*/
|
|
38
|
+
function fetchLatestVersion() {
|
|
39
|
+
return new Promise((resolve) => {
|
|
40
|
+
const timeout = setTimeout(() => resolve(null), 3000); // 3s timeout
|
|
41
|
+
https_1.default.get('https://registry.npmjs.org/claude-adapter/latest', (res) => {
|
|
42
|
+
let data = '';
|
|
43
|
+
res.on('data', chunk => data += chunk);
|
|
44
|
+
res.on('end', () => {
|
|
45
|
+
clearTimeout(timeout);
|
|
46
|
+
try {
|
|
47
|
+
const { version } = JSON.parse(data);
|
|
48
|
+
resolve(version);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
resolve(null);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}).on('error', () => {
|
|
55
|
+
clearTimeout(timeout);
|
|
56
|
+
resolve(null);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Check for updates with 24-hour caching
|
|
62
|
+
* Non-blocking, fails silently on errors
|
|
63
|
+
*/
|
|
64
|
+
async function checkForUpdates() {
|
|
65
|
+
try {
|
|
66
|
+
// Check cache first
|
|
67
|
+
const cache = (0, metadata_1.getCachedLatestVersion)();
|
|
68
|
+
if (cache && isCacheValid(cache.timestamp)) {
|
|
69
|
+
return {
|
|
70
|
+
current: package_json_1.version,
|
|
71
|
+
latest: cache.version,
|
|
72
|
+
hasUpdate: isNewerVersion(cache.version, package_json_1.version)
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
// Fetch from registry
|
|
76
|
+
const latest = await fetchLatestVersion();
|
|
77
|
+
if (!latest) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
// Update cache in metadata
|
|
81
|
+
(0, metadata_1.updateLatestVersion)(latest);
|
|
82
|
+
return {
|
|
83
|
+
current: package_json_1.version,
|
|
84
|
+
latest,
|
|
85
|
+
hasUpdate: isNewerVersion(latest, package_json_1.version)
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Get cached update info synchronously (for use in request converter)
|
|
94
|
+
* Returns null if cache doesn't exist or is expired
|
|
95
|
+
*/
|
|
96
|
+
function getCachedUpdateInfo() {
|
|
97
|
+
try {
|
|
98
|
+
const cache = (0, metadata_1.getCachedLatestVersion)();
|
|
99
|
+
if (cache && isCacheValid(cache.timestamp)) {
|
|
100
|
+
return {
|
|
101
|
+
current: package_json_1.version,
|
|
102
|
+
latest: cache.version,
|
|
103
|
+
hasUpdate: isNewerVersion(cache.version, package_json_1.version)
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
// Ignore errors
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=update.js.map
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.validateAnthropicRequest = validateAnthropicRequest;
|
|
4
|
+
exports.formatValidationErrors = formatValidationErrors;
|
|
5
|
+
/**
|
|
6
|
+
* Validate an incoming Anthropic Messages API request
|
|
7
|
+
*/
|
|
8
|
+
function validateAnthropicRequest(body) {
|
|
9
|
+
const errors = [];
|
|
10
|
+
// Check if body is an object
|
|
11
|
+
if (!body || typeof body !== 'object') {
|
|
12
|
+
return { valid: false, errors: [{ field: 'body', message: 'Request body must be an object' }] };
|
|
13
|
+
}
|
|
14
|
+
const request = body;
|
|
15
|
+
// Required field: model
|
|
16
|
+
if (!request.model || typeof request.model !== 'string') {
|
|
17
|
+
errors.push({ field: 'model', message: 'model is required and must be a string' });
|
|
18
|
+
}
|
|
19
|
+
// Required field: max_tokens
|
|
20
|
+
if (request.max_tokens === undefined || typeof request.max_tokens !== 'number') {
|
|
21
|
+
errors.push({ field: 'max_tokens', message: 'max_tokens is required and must be a number' });
|
|
22
|
+
}
|
|
23
|
+
else if (request.max_tokens <= 0) {
|
|
24
|
+
errors.push({ field: 'max_tokens', message: 'max_tokens must be a positive number' });
|
|
25
|
+
}
|
|
26
|
+
// Required field: messages
|
|
27
|
+
if (!request.messages) {
|
|
28
|
+
errors.push({ field: 'messages', message: 'messages is required' });
|
|
29
|
+
}
|
|
30
|
+
else if (!Array.isArray(request.messages)) {
|
|
31
|
+
errors.push({ field: 'messages', message: 'messages must be an array' });
|
|
32
|
+
}
|
|
33
|
+
else if (request.messages.length === 0) {
|
|
34
|
+
errors.push({ field: 'messages', message: 'messages array cannot be empty' });
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
// Validate each message
|
|
38
|
+
const messageErrors = validateMessages(request.messages);
|
|
39
|
+
errors.push(...messageErrors);
|
|
40
|
+
}
|
|
41
|
+
// Optional field validations
|
|
42
|
+
if (request.temperature !== undefined) {
|
|
43
|
+
if (typeof request.temperature !== 'number' ||
|
|
44
|
+
request.temperature < 0 ||
|
|
45
|
+
request.temperature > 1) {
|
|
46
|
+
errors.push({
|
|
47
|
+
field: 'temperature',
|
|
48
|
+
message: 'temperature must be a number between 0 and 1',
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (request.top_p !== undefined) {
|
|
53
|
+
if (typeof request.top_p !== 'number' || request.top_p < 0 || request.top_p > 1) {
|
|
54
|
+
errors.push({ field: 'top_p', message: 'top_p must be a number between 0 and 1' });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (request.stream !== undefined && typeof request.stream !== 'boolean') {
|
|
58
|
+
errors.push({ field: 'stream', message: 'stream must be a boolean' });
|
|
59
|
+
}
|
|
60
|
+
return { valid: errors.length === 0, errors };
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Validate message array structure
|
|
64
|
+
*/
|
|
65
|
+
function validateMessages(messages) {
|
|
66
|
+
const errors = [];
|
|
67
|
+
for (let i = 0; i < messages.length; i++) {
|
|
68
|
+
const msg = messages[i];
|
|
69
|
+
if (!msg || typeof msg !== 'object') {
|
|
70
|
+
errors.push({ field: `messages[${i}]`, message: 'message must be an object' });
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const message = msg;
|
|
74
|
+
// Validate role - accept any string role for forward compatibility
|
|
75
|
+
// Claude Code may introduce new roles (e.g., from session-start hooks)
|
|
76
|
+
// The converter will map unknown roles to valid OpenAI roles downstream
|
|
77
|
+
if (!message.role || typeof message.role !== 'string') {
|
|
78
|
+
errors.push({
|
|
79
|
+
field: `messages[${i}].role`,
|
|
80
|
+
message: 'role is required and must be a string',
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
// Validate content - allow missing/empty content for forward compatibility
|
|
84
|
+
// Some message types (e.g., hook attachments) may have minimal content
|
|
85
|
+
if (message.content !== undefined && message.content !== null) {
|
|
86
|
+
if (typeof message.content !== 'string' && !Array.isArray(message.content)) {
|
|
87
|
+
errors.push({
|
|
88
|
+
field: `messages[${i}].content`,
|
|
89
|
+
message: 'content must be a string or array',
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
else if (Array.isArray(message.content)) {
|
|
93
|
+
const contentErrors = validateContentBlocks(message.content, i);
|
|
94
|
+
errors.push(...contentErrors);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return errors;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Validate content blocks array
|
|
102
|
+
*/
|
|
103
|
+
function validateContentBlocks(blocks, messageIndex) {
|
|
104
|
+
const errors = [];
|
|
105
|
+
for (let j = 0; j < blocks.length; j++) {
|
|
106
|
+
const block = blocks[j];
|
|
107
|
+
if (!block || typeof block !== 'object') {
|
|
108
|
+
errors.push({
|
|
109
|
+
field: `messages[${messageIndex}].content[${j}]`,
|
|
110
|
+
message: 'content block must be an object',
|
|
111
|
+
});
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const contentBlock = block;
|
|
115
|
+
if (!contentBlock.type || typeof contentBlock.type !== 'string') {
|
|
116
|
+
errors.push({
|
|
117
|
+
field: `messages[${messageIndex}].content[${j}].type`,
|
|
118
|
+
message: 'content block type is required',
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return errors;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Format validation errors into a human-readable message
|
|
126
|
+
*/
|
|
127
|
+
function formatValidationErrors(errors) {
|
|
128
|
+
return errors.map((e) => `${e.field}: ${e.message}`).join('; ');
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=validation.js.map
|