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.
Files changed (59) hide show
  1. package/README.md +51 -0
  2. package/dist/cli.d.ts +15 -0
  3. package/dist/cli.js +188 -0
  4. package/dist/cli.js.map +1 -0
  5. package/dist/config.d.ts +35 -0
  6. package/dist/config.js +73 -0
  7. package/dist/config.js.map +1 -0
  8. package/dist/observer/eventDetector.d.ts +16 -0
  9. package/dist/observer/eventDetector.js +30 -0
  10. package/dist/observer/eventDetector.js.map +1 -0
  11. package/dist/observer/streamObserver.d.ts +8 -0
  12. package/dist/observer/streamObserver.js +24 -0
  13. package/dist/observer/streamObserver.js.map +1 -0
  14. package/dist/providers/converter.d.ts +18 -0
  15. package/dist/providers/converter.js +168 -0
  16. package/dist/providers/converter.js.map +1 -0
  17. package/dist/providers/nim.d.ts +23 -0
  18. package/dist/providers/nim.js +276 -0
  19. package/dist/providers/nim.js.map +1 -0
  20. package/dist/providers/sseBuilder.d.ts +53 -0
  21. package/dist/providers/sseBuilder.js +280 -0
  22. package/dist/providers/sseBuilder.js.map +1 -0
  23. package/dist/providers/thinkParser.d.ts +21 -0
  24. package/dist/providers/thinkParser.js +130 -0
  25. package/dist/providers/thinkParser.js.map +1 -0
  26. package/dist/providers/toolParser.d.ts +31 -0
  27. package/dist/providers/toolParser.js +163 -0
  28. package/dist/providers/toolParser.js.map +1 -0
  29. package/dist/providers/types.d.ts +57 -0
  30. package/dist/providers/types.js +20 -0
  31. package/dist/providers/types.js.map +1 -0
  32. package/dist/proxy/auth.d.ts +13 -0
  33. package/dist/proxy/auth.js +37 -0
  34. package/dist/proxy/auth.js.map +1 -0
  35. package/dist/proxy/commandUtils.d.ts +15 -0
  36. package/dist/proxy/commandUtils.js +155 -0
  37. package/dist/proxy/commandUtils.js.map +1 -0
  38. package/dist/proxy/detection.d.ts +13 -0
  39. package/dist/proxy/detection.js +107 -0
  40. package/dist/proxy/detection.js.map +1 -0
  41. package/dist/proxy/optimizer.d.ts +14 -0
  42. package/dist/proxy/optimizer.js +56 -0
  43. package/dist/proxy/optimizer.js.map +1 -0
  44. package/dist/proxy/routes.d.ts +21 -0
  45. package/dist/proxy/routes.js +142 -0
  46. package/dist/proxy/routes.js.map +1 -0
  47. package/dist/proxy/server.d.ts +31 -0
  48. package/dist/proxy/server.js +87 -0
  49. package/dist/proxy/server.js.map +1 -0
  50. package/dist/proxy/tokenCounter.d.ts +12 -0
  51. package/dist/proxy/tokenCounter.js +91 -0
  52. package/dist/proxy/tokenCounter.js.map +1 -0
  53. package/dist/proxy/types.d.ts +89 -0
  54. package/dist/proxy/types.js +7 -0
  55. package/dist/proxy/types.js.map +1 -0
  56. package/dist/waifu/overlayRenderer.d.ts +14 -0
  57. package/dist/waifu/overlayRenderer.js +49 -0
  58. package/dist/waifu/overlayRenderer.js.map +1 -0
  59. package/package.json +40 -0
