# @kortexya/reasoninglayer

TypeScript SDK for the [Reasoning Layer](https://kortexya.com) API — a knowledge representation and reasoning engine.

## Features

- **Zero runtime dependencies** — only `fetch` and `AbortController` (ES2022)
- **Dual ESM/CJS** — works in Node 18+, modern browsers, Deno, and Bun
- **Full type safety** — strict TypeScript with discriminated unions, no `any` types
- **29 resource clients** covering sorts, terms, inference, query, fuzzy logic, cognitive agents, causal reasoning, optimization, and more
- **8 builder namespaces** — `Value`, `FeatureInput`, `TermInput`, `guard`, `SortBuilder`, `psi`, `allen`, `LP`
- **Automatic retry** — exponential backoff with jitter on 429 and 503 responses
- **WebSocket** — real-time cognitive agent event subscriptions with auto-reconnect
- **Error hierarchy** — typed errors for constraint violations, rate limits, timeouts, and network failures

## Installation

```bash
npm install @kortexya/reasoninglayer
# or
pnpm add @kortexya/reasoninglayer
# or
yarn add @kortexya/reasoninglayer
```

## Quick Start

```typescript
import { ReasoningLayerClient, Value, TermInput, FeatureInput } from '@kortexya/reasoninglayer';

const client = new ReasoningLayerClient({
  baseUrl: 'https://platform.ovh.reasoninglayer.ai',
  tenantId: 'your-tenant-uuid',
  auth: { mode: 'bearer', token: process.env.RL_API_TOKEN! },
});

// Create a sort hierarchy
const personSort = await client.sorts.createSort({ name: 'person' });
const employeeSort = await client.sorts.createSort({
  name: 'employee',
  parents: [personSort.id],
  features: { salary: { value_type: 'Real' } },
});

// Create a psi-term
const term = await client.terms.createTerm({
  sort_id: employeeSort.id,
  features: {
    name: Value.string('Alice'),
    salary: Value.real(95000),
  },
});

// Add inference rules (homoiconic format)
await client.inference.addRule({
  head: TermInput.byName('well_paid', {
    person: FeatureInput.variable('X'),
  }),
  body: [
    {
      term: TermInput.byName('employee', {
        name: FeatureInput.variable('X'),
        salary: FeatureInput.constrainedVar('S', { op: 'gt', value: 80000 }),
      }),
    },
  ],
});

// Query with backward chaining
const result = await client.inference.backwardChain({
  goal: TermInput.byName('well_paid', {
    person: FeatureInput.variable('Who'),
  }),
  max_solutions: 10,
});

console.log(result.solutions); // Solutions with bindings
```

## Resource Clients

| Client | Domain | Key Operations |
|--------|--------|---------------|
| `sorts` | Sort hierarchy | create, GLB/LUB, compare, similarity |
| `terms` | Psi-terms | CRUD, bulk create, validation |
| `inference` | Inference engine | rules, facts, backward/forward chaining, fuzzy, Bayesian, NAF |
| `query` | Query | unifiable, by-sort, structured search, NL query |
| `cognitive` | Cognitive agents | BDI cycle, beliefs, goals, messaging, WebSocket events |
| `fuzzy` | Fuzzy logic | unify, merge, subsumption, top-K search |
| `constraints` | Constraints | arithmetic solving, graph visualization |
| `namespaces` | Namespaces | hierarchy, imports, metadata |
| `collections` | Collections | CRUD, tagging |
| `execution` | Execution | sessions, backtracking, goal stack |
| `causal` | Causal reasoning | Pearl's hierarchy, interventions, counterfactuals |
| `ingestion` | Data ingestion | documents, markdown, RDF, sessions |
| `reviews` | Reviews | approve, reject, merge, re-extract |
| `visualization` | Visualization | lattice, hypergraph, residuation state |
| `ilp` | ILP | pattern learning, GFlowNet, synthesis |
| `reasoning` | Reasoning | entailment, residuation, resource coordination |
| `statistical` | Statistics | correlation, independence, causal discovery |
| `control` | Proof control | cut, findall, forall, NAF, conditionals |
| `spaces` | Spaces | computation spaces, clone, commit, search |
| `row` | Row polymorphism | schema-flexible search, unification |
| `sources` | Data sources | register, schema discovery, ingest |
| `communities` | Communities | detection, memberships, search |
| `utilities` | Utilities | strings, arithmetic, term copy |
| `scenarios` | Scenarios | CRUD, verification |
| `actionReviews` | Action reviews | approve, reject, modify autonomous actions |
| `discovery` | Discovery | causal effect discovery, prediction |
| `extract` | Extraction | entity extraction from text |
| `optimize` | Optimization | LP solve, KB-driven optimization |

## Configuration

```typescript
const client = new ReasoningLayerClient({
  baseUrl: 'https://platform.ovh.reasoninglayer.ai', // Required
  tenantId: 'your-tenant-uuid',                      // Required, sent as X-Tenant-Id
  auth: { mode: 'bearer', token: 'eyJ…' },           // Required — see Authentication below
  userId: 'user-uuid',                       // Optional, sent as X-User-Id
  namespaceId: 'ns-uuid',                    // Optional, sent as X-Namespace-Id
  maxRetries: 3,                             // Default: 3
  timeoutMs: 30000,                          // Default: 30000ms
  retryOn503: true,                          // Default: false
  fetch: customFetch,                        // Optional: custom fetch implementation
  interceptors: [loggingMiddleware],         // Optional: request/response middleware
});
```

### Authentication

The `auth` field is **required** and must be one of two modes. There is no
unauthenticated mode — the SDK will not silently ship requests that the
gateway would reject.

**`bearer` — server-side / programmatic usage.** Send a long-lived API token
(typically a service-account token issued by the auth gateway) on every
request as `Authorization: Bearer <token>`.

```typescript
const client = new ReasoningLayerClient({
  baseUrl: 'https://platform.ovh.reasoninglayer.ai',
  tenantId: '550e8400-e29b-41d4-a716-446655440000',
  auth: { mode: 'bearer', token: process.env.RL_API_TOKEN! },
});
```

**`cookie` — in-browser SPA usage.** Rely on the session cookie set by the
auth gateway after an interactive login. The SDK issues HTTP requests with
`credentials: 'include'` so the cookie is attached even on cross-origin
requests; WebSocket connections rely on the browser attaching the cookie
automatically (same-origin).

```typescript
const client = new ReasoningLayerClient({
  baseUrl: 'https://platform.ovh.reasoninglayer.ai',
  tenantId: '550e8400-e29b-41d4-a716-446655440000',
  auth: { mode: 'cookie' },
});
```

The choice between modes belongs to the **deployment context** of the
caller, not the SDK — pick `bearer` from servers and CI, `cookie` from
browser code that has already gone through the platform's login flow.

## Response Metadata

```typescript
// Default: returns data directly
const sort = await client.sorts.createSort({ name: 'person' });

// With metadata: returns { data, status, headers, rateLimit }
const result = await client.sorts.withMetadata().createSort({ name: 'person' });
console.log(result.status);    // 201
console.log(result.rateLimit); // { limit, remaining, retryAfter }
```

## Error Handling

```typescript
import {
  ApiError,
  ConstraintViolationError,
  RateLimitError,
  TimeoutError,
  NetworkError,
} from '@kortexya/reasoninglayer';

try {
  await client.terms.createTerm({ ... });
} catch (error) {
  if (error instanceof ConstraintViolationError) {
    console.log(error.termId, error.feature, error.constraint);
  } else if (error instanceof RateLimitError) {
    console.log(`Retry after ${error.retryAfter}s`);
  } else if (error instanceof TimeoutError) {
    console.log(`Timed out after ${error.timeoutMs}ms`);
  } else if (error instanceof NetworkError) {
    console.log('Connection failed:', error.message);
  }
}
```

## Builders

The SDK provides builder functions for constructing API request values with full type safety:

```typescript
import { Value, FeatureInput, TermInput, guard, allen, SortBuilder, psi, LP } from '@kortexya/reasoninglayer';

// Tagged values (term CRUD)
Value.string('hello')           // { type: 'String', value: 'hello' }
Value.integer(42)               // { type: 'Integer', value: 42 }
Value.fuzzyNumber('triangular', { a: 20, b: 22, c: 24 })

// Untagged values (inference)
FeatureInput.string('hello')    // 'hello'
FeatureInput.variable('X')      // { name: 'X' }
FeatureInput.constrainedVar('S', { op: 'gt', value: 80000 })

// Guard constraints
guard('gt', 100)                // { guard: { op: 'gt', value: 100 } }

// Allen temporal relations
allen('before')                 // { allen: 'before' }

// LP optimization
LP.maximize({ x: 3, y: 5 })    // objective function
LP.constraint({ x: 1, y: 3 }, '<=', 12)  // linear constraint
LP.nonNegative('x', 'y')       // variable bounds

// Fluent sort builder
new SortBuilder('employee')
  .parents(['person'])
  .feature('salary', 'Real')
  .boundConstraint('salary', 'gt', 50000)
  .build()
```

## Development

```bash
pnpm install                    # Install dependencies
pnpm run build                  # Build (dual ESM/CJS + .d.ts)
pnpm run typecheck              # Type check (tsc --noEmit)
pnpm test                       # Run tests
pnpm run test:watch             # Watch mode
pnpm run lint                   # Lint (eslint src/ tests/)
pnpm run test:ci                # Full gate: typecheck + lint + test
pnpm run update-api-spec [url]  # Fetch backend OpenAPI spec + regenerate src/api-spec/generated/
pnpm run docs:build             # Build documentation site
```

> After a backend API change, run the `/sync-api` Claude Code skill to regenerate the
> generated layer and propagate the delta across the hand-written types, normalizers,
> resources, builders, client wiring, exports, and tests.

## Compatibility

| SDK Version | Min Backend Version | Node | Browser |
|-------------|-------------------|------|---------|
| 1.4.0       | Latest            | 18+  | ES2022+ |

## License

MIT
