Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions lib/core/services/mimo_claw/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# MiMo Claw Integration for Kelivo

将小米 MiMo Claw 的完整 Agent 能力接入 Kelivo。

## 架构

```
Kelivo App
└── MiMoClawProvider
├── MiMoClawAuth (小米 SSO 登录)
│ ├── getLoginUrl() → WebView 登录
│ ├── extractPhFromCookies() → 提取 ph
│ ├── validateCookies() → 验证登录态
│ └── getUserInfo() → 获取用户信息
└── MiMoClawGateway (WebSocket JSON-RPC)
├── connect() → ticket + WS 握手
├── sendChatMessage() → 聊天
├── abortChat() → 中止
├── setSessionModel() → 切换模型
├── getChatHistory() → 历史消息
└── events → 流式事件推送
├── AgentEvent → 流式文本
├── ChatEvent → 增量/最终
├── ToolEvent → 工具调用
├── SessionMessageEvent → 完整消息
└── SessionListEvent → 会话列表
```

## 使用流程

```dart
final provider = MiMoClawProvider();

// 1. 获取登录 URL(在 WebView 中加载)
final loginUrl = provider.getLoginUrl();

// 2. 用户登录后,从 WebView 获取 Cookie
final cookies = 'xiaomichatbot_ph=xxx; ...';

// 3. 完成登录(自动连接 Gateway)
final success = await provider.completeLogin(cookies);

// 4. 监听事件
provider.events.listen((event) {
if (event is AgentEvent) {
print('Agent: ${event.delta}');
} else if (event is ToolEvent) {
print('Tool: ${event.name} - ${event.status}');
}
});

// 5. 发送消息
await provider.sendMessage('帮我写一个 Hello World');

// 6. 监听连接状态
provider.connectionStateStream.listen((state) {
print('连接状态: $state');
});
```

## 协议说明

MiMo Claw 使用 OpenClaw JSON-RPC over WebSocket 协议:

1. **认证**: 小米账号 SSO → Cookie (ph) → WS ticket
2. **连接**: WebSocket + JSON-RPC 握手
3. **通信**:
- 请求: `{ type: "req", id, method, params }`
- 响应: `{ type: "res", id, ok, payload }`
- 事件: `{ type: "event", event, payload }`

## 文件结构

```
lib/core/services/mimo_claw/
├── auth.dart # 小米 SSO 登录服务
├── gateway.dart # WebSocket Gateway 客户端
├── provider.dart # Provider 集成入口
└── README.md # 本文档
```
148 changes: 148 additions & 0 deletions lib/core/services/mimo_claw/auth.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import 'dart:async';
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:dio/dio.dart';
import 'package:web_socket_channel/web_socket_channel.dart';

/// MiMo Claw SSO 登录服务
///
/// 处理小米账号 SSO 登录流程:
/// 1. 获取登录页面 URL
/// 2. 用户在 WebView 中完成登录
/// 3. 从 Cookie 中提取 ph 值
/// 4. 使用 ph 获取 WS ticket
class MiMoClawAuth {
static const String _baseUrl = 'https://aistudio.xiaomimimo.com';
static const String _loginUrl =
'https://account.xiaomi.com/fe/service/login/password';

final Dio _dio;

MiMoClawAuth({Dio? dio}) : _dio = dio ?? Dio();

/// 获取登录 URL(用于 WebView 加载)
String getLoginUrl() {
return '$_loginUrl?followup=$_baseUrl/sts&service=miclaw&_json=true';
}

/// 从 Cookie 字符串中提取 ph 值
String? extractPhFromCookies(String cookies) {
return cookies
.split(';')
.map((c) => c.trim())
.where((c) => c.startsWith('xiaomichatbot_ph='))
.map((c) => c.substring('xiaomichatbot_ph='.length).replaceAll('"', ''))
.firstOrNull;
}

/// 验证 Cookie 是否有效
Future<bool> validateCookies(String cookies) async {
try {
final response = await _dio.get(
'$_baseUrl/open-apis/user/mi/get',
options: Options(
headers: {
'Cookie': cookies,
'User-Agent':
'Mozilla/5.0 (Linux; Android 14) Chrome/137.0.0.0 Mobile Safari/537.36',
},
),
);

if (response.statusCode == 200) {
final data = response.data;
if (data is Map) {
return data['code'] == 0 && data['data'] != null;
}
}
return false;
} catch (e) {
debugPrint('MiMoClawAuth 验证失败: $e');
return false;
}
}

/// 获取用户信息
Future<Map<String, dynamic>?> getUserInfo(String cookies) async {
try {
final response = await _dio.get(
'$_baseUrl/open-apis/user/mi/get',
options: Options(
headers: {
'Cookie': cookies,
'User-Agent':
'Mozilla/5.0 (Linux; Android 14) Chrome/137.0.0.0 Mobile Safari/537.36',
},
),
);

if (response.statusCode == 200) {
final data = response.data;
if (data is Map && data['code'] == 0) {
return data['data'] as Map<String, dynamic>?;
}
}
return null;
} catch (e) {
debugPrint('MiMoClawAuth 获取用户信息失败: $e');
return null;
}
}

/// 获取 WS ticket
Future<String?> getWsTicket(String cookies) async {
try {
final response = await _dio.get(
'$_baseUrl/open-apis/user/ws/ticket',
options: Options(
headers: {
'Cookie': cookies,
'User-Agent':
'Mozilla/5.0 (Linux; Android 14) Chrome/137.0.0.0 Mobile Safari/537.36',
},
),
);

if (response.statusCode == 200) {
final data = response.data;
if (data is Map) {
final ticket = data['data']?['ticket'] ?? data['ticket'];
if (ticket != null && ticket.toString().isNotEmpty) {
return ticket.toString();
}
}
}
return null;
} catch (e) {
debugPrint('MiMoClawAuth 获取 ticket 失败: $e');
return null;
}
}

/// 获取 Bot 配置
Future<Map<String, dynamic>?> getBotConfig(String cookies) async {
try {
final response = await _dio.get(
'$_baseUrl/open-apis/bot/config',
options: Options(
headers: {
'Cookie': cookies,
'User-Agent':
'Mozilla/5.0 (Linux; Android 14) Chrome/137.0.0.0 Mobile Safari/537.36',
},
),
);

if (response.statusCode == 200) {
final data = response.data;
if (data is Map && data['code'] == 0) {
return data['data'] as Map<String, dynamic>?;
}
}
return null;
} catch (e) {
debugPrint('MiMoClawAuth 获取 Bot 配置失败: $e');
return null;
}
}
}
Loading