lcagent-cli 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/bin/cli.js +142 -40
- package/package.json +1 -1
package/README.md
CHANGED
package/dist/bin/cli.js
CHANGED
|
@@ -7,9 +7,38 @@ import { fetch } from '../core/http.js';
|
|
|
7
7
|
import { agentConfigSchema } from '../config/schema.js';
|
|
8
8
|
import { getConfigPath, loadConfig, updateConfig } from '../config/store.js';
|
|
9
9
|
import { getDefaultTools } from '../tools/registry.js';
|
|
10
|
+
import { toOpenAICompatibleTool } from '../tools/types.js';
|
|
10
11
|
function trimTrailingSlash(value) {
|
|
11
12
|
return value.replace(/\/+$/, '');
|
|
12
13
|
}
|
|
14
|
+
function formatError(error) {
|
|
15
|
+
if (!(error instanceof Error)) {
|
|
16
|
+
return String(error);
|
|
17
|
+
}
|
|
18
|
+
const details = [error.message];
|
|
19
|
+
const cause = error.cause;
|
|
20
|
+
if (cause instanceof Error) {
|
|
21
|
+
details.push(`cause=${cause.message}`);
|
|
22
|
+
const nestedCause = cause.cause;
|
|
23
|
+
if (nestedCause instanceof Error) {
|
|
24
|
+
details.push(`nested=${nestedCause.message}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return details.join(' | ');
|
|
28
|
+
}
|
|
29
|
+
async function fetchWithTimeout(url, options, timeoutMs = 5000) {
|
|
30
|
+
const controller = new AbortController();
|
|
31
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
32
|
+
try {
|
|
33
|
+
return await fetch(url, {
|
|
34
|
+
...options,
|
|
35
|
+
signal: controller.signal,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
finally {
|
|
39
|
+
clearTimeout(timer);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
13
42
|
function questionAsync(rl, prompt) {
|
|
14
43
|
return new Promise(resolve => {
|
|
15
44
|
rl.question(prompt, answer => {
|
|
@@ -19,7 +48,7 @@ function questionAsync(rl, prompt) {
|
|
|
19
48
|
}
|
|
20
49
|
async function probeUrl(url) {
|
|
21
50
|
try {
|
|
22
|
-
const response = await
|
|
51
|
+
const response = await fetchWithTimeout(url, {
|
|
23
52
|
method: 'GET',
|
|
24
53
|
headers: { accept: 'application/json,text/plain,*/*' },
|
|
25
54
|
});
|
|
@@ -32,10 +61,75 @@ async function probeUrl(url) {
|
|
|
32
61
|
catch (error) {
|
|
33
62
|
return {
|
|
34
63
|
ok: false,
|
|
35
|
-
detail:
|
|
64
|
+
detail: formatError(error),
|
|
36
65
|
};
|
|
37
66
|
}
|
|
38
67
|
}
|
|
68
|
+
async function runJsonProbe(params) {
|
|
69
|
+
console.log(`- ${params.label}: ${params.url}`);
|
|
70
|
+
try {
|
|
71
|
+
const response = await fetchWithTimeout(params.url, {
|
|
72
|
+
method: params.method ?? 'GET',
|
|
73
|
+
headers: params.headers,
|
|
74
|
+
body: params.body,
|
|
75
|
+
}, 5000);
|
|
76
|
+
const text = await response.text();
|
|
77
|
+
console.log(` HTTP ${response.status}`);
|
|
78
|
+
console.log(` preview: ${text.slice(0, 200) || '(empty body)'}`);
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
console.log(` failed: ${formatError(error)}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async function runOpenAIToolCallingProbe(params) {
|
|
85
|
+
const tool = toOpenAICompatibleTool(getDefaultTools()[0]);
|
|
86
|
+
console.log(`- POST /v1/chat/completions (tool calling): ${params.url}`);
|
|
87
|
+
try {
|
|
88
|
+
const response = await fetchWithTimeout(params.url, {
|
|
89
|
+
method: 'POST',
|
|
90
|
+
headers: {
|
|
91
|
+
'content-type': 'application/json',
|
|
92
|
+
...(params.apiKey ? { Authorization: `Bearer ${params.apiKey}` } : {}),
|
|
93
|
+
},
|
|
94
|
+
body: JSON.stringify({
|
|
95
|
+
model: params.model,
|
|
96
|
+
messages: [
|
|
97
|
+
{
|
|
98
|
+
role: 'user',
|
|
99
|
+
content: 'Call the read_file tool with path set to README.md. Do not answer with plain text only.',
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
tools: [tool],
|
|
103
|
+
tool_choice: 'auto',
|
|
104
|
+
max_tokens: 128,
|
|
105
|
+
}),
|
|
106
|
+
}, 8000);
|
|
107
|
+
const text = await response.text();
|
|
108
|
+
console.log(` HTTP ${response.status}`);
|
|
109
|
+
console.log(` preview: ${text.slice(0, 300) || '(empty body)'}`);
|
|
110
|
+
try {
|
|
111
|
+
const parsed = JSON.parse(text);
|
|
112
|
+
const toolCalls = parsed.choices?.[0]?.message?.tool_calls;
|
|
113
|
+
if (Array.isArray(toolCalls) && toolCalls.length > 0) {
|
|
114
|
+
console.log(` tool calling: supported (${toolCalls.length} tool call returned)`);
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
const finishReason = parsed.choices?.[0]?.finish_reason ?? 'unknown';
|
|
118
|
+
const content = parsed.choices?.[0]?.message?.content ?? '';
|
|
119
|
+
console.log(` tool calling: no tool_calls returned (finish_reason=${finishReason})`);
|
|
120
|
+
if (content) {
|
|
121
|
+
console.log(` assistant content: ${String(content).slice(0, 160)}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
console.log(' tool calling: unable to parse JSON response body');
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
console.log(` failed: ${formatError(error)}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
39
133
|
async function runDoctor() {
|
|
40
134
|
const config = await loadConfig();
|
|
41
135
|
const apiKey = config.apiKey ??
|
|
@@ -49,56 +143,64 @@ async function runDoctor() {
|
|
|
49
143
|
console.log(`- baseUrl: ${config.baseUrl}`);
|
|
50
144
|
console.log(`- model: ${config.model}`);
|
|
51
145
|
console.log(`- apiKey: ${apiKey ? 'configured' : 'not configured'}`);
|
|
52
|
-
const
|
|
146
|
+
const trimmedBaseUrl = trimTrailingSlash(config.baseUrl);
|
|
147
|
+
const rootProbe = await probeUrl(trimmedBaseUrl);
|
|
53
148
|
console.log(`- baseUrl probe: ${rootProbe.detail}`);
|
|
54
149
|
if (config.provider === 'openai-compatible') {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
console.log(`- chat/completions probe: HTTP ${response.status}`);
|
|
72
|
-
console.log(`- response preview: ${text.slice(0, 200) || '(empty body)'}`);
|
|
73
|
-
}
|
|
74
|
-
catch (error) {
|
|
75
|
-
console.log(`- chat/completions probe failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
76
|
-
}
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
const endpoint = `${trimTrailingSlash(config.baseUrl)}/v1/messages`;
|
|
80
|
-
console.log(`- endpoint: ${endpoint}`);
|
|
81
|
-
try {
|
|
82
|
-
const response = await fetch(endpoint, {
|
|
150
|
+
await runJsonProbe({
|
|
151
|
+
label: 'GET /v1',
|
|
152
|
+
url: trimmedBaseUrl,
|
|
153
|
+
headers: { accept: 'application/json,text/plain,*/*' },
|
|
154
|
+
});
|
|
155
|
+
await runJsonProbe({
|
|
156
|
+
label: 'GET /v1/models',
|
|
157
|
+
url: `${trimmedBaseUrl}/models`,
|
|
158
|
+
headers: {
|
|
159
|
+
accept: 'application/json,text/plain,*/*',
|
|
160
|
+
...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
await runJsonProbe({
|
|
164
|
+
label: 'POST /v1/chat/completions',
|
|
165
|
+
url: `${trimmedBaseUrl}/chat/completions`,
|
|
83
166
|
method: 'POST',
|
|
84
167
|
headers: {
|
|
85
168
|
'content-type': 'application/json',
|
|
86
|
-
...(apiKey ? {
|
|
87
|
-
'anthropic-version': '2023-06-01',
|
|
169
|
+
...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
|
|
88
170
|
},
|
|
89
171
|
body: JSON.stringify({
|
|
90
172
|
model: config.model,
|
|
91
|
-
max_tokens: 8,
|
|
92
173
|
messages: [{ role: 'user', content: 'Reply with the single word pong.' }],
|
|
174
|
+
max_tokens: 8,
|
|
93
175
|
}),
|
|
94
176
|
});
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
177
|
+
await runOpenAIToolCallingProbe({
|
|
178
|
+
url: `${trimmedBaseUrl}/chat/completions`,
|
|
179
|
+
apiKey,
|
|
180
|
+
model: config.model,
|
|
181
|
+
});
|
|
182
|
+
return;
|
|
101
183
|
}
|
|
184
|
+
await runJsonProbe({
|
|
185
|
+
label: 'GET base URL',
|
|
186
|
+
url: trimmedBaseUrl,
|
|
187
|
+
headers: { accept: 'application/json,text/plain,*/*' },
|
|
188
|
+
});
|
|
189
|
+
await runJsonProbe({
|
|
190
|
+
label: 'POST /v1/messages',
|
|
191
|
+
url: `${trimmedBaseUrl}/v1/messages`,
|
|
192
|
+
method: 'POST',
|
|
193
|
+
headers: {
|
|
194
|
+
'content-type': 'application/json',
|
|
195
|
+
...(apiKey ? { 'x-api-key': apiKey } : {}),
|
|
196
|
+
'anthropic-version': '2023-06-01',
|
|
197
|
+
},
|
|
198
|
+
body: JSON.stringify({
|
|
199
|
+
model: config.model,
|
|
200
|
+
max_tokens: 8,
|
|
201
|
+
messages: [{ role: 'user', content: 'Reply with the single word pong.' }],
|
|
202
|
+
}),
|
|
203
|
+
});
|
|
102
204
|
}
|
|
103
205
|
async function printAgentRun(prompt) {
|
|
104
206
|
const { engine } = await createApp(process.cwd());
|