Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
ed83b25
feat: add dynamic model capability detection
jerome-benoit Jan 21, 2026
7b3d8a8
refactor: harmonize model capabilities with codebase style
jerome-benoit Jan 22, 2026
f61d411
Merge branch 'main' into feat/dynamic-model-capabilities
jerome-benoit Jan 27, 2026
1228758
Merge origin/main into feat/dynamic-model-capabilities
jerome-benoit Jan 29, 2026
3f61215
chore: lock file maintenance
jerome-benoit Jan 29, 2026
b0f7ee3
Merge branch 'main' into feat/dynamic-model-capabilities
jerome-benoit Jan 29, 2026
0f3ce7e
fix: fix formatting
jerome-benoit Jan 29, 2026
c7376b5
fix: address PR review feedback
jerome-benoit Jan 29, 2026
385480e
Merge branch 'main' into feat/dynamic-model-capabilities
jerome-benoit Jan 30, 2026
b9cafc3
Merge branch 'main' into feat/dynamic-model-capabilities
jerome-benoit Jan 31, 2026
5058596
Merge branch 'main' into feat/dynamic-model-capabilities
jerome-benoit Feb 1, 2026
f4ee9f5
Merge branch 'main' into feat/dynamic-model-capabilities
jerome-benoit Feb 2, 2026
2c17acb
Merge branch 'main' into feat/dynamic-model-capabilities
jerome-benoit Feb 4, 2026
4ddae5e
fix: add mistralai vendor support for SAP AI Core models
jerome-benoit Feb 4, 2026
599ce33
fix: address PR review comments
jerome-benoit Feb 4, 2026
f0184ec
Merge branch 'main' into feat/dynamic-model-capabilities
jerome-benoit Feb 5, 2026
98329de
Merge branch 'main' into feat/dynamic-model-capabilities
jerome-benoit Feb 5, 2026
af57dbc
fix: improve model capability detection patterns
jerome-benoit Feb 5, 2026
d19c3c1
Merge branch 'main' into feat/dynamic-model-capabilities
jerome-benoit Feb 6, 2026
548062f
Merge branch 'main' into feat/dynamic-model-capabilities
jerome-benoit Feb 6, 2026
29a98a1
Merge branch 'main' into feat/dynamic-model-capabilities
jerome-benoit Feb 6, 2026
f15cba1
Merge branch 'main' into feat/dynamic-model-capabilities
jerome-benoit Feb 8, 2026
aede4cf
Merge branch 'main' into feat/dynamic-model-capabilities
jerome-benoit Feb 10, 2026
5a9da82
Merge branch 'main' into feat/dynamic-model-capabilities
jerome-benoit Feb 13, 2026
6993cf4
Merge branch 'main' into feat/dynamic-model-capabilities
jerome-benoit Feb 14, 2026
b49878f
Merge branch 'main' into feat/dynamic-model-capabilities
jerome-benoit Feb 18, 2026
ffdb22f
Merge branch 'main' into feat/dynamic-model-capabilities
jerome-benoit Feb 18, 2026
83e79ec
Merge branch 'main' into feat/dynamic-model-capabilities
jerome-benoit Feb 19, 2026
f4dbaf0
Merge branch 'main' into feat/dynamic-model-capabilities
jerome-benoit Feb 21, 2026
d641e2f
Merge branch 'main' into feat/dynamic-model-capabilities
jerome-benoit Feb 21, 2026
2f6dc4b
Merge branch 'main' into feat/dynamic-model-capabilities
jerome-benoit Feb 21, 2026
afc8d05
Merge branch 'main' into feat/dynamic-model-capabilities
jerome-benoit Feb 21, 2026
a5c3799
Merge branch 'main' into feat/dynamic-model-capabilities
jerome-benoit Feb 24, 2026
2823d5a
Merge branch 'main' into feat/dynamic-model-capabilities
jerome-benoit Feb 26, 2026
910a6df
Merge branch 'main' into feat/dynamic-model-capabilities
jerome-benoit Feb 26, 2026
acece8d
Merge branch 'main' into feat/dynamic-model-capabilities
jerome-benoit Feb 28, 2026
f973a9e
Merge branch 'main' into feat/dynamic-model-capabilities
jerome-benoit Mar 1, 2026
120b161
Merge branch 'main' into feat/dynamic-model-capabilities
jerome-benoit Mar 3, 2026
7e5d794
Merge branch 'main' into feat/dynamic-model-capabilities
jerome-benoit Mar 3, 2026
d5359b8
Merge branch 'main' into feat/dynamic-model-capabilities
jerome-benoit Mar 3, 2026
7cbeb21
Merge remote-tracking branch 'upstream/main' into feat/dynamic-model-…
jerome-benoit Mar 4, 2026
c5fa8b4
Merge branch 'main' into feat/dynamic-model-capabilities
jerome-benoit Mar 6, 2026
ebbb7aa
Merge branch 'main' into feat/dynamic-model-capabilities
jerome-benoit Mar 7, 2026
a4d5a89
Merge branch 'main' into feat/dynamic-model-capabilities
jerome-benoit Mar 7, 2026
dd297e1
Merge branch 'main' into feat/dynamic-model-capabilities
jerome-benoit Mar 11, 2026
7ff6003
Merge branch 'main' into feat/dynamic-model-capabilities
jerome-benoit Mar 11, 2026
0dddb4a
Merge branch 'main' into feat/dynamic-model-capabilities
jerome-benoit Mar 16, 2026
594da67
Merge branch 'main' into feat/dynamic-model-capabilities
jerome-benoit Mar 17, 2026
a6b7353
Merge branch 'main' into feat/dynamic-model-capabilities
jerome-benoit Mar 18, 2026
4da0a87
Merge branch 'main' into feat/dynamic-model-capabilities
jerome-benoit Mar 19, 2026
6577e2a
Merge branch 'main' into feat/dynamic-model-capabilities
jerome-benoit Mar 21, 2026
240b8b6
Merge branch 'main' into feat/dynamic-model-capabilities
jerome-benoit Mar 22, 2026
1a231ad
Merge branch 'main' into feat/dynamic-model-capabilities
jerome-benoit Mar 22, 2026
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
177 changes: 165 additions & 12 deletions API_REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,14 @@ consistently:
- [Re-exported SAP AI SDK Classes](#re-exported-sap-ai-sdk-classes)
- [Re-exported SAP AI SDK Types](#re-exported-sap-ai-sdk-types)
- [`DeploymentConfig`](#deploymentconfig)
- [Model Capabilities Detection](#model-capabilities-detection)
- [Exported Types](#exported-types)
- [`SAPAIModelVendor`](#sapaimodelvendor)
- [`SAPAIModelCapabilities`](#sapaimodelcapabilities)
- [`getSAPAIModelCapabilities(modelId)`](#getsapaimodelcapabilitiesmodelid)
- [`getModelVendor(modelId)`](#getmodelvendormodelid)
- [`modelSupports(modelId, capability)`](#modelsupportsmodelid-capability)
- [Vendor Capability Summary](#vendor-capability-summary)
Comment on lines +98 to +105
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

The Table of Contents bullet for “Model Capabilities Detection” is missing the indentation used by the surrounding “Classes” subsection items, which breaks the TOC nesting/formatting. Indent this entry to match the other Classes children (same as the OrchestrationErrorResponse line above).

Suggested change
- [Model Capabilities Detection](#model-capabilities-detection)
- [Exported Types](#exported-types)
- [`SAPAIModelVendor`](#sapaimodelvendor)
- [`SAPAIModelCapabilities`](#sapaimodelcapabilities)
- [`getSAPAIModelCapabilities(modelId)`](#getsapaimodelcapabilitiesmodelid)
- [`getModelVendor(modelId)`](#getmodelvendormodelid)
- [`modelSupports(modelId, capability)`](#modelsupportsmodelid-capability)
- [Vendor Capability Summary](#vendor-capability-summary)
- [Model Capabilities Detection](#model-capabilities-detection)
- [Exported Types](#exported-types)
- [`SAPAIModelVendor`](#sapaimodelvendor)
- [`SAPAIModelCapabilities`](#sapaimodelcapabilities)
- [`getSAPAIModelCapabilities(modelId)`](#getsapaimodelcapabilitiesmodelid)
- [`getModelVendor(modelId)`](#getmodelvendormodelid)
- [`modelSupports(modelId, capability)`](#modelsupportsmodelid-capability)
- [Vendor Capability Summary](#vendor-capability-summary)

Copilot uses AI. Check for mistakes.
- [Utility Functions](#utility-functions)
- [`getProviderName(providerIdentifier)`](#getprovidernameprovideridentifier)
- [`buildDpiMaskingProvider(config)`](#builddpimaskingproviderconfig)
Expand Down Expand Up @@ -2181,18 +2189,23 @@ Implementation of Vercel AI SDK's `LanguageModelV3` interface.

**Properties:**

| Property | Type | Description |
| ----------------------------- | -------------------------- | --------------------------------------------------- |
| `specificationVersion` | `'v3'` | API specification version (readonly) |
| `modelId` | `SAPAIModelId` | Current model identifier (readonly) |
| `provider` | `string` | Provider identifier (getter, e.g., `'sap-ai.chat'`) |
| `supportedUrls` | `Record<string, RegExp[]>` | URL patterns for supported media (getter) |
| `supportsImageUrls` | `true` | Image URL support flag (readonly) |
| `supportsMultipleCompletions` | `true` | Multiple completions support (readonly) |
| `supportsParallelToolCalls` | `true` | Parallel tool calls support (readonly) |
| `supportsStreaming` | `true` | Streaming support (readonly) |
| `supportsStructuredOutputs` | `true` | Structured output support (readonly) |
| `supportsToolCalls` | `true` | Tool calling support (readonly) |
| Property | Type | Description |
| ----------------------------- | -------------------------- | ------------------------------------------------------ |
| `specificationVersion` | `'v3'` | API specification version (readonly) |
| `modelId` | `SAPAIModelId` | Current model identifier (readonly) |
| `provider` | `string` | Provider identifier (getter, e.g., `'sap-ai.chat'`) |
| `capabilities` | `SAPAIModelCapabilities` | Dynamic model capabilities (getter, cached) |
| `supportedUrls` | `Record<string, RegExp[]>` | URL patterns for supported media (getter) |
| `supportsImageUrls` | `boolean` | Image URL support (getter, model-dependent) |
| `supportsMultipleCompletions` | `boolean` | Multiple completions support (getter, model-dependent) |
| `supportsParallelToolCalls` | `boolean` | Parallel tool calls support (getter, model-dependent) |
| `supportsStreaming` | `boolean` | Streaming support (getter, model-dependent) |
| `supportsStructuredOutputs` | `boolean` | Structured output support (getter, model-dependent) |
| `supportsToolCalls` | `boolean` | Tool calling support (getter, model-dependent) |

> **Note:** Model capabilities are now dynamically determined based on the model vendor
> and type. Use `getSAPAIModelCapabilities(modelId)` to query capabilities programmatically.
> See [Model Capabilities Detection](#model-capabilities-detection) for details.

**Methods:**

Expand Down Expand Up @@ -2600,6 +2613,146 @@ advanced use cases.

---

## Model Capabilities Detection

The provider includes dynamic capability detection based on model vendor and type,
following SAP AI Core's naming convention: `vendor--model-name`.

### Exported Types

#### `SAPAIModelVendor`

Union type of supported vendor prefixes:

```typescript
type SAPAIModelVendor = "aicore" | "amazon" | "anthropic" | "azure" | "cohere" | "google" | "meta" | "mistral" | "mistralai";
```

#### `SAPAIModelCapabilities`

Interface describing the capabilities of a model:

```typescript
interface SAPAIModelCapabilities {
readonly defaultSystemMessageMode: "system" | "developer" | "user";
readonly supportsImageInputs: boolean;
readonly supportsN: boolean;
readonly supportsParallelToolCalls: boolean;
readonly supportsStreaming: boolean;
readonly supportsStructuredOutputs: boolean;
readonly supportsToolCalls: boolean;
readonly vendor: SAPAIModelVendor | "unknown";
}
```

### `getSAPAIModelCapabilities(modelId)`

Returns capability information for a specific SAP AI Core model.

**Signature:**

```typescript
function getSAPAIModelCapabilities(modelId: string): SAPAIModelCapabilities;
```
Comment thread
jerome-benoit marked this conversation as resolved.

**Parameters:**

- `modelId`: The full model identifier (e.g., `"anthropic--claude-3.5-sonnet"`)

**Returns:** `SAPAIModelCapabilities` object with the following properties:

| Property | Type | Description |
| --------------------------- | ----------------------------------- | --------------------------------------------------------- |
| `supportsN` | `boolean` | Multiple completions support (`n` parameter) |
| `supportsImageInputs` | `boolean` | Vision/image input support |
| `supportsParallelToolCalls` | `boolean` | Parallel tool calls in single response |
| `supportsStreaming` | `boolean` | Streaming response support |
| `supportsStructuredOutputs` | `boolean` | JSON schema response format support |
| `supportsToolCalls` | `boolean` | Tool/function calling support |
| `defaultSystemMessageMode` | `"system" \| "developer" \| "user"` | System message mode (`"system"`, `"developer"`, `"user"`) |
| `vendor` | `SAPAIModelVendor \| "unknown"` | Detected vendor or `"unknown"` |

**Example:**

```typescript
import { getSAPAIModelCapabilities } from "@jerome-benoit/sap-ai-provider";

const capabilities = getSAPAIModelCapabilities("amazon--nova-pro");
// {
// supportsN: false, // Amazon doesn't support n parameter
// supportsImageInputs: true,
// supportsParallelToolCalls: true,
// supportsStreaming: true,
// supportsStructuredOutputs: true,
// supportsToolCalls: true,
// defaultSystemMessageMode: "system",
// vendor: "amazon"
// }
```

### `getModelVendor(modelId)`

Extracts the vendor prefix from a SAP AI Core model ID.

**Signature:**

```typescript
function getModelVendor(modelId: string): SAPAIModelVendor | "unknown";
```

**Parameters:**

- `modelId`: The full model identifier (e.g., `"anthropic--claude-3.5-sonnet"`)

**Returns:** The vendor prefix (`"aicore"`, `"amazon"`, `"anthropic"`, `"azure"`,
`"cohere"`, `"google"`, `"meta"`, `"mistral"`, `"mistralai"`) or `"unknown"` if
not recognized.

**Example:**

```typescript
import { getModelVendor } from "@jerome-benoit/sap-ai-provider";

getModelVendor("anthropic--claude-3.5-sonnet"); // "anthropic"
getModelVendor("gpt-4o"); // "unknown"
```

### `modelSupports(modelId, capability)`

Convenience function to check if a model supports a specific capability.

**Signature:**

```typescript
function modelSupports(modelId: string, capability: keyof Omit<SAPAIModelCapabilities, "defaultSystemMessageMode" | "vendor">): boolean;
```

**Example:**

```typescript
import { modelSupports } from "@jerome-benoit/sap-ai-provider";

if (modelSupports("amazon--nova-pro", "supportsN")) {
// Use n parameter for multiple completions
}
```

### Vendor Capability Summary

| Vendor | `supportsN` | `supportsStructuredOutputs` | Notes |
| ----------- | ----------- | --------------------------- | ------------------------------- |
| `azure` | ✅ | ✅ | Full capabilities |
| `google` | ✅ | ✅ | Gemini 1.0 has limited outputs |
| `mistral` | ✅ | ✅ | Small/Tiny have limited outputs |
| `mistralai` | ✅ | ✅ | Small/Tiny have limited outputs |
| `cohere` | ✅ | ✅ | Full capabilities |
| `amazon` | ❌ | ✅ | Titan models very limited |
| `anthropic` | ❌ | ✅ | Claude 2.x has limitations |
| `meta` | ✅ | ❌ | Llama 2 lacks tools |
| `aicore` | ✅ | ❌ | Open source models |

---

### Re-exported SAP AI SDK Classes

The following classes are re-exported from `@sap-ai-sdk/orchestration` for
Expand Down
14 changes: 14 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,20 @@ export { ApiSwitchError, UnsupportedFeatureError } from "./sap-ai-error.js";
*/
export { SAPAILanguageModel } from "./sap-ai-language-model.js";

/**
* Dynamic model capability detection for SAP AI Core models.
*
* Functions for determining model capabilities based on model ID prefix
* following the SAP AI Core naming convention: `vendor--model-name`.
*/
export {
getModelVendor,
getSAPAIModelCapabilities,
modelSupports,
} from "./sap-ai-model-capabilities.js";

export type { SAPAIModelCapabilities, SAPAIModelVendor } from "./sap-ai-model-capabilities.js";
Comment on lines +35 to +47

/**
* Provider options for per-call configuration.
*
Expand Down
108 changes: 90 additions & 18 deletions src/sap-ai-language-model.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1020,27 +1020,99 @@ describe("SAPAILanguageModel", () => {
expect(urls["image/*"]?.[1]?.test("data:image/png;base64,Zm9v")).toBe(true);
});

describe("model capabilities", () => {
const expectedCapabilities = {
supportsImageUrls: true,
supportsMultipleCompletions: true,
supportsParallelToolCalls: true,
supportsStreaming: true,
supportsStructuredOutputs: true,
supportsToolCalls: true,
};
it("should return empty supportedUrls for models without image support", () => {
const model = createModelForApi(api, "meta--llama-3.1-70b");
expect(model.supportedUrls).toEqual({});
});

it("should return false from supportsUrl for models without image support", () => {
const model = createModelForApi(api, "meta--llama-3.1-70b");
expect(model.supportsUrl(new URL("https://example.com/image.png"))).toBe(false);
expect(model.supportsUrl(new URL("data:image/png;base64,Zm9v"))).toBe(false);
});

describe("model capabilities", () => {
it.each([
"any-model",
"gpt-4o",
"anthropic--claude-3.5-sonnet",
"gemini-2.0-flash",
"amazon--nova-pro",
"mistralai--mistral-large-instruct",
"unknown-future-model",
])("should have consistent capabilities for model %s", (modelId) => {
{
expected: {
supportsImageUrls: true,
supportsMultipleCompletions: true,
supportsParallelToolCalls: true,
supportsStreaming: true,
supportsStructuredOutputs: true,
supportsToolCalls: true,
},
modelId: "any-model",
},
{
expected: {
supportsImageUrls: true,
supportsMultipleCompletions: true,
supportsParallelToolCalls: true,
supportsStreaming: true,
supportsStructuredOutputs: true,
supportsToolCalls: true,
},
modelId: "gpt-4o",
},
{
expected: {
supportsImageUrls: true,
supportsMultipleCompletions: false, // Anthropic models don't support n parameter
supportsParallelToolCalls: true,
supportsStreaming: true,
supportsStructuredOutputs: true,
supportsToolCalls: true,
},
modelId: "anthropic--claude-3.5-sonnet",
},
{
expected: {
supportsImageUrls: true,
supportsMultipleCompletions: true,
supportsParallelToolCalls: true,
supportsStreaming: true,
supportsStructuredOutputs: true,
supportsToolCalls: true,
},
modelId: "gemini-2.0-flash",
},
{
expected: {
supportsImageUrls: true,
supportsMultipleCompletions: false, // Amazon models don't support n parameter
supportsParallelToolCalls: true,
supportsStreaming: true,
supportsStructuredOutputs: true,
supportsToolCalls: true,
},
modelId: "amazon--nova-pro",
},
{
expected: {
supportsImageUrls: true,
supportsMultipleCompletions: true,
supportsParallelToolCalls: true,
supportsStreaming: true,
supportsStructuredOutputs: true,
supportsToolCalls: true,
},
modelId: "mistralai--mistral-large-instruct",
},
{
expected: {
supportsImageUrls: true,
supportsMultipleCompletions: true,
supportsParallelToolCalls: true,
supportsStreaming: true,
supportsStructuredOutputs: true,
supportsToolCalls: true,
},
modelId: "unknown-future-model",
},
])("should have correct capabilities for model $modelId", ({ expected, modelId }) => {
const model = createModelForApi(api, modelId);
expect(model).toMatchObject(expectedCapabilities);
expect(model).toMatchObject(expected);
});
});
},
Expand Down
Loading
Loading