@acip/cli 1.6.0 → 1.6.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@acip/cli",
3
- "version": "1.6.0",
3
+ "version": "1.6.1",
4
4
  "description": "Command Line Interface for ACIP",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -24,6 +24,17 @@
24
24
  "access": "public",
25
25
  "registry": "https://registry.npmjs.org/"
26
26
  },
27
+ "scripts": {
28
+ "build": "tsc",
29
+ "dev": "tsc -w",
30
+ "clean": "rimraf dist",
31
+ "test": "NODE_OPTIONS='--experimental-vm-modules' jest",
32
+ "test:watch": "NODE_OPTIONS='--experimental-vm-modules' jest --watch",
33
+ "test:coverage": "NODE_OPTIONS='--experimental-vm-modules' jest --coverage",
34
+ "lint": "eslint src --ext .ts",
35
+ "lint:fix": "eslint src --ext .ts --fix",
36
+ "prepublishOnly": "npm run build"
37
+ },
27
38
  "keywords": [
28
39
  "acip",
29
40
  "cli",
@@ -73,15 +84,5 @@
73
84
  },
74
85
  "engines": {
75
86
  "node": ">=18.0.0"
76
- },
77
- "scripts": {
78
- "build": "tsc",
79
- "dev": "tsc -w",
80
- "clean": "rimraf dist",
81
- "test": "NODE_OPTIONS='--experimental-vm-modules' jest",
82
- "test:watch": "NODE_OPTIONS='--experimental-vm-modules' jest --watch",
83
- "test:coverage": "NODE_OPTIONS='--experimental-vm-modules' jest --coverage",
84
- "lint": "eslint src --ext .ts",
85
- "lint:fix": "eslint src --ext .ts --fix"
86
87
  }
87
- }
88
+ }
@@ -7,6 +7,9 @@ OPENAI_BASE_URL=https://api.openai.com/v1
7
7
  # 默认 AI 模型 ID
8
8
  DEFAULT_MODEL_ID=gpt-5.2
9
9
 
10
+ # 请求超时(毫秒),适用于流式响应
11
+ ACIP_REQUEST_TIMEOUT_MS=180000
12
+
10
13
  # 应用配置
11
14
  APP_PORT=3000
12
15
  NODE_ENV=development
