feat(opencode): v1.18 — перейти с POST /session/:id/prompt на /session/:id/message
All checks were successful
CI / test (push) Successful in 40s
CI / build-and-package (amd64, linux) (push) Successful in 36s
CI / build-and-package (amd64, windows) (push) Successful in 36s

Промпт отправляется в /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 уже был)
This commit is contained in:
Hermes
2026-08-18 18:58:23 +05:00
parent ad9c2dd522
commit e1167c9537
3 changed files with 19 additions and 14 deletions

View File

@@ -44,7 +44,7 @@ var (
} }
) )
// e2eFakeAPI поднимает фейковый opencode serve HTTP API v1.17 и возвращает URL. // e2eFakeAPI поднимает фейковый opencode serve HTTP API v1.18 и возвращает URL.
// По title сессии (ratatoskr-<agent>) определяет агента и возвращает его вердикт // По title сессии (ratatoskr-<agent>) определяет агента и возвращает его вердикт
// как text-часть единственного assistant-сообщения. // как text-часть единственного assistant-сообщения.
func e2eFakeAPI(t *testing.T) string { func e2eFakeAPI(t *testing.T) string {
@@ -66,7 +66,7 @@ func e2eFakeAPI(t *testing.T) string {
mu.Unlock() mu.Unlock()
writeJSON(w, map[string]any{"data": map[string]any{"id": id}}) 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.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte(`{"ok":true}`)) _, _ = w.Write([]byte(`{"ok":true}`))

View File

@@ -13,9 +13,9 @@ import (
// Client — HTTP-взаимодействие с одним opencode serve (режим API). // 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 создать сессию → {data:{id}}
// - POST /api/session/:id/prompt отправить промпт {prompt:{text}} // - POST /api/session/:id/message отправить промпт {parts:[{type:"text"}]}
// - POST /api/session/:id/wait дождаться завершения ответа (блок) // - POST /api/session/:id/wait дождаться завершения ответа (блок)
// - GET /api/session/:id/message?order=desc → {data:[{...}]} история ответов // - GET /api/session/:id/message?order=desc → {data:[{...}]} история ответов
// - POST /api/session/:id/interrupt прервать выполняющийся ответ // - POST /api/session/:id/interrupt прервать выполняющийся ответ
@@ -127,10 +127,13 @@ func (c *Client) Prompt(ctx context.Context, sessionID, prompt string) error {
// Send отправляет промпт в сессию (не ждёт ответа — выполнение идёт в фоне). // Send отправляет промпт в сессию (не ждёт ответа — выполнение идёт в фоне).
// Используется Runner'ом для асинхронного ожидания через поллинг сообщений. // Используется Runner'ом для асинхронного ожидания через поллинг сообщений.
// Формат тела — v1.18: parts:[{type:"text"}], а не {prompt:{text}}.
func (c *Client) Send(ctx context.Context, sessionID, prompt string) error { 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) 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 return err
} }

View File

@@ -9,7 +9,7 @@ import (
"testing" "testing"
) )
// fakeAPIServer — минимальный фейк opencode serve HTTP API v1.17. // fakeAPIServer — минимальный фейк opencode serve HTTP API v1.18.
type fakeAPIServer struct { type fakeAPIServer struct {
messages []sessionMessage messages []sessionMessage
failCreate bool failCreate bool
@@ -25,21 +25,23 @@ func (f *fakeAPIServer) handler() http.Handler {
} }
writeJSON(w, map[string]any{"data": map[string]any{"id": "sess-fake"}}) 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) { mux.HandleFunc("/api/session/{id}/wait", func(w http.ResponseWriter, _ *http.Request) {
writeJSON(w, map[string]any{}) writeJSON(w, map[string]any{})
}) })
mux.HandleFunc("/api/session/{id}/interrupt", func(w http.ResponseWriter, _ *http.Request) { mux.HandleFunc("/api/session/{id}/interrupt", func(w http.ResponseWriter, _ *http.Request) {
writeJSON(w, map[string]any{}) writeJSON(w, map[string]any{})
}) })
mux.HandleFunc("/api/session/{id}/message", func(w http.ResponseWriter, _ *http.Request) { mux.HandleFunc("/api/session/{id}/message", func(w http.ResponseWriter, r *http.Request) {
if len(f.messages) == 0 { // GET — чтение сообщений; POST — отправка промпта (v1.18).
writeJSON(w, map[string]any{"data": []sessionMessage{}}) 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 return
} }
writeJSON(w, map[string]any{"data": f.messages}) writeJSON(w, map[string]any{"data": map[string]any{}})
}) })
return mux return mux
} }