dsh-agentone 0.5.0
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 +104 -0
- package/cordis.patch.yml +5 -0
- package/lib/auth.js +111 -0
- package/lib/client.js +218 -0
- package/lib/http.js +41 -0
- package/lib/index.js +895 -0
- package/lib/model.js +200 -0
- package/lib/page.js +857 -0
- package/lib/proc.js +113 -0
- package/lib/skill.js +132 -0
- package/lib/store.js +60 -0
- package/package.json +41 -0
package/lib/model.js
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
// 平台模型自动配置:登录后把 AgentOne 平台模型写入本机 dsh。
|
|
2
|
+
//
|
|
3
|
+
// 与平台托管实例(app/orchestrator/dsh_orchestrator.py build_dsh_settings_yaml)
|
|
4
|
+
// 同一条命名空间语义,只覆盖两处平台段,用户其它配置原样保留:
|
|
5
|
+
// settings.yaml llm-pi-ai.providers.platform + agent-default-model
|
|
6
|
+
// .credentials.yaml refs.AGENTONE_API_KEY(dsh 凭据库,owner-only,watch 热加载)
|
|
7
|
+
//
|
|
8
|
+
// 密钥形态是 30 天插件令牌(M1 exchange 签发),指向平台 /api/plugin/v1 代理;
|
|
9
|
+
// 真实 Token Hub 密钥不出平台。
|
|
10
|
+
import { readFile, rename, writeFile } from 'node:fs/promises';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
import { parse, stringify } from 'yaml';
|
|
13
|
+
|
|
14
|
+
import { platformError, platformFetch } from './http.js';
|
|
15
|
+
import { loadCredentials, resolveDshHome } from './store.js';
|
|
16
|
+
|
|
17
|
+
export const PROVIDER_KEY = 'platform';
|
|
18
|
+
export const CREDENTIAL_REF = 'AGENTONE_API_KEY';
|
|
19
|
+
|
|
20
|
+
const REASONING_EFFORTS = { off: null, low: 'low', medium: 'medium', high: 'high', max: 'max' };
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 把平台 /models 条目转成 llm-pi-ai models 配置。
|
|
24
|
+
* input(视觉)与 reasoning(思考)是平台能力表下发的声明,插件只翻译:
|
|
25
|
+
* 未声明的旧平台版本回退到全档位思考 + 纯文本。
|
|
26
|
+
*/
|
|
27
|
+
function platformModelEntry(item) {
|
|
28
|
+
const id = typeof item === 'string' ? item : item?.id;
|
|
29
|
+
if (!id) return null;
|
|
30
|
+
const entry = { id, name: friendlyModelName(id) };
|
|
31
|
+
const input = Array.isArray(item?.input) ? item.input : ['text'];
|
|
32
|
+
if (input.includes('image')) entry.input = ['text', 'image'];
|
|
33
|
+
if (item?.reasoning === false) entry.reasoningEfforts = false;
|
|
34
|
+
else entry.reasoningEfforts = { ...REASONING_EFFORTS };
|
|
35
|
+
return entry;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** 已知厂家的展示名(小写 id 前缀 → 名称)。 */
|
|
39
|
+
const VENDOR_NAMES = {
|
|
40
|
+
deepseek: 'DeepSeek',
|
|
41
|
+
glm: 'GLM',
|
|
42
|
+
gpt: 'GPT',
|
|
43
|
+
minimax: 'MiniMax',
|
|
44
|
+
kimi: 'Kimi',
|
|
45
|
+
doubao: '豆包',
|
|
46
|
+
qwen: 'Qwen',
|
|
47
|
+
claude: 'Claude',
|
|
48
|
+
grok: 'Grok',
|
|
49
|
+
gemini: 'Gemini',
|
|
50
|
+
llava: 'LLaVA',
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/** 模型 id → 选择器里的友好显示名:deepseek-v4.1-flash → DeepSeek V4.1 Flash。 */
|
|
54
|
+
export function friendlyModelName(id) {
|
|
55
|
+
const parts = String(id || '').split('-').filter(Boolean);
|
|
56
|
+
if (!parts.length) return String(id || '');
|
|
57
|
+
const [rawVendor, ...rest] = parts;
|
|
58
|
+
const vendor = VENDOR_NAMES[rawVendor.toLowerCase()] ?? rawVendor.toUpperCase();
|
|
59
|
+
const tail = rest
|
|
60
|
+
.map((part) => (/^[a-z]/.test(part) ? part.charAt(0).toUpperCase() + part.slice(1) : part))
|
|
61
|
+
.join(' ');
|
|
62
|
+
return tail ? `${vendor} ${tail}` : vendor;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function parseYamlObject(text) {
|
|
66
|
+
if (!text || !text.trim()) return {};
|
|
67
|
+
try {
|
|
68
|
+
const loaded = parse(text);
|
|
69
|
+
if (loaded && typeof loaded === 'object' && !Array.isArray(loaded)) return loaded;
|
|
70
|
+
} catch {
|
|
71
|
+
// 损坏的 YAML 视为空配置,避免因为一段坏配置让用户彻底不可用
|
|
72
|
+
}
|
|
73
|
+
return {};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** 合并出 settings.yaml 文本:覆盖 platform provider 与 agent-default-model。 */
|
|
77
|
+
export function buildSettingsYaml(existingText, { baseUrl, models, defaultModel }) {
|
|
78
|
+
const settings = parseYamlObject(existingText);
|
|
79
|
+
const existing = settings['llm-pi-ai'];
|
|
80
|
+
const llmPiAi =
|
|
81
|
+
existing && typeof existing === 'object' && existing.providers && typeof existing.providers === 'object'
|
|
82
|
+
? existing
|
|
83
|
+
: { providers: {} };
|
|
84
|
+
llmPiAi.providers[PROVIDER_KEY] = {
|
|
85
|
+
displayName: '平台模型',
|
|
86
|
+
apiKeyEnv: CREDENTIAL_REF,
|
|
87
|
+
api: 'openai-completions',
|
|
88
|
+
baseURL: baseUrl,
|
|
89
|
+
reasoning: 'high',
|
|
90
|
+
models: models
|
|
91
|
+
.map((item) => platformModelEntry(item))
|
|
92
|
+
.filter((entry) => entry !== null),
|
|
93
|
+
};
|
|
94
|
+
settings['llm-pi-ai'] = llmPiAi;
|
|
95
|
+
settings['agent-default-model'] = {
|
|
96
|
+
provider: PROVIDER_KEY,
|
|
97
|
+
model: defaultModel,
|
|
98
|
+
reasoningEffort: 'high',
|
|
99
|
+
};
|
|
100
|
+
return stringify(settings);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** 合并出 .credentials.yaml 文本:写入 refs.AGENTONE_API_KEY,其余 refs/records 保留。 */
|
|
104
|
+
export function buildCredentialsYaml(existingText, token) {
|
|
105
|
+
const document = parseYamlObject(existingText);
|
|
106
|
+
if (!document.refs || typeof document.refs !== 'object') document.refs = {};
|
|
107
|
+
document.refs[CREDENTIAL_REF] = token;
|
|
108
|
+
return stringify(document);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** 登出清理:删除凭据引用;settings 中的 platform provider 与默认模型一并移除。 */
|
|
112
|
+
export function buildLogoutYaml(existingText) {
|
|
113
|
+
const document = parseYamlObject(existingText);
|
|
114
|
+
if (document.refs && typeof document.refs === 'object') delete document.refs[CREDENTIAL_REF];
|
|
115
|
+
return stringify(document);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function buildLogoutSettingsYaml(existingText) {
|
|
119
|
+
const settings = parseYamlObject(existingText);
|
|
120
|
+
const providers = settings['llm-pi-ai']?.providers;
|
|
121
|
+
if (providers && typeof providers === 'object') delete providers[PROVIDER_KEY];
|
|
122
|
+
if (settings['agent-default-model']?.provider === PROVIDER_KEY) {
|
|
123
|
+
delete settings['agent-default-model'];
|
|
124
|
+
}
|
|
125
|
+
return stringify(settings);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export async function fetchPlatformModels(platformUrl, accessToken) {
|
|
129
|
+
const response = await platformFetch(`${platformUrl.replace(/\/+$/, '')}/api/plugin/v1/models`, {
|
|
130
|
+
headers: { Authorization: `Bearer ${accessToken}` },
|
|
131
|
+
});
|
|
132
|
+
if (!response.ok) {
|
|
133
|
+
// 平台 403 带 error.code=plan_required:错误码透传给前端做确定性判断,
|
|
134
|
+
// 不再依赖中文文案正则(文案一改判定就失效)。
|
|
135
|
+
throw await platformError(response, '获取平台模型列表失败');
|
|
136
|
+
}
|
|
137
|
+
const data = await response.json().catch(() => ({}));
|
|
138
|
+
const models = Array.isArray(data.data)
|
|
139
|
+
? data.data
|
|
140
|
+
.map((item) => (typeof item === 'string' ? item : item?.id ? { id: item.id, input: item.input, reasoning: item.reasoning } : null))
|
|
141
|
+
.filter(Boolean)
|
|
142
|
+
: [];
|
|
143
|
+
if (models.length === 0) throw new Error('平台返回的可用模型列表为空');
|
|
144
|
+
return { models, defaultModel: data.default_model || (models[0]?.id ?? models[0]) };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function writeFileAtomic(path, text, mode) {
|
|
148
|
+
const tmp = tmpName(path);
|
|
149
|
+
await writeFile(tmp, text, mode ? { mode } : undefined);
|
|
150
|
+
await rename(tmp, path);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function tmpName(path) {
|
|
154
|
+
return `${path}.agentone-${process.pid}-${Date.now()}.tmp`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* 拉取平台模型并写入本机 dsh 配置。返回 { models, defaultModel }。
|
|
159
|
+
* 失败抛 PlatformError(中文 message + 平台错误码),由调用方记录/展示
|
|
160
|
+
* (不影响已生效的旧配置)。
|
|
161
|
+
*/
|
|
162
|
+
export async function applyPlatformModel(config) {
|
|
163
|
+
const credentials = await loadCredentials(config);
|
|
164
|
+
if (!credentials) throw new Error('尚未登录平台');
|
|
165
|
+
const baseUrl = `${String(credentials.platform_url || config.platformUrl).replace(/\/+$/, '')}/api/plugin/v1`;
|
|
166
|
+
const { models, defaultModel } = await fetchPlatformModels(
|
|
167
|
+
credentials.platform_url || config.platformUrl,
|
|
168
|
+
credentials.access_token,
|
|
169
|
+
);
|
|
170
|
+
const home = resolveDshHome(config);
|
|
171
|
+
const settingsPath = join(home, 'settings.yaml');
|
|
172
|
+
const credentialsPath = join(home, '.credentials.yaml');
|
|
173
|
+
const [existingSettings, existingCredentials] = await Promise.all([
|
|
174
|
+
readFile(settingsPath, 'utf8').catch(() => ''),
|
|
175
|
+
readFile(credentialsPath, 'utf8').catch(() => ''),
|
|
176
|
+
]);
|
|
177
|
+
await writeFileAtomic(credentialsPath, buildCredentialsYaml(existingCredentials, credentials.access_token), 0o600);
|
|
178
|
+
await writeFileAtomic(
|
|
179
|
+
settingsPath,
|
|
180
|
+
buildSettingsYaml(existingSettings, { baseUrl, models, defaultModel }),
|
|
181
|
+
);
|
|
182
|
+
return { models, defaultModel };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** 登出时清理本机配置:删除凭据引用与 platform provider。 */
|
|
186
|
+
export async function removePlatformModel(config) {
|
|
187
|
+
const home = resolveDshHome(config);
|
|
188
|
+
const settingsPath = join(home, 'settings.yaml');
|
|
189
|
+
const credentialsPath = join(home, '.credentials.yaml');
|
|
190
|
+
const [existingSettings, existingCredentials] = await Promise.all([
|
|
191
|
+
readFile(settingsPath, 'utf8').catch(() => ''),
|
|
192
|
+
readFile(credentialsPath, 'utf8').catch(() => ''),
|
|
193
|
+
]);
|
|
194
|
+
if (existingCredentials) {
|
|
195
|
+
await writeFileAtomic(credentialsPath, buildLogoutYaml(existingCredentials), 0o600);
|
|
196
|
+
}
|
|
197
|
+
if (existingSettings) {
|
|
198
|
+
await writeFileAtomic(settingsPath, buildLogoutSettingsYaml(existingSettings));
|
|
199
|
+
}
|
|
200
|
+
}
|