Skip to content
Open
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

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion agents/Aevatar.GAgents.NyxidChat/Skills/system-prompt.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ Delegate a natural-language task to Codex. Use `managed_sandbox` for the fixed i
In an unprofiled turn where this broad tool is present, discover live proxyable services before choosing a slug, then make authenticated requests through NyxID.

### NyxID connected-service tools
When present, `nyxid_service_inventory` is a read-only current-caller inventory capability. Request-local `nyxop_*` tools are separately admitted exact connected-service operations; use only the arguments in each tool's frozen schema. Never substitute a display slug, catalog id, label, endpoint id, remembered value, or inventory result for an operation selector.
When present, `nyxid_service_inventory` is a read-only current-caller inventory capability. If an inventory instance contains `recommended_skill_refs`, load exactly one needed ref with `nyxid_load_recommended_skill` by copying `user_service_id`, `source`, `skill_id`, `literal_version`, and `manifest_digest` from that inventory result; do not load by display name or latest version. Request-local `nyxop_*` tools are separately admitted exact connected-service operations; use only the arguments in each tool's frozen schema. Never substitute a display slug, catalog id, label, endpoint id, remembered value, or inventory result for an operation selector.
For a read-only request asking which services the caller already has connected, answer with the inventory read present in the final request's tool schemas: when `nyxid_service_inventory` is present, follow the System Skill Overlay's catalog/service-inspection procedure; when it is absent, use a read-only management read such as `nyxid_services`. If inventory returns `NYXID_SERVICE_INVENTORY_CREDENTIAL_DENIED`, report that the credential configuration must be corrected before retrying. For transient inventory failures, report a temporary read failure. Do not claim that the binding is absent or recommend `/init` unless the binding is explicitly missing or revoked.

### `nyxid_require_service` — Report a missing connection
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ NyxID service procedures and Ornn user manuals live on the Ornn skill platform,

Everything in this section presumes the named tools appear in the current request's tool schemas. If this turn exposes no tool schemas at all, none of it applies: never write tool-call syntax such as `use_skill(...)` into your reply as text, say plainly that no tools are available in this turn, and answer only from context.

For a read-only request asking which services the caller already has connected, answer with the inventory read present in the final request's tool schemas. When `nyxid_service_inventory` is present, route the read through the catalog/service-inspection path: first call `use_skill(skill="nyxid-service-discovery")`, then call `nyxid_service_inventory`. This route establishes current sender-specific service facts; execution tools only run supplied work and cannot establish that inventory. The loaded skill supplies current NyxID semantics; treat the typed inventory result as the authority for the current sender. When `nyxid_service_inventory` is absent, answer from the read-only NyxID management read that is present instead, such as `nyxid_services`, without chasing the missing inventory tool. If inventory returns `NYXID_SERVICE_INVENTORY_CREDENTIAL_DENIED`, report that the credential configuration must be corrected before retrying. For transient inventory failures, report a temporary read failure. Do not claim that the binding is absent or recommend `/init` unless the binding is explicitly missing or revoked.
For a read-only request asking which services the caller already has connected, answer with the inventory read present in the final request's tool schemas. When `nyxid_service_inventory` is present, route the read through the catalog/service-inspection path: first call `use_skill(skill="nyxid-service-discovery")`, then call `nyxid_service_inventory`. This route establishes current sender-specific service facts; execution tools only run supplied work and cannot establish that inventory. If the selected service exposes `recommended_skill_refs` and `nyxid_load_recommended_skill` is present, load exactly one needed ref by copying the exact `user_service_id`, `source`, `skill_id`, `literal_version`, and `manifest_digest` from inventory. Treat that loaded main document as operation guidance only; execute reads through admitted connected-service tools and their frozen schemas. The loaded skill supplies current NyxID semantics; treat the typed inventory result as the authority for the current sender. When `nyxid_service_inventory` is absent, answer from the read-only NyxID management read that is present instead, such as `nyxid_services`, without chasing the missing inventory tool. If inventory returns `NYXID_SERVICE_INVENTORY_CREDENTIAL_DENIED`, report that the credential configuration must be corrected before retrying. For transient inventory failures, report a temporary read failure. Do not claim that the binding is absent or recommend `/init` unless the binding is explicitly missing or revoked.

`nyxid_require_service` readiness distinguishes two states that must never be conflated: `USER_SERVICE_NOT_VISIBLE` means the service is genuinely not connected and a connect journey is required; `USER_SERVICE_ACCESS_REQUIRED` means the service **is already connected** and only this chat session's one-time authorization is missing — tell the user the service is connected, say the pending step is a service access review approval, and never ask them to connect the service again.

