Files
odidere/internal/llm/client_models.go
dwrz f6fb21d40c Replace go-openai with native client
Drop the github.com/sashabaranov/go-openai dependency in favor of an
internal HTTP client.

The LLM package is now split into:
- llm.go: type definitions (Message, ChatRequest, ChatResponse, etc.)
- client.go: client construction and configuration
- client_completions.go: Completions and CompletionsStream methods
- client_models.go: ListModels
- client_tools.go: tool call execution

CompletionsStream uses typed SSE events (delta, done, message) so the
frontend can render progressive streaming output.

Move tool.go and tool_test.go from internal/tool/ into internal/llm/
to co-locate the registry with the client that consumes it.

Update job.go and service.go to use the new llm.Message types and
Completions/CompletionsStream methods. Remove the Voice field from
chat request/response (voice is now tracked via message metadata).

Frontend: handle delta/done/message SSE events, render a streaming
placeholder that receives progressive text deltas, and finalize with
full message binding (reasoning, tool calls, actions).
2026-06-26 14:22:05 +00:00

93 lines
2.0 KiB
Go

package llm
import (
"context"
"encoding/json"
"fmt"
"io"
"log/slog"
"net/http"
)
// ListModels returns available models from the LLM server.
func (c *Client) ListModels(ctx context.Context) ([]Model, error) {
ctx, cancel := context.WithTimeout(ctx, c.timeout)
defer cancel()
req, err := http.NewRequestWithContext(
ctx, http.MethodGet, c.baseURL+"/models", nil,
)
if err != nil {
return nil, fmt.Errorf("create request: %w", err)
}
if c.key != "" {
req.Header.Set("Authorization", "Bearer "+c.key)
}
req.Header.Set("Content-Type", "application/json")
res, err := c.httpc.Do(req)
if err != nil {
return nil, fmt.Errorf("list models: %w", err)
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
body, err := io.ReadAll(res.Body)
if err != nil {
c.log.ErrorContext(
ctx,
"list models: failed to read error response body",
slog.Any("error", err),
)
return nil, fmt.Errorf(
"list models: status code %d", res.StatusCode,
)
}
var apiError struct {
Error *struct {
Type string `json:"type"`
Code string `json:"code"`
Message string `json:"message"`
} `json:"error"`
}
if err := json.Unmarshal(
body, &apiError,
); err == nil && apiError.Error != nil {
c.log.ErrorContext(
ctx,
"list models: request error",
slog.Int("status", res.StatusCode),
slog.String("code", apiError.Error.Code),
slog.String(
"message", apiError.Error.Message,
),
slog.String("type", apiError.Error.Type),
)
return nil, fmt.Errorf(
"list models: %s: %s",
apiError.Error.Code,
apiError.Error.Message,
)
}
c.log.ErrorContext(
ctx,
"list models: API error",
slog.Int("status", res.StatusCode),
slog.String("body", string(body)),
)
return nil, fmt.Errorf(
"list models: %d: %s",
res.StatusCode, string(body),
)
}
var body ModelsResponse
if err := json.NewDecoder(res.Body).Decode(&body); err != nil {
return nil, fmt.Errorf(
"list models: decode response: %w", err,
)
}
return body.Data, nil
}