331 lines
9.2 KiB
C#
331 lines
9.2 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace AxCopilot.Services.Agent;
|
|
|
|
public class JsonTool : IAgentTool
|
|
{
|
|
private record PathSegment(string Key, int Index, bool IsIndex);
|
|
|
|
public string Name => "json_tool";
|
|
|
|
public string Description => "JSON processing tool. Actions: 'validate' — check if text is valid JSON and report errors; 'format' — pretty-print or minify JSON; 'query' — extract value by dot-path (e.g. 'data.users[0].name'); 'keys' — list top-level keys; 'convert' — convert between JSON/CSV (flat arrays only).";
|
|
|
|
public ToolParameterSchema Parameters
|
|
{
|
|
get
|
|
{
|
|
ToolParameterSchema toolParameterSchema = new ToolParameterSchema();
|
|
Dictionary<string, ToolProperty> dictionary = new Dictionary<string, ToolProperty>();
|
|
ToolProperty obj = new ToolProperty
|
|
{
|
|
Type = "string",
|
|
Description = "Action to perform"
|
|
};
|
|
int num = 5;
|
|
List<string> list = new List<string>(num);
|
|
CollectionsMarshal.SetCount(list, num);
|
|
Span<string> span = CollectionsMarshal.AsSpan(list);
|
|
span[0] = "validate";
|
|
span[1] = "format";
|
|
span[2] = "query";
|
|
span[3] = "keys";
|
|
span[4] = "convert";
|
|
obj.Enum = list;
|
|
dictionary["action"] = obj;
|
|
dictionary["json"] = new ToolProperty
|
|
{
|
|
Type = "string",
|
|
Description = "JSON text to process"
|
|
};
|
|
dictionary["path"] = new ToolProperty
|
|
{
|
|
Type = "string",
|
|
Description = "Dot-path for query action (e.g. 'data.items[0].name')"
|
|
};
|
|
dictionary["minify"] = new ToolProperty
|
|
{
|
|
Type = "string",
|
|
Description = "For format action: 'true' to minify, 'false' to pretty-print (default)"
|
|
};
|
|
ToolProperty obj2 = new ToolProperty
|
|
{
|
|
Type = "string",
|
|
Description = "For convert action: target format"
|
|
};
|
|
num = 1;
|
|
List<string> list2 = new List<string>(num);
|
|
CollectionsMarshal.SetCount(list2, num);
|
|
CollectionsMarshal.AsSpan(list2)[0] = "csv";
|
|
obj2.Enum = list2;
|
|
dictionary["target_format"] = obj2;
|
|
toolParameterSchema.Properties = dictionary;
|
|
num = 2;
|
|
List<string> list3 = new List<string>(num);
|
|
CollectionsMarshal.SetCount(list3, num);
|
|
Span<string> span2 = CollectionsMarshal.AsSpan(list3);
|
|
span2[0] = "action";
|
|
span2[1] = "json";
|
|
toolParameterSchema.Required = list3;
|
|
return toolParameterSchema;
|
|
}
|
|
}
|
|
|
|
public Task<ToolResult> ExecuteAsync(JsonElement args, AgentContext context, CancellationToken ct = default(CancellationToken))
|
|
{
|
|
string text = args.GetProperty("action").GetString() ?? "";
|
|
string json = args.GetProperty("json").GetString() ?? "";
|
|
try
|
|
{
|
|
if (1 == 0)
|
|
{
|
|
}
|
|
JsonElement value;
|
|
JsonElement value2;
|
|
JsonElement value3;
|
|
ToolResult result = text switch
|
|
{
|
|
"validate" => Validate(json),
|
|
"format" => Format(json, args.TryGetProperty("minify", out value) && value.GetString() == "true"),
|
|
"query" => Query(json, args.TryGetProperty("path", out value2) ? (value2.GetString() ?? "") : ""),
|
|
"keys" => Keys(json),
|
|
"convert" => Convert(json, args.TryGetProperty("target_format", out value3) ? (value3.GetString() ?? "csv") : "csv"),
|
|
_ => ToolResult.Fail("Unknown action: " + text),
|
|
};
|
|
if (1 == 0)
|
|
{
|
|
}
|
|
return Task.FromResult(result);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return Task.FromResult(ToolResult.Fail("JSON 처리 오류: " + ex.Message));
|
|
}
|
|
}
|
|
|
|
private static ToolResult Validate(string json)
|
|
{
|
|
try
|
|
{
|
|
using JsonDocument jsonDocument = JsonDocument.Parse(json);
|
|
JsonElement rootElement = jsonDocument.RootElement;
|
|
JsonValueKind valueKind = rootElement.ValueKind;
|
|
if (1 == 0)
|
|
{
|
|
}
|
|
string text = valueKind switch
|
|
{
|
|
JsonValueKind.Object => $"Object ({rootElement.EnumerateObject().Count()} keys)",
|
|
JsonValueKind.Array => $"Array ({rootElement.GetArrayLength()} items)",
|
|
_ => rootElement.ValueKind.ToString(),
|
|
};
|
|
if (1 == 0)
|
|
{
|
|
}
|
|
string text2 = text;
|
|
return ToolResult.Ok("✓ Valid JSON — " + text2);
|
|
}
|
|
catch (JsonException ex)
|
|
{
|
|
return ToolResult.Ok("✗ Invalid JSON — " + ex.Message);
|
|
}
|
|
}
|
|
|
|
private static ToolResult Format(string json, bool minify)
|
|
{
|
|
using JsonDocument jsonDocument = JsonDocument.Parse(json);
|
|
JsonSerializerOptions options = new JsonSerializerOptions
|
|
{
|
|
WriteIndented = !minify
|
|
};
|
|
string text = JsonSerializer.Serialize(jsonDocument.RootElement, options);
|
|
if (text.Length > 8000)
|
|
{
|
|
text = text.Substring(0, 8000) + "\n... (truncated)";
|
|
}
|
|
return ToolResult.Ok(text);
|
|
}
|
|
|
|
private static ToolResult Query(string json, string path)
|
|
{
|
|
if (string.IsNullOrEmpty(path))
|
|
{
|
|
return ToolResult.Fail("path parameter is required for query action");
|
|
}
|
|
using JsonDocument jsonDocument = JsonDocument.Parse(json);
|
|
JsonElement value = jsonDocument.RootElement;
|
|
foreach (PathSegment item in ParsePath(path))
|
|
{
|
|
if (item.IsIndex)
|
|
{
|
|
if (value.ValueKind != JsonValueKind.Array || item.Index >= value.GetArrayLength())
|
|
{
|
|
return ToolResult.Fail($"Array index [{item.Index}] out of range");
|
|
}
|
|
value = value[item.Index];
|
|
}
|
|
else
|
|
{
|
|
if (value.ValueKind != JsonValueKind.Object || !value.TryGetProperty(item.Key, out var value2))
|
|
{
|
|
return ToolResult.Fail("Key '" + item.Key + "' not found");
|
|
}
|
|
value = value2;
|
|
}
|
|
}
|
|
JsonValueKind valueKind = value.ValueKind;
|
|
if (1 == 0)
|
|
{
|
|
}
|
|
string text = valueKind switch
|
|
{
|
|
JsonValueKind.String => value.GetString() ?? "",
|
|
JsonValueKind.Number => value.GetRawText(),
|
|
JsonValueKind.True => "true",
|
|
JsonValueKind.False => "false",
|
|
JsonValueKind.Null => "null",
|
|
_ => JsonSerializer.Serialize(value, new JsonSerializerOptions
|
|
{
|
|
WriteIndented = true
|
|
}),
|
|
};
|
|
if (1 == 0)
|
|
{
|
|
}
|
|
string text2 = text;
|
|
if (text2.Length > 5000)
|
|
{
|
|
text2 = text2.Substring(0, 5000) + "\n... (truncated)";
|
|
}
|
|
return ToolResult.Ok(text2);
|
|
}
|
|
|
|
private static ToolResult Keys(string json)
|
|
{
|
|
using JsonDocument jsonDocument = JsonDocument.Parse(json);
|
|
if (jsonDocument.RootElement.ValueKind != JsonValueKind.Object)
|
|
{
|
|
return ToolResult.Fail("Root element is not an object");
|
|
}
|
|
IEnumerable<string> values = jsonDocument.RootElement.EnumerateObject().Select(delegate(JsonProperty p)
|
|
{
|
|
JsonValueKind valueKind = p.Value.ValueKind;
|
|
if (1 == 0)
|
|
{
|
|
}
|
|
string text;
|
|
switch (valueKind)
|
|
{
|
|
case JsonValueKind.Object:
|
|
text = "object";
|
|
break;
|
|
case JsonValueKind.Array:
|
|
text = $"array[{p.Value.GetArrayLength()}]";
|
|
break;
|
|
case JsonValueKind.String:
|
|
text = "string";
|
|
break;
|
|
case JsonValueKind.Number:
|
|
text = "number";
|
|
break;
|
|
case JsonValueKind.True:
|
|
case JsonValueKind.False:
|
|
text = "boolean";
|
|
break;
|
|
default:
|
|
text = "null";
|
|
break;
|
|
}
|
|
if (1 == 0)
|
|
{
|
|
}
|
|
string text2 = text;
|
|
return " " + p.Name + ": " + text2;
|
|
});
|
|
return ToolResult.Ok($"Keys ({jsonDocument.RootElement.EnumerateObject().Count()}):\n{string.Join("\n", values)}");
|
|
}
|
|
|
|
private static ToolResult Convert(string json, string targetFormat)
|
|
{
|
|
if (targetFormat != "csv")
|
|
{
|
|
return ToolResult.Fail("Unsupported target format: " + targetFormat);
|
|
}
|
|
using JsonDocument jsonDocument = JsonDocument.Parse(json);
|
|
if (jsonDocument.RootElement.ValueKind != JsonValueKind.Array)
|
|
{
|
|
return ToolResult.Fail("JSON must be an array for CSV conversion");
|
|
}
|
|
JsonElement rootElement = jsonDocument.RootElement;
|
|
if (rootElement.GetArrayLength() == 0)
|
|
{
|
|
return ToolResult.Ok("(empty array)");
|
|
}
|
|
List<string> list = new List<string>();
|
|
foreach (JsonElement item2 in rootElement.EnumerateArray())
|
|
{
|
|
if (item2.ValueKind != JsonValueKind.Object)
|
|
{
|
|
return ToolResult.Fail("All array items must be objects for CSV conversion");
|
|
}
|
|
foreach (JsonProperty item3 in item2.EnumerateObject())
|
|
{
|
|
if (!list.Contains(item3.Name))
|
|
{
|
|
list.Add(item3.Name);
|
|
}
|
|
}
|
|
}
|
|
StringBuilder stringBuilder = new StringBuilder();
|
|
stringBuilder.AppendLine(string.Join(",", list.Select((string k) => "\"" + k + "\"")));
|
|
foreach (JsonElement item in rootElement.EnumerateArray())
|
|
{
|
|
JsonElement value;
|
|
IEnumerable<string> values = list.Select((string k) => (!item.TryGetProperty(k, out value)) ? "\"\"" : ((value.ValueKind == JsonValueKind.String) ? ("\"" + value.GetString()?.Replace("\"", "\"\"") + "\"") : value.GetRawText()));
|
|
stringBuilder.AppendLine(string.Join(",", values));
|
|
}
|
|
string text = stringBuilder.ToString();
|
|
if (text.Length > 8000)
|
|
{
|
|
text = text.Substring(0, 8000) + "\n... (truncated)";
|
|
}
|
|
return ToolResult.Ok(text);
|
|
}
|
|
|
|
private static List<PathSegment> ParsePath(string path)
|
|
{
|
|
List<PathSegment> list = new List<PathSegment>();
|
|
string[] array = path.Split('.');
|
|
foreach (string text in array)
|
|
{
|
|
int num = text.IndexOf('[');
|
|
if (num >= 0)
|
|
{
|
|
string text2 = text.Substring(0, num);
|
|
if (!string.IsNullOrEmpty(text2))
|
|
{
|
|
list.Add(new PathSegment(text2, 0, IsIndex: false));
|
|
}
|
|
string text3 = text;
|
|
int num2 = num + 1;
|
|
string s = text3.Substring(num2, text3.Length - num2).TrimEnd(']');
|
|
if (int.TryParse(s, out var result))
|
|
{
|
|
list.Add(new PathSegment("", result, IsIndex: true));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
list.Add(new PathSegment(text, 0, IsIndex: false));
|
|
}
|
|
}
|
|
return list;
|
|
}
|
|
}
|