Features: - Opencode AI chat interface - SSH tunnel for secure API communication - Auto-install Opencode on remote servers - API key management with server-side storage - Session history and management - Integration with flutter_server_box server list
249 lines
6.3 KiB
Dart
249 lines
6.3 KiB
Dart
import 'dart:async';
|
|
import 'dart:convert';
|
|
import 'dart:io';
|
|
|
|
import 'package:dartssh2/dartssh2.dart';
|
|
import 'package:flutter/foundation.dart';
|
|
|
|
/// Opencode API 响应模型
|
|
class OpencodeResponse {
|
|
final String id;
|
|
final String? content;
|
|
final String? error;
|
|
final Map<String, dynamic>? toolCalls;
|
|
final bool isComplete;
|
|
final DateTime timestamp;
|
|
|
|
OpencodeResponse({
|
|
required this.id,
|
|
this.content,
|
|
this.error,
|
|
this.toolCalls,
|
|
this.isComplete = false,
|
|
DateTime? timestamp,
|
|
}) : timestamp = timestamp ?? DateTime.now();
|
|
|
|
factory OpencodeResponse.fromJson(Map<String, dynamic> json) {
|
|
return OpencodeResponse(
|
|
id: json['id'] ?? '',
|
|
content: json['content'],
|
|
error: json['error'],
|
|
toolCalls: json['tool_calls'],
|
|
isComplete: json['is_complete'] ?? false,
|
|
timestamp: json['timestamp'] != null
|
|
? DateTime.parse(json['timestamp'])
|
|
: DateTime.now(),
|
|
);
|
|
}
|
|
|
|
Map<String, dynamic> toJson() => {
|
|
'id': id,
|
|
'content': content,
|
|
'error': error,
|
|
'tool_calls': toolCalls,
|
|
'is_complete': isComplete,
|
|
'timestamp': timestamp.toIso8601String(),
|
|
};
|
|
}
|
|
|
|
/// Opencode 消息模型
|
|
class OpencodeMessage {
|
|
final String id;
|
|
final String role; // 'user', 'assistant', 'system'
|
|
final String content;
|
|
final DateTime timestamp;
|
|
final List<Map<String, dynamic>>? toolCalls;
|
|
final Map<String, dynamic>? toolResults;
|
|
|
|
OpencodeMessage({
|
|
required this.id,
|
|
required this.role,
|
|
required this.content,
|
|
DateTime? timestamp,
|
|
this.toolCalls,
|
|
this.toolResults,
|
|
}) : timestamp = timestamp ?? DateTime.now();
|
|
|
|
factory OpencodeMessage.user(String content) {
|
|
return OpencodeMessage(
|
|
id: _generateId(),
|
|
role: 'user',
|
|
content: content,
|
|
);
|
|
}
|
|
|
|
factory OpencodeMessage.assistant(String content) {
|
|
return OpencodeMessage(
|
|
id: _generateId(),
|
|
role: 'assistant',
|
|
content: content,
|
|
);
|
|
}
|
|
|
|
factory OpencodeMessage.system(String content) {
|
|
return OpencodeMessage(
|
|
id: _generateId(),
|
|
role: 'system',
|
|
content: content,
|
|
);
|
|
}
|
|
|
|
factory OpencodeMessage.fromJson(Map<String, dynamic> json) {
|
|
return OpencodeMessage(
|
|
id: json['id'] ?? _generateId(),
|
|
role: json['role'] ?? 'user',
|
|
content: json['content'] ?? '',
|
|
timestamp: json['timestamp'] != null
|
|
? DateTime.parse(json['timestamp'])
|
|
: DateTime.now(),
|
|
toolCalls: json['tool_calls'] != null
|
|
? List<Map<String, dynamic>>.from(json['tool_calls'])
|
|
: null,
|
|
toolResults: json['tool_results'],
|
|
);
|
|
}
|
|
|
|
Map<String, dynamic> toJson() => {
|
|
'id': id,
|
|
'role': role,
|
|
'content': content,
|
|
'timestamp': timestamp.toIso8601String(),
|
|
'tool_calls': toolCalls,
|
|
'tool_results': toolResults,
|
|
};
|
|
|
|
static String _generateId() {
|
|
return DateTime.now().millisecondsSinceEpoch.toString();
|
|
}
|
|
}
|
|
|
|
/// Opencode 会话配置
|
|
class OpencodeSession {
|
|
final String id;
|
|
final String serverId;
|
|
final String serverName;
|
|
final List<OpencodeMessage> messages;
|
|
final DateTime createdAt;
|
|
DateTime updatedAt;
|
|
String? title;
|
|
|
|
OpencodeSession({
|
|
required this.id,
|
|
required this.serverId,
|
|
required this.serverName,
|
|
this.messages = const [],
|
|
DateTime? createdAt,
|
|
DateTime? updatedAt,
|
|
this.title,
|
|
}) : createdAt = createdAt ?? DateTime.now(),
|
|
updatedAt = updatedAt ?? DateTime.now();
|
|
|
|
factory OpencodeSession.create({
|
|
required String serverId,
|
|
required String serverName,
|
|
String? title,
|
|
}) {
|
|
return OpencodeSession(
|
|
id: _generateId(),
|
|
serverId: serverId,
|
|
serverName: serverName,
|
|
title: title,
|
|
messages: [
|
|
OpencodeMessage.system(
|
|
'You are Opencode, a helpful AI assistant running inside an SSH session. '
|
|
'You have access to the server filesystem and can execute commands via tools.',
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
void addMessage(OpencodeMessage message) {
|
|
messages.add(message);
|
|
updatedAt = DateTime.now();
|
|
}
|
|
|
|
Map<String, dynamic> toJson() => {
|
|
'id': id,
|
|
'server_id': serverId,
|
|
'server_name': serverName,
|
|
'messages': messages.map((m) => m.toJson()).toList(),
|
|
'created_at': createdAt.toIso8601String(),
|
|
'updated_at': updatedAt.toIso8601String(),
|
|
'title': title,
|
|
};
|
|
|
|
factory OpencodeSession.fromJson(Map<String, dynamic> json) {
|
|
return OpencodeSession(
|
|
id: json['id'] ?? _generateId(),
|
|
serverId: json['server_id'] ?? '',
|
|
serverName: json['server_name'] ?? '',
|
|
messages: (json['messages'] as List?)
|
|
?.map((m) => OpencodeMessage.fromJson(m))
|
|
.toList() ??
|
|
[],
|
|
createdAt: json['created_at'] != null
|
|
? DateTime.parse(json['created_at'])
|
|
: DateTime.now(),
|
|
updatedAt: json['updated_at'] != null
|
|
? DateTime.parse(json['updated_at'])
|
|
: DateTime.now(),
|
|
title: json['title'],
|
|
);
|
|
}
|
|
|
|
static String _generateId() {
|
|
return DateTime.now().millisecondsSinceEpoch.toString();
|
|
}
|
|
}
|
|
|
|
/// Opencode 安装状态
|
|
enum OpencodeInstallStatus {
|
|
notInstalled,
|
|
installing,
|
|
installed,
|
|
error,
|
|
}
|
|
|
|
/// Opencode 服务器配置
|
|
class OpencodeServerConfig {
|
|
final String serverId;
|
|
String opencodePath;
|
|
String configPath;
|
|
String apiPort;
|
|
bool autoStart;
|
|
Map<String, String> envVars;
|
|
String? apiKey; // Opencode API Key
|
|
|
|
OpencodeServerConfig({
|
|
required this.serverId,
|
|
this.opencodePath = '/usr/local/bin/opencode',
|
|
this.configPath = '~/.config/opencode',
|
|
this.apiPort = '8080',
|
|
this.autoStart = true,
|
|
this.envVars = const {},
|
|
this.apiKey,
|
|
});
|
|
|
|
Map<String, dynamic> toJson() => {
|
|
'server_id': serverId,
|
|
'opencode_path': opencodePath,
|
|
'config_path': configPath,
|
|
'api_port': apiPort,
|
|
'auto_start': autoStart,
|
|
'env_vars': envVars,
|
|
'api_key': apiKey,
|
|
};
|
|
|
|
factory OpencodeServerConfig.fromJson(Map<String, dynamic> json) {
|
|
return OpencodeServerConfig(
|
|
serverId: json['server_id'] ?? '',
|
|
opencodePath: json['opencode_path'] ?? '/usr/local/bin/opencode',
|
|
configPath: json['config_path'] ?? '~/.config/opencode',
|
|
apiPort: json['api_port'] ?? '8080',
|
|
autoStart: json['auto_start'] ?? true,
|
|
envVars: Map<String, String>.from(json['env_vars'] ?? {}),
|
|
apiKey: json['api_key'],
|
|
);
|
|
}
|
|
}
|