agent fix

This commit is contained in:
2026-09-24 12:36:33 +05:30
parent 294fb8ab93
commit bb14445e21
10 changed files with 661 additions and 15 deletions

View File

@@ -172,7 +172,60 @@ type AssistantConfig struct {
Deep string
}
func (a AssistantConfig) Enabled() bool { return a.Provider != "" && a.Balanced != "" }
func (a AssistantConfig) Enabled() bool { return a.Why() == "" }
// Why says what is missing, or "" when the assistant can run.
//
// A sentence rather than a bool, because "off" is the same answer for four
// different mistakes: no provider, no model, no key, a provider nobody
// recognises. Without this the only symptom is a disabled composer, and the
// difference between "we have not switched it on" and "somebody misspelled a
// variable" is invisible from the outside — which is exactly where this was
// stuck.
// The model is reported before the provider, and that order matters. Since
// `assistantProvider` derives the provider from the model, an empty provider
// means the model is empty too — and naming ASSISTANT_PROVIDER first would send
// an operator to set a variable they no longer need, while the one they
// actually missed went unmentioned.
func (a AssistantConfig) Why() string {
if a.Balanced == "" {
return "ASSISTANT_MODEL is not set; give it the provider's model name, " +
"for example openai/gpt-oss-120b"
}
switch a.Provider {
case "openai", "groq", "ollama", "together", "compatible":
case "":
// Not reachable through Load, which derives it. Reachable when
// something builds this struct by hand, and silence would be worse.
return "ASSISTANT_PROVIDER is not set and could not be derived"
default:
return "ASSISTANT_PROVIDER is " + a.Provider + ", which is not one this server speaks"
}
// A local provider needs no credential; a hosted one always does, and a
// missing key otherwise surfaces as a 401 from the provider on the first
// question rather than as a configuration problem.
if a.APIKey == "" && !isLocalEndpoint(a.BaseURL) {
where := a.BaseURL
if where == "" {
// Empty means the OpenAI default, which is emphatically not local.
// "and is not a local endpoint" is how that read before.
where = "the default https://api.openai.com/v1"
}
return "ASSISTANT_API_KEY is not set, and " + where + " is not a local endpoint"
}
return ""
}
// isLocalEndpoint reports whether a base URL is something running beside us.
//
// Ollama and LM Studio need no key, and demanding one would refuse the setup a
// developer is most likely to have on their own machine.
func isLocalEndpoint(baseURL string) bool {
url := strings.ToLower(baseURL)
return strings.Contains(url, "localhost") ||
strings.Contains(url, "127.0.0.1") ||
strings.Contains(url, "host.docker.internal")
}
// ModelFor resolves a tier to a model name, falling back rather than failing.
//
@@ -192,6 +245,22 @@ func (a AssistantConfig) ModelFor(tier string) string {
return a.Balanced
}
// assistantProvider reads the provider, defaulting to the one shape this
// server speaks.
//
// A deployment that names a model and a key has said what it wants; making it
// also name a protocol it has no choice about is a variable that exists only to
// be forgotten.
func assistantProvider() string {
if named := strings.ToLower(strings.TrimSpace(env("ASSISTANT_PROVIDER", ""))); named != "" {
return named
}
if strings.TrimSpace(env("ASSISTANT_MODEL_BALANCED", env("ASSISTANT_MODEL", ""))) != "" {
return "openai"
}
return ""
}
// IsProduction is true under APP_ENV=production.
func (c *Config) IsProduction() bool { return c.AppEnv == EnvProduction }
@@ -249,7 +318,11 @@ func Load() (*Config, error) {
},
Assistant: AssistantConfig{
Provider: strings.ToLower(env("ASSISTANT_PROVIDER", "")),
// Defaults to "openai" when a model is named, because every endpoint
// this speaks is OpenAI-compatible and the base URL is what actually
// distinguishes them. One less variable to set, and one less way to
// have the assistant silently off.
Provider: assistantProvider(),
BaseURL: env("ASSISTANT_BASE_URL", ""),
APIKey: env("ASSISTANT_API_KEY", ""),
Fast: env("ASSISTANT_MODEL_FAST", ""),