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
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,28 @@
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.adk.agents.ReadonlyContext;
import com.google.adk.tools.BaseTool;
import com.google.adk.tools.BaseToolset;
import com.google.adk.tools.applicationintegrationtoolset.IntegrationConnectorTool.DefaultHttpExecutor;
import com.google.adk.tools.applicationintegrationtoolset.IntegrationConnectorTool.HttpExecutor;
import io.reactivex.rxjava3.core.Flowable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.jspecify.annotations.Nullable;

/** Application Integration Toolset */
public class ApplicationIntegrationToolset {
public class ApplicationIntegrationToolset implements BaseToolset {
String project;
String location;
@Nullable String integration;
@Nullable List<String> triggers;
@Nullable String connection;
@Nullable Map<String, List<String>> entityOperations;
@Nullable List<String> actions;
String serviceAccountJson;
@Nullable String toolNamePrefix;
@Nullable String toolInstructions;
public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
Expand All @@ -35,16 +39,16 @@ public class ApplicationIntegrationToolset {
*
* <p>integrationTool = new ApplicationIntegrationToolset( project="test-project",
* location="us-central1", integration="test-integration",
* triggers=ImmutableList.of("api_trigger/test_trigger",
* "api_trigger/test_trigger_2"),connection=null,enitityOperations=null,actions=null,toolNamePrefix="test-integration-tool",toolInstructions="This
* triggers=ImmutableList.of("api_trigger/test_trigger", "api_trigger/test_trigger_2",
* serviceAccountJson="{....}"),connection=null,enitityOperations=null,actions=null,toolNamePrefix="test-integration-tool",toolInstructions="This
* tool is used to get response from test-integration.");
*
* <p>connectionTool = new ApplicationIntegrationToolset( project="test-project",
* location="us-central1", integration=null, triggers=null, connection="test-connection",
* entityOperations=ImmutableMap.of("Entity1", ImmutableList.of("LIST", "GET", "UPDATE")),
* "Entity2", ImmutableList.of()), actions=ImmutableList.of("ExecuteCustomQuery"),
* toolNamePrefix="test-tool", toolInstructions="This tool is used to list, get and update issues
* in Jira.");
* serviceAccountJson="{....}", toolNamePrefix="test-tool", toolInstructions="This tool is used to
* list, get and update issues in Jira.");
*
* @param project The GCP project ID.
* @param location The GCP location of integration.
Expand All @@ -53,6 +57,9 @@ public class ApplicationIntegrationToolset {
* @param connection(Optional) The connection name.
* @param entityOperations(Optional) The entity operations.
* @param actions(Optional) The actions.
* @param serviceAccountJson(Optional) The service account configuration as a dictionary. Required
* if not using default service credential. Used for fetching the Application Integration or
* Integration Connector resource.
* @param toolNamePrefix(Optional) The tool name prefix.
* @param toolInstructions(Optional) The tool instructions.
*/
Expand All @@ -64,6 +71,7 @@ public ApplicationIntegrationToolset(
String connection,
Map<String, List<String>> entityOperations,
List<String> actions,
String serviceAccountJson,
String toolNamePrefix,
String toolInstructions) {
this(
Expand All @@ -74,9 +82,10 @@ public ApplicationIntegrationToolset(
connection,
entityOperations,
actions,
serviceAccountJson,
toolNamePrefix,
toolInstructions,
new DefaultHttpExecutor());
new DefaultHttpExecutor().createExecutor(serviceAccountJson));
}

ApplicationIntegrationToolset(
Expand All @@ -87,6 +96,7 @@ public ApplicationIntegrationToolset(
String connection,
Map<String, List<String>> entityOperations,
List<String> actions,
String serviceAccountJson,
String toolNamePrefix,
String toolInstructions,
HttpExecutor httpExecutor) {
Expand All @@ -97,6 +107,7 @@ public ApplicationIntegrationToolset(
this.connection = connection;
this.entityOperations = entityOperations;
this.actions = actions;
this.serviceAccountJson = serviceAccountJson;
this.toolNamePrefix = toolNamePrefix;
this.toolInstructions = toolInstructions;
this.httpExecutor = httpExecutor;
Expand All @@ -121,7 +132,7 @@ List<String> getPathUrl(String openApiSchemaString) throws Exception {
return pathUrls;
}

public List<BaseTool> getTools() throws Exception {
private List<BaseTool> getAllTools() throws Exception {
String openApiSchemaString = null;
List<BaseTool> tools = new ArrayList<>();
if (!isNullOrEmpty(this.integration)) {
Expand All @@ -142,7 +153,15 @@ public List<BaseTool> getTools() throws Exception {
if (toolName != null) {
tools.add(
new IntegrationConnectorTool(
openApiSchemaString, pathUrl, toolName, "", null, null, null, this.httpExecutor));
openApiSchemaString,
pathUrl,
toolName,
toolInstructions,
null,
null,
null,
this.serviceAccountJson,
this.httpExecutor));
}
}
} else if (!isNullOrEmpty(this.connection)
Expand Down Expand Up @@ -182,6 +201,7 @@ public List<BaseTool> getTools() throws Exception {
connectionDetails.name,
connectionDetails.serviceName,
connectionDetails.host,
this.serviceAccountJson,
this.httpExecutor));
}
}
Expand All @@ -193,4 +213,19 @@ public List<BaseTool> getTools() throws Exception {

return tools;
}

@Override
public Flowable<BaseTool> getTools(@Nullable ReadonlyContext readonlyContext) {
try {
List<BaseTool> allTools = getAllTools();
return Flowable.fromIterable(allTools);
} catch (Exception e) {
return Flowable.error(e);
}
}

@Override
public void close() throws Exception {
// Nothing to close.
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.google.adk.tools.applicationintegrationtoolset;

import static com.google.common.base.Strings.isNullOrEmpty;
import static java.nio.charset.StandardCharsets.UTF_8;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
Expand All @@ -15,7 +16,9 @@
import com.google.genai.types.FunctionDeclaration;
import com.google.genai.types.Schema;
import io.reactivex.rxjava3.core.Single;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
Expand Down Expand Up @@ -45,10 +48,27 @@ <T> HttpResponse<T> send(HttpRequest request, HttpResponse.BodyHandler<T> respon
throws IOException, InterruptedException;

String getToken() throws IOException;

public HttpExecutor createExecutor(String serviceAccountJson);
}

static class DefaultHttpExecutor implements HttpExecutor {
private final HttpClient client = HttpClient.newHttpClient();
private final String serviceAccountJson;

/** Default constructor for when no service account is specified. */
DefaultHttpExecutor() {
this(null);
}

/**
* Constructor that accepts an optional service account JSON string.
*
* @param serviceAccountJson The service account key as a JSON string, or null.
*/
DefaultHttpExecutor(@Nullable String serviceAccountJson) {
this.serviceAccountJson = serviceAccountJson;
}

@Override
public <T> HttpResponse<T> send(
Expand All @@ -59,12 +79,42 @@ public <T> HttpResponse<T> send(

@Override
public String getToken() throws IOException {
GoogleCredentials credentials =
GoogleCredentials.getApplicationDefault()
.createScoped("https://www.googleapis.com/auth/cloud-platform");
GoogleCredentials credentials;

if (this.serviceAccountJson != null && !this.serviceAccountJson.trim().isEmpty()) {
try (InputStream is = new ByteArrayInputStream(this.serviceAccountJson.getBytes(UTF_8))) {
credentials =
GoogleCredentials.fromStream(is)
.createScoped("https://www.googleapis.com/auth/cloud-platform");
} catch (IOException e) {
throw new IOException("Failed to load credentials from service_account_json.", e);
}
} else {
try {
credentials =
GoogleCredentials.getApplicationDefault()
.createScoped("https://www.googleapis.com/auth/cloud-platform");
} catch (IOException e) {
throw new IOException(
"Please provide a service account or configure Application Default Credentials. To"
+ " set up ADC, see"
+ " https://cloud.google.com/docs/authentication/external/set-up-adc.",
e);
}
}

credentials.refreshIfExpired();
return credentials.getAccessToken().getTokenValue();
}

@Override
public HttpExecutor createExecutor(String serviceAccountJson) {
if (isNullOrEmpty(serviceAccountJson)) {
return new DefaultHttpExecutor();
} else {
return new DefaultHttpExecutor(serviceAccountJson);
}
}
}

private static final ImmutableList<String> EXCLUDE_FIELDS =
Expand All @@ -75,7 +125,11 @@ public String getToken() throws IOException {

/** Constructor for Application Integration Tool for integration */
IntegrationConnectorTool(
String openApiSpec, String pathUrl, String toolName, String toolDescription) {
String openApiSpec,
String pathUrl,
String toolName,
String toolDescription,
String serviceAccountJson) {
this(
openApiSpec,
pathUrl,
Expand All @@ -84,7 +138,8 @@ public String getToken() throws IOException {
null,
null,
null,
new DefaultHttpExecutor());
serviceAccountJson,
new DefaultHttpExecutor().createExecutor(serviceAccountJson));
}

/**
Expand All @@ -98,7 +153,8 @@ public String getToken() throws IOException {
String toolDescription,
String connectionName,
String serviceName,
String host) {
String host,
String serviceAccountJson) {
this(
openApiSpec,
pathUrl,
Expand All @@ -107,7 +163,8 @@ public String getToken() throws IOException {
connectionName,
serviceName,
host,
new DefaultHttpExecutor());
serviceAccountJson,
new DefaultHttpExecutor().createExecutor(serviceAccountJson));
}

IntegrationConnectorTool(
Expand All @@ -118,6 +175,7 @@ public String getToken() throws IOException {
@Nullable String connectionName,
@Nullable String serviceName,
@Nullable String host,
@Nullable String serviceAccountJson,
HttpExecutor httpExecutor) {
super(toolName, toolDescription);
this.openApiSpec = openApiSpec;
Expand Down Expand Up @@ -182,7 +240,7 @@ public Single<Map<String, Object>> runAsync(Map<String, Object> args, ToolContex
}

private String executeIntegration(Map<String, Object> args) throws Exception {
String url = String.format("https://integrations.googleapis.com%s", pathUrl);
String url = String.format("https://integrations.googleapis.com%s", this.pathUrl);
String jsonRequestBody;
try {
jsonRequestBody = OBJECT_MAPPER.writeValueAsString(args);
Expand Down
Loading