Fix streaming paren nesting, auto-detect provider, validate API keys
ober
391c764b812a274c9af792fd9d2a573744afaa5f
--- 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)] --- 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* --- 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)) --- 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) --- 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))) --- 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 ;;;