# JavaScript and TypeScript

`sdk/typescript` is a TypeScript client with no dependencies: it uses the `fetch` built into Node, Deno, Bun and browsers, and Node 22.18+ runs its source directly. Keep the key on your server; never ship it to a browser.

Install it from a clone of [https://github.com/ceyoualigator-debug/jepela](https://github.com/ceyoualigator-debug/jepela) (npm links the folder, and Node runs the TypeScript there):

```bash
git clone https://github.com/ceyoualigator-debug/jepela
npm install ./jepela/sdk/typescript      # then: import { JepelaClient } from "jepela-sdk"
```

```ts
import { JepelaClient, choice, noul, score } from "./sdk/typescript/src/index.ts";

const jepela = new JepelaClient();                       // JEPELA_API_KEY; the key's region picks the address
const r = await jepela.systemOne(
  { message: "We were charged twice for September again.", account: "ACME Logistics" },
  {
    route: choice("Which team should handle this?", { billing: "Charges and refunds", support: "Product problems", other: "None of these" }),
    refund: noul("Is the customer asking for money back?"),
    urgency: score("How urgent is this?", ["Can wait a week", "Should be handled today", "Blocking the customer now"]),
  },
  { subject: "acme", memory: { compare: true } },
);

r.answers.route.choice;          // typed: "billing" | "support" | "other"
r.answers.refund.noul;           // probability of yes
r.memory?.without_memory;        // the same call without the memory
r.warnings;                      // known traps in this request
r.usage.answers_billed;
```

`new JepelaClient({ apiKey, baseUrl, timeoutMs, maxRetries, backoffMs })`: the key falls back to `JEPELA_API_KEY`, the gateway to `JEPELA_BASE_URL` and then the API of the key's region (see Regions); by default 120 000 ms per call, 2 retries, 500 ms backoff.

Every route has a method: `remember`, `forget`, `delete`, `memory`, `rulesAdd`, `rules`, `rulesDelete`, `pin`, `exclude`, `aliases`, `feedback`, `quality`, `calibrationFit`, `calibration`, `goldenAdd`, `golden`, `goldenRun`, `goldenDelete`, `decisionsDelete`, `batchCreate`, `batchUpload`, `batch`, `batches`, `batchResults`, `batchWait`, `finetune`, `finetunes`, `finetuneJob`, `finetuneWait`, `finetuneDelete`, `models`, `usage`, and `signup(email, { inviteCode })`, which needs no key. `request(method, path, body)` calls any route.

```ts
await jepela.memory("acme", true);                   // lines and rules, word for word
await jepela.rulesAdd("mia", "session_result <= -30", "Mia's stop-loss is reached: she stops playing now.", { ttlSeconds: 3600 });
await jepela.pin("acme", "ACME pays for premium support.");
await jepela.exclude("player-9", ["gold_coins"], "Coin bug.", { question: "reward" });
await jepela.aliases("acct-1", ["Globex Corporation"]);
await jepela.goldenDelete(["refund-1"]);             // [] deletes nothing; no argument deletes every case
await jepela.request("GET", "/v1/usage");
```

With `memory: { explain: true }` the answer says what each memory line did; pins and exclusions show up too:

```ts
const e = await jepela.systemOne(state, questions, { subject: "player-9", memory: { explain: true } });
e.memory?.pinned;                // ids of the pinned rules sent
e.memory?.excluded;              // { reward: { gold_coins: "Coin bug." } }
e.memory?.exclusions_skipped;    // exclusions that would have removed every option
e.memory?.explain;               // [{ line, kind: "pinned" | "rule" | "recalled", effect: { question: move } }]
```

Fine-tuning trains a model of your own on your golden cases (at least 20; see Fine-tuning):

```ts
await jepela.finetunes();                               // { jobs, min_cases: 20, bases: ["english", "multilingual", "typed-decisions"] }
const job = await jepela.finetune("english");           // { job, model, cases, held_out, state: "running" }
const done = await jepela.finetuneWait(job.job);        // polls finetuneJob(job.job); default 3 600 000 ms, every 10 000 ms
if (done.usable) await jepela.systemOne(state, questions, { model: job.model });
await jepela.finetuneDelete(job.model);                 // its files are deleted and its name stops working
```

Options on `systemOne`: `model`, `subject`, `memory` (`use`, `top_k`, `min_share`, `compare`, `placebo`, `names`, `question_words`, `focus`, `explain`; see Request options), `robust` (`true` or `{ orders: 1-5 }`, choices of up to 20 options), `windows` (`true` or `{ combine: { question_id: "max" | "mean" | "min" } }`), `derive`, `values`, `cache`, `method` (`"auto"`, the default, matches choices of more than 20 options by vectors and asks the engine the rest; `"engine"` reads every option; `"match"` matches every choice). Each answer has `method` (`"engine"` or `"match"`), and a matched one `similarity` (`{ best, second }`). Measured on BANKING77 (600 bank messages, 77 intents) through the gateway: matching 65.2% right in a median of 2 ms, the engine reading all 77 options 62.2% in 209 ms (Choice).

Errors are classes per status (`AuthenticationError`, `PaymentRequiredError`, `PermissionDeniedError`, `NotFoundError`, `ConflictError`, `BadRequestError`, `RateLimitError` with `retryAfter`, `EngineError`, `ServerError`, `ConnectionFailed`). The client retries 429, 502 and 503 with backoff, and a connection that failed before the request left; a decision that may have arrived is never sent again, so it cannot be billed twice.

The source is not type-checked in this repository (`tsc` is not installed here); `tsconfig.json` has the settings for checking it. Its tests run against the real engine: `python tests/real_steps/test_jepela_typescript_sdk.py`.

## Without the SDK

```javascript
const res = await fetch(`${process.env.JEPELA_BASE_URL}/v1/systemone`, {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.JEPELA_API_KEY}`, "Content-Type": "application/json" },
  body: JSON.stringify({ state, questions, subject }),
});
const data = await res.json();
if (!res.ok) throw new Error(`${res.status} ${data.error.type}: ${data.error.message}`);
```
