178 lines
4.7 KiB
Go
178 lines
4.7 KiB
Go
package kimi
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/chenhg5/cc-connect/core"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestNewKimiSession(t *testing.T) {
|
|
ctx := context.Background()
|
|
ks, err := newKimiSession(ctx, "kimi", "/tmp", "kimi-k2", "default", "resume-123", nil, 0)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, ks)
|
|
assert.True(t, ks.Alive())
|
|
assert.Equal(t, "resume-123", ks.CurrentSessionID())
|
|
|
|
err = ks.Close()
|
|
assert.NoError(t, err)
|
|
assert.False(t, ks.Alive())
|
|
}
|
|
|
|
func TestExtractResumeSessionID(t *testing.T) {
|
|
cases := []struct {
|
|
input string
|
|
expected string
|
|
}{
|
|
{"To resume this session: kimi -r e3690555-60eb-4d50-874b-e3647e9cee5b", "e3690555-60eb-4d50-874b-e3647e9cee5b"},
|
|
{"To resume this session: kimi --resume abc-def", ""},
|
|
{"To resume this session: no-id-here", ""},
|
|
{"random text", ""},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
assert.Equal(t, c.expected, extractResumeSessionID(c.input), "input: %s", c.input)
|
|
}
|
|
}
|
|
|
|
func TestHandleAssistantWithText(t *testing.T) {
|
|
ctx := context.Background()
|
|
ks, _ := newKimiSession(ctx, "kimi", "/tmp", "", "default", "", nil, 0)
|
|
defer ks.Close()
|
|
|
|
ks.handleEvent(map[string]any{
|
|
"role": "assistant",
|
|
"content": []any{
|
|
map[string]any{"type": "text", "text": "Hello!"},
|
|
},
|
|
})
|
|
|
|
// pendingMsgs should buffer the text
|
|
assert.Len(t, ks.pendingMsgs, 1)
|
|
assert.Equal(t, "Hello!", ks.pendingMsgs[0])
|
|
}
|
|
|
|
func TestHandleAssistantWithThink(t *testing.T) {
|
|
ctx := context.Background()
|
|
ks, _ := newKimiSession(ctx, "kimi", "/tmp", "", "default", "", nil, 0)
|
|
defer ks.Close()
|
|
|
|
ks.handleEvent(map[string]any{
|
|
"role": "assistant",
|
|
"content": []any{
|
|
map[string]any{"type": "think", "think": "Let me think..."},
|
|
map[string]any{"type": "text", "text": "Done!"},
|
|
},
|
|
})
|
|
|
|
events := drainEvents(ks.events, 2)
|
|
require.Len(t, events, 1)
|
|
assert.Equal(t, core.EventThinking, events[0].Type)
|
|
assert.Equal(t, "Let me think...", events[0].Content)
|
|
assert.Equal(t, "Done!", ks.pendingMsgs[0])
|
|
}
|
|
|
|
func TestHandleAssistantWithToolCalls(t *testing.T) {
|
|
ctx := context.Background()
|
|
ks, _ := newKimiSession(ctx, "kimi", "/tmp", "", "default", "", nil, 0)
|
|
defer ks.Close()
|
|
|
|
ks.handleEvent(map[string]any{
|
|
"role": "assistant",
|
|
"content": []any{
|
|
map[string]any{"type": "text", "text": "I will run a command"},
|
|
},
|
|
"tool_calls": []any{
|
|
map[string]any{
|
|
"id": "tool_abc",
|
|
"function": map[string]any{
|
|
"name": "Shell",
|
|
"arguments": `{"command":"echo hello"}`,
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
events := drainEvents(ks.events, 3)
|
|
require.Len(t, events, 2)
|
|
assert.Equal(t, core.EventThinking, events[0].Type)
|
|
assert.Equal(t, "I will run a command", events[0].Content)
|
|
assert.Equal(t, core.EventToolUse, events[1].Type)
|
|
assert.Equal(t, "Shell", events[1].ToolName)
|
|
assert.Equal(t, `{"command":"echo hello"}`, events[1].ToolInput)
|
|
assert.Equal(t, "tool_abc", events[1].RequestID)
|
|
}
|
|
|
|
func TestHandleTool(t *testing.T) {
|
|
ctx := context.Background()
|
|
ks, _ := newKimiSession(ctx, "kimi", "/tmp", "", "default", "", nil, 0)
|
|
defer ks.Close()
|
|
|
|
ks.handleEvent(map[string]any{
|
|
"role": "tool",
|
|
"tool_call_id": "tool_abc",
|
|
"content": []any{
|
|
map[string]any{"type": "text", "text": "hello\n"},
|
|
},
|
|
})
|
|
|
|
events := drainEvents(ks.events, 1)
|
|
require.Len(t, events, 1)
|
|
assert.Equal(t, core.EventToolResult, events[0].Type)
|
|
assert.Equal(t, "tool_abc", events[0].ToolName)
|
|
assert.Contains(t, events[0].ToolResult, "hello")
|
|
}
|
|
|
|
func TestFlushPendingAsText(t *testing.T) {
|
|
ctx := context.Background()
|
|
ks, _ := newKimiSession(ctx, "kimi", "/tmp", "", "default", "", nil, 0)
|
|
defer ks.Close()
|
|
|
|
ks.pendingMsgs = []string{"Hello", " ", "world"}
|
|
ks.flushPendingAsText()
|
|
|
|
events := drainEvents(ks.events, 1)
|
|
require.Len(t, events, 1)
|
|
assert.Equal(t, core.EventText, events[0].Type)
|
|
assert.Equal(t, "Hello world", events[0].Content)
|
|
assert.Empty(t, ks.pendingMsgs)
|
|
}
|
|
|
|
func TestFlushPendingAsThinking(t *testing.T) {
|
|
ctx := context.Background()
|
|
ks, _ := newKimiSession(ctx, "kimi", "/tmp", "", "default", "", nil, 0)
|
|
defer ks.Close()
|
|
|
|
ks.pendingMsgs = []string{"Thinking..."}
|
|
ks.flushPendingAsThinking()
|
|
|
|
events := drainEvents(ks.events, 1)
|
|
require.Len(t, events, 1)
|
|
assert.Equal(t, core.EventThinking, events[0].Type)
|
|
assert.Equal(t, "Thinking...", events[0].Content)
|
|
}
|
|
|
|
func TestTruncate(t *testing.T) {
|
|
assert.Equal(t, "hello", truncate("hello", 10))
|
|
assert.Equal(t, "hello world", truncate("hello world", 11))
|
|
assert.Equal(t, "hello worl...", truncate("hello world", 10))
|
|
}
|
|
|
|
func drainEvents(ch <-chan core.Event, max int) []core.Event {
|
|
var events []core.Event
|
|
timeout := time.After(500 * time.Millisecond)
|
|
for i := 0; i < max; i++ {
|
|
select {
|
|
case evt := <-ch:
|
|
events = append(events, evt)
|
|
case <-timeout:
|
|
return events
|
|
}
|
|
}
|
|
return events
|
|
}
|