Skip to content
Closed
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
1 change: 1 addition & 0 deletions .github/workflows/pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ jobs:
cf-space: ${{ secrets.CF_SPACE_AWS }}

- name: SonarQube Scan
continue-on-error: true
Comment thread
Schmarvinius marked this conversation as resolved.
uses: ./.github/actions/scan-with-sonar
with:
java-version: '17'
Expand Down
2 changes: 1 addition & 1 deletion cds-feature-ai-core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ The plugin auto-registers via Java's `ServiceLoader` mechanism - no code changes

In production, bind an SAP AI Core service instance to your application. Supported methods:

- **Service binding** (Cloud Foundry / Kubernetes) - detected automatically via `ServiceBindingUtils`
- **Service binding** (Cloud Foundry / Kubernetes)
- **Environment variable** `AICORE_SERVICE_KEY` - for local hybrid testing (via `cds bind --exec`)

Without a binding the plugin registers a mock implementation.
Expand Down
9 changes: 8 additions & 1 deletion cds-feature-ai-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,19 @@
<dependency>
<groupId>com.sap.cds</groupId>
<artifactId>cds-services-impl</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<finalName>${project.artifactId}</finalName>
<testResources>
<testResource>
<directory>src/test/resources</directory>
</testResource>
<testResource>
<directory>src/gen/srv/src/main/resources</directory>
</testResource>
</testResources>
<plugins>
<plugin>
<groupId>com.sap.cds</groupId>
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* © 2026 SAP SE or an SAP affiliate company and cds-ai contributors.
*/
package com.sap.cds.feature.aicore.api;

import com.sap.cds.services.EventContext;
import com.sap.cds.services.EventName;

