Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -780,7 +780,8 @@ private async Task<AgentToolTerminalOutcome> ExecuteOperationWithContextAsync(
arguments.ServiceSlug,
arguments.OperationId,
arguments.OperationArgumentsJson,
arguments.DocumentRequest),
arguments.DocumentRequest,
arguments.RawRequest),
callId,
"nyxid_invoke_operation",
ct)
Expand Down Expand Up @@ -1029,8 +1030,11 @@ literalVersion is null || manifestDigest is null ||

foreach (var property in root.EnumerateObject())
{
if (property.Name is not ("user_service_id" or "service_slug" or "operation_id" or "operation_arguments" or "document_request"))
if (property.Name is not ("user_service_id" or "service_slug" or "operation_id" or "operation_arguments" or
"document_request" or "method" or "relative_path" or "query" or "headers" or "body"))
{
return null;
}
}

var userServiceId = ReadOptionalString(root, "user_service_id");
Expand All @@ -1040,9 +1044,14 @@ literalVersion is null || manifestDigest is null ||

var hasTypedOperation = root.TryGetProperty("operation_id", out _);
var hasDocumentRequest = root.TryGetProperty("document_request", out var documentRequestElement);
if (hasTypedOperation == hasDocumentRequest)
var hasRawRequest = root.TryGetProperty("method", out _) ||
root.TryGetProperty("relative_path", out _) ||
root.TryGetProperty("query", out _) ||
root.TryGetProperty("headers", out _) ||
root.TryGetProperty("body", out _);
if ((hasTypedOperation ? 1 : 0) + (hasDocumentRequest ? 1 : 0) + (hasRawRequest ? 1 : 0) != 1)
return null;
if (hasDocumentRequest && root.TryGetProperty("operation_arguments", out _))
if ((hasDocumentRequest || hasRawRequest) && root.TryGetProperty("operation_arguments", out _))
return null;

if (hasDocumentRequest)
Expand All @@ -1055,7 +1064,22 @@ literalVersion is null || manifestDigest is null ||
serviceSlug,
OperationId: null,
OperationArgumentsJson: "{}",
documentRequest);
documentRequest,
RawRequest: null);
}

if (hasRawRequest)
{
var rawRequest = ParseRawRequest(root);
return rawRequest is null
? null
: new OperationArguments(
userServiceId,
serviceSlug,
OperationId: null,
OperationArgumentsJson: "{}",
DocumentRequest: null,
rawRequest);
}

var operationId = ReadRequiredString(root, "operation_id");
Expand All @@ -1075,14 +1099,20 @@ literalVersion is null || manifestDigest is null ||
serviceSlug,
operationId,
operationArgumentsJson,
DocumentRequest: null);
DocumentRequest: null,
RawRequest: null);
}
catch (JsonException)
{
return null;
}
}

private static NyxIdConnectedServiceRawRequest? ParseRawRequest(JsonElement root) =>
TryReadAuthoredRequest(root, out var method, out var relativePath, out var requestArgumentsJson)
? new NyxIdConnectedServiceRawRequest(method, relativePath, requestArgumentsJson)
: null;

private static NyxIdConnectedServiceDocumentRequest? ParseDocumentRequest(JsonElement documentRequest)
{
if (documentRequest.ValueKind != JsonValueKind.Object)
Expand All @@ -1093,40 +1123,58 @@ literalVersion is null || manifestDigest is null ||
return null;
}

var method = ReadRequiredString(documentRequest, "method");
var relativePath = ReadRequiredString(documentRequest, "relative_path");
if (method is null || relativePath is null ||
!documentRequest.TryGetProperty("skill_ref", out var skillRefElement) ||
!TryReadDocumentSkillRef(skillRefElement, out var skillRef))
if (!documentRequest.TryGetProperty("skill_ref", out var skillRefElement) ||
!TryReadDocumentSkillRef(skillRefElement, out var skillRef) ||
!TryReadAuthoredRequest(
documentRequest,
out var method,
out var relativePath,
out var requestArgumentsJson))
{
return null;
}

return new NyxIdConnectedServiceDocumentRequest(
method,
relativePath,
requestArgumentsJson,
skillRef);
}

