llmapi-v2 2.1.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.
Files changed (162) hide show
  1. package/.env.example +40 -0
  2. package/Dockerfile +17 -0
  3. package/dist/config.d.ts +48 -0
  4. package/dist/config.js +98 -0
  5. package/dist/config.js.map +1 -0
  6. package/dist/converter/request.d.ts +6 -0
  7. package/dist/converter/request.js +184 -0
  8. package/dist/converter/request.js.map +1 -0
  9. package/dist/converter/response.d.ts +6 -0
  10. package/dist/converter/response.js +76 -0
  11. package/dist/converter/response.js.map +1 -0
  12. package/dist/converter/stream.d.ts +54 -0
  13. package/dist/converter/stream.js +318 -0
  14. package/dist/converter/stream.js.map +1 -0
  15. package/dist/converter/types.d.ts +239 -0
  16. package/dist/converter/types.js +6 -0
  17. package/dist/converter/types.js.map +1 -0
  18. package/dist/data/posts.d.ts +19 -0
  19. package/dist/data/posts.js +462 -0
  20. package/dist/data/posts.js.map +1 -0
  21. package/dist/index.d.ts +1 -0
  22. package/dist/index.js +233 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/middleware/api-key-auth.d.ts +6 -0
  25. package/dist/middleware/api-key-auth.js +76 -0
  26. package/dist/middleware/api-key-auth.js.map +1 -0
  27. package/dist/middleware/quota-guard.d.ts +10 -0
  28. package/dist/middleware/quota-guard.js +27 -0
  29. package/dist/middleware/quota-guard.js.map +1 -0
  30. package/dist/middleware/rate-limiter.d.ts +5 -0
  31. package/dist/middleware/rate-limiter.js +50 -0
  32. package/dist/middleware/rate-limiter.js.map +1 -0
  33. package/dist/middleware/request-logger.d.ts +6 -0
  34. package/dist/middleware/request-logger.js +37 -0
  35. package/dist/middleware/request-logger.js.map +1 -0
  36. package/dist/middleware/session-auth.d.ts +19 -0
  37. package/dist/middleware/session-auth.js +99 -0
  38. package/dist/middleware/session-auth.js.map +1 -0
  39. package/dist/providers/aliyun.d.ts +13 -0
  40. package/dist/providers/aliyun.js +20 -0
  41. package/dist/providers/aliyun.js.map +1 -0
  42. package/dist/providers/base-provider.d.ts +36 -0
  43. package/dist/providers/base-provider.js +133 -0
  44. package/dist/providers/base-provider.js.map +1 -0
  45. package/dist/providers/deepseek.d.ts +11 -0
  46. package/dist/providers/deepseek.js +18 -0
  47. package/dist/providers/deepseek.js.map +1 -0
  48. package/dist/providers/registry.d.ts +18 -0
  49. package/dist/providers/registry.js +98 -0
  50. package/dist/providers/registry.js.map +1 -0
  51. package/dist/providers/types.d.ts +17 -0
  52. package/dist/providers/types.js +3 -0
  53. package/dist/providers/types.js.map +1 -0
  54. package/dist/routes/admin.d.ts +1 -0
  55. package/dist/routes/admin.js +153 -0
  56. package/dist/routes/admin.js.map +1 -0
  57. package/dist/routes/auth.d.ts +2 -0
  58. package/dist/routes/auth.js +318 -0
  59. package/dist/routes/auth.js.map +1 -0
  60. package/dist/routes/blog.d.ts +1 -0
  61. package/dist/routes/blog.js +29 -0
  62. package/dist/routes/blog.js.map +1 -0
  63. package/dist/routes/dashboard.d.ts +1 -0
  64. package/dist/routes/dashboard.js +184 -0
  65. package/dist/routes/dashboard.js.map +1 -0
  66. package/dist/routes/messages.d.ts +1 -0
  67. package/dist/routes/messages.js +309 -0
  68. package/dist/routes/messages.js.map +1 -0
  69. package/dist/routes/models.d.ts +1 -0
  70. package/dist/routes/models.js +39 -0
  71. package/dist/routes/models.js.map +1 -0
  72. package/dist/routes/payment.d.ts +1 -0
  73. package/dist/routes/payment.js +150 -0
  74. package/dist/routes/payment.js.map +1 -0
  75. package/dist/routes/sitemap.d.ts +1 -0
  76. package/dist/routes/sitemap.js +38 -0
  77. package/dist/routes/sitemap.js.map +1 -0
  78. package/dist/services/alipay.d.ts +27 -0
  79. package/dist/services/alipay.js +106 -0
  80. package/dist/services/alipay.js.map +1 -0
  81. package/dist/services/database.d.ts +4 -0
  82. package/dist/services/database.js +170 -0
  83. package/dist/services/database.js.map +1 -0
  84. package/dist/services/health-checker.d.ts +13 -0
  85. package/dist/services/health-checker.js +95 -0
  86. package/dist/services/health-checker.js.map +1 -0
  87. package/dist/services/mailer.d.ts +3 -0
  88. package/dist/services/mailer.js +91 -0
  89. package/dist/services/mailer.js.map +1 -0
  90. package/dist/services/metrics.d.ts +56 -0
  91. package/dist/services/metrics.js +94 -0
  92. package/dist/services/metrics.js.map +1 -0
  93. package/dist/services/remote-control.d.ts +20 -0
  94. package/dist/services/remote-control.js +209 -0
  95. package/dist/services/remote-control.js.map +1 -0
  96. package/dist/services/remote-ws.d.ts +5 -0
  97. package/dist/services/remote-ws.js +143 -0
  98. package/dist/services/remote-ws.js.map +1 -0
  99. package/dist/services/usage.d.ts +13 -0
  100. package/dist/services/usage.js +39 -0
  101. package/dist/services/usage.js.map +1 -0
  102. package/dist/utils/errors.d.ts +27 -0
  103. package/dist/utils/errors.js +48 -0
  104. package/dist/utils/errors.js.map +1 -0
  105. package/dist/utils/logger.d.ts +2 -0
  106. package/dist/utils/logger.js +14 -0
  107. package/dist/utils/logger.js.map +1 -0
  108. package/docker-compose.yml +19 -0
  109. package/package.json +39 -0
  110. package/public/robots.txt +8 -0
  111. package/src/config.ts +140 -0
  112. package/src/converter/request.ts +207 -0
  113. package/src/converter/response.ts +85 -0
  114. package/src/converter/stream.ts +373 -0
  115. package/src/converter/types.ts +257 -0
  116. package/src/data/posts.ts +474 -0
  117. package/src/index.ts +219 -0
  118. package/src/middleware/api-key-auth.ts +82 -0
  119. package/src/middleware/quota-guard.ts +28 -0
  120. package/src/middleware/rate-limiter.ts +61 -0
  121. package/src/middleware/request-logger.ts +36 -0
  122. package/src/middleware/session-auth.ts +91 -0
  123. package/src/providers/aliyun.ts +16 -0
  124. package/src/providers/base-provider.ts +148 -0
  125. package/src/providers/deepseek.ts +14 -0
  126. package/src/providers/registry.ts +111 -0
  127. package/src/providers/types.ts +26 -0
  128. package/src/routes/admin.ts +169 -0
  129. package/src/routes/auth.ts +369 -0
  130. package/src/routes/blog.ts +28 -0
  131. package/src/routes/dashboard.ts +208 -0
  132. package/src/routes/messages.ts +346 -0
  133. package/src/routes/models.ts +37 -0
  134. package/src/routes/payment.ts +189 -0
  135. package/src/routes/sitemap.ts +40 -0
  136. package/src/services/alipay.ts +116 -0
  137. package/src/services/database.ts +187 -0
  138. package/src/services/health-checker.ts +115 -0
  139. package/src/services/mailer.ts +90 -0
  140. package/src/services/metrics.ts +104 -0
  141. package/src/services/remote-control.ts +226 -0
  142. package/src/services/remote-ws.ts +145 -0
  143. package/src/services/usage.ts +57 -0
  144. package/src/types/express.d.ts +46 -0
  145. package/src/utils/errors.ts +44 -0
  146. package/src/utils/logger.ts +8 -0
  147. package/tsconfig.json +17 -0
  148. package/views/pages/404.ejs +14 -0
  149. package/views/pages/admin.ejs +307 -0
  150. package/views/pages/blog-post.ejs +378 -0
  151. package/views/pages/blog.ejs +148 -0
  152. package/views/pages/dashboard.ejs +441 -0
  153. package/views/pages/docs.ejs +807 -0
  154. package/views/pages/index.ejs +416 -0
  155. package/views/pages/login.ejs +170 -0
  156. package/views/pages/orders.ejs +111 -0
  157. package/views/pages/pricing.ejs +379 -0
  158. package/views/pages/register.ejs +397 -0
  159. package/views/pages/remote.ejs +334 -0
  160. package/views/pages/settings.ejs +373 -0
  161. package/views/partials/header.ejs +70 -0
  162. package/views/partials/nav.ejs +140 -0
