From 3aa34ff0e607fb33d071dec03f1209c678872cea Mon Sep 17 00:00:00 2001 From: nicole pardal Date: Mon, 29 Sep 2025 20:06:53 -0700 Subject: [PATCH] added fetch --- api/client.go | 9 +++++++++ api/types.go | 11 +++++++++++ server/routes.go | 51 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+) diff --git a/api/client.go b/api/client.go index bd74f7acd..e47fa2147 100644 --- a/api/client.go +++ b/api/client.go @@ -379,6 +379,15 @@ func (c *Client) WebSearch(ctx context.Context, req *SearchRequest) (*SearchResp return &sr, nil } +// Fetch retrieves content from a URL using the ollama service. +func (c *Client) Fetch(ctx context.Context, req *FetchRequest) (*FetchResponse, error) { + var fr FetchResponse + if err := c.do(ctx, http.MethodPost, "/api/web_fetch", req, &fr); err != nil { + return nil, err + } + return &fr, nil +} + // Copy copies a model - creating a model with another name from an existing // model. func (c *Client) Copy(ctx context.Context, req *CopyRequest) error { diff --git a/api/types.go b/api/types.go index 909277b94..e36ba59d4 100644 --- a/api/types.go +++ b/api/types.go @@ -1074,3 +1074,14 @@ type SearchResult struct { type SearchResponse struct { Results []SearchResult `json:"results"` } + +// Web fetch types +type FetchRequest struct { + URL string `json:"url"` +} + +type FetchResponse struct { + Content string `json:"content"` + Title string `json:"title,omitempty"` + URL string `json:"url"` +} diff --git a/server/routes.go b/server/routes.go index 85c0fa7fa..8e4311a6a 100644 --- a/server/routes.go +++ b/server/routes.go @@ -1280,6 +1280,56 @@ func (s *Server) callWebSearchAPI(query string, maxResults int) ([]api.SearchRes return searchResp.Results, nil } +func (s *Server) FetchHandler(c *gin.Context) { + var req api.FetchRequest + if err := c.ShouldBindJSON(&req); errors.Is(err, io.EOF) { + c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "missing request body"}) + return + } else if err != nil { + c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + // Validate required fields + if req.URL == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "url is required"}) + return + } + + // Call the real web fetch API + content, title, err := s.callWebFetchAPI(req.URL) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + resp := api.FetchResponse{ + Content: content, + Title: title, + URL: req.URL, + } + + c.JSON(http.StatusOK, resp) +} + +func (s *Server) callWebFetchAPI(targetURL string) (string, string, error) { + // Create request to ollama.com web fetch API + fetchReq := api.FetchRequest{ + URL: targetURL, + } + + // Create client to call ollama.com + client := api.NewClient(&url.URL{Scheme: "https", Host: "ollama.com"}, http.DefaultClient) + + // Call the web fetch API + fetchResp, err := client.Fetch(context.Background(), &fetchReq) + if err != nil { + return "", "", err + } + + return fetchResp.Content, fetchResp.Title, nil +} + func (s *Server) HeadBlobHandler(c *gin.Context) { path, err := GetBlobsPath(c.Param("digest")) if err != nil { @@ -1501,6 +1551,7 @@ func (s *Server) GenerateRoutes(rc *ollama.Registry) (http.Handler, error) { r.POST("/api/embed", s.EmbedHandler) r.POST("/api/embeddings", s.EmbeddingsHandler) r.POST("/api/web_search", s.WebSearchHandler) + r.POST("/api/web_fetch", s.FetchHandler) // Inference (OpenAI compatibility) r.POST("/v1/chat/completions", openai.ChatMiddleware(), s.ChatHandler)