private static bool TryReadAuthoredRequest(
JsonElement root,
out string method,
out string relativePath,
out string requestArgumentsJson)
{
method = ReadRequiredString(root, "method") ?? string.Empty;
relativePath = ReadRequiredString(root, "relative_path") ?? string.Empty;
requestArgumentsJson = string.Empty;
if (method.Length == 0 || relativePath.Length == 0)
return false;

var runtimeArguments = new Dictionary<string, JsonElement>(StringComparer.Ordinal);
if (documentRequest.TryGetProperty("query", out var query))
if (root.TryGetProperty("query", out var query))
{
if (query.ValueKind != JsonValueKind.Object)
return null;
return false;
runtimeArguments["query"] = query;
}
if (documentRequest.TryGetProperty("headers", out var headers))
if (root.TryGetProperty("headers", out var headers))
{
if (headers.ValueKind != JsonValueKind.Object)
return null;
return false;
runtimeArguments["headers"] = headers;
}
if (documentRequest.TryGetProperty("body", out var body))
if (root.TryGetProperty("body", out var body))
{
if (body.ValueKind is not (JsonValueKind.Object or JsonValueKind.Null))
return null;
return false;
runtimeArguments["body"] = body;
}

return new NyxIdConnectedServiceDocumentRequest(
method,
relativePath,
JsonSerializer.Serialize(runtimeArguments),
skillRef);
requestArgumentsJson = JsonSerializer.Serialize(runtimeArguments);
return true;
}

private static bool TryReadDocumentSkillRef(JsonElement root, out NyxIdRecommendedSkillRef skillRef)
Expand Down Expand Up @@ -1400,7 +1448,8 @@ private sealed record OperationArguments(
string? ServiceSlug,
string? OperationId,
string OperationArgumentsJson,
NyxIdConnectedServiceDocumentRequest? DocumentRequest);
NyxIdConnectedServiceDocumentRequest? DocumentRequest,
NyxIdConnectedServiceRawRequest? RawRequest);

private sealed record RecommendedSkillArguments(
string UserServiceId,
Expand All @@ -1423,6 +1472,11 @@ private sealed class SenderConnectedServiceOperationTool(ChannelNyxIdConnectedSe
"service_slug":{"type":"string","description":"Exact connected-service slug from inventory or channel runtime selectors."},
"operation_id":{"type":"string","description":"Exact endpoint or operation id named by the loaded recommended skill for typed operation mode."},
"operation_arguments":{"type":"object","description":"Typed mode only. Only path_params, query, headers, body, and response_mode values declared by the operation contract.","additionalProperties":true},
"method":{"type":"string","description":"Raw delegated mode only. HTTP method for a service-relative request when operation_id is omitted.","enum":["GET","HEAD","OPTIONS","POST","PUT","PATCH","DELETE"]},
"relative_path":{"type":"string","description":"Raw delegated mode only. Safe relative service path. Absolute URLs, query strings, fragments, and traversal are rejected."},
"query":{"type":"object","description":"Raw delegated mode only. Query string values for the service-relative request.","additionalProperties":{"type":"string"}},
"headers":{"type":"object","description":"Raw delegated mode only. Non-sensitive headers. Authorization, Host, cookies, API keys, and tokens are rejected.","additionalProperties":{"type":"string"}},
"body":{"type":"object","description":"Raw delegated mode only. JSON object body for methods that allow a body.","additionalProperties":true},
"document_request":{
"type":"object",
"description":"Document-guided mode only. Use only when the loaded recommended skill explicitly describes a service without typed operations. This is not a fallback for a missing operation_id.",
Expand Down Expand Up @@ -1450,14 +1504,15 @@ private sealed class SenderConnectedServiceOperationTool(ChannelNyxIdConnectedSe
}
},
"anyOf":[{"required":["user_service_id"]},{"required":["service_slug"]}],
"oneOf":[{"required":["operation_id"]},{"required":["document_request"]}],
"oneOf":[{"required":["operation_id"]},{"required":["document_request"]},{"required":["method","relative_path"]}],
"additionalProperties":false
}
""";

public string Name => "nyxid_invoke_operation";
public string Description =>
"Invoke one current NyxID connected-service request selected by exact service identity and either a typed operation id or an explicit document-guided request.";
"Invoke one current NyxID connected-service request selected by exact service identity. " +
"Use a typed operation id or document guidance when available; otherwise provide a service-relative delegated request.";
public string ParametersSchema => Schema;
public bool IsReadOnly => false;
public bool IsDestructive => false;
Expand Down
Loading
Loading