gpteam 0.1.0 → 0.1.2

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
@@ -6,7 +6,7 @@ Interactive GPTeam API client configurator.
6
6
  npx gpteam
7
7
  ```
8
8
 
9
- The CLI asks for an API key, detects available models, benchmarks all production ingress endpoints with real API requests, then writes the selected client configuration after backing up existing files.
9
+ The CLI asks for an API key, detects available models, benchmarks all production ingress endpoints with real API requests, then backs up old files and writes the selected client configuration. Ingress endpoints are benchmarked in parallel, while rounds for the same endpoint remain sequential to keep real API request pressure bounded.
10
10
 
11
11
  Supported clients:
12
12
 
package/lib/bench.js CHANGED
@@ -5,14 +5,14 @@ import { inspectSSEBody } from './sse.js';
5
5
 
6
6
  export async function benchmarkNodes(nodes, options) {
7
7
  const rounds = options.rounds || 3;
8
- const results = [];
9
- for (const node of nodes) {
8
+ const runBenchmark = options.benchmarkNode || benchmarkNode;
9
+ const results = await Promise.all(nodes.map(async (node) => {
10
10
  const samples = [];
11
11
  for (let index = 0; index < rounds; index += 1) {
12
- samples.push(await benchmarkNode(node, options));
12
+ samples.push(await runBenchmark(node, options));
13
13
  }
14
- results.push(summarizeNode(node, samples));
15
- }
14
+ return summarizeNode(node, samples);
15
+ }));
16
16
  return results.sort((a, b) => scoreResult(a) - scoreResult(b));
17
17
  }
18
18
 
package/lib/cli.js CHANGED
@@ -20,7 +20,7 @@ export async function runCli(argv = []) {
20
20
  const rl = readline.createInterface({ input, output });
21
21
  try {
22
22
  console.log('GPTeam API 配置助手');
23
- console.log('会先做真实 API 测速,再写入客户端配置;旧配置会自动备份。\n');
23
+ console.log('接下来会用你的 key 跑几次真实请求测速,然后把选中的入口写进客户端配置。原来的配置会先备份。\n');
24
24
 
25
25
  const apiKey = args.key || args.apiKey || await askRequired(rl, '请输入 API key:');
26
26
  const client = await choose(rl, '请选择客户端类型', CLIENTS, args.client);
@@ -31,6 +31,7 @@ export async function runCli(argv = []) {
31
31
  const maxOutputTokens = Number(args.maxOutputTokens || 648);
32
32
 
33
33
  console.log('\n开始真实测速:GET /api/health + POST /v1/responses stream=true');
34
+ console.log('测速会按入口并行执行,每个入口内部仍按轮次顺序执行,避免同时打出过多真实请求。');
34
35
  console.log(`模型:${model.id},测速输出上限:${maxOutputTokens}`);
35
36
  const results = await benchmarkNodes(INGRESS_NODES, {
36
37
  apiKey,
@@ -43,6 +44,7 @@ export async function runCli(argv = []) {
43
44
 
44
45
  const recommended = results.find((item) => item.successRate > 0) || results[0];
45
46
  const selectedNode = await chooseNode(rl, results, args.node, recommended && recommended.node.id);
47
+ assertNodeConfig(selectedNode);
46
48
  const written = writeClientConfig(client.id, {
47
49
  apiKey,
48
50
  model: model.id,
@@ -54,7 +56,8 @@ export async function runCli(argv = []) {
54
56
 
55
57
  console.log('\n已写入配置:');
56
58
  for (const filePath of written) console.log(`- ${filePath}`);
57
- console.log(`入口:${selectedNode.label}(${describeSplit(selectedNode)}) ${selectedNode.baseUrl}`);
59
+ console.log(`入口:${formatNodeLabel(selectedNode)}`);
60
+ console.log(`地址:${selectedNode.baseUrl}`);
58
61
  } finally {
59
62
  rl.close();
60
63
  }
@@ -117,7 +120,7 @@ async function chooseModel(rl, models, preferred) {
117
120
  if (selected) return selected;
118
121
  console.log('\n可用模型:');
119
122
  models.forEach((model, index) => {
120
- console.log(`${index + 1}. ${model.id}(上下文 ${model.contextLength},输出 ${model.maxOutputTokens})`);
123
+ console.log(`${index + 1}. ${formatModelLabel(model)}`);
121
124
  });
122
125
  const answer = await askRequired(rl, '请选择模型序号:');
123
126
  const index = Math.max(1, Math.min(models.length, Number(answer) || 1)) - 1;
@@ -127,11 +130,11 @@ async function chooseModel(rl, models, preferred) {
127
130
  async function askContextLength(rl, model, preferred) {
128
131
  const max = Number(model.contextLength || 400000);
129
132
  if (preferred) return clamp(Number(preferred), 1, max);
130
- const answer = await rl.question(`请输入上下文长度(最大 ${max},输出上限 ${model.maxOutputTokens},默认 ${max}):`);
133
+ const answer = await rl.question(`请输入上下文窗口(最大 ${max},输出上限 ${model.maxOutputTokens},默认 ${max},回车即选择默认):`);
131
134
  return clamp(Number(answer || max), 1, max);
132
135
  }
133
136
 
134
- async function chooseNode(rl, results, preferred, recommendedID) {
137
+ export async function chooseNode(rl, results, preferred, recommendedID) {
135
138
  const nodes = results.map((item) => item.node);
136
139
  const preferredNode = nodes.find((node) => node.id === preferred);
137
140
  if (preferredNode) return preferredNode;
@@ -140,19 +143,20 @@ async function chooseNode(rl, results, preferred, recommendedID) {
140
143
  }
141
144
  return (await choose(rl, '请选择最终写入的入口', nodes.map((node) => ({
142
145
  id: node.id,
143
- label: `${node.label}(${describeSplit(node)})`
146
+ label: formatNodeLabel(node),
147
+ raw: node
144
148
  })))).raw;
145
149
  }
146
150
 
147
151
  async function choose(rl, title, items, preferred) {
148
152
  const found = items.find((item) => item.id === preferred);
149
- if (found) return { ...found, raw: found };
153
+ if (found) return { ...found, raw: found.raw || found };
150
154
  console.log(`\n${title}:`);
151
155
  items.forEach((item, index) => console.log(`${index + 1}. ${item.label}`));
152
156
  const answer = await askRequired(rl, '请输入序号:');
153
157
  const index = Math.max(1, Math.min(items.length, Number(answer) || 1)) - 1;
154
158
  const item = items[index];
155
- return { ...item, raw: item };
159
+ return { ...item, raw: item.raw || item };
156
160
  }
157
161
 
158
162
  async function askRequired(rl, prompt) {
@@ -180,6 +184,22 @@ function clamp(value, min, max) {
180
184
  return Math.max(min, Math.min(max, Math.floor(value)));
181
185
  }
182
186
 
187
+ export function formatModelLabel(model) {
188
+ const context = Number(model.contextLength || 0);
189
+ const outputTokens = Number(model.maxOutputTokens || 0);
190
+ return `${model.id}(上下文窗口 ${context},输出上限 ${outputTokens})`;
191
+ }
192
+
193
+ export function formatNodeLabel(node) {
194
+ return `${node.label}(${describeSplit(node)})`;
195
+ }
196
+
197
+ export function assertNodeConfig(node) {
198
+ if (!node || !node.baseUrl) {
199
+ throw new Error('入口配置异常:缺少 baseUrl,已停止写入客户端配置');
200
+ }
201
+ }
202
+
183
203
  function toCamel(value) {
184
204
  return value.replace(/-([a-z])/g, (_, char) => char.toUpperCase());
185
205
  }
package/lib/help.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export const PACKAGE_NAME = 'gpteam';
2
- export const PACKAGE_VERSION = '0.1.0';
2
+ export const PACKAGE_VERSION = '0.1.2';
3
3
 
4
4
  export function getHelpText() {
5
5
  return [
@@ -13,7 +13,7 @@ export function getHelpText() {
13
13
  ' --api-key <key> 预填 API key',
14
14
  ' --client <id> codex / opencode / claude-code / openclaw',
15
15
  ' --model <id> 预选模型,例如 gpt-5.5',
16
- ' --context <tokens> 预设上下文长度',
16
+ ' --context <tokens> 预设上下文窗口',
17
17
  ' --effort <level> 按模型支持项预选,常见为 none / low / medium / high / xhigh',
18
18
  ' --node <id> jp-direct / jp-split / hk-split / us-split',
19
19
  ' --rounds <n> 每个入口测速轮数,默认 3',
@@ -21,6 +21,6 @@ export function getHelpText() {
21
21
  ' --help 显示帮助',
22
22
  ' --version 显示版本',
23
23
  '',
24
- '说明:测速会真实请求 GET /api/health 和流式 POST /v1/responses,旧配置写入前会自动备份。'
24
+ '说明:测速会请求 GET /api/health 和流式 POST /v1/responses。入口之间并行测速,写新配置前会先备份旧配置。'
25
25
  ].join('\n');
26
26
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gpteam",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "GPTeam API interactive client configurator and ingress benchmark CLI.",
5
5
  "type": "module",
6
6
  "bin": {