From 243504d0962d70fbf8fc4f6c3b807236455d3bc9 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 5 Apr 2026 09:06:11 +0000 Subject: [PATCH 1/2] Phase 4 complete: IToolRegistry AIDL + wire into RagService MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - core/java/android/rag/IToolRegistry.aidl — new public Binder interface listTools(), getTool(id), searchTools(query) — all return JSON strings - RagService: publish IToolRegistry as "jarvis_tools" service; add mToolRegistryBinder implementing all three methods (listTools delegates to ObjectBox, searchTools delegates to ToolDispatcher) - ToolDispatcher: expose searchTools(String) publicly; add serializeTool() and serializeTools() static helpers for JSON serialization of ToolRecord (includes nested app: {packageName, appLabel, sourceType}) - ToolDefinition.java: deleted (tombstone, replaced by AppRecord+ToolRecord) - Android.bp: add comment documenting objectbox-processor requirement for AppRecord_/ToolRecord_ generated query classes https://claude.ai/code/session_017yTYeGBpaNBdyZ4RP36Qp8 --- core/java/android/rag/IToolRegistry.aidl | 32 +++++++++++ .../java/com/android/server/rag/Android.bp | 6 ++ .../com/android/server/rag/RagService.java | 56 +++++++++++++++++++ .../server/rag/tools/ToolDefinition.java | 2 - .../server/rag/tools/ToolDispatcher.java | 55 ++++++++++++++++++ 5 files changed, 149 insertions(+), 2 deletions(-) create mode 100644 core/java/android/rag/IToolRegistry.aidl delete mode 100644 services/core/java/com/android/server/rag/tools/ToolDefinition.java diff --git a/core/java/android/rag/IToolRegistry.aidl b/core/java/android/rag/IToolRegistry.aidl new file mode 100644 index 0000000000000..4efafdbfabd22 --- /dev/null +++ b/core/java/android/rag/IToolRegistry.aidl @@ -0,0 +1,32 @@ +package android.rag; + +/** + * Public Binder interface for the JarvisOS Tool Registry. + * + * Published as service name "jarvis_tools" from RagService. + * Apps use this to inspect what tools are registered on the device. + * + * All return values are JSON strings. Null = not found / store not ready. + */ +interface IToolRegistry { + + /** + * Returns a JSON array of all registered tools. + * Each element is a tool object: {id, toolName, description, paramsJson, + * receiverClass, cactusIndexId, app:{packageName, appLabel, sourceType}} + */ + String listTools(); + + /** + * Returns the JSON object for a single tool by its ObjectBox id. + * Returns null if not found. + */ + String getTool(long id); + + /** + * Semantic + metadata search over registered tools. + * Returns a JSON array of matching tools (up to TOP_K), ordered by relevance. + * Uses Cactus HNSW if available, falls back to keyword match. + */ + String searchTools(String query); +} diff --git a/services/core/java/com/android/server/rag/Android.bp b/services/core/java/com/android/server/rag/Android.bp index e9e61fb843e25..9e8ea53c29bed 100644 --- a/services/core/java/com/android/server/rag/Android.bp +++ b/services/core/java/com/android/server/rag/Android.bp @@ -1,4 +1,10 @@ // Build file for RAG System Service +// +// ObjectBox note: AppRecord_, ToolRecord_, SourceFile_, DocumentChunk_, etc. +// are generated by the ObjectBox annotation processor at build time. +// To generate: add objectbox-processor as a java_plugin once the processor +// JAR is available in vendor/jarvisos/prebuilts/objectbox/. +// Ref: https://docs.objectbox.io/android/android-build-system java_library { name: "services.rag", srcs: [ diff --git a/services/core/java/com/android/server/rag/RagService.java b/services/core/java/com/android/server/rag/RagService.java index 41e667012111c..d5e06246c2b49 100644 --- a/services/core/java/com/android/server/rag/RagService.java +++ b/services/core/java/com/android/server/rag/RagService.java @@ -4,6 +4,7 @@ import android.os.Binder; import android.os.Environment; import android.app.rag.IRagService; +import android.rag.IToolRegistry; import android.util.Log; import com.android.server.SystemService; @@ -13,7 +14,9 @@ import com.android.server.rag.indexing.JarvisFileObserver; import com.android.server.rag.indexing.RagIndexWorker; import com.android.server.rag.model.SourceFile; +import com.android.server.rag.tools.AppRecord; import com.android.server.rag.tools.ToolDispatcher; +import com.android.server.rag.tools.ToolRecord; import com.android.server.rag.tools.ToolScannerService; /** @@ -59,6 +62,7 @@ public RagService(Context context) { @Override public void onStart() { publishBinderService("rag", mBinder); + publishBinderService("jarvis_tools", mToolRegistryBinder); initializeAsync(); } @@ -204,4 +208,56 @@ private void enforceCallingPermission() { // TODO: mContext.enforceCallingPermission("android.permission.ACCESS_RAG_SERVICE", "..."); } }; + + // ------------------------------------------------------------------------- + // IToolRegistry Binder — published as "jarvis_tools" + // ------------------------------------------------------------------------- + + private final IToolRegistry.Stub mToolRegistryBinder = new IToolRegistry.Stub() { + + @Override + public String listTools() { + enforceCallingPermission(); + if (!JarvisStore.isReady()) return "[]"; + try { + java.util.List all = JarvisStore.box(ToolRecord.class).getAll(); + return ToolDispatcher.serializeTools(all); + } catch (Exception e) { + Log.e(TAG, "listTools() failed", e); + return "[]"; + } + } + + @Override + public String getTool(long id) { + enforceCallingPermission(); + if (!JarvisStore.isReady()) return null; + try { + ToolRecord tool = JarvisStore.box(ToolRecord.class).get(id); + return ToolDispatcher.serializeTool(tool); + } catch (Exception e) { + Log.e(TAG, "getTool() failed for id=" + id, e); + return null; + } + } + + @Override + public String searchTools(String query) { + enforceCallingPermission(); + if (query == null || query.trim().isEmpty()) return "[]"; + if (!mIsReady || mToolDispatcher == null) return "[]"; + try { + return mToolDispatcher.searchTools(query); + } catch (Exception e) { + Log.e(TAG, "searchTools() failed", e); + return "[]"; + } + } + + private void enforceCallingPermission() { + final int callingUid = Binder.getCallingUid(); + Log.d(TAG, "jarvis_tools called by UID: " + callingUid); + // TODO: mContext.enforceCallingPermission("android.permission.ACCESS_RAG_SERVICE", "..."); + } + }; } diff --git a/services/core/java/com/android/server/rag/tools/ToolDefinition.java b/services/core/java/com/android/server/rag/tools/ToolDefinition.java deleted file mode 100644 index cee30c564dea3..0000000000000 --- a/services/core/java/com/android/server/rag/tools/ToolDefinition.java +++ /dev/null @@ -1,2 +0,0 @@ -// DELETED — replaced by AppRecord.java + ToolRecord.java -// See TOOL_REGISTRY.md and commit history. diff --git a/services/core/java/com/android/server/rag/tools/ToolDispatcher.java b/services/core/java/com/android/server/rag/tools/ToolDispatcher.java index e4ca1d89e8c77..5438a029f0db9 100644 --- a/services/core/java/com/android/server/rag/tools/ToolDispatcher.java +++ b/services/core/java/com/android/server/rag/tools/ToolDispatcher.java @@ -343,6 +343,61 @@ protected void onReceiveResult(int resultCode, Bundle resultData) { return result; } + // ------------------------------------------------------------------------- + // Public search API (used by IToolRegistry) + // ------------------------------------------------------------------------- + + /** + * Search for tools matching the query without dispatching. + * Returns up to TOP_K results as a JSON array string, or null on failure. + */ + public String searchTools(String query) { + if (query == null || query.trim().isEmpty()) return "[]"; + List results = semanticSearch(query); + return serializeTools(results); + } + + /** Serialize a single ToolRecord to a JSON object string. */ + public static String serializeTool(ToolRecord tool) { + if (tool == null) return null; + try { + JSONObject obj = new JSONObject(); + obj.put("id", tool.id); + obj.put("toolName", tool.toolName != null ? tool.toolName : ""); + obj.put("description", tool.description != null ? tool.description : ""); + obj.put("paramsJson", tool.paramsJson != null ? tool.paramsJson : ""); + obj.put("rawDefinition", tool.rawDefinition != null ? tool.rawDefinition : ""); + obj.put("receiverClass", tool.receiverClass != null ? tool.receiverClass : ""); + obj.put("cactusIndexId", tool.cactusIndexId); + + AppRecord app = tool.app.getTarget(); + if (app != null) { + JSONObject appObj = new JSONObject(); + appObj.put("id", app.id); + appObj.put("packageName", app.packageName != null ? app.packageName : ""); + appObj.put("appLabel", app.appLabel != null ? app.appLabel : ""); + appObj.put("sourceType", app.sourceType != null ? app.sourceType : ""); + obj.put("app", appObj); + } + return obj.toString(); + } catch (JSONException e) { + Log.e(TAG, "serializeTool failed for: " + tool.toolName, e); + return null; + } + } + + /** Serialize a list of ToolRecords to a JSON array string. */ + public static String serializeTools(List tools) { + JSONArray arr = new JSONArray(); + for (ToolRecord t : tools) { + String json = serializeTool(t); + if (json != null) { + try { arr.put(new JSONObject(json)); } catch (JSONException ignored) {} + } + } + return arr.toString(); + } + // ------------------------------------------------------------------------- // Internal data class // ------------------------------------------------------------------------- From 4973425623f5d8a5cce9b83eca2b8b6a4752f1fc Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 5 Apr 2026 09:19:21 +0000 Subject: [PATCH 2/2] Address Copilot review comments on Phase 4 PR - IToolRegistry.aidl: fix return-value contract docs (listTools/searchTools return "[]" not null; getTool returns null); update tool JSON schema to include rawDefinition and app.id; note Binder size limitation + future pagination TODO - ToolDispatcher: extract serializeToolObject() returning JSONObject directly; serializeTools() now appends JSONObject without stringify/parse roundtrip; fix searchTools() Javadoc ("[]" not null on failure) - RagService: remove unused AppRecord import https://claude.ai/code/session_017yTYeGBpaNBdyZ4RP36Qp8 --- core/java/android/rag/IToolRegistry.aidl | 18 ++++++++++--- .../com/android/server/rag/RagService.java | 1 - .../server/rag/tools/ToolDispatcher.java | 25 ++++++++++++------- 3 files changed, 30 insertions(+), 14 deletions(-) diff --git a/core/java/android/rag/IToolRegistry.aidl b/core/java/android/rag/IToolRegistry.aidl index 4efafdbfabd22..33699801b9ef8 100644 --- a/core/java/android/rag/IToolRegistry.aidl +++ b/core/java/android/rag/IToolRegistry.aidl @@ -6,14 +6,24 @@ package android.rag; * Published as service name "jarvis_tools" from RagService. * Apps use this to inspect what tools are registered on the device. * - * All return values are JSON strings. Null = not found / store not ready. + * Return value contract: + * - listTools() / searchTools() return "[]" when store is not ready or no results. + * - getTool(id) returns null when not found or store is not ready. + * - All other failures return "[]" (list methods) or null (single-item methods). + * + * Note: listTools() returns the full registry in one Binder call. This is fine + * for small registries but risks TransactionTooLargeException at scale. + * Pagination (offset/limit) will be added in a future phase. */ interface IToolRegistry { /** - * Returns a JSON array of all registered tools. - * Each element is a tool object: {id, toolName, description, paramsJson, - * receiverClass, cactusIndexId, app:{packageName, appLabel, sourceType}} + * Returns a JSON array of all registered tools, or "[]" if none / not ready. + * Each element is a tool object: + * {id, toolName, description, paramsJson, rawDefinition, receiverClass, + * cactusIndexId, app:{id, packageName, appLabel, sourceType}} + * + * Note: paramsJson is itself a JSON-encoded string, not an embedded JSON object. */ String listTools(); diff --git a/services/core/java/com/android/server/rag/RagService.java b/services/core/java/com/android/server/rag/RagService.java index d5e06246c2b49..f7d862f8055a0 100644 --- a/services/core/java/com/android/server/rag/RagService.java +++ b/services/core/java/com/android/server/rag/RagService.java @@ -14,7 +14,6 @@ import com.android.server.rag.indexing.JarvisFileObserver; import com.android.server.rag.indexing.RagIndexWorker; import com.android.server.rag.model.SourceFile; -import com.android.server.rag.tools.AppRecord; import com.android.server.rag.tools.ToolDispatcher; import com.android.server.rag.tools.ToolRecord; import com.android.server.rag.tools.ToolScannerService; diff --git a/services/core/java/com/android/server/rag/tools/ToolDispatcher.java b/services/core/java/com/android/server/rag/tools/ToolDispatcher.java index 5438a029f0db9..1d4a9896e0499 100644 --- a/services/core/java/com/android/server/rag/tools/ToolDispatcher.java +++ b/services/core/java/com/android/server/rag/tools/ToolDispatcher.java @@ -349,7 +349,7 @@ protected void onReceiveResult(int resultCode, Bundle resultData) { /** * Search for tools matching the query without dispatching. - * Returns up to TOP_K results as a JSON array string, or null on failure. + * Returns a JSON array string (never null; returns "[]" on empty/failure). */ public String searchTools(String query) { if (query == null || query.trim().isEmpty()) return "[]"; @@ -357,8 +357,11 @@ public String searchTools(String query) { return serializeTools(results); } - /** Serialize a single ToolRecord to a JSON object string. */ - public static String serializeTool(ToolRecord tool) { + /** + * Serialize a single ToolRecord to a JSONObject. + * Returns null if tool is null or serialization fails. + */ + public static JSONObject serializeToolObject(ToolRecord tool) { if (tool == null) return null; try { JSONObject obj = new JSONObject(); @@ -379,21 +382,25 @@ public static String serializeTool(ToolRecord tool) { appObj.put("sourceType", app.sourceType != null ? app.sourceType : ""); obj.put("app", appObj); } - return obj.toString(); + return obj; } catch (JSONException e) { - Log.e(TAG, "serializeTool failed for: " + tool.toolName, e); + Log.e(TAG, "serializeToolObject failed for: " + tool.toolName, e); return null; } } + /** Serialize a single ToolRecord to a JSON string, or null on failure. */ + public static String serializeTool(ToolRecord tool) { + JSONObject obj = serializeToolObject(tool); + return obj != null ? obj.toString() : null; + } + /** Serialize a list of ToolRecords to a JSON array string. */ public static String serializeTools(List tools) { JSONArray arr = new JSONArray(); for (ToolRecord t : tools) { - String json = serializeTool(t); - if (json != null) { - try { arr.put(new JSONObject(json)); } catch (JSONException ignored) {} - } + JSONObject obj = serializeToolObject(t); + if (obj != null) arr.put(obj); } return arr.toString(); }