npm.io
0.1.1 • Published 20h ago

@mnemoscale/sdk

Licence
Apache-2.0
Version
0.1.1
Deps
0
Size
151 kB
Vulns
0
Weekly
0

Mnemos TypeScript SDK (@mnemoscale/sdk)

Typed client for the Mnemos memory API, mirroring the Python SDK (libs/sdk/mnemos_sdk) 1 API-key auth, retry/backoff on 429/5xx, typed errors, job polling, a session helper, and webhook signature verification.

  • Zero runtime dependencies — uses the global fetch: Node >= 20, browsers, and edge runtimes all work.
  • ESM + CJS + .d.ts — plain tsc dual build, no bundler.
  • Generated-spec honesty — the wire types are hand-written for readability but verified 1:1 against the committed OpenAPI snapshot (openapi.json) in CI (see below).

Quickstart (first query in under 5 minutes)

Bring up Mnemos as in the repo-root quickstart.py (compose deps + API + outbox relay + extraction worker, then mnemos-admin tenant create ... for an API key, agent id, and namespace id). Then:

npm install @mnemoscale/sdk        # in this repo: cd sdk-ts && npm ci && npm run build
import { MnemosClient } from "@mnemoscale/sdk";

const client = new MnemosClient("http://localhost:8001", process.env.MNEMOS_API_KEY!);

await client.withSession(
  { agentId: process.env.MNEMOS_AGENT_ID!, namespaceId: process.env.MNEMOS_NAMESPACE_ID! },
  async (s) => {
    // Blocks until the extraction job completes (extractAsync to fire-and-poll later).
    await s.extract([{ content: "the customer asked about delivery delays", role: "user" }]);

    const result = await s.query("delivery delays", { topK: 5, rerank: true });
    for (const r of result.results) {
      if (r.from_working_memory) continue; // session turns merged per PRD §7.1
      console.log(r.hybrid_score, r.content);
    }
  },
);

The session is created before the callback and ended afterwards (even on error) — the analogue of the Python SDK's with client.session(...) as s:. On runtimes with explicit resource management you can also write:

await using s = await client.openSession({ agentId, namespaceId });
const accepted = await s.extractAsync([{ content: "..." }]); // fire...
const job = await client.waitForJob(accepted.job_id, { agentId }); // ...and poll later

Responses are returned exactly as they appear on the wire (snake_case keys, UUIDs/datetimes as strings), fully typed; method parameters are camelCase and mapped to the wire shape by the client.

Surface

Everything the Python SDK exposes: createNamespace, listNamespaces, createAgent, createSession / getSession / endSession, extract / getJob / waitForJob, query (routing, topK, metadataFilter, rerank, scoring, efSearch, maxContextTokens, minRerankLogit), feedback, getUsage, getProfile, warmUp, webhooks (createWebhook / listWebhooks / deleteWebhook / testWebhook), sources (getSource / deleteSource / eraseSources / exportSources), plus the session helper (openSession / withSession) and verifyWebhookSignature.

Every method accepts request options: agentId (X-Agent-Id header), correlationId (X-Correlation-Id, echoed by the API and attached to typed errors), timeoutMs, and signal (AbortSignal). Client options: timeoutMs, maxRetries, retryBackoffBaseMs, retryAfterCapMs, pollScheduleMs, maxPolls, defaultHeaders, and injectable fetch/sleep for tests.

Errors and retries (Python SDK parity)

HTTP failures throw subclasses of MnemosError; every HTTP error is a MnemosApiError carrying status, code, message, correlationId, and retryable:

Status Error Retried?
401 AuthenticationError no
402 PaymentRequiredError (code, manageUrl) never — billing suspended
403 PermissionError no
404 NotFoundError no
409 ConflictError no
422 ValidationError no
429 RateLimitError (code) yes, unless X-Mnemos-Retryable: false
500/502/503/504 ServerError yes

Retry policy (identical to the Python SDK): up to maxRetries (default 3) extra attempts with exponential backoff retryBackoffBaseMs * 2^(attempt-1) (default 100 ms → 100/200/400), no jitter. A server-sent Retry-After (seconds or HTTP-date) overrides the computed backoff for that attempt, capped at retryAfterCapMs (default 30 s).

