From e1167c95372feec6aba8e680732246e63dfbe8b1 Mon Sep 17 00:00:00 2001 From: Hermes Date: Tue, 18 Aug 2026 18:58:23 +0500 Subject: [PATCH] =?UTF-8?q?feat(opencode):=20v1.18=20=E2=80=94=20=D0=BF?= =?UTF-8?q?=D0=B5=D1=80=D0=B5=D0=B9=D1=82=D0=B8=20=D1=81=20POST=20/session?= =?UTF-8?q?/:id/prompt=20=D0=BD=D0=B0=20/session/:id/message?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Промпт отправляется в /api/session/:id/message (v1.18) с новым форматом тела parts:[{type:"text"}], вместо {prompt:{text}}. Причина перехода: /prompt сам подставляет не ту модель, что в конфиге; /message использует модель из конфига. - client.go: Send → POST /message + тело parts; шапка-док v1.18 - тесты: fakeAPIServer + e2eFakeAPI отвечают на POST /message (GET уже был) --- internal/app/e2e_test.go | 4 ++-- internal/opencode/client.go | 11 +++++++---- internal/opencode/client_test.go | 18 ++++++++++-------- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/internal/app/e2e_test.go b/internal/app/e2e_test.go index 24ec2c7..9332144 100644 --- a/internal/app/e2e_test.go +++ b/internal/app/e2e_test.go @@ -44,7 +44,7 @@ var ( } ) -// e2eFakeAPI поднимает фейковый opencode serve HTTP API v1.17 и возвращает URL. +// e2eFakeAPI поднимает фейковый opencode serve HTTP API v1.18 и возвращает URL. // По title сессии (ratatoskr-) определяет агента и возвращает его вердикт // как text-часть единственного assistant-сообщения. func e2eFakeAPI(t *testing.T) string { @@ -66,7 +66,7 @@ func e2eFakeAPI(t *testing.T) string { mu.Unlock() writeJSON(w, map[string]any{"data": map[string]any{"id": id}}) - case r.Method == http.MethodPost && strings.HasSuffix(r.URL.Path, "/prompt"): + case r.Method == http.MethodPost && strings.HasSuffix(r.URL.Path, "/message"): w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte(`{"ok":true}`)) diff --git a/internal/opencode/client.go b/internal/opencode/client.go index 79bb6b4..bc4fc24 100644 --- a/internal/opencode/client.go +++ b/internal/opencode/client.go @@ -13,9 +13,9 @@ import ( // Client — HTTP-взаимодействие с одним opencode serve (режим API). // -// Ходит по HTTP API opencode serve (v1.17+, префикс /api): +// Ходит по HTTP API opencode serve (v1.18+, префикс /api): // - POST /api/session создать сессию → {data:{id}} -// - POST /api/session/:id/prompt отправить промпт {prompt:{text}} +// - POST /api/session/:id/message отправить промпт {parts:[{type:"text"}]} // - POST /api/session/:id/wait дождаться завершения ответа (блок) // - GET /api/session/:id/message?order=desc → {data:[{...}]} история ответов // - POST /api/session/:id/interrupt прервать выполняющийся ответ @@ -127,10 +127,13 @@ func (c *Client) Prompt(ctx context.Context, sessionID, prompt string) error { // Send отправляет промпт в сессию (не ждёт ответа — выполнение идёт в фоне). // Используется Runner'ом для асинхронного ожидания через поллинг сообщений. +// Формат тела — v1.18: parts:[{type:"text"}], а не {prompt:{text}}. func (c *Client) Send(ctx context.Context, sessionID, prompt string) error { - payload := map[string]any{"prompt": map[string]string{"text": prompt}} + payload := map[string]any{ + "parts": []map[string]string{{"type": "text", "text": prompt}}, + } b, _ := json.Marshal(payload) - _, err := c.do(ctx, http.MethodPost, "/api/session/"+sessionID+"/prompt", "prompt", b) + _, err := c.do(ctx, http.MethodPost, "/api/session/"+sessionID+"/message", "prompt", b) return err } diff --git a/internal/opencode/client_test.go b/internal/opencode/client_test.go index 32f87c8..710ed7b 100644 --- a/internal/opencode/client_test.go +++ b/internal/opencode/client_test.go @@ -9,7 +9,7 @@ import ( "testing" ) -// fakeAPIServer — минимальный фейк opencode serve HTTP API v1.17. +// fakeAPIServer — минимальный фейк opencode serve HTTP API v1.18. type fakeAPIServer struct { messages []sessionMessage failCreate bool @@ -25,21 +25,23 @@ func (f *fakeAPIServer) handler() http.Handler { } writeJSON(w, map[string]any{"data": map[string]any{"id": "sess-fake"}}) }) - mux.HandleFunc("/api/session/{id}/prompt", func(w http.ResponseWriter, _ *http.Request) { - writeJSON(w, map[string]any{"data": map[string]any{}}) - }) mux.HandleFunc("/api/session/{id}/wait", func(w http.ResponseWriter, _ *http.Request) { writeJSON(w, map[string]any{}) }) mux.HandleFunc("/api/session/{id}/interrupt", func(w http.ResponseWriter, _ *http.Request) { writeJSON(w, map[string]any{}) }) - mux.HandleFunc("/api/session/{id}/message", func(w http.ResponseWriter, _ *http.Request) { - if len(f.messages) == 0 { - writeJSON(w, map[string]any{"data": []sessionMessage{}}) + mux.HandleFunc("/api/session/{id}/message", func(w http.ResponseWriter, r *http.Request) { + // GET — чтение сообщений; POST — отправка промпта (v1.18). + if r.Method == http.MethodGet { + if len(f.messages) == 0 { + writeJSON(w, map[string]any{"data": []sessionMessage{}}) + return + } + writeJSON(w, map[string]any{"data": f.messages}) return } - writeJSON(w, map[string]any{"data": f.messages}) + writeJSON(w, map[string]any{"data": map[string]any{}}) }) return mux }