mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
chore: move chatd and related packages to /x/ subpackage (#23445)
- Moves `coderd/chatd/`, `coderd/gitsync/`, `enterprise/coderd/chatd/` under `x/` parent directories to signal instability - Adds `Experimental:` glue code comments in `coderd/coderd.go` > 🤖 This PR was created with the help of Coder Agents, and was reviewed by my human. 🧑💻
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,139 @@
|
||||
package chatprovider_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
fantasyanthropic "charm.land/fantasy/providers/anthropic"
|
||||
fantasyopenai "charm.land/fantasy/providers/openai"
|
||||
fantasyopenrouter "charm.land/fantasy/providers/openrouter"
|
||||
fantasyvercel "charm.land/fantasy/providers/vercel"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/util/ptr"
|
||||
"github.com/coder/coder/v2/coderd/x/chatd/chatprovider"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
)
|
||||
|
||||
func TestReasoningEffortFromChat(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
provider string
|
||||
input *string
|
||||
want *string
|
||||
}{
|
||||
{
|
||||
name: "OpenAICaseInsensitive",
|
||||
provider: "openai",
|
||||
input: ptr.Ref(" HIGH "),
|
||||
want: ptr.Ref(string(fantasyopenai.ReasoningEffortHigh)),
|
||||
},
|
||||
{
|
||||
name: "AnthropicEffort",
|
||||
provider: "anthropic",
|
||||
input: ptr.Ref("max"),
|
||||
want: ptr.Ref(string(fantasyanthropic.EffortMax)),
|
||||
},
|
||||
{
|
||||
name: "OpenRouterEffort",
|
||||
provider: "openrouter",
|
||||
input: ptr.Ref("medium"),
|
||||
want: ptr.Ref(string(fantasyopenrouter.ReasoningEffortMedium)),
|
||||
},
|
||||
{
|
||||
name: "VercelEffort",
|
||||
provider: "vercel",
|
||||
input: ptr.Ref("xhigh"),
|
||||
want: ptr.Ref(string(fantasyvercel.ReasoningEffortXHigh)),
|
||||
},
|
||||
{
|
||||
name: "InvalidEffortReturnsNil",
|
||||
provider: "openai",
|
||||
input: ptr.Ref("unknown"),
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
name: "UnsupportedProviderReturnsNil",
|
||||
provider: "bedrock",
|
||||
input: ptr.Ref("high"),
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
name: "NilInputReturnsNil",
|
||||
provider: "openai",
|
||||
input: nil,
|
||||
want: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
got := chatprovider.ReasoningEffortFromChat(tt.provider, tt.input)
|
||||
require.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeMissingProviderOptions_OpenRouterNested(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
options := &codersdk.ChatModelProviderOptions{
|
||||
OpenRouter: &codersdk.ChatModelOpenRouterProviderOptions{
|
||||
Reasoning: &codersdk.ChatModelReasoningOptions{
|
||||
Enabled: ptr.Ref(true),
|
||||
},
|
||||
Provider: &codersdk.ChatModelOpenRouterProvider{
|
||||
Order: []string{"openai"},
|
||||
},
|
||||
},
|
||||
}
|
||||
defaults := &codersdk.ChatModelProviderOptions{
|
||||
OpenRouter: &codersdk.ChatModelOpenRouterProviderOptions{
|
||||
Reasoning: &codersdk.ChatModelReasoningOptions{
|
||||
Enabled: ptr.Ref(false),
|
||||
Exclude: ptr.Ref(true),
|
||||
MaxTokens: ptr.Ref[int64](123),
|
||||
Effort: ptr.Ref("high"),
|
||||
},
|
||||
IncludeUsage: ptr.Ref(true),
|
||||
Provider: &codersdk.ChatModelOpenRouterProvider{
|
||||
Order: []string{"anthropic"},
|
||||
AllowFallbacks: ptr.Ref(true),
|
||||
RequireParameters: ptr.Ref(false),
|
||||
DataCollection: ptr.Ref("allow"),
|
||||
Only: []string{"openai"},
|
||||
Ignore: []string{"foo"},
|
||||
Quantizations: []string{"int8"},
|
||||
Sort: ptr.Ref("latency"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
chatprovider.MergeMissingProviderOptions(&options, defaults)
|
||||
|
||||
require.NotNil(t, options)
|
||||
require.NotNil(t, options.OpenRouter)
|
||||
require.NotNil(t, options.OpenRouter.Reasoning)
|
||||
require.True(t, *options.OpenRouter.Reasoning.Enabled)
|
||||
require.Equal(t, true, *options.OpenRouter.Reasoning.Exclude)
|
||||
require.EqualValues(t, 123, *options.OpenRouter.Reasoning.MaxTokens)
|
||||
require.Equal(t, "high", *options.OpenRouter.Reasoning.Effort)
|
||||
require.NotNil(t, options.OpenRouter.IncludeUsage)
|
||||
require.True(t, *options.OpenRouter.IncludeUsage)
|
||||
|
||||
require.NotNil(t, options.OpenRouter.Provider)
|
||||
require.Equal(t, []string{"openai"}, options.OpenRouter.Provider.Order)
|
||||
require.NotNil(t, options.OpenRouter.Provider.AllowFallbacks)
|
||||
require.True(t, *options.OpenRouter.Provider.AllowFallbacks)
|
||||
require.NotNil(t, options.OpenRouter.Provider.RequireParameters)
|
||||
require.False(t, *options.OpenRouter.Provider.RequireParameters)
|
||||
require.Equal(t, "allow", *options.OpenRouter.Provider.DataCollection)
|
||||
require.Equal(t, []string{"openai"}, options.OpenRouter.Provider.Only)
|
||||
require.Equal(t, []string{"foo"}, options.OpenRouter.Provider.Ignore)
|
||||
require.Equal(t, []string{"int8"}, options.OpenRouter.Provider.Quantizations)
|
||||
require.Equal(t, "latency", *options.OpenRouter.Provider.Sort)
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package chatprovider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
"github.com/coder/coder/v2/buildinfo"
|
||||
)
|
||||
|
||||
// UserAgent returns the User-Agent string sent on all outgoing LLM
|
||||
// API requests made by Coder's built-in chat (chatd). The format
|
||||
// mirrors conventions used by other coding agents so that LLM
|
||||
// providers can identify traffic originating from Coder.
|
||||
//
|
||||
// Example: coder-agents/v2.21.0 (linux/amd64)
|
||||
func UserAgent() string {
|
||||
return fmt.Sprintf("coder-agents/%s (%s/%s)",
|
||||
buildinfo.Version(), runtime.GOOS, runtime.GOARCH)
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package chatprovider_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"charm.land/fantasy"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/v2/buildinfo"
|
||||
"github.com/coder/coder/v2/coderd/x/chatd/chatprovider"
|
||||
"github.com/coder/coder/v2/coderd/x/chatd/chattest"
|
||||
)
|
||||
|
||||
func TestUserAgent(t *testing.T) {
|
||||
t.Parallel()
|
||||
ua := chatprovider.UserAgent()
|
||||
|
||||
// Must start with "coder-agents/" so LLM providers can
|
||||
// identify traffic from Coder.
|
||||
require.True(t, strings.HasPrefix(ua, "coder-agents/"),
|
||||
"User-Agent should start with 'coder-agents/', got %q", ua)
|
||||
|
||||
// Must contain the build version.
|
||||
assert.Contains(t, ua, buildinfo.Version())
|
||||
|
||||
// Must contain OS/arch.
|
||||
assert.Contains(t, ua, runtime.GOOS+"/"+runtime.GOARCH)
|
||||
}
|
||||
|
||||
func TestModelFromConfig_UserAgent(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var mu sync.Mutex
|
||||
var capturedUA string
|
||||
|
||||
serverURL := chattest.NewOpenAI(t, func(req *chattest.OpenAIRequest) chattest.OpenAIResponse {
|
||||
mu.Lock()
|
||||
capturedUA = req.Header.Get("User-Agent")
|
||||
mu.Unlock()
|
||||
return chattest.OpenAINonStreamingResponse("hello")
|
||||
})
|
||||
|
||||
expectedUA := chatprovider.UserAgent()
|
||||
keys := chatprovider.ProviderAPIKeys{
|
||||
ByProvider: map[string]string{"openai": "test-key"},
|
||||
BaseURLByProvider: map[string]string{"openai": serverURL},
|
||||
}
|
||||
|
||||
model, err := chatprovider.ModelFromConfig("openai", "gpt-4", keys, expectedUA)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Make a real call so Fantasy sends an HTTP request to the
|
||||
// fake server, which captures the User-Agent header.
|
||||
_, err = model.Generate(context.Background(), fantasy.Call{
|
||||
Prompt: []fantasy.Message{
|
||||
{
|
||||
Role: fantasy.MessageRoleUser,
|
||||
Content: []fantasy.MessagePart{
|
||||
fantasy.TextPart{Text: "hello"},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
mu.Lock()
|
||||
got := capturedUA
|
||||
mu.Unlock()
|
||||
|
||||
require.NotEmpty(t, got, "User-Agent header was not sent")
|
||||
require.Equal(t, expectedUA, got,
|
||||
"User-Agent header should match chatprovider.UserAgent()")
|
||||
}
|
||||
Reference in New Issue
Block a user