mirror of
https://github.com/coder/coder.git
synced 2026-09-23 05:43:53 +08:00
Removes OpenAI Responses "chain mode" from chatd. Closes CODAGT-445.
- Deletes `chatopenai/responses.go` (chain detection, activation, prompt filtering, response ID extraction) and its tests.
- Deletes the `ChainBroken` classification in `chaterror` and the chatloop retry bookkeeping that disabled chain mode mid-generation.
- Drops the `chain_broken` label from the `coderd_chatd_stream_retries_total` metric.
- Stops reading and writing `chat_messages.provider_response_id`
- Deletes the dead `ClearChatMessageProviderResponseIDsByChatID` query. Dropping the column is a follow-up migration.
- Deletes three chatloop hooks no caller sets (`ReloadMessages`, `DisableChainMode`, `PrepareMessages`), the dead `const AgentChatContextSentinelPath`, and stale chain-mode comments.
🤖 Generated by Coder Agents on behalf of @johnstcn.
124 lines
3.6 KiB
Go
124 lines
3.6 KiB
Go
package chatadvisor
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"time"
|
|
|
|
"charm.land/fantasy"
|
|
"golang.org/x/xerrors"
|
|
|
|
stringutil "github.com/coder/coder/v2/coderd/util/strings"
|
|
"github.com/coder/coder/v2/coderd/x/chatd/chatloop"
|
|
"github.com/coder/coder/v2/coderd/x/chatd/chatretry"
|
|
"github.com/coder/coder/v2/codersdk"
|
|
)
|
|
|
|
// RunAdvisorOptions carries optional streaming callbacks for a
|
|
// single RunAdvisor invocation.
|
|
type RunAdvisorOptions struct {
|
|
OnAdviceDelta func(delta string)
|
|
OnAdviceReset func()
|
|
}
|
|
|
|
// RunAdvisor executes a single, tool-less nested advisor call.
|
|
func (rt *Runtime) RunAdvisor(
|
|
ctx context.Context,
|
|
question string,
|
|
conversationSnapshot []fantasy.Message,
|
|
opts *RunAdvisorOptions,
|
|
) (AdvisorResult, error) {
|
|
// Model, MaxUsesPerRun, and MaxOutputTokens are validated by NewRuntime.
|
|
// Runtime fields are unexported so callers cannot bypass that.
|
|
question = strings.TrimSpace(question)
|
|
if question == "" {
|
|
return AdvisorResult{}, xerrors.New("advisor question is required")
|
|
}
|
|
question = stringutil.Truncate(question, advisorQuestionMaxRunes)
|
|
|
|
if !rt.tryAcquire() {
|
|
return AdvisorResult{
|
|
Type: ResultTypeLimitReached,
|
|
RemainingUses: 0,
|
|
}, nil
|
|
}
|
|
|
|
// resetProviderOptionsForNestedCall mutates its argument; give it a
|
|
// clone so the Runtime's stored options stay unchanged across calls.
|
|
nestedProviderOptions := cloneProviderOptions(rt.cfg.ProviderOptions)
|
|
resetProviderOptionsForNestedCall(nestedProviderOptions)
|
|
|
|
assistantOpts := chatloop.GenerateAssistantOptions{
|
|
Model: rt.cfg.Model,
|
|
Messages: BuildAdvisorMessages(question, conversationSnapshot),
|
|
ModelConfig: rt.cfg.ModelConfig,
|
|
ProviderOptions: nestedProviderOptions,
|
|
}
|
|
if opts != nil && opts.OnAdviceDelta != nil {
|
|
assistantOpts.PublishMessagePart = func(role codersdk.ChatMessageRole, part codersdk.ChatMessagePart) {
|
|
if role != codersdk.ChatMessageRoleAssistant ||
|
|
part.Type != codersdk.ChatMessagePartTypeText ||
|
|
part.Text == "" {
|
|
return
|
|
}
|
|
opts.OnAdviceDelta(part.Text)
|
|
}
|
|
}
|
|
|
|
var outcome chatloop.AssistantOutcome
|
|
if err := chatretry.Retry(ctx, func(retryCtx context.Context) error {
|
|
var err error
|
|
outcome, err = chatloop.GenerateAssistant(retryCtx, assistantOpts)
|
|
return err
|
|
}, func(int, error, chatretry.ClassifiedError, time.Duration) {
|
|
if opts != nil && opts.OnAdviceReset != nil {
|
|
opts.OnAdviceReset()
|
|
}
|
|
}); err != nil {
|
|
// Refund the use so a transient provider failure does not
|
|
// permanently exhaust the per-run advisor budget.
|
|
rt.release()
|
|
return AdvisorResult{
|
|
Type: ResultTypeError,
|
|
Error: err.Error(),
|
|
RemainingUses: rt.RemainingUses(),
|
|
}, nil
|
|
}
|
|
|
|
advice := extractAdvisorText(outcome.Step)
|
|
if advice == "" {
|
|
// Refund: the run did not produce advice, so the contract
|
|
// "increments on every successful advisor call" treats this
|
|
// as not consuming a use.
|
|
rt.release()
|
|
return AdvisorResult{
|
|
Type: ResultTypeError,
|
|
Error: "advisor produced no text output",
|
|
RemainingUses: rt.RemainingUses(),
|
|
}, nil
|
|
}
|
|
|
|
return AdvisorResult{
|
|
Type: ResultTypeAdvice,
|
|
Advice: advice,
|
|
AdvisorModel: rt.cfg.Model.Provider() + "/" + rt.cfg.Model.Model(),
|
|
RemainingUses: rt.RemainingUses(),
|
|
}, nil
|
|
}
|
|
|
|
func extractAdvisorText(step chatloop.PersistedStep) string {
|
|
parts := make([]string, 0, len(step.Content))
|
|
for _, content := range step.Content {
|
|
text, ok := fantasy.AsContentType[fantasy.TextContent](content)
|
|
if !ok {
|
|
continue
|
|
}
|
|
trimmed := strings.TrimSpace(text.Text)
|
|
if trimmed == "" {
|
|
continue
|
|
}
|
|
parts = append(parts, trimmed)
|
|
}
|
|
return strings.TrimSpace(strings.Join(parts, "\n\n"))
|
|
}
|