@ia-ccun/code-agent-cli 0.0.57-beta.1 → 0.0.57-beta.3
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/bin/cli.js +4 -0
- package/config/agent/extensions/aicode-llm-provider.ts +168 -0
- package/config/agent/models.json +1 -27
- package/package.json +1 -1
- package/scripts/regression/results/.last_pid +1 -1
- package/scripts/regression/results/.last_workdir +1 -1
- package/scripts/regression/results/02-extension-loading.log +11 -11
- package/scripts/regression/results/03-prompt-stream.log +9 -9
- package/scripts/regression/results/03-rpc-protocol.log +18 -17
- package/scripts/regression/results/04-slash-commands-and-skills.log +12 -12
- package/scripts/regression/results/05-tool-execution.log +5 -5
- package/scripts/regression/results/06-pi-new-features.log +36 -16
package/bin/cli.js
CHANGED
|
@@ -224,12 +224,16 @@ const piAgentPath = fileURLToPath(piAgentPathUrl); // 解码中文路径
|
|
|
224
224
|
const versionCachePath = getVersionCachePath();
|
|
225
225
|
|
|
226
226
|
// 设置环境变量
|
|
227
|
+
/** @type {Record<string, string | undefined>} */
|
|
227
228
|
const env = {
|
|
228
229
|
...process.env,
|
|
229
230
|
AICODE_CLI_CONFIG_DIR: config.configDir,
|
|
230
231
|
AICODE_CLI_NAME: config.name,
|
|
231
232
|
AICODE_CLI_VERSION: localVersion || "",
|
|
232
233
|
AICODE_CLI_VERSION_CACHE: versionCachePath,
|
|
234
|
+
// pi 的 ${VAR} 占位符解析器读 process.env.VERSION,
|
|
235
|
+
// 扩展里的 headers.User-Agent="${VERSION}" 依赖它(缺失会直接抛错)
|
|
236
|
+
VERSION: `aicode-cli/${localVersion || "0.0.0"}`,
|
|
233
237
|
PI_SKIP_VERSION_CHECK: "1",
|
|
234
238
|
PI_OFFLINE: "1", // 禁用包更新检查
|
|
235
239
|
};
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @author: xujianjiang
|
|
3
|
+
* AICode 内网 LLM Provider — 动态模型列表
|
|
4
|
+
*
|
|
5
|
+
* 内网网关每次上下线模型,用户不该手改 models.json。
|
|
6
|
+
* 这里用 pi.registerProvider + refreshModels 钩子,在会话启动时从
|
|
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
|
+
* - 故意忽略 ctx.allowNetwork:aicode-cli 以 PI_OFFLINE=1 启动(内网环境),
|
|
17
|
+
* pi 自己的目录刷新被禁用,只能扩展自己拉内网网关
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import {
|
|
21
|
+
type ExtensionAPI,
|
|
22
|
+
type ProviderModelConfig,
|
|
23
|
+
} from "@earendil-works/pi-coding-agent";
|
|
24
|
+
import { type RefreshModelsContext } from "@earendil-works/pi-ai";
|
|
25
|
+
|
|
26
|
+
const PROVIDER_ID = "aicode";
|
|
27
|
+
const DEFAULT_BASE_URL = "https://ai-llm.nucc.com/v1";
|
|
28
|
+
const DEFAULT_MAX_TOKENS = 8192;
|
|
29
|
+
const FETCH_TIMEOUT_MS = 3000;
|
|
30
|
+
|
|
31
|
+
const BASE_URL = process.env.AICODE_BASE_URL ?? DEFAULT_BASE_URL;
|
|
32
|
+
|
|
33
|
+
// ${VERSION} 由 bin/cli.js 注入子进程;直接用 pi 跑时兜底,避免 header 解析抛错
|
|
34
|
+
process.env.VERSION ??= `aicode-cli/${process.env.AICODE_CLI_VERSION ?? "0.0.0"}`;
|
|
35
|
+
|
|
36
|
+
/** chat 模型的配置形状(ProviderModelConfig 联合类型里的 chat 分支) */
|
|
37
|
+
type ChatModelConfig = Extract<ProviderModelConfig, { type?: "chat" }>;
|
|
38
|
+
|
|
39
|
+
/** 内网 /v1/models 的 ModelItem(只列我们用到的字段) */
|
|
40
|
+
interface InternalModelItem {
|
|
41
|
+
id: string;
|
|
42
|
+
type?: "llm" | "vision" | "embedding" | "rerank";
|
|
43
|
+
max_model_len?: number;
|
|
44
|
+
supports_image_input?: boolean;
|
|
45
|
+
supports_reasoning?: boolean;
|
|
46
|
+
reasoning_parameter_format?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
interface InternalModelList {
|
|
50
|
+
data?: InternalModelItem[];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** 内网 reasoning_parameter_format → pi compat.thinkingFormat */
|
|
54
|
+
const THINKING_FORMAT: Record<string, "qwen-chat-template" | "qwen"> = {
|
|
55
|
+
"qwen-chat-template": "qwen-chat-template",
|
|
56
|
+
qwen: "qwen",
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/** 静态兜底列表:网关不可达时用 */
|
|
60
|
+
const FALLBACK_MODELS: ChatModelConfig[] = [
|
|
61
|
+
{
|
|
62
|
+
id: "deepseek-v4-flash",
|
|
63
|
+
name: "deepseek-v4-flash",
|
|
64
|
+
reasoning: true,
|
|
65
|
+
input: ["text"],
|
|
66
|
+
contextWindow: 262144,
|
|
67
|
+
maxTokens: DEFAULT_MAX_TOKENS,
|
|
68
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
69
|
+
compat: { thinkingFormat: "qwen-chat-template" },
|
|
70
|
+
},
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* 把内网模型列表转成 pi 的模型配置。
|
|
75
|
+
* 纯函数,便于单测。
|
|
76
|
+
*/
|
|
77
|
+
export function toProviderModels(
|
|
78
|
+
items: InternalModelItem[],
|
|
79
|
+
): ChatModelConfig[] {
|
|
80
|
+
const byId = new Map<string, InternalModelItem>();
|
|
81
|
+
|
|
82
|
+
for (const item of items) {
|
|
83
|
+
if (!item.id) continue;
|
|
84
|
+
// embedding / rerank 不是 chat 模型
|
|
85
|
+
if (item.type && item.type !== "llm" && item.type !== "vision") continue;
|
|
86
|
+
|
|
87
|
+
const key = item.id.toLowerCase();
|
|
88
|
+
const existing = byId.get(key);
|
|
89
|
+
// 大小写重复时优先保留全小写的 id(与内网其它模型 id 命名一致)
|
|
90
|
+
if (!existing || (item.id === key && existing.id !== key)) {
|
|
91
|
+
byId.set(key, item);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return [...byId.values()].map((item) => {
|
|
96
|
+
const reasoning = item.supports_reasoning === true;
|
|
97
|
+
const model: ChatModelConfig = {
|
|
98
|
+
id: item.id,
|
|
99
|
+
name: item.id,
|
|
100
|
+
reasoning,
|
|
101
|
+
input: item.supports_image_input ? ["text", "image"] : ["text"],
|
|
102
|
+
contextWindow: item.max_model_len ?? 128000,
|
|
103
|
+
maxTokens: DEFAULT_MAX_TOKENS,
|
|
104
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
if (reasoning) {
|
|
108
|
+
model.compat = {
|
|
109
|
+
thinkingFormat:
|
|
110
|
+
THINKING_FORMAT[item.reasoning_parameter_format ?? ""] ??
|
|
111
|
+
"qwen-chat-template",
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return model;
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** 从内网网关拉取模型列表 */
|
|
120
|
+
async function fetchInternalModels(
|
|
121
|
+
apiKey: string | undefined,
|
|
122
|
+
signal: AbortSignal,
|
|
123
|
+
): Promise<ChatModelConfig[]> {
|
|
124
|
+
const headers: Record<string, string> = { accept: "application/json" };
|
|
125
|
+
if (apiKey) headers.authorization = `Bearer ${apiKey}`;
|
|
126
|
+
|
|
127
|
+
const res = await fetch(`${BASE_URL}/models`, { headers, signal });
|
|
128
|
+
if (!res.ok) {
|
|
129
|
+
throw new Error(`aicode: GET /v1/models failed with ${res.status}`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const body = (await res.json()) as InternalModelList;
|
|
133
|
+
return toProviderModels(body.data ?? []);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export default function (pi: ExtensionAPI) {
|
|
137
|
+
pi.registerProvider(PROVIDER_ID, {
|
|
138
|
+
name: "AICode (ai-llm)",
|
|
139
|
+
baseUrl: BASE_URL,
|
|
140
|
+
api: "openai-completions",
|
|
141
|
+
// 环境变量没设时回落 /login 的 API key 输入框,凭据存 auth.json
|
|
142
|
+
apiKey: "$AICODE_API_KEY",
|
|
143
|
+
headers: { "User-Agent": "${VERSION}" },
|
|
144
|
+
models: FALLBACK_MODELS,
|
|
145
|
+
async refreshModels(
|
|
146
|
+
ctx: RefreshModelsContext,
|
|
147
|
+
): Promise<ProviderModelConfig[]> {
|
|
148
|
+
const credential = ctx.credential;
|
|
149
|
+
const apiKey =
|
|
150
|
+
(credential?.type === "api_key" ? credential.key : undefined) ??
|
|
151
|
+
process.env.AICODE_API_KEY;
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
const signal = AbortSignal.any([
|
|
155
|
+
ctx.signal,
|
|
156
|
+
AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
157
|
+
]);
|
|
158
|
+
const models = await fetchInternalModels(apiKey, signal);
|
|
159
|
+
// 空列表会清空 /model 里的模型,宁可用兜底也不清空
|
|
160
|
+
if (models.length > 0) return models;
|
|
161
|
+
} catch {
|
|
162
|
+
// 网关不可达(超时/断网):沿用静态兜底列表,不影响启动
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return FALLBACK_MODELS;
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
}
|
package/config/agent/models.json
CHANGED
|
@@ -1,29 +1,3 @@
|
|
|
1
1
|
{
|
|
2
|
-
"providers": {
|
|
3
|
-
"aicode": {
|
|
4
|
-
"name": "aicode llm",
|
|
5
|
-
"baseUrl": "https://ai-llm.cc.com/v1",
|
|
6
|
-
"api": "openai-completions",
|
|
7
|
-
"apiKey": "你自己的APIKEY sk-xxxxxxxxxxxxxxxxxx",
|
|
8
|
-
"headers": {
|
|
9
|
-
"User-Agent": "${VERSION}"
|
|
10
|
-
},
|
|
11
|
-
"models": [
|
|
12
|
-
{
|
|
13
|
-
"id": "qwen3.6-35b-a3b",
|
|
14
|
-
"name": "qwen3.6",
|
|
15
|
-
"contextWindow": 128000,
|
|
16
|
-
"maxTokens": 100000,
|
|
17
|
-
"reasoning": false,
|
|
18
|
-
"input": ["text"],
|
|
19
|
-
"cost": {
|
|
20
|
-
"input": 5,
|
|
21
|
-
"output": 5,
|
|
22
|
-
"cacheRead": 0,
|
|
23
|
-
"cacheWrite": 0
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
]
|
|
27
|
-
}
|
|
28
|
-
}
|
|
2
|
+
"providers": {}
|
|
29
3
|
}
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
7895
|
|
@@ -1 +1 @@
|
|
|
1
|
-
/Users/jianjiang/.aicode-regression-
|
|
1
|
+
/Users/jianjiang/.aicode-regression-7873-4949000
|