perf: token estimation memoization + tool catalog cache

ober

2bd0e4f765f962eb0c7f5fa3f50c37b7c9c2fda7

diff --git a/src/jcode/tool/registry.ss b/src/jcode/tool/registry.ss
index eb75ca2..6a0b0f1 100644
--- a/src/jcode/tool/registry.ss
+++ b/src/jcode/tool/registry.ss
@@ -34,6 +34,9 @@
 (def logger (make-logger "tools"))
 
 (def *tools* (make-hash-table))
+(def *tool-version* (make-parameter 0))
+(def (bump-tool-version!)
+  (*tool-version* (+ (*tool-version*) 1)))
 
 ;; ---- Mode (plan vs build) ----
 ;;
@@ -85,9 +88,11 @@
 (def (user-disable-tool! name)
   (let ((lst (*user-disabled-tools*)))
     (unless (member name lst)
-      (*user-disabled-tools* (cons name lst)))))
+      (*user-disabled-tools* (cons name lst))))
+  (bump-tool-version!))
 (def (user-enable-tool! name)
-  (*user-disabled-tools* (remove (lambda (x) (equal? x name)) (*user-disabled-tools*))))
+  (*user-disabled-tools* (remove (lambda (x) (equal? x name)) (*user-disabled-tools*)))
+  (bump-tool-version!))
 (def (user-disabled-tools-list)
   (*user-disabled-tools*))
 
@@ -121,7 +126,8 @@
       `(("name"        . ,name)
         ("description" . ,description)
         ("schema"      . ,schema)
-        ("handler"     . ,handler)))))
+        ("handler"     . ,handler))))
+  (bump-tool-version!))
 
 (def (make-hash-table-from-alist pairs)
   (let ((ht (make-hash-table)))
@@ -181,17 +187,43 @@
                               (catch (_e) #f)))
                        truncated))))))))))))) 
 
+;; --- Tool schema cache ---
+;; Rebuilding the catalog for every request wastes ~20% of local-model
+;; prompt build time. Cache it; invalidate on every registry mutation.
+(def *schema-cache-version*    (make-parameter -1))
+(def *schema-cache-result*     (make-parameter '()))
+(def *schema-cache-compacted-version* (make-parameter -1))
+(def *schema-cache-compacted*  (make-parameter '()))
+
 (def (get-tool-schemas)
-  (let ((mapper (if (current-compact-tool-schemas)
-                  tool->compact-openai-schema
-                  tool->openai-schema)))
-    (map mapper
-         (filter (lambda (t)
-                   (and t
-                        (not (hash-get t "internal"))
-                        (mode-allows-tool? (hash-ref t "name" ""))))
-                 (map (lambda (name) (hash-get *tools* name))
-                      (list-tools))))))
+  (let* ((version (*tool-version*))
+         (compact (current-compact-tool-schemas))
+         (cache-version (cond
+                          (compact (*schema-cache-compacted-version*))
+                          (else (*schema-cache-version*))))
+         (cached (cond
+                   (compact (*schema-cache-compacted*))
+                   (else    (*schema-cache-result*)))))
+    (if (= version cache-version)
+        cached
+        (let* ((mapper (if compact
+                        tool->compact-openai-schema
+                        tool->openai-schema))
+              (result (map mapper
+                           (filter (lambda (t)
+                                     (and t
+                                          (not (hash-get t "internal"))
+                                          (mode-allows-tool? (hash-ref t "name" ""))))
+                                   (map (lambda (name) (hash-get *tools* name))
+                                        (list-tools))))))
+          (if compact
+              (begin
+                (*schema-cache-compacted* result)
+                (*schema-cache-compacted-version* version))
+              (begin
+                (*schema-cache-result* result)
+                (*schema-cache-version* version)))
+          result))))
 
 (def (list-tools)
   (filter (lambda (name) (not (tool-disabled? name)))