@ia-ccun/code-agent-cli 0.0.57-beta.1 → 0.0.57-beta.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/config/agent/extensions/aicode-llm-provider.ts +149 -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 +13 -12
- package/scripts/regression/results/03-prompt-stream.log +9 -9
- package/scripts/regression/results/03-rpc-protocol.log +17 -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 +14 -20
|
@@ -0,0 +1,149 @@
|
|
|
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
|
+
}
|
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
|
+
5531
|
|
@@ -1 +1 @@
|
|
|
1
|
-
/Users/jianjiang/.aicode-regression-
|
|
1
|
+
/Users/jianjiang/.aicode-regression-5502-6601000
|