@ia-ccun/code-agent-cli 0.0.57-beta.2 → 0.0.58-beta.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.
@@ -17,11 +17,20 @@ ONLY_CASES=""
17
17
  QUIET=0
18
18
  while [[ $# -gt 0 ]]; do
19
19
  case "$1" in
20
- --only) ONLY_CASES="$2"; shift 2 ;;
21
- --quiet|-q) QUIET=1; shift ;;
22
- --aicode) export AICODE_BIN="$2"; shift 2 ;;
23
- --help|-h)
24
- cat <<EOF
20
+ --only)
21
+ ONLY_CASES="$2"
22
+ shift 2
23
+ ;;
24
+ --quiet | -q)
25
+ QUIET=1
26
+ shift
27
+ ;;
28
+ --aicode)
29
+ export AICODE_BIN="$2"
30
+ shift 2
31
+ ;;
32
+ --help | -h)
33
+ cat <<EOF
25
34
  Usage: $0 [--only CASES] [--quiet] [--aicode PATH]
26
35
 
27
36
  Options:
@@ -34,9 +43,12 @@ Environment:
34
43
  AICODE_BIN aicode 二进制路径(同 --aicode)
35
44
  AICODE_CONFIG 配置目录(同 --config)
36
45
  EOF
37
- exit 0
38
- ;;
39
- *) echo "Unknown arg: $1"; exit 2 ;;
46
+ exit 0
47
+ ;;
48
+ *)
49
+ echo "Unknown arg: $1"
50
+ exit 2
51
+ ;;
40
52
  esac
41
53
  done
42
54
 
@@ -110,7 +122,6 @@ for case_file in "${CASES_TO_RUN[@]}"; do
110
122
  chmod +x "$SCRIPT_DIR/cases/$case_file" 2>/dev/null
111
123
 
112
124
  if "$SCRIPT_DIR/cases/$case_file"; then
113
- # 从 case 输出里提取 pass/fail 数(不太可靠,但能汇总)
114
125
  :
115
126
  else
116
127
  FAILED_CASES+=("$case_file")
@@ -132,32 +143,15 @@ echo "═══ 汇总 ═══"
132
143
 
133
144
  for case_file in "${CASES_TO_RUN[@]}"; do
134
145
  case_name="${case_file%.sh}"
135
- log="$SCRIPT_DIR/results/${case_name}.log"
136
- result="?"
137
146
  pass_n=0
138
147
  fail_n=0
148
+ skip_n=0
139
149
 
140
- if [[ -f "$log" ]]; then
141
- # 通过 case 退出码判断(case 脚本内部已有 summary + exit)
142
- # 同时作为备份,使用 grep 统计 stdout 输出
143
- case_exit_file="$SCRIPT_DIR/results/.case_exit_${case_name}"
144
- if [[ -f "$case_exit_file" ]]; then
145
- case_exit=$(cat "$case_exit_file")
146
- if [[ "$case_exit" -ne 0 ]]; then
147
- # 整个 case 失败,用 FAIL 计数(不具体到哪个检查)
148
- fail_n=1
149
- pass_n=0
150
- skip_n=0
151
- else
152
- pass_n=1
153
- fail_n=0
154
- skip_n=0
155
- fi
156
- else
157
- pass_n=0; fail_n=0; skip_n=0
158
- fi
150
+ # 用实际退出码判定(case 脚本内部 summary() 返回非 0 即失败)
151
+ if [[ " ${FAILED_CASES[*]:-} " == *" $case_file "* ]]; then
152
+ fail_n=1
159
153
  else
160
- pass_n=0; fail_n=0; skip_n=0
154
+ pass_n=1
161
155
  fi
162
156
 
163
157
  TOTAL_PASS=$((TOTAL_PASS + pass_n))
@@ -184,4 +178,4 @@ if [[ $TOTAL_FAIL -eq 0 ]]; then
184
178
  else
185
179
  echo "✗ $TOTAL_FAIL FAILURES — 详见 results/*.log"
186
180
  exit 1