/**
* Typed {@link EventContext} for the {@code deploymentId} event.
*
* <p>Emitted on the AI Core service to resolve (or create) a deployment matching the given spec
* inside the given resource group. The ON handler performs cache lookup, retry, configuration
* creation, deployment creation and polling.
*/
@EventName(DeploymentIdContext.EVENT)
public interface DeploymentIdContext extends EventContext {

/** Event name constant. */
String EVENT = "deploymentId";

/** Returns the resource group ID to operate in. */
String getResourceGroupId();

/** Sets the resource group ID to operate in. */
void setResourceGroupId(String resourceGroupId);

/** Returns the deployment specification. */
ModelDeploymentSpec getSpec();

/** Sets the deployment specification. */
void setSpec(ModelDeploymentSpec spec);

/** Returns the resolved deployment ID (set by the ON handler). */
String getResult();

/** Sets the resolved deployment ID. */
void setResult(String deploymentId);

/** Creates a new context instance. */
static DeploymentIdContext create() {
return EventContext.create(DeploymentIdContext.class, null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* © 2026 SAP SE or an SAP affiliate company and cds-ai contributors.
*/
package com.sap.cds.feature.aicore.api;

import com.sap.cds.services.EventContext;
import com.sap.cds.services.EventName;
import com.sap.cloud.sdk.services.openapi.apache.apiclient.ApiClient;

/**
* Typed {@link EventContext} for the {@code inferenceClient} event.
*
* <p>Emitted on the AI Core service to build an {@link ApiClient} preconfigured with the inference
* destination for the given deployment.
*/
@EventName(InferenceClientContext.EVENT)
public interface InferenceClientContext extends EventContext {

/** Event name constant. */
String EVENT = "inferenceClient";

/** Returns the resource group ID containing the deployment. */
String getResourceGroupId();

/** Sets the resource group ID containing the deployment. */
void setResourceGroupId(String resourceGroupId);

/** Returns the deployment ID. */
String getDeploymentId();

/** Sets the deployment ID. */
void setDeploymentId(String deploymentId);

/** Returns the configured {@link ApiClient} (set by the ON handler). */
ApiClient getResult();

/** Sets the configured {@link ApiClient}. */
void setResult(ApiClient client);

/** Creates a new context instance. */
static InferenceClientContext create() {
return EventContext.create(InferenceClientContext.class, null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
import java.util.function.Predicate;

/**
* Describes a target AI Core deployment used by {@link AICoreService#deploymentId(String,
* ModelDeploymentSpec)} to look up or create a deployment inside a resource group.
* Describes a target AI Core deployment used by the {@code deploymentId} event to look up or create
* a deployment inside a resource group.
*
* <p>The spec carries the AI Core scenario/executable identification, the human-readable
* configuration name (used as a stable key for caching and idempotent reuse), the parameter
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* © 2026 SAP SE or an SAP affiliate company and cds-ai contributors.
*/
package com.sap.cds.feature.aicore.api;

import com.sap.cds.services.EventContext;
import com.sap.cds.services.EventName;

/**
* Typed {@link EventContext} for the {@code resourceGroup} event.
*
* <p>Emitted on the AI Core service to resolve the AI Core resource group ID for the current
* tenant. In multi-tenancy mode, the resource group is created on-demand if it does not exist. In
* single-tenancy mode, the configured default resource group is returned.
*
* <p>If {@link #getTenantId()} is non-null, the handler uses the explicit tenant ID. Otherwise, the
* current tenant is read from the {@code RequestContext}.
*/
@EventName(ResourceGroupContext.EVENT)
public interface ResourceGroupContext extends EventContext {

/** Event name constant. */
String EVENT = "resourceGroup";

/**
* Returns the explicit tenant ID (optional). If {@code null}, the handler reads the tenant from
* the current {@code RequestContext}.
*/
String getTenantId();

/** Sets an explicit tenant ID. */
void setTenantId(String tenantId);

/** Returns the resolved resource group ID (set by the ON handler). */
String getResult();

/** Sets the resolved resource group ID. */
void setResult(String resourceGroupId);

/** Creates a new context instance. */
static ResourceGroupContext create() {
return EventContext.create(ResourceGroupContext.class, null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* © 2026 SAP SE or an SAP affiliate company and cds-ai contributors.
*/
package com.sap.cds.feature.aicore.core;

import com.sap.ai.sdk.core.AiCoreService;
import com.sap.ai.sdk.core.client.ConfigurationApi;
import com.sap.ai.sdk.core.client.DeploymentApi;
import com.sap.ai.sdk.core.client.ResourceGroupApi;

/**
* Holder for the AI Core SDK API clients, built once from the service binding at startup.
*
* @param deploymentApi client for deployment CRUD operations
* @param configurationApi client for configuration CRUD operations
* @param resourceGroupApi client for resource-group CRUD operations
* @param sdkService the AI Core SDK service for inference destination resolution
*/
public record AICoreClients(
DeploymentApi deploymentApi,
ConfigurationApi configurationApi,
ResourceGroupApi resourceGroupApi,
AiCoreService sdkService) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* © 2026 SAP SE or an SAP affiliate company and cds-ai contributors.
*/
package com.sap.cds.feature.aicore.core;

import com.sap.cds.services.environment.CdsEnvironment;

/**
* Immutable configuration for the AI Core plugin, read once from {@link CdsEnvironment} at startup.
*
* @param defaultResourceGroup the resource group to use when multi-tenancy is disabled
* @param resourceGroupPrefix prefix for tenant-specific resource groups (e.g. "cds-")
* @param maxRetries max retry attempts for transient AI Core errors
* @param initialDelayMs initial backoff delay in milliseconds
* @param multiTenancyEnabled whether multi-tenancy is active
*/
public record AICoreConfig(
String defaultResourceGroup,
String resourceGroupPrefix,
int maxRetries,
long initialDelayMs,
boolean multiTenancyEnabled) {

/** The AI Core resource-group label key used to associate groups with CDS tenants. */
public static final String TENANT_LABEL_KEY = "ext.ai.sap.com/CDS_TENANT_ID";

private static final String DEFAULT_RESOURCE_GROUP = "default";
private static final String DEFAULT_RESOURCE_GROUP_PREFIX = "cds-";
private static final int DEFAULT_MAX_RETRIES = 10;
private static final long DEFAULT_INITIAL_DELAY_MS = 300;

public AICoreConfig {
if (maxRetries < 1) {
throw new IllegalArgumentException("cds.ai.core.maxRetries must be >= 1, got " + maxRetries);
}
if (initialDelayMs < 1) {
throw new IllegalArgumentException(
"cds.ai.core.initialDelayMs must be >= 1, got " + initialDelayMs);
}
if (defaultResourceGroup == null || defaultResourceGroup.isBlank()) {
throw new IllegalArgumentException("cds.ai.core.resourceGroup must not be blank");
}
if (resourceGroupPrefix == null) {
throw new IllegalArgumentException("cds.ai.core.resourceGroupPrefix must not be null");
}
}

/** Creates an {@code AICoreConfig} from the runtime environment properties. */
public static AICoreConfig from(CdsEnvironment env, boolean multiTenancyEnabled) {
return new AICoreConfig(
env.getProperty("cds.ai.core.resourceGroup", String.class, DEFAULT_RESOURCE_GROUP),
env.getProperty(
"cds.ai.core.resourceGroupPrefix", String.class, DEFAULT_RESOURCE_GROUP_PREFIX),
env.getProperty("cds.ai.core.maxRetries", Integer.class, DEFAULT_MAX_RETRIES),
env.getProperty("cds.ai.core.initialDelayMs", Long.class, DEFAULT_INITIAL_DELAY_MS),
multiTenancyEnabled);
}
}
Loading
Loading