chore: remove chats experiment (#18535)

This commit is contained in:
Danny Kopping
2025-06-25 13:03:32 +00:00
committed by GitHub
parent 9fde8353ad
commit 688d2ee3eb
56 changed files with 16 additions and 7505 deletions
-167
View File
@@ -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
}
+3 -589
View File
@@ -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": {
+3 -549
View File
@@ -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
View File
@@ -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
}
}
-125
View File
@@ -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")
})
}
-18
View File
@@ -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,
+2 -14
View File
@@ -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{
-42
View File
@@ -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
-74
View File
@@ -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{})
-24
View File
@@ -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()),
-137
View File
@@ -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 {
-49
View File
@@ -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)
-103
View File
@@ -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()
-40
View File
@@ -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;
-5
View File
@@ -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())
}
-17
View File
@@ -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"`
-7
View File
@@ -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)
-201
View File
@@ -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
-36
View File
@@ -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;
-2
View File
@@ -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);
-25
View File
@@ -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,
})
}
-59
View File
@@ -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))
})
}
}
-150
View File
@@ -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")
})
}
-11
View File
@@ -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,
-8
View File
@@ -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"),
-2
View File
@@ -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()
-31
View File
@@ -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.