package/.env.example ADDED
@@ -0,0 +1,40 @@
1
+ # Server
2
+ PORT=3000
3
+ JWT_SECRET=change-this-to-a-random-secret
4
+ NODE_ENV=production
5
+ LOG_LEVEL=info
6
+
7
+ # PostgreSQL (shared instance)
8
+ DATABASE_URL=postgresql://llmapi_user:change-this@postgres:5432/llmapi
9
+
10
+ # Provider: Alibaba Bailian (DashScope) - PRIMARY
11
+ ALIYUN_ENABLED=true
12
+ ALIYUN_API_KEY=sk-xxx
13
+ ALIYUN_ANTHROPIC_URL=https://dashscope.aliyuncs.com/apps/anthropic
14
+ ALIYUN_OPENAI_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
15
+
16
+ # Provider: DeepSeek - SECONDARY
17
+ DEEPSEEK_ENABLED=false
18
+ DEEPSEEK_API_KEY=sk-f38bd5a0ba454e59af92cd9af5be1200
19
+ DEEPSEEK_ANTHROPIC_URL=https://api.deepseek.com/anthropic
20
+ DEEPSEEK_OPENAI_URL=https://api.deepseek.com/v1
21
+
22
+ # Smart routing
23
+ SMART_ROUTING_LIGHT_MAX_MESSAGES=4
24
+
25
+ # Google OAuth
26
+ GOOGLE_CLIENT_ID=
27
+ GOOGLE_CLIENT_SECRET=
28
+ GOOGLE_REDIRECT_URI=https://llmapi.pro/api/auth/google/callback
29
+
30
+ # Email (Resend) - optional
31
+ RESEND_API_KEY=
32
+
33
+ # Alipay - optional
34
+ ALIPAY_APP_ID=
35
+ ALIPAY_PRIVATE_KEY=
36
+ ALIPAY_PUBLIC_KEY=
37
+ ALIPAY_GATEWAY=https://openapi.alipay.com/gateway.do
38
+
39
+ # Site
40
+ SITE_URL=https://llmapi.pro
package/Dockerfile ADDED
@@ -0,0 +1,17 @@
1
+ FROM node:20-alpine AS builder
2
+ WORKDIR /app
3
+ COPY package*.json ./
4
+ RUN npm ci
5
+ COPY tsconfig.json ./
6
+ COPY src/ ./src/
7
+ RUN npm run build
8
+
9
+ FROM node:20-alpine
10
+ WORKDIR /app
11
+ COPY package*.json ./
12
+ RUN npm ci --omit=dev
13
+ COPY --from=builder /app/dist ./dist
14
+ COPY views/ ./views/
15
+ COPY public/ ./public/
16
+ EXPOSE 3000
17
+ CMD ["node", "dist/index.js"]
@@ -0,0 +1,48 @@
1
+ export interface ProviderConfig {
2
+ name: string;
3
+ /** Native Anthropic-compatible endpoint (preferred) */
4
+ anthropicBaseUrl: string;
5
+ /** OpenAI-compatible endpoint (fallback for providers without native Anthropic support) */
6
+ openaiBaseUrl?: string;
7
+ apiKey: string;
8
+ enabled: boolean;
9
+ timeout: number;
10
+ /** Maps Claude model names to this provider's model names */
11
+ models: Record<string, string>;
12
+ /** Cost per 1M tokens in RMB: { input, output, cached_input } */
13
+ pricing: Record<string, {
14
+ input: number;
15
+ output: number;
16
+ cached_input?: number;
17
+ }>;
18
+ }
19
+ export interface ModelRoute {
20
+ provider: string;
21
+ model: string;
22
+ priority: number;
23
+ }
24
+ /** Smart routing: which models to use based on task complexity */
25
+ export interface SmartRoutingConfig {
26
+ /** Model for heavy coding tasks (tools present, complex) */
27
+ codingModel: {
28
+ provider: string;
29
+ model: string;
30
+ };
31
+ /** Model for simple/chat tasks (no tools or trivial) */
32
+ lightModel: {
33
+ provider: string;
34
+ model: string;
35
+ };
36
+ /** Threshold: if message count < this AND no tools, use light model */
37
+ lightTaskMaxMessages: number;
38
+ }
39
+ export interface AppConfig {
40
+ port: number;
41
+ jwtSecret: string;
42
+ databaseUrl: string;
43
+ providers: ProviderConfig[];
44
+ modelRouting: Record<string, ModelRoute[]>;
45
+ smartRouting: SmartRoutingConfig;
46
+ siteUrl: string;
47
+ }
48
+ export declare function loadConfig(): AppConfig;
package/dist/config.js ADDED
@@ -0,0 +1,98 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.loadConfig = loadConfig;
4
+ function env(key, fallback) {
5
+ const val = process.env[key];
6
+ if (val !== undefined)
7
+ return val;
8
+ if (fallback !== undefined)
9
+ return fallback;
10
+ throw new Error(`Missing required env var: ${key}`);
11
+ }
12
+ function envBool(key, fallback) {
13
+ const val = process.env[key];
14
+ if (val === undefined)
15
+ return fallback;
16
+ return val === 'true' || val === '1';
17
+ }
18
+ function envInt(key, fallback) {
19
+ const val = process.env[key];
20
+ if (val === undefined)
21
+ return fallback;
22
+ const n = parseInt(val, 10);
23
+ return isNaN(n) ? fallback : n;
24
+ }
25
+ function loadConfig() {
26
+ const providers = [];
27
+ // Alibaba Bailian (DashScope) - PRIMARY: native Anthropic endpoint (China domestic)
28
+ if (envBool('ALIYUN_ENABLED', false)) {
29
+ providers.push({
30
+ name: 'aliyun',
31
+ anthropicBaseUrl: env('ALIYUN_ANTHROPIC_URL', 'https://dashscope.aliyuncs.com/apps/anthropic'),
32
+ openaiBaseUrl: env('ALIYUN_OPENAI_URL', 'https://dashscope.aliyuncs.com/compatible-mode/v1'),
33
+ apiKey: env('ALIYUN_API_KEY', ''),
34
+ enabled: true,
35
+ timeout: 300_000,
36
+ models: {
37
+ 'claude-sonnet-4-6': 'qwen3-coder-plus',
38
+ 'claude-opus-4-6': 'qwen-max',
39
+ 'claude-haiku-4-5': 'qwen-plus',
40
+ },
41
+ pricing: {
42
+ 'qwen3-coder-plus': { input: 3.5, output: 7.0, cached_input: 0.35 },
43
+ 'qwen-max': { input: 20.0, output: 60.0, cached_input: 2.0 },
44
+ 'qwen-plus': { input: 0.5, output: 1.5, cached_input: 0.05 },
45
+ },
46
+ });
47
+ }
48
+ // DeepSeek - native Anthropic endpoint for simple tasks
49
+ if (envBool('DEEPSEEK_ENABLED', false)) {
50
+ providers.push({
51
+ name: 'deepseek',
52
+ anthropicBaseUrl: env('DEEPSEEK_ANTHROPIC_URL', 'https://api.deepseek.com/anthropic'),
53
+ openaiBaseUrl: env('DEEPSEEK_OPENAI_URL', 'https://api.deepseek.com/v1'),
54
+ apiKey: env('DEEPSEEK_API_KEY', ''),
55
+ enabled: true,
56
+ timeout: 300_000,
57
+ models: {
58
+ 'claude-sonnet-4-6': 'deepseek-chat',
59
+ 'claude-opus-4-6': 'deepseek-chat',
60
+ 'claude-haiku-4-5': 'deepseek-chat',
61
+ },
62
+ pricing: {
63
+ 'deepseek-chat': { input: 1.0, output: 2.0, cached_input: 0.1 },
64
+ },
65
+ });
66
+ }
67
+ // Build model routing
68
+ const modelRouting = {};
69
+ const claudeModels = ['claude-sonnet-4-6', 'claude-opus-4-6', 'claude-haiku-4-5'];
70
+ for (const claudeModel of claudeModels) {
71
+ const routes = [];
72
+ for (let i = 0; i < providers.length; i++) {
73
+ const p = providers[i];
74
+ if (p.models[claudeModel]) {
75
+ routes.push({ provider: p.name, model: p.models[claudeModel], priority: i });
76
+ }
77
+ }
78
+ if (routes.length > 0) {
79
+ modelRouting[claudeModel] = routes.sort((a, b) => a.priority - b.priority);
80
+ }
81
+ }
82
+ // Smart routing config
83
+ const smartRouting = {
84
+ codingModel: { provider: 'aliyun', model: 'qwen3-coder-plus' },
85
+ lightModel: { provider: 'deepseek', model: 'deepseek-chat' },
86
+ lightTaskMaxMessages: envInt('SMART_ROUTING_LIGHT_MAX_MESSAGES', 4),
87
+ };
88
+ return {
89
+ port: envInt('PORT', 3000),
90
+ jwtSecret: env('JWT_SECRET', 'dev-secret-change-me'),
91
+ databaseUrl: env('DATABASE_URL', 'postgresql://llmapi_user:change-this@postgres:5432/llmapi'),
92
+ providers,
93
+ modelRouting,
94
+ smartRouting,
95
+ siteUrl: env('SITE_URL', 'http://localhost:3000'),
96
+ };
97
+ }
98
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import type { AnthropicRequest, OpenAIRequest } from './types';
2
+ /**
3
+ * Convert an Anthropic Messages API request to OpenAI Chat Completions format.
4
+ * This is the inbound conversion: Claude Code -> our proxy -> provider.
5
+ */
6
+ export declare function convertRequest(req: AnthropicRequest): OpenAIRequest;
@@ -0,0 +1,184 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.convertRequest = convertRequest;
4
+ /**
5
+ * Convert an Anthropic Messages API request to OpenAI Chat Completions format.
6
+ * This is the inbound conversion: Claude Code -> our proxy -> provider.
7
+ */
8
+ function convertRequest(req) {
9
+ const openaiMessages = convertMessages(req.messages, req.system);
10
+ const openaiTools = convertTools(req.tools);
11
+ const openaiToolChoice = convertToolChoice(req.tool_choice);
12
+ const body = {
13
+ model: '', // Will be set by the router
14
+ messages: openaiMessages,
15
+ stream: !!req.stream,
16
+ };
17
+ if (req.max_tokens)
18
+ body.max_tokens = req.max_tokens;
19
+ if (req.temperature !== undefined)
20
+ body.temperature = req.temperature;
21
+ if (req.top_p !== undefined)
22
+ body.top_p = req.top_p;
23
+ if (req.stop_sequences)
24
+ body.stop = req.stop_sequences;
25
+ if (openaiTools)
26
+ body.tools = openaiTools;
27
+ if (openaiToolChoice)
28
+ body.tool_choice = openaiToolChoice;
29
+ if (req.stream)
30
+ body.stream_options = { include_usage: true };
31
+ return body;
32
+ }
33
+ /**
34
+ * Convert Anthropic messages array + system prompt to OpenAI messages.
35
+ */
36
+ function convertMessages(messages, system) {
37
+ const result = [];
38
+ // System prompt
39
+ if (system) {
40
+ const systemText = typeof system === 'string'
41
+ ? system
42
+ : system.map(b => b.text).join('\n');
43
+ if (systemText) {
44
+ result.push({ role: 'system', content: systemText });
45
+ }
46
+ }
47
+ for (const msg of messages) {
48
+ if (msg.role === 'user') {
49
+ convertUserMessage(msg, result);
50
+ }
51
+ else if (msg.role === 'assistant') {
52
+ convertAssistantMessage(msg, result);
53
+ }
54
+ }
55
+ return result;
56
+ }
57
+ /**
58
+ * Convert a user message. Anthropic user messages can contain interleaved
59
+ * text and tool_result blocks. OpenAI requires tool results as separate messages.
60
+ */
61
+ function convertUserMessage(msg, result) {
62
+ if (typeof msg.content === 'string') {
63
+ result.push({ role: 'user', content: msg.content });
64
+ return;
65
+ }
66
+ const textParts = [];
67
+ const imageParts = [];
68
+ for (const block of msg.content) {
69
+ switch (block.type) {
70
+ case 'text':
71
+ textParts.push(block.text);
72
+ break;
73
+ case 'tool_result': {
74
+ // Each tool_result becomes a separate OpenAI tool message
75
+ let content;
76
+ if (typeof block.content === 'string') {
77
+ content = block.content;
78
+ }
79
+ else if (Array.isArray(block.content)) {
80
+ content = block.content
81
+ .map(b => ('text' in b ? b.text : JSON.stringify(b)))
82
+ .join('\n');
83
+ }
84
+ else {
85
+ content = '';
86
+ }
87
+ if (block.is_error) {
88
+ content = `[ERROR] ${content}`;
89
+ }
90
+ result.push({
91
+ role: 'tool',
92
+ tool_call_id: block.tool_use_id,
93
+ content,
94
+ });
95
+ break;
96
+ }
97
+ case 'image': {
98
+ // Convert base64 image to OpenAI image_url format
99
+ const dataUrl = `data:${block.source.media_type};base64,${block.source.data}`;
100
+ imageParts.push({ type: 'image_url', image_url: { url: dataUrl } });
101
+ break;
102
+ }
103
+ }
104
+ }
105
+ // Emit text/image as user message
106
+ if (textParts.length > 0 && imageParts.length === 0) {
107
+ result.push({ role: 'user', content: textParts.join('\n') });
108
+ }
109
+ else if (imageParts.length > 0) {
110
+ const parts = [];
111
+ if (textParts.length > 0) {
112
+ parts.push({ type: 'text', text: textParts.join('\n') });
113
+ }
114
+ parts.push(...imageParts);
115
+ result.push({ role: 'user', content: parts });
116
+ }
117
+ }
118
+ /**
119
+ * Convert an assistant message. Anthropic assistant messages can contain
120
+ * text, thinking, and tool_use blocks. OpenAI uses tool_calls array.
121
+ */
122
+ function convertAssistantMessage(msg, result) {
123
+ if (typeof msg.content === 'string') {
124
+ result.push({ role: 'assistant', content: msg.content });
125
+ return;
126
+ }
127
+ const textParts = [];
128
+ const toolCalls = [];
129
+ for (const block of msg.content) {
130
+ switch (block.type) {
131
+ case 'text':
132
+ textParts.push(block.text);
133
+ break;
134
+ case 'thinking':
135
+ // Thinking blocks: some providers support reasoning_content,
136
+ // but for history replay we skip them (provider generates its own thinking)
137
+ break;
138
+ case 'tool_use':
139
+ toolCalls.push({
140
+ id: block.id,
141
+ type: 'function',
142
+ function: {
143
+ name: block.name,
144
+ arguments: JSON.stringify(block.input),
145
+ },
146
+ });
147
+ break;
148
+ }
149
+ }
150
+ const assistantMsg = { role: 'assistant' };
151
+ assistantMsg.content = textParts.length > 0 ? textParts.join('\n') : null;
152
+ if (toolCalls.length > 0)
153
+ assistantMsg.tool_calls = toolCalls;
154
+ result.push(assistantMsg);
155
+ }
156
+ /**
157
+ * Convert Anthropic tool definitions to OpenAI format.
158
+ */
159
+ function convertTools(tools) {
160
+ if (!tools || tools.length === 0)
161
+ return undefined;
162
+ return tools.map(tool => ({
163
+ type: 'function',
164
+ function: {
165
+ name: tool.name,
166
+ description: tool.description || '',
167
+ parameters: tool.input_schema || {},
168
+ },
169
+ }));
170
+ }
171
+ /**
172
+ * Convert Anthropic tool_choice to OpenAI format.
173
+ */
174
+ function convertToolChoice(choice) {
175
+ if (!choice)
176
+ return undefined;
177
+ switch (choice.type) {
178
+ case 'auto': return 'auto';
179
+ case 'any': return 'required';
180
+ case 'tool': return { type: 'function', function: { name: choice.name } };
181
+ default: return 'auto';
182
+ }
183
+ }
184
+ //# sourceMappingURL=request.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request.js","sourceRoot":"","sources":["../../src/converter/request.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import type { AnthropicResponse, OpenAIResponse } from './types';
2
+ /**
3
+ * Convert an OpenAI Chat Completions response to Anthropic Messages format.
4
+ * Used for non-streaming requests only.
5
+ */
6
+ export declare function convertResponse(openaiResp: OpenAIResponse, displayModel: string): AnthropicResponse;
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.convertResponse = convertResponse;
4
+ const uuid_1 = require("uuid");
5
+ /**
6
+ * Convert an OpenAI Chat Completions response to Anthropic Messages format.
7
+ * Used for non-streaming requests only.
8
+ */
9
+ function convertResponse(openaiResp, displayModel) {
10
+ const choice = openaiResp.choices?.[0];
11
+ if (!choice) {
12
+ return {
13
+ id: `msg_${(0, uuid_1.v4)().replace(/-/g, '')}`,
14
+ type: 'message',
15
+ role: 'assistant',
16
+ model: displayModel,
17
+ content: [{ type: 'text', text: 'No response from backend.' }],
18
+ stop_reason: 'end_turn',
19
+ stop_sequence: null,
20
+ usage: { input_tokens: 0, output_tokens: 0 },
21
+ };
22
+ }
23
+ const msg = choice.message;
24
+ const content = [];
25
+ // Reasoning / thinking content
26
+ if (msg.reasoning_content) {
27
+ content.push({ type: 'thinking', thinking: msg.reasoning_content });
28
+ }
29
+ // Text content
30
+ if (msg.content) {
31
+ content.push({ type: 'text', text: msg.content });
32
+ }
33
+ // Tool calls
34
+ if (msg.tool_calls && msg.tool_calls.length > 0) {
35
+ for (const tc of msg.tool_calls) {
36
+ let input = {};
37
+ try {
38
+ input = JSON.parse(tc.function.arguments);
39
+ }
40
+ catch { }
41
+ content.push({
42
+ type: 'tool_use',
43
+ id: tc.id || `toolu_${(0, uuid_1.v4)().replace(/-/g, '').slice(0, 24)}`,
44
+ name: tc.function.name,
45
+ input,
46
+ });
47
+ }
48
+ }
49
+ // If no content at all, add empty text
50
+ if (content.length === 0) {
51
+ content.push({ type: 'text', text: '' });
52
+ }
53
+ // Determine stop reason
54
+ let stopReason = 'end_turn';
55
+ if (msg.tool_calls && msg.tool_calls.length > 0) {
56
+ stopReason = 'tool_use';
57
+ }
58
+ else if (choice.finish_reason === 'length') {
59
+ stopReason = 'max_tokens';
60
+ }
61
+ const usage = openaiResp.usage || { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 };
62
+ return {
63
+ id: `msg_${(0, uuid_1.v4)().replace(/-/g, '')}`,
64
+ type: 'message',
65
+ role: 'assistant',
66
+ model: displayModel,
67
+ content,
68
+ stop_reason: stopReason,
69
+ stop_sequence: null,
70
+ usage: {
71
+ input_tokens: usage.prompt_tokens,
72
+ output_tokens: usage.completion_tokens,
73
+ },
74
+ };
75
+ }
76
+ //# sourceMappingURL=response.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"response.js","sourceRoot":"","sources":["../../src/converter/response.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ import type { Response } from 'express';
2
+ import type { IncomingMessage } from 'http';
3
+ export interface StreamResult {
4
+ inputTokens: number;
5
+ outputTokens: number;
6
+ thinkingTokens: number;
7
+ ttftMs: number;
8
+ tokensPerSec: number;
9
+ durationMs: number;
10
+ }
11
+ /**
12
+ * Transforms an OpenAI SSE stream into Anthropic SSE stream format.
13
+ *
14
+ * This is the most critical component of the relay service.
15
+ * Claude Code expects Anthropic's streaming format exactly:
16
+ * message_start -> content_block_start/delta/stop (repeated) -> message_delta -> message_stop
17
+ */
18
+ export declare class AnthropicStreamTransformer {
19
+ private res;
20
+ private displayModel;
21
+ private blockIndex;
22
+ private thinkingBlockOpen;
23
+ private textBlockOpen;
24
+ private toolCalls;
25
+ private hasToolUse;
26
+ private finished;
27
+ private buffer;
28
+ private usage;
29
+ private thinkingCharCount;
30
+ private outputCharCount;
31
+ private firstTokenTime;
32
+ private startTime;
33
+ private messageId;
34
+ constructor(res: Response, displayModel: string);
35
+ /**
36
+ * Pipe an OpenAI streaming response through this transformer.
37
+ * Returns usage stats when complete.
38
+ */
39
+ pipe(backendRes: IncomingMessage): Promise<StreamResult>;
40
+ private processBuffer;
41
+ private processChunk;
42
+ private handleThinking;
43
+ private handleText;
44
+ private handleToolCalls;
45
+ private closeThinkingBlock;
46
+ private closeTextBlock;
47
+ private closeAllToolBlocks;
48
+ /**
49
+ * Finalize the stream. Called once when [DONE] or finish_reason is received.
50
+ */
51
+ private finish;
52
+ private sendSSE;
53
+ private getResult;
54
+ }