Skip to content

Align core data serializers and repository interface with Python SDK#9

Merged
Cicatriiz merged 2 commits into
mainfrom
codex/fix-github-issue-number-4
Sep 16, 2025
Merged

Align core data serializers and repository interface with Python SDK#9
Cicatriiz merged 2 commits into
mainfrom
codex/fix-github-issue-number-4

Conversation

@Cicatriiz

@Cicatriiz Cicatriiz commented Sep 15, 2025

Copy link
Copy Markdown
Owner

Summary

  • add serializer infrastructure plus auth, call template, JSON schema, tool, and manual serializers that mirror the Python SDK behaviour
  • extend the concurrent tool repository interface and in-memory implementation with parity methods and ensure call templates self-register for deserialization
  • add unit tests for serializer round-trips and repository APIs and reference the HTTP project in the test suite

Testing

  • not run (dotnet CLI not available in the execution environment)

https://chatgpt.com/codex/tasks/task_e_68c85f8aafa8832291a5cbaa72f01c88

Summary by CodeRabbit

  • New Features

    • Streamable HTTP calls support Body, Timeout, and configurable ContentType (default: text/event-stream).
    • Text calls gain an EnsureUnderRoot option.
    • Repository exposes async methods to get/remove tools and manuals and list tools by manual.
  • Improvements

    • New serialization layer with dedicated serializers and consistent JSON key mapping for schemas (e.g., $schema, $id, minLength, additionalProperties); improved round-trip reliability.
  • Tests

    • New unit tests for repository operations and serialization parity.

@coderabbitai

coderabbitai Bot commented Sep 15, 2025

Copy link
Copy Markdown

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds a dictionary-based Serializer runtime and specific model serializers; annotates JsonSchema JSON names; expands IConcurrentToolRepository with async getters and removal and implements them in InMemToolRepository; moves call-template registrations to static constructors across templates; extends some templates; adds unit tests and a test project reference.

Changes

Cohort / File(s) Summary
Serializer infrastructure
src/Utcp.Core/Serialization/Serializer.cs, src/Utcp.Core/Models/Serialization/ModelSerializers.cs
Adds abstract Serializer with Runtime helpers (dictionary ⇄ JSON, converters) and concrete serializers: AuthSerializer, CallTemplateSerializer, JsonSchemaSerializer, ToolSerializer, UtcpManualSerializer implementing ValidateDictionary/ToDictionary and discriminator handling via PolymorphicRegistry.
JsonSchema JSON annotations
src/Utcp.Core/Models/Tool.cs
Adds [JsonPropertyName] attributes mapping Schema->$schema, Id->$id, AdditionalProperties->additionalProperties, MinLength->minLength, MaxLength->maxLength.
Repository interface expansion
src/Utcp.Core/Interfaces/IConcurrentToolRepository.cs
Adds async methods: RemoveToolAsync, GetManualCallTemplateAsync, GetManualAsync, GetToolAsync, GetToolsByManualAsync.
In-memory repo implementations
src/Utcp.Core/Repositories/InMemToolRepository.cs
Implements new methods: removal from lookup and pruning of manuals under write lock; getters for manual template, manual, tool, and tools-by-manual.
CallTemplate static registration (multiple templates)
src/Utcp.Cli/CliCallTemplate.cs, src/Utcp.Gql/GraphQlCallTemplate.cs, src/Utcp.Http/HttpCallTemplate.cs, src/Utcp.Mcp/McpCallTemplate.cs, src/Utcp.Socket/TcpCallTemplate.cs, src/Utcp.Socket/UdpCallTemplate.cs, src/Utcp.Text/TextCallTemplate.cs
Moves PolymorphicRegistry.RegisterCallTemplateDerivedType(...) from instance constructors to static constructors for each template; removes per-instance registration calls.
Streamable HTTP template additions
src/Utcp.Http/StreamableHttpCallTemplate.cs
Moves registration to static constructor; adds public init properties: Body (object?), Timeout (TimeSpan?), ContentType (string, default "text/event-stream").
Text template addition
src/Utcp.Text/TextCallTemplate.cs
Moves registration to static constructor; adds public bool EnsureUnderRoot { get; init; } = true.
In-memory repo tests
tests/Utcp.Core.Tests/InMemToolRepositoryTests.cs
Adds tests covering RemoveToolAsync behavior, GetToolAsync, GetManualAsync, GetManualCallTemplateAsync, and GetToolsByManualAsync null case.
Serialization parity tests
tests/Utcp.Core.Tests/SerializationParityTests.cs
Adds tests validating round-trip dictionary serialization/deserialization for Auth, CallTemplate (HTTP), JsonSchema (aliases), Tool (nested), and UtcpManual.
Test project reference update
tests/Utcp.Core.Tests/Utcp.Core.Tests.csproj
Adds ProjectReference to src/Utcp.Http/Utcp.Http.csproj and consolidates ItemGroup.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant C as Caller
  participant S as Serializer<T>
  participant R as Runtime
  participant P as PolymorphicRegistry

  rect rgb(240,248,255)
  note over C,S: ToDictionary flow
  C->>S: ToDictionary(obj)
  S->>R: ToDictionary(obj)
  R-->>S: Dictionary<string, object?>
  S-->>C: Dictionary<string, object?>
  end

  rect rgb(248,240,255)
  note over C,S: ValidateDictionary flow (polymorphic)
  C->>S: ValidateDictionary(dict)
  alt Requires discriminator
    S->>P: Resolve type from discriminator
    P-->>S: Concrete Type
    S->>R: Deserialize(Type, dict)
  else Concrete type
    S->>R: Deserialize<T>(dict)
  end
  R-->>S: Instance
  S-->>C: T
  end