187
- fi
181
+ fi
@@ -1,149 +0,0 @@
1
- /**
2
- * @author: xujianjiang
3
- * AICode 内网 LLM Provider — 动态模型列表
4
- *
5
- * 内网网关每次上下线模型,用户不该手改 models.json。
6
- * 这里用 pi.registerProvider + refreshModels 钩子,在会话启动和打开 /model 时
7
- * 自动从 GET {baseUrl}/models 拉取真实模型列表,用户零操作。
8
- *
9
- * ponytail:
10
- * - 内网不返回 maxTokens,统一用 DEFAULT_MAX_TOKENS;个别模型要改走
11
- * models.json 的 providers.aicode.modelOverrides(对动态模型同样生效)
12
- * - 大小写重复的 id(如 Qwen2.5-VL-7B-Instruct / qwen2.5-vl-7b-instruct)
13
- * 按 lowercase 去重,优先保留全小写的那个
14
- * - embedding / rerank 不是 chat 模型,pi 用不了,直接过滤
15
- * - 静态兜底列表放在扩展里,网关不可达时至少还有一个可选模型
16
- */
17
-
18
- import { type ExtensionAPI, type ProviderModelConfig } from "@earendil-works/pi-coding-agent";
19
- import { type RefreshModelsContext } from "@earendil-works/pi-ai";
20
-
21
- const PROVIDER_ID = "aicode";
22
- const DEFAULT_BASE_URL = "https://ai-llm.nucc.com/v1";
23
- const DEFAULT_MAX_TOKENS = 8192;
24
-
25
- const BASE_URL = process.env.AICODE_BASE_URL ?? DEFAULT_BASE_URL;
26
-
27
- /** chat 模型的配置形状(ProviderModelConfig 联合类型里的 chat 分支) */
28
- type ChatModelConfig = Extract<ProviderModelConfig, { type?: "chat" }>;
29
-
30
- /** 内网 /v1/models 的 ModelItem(只列我们用到的字段) */
31
- interface InternalModelItem {
32
- id: string;
33
- type?: "llm" | "vision" | "embedding" | "rerank";
34
- max_model_len?: number;
35
- supports_image_input?: boolean;
36
- supports_reasoning?: boolean;
37
- reasoning_parameter_format?: string;
38
- }
39
-
40
- interface InternalModelList {
41
- data?: InternalModelItem[];
42
- }
43
-
44
- /** 内网 reasoning_parameter_format → pi compat.thinkingFormat */
45
- const THINKING_FORMAT: Record<string, "qwen-chat-template" | "qwen"> = {
46
- "qwen-chat-template": "qwen-chat-template",
47
- qwen: "qwen",
48
- };
49
-
50
- /** 静态兜底列表:网关不可达时用 */
51
- const FALLBACK_MODELS: ChatModelConfig[] = [
52
- {
53
- id: "deepseek-v4-flash",
54
- name: "deepseek-v4-flash",
55
- reasoning: true,
56
- input: ["text"],
57
- contextWindow: 262144,
58
- maxTokens: DEFAULT_MAX_TOKENS,
59
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
60
- compat: { thinkingFormat: "qwen-chat-template" },
61
- },
62
- ];
63
-
64
- /**
65
- * 把内网模型列表转成 pi 的模型配置。
66
- * 纯函数,便于单测。
67
- */
68
- export function toProviderModels(
69
- items: InternalModelItem[],
70
- ): ChatModelConfig[] {
71
- const byId = new Map<string, InternalModelItem>();
72
-
73
- for (const item of items) {
74
- if (!item.id) continue;
75
- // embedding / rerank 不是 chat 模型
76
- if (item.type && item.type !== "llm" && item.type !== "vision") continue;
77
-
78
- const key = item.id.toLowerCase();
79
- const existing = byId.get(key);
80
- // 大小写重复时优先保留全小写的 id(与内网其它模型 id 命名一致)
81
- if (!existing || (item.id === key && existing.id !== key)) {
82
- byId.set(key, item);
83
- }
84
- }
85
-
86
- return [...byId.values()].map((item) => {
87
- const reasoning = item.supports_reasoning === true;
88
- const model: ChatModelConfig = {
89
- id: item.id,
90
- name: item.id,
91
- reasoning,
92
- input: item.supports_image_input ? ["text", "image"] : ["text"],
93
- contextWindow: item.max_model_len ?? 128000,
94
- maxTokens: DEFAULT_MAX_TOKENS,
95
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
96
- };
97
-
98
- if (reasoning) {
99
- model.compat = {
100
- thinkingFormat:
101
- THINKING_FORMAT[item.reasoning_parameter_format ?? ""] ??
102
- "qwen-chat-template",
103
- };
104
- }
105
-
106
- return model;
107
- });
108
- }
109
-
110
- /** 从内网网关拉取模型列表 */
111
- async function fetchInternalModels(
112
- apiKey: string | undefined,
113
- signal: AbortSignal,
114
- ): Promise<ChatModelConfig[]> {
115
- const headers: Record<string, string> = { accept: "application/json" };
116
- if (apiKey) headers.authorization = `Bearer ${apiKey}`;
117
-
118
- const res = await fetch(`${BASE_URL}/models`, { headers, signal });
119
- if (!res.ok) {
120
- throw new Error(`aicode: GET /v1/models failed with ${res.status}`);
121
- }
122
-
123
- const body = (await res.json()) as InternalModelList;
124
- return toProviderModels(body.data ?? []);
125
- }
126
-
127
- export default function (pi: ExtensionAPI) {
128
- pi.registerProvider(PROVIDER_ID, {
129
- name: "AICode (ai-llm)",
130
- baseUrl: BASE_URL,
131
- api: "openai-completions",
132
- // 环境变量没设时回落 /login 的 API key 输入框,凭据存 auth.json
133
- apiKey: "$AICODE_API_KEY",
134
- headers: { "User-Agent": "aicode-cli" },
135
- models: FALLBACK_MODELS,
136
- async refreshModels(
137
- ctx: RefreshModelsContext,
138
- ): Promise<ProviderModelConfig[]> {
139
- // 离线阶段(allowNetwork=false)不发请求,沿用静态兜底列表
140
- if (!ctx.allowNetwork) return FALLBACK_MODELS;
141
-
142
- const credential = ctx.credential;
143
- const apiKey = credential?.type === "api_key" ? credential.key : undefined;
144
- const models = await fetchInternalModels(apiKey, ctx.signal);
145
- // 空列表会清空 /model 里的模型,宁可用兜底也不清空
146
- return models.length > 0 ? models : FALLBACK_MODELS;
147
- },
148
- });
149
- }