-
Notifications
You must be signed in to change notification settings - Fork 1.1k
feat: add per-agent skills support to SDK types and docs (#958) #995
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -618,6 +618,7 @@ A skill was activated for the current conversation. | |
| | `allowedTools` | `string[]` | | Tools auto-approved while this skill is active | | ||
| | `pluginName` | `string` | | Plugin the skill originated from | | ||
| | `pluginVersion` | `string` | | Plugin version | | ||
| | `agentName` | `string` | | Name of the agent that invoked the skill, when invoked by a custom agent | | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The
By contrast, Go and Python use a flat/unified Since these are auto-generated files ( |
||
|
|
||
| --- | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -87,6 +87,67 @@ public async Task Should_Not_Apply_Skill_When_Disabled_Via_DisabledSkills() | |
| await session.DisposeAsync(); | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task Should_Allow_Agent_With_Skills_To_Invoke_Skill() | ||
| { | ||
| var skillsDir = CreateSkillDir(); | ||
| var customAgents = new List<CustomAgentConfig> | ||
| { | ||
| new CustomAgentConfig | ||
| { | ||
| Name = "skill-agent", | ||
| Description = "An agent with access to test-skill", | ||
| Prompt = "You are a helpful test agent.", | ||
| Skills = ["test-skill"] | ||
| } | ||
| }; | ||
|
|
||
| var session = await CreateSessionAsync(new SessionConfig | ||
| { | ||
| SkillDirectories = [skillsDir], | ||
| CustomAgents = customAgents | ||
| }); | ||
|
|
||
|
Comment on lines
+90
to
+110
|
||
| Assert.Matches(@"^[a-f0-9-]+$", session.SessionId); | ||
|
|
||
| // The agent has Skills = ["test-skill"], so the skill content is preloaded into its context | ||
| var message = await session.SendAndWaitAsync(new MessageOptions { Prompt = "Say hello briefly using the test skill." }); | ||
| Assert.NotNull(message); | ||
| Assert.Contains(SkillMarker, message!.Data.Content); | ||
|
|
||
| await session.DisposeAsync(); | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task Should_Not_Provide_Skills_To_Agent_Without_Skills_Field() | ||
| { | ||
| var skillsDir = CreateSkillDir(); | ||
| var customAgents = new List<CustomAgentConfig> | ||
| { | ||
| new CustomAgentConfig | ||
| { | ||
| Name = "no-skill-agent", | ||
| Description = "An agent without skills access", | ||
| Prompt = "You are a helpful test agent." | ||
| } | ||
| }; | ||
|
|
||
| var session = await CreateSessionAsync(new SessionConfig | ||
| { | ||
| SkillDirectories = [skillsDir], | ||
| CustomAgents = customAgents | ||
| }); | ||
|
|
||
| Assert.Matches(@"^[a-f0-9-]+$", session.SessionId); | ||
|
|
||
| // The agent has no Skills field, so no skill content is injected | ||
| var message = await session.SendAndWaitAsync(new MessageOptions { Prompt = "Say hello briefly using the test skill." }); | ||
| Assert.NotNull(message); | ||
| Assert.DoesNotContain(SkillMarker, message!.Data.Content); | ||
|
|
||
| await session.DisposeAsync(); | ||
| } | ||
|
|
||
| [Fact(Skip = "See the big comment around the equivalent test in the Node SDK. Skipped because the feature doesn't work correctly yet.")] | ||
| public async Task Should_Apply_Skill_On_Session_Resume_With_SkillDirectories() | ||
| { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -108,6 +108,81 @@ func TestSkills(t *testing.T) { | |
| session.Disconnect() | ||
| }) | ||
|
|
||
| t.Run("should allow agent with skills to invoke skill", func(t *testing.T) { | ||
| ctx.ConfigureForTest(t) | ||
| cleanSkillsDir(t, ctx.WorkDir) | ||
| skillsDir := createTestSkillDir(t, ctx.WorkDir, skillMarker) | ||
|
|
||
| customAgents := []copilot.CustomAgentConfig{ | ||
| { | ||
| Name: "skill-agent", | ||
| Description: "An agent with access to test-skill", | ||
| Prompt: "You are a helpful test agent.", | ||
| Skills: []string{"test-skill"}, | ||
| }, | ||
| } | ||
|
|
||
| session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{ | ||
| OnPermissionRequest: copilot.PermissionHandler.ApproveAll, | ||
| SkillDirectories: []string{skillsDir}, | ||
| CustomAgents: customAgents, | ||
| }) | ||
|
Comment on lines
+111
to
+129
|
||
| if err != nil { | ||
| t.Fatalf("Failed to create session: %v", err) | ||
| } | ||
|
|
||
| // The agent has Skills: ["test-skill"], so the skill content is preloaded into its context | ||
| message, err := session.SendAndWait(t.Context(), copilot.MessageOptions{ | ||
| Prompt: "Say hello briefly using the test skill.", | ||
| }) | ||
| if err != nil { | ||
| t.Fatalf("Failed to send message: %v", err) | ||
| } | ||
|
|
||
| if message.Data.Content == nil || !strings.Contains(*message.Data.Content, skillMarker) { | ||
| t.Errorf("Expected message to contain skill marker '%s', got: %v", skillMarker, message.Data.Content) | ||
| } | ||
|
|
||
| session.Disconnect() | ||
| }) | ||
|
|
||
| t.Run("should not provide skills to agent without skills field", func(t *testing.T) { | ||
| ctx.ConfigureForTest(t) | ||
| cleanSkillsDir(t, ctx.WorkDir) | ||
| skillsDir := createTestSkillDir(t, ctx.WorkDir, skillMarker) | ||
|
|
||
| customAgents := []copilot.CustomAgentConfig{ | ||
| { | ||
| Name: "no-skill-agent", | ||
| Description: "An agent without skills access", | ||
| Prompt: "You are a helpful test agent.", | ||
| }, | ||
| } | ||
|
|
||
| session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{ | ||
| OnPermissionRequest: copilot.PermissionHandler.ApproveAll, | ||
| SkillDirectories: []string{skillsDir}, | ||
| CustomAgents: customAgents, | ||
| }) | ||
| if err != nil { | ||
| t.Fatalf("Failed to create session: %v", err) | ||
| } | ||
|
|
||
| // The agent has no Skills field, so no skill content is injected | ||
| message, err := session.SendAndWait(t.Context(), copilot.MessageOptions{ | ||
| Prompt: "Say hello briefly using the test skill.", | ||
| }) | ||
| if err != nil { | ||
| t.Fatalf("Failed to send message: %v", err) | ||
| } | ||
|
|
||
| if message.Data.Content != nil && strings.Contains(*message.Data.Content, skillMarker) { | ||
| t.Errorf("Expected message to NOT contain skill marker '%s' when agent has no skills, got: %v", skillMarker, *message.Data.Content) | ||
| } | ||
|
|
||
| session.Disconnect() | ||
| }) | ||
|
|
||
| t.Run("should apply skill on session resume with skillDirectories", func(t *testing.T) { | ||
| t.Skip("See the big comment around the equivalent test in the Node SDK. Skipped because the feature doesn't work correctly yet.") | ||
| ctx.ConfigureForTest(t) | ||
|
|
||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doc adds an
agentNamefield to theskill.invokedevent, but the SDK-generated event payload types currently do not includeagentNameforskill.invoked(e.g., nodejs/src/generated/session-events.ts and dotnet/src/Generated/SessionEvents.cs). Please either update/regenerate the event schemas/types to include this field, or remove it from the docs until it is actually emitted and modeled.