Skip to content

Commit c35bd39

Browse files
committed
tui: parallelize skill downloads for faster loading
Refactored skill discovery to download skills in parallel instead of sequentially, reducing load times when multiple skills need to be fetched from remote URLs. Also updated AGENTS.md with guidance on using dev branch for diffs.
1 parent 266de27 commit c35bd39

File tree

3 files changed

+66
-48
lines changed

3 files changed

+66
-48
lines changed

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
- To regenerate the JavaScript SDK, run `./packages/sdk/js/script/build.ts`.
22
- ALWAYS USE PARALLEL TOOLS WHEN APPLICABLE.
33
- The default branch in this repo is `dev`.
4+
- Local `main` ref may not exist; use `dev` or `origin/dev` for diffs.
45
- Prefer automation: execute requested actions without confirmation unless blocked by missing info or safety/irreversibility.
56

67
## Style Guide

packages/opencode/src/skill/discovery.ts

Lines changed: 62 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import path from "path"
22
import { mkdir } from "fs/promises"
33
import { Log } from "../util/log"
4-
import { Global } from "@/global"
4+
import { Global } from "../global"
55

66
export namespace Discovery {
77
const log = Log.create({ service: "skill-discovery" })
@@ -20,60 +20,77 @@ export namespace Discovery {
2020

2121
async function get(url: string, dest: string): Promise<boolean> {
2222
if (await Bun.file(dest).exists()) return true
23-
try {
24-
const response = await fetch(url)
25-
if (!response.ok) {
26-
log.error("failed to download", { url, status: response.status })
23+
return fetch(url)
24+
.then(async (response) => {
25+
if (!response.ok) {
26+
log.error("failed to download", { url, status: response.status })
27+
return false
28+
}
29+
await Bun.write(dest, await response.text())
30+
return true
31+
})
32+
.catch((err) => {
33+
log.error("failed to download", { url, err })
2734
return false
28-
}
29-
const content = await response.text()
30-
await Bun.write(dest, content)
31-
return true
32-
} catch (err) {
33-
log.error("failed to download", { url, err })
34-
return false
35-
}
35+
})
3636
}
3737

3838
export async function pull(url: string): Promise<string[]> {
3939
const result: string[] = []
40-
const indexUrl = new URL("index.json", url.endsWith("/") ? url : `${url}/`).href
41-
const cacheDir = dir()
42-
43-
try {
44-
log.info("fetching index", { url: indexUrl })
45-
const response = await fetch(indexUrl)
46-
if (!response.ok) {
47-
log.error("failed to fetch index", { url: indexUrl, status: response.status })
48-
return result
49-
}
50-
51-
const index = (await response.json()) as Index
52-
if (!index.skills || !Array.isArray(index.skills)) {
53-
log.warn("invalid index format", { url: indexUrl })
54-
return result
55-
}
40+
const base = url.endsWith("/") ? url : `${url}/`
41+
const index = new URL("index.json", base).href
42+
const cache = dir()
43+
const host = base.slice(0, -1)
5644

57-
for (const skill of index.skills) {
58-
if (!skill.name || !skill.files || !Array.isArray(skill.files)) {
59-
log.warn("invalid skill entry", { url: indexUrl, skill })
60-
continue
45+
log.info("fetching index", { url: index })
46+
const data = await fetch(index)
47+
.then(async (response) => {
48+
if (!response.ok) {
49+
log.error("failed to fetch index", { url: index, status: response.status })
50+
return undefined
6151
}
52+
return response
53+
.json()
54+
.then((json) => json as Index)
55+
.catch((err) => {
56+
log.error("failed to parse index", { url: index, err })
57+
return undefined
58+
})
59+
})
60+
.catch((err) => {
61+
log.error("failed to fetch index", { url: index, err })
62+
return undefined
63+
})
6264

63-
const skillDir = path.join(cacheDir, skill.name)
64-
for (const file of skill.files) {
65-
const fileUrl = new URL(file, `${url.replace(/\/$/, "")}/${skill.name}/`).href
66-
const localPath = path.join(skillDir, file)
67-
await mkdir(path.dirname(localPath), { recursive: true })
68-
await get(fileUrl, localPath)
69-
}
65+
if (!data?.skills || !Array.isArray(data.skills)) {
66+
log.warn("invalid index format", { url: index })
67+
return result
68+
}
7069

71-
const skillMd = path.join(skillDir, "SKILL.md")
72-
if (await Bun.file(skillMd).exists()) result.push(skillDir)
70+
const list = data.skills.filter((skill) => {
71+
if (!skill?.name || !Array.isArray(skill.files)) {
72+
log.warn("invalid skill entry", { url: index, skill })
73+
return false
7374
}
74-
} catch (err) {
75-
log.error("failed to fetch from URL", { url, err })
76-
}
75+
return true
76+
})
77+
78+
await Promise.all(
79+
list.map(async (skill) => {
80+
const root = path.join(cache, skill.name)
81+
await Promise.all(
82+
skill.files.map(async (file) => {
83+
const link = new URL(file, `${host}/${skill.name}/`).href
84+
const dest = path.join(root, file)
85+
await mkdir(path.dirname(dest), { recursive: true })
86+
await get(link, dest)
87+
}),
88+
)
89+
90+
const md = path.join(root, "SKILL.md")
91+
if (await Bun.file(md).exists()) result.push(root)
92+
}),
93+
)
7794

7895
return result
7996
}

packages/opencode/src/skill/skill.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -153,9 +153,9 @@ export namespace Skill {
153153
}
154154

155155
// Download and load skills from URLs
156-
for (const skillUrl of config.skills?.urls ?? []) {
157-
const downloadedDirs = await Discovery.pull(skillUrl)
158-
for (const dir of downloadedDirs) {
156+
for (const url of config.skills?.urls ?? []) {
157+
const list = await Discovery.pull(url)
158+
for (const dir of list) {
159159
dirs.add(dir)
160160
for await (const match of SKILL_GLOB.scan({
161161
cwd: dir,

0 commit comments

Comments
 (0)