Transport failures follow the same replay-safety rule as the Python SDK: connection-phase failures (refused, DNS, unreachable — the request provably never reached the server) are retried for any method; ambiguous failures (timeouts, dropped sockets) are only replayed for idempotent requests (GET/DELETE) or POSTs carrying an idempotencyKey. They surface as MnemosConnectionError / MnemosTimeoutError in both SDKs — neither makes you catch the underlying transport library's exception type.

waitForJob polls 250ms → 500ms → 1s → 2s → 5s (then 5 s repeating, max 60 polls) until the job is completed | partial | failed, else throws MnemosTimeoutError — the Python SDK's schedule exactly.

An AbortSignal passed via request options cancels the whole call, including any in-progress backoff/Retry-After or poll sleep: the sleep ends immediately and the call rejects with the abort reason.

Verifying webhooks (PRD §13.2)

import { verifyWebhookSignature, WebhookVerificationError } from "@mnemoscale/sdk";

// In your webhook endpoint, before parsing the body:
try {
  await verifyWebhookSignature(WEBHOOK_SECRET, req.headers["x-mnemos-signature"], rawBody);
} catch (err) {
  if (err instanceof WebhookVerificationError) {
    // err.reason: malformed_header | stale_timestamp | signature_mismatch
    return new Response(null, { status: 400 });
  }
  throw err;
}

Uses WebCrypto (crypto.subtle), hence async — otherwise identical to the Python SDK's verify_webhook_signature (HMAC-SHA-256 over "{t}.{body}", 300 s replay tolerance, constant-time compare).

OpenAPI snapshot

openapi.json is exported from the FastAPI app by scripts/export_openapi.py and committed, so this package's CI needs no network or Python toolchain. Two gates keep it honest: tests/openapi-parity.test.ts verifies every endpoint the client calls and every wire type's property/required sets against the snapshot, and the Python-side contract test tests/contract/test_openapi_snapshot.py (repo root, contract marker) rebuilds the spec from the live app via this script's build_snapshot() and fails CI when the committed snapshot goes stale. When the v1 API changes:

uv sync --all-packages
uv run python sdk-ts/scripts/export_openapi.py > sdk-ts/openapi.json
cd sdk-ts && npm test   # parity test tells you what to update in src/types.ts

Developing

cd sdk-ts
npm ci
npm run typecheck   # tsc strict, src + tests
npm test            # vitest, mocked fetch — no network, no live services
npm run build       # dist/esm + dist/cjs + .d.ts
npm pack            # publish-ready tarball (runs the build via prepack)

The sdk-ts CI job runs exactly these and uploads the npm pack tarball as the mnemos-sdk-npm artifact.

Releasing

Publishing is automated: .github/workflows/release-sdk-ts.yaml publishes to npm with trusted publishing (GitHub OIDC — no NPM_TOKEN to store, and provenance attached automatically) when a sdk-ts-v<version> tag is pushed.

# 1. bump `version` in package.json and land it on main
# 2. rehearse: Actions -> release-sdk-ts -> Run workflow  (stops at --dry-run)
# 3. release (read the version back, so the tag cannot disagree with package.json):
tag="sdk-ts-v$(node -p "require('./package.json').version")"
git tag "$tag" && git push origin "$tag"

The job re-runs typecheck/tests/build against the tagged commit, refuses a tag that disagrees with package.json or a version already on the registry, publishes, and then installs the published package in a scratch directory and imports it through both the ESM and CJS entry points.

The one-time human setup — the @mnemoscale scope and the trusted-publisher entry — is in docs/runbooks/sdk-release.md. The package is licensed Apache-2.0 (LICENSE).

Parity with the Python SDK

sdk-parity.json at the repo root is the shared surface contract between this package and libs/sdk: methods, error taxonomy, constants, retry policy, and request/client options, named in both languages. tests/parity.test.ts and the Python tests/unit/test_sdk_parity.py assert their own side against it, so adding something to one SDK fails the other's suite until it lands there too. The manifest's notes record the differences that are deliberate because the languages differ — milliseconds here versus seconds in Python, one async client here versus a sync and an async one there, AbortSignal versus asyncio cancellation.

Keywords