feat(agents): add Pi CLIProxyAPI provider integration
- Add Pi detection and launch support - Install the CLIProxyAPI provider extension through Pi - Sync EasyCLIProxyAPI base URL and API key to Pi configuration - Add Pi agent UI, translations, documentation, and tests
This commit is contained in:
1 parent
176cf87225
commit
035c77354b
6 files changed
+441
-7
No files matched your search
@@ -433,12 +433,15 @@ export const zhCN = {
|
||||
'agents.description.opencode': '使用 CPA OpenAI Compatible provider',
|
||||
'agents.description.openclaw': '使用 CPA 模型供应商和默认模型',
|
||||
'agents.description.hermes': '使用 CPA custom provider',
|
||||
'agents.description.pi': '使用 Pi CLI 和 CLIProxyAPI provider 动态加载模型',
|
||||
'agents.list.detecting': '正在检测',
|
||||
'agents.list.unsupported': '当前平台不支持',
|
||||
'agents.list.notInstalled': '未检测到安装',
|
||||
'agents.list.modified': '已修改 · {model}',
|
||||
'agents.list.installedVersion': '已安装 · {version}',
|
||||
'agents.list.installed': '已安装 · 保持原配置',
|
||||
'agents.list.pluginNotInstalled': 'Pi provider 插件未安装',
|
||||
'agents.list.configured': '已配置 · EasyCLIProxyAPI',
|
||||
'agents.model.loading': '正在读取模型',
|
||||
'agents.model.loadFailed': '获取模型失败',
|
||||
'agents.model.select': '选择模型',
|
||||
@@ -594,6 +597,11 @@ export const zhCN = {
|
||||
'agents.launch.unavailable': '无法启动',
|
||||
'agents.launch.closeChatgpt': '关闭 ChatGPT App',
|
||||
'agents.launch.closingChatgpt': '关闭中',
|
||||
'agents.pi.installTitle': 'Pi CLIProxyAPI provider',
|
||||
'agents.pi.installDescription': '安装官方插件,并将当前 EasyCLIProxyAPI 的 baseUrl 和 API key 写入 Pi 配置。',
|
||||
'agents.pi.install': '安装并配置 Pi 插件',
|
||||
'agents.pi.update': '重新同步 Pi 配置',
|
||||
'agents.pi.installing': '正在安装并配置',
|
||||
'agents.default.title': '重置为默认配置',
|
||||
'agents.default.description': '这会使用当前选中模型重写 {name} 的全部相关配置文件,并删除其中其他用户内容。该操作没有备份且无法恢复。',
|
||||
'agents.default.confirm': '确认写入默认配置',
|
||||
@@ -1361,12 +1369,15 @@ export const en: Record<MessageKey, string> = {
|
||||
'agents.description.opencode': 'Uses the CPA OpenAI-compatible provider',
|
||||
'agents.description.openclaw': 'Uses the CPA model provider and default model',
|
||||
'agents.description.hermes': 'Uses the CPA custom provider',
|
||||
'agents.description.pi': 'Loads models dynamically in Pi through the CLIProxyAPI provider',
|
||||
'agents.list.detecting': 'Detecting',
|
||||
'agents.list.unsupported': 'Unsupported on this platform',
|
||||
'agents.list.notInstalled': 'Installation not detected',
|
||||
'agents.list.modified': 'Modified · {model}',
|
||||
'agents.list.installedVersion': 'Installed · {version}',
|
||||
'agents.list.installed': 'Installed · original configuration',
|
||||
'agents.list.pluginNotInstalled': 'Pi provider not installed',
|
||||
'agents.list.configured': 'Configured · EasyCLIProxyAPI',
|
||||
'agents.model.loading': 'Loading models',
|
||||
'agents.model.loadFailed': 'Failed to load models',
|
||||
'agents.model.select': 'Select a model',
|
||||
@@ -1522,6 +1533,11 @@ export const en: Record<MessageKey, string> = {
|
||||
'agents.launch.unavailable': 'Cannot Start',
|
||||
'agents.launch.closeChatgpt': 'Close ChatGPT App',
|
||||
'agents.launch.closingChatgpt': 'Closing',
|
||||
'agents.pi.installTitle': 'Pi CLIProxyAPI provider',
|
||||
'agents.pi.installDescription': 'Install the official plugin and write the current EasyCLIProxyAPI base URL and API key into Pi configuration.',
|
||||
'agents.pi.install': 'Install and Configure Pi',
|
||||
'agents.pi.update': 'Resync Pi Configuration',
|
||||
'agents.pi.installing': 'Installing and configuring',
|
||||
'agents.default.title': 'Reset to Default Configuration',
|
||||
'agents.default.description': 'This rewrites every related {name} configuration file using the selected model and deletes all other user content in those files. No backup is created and the operation cannot be undone.',
|
||||
'agents.default.confirm': 'Write Default Configuration',
|
||||
|
||||
@@ -51,7 +51,8 @@ type AgentClientId =
|
||||
| 'codex'
|
||||
| 'opencode'
|
||||
| 'openclaw'
|
||||
| 'hermes';
|
||||
| 'hermes'
|
||||
| 'pi';
|
||||
|
||||
type AgentModificationState = 'unconfigured' | 'applied' | 'invalid';
|
||||
|
||||
@@ -60,6 +61,7 @@ type AgentConfigStatus = {
|
||||
name: string;
|
||||
supportedPlatform: boolean;
|
||||
installed: boolean;
|
||||
pluginInstalled: boolean;
|
||||
executablePath: string | null;
|
||||
launchTargets: AgentLaunchTarget[];
|
||||
version: string | null;
|
||||
@@ -139,7 +141,7 @@ type AgentDefinition = {
|
||||
name: string;
|
||||
icon?: string;
|
||||
Icon?: ComponentType<{ size?: number; 'aria-hidden'?: boolean }>;
|
||||
descriptionKey: 'agents.description.claudeCode' | 'agents.description.claudeDesktop' | 'agents.description.codex' | 'agents.description.opencode' | 'agents.description.openclaw' | 'agents.description.hermes';
|
||||
descriptionKey: 'agents.description.claudeCode' | 'agents.description.claudeDesktop' | 'agents.description.codex' | 'agents.description.opencode' | 'agents.description.openclaw' | 'agents.description.hermes' | 'agents.description.pi';
|
||||
};
|
||||
|
||||
type AgentSubpageId = 'core' | 'sessions';
|
||||
@@ -187,6 +189,12 @@ const agentDefinitions: AgentDefinition[] = [
|
||||
icon: hermesIcon,
|
||||
descriptionKey: 'agents.description.hermes',
|
||||
},
|
||||
{
|
||||
id: 'pi',
|
||||
name: 'Pi',
|
||||
Icon: Bot,
|
||||
descriptionKey: 'agents.description.pi',
|
||||
},
|
||||
];
|
||||
|
||||
const agentSubpages: AgentSubpageDefinition[] = [
|
||||
@@ -268,6 +276,12 @@ const listStatusText = (status: AgentConfigStatus | undefined) => {
|
||||
if (!status) return translate(locale, 'agents.list.detecting');
|
||||
if (!status.supportedPlatform) return translate(locale, 'agents.list.unsupported');
|
||||
if (!status.installed) return translate(locale, 'agents.list.notInstalled');
|
||||
if (status.id === 'pi' && !status.pluginInstalled) {
|
||||
return translate(locale, 'agents.list.pluginNotInstalled');
|
||||
}
|
||||
if (status.id === 'pi' && status.configured) {
|
||||
return translate(locale, 'agents.list.configured');
|
||||
}
|
||||
if (status.modificationState === 'invalid') return translate(locale, 'agents.status.invalid');
|
||||
if (status.modificationState === 'applied') return translate(locale, 'agents.list.modified', { model: status.appliedModel ?? '—' });
|
||||
return status.version
|
||||
@@ -538,7 +552,7 @@ export function AgentsPage() {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [modelLoading, setModelLoading] = useState(false);
|
||||
const [busyAction, setBusyAction] = useState<
|
||||
'apply' | 'close-config' | 'default' | 'clear' | 'launch' | 'launch-cli' | 'launch-app' | 'close-app' | 'oauth-check' | 'directory' | null
|
||||
'apply' | 'close-config' | 'default' | 'clear' | 'install-pi' | 'launch' | 'launch-cli' | 'launch-app' | 'close-app' | 'oauth-check' | 'directory' | null
|
||||
>(null);
|
||||
const busy = busyAction !== null;
|
||||
const [detectionError, setDetectionError] = useState('');
|
||||
@@ -614,6 +628,12 @@ export function AgentsPage() {
|
||||
}, [loadStatuses]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selected === 'pi') {
|
||||
setModels([]);
|
||||
setModelLoading(false);
|
||||
setModelError('');
|
||||
return;
|
||||
}
|
||||
void loadModels(selected);
|
||||
}, [loadModels, selected]);
|
||||
|
||||
@@ -668,6 +688,7 @@ export function AgentsPage() {
|
||||
const savedSelectedModel = modelByClient[selected] ?? '';
|
||||
const selectedModelOption = findAgentModel(models, savedSelectedModel);
|
||||
const selectedModel = selectedModelOption?.name ?? '';
|
||||
const isPiClient = selected === 'pi';
|
||||
const isClaudeModelMappingClient = selected === 'claude-code' || selected === 'claude-desktop';
|
||||
const claudeMappingModels = useMemo(
|
||||
() => filterAgentModelsByAlias(models, claudeCustomMapping),
|
||||
@@ -738,9 +759,10 @@ export function AgentsPage() {
|
||||
activeStatus?.supportedPlatform
|
||||
&& activeStatus.installed
|
||||
&& !modelLoading
|
||||
&& (isClaudeModelMappingClient
|
||||
&& (isPiClient
|
||||
|| (isClaudeModelMappingClient
|
||||
? claudeMappingsReady
|
||||
: selectedModelOption),
|
||||
: selectedModelOption)),
|
||||
);
|
||||
const launchEnabled = Boolean(
|
||||
activeStatus?.supportedPlatform
|
||||
@@ -901,6 +923,19 @@ export function AgentsPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const installPiProvider = async () => {
|
||||
setBusyAction('install-pi');
|
||||
setConfigurationError('');
|
||||
try {
|
||||
await invoke<AgentConfigActionResult>('install_pi_provider');
|
||||
await reloadStatusesAfterAction();
|
||||
} catch (requestError) {
|
||||
setConfigurationError(String(requestError));
|
||||
} finally {
|
||||
setBusyAction(null);
|
||||
}
|
||||
};
|
||||
|
||||
const closeConfigurationChanges = async () => {
|
||||
setConfigurationError('');
|
||||
setBusyAction('close-config');
|
||||
@@ -1217,7 +1252,7 @@ export function AgentsPage() {
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{!isClaudeModelMappingClient ? (
|
||||
{!isClaudeModelMappingClient && !isPiClient ? (
|
||||
<section className="agent-core-setting-section agent-model-section">
|
||||
<div className="agent-section-heading">
|
||||
<div><strong>{t('agents.useModel')}</strong></div>
|
||||
@@ -1293,6 +1328,38 @@ export function AgentsPage() {
|
||||
</section>
|
||||
) : null}
|
||||
|
||||
{isPiClient ? (
|
||||
<section className={`agent-core-setting-section agent-modification-actions ${activeStatus?.configured ? 'enabled' : ''}`}>
|
||||
<div className="agent-section-heading">
|
||||
<div>
|
||||
<strong>{t('agents.pi.installTitle')}</strong>
|
||||
<span>{t('agents.pi.installDescription')}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="agent-modification-control">
|
||||
<div className="agent-modification-buttons">
|
||||
<button
|
||||
type="button"
|
||||
className="primary-button"
|
||||
onClick={() => void installPiProvider()}
|
||||
disabled={busy || !activeStatus?.installed || !activeStatus.supportedPlatform}
|
||||
>
|
||||
{busyAction === 'install-pi' ? <LoaderCircle size={16} className="spin" /> : null}
|
||||
{busyAction === 'install-pi'
|
||||
? t('agents.pi.installing')
|
||||
: activeStatus?.configured
|
||||
? t('agents.pi.update')
|
||||
: t('agents.pi.install')}
|
||||
</button>
|
||||
</div>
|
||||
{configurationError ? (
|
||||
<span className="agent-inline-message error" role="alert" aria-live="polite">
|
||||
{configurationError}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
</section>
|
||||
) : (
|
||||
<section className={`agent-core-setting-section agent-modification-actions ${activeStatus?.modificationState === 'applied' ? 'enabled' : ''}`}>
|
||||
<div className="agent-section-heading">
|
||||
<div>
|
||||
@@ -1377,6 +1444,7 @@ export function AgentsPage() {
|
||||
) : null}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
<div className="agent-config-footer">
|
||||
<div className="agent-launch-control">
|
||||
|
||||
Reference in new issue
Block a user