package agentserver import ( "strings" "testing" ) const sid = "ses_01a" func TestSSEScannerChunked(t *testing.T) { stream := "retry: 2000\n\n" + ": ping\n\n" + `id: 1` + "\nevent: run.started\ndata: " + `{"seq":1,"type":"run.started","sessionId":"ses_01a","runId":"run_01r","payload":{"provider":"claude","model":"sonnet"}}` + "\n\n" + `id: 2` + "\r\nevent: interaction.required\r\ndata: " + `{"seq":2,"type":"interaction.required","sessionId":"ses_01a","runId":"run_01r","payload":{"interactionId":"itx_01i","kind":"approval"}}` + "\r\n\r\n" + `id: 3` + "\nevent: file.changed\ndata: " + `{"seq":3,"type":"file.changed","sessionId":"ses_01a","runId":"run_01r","payload":{"fileId":"file_01f","path":"outputs/a.docx"}}` + "\n\n" + `id: 4` + "\nevent: message.delta\ndata: " + `{"seq":4,"type":"message.delta","sessionId":"ses_01a","runId":"run_01r","payload":{"text":"hi"}}` + "\n\n" var got []string s := NewSSEScanner(func(data []byte) { for _, r := range ResourcesFromSSEData(data) { got = append(got, r.Id+"@"+r.SessionId) } }) // 按 7 字节切块喂入,覆盖事件跨块的情况 for i := 0; i < len(stream); i += 7 { end := i + 7 if end > len(stream) { end = len(stream) } s.Write([]byte(stream[i:end])) } want := "run_01r@ses_01a,itx_01i@ses_01a,file_01f@ses_01a" if strings.Join(got, ",") != want { t.Fatalf("got %v, want %s", got, want) } } func TestResourcesFromResponse(t *testing.T) { cases := []struct { action Action body string want string }{ {ActionSendMessage, `{"id":"run_01r","sessionId":"ses_01a","status":"queued"}`, "run_01r"}, {ActionUploadFiles, `[{"id":"file_1","sessionId":"ses_01a"},{"id":"file_2","sessionId":"ses_01a"}]`, "file_1,file_2"}, {ActionListFiles, `[{"id":"file_3","sessionId":"ses_01a"}]`, "file_3"}, {ActionEvents, `{"items":[{"seq":1,"type":"run.started","sessionId":"ses_01a","runId":"run_9","payload":{}},` + `{"seq":2,"type":"file.changed","sessionId":"ses_OTHER","runId":"run_9","payload":{"fileId":"file_x"}}],"lastSeq":2}`, "run_9"}, {ActionSessionDetail, `{"id":"ses_01a","activeRunId":"run_7","pendingInteractions":[{"interactionId":"itx_7"}]}`, "run_7,itx_7"}, {ActionSessionDetail, `{"id":"ses_01a","activeRunId":null,"pendingInteractions":[]}`, ""}, {ActionInteractions, `[{"interactionId":"itx_8","kind":"question"}]`, "itx_8"}, {ActionSendMessage, `not json`, ""}, } for _, c := range cases { var ids []string for _, r := range ResourcesFromResponse(c.action, sid, []byte(c.body)) { if r.SessionId != sid { t.Errorf("action %v: 资源 %s 的会话为 %s", c.action, r.Id, r.SessionId) } ids = append(ids, r.Id) } if strings.Join(ids, ",") != c.want { t.Errorf("action %v: got %v, want %s", c.action, ids, c.want) } } } func TestFilterSessionPatch(t *testing.T) { b, title, err := filterSessionPatch([]byte(`{"title":"新标题","model":"opus","permissionMode":"auto","profileId":"prf_x","systemPromptAppend":"x"}`)) if err != nil { t.Fatal(err) } if string(b) != `{"model":"opus","title":"新标题"}` { t.Fatalf("过滤结果 %s", b) } if title == nil || *title != "新标题" { t.Fatalf("title %v", title) } if _, _, err := filterSessionPatch([]byte(`[1]`)); err == nil { t.Fatal("非对象应报错") } }