Skip to content

Commit b3568d7

Browse files
Optimize git pulls
1 parent bb3ce98 commit b3568d7

File tree

6 files changed

+168
-74
lines changed

6 files changed

+168
-74
lines changed

docs/content/tools/docs/gordon.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ services:
2020

2121
volumes:
2222
docker-prompts:
23-
external: true
23+
external: false
2424
```
2525
2626
## debugging

src/git.clj

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,11 @@
3838
parse-ref
3939
add-attributes))
4040

41-
(defn- hashch
41+
(defn hashch
4242
"returns #uuid"
4343
[m]
4444
(str (hasch/uuid5 (hasch/edn-hash (select-keys m [:owner :repo :ref])))))
4545

46-
(comment
47-
(hashch {:owner "docker" :repo "labs-make-runbook"})
48-
(hashch {:owner "docker" :repo "labs-make-runbook" :ref "main"}))
49-
5046
;(def prompts-cache (fs/file "/Users/slim/docker/labs-make-runbook/prompts-cache"))
5147
(defn prompts-cache []
5248
(let [default-dir (fs/file (System/getenv "HOME") ".prompts-cache")]
@@ -96,13 +92,40 @@
9692
(comment
9793
(clone {:dir "/Users/slim/crap" :owner "docker" :repo "labs-make-runbook" :ref "main" :ref-hash "crap"}))
9894

95+
(defn cache-dir [{:keys [ref-hash]}]
96+
(fs/file (prompts-cache) ref-hash))
97+
98+
(defn collect-unique-cache-dirs [coll]
99+
(->> coll
100+
(sort-by (comp :ref-hash :ref))
101+
(partition-by (comp :ref-hash :ref))
102+
(map first)))
103+
104+
(defn refresh-cache
105+
" pure side-effect - clone or pull the cache"
106+
[coll]
107+
(doseq [m coll]
108+
(if (fs/exists? (-> m :ref :dir))
109+
(pull (:ref m))
110+
(clone (:ref m)))
111+
(when (not (= 0 (:exit-code m)))
112+
(jsonrpc/notify :error {:content (str m)}))))
113+
114+
(defn cached-prompt-file [{{:keys [dir path]} :ref :as m}]
115+
(if path
116+
(let [cached-path (fs/file dir path)]
117+
(if (fs/exists? cached-path)
118+
(assoc m :cached-path cached-path)
119+
m))
120+
m))
121+
99122
(defn prompt-file
100123
"returns the path or nil if the github ref does not resolve
101124
throws if the path in the repo does not exist or if the clone fails"
102125
[ref]
103126
(when-let [{:keys [ref path] :as git-ref-map} (parse-github-ref ref)]
104127
(let [ref-hash (hashch (select-keys git-ref-map [:owner :repo :ref]))
105-
dir (fs/file (prompts-cache) ref-hash)
128+
dir (cache-dir {:ref-hash ref-hash})
106129
m (if (fs/exists? dir)
107130
(pull {:dir dir :ref ref})
108131
(clone (merge git-ref-map {:dir (fs/parent dir) :ref-hash ref-hash})))]

src/jsonrpc/db.clj

Lines changed: 51 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,9 @@
88
(def db* (atom {}))
99

1010
(defn get-prompt-data [{:keys [register] :as opts}]
11-
(logger/info "get-prompt-data " register)
1211
(->> register
13-
(map (fn [ref]
14-
[ref
15-
(try
16-
(git/prompt-file ref)
17-
(catch Throwable t
18-
(logger/error t (format "missing ref %s" ref))
19-
:missing))]))
20-
(filter (complement #(= :missing (second %))))
21-
(map (fn [[ref f]]
22-
(let [m (prompts/get-prompts (assoc opts :prompts f))]
12+
(map (fn [{:keys [cached-path]}]
13+
(let [m (prompts/get-prompts (assoc opts :prompts cached-path))]
2314
[(or (-> m :metadata :name) ref) m])))
2415
(into {})))
2516

@@ -34,22 +25,52 @@
3425
(-> db
3526
(assoc :mcp.prompts/registry (merge m (:mcp.prompts/static db)))))
3627

37-
(defn add
38-
"add any static prompts to db"
39-
[opts]
40-
(logger/info "adding static prompts" (:register opts))
41-
(let [prompt-registry (get-prompt-data opts)]
42-
(swap! db* add-static-prompts prompt-registry)))
43-
44-
(comment
45-
(add {:register ["github:docker/labs-ai-tools-for-devs?path=prompts/examples/explain_dockerfile.md"
46-
"github:docker/labs-ai-tools-for-devs?path=prompts/examples/hello_world.md"]}))
47-
48-
(defn merge-dynamic-prompts [{:keys [registry-content] :as opts}]
49-
(logger/info "adding dynamic prompts" registry-content)
50-
(try
51-
(let [{:keys [registry]} (yaml/parse-string registry-content)
52-
prompt-registry (get-prompt-data (assoc opts :register (map :ref (vals registry))))]
53-
(swap! db* add-dynamic-prompts prompt-registry))
54-
(catch Throwable e
55-
(logger/error e "could not merge dynamic prompts"))))
28+
(defn update-dynamic [coll]
29+
(swap! db* add-dynamic-prompts (get-prompt-data {:register coll})))
30+
(defn update-static [coll]
31+
(swap! db* add-static-prompts (get-prompt-data {:register coll})))
32+
33+
(defn missing-cached-prompt-file? [m]
34+
(when (not (contains? m :cached-path))
35+
(logger/warn "missing cached path: %s" (:ref-string m))
36+
m))
37+
38+
(defn add-refs
39+
"update the db with new refs
40+
params
41+
refs - coll of [type ref] type is static or dynamic"
42+
[refs]
43+
(if (seq refs)
44+
(let [git-map-coll (->> refs
45+
(map (fn [[t ref]] {:type t :ref-string ref :ref (git/parse-github-ref ref)}))
46+
(map (fn [m] (assoc-in m [:ref :ref-hash] (git/hashch (:ref m)))))
47+
(map (fn [m] (assoc-in m [:ref :dir] (git/cache-dir (:ref m))))))]
48+
(-> git-map-coll
49+
git/collect-unique-cache-dirs
50+
git/refresh-cache)
51+
(let [typed-colls
52+
(->> git-map-coll
53+
(map git/cached-prompt-file)
54+
(filter (complement missing-cached-prompt-file?))
55+
(sort-by :type)
56+
(partition-by :type))]
57+
(doseq [coll typed-colls]
58+
(case (-> coll first :type)
59+
:dynamic (update-dynamic coll)
60+
:static (update-static coll)
61+
(logger/info "unknown type " (first coll))))))
62+
;; if registry.yaml is empty
63+
(swap! db* (fn [db] (-> db
64+
(update
65+
:mcp.prompts/registry
66+
(constantly (or (:mcp.prompts/static db) {}))))))))
67+
68+
(defn registry-refs
69+
"parse refs from the registry.yaml file - these are dynamic"
70+
[f]
71+
(->>
72+
(yaml/parse-string (slurp f))
73+
:registry
74+
(vals)
75+
(map (fn [{:keys [ref]}] [:dynamic ref]))))
76+

src/jsonrpc/server.clj

Lines changed: 37 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,32 @@
11
(ns jsonrpc.server
22
(:refer-clojure :exclude [run!])
33
(:require
4-
[babashka.fs :as fs]
5-
[cheshire.core :as json]
6-
[clojure.core :as c]
7-
[clojure.core.async :as async]
8-
[clojure.pprint :as pprint]
9-
[clojure.string :as string]
10-
docker
11-
git
12-
graph
13-
jsonrpc
14-
[jsonrpc.db :as db]
15-
[jsonrpc.logger :as logger]
16-
[jsonrpc.producer :as producer]
17-
[lsp4clj.coercer :as coercer]
18-
[lsp4clj.io-chan :as io-chan]
19-
[lsp4clj.io-server :refer [stdio-server]]
20-
[lsp4clj.server :as lsp.server]
21-
[promesa.core :as p]
22-
shutdown
23-
state
24-
[taoensso.timbre :as timbre]
25-
[taoensso.timbre.appenders.core :as appenders]
26-
tools
27-
user-loop
28-
volumes)
4+
[babashka.fs :as fs]
5+
[cheshire.core :as json]
6+
[clj-yaml.core :as yaml]
7+
[clojure.core :as c]
8+
[clojure.core.async :as async]
9+
[clojure.pprint :as pprint]
10+
[clojure.string :as string]
11+
docker
12+
git
13+
graph
14+
jsonrpc
15+
[jsonrpc.db :as db]
16+
[jsonrpc.logger :as logger]
17+
[jsonrpc.producer :as producer]
18+
[lsp4clj.coercer :as coercer]
19+
[lsp4clj.io-chan :as io-chan]
20+
[lsp4clj.io-server :refer [stdio-server]]
21+
[lsp4clj.server :as lsp.server]
22+
[promesa.core :as p]
23+
shutdown
24+
state
25+
[taoensso.timbre :as timbre]
26+
[taoensso.timbre.appenders.core :as appenders]
27+
tools
28+
user-loop
29+
volumes)
2930
(:gen-class))
3031

3132
(set! *warn-on-reflection* true)
@@ -183,9 +184,7 @@
183184
:is-error false})))
184185

185186
(defmethod lsp.server/receive-request "docker/prompts/register" [_ {:keys [db* id]} params]
186-
;; supports only git refs
187-
(lsp.server/discarding-stdout
188-
(db/add (merge @db* params))))
187+
(logger/info "docker/prompts/register"))
189188

190189
(defmethod lsp.server/receive-request "docker/prompts/run"
191190
[_ {:keys [db* id] :as components} {:keys [thread-id] {:keys [file content uri]} :prompts :as params}]
@@ -289,6 +288,8 @@
289288
(publish-docker-notify [_ method params]
290289
(lsp.server/send-notification server method params)))
291290

291+
(def registry "/prompts/registry.yaml")
292+
292293
(defn run-server! [{:keys [trace-level] :or {trace-level "off"} :as opts}]
293294
(lsp.server/discarding-stdout
294295
(let [timbre-logger (->TimbreLogger)
@@ -314,14 +315,13 @@
314315
:server server}]
315316
(swap! db* merge {:log-path log-path} (dissoc opts :in))
316317
;; register static prompts
317-
(when (:register opts)
318-
(try
319-
(db/add opts)
320-
(catch Throwable t
321-
(logger/error t))))
322-
;; register dynamic prompts
323-
(when (fs/exists? (fs/file "/prompts/registry.yaml"))
324-
(db/merge-dynamic-prompts (assoc opts :registry-content (slurp "/prompts/registry.yaml"))))
318+
(db/add-refs
319+
(concat
320+
(->> (:register opts)
321+
(map (fn [ref] [:static ref])))
322+
;; register dynamic prompts
323+
(when (fs/exists? (fs/file registry))
324+
(db/registry-refs registry))))
325325
;; watch dynamic prompts in background
326326
(async/thread
327327
(let [{x :container}
@@ -334,7 +334,7 @@
334334
(let [[_dir _event f] (string/split line #"\s+")]
335335
(when (= f "registry.yaml")
336336
(try
337-
(db/merge-dynamic-prompts (assoc opts :registry-content (slurp "/prompts/registry.yaml")))
337+
(db/add-refs (logger/trace (into [] (db/registry-refs registry))))
338338
(producer/publish-tool-list-changed producer {})
339339
(producer/publish-prompt-list-changed producer {})
340340
(catch Throwable t

test/jsonrpc/db_t.clj

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
(ns jsonrpc.db-t
2+
(:require
3+
[clojure.pprint :refer [pprint]]
4+
[clojure.test :as t]
5+
[jsonrpc.db :as db]))
6+
7+
(def correct-refs
8+
[[:dynamic
9+
"github:docker/labs-ai-tools-for-devs?ref=main&path=prompts/examples/hello_world.md"]
10+
[:dynamic
11+
"github:docker/labs-ai-tools-for-devs?ref=main&path=prompts/examples/mcp-sqlite.md"]
12+
[:dynamic
13+
"github:docker/labs-ai-tools-for-devs?ref=main&path=prompts/examples/curl.md"]
14+
[:dynamic
15+
"github:docker/labs-ai-tools-for-devs?ref=main&path=prompts/examples/ffmpeg.md"]
16+
[:dynamic
17+
"github:docker/labs-ai-tools-for-devs?ref=main&path=prompts/examples/explain_dockerfile.md"]
18+
[:dynamic
19+
"github:docker/labs-ai-tools-for-devs?ref=main&path=prompts/examples/qrencode.md"]])
20+
21+
(t/deftest registry-ref-tests
22+
(t/testing "parse-registry"
23+
(t/is (= correct-refs
24+
(db/registry-refs "test/registry.yaml")))))
25+
26+
(comment
27+
(db/add-refs
28+
(db/registry-refs "test/registry.yaml"))
29+
30+
(db/add-refs
31+
[[:dynamic "github:docker/labs-ai-tools-for-devs?path=prompts/examples/explain_dockerfile.md"]
32+
[:dynamic "github:docker/labs-ai-tools-for-devs?path=prompts/examples/explain_dockerfile1.md"]
33+
[:dynamic "github:docker/labs-ai-tools-for-devs?ref=branch&path=prompts/examples/explain_dockerfile1.md"]
34+
[:dynamic "github:docker/labs-ai-tools-for-devs?ref=branch&path=prompts/examples/explain_dockerfile1.md"]]))
35+
36+
(comment
37+
(pprint @db/db*))

test/registry.yaml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
registry:
2+
hello world:
3+
ref: github:docker/labs-ai-tools-for-devs?ref=main&path=prompts/examples/hello_world.md
4+
mcp-sqlite:
5+
ref: github:docker/labs-ai-tools-for-devs?ref=main&path=prompts/examples/mcp-sqlite.md
6+
curl:
7+
ref: github:docker/labs-ai-tools-for-devs?ref=main&path=prompts/examples/curl.md
8+
ffmpeg:
9+
ref: github:docker/labs-ai-tools-for-devs?ref=main&path=prompts/examples/ffmpeg.md
10+
explain dockerfiles:
11+
ref: github:docker/labs-ai-tools-for-devs?ref=main&path=prompts/examples/explain_dockerfile.md
12+
qr code:
13+
ref: github:docker/labs-ai-tools-for-devs?ref=main&path=prompts/examples/qrencode.md

0 commit comments

Comments
 (0)