AI SDK (@auris/sdk-ai)
@auris/sdk-ai v0.1.0@auris/sdk-ai is a zero-dependency TypeScript client for the Auris AI Gateway. It wraps the /api/v1/ai/* routes and exposes three operations: synchronous chat completions, server-sent event streaming, and text embeddings. Both ESM (import) and CJS (require) builds are included, along with full TypeScript declarations.
The @auris/react SDK depends on this package for its useAurisAI and useAurisChatStream hooks. Use @auris/sdk-ai directly when you need AI features outside of React — in Node.js scripts, CLI tools, backend routes, or framework-agnostic code.
Installation
npm install @auris/sdk-ai --registry https://npm.altovar.netpnpm add @auris/sdk-ai --registry https://npm.altovar.net@auris/sdk-ai is published to the Altovar private registry at https://npm.altovar.net. Make sure your project’s .npmrc is configured to resolve the @auris scope to that registry.
@auris:registry=https://npm.altovar.netAurisAI
AurisAI is the main class. Create one instance per application context.
Constructor
import { AurisAI } from '@auris/sdk-ai'
const ai = new AurisAI({
baseUrl: 'https://api.altovar.net',
token: 'your-access-token', // or a dynamic getter function
tenantId: 'my-tenant',
applicationId: 'app_xxxxx',
})Constructor options (AurisAIOptions):
| Option | Type | Required | Description |
|---|---|---|---|
baseUrl | string | Yes | API base URL. Trailing slashes are stripped automatically. |
token | string | (() => string | Promise<string>) | Yes | Bearer token for all requests. Pass a function to fetch the token dynamically (e.g. refresh before each call). |
tenantId | string | Yes | Tenant identifier. Sent as both x-tenant and x-tenant-id headers on every request. |
applicationId | string | Yes | Default application ID used when no per-call override is provided. |
fetch | typeof globalThis.fetch | No | Custom fetch implementation. Defaults to globalThis.fetch. Useful for environments without a native fetch or for testing. |
Dynamic token example (refresh on each call):
import { AurisAI } from '@auris/sdk-ai'
import { getAurisClient } from './auth'
const ai = new AurisAI({
baseUrl: process.env.AURIS_API_URL,
tenantId: process.env.AURIS_TENANT_ID,
applicationId: process.env.AURIS_APP_ID,
token: async () => {
const client = getAurisClient()
return client.getAccessToken()
},
})Methods
chat(request)
Sends a chat completion request and returns the full response once the model finishes.
Signature:
chat(req: Omit<ChatRequest, 'applicationId'> & { applicationId?: string }): Promise<ChatResponse>applicationIdis optional per call — it falls back to the constructorapplicationId.- Calls
POST /api/v1/ai/chat. - Throws an
AurisAIErrorsubclass on HTTP errors or API-level failures.
Example:
import { AurisAI } from '@auris/sdk-ai'
const ai = new AurisAI({ /* ... */ })
const response = await ai.chat({
model: 'claude-3-5-haiku-20241022',
provider: 'ANTHROPIC',
messages: [
{ role: 'system', content: 'You are a concise assistant.' },
{ role: 'user', content: 'Summarise the Auris IAM product in two sentences.' },
],
maxTokens: 256,
temperature: 0.3,
})
console.log(response.content)
// e.g. "Auris IAM is a self-hosted identity platform…"
console.log(response.usage.totalTokens)
console.log(response.usage.estimatedCostUsd)JSON mode:
const response = await ai.chat({
model: 'mistral-large-latest',
provider: 'MISTRAL',
messages: [
{ role: 'user', content: 'Return a JSON object with fields: name, language, version.' },
],
jsonMode: true,
})
const data = JSON.parse(response.content)chatStream(request)
Streams a chat completion as an async generator. Each iteration yields a ChatStreamChunk as the model produces output, ending with a done chunk that includes usage statistics.
Signature:
async *chatStream(
req: Omit<ChatRequest, 'applicationId'> & { applicationId?: string }
): AsyncIterable<ChatStreamChunk>applicationIdis optional per call — it falls back to the constructorapplicationId.- Calls
POST /api/v1/ai/chat/stream. - Parses the response as a Server-Sent Events stream. Three named event types are emitted:
delta,done, anderror. - Throws an
AurisAIErrorsubclass if the HTTP request fails before streaming begins.
Example (Node.js / server):
import { AurisAI } from '@auris/sdk-ai'
const ai = new AurisAI({ /* ... */ })
let fullText = ''
for await (const chunk of ai.chatStream({
model: 'claude-3-5-haiku-20241022',
provider: 'ANTHROPIC',
messages: [{ role: 'user', content: 'Write a short poem about distributed systems.' }],
maxTokens: 512,
})) {
if (chunk.type === 'delta') {
process.stdout.write(chunk.content)
fullText += chunk.content
} else if (chunk.type === 'done') {
console.log('\n\nFinish reason:', chunk.finishReason)
console.log('Tokens used:', chunk.usage.totalTokens)
} else if (chunk.type === 'error') {
console.error('Stream error:', chunk.code)
}
}Example (Next.js route handler — streaming to the browser):
import { AurisAI } from '@auris/sdk-ai'
import { NextRequest } from 'next/server'
const ai = new AurisAI({ /* ... */ })
export async function POST(req: NextRequest) {
const { messages } = await req.json()
const encoder = new TextEncoder()
const stream = new ReadableStream({
async start(controller) {
for await (const chunk of ai.chatStream({ model: 'claude-3-5-haiku-20241022', messages })) {
if (chunk.type === 'delta') {
controller.enqueue(encoder.encode(chunk.content))
} else if (chunk.type === 'done') {
controller.close()
} else if (chunk.type === 'error') {
controller.error(new Error(chunk.code))
}
}
},
})
return new Response(stream, {
headers: { 'Content-Type': 'text/plain; charset=utf-8' },
})
}embed(request)
Generates vector embeddings for one or more input strings.
Signature:
embed(req: Omit<EmbedRequest, 'applicationId'> & { applicationId?: string }): Promise<EmbedResponse>applicationIdis optional per call — it falls back to the constructorapplicationId.- Calls
POST /api/v1/ai/embeddings. - When
inputis an array,embeddingsin the response is a parallel array (one vector per input string). - Throws an
AurisAIErrorsubclass on failure.
Example:
import { AurisAI } from '@auris/sdk-ai'
const ai = new AurisAI({ /* ... */ })
// Single input
const single = await ai.embed({
model: 'text-embedding-3-small',
input: 'Auris provides identity management for SaaS applications.',
})
console.log(single.embeddings[0].length) // vector dimension
// Batch input
const batch = await ai.embed({
model: 'text-embedding-3-small',
input: [
'Auris IAM handles authentication and authorization.',
'Vigilante is a Windows EDR and XDR product.',
'Altovar Cloud provides object storage.',
],
})
// batch.embeddings[0] → vector for first string
// batch.embeddings[1] → vector for second string
// batch.embeddings[2] → vector for third string
console.log(batch.usage.estimatedCostUsd)Request Headers
Every request automatically includes the following headers:
| Header | Value |
|---|---|
Content-Type | application/json |
Authorization | Bearer {token} |
x-tenant | {tenantId} |
x-tenant-id | {tenantId} |
Both x-tenant and x-tenant-id are sent for compatibility with the current Auris API routing layer.
Type Reference
AurisAIOptions
interface AurisAIOptions {
baseUrl: string
token: string | (() => string | Promise<string>)
tenantId: string
applicationId: string
fetch?: typeof globalThis.fetch
}AiProvider
type AiProvider = 'ANTHROPIC' | 'MINIMAX' | 'MISTRAL'ChatMessage
interface ChatMessage {
role: 'system' | 'user' | 'assistant'
content: string
}ChatRequest
interface ChatRequest {
applicationId: string
provider?: AiProvider // defaults to the gateway's configured provider for the application
model: string // provider-specific model identifier
messages: ChatMessage[]
maxTokens?: number
temperature?: number // 0–1; higher = more random
stopSequences?: string[]
jsonMode?: boolean // instructs the model to return valid JSON
metadata?: {
feature?: string // tag used for usage attribution in the gateway
userId?: string // end-user identifier for audit purposes
}
}ChatResponse
interface ChatResponse {
id: string
provider: AiProvider
model: string
content: string
usage: ChatUsage
finishReason: 'stop' | 'length' | 'content_filter'
}ChatUsage
interface ChatUsage {
promptTokens: number
completionTokens: number
totalTokens: number
estimatedCostUsd: number
}ChatStreamChunk
type ChatStreamChunk =
| { type: 'delta'; content: string }
| { type: 'done'; usage: ChatUsage; finishReason: 'stop' | 'length' | 'content_filter' }
| { type: 'error'; code: string }The stream emits:
- Zero or more
deltachunks carrying incremental text. - Exactly one
donechunk after the model finishes, carrying final usage statistics and the finish reason. - An
errorchunk if the gateway reports an in-stream error. Receivingerrordoes not automatically terminate the generator — break out of the loop if you want to stop.
EmbedRequest
interface EmbedRequest {
applicationId: string
provider?: AiProvider
model: string
input: string | string[] // single string or batch of strings
}EmbedResponse
interface EmbedResponse {
id: string
provider: AiProvider
model: string
embeddings: number[][] // one vector per input string, in order
usage: EmbedUsage
}EmbedUsage
interface EmbedUsage {
promptTokens: number
totalTokens: number
estimatedCostUsd: number
}Error Handling
All methods throw a subclass of AurisAIError on HTTP or API-level failure. Import the classes you need and use instanceof to branch on error type.
import {
AurisAIError,
AurisAIRateLimitError,
AurisAIBudgetExceededError,
AurisAIProviderError,
AurisAICredentialNotFoundError,
} from '@auris/sdk-ai'
try {
const response = await ai.chat({ model: 'claude-3-5-haiku-20241022', messages })
} catch (err) {
if (err instanceof AurisAIRateLimitError) {
// Respect the retry window before retrying
console.error(`Rate limited. Retry after ${err.retryAfterMs}ms.`)
} else if (err instanceof AurisAIBudgetExceededError) {
console.error(`Budget cap reached. Spent $${err.spentUsd} of $${err.limitUsd} (budget: ${err.budgetId})`)
} else if (err instanceof AurisAIProviderError) {
console.error(`Provider error [${err.providerCode}]. Retryable: ${err.retryable}`)
} else if (err instanceof AurisAICredentialNotFoundError) {
console.error(`No credential for provider ${err.provider} on application ${err.applicationId}`)
} else if (err instanceof AurisAIError) {
// Catch-all for other gateway errors
console.error(`AI error [${err.code}]: ${err.message}`)
}
}Error classes:
| Class | code | Extra fields | Description |
|---|---|---|---|
AurisAIError | any | — | Base class. Thrown for unrecognised error codes. |
AurisAIRateLimitError | rate_limited | retryAfterMs: number | Request exceeded the rate limit. Wait retryAfterMs before retrying. |
AurisAIBudgetExceededError | budget_exceeded | budgetId, spentUsd, limitUsd | The application’s AI spend cap has been reached. |
AurisAIProviderError | provider_error | providerCode, retryable | The upstream AI provider returned an error. |
AurisAICredentialNotFoundError | credential_not_found | provider, applicationId | No credential is configured for the requested provider and application pair. |
Usage with @auris/react
@auris/sdk-ai is a dependency of @auris/react. You do not need to call AurisAI directly in React applications — the AurisAIProvider context and associated hooks handle instantiation.
useAurisAI() returns the AurisAI instance from context:
import { useAurisAI } from '@auris/react'
function MyComponent() {
const ai = useAurisAI()
async function handleClick() {
const response = await ai.chat({
model: 'claude-3-5-haiku-20241022',
messages: [{ role: 'user', content: 'Hello' }],
})
console.log(response.content)
}
return <button onClick={handleClick}>Ask AI</button>
}useAurisChatStream(options) provides a managed streaming chat interface:
import { useAurisChatStream } from '@auris/react'
function ChatWidget() {
const { messages, input, setInput, send, streaming, error, budgetExceeded, usage, reset } =
useAurisChatStream({
model: 'claude-3-5-haiku-20241022',
systemPrompt: 'You are a helpful assistant.',
})
return (
<div>
<ul>
{messages.map((m, i) => (
<li key={i}><strong>{m.role}</strong>: {m.content}</li>
))}
</ul>
{budgetExceeded && <p>Spend cap reached. Contact your administrator.</p>}
{error && !budgetExceeded && <p>Error: {error}</p>}
<input
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && send()}
disabled={streaming}
/>
<button onClick={() => send()} disabled={streaming || !input.trim()}>
{streaming ? 'Generating...' : 'Send'}
</button>
<button onClick={reset}>Clear</button>
{usage && (
<small>Tokens: {usage.totalTokens} | Est. cost: ${usage.estimatedCostUsd.toFixed(6)}</small>
)}
</div>
)
}Related Pages
- React SDK —
useAurisAIanduseAurisChatStreamhooks - JavaScript SDK — Core Auris IAM client
- Next.js SDK — Server-side helpers for Next.js