@@ -20,6 +20,119 @@ export function createAssistant(modules) {
20
20
 
21
21
  const history = [];
22
22
  let requestCount = 0;
23
+ const requestTimeoutMs = Number(process.env.ACIP_REQUEST_TIMEOUT_MS || '180000');
24
+
25
+ const resolveModelsUrl = (rawBaseUrl) => {
26
+ const fallback = 'https://api.openai.com/v1';
27
+ const trimmed = (rawBaseUrl || fallback).replace(/\/$/, '');
28
+ if (trimmed.endsWith('/models')) {
29
+ return trimmed;
30
+ }
31
+ if (trimmed.endsWith('/v1')) {
32
+ return `${trimmed}/models`;
33
+ }
34
+ return `${trimmed}/v1/models`;
35
+ };
36
+
37
+ const listModels = async () => {
38
+ const apiKey = process.env.OPENAI_API_KEY || process.env.ACIP_API_KEY;
39
+ const baseUrl = process.env.OPENAI_BASE_URL || process.env.ACIP_BASE_URL;
40
+
41
+ if (!apiKey) {
42
+ console.log('Missing API key. Set OPENAI_API_KEY (or ACIP_API_KEY) in .env file.');
43
+ console.log('');
44
+ return;
45
+ }
46
+
47
+ const modelsUrl = resolveModelsUrl(baseUrl);
48
+ try {
49
+ const response = await fetch(modelsUrl, {
50
+ method: 'GET',
51
+ headers: {
52
+ 'Authorization': `Bearer ${apiKey}`
53
+ }
54
+ });
55
+
56
+ if (!response.ok) {
57
+ let errorMessage = `HTTP ${response.status}`;
58
+ try {
59
+ const error = await response.json();
60
+ errorMessage = error?.error?.message || error?.message || errorMessage;
61
+ } catch {
62
+ const text = await response.text();
63
+ if (text) {
64
+ errorMessage = text;
65
+ }
66
+ }
67
+ console.log(`Models list error: ${errorMessage}`);
68
+ console.log('');
69
+ return;
70
+ }
71
+
72
+ const data = await response.json();
73
+ const models = Array.isArray(data?.data)
74
+ ? data.data
75
+ : Array.isArray(data?.models)
76
+ ? data.models
77
+ : [];
78
+
79
+ if (models.length === 0) {
80
+ console.log('No models returned.');
81
+ console.log('');
82
+ return;
83
+ }
84
+
85
+ console.log('');
86
+ console.log(`--- Models (${models.length}) ---`);
87
+ for (const model of models) {
88
+ const id = model?.id || model?.name || model?.model || JSON.stringify(model);
89
+ console.log(` - ${id}`);
90
+ }
91
+ console.log('---');
92
+ console.log('');
93
+ } catch (error) {
94
+ console.log(`Models list error: ${error.message}`);
95
+ console.log('');
96
+ }
97
+ };
98
+
99
+ const streamReply = (messages) => new Promise((resolve, reject) => {
100
+ const stream = modelInvocation.invokeStream({
101
+ model: modelId,
102
+ provider,
103
+ timeout: requestTimeoutMs,
104
+ messages
105
+ });
106
+
107
+ let full = '';
108
+ process.stdout.write('\nAssistant> ');
109
+
110
+ stream.on('data', (chunk) => {
111
+
112
+ const content = chunk?.output?.content ?? chunk?.content;
113
+
114
+ if (typeof content === 'string') {
115
+
116
+ full += content;
117
+
118
+ process.stdout.write(content);
119
+
120
+ }
121
+
122
+ });
123
+
124
+ stream.on('end', () => {
125
+ process.stdout.write('\n\n');
126
+ resolve(full);
127
+ });
128
+
129
+ stream.on('error', (err) => {
130
+ const message = err?.error || err?.message || 'Unknown error';
131
+ process.stdout.write('\n');
132
+ reject(new Error(message));
133
+ });
134
+ });
135
+
23
136
 
24
137
  async function sendMessage(content) {
25
138
  const messages = [
@@ -28,14 +141,10 @@ export function createAssistant(modules) {
28
141
  { role: 'user', content }
29
142
  ];
30
143
 
31
- const response = await modelInvocation.invoke({
32
- model: modelId,
33
- provider,
34
- messages
35
- });
144
+ const responseContent = await streamReply(messages);
36
145
 
37
146
  history.push({ role: 'user', content });
38
- history.push({ role: 'assistant', content: response.content });
147
+ history.push({ role: 'assistant', content: responseContent });
39
148
  requestCount++;
40
149
 
41
150
  // 自动保存到上下文管理
@@ -43,7 +152,7 @@ export function createAssistant(modules) {
43
152
  try {
44
153
  await contextManager.addToContext(contextId, {
45
154
  type: 'text',
46
- text: `User: ${content}\nAssistant: ${response.content}`,
155
+ text: `User: ${content}\nAssistant: ${responseContent}`,
47
156
  timestamp: Date.now()
48
157
  });
49
158
  } catch { /* 静默降级 */ }
@@ -58,7 +167,7 @@ export function createAssistant(modules) {
58
167
  } catch { /* 静默降级 */ }
59
168
  }
60
169
 
61
- return response.content;
170
+ return responseContent;
62
171
  }
63
172
 
64
173
  async function handleCommand(cmd) {
@@ -72,6 +181,7 @@ export function createAssistant(modules) {
72
181
  console.log(' /clear - Clear conversation history');
73
182
  console.log(' /history - Show conversation history');
74
183
  console.log(' /status - Show module status');
184
+ console.log(' /models - List available models');
75
185
  console.log(' /audit - Show blockchain audit trail');
76
186
  console.log(' /data list - List registered data sources');
77
187
  console.log(' /exit - Exit the application\n');
@@ -120,6 +230,10 @@ export function createAssistant(modules) {
120
230
  return true;
121
231
  }
122
232
 
233
+ case '/models':
234
+ await listModels();
235
+ return true;
236
+
123
237
  case '/audit':
124
238
  if (!auditService) {
125
239
  console.log('Blockchain audit is not available.\n');
@@ -195,8 +309,7 @@ export function createAssistant(modules) {
195
309
  }
196
310
 
197
311
  try {
198
- const reply = await sendMessage(trimmed);
199
- console.log(`\nAssistant> ${reply}\n`);
312
+ await sendMessage(trimmed);
200
313
  } catch (error) {
201
314
  console.error(`\nError: ${error.message}\n`);
202
315
  }
@@ -35,6 +35,119 @@ export function createAssistant(modules: AssistantModules) {
35
35
 
36
36
  const history: Message[] = [];
37
37
  let requestCount = 0;
38
+ const requestTimeoutMs = Number(process.env.ACIP_REQUEST_TIMEOUT_MS || '180000');
39
+
40
+ const resolveModelsUrl = (rawBaseUrl?: string): string => {
41
+ const fallback = 'https://api.openai.com/v1';
42
+ const trimmed = (rawBaseUrl || fallback).replace(/\/$/, '');
43
+ if (trimmed.endsWith('/models')) {
44
+ return trimmed;
45
+ }
46
+ if (trimmed.endsWith('/v1')) {
47
+ return `${trimmed}/models`;
48
+ }
49
+ return `${trimmed}/v1/models`;
50
+ };
51
+
52
+ const listModels = async (): Promise<void> => {
53
+ const apiKey = process.env.OPENAI_API_KEY || process.env.ACIP_API_KEY;
54
+ const baseUrl = process.env.OPENAI_BASE_URL || process.env.ACIP_BASE_URL;
55
+
56
+ if (!apiKey) {
57
+ console.log('Missing API key. Set OPENAI_API_KEY (or ACIP_API_KEY) in .env file.');
58
+ console.log('');
59
+ return;
60
+ }
61
+
62
+ const modelsUrl = resolveModelsUrl(baseUrl);
63
+ try {
64
+ const response = await fetch(modelsUrl, {
65
+ method: 'GET',
66
+ headers: {
67
+ 'Authorization': `Bearer ${apiKey}`
68
+ }
69
+ });
70
+
71
+ if (!response.ok) {
72
+ let errorMessage = `HTTP ${response.status}`;
73
+ try {
74
+ const error: any = await response.json();
75
+ errorMessage = error?.error?.message || error?.message || errorMessage;
76
+ } catch {
77
+ const text = await response.text();
78
+ if (text) {
79
+ errorMessage = text;
80
+ }
81
+ }
82
+ console.log(`Models list error: ${errorMessage}`);
83
+ console.log('');
84
+ return;
85
+ }
86
+
87
+ const data: any = await response.json();
88
+ const models = Array.isArray(data?.data)
89
+ ? data.data
90
+ : Array.isArray(data?.models)
91
+ ? data.models
92
+ : [];
93
+
94
+ if (models.length === 0) {
95
+ console.log('No models returned.');
96
+ console.log('');
97
+ return;
98
+ }
99
+
100
+ console.log('');
101
+ console.log(`--- Models (${models.length}) ---`);
102
+ for (const model of models) {
103
+ const id = model?.id || model?.name || model?.model || JSON.stringify(model);
104
+ console.log(` - ${id}`);
105
+ }
106
+ console.log('---');
107
+ console.log('');
108
+ } catch (error: any) {
109
+ console.log(`Models list error: ${error.message}`);
110
+ console.log('');
111
+ }
112
+ };
113
+
114
+ const streamReply = (messages: Message[]): Promise<string> => new Promise((resolve, reject) => {
115
+ const stream = modelInvocation.invokeStream({
116
+ model: modelId,
117
+ provider,
118
+ timeout: requestTimeoutMs,
119
+ messages
120
+ });
121
+
122
+ let full = '';
123
+ process.stdout.write('\nAssistant> ');
124
+
125
+ stream.on('data', (chunk: any) => {
126
+
127
+ const content = chunk?.output?.content ?? chunk?.content;
128
+
129
+ if (typeof content === 'string') {
130
+
131
+ full += content;
132
+
133
+ process.stdout.write(content);
134
+
135
+ }
136
+
137
+ });
138
+
139
+ stream.on('end', () => {
140
+ process.stdout.write('\n\n');
141
+ resolve(full);
142
+ });
143
+
144
+ stream.on('error', (err: any) => {
145
+ const message = err?.error || err?.message || 'Unknown error';
146
+ process.stdout.write('\n');
147
+ reject(new Error(message));
148
+ });
149
+ });
150
+
38
151
 
39
152
  async function sendMessage(content: string): Promise<string> {
40
153
  const messages: Message[] = [
@@ -43,14 +156,10 @@ export function createAssistant(modules: AssistantModules) {
43
156
  { role: 'user', content }
44
157
  ];
45
158
 
46
- const response = await modelInvocation.invoke({
47
- model: modelId,
48
- provider,
49
- messages
50
- });
159
+ const responseContent = await streamReply(messages);
51
160
 
52
161
  history.push({ role: 'user', content });
53
- history.push({ role: 'assistant', content: response.content });
162
+ history.push({ role: 'assistant', content: responseContent });
54
163
  requestCount++;
55
164
 
56
165
  // 自动保存到上下文管理
@@ -58,7 +167,7 @@ export function createAssistant(modules: AssistantModules) {
58
167
  try {
59
168
  await contextManager.addToContext(contextId, {
60
169
  type: 'text',
61
- text: `User: ${content}\nAssistant: ${response.content}`,
170
+ text: `User: ${content}\nAssistant: ${responseContent}`,
62
171
  timestamp: Date.now()
63
172
  });
64
173
  } catch { /* 静默降级 */ }
@@ -73,7 +182,7 @@ export function createAssistant(modules: AssistantModules) {
73
182
  } catch { /* 静默降级 */ }
74
183
  }
75
184
 
76
- return response.content;
185
+ return responseContent;
77
186
  }
78
187
 
79
188
  async function handleCommand(cmd: string): Promise<boolean> {
@@ -87,6 +196,7 @@ export function createAssistant(modules: AssistantModules) {
87
196
  console.log(' /clear - Clear conversation history');
88
197
  console.log(' /history - Show conversation history');
89
198
  console.log(' /status - Show module status');
199
+ console.log(' /models - List available models');
90
200
  console.log(' /audit - Show blockchain audit trail');
91
201
  console.log(' /data list - List registered data sources');
92
202
  console.log(' /exit - Exit the application\n');
@@ -135,6 +245,10 @@ export function createAssistant(modules: AssistantModules) {
135
245
  return true;
136
246
  }
137
247
 
248
+ case '/models':
249
+ await listModels();
250
+ return true;
251
+
138
252
  case '/audit':
139
253
  if (!auditService) {
140
254
  console.log('Blockchain audit is not available.\n');
@@ -210,8 +324,7 @@ export function createAssistant(modules: AssistantModules) {
210
324
  }
211
325
 
212
326
  try {
213
- const reply = await sendMessage(trimmed);
214
- console.log(`\nAssistant> ${reply}\n`);
327
+ await sendMessage(trimmed);
215
328
  } catch (error: any) {
216
329
  console.error(`\nError: ${error.message}\n`);
217
330
  }
@@ -7,6 +7,7 @@ async function main() {
7
7
  const apiKey = process.env.OPENAI_API_KEY || process.env.ACIP_API_KEY;
8
8
  const baseUrl = process.env.OPENAI_BASE_URL || process.env.ACIP_BASE_URL;
9
9
  const modelId = process.env.DEFAULT_MODEL_ID || 'gpt-5.2';
10
+ const requestTimeoutMs = Number(process.env.ACIP_REQUEST_TIMEOUT_MS || '180000');
10
11
 
11
12
  if (!apiKey) {
12
13
  console.error('Missing API key. Set OPENAI_API_KEY (or ACIP_API_KEY) in .env file.');
@@ -36,7 +37,7 @@ async function main() {
36
37
  providers: [provider],
37
38
  caching: { enabled: true, ttl: 600, maxSize: 100, strategy: 'lru' },
38
39
  observability: { enabled: true, metricsEnabled: true, tracingEnabled: false, exportInterval: 60000 },
39
- streaming: { enabled: false, chunkSize: 1, timeout: 60000 }
40
+ streaming: { enabled: true, chunkSize: 1, timeout: requestTimeoutMs }
40
41
  };
41
42
 
42
43
  const modelInvocation = new ModelInvocationModule(miConfig);
@@ -9,6 +9,7 @@ async function main(): Promise<void> {
9
9
  const apiKey = process.env.OPENAI_API_KEY || process.env.ACIP_API_KEY;
10
10
  const baseUrl = process.env.OPENAI_BASE_URL || process.env.ACIP_BASE_URL;
11
11
  const modelId = process.env.DEFAULT_MODEL_ID || 'gpt-5.2';
12
+ const requestTimeoutMs = Number(process.env.ACIP_REQUEST_TIMEOUT_MS || '180000');
12
13
 
13
14
  if (!apiKey) {
14
15
  console.error('Missing API key. Set OPENAI_API_KEY (or ACIP_API_KEY) in .env file.');
@@ -38,7 +39,7 @@ async function main(): Promise<void> {
38
39
  providers: [provider],
39
40
  caching: { enabled: true, ttl: 600, maxSize: 100, strategy: 'lru' },
40
41
  observability: { enabled: true, metricsEnabled: true, tracingEnabled: false, exportInterval: 60000 },
41
- streaming: { enabled: false, chunkSize: 1, timeout: 60000 }
42
+ streaming: { enabled: true, chunkSize: 1, timeout: requestTimeoutMs }
42
43
  };
43
44
 
44
45
  const modelInvocation = new ModelInvocationModule(miConfig);
@@ -9,6 +9,7 @@ async function main() {
9
9
  const apiKey = process.env.OPENAI_API_KEY || process.env.ACIP_API_KEY;
10
10
  const baseUrl = process.env.OPENAI_BASE_URL || process.env.ACIP_BASE_URL;
11
11
  const modelId = process.env.DEFAULT_MODEL_ID || 'gpt-5.2';
12
+ const requestTimeoutMs = Number(process.env.ACIP_REQUEST_TIMEOUT_MS || '180000');
12
13
 
13
14
  if (!apiKey) {
14
15
  console.error('Missing API key. Set OPENAI_API_KEY (or ACIP_API_KEY) in .env file.');
@@ -38,7 +39,7 @@ async function main() {
38
39
  providers: [provider],
39
40
  caching: { enabled: false, ttl: 0, maxSize: 0, strategy: 'lru' },
40
41
  observability: { enabled: false, metricsEnabled: false, tracingEnabled: false, exportInterval: 0 },
41
- streaming: { enabled: false, chunkSize: 1, timeout: 60000 }
42
+ streaming: { enabled: true, chunkSize: 1, timeout: requestTimeoutMs }
42
43
  };
43
44
 
44
45
  const modelInvocation = new ModelInvocationModule(config);
@@ -53,6 +54,78 @@ async function main() {
53
54
  // 对话历史
54
55
  const history = [];
55
56
 
57
+
58
+ const resolveModelsUrl = (rawBaseUrl) => {
59
+ const fallback = 'https://api.openai.com/v1';
60
+ const trimmed = (rawBaseUrl || fallback).replace(/\/$/, '');
61
+ if (trimmed.endsWith('/models')) {
62
+ return trimmed;
63
+ }
64
+ if (trimmed.endsWith('/v1')) {
65
+ return `${trimmed}/models`;
66
+ }
67
+ return `${trimmed}/v1/models`;
68
+ };
69
+
70
+ const listModels = async () => {
71
+ if (!apiKey) {
72
+ console.log('Missing API key. Set OPENAI_API_KEY (or ACIP_API_KEY) in .env file.');
73
+ console.log('');
74
+ return;
75
+ }
76
+
77
+ const modelsUrl = resolveModelsUrl(baseUrl);
78
+ try {
79
+ const response = await fetch(modelsUrl, {
80
+ method: 'GET',
81
+ headers: {
82
+ 'Authorization': `Bearer ${apiKey}`
83
+ }
84
+ });
85
+
86
+ if (!response.ok) {
87
+ let errorMessage = `HTTP ${response.status}`;
88
+ try {
89
+ const error = await response.json();
90
+ errorMessage = error?.error?.message || error?.message || errorMessage;
91
+ } catch {
92
+ const text = await response.text();
93
+ if (text) {
94
+ errorMessage = text;
95
+ }
96
+ }
97
+ console.log(`Models list error: ${errorMessage}`);
98
+ console.log('');
99
+ return;
100
+ }
101
+
102
+ const data = await response.json();
103
+ const models = Array.isArray(data?.data)
104
+ ? data.data
105
+ : Array.isArray(data?.models)
106
+ ? data.models
107
+ : [];
108
+
109
+ if (models.length === 0) {
110
+ console.log('No models returned.');
111
+ console.log('');
112
+ return;
113
+ }
114
+
115
+ console.log('');
116
+ console.log(`--- Models (${models.length}) ---`);
117
+ for (const model of models) {
118
+ const id = model?.id || model?.name || model?.model || JSON.stringify(model);
119
+ console.log(` - ${id}`);
120
+ }
121
+ console.log('---');
122
+ console.log('');
123
+ } catch (error) {
124
+ console.log(`Models list error: ${error.message}`);
125
+ console.log('');
126
+ }
127
+ };
128
+
56
129
  // 创建 REPL
57
130
  const rl = readline.createInterface({
58
131
  input: process.stdin,
@@ -61,7 +134,7 @@ async function main() {
61
134
 
62
135
  console.log('\n=== ACIP Assistant ===');
63
136
  console.log('Type your message to chat with AI.');
64
- console.log('Commands: /help, /clear, /exit\n');
137
+ console.log('Commands: /help, /models, /clear, /exit\n');
65
138
 
66
139
  const prompt = () => {
67
140
  rl.question('You> ', async (input) => {
@@ -77,12 +150,19 @@ async function main() {
77
150
  if (trimmed === '/help') {
78
151
  console.log('\nCommands:');
79
152
  console.log(' /help - Show this help');
153
+ console.log(' /models - List available models');
80
154
  console.log(' /clear - Clear conversation history');
81
155
  console.log(' /exit - Exit the application\n');
82
156
  prompt();
83
157
  return;
84
158
  }
85
159
 
160
+ if (trimmed === '/models') {
161
+ await listModels();
162
+ prompt();
163
+ return;
164
+ }
165
+
86
166
  if (trimmed === '/clear') {
87
167
  history.length = 0;
88
168
  console.log('Conversation history cleared.\n');
@@ -97,16 +177,39 @@ async function main() {
97
177
  { role: 'user', content: trimmed }
98
178
  ];
99
179
 
100
- const response = await modelInvocation.invoke({
101
- model: modelId,
102
- provider: 'openai',
103
- messages
180
+ const responseContent = await new Promise((resolve, reject) => {
181
+ const stream = modelInvocation.invokeStream({
182
+ model: modelId,
183
+ provider: 'openai',
184
+ timeout: requestTimeoutMs,
185
+ messages
186
+ });
187
+
188
+ let full = '';
189
+ process.stdout.write('\nAssistant> ');
190
+
191
+ stream.on('data', (chunk) => {
192
+ const content = chunk?.output?.content ?? chunk?.content;
193
+ if (typeof content === 'string') {
194
+ full += content;
195
+ process.stdout.write(content);
196
+ }
197
+ });
198
+
199
+ stream.on('end', () => {
200
+ process.stdout.write('\n\n');
201
+ resolve(full);
202
+ });
203
+
204
+ stream.on('error', (err) => {
205
+ const message = err?.error || err?.message || 'Unknown error';
206
+ process.stdout.write('\n');
207
+ reject(new Error(message));
208
+ });
104
209
  });
105
210
 
106
211
  history.push({ role: 'user', content: trimmed });
107
- history.push({ role: 'assistant', content: response.content });
108
-
109
- console.log(`\nAssistant> ${response.content}\n`);
212
+ history.push({ role: 'assistant', content: responseContent });
110
213
  } catch (error) {
111
214
  console.error(`\nError: ${error.message}\n`);
112
215
  }
@@ -12,6 +12,7 @@ async function main(): Promise<void> {
12
12
  const apiKey = process.env.OPENAI_API_KEY || process.env.ACIP_API_KEY;
13
13
  const baseUrl = process.env.OPENAI_BASE_URL || process.env.ACIP_BASE_URL;
14
14
  const modelId = process.env.DEFAULT_MODEL_ID || 'gpt-5.2';
15
+ const requestTimeoutMs = Number(process.env.ACIP_REQUEST_TIMEOUT_MS || '180000');
15
16
 
16
17
  if (!apiKey) {
17
18
  console.error('Missing API key. Set OPENAI_API_KEY (or ACIP_API_KEY) in .env file.');
@@ -41,7 +42,7 @@ async function main(): Promise<void> {
41
42
  providers: [provider],
42
43
  caching: { enabled: false, ttl: 0, maxSize: 0, strategy: 'lru' },
43
44
  observability: { enabled: false, metricsEnabled: false, tracingEnabled: false, exportInterval: 0 },
44
- streaming: { enabled: false, chunkSize: 1, timeout: 60000 }
45
+ streaming: { enabled: true, chunkSize: 1, timeout: requestTimeoutMs }
45
46
  };
46
47
 
47
48
  const modelInvocation = new ModelInvocationModule(config);
@@ -55,6 +56,76 @@ async function main(): Promise<void> {
55
56
 
56
57
  // 对话历史
57
58
  const history: Message[] = [];
59
+ const resolveModelsUrl = (rawBaseUrl?: string): string => {
60
+ const fallback = 'https://api.openai.com/v1';
61
+ const trimmed = (rawBaseUrl || fallback).replace(/\/$/, '');
62
+ if (trimmed.endsWith('/models')) {
63
+ return trimmed;
64
+ }
65
+ if (trimmed.endsWith('/v1')) {
66
+ return `${trimmed}/models`;
67
+ }
68
+ return `${trimmed}/v1/models`;
69
+ };
70
+
71
+ const listModels = async (): Promise<void> => {
72
+ if (!apiKey) {
73
+ console.log('Missing API key. Set OPENAI_API_KEY (or ACIP_API_KEY) in .env file.');
74
+ console.log('');
75
+ return;
76
+ }
77
+
78
+ const modelsUrl = resolveModelsUrl(baseUrl);
79
+ try {
80
+ const response = await fetch(modelsUrl, {
81
+ method: 'GET',
82
+ headers: {
83
+ 'Authorization': `Bearer ${apiKey}`
84
+ }
85
+ });
86
+
87
+ if (!response.ok) {
88
+ let errorMessage = `HTTP ${response.status}`;
89
+ try {
90
+ const error: any = await response.json();
91
+ errorMessage = error?.error?.message || error?.message || errorMessage;
92
+ } catch {
93
+ const text = await response.text();
94
+ if (text) {
95
+ errorMessage = text;
96
+ }
97
+ }
98
+ console.log(`Models list error: ${errorMessage}`);
99
+ console.log('');
100
+ return;
101
+ }
102
+
103
+ const data: any = await response.json();
104
+ const models = Array.isArray(data?.data)
105
+ ? data.data
106
+ : Array.isArray(data?.models)
107
+ ? data.models
108
+ : [];
109
+
110
+ if (models.length === 0) {
111
+ console.log('No models returned.');
112
+ console.log('');
113
+ return;
114
+ }
115
+
116
+ console.log('');
117
+ console.log(`--- Models (${models.length}) ---`);
118
+ for (const model of models) {
119
+ const id = model?.id || model?.name || model?.model || JSON.stringify(model);
120
+ console.log(` - ${id}`);
121
+ }
122
+ console.log('---');
123
+ console.log('');
124
+ } catch (error: any) {
125
+ console.log(`Models list error: ${error.message}`);
126
+ console.log('');
127
+ }
128
+ };
58
129
 
59
130
  // 创建 REPL
60
131
  const rl = readline.createInterface({
@@ -64,7 +135,7 @@ async function main(): Promise<void> {
64
135
 
65
136
  console.log('\n=== ACIP Assistant ===');
66
137
  console.log('Type your message to chat with AI.');
67
- console.log('Commands: /help, /clear, /exit\n');
138
+ console.log('Commands: /help, /models, /clear, /exit\n');
68
139
 
69
140
  const prompt = (): void => {
70
141
  rl.question('You> ', async (input) => {
@@ -80,12 +151,19 @@ async function main(): Promise<void> {
80
151
  if (trimmed === '/help') {
81
152
  console.log('\nCommands:');
82
153
  console.log(' /help - Show this help');
154
+ console.log(' /models - List available models');
83
155
  console.log(' /clear - Clear conversation history');
84
156
  console.log(' /exit - Exit the application\n');
85
157
  prompt();
86
158
  return;
87
159
  }
88
160
 
161
+ if (trimmed === '/models') {
162
+ await listModels();
163
+ prompt();
164
+ return;
165
+ }
166
+
89
167
  if (trimmed === '/clear') {
90
168
  history.length = 0;
91
169
  console.log('Conversation history cleared.\n');
@@ -100,16 +178,39 @@ async function main(): Promise<void> {
100
178
  { role: 'user', content: trimmed }
101
179
  ];
102
180
 
103
- const response = await modelInvocation.invoke({
104
- model: modelId,
105
- provider: 'openai',
106
- messages
181
+ const responseContent = await new Promise<string>((resolve, reject) => {
182
+ const stream = modelInvocation.invokeStream({
183
+ model: modelId,
184
+ provider: 'openai',
185
+ timeout: requestTimeoutMs,
186
+ messages
187
+ });
188
+
189
+ let full = '';
190
+ process.stdout.write('\nAssistant> ');
191
+
192
+ stream.on('data', (chunk: any) => {
193
+ const content = chunk?.output?.content ?? chunk?.content;
194
+ if (typeof content === 'string') {
195
+ full += content;
196
+ process.stdout.write(content);
197
+ }
198
+ });
199
+
200
+ stream.on('end', () => {
201
+ process.stdout.write('\n\n');
202
+ resolve(full);
203
+ });
204
+
205
+ stream.on('error', (err: any) => {
206
+ const message = err?.error || err?.message || 'Unknown error';
207
+ process.stdout.write('\n');
208
+ reject(new Error(message));
209
+ });
107
210
  });
108
211
 
109
212
  history.push({ role: 'user', content: trimmed });
110
- history.push({ role: 'assistant', content: response.content });
111
-
112
- console.log(`\nAssistant> ${response.content}\n`);
213
+ history.push({ role: 'assistant', content: responseContent });
113
214
  } catch (error: any) {
114
215
  console.error(`\nError: ${error.message}\n`);
115
216
  }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2025 ACIP Team
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.