Expand Down
6 changes: 3 additions & 3 deletions docs/contracts/nyxid-assistant-conformance/v1/sources.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
"schema_version": 1,
"aevatar": {
"repository": "https://github.com/AevatarAI/aevatar.git",
"revision": "78e2490bc1ad548ed72f75061d8927f8cf599ac1",
"contract_files_sha256": "8ddfc263edf7bf71f25f23edcc9248a6afdfd4cdbf98a2717fadadd8c740c119",
"revision": "e3fde8e0fa7526d58c8e050fa2a9f71dfbb33349",
"contract_files_sha256": "fd36e48a371db5f74768b6c37d265db184a841feba53966ecf94add17bff1e4c",
"files": {
"agents/Aevatar.GAgents.NyxidChat/NyxIdActionPostconditionPort.cs": "7791de469b567dcde70a0f8e2a88cc818972ca557617a2538294e8ccabd5bda0",
"agents/Aevatar.GAgents.NyxidChat/NyxIdAssistantActionRegistry.cs": "60e6f67c94ae11b1bf0dac036ad8ac0c35901e31787b1f0c8173964f6a12d263",
Expand All @@ -17,7 +17,7 @@
"src/Aevatar.AI.ToolProviders.NyxId/NyxIdAssistantToolSource.cs": "e99f2de69d0eb9e0b9dc235e2d568fc66d9dfb79cb0bb1364e01cc210a8c626f",
"src/Aevatar.AI.ToolProviders.NyxId/Tools/NyxIdRequestKeyCreateTool.cs": "2c4f2cda99154f2e667c6cfd291497e697ef11df17f081f96ec70070a8af8b8c",
"src/Aevatar.AI.ToolProviders.NyxId/Tools/NyxIdRequestKeyRotateTool.cs": "18212bb64644cfbca401065bccce439ea5fa00316deff57d730a0d9ac2650e53",
"src/Aevatar.Mainnet.Host.Api/Hosting/MainnetHostBuilderExtensions.cs": "a6faf5a532b43dd1e1cf56cfdf5a05f473d755073e84d86ea459e4acec7828bd"
"src/Aevatar.Mainnet.Host.Api/Hosting/MainnetHostBuilderExtensions.cs": "030451d96b83849a3dbd0f2c8159f5c4dd72ad79629be4d2183cd9d9fa88d98e"
}
},
"nyxid": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -321,9 +321,66 @@ private static IReadOnlyList<NyxIdServiceInstanceBinding> ParseBindings(
instance.OpenapiSpecUrl = openApiSpecUrl;
if (nodeId is not null)
instance.NodeId = nodeId;
instance.RecommendedSkillRefs.Add(ParseRecommendedSkillRefs(item));
return new NyxIdServiceInstanceBinding(instance, token);
}

private static IReadOnlyList<NyxIdRecommendedSkillRef> ParseRecommendedSkillRefs(JsonElement item)
{
if (!item.TryGetProperty("recommended_skill_refs", out var refsElement) || refsElement.ValueKind == JsonValueKind.Null)
return [];
if (refsElement.ValueKind != JsonValueKind.Array)
throw new NyxIdServiceInventoryContractException();

var refs = new List<NyxIdRecommendedSkillRef>();
foreach (var refElement in refsElement.EnumerateArray())
{
var skillRef = ParseRecommendedSkillRef(refElement)
?? throw new NyxIdServiceInventoryContractException();
refs.Add(skillRef);
}

return refs;
}

private static NyxIdRecommendedSkillRef? ParseRecommendedSkillRef(JsonElement item)
{
if (item.ValueKind != JsonValueKind.Object)
return null;
if (!TryParseRecommendedSkillSource(ReadString(item, "source") ?? ReadString(item, "provider"), out var source))
return null;
var skillId = ReadString(item, "skill_id") ?? ReadString(item, "id") ?? ReadString(item, "guid");
var literalVersion = ReadString(item, "literal_version") ?? ReadString(item, "version");
var manifestDigest = ReadString(item, "manifest_digest") ?? ReadString(item, "digest") ?? ReadString(item, "skill_hash");
if (string.IsNullOrWhiteSpace(skillId) ||
string.IsNullOrWhiteSpace(literalVersion) ||
string.IsNullOrWhiteSpace(manifestDigest))
{
return null;
}

return new NyxIdRecommendedSkillRef
{
Source = source,
SkillId = skillId.Trim(),
LiteralVersion = literalVersion.Trim(),
ManifestDigest = manifestDigest.Trim(),
DisplayName = ReadString(item, "display_name") ?? ReadString(item, "name") ?? string.Empty,
RecommendationName = ReadString(item, "recommendation_name") ?? ReadString(item, "recommended_name") ?? string.Empty,
Revision = ReadString(item, "revision") ?? string.Empty,
};
}

