Ctrl K
ComunidadGuías…

coderd/exp_chats_skill_invocation_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package coderd_test

import (
	"encoding/json"
	"net/http"
	"slices"
	"testing"

	"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/dbauthz"
	"github.com/coder/coder/v2/coderd/database/dbgen"
	"github.com/coder/coder/v2/coderd/x/chatd"
	"github.com/coder/coder/v2/coderd/x/chatd/chatprompt"
	"github.com/coder/coder/v2/codersdk"
	"github.com/coder/coder/v2/testutil"
)

func TestChatSkillInvocation(t *testing.T) {
	t.Parallel()

	ctx := testutil.Context(t, testutil.WaitLong)
	client, db := newChatClientWithDatabase(t)
	firstUser := coderdtest.CreateFirstUser(t, client.Client)
	_ = createChatModel(t, client)
	_, err := client.CreateUserSkill(ctx, codersdk.Me, codersdk.CreateSkillRequest{
		Content: "---\nname: notes\ndescription: Notes\n---\n\nTake notes.\n",
	})
	require.NoError(t, err)

	for _, tt := range []struct {
		name   string
		part   codersdk.ChatInputPart
		detail string
	}{
		{
			name:   "UnknownSource",
			part:   codersdk.ChatInputPart{Type: codersdk.ChatInputPartTypeSkillInvocation, SkillSource: "team", SkillName: "notes"},
			detail: `content[0].skill_source "team" is not supported.`,
		},
		{
			name:   "InvalidName",
			part:   codersdk.ChatInputPart{Type: codersdk.ChatInputPartTypeSkillInvocation, SkillSource: codersdk.ChatSkillSourcePersonal, SkillName: "Bad_Name"},
			detail: "content[0].skill_name must be a kebab-case skill name.",
		},
	} {
		_, err := client.CreateChat(ctx, codersdk.CreateChatRequest{
			OrganizationID: firstUser.OrganizationID,
			Content:        []codersdk.ChatInputPart{tt.part},
		})
		sdkErr := requireSDKError(t, err, http.StatusBadRequest)
		require.Equal(t, "Invalid input part.", sdkErr.Message, tt.name)
		require.Equal(t, tt.detail, sdkErr.Detail, tt.name)
	}

	tooMany := slices.Repeat([]codersdk.ChatInputPart{
		{Type: codersdk.ChatInputPartTypeSkillInvocation, SkillSource: codersdk.ChatSkillSourcePersonal, SkillName: "notes"},
	}, chatd.MaxSkillInvocationsPerMessage+1)
	_, err = client.CreateChat(ctx, codersdk.CreateChatRequest{OrganizationID: firstUser.OrganizationID, Content: tooMany})
	tooManyErr := requireSDKError(t, err, http.StatusBadRequest)
	require.Equal(t, "Too many skill invocations.", tooManyErr.Message)
	require.Equal(t, "content can invoke at most 8 skills.", tooManyErr.Detail)

	// The body is stored with the message but never returned by the API.
	chat, err := client.CreateChat(ctx, codersdk.CreateChatRequest{
		OrganizationID: firstUser.OrganizationID,
		Content: []codersdk.ChatInputPart{
			{Type: codersdk.ChatInputPartTypeSkillInvocation, SkillSource: codersdk.ChatSkillSourcePersonal, SkillName: "notes"},
			{Type: codersdk.ChatInputPartTypeText, Text: "go"},
		},
	})
	require.NoError(t, err)
	messages, err := db.GetChatMessagesByChatID(dbauthz.AsSystemRestricted(ctx), database.GetChatMessagesByChatIDParams{ChatID: chat.ID})
	require.NoError(t, err)
	stored, err := chatprompt.ParseContent(findUserMessage(t, messages))
	require.NoError(t, err)
	require.Equal(t, codersdk.ChatMessagePartTypeSkillInvocation, stored[0].Type)
	require.Equal(t, "Take notes.", stored[0].SkillBody)

	response, err := client.GetChatMessages(ctx, chat.ID, nil)
	require.NoError(t, err)
	var returned []codersdk.ChatMessagePart
	for _, message := range response.Messages {
		for _, part := range message.Content {
			if part.Type == codersdk.ChatMessagePartTypeSkillInvocation {
				returned = append(returned, part)
			}
		}
	}
	require.Equal(t, []codersdk.ChatMessagePart{
		codersdk.ChatMessageSkillInvocation(codersdk.ChatSkillSourcePersonal, "notes"),
	}, returned)

	// A skill the owner cannot load rejects the send and the edit.
	missing := []codersdk.ChatInputPart{
		{Type: codersdk.ChatInputPartTypeSkillInvocation, SkillSource: codersdk.ChatSkillSourcePersonal, SkillName: "missing"},
	}
	_, err = client.CreateChatMessage(ctx, chat.ID, codersdk.CreateChatMessageRequest{Content: missing})
	sdkErr := requireSDKError(t, err, http.StatusBadRequest)
	require.Equal(t, "Skill personal/missing is not available.", sdkErr.Message)
	_, err = client.EditChatMessage(ctx, chat.ID, findUserMessage(t, messages).ID, codersdk.EditChatMessageRequest{Content: missing})
	sdkErr = requireSDKError(t, err, http.StatusBadRequest)
	require.Equal(t, "Skill personal/missing is not available.", sdkErr.Message)

	// A queued-message edit resolves the skill body like a send.
	queuedChat := dbgen.Chat(t, db, database.Chat{
		OrganizationID: firstUser.OrganizationID, OwnerID: firstUser.UserID,
		LastModelConfigID: chat.LastModelConfigID, Status: database.ChatStatusError,
	})
	queued := insertTestChatQueuedMessage(ctx, t, db, queuedChat.ID, queuedTextContent(t, "queued"), chat.LastModelConfigID)
	err = client.EditChatQueuedMessage(ctx, queuedChat.ID, queued.ID, codersdk.EditChatQueuedMessageRequest{Content: missing})
	sdkErr = requireSDKError(t, err, http.StatusBadRequest)
	require.Equal(t, "Skill personal/missing is not available.", sdkErr.Message)
	require.NoError(t, client.EditChatQueuedMessage(ctx, queuedChat.ID, queued.ID, codersdk.EditChatQueuedMessageRequest{
		Content: []codersdk.ChatInputPart{
			{Type: codersdk.ChatInputPartTypeSkillInvocation, SkillSource: codersdk.ChatSkillSourcePersonal, SkillName: "notes"},
		},
	}))
	row, err := db.GetChatQueuedMessageByID(dbauthz.AsSystemRestricted(ctx), database.GetChatQueuedMessageByIDParams{ID: queued.ID, ChatID: queuedChat.ID})
	require.NoError(t, err)
	var queuedParts []codersdk.ChatMessagePart
	require.NoError(t, json.Unmarshal(row.Content, &queuedParts))
	require.Equal(t, "Take notes.", queuedParts[0].SkillBody)
}