Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 9ac4643

Browse files
committed
Initial chat schema
1 parent f2d24bc commit 9ac4643

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1969
-29
lines changed

cli/server.go

+48
Original file line numberDiff line numberDiff line change
@@ -2610,6 +2610,54 @@ func redirectHTTPToHTTPSDeprecation(ctx context.Context, logger slog.Logger, inv
26102610
}
26112611
}
26122612

2613+
func ReadAIProvidersFromEnv(environ []string) ([]codersdk.AIProviderConfig, error) {
2614+
// The index numbers must be in-order.
2615+
sort.Strings(environ)
2616+
2617+
var providers []codersdk.AIProviderConfig
2618+
for _, v := range serpent.ParseEnviron(environ, "CODER_AI_PROVIDER_") {
2619+
tokens := strings.SplitN(v.Name, "_", 2)
2620+
if len(tokens) != 2 {
2621+
return nil, xerrors.Errorf("invalid env var: %s", v.Name)
2622+
}
2623+
2624+
providerNum, err := strconv.Atoi(tokens[0])
2625+
if err != nil {
2626+
return nil, xerrors.Errorf("parse number: %s", v.Name)
2627+
}
2628+
2629+
var provider codersdk.AIProviderConfig
2630+
switch {
2631+
case len(providers) < providerNum:
2632+
return nil, xerrors.Errorf(
2633+
"provider num %v skipped: %s",
2634+
len(providers),
2635+
v.Name,
2636+
)
2637+
case len(providers) == providerNum:
2638+
// At the next next provider.
2639+
providers = append(providers, provider)
2640+
case len(providers) == providerNum+1:
2641+
// At the current provider.
2642+
provider = providers[providerNum]
2643+
}
2644+
2645+
key := tokens[1]
2646+
switch key {
2647+
case "TYPE":
2648+
provider.Type = v.Value
2649+
case "API_KEY":
2650+
provider.APIKey = v.Value
2651+
case "BASE_URL":
2652+
provider.BaseURL = v.Value
2653+
case "MODELS":
2654+
provider.Models = strings.Split(v.Value, " ")
2655+
}
2656+
providers[providerNum] = provider
2657+
}
2658+
return providers, nil
2659+
}
2660+
26132661
// ReadExternalAuthProvidersFromEnv is provided for compatibility purposes with
26142662
// the viper CLI.
26152663
func ReadExternalAuthProvidersFromEnv(environ []string) ([]codersdk.ExternalAuthConfig, error) {

cli/testdata/TestProvisioners_Golden/list.golden

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
ID CREATED AT LAST SEEN AT NAME VERSION TAGS KEY NAME STATUS CURRENT JOB ID CURRENT JOB STATUS PREVIOUS JOB ID PREVIOUS JOB STATUS ORGANIZATION
1+
ID CREATED AT LAST SEEN AT NAME VERSION TAGS KEY NAME STATUS CURRENT JOB ID CURRENT JOB STATUS PREVIOUS JOB ID PREVIOUS JOB STATUS ORGANIZATION
22
00000000-0000-0000-aaaa-000000000000 ====[timestamp]===== ====[timestamp]===== default-provisioner v0.0.0-devel map[owner: scope:organization] built-in idle <nil> <nil> 00000000-0000-0000-bbbb-000000000001 succeeded Coder
33
00000000-0000-0000-aaaa-000000000001 ====[timestamp]===== ====[timestamp]===== provisioner-1 v0.0.0 map[foo:bar owner: scope:organization] built-in busy 00000000-0000-0000-bbbb-000000000002 running <nil> <nil> Coder
44
00000000-0000-0000-aaaa-000000000002 ====[timestamp]===== ====[timestamp]===== provisioner-2 v0.0.0 map[owner: scope:organization] built-in offline <nil> <nil> 00000000-0000-0000-bbbb-000000000003 succeeded Coder
+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
CREATED AT LAST SEEN AT KEY NAME NAME VERSION STATUS TAGS
1+
CREATED AT LAST SEEN AT KEY NAME NAME VERSION STATUS TAGS
22
====[timestamp]===== ====[timestamp]===== built-in test v0.0.0-devel idle map[owner: scope:organization]

cli/testdata/server-config.yaml.golden

+4
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,10 @@ client:
519519
# Support links to display in the top right drop down menu.
520520
# (default: <unset>, type: struct[[]codersdk.LinkConfig])
521521
supportLinks: []
522+
# Configure AI providers.
523+
# (default: <unset>, type: struct[codersdk.AIConfig])
524+
ai:
525+
providers: []
522526
# External Authentication providers.
523527
# (default: <unset>, type: struct[[]codersdk.ExternalAuthConfig])
524528
externalAuthProviders: []

coderd/ai/ai.go

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package ai
2+
3+
import (
4+
"context"
5+
6+
"github.com/kylecarbs/aisdk-go"
7+
)
8+
9+
type Provider func(ctx context.Context, messages []aisdk.Message) (aisdk.DataStream, error)

coderd/apidoc/docs.go

+44
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/apidoc/swagger.json

+44
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/chat.go

+138
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
package coderd
2+
3+
import (
4+
"encoding/json"
5+
"net/http"
6+
"time"
7+
8+
"github.com/coder/coder/v2/coderd/database"
9+
"github.com/coder/coder/v2/coderd/database/db2sdk"
10+
"github.com/coder/coder/v2/coderd/httpapi"
11+
"github.com/coder/coder/v2/coderd/httpmw"
12+
"github.com/coder/coder/v2/codersdk"
13+
"github.com/google/uuid"
14+
"github.com/kylecarbs/aisdk-go"
15+
)
16+
17+
// postChats creates a new chat.
18+
//
19+
// @Summary Create a chat
20+
// @ID post-chat
21+
// @Security CoderSessionToken
22+
// @Produce json
23+
// @Tags Chat
24+
// @Success 201 {object} codersdk.Chat
25+
// @Router /chats [post]
26+
func (api *API) postChats(w http.ResponseWriter, r *http.Request) {
27+
apiKey := httpmw.APIKey(r)
28+
ctx := r.Context()
29+
30+
chat, err := api.Database.InsertChat(ctx, database.InsertChatParams{
31+
ID: uuid.New(),
32+
OwnerID: apiKey.UserID,
33+
CreatedAt: time.Now(),
34+
UpdatedAt: time.Now(),
35+
Title: "New Chat",
36+
})
37+
if err != nil {
38+
httpapi.Write(ctx, w, http.StatusInternalServerError, codersdk.Response{
39+
Message: "Failed to create chat",
40+
Detail: err.Error(),
41+
})
42+
return
43+
}
44+
45+
httpapi.Write(ctx, w, http.StatusCreated, db2sdk.Chat(chat))
46+
}
47+
48+
// listChats lists all chats for a user.
49+
//
50+
// @Summary List chats
51+
// @ID list-chats
52+
// @Security CoderSessionToken
53+
// @Produce json
54+
// @Tags Chat
55+
// @Success 200 {array} codersdk.Chat
56+
// @Router /chats [get]
57+
func (api *API) listChats(w http.ResponseWriter, r *http.Request) {
58+
apiKey := httpmw.APIKey(r)
59+
ctx := r.Context()
60+
61+
chats, err := api.Database.GetChatsByOwnerID(ctx, apiKey.UserID)
62+
if err != nil {
63+
httpapi.Write(ctx, w, http.StatusInternalServerError, codersdk.Response{
64+
Message: "Failed to list chats",
65+
Detail: err.Error(),
66+
})
67+
}
68+
69+
httpapi.Write(ctx, w, http.StatusOK, db2sdk.Chats(chats))
70+
}
71+
72+
// chat returns a chat by ID.
73+
//
74+
// @Summary Get a chat
75+
// @ID get-chat
76+
// @Security CoderSessionToken
77+
// @Produce json
78+
// @Tags Chat
79+
// @Success 200 {object} codersdk.Chat
80+
// @Router /chats/{chat} [get]
81+
func (api *API) chat(w http.ResponseWriter, r *http.Request) {
82+
ctx := r.Context()
83+
chat := httpmw.ChatParam(r)
84+
httpapi.Write(ctx, w, http.StatusOK, db2sdk.Chat(chat))
85+
}
86+
87+
// chatMessages returns the messages of a chat.
88+
//
89+
// @Summary Get chat messages
90+
// @ID get-chat-messages
91+
// @Security CoderSessionToken
92+
// @Produce json
93+
// @Tags Chat
94+
// @Success 200 {array} aisdk.Message
95+
// @Router /chats/{chat}/messages [get]
96+
func (api *API) chatMessages(w http.ResponseWriter, r *http.Request) {
97+
ctx := r.Context()
98+
chat := httpmw.ChatParam(r)
99+
rawMessages, err := api.Database.GetChatMessagesByChatID(ctx, chat.ID)
100+
if err != nil {
101+
httpapi.Write(ctx, w, http.StatusInternalServerError, codersdk.Response{
102+
Message: "Failed to get chat messages",
103+
Detail: err.Error(),
104+
})
105+
}
106+
messages := make([]aisdk.Message, len(rawMessages))
107+
for i, message := range rawMessages {
108+
var msg aisdk.Message
109+
err = json.Unmarshal(message.Content, &msg)
110+
if err != nil {
111+
httpapi.Write(ctx, w, http.StatusInternalServerError, codersdk.Response{
112+
Message: "Failed to unmarshal chat message",
113+
Detail: err.Error(),
114+
})
115+
}
116+
messages[i] = msg
117+
}
118+
119+
httpapi.Write(ctx, w, http.StatusOK, messages)
120+
}
121+
122+
func (api *API) postChatMessage(w http.ResponseWriter, r *http.Request) {
123+
ctx := r.Context()
124+
chat := httpmw.ChatParam(r)
125+
var message aisdk.Message
126+
err := json.NewDecoder(r.Body).Decode(&message)
127+
if err != nil {
128+
httpapi.Write(ctx, w, http.StatusBadRequest, codersdk.Response{
129+
Message: "Failed to decode chat message",
130+
Detail: err.Error(),
131+
})
132+
}
133+
134+
var stream aisdk.DataStream
135+
stream.WithToolCalling(func(toolCall aisdk.ToolCall) any {
136+
return nil
137+
})
138+
}

coderd/coderd.go

+10
Original file line numberDiff line numberDiff line change
@@ -974,6 +974,16 @@ func New(options *Options) *API {
974974
r.Get("/{fileID}", api.fileByID)
975975
r.Post("/", api.postFile)
976976
})
977+
r.Route("/chats", func(r chi.Router) {
978+
r.Use(apiKeyMiddleware)
979+
r.Get("/", api.listChats)
980+
r.Post("/", api.postChats)
981+
r.Route("/{chat}", func(r chi.Router) {
982+
r.Use(httpmw.ExtractChatParam(options.Database))
983+
r.Get("/", api.chat)
984+
r.Get("/messages", api.chatMessages)
985+
})
986+
})
977987
r.Route("/external-auth", func(r chi.Router) {
978988
r.Use(
979989
apiKeyMiddleware,

0 commit comments

Comments
 (0)