chore: remove chats experiment (#18535)
This commit is contained in:
@@ -61,7 +61,6 @@ import (
|
||||
"github.com/coder/serpent"
|
||||
"github.com/coder/wgtunnel/tunnelsdk"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/ai"
|
||||
"github.com/coder/coder/v2/coderd/entitlements"
|
||||
"github.com/coder/coder/v2/coderd/notifications/reports"
|
||||
"github.com/coder/coder/v2/coderd/runtimeconfig"
|
||||
@@ -611,22 +610,6 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
|
||||
)
|
||||
}
|
||||
|
||||
aiProviders, err := ReadAIProvidersFromEnv(os.Environ())
|
||||
if err != nil {
|
||||
return xerrors.Errorf("read ai providers from env: %w", err)
|
||||
}
|
||||
vals.AI.Value.Providers = append(vals.AI.Value.Providers, aiProviders...)
|
||||
for _, provider := range aiProviders {
|
||||
logger.Debug(
|
||||
ctx, "loaded ai provider",
|
||||
slog.F("type", provider.Type),
|
||||
)
|
||||
}
|
||||
languageModels, err := ai.ModelsFromConfig(ctx, vals.AI.Value.Providers)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("create language models: %w", err)
|
||||
}
|
||||
|
||||
realIPConfig, err := httpmw.ParseRealIPConfig(vals.ProxyTrustedHeaders, vals.ProxyTrustedOrigins)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("parse real ip config: %w", err)
|
||||
@@ -657,7 +640,6 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
|
||||
CacheDir: cacheDir,
|
||||
GoogleTokenValidator: googleTokenValidator,
|
||||
ExternalAuthConfigs: externalAuthConfigs,
|
||||
LanguageModels: languageModels,
|
||||
RealIPConfig: realIPConfig,
|
||||
SSHKeygenAlgorithm: sshKeygenAlgorithm,
|
||||
TracerProvider: tracerProvider,
|
||||
|
||||
-3
@@ -526,9 +526,6 @@ client:
|
||||
# Support links to display in the top right drop down menu.
|
||||
# (default: <unset>, type: struct[[]codersdk.LinkConfig])
|
||||
supportLinks: []
|
||||
# Configure AI providers.
|
||||
# (default: <unset>, type: struct[codersdk.AIConfig])
|
||||
ai: {}
|
||||
# External Authentication providers.
|
||||
# (default: <unset>, type: struct[[]codersdk.ExternalAuthConfig])
|
||||
externalAuthProviders: []
|
||||
|
||||
-167
@@ -1,167 +0,0 @@
|
||||
package ai
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/anthropics/anthropic-sdk-go"
|
||||
anthropicoption "github.com/anthropics/anthropic-sdk-go/option"
|
||||
"github.com/kylecarbs/aisdk-go"
|
||||
"github.com/openai/openai-go"
|
||||
openaioption "github.com/openai/openai-go/option"
|
||||
"golang.org/x/xerrors"
|
||||
"google.golang.org/genai"
|
||||
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
)
|
||||
|
||||
type LanguageModel struct {
|
||||
codersdk.LanguageModel
|
||||
StreamFunc StreamFunc
|
||||
}
|
||||
|
||||
type StreamOptions struct {
|
||||
SystemPrompt string
|
||||
Model string
|
||||
Messages []aisdk.Message
|
||||
Thinking bool
|
||||
Tools []aisdk.Tool
|
||||
}
|
||||
|
||||
type StreamFunc func(ctx context.Context, options StreamOptions) (aisdk.DataStream, error)
|
||||
|
||||
// LanguageModels is a map of language model ID to language model.
|
||||
type LanguageModels map[string]LanguageModel
|
||||
|
||||
func ModelsFromConfig(ctx context.Context, configs []codersdk.AIProviderConfig) (LanguageModels, error) {
|
||||
models := make(LanguageModels)
|
||||
|
||||
for _, config := range configs {
|
||||
var streamFunc StreamFunc
|
||||
|
||||
switch config.Type {
|
||||
case "openai":
|
||||
opts := []openaioption.RequestOption{
|
||||
openaioption.WithAPIKey(config.APIKey),
|
||||
}
|
||||
if config.BaseURL != "" {
|
||||
opts = append(opts, openaioption.WithBaseURL(config.BaseURL))
|
||||
}
|
||||
client := openai.NewClient(opts...)
|
||||
streamFunc = func(ctx context.Context, options StreamOptions) (aisdk.DataStream, error) {
|
||||
openaiMessages, err := aisdk.MessagesToOpenAI(options.Messages)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tools := aisdk.ToolsToOpenAI(options.Tools)
|
||||
if options.SystemPrompt != "" {
|
||||
openaiMessages = append([]openai.ChatCompletionMessageParamUnion{
|
||||
openai.SystemMessage(options.SystemPrompt),
|
||||
}, openaiMessages...)
|
||||
}
|
||||
|
||||
return aisdk.OpenAIToDataStream(client.Chat.Completions.NewStreaming(ctx, openai.ChatCompletionNewParams{
|
||||
Messages: openaiMessages,
|
||||
Model: options.Model,
|
||||
Tools: tools,
|
||||
MaxTokens: openai.Int(8192),
|
||||
})), nil
|
||||
}
|
||||
if config.Models == nil {
|
||||
models, err := client.Models.List(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.Models = make([]string, len(models.Data))
|
||||
for i, model := range models.Data {
|
||||
config.Models[i] = model.ID
|
||||
}
|
||||
}
|
||||
case "anthropic":
|
||||
client := anthropic.NewClient(anthropicoption.WithAPIKey(config.APIKey))
|
||||
streamFunc = func(ctx context.Context, options StreamOptions) (aisdk.DataStream, error) {
|
||||
anthropicMessages, systemMessage, err := aisdk.MessagesToAnthropic(options.Messages)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if options.SystemPrompt != "" {
|
||||
systemMessage = []anthropic.TextBlockParam{
|
||||
*anthropic.NewTextBlock(options.SystemPrompt).OfRequestTextBlock,
|
||||
}
|
||||
}
|
||||
return aisdk.AnthropicToDataStream(client.Messages.NewStreaming(ctx, anthropic.MessageNewParams{
|
||||
Messages: anthropicMessages,
|
||||
Model: options.Model,
|
||||
System: systemMessage,
|
||||
Tools: aisdk.ToolsToAnthropic(options.Tools),
|
||||
MaxTokens: 8192,
|
||||
})), nil
|
||||
}
|
||||
if config.Models == nil {
|
||||
models, err := client.Models.List(ctx, anthropic.ModelListParams{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.Models = make([]string, len(models.Data))
|
||||
for i, model := range models.Data {
|
||||
config.Models[i] = model.ID
|
||||
}
|
||||
}
|
||||
case "google":
|
||||
client, err := genai.NewClient(ctx, &genai.ClientConfig{
|
||||
APIKey: config.APIKey,
|
||||
Backend: genai.BackendGeminiAPI,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
streamFunc = func(ctx context.Context, options StreamOptions) (aisdk.DataStream, error) {
|
||||
googleMessages, err := aisdk.MessagesToGoogle(options.Messages)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tools, err := aisdk.ToolsToGoogle(options.Tools)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var systemInstruction *genai.Content
|
||||
if options.SystemPrompt != "" {
|
||||
systemInstruction = &genai.Content{
|
||||
Parts: []*genai.Part{
|
||||
genai.NewPartFromText(options.SystemPrompt),
|
||||
},
|
||||
Role: "model",
|
||||
}
|
||||
}
|
||||
return aisdk.GoogleToDataStream(client.Models.GenerateContentStream(ctx, options.Model, googleMessages, &genai.GenerateContentConfig{
|
||||
SystemInstruction: systemInstruction,
|
||||
Tools: tools,
|
||||
})), nil
|
||||
}
|
||||
if config.Models == nil {
|
||||
models, err := client.Models.List(ctx, &genai.ListModelsConfig{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.Models = make([]string, len(models.Items))
|
||||
for i, model := range models.Items {
|
||||
config.Models[i] = model.Name
|
||||
}
|
||||
}
|
||||
default:
|
||||
return nil, xerrors.Errorf("unsupported model type: %s", config.Type)
|
||||
}
|
||||
|
||||
for _, model := range config.Models {
|
||||
models[model] = LanguageModel{
|
||||
LanguageModel: codersdk.LanguageModel{
|
||||
ID: model,
|
||||
DisplayName: model,
|
||||
Provider: config.Type,
|
||||
},
|
||||
StreamFunc: streamFunc,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return models, nil
|
||||
}
|
||||
Generated
+3
-589
@@ -343,173 +343,6 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/chats": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"CoderSessionToken": []
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Chat"
|
||||
],
|
||||
"summary": "List chats",
|
||||
"operationId": "list-chats",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/codersdk.Chat"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"CoderSessionToken": []
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Chat"
|
||||
],
|
||||
"summary": "Create a chat",
|
||||
"operationId": "create-a-chat",
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Created",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/codersdk.Chat"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/chats/{chat}": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"CoderSessionToken": []
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Chat"
|
||||
],
|
||||
"summary": "Get a chat",
|
||||
"operationId": "get-a-chat",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Chat ID",
|
||||
"name": "chat",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/codersdk.Chat"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/chats/{chat}/messages": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"CoderSessionToken": []
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Chat"
|
||||
],
|
||||
"summary": "Get chat messages",
|
||||
"operationId": "get-chat-messages",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Chat ID",
|
||||
"name": "chat",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/aisdk.Message"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"CoderSessionToken": []
|
||||
}
|
||||
],
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Chat"
|
||||
],
|
||||
"summary": "Create a chat message",
|
||||
"operationId": "create-a-chat-message",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Chat ID",
|
||||
"name": "chat",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "Request body",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/codersdk.CreateChatMessageRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/csp/reports": {
|
||||
"post": {
|
||||
"security": [
|
||||
@@ -826,31 +659,6 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/deployment/llms": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"CoderSessionToken": []
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"General"
|
||||
],
|
||||
"summary": "Get language models",
|
||||
"operationId": "get-language-models",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/codersdk.LanguageModelConfig"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/deployment/ssh": {
|
||||
"get": {
|
||||
"security": [
|
||||
@@ -10617,190 +10425,6 @@ const docTemplate = `{
|
||||
"ReinitializeReasonPrebuildClaimed"
|
||||
]
|
||||
},
|
||||
"aisdk.Attachment": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"contentType": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"aisdk.Message": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"annotations": {
|
||||
"type": "array",
|
||||
"items": {}
|
||||
},
|
||||
"content": {
|
||||
"type": "string"
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"experimental_attachments": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/aisdk.Attachment"
|
||||
}
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"parts": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/aisdk.Part"
|
||||
}
|
||||
},
|
||||
"role": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"aisdk.Part": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"details": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/aisdk.ReasoningDetail"
|
||||
}
|
||||
},
|
||||
"mimeType": {
|
||||
"description": "Type: \"file\"",
|
||||
"type": "string"
|
||||
},
|
||||
"reasoning": {
|
||||
"description": "Type: \"reasoning\"",
|
||||
"type": "string"
|
||||
},
|
||||
"source": {
|
||||
"description": "Type: \"source\"",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/aisdk.SourceInfo"
|
||||
}
|
||||
]
|
||||
},
|
||||
"text": {
|
||||
"description": "Type: \"text\"",
|
||||
"type": "string"
|
||||
},
|
||||
"toolInvocation": {
|
||||
"description": "Type: \"tool-invocation\"",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/aisdk.ToolInvocation"
|
||||
}
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"$ref": "#/definitions/aisdk.PartType"
|
||||
}
|
||||
}
|
||||
},
|
||||
"aisdk.PartType": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"text",
|
||||
"reasoning",
|
||||
"tool-invocation",
|
||||
"source",
|
||||
"file",
|
||||
"step-start"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"PartTypeText",
|
||||
"PartTypeReasoning",
|
||||
"PartTypeToolInvocation",
|
||||
"PartTypeSource",
|
||||
"PartTypeFile",
|
||||
"PartTypeStepStart"
|
||||
]
|
||||
},
|
||||
"aisdk.ReasoningDetail": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "string"
|
||||
},
|
||||
"signature": {
|
||||
"type": "string"
|
||||
},
|
||||
"text": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"aisdk.SourceInfo": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"contentType": {
|
||||
"type": "string"
|
||||
},
|
||||
"data": {
|
||||
"type": "string"
|
||||
},
|
||||
"metadata": {
|
||||
"type": "object",
|
||||
"additionalProperties": {}
|
||||
},
|
||||
"uri": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"aisdk.ToolInvocation": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"args": {},
|
||||
"result": {},
|
||||
"state": {
|
||||
"$ref": "#/definitions/aisdk.ToolInvocationState"
|
||||
},
|
||||
"step": {
|
||||
"type": "integer"
|
||||
},
|
||||
"toolCallId": {
|
||||
"type": "string"
|
||||
},
|
||||
"toolName": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"aisdk.ToolInvocationState": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"call",
|
||||
"partial-call",
|
||||
"result"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"ToolInvocationStateCall",
|
||||
"ToolInvocationStatePartialCall",
|
||||
"ToolInvocationStateResult"
|
||||
]
|
||||
},
|
||||
"coderd.SCIMUser": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -10892,37 +10516,6 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.AIConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"providers": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/codersdk.AIProviderConfig"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.AIProviderConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"base_url": {
|
||||
"description": "BaseURL is the base URL to use for the API provider.",
|
||||
"type": "string"
|
||||
},
|
||||
"models": {
|
||||
"description": "Models is the list of models to use for the API provider.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
"description": "Type is the type of the API provider.",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.APIKey": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@@ -11508,62 +11101,6 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.Chat": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"created_at": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.ChatMessage": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"annotations": {
|
||||
"type": "array",
|
||||
"items": {}
|
||||
},
|
||||
"content": {
|
||||
"type": "string"
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"experimental_attachments": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/aisdk.Attachment"
|
||||
}
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"parts": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/aisdk.Part"
|
||||
}
|
||||
},
|
||||
"role": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.ConnectionLatency": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -11597,20 +11134,6 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.CreateChatMessageRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"$ref": "#/definitions/codersdk.ChatMessage"
|
||||
},
|
||||
"model": {
|
||||
"type": "string"
|
||||
},
|
||||
"thinking": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.CreateFirstUserRequest": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@@ -11898,73 +11421,7 @@ const docTemplate = `{
|
||||
}
|
||||
},
|
||||
"codersdk.CreateTestAuditLogRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"action": {
|
||||
"enum": [
|
||||
"create",
|
||||
"write",
|
||||
"delete",
|
||||
"start",
|
||||
"stop"
|
||||
],
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/codersdk.AuditAction"
|
||||
}
|
||||
]
|
||||
},
|
||||
"additional_fields": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"build_reason": {
|
||||
"enum": [
|
||||
"autostart",
|
||||
"autostop",
|
||||
"initiator"
|
||||
],
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/codersdk.BuildReason"
|
||||
}
|
||||
]
|
||||
},
|
||||
"organization_id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"request_id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"resource_id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"resource_type": {
|
||||
"enum": [
|
||||
"template",
|
||||
"template_version",
|
||||
"user",
|
||||
"workspace",
|
||||
"workspace_build",
|
||||
"git_ssh_key",
|
||||
"auditable_group"
|
||||
],
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/codersdk.ResourceType"
|
||||
}
|
||||
]
|
||||
},
|
||||
"time": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
}
|
||||
}
|
||||
"type": "object"
|
||||
},
|
||||
"codersdk.CreateTokenRequest": {
|
||||
"type": "object",
|
||||
@@ -12410,9 +11867,6 @@ const docTemplate = `{
|
||||
"agent_stat_refresh_interval": {
|
||||
"type": "integer"
|
||||
},
|
||||
"ai": {
|
||||
"$ref": "#/definitions/serpent.Struct-codersdk_AIConfig"
|
||||
},
|
||||
"allow_workspace_renames": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@@ -12741,11 +12195,9 @@ const docTemplate = `{
|
||||
"notifications",
|
||||
"workspace-usage",
|
||||
"web-push",
|
||||
"workspace-prebuilds",
|
||||
"agentic-chat"
|
||||
"workspace-prebuilds"
|
||||
],
|
||||
"x-enum-comments": {
|
||||
"ExperimentAgenticChat": "Enables the new agentic AI chat feature.",
|
||||
"ExperimentAutoFillParameters": "This should not be taken out of experiments until we have redesigned the feature.",
|
||||
"ExperimentExample": "This isn't used for anything.",
|
||||
"ExperimentNotifications": "Sends notifications via SMTP and webhooks following certain events.",
|
||||
@@ -12759,8 +12211,7 @@ const docTemplate = `{
|
||||
"ExperimentNotifications",
|
||||
"ExperimentWorkspaceUsage",
|
||||
"ExperimentWebPush",
|
||||
"ExperimentWorkspacePrebuilds",
|
||||
"ExperimentAgenticChat"
|
||||
"ExperimentWorkspacePrebuilds"
|
||||
]
|
||||
},
|
||||
"codersdk.ExternalAuth": {
|
||||
@@ -13288,33 +12739,6 @@ const docTemplate = `{
|
||||
"RequiredTemplateVariables"
|
||||
]
|
||||
},
|
||||
"codersdk.LanguageModel": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"display_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"description": "ID is used by the provider to identify the LLM.",
|
||||
"type": "string"
|
||||
},
|
||||
"provider": {
|
||||
"description": "Provider is the provider of the LLM. e.g. openai, anthropic, etc.",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.LanguageModelConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"models": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/codersdk.LanguageModel"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.License": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -15233,7 +14657,6 @@ const docTemplate = `{
|
||||
"assign_org_role",
|
||||
"assign_role",
|
||||
"audit_log",
|
||||
"chat",
|
||||
"crypto_key",
|
||||
"debug_info",
|
||||
"deployment_config",
|
||||
@@ -15273,7 +14696,6 @@ const docTemplate = `{
|
||||
"ResourceAssignOrgRole",
|
||||
"ResourceAssignRole",
|
||||
"ResourceAuditLog",
|
||||
"ResourceChat",
|
||||
"ResourceCryptoKey",
|
||||
"ResourceDebugInfo",
|
||||
"ResourceDeploymentConfig",
|
||||
@@ -19342,14 +18764,6 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"serpent.Struct-codersdk_AIConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"value": {
|
||||
"$ref": "#/definitions/codersdk.AIConfig"
|
||||
}
|
||||
}
|
||||
},
|
||||
"serpent.URL": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
Generated
+3
-549
@@ -291,151 +291,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/chats": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"CoderSessionToken": []
|
||||
}
|
||||
],
|
||||
"produces": ["application/json"],
|
||||
"tags": ["Chat"],
|
||||
"summary": "List chats",
|
||||
"operationId": "list-chats",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/codersdk.Chat"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"CoderSessionToken": []
|
||||
}
|
||||
],
|
||||
"produces": ["application/json"],
|
||||
"tags": ["Chat"],
|
||||
"summary": "Create a chat",
|
||||
"operationId": "create-a-chat",
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Created",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/codersdk.Chat"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/chats/{chat}": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"CoderSessionToken": []
|
||||
}
|
||||
],
|
||||
"produces": ["application/json"],
|
||||
"tags": ["Chat"],
|
||||
"summary": "Get a chat",
|
||||
"operationId": "get-a-chat",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Chat ID",
|
||||
"name": "chat",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/codersdk.Chat"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/chats/{chat}/messages": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"CoderSessionToken": []
|
||||
}
|
||||
],
|
||||
"produces": ["application/json"],
|
||||
"tags": ["Chat"],
|
||||
"summary": "Get chat messages",
|
||||
"operationId": "get-chat-messages",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Chat ID",
|
||||
"name": "chat",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/aisdk.Message"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"CoderSessionToken": []
|
||||
}
|
||||
],
|
||||
"consumes": ["application/json"],
|
||||
"produces": ["application/json"],
|
||||
"tags": ["Chat"],
|
||||
"summary": "Create a chat message",
|
||||
"operationId": "create-a-chat-message",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Chat ID",
|
||||
"name": "chat",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "Request body",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/codersdk.CreateChatMessageRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/csp/reports": {
|
||||
"post": {
|
||||
"security": [
|
||||
@@ -708,27 +563,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/deployment/llms": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"CoderSessionToken": []
|
||||
}
|
||||
],
|
||||
"produces": ["application/json"],
|
||||
"tags": ["General"],
|
||||
"summary": "Get language models",
|
||||
"operationId": "get-language-models",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/codersdk.LanguageModelConfig"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/deployment/ssh": {
|
||||
"get": {
|
||||
"security": [
|
||||
@@ -9410,186 +9244,6 @@
|
||||
"enum": ["prebuild_claimed"],
|
||||
"x-enum-varnames": ["ReinitializeReasonPrebuildClaimed"]
|
||||
},
|
||||
"aisdk.Attachment": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"contentType": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"aisdk.Message": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"annotations": {
|
||||
"type": "array",
|
||||
"items": {}
|
||||
},
|
||||
"content": {
|
||||
"type": "string"
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"experimental_attachments": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/aisdk.Attachment"
|
||||
}
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"parts": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/aisdk.Part"
|
||||
}
|
||||
},
|
||||
"role": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"aisdk.Part": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"details": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/aisdk.ReasoningDetail"
|
||||
}
|
||||
},
|
||||
"mimeType": {
|
||||
"description": "Type: \"file\"",
|
||||
"type": "string"
|
||||
},
|
||||
"reasoning": {
|
||||
"description": "Type: \"reasoning\"",
|
||||
"type": "string"
|
||||
},
|
||||
"source": {
|
||||
"description": "Type: \"source\"",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/aisdk.SourceInfo"
|
||||
}
|
||||
]
|
||||
},
|
||||
"text": {
|
||||
"description": "Type: \"text\"",
|
||||
"type": "string"
|
||||
},
|
||||
"toolInvocation": {
|
||||
"description": "Type: \"tool-invocation\"",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/aisdk.ToolInvocation"
|
||||
}
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"$ref": "#/definitions/aisdk.PartType"
|
||||
}
|
||||
}
|
||||
},
|
||||
"aisdk.PartType": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"text",
|
||||
"reasoning",
|
||||
"tool-invocation",
|
||||
"source",
|
||||
"file",
|
||||
"step-start"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"PartTypeText",
|
||||
"PartTypeReasoning",
|
||||
"PartTypeToolInvocation",
|
||||
"PartTypeSource",
|
||||
"PartTypeFile",
|
||||
"PartTypeStepStart"
|
||||
]
|
||||
},
|
||||
"aisdk.ReasoningDetail": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "string"
|
||||
},
|
||||
"signature": {
|
||||
"type": "string"
|
||||
},
|
||||
"text": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"aisdk.SourceInfo": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"contentType": {
|
||||
"type": "string"
|
||||
},
|
||||
"data": {
|
||||
"type": "string"
|
||||
},
|
||||
"metadata": {
|
||||
"type": "object",
|
||||
"additionalProperties": {}
|
||||
},
|
||||
"uri": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"aisdk.ToolInvocation": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"args": {},
|
||||
"result": {},
|
||||
"state": {
|
||||
"$ref": "#/definitions/aisdk.ToolInvocationState"
|
||||
},
|
||||
"step": {
|
||||
"type": "integer"
|
||||
},
|
||||
"toolCallId": {
|
||||
"type": "string"
|
||||
},
|
||||
"toolName": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"aisdk.ToolInvocationState": {
|
||||
"type": "string",
|
||||
"enum": ["call", "partial-call", "result"],
|
||||
"x-enum-varnames": [
|
||||
"ToolInvocationStateCall",
|
||||
"ToolInvocationStatePartialCall",
|
||||
"ToolInvocationStateResult"
|
||||
]
|
||||
},
|
||||
"coderd.SCIMUser": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -9681,37 +9335,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.AIConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"providers": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/codersdk.AIProviderConfig"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.AIProviderConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"base_url": {
|
||||
"description": "BaseURL is the base URL to use for the API provider.",
|
||||
"type": "string"
|
||||
},
|
||||
"models": {
|
||||
"description": "Models is the list of models to use for the API provider.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
"description": "Type is the type of the API provider.",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.APIKey": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@@ -10258,62 +9881,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.Chat": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"created_at": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.ChatMessage": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"annotations": {
|
||||
"type": "array",
|
||||
"items": {}
|
||||
},
|
||||
"content": {
|
||||
"type": "string"
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"experimental_attachments": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/aisdk.Attachment"
|
||||
}
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"parts": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/aisdk.Part"
|
||||
}
|
||||
},
|
||||
"role": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.ConnectionLatency": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -10344,20 +9911,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.CreateChatMessageRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"$ref": "#/definitions/codersdk.ChatMessage"
|
||||
},
|
||||
"model": {
|
||||
"type": "string"
|
||||
},
|
||||
"thinking": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.CreateFirstUserRequest": {
|
||||
"type": "object",
|
||||
"required": ["email", "password", "username"],
|
||||
@@ -10626,63 +10179,7 @@
|
||||
}
|
||||
},
|
||||
"codersdk.CreateTestAuditLogRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"action": {
|
||||
"enum": ["create", "write", "delete", "start", "stop"],
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/codersdk.AuditAction"
|
||||
}
|
||||
]
|
||||
},
|
||||
"additional_fields": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"build_reason": {
|
||||
"enum": ["autostart", "autostop", "initiator"],
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/codersdk.BuildReason"
|
||||
}
|
||||
]
|
||||
},
|
||||
"organization_id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"request_id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"resource_id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"resource_type": {
|
||||
"enum": [
|
||||
"template",
|
||||
"template_version",
|
||||
"user",
|
||||
"workspace",
|
||||
"workspace_build",
|
||||
"git_ssh_key",
|
||||
"auditable_group"
|
||||
],
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/codersdk.ResourceType"
|
||||
}
|
||||
]
|
||||
},
|
||||
"time": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
}
|
||||
}
|
||||
"type": "object"
|
||||
},
|
||||
"codersdk.CreateTokenRequest": {
|
||||
"type": "object",
|
||||
@@ -11110,9 +10607,6 @@
|
||||
"agent_stat_refresh_interval": {
|
||||
"type": "integer"
|
||||
},
|
||||
"ai": {
|
||||
"$ref": "#/definitions/serpent.Struct-codersdk_AIConfig"
|
||||
},
|
||||
"allow_workspace_renames": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@@ -11434,11 +10928,9 @@
|
||||
"notifications",
|
||||
"workspace-usage",
|
||||
"web-push",
|
||||
"workspace-prebuilds",
|
||||
"agentic-chat"
|
||||
"workspace-prebuilds"
|
||||
],
|
||||
"x-enum-comments": {
|
||||
"ExperimentAgenticChat": "Enables the new agentic AI chat feature.",
|
||||
"ExperimentAutoFillParameters": "This should not be taken out of experiments until we have redesigned the feature.",
|
||||
"ExperimentExample": "This isn't used for anything.",
|
||||
"ExperimentNotifications": "Sends notifications via SMTP and webhooks following certain events.",
|
||||
@@ -11452,8 +10944,7 @@
|
||||
"ExperimentNotifications",
|
||||
"ExperimentWorkspaceUsage",
|
||||
"ExperimentWebPush",
|
||||
"ExperimentWorkspacePrebuilds",
|
||||
"ExperimentAgenticChat"
|
||||
"ExperimentWorkspacePrebuilds"
|
||||
]
|
||||
},
|
||||
"codersdk.ExternalAuth": {
|
||||
@@ -11965,33 +11456,6 @@
|
||||
"enum": ["REQUIRED_TEMPLATE_VARIABLES"],
|
||||
"x-enum-varnames": ["RequiredTemplateVariables"]
|
||||
},
|
||||
"codersdk.LanguageModel": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"display_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"description": "ID is used by the provider to identify the LLM.",
|
||||
"type": "string"
|
||||
},
|
||||
"provider": {
|
||||
"description": "Provider is the provider of the LLM. e.g. openai, anthropic, etc.",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.LanguageModelConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"models": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/codersdk.LanguageModel"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.License": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -13825,7 +13289,6 @@
|
||||
"assign_org_role",
|
||||
"assign_role",
|
||||
"audit_log",
|
||||
"chat",
|
||||
"crypto_key",
|
||||
"debug_info",
|
||||
"deployment_config",
|
||||
@@ -13865,7 +13328,6 @@
|
||||
"ResourceAssignOrgRole",
|
||||
"ResourceAssignRole",
|
||||
"ResourceAuditLog",
|
||||
"ResourceChat",
|
||||
"ResourceCryptoKey",
|
||||
"ResourceDebugInfo",
|
||||
"ResourceDeploymentConfig",
|
||||
@@ -17720,14 +17182,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"serpent.Struct-codersdk_AIConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"value": {
|
||||
"$ref": "#/definitions/codersdk.AIConfig"
|
||||
}
|
||||
}
|
||||
},
|
||||
"serpent.URL": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
-366
@@ -1,366 +0,0 @@
|
||||
package coderd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/kylecarbs/aisdk-go"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/ai"
|
||||
"github.com/coder/coder/v2/coderd/database"
|
||||
"github.com/coder/coder/v2/coderd/database/db2sdk"
|
||||
"github.com/coder/coder/v2/coderd/database/dbtime"
|
||||
"github.com/coder/coder/v2/coderd/httpapi"
|
||||
"github.com/coder/coder/v2/coderd/httpmw"
|
||||
"github.com/coder/coder/v2/coderd/util/strings"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
"github.com/coder/coder/v2/codersdk/toolsdk"
|
||||
)
|
||||
|
||||
// postChats creates a new chat.
|
||||
//
|
||||
// @Summary Create a chat
|
||||
// @ID create-a-chat
|
||||
// @Security CoderSessionToken
|
||||
// @Produce json
|
||||
// @Tags Chat
|
||||
// @Success 201 {object} codersdk.Chat
|
||||
// @Router /chats [post]
|
||||
func (api *API) postChats(w http.ResponseWriter, r *http.Request) {
|
||||
apiKey := httpmw.APIKey(r)
|
||||
ctx := r.Context()
|
||||
|
||||
chat, err := api.Database.InsertChat(ctx, database.InsertChatParams{
|
||||
OwnerID: apiKey.UserID,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
Title: "New Chat",
|
||||
})
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, w, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Failed to create chat",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
httpapi.Write(ctx, w, http.StatusCreated, db2sdk.Chat(chat))
|
||||
}
|
||||
|
||||
// listChats lists all chats for a user.
|
||||
//
|
||||
// @Summary List chats
|
||||
// @ID list-chats
|
||||
// @Security CoderSessionToken
|
||||
// @Produce json
|
||||
// @Tags Chat
|
||||
// @Success 200 {array} codersdk.Chat
|
||||
// @Router /chats [get]
|
||||
func (api *API) listChats(w http.ResponseWriter, r *http.Request) {
|
||||
apiKey := httpmw.APIKey(r)
|
||||
ctx := r.Context()
|
||||
|
||||
chats, err := api.Database.GetChatsByOwnerID(ctx, apiKey.UserID)
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, w, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Failed to list chats",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
httpapi.Write(ctx, w, http.StatusOK, db2sdk.Chats(chats))
|
||||
}
|
||||
|
||||
// chat returns a chat by ID.
|
||||
//
|
||||
// @Summary Get a chat
|
||||
// @ID get-a-chat
|
||||
// @Security CoderSessionToken
|
||||
// @Produce json
|
||||
// @Tags Chat
|
||||
// @Param chat path string true "Chat ID"
|
||||
// @Success 200 {object} codersdk.Chat
|
||||
// @Router /chats/{chat} [get]
|
||||
func (*API) chat(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
chat := httpmw.ChatParam(r)
|
||||
httpapi.Write(ctx, w, http.StatusOK, db2sdk.Chat(chat))
|
||||
}
|
||||
|
||||
// chatMessages returns the messages of a chat.
|
||||
//
|
||||
// @Summary Get chat messages
|
||||
// @ID get-chat-messages
|
||||
// @Security CoderSessionToken
|
||||
// @Produce json
|
||||
// @Tags Chat
|
||||
// @Param chat path string true "Chat ID"
|
||||
// @Success 200 {array} aisdk.Message
|
||||
// @Router /chats/{chat}/messages [get]
|
||||
func (api *API) chatMessages(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
chat := httpmw.ChatParam(r)
|
||||
rawMessages, err := api.Database.GetChatMessagesByChatID(ctx, chat.ID)
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, w, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Failed to get chat messages",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
messages := make([]aisdk.Message, len(rawMessages))
|
||||
for i, message := range rawMessages {
|
||||
var msg aisdk.Message
|
||||
err = json.Unmarshal(message.Content, &msg)
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, w, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Failed to unmarshal chat message",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
messages[i] = msg
|
||||
}
|
||||
|
||||
httpapi.Write(ctx, w, http.StatusOK, messages)
|
||||
}
|
||||
|
||||
// postChatMessages creates a new chat message and streams the response.
|
||||
//
|
||||
// @Summary Create a chat message
|
||||
// @ID create-a-chat-message
|
||||
// @Security CoderSessionToken
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Tags Chat
|
||||
// @Param chat path string true "Chat ID"
|
||||
// @Param request body codersdk.CreateChatMessageRequest true "Request body"
|
||||
// @Success 200 {array} aisdk.DataStreamPart
|
||||
// @Router /chats/{chat}/messages [post]
|
||||
func (api *API) postChatMessages(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
chat := httpmw.ChatParam(r)
|
||||
var req codersdk.CreateChatMessageRequest
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, w, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "Failed to decode chat message",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
dbMessages, err := api.Database.GetChatMessagesByChatID(ctx, chat.ID)
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, w, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Failed to get chat messages",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
messages := make([]codersdk.ChatMessage, 0)
|
||||
for _, dbMsg := range dbMessages {
|
||||
var msg codersdk.ChatMessage
|
||||
err = json.Unmarshal(dbMsg.Content, &msg)
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, w, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Failed to unmarshal chat message",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
messages = append(messages, msg)
|
||||
}
|
||||
messages = append(messages, req.Message)
|
||||
|
||||
client := codersdk.New(api.AccessURL)
|
||||
client.SetSessionToken(httpmw.APITokenFromRequest(r))
|
||||
|
||||
tools := make([]aisdk.Tool, 0)
|
||||
handlers := map[string]toolsdk.GenericHandlerFunc{}
|
||||
for _, tool := range toolsdk.All {
|
||||
if tool.Name == "coder_report_task" {
|
||||
continue // This tool requires an agent to run.
|
||||
}
|
||||
tools = append(tools, tool.Tool)
|
||||
handlers[tool.Tool.Name] = tool.Handler
|
||||
}
|
||||
|
||||
provider, ok := api.LanguageModels[req.Model]
|
||||
if !ok {
|
||||
httpapi.Write(ctx, w, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "Model not found",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// If it's the user's first message, generate a title for the chat.
|
||||
if len(messages) == 1 {
|
||||
var acc aisdk.DataStreamAccumulator
|
||||
stream, err := provider.StreamFunc(ctx, ai.StreamOptions{
|
||||
Model: req.Model,
|
||||
SystemPrompt: `- You will generate a short title based on the user's message.
|
||||
- It should be maximum of 40 characters.
|
||||
- Do not use quotes, colons, special characters, or emojis.`,
|
||||
Messages: messages,
|
||||
Tools: []aisdk.Tool{}, // This initial stream doesn't use tools.
|
||||
})
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, w, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Failed to create stream",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
stream = stream.WithAccumulator(&acc)
|
||||
err = stream.Pipe(io.Discard)
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, w, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Failed to pipe stream",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
var newTitle string
|
||||
accMessages := acc.Messages()
|
||||
// If for some reason the stream didn't return any messages, use the
|
||||
// original message as the title.
|
||||
if len(accMessages) == 0 {
|
||||
newTitle = strings.Truncate(messages[0].Content, 40)
|
||||
} else {
|
||||
newTitle = strings.Truncate(accMessages[0].Content, 40)
|
||||
}
|
||||
err = api.Database.UpdateChatByID(ctx, database.UpdateChatByIDParams{
|
||||
ID: chat.ID,
|
||||
Title: newTitle,
|
||||
UpdatedAt: dbtime.Now(),
|
||||
})
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, w, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Failed to update chat title",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Write headers for the data stream!
|
||||
aisdk.WriteDataStreamHeaders(w)
|
||||
|
||||
// Insert the user-requested message into the database!
|
||||
raw, err := json.Marshal([]aisdk.Message{req.Message})
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, w, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Failed to marshal chat message",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
_, err = api.Database.InsertChatMessages(ctx, database.InsertChatMessagesParams{
|
||||
ChatID: chat.ID,
|
||||
CreatedAt: dbtime.Now(),
|
||||
Model: req.Model,
|
||||
Provider: provider.Provider,
|
||||
Content: raw,
|
||||
})
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, w, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Failed to insert chat messages",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
deps, err := toolsdk.NewDeps(client)
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, w, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Failed to create tool dependencies",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
var acc aisdk.DataStreamAccumulator
|
||||
stream, err := provider.StreamFunc(ctx, ai.StreamOptions{
|
||||
Model: req.Model,
|
||||
Messages: messages,
|
||||
Tools: tools,
|
||||
SystemPrompt: `You are a chat assistant for Coder - an open-source platform for creating and managing cloud development environments on any infrastructure. You are expected to be precise, concise, and helpful.
|
||||
|
||||
You are running as an agent - please keep going until the user's query is completely resolved, before ending your turn and yielding back to the user. Only terminate your turn when you are sure that the problem is solved. Do NOT guess or make up an answer.`,
|
||||
})
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, w, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Failed to create stream",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
stream = stream.WithToolCalling(func(toolCall aisdk.ToolCall) aisdk.ToolCallResult {
|
||||
tool, ok := handlers[toolCall.Name]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
toolArgs, err := json.Marshal(toolCall.Args)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
result, err := tool(ctx, deps, toolArgs)
|
||||
if err != nil {
|
||||
return map[string]any{
|
||||
"error": err.Error(),
|
||||
}
|
||||
}
|
||||
return result
|
||||
}).WithAccumulator(&acc)
|
||||
|
||||
err = stream.Pipe(w)
|
||||
if err != nil {
|
||||
// The client disppeared!
|
||||
api.Logger.Error(ctx, "stream pipe error", "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
// acc.Messages() may sometimes return nil. Serializing this
|
||||
// will cause a pq error: "cannot extract elements from a scalar".
|
||||
newMessages := append([]aisdk.Message{}, acc.Messages()...)
|
||||
if len(newMessages) > 0 {
|
||||
raw, err := json.Marshal(newMessages)
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, w, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Failed to marshal chat message",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
messages = append(messages, newMessages...)
|
||||
|
||||
// Insert these messages into the database!
|
||||
_, err = api.Database.InsertChatMessages(ctx, database.InsertChatMessagesParams{
|
||||
ChatID: chat.ID,
|
||||
CreatedAt: dbtime.Now(),
|
||||
Model: req.Model,
|
||||
Provider: provider.Provider,
|
||||
Content: raw,
|
||||
})
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, w, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Failed to insert chat messages",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if acc.FinishReason() == aisdk.FinishReasonToolCalls {
|
||||
continue
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
package coderd_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/coderdtest"
|
||||
"github.com/coder/coder/v2/coderd/database"
|
||||
"github.com/coder/coder/v2/coderd/database/dbgen"
|
||||
"github.com/coder/coder/v2/coderd/database/dbtime"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
"github.com/coder/coder/v2/testutil"
|
||||
)
|
||||
|
||||
func TestChat(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("ExperimentAgenticChatDisabled", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client, _ := coderdtest.NewWithDatabase(t, nil)
|
||||
owner := coderdtest.CreateFirstUser(t, client)
|
||||
memberClient, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
|
||||
|
||||
// Hit the endpoint to get the chat. It should return a 404.
|
||||
ctx := testutil.Context(t, testutil.WaitShort)
|
||||
_, err := memberClient.ListChats(ctx)
|
||||
require.Error(t, err, "list chats should fail")
|
||||
var sdkErr *codersdk.Error
|
||||
require.ErrorAs(t, err, &sdkErr, "request should fail with an SDK error")
|
||||
require.Equal(t, http.StatusForbidden, sdkErr.StatusCode())
|
||||
})
|
||||
|
||||
t.Run("ChatCRUD", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dv := coderdtest.DeploymentValues(t)
|
||||
dv.Experiments = []string{string(codersdk.ExperimentAgenticChat)}
|
||||
dv.AI.Value = codersdk.AIConfig{
|
||||
Providers: []codersdk.AIProviderConfig{
|
||||
{
|
||||
Type: "fake",
|
||||
APIKey: "",
|
||||
BaseURL: "http://localhost",
|
||||
Models: []string{"fake-model"},
|
||||
},
|
||||
},
|
||||
}
|
||||
client, db := coderdtest.NewWithDatabase(t, &coderdtest.Options{
|
||||
DeploymentValues: dv,
|
||||
})
|
||||
owner := coderdtest.CreateFirstUser(t, client)
|
||||
memberClient, memberUser := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
|
||||
|
||||
// Seed the database with some data.
|
||||
dbChat := dbgen.Chat(t, db, database.Chat{
|
||||
OwnerID: memberUser.ID,
|
||||
CreatedAt: dbtime.Now().Add(-time.Hour),
|
||||
UpdatedAt: dbtime.Now().Add(-time.Hour),
|
||||
Title: "This is a test chat",
|
||||
})
|
||||
_ = dbgen.ChatMessage(t, db, database.ChatMessage{
|
||||
ChatID: dbChat.ID,
|
||||
CreatedAt: dbtime.Now().Add(-time.Hour),
|
||||
Content: []byte(`[{"content": "Hello world"}]`),
|
||||
Model: "fake model",
|
||||
Provider: "fake",
|
||||
})
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitShort)
|
||||
|
||||
// Listing chats should return the chat we just inserted.
|
||||
chats, err := memberClient.ListChats(ctx)
|
||||
require.NoError(t, err, "list chats should succeed")
|
||||
require.Len(t, chats, 1, "response should have one chat")
|
||||
require.Equal(t, dbChat.ID, chats[0].ID, "unexpected chat ID")
|
||||
require.Equal(t, dbChat.Title, chats[0].Title, "unexpected chat title")
|
||||
require.Equal(t, dbChat.CreatedAt.UTC(), chats[0].CreatedAt.UTC(), "unexpected chat created at")
|
||||
require.Equal(t, dbChat.UpdatedAt.UTC(), chats[0].UpdatedAt.UTC(), "unexpected chat updated at")
|
||||
|
||||
// Fetching a single chat by ID should return the same chat.
|
||||
chat, err := memberClient.Chat(ctx, dbChat.ID)
|
||||
require.NoError(t, err, "get chat should succeed")
|
||||
require.Equal(t, chats[0], chat, "get chat should return the same chat")
|
||||
|
||||
// Listing chat messages should return the message we just inserted.
|
||||
messages, err := memberClient.ChatMessages(ctx, dbChat.ID)
|
||||
require.NoError(t, err, "list chat messages should succeed")
|
||||
require.Len(t, messages, 1, "response should have one message")
|
||||
require.Equal(t, "Hello world", messages[0].Content, "response should have the correct message content")
|
||||
|
||||
// Creating a new chat will fail because the model does not exist.
|
||||
// TODO: Test the message streaming functionality with a mock model.
|
||||
// Inserting a chat message will fail due to the model not existing.
|
||||
_, err = memberClient.CreateChatMessage(ctx, dbChat.ID, codersdk.CreateChatMessageRequest{
|
||||
Model: "echo",
|
||||
Message: codersdk.ChatMessage{
|
||||
Role: "user",
|
||||
Content: "Hello world",
|
||||
},
|
||||
Thinking: false,
|
||||
})
|
||||
require.Error(t, err, "create chat message should fail")
|
||||
var sdkErr *codersdk.Error
|
||||
require.ErrorAs(t, err, &sdkErr, "create chat should fail with an SDK error")
|
||||
require.Equal(t, http.StatusBadRequest, sdkErr.StatusCode(), "create chat should fail with a 400 when model does not exist")
|
||||
|
||||
// Creating a new chat message with malformed content should fail.
|
||||
res, err := memberClient.Request(ctx, http.MethodPost, "/api/v2/chats/"+dbChat.ID.String()+"/messages", strings.NewReader(`{malformed json}`))
|
||||
require.NoError(t, err)
|
||||
defer res.Body.Close()
|
||||
apiErr := codersdk.ReadBodyAsError(res)
|
||||
require.Contains(t, apiErr.Error(), "Failed to decode chat message")
|
||||
|
||||
_, err = memberClient.CreateChat(ctx)
|
||||
require.NoError(t, err, "create chat should succeed")
|
||||
chats, err = memberClient.ListChats(ctx)
|
||||
require.NoError(t, err, "list chats should succeed")
|
||||
require.Len(t, chats, 2, "response should have two chats")
|
||||
})
|
||||
}
|
||||
@@ -45,7 +45,6 @@ import (
|
||||
|
||||
"github.com/coder/coder/v2/codersdk/drpcsdk"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/ai"
|
||||
"github.com/coder/coder/v2/coderd/cryptokeys"
|
||||
"github.com/coder/coder/v2/coderd/entitlements"
|
||||
"github.com/coder/coder/v2/coderd/files"
|
||||
@@ -160,7 +159,6 @@ type Options struct {
|
||||
Authorizer rbac.Authorizer
|
||||
AzureCertificates x509.VerifyOptions
|
||||
GoogleTokenValidator *idtoken.Validator
|
||||
LanguageModels ai.LanguageModels
|
||||
GithubOAuth2Config *GithubOAuth2Config
|
||||
OIDCConfig *OIDCConfig
|
||||
PrometheusRegistry *prometheus.Registry
|
||||
@@ -976,7 +974,6 @@ func New(options *Options) *API {
|
||||
r.Get("/config", api.deploymentValues)
|
||||
r.Get("/stats", api.deploymentStats)
|
||||
r.Get("/ssh", api.sshConfig)
|
||||
r.Get("/llms", api.deploymentLLMs)
|
||||
})
|
||||
r.Route("/experiments", func(r chi.Router) {
|
||||
r.Use(apiKeyMiddleware)
|
||||
@@ -1019,21 +1016,6 @@ func New(options *Options) *API {
|
||||
r.Get("/{fileID}", api.fileByID)
|
||||
r.Post("/", api.postFile)
|
||||
})
|
||||
// Chats are an experimental feature
|
||||
r.Route("/chats", func(r chi.Router) {
|
||||
r.Use(
|
||||
apiKeyMiddleware,
|
||||
httpmw.RequireExperiment(api.Experiments, codersdk.ExperimentAgenticChat),
|
||||
)
|
||||
r.Get("/", api.listChats)
|
||||
r.Post("/", api.postChats)
|
||||
r.Route("/{chat}", func(r chi.Router) {
|
||||
r.Use(httpmw.ExtractChatParam(options.Database))
|
||||
r.Get("/", api.chat)
|
||||
r.Get("/messages", api.chatMessages)
|
||||
r.Post("/messages", api.postChatMessages)
|
||||
})
|
||||
})
|
||||
r.Route("/external-auth", func(r chi.Router) {
|
||||
r.Use(
|
||||
apiKeyMiddleware,
|
||||
|
||||
@@ -16,6 +16,8 @@ import (
|
||||
"golang.org/x/xerrors"
|
||||
"tailscale.com/tailcfg"
|
||||
|
||||
previewtypes "github.com/coder/preview/types"
|
||||
|
||||
agentproto "github.com/coder/coder/v2/agent/proto"
|
||||
"github.com/coder/coder/v2/coderd/database"
|
||||
"github.com/coder/coder/v2/coderd/rbac"
|
||||
@@ -26,7 +28,6 @@ import (
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
"github.com/coder/coder/v2/provisionersdk/proto"
|
||||
"github.com/coder/coder/v2/tailnet"
|
||||
previewtypes "github.com/coder/preview/types"
|
||||
)
|
||||
|
||||
// List is a helper function to reduce boilerplate when converting slices of
|
||||
@@ -803,19 +804,6 @@ func AgentProtoConnectionActionToAuditAction(action database.AuditAction) (agent
|
||||
}
|
||||
}
|
||||
|
||||
func Chat(chat database.Chat) codersdk.Chat {
|
||||
return codersdk.Chat{
|
||||
ID: chat.ID,
|
||||
Title: chat.Title,
|
||||
CreatedAt: chat.CreatedAt,
|
||||
UpdatedAt: chat.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func Chats(chats []database.Chat) []codersdk.Chat {
|
||||
return List(chats, Chat)
|
||||
}
|
||||
|
||||
func PreviewParameter(param previewtypes.Parameter) codersdk.PreviewParameter {
|
||||
return codersdk.PreviewParameter{
|
||||
PreviewParameterData: codersdk.PreviewParameterData{
|
||||
|
||||
@@ -1373,10 +1373,6 @@ func (q *querier) DeleteApplicationConnectAPIKeysByUserID(ctx context.Context, u
|
||||
return q.db.DeleteApplicationConnectAPIKeysByUserID(ctx, userID)
|
||||
}
|
||||
|
||||
func (q *querier) DeleteChat(ctx context.Context, id uuid.UUID) error {
|
||||
return deleteQ(q.log, q.auth, q.db.GetChatByID, q.db.DeleteChat)(ctx, id)
|
||||
}
|
||||
|
||||
func (q *querier) DeleteCoordinator(ctx context.Context, id uuid.UUID) error {
|
||||
if err := q.authorizeContext(ctx, policy.ActionDelete, rbac.ResourceTailnetCoordinator); err != nil {
|
||||
return err
|
||||
@@ -1814,22 +1810,6 @@ func (q *querier) GetAuthorizationUserRoles(ctx context.Context, userID uuid.UUI
|
||||
return q.db.GetAuthorizationUserRoles(ctx, userID)
|
||||
}
|
||||
|
||||
func (q *querier) GetChatByID(ctx context.Context, id uuid.UUID) (database.Chat, error) {
|
||||
return fetch(q.log, q.auth, q.db.GetChatByID)(ctx, id)
|
||||
}
|
||||
|
||||
func (q *querier) GetChatMessagesByChatID(ctx context.Context, chatID uuid.UUID) ([]database.ChatMessage, error) {
|
||||
c, err := q.GetChatByID(ctx, chatID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return q.db.GetChatMessagesByChatID(ctx, c.ID)
|
||||
}
|
||||
|
||||
func (q *querier) GetChatsByOwnerID(ctx context.Context, ownerID uuid.UUID) ([]database.Chat, error) {
|
||||
return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.GetChatsByOwnerID)(ctx, ownerID)
|
||||
}
|
||||
|
||||
func (q *querier) GetCoordinatorResumeTokenSigningKey(ctx context.Context) (string, error) {
|
||||
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceSystem); err != nil {
|
||||
return "", err
|
||||
@@ -3525,21 +3505,6 @@ func (q *querier) InsertAuditLog(ctx context.Context, arg database.InsertAuditLo
|
||||
return insert(q.log, q.auth, rbac.ResourceAuditLog, q.db.InsertAuditLog)(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) InsertChat(ctx context.Context, arg database.InsertChatParams) (database.Chat, error) {
|
||||
return insert(q.log, q.auth, rbac.ResourceChat.WithOwner(arg.OwnerID.String()), q.db.InsertChat)(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) InsertChatMessages(ctx context.Context, arg database.InsertChatMessagesParams) ([]database.ChatMessage, error) {
|
||||
c, err := q.db.GetChatByID(ctx, arg.ChatID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := q.authorizeContext(ctx, policy.ActionUpdate, c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return q.db.InsertChatMessages(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) InsertCryptoKey(ctx context.Context, arg database.InsertCryptoKeyParams) (database.CryptoKey, error) {
|
||||
if err := q.authorizeContext(ctx, policy.ActionCreate, rbac.ResourceCryptoKey); err != nil {
|
||||
return database.CryptoKey{}, err
|
||||
@@ -4201,13 +4166,6 @@ func (q *querier) UpdateAPIKeyByID(ctx context.Context, arg database.UpdateAPIKe
|
||||
return update(q.log, q.auth, fetch, q.db.UpdateAPIKeyByID)(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) UpdateChatByID(ctx context.Context, arg database.UpdateChatByIDParams) error {
|
||||
fetch := func(ctx context.Context, arg database.UpdateChatByIDParams) (database.Chat, error) {
|
||||
return q.db.GetChatByID(ctx, arg.ID)
|
||||
}
|
||||
return update(q.log, q.auth, fetch, q.db.UpdateChatByID)(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) UpdateCryptoKeyDeletesAt(ctx context.Context, arg database.UpdateCryptoKeyDeletesAtParams) (database.CryptoKey, error) {
|
||||
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceCryptoKey); err != nil {
|
||||
return database.CryptoKey{}, err
|
||||
|
||||
@@ -5549,80 +5549,6 @@ func (s *MethodTestSuite) TestResourcesProvisionerdserver() {
|
||||
}))
|
||||
}
|
||||
|
||||
func (s *MethodTestSuite) TestChat() {
|
||||
createChat := func(t *testing.T, db database.Store) (database.User, database.Chat, database.ChatMessage) {
|
||||
t.Helper()
|
||||
|
||||
usr := dbgen.User(t, db, database.User{})
|
||||
chat := dbgen.Chat(s.T(), db, database.Chat{
|
||||
OwnerID: usr.ID,
|
||||
})
|
||||
msg := dbgen.ChatMessage(s.T(), db, database.ChatMessage{
|
||||
ChatID: chat.ID,
|
||||
})
|
||||
|
||||
return usr, chat, msg
|
||||
}
|
||||
|
||||
s.Run("DeleteChat", s.Subtest(func(db database.Store, check *expects) {
|
||||
_, c, _ := createChat(s.T(), db)
|
||||
check.Args(c.ID).Asserts(c, policy.ActionDelete)
|
||||
}))
|
||||
|
||||
s.Run("GetChatByID", s.Subtest(func(db database.Store, check *expects) {
|
||||
_, c, _ := createChat(s.T(), db)
|
||||
check.Args(c.ID).Asserts(c, policy.ActionRead).Returns(c)
|
||||
}))
|
||||
|
||||
s.Run("GetChatMessagesByChatID", s.Subtest(func(db database.Store, check *expects) {
|
||||
_, c, m := createChat(s.T(), db)
|
||||
check.Args(c.ID).Asserts(c, policy.ActionRead).Returns([]database.ChatMessage{m})
|
||||
}))
|
||||
|
||||
s.Run("GetChatsByOwnerID", s.Subtest(func(db database.Store, check *expects) {
|
||||
u1, u1c1, _ := createChat(s.T(), db)
|
||||
u1c2 := dbgen.Chat(s.T(), db, database.Chat{
|
||||
OwnerID: u1.ID,
|
||||
CreatedAt: u1c1.CreatedAt.Add(time.Hour),
|
||||
})
|
||||
_, _, _ = createChat(s.T(), db) // other user's chat
|
||||
check.Args(u1.ID).Asserts(u1c2, policy.ActionRead, u1c1, policy.ActionRead).Returns([]database.Chat{u1c2, u1c1})
|
||||
}))
|
||||
|
||||
s.Run("InsertChat", s.Subtest(func(db database.Store, check *expects) {
|
||||
usr := dbgen.User(s.T(), db, database.User{})
|
||||
check.Args(database.InsertChatParams{
|
||||
OwnerID: usr.ID,
|
||||
Title: "test chat",
|
||||
CreatedAt: dbtime.Now(),
|
||||
UpdatedAt: dbtime.Now(),
|
||||
}).Asserts(rbac.ResourceChat.WithOwner(usr.ID.String()), policy.ActionCreate)
|
||||
}))
|
||||
|
||||
s.Run("InsertChatMessages", s.Subtest(func(db database.Store, check *expects) {
|
||||
usr := dbgen.User(s.T(), db, database.User{})
|
||||
chat := dbgen.Chat(s.T(), db, database.Chat{
|
||||
OwnerID: usr.ID,
|
||||
})
|
||||
check.Args(database.InsertChatMessagesParams{
|
||||
ChatID: chat.ID,
|
||||
CreatedAt: dbtime.Now(),
|
||||
Model: "test-model",
|
||||
Provider: "test-provider",
|
||||
Content: []byte(`[]`),
|
||||
}).Asserts(chat, policy.ActionUpdate)
|
||||
}))
|
||||
|
||||
s.Run("UpdateChatByID", s.Subtest(func(db database.Store, check *expects) {
|
||||
_, c, _ := createChat(s.T(), db)
|
||||
check.Args(database.UpdateChatByIDParams{
|
||||
ID: c.ID,
|
||||
Title: "new title",
|
||||
UpdatedAt: dbtime.Now(),
|
||||
}).Asserts(c, policy.ActionUpdate)
|
||||
}))
|
||||
}
|
||||
|
||||
func (s *MethodTestSuite) TestAuthorizePrebuiltWorkspace() {
|
||||
s.Run("PrebuildDelete/InsertWorkspaceBuild", s.Subtest(func(db database.Store, check *expects) {
|
||||
u := dbgen.User(s.T(), db, database.User{})
|
||||
|
||||
@@ -143,30 +143,6 @@ func APIKey(t testing.TB, db database.Store, seed database.APIKey) (key database
|
||||
return key, fmt.Sprintf("%s-%s", key.ID, secret)
|
||||
}
|
||||
|
||||
func Chat(t testing.TB, db database.Store, seed database.Chat) database.Chat {
|
||||
chat, err := db.InsertChat(genCtx, database.InsertChatParams{
|
||||
OwnerID: takeFirst(seed.OwnerID, uuid.New()),
|
||||
CreatedAt: takeFirst(seed.CreatedAt, dbtime.Now()),
|
||||
UpdatedAt: takeFirst(seed.UpdatedAt, dbtime.Now()),
|
||||
Title: takeFirst(seed.Title, "Test Chat"),
|
||||
})
|
||||
require.NoError(t, err, "insert chat")
|
||||
return chat
|
||||
}
|
||||
|
||||
func ChatMessage(t testing.TB, db database.Store, seed database.ChatMessage) database.ChatMessage {
|
||||
msg, err := db.InsertChatMessages(genCtx, database.InsertChatMessagesParams{
|
||||
CreatedAt: takeFirst(seed.CreatedAt, dbtime.Now()),
|
||||
ChatID: takeFirst(seed.ChatID, uuid.New()),
|
||||
Model: takeFirst(seed.Model, "train"),
|
||||
Provider: takeFirst(seed.Provider, "thomas"),
|
||||
Content: takeFirstSlice(seed.Content, []byte(`[{"text": "Choo choo!"}]`)),
|
||||
})
|
||||
require.NoError(t, err, "insert chat message")
|
||||
require.Len(t, msg, 1, "insert one chat message did not return exactly one message")
|
||||
return msg[0]
|
||||
}
|
||||
|
||||
func WorkspaceAgentPortShare(t testing.TB, db database.Store, orig database.WorkspaceAgentPortShare) database.WorkspaceAgentPortShare {
|
||||
ps, err := db.UpsertWorkspaceAgentPortShare(genCtx, database.UpsertWorkspaceAgentPortShareParams{
|
||||
WorkspaceID: takeFirst(orig.WorkspaceID, uuid.New()),
|
||||
|
||||
@@ -215,8 +215,6 @@ type data struct {
|
||||
|
||||
// New tables
|
||||
auditLogs []database.AuditLog
|
||||
chats []database.Chat
|
||||
chatMessages []database.ChatMessage
|
||||
cryptoKeys []database.CryptoKey
|
||||
dbcryptKeys []database.DBCryptKey
|
||||
files []database.File
|
||||
@@ -1909,19 +1907,6 @@ func (q *FakeQuerier) DeleteApplicationConnectAPIKeysByUserID(_ context.Context,
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) DeleteChat(ctx context.Context, id uuid.UUID) error {
|
||||
q.mutex.Lock()
|
||||
defer q.mutex.Unlock()
|
||||
|
||||
for i, chat := range q.chats {
|
||||
if chat.ID == id {
|
||||
q.chats = append(q.chats[:i], q.chats[i+1:]...)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return sql.ErrNoRows
|
||||
}
|
||||
|
||||
func (*FakeQuerier) DeleteCoordinator(context.Context, uuid.UUID) error {
|
||||
return ErrUnimplemented
|
||||
}
|
||||
@@ -2955,47 +2940,6 @@ func (q *FakeQuerier) GetAuthorizationUserRoles(_ context.Context, userID uuid.U
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) GetChatByID(ctx context.Context, id uuid.UUID) (database.Chat, error) {
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
|
||||
for _, chat := range q.chats {
|
||||
if chat.ID == id {
|
||||
return chat, nil
|
||||
}
|
||||
}
|
||||
return database.Chat{}, sql.ErrNoRows
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) GetChatMessagesByChatID(ctx context.Context, chatID uuid.UUID) ([]database.ChatMessage, error) {
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
|
||||
messages := []database.ChatMessage{}
|
||||
for _, chatMessage := range q.chatMessages {
|
||||
if chatMessage.ChatID == chatID {
|
||||
messages = append(messages, chatMessage)
|
||||
}
|
||||
}
|
||||
return messages, nil
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) GetChatsByOwnerID(ctx context.Context, ownerID uuid.UUID) ([]database.Chat, error) {
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
|
||||
chats := []database.Chat{}
|
||||
for _, chat := range q.chats {
|
||||
if chat.OwnerID == ownerID {
|
||||
chats = append(chats, chat)
|
||||
}
|
||||
}
|
||||
sort.Slice(chats, func(i, j int) bool {
|
||||
return chats[i].CreatedAt.After(chats[j].CreatedAt)
|
||||
})
|
||||
return chats, nil
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) GetCoordinatorResumeTokenSigningKey(_ context.Context) (string, error) {
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
@@ -8630,66 +8574,6 @@ func (q *FakeQuerier) InsertAuditLog(_ context.Context, arg database.InsertAudit
|
||||
return alog, nil
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) InsertChat(ctx context.Context, arg database.InsertChatParams) (database.Chat, error) {
|
||||
err := validateDatabaseType(arg)
|
||||
if err != nil {
|
||||
return database.Chat{}, err
|
||||
}
|
||||
|
||||
q.mutex.Lock()
|
||||
defer q.mutex.Unlock()
|
||||
|
||||
chat := database.Chat{
|
||||
ID: uuid.New(),
|
||||
CreatedAt: arg.CreatedAt,
|
||||
UpdatedAt: arg.UpdatedAt,
|
||||
OwnerID: arg.OwnerID,
|
||||
Title: arg.Title,
|
||||
}
|
||||
q.chats = append(q.chats, chat)
|
||||
|
||||
return chat, nil
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) InsertChatMessages(ctx context.Context, arg database.InsertChatMessagesParams) ([]database.ChatMessage, error) {
|
||||
err := validateDatabaseType(arg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
q.mutex.Lock()
|
||||
defer q.mutex.Unlock()
|
||||
|
||||
id := int64(0)
|
||||
if len(q.chatMessages) > 0 {
|
||||
id = q.chatMessages[len(q.chatMessages)-1].ID
|
||||
}
|
||||
|
||||
messages := make([]database.ChatMessage, 0)
|
||||
|
||||
rawMessages := make([]json.RawMessage, 0)
|
||||
err = json.Unmarshal(arg.Content, &rawMessages)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, content := range rawMessages {
|
||||
id++
|
||||
_ = content
|
||||
messages = append(messages, database.ChatMessage{
|
||||
ID: id,
|
||||
ChatID: arg.ChatID,
|
||||
CreatedAt: arg.CreatedAt,
|
||||
Model: arg.Model,
|
||||
Provider: arg.Provider,
|
||||
Content: content,
|
||||
})
|
||||
}
|
||||
|
||||
q.chatMessages = append(q.chatMessages, messages...)
|
||||
return messages, nil
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) InsertCryptoKey(_ context.Context, arg database.InsertCryptoKeyParams) (database.CryptoKey, error) {
|
||||
err := validateDatabaseType(arg)
|
||||
if err != nil {
|
||||
@@ -10638,27 +10522,6 @@ func (q *FakeQuerier) UpdateAPIKeyByID(_ context.Context, arg database.UpdateAPI
|
||||
return sql.ErrNoRows
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) UpdateChatByID(ctx context.Context, arg database.UpdateChatByIDParams) error {
|
||||
err := validateDatabaseType(arg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
q.mutex.Lock()
|
||||
defer q.mutex.Unlock()
|
||||
|
||||
for i, chat := range q.chats {
|
||||
if chat.ID == arg.ID {
|
||||
q.chats[i].Title = arg.Title
|
||||
q.chats[i].UpdatedAt = arg.UpdatedAt
|
||||
q.chats[i] = chat
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return sql.ErrNoRows
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) UpdateCryptoKeyDeletesAt(_ context.Context, arg database.UpdateCryptoKeyDeletesAtParams) (database.CryptoKey, error) {
|
||||
err := validateDatabaseType(arg)
|
||||
if err != nil {
|
||||
|
||||
@@ -249,13 +249,6 @@ func (m queryMetricsStore) DeleteApplicationConnectAPIKeysByUserID(ctx context.C
|
||||
return err
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) DeleteChat(ctx context.Context, id uuid.UUID) error {
|
||||
start := time.Now()
|
||||
r0 := m.s.DeleteChat(ctx, id)
|
||||
m.queryLatencies.WithLabelValues("DeleteChat").Observe(time.Since(start).Seconds())
|
||||
return r0
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) DeleteCoordinator(ctx context.Context, id uuid.UUID) error {
|
||||
start := time.Now()
|
||||
r0 := m.s.DeleteCoordinator(ctx, id)
|
||||
@@ -648,27 +641,6 @@ func (m queryMetricsStore) GetAuthorizationUserRoles(ctx context.Context, userID
|
||||
return row, err
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) GetChatByID(ctx context.Context, id uuid.UUID) (database.Chat, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.GetChatByID(ctx, id)
|
||||
m.queryLatencies.WithLabelValues("GetChatByID").Observe(time.Since(start).Seconds())
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) GetChatMessagesByChatID(ctx context.Context, chatID uuid.UUID) ([]database.ChatMessage, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.GetChatMessagesByChatID(ctx, chatID)
|
||||
m.queryLatencies.WithLabelValues("GetChatMessagesByChatID").Observe(time.Since(start).Seconds())
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) GetChatsByOwnerID(ctx context.Context, ownerID uuid.UUID) ([]database.Chat, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.GetChatsByOwnerID(ctx, ownerID)
|
||||
m.queryLatencies.WithLabelValues("GetChatsByOwnerID").Observe(time.Since(start).Seconds())
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) GetCoordinatorResumeTokenSigningKey(ctx context.Context) (string, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.GetCoordinatorResumeTokenSigningKey(ctx)
|
||||
@@ -2083,20 +2055,6 @@ func (m queryMetricsStore) InsertAuditLog(ctx context.Context, arg database.Inse
|
||||
return log, err
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) InsertChat(ctx context.Context, arg database.InsertChatParams) (database.Chat, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.InsertChat(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("InsertChat").Observe(time.Since(start).Seconds())
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) InsertChatMessages(ctx context.Context, arg database.InsertChatMessagesParams) ([]database.ChatMessage, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.InsertChatMessages(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("InsertChatMessages").Observe(time.Since(start).Seconds())
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) InsertCryptoKey(ctx context.Context, arg database.InsertCryptoKeyParams) (database.CryptoKey, error) {
|
||||
start := time.Now()
|
||||
key, err := m.s.InsertCryptoKey(ctx, arg)
|
||||
@@ -2622,13 +2580,6 @@ func (m queryMetricsStore) UpdateAPIKeyByID(ctx context.Context, arg database.Up
|
||||
return err
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) UpdateChatByID(ctx context.Context, arg database.UpdateChatByIDParams) error {
|
||||
start := time.Now()
|
||||
r0 := m.s.UpdateChatByID(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("UpdateChatByID").Observe(time.Since(start).Seconds())
|
||||
return r0
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) UpdateCryptoKeyDeletesAt(ctx context.Context, arg database.UpdateCryptoKeyDeletesAtParams) (database.CryptoKey, error) {
|
||||
start := time.Now()
|
||||
key, err := m.s.UpdateCryptoKeyDeletesAt(ctx, arg)
|
||||
|
||||
@@ -376,20 +376,6 @@ func (mr *MockStoreMockRecorder) DeleteApplicationConnectAPIKeysByUserID(ctx, us
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteApplicationConnectAPIKeysByUserID", reflect.TypeOf((*MockStore)(nil).DeleteApplicationConnectAPIKeysByUserID), ctx, userID)
|
||||
}
|
||||
|
||||
// DeleteChat mocks base method.
|
||||
func (m *MockStore) DeleteChat(ctx context.Context, id uuid.UUID) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "DeleteChat", ctx, id)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// DeleteChat indicates an expected call of DeleteChat.
|
||||
func (mr *MockStoreMockRecorder) DeleteChat(ctx, id any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteChat", reflect.TypeOf((*MockStore)(nil).DeleteChat), ctx, id)
|
||||
}
|
||||
|
||||
// DeleteCoordinator mocks base method.
|
||||
func (m *MockStore) DeleteCoordinator(ctx context.Context, id uuid.UUID) error {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -1292,51 +1278,6 @@ func (mr *MockStoreMockRecorder) GetAuthorizedWorkspacesAndAgentsByOwnerID(ctx,
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAuthorizedWorkspacesAndAgentsByOwnerID", reflect.TypeOf((*MockStore)(nil).GetAuthorizedWorkspacesAndAgentsByOwnerID), ctx, ownerID, prepared)
|
||||
}
|
||||
|
||||
// GetChatByID mocks base method.
|
||||
func (m *MockStore) GetChatByID(ctx context.Context, id uuid.UUID) (database.Chat, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetChatByID", ctx, id)
|
||||
ret0, _ := ret[0].(database.Chat)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetChatByID indicates an expected call of GetChatByID.
|
||||
func (mr *MockStoreMockRecorder) GetChatByID(ctx, id any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChatByID", reflect.TypeOf((*MockStore)(nil).GetChatByID), ctx, id)
|
||||
}
|
||||
|
||||
// GetChatMessagesByChatID mocks base method.
|
||||
func (m *MockStore) GetChatMessagesByChatID(ctx context.Context, chatID uuid.UUID) ([]database.ChatMessage, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetChatMessagesByChatID", ctx, chatID)
|
||||
ret0, _ := ret[0].([]database.ChatMessage)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetChatMessagesByChatID indicates an expected call of GetChatMessagesByChatID.
|
||||
func (mr *MockStoreMockRecorder) GetChatMessagesByChatID(ctx, chatID any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChatMessagesByChatID", reflect.TypeOf((*MockStore)(nil).GetChatMessagesByChatID), ctx, chatID)
|
||||
}
|
||||
|
||||
// GetChatsByOwnerID mocks base method.
|
||||
func (m *MockStore) GetChatsByOwnerID(ctx context.Context, ownerID uuid.UUID) ([]database.Chat, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetChatsByOwnerID", ctx, ownerID)
|
||||
ret0, _ := ret[0].([]database.Chat)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetChatsByOwnerID indicates an expected call of GetChatsByOwnerID.
|
||||
func (mr *MockStoreMockRecorder) GetChatsByOwnerID(ctx, ownerID any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChatsByOwnerID", reflect.TypeOf((*MockStore)(nil).GetChatsByOwnerID), ctx, ownerID)
|
||||
}
|
||||
|
||||
// GetCoordinatorResumeTokenSigningKey mocks base method.
|
||||
func (m *MockStore) GetCoordinatorResumeTokenSigningKey(ctx context.Context) (string, error) {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -4411,36 +4352,6 @@ func (mr *MockStoreMockRecorder) InsertAuditLog(ctx, arg any) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertAuditLog", reflect.TypeOf((*MockStore)(nil).InsertAuditLog), ctx, arg)
|
||||
}
|
||||
|
||||
// InsertChat mocks base method.
|
||||
func (m *MockStore) InsertChat(ctx context.Context, arg database.InsertChatParams) (database.Chat, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "InsertChat", ctx, arg)
|
||||
ret0, _ := ret[0].(database.Chat)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// InsertChat indicates an expected call of InsertChat.
|
||||
func (mr *MockStoreMockRecorder) InsertChat(ctx, arg any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertChat", reflect.TypeOf((*MockStore)(nil).InsertChat), ctx, arg)
|
||||
}
|
||||
|
||||
// InsertChatMessages mocks base method.
|
||||
func (m *MockStore) InsertChatMessages(ctx context.Context, arg database.InsertChatMessagesParams) ([]database.ChatMessage, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "InsertChatMessages", ctx, arg)
|
||||
ret0, _ := ret[0].([]database.ChatMessage)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// InsertChatMessages indicates an expected call of InsertChatMessages.
|
||||
func (mr *MockStoreMockRecorder) InsertChatMessages(ctx, arg any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertChatMessages", reflect.TypeOf((*MockStore)(nil).InsertChatMessages), ctx, arg)
|
||||
}
|
||||
|
||||
// InsertCryptoKey mocks base method.
|
||||
func (m *MockStore) InsertCryptoKey(ctx context.Context, arg database.InsertCryptoKeyParams) (database.CryptoKey, error) {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -5575,20 +5486,6 @@ func (mr *MockStoreMockRecorder) UpdateAPIKeyByID(ctx, arg any) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateAPIKeyByID", reflect.TypeOf((*MockStore)(nil).UpdateAPIKeyByID), ctx, arg)
|
||||
}
|
||||
|
||||
// UpdateChatByID mocks base method.
|
||||
func (m *MockStore) UpdateChatByID(ctx context.Context, arg database.UpdateChatByIDParams) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "UpdateChatByID", ctx, arg)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// UpdateChatByID indicates an expected call of UpdateChatByID.
|
||||
func (mr *MockStoreMockRecorder) UpdateChatByID(ctx, arg any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateChatByID", reflect.TypeOf((*MockStore)(nil).UpdateChatByID), ctx, arg)
|
||||
}
|
||||
|
||||
// UpdateCryptoKeyDeletesAt mocks base method.
|
||||
func (m *MockStore) UpdateCryptoKeyDeletesAt(ctx context.Context, arg database.UpdateCryptoKeyDeletesAtParams) (database.CryptoKey, error) {
|
||||
m.ctrl.T.Helper()
|
||||
|
||||
Generated
-40
@@ -822,32 +822,6 @@ CREATE TABLE audit_logs (
|
||||
resource_icon text NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE chat_messages (
|
||||
id bigint NOT NULL,
|
||||
chat_id uuid NOT NULL,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
model text NOT NULL,
|
||||
provider text NOT NULL,
|
||||
content jsonb NOT NULL
|
||||
);
|
||||
|
||||
CREATE SEQUENCE chat_messages_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
ALTER SEQUENCE chat_messages_id_seq OWNED BY chat_messages.id;
|
||||
|
||||
CREATE TABLE chats (
|
||||
id uuid DEFAULT gen_random_uuid() NOT NULL,
|
||||
owner_id uuid NOT NULL,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
updated_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
title text NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE crypto_keys (
|
||||
feature crypto_key_feature NOT NULL,
|
||||
sequence integer NOT NULL,
|
||||
@@ -2342,8 +2316,6 @@ CREATE VIEW workspaces_expanded AS
|
||||
|
||||
COMMENT ON VIEW workspaces_expanded IS 'Joins in the display name information such as username, avatar, and organization name.';
|
||||
|
||||
ALTER TABLE ONLY chat_messages ALTER COLUMN id SET DEFAULT nextval('chat_messages_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY licenses ALTER COLUMN id SET DEFAULT nextval('licenses_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY provisioner_job_logs ALTER COLUMN id SET DEFAULT nextval('provisioner_job_logs_id_seq'::regclass);
|
||||
@@ -2365,12 +2337,6 @@ ALTER TABLE ONLY api_keys
|
||||
ALTER TABLE ONLY audit_logs
|
||||
ADD CONSTRAINT audit_logs_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY chat_messages
|
||||
ADD CONSTRAINT chat_messages_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY chats
|
||||
ADD CONSTRAINT chats_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY crypto_keys
|
||||
ADD CONSTRAINT crypto_keys_pkey PRIMARY KEY (feature, sequence);
|
||||
|
||||
@@ -2867,12 +2833,6 @@ forward without requiring a migration to clean up historical data.';
|
||||
ALTER TABLE ONLY api_keys
|
||||
ADD CONSTRAINT api_keys_user_id_uuid_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY chat_messages
|
||||
ADD CONSTRAINT chat_messages_chat_id_fkey FOREIGN KEY (chat_id) REFERENCES chats(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY chats
|
||||
ADD CONSTRAINT chats_owner_id_fkey FOREIGN KEY (owner_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY crypto_keys
|
||||
ADD CONSTRAINT crypto_keys_secret_key_id_fkey FOREIGN KEY (secret_key_id) REFERENCES dbcrypt_keys(active_key_digest);
|
||||
|
||||
|
||||
@@ -7,8 +7,6 @@ type ForeignKeyConstraint string
|
||||
// ForeignKeyConstraint enums.
|
||||
const (
|
||||
ForeignKeyAPIKeysUserIDUUID ForeignKeyConstraint = "api_keys_user_id_uuid_fkey" // ALTER TABLE ONLY api_keys ADD CONSTRAINT api_keys_user_id_uuid_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
ForeignKeyChatMessagesChatID ForeignKeyConstraint = "chat_messages_chat_id_fkey" // ALTER TABLE ONLY chat_messages ADD CONSTRAINT chat_messages_chat_id_fkey FOREIGN KEY (chat_id) REFERENCES chats(id) ON DELETE CASCADE;
|
||||
ForeignKeyChatsOwnerID ForeignKeyConstraint = "chats_owner_id_fkey" // ALTER TABLE ONLY chats ADD CONSTRAINT chats_owner_id_fkey FOREIGN KEY (owner_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
ForeignKeyCryptoKeysSecretKeyID ForeignKeyConstraint = "crypto_keys_secret_key_id_fkey" // ALTER TABLE ONLY crypto_keys ADD CONSTRAINT crypto_keys_secret_key_id_fkey FOREIGN KEY (secret_key_id) REFERENCES dbcrypt_keys(active_key_digest);
|
||||
ForeignKeyGitAuthLinksOauthAccessTokenKeyID ForeignKeyConstraint = "git_auth_links_oauth_access_token_key_id_fkey" // ALTER TABLE ONLY external_auth_links ADD CONSTRAINT git_auth_links_oauth_access_token_key_id_fkey FOREIGN KEY (oauth_access_token_key_id) REFERENCES dbcrypt_keys(active_key_digest);
|
||||
ForeignKeyGitAuthLinksOauthRefreshTokenKeyID ForeignKeyConstraint = "git_auth_links_oauth_refresh_token_key_id_fkey" // ALTER TABLE ONLY external_auth_links ADD CONSTRAINT git_auth_links_oauth_refresh_token_key_id_fkey FOREIGN KEY (oauth_refresh_token_key_id) REFERENCES dbcrypt_keys(active_key_digest);
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
-- noop
|
||||
@@ -0,0 +1,2 @@
|
||||
DROP TABLE IF EXISTS chat_messages;
|
||||
DROP TABLE IF EXISTS chats;
|
||||
@@ -611,8 +611,3 @@ func (m WorkspaceAgentVolumeResourceMonitor) Debounce(
|
||||
|
||||
return m.DebouncedUntil, false
|
||||
}
|
||||
|
||||
func (c Chat) RBACObject() rbac.Object {
|
||||
return rbac.ResourceChat.WithID(c.ID).
|
||||
WithOwner(c.OwnerID.String())
|
||||
}
|
||||
|
||||
@@ -2781,23 +2781,6 @@ type AuditLog struct {
|
||||
ResourceIcon string `db:"resource_icon" json:"resource_icon"`
|
||||
}
|
||||
|
||||
type Chat struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
OwnerID uuid.UUID `db:"owner_id" json:"owner_id"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||
Title string `db:"title" json:"title"`
|
||||
}
|
||||
|
||||
type ChatMessage struct {
|
||||
ID int64 `db:"id" json:"id"`
|
||||
ChatID uuid.UUID `db:"chat_id" json:"chat_id"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
Model string `db:"model" json:"model"`
|
||||
Provider string `db:"provider" json:"provider"`
|
||||
Content json.RawMessage `db:"content" json:"content"`
|
||||
}
|
||||
|
||||
type CryptoKey struct {
|
||||
Feature CryptoKeyFeature `db:"feature" json:"feature"`
|
||||
Sequence int32 `db:"sequence" json:"sequence"`
|
||||
|
||||
@@ -79,7 +79,6 @@ type sqlcQuerier interface {
|
||||
// be recreated.
|
||||
DeleteAllWebpushSubscriptions(ctx context.Context) error
|
||||
DeleteApplicationConnectAPIKeysByUserID(ctx context.Context, userID uuid.UUID) error
|
||||
DeleteChat(ctx context.Context, id uuid.UUID) error
|
||||
DeleteCoordinator(ctx context.Context, id uuid.UUID) error
|
||||
DeleteCryptoKey(ctx context.Context, arg DeleteCryptoKeyParams) (CryptoKey, error)
|
||||
DeleteCustomRole(ctx context.Context, arg DeleteCustomRoleParams) error
|
||||
@@ -154,9 +153,6 @@ type sqlcQuerier interface {
|
||||
// This function returns roles for authorization purposes. Implied member roles
|
||||
// are included.
|
||||
GetAuthorizationUserRoles(ctx context.Context, userID uuid.UUID) (GetAuthorizationUserRolesRow, error)
|
||||
GetChatByID(ctx context.Context, id uuid.UUID) (Chat, error)
|
||||
GetChatMessagesByChatID(ctx context.Context, chatID uuid.UUID) ([]ChatMessage, error)
|
||||
GetChatsByOwnerID(ctx context.Context, ownerID uuid.UUID) ([]Chat, error)
|
||||
GetCoordinatorResumeTokenSigningKey(ctx context.Context) (string, error)
|
||||
GetCryptoKeyByFeatureAndSequence(ctx context.Context, arg GetCryptoKeyByFeatureAndSequenceParams) (CryptoKey, error)
|
||||
GetCryptoKeys(ctx context.Context) ([]CryptoKey, error)
|
||||
@@ -472,8 +468,6 @@ type sqlcQuerier interface {
|
||||
// every member of the org.
|
||||
InsertAllUsersGroup(ctx context.Context, organizationID uuid.UUID) (Group, error)
|
||||
InsertAuditLog(ctx context.Context, arg InsertAuditLogParams) (AuditLog, error)
|
||||
InsertChat(ctx context.Context, arg InsertChatParams) (Chat, error)
|
||||
InsertChatMessages(ctx context.Context, arg InsertChatMessagesParams) ([]ChatMessage, error)
|
||||
InsertCryptoKey(ctx context.Context, arg InsertCryptoKeyParams) (CryptoKey, error)
|
||||
InsertCustomRole(ctx context.Context, arg InsertCustomRoleParams) (CustomRole, error)
|
||||
InsertDBCryptKey(ctx context.Context, arg InsertDBCryptKeyParams) error
|
||||
@@ -567,7 +561,6 @@ type sqlcQuerier interface {
|
||||
UnarchiveTemplateVersion(ctx context.Context, arg UnarchiveTemplateVersionParams) error
|
||||
UnfavoriteWorkspace(ctx context.Context, id uuid.UUID) error
|
||||
UpdateAPIKeyByID(ctx context.Context, arg UpdateAPIKeyByIDParams) error
|
||||
UpdateChatByID(ctx context.Context, arg UpdateChatByIDParams) error
|
||||
UpdateCryptoKeyDeletesAt(ctx context.Context, arg UpdateCryptoKeyDeletesAtParams) (CryptoKey, error)
|
||||
UpdateCustomRole(ctx context.Context, arg UpdateCustomRoleParams) (CustomRole, error)
|
||||
UpdateExternalAuthLink(ctx context.Context, arg UpdateExternalAuthLinkParams) (ExternalAuthLink, error)
|
||||
|
||||
@@ -766,207 +766,6 @@ func (q *sqlQuerier) InsertAuditLog(ctx context.Context, arg InsertAuditLogParam
|
||||
return i, err
|
||||
}
|
||||
|
||||
const deleteChat = `-- name: DeleteChat :exec
|
||||
DELETE FROM chats WHERE id = $1
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) DeleteChat(ctx context.Context, id uuid.UUID) error {
|
||||
_, err := q.db.ExecContext(ctx, deleteChat, id)
|
||||
return err
|
||||
}
|
||||
|
||||
const getChatByID = `-- name: GetChatByID :one
|
||||
SELECT id, owner_id, created_at, updated_at, title FROM chats
|
||||
WHERE id = $1
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) GetChatByID(ctx context.Context, id uuid.UUID) (Chat, error) {
|
||||
row := q.db.QueryRowContext(ctx, getChatByID, id)
|
||||
var i Chat
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.OwnerID,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.Title,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getChatMessagesByChatID = `-- name: GetChatMessagesByChatID :many
|
||||
SELECT id, chat_id, created_at, model, provider, content FROM chat_messages
|
||||
WHERE chat_id = $1
|
||||
ORDER BY created_at ASC
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) GetChatMessagesByChatID(ctx context.Context, chatID uuid.UUID) ([]ChatMessage, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getChatMessagesByChatID, chatID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []ChatMessage
|
||||
for rows.Next() {
|
||||
var i ChatMessage
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.ChatID,
|
||||
&i.CreatedAt,
|
||||
&i.Model,
|
||||
&i.Provider,
|
||||
&i.Content,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getChatsByOwnerID = `-- name: GetChatsByOwnerID :many
|
||||
SELECT id, owner_id, created_at, updated_at, title FROM chats
|
||||
WHERE owner_id = $1
|
||||
ORDER BY created_at DESC
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) GetChatsByOwnerID(ctx context.Context, ownerID uuid.UUID) ([]Chat, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getChatsByOwnerID, ownerID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []Chat
|
||||
for rows.Next() {
|
||||
var i Chat
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.OwnerID,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.Title,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const insertChat = `-- name: InsertChat :one
|
||||
INSERT INTO chats (owner_id, created_at, updated_at, title)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
RETURNING id, owner_id, created_at, updated_at, title
|
||||
`
|
||||
|
||||
type InsertChatParams struct {
|
||||
OwnerID uuid.UUID `db:"owner_id" json:"owner_id"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||
Title string `db:"title" json:"title"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) InsertChat(ctx context.Context, arg InsertChatParams) (Chat, error) {
|
||||
row := q.db.QueryRowContext(ctx, insertChat,
|
||||
arg.OwnerID,
|
||||
arg.CreatedAt,
|
||||
arg.UpdatedAt,
|
||||
arg.Title,
|
||||
)
|
||||
var i Chat
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.OwnerID,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.Title,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const insertChatMessages = `-- name: InsertChatMessages :many
|
||||
INSERT INTO chat_messages (chat_id, created_at, model, provider, content)
|
||||
SELECT
|
||||
$1 :: uuid AS chat_id,
|
||||
$2 :: timestamptz AS created_at,
|
||||
$3 :: VARCHAR(127) AS model,
|
||||
$4 :: VARCHAR(127) AS provider,
|
||||
jsonb_array_elements($5 :: jsonb) AS content
|
||||
RETURNING chat_messages.id, chat_messages.chat_id, chat_messages.created_at, chat_messages.model, chat_messages.provider, chat_messages.content
|
||||
`
|
||||
|
||||
type InsertChatMessagesParams struct {
|
||||
ChatID uuid.UUID `db:"chat_id" json:"chat_id"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
Model string `db:"model" json:"model"`
|
||||
Provider string `db:"provider" json:"provider"`
|
||||
Content json.RawMessage `db:"content" json:"content"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) InsertChatMessages(ctx context.Context, arg InsertChatMessagesParams) ([]ChatMessage, error) {
|
||||
rows, err := q.db.QueryContext(ctx, insertChatMessages,
|
||||
arg.ChatID,
|
||||
arg.CreatedAt,
|
||||
arg.Model,
|
||||
arg.Provider,
|
||||
arg.Content,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []ChatMessage
|
||||
for rows.Next() {
|
||||
var i ChatMessage
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.ChatID,
|
||||
&i.CreatedAt,
|
||||
&i.Model,
|
||||
&i.Provider,
|
||||
&i.Content,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const updateChatByID = `-- name: UpdateChatByID :exec
|
||||
UPDATE chats
|
||||
SET title = $2, updated_at = $3
|
||||
WHERE id = $1
|
||||
`
|
||||
|
||||
type UpdateChatByIDParams struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
Title string `db:"title" json:"title"`
|
||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) UpdateChatByID(ctx context.Context, arg UpdateChatByIDParams) error {
|
||||
_, err := q.db.ExecContext(ctx, updateChatByID, arg.ID, arg.Title, arg.UpdatedAt)
|
||||
return err
|
||||
}
|
||||
|
||||
const deleteCryptoKey = `-- name: DeleteCryptoKey :one
|
||||
UPDATE crypto_keys
|
||||
SET secret = NULL, secret_key_id = NULL
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
-- name: InsertChat :one
|
||||
INSERT INTO chats (owner_id, created_at, updated_at, title)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
RETURNING *;
|
||||
|
||||
-- name: UpdateChatByID :exec
|
||||
UPDATE chats
|
||||
SET title = $2, updated_at = $3
|
||||
WHERE id = $1;
|
||||
|
||||
-- name: GetChatsByOwnerID :many
|
||||
SELECT * FROM chats
|
||||
WHERE owner_id = $1
|
||||
ORDER BY created_at DESC;
|
||||
|
||||
-- name: GetChatByID :one
|
||||
SELECT * FROM chats
|
||||
WHERE id = $1;
|
||||
|
||||
-- name: InsertChatMessages :many
|
||||
INSERT INTO chat_messages (chat_id, created_at, model, provider, content)
|
||||
SELECT
|
||||
@chat_id :: uuid AS chat_id,
|
||||
@created_at :: timestamptz AS created_at,
|
||||
@model :: VARCHAR(127) AS model,
|
||||
@provider :: VARCHAR(127) AS provider,
|
||||
jsonb_array_elements(@content :: jsonb) AS content
|
||||
RETURNING chat_messages.*;
|
||||
|
||||
-- name: GetChatMessagesByChatID :many
|
||||
SELECT * FROM chat_messages
|
||||
WHERE chat_id = $1
|
||||
ORDER BY created_at ASC;
|
||||
|
||||
-- name: DeleteChat :exec
|
||||
DELETE FROM chats WHERE id = $1;
|
||||
@@ -9,8 +9,6 @@ const (
|
||||
UniqueAgentStatsPkey UniqueConstraint = "agent_stats_pkey" // ALTER TABLE ONLY workspace_agent_stats ADD CONSTRAINT agent_stats_pkey PRIMARY KEY (id);
|
||||
UniqueAPIKeysPkey UniqueConstraint = "api_keys_pkey" // ALTER TABLE ONLY api_keys ADD CONSTRAINT api_keys_pkey PRIMARY KEY (id);
|
||||
UniqueAuditLogsPkey UniqueConstraint = "audit_logs_pkey" // ALTER TABLE ONLY audit_logs ADD CONSTRAINT audit_logs_pkey PRIMARY KEY (id);
|
||||
UniqueChatMessagesPkey UniqueConstraint = "chat_messages_pkey" // ALTER TABLE ONLY chat_messages ADD CONSTRAINT chat_messages_pkey PRIMARY KEY (id);
|
||||
UniqueChatsPkey UniqueConstraint = "chats_pkey" // ALTER TABLE ONLY chats ADD CONSTRAINT chats_pkey PRIMARY KEY (id);
|
||||
UniqueCryptoKeysPkey UniqueConstraint = "crypto_keys_pkey" // ALTER TABLE ONLY crypto_keys ADD CONSTRAINT crypto_keys_pkey PRIMARY KEY (feature, sequence);
|
||||
UniqueCustomRolesUniqueKey UniqueConstraint = "custom_roles_unique_key" // ALTER TABLE ONLY custom_roles ADD CONSTRAINT custom_roles_unique_key UNIQUE (name, organization_id);
|
||||
UniqueDbcryptKeysActiveKeyDigestKey UniqueConstraint = "dbcrypt_keys_active_key_digest_key" // ALTER TABLE ONLY dbcrypt_keys ADD CONSTRAINT dbcrypt_keys_active_key_digest_key UNIQUE (active_key_digest);
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
package coderd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/kylecarbs/aisdk-go"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/httpapi"
|
||||
"github.com/coder/coder/v2/coderd/rbac"
|
||||
"github.com/coder/coder/v2/coderd/rbac/policy"
|
||||
@@ -87,25 +84,3 @@ func buildInfoHandler(resp codersdk.BuildInfoResponse) http.HandlerFunc {
|
||||
func (api *API) sshConfig(rw http.ResponseWriter, r *http.Request) {
|
||||
httpapi.Write(r.Context(), rw, http.StatusOK, api.SSHConfig)
|
||||
}
|
||||
|
||||
type LanguageModel struct {
|
||||
codersdk.LanguageModel
|
||||
Provider func(ctx context.Context, messages []aisdk.Message, thinking bool) (aisdk.DataStream, error)
|
||||
}
|
||||
|
||||
// @Summary Get language models
|
||||
// @ID get-language-models
|
||||
// @Security CoderSessionToken
|
||||
// @Produce json
|
||||
// @Tags General
|
||||
// @Success 200 {object} codersdk.LanguageModelConfig
|
||||
// @Router /deployment/llms [get]
|
||||
func (api *API) deploymentLLMs(rw http.ResponseWriter, r *http.Request) {
|
||||
models := make([]codersdk.LanguageModel, 0, len(api.LanguageModels))
|
||||
for _, model := range api.LanguageModels {
|
||||
models = append(models, model.LanguageModel)
|
||||
}
|
||||
httpapi.Write(r.Context(), rw, http.StatusOK, codersdk.LanguageModelConfig{
|
||||
Models: models,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
package httpmw
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/database"
|
||||
"github.com/coder/coder/v2/coderd/httpapi"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
)
|
||||
|
||||
type chatContextKey struct{}
|
||||
|
||||
func ChatParam(r *http.Request) database.Chat {
|
||||
chat, ok := r.Context().Value(chatContextKey{}).(database.Chat)
|
||||
if !ok {
|
||||
panic("developer error: chat param middleware not provided")
|
||||
}
|
||||
return chat
|
||||
}
|
||||
|
||||
func ExtractChatParam(db database.Store) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
arg := chi.URLParam(r, "chat")
|
||||
if arg == "" {
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "\"chat\" must be provided.",
|
||||
})
|
||||
return
|
||||
}
|
||||
chatID, err := uuid.Parse(arg)
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "Invalid chat ID.",
|
||||
})
|
||||
return
|
||||
}
|
||||
chat, err := db.GetChatByID(ctx, chatID)
|
||||
if httpapi.Is404Error(err) {
|
||||
httpapi.ResourceNotFound(rw)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Failed to get chat.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
ctx = context.WithValue(ctx, chatContextKey{}, chat)
|
||||
next.ServeHTTP(rw, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
package httpmw_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/database"
|
||||
"github.com/coder/coder/v2/coderd/database/dbgen"
|
||||
"github.com/coder/coder/v2/coderd/database/dbtestutil"
|
||||
"github.com/coder/coder/v2/coderd/database/dbtime"
|
||||
"github.com/coder/coder/v2/coderd/httpmw"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
)
|
||||
|
||||
func TestExtractChat(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
setupAuthentication := func(db database.Store) (*http.Request, database.User) {
|
||||
r := httptest.NewRequest("GET", "/", nil)
|
||||
|
||||
user := dbgen.User(t, db, database.User{
|
||||
ID: uuid.New(),
|
||||
})
|
||||
_, token := dbgen.APIKey(t, db, database.APIKey{
|
||||
UserID: user.ID,
|
||||
})
|
||||
r.Header.Set(codersdk.SessionTokenHeader, token)
|
||||
r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, chi.NewRouteContext()))
|
||||
return r, user
|
||||
}
|
||||
|
||||
t.Run("None", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
var (
|
||||
db, _ = dbtestutil.NewDB(t)
|
||||
rw = httptest.NewRecorder()
|
||||
r, _ = setupAuthentication(db)
|
||||
rtr = chi.NewRouter()
|
||||
)
|
||||
rtr.Use(
|
||||
httpmw.ExtractAPIKeyMW(httpmw.ExtractAPIKeyConfig{
|
||||
DB: db,
|
||||
RedirectToLogin: false,
|
||||
}),
|
||||
httpmw.ExtractChatParam(db),
|
||||
)
|
||||
rtr.Get("/", nil)
|
||||
rtr.ServeHTTP(rw, r)
|
||||
res := rw.Result()
|
||||
defer res.Body.Close()
|
||||
require.Equal(t, http.StatusBadRequest, res.StatusCode)
|
||||
})
|
||||
|
||||
t.Run("InvalidUUID", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
var (
|
||||
db, _ = dbtestutil.NewDB(t)
|
||||
rw = httptest.NewRecorder()
|
||||
r, _ = setupAuthentication(db)
|
||||
rtr = chi.NewRouter()
|
||||
)
|
||||
chi.RouteContext(r.Context()).URLParams.Add("chat", "not-a-uuid")
|
||||
rtr.Use(
|
||||
httpmw.ExtractAPIKeyMW(httpmw.ExtractAPIKeyConfig{
|
||||
DB: db,
|
||||
RedirectToLogin: false,
|
||||
}),
|
||||
httpmw.ExtractChatParam(db),
|
||||
)
|
||||
rtr.Get("/", nil)
|
||||
rtr.ServeHTTP(rw, r)
|
||||
res := rw.Result()
|
||||
defer res.Body.Close()
|
||||
require.Equal(t, http.StatusBadRequest, res.StatusCode) // Changed from NotFound in org test to BadRequest as per chat.go
|
||||
})
|
||||
|
||||
t.Run("NotFound", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
var (
|
||||
db, _ = dbtestutil.NewDB(t)
|
||||
rw = httptest.NewRecorder()
|
||||
r, _ = setupAuthentication(db)
|
||||
rtr = chi.NewRouter()
|
||||
)
|
||||
chi.RouteContext(r.Context()).URLParams.Add("chat", uuid.NewString())
|
||||
rtr.Use(
|
||||
httpmw.ExtractAPIKeyMW(httpmw.ExtractAPIKeyConfig{
|
||||
DB: db,
|
||||
RedirectToLogin: false,
|
||||
}),
|
||||
httpmw.ExtractChatParam(db),
|
||||
)
|
||||
rtr.Get("/", nil)
|
||||
rtr.ServeHTTP(rw, r)
|
||||
res := rw.Result()
|
||||
defer res.Body.Close()
|
||||
require.Equal(t, http.StatusNotFound, res.StatusCode)
|
||||
})
|
||||
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
var (
|
||||
db, _ = dbtestutil.NewDB(t)
|
||||
rw = httptest.NewRecorder()
|
||||
r, user = setupAuthentication(db)
|
||||
rtr = chi.NewRouter()
|
||||
)
|
||||
|
||||
// Create a test chat
|
||||
testChat := dbgen.Chat(t, db, database.Chat{
|
||||
ID: uuid.New(),
|
||||
OwnerID: user.ID,
|
||||
CreatedAt: dbtime.Now(),
|
||||
UpdatedAt: dbtime.Now(),
|
||||
Title: "Test Chat",
|
||||
})
|
||||
|
||||
rtr.Use(
|
||||
httpmw.ExtractAPIKeyMW(httpmw.ExtractAPIKeyConfig{
|
||||
DB: db,
|
||||
RedirectToLogin: false,
|
||||
}),
|
||||
httpmw.ExtractChatParam(db),
|
||||
)
|
||||
rtr.Get("/", func(rw http.ResponseWriter, r *http.Request) {
|
||||
chat := httpmw.ChatParam(r)
|
||||
require.NotZero(t, chat)
|
||||
assert.Equal(t, testChat.ID, chat.ID)
|
||||
assert.WithinDuration(t, testChat.CreatedAt, chat.CreatedAt, time.Second)
|
||||
assert.WithinDuration(t, testChat.UpdatedAt, chat.UpdatedAt, time.Second)
|
||||
assert.Equal(t, testChat.Title, chat.Title)
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
// Try by ID
|
||||
chi.RouteContext(r.Context()).URLParams.Add("chat", testChat.ID.String())
|
||||
rtr.ServeHTTP(rw, r)
|
||||
res := rw.Result()
|
||||
defer res.Body.Close()
|
||||
require.Equal(t, http.StatusOK, res.StatusCode, "by id")
|
||||
})
|
||||
}
|
||||
@@ -54,16 +54,6 @@ var (
|
||||
Type: "audit_log",
|
||||
}
|
||||
|
||||
// ResourceChat
|
||||
// Valid Actions
|
||||
// - "ActionCreate" :: create a chat
|
||||
// - "ActionDelete" :: delete a chat
|
||||
// - "ActionRead" :: read a chat
|
||||
// - "ActionUpdate" :: update a chat
|
||||
ResourceChat = Object{
|
||||
Type: "chat",
|
||||
}
|
||||
|
||||
// ResourceCryptoKey
|
||||
// Valid Actions
|
||||
// - "ActionCreate" :: create crypto keys
|
||||
@@ -378,7 +368,6 @@ func AllResources() []Objecter {
|
||||
ResourceAssignOrgRole,
|
||||
ResourceAssignRole,
|
||||
ResourceAuditLog,
|
||||
ResourceChat,
|
||||
ResourceCryptoKey,
|
||||
ResourceDebugInfo,
|
||||
ResourceDeploymentConfig,
|
||||
|
||||
@@ -124,14 +124,6 @@ var RBACPermissions = map[string]PermissionDefinition{
|
||||
ActionRead: actDef("read and use a workspace proxy"),
|
||||
},
|
||||
},
|
||||
"chat": {
|
||||
Actions: map[Action]ActionDefinition{
|
||||
ActionCreate: actDef("create a chat"),
|
||||
ActionRead: actDef("read a chat"),
|
||||
ActionDelete: actDef("delete a chat"),
|
||||
ActionUpdate: actDef("update a chat"),
|
||||
},
|
||||
},
|
||||
"license": {
|
||||
Actions: map[Action]ActionDefinition{
|
||||
ActionCreate: actDef("create a license"),
|
||||
|
||||
@@ -305,8 +305,6 @@ func ReloadBuiltinRoles(opts *RoleOptions) {
|
||||
ResourceOrganizationMember.Type: {policy.ActionRead},
|
||||
// Users can create provisioner daemons scoped to themselves.
|
||||
ResourceProvisionerDaemon.Type: {policy.ActionRead, policy.ActionCreate, policy.ActionRead, policy.ActionUpdate},
|
||||
// Users can create, read, update, and delete their own agentic chat messages.
|
||||
ResourceChat.Type: {policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete},
|
||||
})...,
|
||||
),
|
||||
}.withCachedRegoValue()
|
||||
|
||||
@@ -849,37 +849,6 @@ func TestRolePermissions(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
// Members may read their own chats.
|
||||
{
|
||||
Name: "CreateReadUpdateDeleteMyChats",
|
||||
Actions: []policy.Action{policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete},
|
||||
Resource: rbac.ResourceChat.WithOwner(currentUser.String()),
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {memberMe, orgMemberMe, owner},
|
||||
false: {
|
||||
userAdmin, orgUserAdmin, templateAdmin,
|
||||
orgAuditor, orgTemplateAdmin,
|
||||
otherOrgMember, otherOrgAuditor, otherOrgUserAdmin, otherOrgTemplateAdmin,
|
||||
orgAdmin, otherOrgAdmin,
|
||||
},
|
||||
},
|
||||
},
|
||||
// Only owners can create, read, update, and delete other users' chats.
|
||||
{
|
||||
Name: "CreateReadUpdateDeleteOtherUserChats",
|
||||
Actions: []policy.Action{policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete},
|
||||
Resource: rbac.ResourceChat.WithOwner(uuid.NewString()), // some other user
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner},
|
||||
false: {
|
||||
memberMe, orgMemberMe,
|
||||
userAdmin, orgUserAdmin, templateAdmin,
|
||||
orgAuditor, orgTemplateAdmin,
|
||||
otherOrgMember, otherOrgAuditor, otherOrgUserAdmin, otherOrgTemplateAdmin,
|
||||
orgAdmin, otherOrgAdmin,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// We expect every permission to be tested above.
|
||||
|
||||
@@ -1,153 +0,0 @@
|
||||
package codersdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/kylecarbs/aisdk-go"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// CreateChat creates a new chat.
|
||||
func (c *Client) CreateChat(ctx context.Context) (Chat, error) {
|
||||
res, err := c.Request(ctx, http.MethodPost, "/api/v2/chats", nil)
|
||||
if err != nil {
|
||||
return Chat{}, xerrors.Errorf("execute request: %w", err)
|
||||
}
|
||||
if res.StatusCode != http.StatusCreated {
|
||||
return Chat{}, ReadBodyAsError(res)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
var chat Chat
|
||||
return chat, json.NewDecoder(res.Body).Decode(&chat)
|
||||
}
|
||||
|
||||
type Chat struct {
|
||||
ID uuid.UUID `json:"id" format:"uuid"`
|
||||
CreatedAt time.Time `json:"created_at" format:"date-time"`
|
||||
UpdatedAt time.Time `json:"updated_at" format:"date-time"`
|
||||
Title string `json:"title"`
|
||||
}
|
||||
|
||||
// ListChats lists all chats.
|
||||
func (c *Client) ListChats(ctx context.Context) ([]Chat, error) {
|
||||
res, err := c.Request(ctx, http.MethodGet, "/api/v2/chats", nil)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("execute request: %w", err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return nil, ReadBodyAsError(res)
|
||||
}
|
||||
|
||||
var chats []Chat
|
||||
return chats, json.NewDecoder(res.Body).Decode(&chats)
|
||||
}
|
||||
|
||||
// Chat returns a chat by ID.
|
||||
func (c *Client) Chat(ctx context.Context, id uuid.UUID) (Chat, error) {
|
||||
res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/chats/%s", id), nil)
|
||||
if err != nil {
|
||||
return Chat{}, xerrors.Errorf("execute request: %w", err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return Chat{}, ReadBodyAsError(res)
|
||||
}
|
||||
var chat Chat
|
||||
return chat, json.NewDecoder(res.Body).Decode(&chat)
|
||||
}
|
||||
|
||||
// ChatMessages returns the messages of a chat.
|
||||
func (c *Client) ChatMessages(ctx context.Context, id uuid.UUID) ([]ChatMessage, error) {
|
||||
res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/chats/%s/messages", id), nil)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("execute request: %w", err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return nil, ReadBodyAsError(res)
|
||||
}
|
||||
var messages []ChatMessage
|
||||
return messages, json.NewDecoder(res.Body).Decode(&messages)
|
||||
}
|
||||
|
||||
type ChatMessage = aisdk.Message
|
||||
|
||||
type CreateChatMessageRequest struct {
|
||||
Model string `json:"model"`
|
||||
Message ChatMessage `json:"message"`
|
||||
Thinking bool `json:"thinking"`
|
||||
}
|
||||
|
||||
// CreateChatMessage creates a new chat message and streams the response.
|
||||
// If the provided message has a conflicting ID with an existing message,
|
||||
// it will be overwritten.
|
||||
func (c *Client) CreateChatMessage(ctx context.Context, id uuid.UUID, req CreateChatMessageRequest) (<-chan aisdk.DataStreamPart, error) {
|
||||
res, err := c.Request(ctx, http.MethodPost, fmt.Sprintf("/api/v2/chats/%s/messages", id), req)
|
||||
defer func() {
|
||||
if res != nil && res.Body != nil {
|
||||
_ = res.Body.Close()
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("execute request: %w", err)
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return nil, ReadBodyAsError(res)
|
||||
}
|
||||
nextEvent := ServerSentEventReader(ctx, res.Body)
|
||||
|
||||
wc := make(chan aisdk.DataStreamPart, 256)
|
||||
go func() {
|
||||
defer close(wc)
|
||||
defer res.Body.Close()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
sse, err := nextEvent()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if sse.Type != ServerSentEventTypeData {
|
||||
continue
|
||||
}
|
||||
var part aisdk.DataStreamPart
|
||||
b, ok := sse.Data.([]byte)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
err = json.Unmarshal(b, &part)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case wc <- part:
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return wc, nil
|
||||
}
|
||||
|
||||
func (c *Client) DeleteChat(ctx context.Context, id uuid.UUID) error {
|
||||
res, err := c.Request(ctx, http.MethodDelete, fmt.Sprintf("/api/v2/chats/%s", id), nil)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("execute request: %w", err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusNoContent {
|
||||
return ReadBodyAsError(res)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -383,7 +383,6 @@ type DeploymentValues struct {
|
||||
DisablePasswordAuth serpent.Bool `json:"disable_password_auth,omitempty" typescript:",notnull"`
|
||||
Support SupportConfig `json:"support,omitempty" typescript:",notnull"`
|
||||
ExternalAuthConfigs serpent.Struct[[]ExternalAuthConfig] `json:"external_auth,omitempty" typescript:",notnull"`
|
||||
AI serpent.Struct[AIConfig] `json:"ai,omitempty" typescript:",notnull"`
|
||||
SSHConfig SSHConfig `json:"config_ssh,omitempty" typescript:",notnull"`
|
||||
WgtunnelHost serpent.String `json:"wgtunnel_host,omitempty" typescript:",notnull"`
|
||||
DisableOwnerWorkspaceExec serpent.Bool `json:"disable_owner_workspace_exec,omitempty" typescript:",notnull"`
|
||||
@@ -2681,15 +2680,6 @@ Write out the current server config as YAML to stdout.`,
|
||||
Value: &c.Support.Links,
|
||||
Hidden: false,
|
||||
},
|
||||
{
|
||||
// Env handling is done in cli.ReadAIProvidersFromEnv
|
||||
Name: "AI",
|
||||
Description: "Configure AI providers.",
|
||||
YAML: "ai",
|
||||
Value: &c.AI,
|
||||
// Hidden because this is experimental.
|
||||
Hidden: true,
|
||||
},
|
||||
{
|
||||
// Env handling is done in cli.ReadGitAuthFromEnvironment
|
||||
Name: "External Auth Providers",
|
||||
@@ -3368,7 +3358,6 @@ const (
|
||||
ExperimentWorkspaceUsage Experiment = "workspace-usage" // Enables the new workspace usage tracking.
|
||||
ExperimentWebPush Experiment = "web-push" // Enables web push notifications through the browser.
|
||||
ExperimentWorkspacePrebuilds Experiment = "workspace-prebuilds" // Enables the new workspace prebuilds feature.
|
||||
ExperimentAgenticChat Experiment = "agentic-chat" // Enables the new agentic AI chat feature.
|
||||
)
|
||||
|
||||
// ExperimentsKnown should include all experiments defined above.
|
||||
@@ -3379,7 +3368,6 @@ var ExperimentsKnown = Experiments{
|
||||
ExperimentWorkspaceUsage,
|
||||
ExperimentWebPush,
|
||||
ExperimentWorkspacePrebuilds,
|
||||
ExperimentAgenticChat,
|
||||
}
|
||||
|
||||
// ExperimentsSafe should include all experiments that are safe for
|
||||
@@ -3597,32 +3585,6 @@ func (c *Client) SSHConfiguration(ctx context.Context) (SSHConfigResponse, error
|
||||
return sshConfig, json.NewDecoder(res.Body).Decode(&sshConfig)
|
||||
}
|
||||
|
||||
type LanguageModelConfig struct {
|
||||
Models []LanguageModel `json:"models"`
|
||||
}
|
||||
|
||||
// LanguageModel is a language model that can be used for chat.
|
||||
type LanguageModel struct {
|
||||
// ID is used by the provider to identify the LLM.
|
||||
ID string `json:"id"`
|
||||
DisplayName string `json:"display_name"`
|
||||
// Provider is the provider of the LLM. e.g. openai, anthropic, etc.
|
||||
Provider string `json:"provider"`
|
||||
}
|
||||
|
||||
func (c *Client) LanguageModelConfig(ctx context.Context) (LanguageModelConfig, error) {
|
||||
res, err := c.Request(ctx, http.MethodGet, "/api/v2/deployment/llms", nil)
|
||||
if err != nil {
|
||||
return LanguageModelConfig{}, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return LanguageModelConfig{}, ReadBodyAsError(res)
|
||||
}
|
||||
var llms LanguageModelConfig
|
||||
return llms, json.NewDecoder(res.Body).Decode(&llms)
|
||||
}
|
||||
|
||||
type CryptoKeyFeature string
|
||||
|
||||
const (
|
||||
|
||||
@@ -9,7 +9,6 @@ const (
|
||||
ResourceAssignOrgRole RBACResource = "assign_org_role"
|
||||
ResourceAssignRole RBACResource = "assign_role"
|
||||
ResourceAuditLog RBACResource = "audit_log"
|
||||
ResourceChat RBACResource = "chat"
|
||||
ResourceCryptoKey RBACResource = "crypto_key"
|
||||
ResourceDebugInfo RBACResource = "debug_info"
|
||||
ResourceDeploymentConfig RBACResource = "deployment_config"
|
||||
@@ -73,7 +72,6 @@ var RBACResourceActions = map[RBACResource][]RBACAction{
|
||||
ResourceAssignOrgRole: {ActionAssign, ActionCreate, ActionDelete, ActionRead, ActionUnassign, ActionUpdate},
|
||||
ResourceAssignRole: {ActionAssign, ActionRead, ActionUnassign},
|
||||
ResourceAuditLog: {ActionCreate, ActionRead},
|
||||
ResourceChat: {ActionCreate, ActionDelete, ActionRead, ActionUpdate},
|
||||
ResourceCryptoKey: {ActionCreate, ActionDelete, ActionRead, ActionUpdate},
|
||||
ResourceDebugInfo: {ActionRead},
|
||||
ResourceDeploymentConfig: {ActionRead, ActionUpdate},
|
||||
|
||||
Generated
-372
@@ -1,372 +0,0 @@
|
||||
# Chat
|
||||
|
||||
## List chats
|
||||
|
||||
### Code samples
|
||||
|
||||
```shell
|
||||
# Example request using curl
|
||||
curl -X GET http://coder-server:8080/api/v2/chats \
|
||||
-H 'Accept: application/json' \
|
||||
-H 'Coder-Session-Token: API_KEY'
|
||||
```
|
||||
|
||||
`GET /chats`
|
||||
|
||||
### Example responses
|
||||
|
||||
> 200 Response
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"title": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### Responses
|
||||
|
||||
| Status | Meaning | Description | Schema |
|
||||
|--------|---------------------------------------------------------|-------------|---------------------------------------------------|
|
||||
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | array of [codersdk.Chat](schemas.md#codersdkchat) |
|
||||
|
||||
<h3 id="list-chats-responseschema">Response Schema</h3>
|
||||
|
||||
Status Code **200**
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
|----------------|-------------------|----------|--------------|-------------|
|
||||
| `[array item]` | array | false | | |
|
||||
| `» created_at` | string(date-time) | false | | |
|
||||
| `» id` | string(uuid) | false | | |
|
||||
| `» title` | string | false | | |
|
||||
| `» updated_at` | string(date-time) | false | | |
|
||||
|
||||
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||
|
||||
## Create a chat
|
||||
|
||||
### Code samples
|
||||
|
||||
```shell
|
||||
# Example request using curl
|
||||
curl -X POST http://coder-server:8080/api/v2/chats \
|
||||
-H 'Accept: application/json' \
|
||||
-H 'Coder-Session-Token: API_KEY'
|
||||
```
|
||||
|
||||
`POST /chats`
|
||||
|
||||
### Example responses
|
||||
|
||||
> 201 Response
|
||||
|
||||
```json
|
||||
{
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"title": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Responses
|
||||
|
||||
| Status | Meaning | Description | Schema |
|
||||
|--------|--------------------------------------------------------------|-------------|------------------------------------------|
|
||||
| 201 | [Created](https://tools.ietf.org/html/rfc7231#section-6.3.2) | Created | [codersdk.Chat](schemas.md#codersdkchat) |
|
||||
|
||||
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||
|
||||
## Get a chat
|
||||
|
||||
### Code samples
|
||||
|
||||
```shell
|
||||
# Example request using curl
|
||||
curl -X GET http://coder-server:8080/api/v2/chats/{chat} \
|
||||
-H 'Accept: application/json' \
|
||||
-H 'Coder-Session-Token: API_KEY'
|
||||
```
|
||||
|
||||
`GET /chats/{chat}`
|
||||
|
||||
### Parameters
|
||||
|
||||
| Name | In | Type | Required | Description |
|
||||
|--------|------|--------|----------|-------------|
|
||||
| `chat` | path | string | true | Chat ID |
|
||||
|
||||
### Example responses
|
||||
|
||||
> 200 Response
|
||||
|
||||
```json
|
||||
{
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"title": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Responses
|
||||
|
||||
| Status | Meaning | Description | Schema |
|
||||
|--------|---------------------------------------------------------|-------------|------------------------------------------|
|
||||
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.Chat](schemas.md#codersdkchat) |
|
||||
|
||||
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||
|
||||
## Get chat messages
|
||||
|
||||
### Code samples
|
||||
|
||||
```shell
|
||||
# Example request using curl
|
||||
curl -X GET http://coder-server:8080/api/v2/chats/{chat}/messages \
|
||||
-H 'Accept: application/json' \
|
||||
-H 'Coder-Session-Token: API_KEY'
|
||||
```
|
||||
|
||||
`GET /chats/{chat}/messages`
|
||||
|
||||
### Parameters
|
||||
|
||||
| Name | In | Type | Required | Description |
|
||||
|--------|------|--------|----------|-------------|
|
||||
| `chat` | path | string | true | Chat ID |
|
||||
|
||||
### Example responses
|
||||
|
||||
> 200 Response
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"annotations": [
|
||||
null
|
||||
],
|
||||
"content": "string",
|
||||
"createdAt": [
|
||||
0
|
||||
],
|
||||
"experimental_attachments": [
|
||||
{
|
||||
"contentType": "string",
|
||||
"name": "string",
|
||||
"url": "string"
|
||||
}
|
||||
],
|
||||
"id": "string",
|
||||
"parts": [
|
||||
{
|
||||
"data": [
|
||||
0
|
||||
],
|
||||
"details": [
|
||||
{
|
||||
"data": "string",
|
||||
"signature": "string",
|
||||
"text": "string",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"mimeType": "string",
|
||||
"reasoning": "string",
|
||||
"source": {
|
||||
"contentType": "string",
|
||||
"data": "string",
|
||||
"metadata": {
|
||||
"property1": null,
|
||||
"property2": null
|
||||
},
|
||||
"uri": "string"
|
||||
},
|
||||
"text": "string",
|
||||
"toolInvocation": {
|
||||
"args": null,
|
||||
"result": null,
|
||||
"state": "call",
|
||||
"step": 0,
|
||||
"toolCallId": "string",
|
||||
"toolName": "string"
|
||||
},
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"role": "string"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### Responses
|
||||
|
||||
| Status | Meaning | Description | Schema |
|
||||
|--------|---------------------------------------------------------|-------------|---------------------------------------------------|
|
||||
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | array of [aisdk.Message](schemas.md#aisdkmessage) |
|
||||
|
||||
<h3 id="get-chat-messages-responseschema">Response Schema</h3>
|
||||
|
||||
Status Code **200**
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
|------------------------------|------------------------------------------------------------------|----------|--------------|-------------------------|
|
||||
| `[array item]` | array | false | | |
|
||||
| `» annotations` | array | false | | |
|
||||
| `» content` | string | false | | |
|
||||
| `» createdAt` | array | false | | |
|
||||
| `» experimental_attachments` | array | false | | |
|
||||
| `»» contentType` | string | false | | |
|
||||
| `»» name` | string | false | | |
|
||||
| `»» url` | string | false | | |
|
||||
| `» id` | string | false | | |
|
||||
| `» parts` | array | false | | |
|
||||
| `»» data` | array | false | | |
|
||||
| `»» details` | array | false | | |
|
||||
| `»»» data` | string | false | | |
|
||||
| `»»» signature` | string | false | | |
|
||||
| `»»» text` | string | false | | |
|
||||
| `»»» type` | string | false | | |
|
||||
| `»» mimeType` | string | false | | Type: "file" |
|
||||
| `»» reasoning` | string | false | | Type: "reasoning" |
|
||||
| `»» source` | [aisdk.SourceInfo](schemas.md#aisdksourceinfo) | false | | Type: "source" |
|
||||
| `»»» contentType` | string | false | | |
|
||||
| `»»» data` | string | false | | |
|
||||
| `»»» metadata` | object | false | | |
|
||||
| `»»»» [any property]` | any | false | | |
|
||||
| `»»» uri` | string | false | | |
|
||||
| `»» text` | string | false | | Type: "text" |
|
||||
| `»» toolInvocation` | [aisdk.ToolInvocation](schemas.md#aisdktoolinvocation) | false | | Type: "tool-invocation" |
|
||||
| `»»» args` | any | false | | |
|
||||
| `»»» result` | any | false | | |
|
||||
| `»»» state` | [aisdk.ToolInvocationState](schemas.md#aisdktoolinvocationstate) | false | | |
|
||||
| `»»» step` | integer | false | | |
|
||||
| `»»» toolCallId` | string | false | | |
|
||||
| `»»» toolName` | string | false | | |
|
||||
| `»» type` | [aisdk.PartType](schemas.md#aisdkparttype) | false | | |
|
||||
| `» role` | string | false | | |
|
||||
|
||||
#### Enumerated Values
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------------------|
|
||||
| `state` | `call` |
|
||||
| `state` | `partial-call` |
|
||||
| `state` | `result` |
|
||||
| `type` | `text` |
|
||||
| `type` | `reasoning` |
|
||||
| `type` | `tool-invocation` |
|
||||
| `type` | `source` |
|
||||
| `type` | `file` |
|
||||
| `type` | `step-start` |
|
||||
|
||||
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||
|
||||
## Create a chat message
|
||||
|
||||
### Code samples
|
||||
|
||||
```shell
|
||||
# Example request using curl
|
||||
curl -X POST http://coder-server:8080/api/v2/chats/{chat}/messages \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'Accept: application/json' \
|
||||
-H 'Coder-Session-Token: API_KEY'
|
||||
```
|
||||
|
||||
`POST /chats/{chat}/messages`
|
||||
|
||||
> Body parameter
|
||||
|
||||
```json
|
||||
{
|
||||
"message": {
|
||||
"annotations": [
|
||||
null
|
||||
],
|
||||
"content": "string",
|
||||
"createdAt": [
|
||||
0
|
||||
],
|
||||
"experimental_attachments": [
|
||||
{
|
||||
"contentType": "string",
|
||||
"name": "string",
|
||||
"url": "string"
|
||||
}
|
||||
],
|
||||
"id": "string",
|
||||
"parts": [
|
||||
{
|
||||
"data": [
|
||||
0
|
||||
],
|
||||
"details": [
|
||||
{
|
||||
"data": "string",
|
||||
"signature": "string",
|
||||
"text": "string",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"mimeType": "string",
|
||||
"reasoning": "string",
|
||||
"source": {
|
||||
"contentType": "string",
|
||||
"data": "string",
|
||||
"metadata": {
|
||||
"property1": null,
|
||||
"property2": null
|
||||
},
|
||||
"uri": "string"
|
||||
},
|
||||
"text": "string",
|
||||
"toolInvocation": {
|
||||
"args": null,
|
||||
"result": null,
|
||||
"state": "call",
|
||||
"step": 0,
|
||||
"toolCallId": "string",
|
||||
"toolName": "string"
|
||||
},
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"role": "string"
|
||||
},
|
||||
"model": "string",
|
||||
"thinking": true
|
||||
}
|
||||
```
|
||||
|
||||
### Parameters
|
||||
|
||||
| Name | In | Type | Required | Description |
|
||||
|--------|------|----------------------------------------------------------------------------------|----------|--------------|
|
||||
| `chat` | path | string | true | Chat ID |
|
||||
| `body` | body | [codersdk.CreateChatMessageRequest](schemas.md#codersdkcreatechatmessagerequest) | true | Request body |
|
||||
|
||||
### Example responses
|
||||
|
||||
> 200 Response
|
||||
|
||||
```json
|
||||
[
|
||||
null
|
||||
]
|
||||
```
|
||||
|
||||
### Responses
|
||||
|
||||
| Status | Meaning | Description | Schema |
|
||||
|--------|---------------------------------------------------------|-------------|--------------------|
|
||||
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | array of undefined |
|
||||
|
||||
<h3 id="create-a-chat-message-responseschema">Response Schema</h3>
|
||||
|
||||
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||
Generated
-50
@@ -161,19 +161,6 @@ curl -X GET http://coder-server:8080/api/v2/deployment/config \
|
||||
"user": {}
|
||||
},
|
||||
"agent_stat_refresh_interval": 0,
|
||||
"ai": {
|
||||
"value": {
|
||||
"providers": [
|
||||
{
|
||||
"base_url": "string",
|
||||
"models": [
|
||||
"string"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"allow_workspace_renames": true,
|
||||
"autobuild_poll_interval": 0,
|
||||
"browser_only": true,
|
||||
@@ -586,43 +573,6 @@ curl -X GET http://coder-server:8080/api/v2/deployment/config \
|
||||
|
||||
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||
|
||||
## Get language models
|
||||
|
||||
### Code samples
|
||||
|
||||
```shell
|
||||
# Example request using curl
|
||||
curl -X GET http://coder-server:8080/api/v2/deployment/llms \
|
||||
-H 'Accept: application/json' \
|
||||
-H 'Coder-Session-Token: API_KEY'
|
||||
```
|
||||
|
||||
`GET /deployment/llms`
|
||||
|
||||
### Example responses
|
||||
|
||||
> 200 Response
|
||||
|
||||
```json
|
||||
{
|
||||
"models": [
|
||||
{
|
||||
"display_name": "string",
|
||||
"id": "string",
|
||||
"provider": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Responses
|
||||
|
||||
| Status | Meaning | Description | Schema |
|
||||
|--------|---------------------------------------------------------|-------------|------------------------------------------------------------------------|
|
||||
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.LanguageModelConfig](schemas.md#codersdklanguagemodelconfig) |
|
||||
|
||||
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||
|
||||
## SSH Config
|
||||
|
||||
### Code samples
|
||||
|
||||
Generated
-5
@@ -187,7 +187,6 @@ Status Code **200**
|
||||
| `resource_type` | `assign_org_role` |
|
||||
| `resource_type` | `assign_role` |
|
||||
| `resource_type` | `audit_log` |
|
||||
| `resource_type` | `chat` |
|
||||
| `resource_type` | `crypto_key` |
|
||||
| `resource_type` | `debug_info` |
|
||||
| `resource_type` | `deployment_config` |
|
||||
@@ -357,7 +356,6 @@ Status Code **200**
|
||||
| `resource_type` | `assign_org_role` |
|
||||
| `resource_type` | `assign_role` |
|
||||
| `resource_type` | `audit_log` |
|
||||
| `resource_type` | `chat` |
|
||||
| `resource_type` | `crypto_key` |
|
||||
| `resource_type` | `debug_info` |
|
||||
| `resource_type` | `deployment_config` |
|
||||
@@ -527,7 +525,6 @@ Status Code **200**
|
||||
| `resource_type` | `assign_org_role` |
|
||||
| `resource_type` | `assign_role` |
|
||||
| `resource_type` | `audit_log` |
|
||||
| `resource_type` | `chat` |
|
||||
| `resource_type` | `crypto_key` |
|
||||
| `resource_type` | `debug_info` |
|
||||
| `resource_type` | `deployment_config` |
|
||||
@@ -666,7 +663,6 @@ Status Code **200**
|
||||
| `resource_type` | `assign_org_role` |
|
||||
| `resource_type` | `assign_role` |
|
||||
| `resource_type` | `audit_log` |
|
||||
| `resource_type` | `chat` |
|
||||
| `resource_type` | `crypto_key` |
|
||||
| `resource_type` | `debug_info` |
|
||||
| `resource_type` | `deployment_config` |
|
||||
@@ -1027,7 +1023,6 @@ Status Code **200**
|
||||
| `resource_type` | `assign_org_role` |
|
||||
| `resource_type` | `assign_role` |
|
||||
| `resource_type` | `audit_log` |
|
||||
| `resource_type` | `chat` |
|
||||
| `resource_type` | `crypto_key` |
|
||||
| `resource_type` | `debug_info` |
|
||||
| `resource_type` | `deployment_config` |
|
||||
|
||||
Generated
+2
-581
@@ -212,250 +212,6 @@
|
||||
|--------------------|
|
||||
| `prebuild_claimed` |
|
||||
|
||||
## aisdk.Attachment
|
||||
|
||||
```json
|
||||
{
|
||||
"contentType": "string",
|
||||
"name": "string",
|
||||
"url": "string"
|
||||
}
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
|---------------|--------|----------|--------------|-------------|
|
||||
| `contentType` | string | false | | |
|
||||
| `name` | string | false | | |
|
||||
| `url` | string | false | | |
|
||||
|
||||
## aisdk.Message
|
||||
|
||||
```json
|
||||
{
|
||||
"annotations": [
|
||||
null
|
||||
],
|
||||
"content": "string",
|
||||
"createdAt": [
|
||||
0
|
||||
],
|
||||
"experimental_attachments": [
|
||||
{
|
||||
"contentType": "string",
|
||||
"name": "string",
|
||||
"url": "string"
|
||||
}
|
||||
],
|
||||
"id": "string",
|
||||
"parts": [
|
||||
{
|
||||
"data": [
|
||||
0
|
||||
],
|
||||
"details": [
|
||||
{
|
||||
"data": "string",
|
||||
"signature": "string",
|
||||
"text": "string",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"mimeType": "string",
|
||||
"reasoning": "string",
|
||||
"source": {
|
||||
"contentType": "string",
|
||||
"data": "string",
|
||||
"metadata": {
|
||||
"property1": null,
|
||||
"property2": null
|
||||
},
|
||||
"uri": "string"
|
||||
},
|
||||
"text": "string",
|
||||
"toolInvocation": {
|
||||
"args": null,
|
||||
"result": null,
|
||||
"state": "call",
|
||||
"step": 0,
|
||||
"toolCallId": "string",
|
||||
"toolName": "string"
|
||||
},
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"role": "string"
|
||||
}
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
|----------------------------|-----------------------------------------------|----------|--------------|-------------|
|
||||
| `annotations` | array of undefined | false | | |
|
||||
| `content` | string | false | | |
|
||||
| `createdAt` | array of integer | false | | |
|
||||
| `experimental_attachments` | array of [aisdk.Attachment](#aisdkattachment) | false | | |
|
||||
| `id` | string | false | | |
|
||||
| `parts` | array of [aisdk.Part](#aisdkpart) | false | | |
|
||||
| `role` | string | false | | |
|
||||
|
||||
## aisdk.Part
|
||||
|
||||
```json
|
||||
{
|
||||
"data": [
|
||||
0
|
||||
],
|
||||
"details": [
|
||||
{
|
||||
"data": "string",
|
||||
"signature": "string",
|
||||
"text": "string",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"mimeType": "string",
|
||||
"reasoning": "string",
|
||||
"source": {
|
||||
"contentType": "string",
|
||||
"data": "string",
|
||||
"metadata": {
|
||||
"property1": null,
|
||||
"property2": null
|
||||
},
|
||||
"uri": "string"
|
||||
},
|
||||
"text": "string",
|
||||
"toolInvocation": {
|
||||
"args": null,
|
||||
"result": null,
|
||||
"state": "call",
|
||||
"step": 0,
|
||||
"toolCallId": "string",
|
||||
"toolName": "string"
|
||||
},
|
||||
"type": "text"
|
||||
}
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
|------------------|---------------------------------------------------------|----------|--------------|-------------------------|
|
||||
| `data` | array of integer | false | | |
|
||||
| `details` | array of [aisdk.ReasoningDetail](#aisdkreasoningdetail) | false | | |
|
||||
| `mimeType` | string | false | | Type: "file" |
|
||||
| `reasoning` | string | false | | Type: "reasoning" |
|
||||
| `source` | [aisdk.SourceInfo](#aisdksourceinfo) | false | | Type: "source" |
|
||||
| `text` | string | false | | Type: "text" |
|
||||
| `toolInvocation` | [aisdk.ToolInvocation](#aisdktoolinvocation) | false | | Type: "tool-invocation" |
|
||||
| `type` | [aisdk.PartType](#aisdkparttype) | false | | |
|
||||
|
||||
## aisdk.PartType
|
||||
|
||||
```json
|
||||
"text"
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
#### Enumerated Values
|
||||
|
||||
| Value |
|
||||
|-------------------|
|
||||
| `text` |
|
||||
| `reasoning` |
|
||||
| `tool-invocation` |
|
||||
| `source` |
|
||||
| `file` |
|
||||
| `step-start` |
|
||||
|
||||
## aisdk.ReasoningDetail
|
||||
|
||||
```json
|
||||
{
|
||||
"data": "string",
|
||||
"signature": "string",
|
||||
"text": "string",
|
||||
"type": "string"
|
||||
}
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
|-------------|--------|----------|--------------|-------------|
|
||||
| `data` | string | false | | |
|
||||
| `signature` | string | false | | |
|
||||
| `text` | string | false | | |
|
||||
| `type` | string | false | | |
|
||||
|
||||
## aisdk.SourceInfo
|
||||
|
||||
```json
|
||||
{
|
||||
"contentType": "string",
|
||||
"data": "string",
|
||||
"metadata": {
|
||||
"property1": null,
|
||||
"property2": null
|
||||
},
|
||||
"uri": "string"
|
||||
}
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
|--------------------|--------|----------|--------------|-------------|
|
||||
| `contentType` | string | false | | |
|
||||
| `data` | string | false | | |
|
||||
| `metadata` | object | false | | |
|
||||
| » `[any property]` | any | false | | |
|
||||
| `uri` | string | false | | |
|
||||
|
||||
## aisdk.ToolInvocation
|
||||
|
||||
```json
|
||||
{
|
||||
"args": null,
|
||||
"result": null,
|
||||
"state": "call",
|
||||
"step": 0,
|
||||
"toolCallId": "string",
|
||||
"toolName": "string"
|
||||
}
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
|--------------|--------------------------------------------------------|----------|--------------|-------------|
|
||||
| `args` | any | false | | |
|
||||
| `result` | any | false | | |
|
||||
| `state` | [aisdk.ToolInvocationState](#aisdktoolinvocationstate) | false | | |
|
||||
| `step` | integer | false | | |
|
||||
| `toolCallId` | string | false | | |
|
||||
| `toolName` | string | false | | |
|
||||
|
||||
## aisdk.ToolInvocationState
|
||||
|
||||
```json
|
||||
"call"
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
#### Enumerated Values
|
||||
|
||||
| Value |
|
||||
|----------------|
|
||||
| `call` |
|
||||
| `partial-call` |
|
||||
| `result` |
|
||||
|
||||
## coderd.SCIMUser
|
||||
|
||||
```json
|
||||
@@ -579,48 +335,6 @@
|
||||
| `groups` | array of [codersdk.Group](#codersdkgroup) | false | | |
|
||||
| `users` | array of [codersdk.ReducedUser](#codersdkreduceduser) | false | | |
|
||||
|
||||
## codersdk.AIConfig
|
||||
|
||||
```json
|
||||
{
|
||||
"providers": [
|
||||
{
|
||||
"base_url": "string",
|
||||
"models": [
|
||||
"string"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
|-------------|-----------------------------------------------------------------|----------|--------------|-------------|
|
||||
| `providers` | array of [codersdk.AIProviderConfig](#codersdkaiproviderconfig) | false | | |
|
||||
|
||||
## codersdk.AIProviderConfig
|
||||
|
||||
```json
|
||||
{
|
||||
"base_url": "string",
|
||||
"models": [
|
||||
"string"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
|------------|-----------------|----------|--------------|-----------------------------------------------------------|
|
||||
| `base_url` | string | false | | Base URL is the base URL to use for the API provider. |
|
||||
| `models` | array of string | false | | Models is the list of models to use for the API provider. |
|
||||
| `type` | string | false | | Type is the type of the API provider. |
|
||||
|
||||
## codersdk.APIKey
|
||||
|
||||
```json
|
||||
@@ -1354,97 +1068,6 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
||||
| `one_time_passcode` | string | true | | |
|
||||
| `password` | string | true | | |
|
||||
|
||||
## codersdk.Chat
|
||||
|
||||
```json
|
||||
{
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"title": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
|--------------|--------|----------|--------------|-------------|
|
||||
| `created_at` | string | false | | |
|
||||
| `id` | string | false | | |
|
||||
| `title` | string | false | | |
|
||||
| `updated_at` | string | false | | |
|
||||
|
||||
## codersdk.ChatMessage
|
||||
|
||||
```json
|
||||
{
|
||||
"annotations": [
|
||||
null
|
||||
],
|
||||
"content": "string",
|
||||
"createdAt": [
|
||||
0
|
||||
],
|
||||
"experimental_attachments": [
|
||||
{
|
||||
"contentType": "string",
|
||||
"name": "string",
|
||||
"url": "string"
|
||||
}
|
||||
],
|
||||
"id": "string",
|
||||
"parts": [
|
||||
{
|
||||
"data": [
|
||||
0
|
||||
],
|
||||
"details": [
|
||||
{
|
||||
"data": "string",
|
||||
"signature": "string",
|
||||
"text": "string",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"mimeType": "string",
|
||||
"reasoning": "string",
|
||||
"source": {
|
||||
"contentType": "string",
|
||||
"data": "string",
|
||||
"metadata": {
|
||||
"property1": null,
|
||||
"property2": null
|
||||
},
|
||||
"uri": "string"
|
||||
},
|
||||
"text": "string",
|
||||
"toolInvocation": {
|
||||
"args": null,
|
||||
"result": null,
|
||||
"state": "call",
|
||||
"step": 0,
|
||||
"toolCallId": "string",
|
||||
"toolName": "string"
|
||||
},
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"role": "string"
|
||||
}
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
|----------------------------|-----------------------------------------------|----------|--------------|-------------|
|
||||
| `annotations` | array of undefined | false | | |
|
||||
| `content` | string | false | | |
|
||||
| `createdAt` | array of integer | false | | |
|
||||
| `experimental_attachments` | array of [aisdk.Attachment](#aisdkattachment) | false | | |
|
||||
| `id` | string | false | | |
|
||||
| `parts` | array of [aisdk.Part](#aisdkpart) | false | | |
|
||||
| `role` | string | false | | |
|
||||
|
||||
## codersdk.ConnectionLatency
|
||||
|
||||
```json
|
||||
@@ -1477,77 +1100,6 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
||||
| `password` | string | true | | |
|
||||
| `to_type` | [codersdk.LoginType](#codersdklogintype) | true | | To type is the login type to convert to. |
|
||||
|
||||
## codersdk.CreateChatMessageRequest
|
||||
|
||||
```json
|
||||
{
|
||||
"message": {
|
||||
"annotations": [
|
||||
null
|
||||
],
|
||||
"content": "string",
|
||||
"createdAt": [
|
||||
0
|
||||
],
|
||||
"experimental_attachments": [
|
||||
{
|
||||
"contentType": "string",
|
||||
"name": "string",
|
||||
"url": "string"
|
||||
}
|
||||
],
|
||||
"id": "string",
|
||||
"parts": [
|
||||
{
|
||||
"data": [
|
||||
0
|
||||
],
|
||||
"details": [
|
||||
{
|
||||
"data": "string",
|
||||
"signature": "string",
|
||||
"text": "string",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"mimeType": "string",
|
||||
"reasoning": "string",
|
||||
"source": {
|
||||
"contentType": "string",
|
||||
"data": "string",
|
||||
"metadata": {
|
||||
"property1": null,
|
||||
"property2": null
|
||||
},
|
||||
"uri": "string"
|
||||
},
|
||||
"text": "string",
|
||||
"toolInvocation": {
|
||||
"args": null,
|
||||
"result": null,
|
||||
"state": "call",
|
||||
"step": 0,
|
||||
"toolCallId": "string",
|
||||
"toolName": "string"
|
||||
},
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"role": "string"
|
||||
},
|
||||
"model": "string",
|
||||
"thinking": true
|
||||
}
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
|------------|----------------------------------------------|----------|--------------|-------------|
|
||||
| `message` | [codersdk.ChatMessage](#codersdkchatmessage) | false | | |
|
||||
| `model` | string | false | | |
|
||||
| `thinking` | boolean | false | | |
|
||||
|
||||
## codersdk.CreateFirstUserRequest
|
||||
|
||||
```json
|
||||
@@ -1812,52 +1364,12 @@ This is required on creation to enable a user-flow of validating a template work
|
||||
## codersdk.CreateTestAuditLogRequest
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "create",
|
||||
"additional_fields": [
|
||||
0
|
||||
],
|
||||
"build_reason": "autostart",
|
||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||
"request_id": "266ea41d-adf5-480b-af50-15b940c2b846",
|
||||
"resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f",
|
||||
"resource_type": "template",
|
||||
"time": "2019-08-24T14:15:22Z"
|
||||
}
|
||||
{}
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
|---------------------|------------------------------------------------|----------|--------------|-------------|
|
||||
| `action` | [codersdk.AuditAction](#codersdkauditaction) | false | | |
|
||||
| `additional_fields` | array of integer | false | | |
|
||||
| `build_reason` | [codersdk.BuildReason](#codersdkbuildreason) | false | | |
|
||||
| `organization_id` | string | false | | |
|
||||
| `request_id` | string | false | | |
|
||||
| `resource_id` | string | false | | |
|
||||
| `resource_type` | [codersdk.ResourceType](#codersdkresourcetype) | false | | |
|
||||
| `time` | string | false | | |
|
||||
|
||||
#### Enumerated Values
|
||||
|
||||
| Property | Value |
|
||||
|-----------------|--------------------|
|
||||
| `action` | `create` |
|
||||
| `action` | `write` |
|
||||
| `action` | `delete` |
|
||||
| `action` | `start` |
|
||||
| `action` | `stop` |
|
||||
| `build_reason` | `autostart` |
|
||||
| `build_reason` | `autostop` |
|
||||
| `build_reason` | `initiator` |
|
||||
| `resource_type` | `template` |
|
||||
| `resource_type` | `template_version` |
|
||||
| `resource_type` | `user` |
|
||||
| `resource_type` | `workspace` |
|
||||
| `resource_type` | `workspace_build` |
|
||||
| `resource_type` | `git_ssh_key` |
|
||||
| `resource_type` | `auditable_group` |
|
||||
None
|
||||
|
||||
## codersdk.CreateTokenRequest
|
||||
|
||||
@@ -2328,19 +1840,6 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o
|
||||
"user": {}
|
||||
},
|
||||
"agent_stat_refresh_interval": 0,
|
||||
"ai": {
|
||||
"value": {
|
||||
"providers": [
|
||||
{
|
||||
"base_url": "string",
|
||||
"models": [
|
||||
"string"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"allow_workspace_renames": true,
|
||||
"autobuild_poll_interval": 0,
|
||||
"browser_only": true,
|
||||
@@ -2829,19 +2328,6 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o
|
||||
"user": {}
|
||||
},
|
||||
"agent_stat_refresh_interval": 0,
|
||||
"ai": {
|
||||
"value": {
|
||||
"providers": [
|
||||
{
|
||||
"base_url": "string",
|
||||
"models": [
|
||||
"string"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"allow_workspace_renames": true,
|
||||
"autobuild_poll_interval": 0,
|
||||
"browser_only": true,
|
||||
@@ -3221,7 +2707,6 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o
|
||||
| `address` | [serpent.HostPort](#serpenthostport) | false | | Deprecated: Use HTTPAddress or TLS.Address instead. |
|
||||
| `agent_fallback_troubleshooting_url` | [serpent.URL](#serpenturl) | false | | |
|
||||
| `agent_stat_refresh_interval` | integer | false | | |
|
||||
| `ai` | [serpent.Struct-codersdk_AIConfig](#serpentstruct-codersdk_aiconfig) | false | | |
|
||||
| `allow_workspace_renames` | boolean | false | | |
|
||||
| `autobuild_poll_interval` | integer | false | | |
|
||||
| `browser_only` | boolean | false | | |
|
||||
@@ -3512,7 +2997,6 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o
|
||||
| `workspace-usage` |
|
||||
| `web-push` |
|
||||
| `workspace-prebuilds` |
|
||||
| `agentic-chat` |
|
||||
|
||||
## codersdk.ExternalAuth
|
||||
|
||||
@@ -4152,44 +3636,6 @@ Git clone makes use of this by parsing the URL from: 'Username for "https://gith
|
||||
|-------------------------------|
|
||||
| `REQUIRED_TEMPLATE_VARIABLES` |
|
||||
|
||||
## codersdk.LanguageModel
|
||||
|
||||
```json
|
||||
{
|
||||
"display_name": "string",
|
||||
"id": "string",
|
||||
"provider": "string"
|
||||
}
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
|----------------|--------|----------|--------------|-------------------------------------------------------------------|
|
||||
| `display_name` | string | false | | |
|
||||
| `id` | string | false | | ID is used by the provider to identify the LLM. |
|
||||
| `provider` | string | false | | Provider is the provider of the LLM. e.g. openai, anthropic, etc. |
|
||||
|
||||
## codersdk.LanguageModelConfig
|
||||
|
||||
```json
|
||||
{
|
||||
"models": [
|
||||
{
|
||||
"display_name": "string",
|
||||
"id": "string",
|
||||
"provider": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
|----------|-----------------------------------------------------------|----------|--------------|-------------|
|
||||
| `models` | array of [codersdk.LanguageModel](#codersdklanguagemodel) | false | | |
|
||||
|
||||
## codersdk.License
|
||||
|
||||
```json
|
||||
@@ -6307,7 +5753,6 @@ Git clone makes use of this by parsing the URL from: 'Username for "https://gith
|
||||
| `assign_org_role` |
|
||||
| `assign_role` |
|
||||
| `audit_log` |
|
||||
| `chat` |
|
||||
| `crypto_key` |
|
||||
| `debug_info` |
|
||||
| `deployment_config` |
|
||||
@@ -12269,30 +11714,6 @@ None
|
||||
|---------|-----------------------------------------------------|----------|--------------|-------------|
|
||||
| `value` | array of [codersdk.LinkConfig](#codersdklinkconfig) | false | | |
|
||||
|
||||
## serpent.Struct-codersdk_AIConfig
|
||||
|
||||
```json
|
||||
{
|
||||
"value": {
|
||||
"providers": [
|
||||
{
|
||||
"base_url": "string",
|
||||
"models": [
|
||||
"string"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
|---------|----------------------------------------|----------|--------------|-------------|
|
||||
| `value` | [codersdk.AIConfig](#codersdkaiconfig) | false | | |
|
||||
|
||||
## serpent.URL
|
||||
|
||||
```json
|
||||
|
||||
@@ -481,14 +481,11 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/anthropics/anthropic-sdk-go v0.2.0-beta.3
|
||||
github.com/coder/agentapi-sdk-go v0.0.0-20250505131810-560d1d88d225
|
||||
github.com/coder/preview v1.0.1
|
||||
github.com/fsnotify/fsnotify v1.9.0
|
||||
github.com/kylecarbs/aisdk-go v0.0.8
|
||||
github.com/mark3labs/mcp-go v0.32.0
|
||||
github.com/openai/openai-go v0.1.0-beta.10
|
||||
google.golang.org/genai v0.7.0
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -505,6 +502,7 @@ require (
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.50.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.50.0 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.3.1 // indirect
|
||||
github.com/anthropics/anthropic-sdk-go v0.2.0-beta.3 // indirect
|
||||
github.com/aquasecurity/go-version v0.0.1 // indirect
|
||||
github.com/aquasecurity/trivy v0.58.2 // indirect
|
||||
github.com/aws/aws-sdk-go v1.55.7 // indirect
|
||||
@@ -522,6 +520,7 @@ require (
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
||||
github.com/moby/sys/user v0.4.0 // indirect
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
|
||||
github.com/openai/openai-go v0.1.0-beta.10 // indirect
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1 // indirect
|
||||
github.com/samber/lo v1.50.0 // indirect
|
||||
@@ -536,5 +535,6 @@ require (
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.35.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.35.0 // indirect
|
||||
google.golang.org/genai v0.7.0 // indirect
|
||||
k8s.io/utils v0.0.0-20241210054802-24370beab758 // indirect
|
||||
)
|
||||
|
||||
@@ -34,8 +34,6 @@
|
||||
"update-emojis": "cp -rf ./node_modules/emoji-datasource-apple/img/apple/64/* ./static/emojis"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ai-sdk/provider-utils": "2.2.6",
|
||||
"@ai-sdk/react": "1.2.6",
|
||||
"@emoji-mart/data": "1.2.1",
|
||||
"@emoji-mart/react": "1.1.1",
|
||||
"@emotion/cache": "11.14.0",
|
||||
@@ -110,7 +108,6 @@
|
||||
"react-virtualized-auto-sizer": "1.0.24",
|
||||
"react-window": "1.8.11",
|
||||
"recharts": "2.15.0",
|
||||
"rehype-raw": "7.0.0",
|
||||
"remark-gfm": "4.0.0",
|
||||
"resize-observer-polyfill": "1.5.1",
|
||||
"semver": "7.6.2",
|
||||
|
||||
Generated
-216
@@ -16,12 +16,6 @@ importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
'@ai-sdk/provider-utils':
|
||||
specifier: 2.2.6
|
||||
version: 2.2.6(zod@3.24.3)
|
||||
'@ai-sdk/react':
|
||||
specifier: 1.2.6
|
||||
version: 1.2.6(react@18.3.1)(zod@3.24.3)
|
||||
'@emoji-mart/data':
|
||||
specifier: 1.2.1
|
||||
version: 1.2.1
|
||||
@@ -244,9 +238,6 @@ importers:
|
||||
recharts:
|
||||
specifier: 2.15.0
|
||||
version: 2.15.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
rehype-raw:
|
||||
specifier: 7.0.0
|
||||
version: 7.0.0
|
||||
remark-gfm:
|
||||
specifier: 4.0.0
|
||||
version: 4.0.0
|
||||
@@ -492,42 +483,6 @@ packages:
|
||||
'@adobe/css-tools@4.4.1':
|
||||
resolution: {integrity: sha512-12WGKBQzjUAI4ayyF4IAtfw2QR/IDoqk6jTddXDhtYTJF9ASmoE1zst7cVtP0aL/F1jUJL5r+JxKXKEgHNbEUQ==, tarball: https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.1.tgz}
|
||||
|
||||
'@ai-sdk/provider-utils@2.2.4':
|
||||
resolution: {integrity: sha512-13sEGBxB6kgaMPGOgCLYibF6r8iv8mgjhuToFrOTU09bBxbFQd8ZoARarCfJN6VomCUbUvMKwjTBLb1vQnN+WA==, tarball: https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-2.2.4.tgz}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
zod: ^3.23.8
|
||||
|
||||
'@ai-sdk/provider-utils@2.2.6':
|
||||
resolution: {integrity: sha512-sUlZ7Gnq84DCGWMQRIK8XVbkzIBnvPR1diV4v6JwPgpn5armnLI/j+rqn62MpLrU5ZCQZlDKl/Lw6ed3ulYqaA==, tarball: https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-2.2.6.tgz}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
zod: ^3.23.8
|
||||
|
||||
'@ai-sdk/provider@1.1.0':
|
||||
resolution: {integrity: sha512-0M+qjp+clUD0R1E5eWQFhxEvWLNaOtGQRUaBn8CUABnSKredagq92hUS9VjOzGsTm37xLfpaxl97AVtbeOsHew==, tarball: https://registry.npmjs.org/@ai-sdk/provider/-/provider-1.1.0.tgz}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@ai-sdk/provider@1.1.2':
|
||||
resolution: {integrity: sha512-ITdgNilJZwLKR7X5TnUr1BsQW6UTX5yFp0h66Nfx8XjBYkWD9W3yugr50GOz3CnE9m/U/Cd5OyEbTMI0rgi6ZQ==, tarball: https://registry.npmjs.org/@ai-sdk/provider/-/provider-1.1.2.tgz}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@ai-sdk/react@1.2.6':
|
||||
resolution: {integrity: sha512-5BFChNbcYtcY9MBStcDev7WZRHf0NpTrk8yfSoedWctB3jfWkFd1HECBvdc8w3mUQshF2MumLHtAhRO7IFtGGQ==, tarball: https://registry.npmjs.org/@ai-sdk/react/-/react-1.2.6.tgz}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
react: ^18 || ^19 || ^19.0.0-rc
|
||||
zod: ^3.23.8
|
||||
peerDependenciesMeta:
|
||||
zod:
|
||||
optional: true
|
||||
|
||||
'@ai-sdk/ui-utils@1.2.5':
|
||||
resolution: {integrity: sha512-XDgqnJcaCkDez7qolvk+PDbs/ceJvgkNkxkOlc9uDWqxfDJxtvCZ+14MP/1qr4IBwGIgKVHzMDYDXvqVhSWLzg==, tarball: https://registry.npmjs.org/@ai-sdk/ui-utils/-/ui-utils-1.2.5.tgz}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
zod: ^3.23.8
|
||||
|
||||
'@alloc/quick-lru@5.2.0':
|
||||
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==, tarball: https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz}
|
||||
engines: {node: '>=10'}
|
||||
@@ -4030,33 +3985,18 @@ packages:
|
||||
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==, tarball: https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
hast-util-from-parse5@8.0.3:
|
||||
resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==, tarball: https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz}
|
||||
|
||||
hast-util-parse-selector@2.2.5:
|
||||
resolution: {integrity: sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==, tarball: https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz}
|
||||
|
||||
hast-util-parse-selector@4.0.0:
|
||||
resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==, tarball: https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz}
|
||||
|
||||
hast-util-raw@9.1.0:
|
||||
resolution: {integrity: sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==, tarball: https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz}
|
||||
|
||||
hast-util-to-jsx-runtime@2.3.2:
|
||||
resolution: {integrity: sha512-1ngXYb+V9UT5h+PxNRa1O1FYguZK/XL+gkeqvp7EdHlB9oHUG0eYRo/vY5inBdcqo3RkPMC58/H94HvkbfGdyg==, tarball: https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.2.tgz}
|
||||
|
||||
hast-util-to-parse5@8.0.0:
|
||||
resolution: {integrity: sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==, tarball: https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz}
|
||||
|
||||
hast-util-whitespace@3.0.0:
|
||||
resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==, tarball: https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz}
|
||||
|
||||
hastscript@6.0.0:
|
||||
resolution: {integrity: sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==, tarball: https://registry.npmjs.org/hastscript/-/hastscript-6.0.0.tgz}
|
||||
|
||||
hastscript@9.0.1:
|
||||
resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==, tarball: https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz}
|
||||
|
||||
headers-polyfill@4.0.3:
|
||||
resolution: {integrity: sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==, tarball: https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-4.0.3.tgz}
|
||||
|
||||
@@ -4079,9 +4019,6 @@ packages:
|
||||
html-url-attributes@3.0.1:
|
||||
resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==, tarball: https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz}
|
||||
|
||||
html-void-elements@3.0.0:
|
||||
resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==, tarball: https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz}
|
||||
|
||||
http-errors@2.0.0:
|
||||
resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==, tarball: https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz}
|
||||
engines: {node: '>= 0.8'}
|
||||
@@ -4585,9 +4522,6 @@ packages:
|
||||
json-schema-traverse@0.4.1:
|
||||
resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==, tarball: https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz}
|
||||
|
||||
json-schema@0.4.0:
|
||||
resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==, tarball: https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz}
|
||||
|
||||
json-stable-stringify-without-jsonify@1.0.1:
|
||||
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==, tarball: https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz}
|
||||
|
||||
@@ -5348,9 +5282,6 @@ packages:
|
||||
property-information@6.5.0:
|
||||
resolution: {integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==, tarball: https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz}
|
||||
|
||||
property-information@7.0.0:
|
||||
resolution: {integrity: sha512-7D/qOz/+Y4X/rzSB6jKxKUsQnphO046ei8qxG59mtM3RG3DHgTK81HrxrmoDVINJb8NKT5ZsRbwHvQ6B68Iyhg==, tarball: https://registry.npmjs.org/property-information/-/property-information-7.0.0.tgz}
|
||||
|
||||
protobufjs@7.4.0:
|
||||
resolution: {integrity: sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==, tarball: https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz}
|
||||
engines: {node: '>=12.0.0'}
|
||||
@@ -5611,9 +5542,6 @@ packages:
|
||||
resolution: {integrity: sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==, tarball: https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
rehype-raw@7.0.0:
|
||||
resolution: {integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==, tarball: https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz}
|
||||
|
||||
remark-gfm@4.0.0:
|
||||
resolution: {integrity: sha512-U92vJgBPkbw4Zfu/IiW2oTZLSL3Zpv+uI7My2eq8JxKgqraFdU8YUGicEJCEgSbeaG+QDFqIcwwfMTOEelPxuA==, tarball: https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.0.tgz}
|
||||
|
||||
@@ -5718,9 +5646,6 @@ packages:
|
||||
scheduler@0.23.2:
|
||||
resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==, tarball: https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz}
|
||||
|
||||
secure-json-parse@2.7.0:
|
||||
resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==, tarball: https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz}
|
||||
|
||||
semver@7.6.2:
|
||||
resolution: {integrity: sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==, tarball: https://registry.npmjs.org/semver/-/semver-7.6.2.tgz}
|
||||
engines: {node: '>=10'}
|
||||
@@ -5958,11 +5883,6 @@ packages:
|
||||
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==, tarball: https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
swr@2.3.3:
|
||||
resolution: {integrity: sha512-dshNvs3ExOqtZ6kJBaAsabhPdHyeY4P2cKwRCniDVifBMoG/SVI7tfLWqPXriVspf2Rg4tPzXJTnwaihIeFw2A==, tarball: https://registry.npmjs.org/swr/-/swr-2.3.3.tgz}
|
||||
peerDependencies:
|
||||
react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
|
||||
symbol-tree@3.2.4:
|
||||
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==, tarball: https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz}
|
||||
|
||||
@@ -6000,10 +5920,6 @@ packages:
|
||||
thenify@3.3.1:
|
||||
resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==, tarball: https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz}
|
||||
|
||||
throttleit@2.1.0:
|
||||
resolution: {integrity: sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==, tarball: https://registry.npmjs.org/throttleit/-/throttleit-2.1.0.tgz}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
tiny-case@1.0.3:
|
||||
resolution: {integrity: sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==, tarball: https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz}
|
||||
|
||||
@@ -6309,9 +6225,6 @@ packages:
|
||||
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==, tarball: https://registry.npmjs.org/vary/-/vary-1.1.2.tgz}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
vfile-location@5.0.3:
|
||||
resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==, tarball: https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz}
|
||||
|
||||
vfile-message@4.0.2:
|
||||
resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==, tarball: https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz}
|
||||
|
||||
@@ -6411,9 +6324,6 @@ packages:
|
||||
wcwidth@1.0.1:
|
||||
resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==, tarball: https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz}
|
||||
|
||||
web-namespaces@2.0.1:
|
||||
resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==, tarball: https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz}
|
||||
|
||||
webidl-conversions@7.0.0:
|
||||
resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==, tarball: https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz}
|
||||
engines: {node: '>=12'}
|
||||
@@ -6545,11 +6455,6 @@ packages:
|
||||
yup@1.6.1:
|
||||
resolution: {integrity: sha512-JED8pB50qbA4FOkDol0bYF/p60qSEDQqBD0/qeIrUCG1KbPBIQ776fCUNb9ldbPcSTxA69g/47XTo4TqWiuXOA==, tarball: https://registry.npmjs.org/yup/-/yup-1.6.1.tgz}
|
||||
|
||||
zod-to-json-schema@3.24.5:
|
||||
resolution: {integrity: sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==, tarball: https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz}
|
||||
peerDependencies:
|
||||
zod: ^3.24.1
|
||||
|
||||
zod-validation-error@3.4.0:
|
||||
resolution: {integrity: sha512-ZOPR9SVY6Pb2qqO5XHt+MkkTRxGXb4EVtnjc9JpXUOtUB1T9Ru7mZOT361AN3MsetVe7R0a1KZshJDZdgp9miQ==, tarball: https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-3.4.0.tgz}
|
||||
engines: {node: '>=18.0.0'}
|
||||
@@ -6569,45 +6474,6 @@ snapshots:
|
||||
|
||||
'@adobe/css-tools@4.4.1': {}
|
||||
|
||||
'@ai-sdk/provider-utils@2.2.4(zod@3.24.3)':
|
||||
dependencies:
|
||||
'@ai-sdk/provider': 1.1.0
|
||||
nanoid: 3.3.8
|
||||
secure-json-parse: 2.7.0
|
||||
zod: 3.24.3
|
||||
|
||||
'@ai-sdk/provider-utils@2.2.6(zod@3.24.3)':
|
||||
dependencies:
|
||||
'@ai-sdk/provider': 1.1.2
|
||||
nanoid: 3.3.8
|
||||
secure-json-parse: 2.7.0
|
||||
zod: 3.24.3
|
||||
|
||||
'@ai-sdk/provider@1.1.0':
|
||||
dependencies:
|
||||
json-schema: 0.4.0
|
||||
|
||||
'@ai-sdk/provider@1.1.2':
|
||||
dependencies:
|
||||
json-schema: 0.4.0
|
||||
|
||||
'@ai-sdk/react@1.2.6(react@18.3.1)(zod@3.24.3)':
|
||||
dependencies:
|
||||
'@ai-sdk/provider-utils': 2.2.4(zod@3.24.3)
|
||||
'@ai-sdk/ui-utils': 1.2.5(zod@3.24.3)
|
||||
react: 18.3.1
|
||||
swr: 2.3.3(react@18.3.1)
|
||||
throttleit: 2.1.0
|
||||
optionalDependencies:
|
||||
zod: 3.24.3
|
||||
|
||||
'@ai-sdk/ui-utils@1.2.5(zod@3.24.3)':
|
||||
dependencies:
|
||||
'@ai-sdk/provider': 1.1.0
|
||||
'@ai-sdk/provider-utils': 2.2.4(zod@3.24.3)
|
||||
zod: 3.24.3
|
||||
zod-to-json-schema: 3.24.5(zod@3.24.3)
|
||||
|
||||
'@alloc/quick-lru@5.2.0': {}
|
||||
|
||||
'@ampproject/remapping@2.3.0':
|
||||
@@ -10430,39 +10296,8 @@ snapshots:
|
||||
dependencies:
|
||||
function-bind: 1.1.2
|
||||
|
||||
hast-util-from-parse5@8.0.3:
|
||||
dependencies:
|
||||
'@types/hast': 3.0.4
|
||||
'@types/unist': 3.0.3
|
||||
devlop: 1.1.0
|
||||
hastscript: 9.0.1
|
||||
property-information: 7.0.0
|
||||
vfile: 6.0.3
|
||||
vfile-location: 5.0.3
|
||||
web-namespaces: 2.0.1
|
||||
|
||||
hast-util-parse-selector@2.2.5: {}
|
||||
|
||||
hast-util-parse-selector@4.0.0:
|
||||
dependencies:
|
||||
'@types/hast': 3.0.4
|
||||
|
||||
hast-util-raw@9.1.0:
|
||||
dependencies:
|
||||
'@types/hast': 3.0.4
|
||||
'@types/unist': 3.0.3
|
||||
'@ungap/structured-clone': 1.3.0
|
||||
hast-util-from-parse5: 8.0.3
|
||||
hast-util-to-parse5: 8.0.0
|
||||
html-void-elements: 3.0.0
|
||||
mdast-util-to-hast: 13.2.0
|
||||
parse5: 7.1.2
|
||||
unist-util-position: 5.0.0
|
||||
unist-util-visit: 5.0.0
|
||||
vfile: 6.0.3
|
||||
web-namespaces: 2.0.1
|
||||
zwitch: 2.0.4
|
||||
|
||||
hast-util-to-jsx-runtime@2.3.2:
|
||||
dependencies:
|
||||
'@types/estree': 1.0.6
|
||||
@@ -10483,16 +10318,6 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
hast-util-to-parse5@8.0.0:
|
||||
dependencies:
|
||||
'@types/hast': 3.0.4
|
||||
comma-separated-tokens: 2.0.3
|
||||
devlop: 1.1.0
|
||||
property-information: 6.5.0
|
||||
space-separated-tokens: 2.0.2
|
||||
web-namespaces: 2.0.1
|
||||
zwitch: 2.0.4
|
||||
|
||||
hast-util-whitespace@3.0.0:
|
||||
dependencies:
|
||||
'@types/hast': 3.0.4
|
||||
@@ -10505,14 +10330,6 @@ snapshots:
|
||||
property-information: 5.6.0
|
||||
space-separated-tokens: 1.1.5
|
||||
|
||||
hastscript@9.0.1:
|
||||
dependencies:
|
||||
'@types/hast': 3.0.4
|
||||
comma-separated-tokens: 2.0.3
|
||||
hast-util-parse-selector: 4.0.0
|
||||
property-information: 7.0.0
|
||||
space-separated-tokens: 2.0.2
|
||||
|
||||
headers-polyfill@4.0.3: {}
|
||||
|
||||
highlight.js@10.7.3: {}
|
||||
@@ -10531,8 +10348,6 @@ snapshots:
|
||||
|
||||
html-url-attributes@3.0.1: {}
|
||||
|
||||
html-void-elements@3.0.0: {}
|
||||
|
||||
http-errors@2.0.0:
|
||||
dependencies:
|
||||
depd: 2.0.0
|
||||
@@ -11260,8 +11075,6 @@ snapshots:
|
||||
json-schema-traverse@0.4.1:
|
||||
optional: true
|
||||
|
||||
json-schema@0.4.0: {}
|
||||
|
||||
json-stable-stringify-without-jsonify@1.0.1:
|
||||
optional: true
|
||||
|
||||
@@ -12295,8 +12108,6 @@ snapshots:
|
||||
|
||||
property-information@6.5.0: {}
|
||||
|
||||
property-information@7.0.0: {}
|
||||
|
||||
protobufjs@7.4.0:
|
||||
dependencies:
|
||||
'@protobufjs/aspromise': 1.1.2
|
||||
@@ -12620,12 +12431,6 @@ snapshots:
|
||||
define-properties: 1.2.1
|
||||
set-function-name: 2.0.1
|
||||
|
||||
rehype-raw@7.0.0:
|
||||
dependencies:
|
||||
'@types/hast': 3.0.4
|
||||
hast-util-raw: 9.1.0
|
||||
vfile: 6.0.3
|
||||
|
||||
remark-gfm@4.0.0:
|
||||
dependencies:
|
||||
'@types/mdast': 4.0.3
|
||||
@@ -12763,8 +12568,6 @@ snapshots:
|
||||
dependencies:
|
||||
loose-envify: 1.4.0
|
||||
|
||||
secure-json-parse@2.7.0: {}
|
||||
|
||||
semver@7.6.2: {}
|
||||
|
||||
send@0.19.0:
|
||||
@@ -13014,12 +12817,6 @@ snapshots:
|
||||
|
||||
supports-preserve-symlinks-flag@1.0.0: {}
|
||||
|
||||
swr@2.3.3(react@18.3.1):
|
||||
dependencies:
|
||||
dequal: 2.0.3
|
||||
react: 18.3.1
|
||||
use-sync-external-store: 1.4.0(react@18.3.1)
|
||||
|
||||
symbol-tree@3.2.4: {}
|
||||
|
||||
tailwind-merge@2.6.0: {}
|
||||
@@ -13078,8 +12875,6 @@ snapshots:
|
||||
dependencies:
|
||||
any-promise: 1.3.0
|
||||
|
||||
throttleit@2.1.0: {}
|
||||
|
||||
tiny-case@1.0.3: {}
|
||||
|
||||
tiny-invariant@1.3.3: {}
|
||||
@@ -13376,11 +13171,6 @@ snapshots:
|
||||
|
||||
vary@1.1.2: {}
|
||||
|
||||
vfile-location@5.0.3:
|
||||
dependencies:
|
||||
'@types/unist': 3.0.3
|
||||
vfile: 6.0.3
|
||||
|
||||
vfile-message@4.0.2:
|
||||
dependencies:
|
||||
'@types/unist': 3.0.3
|
||||
@@ -13456,8 +13246,6 @@ snapshots:
|
||||
dependencies:
|
||||
defaults: 1.0.4
|
||||
|
||||
web-namespaces@2.0.1: {}
|
||||
|
||||
webidl-conversions@7.0.0: {}
|
||||
|
||||
webpack-sources@3.2.3: {}
|
||||
@@ -13572,10 +13360,6 @@ snapshots:
|
||||
toposort: 2.0.2
|
||||
type-fest: 2.19.0
|
||||
|
||||
zod-to-json-schema@3.24.5(zod@3.24.3):
|
||||
dependencies:
|
||||
zod: 3.24.3
|
||||
|
||||
zod-validation-error@3.4.0(zod@3.24.3):
|
||||
dependencies:
|
||||
zod: 3.24.3
|
||||
|
||||
@@ -818,13 +818,6 @@ class ApiMethods {
|
||||
return response.data;
|
||||
};
|
||||
|
||||
getDeploymentLLMs = async (): Promise<TypesGen.LanguageModelConfig> => {
|
||||
const response = await this.axios.get<TypesGen.LanguageModelConfig>(
|
||||
"/api/v2/deployment/llms",
|
||||
);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
getOrganizationIdpSyncClaimFieldValues = async (
|
||||
organization: string,
|
||||
field: string,
|
||||
@@ -2584,23 +2577,6 @@ class ApiMethods {
|
||||
markAllInboxNotificationsAsRead = async () => {
|
||||
await this.axios.put<void>("/api/v2/notifications/inbox/mark-all-as-read");
|
||||
};
|
||||
|
||||
createChat = async () => {
|
||||
const res = await this.axios.post<TypesGen.Chat>("/api/v2/chats");
|
||||
return res.data;
|
||||
};
|
||||
|
||||
getChats = async () => {
|
||||
const res = await this.axios.get<TypesGen.Chat[]>("/api/v2/chats");
|
||||
return res.data;
|
||||
};
|
||||
|
||||
getChatMessages = async (chatId: string) => {
|
||||
const res = await this.axios.get<TypesGen.ChatMessage[]>(
|
||||
`/api/v2/chats/${chatId}/messages`,
|
||||
);
|
||||
return res.data;
|
||||
};
|
||||
}
|
||||
|
||||
// Experimental API methods call endpoints under the /api/experimental/ prefix.
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import { API } from "api/api";
|
||||
import type { QueryClient } from "react-query";
|
||||
|
||||
export const createChat = (queryClient: QueryClient) => {
|
||||
return {
|
||||
mutationFn: API.createChat,
|
||||
onSuccess: async () => {
|
||||
await queryClient.invalidateQueries({ queryKey: ["chats"] });
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const getChats = () => {
|
||||
return {
|
||||
queryKey: ["chats"],
|
||||
queryFn: API.getChats,
|
||||
};
|
||||
};
|
||||
|
||||
export const getChatMessages = (chatID: string) => {
|
||||
return {
|
||||
queryKey: ["chatMessages", chatID],
|
||||
queryFn: () => API.getChatMessages(chatID),
|
||||
};
|
||||
};
|
||||
@@ -39,10 +39,3 @@ export const deploymentIdpSyncFieldValues = (field: string) => {
|
||||
queryFn: () => API.getDeploymentIdpSyncFieldValues(field),
|
||||
};
|
||||
};
|
||||
|
||||
export const deploymentLanguageModels = () => {
|
||||
return {
|
||||
queryKey: ["deployment", "llms"],
|
||||
queryFn: API.getDeploymentLLMs,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -31,12 +31,6 @@ export const RBACResourceActions: Partial<
|
||||
create: "create new audit log entries",
|
||||
read: "read audit logs",
|
||||
},
|
||||
chat: {
|
||||
create: "create a chat",
|
||||
delete: "delete a chat",
|
||||
read: "read a chat",
|
||||
update: "update a chat",
|
||||
},
|
||||
crypto_key: {
|
||||
create: "create crypto keys",
|
||||
delete: "delete crypto keys",
|
||||
|
||||
Generated
-47
@@ -311,28 +311,6 @@ export interface ChangePasswordWithOneTimePasscodeRequest {
|
||||
readonly one_time_passcode: string;
|
||||
}
|
||||
|
||||
// From codersdk/chat.go
|
||||
export interface Chat {
|
||||
readonly id: string;
|
||||
readonly created_at: string;
|
||||
readonly updated_at: string;
|
||||
readonly title: string;
|
||||
}
|
||||
|
||||
// From codersdk/chat.go
|
||||
export interface ChatMessage {
|
||||
readonly id: string;
|
||||
readonly createdAt?: Record<string, string>;
|
||||
readonly content: string;
|
||||
readonly role: string;
|
||||
// external type "github.com/kylecarbs/aisdk-go.Part", to include this type the package must be explicitly included in the parsing
|
||||
readonly parts?: readonly unknown[];
|
||||
// empty interface{} type, falling back to unknown
|
||||
readonly annotations?: readonly unknown[];
|
||||
// external type "github.com/kylecarbs/aisdk-go.Attachment", to include this type the package must be explicitly included in the parsing
|
||||
readonly experimental_attachments?: readonly unknown[];
|
||||
}
|
||||
|
||||
// From codersdk/client.go
|
||||
export const CoderDesktopTelemetryHeader = "Coder-Desktop-Telemetry";
|
||||
|
||||
@@ -354,14 +332,6 @@ export interface ConvertLoginRequest {
|
||||
readonly password: string;
|
||||
}
|
||||
|
||||
// From codersdk/chat.go
|
||||
export interface CreateChatMessageRequest {
|
||||
readonly model: string;
|
||||
// external type "github.com/kylecarbs/aisdk-go.Message", to include this type the package must be explicitly included in the parsing
|
||||
readonly message: unknown;
|
||||
readonly thinking: boolean;
|
||||
}
|
||||
|
||||
// From codersdk/users.go
|
||||
export interface CreateFirstUserRequest {
|
||||
readonly email: string;
|
||||
@@ -726,7 +696,6 @@ export interface DeploymentValues {
|
||||
readonly disable_password_auth?: boolean;
|
||||
readonly support?: SupportConfig;
|
||||
readonly external_auth?: SerpentStruct<ExternalAuthConfig[]>;
|
||||
readonly ai?: SerpentStruct<AIConfig>;
|
||||
readonly config_ssh?: SSHConfig;
|
||||
readonly wgtunnel_host?: string;
|
||||
readonly disable_owner_workspace_exec?: boolean;
|
||||
@@ -834,7 +803,6 @@ export const EntitlementsWarningHeader = "X-Coder-Entitlements-Warning";
|
||||
|
||||
// From codersdk/deployment.go
|
||||
export type Experiment =
|
||||
| "agentic-chat"
|
||||
| "auto-fill-parameters"
|
||||
| "example"
|
||||
| "notifications"
|
||||
@@ -843,7 +811,6 @@ export type Experiment =
|
||||
| "workspace-usage";
|
||||
|
||||
export const Experiments: Experiment[] = [
|
||||
"agentic-chat",
|
||||
"auto-fill-parameters",
|
||||
"example",
|
||||
"notifications",
|
||||
@@ -1259,18 +1226,6 @@ export type JobErrorCode = "REQUIRED_TEMPLATE_VARIABLES";
|
||||
|
||||
export const JobErrorCodes: JobErrorCode[] = ["REQUIRED_TEMPLATE_VARIABLES"];
|
||||
|
||||
// From codersdk/deployment.go
|
||||
export interface LanguageModel {
|
||||
readonly id: string;
|
||||
readonly display_name: string;
|
||||
readonly provider: string;
|
||||
}
|
||||
|
||||
// From codersdk/deployment.go
|
||||
export interface LanguageModelConfig {
|
||||
readonly models: readonly LanguageModel[];
|
||||
}
|
||||
|
||||
// From codersdk/licenses.go
|
||||
export interface License {
|
||||
readonly id: number;
|
||||
@@ -2186,7 +2141,6 @@ export type RBACResource =
|
||||
| "assign_org_role"
|
||||
| "assign_role"
|
||||
| "audit_log"
|
||||
| "chat"
|
||||
| "crypto_key"
|
||||
| "debug_info"
|
||||
| "deployment_config"
|
||||
@@ -2226,7 +2180,6 @@ export const RBACResources: RBACResource[] = [
|
||||
"assign_org_role",
|
||||
"assign_role",
|
||||
"audit_log",
|
||||
"chat",
|
||||
"crypto_key",
|
||||
"debug_info",
|
||||
"deployment_config",
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
import { experiments } from "api/queries/experiments";
|
||||
|
||||
import { useEmbeddedMetadata } from "hooks/useEmbeddedMetadata";
|
||||
import { useQuery } from "react-query";
|
||||
|
||||
interface AgenticChat {
|
||||
readonly enabled: boolean;
|
||||
}
|
||||
|
||||
export const useAgenticChat = (): AgenticChat => {
|
||||
const { metadata } = useEmbeddedMetadata();
|
||||
const enabledExperimentsQuery = useQuery(experiments(metadata.experiments));
|
||||
return {
|
||||
enabled: enabledExperimentsQuery.data?.includes("agentic-chat") ?? false,
|
||||
};
|
||||
};
|
||||
@@ -4,7 +4,6 @@ import { Button } from "components/Button/Button";
|
||||
import { ExternalImage } from "components/ExternalImage/ExternalImage";
|
||||
import { CoderIcon } from "components/Icons/CoderIcon";
|
||||
import type { ProxyContextValue } from "contexts/ProxyContext";
|
||||
import { useAgenticChat } from "contexts/useAgenticChat";
|
||||
import { useWebpushNotifications } from "contexts/useWebpushNotifications";
|
||||
import { useEmbeddedMetadata } from "hooks/useEmbeddedMetadata";
|
||||
import { NotificationsInbox } from "modules/notifications/NotificationsInbox/NotificationsInbox";
|
||||
@@ -141,7 +140,6 @@ interface NavItemsProps {
|
||||
|
||||
const NavItems: FC<NavItemsProps> = ({ className }) => {
|
||||
const location = useLocation();
|
||||
const agenticChat = useAgenticChat();
|
||||
const { metadata } = useEmbeddedMetadata();
|
||||
|
||||
return (
|
||||
@@ -165,16 +163,6 @@ const NavItems: FC<NavItemsProps> = ({ className }) => {
|
||||
>
|
||||
Templates
|
||||
</NavLink>
|
||||
{agenticChat.enabled && (
|
||||
<NavLink
|
||||
className={({ isActive }) => {
|
||||
return cn(linkStyles.default, isActive ? linkStyles.active : "");
|
||||
}}
|
||||
to="/chat"
|
||||
>
|
||||
Chat
|
||||
</NavLink>
|
||||
)}
|
||||
{metadata["tasks-tab-visible"].value && (
|
||||
<NavLink
|
||||
className={({ isActive }) => {
|
||||
|
||||
@@ -1,164 +0,0 @@
|
||||
import { useTheme } from "@emotion/react";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import Paper from "@mui/material/Paper";
|
||||
import Stack from "@mui/material/Stack";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import { createChat } from "api/queries/chats";
|
||||
import type { Chat } from "api/typesGenerated";
|
||||
import { Button } from "components/Button/Button";
|
||||
import { Margins } from "components/Margins/Margins";
|
||||
import { useAuthenticated } from "hooks";
|
||||
import { SendIcon } from "lucide-react";
|
||||
import { type FC, type FormEvent, useState } from "react";
|
||||
import { useMutation, useQueryClient } from "react-query";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { LanguageModelSelector } from "./LanguageModelSelector";
|
||||
|
||||
export interface ChatLandingLocationState {
|
||||
chat: Chat;
|
||||
message: string;
|
||||
}
|
||||
|
||||
const ChatLanding: FC = () => {
|
||||
const { user } = useAuthenticated();
|
||||
const theme = useTheme();
|
||||
const [input, setInput] = useState("");
|
||||
const navigate = useNavigate();
|
||||
const queryClient = useQueryClient();
|
||||
const createChatMutation = useMutation(createChat(queryClient));
|
||||
|
||||
return (
|
||||
<Margins>
|
||||
<div
|
||||
css={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
marginTop: theme.spacing(24),
|
||||
alignItems: "center",
|
||||
paddingBottom: theme.spacing(4),
|
||||
}}
|
||||
>
|
||||
{/* Initial Welcome Message Area */}
|
||||
<div
|
||||
css={{
|
||||
flexGrow: 1,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing(1),
|
||||
padding: theme.spacing(1),
|
||||
width: "100%",
|
||||
maxWidth: "700px",
|
||||
marginBottom: theme.spacing(4),
|
||||
}}
|
||||
>
|
||||
<h1
|
||||
css={{
|
||||
fontSize: theme.typography.h4.fontSize,
|
||||
fontWeight: theme.typography.h4.fontWeight,
|
||||
lineHeight: theme.typography.h4.lineHeight,
|
||||
marginBottom: theme.spacing(1),
|
||||
textAlign: "center",
|
||||
}}
|
||||
>
|
||||
Good evening, {(user.name ?? user.username).split(" ")[0]}
|
||||
</h1>
|
||||
<p
|
||||
css={{
|
||||
fontSize: theme.typography.h6.fontSize,
|
||||
fontWeight: theme.typography.h6.fontWeight,
|
||||
lineHeight: theme.typography.h6.lineHeight,
|
||||
color: theme.palette.text.secondary,
|
||||
textAlign: "center",
|
||||
margin: 0,
|
||||
maxWidth: "500px",
|
||||
marginInline: "auto",
|
||||
}}
|
||||
>
|
||||
How can I help you today?
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Input Form and Suggestions - Always Visible */}
|
||||
<div css={{ width: "100%", maxWidth: "700px", marginTop: "auto" }}>
|
||||
<Stack
|
||||
direction="row"
|
||||
spacing={2}
|
||||
justifyContent="center"
|
||||
sx={{ mb: 2 }}
|
||||
>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setInput("Help me work on issue #...")}
|
||||
>
|
||||
Work on Issue
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setInput("Help me build a template for...")}
|
||||
>
|
||||
Build a Template
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setInput("Help me start a new project using...")}
|
||||
>
|
||||
Start a Project
|
||||
</Button>
|
||||
</Stack>
|
||||
<LanguageModelSelector />
|
||||
<Paper
|
||||
component="form"
|
||||
onSubmit={async (e: FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
setInput("");
|
||||
const chat = await createChatMutation.mutateAsync();
|
||||
navigate(`/chat/${chat.id}`, {
|
||||
state: {
|
||||
chat,
|
||||
message: input,
|
||||
},
|
||||
});
|
||||
}}
|
||||
elevation={2}
|
||||
css={{
|
||||
padding: "16px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
width: "100%",
|
||||
borderRadius: "12px",
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
}}
|
||||
>
|
||||
<TextField
|
||||
value={input}
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setInput(event.target.value);
|
||||
}}
|
||||
placeholder="Ask Coder..."
|
||||
required
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
multiline
|
||||
maxRows={5}
|
||||
css={{
|
||||
marginRight: theme.spacing(1),
|
||||
"& .MuiOutlinedInput-root": {
|
||||
borderRadius: "8px",
|
||||
padding: "10px 14px",
|
||||
},
|
||||
}}
|
||||
autoFocus
|
||||
/>
|
||||
<IconButton type="submit" color="primary" disabled={!input.trim()}>
|
||||
<SendIcon />
|
||||
</IconButton>
|
||||
</Paper>
|
||||
</div>
|
||||
</div>
|
||||
</Margins>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChatLanding;
|
||||
@@ -1,242 +0,0 @@
|
||||
import { useTheme } from "@emotion/react";
|
||||
import List from "@mui/material/List";
|
||||
import ListItem from "@mui/material/ListItem";
|
||||
import ListItemButton from "@mui/material/ListItemButton";
|
||||
import ListItemText from "@mui/material/ListItemText";
|
||||
import Paper from "@mui/material/Paper";
|
||||
import { createChat, getChats } from "api/queries/chats";
|
||||
import { deploymentLanguageModels } from "api/queries/deployment";
|
||||
import type { LanguageModelConfig } from "api/typesGenerated";
|
||||
import { ErrorAlert } from "components/Alert/ErrorAlert";
|
||||
import { Button } from "components/Button/Button";
|
||||
import { Loader } from "components/Loader/Loader";
|
||||
import { Margins } from "components/Margins/Margins";
|
||||
import { useAgenticChat } from "contexts/useAgenticChat";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
import {
|
||||
type FC,
|
||||
type PropsWithChildren,
|
||||
createContext,
|
||||
useContext,
|
||||
useEffect,
|
||||
useState,
|
||||
} from "react";
|
||||
import { useMutation, useQuery, useQueryClient } from "react-query";
|
||||
import { Link, Outlet, useNavigate, useParams } from "react-router-dom";
|
||||
|
||||
interface ChatContext {
|
||||
selectedModel: string;
|
||||
modelConfig: LanguageModelConfig;
|
||||
|
||||
setSelectedModel: (model: string) => void;
|
||||
}
|
||||
export const useChatContext = (): ChatContext => {
|
||||
const context = useContext(ChatContext);
|
||||
if (!context) {
|
||||
throw new Error("useChatContext must be used within a ChatProvider");
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
const ChatContext = createContext<ChatContext | undefined>(undefined);
|
||||
|
||||
const SELECTED_MODEL_KEY = "coder_chat_selected_model";
|
||||
|
||||
const ChatProvider: FC<PropsWithChildren> = ({ children }) => {
|
||||
const [selectedModel, setSelectedModel] = useState<string>(() => {
|
||||
const savedModel = localStorage.getItem(SELECTED_MODEL_KEY);
|
||||
return savedModel || "";
|
||||
});
|
||||
const modelConfigQuery = useQuery(deploymentLanguageModels());
|
||||
useEffect(() => {
|
||||
if (!modelConfigQuery.data) {
|
||||
return;
|
||||
}
|
||||
if (selectedModel === "") {
|
||||
const firstModel = modelConfigQuery.data.models[0]?.id; // Handle empty models array
|
||||
if (firstModel) {
|
||||
setSelectedModel(firstModel);
|
||||
localStorage.setItem(SELECTED_MODEL_KEY, firstModel);
|
||||
}
|
||||
}
|
||||
}, [modelConfigQuery.data, selectedModel]);
|
||||
|
||||
if (modelConfigQuery.error) {
|
||||
return <ErrorAlert error={modelConfigQuery.error} />;
|
||||
}
|
||||
|
||||
if (!modelConfigQuery.data) {
|
||||
return <Loader fullscreen />;
|
||||
}
|
||||
|
||||
const handleSetSelectedModel = (model: string) => {
|
||||
setSelectedModel(model);
|
||||
localStorage.setItem(SELECTED_MODEL_KEY, model);
|
||||
};
|
||||
|
||||
return (
|
||||
<ChatContext.Provider
|
||||
value={{
|
||||
selectedModel,
|
||||
modelConfig: modelConfigQuery.data,
|
||||
setSelectedModel: handleSetSelectedModel,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</ChatContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const ChatLayout: FC = () => {
|
||||
const agenticChat = useAgenticChat();
|
||||
const queryClient = useQueryClient();
|
||||
const { data: chats, isLoading: chatsLoading } = useQuery(getChats());
|
||||
const createChatMutation = useMutation(createChat(queryClient));
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
const { chatID } = useParams<{ chatID?: string }>();
|
||||
|
||||
const handleNewChat = () => {
|
||||
navigate("/chat");
|
||||
};
|
||||
|
||||
if (!agenticChat.enabled) {
|
||||
return (
|
||||
<Margins>
|
||||
<div
|
||||
css={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
marginTop: "24px",
|
||||
alignItems: "center",
|
||||
paddingBottom: "16px",
|
||||
}}
|
||||
>
|
||||
<h1>Agentic Chat is not enabled</h1>
|
||||
<p>
|
||||
Agentic Chat is an experimental feature and is not enabled by
|
||||
default. Please contact your administrator for more information.
|
||||
</p>
|
||||
</div>
|
||||
</Margins>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
// Outermost container: controls height and prevents page scroll
|
||||
<div
|
||||
css={{
|
||||
display: "flex",
|
||||
height: "calc(100vh - 164px)", // Assuming header height is 64px
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{/* Sidebar Container (using Paper for background/border) */}
|
||||
<Paper
|
||||
elevation={1}
|
||||
square // Removes border-radius
|
||||
css={{
|
||||
width: 260,
|
||||
flexShrink: 0,
|
||||
borderRight: `1px solid ${theme.palette.divider}`,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
height: "100%", // Take full height of the parent flex container
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
}}
|
||||
>
|
||||
{/* Sidebar Header */}
|
||||
<div
|
||||
css={{
|
||||
padding: theme.spacing(1.5, 2),
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
borderBottom: `1px solid ${theme.palette.divider}`,
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
{/* Replaced Typography with div + styling */}
|
||||
<div
|
||||
css={{
|
||||
fontWeight: 600,
|
||||
fontSize: theme.typography.subtitle1.fontSize,
|
||||
lineHeight: theme.typography.subtitle1.lineHeight,
|
||||
}}
|
||||
>
|
||||
Chats
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleNewChat}
|
||||
disabled={createChatMutation.isPending}
|
||||
>
|
||||
<PlusIcon />
|
||||
New Chat
|
||||
</Button>
|
||||
</div>
|
||||
{/* Sidebar Scrollable List Area */}
|
||||
<div css={{ overflowY: "auto", flexGrow: 1 }}>
|
||||
{chatsLoading ? (
|
||||
<Loader />
|
||||
) : chats && chats.length > 0 ? (
|
||||
<List dense>
|
||||
{chats.map((chat) => (
|
||||
<ListItem key={chat.id} disablePadding>
|
||||
<ListItemButton
|
||||
component={Link}
|
||||
to={`/chat/${chat.id}`}
|
||||
selected={chatID === chat.id}
|
||||
css={{
|
||||
padding: theme.spacing(1, 2),
|
||||
}}
|
||||
>
|
||||
<ListItemText
|
||||
primary={chat.title || `Chat ${chat.id}`}
|
||||
primaryTypographyProps={{
|
||||
noWrap: true,
|
||||
variant: "body2",
|
||||
style: { overflow: "hidden", textOverflow: "ellipsis" },
|
||||
}}
|
||||
/>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
) : (
|
||||
// Replaced Typography with div + styling
|
||||
<div
|
||||
css={{
|
||||
padding: theme.spacing(2),
|
||||
textAlign: "center",
|
||||
fontSize: theme.typography.body2.fontSize,
|
||||
color: theme.palette.text.secondary,
|
||||
}}
|
||||
>
|
||||
No chats yet. Start a new one!
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Paper>
|
||||
|
||||
{/* Main Content Area Container */}
|
||||
<div
|
||||
css={{
|
||||
flexGrow: 1, // Takes remaining width
|
||||
height: "100%", // Takes full height of parent
|
||||
overflow: "hidden", // Prevents this container from scrolling
|
||||
display: "flex",
|
||||
flexDirection: "column", // Stacks ChatProvider/Outlet
|
||||
position: "relative", // Context for potential absolute children
|
||||
backgroundColor: theme.palette.background.default, // Ensure background consistency
|
||||
}}
|
||||
>
|
||||
<ChatProvider>
|
||||
{/* Outlet renders ChatMessages, which should have its own internal scroll */}
|
||||
<Outlet />
|
||||
</ChatProvider>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,491 +0,0 @@
|
||||
import { type Message, useChat } from "@ai-sdk/react";
|
||||
import { type Theme, keyframes, useTheme } from "@emotion/react";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import Paper from "@mui/material/Paper";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import { getChatMessages } from "api/queries/chats";
|
||||
import type { ChatMessage, CreateChatMessageRequest } from "api/typesGenerated";
|
||||
import { ErrorAlert } from "components/Alert/ErrorAlert";
|
||||
import { Loader } from "components/Loader/Loader";
|
||||
import { SendIcon } from "lucide-react";
|
||||
import {
|
||||
type FC,
|
||||
type KeyboardEvent,
|
||||
memo,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
} from "react";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import { useQuery } from "react-query";
|
||||
import { useLocation, useParams } from "react-router-dom";
|
||||
import rehypeRaw from "rehype-raw";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import type { ChatLandingLocationState } from "./ChatLanding";
|
||||
import { useChatContext } from "./ChatLayout";
|
||||
import { ChatToolInvocation } from "./ChatToolInvocation";
|
||||
import { LanguageModelSelector } from "./LanguageModelSelector";
|
||||
|
||||
const fadeIn = keyframes`
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(5px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
`;
|
||||
|
||||
const renderReasoning = (reasoning: string, theme: Theme) => (
|
||||
<div
|
||||
css={{
|
||||
marginTop: theme.spacing(1),
|
||||
marginLeft: theme.spacing(2),
|
||||
borderLeft: `2px solid ${theme.palette.grey[400]}`,
|
||||
paddingLeft: theme.spacing(1.5),
|
||||
fontStyle: "italic",
|
||||
color: theme.palette.text.secondary,
|
||||
animation: `${fadeIn} 0.3s ease-out`,
|
||||
fontSize: "0.875em",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
css={{
|
||||
color: theme.palette.grey[700],
|
||||
fontWeight: 500,
|
||||
marginBottom: theme.spacing(0.5),
|
||||
}}
|
||||
>
|
||||
💭 Reasoning:
|
||||
</div>
|
||||
<div
|
||||
css={{
|
||||
whiteSpace: "pre-wrap",
|
||||
backgroundColor: theme.palette.action.hover,
|
||||
padding: theme.spacing(1.5),
|
||||
borderRadius: "6px",
|
||||
fontSize: "0.95em",
|
||||
lineHeight: 1.5,
|
||||
}}
|
||||
>
|
||||
{reasoning}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
interface MessageBubbleProps {
|
||||
message: Message;
|
||||
}
|
||||
|
||||
const MessageBubble: FC<MessageBubbleProps> = memo(({ message }) => {
|
||||
const theme = useTheme();
|
||||
const isUser = message.role === "user";
|
||||
|
||||
return (
|
||||
<div
|
||||
css={{
|
||||
display: "flex",
|
||||
justifyContent: isUser ? "flex-end" : "flex-start",
|
||||
maxWidth: "80%",
|
||||
marginLeft: isUser ? "auto" : 0,
|
||||
animation: `${fadeIn} 0.3s ease-out`,
|
||||
}}
|
||||
>
|
||||
<Paper
|
||||
elevation={isUser ? 1 : 0}
|
||||
variant={isUser ? "elevation" : "outlined"}
|
||||
css={{
|
||||
padding: theme.spacing(1.25, 1.75),
|
||||
fontSize: "0.925rem",
|
||||
lineHeight: 1.5,
|
||||
backgroundColor: isUser
|
||||
? theme.palette.grey[900]
|
||||
: theme.palette.background.paper,
|
||||
borderColor: !isUser ? theme.palette.divider : undefined,
|
||||
color: isUser ? theme.palette.grey[50] : theme.palette.text.primary,
|
||||
borderRadius: "16px",
|
||||
borderBottomRightRadius: isUser ? "4px" : "16px",
|
||||
borderBottomLeftRadius: isUser ? "16px" : "4px",
|
||||
width: "auto",
|
||||
maxWidth: "100%",
|
||||
"& img": {
|
||||
maxWidth: "100%",
|
||||
maxHeight: "400px",
|
||||
height: "auto",
|
||||
borderRadius: "8px",
|
||||
marginTop: theme.spacing(1),
|
||||
marginBottom: theme.spacing(1),
|
||||
},
|
||||
"& p": {
|
||||
margin: theme.spacing(1, 0),
|
||||
"&:first-of-type": {
|
||||
marginTop: 0,
|
||||
},
|
||||
"&:last-of-type": {
|
||||
marginBottom: 0,
|
||||
},
|
||||
},
|
||||
"& ul, & ol": {
|
||||
margin: theme.spacing(1.5, 0),
|
||||
paddingLeft: theme.spacing(3),
|
||||
},
|
||||
"& li": {
|
||||
margin: theme.spacing(0.5, 0),
|
||||
},
|
||||
"& code:not(pre > code)": {
|
||||
backgroundColor: isUser
|
||||
? theme.palette.grey[700]
|
||||
: theme.palette.action.hover,
|
||||
color: isUser ? theme.palette.grey[50] : theme.palette.text.primary,
|
||||
padding: theme.spacing(0.25, 0.75),
|
||||
borderRadius: "4px",
|
||||
fontSize: "0.875em",
|
||||
fontFamily: "monospace",
|
||||
},
|
||||
"& pre": {
|
||||
backgroundColor: isUser
|
||||
? theme.palette.common.black
|
||||
: theme.palette.grey[100],
|
||||
color: isUser
|
||||
? theme.palette.grey[100]
|
||||
: theme.palette.text.primary,
|
||||
padding: theme.spacing(1.5),
|
||||
borderRadius: "8px",
|
||||
overflowX: "auto",
|
||||
margin: theme.spacing(1.5, 0),
|
||||
width: "100%",
|
||||
"& code": {
|
||||
backgroundColor: "transparent",
|
||||
padding: 0,
|
||||
fontSize: "0.875em",
|
||||
fontFamily: "monospace",
|
||||
color: "inherit",
|
||||
},
|
||||
},
|
||||
"& a": {
|
||||
color: isUser
|
||||
? theme.palette.grey[100]
|
||||
: theme.palette.primary.main,
|
||||
textDecoration: "underline",
|
||||
fontWeight: 500,
|
||||
"&:hover": {
|
||||
textDecoration: "none",
|
||||
color: isUser
|
||||
? theme.palette.grey[300]
|
||||
: theme.palette.primary.dark,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
{message.role === "assistant" && message.parts ? (
|
||||
<div>
|
||||
{message.parts.map((part) => {
|
||||
switch (part.type) {
|
||||
case "text":
|
||||
return (
|
||||
<ReactMarkdown
|
||||
key={message.id}
|
||||
remarkPlugins={[remarkGfm]}
|
||||
rehypePlugins={[rehypeRaw]}
|
||||
css={{
|
||||
"& pre": {
|
||||
backgroundColor: theme.palette.background.default,
|
||||
},
|
||||
}}
|
||||
>
|
||||
{part.text}
|
||||
</ReactMarkdown>
|
||||
);
|
||||
case "tool-invocation":
|
||||
return (
|
||||
<div key={message.id}>
|
||||
<ChatToolInvocation
|
||||
toolInvocation={
|
||||
part.toolInvocation as ChatToolInvocation
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
case "reasoning":
|
||||
return (
|
||||
<div key={message.id}>
|
||||
{renderReasoning(part.reasoning, theme)}
|
||||
</div>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[remarkGfm]}
|
||||
rehypePlugins={[rehypeRaw]}
|
||||
>
|
||||
{message.content}
|
||||
</ReactMarkdown>
|
||||
)}
|
||||
</Paper>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
interface ChatViewProps {
|
||||
messages: Message[];
|
||||
input: string;
|
||||
handleInputChange: React.ChangeEventHandler<
|
||||
HTMLInputElement | HTMLTextAreaElement
|
||||
>;
|
||||
handleSubmit: (e?: React.FormEvent<HTMLFormElement>) => void;
|
||||
isLoading: boolean;
|
||||
chatID: string;
|
||||
}
|
||||
|
||||
const ChatView: FC<ChatViewProps> = ({
|
||||
messages,
|
||||
input,
|
||||
handleInputChange,
|
||||
handleSubmit,
|
||||
isLoading,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||
const inputRef = useRef<HTMLTextAreaElement>(null);
|
||||
const chatContext = useChatContext();
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
messagesEndRef.current?.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "end",
|
||||
});
|
||||
}, 50);
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
inputRef.current?.focus();
|
||||
}, []);
|
||||
|
||||
const handleKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
|
||||
if (event.key === "Enter" && !event.shiftKey) {
|
||||
event.preventDefault();
|
||||
handleSubmit();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
css={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
height: "100%",
|
||||
backgroundColor: theme.palette.background.default,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
css={{
|
||||
flexGrow: 1,
|
||||
overflowY: "auto",
|
||||
padding: theme.spacing(3),
|
||||
}}
|
||||
>
|
||||
<div
|
||||
css={{
|
||||
maxWidth: "900px",
|
||||
width: "100%",
|
||||
margin: "0 auto",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: theme.spacing(3),
|
||||
}}
|
||||
>
|
||||
{messages.map((message) => (
|
||||
<MessageBubble key={`message-${message.id}`} message={message} />
|
||||
))}
|
||||
<div ref={messagesEndRef} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
css={{
|
||||
width: "100%",
|
||||
maxWidth: "900px",
|
||||
margin: "0 auto",
|
||||
padding: theme.spacing(2, 3, 2, 3),
|
||||
backgroundColor: theme.palette.background.default,
|
||||
borderTop: `1px solid ${theme.palette.divider}`,
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
<Paper
|
||||
component="form"
|
||||
onSubmit={handleSubmit}
|
||||
elevation={0}
|
||||
variant="outlined"
|
||||
css={{
|
||||
padding: theme.spacing(0.5, 0.5, 0.5, 1.5),
|
||||
display: "flex",
|
||||
alignItems: "flex-start",
|
||||
width: "100%",
|
||||
borderRadius: "12px",
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
transition: "border-color 0.2s ease",
|
||||
"&:focus-within": {
|
||||
borderColor: theme.palette.primary.main,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<div
|
||||
css={{
|
||||
marginRight: theme.spacing(1),
|
||||
alignSelf: "flex-end",
|
||||
marginBottom: theme.spacing(0.5),
|
||||
}}
|
||||
>
|
||||
<LanguageModelSelector />
|
||||
</div>
|
||||
<TextField
|
||||
inputRef={inputRef}
|
||||
value={input}
|
||||
disabled={isLoading || chatContext.selectedModel === ""}
|
||||
onChange={handleInputChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder="Ask Coder..."
|
||||
fullWidth
|
||||
variant="standard"
|
||||
multiline
|
||||
maxRows={5}
|
||||
InputProps={{ disableUnderline: true }}
|
||||
css={{
|
||||
alignSelf: "center",
|
||||
padding: theme.spacing(0.75, 0),
|
||||
fontSize: "0.9rem",
|
||||
}}
|
||||
autoFocus
|
||||
/>
|
||||
<IconButton
|
||||
type="submit"
|
||||
color="primary"
|
||||
disabled={
|
||||
!input.trim() || isLoading || chatContext.selectedModel === ""
|
||||
}
|
||||
css={{
|
||||
alignSelf: "flex-end",
|
||||
marginBottom: theme.spacing(0.5),
|
||||
transition: "transform 0.2s ease, background-color 0.2s ease",
|
||||
"&:not(:disabled):hover": {
|
||||
transform: "scale(1.1)",
|
||||
backgroundColor: theme.palette.action.hover,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<SendIcon />
|
||||
</IconButton>
|
||||
</Paper>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const ChatMessages: FC = () => {
|
||||
const { chatID } = useParams();
|
||||
if (!chatID) {
|
||||
throw new Error("Chat ID is required in URL path /chat/:chatID");
|
||||
}
|
||||
|
||||
const { state } = useLocation();
|
||||
const transferredState = state as ChatLandingLocationState | undefined;
|
||||
|
||||
const messagesQuery = useQuery<ChatMessage[], Error>(getChatMessages(chatID));
|
||||
|
||||
const chatContext = useChatContext();
|
||||
|
||||
const {
|
||||
messages,
|
||||
input,
|
||||
handleInputChange,
|
||||
handleSubmit: originalHandleSubmit,
|
||||
isLoading,
|
||||
setInput,
|
||||
setMessages,
|
||||
} = useChat({
|
||||
id: chatID,
|
||||
api: `/api/v2/chats/${chatID}/messages`,
|
||||
experimental_prepareRequestBody: (options): CreateChatMessageRequest => {
|
||||
const userMessages = options.messages.filter(
|
||||
(message) => message.role === "user",
|
||||
);
|
||||
const mostRecentUserMessage = userMessages.at(-1);
|
||||
return {
|
||||
model: chatContext.selectedModel,
|
||||
message: mostRecentUserMessage,
|
||||
thinking: false,
|
||||
};
|
||||
},
|
||||
initialInput: transferredState?.message,
|
||||
initialMessages: messagesQuery.data as Message[] | undefined,
|
||||
});
|
||||
|
||||
// Update messages from query data when it loads
|
||||
useEffect(() => {
|
||||
if (messagesQuery.data && messages.length === 0) {
|
||||
setMessages(messagesQuery.data as Message[]);
|
||||
}
|
||||
}, [messagesQuery.data, messages.length, setMessages]);
|
||||
|
||||
const handleSubmitCallback = useCallback(
|
||||
(e?: React.FormEvent<HTMLFormElement>) => {
|
||||
if (e) e.preventDefault();
|
||||
if (!input.trim()) return;
|
||||
originalHandleSubmit();
|
||||
setInput(""); // Clear input after submit
|
||||
},
|
||||
[input, originalHandleSubmit, setInput],
|
||||
);
|
||||
|
||||
// Clear input and potentially submit on initial load with message
|
||||
useEffect(() => {
|
||||
if (transferredState?.message && input === transferredState.message) {
|
||||
// Prevent submitting if messages already exist (e.g., browser back/forward)
|
||||
if (messages.length === (messagesQuery.data?.length ?? 0)) {
|
||||
handleSubmitCallback(); // Use the correct callback name
|
||||
}
|
||||
// Clear the state to prevent re-submission on subsequent renders/navigation
|
||||
window.history.replaceState({}, document.title);
|
||||
}
|
||||
}, [
|
||||
transferredState?.message,
|
||||
input,
|
||||
handleSubmitCallback,
|
||||
messages.length,
|
||||
messagesQuery.data?.length,
|
||||
]); // Use the correct callback name
|
||||
|
||||
useEffect(() => {
|
||||
if (transferredState?.message) {
|
||||
// Logic potentially related to transferredState can go here if needed,
|
||||
}
|
||||
}, [transferredState?.message]);
|
||||
|
||||
if (messagesQuery.error) {
|
||||
return <ErrorAlert error={messagesQuery.error} />;
|
||||
}
|
||||
|
||||
if (messagesQuery.isLoading && messages.length === 0) {
|
||||
return <Loader fullscreen />;
|
||||
}
|
||||
|
||||
return (
|
||||
<ChatView
|
||||
key={chatID}
|
||||
chatID={chatID}
|
||||
messages={messages}
|
||||
input={input}
|
||||
handleInputChange={handleInputChange}
|
||||
handleSubmit={handleSubmitCallback}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
);
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,880 +0,0 @@
|
||||
import type { ToolCall, ToolResult } from "@ai-sdk/provider-utils";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import ArticleIcon from "@mui/icons-material/Article";
|
||||
import BuildIcon from "@mui/icons-material/Build";
|
||||
import CodeIcon from "@mui/icons-material/Code";
|
||||
import FileUploadIcon from "@mui/icons-material/FileUpload";
|
||||
import PersonIcon from "@mui/icons-material/Person";
|
||||
import SettingsIcon from "@mui/icons-material/Settings";
|
||||
import CircularProgress from "@mui/material/CircularProgress";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import type * as TypesGen from "api/typesGenerated";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import {
|
||||
CircleAlertIcon,
|
||||
CircleCheckIcon,
|
||||
InfoIcon,
|
||||
TrashIcon,
|
||||
} from "lucide-react";
|
||||
import type React from "react";
|
||||
import { type FC, memo, useMemo, useState } from "react";
|
||||
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
||||
import { dracula } from "react-syntax-highlighter/dist/cjs/styles/prism";
|
||||
import { vscDarkPlus } from "react-syntax-highlighter/dist/cjs/styles/prism";
|
||||
import { TabLink, Tabs, TabsList } from "../../components/Tabs/Tabs";
|
||||
|
||||
interface ChatToolInvocationProps {
|
||||
toolInvocation: ChatToolInvocation;
|
||||
}
|
||||
|
||||
export const ChatToolInvocation: FC<ChatToolInvocationProps> = ({
|
||||
toolInvocation,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const friendlyName = useMemo(() => {
|
||||
return toolInvocation.toolName
|
||||
.replace("coder_", "")
|
||||
.replace(/_/g, " ")
|
||||
.replace(/\b\w/g, (char) => char.toUpperCase());
|
||||
}, [toolInvocation.toolName]);
|
||||
|
||||
const hasError = useMemo(() => {
|
||||
if (toolInvocation.state !== "result") {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
typeof toolInvocation.result === "object" &&
|
||||
toolInvocation.result !== null &&
|
||||
"error" in toolInvocation.result
|
||||
);
|
||||
}, [toolInvocation]);
|
||||
const statusColor = useMemo(() => {
|
||||
if (toolInvocation.state !== "result") {
|
||||
return theme.palette.info.main;
|
||||
}
|
||||
return hasError ? theme.palette.error.main : theme.palette.success.main;
|
||||
}, [toolInvocation, hasError, theme]);
|
||||
const tooltipContent = useMemo(() => {
|
||||
return (
|
||||
<SyntaxHighlighter
|
||||
language="json"
|
||||
style={dracula}
|
||||
css={{
|
||||
maxHeight: 300,
|
||||
overflow: "auto",
|
||||
fontSize: 14,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
padding: theme.spacing(1),
|
||||
scrollbarWidth: "thin",
|
||||
scrollbarColor: "auto",
|
||||
}}
|
||||
>
|
||||
{JSON.stringify(toolInvocation, null, 2)}
|
||||
</SyntaxHighlighter>
|
||||
);
|
||||
}, [toolInvocation, theme.shape.borderRadius, theme.spacing]);
|
||||
|
||||
return (
|
||||
<div
|
||||
css={{
|
||||
marginTop: theme.spacing(1),
|
||||
marginBottom: theme.spacing(2),
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: theme.spacing(0.75),
|
||||
width: "fit-content",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
css={{ display: "flex", alignItems: "center", gap: theme.spacing(1) }}
|
||||
>
|
||||
{toolInvocation.state !== "result" && (
|
||||
<CircularProgress
|
||||
size={16}
|
||||
css={{
|
||||
color: statusColor,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{toolInvocation.state === "result" ? (
|
||||
hasError ? (
|
||||
<CircleAlertIcon
|
||||
className="size-icon-xs"
|
||||
style={{ color: statusColor }}
|
||||
/>
|
||||
) : (
|
||||
<CircleCheckIcon
|
||||
className="size-icon-xs"
|
||||
style={{ color: statusColor }}
|
||||
/>
|
||||
)
|
||||
) : null}
|
||||
<div
|
||||
css={{
|
||||
fontSize: "0.9rem",
|
||||
fontWeight: 500,
|
||||
color: theme.palette.text.primary,
|
||||
}}
|
||||
>
|
||||
{friendlyName}
|
||||
</div>
|
||||
<Tooltip title={tooltipContent}>
|
||||
<InfoIcon size={12} color={theme.palette.text.disabled} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
{toolInvocation.state === "result" ? (
|
||||
<ChatToolInvocationResultPreview toolInvocation={toolInvocation} />
|
||||
) : (
|
||||
<ChatToolInvocationCallPreview toolInvocation={toolInvocation} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ChatToolInvocationCallPreview: FC<{
|
||||
toolInvocation: Extract<
|
||||
ChatToolInvocation,
|
||||
{ state: "call" | "partial-call" }
|
||||
>;
|
||||
}> = memo(({ toolInvocation }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
let content: React.ReactNode;
|
||||
switch (toolInvocation.toolName) {
|
||||
case "coder_upload_tar_file":
|
||||
content = (
|
||||
<FilePreview
|
||||
files={toolInvocation.args?.files || {}}
|
||||
prefix="Uploading files:"
|
||||
/>
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!content) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <div css={{ paddingLeft: theme.spacing(3) }}>{content}</div>;
|
||||
});
|
||||
|
||||
const ChatToolInvocationResultPreview: FC<{
|
||||
toolInvocation: Extract<ChatToolInvocation, { state: "result" }>;
|
||||
}> = memo(({ toolInvocation }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
if (!toolInvocation.result) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (
|
||||
typeof toolInvocation.result === "object" &&
|
||||
"error" in toolInvocation.result
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let content: React.ReactNode;
|
||||
switch (toolInvocation.toolName) {
|
||||
case "coder_get_workspace":
|
||||
case "coder_create_workspace":
|
||||
content = (
|
||||
<div
|
||||
css={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing(1.5),
|
||||
}}
|
||||
>
|
||||
{toolInvocation.result.template_icon && (
|
||||
<img
|
||||
src={toolInvocation.result.template_icon || "/icon/code.svg"}
|
||||
alt={toolInvocation.result.template_display_name || "Template"}
|
||||
css={{
|
||||
width: 32,
|
||||
height: 32,
|
||||
borderRadius: theme.shape.borderRadius / 2,
|
||||
objectFit: "contain",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<div>
|
||||
<div css={{ fontWeight: 500, lineHeight: 1.4 }}>
|
||||
{toolInvocation.result.name}
|
||||
</div>
|
||||
<div
|
||||
css={{
|
||||
fontSize: "0.875rem",
|
||||
color: theme.palette.text.secondary,
|
||||
lineHeight: 1.4,
|
||||
}}
|
||||
>
|
||||
{toolInvocation.result.template_display_name}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
break;
|
||||
case "coder_list_workspaces":
|
||||
content = (
|
||||
<div
|
||||
css={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: theme.spacing(1.5),
|
||||
}}
|
||||
>
|
||||
{toolInvocation.result.map((workspace) => (
|
||||
<div
|
||||
key={workspace.id}
|
||||
css={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing(1.5),
|
||||
}}
|
||||
>
|
||||
{workspace.template_icon && (
|
||||
<img
|
||||
src={workspace.template_icon || "/icon/code.svg"}
|
||||
alt={workspace.template_display_name || "Template"}
|
||||
css={{
|
||||
width: 32,
|
||||
height: 32,
|
||||
borderRadius: theme.shape.borderRadius / 2,
|
||||
objectFit: "contain",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<div>
|
||||
<div css={{ fontWeight: 500, lineHeight: 1.4 }}>
|
||||
{workspace.name}
|
||||
</div>
|
||||
<div
|
||||
css={{
|
||||
fontSize: "0.875rem",
|
||||
color: theme.palette.text.secondary,
|
||||
lineHeight: 1.4,
|
||||
}}
|
||||
>
|
||||
{workspace.template_display_name}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
break;
|
||||
case "coder_list_templates": {
|
||||
const templates = toolInvocation.result;
|
||||
content = (
|
||||
<div
|
||||
css={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: theme.spacing(1.5),
|
||||
}}
|
||||
>
|
||||
{templates.map((template) => (
|
||||
<div
|
||||
key={template.id}
|
||||
css={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing(1.5),
|
||||
}}
|
||||
>
|
||||
<CodeIcon sx={{ width: 32, height: 32 }} />
|
||||
<div>
|
||||
<div css={{ fontWeight: 500, lineHeight: 1.4 }}>
|
||||
{template.name}
|
||||
</div>
|
||||
<div
|
||||
css={{
|
||||
fontSize: "0.875rem",
|
||||
color: theme.palette.text.secondary,
|
||||
lineHeight: 1.4,
|
||||
whiteSpace: "nowrap",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
maxWidth: 200,
|
||||
}}
|
||||
title={template.description}
|
||||
>
|
||||
{template.description}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{templates.length === 0 && <div>No templates found.</div>}
|
||||
</div>
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "coder_template_version_parameters": {
|
||||
const params = toolInvocation.result;
|
||||
content = (
|
||||
<div
|
||||
css={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing(1),
|
||||
fontSize: "0.875rem",
|
||||
color: theme.palette.text.secondary,
|
||||
}}
|
||||
>
|
||||
<SettingsIcon fontSize="small" />
|
||||
{params.length > 0
|
||||
? `${params.length} parameter(s)`
|
||||
: "No parameters"}
|
||||
</div>
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "coder_get_authenticated_user": {
|
||||
const user = toolInvocation.result;
|
||||
content = (
|
||||
<div
|
||||
css={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing(1.5),
|
||||
}}
|
||||
>
|
||||
<Avatar src={user.avatar_url}>
|
||||
<PersonIcon />
|
||||
</Avatar>
|
||||
<div>
|
||||
<div css={{ fontWeight: 500, lineHeight: 1.4 }}>
|
||||
{user.username}
|
||||
</div>
|
||||
<div
|
||||
css={{
|
||||
fontSize: "0.875rem",
|
||||
color: theme.palette.text.secondary,
|
||||
lineHeight: 1.4,
|
||||
}}
|
||||
>
|
||||
{user.email}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "coder_create_workspace_build": {
|
||||
const build = toolInvocation.result;
|
||||
content = (
|
||||
<div
|
||||
css={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing(1),
|
||||
fontSize: "0.875rem",
|
||||
color: theme.palette.text.secondary,
|
||||
}}
|
||||
>
|
||||
<BuildIcon fontSize="small" />
|
||||
Build #{build.build_number} ({build.transition}) status:{" "}
|
||||
{build.status}
|
||||
</div>
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "coder_create_template_version": {
|
||||
const version = toolInvocation.result;
|
||||
content = (
|
||||
<div
|
||||
css={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing(1),
|
||||
}}
|
||||
>
|
||||
<CodeIcon fontSize="small" />
|
||||
<div>
|
||||
<div css={{ fontWeight: 500, lineHeight: 1.4 }}>{version.name}</div>
|
||||
{version.message && (
|
||||
<div
|
||||
css={{
|
||||
fontSize: "0.875rem",
|
||||
color: theme.palette.text.secondary,
|
||||
lineHeight: 1.4,
|
||||
}}
|
||||
>
|
||||
{version.message}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "coder_get_workspace_agent_logs":
|
||||
case "coder_get_workspace_build_logs":
|
||||
case "coder_get_template_version_logs": {
|
||||
const logs = toolInvocation.result;
|
||||
const totalLines = logs.length;
|
||||
const maxLinesToShow = 5;
|
||||
const lastLogs = logs.slice(-maxLinesToShow);
|
||||
const hiddenLines = totalLines - lastLogs.length;
|
||||
|
||||
const totalLinesText = `${totalLines} log line${totalLines !== 1 ? "s" : ""}`;
|
||||
const hiddenLinesText =
|
||||
hiddenLines > 0
|
||||
? `... hiding ${hiddenLines} more line${hiddenLines !== 1 ? "s" : ""} ...`
|
||||
: null;
|
||||
|
||||
const logsToShow = hiddenLinesText
|
||||
? [hiddenLinesText, ...lastLogs]
|
||||
: lastLogs;
|
||||
|
||||
content = (
|
||||
<div
|
||||
css={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: theme.spacing(0.5),
|
||||
}}
|
||||
>
|
||||
<div
|
||||
css={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing(1),
|
||||
fontSize: "0.875rem",
|
||||
color: theme.palette.text.secondary,
|
||||
}}
|
||||
>
|
||||
<ArticleIcon fontSize="small" />
|
||||
Retrieved {totalLinesText}.
|
||||
</div>
|
||||
{logsToShow.length > 0 && (
|
||||
<SyntaxHighlighter
|
||||
language="log"
|
||||
style={dracula}
|
||||
customStyle={{
|
||||
fontSize: "0.8rem",
|
||||
padding: theme.spacing(1),
|
||||
margin: 0,
|
||||
maxHeight: 150,
|
||||
overflowY: "auto",
|
||||
scrollbarWidth: "thin",
|
||||
scrollbarColor: "auto",
|
||||
}}
|
||||
showLineNumbers={false}
|
||||
lineNumberStyle={{ display: "none" }}
|
||||
>
|
||||
{logsToShow.join("\n")}
|
||||
</SyntaxHighlighter>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "coder_update_template_active_version":
|
||||
content = (
|
||||
<div
|
||||
css={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing(1),
|
||||
fontSize: "0.875rem",
|
||||
color: theme.palette.text.secondary,
|
||||
}}
|
||||
>
|
||||
<SettingsIcon fontSize="small" />
|
||||
{toolInvocation.result}
|
||||
</div>
|
||||
);
|
||||
break;
|
||||
case "coder_upload_tar_file":
|
||||
content = (
|
||||
<FilePreview files={toolInvocation.args.files} prefix={"Uploaded!"} />
|
||||
);
|
||||
break;
|
||||
case "coder_create_template": {
|
||||
const template = toolInvocation.result;
|
||||
content = (
|
||||
<div
|
||||
css={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing(1.5),
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={template.icon || "/icon/code.svg"}
|
||||
alt={template.display_name || "Template"}
|
||||
css={{
|
||||
width: 32,
|
||||
height: 32,
|
||||
borderRadius: theme.shape.borderRadius / 2,
|
||||
objectFit: "contain",
|
||||
}}
|
||||
/>
|
||||
<div>
|
||||
<div css={{ fontWeight: 500, lineHeight: 1.4 }}>
|
||||
{template.name}
|
||||
</div>
|
||||
<div
|
||||
css={{
|
||||
fontSize: "0.875rem",
|
||||
color: theme.palette.text.secondary,
|
||||
lineHeight: 1.4,
|
||||
}}
|
||||
>
|
||||
{template.display_name}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "coder_delete_template":
|
||||
content = (
|
||||
<div
|
||||
css={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing(1),
|
||||
fontSize: "0.875rem",
|
||||
color: theme.palette.text.secondary,
|
||||
}}
|
||||
>
|
||||
<TrashIcon className="size-icon-xs" />
|
||||
{toolInvocation.result}
|
||||
</div>
|
||||
);
|
||||
break;
|
||||
case "coder_get_template_version": {
|
||||
const version = toolInvocation.result;
|
||||
content = (
|
||||
<div
|
||||
css={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing(1),
|
||||
}}
|
||||
>
|
||||
<CodeIcon fontSize="small" />
|
||||
<div>
|
||||
<div css={{ fontWeight: 500, lineHeight: 1.4 }}>{version.name}</div>
|
||||
{version.message && (
|
||||
<div
|
||||
css={{
|
||||
fontSize: "0.875rem",
|
||||
color: theme.palette.text.secondary,
|
||||
lineHeight: 1.4,
|
||||
}}
|
||||
>
|
||||
{version.message}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "coder_download_tar_file": {
|
||||
const files = toolInvocation.result;
|
||||
content = <FilePreview files={files} prefix="Files:" />;
|
||||
break;
|
||||
}
|
||||
// Add default case or handle other tools if necessary
|
||||
}
|
||||
return (
|
||||
<div
|
||||
css={{
|
||||
paddingLeft: theme.spacing(3),
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
// New component to preview files with tabs
|
||||
const FilePreview: FC<{ files: Record<string, string>; prefix?: string }> =
|
||||
memo(({ files, prefix }) => {
|
||||
const theme = useTheme();
|
||||
const [selectedTab, setSelectedTab] = useState(0);
|
||||
const fileEntries = useMemo(() => Object.entries(files), [files]);
|
||||
|
||||
if (fileEntries.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handleTabChange = (index: number) => {
|
||||
setSelectedTab(index);
|
||||
};
|
||||
|
||||
const getLanguage = (filename: string): string => {
|
||||
if (filename.includes("Dockerfile")) {
|
||||
return "dockerfile";
|
||||
}
|
||||
const extension = filename.split(".").pop()?.toLowerCase();
|
||||
switch (extension) {
|
||||
case "tf":
|
||||
return "hcl";
|
||||
case "json":
|
||||
return "json";
|
||||
case "yaml":
|
||||
case "yml":
|
||||
return "yaml";
|
||||
case "js":
|
||||
case "jsx":
|
||||
return "javascript";
|
||||
case "ts":
|
||||
case "tsx":
|
||||
return "typescript";
|
||||
case "py":
|
||||
return "python";
|
||||
case "go":
|
||||
return "go";
|
||||
case "rb":
|
||||
return "ruby";
|
||||
case "java":
|
||||
return "java";
|
||||
case "sh":
|
||||
return "bash";
|
||||
case "md":
|
||||
return "markdown";
|
||||
default:
|
||||
return "plaintext";
|
||||
}
|
||||
};
|
||||
|
||||
// Get filename and content based on the selectedTab index
|
||||
const [selectedFilename, selectedContent] = fileEntries[selectedTab] ?? [
|
||||
"",
|
||||
"",
|
||||
];
|
||||
|
||||
return (
|
||||
<div
|
||||
css={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: theme.spacing(1),
|
||||
width: "100%",
|
||||
maxWidth: 400,
|
||||
}}
|
||||
>
|
||||
{prefix && (
|
||||
<div
|
||||
css={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing(1),
|
||||
fontSize: "0.875rem",
|
||||
color: theme.palette.text.secondary,
|
||||
}}
|
||||
>
|
||||
<FileUploadIcon fontSize="small" />
|
||||
{prefix}
|
||||
</div>
|
||||
)}
|
||||
{/* Use custom Tabs component with active prop */}
|
||||
<Tabs active={selectedFilename} className="flex-shrink-0">
|
||||
<TabsList>
|
||||
{fileEntries.map(([filename], index) => (
|
||||
<TabLink
|
||||
key={filename}
|
||||
value={filename} // This matches the 'active' prop on Tabs
|
||||
to="" // Dummy link, not navigating
|
||||
css={{ whiteSpace: "nowrap" }} // Prevent wrapping
|
||||
onClick={(e) => {
|
||||
e.preventDefault(); // Prevent any potential default link behavior
|
||||
handleTabChange(index);
|
||||
}}
|
||||
>
|
||||
{filename}
|
||||
</TabLink>
|
||||
))}
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
<SyntaxHighlighter
|
||||
language={getLanguage(selectedFilename)}
|
||||
style={vscDarkPlus}
|
||||
customStyle={{
|
||||
fontSize: "0.8rem",
|
||||
padding: theme.spacing(1),
|
||||
margin: 0,
|
||||
maxHeight: 200,
|
||||
overflowY: "auto",
|
||||
scrollbarWidth: "thin",
|
||||
scrollbarColor: "auto",
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
}}
|
||||
showLineNumbers={false}
|
||||
lineNumberStyle={{ display: "none" }}
|
||||
>
|
||||
{selectedContent}
|
||||
</SyntaxHighlighter>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
// TODO: generate these from codersdk/toolsdk.go.
|
||||
export type ChatToolInvocation =
|
||||
| ToolInvocation<
|
||||
"coder_get_workspace",
|
||||
{
|
||||
workspace_id: string;
|
||||
},
|
||||
TypesGen.Workspace
|
||||
>
|
||||
| ToolInvocation<
|
||||
"coder_create_workspace",
|
||||
{
|
||||
user: string;
|
||||
template_version_id: string;
|
||||
name: string;
|
||||
rich_parameters: Record<string, string>;
|
||||
},
|
||||
TypesGen.Workspace
|
||||
>
|
||||
| ToolInvocation<
|
||||
"coder_list_workspaces",
|
||||
{
|
||||
owner: string;
|
||||
},
|
||||
Pick<
|
||||
TypesGen.Workspace,
|
||||
| "id"
|
||||
| "name"
|
||||
| "template_id"
|
||||
| "template_name"
|
||||
| "template_display_name"
|
||||
| "template_icon"
|
||||
| "template_active_version_id"
|
||||
| "outdated"
|
||||
>[]
|
||||
>
|
||||
| ToolInvocation<
|
||||
"coder_list_templates",
|
||||
Record<string, never>,
|
||||
Pick<
|
||||
TypesGen.Template,
|
||||
| "id"
|
||||
| "name"
|
||||
| "description"
|
||||
| "active_version_id"
|
||||
| "active_user_count"
|
||||
>[]
|
||||
>
|
||||
| ToolInvocation<
|
||||
"coder_template_version_parameters",
|
||||
{
|
||||
template_version_id: string;
|
||||
},
|
||||
TypesGen.TemplateVersionParameter[]
|
||||
>
|
||||
| ToolInvocation<
|
||||
"coder_get_authenticated_user",
|
||||
Record<string, never>,
|
||||
TypesGen.User
|
||||
>
|
||||
| ToolInvocation<
|
||||
"coder_create_workspace_build",
|
||||
{
|
||||
workspace_id: string;
|
||||
template_version_id?: string;
|
||||
transition: "start" | "stop" | "delete";
|
||||
},
|
||||
TypesGen.WorkspaceBuild
|
||||
>
|
||||
| ToolInvocation<
|
||||
"coder_create_template_version",
|
||||
{
|
||||
template_id?: string;
|
||||
file_id: string;
|
||||
},
|
||||
TypesGen.TemplateVersion
|
||||
>
|
||||
| ToolInvocation<
|
||||
"coder_get_workspace_agent_logs",
|
||||
{
|
||||
workspace_agent_id: string;
|
||||
},
|
||||
string[]
|
||||
>
|
||||
| ToolInvocation<
|
||||
"coder_get_workspace_build_logs",
|
||||
{
|
||||
workspace_build_id: string;
|
||||
},
|
||||
string[]
|
||||
>
|
||||
| ToolInvocation<
|
||||
"coder_get_template_version_logs",
|
||||
{
|
||||
template_version_id: string;
|
||||
},
|
||||
string[]
|
||||
>
|
||||
| ToolInvocation<
|
||||
"coder_get_template_version",
|
||||
{
|
||||
template_version_id: string;
|
||||
},
|
||||
TypesGen.TemplateVersion
|
||||
>
|
||||
| ToolInvocation<
|
||||
"coder_download_tar_file",
|
||||
{
|
||||
file_id: string;
|
||||
},
|
||||
Record<string, string>
|
||||
>
|
||||
| ToolInvocation<
|
||||
"coder_update_template_active_version",
|
||||
{
|
||||
template_id: string;
|
||||
template_version_id: string;
|
||||
},
|
||||
string
|
||||
>
|
||||
| ToolInvocation<
|
||||
"coder_upload_tar_file",
|
||||
{
|
||||
files: Record<string, string>;
|
||||
},
|
||||
TypesGen.UploadResponse
|
||||
>
|
||||
| ToolInvocation<
|
||||
"coder_create_template",
|
||||
{
|
||||
name: string;
|
||||
},
|
||||
TypesGen.Template
|
||||
>
|
||||
| ToolInvocation<
|
||||
"coder_delete_template",
|
||||
{
|
||||
template_id: string;
|
||||
},
|
||||
string
|
||||
>;
|
||||
|
||||
type ToolInvocation<N extends string, A, R> =
|
||||
| ({
|
||||
state: "partial-call";
|
||||
step?: number;
|
||||
} & ToolCall<N, A>)
|
||||
| ({
|
||||
state: "call";
|
||||
step?: number;
|
||||
} & ToolCall<N, A>)
|
||||
| ({
|
||||
state: "result";
|
||||
step?: number;
|
||||
} & ToolResult<
|
||||
N,
|
||||
A,
|
||||
| R
|
||||
| {
|
||||
error: string;
|
||||
}
|
||||
>);
|
||||
@@ -1,73 +0,0 @@
|
||||
import { useTheme } from "@emotion/react";
|
||||
import FormControl from "@mui/material/FormControl";
|
||||
import InputLabel from "@mui/material/InputLabel";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import Select from "@mui/material/Select";
|
||||
import { deploymentLanguageModels } from "api/queries/deployment";
|
||||
import type { LanguageModel } from "api/typesGenerated"; // Assuming types live here based on project structure
|
||||
import { Loader } from "components/Loader/Loader";
|
||||
import type { FC } from "react";
|
||||
import { useQuery } from "react-query";
|
||||
import { useChatContext } from "./ChatLayout";
|
||||
|
||||
export const LanguageModelSelector: FC = () => {
|
||||
const theme = useTheme();
|
||||
const { setSelectedModel, modelConfig, selectedModel } = useChatContext();
|
||||
const {
|
||||
data: languageModelConfig,
|
||||
isLoading,
|
||||
error,
|
||||
} = useQuery(deploymentLanguageModels());
|
||||
|
||||
if (isLoading) {
|
||||
return <Loader size="sm" />;
|
||||
}
|
||||
|
||||
if (error || !languageModelConfig) {
|
||||
console.error("Failed to load language models:", error);
|
||||
return (
|
||||
<div css={{ color: theme.palette.error.main }}>Error loading models.</div>
|
||||
);
|
||||
}
|
||||
|
||||
const models = Array.from(languageModelConfig.models).toSorted((a, b) => {
|
||||
// Sort by provider first, then by display name
|
||||
const compareProvider = a.provider.localeCompare(b.provider);
|
||||
if (compareProvider !== 0) {
|
||||
return compareProvider;
|
||||
}
|
||||
return a.display_name.localeCompare(b.display_name);
|
||||
});
|
||||
|
||||
if (models.length === 0) {
|
||||
return (
|
||||
<div css={{ color: theme.palette.text.disabled }}>
|
||||
No language models available.
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<FormControl fullWidth size="small">
|
||||
<InputLabel id="model-select-label">Model</InputLabel>
|
||||
<Select
|
||||
labelId="model-select-label"
|
||||
value={selectedModel}
|
||||
label="Model"
|
||||
onChange={(e) => setSelectedModel(e.target.value)}
|
||||
disabled={isLoading || models.length === 0}
|
||||
>
|
||||
{!selectedModel && (
|
||||
<MenuItem value="" disabled>
|
||||
Select a model...
|
||||
</MenuItem>
|
||||
)}
|
||||
{models.map((model: LanguageModel) => (
|
||||
<MenuItem key={model.id} value={model.id}>
|
||||
{model.display_name} ({model.provider})
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
);
|
||||
};
|
||||
@@ -1,6 +1,4 @@
|
||||
import { GlobalErrorBoundary } from "components/ErrorBoundary/GlobalErrorBoundary";
|
||||
import { ChatLayout } from "pages/ChatPage/ChatLayout";
|
||||
import { ChatMessages } from "pages/ChatPage/ChatMessages";
|
||||
import { TemplateRedirectController } from "pages/TemplatePage/TemplateRedirectController";
|
||||
import { Suspense, lazy } from "react";
|
||||
import {
|
||||
@@ -33,7 +31,6 @@ const NotFoundPage = lazy(() => import("./pages/404Page/404Page"));
|
||||
const DeploymentSettingsLayout = lazy(
|
||||
() => import("./modules/management/DeploymentSettingsLayout"),
|
||||
);
|
||||
const ChatLanding = lazy(() => import("./pages/ChatPage/ChatLanding"));
|
||||
const DeploymentConfigProvider = lazy(
|
||||
() => import("./modules/management/DeploymentConfigProvider"),
|
||||
);
|
||||
@@ -436,11 +433,6 @@ export const router = createBrowserRouter(
|
||||
|
||||
<Route path="/audit" element={<AuditPage />} />
|
||||
|
||||
<Route path="/chat" element={<ChatLayout />}>
|
||||
<Route index element={<ChatLanding />} />
|
||||
<Route path=":chatID" element={<ChatMessages />} />
|
||||
</Route>
|
||||
|
||||
<Route path="/tasks" element={<TasksPage />} />
|
||||
|
||||
<Route path="/organizations" element={<OrganizationSettingsLayout />}>
|
||||
|
||||
Reference in New Issue
Block a user