diff --git a/entitleme.plist b/entitleme.plist new file mode 100644 index 0000000..60c9cb3 --- /dev/null +++ b/entitleme.plist @@ -0,0 +1,9 @@ + + + + com.apple.security.cs.allow-jit + + + + diff --git a/pkg/build.sh b/pkg/build.sh index e9375fa..c39f4dc 100755 --- a/pkg/build.sh +++ b/pkg/build.sh @@ -43,6 +43,31 @@ codesign --force \ --strict \ "${PKG_CLI_BIN_PATH}/tiles" + +echo "Embedding Pi" +# Copying pi artifacts into extracted pi folder +cp pi-darwin-arm64.tar.gz "${PKG_LIBS_PATH}" + +tar -xvf "${PKG_LIBS_PATH}/pi-darwin-arm64.tar.gz" -C "${PKG_LIBS_PATH}" + +rm "${PKG_LIBS_PATH}/pi-darwin-arm64.tar.gz" + +# removing unnecessary files +# rm -rf "${DIST_DIR}/tmp/pi/examples" + +# Signing the pi binary + +echo "Signing Pi binary..." + +codesign --force \ + --sign "$DEVELOPER_ID_APPLICATION" \ + --options runtime \ + --timestamp \ + --entitlements entitleme.plist \ + --strict \ + "${PKG_LIBS_PATH}/pi/pi" + + # Build venvstack and move to /usr/local/share/tiles # # flushing this folder, else the final zip will have previous app-server zips too (#84) diff --git a/pkg/bundle_network_installer.sh b/pkg/bundle_network_installer.sh index f6e8d45..b239087 100755 --- a/pkg/bundle_network_installer.sh +++ b/pkg/bundle_network_installer.sh @@ -14,6 +14,7 @@ productbuild \ # signing productsign \ --sign "$DEVELOPER_ID_INSTALLER" \ + --entitlements entitleme.plist \ pkg/tiles-dist-unsigned.pkg \ pkg/tiles.pkg diff --git a/scripts/bundler.sh b/scripts/bundler.sh index 8b962b8..c57a23b 100755 --- a/scripts/bundler.sh +++ b/scripts/bundler.sh @@ -16,7 +16,8 @@ VERSION=$(grep '^version' tiles/Cargo.toml | head -1 | awk -F'"' '{print $2}') OS=$(uname -s | tr '[:upper:]' '[:lower:]') ARCH=$(uname -m) -# Final tar.gz name +# Name for final tar.gz + OUT_NAME="${BINARY_NAME}-v${VERSION}-${ARCH}-${OS}" echo "🚀 Building ${BINARY_NAME} (${TARGET} mode)..." @@ -24,27 +25,20 @@ echo "🚀 Building ${BINARY_NAME} (${TARGET} mode)..." cargo build -p tiles --${TARGET} -# Destination where the release build is generated +# Destination where the release binary is generated CLI_BIN_PATH="target/${TARGET}/${BINARY_NAME}" chmod +x "${CLI_BIN_PATH}" -# echo "Signing the Tiles binary..." - -# # Signing the tiles binary -# codesign --force \ -# --sign "$DEVELOPER_ID_APPLICATION"\ -# --options runtime \ -# --timestamp \ -# --strict \ -# "${CLI_BIN_PATH}" +echo "Signing the Tiles binary..." -# # echo "Notarizing Tiles binary..." - -# # notarizing the tiles binary -# xcrun notarytool submit --force "${CLI_BIN_PATH}" \ -# --keychain-profile "tiles-notary-profile" \ -# --wait +# Signing the tiles binary +codesign --force \ + --sign "$DEVELOPER_ID_APPLICATION"\ + --options runtime \ + --timestamp \ + --strict \ + "${CLI_BIN_PATH}" mkdir -p "${DIST_DIR}/tmp" @@ -60,43 +54,41 @@ tar -xvf "${DIST_DIR}/tmp/pi-darwin-arm64.tar.gz" -C "${DIST_DIR}/tmp" rm "${DIST_DIR}/tmp/pi-darwin-arm64.tar.gz" # removing unnecessary files -rm -rf "${DIST_DIR}/tmp/pi/examples" +# rm -rf "${DIST_DIR}/tmp/pi/examples" # Signing the pi binary -# codesign --force \ -# --sign "$DEVELOPER_ID_APPLICATION"\ -# --options runtime \ -# --timestamp \ -# --strict \ -# "${DIST_DIR}/tmp/pi/pi" -# echo "Notarizing Pibinary..." +echo "Signing Pi binary..." + +codesign --force \ + --sign "$DEVELOPER_ID_APPLICATION" \ + --options runtime \ + --timestamp \ + --entitlements entitleme.plist \ + --strict \ + "${DIST_DIR}/tmp/pi/pi" -# # notarizing the pi binary -# xcrun notarytool submit --force "${DIST_DIR}/tmp/pi/pi" \ -# --keychain-profile "tiles-notary-profile" \ -# --wait # flushing this folder, else the final zip will have previous app-server zips too (#84) -# rm -rf "${SERVER_DIR}/stack_export_prod" +rm -rf "${SERVER_DIR}/stack_export_prod" -# echo "🔒 Locking the venvstack...." +echo "🔒 Locking the venvstack...." -# venvstacks lock server/stack/venvstacks.toml +venvstacks lock server/stack/venvstacks.toml -# echo "🛠️ Building the venvstack...." +echo "🛠️ Building the venvstack...." -# venvstacks build server/stack/venvstacks.toml +venvstacks build server/stack/venvstacks.toml -# echo "📦 Publishing the venvstack...." +echo "📦 Publishing the venvstack...." -# venvstacks publish --tag-outputs --output-dir ../stack_export_prod server/stack/venvstacks.toml +venvstacks publish --tag-outputs --output-dir ../stack_export_prod server/stack/venvstacks.toml cp -r "${SERVER_DIR}" "${DIST_DIR}/tmp/" rm -rf "${DIST_DIR}/tmp/server/__pycache__" -rm -rf "${DIST_DIR}/tmp/server/mem_agent/__pycache__" -rm -rf "${DIST_DIR}/tmp/server/backend/__pycache__" +# rm -rf "${DIST_DIR}/tmp/server/mem_agent/__pycache__" +# rm -rf "${DIST_DIR}/tmp/server/backend/__pycache__" rm -rf "${DIST_DIR}/tmp/server/.venv" rm -rf "${DIST_DIR}/tmp/server/stack" diff --git a/scripts/install.sh b/scripts/install.sh index 1dbcae7..e6de591 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -14,6 +14,7 @@ MODELFILE_DIR="/usr/local/share/tiles/modelfiles" # Modelfile server folder PI_DIR="/usr/local/share/tiles/pi" TMPDIR="$(mktemp -d)" + OS=$(uname -s | tr '[:upper:]' '[:lower:]') ARCH=$(uname -m) @@ -57,11 +58,8 @@ rm -rf "${PI_DIR}" mkdir -p "${PI_DIR}" -cp -r "${TMPDIR}/pi-darwin-arm64.tar.gz" "${PI_DIR}/" - -tar -xvf "${PI_DIR}/pi-darwin-arm64.tar.gz" +cp -r "${TMPDIR}/pi"/* "${PI_DIR}/" -rm "${PI_DIR}/pi-darwin-arm64.tar.gz" log "📦 Installing Python server to ${SERVER_DIR}..." rm -rf "${SERVER_DIR}" diff --git a/scripts/tar.exclude b/scripts/tar.exclude index 02539a9..bd2e0a1 100644 --- a/scripts/tar.exclude +++ b/scripts/tar.exclude @@ -5,4 +5,3 @@ __pycache__ .env .git .DS_Store - diff --git a/server/api.py b/server/api.py index e55cc54..d0b13e1 100644 --- a/server/api.py +++ b/server/api.py @@ -34,7 +34,7 @@ app = FastAPI() @app.get("/ping") async def ping(): - return {"message": "Badda-Bing Badda-Bang"} + return {"message": "Welcome to the jungle"} @app.post("/start") @@ -78,20 +78,36 @@ async def create_chat_completion(request: ChatCompletionRequest): raise HTTPException(status_code=500, detail=str(e)) -@app.exception_handler(RequestValidationError) -async def validation_exception_handler(request: Request, exc: RequestValidationError): +@app.exception_handler(HTTPException) +async def validation_exception_handler(request: Request, exc: HTTPException): return JSONResponse( - status_code=422, - content={"detail": exc.errors()}, + status_code=exc.status_code, + content={"detail": exc.detail}, ) +@app.middleware("http") +async def catch_all(request, call_next): + try: + return await call_next(request) + except Exception as e: + print("UNCAUGHT:", repr(e)) + raise + + @app.post("/v1/responses") async def create_chat_response(request: ResponsesRequest): """ Create a response with openResponses format """ + try: + ResponsesRequest.model_validate(request) + except Exception as e: + print(e) + + print(f"REQUEST => {request}") + if request.stream: return StreamingResponse( runtime.backend.generate_response_chat_stream(request), diff --git a/server/backend/mlx.py b/server/backend/mlx.py index d6c3395..5899096 100644 --- a/server/backend/mlx.py +++ b/server/backend/mlx.py @@ -29,6 +29,10 @@ from ..reasoning_utils import ReasoningExtractor from ..cache_utils import get_model_path from ..schemas import ( + CAssistantMessageItemParam, + CDeveloperMessageItemParam, + CSystemMessageItemParam, + CUserMessageItemParam, ChatCompletionRequest, ChatMessage, GenerationMetrics, @@ -721,7 +725,7 @@ def build_harmony_conversation( for item in convos: print(f"ITEM {item}") match item: - case UserMessageItemParam(): + case CUserMessageItemParam(): content = "" if isinstance(item.content, list): content = item.content[0].text @@ -730,7 +734,7 @@ def build_harmony_conversation( convo_list.append( Message.from_role_and_content(Role.USER, content) # pyright: ignore ) - case DeveloperMessageItemParam(): + case CDeveloperMessageItemParam(): convo_list.append( Message.from_role_and_content( Role.DEVELOPER, @@ -739,13 +743,13 @@ def build_harmony_conversation( ), # pyright: ignore ) ) ) - case AssistantMessageItemParam(): + case CAssistantMessageItemParam(): convo_list.append( Message.from_role_and_content( Role.ASSISTANT, item.content.root ) # pyright: ignore ) - case SystemMessageItemParam(): + case CSystemMessageItemParam(): convo_list.append( Message.from_role_and_content(Role.SYSTEM, item.content.root) ) diff --git a/server/pyproject.toml b/server/pyproject.toml index 3ad4e4f..62dc5d4 100644 --- a/server/pyproject.toml +++ b/server/pyproject.toml @@ -21,4 +21,4 @@ build-backend = "setuptools.build_meta" exclude = ["backend", "backend.*"] [tool.uv] -exclude-newer="10 days" +exclude-newer="2026-04-05T09:43:13.300458+00:00" diff --git a/server/schemas.py b/server/schemas.py index 0c1c131..771a681 100644 --- a/server/schemas.py +++ b/server/schemas.py @@ -1,6 +1,6 @@ from dataclasses import dataclass from enum import Enum, auto -from typing import Any, Dict, List, Union, override +from typing import Any, Dict, List, Literal, Union, override from openresponses_types import ReasoningParam, TruncationEnum from openresponses_types.types import ( @@ -88,6 +88,30 @@ class downloadRequest(BaseModel): model: str +class CUserMessageItemParam(UserMessageItemParam): + type: Literal["message"] = "message" + + +class CSystemMessageItemParam(SystemMessageItemParam): + type: Literal["message"] = "message" + + +class CDeveloperMessageItemParam(DeveloperMessageItemParam): + type: Literal["message"] = "message" + + +class CAssistantMessageItemParam(AssistantMessageItemParam): + type: Literal["message"] = "message" + + +class CFunctionCallItemParam(FunctionCallItemParam): + type: Literal["function_call"] = "function_call" + + +class CFunctionCallOutputItemParam(FunctionCallOutputItemParam): + type: Literal["function_call_output"] = "function_call_output" + + class ResponsesRequest(BaseModel): model: str = "mlx-community/gpt-oss-20b-MXFP4-Q4" input: ( @@ -95,12 +119,12 @@ class ResponsesRequest(BaseModel): | list[ ItemReferenceParam | ReasoningItemParam - | UserMessageItemParam - | SystemMessageItemParam - | DeveloperMessageItemParam - | AssistantMessageItemParam - | FunctionCallItemParam - | FunctionCallOutputItemParam + | CUserMessageItemParam + | CSystemMessageItemParam + | CDeveloperMessageItemParam + | CAssistantMessageItemParam + | CFunctionCallItemParam + | CFunctionCallOutputItemParam ] ) reasoning: ReasoningParam = ReasoningParam( diff --git a/server/stack/requirements/app-server/pylock.app-server.meta.json b/server/stack/requirements/app-server/pylock.app-server.meta.json index cfa1b88..25fd480 100644 --- a/server/stack/requirements/app-server/pylock.app-server.meta.json +++ b/server/stack/requirements/app-server/pylock.app-server.meta.json @@ -1,5 +1,5 @@ { - "lock_input_hash": "sha256:d7a2f2a68301eb65d01fc121715ef8b96a6af48a315d580340d976fde6e1dc47", + "lock_input_hash": "sha256:3c4cafa1e5147c3de588c39652d8ba0046be8d1bea5ae8a547a7bb8b1bb9e734", "lock_version": 1, "locked_at": "2026-04-05T09:43:13.300458+00:00", "other_inputs_hash": "sha256:63b3c2cfe2ec414938e81dace7aac779c7b902bae681618cd8827e9f16880985", diff --git a/server/stack/requirements/app-server/requirements-app-server.in b/server/stack/requirements/app-server/requirements-app-server.in index 346cc5f..8bfa8b9 100644 --- a/server/stack/requirements/app-server/requirements-app-server.in +++ b/server/stack/requirements/app-server/requirements-app-server.in @@ -6,3 +6,4 @@ mlx-lm==0.31.0 black==25.9.0 openai-harmony==0.0.8 openresponses-types +httpx==0.28.1 diff --git a/server/stack/venvstacks.toml b/server/stack/venvstacks.toml index db30051..6d67ecb 100644 --- a/server/stack/venvstacks.toml +++ b/server/stack/venvstacks.toml @@ -27,10 +27,13 @@ requirements = [ "mlx-lm==0.31.0", "black==25.9.0", "openai-harmony==0.0.8", - "openresponses-types" + "openresponses-types", + "httpx==0.28.1" ] [tool.uv] +exclude-newer="2026-04-05T09:43:13.300458+00:00" + # Only resolve for the relevant target platforms environments = [ "sys_platform == 'darwin' and platform_machine == 'arm64'", diff --git a/tiles/src/core/chats.rs b/tiles/src/core/chats.rs index a1927cd..adcd7e9 100644 --- a/tiles/src/core/chats.rs +++ b/tiles/src/core/chats.rs @@ -154,7 +154,7 @@ pub fn get_delta(conn: &Connection, user_id: &str, last_row_couter: i64) -> Resu let session_id: String = session_id_db.unwrap_or("".to_owned()); - if session_id.len() > 0 && !session_map.contains_key(&session_id) { + if !session_id.is_empty() && !session_map.contains_key(&session_id) { // lets fetch the session details match fetch_session(conn, &session_id) { Ok(session) => { @@ -187,10 +187,7 @@ pub fn get_delta(conn: &Connection, user_id: &str, last_row_couter: i64) -> Resu let sessions: Vec = session_map.into_values().collect(); - Ok(DeltaChat { - chats: chats, - sessions: sessions, - }) + Ok(DeltaChat { chats, sessions }) } pub fn apply_delta(chat_conn: &mut Connection, delta_chats: DeltaChat) -> Result<()> { diff --git a/tiles/src/runtime/mlx.rs b/tiles/src/runtime/mlx.rs index a9b0795..3abe0e1 100644 --- a/tiles/src/runtime/mlx.rs +++ b/tiles/src/runtime/mlx.rs @@ -1,4 +1,4 @@ -use crate::core::accounts::{User, get_current_user}; +use crate::core::accounts::get_current_user; use crate::core::chats::{Message, create_session, save_chat}; use crate::core::storage::db::Dbconn; use crate::runtime::RunArgs; @@ -37,6 +37,7 @@ pub struct BenchmarkMetrics { total_latency_s: f64, } +#[allow(dead_code)] impl BenchmarkMetrics { fn update(&mut self, metrics: BenchmarkMetrics) -> &Self { if self.ttft_ms == 0.0 { @@ -323,7 +324,7 @@ async fn run_model_with_server( async fn start_repl( mlx_runtime: &MLXRuntime, modelfile: &Modelfile, - run_args: &RunArgs, + _run_args: &RunArgs, db_conn: &Dbconn, ) -> Result<()> { let modelname = modelfile @@ -337,10 +338,10 @@ async fn start_repl( let config = Config::builder().auto_add_history(true).build(); let mut editor = Editor::::with_config(config).unwrap(); editor.set_helper(Some(TilesHinter)); - let mut g_reply: String = "".to_owned(); - let mut prev_response_id: String = String::from(""); + // let mut g_reply: String = "".to_owned(); + // let mut prev_response_id: String = String::from(""); - let mut conversations: Vec = vec![]; + // let mut conversations: Vec = vec![]; let mut pi_process = start_pi_rpc(&modelname)?; let mut session_id = String::new(); @@ -361,7 +362,6 @@ async fn start_repl( let state: GetStateData = serde_json::from_value(msg.data.expect("get state parsing failed"))?; session_id = state.session_id; - info!("Current session: {}", session_id); } loop { @@ -432,15 +432,12 @@ async fn start_repl( } } - let mut remaining_count = run_args.relay_count; - let mut python_code: String = "".to_owned(); - let mut bench_metrics: BenchmarkMetrics = BenchmarkMetrics { - ttft_ms: 0.0, - total_tokens: 0, - tokens_per_second: 0.0, - total_latency_s: 0.0, - }; - let mut is_agent_streaming: bool = false; + // let mut bench_metrics: BenchmarkMetrics = BenchmarkMetrics { + // ttft_ms: 0.0, + // total_tokens: 0, + // tokens_per_second: 0.0, + // total_latency_s: 0.0, + // }; let reader = BufReader::new(&mut stdout); let mut session_turn_count = 0; let mut last_chat_id: String = "".to_owned(); @@ -472,7 +469,7 @@ async fn start_repl( // first time if session_turn_count == 1 { info!("Created session {}", session_id); - create_session(&db_conn.chat, &session_id, "dummy", ¤t_user.user_id)?; + create_session(&db_conn.chat, &session_id, &input, ¤t_user.user_id)?; } let parent_chat_id = if session_turn_count == 1 { None @@ -513,7 +510,6 @@ async fn start_repl( if response_msg.success { match response_msg.command { CommandType::Unknown => { - println!("{}", line); continue; } cmd => process_command(cmd, response_msg.data)?, @@ -819,6 +815,7 @@ async fn load_model( // } // } +#[allow(dead_code)] fn extract_python(content: &str) -> String { if content.contains("") && content.contains("") { let list_a = content.split("").collect::>(); @@ -853,6 +850,8 @@ fn get_default_modelfile(memory_mode: bool) -> Result { } } +//TODO: Deprecated if not needed +#[allow(dead_code)] fn create_chat_input(input: &str, prompt: &str, conversations: &[Message]) -> Vec { let dev_msg = Message { r#type: "message".to_owned(), diff --git a/tiles/src/utils/config.rs b/tiles/src/utils/config.rs index 6030c3d..5887b07 100644 --- a/tiles/src/utils/config.rs +++ b/tiles/src/utils/config.rs @@ -443,7 +443,7 @@ pub fn create_pi_provider_config(model_name: &str, enpoint_base_url: &str) -> Re #[allow(dead_code)] fn try_update_pi_provider_model(config: &str, model_name: &str) -> Result { - let mut pi_model_config: PiModelConfig = serde_json::from_str(&config)?; + let mut pi_model_config: PiModelConfig = serde_json::from_str(config)?; let mut tiles_provider_config: PiProviderConfig = pi_model_config .providers .get("tiles")