Fix streaming paren nesting, auto-detect provider, validate API keys

ober

391c764b812a274c9af792fd9d2a573744afaa5f

diff --git a/lib/jcode/core/agent.sls b/lib/jcode/core/agent.sls
index e8243aa..0471edf 100644
--- a/lib/jcode/core/agent.sls
+++ b/lib/jcode/core/agent.sls
@@ -92,7 +92,9 @@
        (let* ([provider-name (or (current-provider-override)
                                  (config-provider))]
               [api-key (config-get-provider-key provider-name)]
-              [model (or (current-model-override) (config-model))])
+              [model (or (current-model-override)
+                         (config-ref "model")
+                         (config-default-model provider-name))])
          (make-provider provider-name api-key model)))
   (def (agent-chat user-input)
        (let* ([provider (get-current-provider)]
diff --git a/lib/jcode/core/config.sls b/lib/jcode/core/config.sls
index 01386d9..a7ba4b2 100644
--- a/lib/jcode/core/config.sls
+++ b/lib/jcode/core/config.sls
@@ -4,7 +4,8 @@
 
 (library (jcode core config)
   (export load-config config-ref config-api-key config-model
-    config-provider config-get-provider-key *config*)
+    config-provider config-get-provider-key config-default-model
+    *config*)
   (import
     (except (chezscheme) make-hash-table hash-table? iota \x31;+ \x31;-
       getenv path-extension path-absolute? thread? make-mutex
@@ -91,9 +92,31 @@
   (def (config-get-provider-key provider)
        (config-ref "providers" provider "api_key"))
   (def (config-model)
-       (or (config-ref "model") "claude-sonnet-4-20250514"))
+       (or (config-ref "model")
+           (config-default-model (config-provider))))
+  (def (config-default-model provider)
+       (case (string->symbol provider)
+         [(anthropic) "claude-sonnet-4-20250514"]
+         [(openai) "gpt-4o"]
+         [(openrouter) "anthropic/claude-sonnet-4"]
+         [(deepseek) "deepseek-chat"]
+         [(google) "gemini-2.0-flash"]
+         [(ollama) "llama3.2"]
+         [else "gpt-4o"]))
   (def (config-provider)
-       (or (config-ref "provider") "anthropic"))
+       (or (config-ref "provider")
+           (config-detect-provider)
+           "anthropic"))
+  (def (config-detect-provider)
+       (let loop ([names '("anthropic"
+                            "openrouter"
+                            "openai"
+                            "deepseek"
+                            "google")])
+         (cond
+           [(null? names) #f]
+           [(config-get-provider-key (car names)) (car names)]
+           [#t (loop (cdr names))])))
   (def (config-api-key)
        (config-get-provider-key (config-provider)))
   (define-syntax *config*
diff --git a/lib/jcode/provider/provider.sls b/lib/jcode/provider/provider.sls
index e0fc074..8fc8c71 100644
--- a/lib/jcode/provider/provider.sls
+++ b/lib/jcode/provider/provider.sls
@@ -41,6 +41,11 @@
          [else
           (error 'provider-default-url "Unknown provider" name)]))
   (def (provider-chat provider messages tools)
+       (unless (provider-api-key provider)
+         (error 'provider-chat
+           (format
+             "No API key configured for provider '~a'. Set the appropriate env var or add it to jcode.json."
+             (provider-name provider))))
        (log-info
          logger
          "chat"
@@ -353,7 +358,10 @@
               [body (openai-stream-body provider messages tools)]
               [text-acc (open-output-string)]
               [tc-table (make-hash-table)])
-         (http-post-stream url headers (json-object->string body)
+         (http-post-stream
+           url
+           headers
+           (json-object->string body)
            (lambda (event-str)
              (when event-str
                (let* ([data (if (string-prefix? "data: " event-str)
@@ -409,19 +417,18 @@
                                                 (or (hash-get acc "args")
                                                     "")
                                                 args)))))))
-                                  tcs)))))))]))))
-           (let* ([content (get-output-string text-acc)]
-                  [indices (sort < (hash-keys tc-table))]
-                  [tool-calls (map (lambda (idx)
-                                     (let ([acc (hash-ref tc-table idx)])
-                                       (restore-tool-call
-                                         (or (hash-get acc "id")
-                                             (format "call_~a" idx))
-                                         (or (hash-get acc "name")
-                                             "unknown")
-                                         (or (hash-get acc "args") "{}"))))
-                                   indices)])
-             (values content tool-calls)))))
+                                  tcs)))))))])))))
+         (let* ([content (get-output-string text-acc)]
+                [indices (sort < (hash-keys tc-table))]
+                [tool-calls (map (lambda (idx)
+                                   (let ([acc (hash-ref tc-table idx)])
+                                     (restore-tool-call
+                                       (or (hash-get acc "id")
+                                           (format "call_~a" idx))
+                                       (or (hash-get acc "name") "unknown")
+                                       (or (hash-get acc "args") "{}"))))
+                                 indices)])
+           (values content tool-calls))))
   (def (anthropic-stream-headers provider)
        `(("Content-Type" . "application/json")
           ("x-api-key" . ,(provider-api-key provider))
diff --git a/src/jcode/core/agent.ss b/src/jcode/core/agent.ss
index 289d2f0..d468741 100644
--- a/src/jcode/core/agent.ss
+++ b/src/jcode/core/agent.ss
@@ -91,7 +91,9 @@ Be concise and helpful. When editing files, make minimal changes.")
 (def (get-current-provider)
   (let* ((provider-name (or (current-provider-override) (config-provider)))
          (api-key (config-get-provider-key provider-name))
-         (model (or (current-model-override) (config-model))))
+         (model (or (current-model-override)
+                    (config-ref "model")
+                    (config-default-model provider-name))))
     (make-provider provider-name api-key model)))
 
 (def (agent-chat user-input)
diff --git a/src/jcode/core/config.ss b/src/jcode/core/config.ss
index bf03dc5..bee34f5 100644
--- a/src/jcode/core/config.ss
+++ b/src/jcode/core/config.ss
@@ -6,6 +6,7 @@
         config-model
         config-provider
         config-get-provider-key
+        config-default-model
         *config*)
 
 (import :std/text/json
@@ -87,10 +88,31 @@
   (config-ref "providers" provider "api_key"))
 
 (def (config-model)
-  (or (config-ref "model") "claude-sonnet-4-20250514"))
+  (or (config-ref "model")
+      (config-default-model (config-provider))))
+
+(def (config-default-model provider)
+  (case (string->symbol provider)
+    ((anthropic)   "claude-sonnet-4-20250514")
+    ((openai)      "gpt-4o")
+    ((openrouter)  "anthropic/claude-sonnet-4")
+    ((deepseek)    "deepseek-chat")
+    ((google)      "gemini-2.0-flash")
+    ((ollama)      "llama3.2")
+    (else          "gpt-4o")))
 
 (def (config-provider)
-  (or (config-ref "provider") "anthropic"))
+  (or (config-ref "provider")
+      (config-detect-provider)
+      "anthropic"))
+
+(def (config-detect-provider)
+  ;; Auto-detect first provider with a configured key
+  (let loop ((names '("anthropic" "openrouter" "openai" "deepseek" "google")))
+    (cond
+      ((null? names) #f)
+      ((config-get-provider-key (car names)) (car names))
+      (#t (loop (cdr names))))))
 
 (def (config-api-key)
   (config-get-provider-key (config-provider)))
diff --git a/src/jcode/provider/provider.ss b/src/jcode/provider/provider.ss
index 138fa7b..fd9071a 100644
--- a/src/jcode/provider/provider.ss
+++ b/src/jcode/provider/provider.ss
@@ -42,6 +42,10 @@
     (else (error 'provider-default-url "Unknown provider" name))))
 
 (def (provider-chat provider messages tools)
+  (unless (provider-api-key provider)
+    (error 'provider-chat
+      (format "No API key configured for provider '~a'. Set the appropriate env var or add it to jcode.json."
+        (provider-name provider))))
   (log-info logger "chat"
     `((provider . ,(provider-name provider))
       (model    . ,(provider-model provider))
@@ -365,7 +369,7 @@
                                          (string-append
                                            (or (hash-get acc "args") "")
                                            args)))))))
-                             tcs))))))))))))
+                             tcs)))))))))))))
     ;; Build result
     (let* ((content (get-output-string text-acc))
            (indices (sort < (hash-keys tc-table)))
@@ -377,7 +381,7 @@
                         (or (hash-get acc "name") "unknown")
                         (or (hash-get acc "args") "{}"))))
                   indices)))
-      (values content tool-calls)))))
+      (values content tool-calls))))
 
 ;;; Anthropic Streaming ;;;