qwenproxy-cli 1.0.0 → 1.0.1
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 +14 -14
- package/README.md +906 -906
- package/bin/qwenproxy.js +5 -1
- package/package.json +78 -78
- package/src/api/error-classifier.ts +159 -159
- package/src/api/error-helpers.ts +118 -118
- package/src/api/models.ts +261 -261
- package/src/api/server.ts +860 -859
- package/src/cache/memory-cache.ts +385 -385
- package/src/clean-cache.ts +204 -204
- package/src/core/account-concurrency.ts +671 -671
- package/src/core/account-manager.ts +301 -297
- package/src/core/account-priority.ts +163 -163
- package/src/core/accounts.ts +186 -186
- package/src/core/config.ts +383 -383
- package/src/core/crypto-utils.ts +79 -79
- package/src/core/database.ts +276 -276
- package/src/core/errors.ts +118 -118
- package/src/core/logger.ts +269 -269
- package/src/core/memory-usage.ts +84 -84
- package/src/core/metrics.ts +291 -291
- package/src/core/model-alias.ts +77 -77
- package/src/core/model-registry.ts +544 -544
- package/src/core/mutex.ts +119 -119
- package/src/core/paths.ts +199 -199
- package/src/core/prompt-limits.ts +214 -214
- package/src/core/reasoning-effort.ts +102 -102
- package/src/core/stream-registry.ts +96 -96
- package/src/core/waf-isolation.ts +117 -117
- package/src/core/watchdog.ts +195 -195
- package/src/delete-chats.ts +23 -23
- package/src/index.ts +65 -64
- package/src/login.ts +147 -147
- package/src/reset-cooldowns.ts +11 -11
- package/src/routes/anthropic/index.ts +355 -355
- package/src/routes/anthropic/translate.ts +522 -522
- package/src/routes/anthropic/types.ts +154 -154
- package/src/routes/anthropic/validation.ts +144 -144
- package/src/routes/chat/account.ts +1817 -1817
- package/src/routes/chat/context.ts +241 -241
- package/src/routes/chat/errors.ts +85 -85
- package/src/routes/chat/helpers.ts +268 -268
- package/src/routes/chat/index.ts +618 -618
- package/src/routes/chat/media.ts +285 -285
- package/src/routes/chat/retry-policy.ts +754 -754
- package/src/routes/chat/stop.ts +98 -98
- package/src/routes/chat/streaming.ts +2710 -2710
- package/src/routes/chat/validation.ts +526 -526
- package/src/routes/chat.ts +2 -2
- package/src/routes/completions.ts +290 -290
- package/src/routes/images.ts +139 -139
- package/src/routes/responses/adapter.ts +503 -503
- package/src/routes/responses/index.ts +405 -405
- package/src/routes/responses/state.ts +230 -230
- package/src/routes/responses/streaming.ts +528 -528
- package/src/routes/responses/types.ts +285 -285
- package/src/routes/responses/validation.ts +202 -202
- package/src/routes/upload.ts +731 -731
- package/src/routes/videos.ts +214 -214
- package/src/services/auth-playwright.ts +173 -173
- package/src/services/captcha-coordinator.ts +161 -161
- package/src/services/captcha-solver.ts +553 -553
- package/src/services/chat-cleanup.ts +80 -80
- package/src/services/context-meter.ts +317 -317
- package/src/services/fingerprint.ts +242 -242
- package/src/services/human-behavior.ts +173 -173
- package/src/services/media-generation.ts +1748 -1748
- package/src/services/playwright.ts +2800 -2800
- package/src/services/qwen-chat-pool.ts +345 -345
- package/src/services/qwen-errors.ts +133 -133
- package/src/services/qwen-headers.ts +79 -79
- package/src/services/qwen-thread-state.ts +393 -393
- package/src/services/qwen-url.ts +19 -19
- package/src/services/qwen.ts +3126 -3126
- package/src/services/session-keeper.ts +88 -88
- package/src/services/token-estimation-metrics.ts +118 -118
- package/src/sync/claude-code.ts +75 -75
- package/src/sync/codex.ts +123 -123
- package/src/sync/index.ts +362 -362
- package/src/sync/omp.ts +105 -105
- package/src/sync/opencode.ts +214 -214
- package/src/sync/types.ts +53 -53
- package/src/sync/utils.ts +27 -27
- package/src/sync-clients.ts +189 -189
- package/src/tools/instructions.ts +137 -137
- package/src/tools/manifest.ts +81 -81
- package/src/tools/parser.ts +2989 -2989
- package/src/tools/toolcall-tags.ts +142 -142
- package/src/tui/app.ts +264 -264
- package/src/tui/index.ts +61 -61
- package/src/tui/markdown.ts +258 -258
- package/src/tui/proxy-client.ts +331 -326
- package/src/tui/screen.ts +294 -278
- package/src/tui/server-manager.ts +270 -270
- package/src/tui/theme.ts +432 -432
- package/src/tui/types.ts +33 -33
- package/src/tui/views/accounts-view.ts +656 -656
- package/src/tui/views/chat-view.ts +1018 -823
- package/src/tui/views/logs-view.ts +479 -413
- package/src/tui/views/status-view.ts +204 -204
- package/src/tui/views/storage-view.ts +304 -291
- package/src/tui/views/sync-view.ts +409 -409
- package/src/types/ali-oss.d.ts +32 -32
- package/src/utils/context-truncation.ts +84 -84
- package/src/utils/json.ts +380 -380
- package/src/utils/session-id.ts +37 -37
- package/src/utils/tool-call-guard.ts +84 -84
- package/src/utils/types.ts +109 -109
|
@@ -1,154 +1,154 @@
|
|
|
1
|
-
// Anthropic API Types
|
|
2
|
-
|
|
3
|
-
export interface AnthropicRequest {
|
|
4
|
-
model: string;
|
|
5
|
-
max_tokens: number;
|
|
6
|
-
system?: string | AnthropicContentBlock[];
|
|
7
|
-
messages: AnthropicMessage[];
|
|
8
|
-
tools?: AnthropicTool[];
|
|
9
|
-
tool_choice?: AnthropicToolChoice;
|
|
10
|
-
thinking?: AnthropicThinking;
|
|
11
|
-
stream?: boolean;
|
|
12
|
-
temperature?: number;
|
|
13
|
-
top_p?: number;
|
|
14
|
-
top_k?: number;
|
|
15
|
-
metadata?: Record<string, unknown>;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface AnthropicThinking {
|
|
19
|
-
type: "enabled" | "disabled";
|
|
20
|
-
budget_tokens?: number;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export interface AnthropicMessage {
|
|
24
|
-
role: "user" | "assistant";
|
|
25
|
-
content: string | AnthropicContentBlock[];
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export interface AnthropicContentBlock {
|
|
29
|
-
type: "text" | "tool_use" | "tool_result" | "image" | "document" | "thinking";
|
|
30
|
-
text?: string;
|
|
31
|
-
id?: string;
|
|
32
|
-
name?: string;
|
|
33
|
-
input?: Record<string, unknown>;
|
|
34
|
-
tool_use_id?: string;
|
|
35
|
-
content?: string | AnthropicContentBlock[];
|
|
36
|
-
is_error?: boolean;
|
|
37
|
-
source?: AnthropicImageSource;
|
|
38
|
-
thinking?: string;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export interface AnthropicImageSource {
|
|
42
|
-
type: "base64" | "url";
|
|
43
|
-
media_type: string;
|
|
44
|
-
data?: string;
|
|
45
|
-
url?: string;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export interface AnthropicTool {
|
|
49
|
-
name: string;
|
|
50
|
-
description?: string;
|
|
51
|
-
input_schema: Record<string, unknown>;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export interface AnthropicToolChoice {
|
|
55
|
-
type: "auto" | "any" | "tool" | "none";
|
|
56
|
-
name?: string;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export interface AnthropicResponse {
|
|
60
|
-
id: string;
|
|
61
|
-
type: "message";
|
|
62
|
-
role: "assistant";
|
|
63
|
-
content: AnthropicResponseContentBlock[];
|
|
64
|
-
model: string;
|
|
65
|
-
stop_reason: "end_turn" | "tool_use" | "max_tokens" | "stop_sequence" | null;
|
|
66
|
-
stop_sequence: string | null;
|
|
67
|
-
usage: AnthropicUsage;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export interface AnthropicResponseContentBlock {
|
|
71
|
-
type: "text" | "tool_use" | "thinking";
|
|
72
|
-
text?: string;
|
|
73
|
-
id?: string;
|
|
74
|
-
name?: string;
|
|
75
|
-
input?: Record<string, unknown>;
|
|
76
|
-
thinking?: string;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export interface AnthropicUsage {
|
|
80
|
-
input_tokens: number;
|
|
81
|
-
output_tokens: number;
|
|
82
|
-
cache_creation_input_tokens?: number;
|
|
83
|
-
cache_read_input_tokens?: number;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export interface AnthropicError {
|
|
87
|
-
type: "error";
|
|
88
|
-
error: {
|
|
89
|
-
type: string;
|
|
90
|
-
message: string;
|
|
91
|
-
};
|
|
92
|
-
request_id?: string;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Internal OpenAI-compatible types for conversion
|
|
96
|
-
export interface OpenAIRequest {
|
|
97
|
-
model: string;
|
|
98
|
-
messages: OpenAIMessage[];
|
|
99
|
-
max_tokens?: number;
|
|
100
|
-
max_completion_tokens?: number;
|
|
101
|
-
tools?: OpenAITool[];
|
|
102
|
-
tool_choice?: string | object;
|
|
103
|
-
reasoning_effort?: string;
|
|
104
|
-
stream?: boolean;
|
|
105
|
-
temperature?: number;
|
|
106
|
-
top_p?: number;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export interface OpenAIMessage {
|
|
110
|
-
role: "system" | "user" | "assistant" | "tool";
|
|
111
|
-
content: string | null | Array<{ type: string; text?: string; image_url?: { url: string } }>;
|
|
112
|
-
tool_calls?: OpenAIToolCall[];
|
|
113
|
-
tool_call_id?: string;
|
|
114
|
-
reasoning_content?: string;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
export interface OpenAITool {
|
|
118
|
-
type: "function";
|
|
119
|
-
function: {
|
|
120
|
-
name: string;
|
|
121
|
-
description?: string;
|
|
122
|
-
parameters?: Record<string, unknown>;
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
export interface OpenAIToolCall {
|
|
127
|
-
id: string;
|
|
128
|
-
type: "function";
|
|
129
|
-
function: {
|
|
130
|
-
name: string;
|
|
131
|
-
arguments: string;
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
export interface OpenAIResponse {
|
|
136
|
-
id: string;
|
|
137
|
-
object: "chat.completion";
|
|
138
|
-
created: number;
|
|
139
|
-
model: string;
|
|
140
|
-
choices: OpenAIChoice[];
|
|
141
|
-
usage: OpenAIUsage;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
export interface OpenAIChoice {
|
|
145
|
-
index: number;
|
|
146
|
-
message: OpenAIMessage;
|
|
147
|
-
finish_reason: "stop" | "tool_calls" | "length" | "content_filter" | null;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
export interface OpenAIUsage {
|
|
151
|
-
prompt_tokens: number;
|
|
152
|
-
completion_tokens: number;
|
|
153
|
-
total_tokens: number;
|
|
154
|
-
}
|
|
1
|
+
// Anthropic API Types
|
|
2
|
+
|
|
3
|
+
export interface AnthropicRequest {
|
|
4
|
+
model: string;
|
|
5
|
+
max_tokens: number;
|
|
6
|
+
system?: string | AnthropicContentBlock[];
|
|
7
|
+
messages: AnthropicMessage[];
|
|
8
|
+
tools?: AnthropicTool[];
|
|
9
|
+
tool_choice?: AnthropicToolChoice;
|
|
10
|
+
thinking?: AnthropicThinking;
|
|
11
|
+
stream?: boolean;
|
|
12
|
+
temperature?: number;
|
|
13
|
+
top_p?: number;
|
|
14
|
+
top_k?: number;
|
|
15
|
+
metadata?: Record<string, unknown>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface AnthropicThinking {
|
|
19
|
+
type: "enabled" | "disabled";
|
|
20
|
+
budget_tokens?: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface AnthropicMessage {
|
|
24
|
+
role: "user" | "assistant";
|
|
25
|
+
content: string | AnthropicContentBlock[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface AnthropicContentBlock {
|
|
29
|
+
type: "text" | "tool_use" | "tool_result" | "image" | "document" | "thinking";
|
|
30
|
+
text?: string;
|
|
31
|
+
id?: string;
|
|
32
|
+
name?: string;
|
|
33
|
+
input?: Record<string, unknown>;
|
|
34
|
+
tool_use_id?: string;
|
|
35
|
+
content?: string | AnthropicContentBlock[];
|
|
36
|
+
is_error?: boolean;
|
|
37
|
+
source?: AnthropicImageSource;
|
|
38
|
+
thinking?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface AnthropicImageSource {
|
|
42
|
+
type: "base64" | "url";
|
|
43
|
+
media_type: string;
|
|
44
|
+
data?: string;
|
|
45
|
+
url?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface AnthropicTool {
|
|
49
|
+
name: string;
|
|
50
|
+
description?: string;
|
|
51
|
+
input_schema: Record<string, unknown>;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface AnthropicToolChoice {
|
|
55
|
+
type: "auto" | "any" | "tool" | "none";
|
|
56
|
+
name?: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface AnthropicResponse {
|
|
60
|
+
id: string;
|
|
61
|
+
type: "message";
|
|
62
|
+
role: "assistant";
|
|
63
|
+
content: AnthropicResponseContentBlock[];
|
|
64
|
+
model: string;
|
|
65
|
+
stop_reason: "end_turn" | "tool_use" | "max_tokens" | "stop_sequence" | null;
|
|
66
|
+
stop_sequence: string | null;
|
|
67
|
+
usage: AnthropicUsage;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface AnthropicResponseContentBlock {
|
|
71
|
+
type: "text" | "tool_use" | "thinking";
|
|
72
|
+
text?: string;
|
|
73
|
+
id?: string;
|
|
74
|
+
name?: string;
|
|
75
|
+
input?: Record<string, unknown>;
|
|
76
|
+
thinking?: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface AnthropicUsage {
|
|
80
|
+
input_tokens: number;
|
|
81
|
+
output_tokens: number;
|
|
82
|
+
cache_creation_input_tokens?: number;
|
|
83
|
+
cache_read_input_tokens?: number;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface AnthropicError {
|
|
87
|
+
type: "error";
|
|
88
|
+
error: {
|
|
89
|
+
type: string;
|
|
90
|
+
message: string;
|
|
91
|
+
};
|
|
92
|
+
request_id?: string;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Internal OpenAI-compatible types for conversion
|
|
96
|
+
export interface OpenAIRequest {
|
|
97
|
+
model: string;
|
|
98
|
+
messages: OpenAIMessage[];
|
|
99
|
+
max_tokens?: number;
|
|
100
|
+
max_completion_tokens?: number;
|
|
101
|
+
tools?: OpenAITool[];
|
|
102
|
+
tool_choice?: string | object;
|
|
103
|
+
reasoning_effort?: string;
|
|
104
|
+
stream?: boolean;
|
|
105
|
+
temperature?: number;
|
|
106
|
+
top_p?: number;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface OpenAIMessage {
|
|
110
|
+
role: "system" | "user" | "assistant" | "tool";
|
|
111
|
+
content: string | null | Array<{ type: string; text?: string; image_url?: { url: string } }>;
|
|
112
|
+
tool_calls?: OpenAIToolCall[];
|
|
113
|
+
tool_call_id?: string;
|
|
114
|
+
reasoning_content?: string;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface OpenAITool {
|
|
118
|
+
type: "function";
|
|
119
|
+
function: {
|
|
120
|
+
name: string;
|
|
121
|
+
description?: string;
|
|
122
|
+
parameters?: Record<string, unknown>;
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export interface OpenAIToolCall {
|
|
127
|
+
id: string;
|
|
128
|
+
type: "function";
|
|
129
|
+
function: {
|
|
130
|
+
name: string;
|
|
131
|
+
arguments: string;
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export interface OpenAIResponse {
|
|
136
|
+
id: string;
|
|
137
|
+
object: "chat.completion";
|
|
138
|
+
created: number;
|
|
139
|
+
model: string;
|
|
140
|
+
choices: OpenAIChoice[];
|
|
141
|
+
usage: OpenAIUsage;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export interface OpenAIChoice {
|
|
145
|
+
index: number;
|
|
146
|
+
message: OpenAIMessage;
|
|
147
|
+
finish_reason: "stop" | "tool_calls" | "length" | "content_filter" | null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export interface OpenAIUsage {
|
|
151
|
+
prompt_tokens: number;
|
|
152
|
+
completion_tokens: number;
|
|
153
|
+
total_tokens: number;
|
|
154
|
+
}
|
|
@@ -1,144 +1,144 @@
|
|
|
1
|
-
export interface ValidationResult {
|
|
2
|
-
valid: boolean;
|
|
3
|
-
error?: string;
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Validate Anthropic request format
|
|
8
|
-
*/
|
|
9
|
-
export function validateAnthropicRequest(body: unknown): ValidationResult {
|
|
10
|
-
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
|
11
|
-
return { valid: false, error: "Request body must be a JSON object" };
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const req = body as Record<string, unknown>;
|
|
15
|
-
|
|
16
|
-
// Model is required
|
|
17
|
-
if (!req.model || typeof req.model !== "string") {
|
|
18
|
-
return { valid: false, error: "Missing or invalid 'model' field" };
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// max_tokens is required
|
|
22
|
-
if (req.max_tokens === undefined || req.max_tokens === null) {
|
|
23
|
-
return { valid: false, error: "Missing required field: max_tokens" };
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
if (typeof req.max_tokens !== "number" || req.max_tokens < 1) {
|
|
27
|
-
return { valid: false, error: "max_tokens must be a positive number" };
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// messages is required
|
|
31
|
-
if (!req.messages || !Array.isArray(req.messages)) {
|
|
32
|
-
return { valid: false, error: "Missing or invalid 'messages' field" };
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
if (req.messages.length === 0) {
|
|
36
|
-
return { valid: false, error: "messages array cannot be empty" };
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Validate each message
|
|
40
|
-
for (let i = 0; i < req.messages.length; i++) {
|
|
41
|
-
const msg = req.messages[i];
|
|
42
|
-
if (!msg || typeof msg !== "object") {
|
|
43
|
-
return { valid: false, error: `messages[${i}] must be an object` };
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (
|
|
47
|
-
msg.role !== "user" &&
|
|
48
|
-
msg.role !== "assistant" &&
|
|
49
|
-
msg.role !== "system"
|
|
50
|
-
) {
|
|
51
|
-
return {
|
|
52
|
-
valid: false,
|
|
53
|
-
error: `messages[${i}].role must be 'user', 'assistant' or 'system'`,
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (msg.content === undefined || msg.content === null) {
|
|
58
|
-
return { valid: false, error: `messages[${i}].content is required` };
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Content can be string or array of content blocks
|
|
62
|
-
if (typeof msg.content !== "string" && !Array.isArray(msg.content)) {
|
|
63
|
-
return {
|
|
64
|
-
valid: false,
|
|
65
|
-
error: `messages[${i}].content must be a string or array of content blocks`,
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Validate tools if present
|
|
71
|
-
if (req.tools !== undefined) {
|
|
72
|
-
if (!Array.isArray(req.tools)) {
|
|
73
|
-
return { valid: false, error: "'tools' must be an array" };
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
for (let i = 0; i < req.tools.length; i++) {
|
|
77
|
-
const tool = req.tools[i];
|
|
78
|
-
if (!tool || typeof tool !== "object") {
|
|
79
|
-
return { valid: false, error: `tools[${i}] must be an object` };
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (!tool.name || typeof tool.name !== "string") {
|
|
83
|
-
return {
|
|
84
|
-
valid: false,
|
|
85
|
-
error: `tools[${i}].name is required and must be a string`,
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (tool.input_schema && typeof tool.input_schema !== "object") {
|
|
90
|
-
return {
|
|
91
|
-
valid: false,
|
|
92
|
-
error: `tools[${i}].input_schema must be an object`,
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Validate tool_choice if present
|
|
99
|
-
if (req.tool_choice !== undefined && req.tool_choice !== null) {
|
|
100
|
-
const toolChoice = req.tool_choice as Record<string, unknown>;
|
|
101
|
-
if (typeof toolChoice !== "object" || !toolChoice.type) {
|
|
102
|
-
return {
|
|
103
|
-
valid: false,
|
|
104
|
-
error: "'tool_choice' must be an object with a 'type' field",
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const validTypes = ["auto", "any", "tool", "none"];
|
|
109
|
-
if (!validTypes.includes(toolChoice.type as string)) {
|
|
110
|
-
return {
|
|
111
|
-
valid: false,
|
|
112
|
-
error: `tool_choice.type must be one of: ${validTypes.join(", ")}`,
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
if (toolChoice.type === "tool" && !toolChoice.name) {
|
|
117
|
-
return {
|
|
118
|
-
valid: false,
|
|
119
|
-
error: "tool_choice.name is required when type is 'tool'",
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Validate stream if present
|
|
125
|
-
if (req.stream !== undefined && typeof req.stream !== "boolean") {
|
|
126
|
-
return { valid: false, error: "'stream' must be a boolean" };
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Validate temperature if present
|
|
130
|
-
if (req.temperature !== undefined) {
|
|
131
|
-
if (
|
|
132
|
-
typeof req.temperature !== "number" ||
|
|
133
|
-
req.temperature < 0 ||
|
|
134
|
-
req.temperature > 1
|
|
135
|
-
) {
|
|
136
|
-
return {
|
|
137
|
-
valid: false,
|
|
138
|
-
error: "'temperature' must be a number between 0 and 1",
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
return { valid: true };
|
|
144
|
-
}
|
|
1
|
+
export interface ValidationResult {
|
|
2
|
+
valid: boolean;
|
|
3
|
+
error?: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Validate Anthropic request format
|
|
8
|
+
*/
|
|
9
|
+
export function validateAnthropicRequest(body: unknown): ValidationResult {
|
|
10
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
|
11
|
+
return { valid: false, error: "Request body must be a JSON object" };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const req = body as Record<string, unknown>;
|
|
15
|
+
|
|
16
|
+
// Model is required
|
|
17
|
+
if (!req.model || typeof req.model !== "string") {
|
|
18
|
+
return { valid: false, error: "Missing or invalid 'model' field" };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// max_tokens is required
|
|
22
|
+
if (req.max_tokens === undefined || req.max_tokens === null) {
|
|
23
|
+
return { valid: false, error: "Missing required field: max_tokens" };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (typeof req.max_tokens !== "number" || req.max_tokens < 1) {
|
|
27
|
+
return { valid: false, error: "max_tokens must be a positive number" };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// messages is required
|
|
31
|
+
if (!req.messages || !Array.isArray(req.messages)) {
|
|
32
|
+
return { valid: false, error: "Missing or invalid 'messages' field" };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (req.messages.length === 0) {
|
|
36
|
+
return { valid: false, error: "messages array cannot be empty" };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Validate each message
|
|
40
|
+
for (let i = 0; i < req.messages.length; i++) {
|
|
41
|
+
const msg = req.messages[i];
|
|
42
|
+
if (!msg || typeof msg !== "object") {
|
|
43
|
+
return { valid: false, error: `messages[${i}] must be an object` };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (
|
|
47
|
+
msg.role !== "user" &&
|
|
48
|
+
msg.role !== "assistant" &&
|
|
49
|
+
msg.role !== "system"
|
|
50
|
+
) {
|
|
51
|
+
return {
|
|
52
|
+
valid: false,
|
|
53
|
+
error: `messages[${i}].role must be 'user', 'assistant' or 'system'`,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (msg.content === undefined || msg.content === null) {
|
|
58
|
+
return { valid: false, error: `messages[${i}].content is required` };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Content can be string or array of content blocks
|
|
62
|
+
if (typeof msg.content !== "string" && !Array.isArray(msg.content)) {
|
|
63
|
+
return {
|
|
64
|
+
valid: false,
|
|
65
|
+
error: `messages[${i}].content must be a string or array of content blocks`,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Validate tools if present
|
|
71
|
+
if (req.tools !== undefined) {
|
|
72
|
+
if (!Array.isArray(req.tools)) {
|
|
73
|
+
return { valid: false, error: "'tools' must be an array" };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
for (let i = 0; i < req.tools.length; i++) {
|
|
77
|
+
const tool = req.tools[i];
|
|
78
|
+
if (!tool || typeof tool !== "object") {
|
|
79
|
+
return { valid: false, error: `tools[${i}] must be an object` };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (!tool.name || typeof tool.name !== "string") {
|
|
83
|
+
return {
|
|
84
|
+
valid: false,
|
|
85
|
+
error: `tools[${i}].name is required and must be a string`,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (tool.input_schema && typeof tool.input_schema !== "object") {
|
|
90
|
+
return {
|
|
91
|
+
valid: false,
|
|
92
|
+
error: `tools[${i}].input_schema must be an object`,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Validate tool_choice if present
|
|
99
|
+
if (req.tool_choice !== undefined && req.tool_choice !== null) {
|
|
100
|
+
const toolChoice = req.tool_choice as Record<string, unknown>;
|
|
101
|
+
if (typeof toolChoice !== "object" || !toolChoice.type) {
|
|
102
|
+
return {
|
|
103
|
+
valid: false,
|
|
104
|
+
error: "'tool_choice' must be an object with a 'type' field",
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const validTypes = ["auto", "any", "tool", "none"];
|
|
109
|
+
if (!validTypes.includes(toolChoice.type as string)) {
|
|
110
|
+
return {
|
|
111
|
+
valid: false,
|
|
112
|
+
error: `tool_choice.type must be one of: ${validTypes.join(", ")}`,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (toolChoice.type === "tool" && !toolChoice.name) {
|
|
117
|
+
return {
|
|
118
|
+
valid: false,
|
|
119
|
+
error: "tool_choice.name is required when type is 'tool'",
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Validate stream if present
|
|
125
|
+
if (req.stream !== undefined && typeof req.stream !== "boolean") {
|
|
126
|
+
return { valid: false, error: "'stream' must be a boolean" };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Validate temperature if present
|
|
130
|
+
if (req.temperature !== undefined) {
|
|
131
|
+
if (
|
|
132
|
+
typeof req.temperature !== "number" ||
|
|
133
|
+
req.temperature < 0 ||
|
|
134
|
+
req.temperature > 1
|
|
135
|
+
) {
|
|
136
|
+
return {
|
|
137
|
+
valid: false,
|
|
138
|
+
error: "'temperature' must be a number between 0 and 1",
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return { valid: true };
|
|
144
|
+
}
|