@heysalad/cheri-cli 0.4.0 → 0.5.0
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/package.json +1 -1
- package/src/commands/agent.js +114 -148
- package/src/lib/branding.js +1 -0
- package/src/lib/config-store.js +4 -0
- package/src/lib/providers/anthropic.js +2 -2
- package/src/lib/providers/base.js +1 -1
- package/src/lib/providers/bedrock.js +7 -0
- package/src/lib/providers/gemini.js +2 -2
- package/src/lib/providers/index.js +7 -1
- package/src/lib/providers/openai.js +2 -2
- package/src/lib/deepseek-client.js +0 -64
package/package.json
CHANGED
package/src/commands/agent.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { apiClient } from "../lib/api-client.js";
|
|
2
2
|
import { getConfigValue, setConfigValue } from "../lib/config-store.js";
|
|
3
|
-
import {
|
|
3
|
+
import { createProvider } from "../lib/providers/index.js";
|
|
4
4
|
import { log } from "../lib/logger.js";
|
|
5
5
|
import chalk from "chalk";
|
|
6
6
|
|
|
@@ -8,129 +8,96 @@ const SYSTEM_PROMPT = `You are Cheri Agent, an AI assistant for the Cheri cloud
|
|
|
8
8
|
|
|
9
9
|
const TOOLS = [
|
|
10
10
|
{
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
description: "Get the current user's account information",
|
|
15
|
-
parameters: { type: "object", properties: {}, required: [] },
|
|
16
|
-
},
|
|
11
|
+
name: "get_account_info",
|
|
12
|
+
description: "Get the current user's account information",
|
|
13
|
+
parameters: { type: "object", properties: {}, required: [] },
|
|
17
14
|
},
|
|
18
15
|
{
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
description: "List all cloud workspaces for the current user",
|
|
23
|
-
parameters: { type: "object", properties: {}, required: [] },
|
|
24
|
-
},
|
|
16
|
+
name: "list_workspaces",
|
|
17
|
+
description: "List all cloud workspaces for the current user",
|
|
18
|
+
parameters: { type: "object", properties: {}, required: [] },
|
|
25
19
|
},
|
|
26
20
|
{
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
type: "
|
|
33
|
-
properties: {
|
|
34
|
-
repo: { type: "string", description: "GitHub repo in owner/name format" },
|
|
35
|
-
},
|
|
36
|
-
required: ["repo"],
|
|
21
|
+
name: "create_workspace",
|
|
22
|
+
description: "Launch a new cloud workspace for a GitHub repository",
|
|
23
|
+
parameters: {
|
|
24
|
+
type: "object",
|
|
25
|
+
properties: {
|
|
26
|
+
repo: { type: "string", description: "GitHub repo in owner/name format" },
|
|
37
27
|
},
|
|
28
|
+
required: ["repo"],
|
|
38
29
|
},
|
|
39
30
|
},
|
|
40
31
|
{
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
type: "
|
|
47
|
-
properties: {
|
|
48
|
-
id: { type: "string", description: "Workspace ID to stop" },
|
|
49
|
-
},
|
|
50
|
-
required: ["id"],
|
|
32
|
+
name: "stop_workspace",
|
|
33
|
+
description: "Stop and delete a running workspace",
|
|
34
|
+
parameters: {
|
|
35
|
+
type: "object",
|
|
36
|
+
properties: {
|
|
37
|
+
id: { type: "string", description: "Workspace ID to stop" },
|
|
51
38
|
},
|
|
39
|
+
required: ["id"],
|
|
52
40
|
},
|
|
53
41
|
},
|
|
54
42
|
{
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
type: "
|
|
61
|
-
properties: {
|
|
62
|
-
id: { type: "string", description: "Workspace ID" },
|
|
63
|
-
},
|
|
64
|
-
required: ["id"],
|
|
43
|
+
name: "get_workspace_status",
|
|
44
|
+
description: "Get the status of a specific workspace",
|
|
45
|
+
parameters: {
|
|
46
|
+
type: "object",
|
|
47
|
+
properties: {
|
|
48
|
+
id: { type: "string", description: "Workspace ID" },
|
|
65
49
|
},
|
|
50
|
+
required: ["id"],
|
|
66
51
|
},
|
|
67
52
|
},
|
|
68
53
|
{
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
description: "Retrieve all stored memory entries",
|
|
73
|
-
parameters: { type: "object", properties: {}, required: [] },
|
|
74
|
-
},
|
|
54
|
+
name: "get_memory",
|
|
55
|
+
description: "Retrieve all stored memory entries",
|
|
56
|
+
parameters: { type: "object", properties: {}, required: [] },
|
|
75
57
|
},
|
|
76
58
|
{
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
type: "
|
|
83
|
-
|
|
84
|
-
content: { type: "string", description: "Memory content to store" },
|
|
85
|
-
category: { type: "string", description: "Optional category (defaults to 'general')" },
|
|
86
|
-
},
|
|
87
|
-
required: ["content"],
|
|
59
|
+
name: "add_memory",
|
|
60
|
+
description: "Add a new memory entry for the user",
|
|
61
|
+
parameters: {
|
|
62
|
+
type: "object",
|
|
63
|
+
properties: {
|
|
64
|
+
content: { type: "string", description: "Memory content to store" },
|
|
65
|
+
category: { type: "string", description: "Optional category (defaults to 'general')" },
|
|
88
66
|
},
|
|
67
|
+
required: ["content"],
|
|
89
68
|
},
|
|
90
69
|
},
|
|
91
70
|
{
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
description: "Clear all stored memory entries",
|
|
96
|
-
parameters: { type: "object", properties: {}, required: [] },
|
|
97
|
-
},
|
|
71
|
+
name: "clear_memory",
|
|
72
|
+
description: "Clear all stored memory entries",
|
|
73
|
+
parameters: { type: "object", properties: {}, required: [] },
|
|
98
74
|
},
|
|
99
75
|
{
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
description: "Get the user's API usage and rate limit statistics",
|
|
104
|
-
parameters: { type: "object", properties: {}, required: [] },
|
|
105
|
-
},
|
|
76
|
+
name: "get_usage",
|
|
77
|
+
description: "Get the user's API usage and rate limit statistics",
|
|
78
|
+
parameters: { type: "object", properties: {}, required: [] },
|
|
106
79
|
},
|
|
107
80
|
{
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
type: "
|
|
114
|
-
properties: {
|
|
115
|
-
key: { type: "string", description: "Config key, e.g. 'ai.provider'" },
|
|
116
|
-
},
|
|
117
|
-
required: ["key"],
|
|
81
|
+
name: "get_config",
|
|
82
|
+
description: "Get a configuration value by key (dot notation supported)",
|
|
83
|
+
parameters: {
|
|
84
|
+
type: "object",
|
|
85
|
+
properties: {
|
|
86
|
+
key: { type: "string", description: "Config key, e.g. 'ai.provider'" },
|
|
118
87
|
},
|
|
88
|
+
required: ["key"],
|
|
119
89
|
},
|
|
120
90
|
},
|
|
121
91
|
{
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
type: "
|
|
128
|
-
|
|
129
|
-
key: { type: "string", description: "Config key" },
|
|
130
|
-
value: { type: "string", description: "Value to set" },
|
|
131
|
-
},
|
|
132
|
-
required: ["key", "value"],
|
|
92
|
+
name: "set_config",
|
|
93
|
+
description: "Set a configuration value",
|
|
94
|
+
parameters: {
|
|
95
|
+
type: "object",
|
|
96
|
+
properties: {
|
|
97
|
+
key: { type: "string", description: "Config key" },
|
|
98
|
+
value: { type: "string", description: "Value to set" },
|
|
133
99
|
},
|
|
100
|
+
required: ["key", "value"],
|
|
134
101
|
},
|
|
135
102
|
},
|
|
136
103
|
];
|
|
@@ -170,87 +137,86 @@ async function executeTool(name, args) {
|
|
|
170
137
|
}
|
|
171
138
|
|
|
172
139
|
export async function runAgent(userRequest) {
|
|
140
|
+
const providerName = getConfigValue("agent.provider") || getConfigValue("ai.provider") || "anthropic";
|
|
141
|
+
const model = getConfigValue("agent.model") || undefined;
|
|
142
|
+
|
|
143
|
+
const provider = await createProvider({ provider: providerName, model });
|
|
144
|
+
|
|
173
145
|
const messages = [
|
|
174
|
-
{ role: "system", content: SYSTEM_PROMPT },
|
|
175
146
|
{ role: "user", content: userRequest },
|
|
176
147
|
];
|
|
177
148
|
|
|
178
149
|
const MAX_ITERATIONS = 10;
|
|
179
150
|
|
|
180
151
|
for (let i = 0; i < MAX_ITERATIONS; i++) {
|
|
181
|
-
let
|
|
182
|
-
const toolCalls =
|
|
183
|
-
|
|
184
|
-
for await (const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
152
|
+
let fullText = "";
|
|
153
|
+
const toolCalls = [];
|
|
154
|
+
|
|
155
|
+
for await (const event of provider.chat(messages, TOOLS, { systemPrompt: SYSTEM_PROMPT })) {
|
|
156
|
+
switch (event.type) {
|
|
157
|
+
case "text":
|
|
158
|
+
process.stdout.write(event.content);
|
|
159
|
+
fullText += event.content;
|
|
160
|
+
break;
|
|
161
|
+
|
|
162
|
+
case "tool_use_start":
|
|
163
|
+
toolCalls.push({ id: event.id, name: event.name, input: {} });
|
|
164
|
+
break;
|
|
165
|
+
|
|
166
|
+
case "tool_input_delta":
|
|
167
|
+
// accumulated by provider, final input comes in tool_use_end
|
|
168
|
+
break;
|
|
169
|
+
|
|
170
|
+
case "tool_use_end":
|
|
171
|
+
if (toolCalls.length > 0) {
|
|
172
|
+
toolCalls[toolCalls.length - 1].input = event.input;
|
|
200
173
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
174
|
+
break;
|
|
175
|
+
|
|
176
|
+
case "done":
|
|
177
|
+
break;
|
|
205
178
|
}
|
|
206
179
|
}
|
|
207
180
|
|
|
208
|
-
const toolCallList = Object.values(toolCalls);
|
|
209
|
-
|
|
210
181
|
// No tool calls — final text response, done
|
|
211
|
-
if (
|
|
212
|
-
if (
|
|
182
|
+
if (toolCalls.length === 0) {
|
|
183
|
+
if (fullText) process.stdout.write("\n");
|
|
213
184
|
return;
|
|
214
185
|
}
|
|
215
186
|
|
|
216
|
-
|
|
217
|
-
const assistantMsg = { role: "assistant", content: textContent || null, tool_calls: [] };
|
|
218
|
-
for (const tc of toolCallList) {
|
|
219
|
-
assistantMsg.tool_calls.push({
|
|
220
|
-
id: tc.id,
|
|
221
|
-
type: "function",
|
|
222
|
-
function: { name: tc.function.name, arguments: tc.function.arguments },
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
messages.push(assistantMsg);
|
|
187
|
+
if (fullText) process.stdout.write("\n");
|
|
226
188
|
|
|
227
|
-
//
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
189
|
+
// Build assistant message in Anthropic content-block format
|
|
190
|
+
const assistantContent = [];
|
|
191
|
+
if (fullText) {
|
|
192
|
+
assistantContent.push({ type: "text", text: fullText });
|
|
193
|
+
}
|
|
194
|
+
for (const tc of toolCalls) {
|
|
195
|
+
assistantContent.push({ type: "tool_use", id: tc.id, name: tc.name, input: tc.input });
|
|
196
|
+
}
|
|
197
|
+
messages.push({ role: "assistant", content: assistantContent });
|
|
236
198
|
|
|
237
|
-
|
|
199
|
+
// Execute each tool call and build tool results
|
|
200
|
+
const toolResults = [];
|
|
201
|
+
for (const tc of toolCalls) {
|
|
202
|
+
log.info(`Calling ${chalk.cyan(tc.name)}${Object.keys(tc.input).length ? chalk.dim(" " + JSON.stringify(tc.input)) : ""}`);
|
|
238
203
|
|
|
239
|
-
const result = await executeTool(
|
|
240
|
-
const resultStr = JSON.stringify(result);
|
|
204
|
+
const result = await executeTool(tc.name, tc.input);
|
|
241
205
|
|
|
242
206
|
if (result.error) {
|
|
243
207
|
log.error(result.error);
|
|
244
208
|
} else {
|
|
245
|
-
log.success(
|
|
209
|
+
log.success(tc.name);
|
|
246
210
|
}
|
|
247
211
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
content:
|
|
212
|
+
toolResults.push({
|
|
213
|
+
type: "tool_result",
|
|
214
|
+
tool_use_id: tc.id,
|
|
215
|
+
content: JSON.stringify(result),
|
|
252
216
|
});
|
|
253
217
|
}
|
|
218
|
+
|
|
219
|
+
messages.push({ role: "user", content: toolResults });
|
|
254
220
|
}
|
|
255
221
|
|
|
256
222
|
log.warn("Agent reached maximum iterations (10). Stopping.");
|
package/src/lib/branding.js
CHANGED
package/src/lib/config-store.js
CHANGED
|
@@ -5,7 +5,7 @@ export class AnthropicProvider extends BaseProvider {
|
|
|
5
5
|
super(apiKey, model);
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
async *chat(messages, tools) {
|
|
8
|
+
async *chat(messages, tools, options = {}) {
|
|
9
9
|
const { default: Anthropic } = await import("@anthropic-ai/sdk");
|
|
10
10
|
const client = new Anthropic({ apiKey: this.apiKey });
|
|
11
11
|
|
|
@@ -18,7 +18,7 @@ export class AnthropicProvider extends BaseProvider {
|
|
|
18
18
|
const stream = await client.messages.stream({
|
|
19
19
|
model: this.model,
|
|
20
20
|
max_tokens: 8192,
|
|
21
|
-
system: SYSTEM_PROMPT,
|
|
21
|
+
system: options.systemPrompt || SYSTEM_PROMPT,
|
|
22
22
|
messages,
|
|
23
23
|
tools: anthropicTools.length > 0 ? anthropicTools : undefined,
|
|
24
24
|
});
|
|
@@ -24,7 +24,7 @@ export class BaseProvider {
|
|
|
24
24
|
* { type: "tool_use_end", id: string, name: string, input: object }
|
|
25
25
|
* { type: "done", stopReason: string }
|
|
26
26
|
*/
|
|
27
|
-
async *chat(messages, tools) {
|
|
27
|
+
async *chat(messages, tools, options = {}) {
|
|
28
28
|
throw new Error("chat() must be implemented by subclass");
|
|
29
29
|
}
|
|
30
30
|
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { OpenAIProvider } from "./openai.js";
|
|
2
|
+
|
|
3
|
+
export class BedrockProvider extends OpenAIProvider {
|
|
4
|
+
constructor(apiKey, model = "anthropic.claude-opus-4-6-v1:0", region = "us-east-1") {
|
|
5
|
+
super(apiKey, model, `https://bedrock-runtime.${region}.amazonaws.com/openai/v1`);
|
|
6
|
+
}
|
|
7
|
+
}
|
|
@@ -5,7 +5,7 @@ export class GeminiProvider extends BaseProvider {
|
|
|
5
5
|
super(apiKey, model);
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
async *chat(messages, tools) {
|
|
8
|
+
async *chat(messages, tools, options = {}) {
|
|
9
9
|
const { GoogleGenerativeAI } = await import("@google/generative-ai");
|
|
10
10
|
const genAI = new GoogleGenerativeAI(this.apiKey);
|
|
11
11
|
|
|
@@ -19,7 +19,7 @@ export class GeminiProvider extends BaseProvider {
|
|
|
19
19
|
|
|
20
20
|
const genModel = genAI.getGenerativeModel({
|
|
21
21
|
model: this.model,
|
|
22
|
-
systemInstruction: SYSTEM_PROMPT,
|
|
22
|
+
systemInstruction: options.systemPrompt || SYSTEM_PROMPT,
|
|
23
23
|
tools: geminiTools,
|
|
24
24
|
});
|
|
25
25
|
|
|
@@ -11,6 +11,7 @@ export async function createProvider(options = {}) {
|
|
|
11
11
|
openai: "OPENAI_API_KEY",
|
|
12
12
|
deepseek: "DEEPSEEK_API_KEY",
|
|
13
13
|
gemini: "GEMINI_API_KEY",
|
|
14
|
+
bedrock: "AWS_BEARER_TOKEN_BEDROCK",
|
|
14
15
|
};
|
|
15
16
|
|
|
16
17
|
const apiKey = process.env[envKeys[provider]] || getConfigValue(`ai.keys.${provider}`);
|
|
@@ -41,7 +42,12 @@ export async function createProvider(options = {}) {
|
|
|
41
42
|
const { GeminiProvider } = await import("./gemini.js");
|
|
42
43
|
return new GeminiProvider(apiKey, model);
|
|
43
44
|
}
|
|
45
|
+
case "bedrock": {
|
|
46
|
+
const { BedrockProvider } = await import("./bedrock.js");
|
|
47
|
+
const region = getConfigValue("bedrock.region") || "us-east-1";
|
|
48
|
+
return new BedrockProvider(apiKey, model, region);
|
|
49
|
+
}
|
|
44
50
|
default:
|
|
45
|
-
throw new Error(`Unknown provider: ${provider}. Supported: anthropic, openai, deepseek, gemini`);
|
|
51
|
+
throw new Error(`Unknown provider: ${provider}. Supported: anthropic, openai, deepseek, gemini, bedrock`);
|
|
46
52
|
}
|
|
47
53
|
}
|
|
@@ -6,14 +6,14 @@ export class OpenAIProvider extends BaseProvider {
|
|
|
6
6
|
this.baseURL = baseURL;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
async *chat(messages, tools) {
|
|
9
|
+
async *chat(messages, tools, options = {}) {
|
|
10
10
|
const { default: OpenAI } = await import("openai");
|
|
11
11
|
const clientOpts = { apiKey: this.apiKey };
|
|
12
12
|
if (this.baseURL) clientOpts.baseURL = this.baseURL;
|
|
13
13
|
const client = new OpenAI(clientOpts);
|
|
14
14
|
|
|
15
15
|
// Convert from Anthropic message format to OpenAI format
|
|
16
|
-
const openaiMessages = [{ role: "system", content: SYSTEM_PROMPT }];
|
|
16
|
+
const openaiMessages = [{ role: "system", content: options.systemPrompt || SYSTEM_PROMPT }];
|
|
17
17
|
for (const msg of messages) {
|
|
18
18
|
if (typeof msg.content === "string") {
|
|
19
19
|
openaiMessages.push({ role: msg.role, content: msg.content });
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { getConfigValue } from "./config-store.js";
|
|
2
|
-
|
|
3
|
-
export function getApiKey() {
|
|
4
|
-
const key = getConfigValue("deepseekApiKey") || getConfigValue("ai.keys.deepseek");
|
|
5
|
-
if (!key) {
|
|
6
|
-
throw new Error(
|
|
7
|
-
"DeepSeek API key not set. Run: cheri config set deepseekApiKey sk-..."
|
|
8
|
-
);
|
|
9
|
-
}
|
|
10
|
-
return key;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function getModel() {
|
|
14
|
-
return getConfigValue("agent.model") || "deepseek-chat";
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export async function* streamChatCompletion(messages, tools) {
|
|
18
|
-
const apiKey = getApiKey();
|
|
19
|
-
const model = getModel();
|
|
20
|
-
|
|
21
|
-
const body = {
|
|
22
|
-
model,
|
|
23
|
-
messages,
|
|
24
|
-
stream: true,
|
|
25
|
-
};
|
|
26
|
-
if (tools && tools.length > 0) {
|
|
27
|
-
body.tools = tools;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const res = await fetch("https://api.deepseek.com/chat/completions", {
|
|
31
|
-
method: "POST",
|
|
32
|
-
headers: {
|
|
33
|
-
"Content-Type": "application/json",
|
|
34
|
-
Authorization: `Bearer ${apiKey}`,
|
|
35
|
-
},
|
|
36
|
-
body: JSON.stringify(body),
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
if (!res.ok) {
|
|
40
|
-
const text = await res.text();
|
|
41
|
-
throw new Error(`DeepSeek API error (${res.status}): ${text}`);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const decoder = new TextDecoder();
|
|
45
|
-
let buffer = "";
|
|
46
|
-
|
|
47
|
-
for await (const chunk of res.body) {
|
|
48
|
-
buffer += decoder.decode(chunk, { stream: true });
|
|
49
|
-
const lines = buffer.split("\n");
|
|
50
|
-
buffer = lines.pop();
|
|
51
|
-
|
|
52
|
-
for (const line of lines) {
|
|
53
|
-
const trimmed = line.trim();
|
|
54
|
-
if (!trimmed || !trimmed.startsWith("data: ")) continue;
|
|
55
|
-
const data = trimmed.slice(6);
|
|
56
|
-
if (data === "[DONE]") return;
|
|
57
|
-
try {
|
|
58
|
-
yield JSON.parse(data);
|
|
59
|
-
} catch {
|
|
60
|
-
// skip malformed chunks
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|