- Update control-plane and edge domain rules - Update roadmap phase and current status - Archive client-integration-clone-adoption and nexo-notification-message-smoke-alignment milestones - Update control-plane-client milestone - Add client assets, lib/src, integrations tests - Add google-services.json for client android - Update client pubspec, build configs, and platform manifests
215 lines
6.1 KiB
Bash
Executable file
215 lines
6.1 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
TMP_DIR=$(mktemp -d)
|
|
|
|
EDGE_PID=""
|
|
NODE_PID=""
|
|
OLLAMA_PID=""
|
|
EDGE_FD_OPEN=0
|
|
|
|
cleanup() {
|
|
if [ "$EDGE_FD_OPEN" -eq 1 ]; then
|
|
{ echo "/exit" >&3; } 2>/dev/null || true
|
|
exec 3>&- 2>/dev/null || true
|
|
fi
|
|
if [ -n "$EDGE_PID" ]; then wait "$EDGE_PID" 2>/dev/null || true; fi
|
|
if [ -n "$NODE_PID" ]; then kill "$NODE_PID" 2>/dev/null || true; fi
|
|
if [ -n "$OLLAMA_PID" ]; then kill "$OLLAMA_PID" 2>/dev/null || true; fi
|
|
rm -rf "$TMP_DIR" 2>/dev/null || true
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
PORT=$((31000 + RANDOM % 5000))
|
|
BOOTSTRAP_PORT=$((21000 + RANDOM % 5000))
|
|
OPENAI_PORT=$((36000 + RANDOM % 5000))
|
|
OLLAMA_PORT=$((41000 + RANDOM % 5000))
|
|
EDGE_METRICS_PORT=$((46000 + RANDOM % 5000))
|
|
NODE_METRICS_PORT=$((51000 + RANDOM % 5000))
|
|
MODEL="fake-ollama-model"
|
|
|
|
FAKE_OLLAMA="$TMP_DIR/fake_ollama.go"
|
|
cat > "$FAKE_OLLAMA" <<'EOF'
|
|
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
)
|
|
|
|
type chatRequest struct {
|
|
Model string `json:"model"`
|
|
Messages []struct {
|
|
Role string `json:"role"`
|
|
Content string `json:"content"`
|
|
} `json:"messages"`
|
|
Options struct {
|
|
NumCtx int `json:"num_ctx"`
|
|
} `json:"options"`
|
|
}
|
|
|
|
func main() {
|
|
if len(os.Args) < 2 {
|
|
log.Fatal("missing listen addr")
|
|
}
|
|
mux := http.NewServeMux()
|
|
mux.HandleFunc("/api/tags", func(w http.ResponseWriter, _ *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_, _ = w.Write([]byte(`{"models":[{"name":"fake-ollama-model"}]}`))
|
|
})
|
|
mux.HandleFunc("/api/chat", func(w http.ResponseWriter, r *http.Request) {
|
|
var req chatRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
return
|
|
}
|
|
if req.Model != "fake-ollama-model" {
|
|
http.Error(w, "unexpected model: "+req.Model, http.StatusBadRequest)
|
|
return
|
|
}
|
|
if req.Options.NumCtx != 262144 {
|
|
http.Error(w, "unexpected num_ctx", http.StatusBadRequest)
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", "application/x-ndjson")
|
|
_, _ = w.Write([]byte(`{"message":{"role":"assistant","thinking":"thinking..."},"done":false}` + "\n"))
|
|
if f, ok := w.(http.Flusher); ok {
|
|
f.Flush()
|
|
}
|
|
_, _ = w.Write([]byte(`{"message":{"role":"assistant","content":"IOP_OPENAI_"},"done":false}` + "\n"))
|
|
if f, ok := w.(http.Flusher); ok {
|
|
f.Flush()
|
|
}
|
|
_, _ = w.Write([]byte(`{"message":{"role":"assistant","content":"OLLAMA_OK"},"done":false}` + "\n"))
|
|
_, _ = w.Write([]byte(`{"done":true,"prompt_eval_count":5,"eval_count":2}` + "\n"))
|
|
})
|
|
log.Fatal(http.ListenAndServe(os.Args[1], mux))
|
|
}
|
|
EOF
|
|
|
|
go run "$FAKE_OLLAMA" "127.0.0.1:$OLLAMA_PORT" > "$TMP_DIR/fake_ollama.out" 2>&1 &
|
|
OLLAMA_PID=$!
|
|
|
|
wait_port() {
|
|
local host="$1"
|
|
local port="$2"
|
|
local label="$3"
|
|
local deadline=$((SECONDS + 20))
|
|
while ! timeout 1 bash -c 'cat < /dev/null > /dev/tcp/"$1"/"$2"' _ "$host" "$port" 2>/dev/null; do
|
|
if (( SECONDS >= deadline )); then
|
|
echo "[openai-ollama] $label did not open on $host:$port"
|
|
return 1
|
|
fi
|
|
sleep 0.2
|
|
done
|
|
}
|
|
|
|
wait_port 127.0.0.1 "$OLLAMA_PORT" "fake ollama"
|
|
|
|
EDGE_CONFIG="$TMP_DIR/edge.yaml"
|
|
NODE_CONFIG="$TMP_DIR/node.yaml"
|
|
cat > "$EDGE_CONFIG" <<EOF
|
|
server:
|
|
listen: "127.0.0.1:$PORT"
|
|
metrics:
|
|
port: $EDGE_METRICS_PORT
|
|
bootstrap:
|
|
listen: "127.0.0.1:$BOOTSTRAP_PORT"
|
|
artifact_dir: "$TMP_DIR/artifacts"
|
|
openai:
|
|
enabled: true
|
|
listen: "127.0.0.1:$OPENAI_PORT"
|
|
adapter: "ollama"
|
|
target: "$MODEL"
|
|
models:
|
|
- "$MODEL"
|
|
session_id: "cline"
|
|
timeout_sec: 30
|
|
strict_output: false
|
|
console:
|
|
adapter: mock
|
|
target: mock-echo
|
|
session_id: default
|
|
nodes:
|
|
- id: test-node
|
|
alias: test-node
|
|
token: test-token
|
|
runtime:
|
|
workspace_root: "$TMP_DIR/workspace"
|
|
adapters:
|
|
ollama:
|
|
enabled: true
|
|
base_url: "http://127.0.0.1:$OLLAMA_PORT"
|
|
context_size: 262144
|
|
EOF
|
|
|
|
cat > "$NODE_CONFIG" <<EOF
|
|
transport:
|
|
edge_addr: "127.0.0.1:$PORT"
|
|
token: test-token
|
|
reconnect_interval: 1s
|
|
metrics:
|
|
port: $NODE_METRICS_PORT
|
|
node:
|
|
id: test-node
|
|
alias: test-node
|
|
EOF
|
|
|
|
EDGE_OUT="$TMP_DIR/edge.out"
|
|
NODE_OUT="$TMP_DIR/node.out"
|
|
mkfifo "$TMP_DIR/edge_fifo"
|
|
IOP_EDGE_CONFIG="$EDGE_CONFIG" "$REPO_ROOT/scripts/dev/edge.sh" < "$TMP_DIR/edge_fifo" > "$EDGE_OUT" 2>&1 &
|
|
EDGE_PID=$!
|
|
exec 3> "$TMP_DIR/edge_fifo"
|
|
EDGE_FD_OPEN=1
|
|
|
|
wait_port 127.0.0.1 "$PORT" "edge node transport"
|
|
wait_port 127.0.0.1 "$OPENAI_PORT" "edge openai api"
|
|
|
|
IOP_NODE_CONFIG="$NODE_CONFIG" "$REPO_ROOT/scripts/dev/node.sh" > "$NODE_OUT" 2>&1 &
|
|
NODE_PID=$!
|
|
|
|
deadline=$((SECONDS + 30))
|
|
while ! grep -q '\[node0-evt\] connected reason="registered"' "$EDGE_OUT"; do
|
|
if (( SECONDS >= deadline )); then
|
|
echo "[openai-ollama] node registration timed out"
|
|
echo "=== EDGE OUTPUT ==="; cat "$EDGE_OUT"
|
|
echo "=== NODE OUTPUT ==="; cat "$NODE_OUT"
|
|
exit 1
|
|
fi
|
|
sleep 0.2
|
|
done
|
|
|
|
MODELS_OUT="$TMP_DIR/models.json"
|
|
curl -fsS "http://127.0.0.1:$OPENAI_PORT/v1/models" > "$MODELS_OUT"
|
|
grep -q "$MODEL" "$MODELS_OUT"
|
|
|
|
CHAT_OUT="$TMP_DIR/chat.json"
|
|
curl -fsS \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"model":"client-request-model","messages":[{"role":"user","content":"say the test token"}]}' \
|
|
"http://127.0.0.1:$OPENAI_PORT/v1/chat/completions" > "$CHAT_OUT"
|
|
grep -q "IOP_OPENAI_OLLAMA_OK" "$CHAT_OUT"
|
|
grep -q "reasoning_content" "$CHAT_OUT"
|
|
|
|
STREAM_OUT="$TMP_DIR/stream.txt"
|
|
curl -fsS -N \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"model":"client-request-model","stream":true,"messages":[{"role":"user","content":"stream the test token"}]}' \
|
|
"http://127.0.0.1:$OPENAI_PORT/v1/chat/completions" > "$STREAM_OUT"
|
|
grep -q '"reasoning_content":"thinking..."' "$STREAM_OUT"
|
|
grep -q '"content":"IOP_OPENAI_"' "$STREAM_OUT"
|
|
grep -q 'data: \[DONE\]' "$STREAM_OUT"
|
|
|
|
if grep -i -E "node reported error|error run_id=|\[[^]]+-evt\] error|panic:" "$EDGE_OUT" "$NODE_OUT" >/dev/null; then
|
|
echo "[openai-ollama] detected failure marker"
|
|
echo "=== EDGE OUTPUT ==="; cat "$EDGE_OUT"
|
|
echo "=== NODE OUTPUT ==="; cat "$NODE_OUT"
|
|
exit 1
|
|
fi
|
|
|
|
echo "[openai-ollama] OpenAI-compatible Ollama serving test PASSED."
|