mirror of
https://github.com/logseq/logseq.git
synced 2026-05-24 04:34:14 +00:00
fix: sprites session
This commit is contained in:
@@ -150,6 +150,13 @@
|
||||
(fn []
|
||||
(set! (.-runtime-events-stream self) nil)))))))
|
||||
|
||||
(defn- start-runtime-events-stream-background! [^js self session-id runtime]
|
||||
;; Fire-and-forget start. Returning nil avoids p/let awaiting the stream task.
|
||||
(when (and (map? runtime)
|
||||
(string? (:session-id runtime)))
|
||||
(start-runtime-events-stream! self session-id runtime)
|
||||
nil))
|
||||
|
||||
(defn- <transition! [^js self to-status event-type data]
|
||||
(p/let [session (<get-session self)]
|
||||
(cond
|
||||
@@ -190,9 +197,9 @@
|
||||
:sprite-name (:sprite-name runtime)}
|
||||
:ts (common/now-ms)})]
|
||||
(p/let [_ (<put-session! self session)
|
||||
_ (<put-events! self events)
|
||||
_ (when-not (terminal-status? (:status session))
|
||||
(start-runtime-events-stream! self (:id session) runtime))]
|
||||
_ (<put-events! self events)]
|
||||
(when-not (terminal-status? (:status session))
|
||||
(start-runtime-events-stream-background! self (:id session) runtime))
|
||||
runtime))))))
|
||||
|
||||
(defn- handle-init [^js self request]
|
||||
@@ -205,7 +212,7 @@
|
||||
session (<get-session self)]
|
||||
(when (and (map? (:runtime session))
|
||||
(not (terminal-status? (:status session))))
|
||||
(start-runtime-events-stream! self session-id (:runtime session)))
|
||||
(start-runtime-events-stream-background! self session-id (:runtime session)))
|
||||
(http/json-response :sessions/create
|
||||
{:session-id session-id
|
||||
:status (:status session)
|
||||
@@ -301,21 +308,20 @@
|
||||
(p/let [_ (when (and runtime
|
||||
provider
|
||||
(string? (:session-id runtime)))
|
||||
(do
|
||||
(start-runtime-events-stream! self (:id current-session) runtime)
|
||||
(-> (runtime-provider/<send-message! provider
|
||||
runtime
|
||||
{:message message
|
||||
:kind (:kind body)})
|
||||
(.catch (fn [error]
|
||||
(log/error :agent/runtime-message-error
|
||||
{:session-id (:id current-session)
|
||||
:runtime-session-id (:session-id runtime)
|
||||
:error error})
|
||||
(<append-event! self {:type "agent.runtime.error"
|
||||
:data {:session-id (:id current-session)
|
||||
:message (str error)}
|
||||
:ts (common/now-ms)}))))))]
|
||||
(start-runtime-events-stream! self (:id current-session) runtime)
|
||||
(-> (runtime-provider/<send-message! provider
|
||||
runtime
|
||||
{:message message
|
||||
:kind (:kind body)})
|
||||
(.catch (fn [error]
|
||||
(log/error :agent/runtime-message-error
|
||||
{:session-id (:id current-session)
|
||||
:runtime-session-id (:session-id runtime)
|
||||
:error error})
|
||||
(<append-event! self {:type "agent.runtime.error"
|
||||
:data {:session-id (:id current-session)
|
||||
:message (str error)}
|
||||
:ts (common/now-ms)})))))]
|
||||
(http/json-response :sessions/message {:ok true})))))))))))
|
||||
|
||||
(defn- handle-cancel [^js self request]
|
||||
@@ -344,7 +350,7 @@
|
||||
_ (when (and runtime
|
||||
provider
|
||||
(string? (:session-id runtime)))
|
||||
(start-runtime-events-stream! self (:id current-session) runtime))
|
||||
(start-runtime-events-stream-background! self (:id current-session) runtime))
|
||||
_ (when (and runtime
|
||||
provider
|
||||
(string? (:session-id runtime)))
|
||||
|
||||
@@ -99,8 +99,8 @@
|
||||
(defn- ->base64
|
||||
[value]
|
||||
(when (string? value)
|
||||
(let [bytes (.encode (js/TextEncoder.) value)
|
||||
binary (apply str (map char bytes))]
|
||||
(let [payload (.encode (js/TextEncoder.) value)
|
||||
binary (apply str (map char payload))]
|
||||
(js/btoa binary))))
|
||||
|
||||
(defn- curl-auth-arg [token]
|
||||
@@ -183,8 +183,30 @@
|
||||
(let [base (sprites-base-url env)
|
||||
token (sprites-token env)
|
||||
q (build-query (map (fn [c] [:cmd c]) cmd-vec))
|
||||
url (sprites-http-url base (str "/v1/sprites/" sprite-name "/exec?" q))]
|
||||
(fetch-json! "POST" url {:token token})))
|
||||
url (sprites-http-url base (str "/v1/sprites/" sprite-name "/exec?" q))
|
||||
^js headers (js/Headers.)]
|
||||
(.set headers "Accept" "application/json")
|
||||
(when token (token-header! headers token))
|
||||
(p/let [resp (js/fetch url #js {:method "POST" :headers headers})
|
||||
status (.-status resp)
|
||||
text (->promise (.text resp))
|
||||
parsed (parse-json-safe text)]
|
||||
(when-not (<= 200 status 299)
|
||||
(throw (ex-info "sprites exec failed"
|
||||
{:status status
|
||||
:url url
|
||||
:body parsed
|
||||
:raw-body text})))
|
||||
(cond
|
||||
(and (map? parsed) (seq parsed))
|
||||
parsed
|
||||
|
||||
(string/blank? text)
|
||||
{}
|
||||
|
||||
:else
|
||||
{:stdout text
|
||||
:stderr ""}))))
|
||||
|
||||
;; -----------------------
|
||||
;; Worker WS upgrade to Sprites exec
|
||||
@@ -307,17 +329,27 @@
|
||||
" --no-telemetry >/tmp/sandbox-agent.log 2>&1 &"))]
|
||||
(sprites-exec-post! env sprite-name ["bash" "-lc" bootstrap])))
|
||||
|
||||
(declare sprites-exec-output)
|
||||
(defn- <sprite-health! [^js env sprite-name port agent-token retries interval-ms]
|
||||
(if (<= retries 0)
|
||||
(throw (ex-info "sandbox-agent health check timed out in sprite"
|
||||
{:sprite sprite-name :port port}))
|
||||
(let [script (str "curl -fsS "
|
||||
(let [script (str "if curl -fsS "
|
||||
(curl-auth-arg agent-token)
|
||||
" "
|
||||
(sprite-local-url port "/v1/health")
|
||||
" >/dev/null")]
|
||||
" >/dev/null; then echo __HEALTH_OK__; else echo __HEALTH_FAIL__; fi")]
|
||||
(-> (sprites-exec-post! env sprite-name ["bash" "-lc" script])
|
||||
(p/then (fn [_] true))
|
||||
(p/then (fn [result]
|
||||
(let [{:keys [stdout stderr]} (sprites-exec-output result)
|
||||
output (str stdout "\n" stderr)]
|
||||
(when-not (string/includes? output "__HEALTH_OK__")
|
||||
(throw (ex-info "sprite health check failed"
|
||||
{:sprite sprite-name
|
||||
:port port
|
||||
:stdout stdout
|
||||
:stderr stderr})))
|
||||
true)))
|
||||
(p/catch (fn [_]
|
||||
(p/let [_ (p/delay interval-ms)]
|
||||
(<sprite-health! env sprite-name port agent-token (dec retries) interval-ms))))))))
|
||||
@@ -371,8 +403,53 @@
|
||||
"default"))}))
|
||||
|
||||
(defn- message-payload [message]
|
||||
(cond-> {:message (:message message)}
|
||||
(string? (:kind message)) (assoc :kind (:kind message))))
|
||||
{:message (:message message)})
|
||||
|
||||
(def ^:private sprite-http-status-marker "__HTTP_STATUS__")
|
||||
|
||||
(defn- parse-sprite-http-status [text]
|
||||
(when (string? text)
|
||||
(some-> (re-seq (re-pattern (str sprite-http-status-marker ":(\\d+)")) text)
|
||||
last
|
||||
second
|
||||
(parse-int 400))))
|
||||
|
||||
(defn- strip-sprite-http-status [text]
|
||||
(if-not (string? text)
|
||||
""
|
||||
(string/replace text
|
||||
(re-pattern (str "(?:\\n?" sprite-http-status-marker ":\\d+)+\\s*$"))
|
||||
"")))
|
||||
|
||||
(defn- sprites-exec-output [result]
|
||||
(let [stdout (or (:stdout result)
|
||||
(get-in result [:result :stdout])
|
||||
(get-in result [:data :stdout])
|
||||
(get-in result [:output :stdout]))
|
||||
stderr (or (:stderr result)
|
||||
(get-in result [:result :stderr])
|
||||
(get-in result [:data :stderr])
|
||||
(get-in result [:output :stderr]))
|
||||
exit-code (or (:exitCode result)
|
||||
(:exit-code result)
|
||||
(get-in result [:result :exitCode])
|
||||
(get-in result [:result :exit-code])
|
||||
(get-in result [:data :exitCode])
|
||||
(get-in result [:data :exit-code])
|
||||
(get-in result [:output :exitCode])
|
||||
(get-in result [:output :exit-code]))]
|
||||
{:stdout (cond
|
||||
(string? stdout) stdout
|
||||
(some? stdout) (str stdout)
|
||||
:else "")
|
||||
:stderr (cond
|
||||
(string? stderr) stderr
|
||||
(some? stderr) (str stderr)
|
||||
:else "")
|
||||
:exit-code (cond
|
||||
(number? exit-code) exit-code
|
||||
(string? exit-code) (parse-int exit-code nil)
|
||||
:else nil)}))
|
||||
|
||||
(defn- <sprite-create-session! [^js env sprite-name port agent-token session-id payload]
|
||||
(let [script (str "resp=$(curl -sS -w '\\n%{http_code}' -X POST -H 'content-type: application/json' "
|
||||
@@ -382,25 +459,63 @@
|
||||
(curl-json-arg payload)
|
||||
" "
|
||||
(sprite-local-url port (str "/v1/sessions/" session-id)) "); "
|
||||
"status=$(echo \"$resp\" | tail -n1); "
|
||||
"http_status=$(echo \"$resp\" | tail -n1); "
|
||||
"body=$(echo \"$resp\" | sed '$d'); "
|
||||
"printf \"%s\" \"$body\"; "
|
||||
"printf \"STATUS:%s\" \"$status\" 1>&2")]
|
||||
"printf \"\\n" sprite-http-status-marker ":%s\" \"$http_status\"; ")]
|
||||
(p/let [result (sprites-exec-post! env sprite-name ["bash" "-lc" script])
|
||||
stdout (or (:stdout result) "")
|
||||
stderr (or (:stderr result) "")
|
||||
status (some-> (re-find #"STATUS:(\\d+)" stderr)
|
||||
second
|
||||
(parse-int 400))
|
||||
parsed (parse-json-safe stdout)]
|
||||
(when (and (number? status) (not (<= 200 status 299)))
|
||||
{:keys [stdout stderr exit-code]} (sprites-exec-output result)
|
||||
status (or (parse-sprite-http-status stderr)
|
||||
(parse-sprite-http-status stdout))
|
||||
parsed (parse-json-safe (strip-sprite-http-status stdout))]
|
||||
(when (or
|
||||
(not (number? status))
|
||||
(and (number? status) (not (<= 200 status 299))))
|
||||
(throw (ex-info "sandbox-agent create-session failed"
|
||||
{:status status
|
||||
:body parsed
|
||||
:raw-body stdout
|
||||
:stderr stderr})))
|
||||
(println :debug :sprite-create-session :status status :exit-code exit-code)
|
||||
(assoc parsed :session-id session-id))))
|
||||
|
||||
(defn- transient-create-session-error?
|
||||
[error]
|
||||
(let [{:keys [status raw-body stderr]} (ex-data error)
|
||||
msg (ex-message error)
|
||||
haystack (-> (str (or raw-body "") "\n" (or stderr "") "\n" (or msg ""))
|
||||
string/lower-case)]
|
||||
(or (= status 0)
|
||||
(string/includes? haystack "__http_status__:000")
|
||||
(string/includes? haystack "failed to connect")
|
||||
(string/includes? haystack "could not connect")
|
||||
(string/includes? haystack "connection refused")
|
||||
(string/includes? haystack "connect(): connection refused")
|
||||
(string/includes? haystack "connection reset")
|
||||
(string/includes? haystack "timed out"))))
|
||||
|
||||
(defn- <sprite-create-session-with-retry!
|
||||
[^js env sprite-name port agent-token session-id payload retries interval-ms]
|
||||
(-> (<sprite-create-session! env sprite-name port agent-token session-id payload)
|
||||
(p/catch (fn [error]
|
||||
(if (and (> retries 0) (transient-create-session-error? error))
|
||||
(do
|
||||
(println :debug :sprite-create-session-retry
|
||||
{:sprite sprite-name
|
||||
:session-id session-id
|
||||
:retries-left retries
|
||||
:error (ex-data error)})
|
||||
(p/let [_ (p/delay interval-ms)]
|
||||
(<sprite-create-session-with-retry! env
|
||||
sprite-name
|
||||
port
|
||||
agent-token
|
||||
session-id
|
||||
payload
|
||||
(dec retries)
|
||||
interval-ms)))
|
||||
(p/rejected error))))))
|
||||
|
||||
(defn- <sprite-clone-repo! [^js env sprite-name session-id task]
|
||||
(when-let [cmd (repo-clone-command env session-id task)]
|
||||
(sprites-exec-post! env sprite-name ["bash" "-lc" cmd])))
|
||||
@@ -684,13 +799,22 @@
|
||||
port (parse-int (env-str env "SPRITES_SANDBOX_AGENT_PORT") 2468)
|
||||
agent-token (env-str env "SANDBOX_AGENT_TOKEN") ;; may be nil if --no-token
|
||||
health-retries (parse-int (env-str env "SPRITES_HEALTH_RETRIES") 120)
|
||||
health-interval-ms (parse-int (env-str env "SPRITES_HEALTH_INTERVAL_MS") 500)]
|
||||
health-interval-ms (parse-int (env-str env "SPRITES_HEALTH_INTERVAL_MS") 500)
|
||||
create-session-retries (parse-int (env-str env "SPRITES_CREATE_SESSION_RETRIES") 20)
|
||||
create-session-interval-ms (parse-int (env-str env "SPRITES_CREATE_SESSION_INTERVAL_MS") 250)]
|
||||
(p/let [_ (sprites-create-or-get! env name)
|
||||
_ (<sprite-clone-repo! env name session-id task)
|
||||
_ (<sprite-bootstrap! env name port task session-id)
|
||||
_ (<sprite-health! env name port agent-token health-retries health-interval-ms)
|
||||
payload (session-payload task)
|
||||
response (<sprite-create-session! env name port agent-token session-id payload)]
|
||||
response (<sprite-create-session-with-retry! env
|
||||
name
|
||||
port
|
||||
agent-token
|
||||
session-id
|
||||
payload
|
||||
create-session-retries
|
||||
create-session-interval-ms)]
|
||||
{:provider "sprites"
|
||||
:sprite-name name
|
||||
:sandbox-port port
|
||||
@@ -723,14 +847,18 @@
|
||||
(env-str env "SANDBOX_AGENT_TOKEN"))]
|
||||
(when-not (string? name)
|
||||
(throw (ex-info "missing sprite-name on runtime" {:runtime runtime})))
|
||||
(let [script (str "curl -fsS -X POST "
|
||||
(let [payload-arg (curl-json-arg (message-payload message))
|
||||
auth-arg (curl-auth-arg agent-token)
|
||||
messages-url (sprite-local-url port (str "/v1/sessions/" (:session-id runtime) "/messages"))
|
||||
script (str "curl -fsS -X POST "
|
||||
"-H 'content-type: application/json' "
|
||||
(curl-auth-arg agent-token)
|
||||
auth-arg
|
||||
" "
|
||||
(curl-json-arg (message-payload message))
|
||||
payload-arg
|
||||
" "
|
||||
(sprite-local-url port (str "/v1/sessions/" (:session-id runtime) "/messages"))
|
||||
messages-url
|
||||
" >/dev/null")]
|
||||
(js/console.log "[agent:sprites-send-message]" script)
|
||||
(p/let [_ (sprites-exec-post! env name ["bash" "-lc" script])]
|
||||
true))))
|
||||
|
||||
|
||||
133
deps/db-sync/test/logseq/db_sync/agent_do_test.cljs
vendored
133
deps/db-sync/test/logseq/db_sync/agent_do_test.cljs
vendored
@@ -149,3 +149,136 @@
|
||||
(set! js/fetch original-fetch)
|
||||
(is false (str "unexpected error: " error))
|
||||
(done))))))))
|
||||
|
||||
(deftest init-does-not-wait-for-open-events-stream-test
|
||||
(testing "session init returns immediately even when runtime events stream stays open"
|
||||
(async done
|
||||
(let [calls (atom {:create 0
|
||||
:events-sse 0})
|
||||
original-fetch js/fetch
|
||||
env #js {"AGENT_RUNTIME_PROVIDER" "local-dev"
|
||||
"SANDBOX_AGENT_URL" "http://sandbox.local"}
|
||||
self (make-self env)
|
||||
headers {"content-type" "application/json"
|
||||
"x-user-id" "user-1"}
|
||||
init-body {:id "sess-init-fast"
|
||||
:project {:id "project-1"}
|
||||
:agent "codex"}
|
||||
timeout-id (js/setTimeout (fn []
|
||||
(set! js/fetch original-fetch)
|
||||
(is false "init blocked waiting on events stream")
|
||||
(done))
|
||||
250)]
|
||||
(set! js/fetch
|
||||
(fn [request]
|
||||
(let [url (.-url request)
|
||||
method (.-method request)]
|
||||
(cond
|
||||
(and (= "POST" method)
|
||||
(string/includes? url "/v1/sessions/sess-init-fast")
|
||||
(not (string/includes? url "/messages")))
|
||||
(do
|
||||
(swap! calls update :create inc)
|
||||
(js/Promise.resolve
|
||||
(js/Response.
|
||||
(js/JSON.stringify #js {:ok true})
|
||||
#js {:status 200
|
||||
:headers #js {"content-type" "application/json"}})))
|
||||
|
||||
(and (= "GET" method)
|
||||
(string/includes? url "/v1/sessions/sess-init-fast/events/sse"))
|
||||
(do
|
||||
(swap! calls update :events-sse inc)
|
||||
(let [stream (js/TransformStream.)]
|
||||
;; Never close/read completion from this stream.
|
||||
(js/Promise.resolve
|
||||
(js/Response.
|
||||
(.-readable stream)
|
||||
#js {:status 200
|
||||
:headers #js {"content-type" "text/event-stream"}}))))
|
||||
|
||||
:else
|
||||
(js/Promise.resolve
|
||||
(js/Response.
|
||||
(js/JSON.stringify #js {:error "unhandled request"})
|
||||
#js {:status 500
|
||||
:headers #js {"content-type" "application/json"}}))))))
|
||||
|
||||
(-> (agent-do/handle-fetch self
|
||||
(json-request "http://db-sync.local/__session__/init"
|
||||
"POST"
|
||||
init-body
|
||||
headers))
|
||||
(.then (fn [resp]
|
||||
(js/clearTimeout timeout-id)
|
||||
(set! js/fetch original-fetch)
|
||||
(is (= 200 (.-status resp)))
|
||||
(is (= 1 (:create @calls)))
|
||||
(is (= 1 (:events-sse @calls)))
|
||||
(done)))
|
||||
(.catch (fn [error]
|
||||
(js/clearTimeout timeout-id)
|
||||
(set! js/fetch original-fetch)
|
||||
(is false (str "unexpected error: " error))
|
||||
(done))))))))
|
||||
|
||||
(deftest init-does-not-await-start-runtime-events-stream-return-test
|
||||
(testing "session init should not await start-runtime-events-stream! promise"
|
||||
(async done
|
||||
(let [calls (atom {:create 0})
|
||||
original-fetch js/fetch
|
||||
env #js {"AGENT_RUNTIME_PROVIDER" "local-dev"
|
||||
"SANDBOX_AGENT_URL" "http://sandbox.local"}
|
||||
self (make-self env)
|
||||
headers {"content-type" "application/json"
|
||||
"x-user-id" "user-1"}
|
||||
init-body {:id "sess-init-no-await"
|
||||
:project {:id "project-1"}
|
||||
:agent "codex"}
|
||||
timeout-id (js/setTimeout (fn []
|
||||
(set! js/fetch original-fetch)
|
||||
(is false "init awaited start-runtime-events-stream! promise")
|
||||
(done))
|
||||
250)]
|
||||
(set! js/fetch
|
||||
(fn [request]
|
||||
(let [url (.-url request)
|
||||
method (.-method request)]
|
||||
(cond
|
||||
(and (= "POST" method)
|
||||
(string/includes? url "/v1/sessions/sess-init-no-await")
|
||||
(not (string/includes? url "/messages")))
|
||||
(do
|
||||
(swap! calls update :create inc)
|
||||
(js/Promise.resolve
|
||||
(js/Response.
|
||||
(js/JSON.stringify #js {:ok true})
|
||||
#js {:status 200
|
||||
:headers #js {"content-type" "application/json"}})))
|
||||
|
||||
:else
|
||||
(js/Promise.resolve
|
||||
(js/Response.
|
||||
(js/JSON.stringify #js {:error "unhandled request"})
|
||||
#js {:status 500
|
||||
:headers #js {"content-type" "application/json"}}))))))
|
||||
|
||||
(with-redefs [agent-do/start-runtime-events-stream!
|
||||
(fn [& _]
|
||||
(js/Promise. (fn [_resolve _reject])))]
|
||||
(-> (agent-do/handle-fetch self
|
||||
(json-request "http://db-sync.local/__session__/init"
|
||||
"POST"
|
||||
init-body
|
||||
headers))
|
||||
(.then (fn [resp]
|
||||
(js/clearTimeout timeout-id)
|
||||
(set! js/fetch original-fetch)
|
||||
(is (= 200 (.-status resp)))
|
||||
(is (= 1 (:create @calls)))
|
||||
(done)))
|
||||
(.catch (fn [error]
|
||||
(js/clearTimeout timeout-id)
|
||||
(set! js/fetch original-fetch)
|
||||
(is false (str "unexpected error: " error))
|
||||
(done)))))))))
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
[clojure.string :as string]
|
||||
[logseq.db-sync.worker.agent.runtime-provider :as runtime-provider]))
|
||||
|
||||
(defn- fetch-url [request]
|
||||
(cond
|
||||
(string? request) request
|
||||
(instance? js/URL request) (.toString request)
|
||||
(some? request) (.-url request)
|
||||
:else ""))
|
||||
|
||||
(defn- fetch-method [request init]
|
||||
(or (when (some? init) (aget init "method"))
|
||||
(when (and (some? request) (not (string? request))) (.-method request))
|
||||
"GET"))
|
||||
|
||||
(deftest provider-kind-test
|
||||
(testing "normalizes configured runtime provider"
|
||||
(is (= "sprites" (runtime-provider/provider-kind #js {})))
|
||||
@@ -100,9 +112,9 @@
|
||||
task {:agent {:provider "codex"}}
|
||||
original-fetch js/fetch]
|
||||
(set! js/fetch
|
||||
(fn [request]
|
||||
(is (= "http://127.0.0.1:2468/v1/sessions/sess-1" (.-url request)))
|
||||
(is (= "POST" (.-method request)))
|
||||
(fn [request init]
|
||||
(is (= "http://127.0.0.1:2468/v1/sessions/sess-1" (fetch-url request)))
|
||||
(is (= "POST" (fetch-method request init)))
|
||||
(js/Promise.resolve
|
||||
(js/Response.
|
||||
(js/JSON.stringify #js {:ok true})
|
||||
@@ -128,9 +140,9 @@
|
||||
:session-id "sess-2"}
|
||||
original-fetch js/fetch]
|
||||
(set! js/fetch
|
||||
(fn [request]
|
||||
(is (= "http://sandbox.local/v1/sessions/sess-2/events/sse" (.-url request)))
|
||||
(is (= "GET" (.-method request)))
|
||||
(fn [request init]
|
||||
(is (= "http://sandbox.local/v1/sessions/sess-2/events/sse" (fetch-url request)))
|
||||
(is (= "GET" (fetch-method request init)))
|
||||
(js/Promise.resolve
|
||||
(js/Response. "data: {\"type\":\"ok\"}\n\n"
|
||||
#js {:status 200
|
||||
@@ -154,9 +166,9 @@
|
||||
:session-id "sess-2"}
|
||||
original-fetch js/fetch]
|
||||
(set! js/fetch
|
||||
(fn [request]
|
||||
(is (= "http://sandbox.local/v1/sessions/sess-2/messages" (.-url request)))
|
||||
(is (= "POST" (.-method request)))
|
||||
(fn [request init]
|
||||
(is (= "http://sandbox.local/v1/sessions/sess-2/messages" (fetch-url request)))
|
||||
(is (= "POST" (fetch-method request init)))
|
||||
(js/Promise.resolve
|
||||
(js/Response.
|
||||
(js/JSON.stringify #js {:ok true})
|
||||
@@ -172,6 +184,237 @@
|
||||
(is false (str "unexpected error: " error))
|
||||
(done)))))))
|
||||
|
||||
(deftest sprites-provider-send-message-test
|
||||
(async done
|
||||
(let [captured (atom nil)
|
||||
env #js {"SPRITE_TOKEN" "sprite-token"}
|
||||
provider (runtime-provider/create-provider env "sprites")
|
||||
runtime {:provider "sprites"
|
||||
:sprite-name "sprite-1"
|
||||
:sandbox-port 2468
|
||||
:session-id "sess-4"}
|
||||
original-fetch js/fetch]
|
||||
(set! js/fetch
|
||||
(fn [request init]
|
||||
(let [url (fetch-url request)
|
||||
parsed (js/URL. url)
|
||||
cmds (vec (.getAll (.-searchParams parsed) "cmd"))]
|
||||
(reset! captured {:url url
|
||||
:method (fetch-method request init)
|
||||
:script (nth cmds 2 nil)})
|
||||
(js/Promise.resolve
|
||||
(js/Response.
|
||||
(js/JSON.stringify #js {:ok true})
|
||||
#js {:status 200 :headers #js {"content-type" "application/json"}})))))
|
||||
(-> (runtime-provider/<send-message! provider runtime {:message "hello sprites"})
|
||||
(.then (fn [ok?]
|
||||
(set! js/fetch original-fetch)
|
||||
(is (true? ok?))
|
||||
(is (= "POST" (:method @captured)))
|
||||
(is (string/includes? (:url @captured) "/v1/sprites/sprite-1/exec"))
|
||||
(is (string/includes? (:script @captured) "/v1/sessions/sess-4/messages"))
|
||||
(is (not (string/includes? (:script @captured) "/messages/stream")))
|
||||
(done)))
|
||||
(.catch (fn [error]
|
||||
(set! js/fetch original-fetch)
|
||||
(is false (str "unexpected error: " error))
|
||||
(done)))))))
|
||||
|
||||
(deftest sprites-provider-provision-parses-nested-exec-output-test
|
||||
(async done
|
||||
(let [env #js {"SPRITE_TOKEN" "sprite-token"}
|
||||
provider (runtime-provider/create-provider env "sprites")
|
||||
task {:agent {:provider "codex"}}
|
||||
original-fetch js/fetch]
|
||||
(set! js/fetch
|
||||
(fn [request init]
|
||||
(let [url (fetch-url request)
|
||||
method (fetch-method request init)]
|
||||
(cond
|
||||
(and (= "POST" method)
|
||||
(string/includes? url "/v1/sprites")
|
||||
(not (string/includes? url "/exec")))
|
||||
(js/Promise.resolve
|
||||
(js/Response.
|
||||
(js/JSON.stringify #js {:name "logseq-task-sess-ok"})
|
||||
#js {:status 200 :headers #js {"content-type" "application/json"}}))
|
||||
|
||||
(and (= "POST" method)
|
||||
(string/includes? url "/v1/sprites/logseq-task-sess-ok/exec"))
|
||||
(let [parsed (js/URL. url)
|
||||
cmds (vec (.getAll (.-searchParams parsed) "cmd"))
|
||||
script (nth cmds 2 nil)
|
||||
create-session? (and (string? script)
|
||||
(string/includes? script "/v1/sessions/sess-ok"))]
|
||||
(js/Promise.resolve
|
||||
(js/Response.
|
||||
(js/JSON.stringify
|
||||
(clj->js (if create-session?
|
||||
{:result {:stdout "{\"ok\":true}\n__HTTP_STATUS__:200__HTTP_STATUS__:200"
|
||||
:stderr ""}}
|
||||
{:result {:stdout ""
|
||||
:stderr ""}})))
|
||||
#js {:status 200 :headers #js {"content-type" "application/json"}})))
|
||||
|
||||
:else
|
||||
(js/Promise.resolve
|
||||
(js/Response.
|
||||
(js/JSON.stringify #js {:error "unexpected request"})
|
||||
#js {:status 500 :headers #js {"content-type" "application/json"}}))))))
|
||||
(-> (runtime-provider/<provision-runtime! provider "sess-ok" task)
|
||||
(.then (fn [runtime]
|
||||
(set! js/fetch original-fetch)
|
||||
(is (= "sess-ok" (:session-id runtime)))
|
||||
(is (= "sprites" (:provider runtime)))
|
||||
(done)))
|
||||
(.catch (fn [error]
|
||||
(set! js/fetch original-fetch)
|
||||
(is false (str "unexpected error: " error))
|
||||
(done)))))))
|
||||
|
||||
(deftest sprites-provider-provision-retries-create-session-on-transient-connection-error-test
|
||||
(async done
|
||||
(let [calls (atom {:create-session 0})
|
||||
env #js {"SPRITE_TOKEN" "sprite-token"
|
||||
"SPRITES_HEALTH_RETRIES" "2"
|
||||
"SPRITES_HEALTH_INTERVAL_MS" "1"}
|
||||
provider (runtime-provider/create-provider env "sprites")
|
||||
task {:agent {:provider "codex"}}
|
||||
original-fetch js/fetch]
|
||||
(set! js/fetch
|
||||
(fn [request init]
|
||||
(let [url (fetch-url request)
|
||||
method (fetch-method request init)]
|
||||
(cond
|
||||
(and (= "POST" method)
|
||||
(string/includes? url "/v1/sprites")
|
||||
(not (string/includes? url "/exec")))
|
||||
(js/Promise.resolve
|
||||
(js/Response.
|
||||
(js/JSON.stringify #js {:name "logseq-task-sess-retry"})
|
||||
#js {:status 200 :headers #js {"content-type" "application/json"}}))
|
||||
|
||||
(and (= "POST" method)
|
||||
(string/includes? url "/v1/sprites/logseq-task-sess-retry/exec"))
|
||||
(let [parsed (js/URL. url)
|
||||
cmds (vec (.getAll (.-searchParams parsed) "cmd"))
|
||||
script (nth cmds 2 nil)
|
||||
create-session? (and (string? script)
|
||||
(string/includes? script "/v1/sessions/sess-retry"))
|
||||
health? (and (string? script)
|
||||
(string/includes? script "/v1/health"))]
|
||||
(cond
|
||||
create-session?
|
||||
(let [n (swap! calls update :create-session inc)]
|
||||
(js/Promise.resolve
|
||||
(js/Response.
|
||||
(js/JSON.stringify
|
||||
(clj->js (if (= 1 (:create-session n))
|
||||
{:result {:stdout "curl: (7) Failed to connect to 127.0.0.1 port 2468\n__HTTP_STATUS__:000"
|
||||
:stderr ""}}
|
||||
{:result {:stdout "{\"ok\":true}\n__HTTP_STATUS__:200"
|
||||
:stderr ""}})))
|
||||
#js {:status 200 :headers #js {"content-type" "application/json"}})))
|
||||
|
||||
health?
|
||||
(js/Promise.resolve
|
||||
(js/Response.
|
||||
(js/JSON.stringify (clj->js {:result {:stdout "__HEALTH_OK__" :stderr ""}}))
|
||||
#js {:status 200 :headers #js {"content-type" "application/json"}}))
|
||||
|
||||
:else
|
||||
(js/Promise.resolve
|
||||
(js/Response.
|
||||
(js/JSON.stringify (clj->js {:result {:stdout "" :stderr ""}}))
|
||||
#js {:status 200 :headers #js {"content-type" "application/json"}}))))
|
||||
|
||||
:else
|
||||
(js/Promise.resolve
|
||||
(js/Response.
|
||||
(js/JSON.stringify #js {:error "unexpected request"})
|
||||
#js {:status 500 :headers #js {"content-type" "application/json"}}))))))
|
||||
(-> (runtime-provider/<provision-runtime! provider "sess-retry" task)
|
||||
(.then (fn [runtime]
|
||||
(set! js/fetch original-fetch)
|
||||
(is (= "sess-retry" (:session-id runtime)))
|
||||
(is (= "sprites" (:provider runtime)))
|
||||
(is (= 2 (:create-session @calls)))
|
||||
(done)))
|
||||
(.catch (fn [error]
|
||||
(set! js/fetch original-fetch)
|
||||
(is false (str "unexpected error: " error))
|
||||
(done)))))))
|
||||
|
||||
(deftest sprites-provider-provision-does-not-retry-create-session-on-http-400-test
|
||||
(async done
|
||||
(let [calls (atom {:create-session 0})
|
||||
env #js {"SPRITE_TOKEN" "sprite-token"
|
||||
"SPRITES_HEALTH_RETRIES" "2"
|
||||
"SPRITES_HEALTH_INTERVAL_MS" "1"}
|
||||
provider (runtime-provider/create-provider env "sprites")
|
||||
task {:agent {:provider "codex"}}
|
||||
original-fetch js/fetch]
|
||||
(set! js/fetch
|
||||
(fn [request init]
|
||||
(let [url (fetch-url request)
|
||||
method (fetch-method request init)]
|
||||
(cond
|
||||
(and (= "POST" method)
|
||||
(string/includes? url "/v1/sprites")
|
||||
(not (string/includes? url "/exec")))
|
||||
(js/Promise.resolve
|
||||
(js/Response.
|
||||
(js/JSON.stringify #js {:name "logseq-task-sess-400"})
|
||||
#js {:status 200 :headers #js {"content-type" "application/json"}}))
|
||||
|
||||
(and (= "POST" method)
|
||||
(string/includes? url "/v1/sprites/logseq-task-sess-400/exec"))
|
||||
(let [parsed (js/URL. url)
|
||||
cmds (vec (.getAll (.-searchParams parsed) "cmd"))
|
||||
script (nth cmds 2 nil)
|
||||
create-session? (and (string? script)
|
||||
(string/includes? script "/v1/sessions/sess-400"))
|
||||
health? (and (string? script)
|
||||
(string/includes? script "/v1/health"))]
|
||||
(cond
|
||||
create-session?
|
||||
(do
|
||||
(swap! calls update :create-session inc)
|
||||
(js/Promise.resolve
|
||||
(js/Response.
|
||||
(js/JSON.stringify
|
||||
(clj->js {:result {:stdout "{\"error\":\"bad request\"}\n__HTTP_STATUS__:400"
|
||||
:stderr ""}}))
|
||||
#js {:status 200 :headers #js {"content-type" "application/json"}})))
|
||||
|
||||
health?
|
||||
(js/Promise.resolve
|
||||
(js/Response.
|
||||
(js/JSON.stringify (clj->js {:result {:stdout "__HEALTH_OK__" :stderr ""}}))
|
||||
#js {:status 200 :headers #js {"content-type" "application/json"}}))
|
||||
|
||||
:else
|
||||
(js/Promise.resolve
|
||||
(js/Response.
|
||||
(js/JSON.stringify (clj->js {:result {:stdout "" :stderr ""}}))
|
||||
#js {:status 200 :headers #js {"content-type" "application/json"}}))))
|
||||
|
||||
:else
|
||||
(js/Promise.resolve
|
||||
(js/Response.
|
||||
(js/JSON.stringify #js {:error "unexpected request"})
|
||||
#js {:status 500 :headers #js {"content-type" "application/json"}}))))))
|
||||
(-> (runtime-provider/<provision-runtime! provider "sess-400" task)
|
||||
(.then (fn [_]
|
||||
(set! js/fetch original-fetch)
|
||||
(is false "expected provisioning to fail")
|
||||
(done)))
|
||||
(.catch (fn [error]
|
||||
(set! js/fetch original-fetch)
|
||||
(is (= 1 (:create-session @calls)))
|
||||
(is (= 400 (:status (ex-data error))))
|
||||
(done)))))))
|
||||
|
||||
(deftest cloudflare-provider-provision-test
|
||||
(async done
|
||||
(let [calls (atom {:health 0})
|
||||
|
||||
Reference in New Issue
Block a user