lcagent-cli 0.1.2 → 0.1.4

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 CHANGED
@@ -164,3 +164,5 @@ npm run start -- doctor
164
164
  ```bash
165
165
  npm run start -- doctor
166
166
  ```
167
+
168
+ `doctor` 会额外探测多个端点(如 `/v1`、`/v1/models`、`/v1/chat/completions`),并尽量打印底层网络错误,方便区分 DNS、TCP 超时、HTTP 返回和认证问题。
package/dist/bin/cli.js CHANGED
@@ -10,6 +10,34 @@ import { getDefaultTools } from '../tools/registry.js';
10
10
  function trimTrailingSlash(value) {
11
11
  return value.replace(/\/+$/, '');
12
12
  }
13
+ function formatError(error) {
14
+ if (!(error instanceof Error)) {
15
+ return String(error);
16
+ }
17
+ const details = [error.message];
18
+ const cause = error.cause;
19
+ if (cause instanceof Error) {
20
+ details.push(`cause=${cause.message}`);
21
+ const nestedCause = cause.cause;
22
+ if (nestedCause instanceof Error) {
23
+ details.push(`nested=${nestedCause.message}`);
24
+ }
25
+ }
26
+ return details.join(' | ');
27
+ }
28
+ async function fetchWithTimeout(url, options, timeoutMs = 5000) {
29
+ const controller = new AbortController();
30
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
31
+ try {
32
+ return await fetch(url, {
33
+ ...options,
34
+ signal: controller.signal,
35
+ });
36
+ }
37
+ finally {
38
+ clearTimeout(timer);
39
+ }
40
+ }
13
41
  function questionAsync(rl, prompt) {
14
42
  return new Promise(resolve => {
15
43
  rl.question(prompt, answer => {
@@ -19,7 +47,7 @@ function questionAsync(rl, prompt) {
19
47
  }
20
48
  async function probeUrl(url) {
21
49
  try {
22
- const response = await fetch(url, {
50
+ const response = await fetchWithTimeout(url, {
23
51
  method: 'GET',
24
52
  headers: { accept: 'application/json,text/plain,*/*' },
25
53
  });
@@ -32,10 +60,26 @@ async function probeUrl(url) {
32
60
  catch (error) {
33
61
  return {
34
62
  ok: false,
35
- detail: error instanceof Error ? error.message : String(error),
63
+ detail: formatError(error),
36
64
  };
37
65
  }
38
66
  }
67
+ async function runJsonProbe(params) {
68
+ console.log(`- ${params.label}: ${params.url}`);
69
+ try {
70
+ const response = await fetchWithTimeout(params.url, {
71
+ method: params.method ?? 'GET',
72
+ headers: params.headers,
73
+ body: params.body,
74
+ }, 5000);
75
+ const text = await response.text();
76
+ console.log(` HTTP ${response.status}`);
77
+ console.log(` preview: ${text.slice(0, 200) || '(empty body)'}`);
78
+ }
79
+ catch (error) {
80
+ console.log(` failed: ${formatError(error)}`);
81
+ }
82
+ }
39
83
  async function runDoctor() {
40
84
  const config = await loadConfig();
41
85
  const apiKey = config.apiKey ??
@@ -49,56 +93,59 @@ async function runDoctor() {
49
93
  console.log(`- baseUrl: ${config.baseUrl}`);
50
94
  console.log(`- model: ${config.model}`);
51
95
  console.log(`- apiKey: ${apiKey ? 'configured' : 'not configured'}`);
52
- const rootProbe = await probeUrl(trimTrailingSlash(config.baseUrl));
96
+ const trimmedBaseUrl = trimTrailingSlash(config.baseUrl);
97
+ const rootProbe = await probeUrl(trimmedBaseUrl);
53
98
  console.log(`- baseUrl probe: ${rootProbe.detail}`);
54
99
  if (config.provider === 'openai-compatible') {
55
- const endpoint = `${trimTrailingSlash(config.baseUrl)}/chat/completions`;
56
- console.log(`- endpoint: ${endpoint}`);
57
- try {
58
- const response = await fetch(endpoint, {
59
- method: 'POST',
60
- headers: {
61
- 'content-type': 'application/json',
62
- ...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
63
- },
64
- body: JSON.stringify({
65
- model: config.model,
66
- messages: [{ role: 'user', content: 'Reply with the single word pong.' }],
67
- max_tokens: 8,
68
- }),
69
- });
70
- const text = await response.text();
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, {
100
+ await runJsonProbe({
101
+ label: 'GET /v1',
102
+ url: trimmedBaseUrl,
103
+ headers: { accept: 'application/json,text/plain,*/*' },
104
+ });
105
+ await runJsonProbe({
106
+ label: 'GET /v1/models',
107
+ url: `${trimmedBaseUrl}/models`,
108
+ headers: {
109
+ accept: 'application/json,text/plain,*/*',
110
+ ...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
111
+ },
112
+ });
113
+ await runJsonProbe({
114
+ label: 'POST /v1/chat/completions',
115
+ url: `${trimmedBaseUrl}/chat/completions`,
83
116
  method: 'POST',
84
117
  headers: {
85
118
  'content-type': 'application/json',
86
- ...(apiKey ? { 'x-api-key': apiKey } : {}),
87
- 'anthropic-version': '2023-06-01',
119
+ ...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
88
120
  },
89
121
  body: JSON.stringify({
90
122
  model: config.model,
91
- max_tokens: 8,
92
123
  messages: [{ role: 'user', content: 'Reply with the single word pong.' }],
124
+ max_tokens: 8,
93
125
  }),
94
126
  });
95
- const text = await response.text();
96
- console.log(`- v1/messages probe: HTTP ${response.status}`);
97
- console.log(`- response preview: ${text.slice(0, 200) || '(empty body)'}`);
98
- }
99
- catch (error) {
100
- console.log(`- v1/messages probe failed: ${error instanceof Error ? error.message : String(error)}`);
127
+ return;
101
128
  }
129
+ await runJsonProbe({
130
+ label: 'GET base URL',
131
+ url: trimmedBaseUrl,
132
+ headers: { accept: 'application/json,text/plain,*/*' },
133
+ });
134
+ await runJsonProbe({
135
+ label: 'POST /v1/messages',
136
+ url: `${trimmedBaseUrl}/v1/messages`,
137
+ method: 'POST',
138
+ headers: {
139
+ 'content-type': 'application/json',
140
+ ...(apiKey ? { 'x-api-key': apiKey } : {}),
141
+ 'anthropic-version': '2023-06-01',
142
+ },
143
+ body: JSON.stringify({
144
+ model: config.model,
145
+ max_tokens: 8,
146
+ messages: [{ role: 'user', content: 'Reply with the single word pong.' }],
147
+ }),
148
+ });
102
149
  }
103
150
  async function printAgentRun(prompt) {
104
151
  const { engine } = await createApp(process.cwd());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lcagent-cli",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "A minimal coding agent CLI for terminal-based coding workflows.",
5
5
  "type": "module",
6
6
  "publishConfig": {