Skip to Content
SDKsAI SDK

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.net
pnpm 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.net

AurisAI

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):

OptionTypeRequiredDescription
baseUrlstringYesAPI base URL. Trailing slashes are stripped automatically.
tokenstring | (() => string | Promise<string>)YesBearer token for all requests. Pass a function to fetch the token dynamically (e.g. refresh before each call).
tenantIdstringYesTenant identifier. Sent as both x-tenant and x-tenant-id headers on every request.
applicationIdstringYesDefault application ID used when no per-call override is provided.
fetchtypeof globalThis.fetchNoCustom 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>
  • applicationId is optional per call — it falls back to the constructor applicationId.
  • Calls POST /api/v1/ai/chat.
  • Throws an AurisAIError subclass 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>
  • applicationId is optional per call — it falls back to the constructor applicationId.
  • Calls POST /api/v1/ai/chat/stream.
  • Parses the response as a Server-Sent Events stream. Three named event types are emitted: delta, done, and error.
  • Throws an AurisAIError subclass 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>
  • applicationId is optional per call — it falls back to the constructor applicationId.
  • Calls POST /api/v1/ai/embeddings.
  • When input is an array, embeddings in the response is a parallel array (one vector per input string).
  • Throws an AurisAIError subclass 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:

HeaderValue
Content-Typeapplication/json
AuthorizationBearer {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 delta chunks carrying incremental text.
  • Exactly one done chunk after the model finishes, carrying final usage statistics and the finish reason.
  • An error chunk if the gateway reports an in-stream error. Receiving error does 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:

ClasscodeExtra fieldsDescription
AurisAIErroranyBase class. Thrown for unrecognised error codes.
AurisAIRateLimitErrorrate_limitedretryAfterMs: numberRequest exceeded the rate limit. Wait retryAfterMs before retrying.
AurisAIBudgetExceededErrorbudget_exceededbudgetId, spentUsd, limitUsdThe application’s AI spend cap has been reached.
AurisAIProviderErrorprovider_errorproviderCode, retryableThe upstream AI provider returned an error.
AurisAICredentialNotFoundErrorcredential_not_foundprovider, applicationIdNo 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> ) }