@xalia/agent 1.0.19
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/.prettierrc.json +11 -0
- package/README.md +57 -0
- package/dist/agent.js +278 -0
- package/dist/agentUtils.js +88 -0
- package/dist/chat.js +278 -0
- package/dist/dummyLLM.js +28 -0
- package/dist/files.js +115 -0
- package/dist/iplatform.js +2 -0
- package/dist/llm.js +2 -0
- package/dist/main.js +136 -0
- package/dist/mcpServerManager.js +269 -0
- package/dist/nodePlatform.js +61 -0
- package/dist/openAILLM.js +31 -0
- package/dist/options.js +79 -0
- package/dist/prompt.js +83 -0
- package/dist/sudoMcpServerManager.js +174 -0
- package/dist/test/imageLoad.test.js +14 -0
- package/dist/test/mcpServerManager.test.js +71 -0
- package/dist/test/prompt.test.js +26 -0
- package/dist/test/sudoMcpServerManager.test.js +49 -0
- package/dist/tokenAuth.js +39 -0
- package/dist/tools.js +44 -0
- package/eslint.config.mjs +25 -0
- package/frog.png +0 -0
- package/package.json +41 -0
- package/scripts/git_message +31 -0
- package/scripts/git_wip +21 -0
- package/scripts/pr_message +18 -0
- package/scripts/pr_review +16 -0
- package/scripts/sudomcp_import +23 -0
- package/scripts/test_script +60 -0
- package/src/agent.ts +357 -0
- package/src/agentUtils.ts +188 -0
- package/src/chat.ts +325 -0
- package/src/dummyLLM.ts +36 -0
- package/src/files.ts +95 -0
- package/src/iplatform.ts +11 -0
- package/src/llm.ts +12 -0
- package/src/main.ts +171 -0
- package/src/mcpServerManager.ts +365 -0
- package/src/nodePlatform.ts +24 -0
- package/src/openAILLM.ts +43 -0
- package/src/options.ts +103 -0
- package/src/prompt.ts +93 -0
- package/src/sudoMcpServerManager.ts +268 -0
- package/src/test/imageLoad.test.ts +14 -0
- package/src/test/mcpServerManager.test.ts +98 -0
- package/src/test/prompt.test.src +0 -0
- package/src/test/prompt.test.ts +26 -0
- package/src/test/sudoMcpServerManager.test.ts +63 -0
- package/src/tokenAuth.ts +50 -0
- package/src/tools.ts +57 -0
- package/test_data/background_test_profile.json +7 -0
- package/test_data/background_test_script.json +11 -0
- package/test_data/dummyllm_script_simplecalc.json +28 -0
- package/test_data/git_message_profile.json +4 -0
- package/test_data/git_wip_system.txt +5 -0
- package/test_data/pr_message_profile.json +4 -0
- package/test_data/pr_review_profile.json +4 -0
- package/test_data/prompt_simplecalc.txt +1 -0
- package/test_data/simplecalc_profile.json +4 -0
- package/test_data/sudomcp_import_profile.json +4 -0
- package/test_data/test_script_profile.json +9 -0
- package/tsconfig.json +13 -0
@@ -0,0 +1,365 @@
|
|
1
|
+
import { ChatCompletionTool } from "openai/resources.mjs";
|
2
|
+
import {
|
3
|
+
SSEClientTransport,
|
4
|
+
SSEClientTransportOptions,
|
5
|
+
} from "@modelcontextprotocol/sdk/client/sse.js";
|
6
|
+
import { Client as McpClient } from "@modelcontextprotocol/sdk/client/index.js";
|
7
|
+
import { TokenAuth } from "./tokenAuth";
|
8
|
+
import { Tool } from "@modelcontextprotocol/sdk/types.js";
|
9
|
+
import { strict as assert } from "assert";
|
10
|
+
import { getLogger } from "@xalia/xmcp/sdk";
|
11
|
+
|
12
|
+
const logger = getLogger();
|
13
|
+
|
14
|
+
/// Callback into an Mcp server
|
15
|
+
export type McpCallback = { (args: string): Promise<string> };
|
16
|
+
|
17
|
+
/// Map of tool name to callback
|
18
|
+
export type McpCallbacks = { [toolName: string]: McpCallback };
|
19
|
+
|
20
|
+
/// Map of tool name to enabled flag
|
21
|
+
export type EnabledTools = { [toolName: string]: boolean };
|
22
|
+
|
23
|
+
/// This represents the full configuration of McpServers and tools. It cannot
|
24
|
+
/// be used directly by this class (since this class does not currently handle
|
25
|
+
/// creating the transports), but should be usable by a catalogue
|
26
|
+
/// (e.g. SudoMcpServerManager) to reconstruct the state.
|
27
|
+
export type McpServerManagerSettings = {
|
28
|
+
[serverName: string]: EnabledTools;
|
29
|
+
};
|
30
|
+
|
31
|
+
/**
|
32
|
+
* The (read-only) McpServerInfo to expose to external classes. Callers
|
33
|
+
* should not modify this data directly. Only through the McpServerManager
|
34
|
+
* class.
|
35
|
+
*/
|
36
|
+
export class McpServerInfo {
|
37
|
+
private readonly tools: Tool[]; // TODO: May not need both tools and toolsMap
|
38
|
+
private readonly toolsMap: { [toolName: string]: Tool };
|
39
|
+
protected enabledToolsMap: EnabledTools;
|
40
|
+
|
41
|
+
constructor(tools: Tool[]) {
|
42
|
+
const toolsMap: { [toolName: string]: Tool } = {};
|
43
|
+
|
44
|
+
for (const mcpTool of tools) {
|
45
|
+
const toolName = mcpTool.name;
|
46
|
+
toolsMap[toolName] = mcpTool;
|
47
|
+
}
|
48
|
+
|
49
|
+
this.tools = tools;
|
50
|
+
this.toolsMap = toolsMap;
|
51
|
+
this.enabledToolsMap = {};
|
52
|
+
}
|
53
|
+
|
54
|
+
public getEnabledTools(): EnabledTools {
|
55
|
+
return this.enabledToolsMap;
|
56
|
+
}
|
57
|
+
|
58
|
+
public getTools(): Tool[] {
|
59
|
+
return this.tools;
|
60
|
+
}
|
61
|
+
|
62
|
+
public getTool(toolName: string): Tool {
|
63
|
+
return this.toolsMap[toolName];
|
64
|
+
}
|
65
|
+
}
|
66
|
+
|
67
|
+
/**
|
68
|
+
* The internal class holds server info and allows it to be updated. Managed
|
69
|
+
* by McpServerManager. Do not access these methods except via the
|
70
|
+
* McpServerManager.
|
71
|
+
*/
|
72
|
+
class McpServerInfoInternal extends McpServerInfo {
|
73
|
+
private readonly client: McpClient;
|
74
|
+
private readonly callbacks: McpCallbacks;
|
75
|
+
|
76
|
+
constructor(client: McpClient, tools: Tool[]) {
|
77
|
+
super(tools);
|
78
|
+
|
79
|
+
const callbacks: McpCallbacks = {};
|
80
|
+
|
81
|
+
for (const mcpTool of tools) {
|
82
|
+
const toolName = mcpTool.name;
|
83
|
+
|
84
|
+
// Create callback
|
85
|
+
const callback = async (argStr: string): Promise<string> => {
|
86
|
+
logger.debug(
|
87
|
+
`cb for ${toolName} invoked with args (${typeof argStr}): ` +
|
88
|
+
`${JSON.stringify(argStr)}`
|
89
|
+
);
|
90
|
+
|
91
|
+
const argsObj = JSON.parse(argStr);
|
92
|
+
const toolResult = await client.callTool({
|
93
|
+
name: toolName,
|
94
|
+
arguments: argsObj,
|
95
|
+
});
|
96
|
+
logger.debug(
|
97
|
+
`cb for ${toolName} returned: ${JSON.stringify(toolResult)}`
|
98
|
+
);
|
99
|
+
|
100
|
+
assert(typeof toolResult === "object");
|
101
|
+
const content = toolResult.content as { [a: number]: unknown };
|
102
|
+
assert(typeof content === "object");
|
103
|
+
assert(content);
|
104
|
+
const content0 = content[0] as { text: string };
|
105
|
+
assert(typeof content0 === "object");
|
106
|
+
const content0Text = content0.text;
|
107
|
+
assert(typeof content0Text === "string");
|
108
|
+
return content0Text;
|
109
|
+
};
|
110
|
+
|
111
|
+
callbacks[toolName] = callback;
|
112
|
+
}
|
113
|
+
|
114
|
+
this.client = client;
|
115
|
+
this.callbacks = callbacks;
|
116
|
+
}
|
117
|
+
|
118
|
+
public async shutdown(): Promise<void> {
|
119
|
+
await this.client.close();
|
120
|
+
}
|
121
|
+
|
122
|
+
public enableTool(toolName: string) {
|
123
|
+
this.enabledToolsMap[toolName] = true;
|
124
|
+
}
|
125
|
+
|
126
|
+
public disableTool(toolName: string) {
|
127
|
+
delete this.enabledToolsMap[toolName];
|
128
|
+
}
|
129
|
+
|
130
|
+
public getCallback(toolName: string) {
|
131
|
+
return this.callbacks[toolName];
|
132
|
+
}
|
133
|
+
}
|
134
|
+
|
135
|
+
/**
|
136
|
+
* Manage a set of MCP servers, where the tools for each server have an
|
137
|
+
* 'enabled' flag. Tools are disabled by default. The set of enabled tools
|
138
|
+
* over all servers is exposed as a single list of OpenAI functions.
|
139
|
+
*/
|
140
|
+
export class McpServerManager {
|
141
|
+
private mcpServers: { [serverName: string]: McpServerInfoInternal } = {};
|
142
|
+
private enabledToolsDirty: boolean = true;
|
143
|
+
private enabledOpenAITools: ChatCompletionTool[] = [];
|
144
|
+
|
145
|
+
public async shutdown() {
|
146
|
+
await Promise.all(
|
147
|
+
Object.keys(this.mcpServers).map((name) => {
|
148
|
+
logger.debug(`shutting down: ${name}...`);
|
149
|
+
this.mcpServers[name].shutdown();
|
150
|
+
})
|
151
|
+
);
|
152
|
+
|
153
|
+
this.mcpServers = {};
|
154
|
+
}
|
155
|
+
|
156
|
+
public getMcpServerNames(): string[] {
|
157
|
+
return Object.keys(this.mcpServers);
|
158
|
+
}
|
159
|
+
|
160
|
+
public getMcpServer(mcpServerName: string): McpServerInfo {
|
161
|
+
return this.getMcpServerInternal(mcpServerName);
|
162
|
+
}
|
163
|
+
|
164
|
+
public async addMcpServer(
|
165
|
+
mcpServerName: string,
|
166
|
+
url: string,
|
167
|
+
apiKey?: string,
|
168
|
+
tools?: Tool[]
|
169
|
+
): Promise<void> {
|
170
|
+
logger.debug(`Adding mcp server ${mcpServerName}: ${url}`);
|
171
|
+
const sseTransportOptions: SSEClientTransportOptions = {};
|
172
|
+
if (apiKey) {
|
173
|
+
sseTransportOptions.authProvider = new TokenAuth(apiKey);
|
174
|
+
}
|
175
|
+
const urlO = new URL(url);
|
176
|
+
const transport = new SSEClientTransport(urlO, sseTransportOptions);
|
177
|
+
const client = new McpClient({
|
178
|
+
name: "@xalia/agent",
|
179
|
+
version: "1.0.0",
|
180
|
+
});
|
181
|
+
|
182
|
+
try {
|
183
|
+
await client.connect(transport);
|
184
|
+
} catch (e) {
|
185
|
+
// TODO: is this catch necessary?
|
186
|
+
await client.close();
|
187
|
+
throw e;
|
188
|
+
}
|
189
|
+
await this.addMcpServerWithClient(client, mcpServerName, tools);
|
190
|
+
}
|
191
|
+
|
192
|
+
/**
|
193
|
+
* Add MCP server from an already connected McpClient.
|
194
|
+
*/
|
195
|
+
public async addMcpServerWithClient(
|
196
|
+
client: McpClient,
|
197
|
+
mcpServerName: string,
|
198
|
+
tools?: Tool[]
|
199
|
+
): Promise<void> {
|
200
|
+
try {
|
201
|
+
// TODO; require the tools to be passed in.
|
202
|
+
|
203
|
+
if (!tools) {
|
204
|
+
const mcpTools = await client.listTools();
|
205
|
+
tools = mcpTools.tools;
|
206
|
+
}
|
207
|
+
|
208
|
+
this.mcpServers[mcpServerName] = new McpServerInfoInternal(client, tools);
|
209
|
+
} catch (e) {
|
210
|
+
await client.close();
|
211
|
+
throw e;
|
212
|
+
}
|
213
|
+
}
|
214
|
+
|
215
|
+
public async removeMcpServer(mcpServerName: string) {
|
216
|
+
const server = this.getMcpServerInternal(mcpServerName);
|
217
|
+
delete this.mcpServers[mcpServerName];
|
218
|
+
await server.shutdown();
|
219
|
+
this.enabledToolsDirty = true;
|
220
|
+
}
|
221
|
+
|
222
|
+
public enableAllTools(mcpServerName: string) {
|
223
|
+
logger.debug(`enableAllTools: ${mcpServerName}`);
|
224
|
+
const server = this.getMcpServerInternal(mcpServerName);
|
225
|
+
for (const tool of server.getTools()) {
|
226
|
+
logger.debug(`enable: ${tool.name}`);
|
227
|
+
server.enableTool(tool.name);
|
228
|
+
}
|
229
|
+
this.enabledToolsDirty = true;
|
230
|
+
}
|
231
|
+
|
232
|
+
public disableAllTools(mcpServerName: string) {
|
233
|
+
logger.debug(`disableAllTools: ${mcpServerName}`);
|
234
|
+
const server = this.getMcpServerInternal(mcpServerName);
|
235
|
+
for (const tool of server.getTools()) {
|
236
|
+
logger.debug(`disable: ${tool.name}`);
|
237
|
+
server.disableTool(tool.name);
|
238
|
+
}
|
239
|
+
this.enabledToolsDirty = true;
|
240
|
+
}
|
241
|
+
|
242
|
+
public enableTool(mcpServerName: string, toolName: string) {
|
243
|
+
logger.debug(`enableTool: ${mcpServerName} ${toolName}`);
|
244
|
+
const server = this.getMcpServerInternal(mcpServerName);
|
245
|
+
server.enableTool(toolName);
|
246
|
+
this.enabledToolsDirty = true;
|
247
|
+
}
|
248
|
+
|
249
|
+
public disableTool(mcpServerName: string, toolName: string) {
|
250
|
+
const server = this.getMcpServerInternal(mcpServerName);
|
251
|
+
server.disableTool(toolName);
|
252
|
+
this.enabledToolsDirty = true;
|
253
|
+
}
|
254
|
+
|
255
|
+
public getOpenAITools(): ChatCompletionTool[] {
|
256
|
+
if (this.enabledToolsDirty) {
|
257
|
+
this.enabledOpenAITools = computeOpenAIToolList(this.mcpServers);
|
258
|
+
this.enabledToolsDirty = false;
|
259
|
+
}
|
260
|
+
|
261
|
+
return this.enabledOpenAITools;
|
262
|
+
}
|
263
|
+
|
264
|
+
/**
|
265
|
+
* Note the `qualifiedToolName` is the full `{mcpServerName}/{toolName}` as
|
266
|
+
* in the openai spec.
|
267
|
+
*/
|
268
|
+
public async invoke(
|
269
|
+
qualifiedToolName: string,
|
270
|
+
args: unknown
|
271
|
+
): Promise<string> {
|
272
|
+
const [mcpServerName, toolName] = splitQualifiedName(qualifiedToolName);
|
273
|
+
logger.debug(`invoke: qualified: ${qualifiedToolName}`);
|
274
|
+
logger.debug(
|
275
|
+
`invoke: mcpServerName: ${mcpServerName}, toolName: ${toolName}`
|
276
|
+
);
|
277
|
+
logger.debug(`invoke: args: ${JSON.stringify(args)}`);
|
278
|
+
|
279
|
+
const server = this.getMcpServerInternal(mcpServerName);
|
280
|
+
const cb = server.getCallback(toolName);
|
281
|
+
if (!cb) {
|
282
|
+
throw `Unknown tool ${qualifiedToolName}`;
|
283
|
+
}
|
284
|
+
|
285
|
+
return cb(JSON.stringify(args));
|
286
|
+
}
|
287
|
+
|
288
|
+
/**
|
289
|
+
* "Settings" refers to the set of added servers and enabled tools.
|
290
|
+
*/
|
291
|
+
public getMcpServerSettings(): McpServerManagerSettings {
|
292
|
+
const config: McpServerManagerSettings = {};
|
293
|
+
for (const [serverName, server] of Object.entries(this.mcpServers)) {
|
294
|
+
config[serverName] = structuredClone(server.getEnabledTools());
|
295
|
+
}
|
296
|
+
|
297
|
+
return config;
|
298
|
+
}
|
299
|
+
|
300
|
+
private getMcpServerInternal(mcpServerName: string): McpServerInfoInternal {
|
301
|
+
const server = this.mcpServers[mcpServerName];
|
302
|
+
if (server) {
|
303
|
+
return server;
|
304
|
+
}
|
305
|
+
throw Error(`unknown server ${mcpServerName}`);
|
306
|
+
}
|
307
|
+
}
|
308
|
+
|
309
|
+
export function computeQualifiedName(
|
310
|
+
mcpServerName: string,
|
311
|
+
toolName: string
|
312
|
+
): string {
|
313
|
+
return `${mcpServerName}__${toolName}`;
|
314
|
+
}
|
315
|
+
|
316
|
+
export function splitQualifiedName(
|
317
|
+
qualifiedToolName: string
|
318
|
+
): [string, string] {
|
319
|
+
const delimIdx = qualifiedToolName.indexOf("__");
|
320
|
+
if (delimIdx < 0) {
|
321
|
+
throw Error(`invalid qualified name: ${qualifiedToolName}`);
|
322
|
+
}
|
323
|
+
|
324
|
+
return [
|
325
|
+
qualifiedToolName.slice(0, delimIdx),
|
326
|
+
qualifiedToolName.slice(delimIdx + 2),
|
327
|
+
];
|
328
|
+
}
|
329
|
+
|
330
|
+
export function computeOpenAIToolList(mcpServers: {
|
331
|
+
[mcpServer: string]: McpServerInfoInternal;
|
332
|
+
}): ChatCompletionTool[] {
|
333
|
+
const openaiTools: ChatCompletionTool[] = [];
|
334
|
+
|
335
|
+
for (const mcpServerName in mcpServers) {
|
336
|
+
const mcpServer = mcpServers[mcpServerName];
|
337
|
+
const tools = mcpServer.getTools();
|
338
|
+
const enabled = mcpServer.getEnabledTools();
|
339
|
+
|
340
|
+
for (const mcpTool of tools) {
|
341
|
+
const toolName = mcpTool.name;
|
342
|
+
if (enabled[toolName]) {
|
343
|
+
const qualifiedName = computeQualifiedName(mcpServerName, toolName);
|
344
|
+
const openaiTool = mcpToolToOpenAITool(mcpTool, qualifiedName);
|
345
|
+
openaiTools.push(openaiTool);
|
346
|
+
}
|
347
|
+
}
|
348
|
+
}
|
349
|
+
|
350
|
+
return openaiTools;
|
351
|
+
}
|
352
|
+
|
353
|
+
export function mcpToolToOpenAITool(
|
354
|
+
tool: Tool,
|
355
|
+
qualifiedName?: string
|
356
|
+
): ChatCompletionTool {
|
357
|
+
return {
|
358
|
+
type: "function",
|
359
|
+
function: {
|
360
|
+
name: qualifiedName || tool.name,
|
361
|
+
description: tool.description,
|
362
|
+
parameters: tool.inputSchema,
|
363
|
+
},
|
364
|
+
};
|
365
|
+
}
|
@@ -0,0 +1,24 @@
|
|
1
|
+
import * as fs from "fs";
|
2
|
+
import { execSync } from "child_process";
|
3
|
+
import { IPlatform } from "./iplatform";
|
4
|
+
|
5
|
+
/**
|
6
|
+
* Implementation of the IPlatform interface for node.js
|
7
|
+
*/
|
8
|
+
export const NODE_PLATFORM: IPlatform = {
|
9
|
+
openUrl: (url: string) => {
|
10
|
+
const platform = process.platform;
|
11
|
+
if (platform === "darwin") {
|
12
|
+
execSync(`open '${url}'`);
|
13
|
+
} else if (platform === "linux") {
|
14
|
+
execSync(`xdg-open '${url}'`);
|
15
|
+
} else if (platform === "win32") {
|
16
|
+
execSync(`start '${url}'`);
|
17
|
+
} else {
|
18
|
+
throw `Unknown platform ${platform}`;
|
19
|
+
}
|
20
|
+
},
|
21
|
+
load: async (filename: string): Promise<string> => {
|
22
|
+
return fs.readFileSync(filename, { encoding: "utf8" });
|
23
|
+
},
|
24
|
+
};
|
package/src/openAILLM.ts
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
import { ILLM } from "./llm";
|
2
|
+
import { OpenAI } from "openai";
|
3
|
+
|
4
|
+
export class OpenAILLM implements ILLM {
|
5
|
+
private readonly openai: OpenAI;
|
6
|
+
private model: string;
|
7
|
+
|
8
|
+
constructor(
|
9
|
+
apiKey: string,
|
10
|
+
apiUrl: string | undefined,
|
11
|
+
model: string | undefined
|
12
|
+
) {
|
13
|
+
this.openai = new OpenAI({
|
14
|
+
apiKey,
|
15
|
+
baseURL: apiUrl,
|
16
|
+
dangerouslyAllowBrowser: true,
|
17
|
+
});
|
18
|
+
this.model = model || "gpt-4o-mini";
|
19
|
+
}
|
20
|
+
|
21
|
+
public setModel(model: string) {
|
22
|
+
this.model = model;
|
23
|
+
}
|
24
|
+
|
25
|
+
getModel(): string {
|
26
|
+
return this.model;
|
27
|
+
}
|
28
|
+
|
29
|
+
getUrl(): string {
|
30
|
+
return this.openai.baseURL;
|
31
|
+
}
|
32
|
+
|
33
|
+
public async getConversationResponse(
|
34
|
+
messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[],
|
35
|
+
tools?: OpenAI.Chat.Completions.ChatCompletionTool[]
|
36
|
+
): Promise<OpenAI.Chat.Completions.ChatCompletion> {
|
37
|
+
return this.openai.chat.completions.create({
|
38
|
+
model: this.model,
|
39
|
+
messages,
|
40
|
+
tools,
|
41
|
+
});
|
42
|
+
}
|
43
|
+
}
|
package/src/options.ts
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
import { boolean, option, optional, string, flag, number } from "cmd-ts";
|
2
|
+
import { ArgParser } from "cmd-ts/dist/cjs/argparser";
|
3
|
+
import { Descriptive, ProvidesHelp } from "cmd-ts/dist/cjs/helpdoc";
|
4
|
+
|
5
|
+
export type Option = ReturnType<typeof option>;
|
6
|
+
export type OptionalOption = ArgParser<string | undefined> &
|
7
|
+
ProvidesHelp &
|
8
|
+
Partial<Descriptive>;
|
9
|
+
|
10
|
+
/// Prevents env content from being displayed in the help text.
|
11
|
+
export function secretOption({
|
12
|
+
long,
|
13
|
+
short,
|
14
|
+
env,
|
15
|
+
description,
|
16
|
+
}: {
|
17
|
+
long: string;
|
18
|
+
short?: string;
|
19
|
+
env?: string;
|
20
|
+
description: string;
|
21
|
+
}): OptionalOption {
|
22
|
+
if (env) {
|
23
|
+
return option({
|
24
|
+
type: optional(string),
|
25
|
+
long,
|
26
|
+
short,
|
27
|
+
description: `${description} [env: ${env}]`,
|
28
|
+
defaultValue: () => process.env[env],
|
29
|
+
defaultValueIsSerializable: false, // hides the value from --help
|
30
|
+
});
|
31
|
+
}
|
32
|
+
|
33
|
+
return option({
|
34
|
+
type: optional(string),
|
35
|
+
long,
|
36
|
+
short,
|
37
|
+
description: `${description} (can also be set via ${env} env var)`,
|
38
|
+
});
|
39
|
+
}
|
40
|
+
|
41
|
+
export const promptFile = option({
|
42
|
+
type: optional(string),
|
43
|
+
long: "prompt",
|
44
|
+
short: "p",
|
45
|
+
description: "File containing user's first prompt to LLM",
|
46
|
+
});
|
47
|
+
|
48
|
+
export const imageFile = option({
|
49
|
+
type: optional(string),
|
50
|
+
long: "image",
|
51
|
+
short: "i",
|
52
|
+
description: "File containing image input",
|
53
|
+
});
|
54
|
+
|
55
|
+
export const systemPromptFile = option({
|
56
|
+
type: optional(string),
|
57
|
+
long: "sysprompt",
|
58
|
+
short: "s",
|
59
|
+
description: "File containing system prompt",
|
60
|
+
});
|
61
|
+
|
62
|
+
export const llmModel = option({
|
63
|
+
type: optional(string),
|
64
|
+
long: "model",
|
65
|
+
short: "m",
|
66
|
+
description: "LLM model",
|
67
|
+
env: "LLM_MODEL",
|
68
|
+
});
|
69
|
+
|
70
|
+
export const oneShot = flag({
|
71
|
+
type: boolean,
|
72
|
+
long: "one-shot",
|
73
|
+
short: "1",
|
74
|
+
description: "Exit after first reply (implies --approve-tools)",
|
75
|
+
});
|
76
|
+
|
77
|
+
export const approveTools = flag({
|
78
|
+
type: boolean,
|
79
|
+
long: "approve-tools",
|
80
|
+
short: "y",
|
81
|
+
description: "Automatically approve all tool calls",
|
82
|
+
});
|
83
|
+
|
84
|
+
export const approveToolsUpTo = option({
|
85
|
+
type: optional(number),
|
86
|
+
long: "approve-tools-up-to",
|
87
|
+
description: "Automatically approve all tool calls up to some number",
|
88
|
+
});
|
89
|
+
|
90
|
+
export const openaiApiKey = secretOption({
|
91
|
+
long: "openai-api-key",
|
92
|
+
short: "o",
|
93
|
+
description: "OpenAI (or compatible protocol) API Key",
|
94
|
+
env: "OPENAI_API_KEY",
|
95
|
+
});
|
96
|
+
|
97
|
+
export const openaiApiUrl = option({
|
98
|
+
type: optional(string),
|
99
|
+
long: "openai-api-url",
|
100
|
+
short: "u",
|
101
|
+
description: "OpenAI (or compatible protocol) RPC endpoint url",
|
102
|
+
env: "OPENAI_API_URL",
|
103
|
+
});
|
package/src/prompt.ts
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
import readline from "readline";
|
2
|
+
|
3
|
+
const DEFAULT_PROMPT: string = "USER: ";
|
4
|
+
|
5
|
+
export class Prompt {
|
6
|
+
private line: string | undefined;
|
7
|
+
private online: { (line: string | undefined): void } | undefined;
|
8
|
+
// private onerror: { (reason?: any): void } | undefined;
|
9
|
+
private prompt: readline.Interface;
|
10
|
+
|
11
|
+
constructor() {
|
12
|
+
this.prompt = readline.createInterface({
|
13
|
+
input: process.stdin,
|
14
|
+
output: process.stdout,
|
15
|
+
prompt: DEFAULT_PROMPT,
|
16
|
+
});
|
17
|
+
|
18
|
+
this.prompt.on("line", (line) => {
|
19
|
+
this.line = line;
|
20
|
+
this.resolve();
|
21
|
+
});
|
22
|
+
this.prompt.on("close", () => {
|
23
|
+
this.line = undefined;
|
24
|
+
this.resolve();
|
25
|
+
});
|
26
|
+
}
|
27
|
+
|
28
|
+
async run(prompt?: string): Promise<string | undefined> {
|
29
|
+
// Clear any line
|
30
|
+
this.line = "";
|
31
|
+
|
32
|
+
return new Promise<string | undefined>((r) => {
|
33
|
+
this.online = r;
|
34
|
+
if (prompt) {
|
35
|
+
this.prompt.setPrompt(prompt);
|
36
|
+
}
|
37
|
+
this.prompt.prompt();
|
38
|
+
if (prompt) {
|
39
|
+
this.prompt.setPrompt(DEFAULT_PROMPT);
|
40
|
+
}
|
41
|
+
});
|
42
|
+
}
|
43
|
+
|
44
|
+
shutdown(): void {
|
45
|
+
this.prompt.close();
|
46
|
+
}
|
47
|
+
|
48
|
+
private resolve(): void {
|
49
|
+
if (this.online) {
|
50
|
+
this.online(this.line);
|
51
|
+
}
|
52
|
+
this.online = undefined;
|
53
|
+
}
|
54
|
+
}
|
55
|
+
|
56
|
+
/**
|
57
|
+
* Support prompts:
|
58
|
+
* - some text (msg: some text, cmds: undefined)
|
59
|
+
* - :i image.png some text (msg: some text, cmds: ["i", "image.png"])
|
60
|
+
* - :i image.png (msg: undefined, cmds: ["i", "image.png"])
|
61
|
+
* - :l (msg: undefined, cmds: ["l"])
|
62
|
+
* - :e toolName .. (msg: undefined, cmds: ["e", "toolName", ...])
|
63
|
+
* - :ea toolName .. (msg: undefined, cmds: ["ea", "toolName", ...])
|
64
|
+
*/
|
65
|
+
export function parsePrompt(prompt: string): {
|
66
|
+
msg?: string;
|
67
|
+
cmds?: string[];
|
68
|
+
} {
|
69
|
+
prompt = prompt.trim();
|
70
|
+
|
71
|
+
let msg: string | undefined = undefined;
|
72
|
+
let cmds: string[] | undefined = undefined;
|
73
|
+
|
74
|
+
if (prompt.startsWith(":") || prompt.startsWith("/")) {
|
75
|
+
cmds = prompt.split(" ");
|
76
|
+
cmds[0] = cmds[0].slice(1);
|
77
|
+
|
78
|
+
if (cmds[0] == "i") {
|
79
|
+
// :i is special as it may have a trailing message
|
80
|
+
const fileDelim = prompt.indexOf(" ", 3);
|
81
|
+
if (fileDelim < 0) {
|
82
|
+
cmds = [cmds[0], prompt.slice(3)];
|
83
|
+
} else {
|
84
|
+
msg = prompt.slice(fileDelim + 1);
|
85
|
+
cmds = [cmds[0], prompt.slice(3, fileDelim)];
|
86
|
+
}
|
87
|
+
}
|
88
|
+
} else {
|
89
|
+
msg = prompt;
|
90
|
+
}
|
91
|
+
|
92
|
+
return { msg, cmds };
|
93
|
+
}
|