@ai-ide-bridge/cli 1.0.5 → 1.1.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 (140) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/commands/configure.js +78 -10
  3. package/dist/commands/daemon.d.ts +1 -0
  4. package/dist/commands/daemon.js +107 -13
  5. package/dist/commands/doctor.js +1 -1
  6. package/dist/commands/init.js +70 -5
  7. package/dist/commands/login.d.ts +1 -0
  8. package/dist/commands/login.js +62 -0
  9. package/dist/commands/logout.d.ts +1 -0
  10. package/dist/commands/logout.js +12 -0
  11. package/dist/commands/start.js +4 -4
  12. package/dist/core/config.d.ts +4 -0
  13. package/dist/core/config.js +43 -0
  14. package/dist/core/daemon-session.d.ts +14 -0
  15. package/dist/core/daemon-session.js +179 -0
  16. package/dist/core/daemon.d.ts +16 -0
  17. package/dist/core/daemon.js +168 -0
  18. package/dist/core/formatter.d.ts +3 -0
  19. package/dist/core/formatter.js +44 -0
  20. package/dist/core/index.d.ts +9 -0
  21. package/dist/core/index.js +9 -0
  22. package/dist/core/parser.d.ts +164 -0
  23. package/dist/core/parser.js +37 -0
  24. package/dist/core/registry.d.ts +16 -0
  25. package/dist/core/registry.js +53 -0
  26. package/dist/core/server.d.ts +19 -0
  27. package/dist/core/server.js +185 -0
  28. package/dist/core/session.d.ts +11 -0
  29. package/dist/core/session.js +39 -0
  30. package/dist/core/types.d.ts +166 -0
  31. package/dist/core/types.js +44 -0
  32. package/dist/index.js +22 -5
  33. package/dist/oauth/device-flow.d.ts +12 -0
  34. package/dist/oauth/device-flow.js +93 -0
  35. package/dist/oauth/flow.d.ts +11 -0
  36. package/dist/oauth/flow.js +75 -0
  37. package/dist/oauth/index.d.ts +6 -0
  38. package/dist/oauth/index.js +5 -0
  39. package/dist/oauth/lifecycle.d.ts +13 -0
  40. package/dist/oauth/lifecycle.js +56 -0
  41. package/dist/oauth/providers.d.ts +2 -0
  42. package/dist/oauth/providers.js +19 -0
  43. package/dist/oauth/storage-file.d.ts +2 -0
  44. package/dist/oauth/storage-file.js +68 -0
  45. package/dist/oauth/storage.d.ts +2 -0
  46. package/dist/oauth/storage.js +4 -0
  47. package/dist/oauth/types.d.ts +44 -0
  48. package/dist/oauth/types.js +1 -0
  49. package/dist/plugins/copilot/auth.d.ts +7 -0
  50. package/dist/plugins/copilot/auth.js +30 -0
  51. package/dist/plugins/copilot/index.d.ts +5 -0
  52. package/dist/plugins/copilot/index.js +4 -0
  53. package/dist/plugins/copilot/plugin.d.ts +8 -0
  54. package/dist/plugins/copilot/plugin.js +29 -0
  55. package/dist/plugins/copilot/session.d.ts +8 -0
  56. package/dist/plugins/copilot/session.js +115 -0
  57. package/dist/plugins/copilot/tools.d.ts +10 -0
  58. package/dist/plugins/copilot/tools.js +10 -0
  59. package/dist/plugins/copilot/types.d.ts +15 -0
  60. package/dist/plugins/copilot/types.js +27 -0
  61. package/dist/plugins/cursor/index.d.ts +2 -0
  62. package/dist/plugins/cursor/index.js +2 -0
  63. package/dist/plugins/cursor/plugin.d.ts +8 -0
  64. package/dist/plugins/cursor/plugin.js +36 -0
  65. package/dist/plugins/cursor/session.d.ts +11 -0
  66. package/dist/plugins/cursor/session.js +69 -0
  67. package/dist/plugins/cursor/tools.d.ts +11 -0
  68. package/dist/plugins/cursor/tools.js +13 -0
  69. package/dist/plugins/windsurf/auth.d.ts +3 -0
  70. package/dist/plugins/windsurf/auth.js +20 -0
  71. package/dist/plugins/windsurf/daemon.d.ts +6 -0
  72. package/dist/plugins/windsurf/daemon.js +16 -0
  73. package/dist/plugins/windsurf/index.d.ts +5 -0
  74. package/dist/plugins/windsurf/index.js +4 -0
  75. package/dist/plugins/windsurf/models.d.ts +2 -0
  76. package/dist/plugins/windsurf/models.js +42 -0
  77. package/dist/plugins/windsurf/plugin.d.ts +8 -0
  78. package/dist/plugins/windsurf/plugin.js +31 -0
  79. package/dist/plugins/windsurf/session.d.ts +5 -0
  80. package/dist/plugins/windsurf/session.js +6 -0
  81. package/dist/plugins/windsurf/tools.d.ts +3 -0
  82. package/dist/plugins/windsurf/tools.js +10 -0
  83. package/dist/plugins/windsurf/types.d.ts +22 -0
  84. package/dist/plugins/windsurf/types.js +1 -0
  85. package/dist/utils/config.d.ts +1 -1
  86. package/dist/utils/config.js +1 -1
  87. package/dist/utils/opencode.d.ts +3 -1
  88. package/dist/utils/opencode.js +3 -3
  89. package/dist/utils/platform.d.ts +1 -0
  90. package/dist/utils/platform.js +3 -0
  91. package/package.json +3 -5
  92. package/src/commands/configure.ts +107 -12
  93. package/src/commands/daemon.ts +112 -13
  94. package/src/commands/doctor.ts +1 -1
  95. package/src/commands/init.ts +72 -5
  96. package/src/commands/login.ts +98 -0
  97. package/src/commands/logout.ts +15 -0
  98. package/src/commands/start.ts +4 -4
  99. package/src/core/config.ts +45 -0
  100. package/src/core/daemon-session.ts +199 -0
  101. package/src/core/daemon.ts +206 -0
  102. package/src/core/formatter.ts +56 -0
  103. package/src/core/index.ts +9 -0
  104. package/src/core/parser.ts +47 -0
  105. package/src/core/registry.ts +62 -0
  106. package/src/core/server.ts +211 -0
  107. package/src/core/session.ts +54 -0
  108. package/src/core/types.ts +100 -0
  109. package/src/index.ts +22 -4
  110. package/src/oauth/device-flow.ts +111 -0
  111. package/src/oauth/flow.ts +94 -0
  112. package/src/oauth/index.ts +6 -0
  113. package/src/oauth/lifecycle.ts +77 -0
  114. package/src/oauth/providers.ts +21 -0
  115. package/src/oauth/storage-file.ts +77 -0
  116. package/src/oauth/storage.ts +6 -0
  117. package/src/oauth/types.ts +50 -0
  118. package/src/plugins/copilot/auth.ts +39 -0
  119. package/src/plugins/copilot/index.ts +5 -0
  120. package/src/plugins/copilot/plugin.ts +31 -0
  121. package/src/plugins/copilot/session.ts +130 -0
  122. package/src/plugins/copilot/tools.ts +21 -0
  123. package/src/plugins/copilot/types.ts +43 -0
  124. package/src/plugins/cursor/index.ts +2 -0
  125. package/src/plugins/cursor/plugin.ts +37 -0
  126. package/src/plugins/cursor/session.ts +78 -0
  127. package/src/plugins/cursor/tools.ts +25 -0
  128. package/src/plugins/windsurf/auth.ts +23 -0
  129. package/src/plugins/windsurf/daemon.ts +24 -0
  130. package/src/plugins/windsurf/index.ts +5 -0
  131. package/src/plugins/windsurf/models.ts +44 -0
  132. package/src/plugins/windsurf/plugin.ts +34 -0
  133. package/src/plugins/windsurf/session.ts +8 -0
  134. package/src/plugins/windsurf/tools.ts +13 -0
  135. package/src/plugins/windsurf/types.ts +24 -0
  136. package/src/utils/config.ts +1 -1
  137. package/src/utils/opencode.ts +4 -3
  138. package/src/utils/platform.ts +3 -0
  139. package/test/configure.test.ts +19 -4
  140. package/test/daemon.test.ts +224 -0
