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
21 changes: 20 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
# oci-genai-auth-java

The **OCI GenAI Auth** Java library provides OCI request-signing helpers for the OpenAI-compatible REST APIs hosted by OCI Generative AI.
The **OCI GenAI Auth** Java library provides OCI request-signing helpers for SDKs that call OCI Generative AI, including the OpenAI and Google Gen AI SDKs.

## Table of Contents

- [Installation](#installation)
- [Using OCI IAM Auth](#using-oci-iam-auth)
- [Using API Key Auth](#using-api-key-auth)
- [Using the Google Gen AI SDK](#using-the-google-gen-ai-sdk)
- [Using OCI Enterprise AI Agents APIs](#using-oci-enterprise-ai-agents-apis)
- [Examples](#examples)
- [Release Notes](#release-notes)
Expand Down Expand Up @@ -41,6 +42,24 @@ Requires **Java 17+** and **Maven 3.8+**.
</dependencies>
```

## Using the Google Gen AI SDK

For OCI Generative AI API-key authentication, configure the official Google Gen AI SDK with OCI's `/google` endpoint:

```java
import com.google.genai.Client;
import com.google.genai.types.HttpOptions;

Client client = Client.builder()
.apiKey(System.getenv("OCI_GENAI_API_KEY"))
.httpOptions(HttpOptions.builder()
.baseUrl("https://inference.generativeai.us-chicago-1.oci.oraclecloud.com/google")
.build())
.build();
```

The signing interceptor also removes `Authorization`, `X-Api-Key`, `x-goog-api-key`, and the `key` query parameter before applying OCI IAM signing. This prevents SDK credentials from conflicting when using an OCI-signing OkHttp transport.

## Using OCI IAM Auth

Use OCI IAM auth when you want to sign requests with your OCI profile (session/user/resource/instance principal). Recommended if you are building OCI-native production workloads.
Expand Down
90 changes: 90 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Examples

This directory contains Java examples for OCI Generative AI compatibility endpoints:

- `enterprise_ai_agents/` uses the OpenAI Java SDK with OCI Enterprise AI Agents' OpenAI-compatible `/openai/v1` endpoint.
- `google/` uses the Google Gen AI Java SDK with OCI Generative AI's Google-compatible `/google` endpoint.

All examples are standalone Java classes. They require Java 17 or later and Maven 3.8 or later.

## Example catalog

| File | Authentication | Description |
| --- | --- | --- |
| `enterprise_ai_agents/QuickstartResponsesOciIam.java` | OCI IAM | Create a Responses API response. |
| `enterprise_ai_agents/QuickstartResponsesApiKey.java` | API key | Create a Responses API response. |
| `enterprise_ai_agents/responses/CreateResponse.java` | OCI IAM | Create a response. |
| `enterprise_ai_agents/responses/StreamingTextDelta.java` | OCI IAM | Stream response text deltas. |
| `enterprise_ai_agents/tools/FunctionCalling.java` | OCI IAM | Use function calling. |
| `enterprise_ai_agents/tools/WebSearch.java` | OCI IAM | Use web search. |
| `google/GenerateContentApiKey.java` | API key | Generate content through the Google Gen AI SDK. |

## Configure authentication

### API key examples

Set an OCI Generative AI API key in the environment. Do not add credentials to source files.

```bash
export OCI_GENAI_API_KEY=<your_oci_genai_api_key>
```

### OCI IAM examples

Ensure your OCI CLI profile is configured and authenticated. The Enterprise AI Agents IAM examples use the profile configured in their source files (normally `DEFAULT`).

## Configure the selected example

Enterprise AI Agents examples require values such as `PROJECT_OCID` and `REGION`. Set the constants in the chosen source file before compiling it. They use an endpoint in the form:

```text
https://inference.generativeai.<region>.oci.oraclecloud.com/openai/v1
```

The Google example can be configured through environment variables:

| Variable | Default | Purpose |
| --- | --- | --- |
| `OCI_GENAI_REGION` | `us-chicago-1` | Region used to construct the endpoint. |
| `OCI_GENAI_GOOGLE_BASE_URL` | Derived from `OCI_GENAI_REGION` | Full endpoint override. |
| `OCI_GENAI_GOOGLE_MODEL` | `google.gemini-2.5-flash` | Google model ID. |

Its default endpoint is:

```text
https://inference.generativeai.<region>.oci.oraclecloud.com/google
```

## Build and run any example

Run these commands from the repository root. First build the core module and create a classpath containing its SDK dependencies:

```bash
mvn -pl oci-genai-auth-java-core package -DskipTests
mvn -pl oci-genai-auth-java-core dependency:build-classpath \
-Dmdep.outputFile=/tmp/oci-genai-auth-java-examples.classpath
```

Set `EXAMPLE` to the Java file you want to run and `CLASS_NAME` to its class name. All current examples use the default package, so the class name is the filename without `.java`.

```bash
EXAMPLE=examples/google/GenerateContentApiKey.java
CLASS_NAME=GenerateContentApiKey
CLASS_DIR=/tmp/oci-genai-auth-java-example-classes

mkdir -p "$CLASS_DIR"
javac -cp "oci-genai-auth-java-core/target/classes:$(cat /tmp/oci-genai-auth-java-examples.classpath)" \
-d "$CLASS_DIR" "$EXAMPLE"

java -cp "$CLASS_DIR:oci-genai-auth-java-core/target/classes:$(cat /tmp/oci-genai-auth-java-examples.classpath)" \
"$CLASS_NAME"
```

For example, to run the IAM Responses quickstart, use:

```bash
EXAMPLE=examples/enterprise_ai_agents/QuickstartResponsesOciIam.java
CLASS_NAME=QuickstartResponsesOciIam
```

On Windows, replace the `:` classpath separator with `;`, and use the platform-equivalent environment-variable commands.
55 changes: 0 additions & 55 deletions examples/enterprise_ai_agents/README.md

This file was deleted.

42 changes: 42 additions & 0 deletions examples/google/GenerateContentApiKey.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright (c) 2026 Oracle and/or its affiliates.
* Licensed under the Universal Permissive License v 1.0 as shown at
* https://oss.oracle.com/licenses/upl/
*/

/**
* Generate content through the Google Gen AI Java SDK with an OCI Generative AI API key.
*
* <p>Set {@code OCI_GENAI_API_KEY} before running this example. The Google SDK dependency is
* available as {@code com.google.genai:google-genai}.
*/

import com.google.genai.Client;
import com.google.genai.types.GenerateContentResponse;
import com.google.genai.types.HttpOptions;

public class GenerateContentApiKey {

private static final String REGION = System.getenv().getOrDefault("OCI_GENAI_REGION", "us-chicago-1");
private static final String MODEL =
System.getenv().getOrDefault("OCI_GENAI_GOOGLE_MODEL", "google.gemini-2.5-flash");
private static final String BASE_URL = System.getenv().getOrDefault(
"OCI_GENAI_GOOGLE_BASE_URL",
"https://inference.generativeai." + REGION + ".oci.oraclecloud.com/google");

public static void main(String[] args) {
String apiKey = System.getenv("OCI_GENAI_API_KEY");
if (apiKey == null || apiKey.isBlank()) {
throw new IllegalStateException("Set OCI_GENAI_API_KEY before running this example.");
}

Client client = Client.builder()
.apiKey(apiKey)
.httpOptions(HttpOptions.builder().baseUrl(BASE_URL).build())
.build();

GenerateContentResponse response = client.models.generateContent(
MODEL, "Write a one-sentence bedtime story about a unicorn.", null);
System.out.println(response.text());
}
}
8 changes: 8 additions & 0 deletions oci-genai-auth-java-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,14 @@
<optional>true</optional>
</dependency>

<!-- Google Gen AI SDK — optional; required only for Google SDK examples -->
<dependency>
<groupId>com.google.genai</groupId>
<artifactId>google-genai</artifactId>
<version>${google-genai.version}</version>
<optional>true</optional>
</dependency>

<!-- OpenAI SDK — integration tests only -->
<dependency>
<groupId>com.openai</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import com.oracle.bmc.http.signing.SigningStrategy;
import com.oracle.bmc.http.client.io.DuplicatableInputStream;
import okhttp3.Interceptor;
import okhttp3.HttpUrl;
import okhttp3.MediaType;
import okhttp3.Request;
import okhttp3.RequestBody;
Expand Down Expand Up @@ -61,22 +62,35 @@ public OciSigningInterceptor(BasicAuthenticationDetailsProvider authProvider) {
public Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();

URI uri = originalRequest.url().uri();
String method = originalRequest.method();
// SDKs such as Google Gen AI attach their own API-key credentials. OCI
// IAM requests must carry only the OCI signature, so remove those
// credentials before computing and sending the signature.
HttpUrl sanitizedUrl = originalRequest.url().newBuilder()
.removeAllQueryParameters("key")
.build();
Request sanitizedRequest = originalRequest.newBuilder()
.url(sanitizedUrl)
.removeHeader("Authorization")
.removeHeader("X-Api-Key")
.removeHeader("x-goog-api-key")
.build();

URI uri = sanitizedRequest.url().uri();
String method = sanitizedRequest.method();

// Read the request body for signing (OCI signs the body digest)
byte[] bodyBytes = null;
if (originalRequest.body() != null) {
if (sanitizedRequest.body() != null) {
Buffer buffer = new Buffer();
originalRequest.body().writeTo(buffer);
sanitizedRequest.body().writeTo(buffer);
bodyBytes = buffer.readByteArray();
}

// Build the headers map that OCI signing expects. OkHttp adds body-derived
// Content-Type and Content-Length later, so provide the exact values here.
Map<String, List<String>> existingHeaders = new HashMap<>();
for (String name : originalRequest.headers().names()) {
List<String> values = originalRequest.headers(name);
for (String name : sanitizedRequest.headers().names()) {
List<String> values = sanitizedRequest.headers(name);
if ("content-type".equalsIgnoreCase(name)) {
values = values.stream()
.map(OciSigningInterceptor::normalizeContentType)
Expand All @@ -85,9 +99,9 @@ public Response intercept(Chain chain) throws IOException {
existingHeaders.put(name, values);
}
if (bodyBytes != null) {
if (!containsHeader(existingHeaders, "content-type") && originalRequest.body().contentType() != null) {
if (!containsHeader(existingHeaders, "content-type") && sanitizedRequest.body().contentType() != null) {
existingHeaders.put("content-type",
List.of(normalizeContentType(originalRequest.body().contentType().toString())));
List.of(normalizeContentType(sanitizedRequest.body().contentType().toString())));
}
existingHeaders.put("content-length", List.of(String.valueOf(bodyBytes.length)));
}
Expand All @@ -104,16 +118,16 @@ public Response intercept(Chain chain) throws IOException {
}

// Build a new request with all signed headers applied
Request.Builder signedRequestBuilder = originalRequest.newBuilder();
Request.Builder signedRequestBuilder = sanitizedRequest.newBuilder();
for (Map.Entry<String, String> entry : signedHeaders.entrySet()) {
signedRequestBuilder.header(entry.getKey(), entry.getValue());
}

// Re-attach the body (it was consumed during signing).
// Use byte[] to prevent OkHttp from re-appending "; charset=utf-8".
if (bodyBytes != null) {
MediaType contentType = originalRequest.body() != null
? originalRequest.body().contentType()
MediaType contentType = sanitizedRequest.body() != null
? sanitizedRequest.body().contentType()
: MediaType.parse("application/json");
// Strip charset to keep content-type consistent with what was signed
if (contentType != null && contentType.charset() != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,25 @@ void preservesOriginalHeaders() throws Exception {
assertNotNull(request.getHeader("Authorization"));
}

@Test
void removesSdkApiKeyCredentialsBeforeSigning() throws Exception {
server.enqueue(new MockResponse().setResponseCode(200));

client.newCall(new Request.Builder()
.url(server.url("/test?key=google-sdk-key&keep=value"))
.header("Authorization", "Bearer sdk-token")
.header("X-Api-Key", "sdk-key")
.header("x-goog-api-key", "google-sdk-key")
.build()).execute().close();

RecordedRequest request = server.takeRequest();
assertNull(request.getHeader("X-Api-Key"));
assertNull(request.getHeader("x-goog-api-key"));
assertTrue(request.getHeader("Authorization").startsWith("Signature"));
assertEquals("value", request.getRequestUrl().queryParameter("keep"));
assertNull(request.getRequestUrl().queryParameter("key"));
}

@Test
void throwsOnNullAuthProvider() {
assertThrows(NullPointerException.class, () ->
Expand Down
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
<okhttp.version>5.3.2</okhttp.version>
<slf4j.version>2.0.17</slf4j.version>
<openai-java.version>4.39.1</openai-java.version>
<google-genai.version>1.17.0</google-genai.version>

<!-- Test dependencies -->
<junit-jupiter.version>5.11.4</junit-jupiter.version>
Expand Down
Loading