private static bool TryParseRecommendedSkillSource(string? value, out NyxIdRecommendedSkillSource source)
{
source = value switch
{
"ornn" => NyxIdRecommendedSkillSource.Ornn,
_ => NyxIdRecommendedSkillSource.Unspecified,
};
return source != NyxIdRecommendedSkillSource.Unspecified;
}

private static bool TryReadNodeId(JsonElement item, out string? nodeId)
{
nodeId = null;
Expand Down Expand Up @@ -445,6 +502,7 @@ private static bool SameAuthority(NyxIdServiceInstance left, NyxIdServiceInstanc
string.Equals(left.EndpointUrl, right.EndpointUrl, StringComparison.Ordinal) &&
string.Equals(left.OpenapiSpecUrl, right.OpenapiSpecUrl, StringComparison.Ordinal) &&
string.Equals(left.NodeId, right.NodeId, StringComparison.Ordinal) &&
left.RecommendedSkillRefs.SequenceEqual(right.RecommendedSkillRefs) &&
Equals(left.CallerExecutionReadiness, right.CallerExecutionReadiness) &&
Equals(left.RouteConstraint, right.RouteConstraint);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,21 @@ enum NyxIdServiceNodeStatus {
NYX_ID_SERVICE_NODE_STATUS_INACCESSIBLE = 6;
}

enum NyxIdRecommendedSkillSource {
NYX_ID_RECOMMENDED_SKILL_SOURCE_UNSPECIFIED = 0;
NYX_ID_RECOMMENDED_SKILL_SOURCE_ORNN = 1;
}

message NyxIdRecommendedSkillRef {
NyxIdRecommendedSkillSource source = 1;
string skill_id = 2;
string literal_version = 3;
string manifest_digest = 4;
string display_name = 5;
string recommendation_name = 6;
string revision = 7;
}

message NyxIdServiceCallerExecutionReadiness {
NyxIdServiceCredentialStatus credential_status = 1;
bool connected = 2;
Expand Down Expand Up @@ -67,6 +82,7 @@ message NyxIdServiceInstance {
optional string catalog_service_slug = 14;
NyxIdServiceCallerExecutionReadiness caller_execution_readiness = 15;
optional string openapi_spec_url = 16;
repeated NyxIdRecommendedSkillRef recommended_skill_refs = 17;
}

message NyxIdServiceUpdateRequest {
Expand Down
31 changes: 31 additions & 0 deletions test/Aevatar.AI.Tests/NyxIdServiceInstanceClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,37 @@ await act.Should().ThrowAsync<NyxIdServiceInventoryContractException>()
.WithMessage("NYXID_SERVICE_INVENTORY_CONTRACT_INVALID");
}

[Fact]
public async Task ReadAsync_RecommendedSkillRefs_MapsExactOrnnReferenceWithoutCatalogBackfill()
{
var handler = new InventoryHandler();
handler.KeysByToken["user-token"] = Keys("""
{"id":"us-personal","slug":"calendar","catalog_service_id":"catalog-calendar",
"catalog_service_slug":"api-calendar","is_active":true,"connected":true,"status":"active",
"credential_source":{"type":"personal"},
"recommended_skill_refs":[{
"source":"ornn",
"skill_id":"11111111-1111-1111-1111-111111111111",
"literal_version":"1.2",
"manifest_digest":"sha256:000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
"display_name":"Calendar Reader",
"recommendation_name":"read-calendar-events",
"revision":"rev-1"
}]}
""");

var result = await CreateReader(handler).ReadAsync("user-token", organizationToken: null);

var skillRef = result.Instances.Should().ContainSingle().Subject.RecommendedSkillRefs
.Should().ContainSingle().Subject;
skillRef.Source.Should().Be(NyxIdRecommendedSkillSource.Ornn);
skillRef.SkillId.Should().Be("11111111-1111-1111-1111-111111111111");
skillRef.LiteralVersion.Should().Be("1.2");
skillRef.ManifestDigest.Should().StartWith("sha256:");
skillRef.RecommendationName.Should().Be("read-calendar-events");
skillRef.Revision.Should().Be("rev-1");
}

[Fact]
public async Task ReadAsync_GenuineEmptyKeys_ReturnsEmptyInventory()
{
Expand Down
Loading
Loading