Loading
sequenceDiagram
  autonumber
  participant U as User
  participant Repo as InMemToolRepository
  participant L as writeLock
  participant T as toolsByName
  participant M as manualsByName

  U->>Repo: RemoveToolAsync(toolName)
  Repo->>L: Enter
  Repo->>T: TryRemove(toolName)
  alt Tool found
    Repo->>M: ForEach(manual) remove tool by name
    M-->>Repo: Updated manuals (where changed)
    Repo-->>U: true
  else Tool missing
    Repo-->>U: false
  end
  Repo->>L: Exit
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

A rabbit taps keys with a twitch of delight,
New serializers hop into tidy byte-light,
Repos fetch carrots (tools) by name on cue,
Static burrows register once—not anew,
JSON fields sparkle with dollar-signed flair. 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title accurately and concisely reflects the primary work in the changeset—adding core serializers and aligning the repository interface with the Python SDK—and matches the PR objectives and file summaries describing serializer infrastructure, specific serializers, and repository/API extensions. It is clear, specific, and relevant to the main changes without including unnecessary detail.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch codex/fix-github-issue-number-4

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1642345 and 0691621.

📒 Files selected for processing (2)
  • tests/Utcp.Core.Tests/InMemToolRepositoryTests.cs (1 hunks)
  • tests/Utcp.Core.Tests/SerializationParityTests.cs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/Utcp.Core.Tests/InMemToolRepositoryTests.cs
  • tests/Utcp.Core.Tests/SerializationParityTests.cs
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: build-test-pack (windows-latest)
  • GitHub Check: Cursor Bugbot
  • GitHub Check: build-test-pack (windows-latest)

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

cursor[bot]

This comment was marked as outdated.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (23)
tests/Utcp.Core.Tests/Utcp.Core.Tests.csproj (1)

13-18: Mark test-only packages as PrivateAssets to avoid leaking to consumers.

Prevents transitive flow of test deps.

Apply:

-    <PackageReference Include="coverlet.collector" Version="6.0.0" />
-    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
-    <PackageReference Include="xunit" Version="2.5.3" />
-    <PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" />
-    <PackageReference Include="FluentAssertions" Version="6.12.0" />
+    <PackageReference Include="coverlet.collector" Version="6.0.0" PrivateAssets="all" />
+    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" PrivateAssets="all" />
+    <PackageReference Include="xunit" Version="2.5.3" PrivateAssets="all" />
+    <PackageReference Include="xunit.runner.visualstudio" Version="2.5.3">
+      <PrivateAssets>all</PrivateAssets>
+      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+    </PackageReference>
+    <PackageReference Include="FluentAssertions" Version="6.12.0" PrivateAssets="all" />
src/Utcp.Cli/CliCallTemplate.cs (1)

11-14: Same static registration concern as others—ensure types are initialized before deserialization.

Either add a bootstrap or module initializer, or guarantee tests/apps touch these types early.

src/Utcp.Gql/GraphQlCallTemplate.cs (1)

11-14: Registration timing + Uri handling in serializer.

  • Ensure GraphQlCallTemplate is initialized before “gql” deserialization occurs.
  • Confirm the serializer round-trips System.Uri losslessly (absolute vs relative, normalization). If not, serialize as string explicitly.
