waifu-code 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/README.md +51 -0
- package/dist/cli.d.ts +15 -0
- package/dist/cli.js +188 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +35 -0
- package/dist/config.js +73 -0
- package/dist/config.js.map +1 -0
- package/dist/observer/eventDetector.d.ts +16 -0
- package/dist/observer/eventDetector.js +30 -0
- package/dist/observer/eventDetector.js.map +1 -0
- package/dist/observer/streamObserver.d.ts +8 -0
- package/dist/observer/streamObserver.js +24 -0
- package/dist/observer/streamObserver.js.map +1 -0
- package/dist/providers/converter.d.ts +18 -0
- package/dist/providers/converter.js +168 -0
- package/dist/providers/converter.js.map +1 -0
- package/dist/providers/nim.d.ts +23 -0
- package/dist/providers/nim.js +276 -0
- package/dist/providers/nim.js.map +1 -0
- package/dist/providers/sseBuilder.d.ts +53 -0
- package/dist/providers/sseBuilder.js +280 -0
- package/dist/providers/sseBuilder.js.map +1 -0
- package/dist/providers/thinkParser.d.ts +21 -0
- package/dist/providers/thinkParser.js +130 -0
- package/dist/providers/thinkParser.js.map +1 -0
- package/dist/providers/toolParser.d.ts +31 -0
- package/dist/providers/toolParser.js +163 -0
- package/dist/providers/toolParser.js.map +1 -0
- package/dist/providers/types.d.ts +57 -0
- package/dist/providers/types.js +20 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/proxy/auth.d.ts +13 -0
- package/dist/proxy/auth.js +37 -0
- package/dist/proxy/auth.js.map +1 -0
- package/dist/proxy/commandUtils.d.ts +15 -0
- package/dist/proxy/commandUtils.js +155 -0
- package/dist/proxy/commandUtils.js.map +1 -0
- package/dist/proxy/detection.d.ts +13 -0
- package/dist/proxy/detection.js +107 -0
- package/dist/proxy/detection.js.map +1 -0
- package/dist/proxy/optimizer.d.ts +14 -0
- package/dist/proxy/optimizer.js +56 -0
- package/dist/proxy/optimizer.js.map +1 -0
- package/dist/proxy/routes.d.ts +21 -0
- package/dist/proxy/routes.js +142 -0
- package/dist/proxy/routes.js.map +1 -0
- package/dist/proxy/server.d.ts +31 -0
- package/dist/proxy/server.js +87 -0
- package/dist/proxy/server.js.map +1 -0
- package/dist/proxy/tokenCounter.d.ts +12 -0
- package/dist/proxy/tokenCounter.js +91 -0
- package/dist/proxy/tokenCounter.js.map +1 -0
- package/dist/proxy/types.d.ts +89 -0
- package/dist/proxy/types.js +7 -0
- package/dist/proxy/types.js.map +1 -0
- package/dist/waifu/overlayRenderer.d.ts +14 -0
- package/dist/waifu/overlayRenderer.js +49 -0
- package/dist/waifu/overlayRenderer.js.map +1 -0
- package/package.json +40 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command parsing utilities for API optimizations.
|
|
3
|
+
* Port of Python command_utils.py from the proxy server.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Extract the command prefix for fast prefix detection.
|
|
7
|
+
* Returns the command prefix (e.g., "git", "git commit", "npm install")
|
|
8
|
+
* or "none" if no valid command found.
|
|
9
|
+
*/
|
|
10
|
+
export function extractCommandPrefix(command) {
|
|
11
|
+
if (command.includes("`") || command.includes("$(")) {
|
|
12
|
+
return "command_injection_detected";
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
// Simple shell-like splitting (not full shlex but good enough)
|
|
16
|
+
const parts = shellSplit(command);
|
|
17
|
+
if (parts.length === 0)
|
|
18
|
+
return "none";
|
|
19
|
+
// Skip env var assignments
|
|
20
|
+
let cmdStart = 0;
|
|
21
|
+
for (let i = 0; i < parts.length; i++) {
|
|
22
|
+
if (parts[i].includes("=") && !parts[i].startsWith("-")) {
|
|
23
|
+
cmdStart = i + 1;
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (cmdStart >= parts.length)
|
|
30
|
+
return "none";
|
|
31
|
+
const cmdParts = parts.slice(cmdStart);
|
|
32
|
+
if (cmdParts.length === 0)
|
|
33
|
+
return "none";
|
|
34
|
+
const firstWord = cmdParts[0];
|
|
35
|
+
const twoWordCommands = new Set([
|
|
36
|
+
"git", "npm", "docker", "kubectl", "cargo", "go", "pip", "yarn",
|
|
37
|
+
]);
|
|
38
|
+
if (twoWordCommands.has(firstWord) && cmdParts.length > 1) {
|
|
39
|
+
const secondWord = cmdParts[1];
|
|
40
|
+
if (!secondWord.startsWith("-")) {
|
|
41
|
+
return `${firstWord} ${secondWord}`;
|
|
42
|
+
}
|
|
43
|
+
return firstWord;
|
|
44
|
+
}
|
|
45
|
+
if (cmdStart > 0) {
|
|
46
|
+
return parts.slice(0, cmdStart).join(" ") + " " + firstWord;
|
|
47
|
+
}
|
|
48
|
+
return firstWord;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
const split = command.split(/\s+/);
|
|
52
|
+
return split[0] || "none";
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Extract file paths from a command locally.
|
|
57
|
+
* Returns filepath extraction result in <filepaths> format.
|
|
58
|
+
*/
|
|
59
|
+
export function extractFilepathsFromCommand(command, _output) {
|
|
60
|
+
const LISTING_COMMANDS = new Set([
|
|
61
|
+
"ls", "dir", "find", "tree", "pwd", "cd", "mkdir", "rmdir", "rm",
|
|
62
|
+
]);
|
|
63
|
+
const READING_COMMANDS = new Set([
|
|
64
|
+
"cat", "head", "tail", "less", "more", "bat", "type",
|
|
65
|
+
]);
|
|
66
|
+
try {
|
|
67
|
+
const parts = shellSplit(command);
|
|
68
|
+
if (parts.length === 0)
|
|
69
|
+
return "<filepaths>\n</filepaths>";
|
|
70
|
+
// Get base command name (handle paths like /usr/bin/cat)
|
|
71
|
+
const baseCmd = parts[0]
|
|
72
|
+
.split("/").pop()
|
|
73
|
+
.split("\\").pop()
|
|
74
|
+
.toLowerCase();
|
|
75
|
+
if (LISTING_COMMANDS.has(baseCmd)) {
|
|
76
|
+
return "<filepaths>\n</filepaths>";
|
|
77
|
+
}
|
|
78
|
+
if (READING_COMMANDS.has(baseCmd)) {
|
|
79
|
+
const filepaths = parts.slice(1).filter((p) => !p.startsWith("-"));
|
|
80
|
+
if (filepaths.length > 0) {
|
|
81
|
+
return `<filepaths>\n${filepaths.join("\n")}\n</filepaths>`;
|
|
82
|
+
}
|
|
83
|
+
return "<filepaths>\n</filepaths>";
|
|
84
|
+
}
|
|
85
|
+
if (baseCmd === "grep") {
|
|
86
|
+
const flagsWithArgs = new Set(["-e", "-f", "-m", "-A", "-B", "-C"]);
|
|
87
|
+
let patternViaFlag = false;
|
|
88
|
+
const positional = [];
|
|
89
|
+
let skipNext = false;
|
|
90
|
+
for (const part of parts.slice(1)) {
|
|
91
|
+
if (skipNext) {
|
|
92
|
+
skipNext = false;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (part.startsWith("-")) {
|
|
96
|
+
if (flagsWithArgs.has(part)) {
|
|
97
|
+
if (part === "-e" || part === "-f")
|
|
98
|
+
patternViaFlag = true;
|
|
99
|
+
skipNext = true;
|
|
100
|
+
}
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
positional.push(part);
|
|
104
|
+
}
|
|
105
|
+
const filepaths = patternViaFlag ? positional : positional.slice(1);
|
|
106
|
+
if (filepaths.length > 0) {
|
|
107
|
+
return `<filepaths>\n${filepaths.join("\n")}\n</filepaths>`;
|
|
108
|
+
}
|
|
109
|
+
return "<filepaths>\n</filepaths>";
|
|
110
|
+
}
|
|
111
|
+
return "<filepaths>\n</filepaths>";
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return "<filepaths>\n</filepaths>";
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/** Simple shell-like argument splitting. */
|
|
118
|
+
function shellSplit(cmd) {
|
|
119
|
+
const parts = [];
|
|
120
|
+
let current = "";
|
|
121
|
+
let inSingle = false;
|
|
122
|
+
let inDouble = false;
|
|
123
|
+
let escape = false;
|
|
124
|
+
for (const ch of cmd) {
|
|
125
|
+
if (escape) {
|
|
126
|
+
current += ch;
|
|
127
|
+
escape = false;
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (ch === "\\") {
|
|
131
|
+
escape = true;
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
if (ch === "'" && !inDouble) {
|
|
135
|
+
inSingle = !inSingle;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (ch === '"' && !inSingle) {
|
|
139
|
+
inDouble = !inDouble;
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if (/\s/.test(ch) && !inSingle && !inDouble) {
|
|
143
|
+
if (current) {
|
|
144
|
+
parts.push(current);
|
|
145
|
+
current = "";
|
|
146
|
+
}
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
current += ch;
|
|
150
|
+
}
|
|
151
|
+
if (current)
|
|
152
|
+
parts.push(current);
|
|
153
|
+
return parts;
|
|
154
|
+
}
|
|
155
|
+
//# sourceMappingURL=commandUtils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commandUtils.js","sourceRoot":"","sources":["../../src/proxy/commandUtils.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request type detection utilities for API optimizations.
|
|
3
|
+
*
|
|
4
|
+
* Detects quota checks, title generation, prefix detection,
|
|
5
|
+
* suggestion mode, and filepath extraction requests.
|
|
6
|
+
* Port of Python detection.py from the proxy server.
|
|
7
|
+
*/
|
|
8
|
+
import type { MessagesRequest } from "./types.js";
|
|
9
|
+
export declare function isQuotaCheckRequest(requestData: MessagesRequest): boolean;
|
|
10
|
+
export declare function isTitleGenerationRequest(requestData: MessagesRequest): boolean;
|
|
11
|
+
export declare function isPrefixDetectionRequest(requestData: MessagesRequest): [boolean, string];
|
|
12
|
+
export declare function isSuggestionModeRequest(requestData: MessagesRequest): boolean;
|
|
13
|
+
export declare function isFilepathExtractionRequest(requestData: MessagesRequest): [boolean, string, string];
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request type detection utilities for API optimizations.
|
|
3
|
+
*
|
|
4
|
+
* Detects quota checks, title generation, prefix detection,
|
|
5
|
+
* suggestion mode, and filepath extraction requests.
|
|
6
|
+
* Port of Python detection.py from the proxy server.
|
|
7
|
+
*/
|
|
8
|
+
function extractTextFromContent(content) {
|
|
9
|
+
if (typeof content === "string")
|
|
10
|
+
return content;
|
|
11
|
+
if (Array.isArray(content)) {
|
|
12
|
+
const parts = [];
|
|
13
|
+
for (const block of content) {
|
|
14
|
+
if (block && typeof block === "object" && "text" in block) {
|
|
15
|
+
const text = block.text;
|
|
16
|
+
if (text && typeof text === "string")
|
|
17
|
+
parts.push(text);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return parts.join("");
|
|
21
|
+
}
|
|
22
|
+
return "";
|
|
23
|
+
}
|
|
24
|
+
export function isQuotaCheckRequest(requestData) {
|
|
25
|
+
if (requestData.max_tokens === 1 &&
|
|
26
|
+
requestData.messages.length === 1 &&
|
|
27
|
+
requestData.messages[0].role === "user") {
|
|
28
|
+
const text = extractTextFromContent(requestData.messages[0].content);
|
|
29
|
+
return text.toLowerCase().includes("quota");
|
|
30
|
+
}
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
export function isTitleGenerationRequest(requestData) {
|
|
34
|
+
if (!requestData.system || (requestData.tools && requestData.tools.length > 0)) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
const systemText = extractTextFromContent(requestData.system).toLowerCase();
|
|
38
|
+
return systemText.includes("new conversation topic") && systemText.includes("title");
|
|
39
|
+
}
|
|
40
|
+
export function isPrefixDetectionRequest(requestData) {
|
|
41
|
+
if (requestData.messages.length !== 1 ||
|
|
42
|
+
requestData.messages[0].role !== "user") {
|
|
43
|
+
return [false, ""];
|
|
44
|
+
}
|
|
45
|
+
const content = extractTextFromContent(requestData.messages[0].content);
|
|
46
|
+
if (content.includes("<policy_spec>") && content.includes("Command:")) {
|
|
47
|
+
try {
|
|
48
|
+
const cmdStart = content.lastIndexOf("Command:") + "Command:".length;
|
|
49
|
+
return [true, content.slice(cmdStart).trim()];
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// ignore
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return [false, ""];
|
|
56
|
+
}
|
|
57
|
+
export function isSuggestionModeRequest(requestData) {
|
|
58
|
+
for (const msg of requestData.messages) {
|
|
59
|
+
if (msg.role === "user") {
|
|
60
|
+
const text = extractTextFromContent(msg.content);
|
|
61
|
+
if (text.includes("[SUGGESTION MODE:"))
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
export function isFilepathExtractionRequest(requestData) {
|
|
68
|
+
if (requestData.messages.length !== 1 ||
|
|
69
|
+
requestData.messages[0].role !== "user") {
|
|
70
|
+
return [false, "", ""];
|
|
71
|
+
}
|
|
72
|
+
if (requestData.tools && requestData.tools.length > 0) {
|
|
73
|
+
return [false, "", ""];
|
|
74
|
+
}
|
|
75
|
+
const content = extractTextFromContent(requestData.messages[0].content);
|
|
76
|
+
if (!content.includes("Command:") || !content.includes("Output:")) {
|
|
77
|
+
return [false, "", ""];
|
|
78
|
+
}
|
|
79
|
+
const userHasFilepaths = content.toLowerCase().includes("filepaths") ||
|
|
80
|
+
content.toLowerCase().includes("<filepaths>");
|
|
81
|
+
const systemText = requestData.system
|
|
82
|
+
? extractTextFromContent(requestData.system).toLowerCase()
|
|
83
|
+
: "";
|
|
84
|
+
const systemHasExtract = systemText.includes("extract any file paths") ||
|
|
85
|
+
systemText.includes("file paths that this command");
|
|
86
|
+
if (!userHasFilepaths && !systemHasExtract) {
|
|
87
|
+
return [false, "", ""];
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
const cmdStart = content.indexOf("Command:") + "Command:".length;
|
|
91
|
+
const outputMarker = content.indexOf("Output:", cmdStart);
|
|
92
|
+
if (outputMarker === -1)
|
|
93
|
+
return [false, "", ""];
|
|
94
|
+
const command = content.slice(cmdStart, outputMarker).trim();
|
|
95
|
+
let output = content.slice(outputMarker + "Output:".length).trim();
|
|
96
|
+
for (const marker of ["<", "\n\n"]) {
|
|
97
|
+
if (output.includes(marker)) {
|
|
98
|
+
output = output.split(marker)[0].trim();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return [true, command, output];
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return [false, "", ""];
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=detection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detection.js","sourceRoot":"","sources":["../../src/proxy/detection.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request optimization handlers for fast-path API responses.
|
|
3
|
+
*
|
|
4
|
+
* These intercept trivial requests (quota checks, title generation, etc.)
|
|
5
|
+
* and return instant mock responses instead of routing to NIM.
|
|
6
|
+
* Port of Python optimization_handlers.py from the proxy server.
|
|
7
|
+
*/
|
|
8
|
+
import type { MessagesRequest, MessagesResponse } from "./types.js";
|
|
9
|
+
/**
|
|
10
|
+
* Run optimization handlers in order.
|
|
11
|
+
* Returns a MessagesResponse if any match, or null if the request
|
|
12
|
+
* should be routed to the provider.
|
|
13
|
+
*/
|
|
14
|
+
export declare function tryOptimizations(requestData: MessagesRequest): MessagesResponse | null;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request optimization handlers for fast-path API responses.
|
|
3
|
+
*
|
|
4
|
+
* These intercept trivial requests (quota checks, title generation, etc.)
|
|
5
|
+
* and return instant mock responses instead of routing to NIM.
|
|
6
|
+
* Port of Python optimization_handlers.py from the proxy server.
|
|
7
|
+
*/
|
|
8
|
+
import { randomUUID } from "node:crypto";
|
|
9
|
+
import { extractCommandPrefix, extractFilepathsFromCommand } from "./commandUtils.js";
|
|
10
|
+
import { isQuotaCheckRequest, isTitleGenerationRequest, isPrefixDetectionRequest, isSuggestionModeRequest, isFilepathExtractionRequest, } from "./detection.js";
|
|
11
|
+
function makeResponse(model, text, inputTokens, outputTokens) {
|
|
12
|
+
return {
|
|
13
|
+
id: `msg_${randomUUID()}`,
|
|
14
|
+
model,
|
|
15
|
+
role: "assistant",
|
|
16
|
+
content: [{ type: "text", text }],
|
|
17
|
+
type: "message",
|
|
18
|
+
stop_reason: "end_turn",
|
|
19
|
+
stop_sequence: null,
|
|
20
|
+
usage: {
|
|
21
|
+
input_tokens: inputTokens,
|
|
22
|
+
output_tokens: outputTokens,
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Run optimization handlers in order.
|
|
28
|
+
* Returns a MessagesResponse if any match, or null if the request
|
|
29
|
+
* should be routed to the provider.
|
|
30
|
+
*/
|
|
31
|
+
export function tryOptimizations(requestData) {
|
|
32
|
+
// 1. Quota mock
|
|
33
|
+
if (isQuotaCheckRequest(requestData)) {
|
|
34
|
+
return makeResponse(requestData.model, "Quota check passed.", 10, 5);
|
|
35
|
+
}
|
|
36
|
+
// 2. Prefix detection
|
|
37
|
+
const [isPrefix, command] = isPrefixDetectionRequest(requestData);
|
|
38
|
+
if (isPrefix) {
|
|
39
|
+
return makeResponse(requestData.model, extractCommandPrefix(command), 100, 5);
|
|
40
|
+
}
|
|
41
|
+
// 3. Title skip
|
|
42
|
+
if (isTitleGenerationRequest(requestData)) {
|
|
43
|
+
return makeResponse(requestData.model, "Conversation", 100, 5);
|
|
44
|
+
}
|
|
45
|
+
// 4. Suggestion skip
|
|
46
|
+
if (isSuggestionModeRequest(requestData)) {
|
|
47
|
+
return makeResponse(requestData.model, "", 100, 1);
|
|
48
|
+
}
|
|
49
|
+
// 5. Filepath extraction
|
|
50
|
+
const [isFp, cmd, output] = isFilepathExtractionRequest(requestData);
|
|
51
|
+
if (isFp) {
|
|
52
|
+
return makeResponse(requestData.model, extractFilepathsFromCommand(cmd, output), 100, 10);
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=optimizer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"optimizer.js","sourceRoot":"","sources":["../../src/proxy/optimizer.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,oBAAoB,EAAE,2BAA2B,EAAE,MAAM,mBAAmB,CAAC;AACtF,OAAO,EACL,mBAAmB,EACnB,wBAAwB,EACxB,wBAAwB,EACxB,uBAAuB,EACvB,2BAA2B,GAC5B,MAAM,gBAAgB,CAAC;AAGxB,SAAS,YAAY,CACnB,KAAa,EACb,IAAY,EACZ,WAAmB,EACnB,YAAoB;IAEpB,OAAO;QACL,EAAE,EAAE,OAAO,UAAU,EAAE,EAAE;QACzB,KAAK;QACL,IAAI,EAAE,WAAW;QACjB,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;QACjC,IAAI,EAAE,SAAS;QACf,WAAW,EAAE,UAAU;QACvB,aAAa,EAAE,IAAI;QACnB,KAAK,EAAE;YACL,YAAY,EAAE,WAAW;YACzB,aAAa,EAAE,YAAY;SAC5B;KACF,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAC9B,WAA4B;IAE5B,gBAAgB;IAChB,IAAI,mBAAmB,CAAC,WAAW,CAAC,EAAE,CAAC;QACrC,OAAO,YAAY,CAAC,WAAW,CAAC,KAAK,EAAE,qBAAqB,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;IACvE,CAAC;IAED,sBAAsB;IACtB,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,GAAG,wBAAwB,CAAC,WAAW,CAAC,CAAC;IAClE,IAAI,QAAQ,EAAE,CAAC;QACb,OAAO,YAAY,CAAC,WAAW,CAAC,KAAK,EAAE,oBAAoB,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC;IAChF,CAAC;IAED,gBAAgB;IAChB,IAAI,wBAAwB,CAAC,WAAW,CAAC,EAAE,CAAC;QAC1C,OAAO,YAAY,CAAC,WAAW,CAAC,KAAK,EAAE,cAAc,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC;IACjE,CAAC;IAED,qBAAqB;IACrB,IAAI,uBAAuB,CAAC,WAAW,CAAC,EAAE,CAAC;QACzC,OAAO,YAAY,CAAC,WAAW,CAAC,KAAK,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC;IACrD,CAAC;IAED,yBAAyB;IACzB,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,2BAA2B,CAAC,WAAW,CAAC,CAAC;IACrE,IAAI,IAAI,EAAE,CAAC;QACT,OAAO,YAAY,CACjB,WAAW,CAAC,KAAK,EACjB,2BAA2B,CAAC,GAAG,EAAE,MAAM,CAAC,EACxC,GAAG,EACH,EAAE,CACH,CAAC;IACJ,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP route handlers for the proxy server.
|
|
3
|
+
*
|
|
4
|
+
* Handles:
|
|
5
|
+
* POST /v1/messages — Streaming messages (main endpoint)
|
|
6
|
+
* POST /v1/messages/count_tokens — Token counting
|
|
7
|
+
* GET /health — Health check
|
|
8
|
+
* GET / — Status
|
|
9
|
+
*/
|
|
10
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
11
|
+
import { type NimConfig } from "../providers/nim.js";
|
|
12
|
+
export interface RouteContext {
|
|
13
|
+
nimConfig: NimConfig;
|
|
14
|
+
authToken?: string;
|
|
15
|
+
model: string;
|
|
16
|
+
detector?: import("../observer/eventDetector.js").EventDetector;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Main request router. Called for every incoming HTTP request.
|
|
20
|
+
*/
|
|
21
|
+
export declare function handleRequest(req: IncomingMessage, res: ServerResponse, ctx: RouteContext): Promise<void>;
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP route handlers for the proxy server.
|
|
3
|
+
*
|
|
4
|
+
* Handles:
|
|
5
|
+
* POST /v1/messages — Streaming messages (main endpoint)
|
|
6
|
+
* POST /v1/messages/count_tokens — Token counting
|
|
7
|
+
* GET /health — Health check
|
|
8
|
+
* GET / — Status
|
|
9
|
+
*/
|
|
10
|
+
import { randomUUID } from "node:crypto";
|
|
11
|
+
import { streamNimResponse } from "../providers/nim.js";
|
|
12
|
+
import { validateAuth } from "./auth.js";
|
|
13
|
+
import { tryOptimizations } from "./optimizer.js";
|
|
14
|
+
import { getTokenCount } from "./tokenCounter.js";
|
|
15
|
+
/** Read the full request body as JSON. */
|
|
16
|
+
async function readBody(req) {
|
|
17
|
+
return new Promise((resolve, reject) => {
|
|
18
|
+
const chunks = [];
|
|
19
|
+
req.on("data", (chunk) => chunks.push(chunk));
|
|
20
|
+
req.on("end", () => {
|
|
21
|
+
try {
|
|
22
|
+
const raw = Buffer.concat(chunks).toString("utf-8");
|
|
23
|
+
if (!raw)
|
|
24
|
+
return resolve({});
|
|
25
|
+
resolve(JSON.parse(raw));
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
console.error("[readBody] Raw body string:", Buffer.concat(chunks).toString("utf-8").slice(0, 500));
|
|
29
|
+
reject(new Error("Invalid JSON body"));
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
req.on("error", reject);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
/** Send a JSON response. */
|
|
36
|
+
function sendJson(res, statusCode, data) {
|
|
37
|
+
const body = JSON.stringify(data);
|
|
38
|
+
res.writeHead(statusCode, {
|
|
39
|
+
"Content-Type": "application/json",
|
|
40
|
+
"Content-Length": Buffer.byteLength(body),
|
|
41
|
+
});
|
|
42
|
+
res.end(body);
|
|
43
|
+
}
|
|
44
|
+
/** Handle POST /v1/messages — streaming messages. */
|
|
45
|
+
async function handleMessages(req, res, ctx) {
|
|
46
|
+
ctx.detector?.onThinking();
|
|
47
|
+
const requestData = await readBody(req);
|
|
48
|
+
const requestId = `req_${randomUUID().replace(/-/g, "").slice(0, 12)}`;
|
|
49
|
+
if (!requestData.messages || requestData.messages.length === 0) {
|
|
50
|
+
sendJson(res, 400, { error: { message: "messages cannot be empty" } });
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
// Try optimizations first
|
|
54
|
+
const optimized = tryOptimizations(requestData);
|
|
55
|
+
if (optimized) {
|
|
56
|
+
sendJson(res, 200, optimized);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
// Calculate input tokens
|
|
60
|
+
const inputTokens = getTokenCount(requestData.messages, requestData.system, requestData.tools);
|
|
61
|
+
// Stream SSE response
|
|
62
|
+
res.writeHead(200, {
|
|
63
|
+
"Content-Type": "text/event-stream",
|
|
64
|
+
"Cache-Control": "no-cache",
|
|
65
|
+
Connection: "keep-alive",
|
|
66
|
+
"X-Accel-Buffering": "no",
|
|
67
|
+
});
|
|
68
|
+
try {
|
|
69
|
+
for await (const event of streamNimResponse(requestData, ctx.nimConfig, inputTokens, requestId, ctx.detector)) {
|
|
70
|
+
res.write(event);
|
|
71
|
+
}
|
|
72
|
+
ctx.detector?.setCompletion();
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
// If headers already sent, we can only close
|
|
76
|
+
console.error(`[waifu] Stream error: ${err.message}`);
|
|
77
|
+
}
|
|
78
|
+
res.end();
|
|
79
|
+
}
|
|
80
|
+
/** Handle POST /v1/messages/count_tokens — token counting. */
|
|
81
|
+
async function handleCountTokens(req, res) {
|
|
82
|
+
const requestData = await readBody(req);
|
|
83
|
+
const tokens = getTokenCount(requestData.messages, requestData.system, requestData.tools);
|
|
84
|
+
sendJson(res, 200, { input_tokens: tokens });
|
|
85
|
+
}
|
|
86
|
+
/** Handle GET /health — health check. */
|
|
87
|
+
function handleHealth(res) {
|
|
88
|
+
sendJson(res, 200, { status: "healthy" });
|
|
89
|
+
}
|
|
90
|
+
/** Handle GET / — root status. */
|
|
91
|
+
function handleRoot(res, ctx) {
|
|
92
|
+
sendJson(res, 200, {
|
|
93
|
+
status: "ok",
|
|
94
|
+
provider: "nvidia_nim",
|
|
95
|
+
model: ctx.model,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Main request router. Called for every incoming HTTP request.
|
|
100
|
+
*/
|
|
101
|
+
export async function handleRequest(req, res, ctx) {
|
|
102
|
+
const url = req.url ?? "/";
|
|
103
|
+
const method = req.method ?? "GET";
|
|
104
|
+
if (process.env.WAIFU_VERBOSE) {
|
|
105
|
+
console.log(`\n==== [INCOMING] ${method} ${url} ====\n`);
|
|
106
|
+
}
|
|
107
|
+
// Health check doesn't need auth
|
|
108
|
+
const pathname = url.split("?")[0];
|
|
109
|
+
if (method === "GET" && pathname === "/health") {
|
|
110
|
+
handleHealth(res);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
// Auth check for all other routes
|
|
114
|
+
const authError = validateAuth(req, ctx.authToken);
|
|
115
|
+
if (authError) {
|
|
116
|
+
sendJson(res, 401, { error: { message: authError } });
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
if (method === "POST" && pathname === "/v1/messages") {
|
|
121
|
+
await handleMessages(req, res, ctx);
|
|
122
|
+
}
|
|
123
|
+
else if (method === "POST" && pathname === "/v1/messages/count_tokens") {
|
|
124
|
+
await handleCountTokens(req, res);
|
|
125
|
+
}
|
|
126
|
+
else if (method === "GET" && pathname === "/") {
|
|
127
|
+
handleRoot(res, ctx);
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
sendJson(res, 404, { error: { message: "Not found", path: url } });
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
catch (err) {
|
|
134
|
+
console.error(`[waifu] Request error: ${err.message}`);
|
|
135
|
+
if (!res.headersSent) {
|
|
136
|
+
sendJson(res, 500, {
|
|
137
|
+
error: { message: err.message ?? "Internal server error" },
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=routes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.js","sourceRoot":"","sources":["../../src/proxy/routes.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Embedded proxy HTTP server.
|
|
3
|
+
*
|
|
4
|
+
* Starts a local HTTP server that translates Anthropic API requests
|
|
5
|
+
* to NVIDIA NIM format. Finds a free port automatically.
|
|
6
|
+
*/
|
|
7
|
+
import { type Server } from "node:http";
|
|
8
|
+
export interface ProxyServerOptions {
|
|
9
|
+
nimApiKey: string;
|
|
10
|
+
model?: string;
|
|
11
|
+
authToken: string;
|
|
12
|
+
port?: number;
|
|
13
|
+
host?: string;
|
|
14
|
+
detector?: import("../observer/eventDetector.js").EventDetector;
|
|
15
|
+
}
|
|
16
|
+
export interface RunningProxy {
|
|
17
|
+
port: number;
|
|
18
|
+
host: string;
|
|
19
|
+
server: Server;
|
|
20
|
+
stop: () => Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Start the proxy server.
|
|
24
|
+
* Returns a handle with the port, host, and a stop() function.
|
|
25
|
+
*/
|
|
26
|
+
export declare function startProxyServer(options: ProxyServerOptions): Promise<RunningProxy>;
|
|
27
|
+
/**
|
|
28
|
+
* Wait for the proxy server to be healthy.
|
|
29
|
+
* Polls the /health endpoint with retries.
|
|
30
|
+
*/
|
|
31
|
+
export declare function waitForHealth(host: string, port: number, maxRetries?: number, intervalMs?: number): Promise<boolean>;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Embedded proxy HTTP server.
|
|
3
|
+
*
|
|
4
|
+
* Starts a local HTTP server that translates Anthropic API requests
|
|
5
|
+
* to NVIDIA NIM format. Finds a free port automatically.
|
|
6
|
+
*/
|
|
7
|
+
import { createServer } from "node:http";
|
|
8
|
+
import { handleRequest } from "./routes.js";
|
|
9
|
+
/**
|
|
10
|
+
* Find a free port by binding to port 0 and reading back the assigned port.
|
|
11
|
+
*/
|
|
12
|
+
async function findFreePort(host) {
|
|
13
|
+
return new Promise((resolve, reject) => {
|
|
14
|
+
const srv = createServer();
|
|
15
|
+
srv.listen(0, host, () => {
|
|
16
|
+
const addr = srv.address();
|
|
17
|
+
if (addr && typeof addr === "object") {
|
|
18
|
+
const port = addr.port;
|
|
19
|
+
srv.close(() => resolve(port));
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
srv.close(() => reject(new Error("Could not determine port")));
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
srv.on("error", reject);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Start the proxy server.
|
|
30
|
+
* Returns a handle with the port, host, and a stop() function.
|
|
31
|
+
*/
|
|
32
|
+
export async function startProxyServer(options) {
|
|
33
|
+
const host = options.host ?? "127.0.0.1";
|
|
34
|
+
const port = options.port ?? (await findFreePort(host));
|
|
35
|
+
const ctx = {
|
|
36
|
+
nimConfig: {
|
|
37
|
+
apiKey: options.nimApiKey,
|
|
38
|
+
model: options.model,
|
|
39
|
+
},
|
|
40
|
+
authToken: options.authToken,
|
|
41
|
+
model: options.model ?? "moonshotai/kimi-k2.5",
|
|
42
|
+
detector: options.detector,
|
|
43
|
+
};
|
|
44
|
+
const server = createServer((req, res) => {
|
|
45
|
+
handleRequest(req, res, ctx).catch((err) => {
|
|
46
|
+
console.error(`[waifu] Unhandled error: ${err.message}`);
|
|
47
|
+
if (!res.headersSent) {
|
|
48
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
49
|
+
res.end(JSON.stringify({ error: { message: "Internal server error" } }));
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
return new Promise((resolve, reject) => {
|
|
54
|
+
server.on("error", reject);
|
|
55
|
+
server.listen(port, host, () => {
|
|
56
|
+
const stop = async () => {
|
|
57
|
+
return new Promise((resolveStop) => {
|
|
58
|
+
server.close(() => resolveStop());
|
|
59
|
+
// Force-close after 2 seconds
|
|
60
|
+
setTimeout(() => resolveStop(), 2000);
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
resolve({ port, host, server, stop });
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Wait for the proxy server to be healthy.
|
|
69
|
+
* Polls the /health endpoint with retries.
|
|
70
|
+
*/
|
|
71
|
+
export async function waitForHealth(host, port, maxRetries = 30, intervalMs = 100) {
|
|
72
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
73
|
+
try {
|
|
74
|
+
const resp = await fetch(`http://${host}:${port}/health`, {
|
|
75
|
+
signal: AbortSignal.timeout(1000),
|
|
76
|
+
});
|
|
77
|
+
if (resp.ok)
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
// Not ready yet
|
|
82
|
+
}
|
|
83
|
+
await new Promise((r) => setTimeout(r, intervalMs));
|
|
84
|
+
}
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=server.js.map
|