mirror of
https://github.com/aljazceru/opencode.git
synced 2026-01-06 01:14:52 +01:00
initial tool call stream
This commit is contained in:
@@ -380,6 +380,21 @@ func (a *agent) processEvent(ctx context.Context, sessionID string, assistantMsg
|
||||
case provider.EventContentDelta:
|
||||
assistantMsg.AppendContent(event.Content)
|
||||
return a.messages.Update(ctx, *assistantMsg)
|
||||
case provider.EventToolUseStart:
|
||||
assistantMsg.AddToolCall(*event.ToolCall)
|
||||
return a.messages.Update(ctx, *assistantMsg)
|
||||
// TODO: see how to handle this
|
||||
// case provider.EventToolUseDelta:
|
||||
// tm := time.Unix(assistantMsg.UpdatedAt, 0)
|
||||
// assistantMsg.AppendToolCallInput(event.ToolCall.ID, event.ToolCall.Input)
|
||||
// if time.Since(tm) > 1000*time.Millisecond {
|
||||
// err := a.messages.Update(ctx, *assistantMsg)
|
||||
// assistantMsg.UpdatedAt = time.Now().Unix()
|
||||
// return err
|
||||
// }
|
||||
case provider.EventToolUseStop:
|
||||
assistantMsg.FinishToolCall(event.ToolCall.ID)
|
||||
return a.messages.Update(ctx, *assistantMsg)
|
||||
case provider.EventError:
|
||||
if errors.Is(event.Error, context.Canceled) {
|
||||
logging.InfoPersist(fmt.Sprintf("Event processing canceled for session: %s", sessionID))
|
||||
@@ -456,6 +471,13 @@ func createAgentProvider(agentName config.AgentName) (provider.Provider, error)
|
||||
provider.WithReasoningEffort(agentConfig.ReasoningEffort),
|
||||
),
|
||||
)
|
||||
} else if model.Provider == models.ProviderAnthropic && model.CanReason {
|
||||
opts = append(
|
||||
opts,
|
||||
provider.WithAnthropicOptions(
|
||||
provider.WithAnthropicShouldThinkFn(provider.DefaultShouldThinkFn),
|
||||
),
|
||||
)
|
||||
}
|
||||
agentProvider, err := provider.NewProvider(
|
||||
model.Provider,
|
||||
|
||||
@@ -93,8 +93,7 @@ func (a *anthropicClient) convertMessages(messages []message.Message) (anthropic
|
||||
}
|
||||
|
||||
if len(blocks) == 0 {
|
||||
logging.Warn("There is a message without content, investigate")
|
||||
// This should never happend but we log this because we might have a bug in our cleanup method
|
||||
logging.Warn("There is a message without content, investigate, this should not happen")
|
||||
continue
|
||||
}
|
||||
anthropicMessages = append(anthropicMessages, anthropic.NewAssistantMessage(blocks...))
|
||||
@@ -196,8 +195,8 @@ func (a *anthropicClient) send(ctx context.Context, messages []message.Message,
|
||||
preparedMessages := a.preparedMessages(a.convertMessages(messages), a.convertTools(tools))
|
||||
cfg := config.Get()
|
||||
if cfg.Debug {
|
||||
jsonData, _ := json.Marshal(preparedMessages)
|
||||
logging.Debug("Prepared messages", "messages", string(jsonData))
|
||||
// jsonData, _ := json.Marshal(preparedMessages)
|
||||
// logging.Debug("Prepared messages", "messages", string(jsonData))
|
||||
}
|
||||
attempts := 0
|
||||
for {
|
||||
@@ -243,8 +242,8 @@ func (a *anthropicClient) stream(ctx context.Context, messages []message.Message
|
||||
preparedMessages := a.preparedMessages(a.convertMessages(messages), a.convertTools(tools))
|
||||
cfg := config.Get()
|
||||
if cfg.Debug {
|
||||
jsonData, _ := json.Marshal(preparedMessages)
|
||||
logging.Debug("Prepared messages", "messages", string(jsonData))
|
||||
// jsonData, _ := json.Marshal(preparedMessages)
|
||||
// logging.Debug("Prepared messages", "messages", string(jsonData))
|
||||
}
|
||||
attempts := 0
|
||||
eventChan := make(chan ProviderEvent)
|
||||
@@ -257,6 +256,7 @@ func (a *anthropicClient) stream(ctx context.Context, messages []message.Message
|
||||
)
|
||||
accumulatedMessage := anthropic.Message{}
|
||||
|
||||
currentToolCallID := ""
|
||||
for anthropicStream.Next() {
|
||||
event := anthropicStream.Current()
|
||||
err := accumulatedMessage.Accumulate(event)
|
||||
@@ -267,7 +267,19 @@ func (a *anthropicClient) stream(ctx context.Context, messages []message.Message
|
||||
|
||||
switch event := event.AsAny().(type) {
|
||||
case anthropic.ContentBlockStartEvent:
|
||||
eventChan <- ProviderEvent{Type: EventContentStart}
|
||||
if event.ContentBlock.Type == "text" {
|
||||
eventChan <- ProviderEvent{Type: EventContentStart}
|
||||
} else if event.ContentBlock.Type == "tool_use" {
|
||||
currentToolCallID = event.ContentBlock.ID
|
||||
eventChan <- ProviderEvent{
|
||||
Type: EventToolUseStart,
|
||||
ToolCall: &message.ToolCall{
|
||||
ID: event.ContentBlock.ID,
|
||||
Name: event.ContentBlock.Name,
|
||||
Finished: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
case anthropic.ContentBlockDeltaEvent:
|
||||
if event.Delta.Type == "thinking_delta" && event.Delta.Thinking != "" {
|
||||
@@ -280,11 +292,30 @@ func (a *anthropicClient) stream(ctx context.Context, messages []message.Message
|
||||
Type: EventContentDelta,
|
||||
Content: event.Delta.Text,
|
||||
}
|
||||
} else if event.Delta.Type == "input_json_delta" {
|
||||
if currentToolCallID != "" {
|
||||
eventChan <- ProviderEvent{
|
||||
Type: EventToolUseDelta,
|
||||
ToolCall: &message.ToolCall{
|
||||
ID: currentToolCallID,
|
||||
Finished: false,
|
||||
Input: event.Delta.JSON.PartialJSON.Raw(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: check if we can somehow stream tool calls
|
||||
|
||||
case anthropic.ContentBlockStopEvent:
|
||||
eventChan <- ProviderEvent{Type: EventContentStop}
|
||||
if currentToolCallID != "" {
|
||||
eventChan <- ProviderEvent{
|
||||
Type: EventToolUseStop,
|
||||
ToolCall: &message.ToolCall{
|
||||
ID: currentToolCallID,
|
||||
},
|
||||
}
|
||||
currentToolCallID = ""
|
||||
} else {
|
||||
eventChan <- ProviderEvent{Type: EventContentStop}
|
||||
}
|
||||
|
||||
case anthropic.MessageStopEvent:
|
||||
content := ""
|
||||
@@ -378,10 +409,11 @@ func (a *anthropicClient) toolCalls(msg anthropic.Message) []message.ToolCall {
|
||||
switch variant := block.AsAny().(type) {
|
||||
case anthropic.ToolUseBlock:
|
||||
toolCall := message.ToolCall{
|
||||
ID: variant.ID,
|
||||
Name: variant.Name,
|
||||
Input: string(variant.Input),
|
||||
Type: string(variant.Type),
|
||||
ID: variant.ID,
|
||||
Name: variant.Name,
|
||||
Input: string(variant.Input),
|
||||
Type: string(variant.Type),
|
||||
Finished: true,
|
||||
}
|
||||
toolCalls = append(toolCalls, toolCall)
|
||||
}
|
||||
|
||||
@@ -344,10 +344,11 @@ func (o *openaiClient) toolCalls(completion openai.ChatCompletion) []message.Too
|
||||
if len(completion.Choices) > 0 && len(completion.Choices[0].Message.ToolCalls) > 0 {
|
||||
for _, call := range completion.Choices[0].Message.ToolCalls {
|
||||
toolCall := message.ToolCall{
|
||||
ID: call.ID,
|
||||
Name: call.Function.Name,
|
||||
Input: call.Function.Arguments,
|
||||
Type: "function",
|
||||
ID: call.ID,
|
||||
Name: call.Function.Name,
|
||||
Input: call.Function.Arguments,
|
||||
Type: "function",
|
||||
Finished: true,
|
||||
}
|
||||
toolCalls = append(toolCalls, toolCall)
|
||||
}
|
||||
|
||||
@@ -15,6 +15,9 @@ const maxRetries = 8
|
||||
|
||||
const (
|
||||
EventContentStart EventType = "content_start"
|
||||
EventToolUseStart EventType = "tool_use_start"
|
||||
EventToolUseDelta EventType = "tool_use_delta"
|
||||
EventToolUseStop EventType = "tool_use_stop"
|
||||
EventContentDelta EventType = "content_delta"
|
||||
EventThinkingDelta EventType = "thinking_delta"
|
||||
EventContentStop EventType = "content_stop"
|
||||
@@ -43,8 +46,8 @@ type ProviderEvent struct {
|
||||
Content string
|
||||
Thinking string
|
||||
Response *ProviderResponse
|
||||
|
||||
Error error
|
||||
ToolCall *message.ToolCall
|
||||
Error error
|
||||
}
|
||||
type Provider interface {
|
||||
SendMessages(ctx context.Context, messages []message.Message, tools []tools.BaseTool) (*ProviderResponse, error)
|
||||
|
||||
Reference in New Issue
Block a user