Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions cmd/dotenv/promptModel.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/datarobot/cli/internal/drapi"
"github.com/datarobot/cli/internal/envbuilder"
"github.com/datarobot/cli/tui"
)
Expand Down Expand Up @@ -92,6 +93,10 @@ func (d itemDelegate) Render(w io.Writer, m list.Model, index int, listItem list
}

func newPromptModel(prompt envbuilder.UserPrompt, successCmd tea.Cmd) (promptModel, tea.Cmd) {
if prompt.Type == "llmgw_catalog" {
return newLLMListPrompt(prompt, successCmd)
}

if len(prompt.Options) == 0 {
return newTextInputPrompt(prompt, successCmd)
}
Expand Down Expand Up @@ -170,6 +175,25 @@ func newListPrompt(prompt envbuilder.UserPrompt, successCmd tea.Cmd) (promptMode
}, cmd
}

func newLLMListPrompt(prompt envbuilder.UserPrompt, successCmd tea.Cmd) (promptModel, tea.Cmd) {
llms, err := drapi.GetLLMs()
if err != nil {
return promptModel{}, nil
}

for _, llm := range llms.LLMs {
prompt.Options = append(prompt.Options, envbuilder.PromptOption{
Blank: false,
Checked: false,
Name: fmt.Sprintf("%s (%s)", llm.Name, llm.Provider),
Value: llm.LlmID,
Requires: "",
})
}

return newListPrompt(prompt, successCmd)
}

func (pm promptModel) GetValues() []string {
if len(pm.prompt.Options) == 0 {
return []string{strings.TrimSpace(pm.input.Value())}
Expand Down
2 changes: 1 addition & 1 deletion internal/config/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func GetEndpointURL(endpoint string) (string, error) {
return "", errors.New("Empty URL.")
}

return url.JoinPath(baseURL, endpoint)
return baseURL + endpoint, nil
}

func GetUserAgentHeader() string {
Expand Down
85 changes: 85 additions & 0 deletions internal/drapi/get.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Copyright 2025 DataRobot, Inc. and its affiliates.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package drapi

import (
"encoding/json"
"errors"
"net/http"
"time"

"github.com/charmbracelet/log"
"github.com/datarobot/cli/internal/config"
)

var token string

func Get(url, info string) (*http.Response, error) {
var err error

// memoize token to avoid extra VerifyToken() calls
if token == "" {
token, err = config.GetAPIKey()
if err != nil {
return nil, err
}
}

req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return nil, err
}

req.Header.Add("Authorization", "Bearer "+token)
req.Header.Add("User-Agent", config.GetUserAgentHeader())

if info != "" {
log.Infof("Fetching %s from: %s", info, url)
}

log.Debug("Request Info: \n" + config.RedactedReqInfo(req))

client := &http.Client{
Timeout: 30 * time.Second,
}

resp, err := client.Do(req)
if err != nil {
return nil, err
}

if resp.StatusCode != http.StatusOK {
resp.Body.Close()
return nil, errors.New("Response status code is " + resp.Status + ".")
}

return resp, err
}

func GetJSON(url, info string, v any) error {
resp, err := Get(url, info)
if err != nil {
return err
}

err = json.NewDecoder(resp.Body).Decode(&v)
if err != nil {
return err
}

resp.Body.Close()

return nil
}
96 changes: 96 additions & 0 deletions internal/drapi/llms.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Copyright 2025 DataRobot, Inc. and its affiliates.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package drapi

import (
"github.com/datarobot/cli/internal/config"
)

type LLM struct {
LlmID string `json:"llmId"`
Name string `json:"name"`
Provider string `json:"provider"`
IsActive bool `json:"isActive"`

//Model string `json:"model"`
//Version string `json:"version"`
//Description string `json:"description"`
//Creator string `json:"creator"`
//ContextSize int `json:"contextSize"`
//MaxCompletionTokens int `json:"maxCompletionTokens"`
//Capabilities []string `json:"capabilities"`
//SupportedLanguages []string `json:"supportedLanguages"`
//InputTypes []string `json:"inputTypes"`
//OutputTypes []string `json:"outputTypes"`
//DocumentationLink string `json:"documentationLink"`
//DateAdded string `json:"dateAdded"`
//License string `json:"license"`
//IsPreview bool `json:"isPreview"`
//IsMetered bool `json:"isMetered"`
//RetirementDate string `json:"retirementDate"`
//SuggestedReplacement string `json:"suggestedReplacement"`
//IsDeprecated bool `json:"isDeprecated"`
//AvailableRegions []string `json:"availableRegions"`
//
//ReferenceLinks []struct {
// Name string `json:"name"`
// URL string `json:"url"`
//} `json:"referenceLinks"`
//
//AvailableLitellmEndpoints struct {
// SupportsChatCompletions bool `json:"supportsChatCompletions"`
// SupportsResponses bool `json:"supportsResponses"`
//} `json:"availableLitellmEndpoints"`
}

type LLMList struct {
LLMs []LLM `json:"data"`
Count int `json:"count"`
TotalCount int `json:"totalCount"`
Next string `json:"next"`
Previous string `json:"previous"`
}

func GetLLMs() (*LLMList, error) {
url, err := config.GetEndpointURL("/api/v2/genai/llmgw/catalog/?limit=100")
if err != nil {
return nil, err
}

var llmList LLMList

var active []LLM

for url != "" {
llmList = LLMList{}

err = GetJSON(url, "LLMs", &llmList)
if err != nil {
return nil, err
}

for _, llm := range llmList.LLMs {
if llm.IsActive {
active = append(active, llm)
}
}

url = llmList.Next
}

llmList.LLMs = active

return &llmList, nil
}
48 changes: 12 additions & 36 deletions internal/drapi/templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,11 @@
package drapi

import (
"encoding/json"
"errors"
"fmt"
"net/http"
"sort"
"strings"
"time"

"github.com/charmbracelet/log"
"github.com/datarobot/cli/internal/config"
)

Expand Down Expand Up @@ -126,48 +122,28 @@ func (tl TemplateList) SortByName() TemplateList {
}

func GetTemplates() (*TemplateList, error) {
datarobotEndpoint, err := config.GetEndpointURL("/api/v2/applicationTemplates/")
url, err := config.GetEndpointURL("/api/v2/applicationTemplates/?limit=100")
if err != nil {
return nil, err
}

log.Info("Fetching templates from " + datarobotEndpoint)

req, err := http.NewRequest(http.MethodGet, datarobotEndpoint+"?limit=100", nil)
if err != nil {
return nil, err
}

token, err := config.GetAPIKey()
if err != nil {
return nil, err
}

req.Header.Add("Authorization", "Bearer "+token)
req.Header.Add("User-Agent", config.GetUserAgentHeader())
var templateList TemplateList

log.Debug("Request Info: \n" + config.RedactedReqInfo(req))
var templates []Template

client := &http.Client{
Timeout: 30 * time.Second,
}
for url != "" {
templateList = TemplateList{}

resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
err = GetJSON(url, "templates", &templateList)
if err != nil {
return nil, err
}

if resp.StatusCode != http.StatusOK {
return nil, errors.New("Response status code is " + resp.Status + ".")
templates = append(templates, templateList.Templates...)
url = templateList.Next
}

var templateList TemplateList

err = json.NewDecoder(resp.Body).Decode(&templateList)
if err != nil {
return nil, err
}
templateList.Templates = templates

return &templateList, nil
}
Expand Down
Loading