@@ -0,0 +1,179 @@
1
+ const MAX_BUFFER_SIZE = 1024 * 1024;
2
+ export class DaemonBridgeSession {
3
+ daemon;
4
+ token;
5
+ model;
6
+ cwd;
7
+ proc = null;
8
+ requestId = 0;
9
+ busy = false;
10
+ constructor(daemon, token, model, cwd) {
11
+ this.daemon = daemon;
12
+ this.token = token;
13
+ this.model = model;
14
+ this.cwd = cwd;
15
+ }
16
+ async *send(messages, tools) {
17
+ if (this.busy) {
18
+ yield {
19
+ type: 'error',
20
+ content: 'Session is busy — concurrent send() calls are not supported',
21
+ finishReason: 'error',
22
+ };
23
+ return;
24
+ }
25
+ this.busy = true;
26
+ if (!this.proc) {
27
+ const binaryPath = await this.daemon.locate();
28
+ if (!binaryPath) {
29
+ this.busy = false;
30
+ throw new Error(`Daemon binary '${this.daemon.binaryName}' not found`);
31
+ }
32
+ this.proc = this.daemon.spawn(binaryPath, []);
33
+ }
34
+ const id = ++this.requestId;
35
+ const request = {
36
+ jsonrpc: '2.0',
37
+ id,
38
+ method: 'chat/completions',
39
+ params: {
40
+ token: this.token,
41
+ model: this.model,
42
+ messages,
43
+ stream: true,
44
+ tools: tools?.map((t) => ({
45
+ type: 'function',
46
+ function: {
47
+ name: t.function.name,
48
+ description: t.function.description,
49
+ parameters: t.function.parameters,
50
+ },
51
+ })),
52
+ },
53
+ };
54
+ this.proc.stdin.write(JSON.stringify(request) + '\n');
55
+ let buffer = '';
56
+ let stderrBuffer = '';
57
+ let finished = false;
58
+ let capturedError = null;
59
+ let onDataResolve = null;
60
+ const onStderr = (data) => {
61
+ stderrBuffer += data.toString();
62
+ };
63
+ this.proc.stderr?.on('data', onStderr);
64
+ const onData = (data) => {
65
+ if (buffer.length <= MAX_BUFFER_SIZE) {
66
+ buffer += data.toString();
67
+ }
68
+ if (buffer.length > MAX_BUFFER_SIZE && !capturedError) {
69
+ capturedError = new Error(`stdout buffer exceeded max size of ${MAX_BUFFER_SIZE} bytes`);
70
+ onDataResolve?.();
71
+ }
72
+ onDataResolve?.();
73
+ };
74
+ const onError = (err) => {
75
+ if (!capturedError) {
76
+ capturedError = err;
77
+ onDataResolve?.();
78
+ }
79
+ };
80
+ const onExit = (code) => {
81
+ if (!finished && !capturedError) {
82
+ capturedError = new Error(`Process exited with code ${code ?? 'unknown'}`);
83
+ onDataResolve?.();
84
+ }
85
+ };
86
+ this.proc.stdout.on('data', onData);
87
+ this.proc.on('error', onError);
88
+ this.proc.on('exit', onExit);
89
+ try {
90
+ while (!finished) {
91
+ if (capturedError) {
92
+ const stderrInfo = stderrBuffer.trim() ? ` (stderr: ${stderrBuffer.slice(0, 500)})` : '';
93
+ yield {
94
+ type: 'error',
95
+ content: `Process error: ${capturedError.message}${stderrInfo}`,
96
+ finishReason: 'error',
97
+ };
98
+ break;
99
+ }
100
+ const newlineIndex = buffer.indexOf('\n');
101
+ if (newlineIndex === -1) {
102
+ const waitForData = new Promise((resolve) => {
103
+ onDataResolve = resolve;
104
+ });
105
+ await waitForData;
106
+ continue;
107
+ }
108
+ const line = buffer.slice(0, newlineIndex);
109
+ buffer = buffer.slice(newlineIndex + 1);
110
+ if (!line.trim())
111
+ continue;
112
+ let parsed;
113
+ try {
114
+ parsed = JSON.parse(line);
115
+ }
116
+ catch {
117
+ continue;
118
+ }
119
+ if (parsed.error) {
120
+ yield {
121
+ type: 'error',
122
+ content: `JSON-RPC error: ${parsed.error.message}`,
123
+ finishReason: 'error',
124
+ };
125
+ finished = true;
126
+ continue;
127
+ }
128
+ if (parsed.method === 'chat/chunk' && parsed.params) {
129
+ const delta = parsed.params.delta;
130
+ if (delta?.content) {
131
+ yield { type: 'text', content: delta.content };
132
+ }
133
+ if (delta?.tool_calls) {
134
+ for (const tc of delta.tool_calls) {
135
+ yield {
136
+ type: 'tool_call',
137
+ toolCall: {
138
+ id: tc.id ?? '',
139
+ name: tc.name ?? '',
140
+ arguments: tc.arguments ?? '',
141
+ },
142
+ };
143
+ }
144
+ }
145
+ }
146
+ if (parsed.method === 'chat/done' && parsed.params) {
147
+ const reason = parsed.params.finishReason;
148
+ yield {
149
+ type: 'done',
150
+ finishReason: reason === 'stop'
151
+ ? 'stop'
152
+ : reason === 'tool_calls'
153
+ ? 'tool_calls'
154
+ : reason === 'length'
155
+ ? 'length'
156
+ : 'error',
157
+ };
158
+ finished = true;
159
+ }
160
+ if (parsed.id === id && parsed.result) {
161
+ finished = true;
162
+ }
163
+ }
164
+ }
165
+ finally {
166
+ this.proc?.stdout?.removeListener('data', onData);
167
+ this.proc?.removeListener('error', onError);
168
+ this.proc?.removeListener('exit', onExit);
169
+ this.proc?.stderr?.removeListener('data', onStderr);
170
+ this.busy = false;
171
+ }
172
+ }
173
+ async dispose() {
174
+ if (this.proc) {
175
+ this.proc.kill();
176
+ this.proc = null;
177
+ }
178
+ }
179
+ }
@@ -0,0 +1,16 @@
1
+ import { type ChildProcess } from 'node:child_process';
2
+ export interface DaemonManager {
3
+ binaryName: string;
4
+ locate(): Promise<string | null>;
5
+ download(): Promise<string>;
6
+ spawn(binaryPath: string, args: string[]): ChildProcess;
7
+ healthCheck(port?: number): Promise<boolean>;
8
+ }
9
+ export declare function createDaemonManager(config: {
10
+ binaryName: string;
11
+ downloadUrl: string;
12
+ checksum: string;
13
+ knownPaths: string[];
14
+ envVar?: string;
15
+ daemonsDir?: string;
16
+ }): DaemonManager;
@@ -0,0 +1,168 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { accessSync, chmodSync, constants, existsSync, mkdirSync, unlinkSync, writeFileSync, } from 'node:fs';
3
+ import { homedir, platform, arch } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { createHash } from 'node:crypto';
6
+ import { get as httpGet, request as httpRequest } from 'node:http';
7
+ import { request as httpsRequest } from 'node:https';
8
+ import { URL } from 'node:url';
9
+ export function createDaemonManager(config) {
10
+ const defaultDaemonsDir = () => join(homedir(), '.llm-bridge', 'daemons');
11
+ const getDaemonsDir = () => config.daemonsDir ?? defaultDaemonsDir();
12
+ return {
13
+ binaryName: config.binaryName,
14
+ async locate() {
15
+ if (config.envVar && process.env[config.envVar]) {
16
+ const envPath = process.env[config.envVar];
17
+ if (existsSync(envPath)) {
18
+ try {
19
+ accessSync(envPath, constants.X_OK);
20
+ return envPath;
21
+ }
22
+ catch {
23
+ // Not executable, skip
24
+ }
25
+ }
26
+ }
27
+ for (const p of config.knownPaths) {
28
+ if (existsSync(p)) {
29
+ try {
30
+ accessSync(p, constants.X_OK);
31
+ return p;
32
+ }
33
+ catch {
34
+ // Not executable, skip
35
+ }
36
+ }
37
+ }
38
+ const daemonsDir = getDaemonsDir();
39
+ const managedPath = join(daemonsDir, config.binaryName);
40
+ if (existsSync(managedPath)) {
41
+ try {
42
+ accessSync(managedPath, constants.X_OK);
43
+ return managedPath;
44
+ }
45
+ catch {
46
+ // Not executable, skip
47
+ }
48
+ }
49
+ return null;
50
+ },
51
+ async download() {
52
+ const daemonsDir = getDaemonsDir();
53
+ mkdirSync(daemonsDir, { recursive: true });
54
+ const destPath = join(daemonsDir, config.binaryName);
55
+ const url = config.downloadUrl.replace('{platform}', platform()).replace('{arch}', arch());
56
+ const MAX_REDIRECTS = 5;
57
+ const MAX_DOWNLOAD_SIZE = 500 * 1024 * 1024;
58
+ let currentReq = null;
59
+ const followRedirect = (currentUrl, redirectCount) => new Promise((resolve, reject) => {
60
+ const parsed = new URL(currentUrl);
61
+ const requestFn = parsed.protocol === 'https:' ? httpsRequest : httpRequest;
62
+ currentReq = requestFn(currentUrl, (res) => {
63
+ if (res.statusCode === 301 ||
64
+ res.statusCode === 302 ||
65
+ res.statusCode === 307 ||
66
+ res.statusCode === 308) {
67
+ if (redirectCount >= MAX_REDIRECTS) {
68
+ reject(new Error('Too many redirects'));
69
+ return;
70
+ }
71
+ const location = res.headers.location;
72
+ if (location) {
73
+ const redirectUrl = new URL(location, currentUrl);
74
+ if (redirectUrl.protocol !== 'http:' && redirectUrl.protocol !== 'https:') {
75
+ reject(new Error(`Redirect to unsupported protocol: ${redirectUrl.protocol}`));
76
+ res.resume();
77
+ return;
78
+ }
79
+ res.resume();
80
+ followRedirect(redirectUrl.href, redirectCount + 1)
81
+ .then(resolve)
82
+ .catch(reject);
83
+ }
84
+ else {
85
+ reject(new Error('Redirect with no location'));
86
+ }
87
+ return;
88
+ }
89
+ if (res.statusCode !== 200) {
90
+ reject(new Error(`Download failed: ${res.statusCode}`));
91
+ return;
92
+ }
93
+ const chunks = [];
94
+ let totalSize = 0;
95
+ res.on('data', (chunk) => {
96
+ totalSize += chunk.length;
97
+ if (totalSize > MAX_DOWNLOAD_SIZE) {
98
+ reject(new Error(`Download exceeds maximum size (${MAX_DOWNLOAD_SIZE} bytes)`));
99
+ res.destroy();
100
+ return;
101
+ }
102
+ chunks.push(chunk);
103
+ });
104
+ res.on('end', () => {
105
+ resolve(Buffer.concat(chunks));
106
+ });
107
+ });
108
+ currentReq.on('error', reject);
109
+ currentReq.end();
110
+ });
111
+ const downloadPromise = followRedirect(url, 0);
112
+ const timeoutPromise = new Promise((_, reject) => {
113
+ setTimeout(() => {
114
+ currentReq?.destroy();
115
+ reject(new Error('Download timeout (30s)'));
116
+ }, 30000);
117
+ });
118
+ return Promise.race([downloadPromise, timeoutPromise]).then((data) => {
119
+ try {
120
+ writeFileSync(destPath, data);
121
+ const hash = createHash('sha256').update(data).digest('hex');
122
+ if (hash !== config.checksum) {
123
+ throw new Error(`Checksum mismatch: expected ${config.checksum}, got ${hash}`);
124
+ }
125
+ chmodSync(destPath, 0o755);
126
+ return destPath;
127
+ }
128
+ catch (err) {
129
+ try {
130
+ unlinkSync(destPath);
131
+ }
132
+ catch { }
133
+ throw err;
134
+ }
135
+ });
136
+ },
137
+ spawn(binaryPath, args) {
138
+ return spawn(binaryPath, args, {
139
+ stdio: ['pipe', 'pipe', 'pipe'],
140
+ });
141
+ },
142
+ async healthCheck(port) {
143
+ const path = await this.locate();
144
+ if (!path)
145
+ return false;
146
+ if (!existsSync(path))
147
+ return false;
148
+ if (port) {
149
+ try {
150
+ return new Promise((resolve) => {
151
+ const req = httpGet(`http://127.0.0.1:${port}/health`, (res) => {
152
+ resolve(res.statusCode === 200);
153
+ });
154
+ req.on('error', () => resolve(false));
155
+ req.setTimeout(5000, () => {
156
+ req.destroy();
157
+ resolve(false);
158
+ });
159
+ });
160
+ }
161
+ catch {
162
+ return false;
163
+ }
164
+ }
165
+ return true;
166
+ },
167
+ };
168
+ }
@@ -0,0 +1,3 @@
1
+ import type { StreamChunk } from './types.js';
2
+ export declare function formatStreamChunk(chunk: StreamChunk, model: string, completionId: string): string;
3
+ export declare function formatCompletion(content: string, model: string, completionId: string): Record<string, unknown>;
@@ -0,0 +1,44 @@
1
+ export function formatStreamChunk(chunk, model, completionId) {
2
+ const delta = {};
3
+ let finishReason = null;
4
+ if (chunk.type === 'text' && chunk.content) {
5
+ delta.content = chunk.content;
6
+ }
7
+ if (chunk.type === 'tool_call' && chunk.toolCall) {
8
+ delta.tool_calls = [
9
+ {
10
+ index: 0,
11
+ id: chunk.toolCall.id,
12
+ type: 'function',
13
+ function: { name: chunk.toolCall.name, arguments: chunk.toolCall.arguments },
14
+ },
15
+ ];
16
+ }
17
+ if (chunk.finishReason) {
18
+ finishReason = chunk.finishReason;
19
+ }
20
+ const payload = {
21
+ id: completionId,
22
+ object: 'chat.completion.chunk',
23
+ created: Math.floor(Date.now() / 1000),
24
+ model,
25
+ choices: [{ index: 0, delta, finish_reason: finishReason }],
26
+ };
27
+ return `data: ${JSON.stringify(payload)}\n\n`;
28
+ }
29
+ export function formatCompletion(content, model, completionId) {
30
+ return {
31
+ id: completionId,
32
+ object: 'chat.completion',
33
+ created: Math.floor(Date.now() / 1000),
34
+ model,
35
+ choices: [
36
+ {
37
+ index: 0,
38
+ message: { role: 'assistant', content },
39
+ finish_reason: 'stop',
40
+ },
41
+ ],
42
+ usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },
43
+ };
44
+ }
@@ -0,0 +1,9 @@
1
+ export * from './types.js';
2
+ export { BridgeServer } from './server.js';
3
+ export { SessionStore } from './session.js';
4
+ export { parseChatRequest, parseModelsRequest } from './parser.js';
5
+ export { formatStreamChunk, formatCompletion } from './formatter.js';
6
+ export { PluginRegistry } from './registry.js';
7
+ export { loadConfig, saveConfig, configPath } from './config.js';
8
+ export { createDaemonManager, type DaemonManager } from './daemon.js';
9
+ export { DaemonBridgeSession } from './daemon-session.js';
@@ -0,0 +1,9 @@
1
+ export * from './types.js';
2
+ export { BridgeServer } from './server.js';
3
+ export { SessionStore } from './session.js';
4
+ export { parseChatRequest, parseModelsRequest } from './parser.js';
5
+ export { formatStreamChunk, formatCompletion } from './formatter.js';
6
+ export { PluginRegistry } from './registry.js';
7
+ export { loadConfig, saveConfig, configPath } from './config.js';
8
+ export { createDaemonManager } from './daemon.js';
9
+ export { DaemonBridgeSession } from './daemon-session.js';
@@ -0,0 +1,164 @@
1
+ import http from 'node:http';
2
+ import { z } from 'zod';
3
+ declare const ChatRequestSchema: z.ZodObject<{
4
+ model: z.ZodString;
5
+ messages: z.ZodArray<z.ZodObject<{
6
+ role: z.ZodEnum<["system", "user", "assistant", "tool", "function"]>;
7
+ content: z.ZodOptional<z.ZodNullable<z.ZodString>>;
8
+ name: z.ZodOptional<z.ZodString>;
9
+ tool_call_id: z.ZodOptional<z.ZodString>;
10
+ tool_calls: z.ZodOptional<z.ZodArray<z.ZodObject<{
11
+ id: z.ZodString;
12
+ type: z.ZodLiteral<"function">;
13
+ function: z.ZodObject<{
14
+ name: z.ZodString;
15
+ arguments: z.ZodString;
16
+ }, "strip", z.ZodTypeAny, {
17
+ name: string;
18
+ arguments: string;
19
+ }, {
20
+ name: string;
21
+ arguments: string;
22
+ }>;
23
+ }, "strip", z.ZodTypeAny, {
24
+ function: {
25
+ name: string;
26
+ arguments: string;
27
+ };
28
+ type: "function";
29
+ id: string;
30
+ }, {
31
+ function: {
32
+ name: string;
33
+ arguments: string;
34
+ };
35
+ type: "function";
36
+ id: string;
37
+ }>, "many">>;
38
+ }, "strip", z.ZodTypeAny, {
39
+ role: "function" | "system" | "user" | "assistant" | "tool";
40
+ content?: string | null | undefined;
41
+ name?: string | undefined;
42
+ tool_call_id?: string | undefined;
43
+ tool_calls?: {
44
+ function: {
45
+ name: string;
46
+ arguments: string;
47
+ };
48
+ type: "function";
49
+ id: string;
50
+ }[] | undefined;
51
+ }, {
52
+ role: "function" | "system" | "user" | "assistant" | "tool";
53
+ content?: string | null | undefined;
54
+ name?: string | undefined;
55
+ tool_call_id?: string | undefined;
56
+ tool_calls?: {
57
+ function: {
58
+ name: string;
59
+ arguments: string;
60
+ };
61
+ type: "function";
62
+ id: string;
63
+ }[] | undefined;
64
+ }>, "many">;
65
+ stream: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
66
+ tools: z.ZodOptional<z.ZodArray<z.ZodObject<{
67
+ type: z.ZodLiteral<"function">;
68
+ function: z.ZodObject<{
69
+ name: z.ZodString;
70
+ description: z.ZodOptional<z.ZodString>;
71
+ parameters: z.ZodRecord<z.ZodString, z.ZodUnknown>;
72
+ }, "strip", z.ZodTypeAny, {
73
+ name: string;
74
+ parameters: Record<string, unknown>;
75
+ description?: string | undefined;
76
+ }, {
77
+ name: string;
78
+ parameters: Record<string, unknown>;
79
+ description?: string | undefined;
80
+ }>;
81
+ }, "strip", z.ZodTypeAny, {
82
+ function: {
83
+ name: string;
84
+ parameters: Record<string, unknown>;
85
+ description?: string | undefined;
86
+ };
87
+ type: "function";
88
+ }, {
89
+ function: {
90
+ name: string;
91
+ parameters: Record<string, unknown>;
92
+ description?: string | undefined;
93
+ };
94
+ type: "function";
95
+ }>, "many">>;
96
+ tool_choice: z.ZodOptional<z.ZodUnknown>;
97
+ }, "strip", z.ZodTypeAny, {
98
+ model: string;
99
+ messages: {
100
+ role: "function" | "system" | "user" | "assistant" | "tool";
101
+ content?: string | null | undefined;
102
+ name?: string | undefined;
103
+ tool_call_id?: string | undefined;
104
+ tool_calls?: {
105
+ function: {
106
+ name: string;
107
+ arguments: string;
108
+ };
109
+ type: "function";
110
+ id: string;
111
+ }[] | undefined;
112
+ }[];
113
+ stream: boolean;
114
+ tools?: {
115
+ function: {
116
+ name: string;
117
+ parameters: Record<string, unknown>;
118
+ description?: string | undefined;
119
+ };
120
+ type: "function";
121
+ }[] | undefined;
122
+ tool_choice?: unknown;
123
+ }, {
124
+ model: string;
125
+ messages: {
126
+ role: "function" | "system" | "user" | "assistant" | "tool";
127
+ content?: string | null | undefined;
128
+ name?: string | undefined;
129
+ tool_call_id?: string | undefined;
130
+ tool_calls?: {
131
+ function: {
132
+ name: string;
133
+ arguments: string;
134
+ };
135
+ type: "function";
136
+ id: string;
137
+ }[] | undefined;
138
+ }[];
139
+ tools?: {
140
+ function: {
141
+ name: string;
142
+ parameters: Record<string, unknown>;
143
+ description?: string | undefined;
144
+ };
145
+ type: "function";
146
+ }[] | undefined;
147
+ stream?: boolean | undefined;
148
+ tool_choice?: unknown;
149
+ }>;
150
+ export type ParsedChatRequest = z.infer<typeof ChatRequestSchema>;
151
+ export declare function parseChatRequest(req: http.IncomingMessage): Promise<{
152
+ success: true;
153
+ data: ParsedChatRequest;
154
+ } | {
155
+ success: false;
156
+ error: string;
157
+ }>;
158
+ export declare function parseModelsRequest(_req: http.IncomingMessage): {
159
+ success: true;
160
+ } | {
161
+ success: false;
162
+ error: string;
163
+ };
164
+ export {};
@@ -0,0 +1,37 @@
1
+ import { z } from 'zod';
2
+ import { MessageSchema, ToolDefinitionSchema } from './types.js';
3
+ const ChatRequestSchema = z.object({
4
+ model: z.string().min(1, 'model is required'),
5
+ messages: z.array(MessageSchema).min(1, 'messages must have at least one message'),
6
+ stream: z.boolean().optional().default(false),
7
+ tools: z.array(ToolDefinitionSchema).optional(),
8
+ tool_choice: z.unknown().optional(),
9
+ });
10
+ export async function parseChatRequest(req) {
11
+ try {
12
+ const body = await readBody(req);
13
+ const json = JSON.parse(body);
14
+ const result = ChatRequestSchema.safeParse(json);
15
+ if (!result.success) {
16
+ return { success: false, error: result.error.errors[0].message };
17
+ }
18
+ return { success: true, data: result.data };
19
+ }
20
+ catch (e) {
21
+ if (e instanceof SyntaxError) {
22
+ return { success: false, error: 'Invalid JSON body' };
23
+ }
24
+ return { success: false, error: e instanceof Error ? e.message : 'Unknown error' };
25
+ }
26
+ }
27
+ export function parseModelsRequest(_req) {
28
+ return { success: true };
29
+ }
30
+ function readBody(req) {
31
+ return new Promise((resolve, reject) => {
32
+ const chunks = [];
33
+ req.on('data', (c) => chunks.push(c));
34
+ req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
35
+ req.on('error', reject);
36
+ });
37
+ }
@@ -0,0 +1,16 @@
1
+ import type { BridgePlugin, PluginHealth } from './types.js';
2
+ export declare class PluginRegistry {
3
+ private plugins;
4
+ private activePluginName;
5
+ private defaultPluginName;
6
+ private health;
7
+ register(plugin: BridgePlugin): void;
8
+ getPlugin(name: string): BridgePlugin | undefined;
9
+ setActive(name: string): void;
10
+ setDefault(name: string): void;
11
+ getActivePlugin(): BridgePlugin | null;
12
+ getDefaultPlugin(): BridgePlugin | null;
13
+ listPlugins(): BridgePlugin[];
14
+ markUnhealthy(name: string, error: string): void;
15
+ getHealth(name: string): PluginHealth | undefined;
16
+ }