Skip to content

Commit 5830090

Browse files
committed
feat: skills installer codex
1 parent 8fe04b4 commit 5830090

File tree

2 files changed

+103
-8
lines changed

2 files changed

+103
-8
lines changed

experimental/aitools/lib/installer/installer.go

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ const (
2525
skillsRepoOwner = "databricks"
2626
skillsRepoName = "databricks-agent-skills"
2727
skillsRepoPath = "skills"
28-
defaultSkillsRepoRef = "v0.1.3"
28+
defaultSkillsRepoRef = "v0.1.4"
2929
)
3030

3131
// fetchFileFn is the function used to download individual skill files.
@@ -73,9 +73,20 @@ func FetchManifest(ctx context.Context) (*Manifest, error) {
7373
return src.FetchManifest(ctx, ref)
7474
}
7575

76+
// sharedFilePrefix marks files in the manifest that live at the repo root
77+
// rather than under skills/<name>/. The CLI strips this prefix when writing
78+
// to disk and adjusts the download URL accordingly.
79+
const sharedFilePrefix = "@root:"
80+
7681
func fetchSkillFile(ctx context.Context, ref, skillName, filePath string) ([]byte, error) {
77-
url := fmt.Sprintf("https://raw.githubusercontent.com/%s/%s/%s/%s/%s/%s",
78-
skillsRepoOwner, skillsRepoName, ref, skillsRepoPath, skillName, filePath)
82+
var url string
83+
if rootPath, ok := strings.CutPrefix(filePath, sharedFilePrefix); ok {
84+
url = fmt.Sprintf("https://raw.githubusercontent.com/%s/%s/%s/%s",
85+
skillsRepoOwner, skillsRepoName, ref, rootPath)
86+
} else {
87+
url = fmt.Sprintf("https://raw.githubusercontent.com/%s/%s/%s/%s/%s/%s",
88+
skillsRepoOwner, skillsRepoName, ref, skillsRepoPath, skillName, filePath)
89+
}
7990

8091
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
8192
if err != nil {
@@ -506,6 +517,11 @@ func installSkillToDir(ctx context.Context, ref, skillName, destDir string, file
506517
return err
507518
}
508519

520+
// Strip the @root: prefix so shared assets land at a local path
521+
// (e.g. "@root:assets/databricks.svg" → "assets/databricks.svg").
522+
if rootPath, ok := strings.CutPrefix(file, sharedFilePrefix); ok {
523+
file = rootPath
524+
}
509525
destPath := filepath.Join(destDir, file)
510526

511527
if err := os.MkdirAll(filepath.Dir(destPath), 0o755); err != nil {

experimental/aitools/lib/installer/installer_test.go

Lines changed: 84 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ func TestInstallSkillsForAgentsWritesState(t *testing.T) {
208208
assert.Equal(t, "0.1.0", state.Skills["databricks-sql"])
209209
assert.Equal(t, "0.1.0", state.Skills["databricks-jobs"])
210210

211-
assert.Contains(t, stderr.String(), "Installed 2 skills (v0.1.3).")
211+
assert.Contains(t, stderr.String(), "Installed 2 skills (v0.1.4).")
212212
}
213213

214214
func TestInstallSkillForSingleWritesState(t *testing.T) {
@@ -231,7 +231,7 @@ func TestInstallSkillForSingleWritesState(t *testing.T) {
231231
assert.Len(t, state.Skills, 1)
232232
assert.Equal(t, "0.1.0", state.Skills["databricks-sql"])
233233

234-
assert.Contains(t, stderr.String(), "Installed 1 skill (v0.1.3).")
234+
assert.Contains(t, stderr.String(), "Installed 1 skill (v0.1.4).")
235235
}
236236

237237
func TestInstallSkillsSpecificNotFound(t *testing.T) {
@@ -274,7 +274,7 @@ func TestExperimentalSkillsSkippedByDefault(t *testing.T) {
274274
assert.Len(t, state.Skills, 2)
275275
assert.NotContains(t, state.Skills, "databricks-experimental")
276276

277-
assert.Contains(t, stderr.String(), "Installed 2 skills (v0.1.3).")
277+
assert.Contains(t, stderr.String(), "Installed 2 skills (v0.1.4).")
278278
}
279279

280280
func TestExperimentalSkillsIncludedWithFlag(t *testing.T) {
@@ -304,7 +304,7 @@ func TestExperimentalSkillsIncludedWithFlag(t *testing.T) {
304304
assert.Contains(t, state.Skills, "databricks-experimental")
305305
assert.True(t, state.IncludeExperimental)
306306

307-
assert.Contains(t, stderr.String(), "Installed 3 skills (v0.1.3).")
307+
assert.Contains(t, stderr.String(), "Installed 3 skills (v0.1.4).")
308308
}
309309

310310
func TestMinCLIVersionSkipWithWarningForInstallAll(t *testing.T) {
@@ -338,7 +338,7 @@ func TestMinCLIVersionSkipWithWarningForInstallAll(t *testing.T) {
338338
assert.Len(t, state.Skills, 2)
339339
assert.NotContains(t, state.Skills, "databricks-future")
340340

341-
assert.Contains(t, stderr.String(), "Installed 2 skills (v0.1.3).")
341+
assert.Contains(t, stderr.String(), "Installed 2 skills (v0.1.4).")
342342
assert.Contains(t, logBuf.String(), "requires CLI version 0.300.0")
343343
}
344344

@@ -724,3 +724,82 @@ func TestSupportsProjectScopeSetCorrectly(t *testing.T) {
724724
assert.Equal(t, want, agent.SupportsProjectScope, "SupportsProjectScope for %s", agent.Name)
725725
}
726726
}
727+
728+
// --- @root: prefix tests ---
729+
730+
func TestFetchSharedFilePrefix(t *testing.T) {
731+
tmp := setupTestHome(t)
732+
ctx := cmdio.MockDiscard(t.Context())
733+
734+
var capturedURLs []string
735+
orig := fetchFileFn
736+
t.Cleanup(func() { fetchFileFn = orig })
737+
fetchFileFn = func(_ context.Context, _, skillName, filePath string) ([]byte, error) {
738+
capturedURLs = append(capturedURLs, skillName+":"+filePath)
739+
return []byte("content-" + filePath), nil
740+
}
741+
742+
src := &mockManifestSource{manifest: &Manifest{
743+
Version: "1",
744+
UpdatedAt: "2024-01-01",
745+
Skills: map[string]SkillMeta{
746+
"databricks": {
747+
Version: "0.1.0",
748+
Files: []string{
749+
"SKILL.md",
750+
"agents/openai.yaml",
751+
"@root:assets/databricks.svg",
752+
},
753+
},
754+
},
755+
}}
756+
757+
agent := testAgent(tmp)
758+
err := InstallSkillsForAgents(ctx, src, []*agents.Agent{agent}, InstallOptions{})
759+
require.NoError(t, err)
760+
761+
// Verify files were fetched with the correct paths (including @root: prefix).
762+
assert.Contains(t, capturedURLs, "databricks:SKILL.md")
763+
assert.Contains(t, capturedURLs, "databricks:agents/openai.yaml")
764+
assert.Contains(t, capturedURLs, "databricks:@root:assets/databricks.svg")
765+
}
766+
767+
func TestSharedFilePrefixStrippedOnDisk(t *testing.T) {
768+
tmp := setupTestHome(t)
769+
ctx := cmdio.MockDiscard(t.Context())
770+
771+
orig := fetchFileFn
772+
t.Cleanup(func() { fetchFileFn = orig })
773+
fetchFileFn = func(_ context.Context, _, _, filePath string) ([]byte, error) {
774+
return []byte("content-" + filePath), nil
775+
}
776+
777+
src := &mockManifestSource{manifest: &Manifest{
778+
Version: "1",
779+
UpdatedAt: "2024-01-01",
780+
Skills: map[string]SkillMeta{
781+
"databricks": {
782+
Version: "0.1.0",
783+
Files: []string{
784+
"SKILL.md",
785+
"@root:assets/databricks.svg",
786+
"@root:assets/databricks.png",
787+
},
788+
},
789+
},
790+
}}
791+
792+
agent := testAgent(tmp)
793+
err := InstallSkillsForAgents(ctx, src, []*agents.Agent{agent}, InstallOptions{})
794+
require.NoError(t, err)
795+
796+
// Canonical dir should have stripped prefix paths.
797+
globalDir := filepath.Join(tmp, ".databricks", "aitools", "skills", "databricks")
798+
_, err = os.Stat(filepath.Join(globalDir, "SKILL.md"))
799+
assert.NoError(t, err)
800+
_, err = os.Stat(filepath.Join(globalDir, "assets", "databricks.svg"))
801+
assert.NoError(t, err, "shared asset should be written under assets/ with @root: stripped")
802+
_, err = os.Stat(filepath.Join(globalDir, "assets", "databricks.png"))
803+
assert.NoError(t, err)
804+
}
805+

0 commit comments

Comments
 (0)