Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions core/java/android/rag/IToolRegistry.aidl
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
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.
*
* 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.
*/
Comment on lines +7 to +17
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The AIDL docs say "All return values are JSON strings. Null = not found / store not ready", but listTools()/searchTools() are implemented to return "[]" when the store isn't ready or the query is empty. Please align the documentation with the actual contract (or change the implementation to match the docs).

Copilot uses AI. Check for mistakes.
interface IToolRegistry {

/**
* 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();

/**
* 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);
}
6 changes: 6 additions & 0 deletions services/core/java/com/android/server/rag/Android.bp
Original file line number Diff line number Diff line change
@@ -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: [
Expand Down
55 changes: 55 additions & 0 deletions services/core/java/com/android/server/rag/RagService.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -14,6 +15,7 @@
import com.android.server.rag.indexing.RagIndexWorker;
import com.android.server.rag.model.SourceFile;
import com.android.server.rag.tools.ToolDispatcher;
import com.android.server.rag.tools.ToolRecord;
import com.android.server.rag.tools.ToolScannerService;

/**
Expand Down Expand Up @@ -59,6 +61,7 @@ public RagService(Context context) {
@Override
public void onStart() {
publishBinderService("rag", mBinder);
publishBinderService("jarvis_tools", mToolRegistryBinder);
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

jarvis_tools is being published as a public Binder service, but enforceCallingPermission() currently only logs the caller UID and does not enforce any permission/allowlist. This effectively exposes the full tool registry (and semantic search) to any app on the device. Please enforce an appropriate signature/system permission (or otherwise restrict access) before exposing this service.

Suggested change
publishBinderService("jarvis_tools", mToolRegistryBinder);
Log.w(TAG, "Not publishing jarvis_tools Binder service: caller permission enforcement is not implemented");

Copilot uses AI. Check for mistakes.
initializeAsync();
}

Expand Down Expand Up @@ -204,4 +207,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<ToolRecord> all = JarvisStore.box(ToolRecord.class).getAll();
return ToolDispatcher.serializeTools(all);
} catch (Exception e) {
Comment on lines +217 to +224
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Binder transactions have a hard size limit (~1MB). Returning all tools as one JSON string (and including potentially large fields like rawDefinition/paramsJson) risks TransactionTooLargeException as the registry grows. Consider adding paging (offset/limit), a lightweight summary listing, and/or omitting large fields from listTools() results.

Copilot uses AI. Check for mistakes.
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", "...");
}
};
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,68 @@ protected void onReceiveResult(int resultCode, Bundle resultData) {
return result;
}

// -------------------------------------------------------------------------
// Public search API (used by IToolRegistry)
// -------------------------------------------------------------------------

/**
* Search for tools matching the query without dispatching.
* Returns a JSON array string (never null; returns "[]" on empty/failure).
*/
public String searchTools(String query) {
if (query == null || query.trim().isEmpty()) return "[]";
List<ToolRecord> results = semanticSearch(query);
return serializeTools(results);
}
Comment on lines +350 to +358
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Javadoc says searchTools returns "null on failure", but the implementation always returns a JSON array string for non-empty queries (and will throw if an unexpected runtime exception occurs). Please update the comment to reflect the real behavior/contract (or handle exceptions and return a consistent failure value).

Copilot uses AI. Check for mistakes.

/**
* 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();
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;
} catch (JSONException 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<ToolRecord> tools) {
JSONArray arr = new JSONArray();
for (ToolRecord t : tools) {
JSONObject obj = serializeToolObject(t);
if (obj != null) arr.put(obj);
}
Comment on lines +398 to +404
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

serializeTools() builds each element by calling serializeTool() (which produces a JSON string) and then immediately parsing that string back into a JSONObject. This adds avoidable overhead. Prefer constructing the JSONObject once (e.g., have serializeTool return a JSONObject or accept a JSONArray to append into) to avoid the stringify/parse roundtrip.

Copilot uses AI. Check for mistakes.
return arr.toString();
}

// -------------------------------------------------------------------------
// Internal data class
// -------------------------------------------------------------------------
Expand Down