src/Utcp.Text/TextCallTemplate.cs (2)

11-14: Static registration timing—same note as other templates.

Ensure deterministic init or provide bootstrap.


19-19: New EnsureUnderRoot = true: clarify semantics and enforce consistently.

  • If this is a path traversal guard, ensure all text I/O honors it.
  • Consider a clearer name (RestrictToRoot/EnforceRoot) and document root resolution rules.

Do tests cover both true/false cases and path traversal edge cases (.., symlinks)?

src/Utcp.Socket/UdpCallTemplate.cs (2)

11-14: Static registration timing—same bootstrap consideration.


16-17: Validate UDP port range.

Add validation (1–65535) to avoid invalid templates at the edge.

Apply:

-    public int Port { get; init; }
+    [System.ComponentModel.DataAnnotations.Range(1, 65535)]
+    public int Port { get; init; }
src/Utcp.Http/HttpCallTemplate.cs (3)

12-15: Static registration timing—ensure deterministic init or bootstrap.

Same guidance as other templates.


6-6: Unused import.

System.Net.Http.Headers isn’t used here; remove to keep file tidy.

-using System.Net.Http.Headers;

17-22: Headers case-insensitivity and Body with GET.

  • Consider documenting that header keys should be treated case-insensitively; recommend callers pass Dictionary<string,string>(StringComparer.OrdinalIgnoreCase).
  • Body with default GET is unusual; ensure callers/tests expect this.
src/Utcp.Core/Models/Tool.cs (1)

19-41: JsonPropertyName attributes: STJ already imported; add JsonExtensionData to preserve unknown schema members.

  • System.Text.Json.Serialization is already globally imported (src/Utcp.Core/GlobalUsings.cs:12) — no local using needed.
  • Add a JsonExtensionData property to JsonSchema (src/Utcp.Core/Models/Tool.cs) to avoid losing unknown JSON members during round-trips; e.g.:
    [JsonExtensionData] public Dictionary<string, JsonElement> Extra { get; init; } = new();
src/Utcp.Mcp/McpCallTemplate.cs (1)

11-14: Make call‑template registration deterministic; registry is already concurrent

  • PolymorphicRegistry already uses ConcurrentDictionary — no Dictionary race. (src/Utcp.Core/Serialization/PolymorphicRegistry.cs)
  • Static self‑registration is still non‑deterministic: per‑type static ctors (e.g. src/Utcp.Mcp/McpCallTemplate.cs and other *CallTemplate.cs files) run only on first type access; deserializing a discriminator like "mcp" before the type is touched can miss the mapping. Fix by centralizing registration (PolymorphicRegistry static ctor), adding per‑assembly [ModuleInitializer]s, or performing a startup bootstrap (assembly scan or explicit type loads).
  • Optional: harden RegisterCallTemplateDerivedType to detect/throw on conflicting discriminator registrations instead of silently overwriting.
tests/Utcp.Core.Tests/InMemToolRepositoryTests.cs (1)

11-43: Unit test logic looks good; build currently blocked by required-member error from HttpCallTemplate.

Root cause is missing [SetsRequiredMembers] on the template’s ctor (see separate comments). As a temporary workaround (not preferred), you could set CallTemplateType explicitly in the initializer:

         var manualTemplate = new HttpCallTemplate
         {
             Name = "manual",
             Url = new Uri("https://api.example.com/utcp"),
+            CallTemplateType = "http",
         };
src/Utcp.Http/StreamableHttpCallTemplate.cs (1)

19-21: New properties are reasonable; minor naming nit.

For SSE, “text/event-stream” is typically sent as Accept rather than Content-Type on requests. Consider renaming to Accept or clarifying semantics in docs.

tests/Utcp.Core.Tests/SerializationParityTests.cs (2)

53-74: Numeric assertion type-safety: prefer 1L to match dictionary number shape.

Runtime converts integers to long (Int64). Using 1L avoids brittle boxing comparisons.

-        dict.Should().ContainKey("minLength").WhoseValue.Should().Be(1);
+        dict.Should().ContainKey("minLength").WhoseValue.Should().Be(1L);

106-134: Same CS9035 consideration for HttpCallTemplate here.

Prefer ctor attribute fix; fallback is to set the property explicitly:

         var callTemplate = new HttpCallTemplate
         {
             Name = "manual",
             Url = new Uri("https://api.example.com/tool"),
+            CallTemplateType = "http",
         };
