Files
AX-Copilot-Codex/.decompiledproj/AxCopilot/Services/Agent/HttpTool.cs

226 lines
6.6 KiB
C#

using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
namespace AxCopilot.Services.Agent;
public class HttpTool : IAgentTool
{
private static readonly HttpClient _client = new HttpClient
{
Timeout = TimeSpan.FromSeconds(30.0)
};
public string Name => "http_tool";
public string Description => "Make HTTP requests to local or internal APIs. Supports GET, POST, PUT, DELETE methods with JSON body and custom headers. Only allows localhost and internal network addresses (security restriction). Use this for testing APIs, fetching data from internal services, or webhooks.";
public ToolParameterSchema Parameters
{
get
{
ToolParameterSchema toolParameterSchema = new ToolParameterSchema();
Dictionary<string, ToolProperty> dictionary = new Dictionary<string, ToolProperty>();
ToolProperty obj = new ToolProperty
{
Type = "string",
Description = "HTTP method"
};
int num = 5;
List<string> list = new List<string>(num);
CollectionsMarshal.SetCount(list, num);
Span<string> span = CollectionsMarshal.AsSpan(list);
span[0] = "GET";
span[1] = "POST";
span[2] = "PUT";
span[3] = "DELETE";
span[4] = "PATCH";
obj.Enum = list;
dictionary["method"] = obj;
dictionary["url"] = new ToolProperty
{
Type = "string",
Description = "Request URL (localhost or internal network only)"
};
dictionary["body"] = new ToolProperty
{
Type = "string",
Description = "Request body (JSON string, for POST/PUT/PATCH)"
};
dictionary["headers"] = new ToolProperty
{
Type = "string",
Description = "Custom headers as JSON object, e.g. {\"Authorization\": \"Bearer token\"}"
};
dictionary["timeout"] = new ToolProperty
{
Type = "string",
Description = "Request timeout in seconds (default: 30, max: 120)"
};
toolParameterSchema.Properties = dictionary;
num = 2;
List<string> list2 = new List<string>(num);
CollectionsMarshal.SetCount(list2, num);
Span<string> span2 = CollectionsMarshal.AsSpan(list2);
span2[0] = "method";
span2[1] = "url";
toolParameterSchema.Required = list2;
return toolParameterSchema;
}
}
public async Task<ToolResult> ExecuteAsync(JsonElement args, AgentContext context, CancellationToken ct = default(CancellationToken))
{
string method = args.GetProperty("method").GetString()?.ToUpperInvariant() ?? "GET";
string url = args.GetProperty("url").GetString() ?? "";
JsonElement b;
string body = (args.TryGetProperty("body", out b) ? (b.GetString() ?? "") : "");
JsonElement h;
string headers = (args.TryGetProperty("headers", out h) ? (h.GetString() ?? "") : "");
JsonElement t;
int ts;
int timeout = ((!args.TryGetProperty("timeout", out t)) ? 30 : (int.TryParse(t.GetString(), out ts) ? Math.Min(ts, 120) : 30));
if (!IsAllowedHost(url))
{
return ToolResult.Fail("보안 제한: localhost, 127.0.0.1, 사내 네트워크(10.x, 172.16-31.x, 192.168.x)만 허용됩니다.");
}
try
{
HttpMethod httpMethod = new HttpMethod(method);
using HttpRequestMessage request = new HttpRequestMessage(httpMethod, url);
if (!string.IsNullOrEmpty(headers))
{
using JsonDocument headerDoc = JsonDocument.Parse(headers);
foreach (JsonProperty prop in headerDoc.RootElement.EnumerateObject())
{
request.Headers.TryAddWithoutValidation(prop.Name, prop.Value.GetString());
}
}
bool flag = !string.IsNullOrEmpty(body);
bool flag2 = flag;
if (flag2)
{
bool flag3;
switch (method)
{
case "POST":
case "PUT":
case "PATCH":
flag3 = true;
break;
default:
flag3 = false;
break;
}
flag2 = flag3;
}
if (flag2)
{
request.Content = new StringContent(body, Encoding.UTF8, "application/json");
}
using CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
cts.CancelAfter(TimeSpan.FromSeconds(timeout));
using HttpResponseMessage response = await _client.SendAsync(request, cts.Token);
int statusCode = (int)response.StatusCode;
string responseBody = await response.Content.ReadAsStringAsync(cts.Token);
StringBuilder sb = new StringBuilder();
StringBuilder stringBuilder = sb;
StringBuilder stringBuilder2 = stringBuilder;
StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(6, 2, stringBuilder);
handler.AppendLiteral("HTTP ");
handler.AppendFormatted(statusCode);
handler.AppendLiteral(" ");
handler.AppendFormatted(response.ReasonPhrase);
stringBuilder2.AppendLine(ref handler);
stringBuilder = sb;
StringBuilder stringBuilder3 = stringBuilder;
handler = new StringBuilder.AppendInterpolatedStringHandler(14, 1, stringBuilder);
handler.AppendLiteral("Content-Type: ");
handler.AppendFormatted(response.Content.Headers.ContentType);
stringBuilder3.AppendLine(ref handler);
sb.AppendLine();
MediaTypeHeaderValue? contentType = response.Content.Headers.ContentType;
if (contentType != null && contentType.MediaType?.Contains("json") == true)
{
try
{
using JsonDocument doc = JsonDocument.Parse(responseBody);
responseBody = JsonSerializer.Serialize(doc.RootElement, new JsonSerializerOptions
{
WriteIndented = true
});
}
catch
{
}
}
if (responseBody.Length > 8000)
{
responseBody = responseBody.Substring(0, 8000) + $"\n... (truncated, total {responseBody.Length} chars)";
}
sb.Append(responseBody);
return ToolResult.Ok(sb.ToString());
}
catch (TaskCanceledException)
{
return ToolResult.Fail($"요청 시간 초과 ({timeout}초)");
}
catch (Exception ex2)
{
return ToolResult.Fail("HTTP 요청 실패: " + ex2.Message);
}
}
private static bool IsAllowedHost(string url)
{
if (!Uri.TryCreate(url, UriKind.Absolute, out Uri result))
{
return false;
}
string host = result.Host;
bool flag;
switch (host)
{
case "localhost":
case "127.0.0.1":
case "::1":
flag = true;
break;
default:
flag = false;
break;
}
if (flag)
{
return true;
}
if (IPAddress.TryParse(host, out IPAddress address))
{
byte[] addressBytes = address.GetAddressBytes();
if (addressBytes.Length == 4)
{
if (addressBytes[0] == 10)
{
return true;
}
if (addressBytes[0] == 172 && addressBytes[1] >= 16 && addressBytes[1] <= 31)
{
return true;
}
if (addressBytes[0] == 192 && addressBytes[1] == 168)
{
return true;
}
}
}
return false;
}
}