package/README.md ADDED
@@ -0,0 +1,51 @@
1
+ # waifu CLI
2
+
3
+ A streamlined proxy and wrapper for Claude Code that transparently routes API requests through NVIDIA NIM, eliminating the need for complex Python proxy setups.
4
+
5
+ By default, it uses `moonshotai/kimi-k2-thinking` for intelligent responses and natively supports Anthropic streaming APIs.
6
+
7
+ ## Installation
8
+
9
+ Install globally via npm:
10
+
11
+ ```bash
12
+ npm install -g .
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ Simply run:
18
+
19
+ ```bash
20
+ waifu
21
+ ```
22
+
23
+ On first launch, it will prompt you for your NVIDIA NIM API key. This key is saved automatically to `~/.waifu/config.json`.
24
+
25
+ `waifu` will immediately start the integrated TypeScript proxy in the background on a random available port and securely launch your locally-installed `claude-code` CLI natively. No manual configuration or `$env` modifications are required!
26
+
27
+ ### Options
28
+
29
+ ```bash
30
+ Usage: waifu [options]
31
+
32
+ Run the Claude Code CLI through the NVIDIA NIM proxy.
33
+
34
+ Options:
35
+ -v, --version Output the current version
36
+ -k, --nim-key <key> NVIDIA NIM API key (saved automatically)
37
+ -m, --model <model> Specific NIM model to use (default: moonshotai/kimi-k2-thinking)
38
+ -p, --port <port> Port to run the proxy on (default: auto)
39
+ --proxy-only Start only the proxy server without launching claude
40
+ --verbose Enable verbose logging for debugging
41
+ -h, --help Display help for command
42
+ ```
43
+
44
+ ## How It Works
45
+
46
+ This tool is a drop-in replacement for the original Python proxy server. It relies on a hyper-efficient native NodeJS integration:
47
+
48
+ 1. Intercepts Anthropic Server-Sent Events (SSE).
49
+ 2. Converts Claude's messages format directly to NIM compatible JSON.
50
+ 3. Fixes Anthropic API quirks (like `?beta=true` queries and streaming header strictness) seamlessly underneath the hood.
51
+ 4. Auto-detects and preserves `<think>` tags returned by supported models without breaking the CLI UI experience.
package/dist/cli.d.ts ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * waifu — Free coding assistant CLI.
4
+ *
5
+ * Starts an embedded proxy server that translates Anthropic API
6
+ * requests to NVIDIA NIM, then launches claude-code CLI with
7
+ * the proxy configured automatically.
8
+ *
9
+ * Usage:
10
+ * waifu # Use saved NIM key
11
+ * waifu --nim-key nvapi-xxx # Provide NIM key (saved for next time)
12
+ * waifu config # Show current configuration
13
+ * waifu config --nim-key xxx # Save NIM key without starting
14
+ */
15
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,188 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * waifu — Free coding assistant CLI.
4
+ *
5
+ * Starts an embedded proxy server that translates Anthropic API
6
+ * requests to NVIDIA NIM, then launches claude-code CLI with
7
+ * the proxy configured automatically.
8
+ *
9
+ * Usage:
10
+ * waifu # Use saved NIM key
11
+ * waifu --nim-key nvapi-xxx # Provide NIM key (saved for next time)
12
+ * waifu config # Show current configuration
13
+ * waifu config --nim-key xxx # Save NIM key without starting
14
+ */
15
+ import { spawn } from "node:child_process";
16
+ import { randomUUID } from "node:crypto";
17
+ import { Command } from "commander";
18
+ import { resolveNimApiKey, resolveModel, loadConfig, saveConfig, getConfigFile, } from "./config.js";
19
+ import { startProxyServer, waitForHealth } from "./proxy/server.js";
20
+ import { DEFAULT_MODEL } from "./providers/nim.js";
21
+ import { EventDetector } from "./observer/eventDetector.js";
22
+ import { OverlayRenderer } from "./waifu/overlayRenderer.js";
23
+ const VERSION = "1.0.0";
24
+ const program = new Command();
25
+ program
26
+ .name("waifu")
27
+ .description("Free coding assistant — Claude Code powered by NVIDIA NIM")
28
+ .version(VERSION)
29
+ .enablePositionalOptions()
30
+ .passThroughOptions();
31
+ // ── Main command (default) ──
32
+ program
33
+ .option("--nim-key <key>", "NVIDIA NIM API key")
34
+ .option("--model <model>", `Model to use (default: ${DEFAULT_MODEL})`)
35
+ .option("--port <port>", "Proxy server port (default: auto)")
36
+ .option("--proxy-only", "Start proxy server only (don't launch claude)")
37
+ .option("--no-waifu", "Disable waifu overlay and sounds")
38
+ .option("--verbose", "Enable verbose logging")
39
+ .action(async (opts) => {
40
+ const nimKey = resolveNimApiKey(opts.nimKey);
41
+ const model = resolveModel(opts.model);
42
+ const verbose = opts.verbose ?? false;
43
+ if (!nimKey) {
44
+ console.error("\x1b[31m✗ No NVIDIA NIM API key found.\x1b[0m\n\n" +
45
+ "Provide your key in one of these ways:\n" +
46
+ " waifu --nim-key nvapi-xxx\n" +
47
+ " waifu config --nim-key nvapi-xxx\n" +
48
+ " export NIM_API_KEY=nvapi-xxx\n\n" +
49
+ "Get a free key at: \x1b[36mhttps://build.nvidia.com/settings/api-keys\x1b[0m");
50
+ process.exit(1);
51
+ }
52
+ // Save the key for next time if provided via CLI
53
+ if (opts.nimKey) {
54
+ const config = loadConfig();
55
+ config.nimApiKey = opts.nimKey;
56
+ if (opts.model)
57
+ config.model = opts.model;
58
+ saveConfig(config);
59
+ if (verbose)
60
+ console.log(`[waifu] Config saved to ${getConfigFile()}`);
61
+ }
62
+ // Generate internal auth token
63
+ const authToken = randomUUID();
64
+ // Initialize waifu overlay if enabled
65
+ const useWaifu = opts.waifu !== false;
66
+ let detector;
67
+ if (useWaifu) {
68
+ detector = new EventDetector();
69
+ const renderer = new OverlayRenderer(true);
70
+ detector.on("event", (ev) => renderer.render(ev));
71
+ }
72
+ // Start proxy server
73
+ if (verbose)
74
+ console.log("[waifu] Starting proxy server...");
75
+ const proxy = await startProxyServer({
76
+ nimApiKey: nimKey,
77
+ model,
78
+ authToken,
79
+ port: opts.port ? parseInt(opts.port, 10) : undefined,
80
+ detector,
81
+ });
82
+ console.log(`\x1b[32m✓ Proxy started\x1b[0m on http://${proxy.host}:${proxy.port} → NIM (${model})`);
83
+ // Wait for health check
84
+ const healthy = await waitForHealth(proxy.host, proxy.port);
85
+ if (!healthy) {
86
+ console.error("\x1b[31m✗ Proxy health check failed\x1b[0m");
87
+ await proxy.stop();
88
+ process.exit(1);
89
+ }
90
+ if (opts.proxyOnly) {
91
+ console.log("\n\x1b[33mProxy-only mode.\x1b[0m Use these env vars with claude:\n" +
92
+ ` ANTHROPIC_AUTH_TOKEN=${authToken}\n` +
93
+ ` ANTHROPIC_BASE_URL=http://${proxy.host}:${proxy.port}\n`);
94
+ // Keep running until interrupted
95
+ process.on("SIGINT", async () => {
96
+ console.log("\n[waifu] Shutting down...");
97
+ await proxy.stop();
98
+ process.exit(0);
99
+ });
100
+ process.on("SIGTERM", async () => {
101
+ await proxy.stop();
102
+ process.exit(0);
103
+ });
104
+ return;
105
+ }
106
+ // Launch claude-code CLI
107
+ if (verbose)
108
+ console.log("[waifu] Launching claude...");
109
+ const claudeCmd = "claude";
110
+ const claudeArgs = process.argv.slice(2).filter((arg) => !arg.startsWith("--nim-key") &&
111
+ !arg.startsWith("--model") &&
112
+ !arg.startsWith("--port") &&
113
+ !arg.startsWith("--proxy-only") &&
114
+ !arg.startsWith("--verbose") &&
115
+ // Also filter out the values for key/model/port
116
+ arg !== opts.nimKey &&
117
+ arg !== opts.model &&
118
+ arg !== opts.port &&
119
+ arg !== "--no-waifu");
120
+ const child = spawn(claudeCmd, claudeArgs, {
121
+ stdio: "inherit",
122
+ env: {
123
+ ...process.env,
124
+ ANTHROPIC_AUTH_TOKEN: authToken,
125
+ ANTHROPIC_BASE_URL: `http://${proxy.host}:${proxy.port}`,
126
+ // Disable the update check (we're not the official CLI)
127
+ CLAUDE_CODE_SKIP_UPDATE_CHECK: "1",
128
+ },
129
+ shell: process.platform === "win32",
130
+ });
131
+ child.on("error", (err) => {
132
+ if (err.code === "ENOENT") {
133
+ console.error("\x1b[31m✗ 'claude' CLI not found.\x1b[0m\n\n" +
134
+ "Install it first:\n" +
135
+ " npm install -g @anthropic-ai/claude-code\n");
136
+ }
137
+ else {
138
+ console.error(`\x1b[31m✗ Failed to start claude: ${err.message}\x1b[0m`);
139
+ }
140
+ proxy.stop().then(() => process.exit(1));
141
+ });
142
+ child.on("exit", async (code) => {
143
+ await proxy.stop();
144
+ process.exit(code ?? 0);
145
+ });
146
+ // Cleanup on signals
147
+ const cleanup = async () => {
148
+ child.kill();
149
+ await proxy.stop();
150
+ process.exit(0);
151
+ };
152
+ process.on("SIGINT", cleanup);
153
+ process.on("SIGTERM", cleanup);
154
+ });
155
+ // ── Config subcommand ──
156
+ program
157
+ .command("config")
158
+ .description("View or update waifu configuration")
159
+ .option("--nim-key <key>", "Set NVIDIA NIM API key")
160
+ .option("--model <model>", "Set default model")
161
+ .option("--reset", "Reset all configuration")
162
+ .action((opts) => {
163
+ if (opts.reset) {
164
+ saveConfig({});
165
+ console.log("✓ Configuration reset.");
166
+ return;
167
+ }
168
+ if (opts.nimKey || opts.model) {
169
+ const config = loadConfig();
170
+ if (opts.nimKey)
171
+ config.nimApiKey = opts.nimKey;
172
+ if (opts.model)
173
+ config.model = opts.model;
174
+ saveConfig(config);
175
+ console.log(`✓ Configuration saved to ${getConfigFile()}`);
176
+ return;
177
+ }
178
+ // Show current config
179
+ const config = loadConfig();
180
+ const key = config.nimApiKey;
181
+ console.log("\n\x1b[1mwaifu configuration\x1b[0m");
182
+ console.log(` Config file: ${getConfigFile()}`);
183
+ console.log(` NIM API Key: ${key ? key.slice(0, 8) + "..." + key.slice(-4) : "\x1b[33m(not set)\x1b[0m"}`);
184
+ console.log(` Model: ${config.model ?? `${DEFAULT_MODEL} (default)`}`);
185
+ console.log("");
186
+ });
187
+ program.parse();
188
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Configuration management for waifu CLI.
3
+ *
4
+ * Stores NIM API key and settings in ~/.waifu/config.json.
5
+ * Cross-platform: uses os.homedir() for the config directory.
6
+ */
7
+ export interface WaifuConfig {
8
+ nimApiKey?: string;
9
+ model?: string;
10
+ }
11
+ /** Load config from disk. Returns empty config if file doesn't exist. */
12
+ export declare function loadConfig(): WaifuConfig;
13
+ /** Save config to disk. Creates the config directory if needed. */
14
+ export declare function saveConfig(config: WaifuConfig): void;
15
+ /**
16
+ * Resolve the NIM API key from (in priority order):
17
+ * 1. CLI flag --nim-key
18
+ * 2. Environment variable NIM_API_KEY or NVIDIA_NIM_API_KEY
19
+ * 3. Persisted config file ~/.waifu/config.json
20
+ *
21
+ * Returns the key or null if not found.
22
+ */
23
+ export declare function resolveNimApiKey(cliKey?: string): string | null;
24
+ /**
25
+ * Resolve the model from (in priority order):
26
+ * 1. CLI flag --model
27
+ * 2. Environment variable WAIFU_MODEL
28
+ * 3. Persisted config file
29
+ * 4. Default: moonshotai/kimi-k2.5
30
+ */
31
+ export declare function resolveModel(cliModel?: string): string;
32
+ /** Get the config directory path. */
33
+ export declare function getConfigDir(): string;
34
+ /** Get the config file path. */
35
+ export declare function getConfigFile(): string;
package/dist/config.js ADDED
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Configuration management for waifu CLI.
3
+ *
4
+ * Stores NIM API key and settings in ~/.waifu/config.json.
5
+ * Cross-platform: uses os.homedir() for the config directory.
6
+ */
7
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
8
+ import { homedir } from "node:os";
9
+ import { join } from "node:path";
10
+ const CONFIG_DIR = join(homedir(), ".waifu");
11
+ const CONFIG_FILE = join(CONFIG_DIR, "config.json");
12
+ /** Load config from disk. Returns empty config if file doesn't exist. */
13
+ export function loadConfig() {
14
+ try {
15
+ if (existsSync(CONFIG_FILE)) {
16
+ const raw = readFileSync(CONFIG_FILE, "utf-8");
17
+ return JSON.parse(raw);
18
+ }
19
+ }
20
+ catch {
21
+ // Corrupted config — treat as empty
22
+ }
23
+ return {};
24
+ }
25
+ /** Save config to disk. Creates the config directory if needed. */
26
+ export function saveConfig(config) {
27
+ if (!existsSync(CONFIG_DIR)) {
28
+ mkdirSync(CONFIG_DIR, { recursive: true });
29
+ }
30
+ writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + "\n", "utf-8");
31
+ }
32
+ /**
33
+ * Resolve the NIM API key from (in priority order):
34
+ * 1. CLI flag --nim-key
35
+ * 2. Environment variable NIM_API_KEY or NVIDIA_NIM_API_KEY
36
+ * 3. Persisted config file ~/.waifu/config.json
37
+ *
38
+ * Returns the key or null if not found.
39
+ */
40
+ export function resolveNimApiKey(cliKey) {
41
+ if (cliKey)
42
+ return cliKey;
43
+ if (process.env.NIM_API_KEY)
44
+ return process.env.NIM_API_KEY;
45
+ if (process.env.NVIDIA_NIM_API_KEY)
46
+ return process.env.NVIDIA_NIM_API_KEY;
47
+ const config = loadConfig();
48
+ return config.nimApiKey ?? null;
49
+ }
50
+ /**
51
+ * Resolve the model from (in priority order):
52
+ * 1. CLI flag --model
53
+ * 2. Environment variable WAIFU_MODEL
54
+ * 3. Persisted config file
55
+ * 4. Default: moonshotai/kimi-k2.5
56
+ */
57
+ export function resolveModel(cliModel) {
58
+ if (cliModel)
59
+ return cliModel;
60
+ if (process.env.WAIFU_MODEL)
61
+ return process.env.WAIFU_MODEL;
62
+ const config = loadConfig();
63
+ return config.model ?? "moonshotai/kimi-k2-thinking";
64
+ }
65
+ /** Get the config directory path. */
66
+ export function getConfigDir() {
67
+ return CONFIG_DIR;
68
+ }
69
+ /** Get the config file path. */
70
+ export function getConfigFile() {
71
+ return CONFIG_FILE;
72
+ }
73
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAOjC,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,CAAC,CAAC;AAC7C,MAAM,WAAW,GAAG,IAAI,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;AAEpD,yEAAyE;AACzE,MAAM,UAAU,UAAU;IACxB,IAAI,CAAC;QACH,IAAI,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;YAC5B,MAAM,GAAG,GAAG,YAAY,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;YAC/C,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAgB,CAAC;QACxC,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,oCAAoC;IACtC,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,UAAU,CAAC,MAAmB;IAC5C,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC5B,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7C,CAAC;IACD,aAAa,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,OAAO,CAAC,CAAC;AAC9E,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAe;IAC9C,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC;IAC1B,IAAI,OAAO,CAAC,GAAG,CAAC,WAAW;QAAE,OAAO,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC;IAC5D,IAAI,OAAO,CAAC,GAAG,CAAC,kBAAkB;QAAE,OAAO,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;IAE1E,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,OAAO,MAAM,CAAC,SAAS,IAAI,IAAI,CAAC;AAClC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,QAAiB;IAC5C,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAC9B,IAAI,OAAO,CAAC,GAAG,CAAC,WAAW;QAAE,OAAO,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC;IAE5D,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,OAAO,MAAM,CAAC,KAAK,IAAI,6BAA6B,CAAC;AACvD,CAAC;AAED,qCAAqC;AACrC,MAAM,UAAU,YAAY;IAC1B,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,gCAAgC;AAChC,MAAM,UAAU,aAAa;IAC3B,OAAO,WAAW,CAAC;AACrB,CAAC"}
@@ -0,0 +1,16 @@
1
+ import { EventEmitter } from "node:events";
2
+ export type WaifuEvent = "thinking" | "permission" | "error" | "completion" | "idle";
3
+ /**
4
+ * EventDetector now receives high-level events from the Proxy Server
5
+ * instead of raw terminal bytes.
6
+ */
7
+ export declare class EventDetector extends EventEmitter {
8
+ private currentState;
9
+ constructor();
10
+ /** Called when the model starts sending reasoning (thinking) content */
11
+ onThinking(): void;
12
+ /** Called when the message stream is complete */
13
+ setCompletion(): void;
14
+ private updateState;
15
+ reset(): void;
16
+ }
@@ -0,0 +1,30 @@
1
+ import { EventEmitter } from "node:events";
2
+ /**
3
+ * EventDetector now receives high-level events from the Proxy Server
4
+ * instead of raw terminal bytes.
5
+ */
6
+ export class EventDetector extends EventEmitter {
7
+ currentState = "idle";
8
+ constructor() {
9
+ super();
10
+ }
11
+ /** Called when the model starts sending reasoning (thinking) content */
12
+ onThinking() {
13
+ this.updateState("thinking");
14
+ }
15
+ /** Called when the message stream is complete */
16
+ setCompletion() {
17
+ // Reset to idle after a short delay
18
+ setTimeout(() => this.updateState("idle"), 1000);
19
+ }
20
+ updateState(newState) {
21
+ if (this.currentState === newState)
22
+ return;
23
+ this.currentState = newState;
24
+ this.emit("event", newState);
25
+ }
26
+ reset() {
27
+ this.updateState("idle");
28
+ }
29
+ }
30
+ //# sourceMappingURL=eventDetector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eventDetector.js","sourceRoot":"","sources":["../../src/observer/eventDetector.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAI3C;;;GAGG;AACH,MAAM,OAAO,aAAc,SAAQ,YAAY;IACrC,YAAY,GAAe,MAAM,CAAC;IAE1C;QACE,KAAK,EAAE,CAAC;IACV,CAAC;IAED,wEAAwE;IACjE,UAAU;QACf,IAAI,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC;IAC/B,CAAC;IAED,iDAAiD;IAC1C,aAAa;QAClB,oCAAoC;QACpC,UAAU,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC;IACnD,CAAC;IAGO,WAAW,CAAC,QAAoB;QACtC,IAAI,IAAI,CAAC,YAAY,KAAK,QAAQ;YAAE,OAAO;QAC3C,IAAI,CAAC,YAAY,GAAG,QAAQ,CAAC;QAC7B,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IAC/B,CAAC;IAEM,KAAK;QACV,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;IAC3B,CAAC;CACF"}
@@ -0,0 +1,8 @@
1
+ import { ChildProcess } from "node:child_process";
2
+ import { EventDetector } from "./eventDetector.js";
3
+ import { OverlayRenderer } from "../waifu/overlayRenderer.js";
4
+ /**
5
+ * StreamObserver listens to a child process's stdout and routes chunks
6
+ * to terminal and our event detector / renderer for the waifu overlay.
7
+ */
8
+ export declare function createStreamObserver(child: ChildProcess, detector: EventDetector, renderer: OverlayRenderer): void;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * StreamObserver listens to a child process's stdout and routes chunks
3
+ * to terminal and our event detector / renderer for the waifu overlay.
4
+ */
5
+ export function createStreamObserver(child, detector, renderer) {
6
+ if (!child.stdout) {
7
+ throw new Error("Child process has no stdout to observe.");
8
+ }
9
+ child.stdout.on("data", (chunk) => {
10
+ const text = chunk.toString("utf8");
11
+ // 1. Immediately write to terminal (CRITICAL: DO NOT DELAY)
12
+ process.stdout.write(chunk);
13
+ // 2. Feed to detector for analysis
14
+ detector.onChunk(text);
15
+ });
16
+ child.on("exit", () => {
17
+ detector.setCompletion();
18
+ });
19
+ // Listen for detector events
20
+ detector.on("event", (event) => {
21
+ renderer.render(event);
22
+ });
23
+ }
24
+ //# sourceMappingURL=streamObserver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"streamObserver.js","sourceRoot":"","sources":["../../src/observer/streamObserver.ts"],"names":[],"mappings":"AAIA;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAClC,KAAmB,EACnB,QAAuB,EACvB,QAAyB;IAEzB,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;QAClB,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;IAC7D,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;QACxC,MAAM,IAAI,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAEpC,4DAA4D;QAC5D,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAE5B,mCAAmC;QACnC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,CAAC,CAAC,CAAC;IAEH,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE;QACpB,QAAQ,CAAC,aAAa,EAAE,CAAC;IAC3B,CAAC,CAAC,CAAC;IAEH,6BAA6B;IAC7B,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAa,EAAE,EAAE;QACrC,QAAQ,CAAC,MAAM,CAAC,KAAY,CAAC,CAAC;IAChC,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Anthropic ↔ OpenAI message format converter.
3
+ *
4
+ * Converts Anthropic-style messages (with content blocks like text, thinking,
5
+ * tool_use, tool_result) to OpenAI chat completions format.
6
+ * Port of Python AnthropicToOpenAIConverter from the proxy server.
7
+ */
8
+ /** Convert a list of Anthropic messages to OpenAI format. */
9
+ export declare function convertMessages(messages: any[]): any[];
10
+ /** Convert Anthropic tools to OpenAI format. */
11
+ export declare function convertTools(tools: any[]): any[];
12
+ /** Convert Anthropic system prompt to OpenAI format. */
13
+ export declare function convertSystemPrompt(system: string | any[] | undefined | null): {
14
+ role: string;
15
+ content: string;
16
+ } | null;
17
+ /** Build the common parts of an OpenAI-format request body. */
18
+ export declare function buildBaseRequestBody(requestData: any): any;
@@ -0,0 +1,168 @@
1
+ /**
2
+ * Anthropic ↔ OpenAI message format converter.
3
+ *
4
+ * Converts Anthropic-style messages (with content blocks like text, thinking,
5
+ * tool_use, tool_result) to OpenAI chat completions format.
6
+ * Port of Python AnthropicToOpenAIConverter from the proxy server.
7
+ */
8
+ /* eslint-disable @typescript-eslint/no-explicit-any */
9
+ function getBlockAttr(block, attr, defaultVal = undefined) {
10
+ if (block && typeof block === "object") {
11
+ return block[attr] ?? defaultVal;
12
+ }
13
+ return defaultVal;
14
+ }
15
+ function getBlockType(block) {
16
+ return getBlockAttr(block, "type");
17
+ }
18
+ /** Convert a list of Anthropic messages to OpenAI format. */
19
+ export function convertMessages(messages) {
20
+ const result = [];
21
+ for (const msg of messages) {
22
+ const role = msg.role;
23
+ const content = msg.content;
24
+ if (typeof content === "string") {
25
+ result.push({ role, content });
26
+ }
27
+ else if (Array.isArray(content)) {
28
+ if (role === "assistant") {
29
+ result.push(...convertAssistantMessage(content));
30
+ }
31
+ else if (role === "user") {
32
+ result.push(...convertUserMessage(content));
33
+ }
34
+ }
35
+ else {
36
+ result.push({ role, content: String(content) });
37
+ }
38
+ }
39
+ return result;
40
+ }
41
+ function convertAssistantMessage(content) {
42
+ const contentParts = [];
43
+ const toolCalls = [];
44
+ for (const block of content) {
45
+ const blockType = getBlockType(block);
46
+ if (blockType === "text") {
47
+ contentParts.push(getBlockAttr(block, "text", ""));
48
+ }
49
+ else if (blockType === "thinking") {
50
+ const thinking = getBlockAttr(block, "thinking", "");
51
+ contentParts.push(`<think>\n${thinking}\n</think>`);
52
+ }
53
+ else if (blockType === "tool_use") {
54
+ const toolInput = getBlockAttr(block, "input", {});
55
+ toolCalls.push({
56
+ id: getBlockAttr(block, "id"),
57
+ type: "function",
58
+ function: {
59
+ name: getBlockAttr(block, "name"),
60
+ arguments: typeof toolInput === "object"
61
+ ? JSON.stringify(toolInput)
62
+ : String(toolInput),
63
+ },
64
+ });
65
+ }
66
+ }
67
+ let contentStr = contentParts.join("\n\n");
68
+ // Ensure content is never empty for assistant messages
69
+ // NIM requires non-empty content if there are no tool calls
70
+ if (!contentStr && toolCalls.length === 0) {
71
+ contentStr = " ";
72
+ }
73
+ const msg = { role: "assistant", content: contentStr };
74
+ if (toolCalls.length > 0) {
75
+ msg.tool_calls = toolCalls;
76
+ }
77
+ return [msg];
78
+ }
79
+ function convertUserMessage(content) {
80
+ const result = [];
81
+ const textParts = [];
82
+ const flushText = () => {
83
+ if (textParts.length > 0) {
84
+ result.push({ role: "user", content: textParts.join("\n") });
85
+ textParts.length = 0;
86
+ }
87
+ };
88
+ for (const block of content) {
89
+ const blockType = getBlockType(block);
90
+ if (blockType === "text") {
91
+ textParts.push(getBlockAttr(block, "text", ""));
92
+ }
93
+ else if (blockType === "tool_result") {
94
+ flushText();
95
+ let toolContent = getBlockAttr(block, "content", "");
96
+ if (Array.isArray(toolContent)) {
97
+ toolContent = toolContent
98
+ .map((item) => typeof item === "object" ? item.text ?? String(item) : String(item))
99
+ .join("\n");
100
+ }
101
+ result.push({
102
+ role: "tool",
103
+ tool_call_id: getBlockAttr(block, "tool_use_id"),
104
+ content: toolContent ? String(toolContent) : "",
105
+ });
106
+ }
107
+ }
108
+ flushText();
109
+ return result;
110
+ }
111
+ /** Convert Anthropic tools to OpenAI format. */
112
+ export function convertTools(tools) {
113
+ return tools.map((tool) => ({
114
+ type: "function",
115
+ function: {
116
+ name: tool.name,
117
+ description: tool.description || "",
118
+ parameters: tool.input_schema,
119
+ },
120
+ }));
121
+ }
122
+ /** Convert Anthropic system prompt to OpenAI format. */
123
+ export function convertSystemPrompt(system) {
124
+ if (typeof system === "string") {
125
+ return { role: "system", content: system };
126
+ }
127
+ if (Array.isArray(system)) {
128
+ const textParts = system
129
+ .filter((block) => getBlockType(block) === "text")
130
+ .map((block) => getBlockAttr(block, "text", ""));
131
+ if (textParts.length > 0) {
132
+ return { role: "system", content: textParts.join("\n\n").trim() };
133
+ }
134
+ }
135
+ return null;
136
+ }
137
+ /** Build the common parts of an OpenAI-format request body. */
138
+ export function buildBaseRequestBody(requestData) {
139
+ const messages = convertMessages(requestData.messages);
140
+ const system = requestData.system;
141
+ if (system) {
142
+ const systemMsg = convertSystemPrompt(system);
143
+ if (systemMsg) {
144
+ messages.unshift(systemMsg);
145
+ }
146
+ }
147
+ const body = { model: requestData.model, messages };
148
+ if (requestData.max_tokens != null) {
149
+ body.max_tokens = requestData.max_tokens;
150
+ }
151
+ if (requestData.temperature != null) {
152
+ body.temperature = requestData.temperature;
153
+ }
154
+ if (requestData.top_p != null) {
155
+ body.top_p = requestData.top_p;
156
+ }
157
+ if (requestData.stop_sequences) {
158
+ body.stop = requestData.stop_sequences;
159
+ }
160
+ if (requestData.tools && requestData.tools.length > 0) {
161
+ body.tools = convertTools(requestData.tools);
162
+ }
163
+ if (requestData.tool_choice) {
164
+ body.tool_choice = requestData.tool_choice;
165
+ }
166
+ return body;
167
+ }
168
+ //# sourceMappingURL=converter.js.map