src/Utcp.Core/Serialization/Serializer.cs (1)

97-110: Minor: numeric conversion policy.

Current order yields Int64 or Double, else Decimal. Document this in serializer remarks to set expectations for dictionary value types and avoid brittle tests.

src/Utcp.Core/Interfaces/IConcurrentToolRepository.cs (2)

19-19: Return empty collections instead of null for GetToolsByManualAsync.

Returning null forces callers into null-checks; an empty list communicates “no tools” unambiguously and aligns with other getters that never return null.

If you can still change the interface, prefer:

-Task<IReadOnlyList<Tool>?> GetToolsByManualAsync(string manualName, CancellationToken cancellationToken = default);
+Task<IReadOnlyList<Tool>> GetToolsByManualAsync(string manualName, CancellationToken cancellationToken = default);

14-14: Parameter name is misleading.

GetManualCallTemplateAsync(string manualCallTemplateName) appears to index by the manual’s name (see repository). Rename the parameter to manualName for clarity and consistency.

src/Utcp.Core/Repositories/InMemToolRepository.cs (4)

81-93: Avoid double lookup; operate on snapshot to reduce churn.

You lookup each manual twice (Keys.ToList() then TryGetValue). Iterate over ToArray() of KeyValuePairs to avoid the second lookup.

-            foreach (var manualName in this.manualsByName.Keys.ToList())
-            {
-                if (!this.manualsByName.TryGetValue(manualName, out var manual))
-                {
-                    continue;
-                }
+            foreach (var kv in this.manualsByName.ToArray())
+            {
+                var manualName = kv.Key;
+                var manual = kv.Value;

109-114: Naming: parameter likely represents the manual name.

If manualTemplatesByName is keyed by manual name (set in SaveManualAsync), the parameter should be manualName to match usage.


139-147: Prefer returning empty list over null for GetToolsByManualAsync.

This avoids null checks and is consistent with other getters.

-    public Task<IReadOnlyList<Tool>?> GetToolsByManualAsync(string manualName, CancellationToken cancellationToken = default)
+    public Task<IReadOnlyList<Tool>> GetToolsByManualAsync(string manualName, CancellationToken cancellationToken = default)
     {
-        if (this.manualsByName.TryGetValue(manualName, out var manual))
-        {
-            return Task.FromResult<IReadOnlyList<Tool>?>(manual.Tools.ToList());
-        }
-
-        return Task.FromResult<IReadOnlyList<Tool>?>(null);
+        return Task.FromResult(
+            this.manualsByName.TryGetValue(manualName, out var manual)
+                ? (IReadOnlyList<Tool>)manual.Tools.ToList()
+                : Array.Empty<Tool>());
     }

Note: requires corresponding interface change.


17-44: Assumption: tool names are globally unique.

Saving a manual inserts each tool into a global toolsByName by tool name; saving another manual with a tool of the same name will overwrite. If uniqueness isn’t guaranteed across manuals, keying should include the manual scope.

If not globally unique, consider composite keys ($"{manualName}:{tool.Name}") or a per-manual index.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b382a60 and 1642345.

📒 Files selected for processing (16)
  • src/Utcp.Cli/CliCallTemplate.cs (1 hunks)
  • src/Utcp.Core/Interfaces/IConcurrentToolRepository.cs (1 hunks)
  • src/Utcp.Core/Models/Serialization/ModelSerializers.cs (1 hunks)
  • src/Utcp.Core/Models/Tool.cs (2 hunks)
  • src/Utcp.Core/Repositories/InMemToolRepository.cs (1 hunks)
  • src/Utcp.Core/Serialization/Serializer.cs (1 hunks)
  • src/Utcp.Gql/GraphQlCallTemplate.cs (1 hunks)
  • src/Utcp.Http/HttpCallTemplate.cs (1 hunks)
  • src/Utcp.Http/StreamableHttpCallTemplate.cs (1 hunks)
  • src/Utcp.Mcp/McpCallTemplate.cs (1 hunks)
  • src/Utcp.Socket/TcpCallTemplate.cs (1 hunks)
  • src/Utcp.Socket/UdpCallTemplate.cs (1 hunks)
  • src/Utcp.Text/TextCallTemplate.cs (1 hunks)
  • tests/Utcp.Core.Tests/InMemToolRepositoryTests.cs (1 hunks)
  • tests/Utcp.Core.Tests/SerializationParityTests.cs (1 hunks)
  • tests/Utcp.Core.Tests/Utcp.Core.Tests.csproj (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (14)
src/Utcp.Socket/UdpCallTemplate.cs (1)
src/Utcp.Core/Serialization/PolymorphicRegistry.cs (1)
  • RegisterCallTemplateDerivedType (22-25)
src/Utcp.Core/Models/Serialization/ModelSerializers.cs (2)
src/Utcp.Core/Serialization/Serializer.cs (7)
  • Serializer (10-112)
  • Runtime (21-111)
  • Runtime (25-34)
  • Deserialize (64-68)
  • Dictionary (14-14)
  • Dictionary (36-50)
  • Dictionary (86-95)
src/Utcp.Core/Serialization/PolymorphicRegistry.cs (2)
  • TryGetAuthType (37-40)
  • TryGetCallTemplateType (32-35)
tests/Utcp.Core.Tests/InMemToolRepositoryTests.cs (2)
src/Utcp.Core/Interfaces/IConcurrentToolRepository.cs (11)
  • Task (10-10)
  • Task (11-11)
  • Task (12-12)
  • Task (13-13)
  • Task (14-14)
  • Task (15-15)
  • Task (16-16)
  • Task (17-17)
  • Task (18-18)
  • Task (19-19)
  • Task (20-20)
src/Utcp.Core/Repositories/InMemToolRepository.cs (6)
  • Task (17-44)
  • Task (46-69)
  • Task (71-101)
  • Task (103-107)
  • Task (109-113)
  • InMemToolRepository (10-154)
tests/Utcp.Core.Tests/SerializationParityTests.cs (3)
src/Utcp.Core/Models/Serialization/ModelSerializers.cs (13)
  • AuthSerializer (9-35)
  • CallTemplateSerializer (37-63)
  • Dictionary (31-34)
  • Dictionary (59-62)
  • Dictionary (72-75)
  • Dictionary (85-88)
  • Dictionary (98-101)
  • JsonSchemaSerializer (65-76)
  • JsonSchema (67-70)
  • Tool (80-83)
  • ToolSerializer (78-89)
  • UtcpManual (93-96)
  • UtcpManualSerializer (91-102)
src/Utcp.Http/HttpCallTemplate.cs (2)
  • HttpCallTemplate (12-15)
  • HttpCallTemplate (23-26)
src/Utcp.Core/Serialization/Serializer.cs (3)
  • Dictionary (14-14)
  • Dictionary (36-50)
  • Dictionary (86-95)
src/Utcp.Mcp/McpCallTemplate.cs (1)
src/Utcp.Core/Serialization/PolymorphicRegistry.cs (1)
  • RegisterCallTemplateDerivedType (22-25)
src/Utcp.Text/TextCallTemplate.cs (1)
src/Utcp.Core/Serialization/PolymorphicRegistry.cs (1)
  • RegisterCallTemplateDerivedType (22-25)
src/Utcp.Socket/TcpCallTemplate.cs (1)
src/Utcp.Core/Serialization/PolymorphicRegistry.cs (1)
  • RegisterCallTemplateDerivedType (22-25)
src/Utcp.Gql/GraphQlCallTemplate.cs (1)
src/Utcp.Core/Serialization/PolymorphicRegistry.cs (1)
  • RegisterCallTemplateDerivedType (22-25)
src/Utcp.Core/Repositories/InMemToolRepository.cs (1)
src/Utcp.Core/Interfaces/IConcurrentToolRepository.cs (11)
  • Task (10-10)
  • Task (11-11)
  • Task (12-12)
  • Task (13-13)
  • Task (14-14)
  • Task (15-15)
  • Task (16-16)
  • Task (17-17)
  • Task (18-18)
  • Task (19-19)
  • Task (20-20)
src/Utcp.Core/Interfaces/IConcurrentToolRepository.cs (1)
src/Utcp.Core/Models/Serialization/ModelSerializers.cs (3)
  • CallTemplate (39-57)
  • UtcpManual (93-96)
  • Tool (80-83)
src/Utcp.Http/HttpCallTemplate.cs (1)
src/Utcp.Core/Serialization/PolymorphicRegistry.cs (1)
  • RegisterCallTemplateDerivedType (22-25)
src/Utcp.Cli/CliCallTemplate.cs (1)
src/Utcp.Core/Serialization/PolymorphicRegistry.cs (1)
  • RegisterCallTemplateDerivedType (22-25)
src/Utcp.Core/Serialization/Serializer.cs (2)
src/Utcp.Core/Models/Serialization/ModelSerializers.cs (5)
  • Dictionary (31-34)
  • Dictionary (59-62)
  • Dictionary (72-75)
  • Dictionary (85-88)
  • Dictionary (98-101)
src/Utcp.Core/Models/Serialization/CustomConverters.cs (2)
  • CallTemplateJsonConverter (11-35)
  • AuthJsonConverter (37-61)
src/Utcp.Http/StreamableHttpCallTemplate.cs (1)
src/Utcp.Core/Serialization/PolymorphicRegistry.cs (1)
  • RegisterCallTemplateDerivedType (22-25)
🪛 GitHub Check: build-test-pack (macos-latest)
tests/Utcp.Core.Tests/InMemToolRepositoryTests.cs

[failure] 49-49:
Required member 'CallTemplate.CallTemplateType' must be set in the object initializer or attribute constructor.


[failure] 15-15:
Required member 'CallTemplate.CallTemplateType' must be set in the object initializer or attribute constructor.


[failure] 49-49:
Required member 'CallTemplate.CallTemplateType' must be set in the object initializer or attribute constructor.


[failure] 15-15:
Required member 'CallTemplate.CallTemplateType' must be set in the object initializer or attribute constructor.

tests/Utcp.Core.Tests/SerializationParityTests.cs

[failure] 109-109:
Required member 'CallTemplate.CallTemplateType' must be set in the object initializer or attribute constructor.


[failure] 79-79:
Required member 'CallTemplate.CallTemplateType' must be set in the object initializer or attribute constructor.


[failure] 36-36:
Required member 'CallTemplate.CallTemplateType' must be set in the object initializer or attribute constructor.


[failure] 109-109:
Required member 'CallTemplate.CallTemplateType' must be set in the object initializer or attribute constructor.


[failure] 79-79:
Required member 'CallTemplate.CallTemplateType' must be set in the object initializer or attribute constructor.


[failure] 36-36:
Required member 'CallTemplate.CallTemplateType' must be set in the object initializer or attribute constructor.

🪛 GitHub Check: build-test-pack (ubuntu-latest)
tests/Utcp.Core.Tests/InMemToolRepositoryTests.cs

[failure] 49-49:
Required member 'CallTemplate.CallTemplateType' must be set in the object initializer or attribute constructor.


[failure] 15-15:
Required member 'CallTemplate.CallTemplateType' must be set in the object initializer or attribute constructor.


[failure] 49-49:
Required member 'CallTemplate.CallTemplateType' must be set in the object initializer or attribute constructor.


[failure] 15-15:
Required member 'CallTemplate.CallTemplateType' must be set in the object initializer or attribute constructor.

tests/Utcp.Core.Tests/SerializationParityTests.cs

[failure] 109-109:
Required member 'CallTemplate.CallTemplateType' must be set in the object initializer or attribute constructor.


[failure] 79-79:
Required member 'CallTemplate.CallTemplateType' must be set in the object initializer or attribute constructor.


[failure] 36-36:
Required member 'CallTemplate.CallTemplateType' must be set in the object initializer or attribute constructor.


[failure] 109-109:
Required member 'CallTemplate.CallTemplateType' must be set in the object initializer or attribute constructor.


[failure] 79-79:
Required member 'CallTemplate.CallTemplateType' must be set in the object initializer or attribute constructor.


[failure] 36-36:
Required member 'CallTemplate.CallTemplateType' must be set in the object initializer or attribute constructor.

🪛 GitHub Actions: build
tests/Utcp.Core.Tests/InMemToolRepositoryTests.cs

[error] 15-15: dotnet build Utcp.sln -c Release --no-restore: CS9035: Required member 'CallTemplate.CallTemplateType' must be set in the object initializer or attribute constructor.

tests/Utcp.Core.Tests/SerializationParityTests.cs

[error] 36-36: dotnet build Utcp.sln -c Release --no-restore: CS9035: Required member 'CallTemplate.CallTemplateType' must be set in the object initializer or attribute constructor.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cursor Bugbot
🔇 Additional comments (18)
tests/Utcp.Core.Tests/Utcp.Core.Tests.csproj (1)

24-27: Good: add Utcp.Http reference to enable Http-related tests.

Matches the new serializer + call-template coverage.

src/Utcp.Socket/TcpCallTemplate.cs (2)

11-14: Static type registration moved to a static ctor — good change.

This avoids repeated per-instance registration and eliminates races during first use.


19-22: Mark parameterless CallTemplate ctors with [SetsRequiredMembers] to fix CS9035

Annotate the parameterless constructor so the compiler recognizes that the required base member (CallTemplateType) is set.

+using System.Diagnostics.CodeAnalysis;
 using Utcp.Core.Models;
 using Utcp.Core.Serialization;

 public sealed record TcpCallTemplate : CallTemplate
 {
@@
-    public TcpCallTemplate()
+    [SetsRequiredMembers]
+    public TcpCallTemplate()
     {
         CallTemplateType = "tcp";
     }
 }

Apply the same attribute to parameterless ctors for: HttpCallTemplate, StreamableHttpCallTemplate, UdpCallTemplate, TextCallTemplate, CliCallTemplate, GraphQlCallTemplate, McpCallTemplate.

tests/Utcp.Core.Tests/InMemToolRepositoryTests.cs (3)

45-64: LGTM: positive path for GetManualCallTemplateAsync.

Covers storing and retrieving the manual’s template.


66-72: LGTM: null contract for unknown manual.

This matches the interface’s nullable return type.


74-80: LGTM: RemoveToolAsync negative path covered.

Good guard ensuring no false positives on missing tool.

src/Utcp.Http/StreamableHttpCallTemplate.cs (2)

11-14: Static registration via type initializer — good.

Consistent with the new pattern and avoids redundant work.


23-26: Do not add [SetsRequiredMembers] to these ctors — they don't initialize all required members (Name).

CallTemplate (src/Utcp.Core/Models/CallTemplate.cs) declares required CallTemplateType and Name; the parameterless constructors only set CallTemplateType. Marking them [SetsRequiredMembers] would suppress the compiler check for Name and could hide missing initializations. Either initialize all required members in the ctor and then add [SetsRequiredMembers], or leave the ctors unchanged and keep callers supplying Name via object initializers. (learn.microsoft.com)

Likely an incorrect or invalid review comment.

tests/Utcp.Core.Tests/SerializationParityTests.cs (3)

12-30: LGTM: Auth round‑trip and discriminator assertion.

Covers both ToDictionary and ValidateDictionary for a built‑in type.


76-104: LGTM: nested Tool round‑trip including embedded HttpCallTemplate and JsonSchema.

Covers key presence and deep equivalence; comparing JsonSchema by members is appropriate.


33-50: No change required — ctor already sets the required member

HttpCallTemplate's parameterless constructor assigns CallTemplateType = "http" (src/Utcp.Http/HttpCallTemplate.cs), so the CS9035 concern is incorrect; do not add [SetsRequiredMembers] or modify tests.

Likely an incorrect or invalid review comment.

src/Utcp.Core/Serialization/Serializer.cs (2)

21-34: Good: central, one‑time JsonSerializerOptions with custom converters.

SnakeCaseLower and the polymorphic converters match the serializer expectations.


36-50: Defensive checks and object-only guarantee are appropriate.

ToDictionary throws for non-object roots; ConvertElement handles all kinds.

src/Utcp.Core/Interfaces/IConcurrentToolRepository.cs (1)

12-12: Specify case-sensitivity in contract (tool/manual name lookups).

Define whether lookups are case-sensitive. The current repo implementation mixes sensitivities, which can surprise callers.

Add XML doc comments clarifying the expectation and ensure implementation matches.

Also applies to: 18-18

src/Utcp.Core/Repositories/InMemToolRepository.cs (1)

121-126: Confirm immutability or defensive copy for GetManualAsync.

If UtcpManual.Tools is mutable, returning the stored instance exposes internal state. Ensure UtcpManual is effectively immutable or return a copy.

src/Utcp.Core/Models/Serialization/ModelSerializers.cs (3)

9-35: AuthSerializer: solid discriminator validation.

The flow (discriminator → registry → deserialize) is correct and failures are explicit.


37-63: CallTemplateSerializer: LGTM.

Mirrors AuthSerializer with clear errors; aligns with PolymorphicRegistry.


65-102: JsonSchema/Tool/UtcpManual serializers: simple and correct passthroughs.

Directly delegating to Runtime keeps behavior consistent with global options (snake_case, converters).

Comment on lines +71 to +101
public async Task<bool> RemoveToolAsync(string toolName, CancellationToken cancellationToken = default)
{
await this.writeLock.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
if (!this.toolsByName.TryRemove(toolName, out _))
{
return false;
}

foreach (var manualName in this.manualsByName.Keys.ToList())
{
if (!this.manualsByName.TryGetValue(manualName, out var manual))
{
continue;
}

var remaining = manual.Tools.Where(t => !string.Equals(t.Name, toolName, StringComparison.OrdinalIgnoreCase)).ToList();
if (remaining.Count != manual.Tools.Count)
{
this.manualsByName[manualName] = manual with { Tools = remaining };
}
}

return true;
}
finally
{
this.writeLock.Release();
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Inconsistent case handling can make RemoveToolAsync fail unexpectedly.

toolsByName.TryRemove(toolName, out _) is case-sensitive, but the subsequent manual update compares names with OrdinalIgnoreCase. If callers pass a different casing, removal returns false and manuals aren’t updated.

Two fixes; prefer A:

A) Make dictionaries case-insensitive (applies outside this hunk):

-    private readonly ConcurrentDictionary<string, Tool> toolsByName = new();
-    private readonly ConcurrentDictionary<string, UtcpManual> manualsByName = new();
-    private readonly ConcurrentDictionary<string, CallTemplate> manualTemplatesByName = new();
+    private readonly ConcurrentDictionary<string, Tool> toolsByName = new(StringComparer.OrdinalIgnoreCase);
+    private readonly ConcurrentDictionary<string, UtcpManual> manualsByName = new(StringComparer.OrdinalIgnoreCase);
+    private readonly ConcurrentDictionary<string, CallTemplate> manualTemplatesByName = new(StringComparer.OrdinalIgnoreCase);

B) Or, perform a case-insensitive key resolution before removing:

-            if (!this.toolsByName.TryRemove(toolName, out _))
+            var key = this.toolsByName.Keys.FirstOrDefault(k => string.Equals(k, toolName, StringComparison.OrdinalIgnoreCase));
+            if (key is null || !this.toolsByName.TryRemove(key, out _))
             {
                 return false;
             }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public async Task<bool> RemoveToolAsync(string toolName, CancellationToken cancellationToken = default)
{
await this.writeLock.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
if (!this.toolsByName.TryRemove(toolName, out _))
{
return false;
}
foreach (var manualName in this.manualsByName.Keys.ToList())
{
if (!this.manualsByName.TryGetValue(manualName, out var manual))
{
continue;
}
var remaining = manual.Tools.Where(t => !string.Equals(t.Name, toolName, StringComparison.OrdinalIgnoreCase)).ToList();
if (remaining.Count != manual.Tools.Count)
{
this.manualsByName[manualName] = manual with { Tools = remaining };
}
}
return true;
}
finally
{
this.writeLock.Release();
}
}
public async Task<bool> RemoveToolAsync(string toolName, CancellationToken cancellationToken = default)
{
await this.writeLock.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
var key = this.toolsByName.Keys.FirstOrDefault(k => string.Equals(k, toolName, StringComparison.OrdinalIgnoreCase));
if (key is null || !this.toolsByName.TryRemove(key, out _))
{
return false;
}
foreach (var manualName in this.manualsByName.Keys.ToList())
{
if (!this.manualsByName.TryGetValue(manualName, out var manual))
{
continue;
}
var remaining = manual.Tools.Where(t => !string.Equals(t.Name, toolName, StringComparison.OrdinalIgnoreCase)).ToList();
if (remaining.Count != manual.Tools.Count)
{
this.manualsByName[manualName] = manual with { Tools = remaining };
}
}
return true;
}
finally
{
this.writeLock.Release();
}
}
🤖 Prompt for AI Agents
In src/Utcp.Core/Repositories/InMemToolRepository.cs around lines 71 to 101,
RemoveToolAsync uses toolsByName.TryRemove(toolName, out _) which is
case-sensitive while later comparisons use StringComparison.OrdinalIgnoreCase,
causing removals to fail for differently-cased input; fix by making the relevant
dictionaries (toolsByName and manualsByName) use a case-insensitive comparer
(e.g., StringComparer.OrdinalIgnoreCase) at construction so keys are normalized
and TryRemove behaves consistently, or if you prefer the alternate approach,
resolve the actual stored key by searching toolsByName.Keys with an
ordinal-ignore-case match and remove that concrete key before updating manuals.

@Cicatriiz
Cicatriiz merged commit df858d2 into main Sep 16, 2025
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant