using System.Text.Json.Serialization;
namespace MAF1.PluginContract;
///
/// 每个插件目录里的 plugin.json。网页宿主扫描这个文件来画节点端口,不会加载插件 DLL。
/// Id 会出现在画布节点的 Type 上。
///
public sealed class PluginManifest
{
public string Id { get; set; } = "";
public string Name { get; set; } = "";
public string Description { get; set; } = "";
public string Version { get; set; } = "1.0.0";
/// stdin/stdout 协议主版本。缺省或 0 视为 1。宿主拒绝大于 的值。
public int ProtocolVersion { get; set; }
public PluginLaunch Launch { get; set; } = new();
public int TimeoutSeconds { get; set; }
public Dictionary Env { get; set; } = new(StringComparer.OrdinalIgnoreCase);
public List Credentials { get; set; } = [];
public List Inputs { get; set; } = [];
public List Outputs { get; set; } = [];
}
/// 如何启动插件进程。Command 可以是相对插件目录的 exe,或 dotnet + dll。
public sealed class PluginLaunch
{
public string Command { get; set; } = "";
public List Args { get; set; } = [];
}
/// 画布上的一个输入或输出端口。Name 必须和代码里读写的字段一致。
public sealed class PluginPort
{
public string Name { get; set; } = "";
public string Type { get; set; } = "string";
public string Description { get; set; } = "";
public bool Required { get; set; }
}
/// 插件声明「我需要哪种凭据」。宿主按 Type 注入,Key 不进流程图 JSON。
public sealed class PluginCredentialNeed
{
public string Name { get; set; } = "";
public string Type { get; set; } = "";
public bool Required { get; set; } = true;
public string Description { get; set; } = "";
}
/// stdin/stdout JSON 协议版本。缺省字段按 1 处理,便于旧 plugin.json 继续用。
public static class PluginProtocol
{
public const int Current = 1;
public static int Normalize(int version) => version <= 0 ? 1 : version;
public static bool IsSupported(int version)
{
int normalized = Normalize(version);
return normalized >= 1 && normalized <= Current;
}
}
/// 凭据 type 约定。宿主按类型注入环境变量,未知类型只传 stdin Extra。
public static class PluginCredentialTypes
{
public const string OpenAiCompatible = "openai-compatible";
public const string Llm = "llm";
public const string OpenWeather = "openweather";
public static bool IsLlm(string? type)
=> type is OpenAiCompatible or Llm
|| string.Equals(type, "openai-compatible", StringComparison.OrdinalIgnoreCase)
|| string.Equals(type, "llm", StringComparison.OrdinalIgnoreCase);
public static bool IsOpenWeather(string? type)
=> string.Equals(type, OpenWeather, StringComparison.OrdinalIgnoreCase);
public static bool Matches(string? declaredType, string? recordType)
{
if (IsLlm(declaredType) && IsLlm(recordType))
{
return true;
}
return string.Equals(declaredType, recordType, StringComparison.OrdinalIgnoreCase);
}
}
/// 宿主写入插件 stdin 的整包请求:业务输入 + 凭据。
public sealed class PluginRequest
{
public int ProtocolVersion { get; set; } = PluginProtocol.Current;
public Dictionary Inputs { get; set; } = new(StringComparer.OrdinalIgnoreCase);
[JsonPropertyName("credentials")]
public Dictionary Credentials { get; set; } = new(StringComparer.OrdinalIgnoreCase);
}
/// 实际的 endpoint / apiKey / model。只在宿主→插件进程之间传递。
public sealed class PluginCredentialPayload
{
public string Id { get; set; } = "";
public string Type { get; set; } = "";
public string? Endpoint { get; set; }
public string? ApiKey { get; set; }
public string? Model { get; set; }
public Dictionary Extra { get; set; } = new(StringComparer.OrdinalIgnoreCase);
}
/// 插件 JSON 统一 camelCase,和网页前端字段名对齐。
public static class PluginJson
{
public static readonly System.Text.Json.JsonSerializerOptions Options = new()
{
PropertyNameCaseInsensitive = true,
PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase,
WriteIndented = false,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
};
}