mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: persist skills as message parts like AGENTS.md (#23748)
## Summary Skills are now discovered once on the first turn (or when the workspace agent changes) and persisted as `skill` message parts alongside `context-file` parts. On subsequent turns, the skill index is reconstructed from persisted parts instead of re-dialing the workspace agent. This makes skills consistent with the AGENTS.md pattern and is groundwork for a future `/context` endpoint that surfaces loaded workspace context to the frontend. ## Changes - Add `skill` `ChatMessagePartType` with `SkillName` and `SkillDescription` fields - Extend `persistInstructionFiles` to also discover and persist skills as parts - Add `skillsFromParts()` to reconstruct skill index from persisted parts on subsequent turns - Update `runChat()` to use `skillsFromParts` instead of re-dialing workspace for skills - Frontend: handle new `skill` part type (skip rendering, hide metadata-only messages) ## Before / After | | AGENTS.md | Skills | |---|---|---| | **Before** | Persist as `context-file` parts, reconstruct from parts | In-memory `skillsCache` only, re-dial workspace on cache miss | | **After** | Persist as `context-file` parts, reconstruct from parts | Persist as `skill` parts, reconstruct from parts | The in-memory `skillsCache` remains for `read_skill`/`read_skill_file` tool calls that need full skill bodies on demand. <details><summary>Design context</summary> This is the first step toward a unified workspace context representation. Currently: - Context files are persisted as message parts (works) - Skills were only in-memory (inconsistent) - Workspace MCP servers are cached in-memory (future work) Persisting skills as parts means a future `/context` endpoint can query both context files and skills from the same message parts in the DB, without depending on ephemeral server-side caches. </details>
This commit is contained in:
@@ -116,6 +116,7 @@ const (
|
||||
ChatMessagePartTypeFile ChatMessagePartType = "file"
|
||||
ChatMessagePartTypeFileReference ChatMessagePartType = "file-reference"
|
||||
ChatMessagePartTypeContextFile ChatMessagePartType = "context-file"
|
||||
ChatMessagePartTypeSkill ChatMessagePartType = "skill"
|
||||
)
|
||||
|
||||
// AllChatMessagePartTypes returns all known ChatMessagePartType values.
|
||||
@@ -129,6 +130,7 @@ func AllChatMessagePartTypes() []ChatMessagePartType {
|
||||
ChatMessagePartTypeFile,
|
||||
ChatMessagePartTypeFileReference,
|
||||
ChatMessagePartTypeContextFile,
|
||||
ChatMessagePartTypeSkill,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,6 +213,16 @@ type ChatMessagePart struct {
|
||||
// workspace agent. Internal only: same purpose as
|
||||
// ContextFileOS.
|
||||
ContextFileDirectory string `json:"context_file_directory,omitempty" typescript:"-"`
|
||||
// SkillName is the kebab-case name of a discovered skill
|
||||
// from the workspace's .agents/skills/ directory.
|
||||
SkillName string `json:"skill_name" variants:"skill"`
|
||||
// SkillDescription is the short description from the skill's
|
||||
// SKILL.md frontmatter.
|
||||
SkillDescription string `json:"skill_description,omitempty" variants:"skill?"`
|
||||
// SkillDir is the absolute path to the skill directory inside
|
||||
// the workspace filesystem. Internal only: used by
|
||||
// read_skill/read_skill_file tools to locate skill files.
|
||||
SkillDir string `json:"skill_dir,omitempty" typescript:"-"`
|
||||
}
|
||||
|
||||
// StripInternal removes internal-only fields that must not be
|
||||
@@ -227,6 +239,7 @@ func (p *ChatMessagePart) StripInternal() {
|
||||
p.ContextFileContent = ""
|
||||
p.ContextFileOS = ""
|
||||
p.ContextFileDirectory = ""
|
||||
p.SkillDir = ""
|
||||
}
|
||||
|
||||
// ChatMessageText builds a text chat message part.
|
||||
|
||||
@@ -238,6 +238,7 @@ func TestChatMessagePartVariantTags(t *testing.T) {
|
||||
"context_file_content": "internal only, stripped before API responses (typescript:\"-\")",
|
||||
"context_file_os": "internal only, used during prompt expansion (typescript:\"-\")",
|
||||
"context_file_directory": "internal only, used during prompt expansion (typescript:\"-\")",
|
||||
"skill_dir": "internal only, used by read_skill tools (typescript:\"-\")",
|
||||
}
|
||||
knownTypes := make(map[codersdk.ChatMessagePartType]bool)
|
||||
for _, pt := range codersdk.AllChatMessagePartTypes() {
|
||||
|
||||
Reference in New Issue
Block a user