npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@majkapp/majk-chat-common

v1.0.82

Published

Batteries-included embedding API for Majk Chat — one import, one create() call

Readme

@majkapp/majk-chat-common

Batteries-included embedding API for majk-chat. One import. One create() call. Full LLM agent — tools, sessions, token tracking — wired and ready.


Table of Contents

  1. Installation
  2. Quick Start
  3. Credential Resolution
  4. Tool Presets
  5. Custom Tools
  6. Permission Gates
  7. Session Persistence
  8. Token Tracking & Cost Estimation
  9. Context Management
  10. MCP Integration
  11. Callbacks
  12. Streaming
  13. Full Loop History
  14. Lifecycle Extensions
  15. Full Config Reference

Installation

npm install @majkapp/majk-chat-common

Node ≥ 18 required. The package is ESM-compatible and ships full TypeScript declarations.


Quick Start

import { MajkChat } from '@majkapp/majk-chat-common';

// Create an agent (read-only tools, context trimming, 10-step tool loop)
const agent = await MajkChat.create({ provider: 'anthropic' });

const { text, toolsUsed, stepsTaken } = await agent.chat(
  'List the TypeScript files in src/ and summarise what each does.'
);

console.log(text);
console.log('Tools called:', toolsUsed);
console.log('Steps taken:', stepsTaken);

await agent.destroy();

One-shot helper

For a single question that needs no session management:

import { chat } from '@majkapp/majk-chat-common';

const answer = await chat('What is the capital of France?', {
  provider: 'anthropic',
  tools: 'none',
});
console.log(answer); // "Paris"

Credential Resolution

Credentials are resolved once when MajkChat.create() is called. The first truthy value in the chain wins:

config.apiKey                    ← HIGHEST PRIORITY
  ↓ (if absent)
process.env  (shell / CI)        ← MEDIUM PRIORITY
  ↓ (if absent)
.env.local file                  ← LOWEST PRIORITY

Standard environment variable names

| Provider | Variable(s) | |------------------|-------------------------------------------------------------------------| | anthropic | ANTHROPIC_API_KEY | | openai | OPENAI_API_KEY · OPENAI_ORG_ID (optional) | | azure-openai | AZURE_OPENAI_API_KEY · AZURE_OPENAI_ENDPOINT · AZURE_OPENAI_DEPLOYMENT | | bedrock | AWS_ACCESS_KEY_ID · AWS_SECRET_ACCESS_KEY · AWS_REGION · AWS_SESSION_TOKEN |

Common patterns

Development / testing — put keys in .env.local (git-ignored):

# .env.local
ANTHROPIC_API_KEY=sk-ant-...
OPENAI_API_KEY=sk-...

CI / Production — inject through your deployment environment:

export ANTHROPIC_API_KEY=${{ secrets.ANTHROPIC_API_KEY }}

Programmatic (multi-tenant, per-user keys) — pass apiKey directly:

const agent = await MajkChat.create({
  provider: 'anthropic',
  apiKey: await vault.getSecret('anthropic-key'),
});

Tool Presets

Pass a ToolPreset string to the tools option for the most common configurations:

| Preset | Description | |----------------|------------------------------------------------------------------| | 'read-only' | ls, read, glob, grep, web_fetch, document parsers. Default. | | 'all' | Everything in read-only plus bash, write, edit, process management | | 'filesystem' | All filesystem tools (read + write), no web or documents | | 'web' | web_fetch only | | 'documents' | Document parsers only (PDF, Excel, Word, CSV) | | 'planning' | Planning-mode tools (enter_planning_mode, todo_write, etc.) | | 'none' | No tools — pure LLM conversation |

// Safe codebase analysis — no writes possible
const agent = await MajkChat.create({
  provider: 'openai',
  tools: 'read-only',
});

// Full agentic automation
const agent = await MajkChat.create({
  provider: 'anthropic',
  tools: 'all',
});

// Minimal — just chat, no tools
const agent = await MajkChat.create({
  provider: 'anthropic',
  tools: 'none',
});

Custom Tools

Pass FunctionToolDef objects alongside (or instead of) a preset:

import { MajkChat, defineTool } from '@majkapp/majk-chat-common';

// Type-safe definition with defineTool<TArgs>()
const lookupCustomer = defineTool<{ id: string }>({
  name: 'lookup_customer',
  description: 'Return a customer record by their unique ID.',
  parameters: {
    type: 'object',
    properties: { id: { type: 'string', description: 'Customer UUID' } },
    required: ['id'],
  },
  execute: async ({ id }, context) => {
    console.log('conversation:', context.conversationId);
    const customer = await db.customers.findById(id);
    return customer; // Objects are auto-serialized to JSON for the LLM
  },
});

const sendEmail = defineTool<{ to: string; subject: string; body: string }>({
  name: 'send_email',
  description: 'Send an email.',
  parameters: {
    type: 'object',
    properties: {
      to:      { type: 'string' },
      subject: { type: 'string' },
      body:    { type: 'string' },
    },
    required: ['to', 'subject', 'body'],
  },
  sideEffects: 'external', // Classify side-effect level
  execute: async ({ to, subject, body }) => {
    await mailer.send({ to, subject, body });
    return `Email sent to ${to}`;
  },
});

const agent = await MajkChat.create({
  provider: 'anthropic',
  // Mix a preset with custom tools in one array
  tools: [lookupCustomer, sendEmail],
});

ToolExecutionContext

The second argument to execute() provides runtime context:

execute: async (args, context) => {
  context.conversationId; // string — stable per-agent-instance ID
  context.sessionId;      // string | undefined — set when sessions are configured
  ...
}

Using ToolCatalog for individual built-in tools

import { MajkChat, ToolCatalog } from '@majkapp/majk-chat-common';

const agent = await MajkChat.create({
  provider: 'anthropic',
  tools: [
    { executor: ToolCatalog.filesystem.Read.executor, readOnly: true, sideEffects: 'read', pkg: 'basic-tools', description: 'Read files' },
    myCustomTool,
  ],
});

// Or access directly via the static property:
MajkChat.tools.filesystem.Bash
MajkChat.tools.web.WebFetch
MajkChat.tools.documents.UniversalAnalyzer

Permission Gates

FunctionToolDef.requiresPermission lets you gate individual tool invocations:

| Value | Behavior | |--------------------|-----------------------------------------------------------------| | undefined / false | No gate — always allow execution (default) | | true | Requires handler — denied when no handler present (safe-side) | | (args) => boolean \| Promise<boolean> | Async predicate — true = allow, false = deny |

// Always deny — useful for a "stub" tool you want the LLM to see but not run
const restrictedOp = defineTool<{ path: string }>({
  name: 'delete_file',
  description: 'Delete a file.',
  parameters: { type: 'object', properties: { path: { type: 'string' } }, required: ['path'] },
  requiresPermission: true,  // No handler configured → denied automatically
  execute: async ({ path }) => { /* never reached */ },
});

// Dynamic gate — inspect the specific args before deciding
const dangerousWrite = defineTool<{ path: string; content: string }>({
  name: 'write_config',
  description: 'Overwrite a config file.',
  parameters: {
    type: 'object',
    properties: { path: { type: 'string' }, content: { type: 'string' } },
    required: ['path', 'content'],
  },
  requiresPermission: async ({ path }) => {
    // Only allow writes inside the project directory
    return path.startsWith(process.cwd());
  },
  execute: async ({ path, content }) => {
    await fs.writeFile(path, content, 'utf-8');
    return `Written: ${path}`;
  },
});

Session Persistence

In-memory (default, auto-cleared on restart)

const agent = await MajkChat.create({
  provider: 'anthropic',
  sessions: 'memory',
  sessionId: 'project-onboarding',
  autoSave: true,       // Default when sessions is configured
});

// History persists across chat() calls within the same process
await agent.chat('My name is Alice.');
await agent.chat('What is my name?'); // → "Your name is Alice."

Filesystem sessions (persist across restarts)

const agent = await MajkChat.create({
  provider: 'anthropic',
  sessions: { type: 'filesystem', path: './sessions' },
  sessionId: 'user-alice',  // Loads existing data if the ID exists
});

await agent.chat('Remember: my favourite colour is blue.');
await agent.destroy();

// In a later process — same session ID restores history automatically:
const agent2 = await MajkChat.create({
  provider: 'anthropic',
  sessions: { type: 'filesystem', path: './sessions' },
  sessionId: 'user-alice',
});
const { text } = await agent2.chat('What is my favourite colour?');
// → "Your favourite colour is blue."
await agent2.destroy();

Custom session store (Redis, DynamoDB, Postgres, …)

Implement the SessionStore interface:

import type { SessionStore, SessionStoreData, SessionStoreSummary } from '@majkapp/majk-chat-common';
import { createClient } from 'redis';

class RedisSessionStore implements SessionStore {
  constructor(private redis: ReturnType<typeof createClient>) {}

  async save(id: string, data: SessionStoreData): Promise<void> {
    await this.redis.set(`session:${id}`, JSON.stringify(data));
  }

  async load(id: string): Promise<SessionStoreData | null> {
    const raw = await this.redis.get(`session:${id}`);
    if (!raw) return null;
    const parsed = JSON.parse(raw);
    parsed.createdAt = new Date(parsed.createdAt);
    parsed.updatedAt = new Date(parsed.updatedAt);
    return parsed;
  }

  async list(): Promise<SessionStoreSummary[]> {
    const keys = await this.redis.keys('session:*');
    // ... load and summarise
    return [];
  }

  async delete(id: string): Promise<void> {
    await this.redis.del(`session:${id}`);
  }
}

const agent = await MajkChat.create({
  provider: 'anthropic',
  sessions: new RedisSessionStore(redisClient),
  sessionId: 'user-42',
});

Manual session management

// Explicit save
const sessionId = await agent.saveSession('my-session-id');

// Load into an existing agent
await agent.loadSession('my-session-id');

// Inspect history
const history = agent.getHistory(); // Message[]

// Clear history (does not delete persisted data)
agent.clearHistory();

// List all sessions
const sessions = await agent.listSessions(); // SessionStoreSummary[]

Token Tracking & Cost Estimation

Every chat() call returns token usage for that turn. You can also register a global callback for streaming events and aggregate stats.

Per-call usage

const { text, tokenUsage } = await agent.chat('Hello!');

if (tokenUsage) {
  console.log(tokenUsage.inputTokens);    // Prompt tokens
  console.log(tokenUsage.outputTokens);   // Completion tokens
  console.log(tokenUsage.totalTokens);    // Sum
  console.log(tokenUsage.cacheReadTokens);  // Anthropic prompt-cache hits
  console.log(tokenUsage.cacheWriteTokens); // Anthropic prompt-cache writes
}

Global tracker callback

const agent = await MajkChat.create({
  provider: 'anthropic',
  tokenTracker: async (event) => {
    await analytics.track('llm_token_usage', {
      model:     event.model,
      provider:  event.provider,
      input:     event.inputTokens,
      output:    event.outputTokens,
      total:     event.totalTokens,
      cost:      event.estimatedCostUsd,
      sessionId: event.conversationId,
    });
  },
});

Aggregate stats

const stats = agent.getTokenStats();
console.log(stats.totalInputTokens);    // Across all chat() calls
console.log(stats.totalOutputTokens);
console.log(stats.requestCount);
console.log(stats.estimatedTotalCostUsd); // Set when pricing is configured
console.log(stats.byModel);             // Breakdown per model ID

// Reset counters (e.g. per billing period)
agent.resetTokenStats();

Cost estimation

const agent = await MajkChat.create({
  provider: 'anthropic',
  pricing: {
    perMillionInputTokens:  3.00,   // USD per 1M prompt tokens
    perMillionOutputTokens: 15.00,  // USD per 1M completion tokens
    perMillionCacheReadTokens:  0.30,
    perMillionCacheWriteTokens: 3.75,
    // Per-model overrides:
    models: {
      'claude-haiku-4': {
        perMillionInputTokens:  0.80,
        perMillionOutputTokens: 4.00,
      },
    },
  },
});

const { tokenUsage } = await agent.chat('Summarise the repo.');
console.log(`This call cost $${tokenUsage?.estimatedCostUsd?.toFixed(6)}`);
console.log(`Running total: $${agent.getTokenStats().estimatedTotalCostUsd?.toFixed(4)}`);

Context Management

Automatic prompt-size management is on by default. Tune or disable it via contextManagement:

const agent = await MajkChat.create({
  provider: 'anthropic',
  contextManagement: {
    enabled: true,                // Default: true
    maxTotalChars: 180_000,       // ~45k tokens. Prune oldest messages when exceeded.
    maxToolResultLength: 6_000,   // Truncate tool results larger than this.
    minMessages: 3,               // Always keep at least this many recent messages.
  },
});

To disable entirely (e.g. when you manage the context window yourself):

contextManagement: { enabled: false }

MCP Integration

Connect any Model Context Protocol server:

// From a config file
const agent = await MajkChat.create({
  provider: 'anthropic',
  mcp: { configPath: './mcp-servers.json' },
});

// Inline JSON string
const agent = await MajkChat.create({
  provider: 'anthropic',
  mcp: {
    configString: JSON.stringify({
      mcpServers: {
        github: {
          command: 'npx',
          args: ['-y', '@modelcontextprotocol/server-github'],
          env: { GITHUB_PERSONAL_ACCESS_TOKEN: process.env.GITHUB_TOKEN! },
        },
      },
    }),
  },
});

// Programmatic server definitions
const agent = await MajkChat.create({
  provider: 'anthropic',
  mcp: {
    servers: {
      'my-db-server': {
        command: 'node',
        args: ['./mcp-servers/db.js'],
        env: { DB_URL: process.env.DATABASE_URL! },
      },
    },
  },
});

// Always call destroy() to close MCP connections cleanly
await agent.destroy();

Callbacks

Observe every tool call and result in real time:

const agent = await MajkChat.create({
  provider: 'anthropic',
  onToolCall: (toolName, args) => {
    console.log(`→ ${toolName}`, args);
  },
  onToolResult: (toolName, result, success) => {
    console.log(`← ${toolName} ${success ? '✓' : '✗'}`, result.slice(0, 200));
  },
});

Streaming

agent.stream(message, options?) returns an AsyncIterableIterator<StreamEvent> that yields one event for every meaningful thing that happens in the tool loop — LLM responses, tool calls, tool results, step boundaries, and a final complete event when the loop exits.

Basic iteration

import { MajkChat } from '@majkapp/majk-chat-common';
import type { StreamEvent } from '@majkapp/majk-chat-common';

const agent = await MajkChat.create({ provider: 'anthropic' });

for await (const event of agent.stream('Refactor the auth module')) {
  switch (event.type) {
    case 'step_start':
      console.log(`Step ${event.data.step} of ${event.data.maxSteps}`);
      break;
    case 'tool_calls_detected':
      for (const tc of event.data.toolCalls)
        console.log('  calling:', tc.function.name);
      break;
    case 'tool_result':
      console.log('  result:', event.data.result);
      break;
    case 'complete':
      console.log('Final answer:', event.data.response.choices[0].message.content);
      break;
  }
}

StreamEvent types

| event.type | Emitted when | Key fields in event.data | |---|---|---| | start | Loop begins | maxSteps | | step_start | A new iteration starts | step, maxSteps | | message | LLM returns a complete response (default mode) | response | | content_start | Streaming token block opens | index, contentBlock | | content_delta | Token delta arrives (streamTokens mode) | index, delta | | content_end | Streaming token block closes | index | | tool_calls_detected | LLM requested one or more tools | toolCalls | | tool_result | A tool finished executing | toolCall, result | | step_complete | One iteration finished | step, toolCallCount | | complete | Loop finished, final response ready | response, steps | | error | Unrecoverable error | error |

Token-delta mode vs complete-message mode

By default stream() waits for the full LLM response before emitting a message event — clean, no partial text.

Set streamTokens: true to receive incremental content_delta events as the model generates each token (useful for real-time UI rendering):

// Complete-message mode (DEFAULT) — one 'message' event per LLM call
for await (const ev of agent.stream('Summarise this')) { ... }

// Token-delta mode — 'content_delta' events fire as tokens arrive
for await (const ev of agent.stream('Summarise this', { streamTokens: true })) {
  if (ev.type === 'content_delta') process.stdout.write(ev.data.delta.text ?? '');
}

The two modes are independent of step events — tool calls, tool results, and step boundaries are emitted in both modes.

Stopping the loop

Break out of the iterator — immediate, no further events consumed:

for await (const ev of agent.stream('Long task')) {
  if (shouldStop) break;          // loop exits; history unchanged
}

AbortSignal — cancel from a different scope (e.g. HTTP request timeout); the loop exits cleanly at the next step boundary:

const controller = new AbortController();
setTimeout(() => controller.abort(), 30_000); // 30-second deadline

for await (const ev of agent.stream('Long task', { signal: controller.signal })) {
  // ...
}

StreamOptions

interface StreamOptions {
  streamTokens?: boolean;   // false (default) = complete-message mode
                            // true = token-delta mode
  signal?: AbortSignal;     // Cancel the loop from another scope
}

History and auto-save

Conversation history and auto-save behave identically to chat(): both the user turn and the final assistant turn are appended to history only when the complete event is received. If you break before complete, history is unchanged.


Full Loop History

By default chat() returns only the final assistant message. Set includeFullHistory: true to get every message that passed through the orchestrator — system prompt, user turn, every intermediate assistant message, every tool call, and every tool result:

const agent = await MajkChat.create({
  provider: 'anthropic',
  includeFullHistory: true,
});

const { text, completeMessages } = await agent.chat('What files are in src/?');

// completeMessages is Message[] in OpenAI wire format:
// [
//   { role: 'system',    content: '...' },
//   { role: 'user',      content: 'What files are in src/?' },
//   { role: 'assistant', content: null, tool_calls: [{ id: 'tc-1', ... }] },
//   { role: 'tool',      content: '["index.ts","types.ts"]', tool_call_id: 'tc-1' },
//   { role: 'assistant', content: 'The files in src/ are ...' },
// ]
console.log(completeMessages);

The format is normalised to OpenAI message objects regardless of which provider is used — every provider adapter maps its native wire format to this shape before the orchestrator processes messages.

stream() always returns full history in the complete event's response.complete_messages field without requiring this flag.


Lifecycle Extensions

Extensions add hooks that run at every stage of the orchestrator loop. Pass an array of OrchestratorExtension objects to MajkChat.create():

import { MajkChat } from '@majkapp/majk-chat-common';
import type { OrchestratorExtension, ExecutionContext } from '@majkapp/majk-chat-common';

const auditExtension: OrchestratorExtension = {
  name: 'audit-log',
  priority: 10,          // Lower number = runs earlier when multiple extensions registered

  async beforeStep(ctx: ExecutionContext): Promise<ExecutionContext> {
    console.log(`[audit] step ${ctx.step} starting`);
    return ctx;
  },

  async afterChatCompletion(
    ctx: ExecutionContext,
    response: any
  ): Promise<ExecutionContext> {
    console.log(`[audit] LLM responded at step ${ctx.step}`);
    return ctx;
  },

  async afterToolExecution(
    ctx: ExecutionContext,
    toolCall: any,
    result: string
  ): Promise<ExecutionContext> {
    await db.audit.insert({ step: ctx.step, tool: toolCall.function.name, result });
    return ctx;
  },
};

const agent = await MajkChat.create({
  provider: 'anthropic',
  extensions: [auditExtension],
});

All 8 hooks

| Hook | Called | Arguments beyond ctx | |---|---|---| | beforeExecution | Once before the entire loop starts | — | | beforeStep | Before each iteration | — | | beforeChatCompletion | Before each LLM API call | request | | afterChatCompletion | After each LLM API call | response | | beforeToolExecution | Before each tool is run | toolCall | | afterToolExecution | After each tool finishes | toolCall, result | | afterStep | After each iteration completes | — | | afterExecution | Once after the entire loop finishes | response |

Every hook receives an ExecutionContext (carrying step, messages, request, and arbitrary metadata) and must return it (optionally mutated). Mutations accumulate — later hooks see changes made by earlier ones.

Common patterns

// Token budget guard — abort if too many steps
const stepGuard: OrchestratorExtension = {
  name: 'step-guard',
  priority: 1,
  async beforeStep(ctx) {
    if (ctx.step >= 5) throw new Error('Step limit exceeded');
    return ctx;
  },
};

// Request mutation — inject dynamic context into every LLM call
const contextInjector: OrchestratorExtension = {
  name: 'context-injector',
  priority: 5,
  async beforeChatCompletion(ctx, request) {
    request.messages = [
      ...request.messages,
      { role: 'system', content: `Current user: ${getCurrentUser()}` },
    ];
    return ctx;
  },
};

// Multiple extensions — all run in priority order
const agent = await MajkChat.create({
  provider: 'anthropic',
  extensions: [stepGuard, contextInjector, auditExtension],
});

Full Config Reference

interface MajkChatConfig {
  // ── Required ────────────────────────────────────────────────────────────────
  provider: 'anthropic' | 'openai' | 'azure-openai' | 'bedrock' | 'anthropic-bedrock';

  // ── Credentials ─────────────────────────────────────────────────────────────
  apiKey?:   string;   // Explicit key — highest priority (see Credential Resolution)
  model?:    string;   // Defaults: anthropic→'claude-sonnet-4-5', openai→'gpt-4o'

  // ── Agent identity ───────────────────────────────────────────────────────────
  systemPrompt?:       string;   // Replaces default system prompt entirely
  appendSystemPrompt?: string;   // Appended to default (or custom) system prompt

  // ── Tools ───────────────────────────────────────────────────────────────────
  tools?: ToolPreset                                          // 'read-only' (default)
        | Array<ToolCatalogEntry | FunctionToolDef | ToolExecutor>;

  // ── MCP ─────────────────────────────────────────────────────────────────────
  mcp?: {
    configPath?:   string;                         // Path to JSON config file
    configString?: string;                         // Inline JSON string
    servers?:      Record<string, { command: string; args?: string[]; env?: Record<string, string> }>;
  };

  // ── Sessions ─────────────────────────────────────────────────────────────────
  sessions?:  'memory'
            | { type: 'filesystem'; path: string }
            | SessionStore;          // Custom (Redis, DynamoDB, etc.)
  sessionId?:  string;               // Resume existing or name new session
  autoSave?:   boolean;              // Default: true when sessions is set

  // ── Context management ───────────────────────────────────────────────────────
  contextManagement?: {
    enabled?:             boolean;   // Default: true
    maxTotalChars?:       number;    // Default: 180,000
    maxToolResultLength?: number;    // Default: 6,000
    minMessages?:         number;    // Default: 3
  };

  // ── Execution ────────────────────────────────────────────────────────────────
  maxSteps?: number;                 // Max tool-use iterations per chat(). Default: 10

  // ── Streaming & history ──────────────────────────────────────────────────────
  includeFullHistory?: boolean;      // Populate completeMessages in chat() response. Default: false

  // ── Lifecycle extensions ─────────────────────────────────────────────────────
  extensions?: OrchestratorExtension[];  // Custom hooks run at each loop stage

  // ── Callbacks ────────────────────────────────────────────────────────────────
  onToolCall?:   (toolName: string, args: Record<string, unknown>) => void | Promise<void>;
  onToolResult?: (toolName: string, result: string, success: boolean) => void | Promise<void>;

  // ── Token tracking ───────────────────────────────────────────────────────────
  tokenTracker?: TokenTracker | ((event: TokenUsageEvent) => void | Promise<void>);
  pricing?: {
    perMillionInputTokens?:      number;
    perMillionOutputTokens?:     number;
    perMillionCacheReadTokens?:  number;
    perMillionCacheWriteTokens?: number;
    models?: Record<string, { perMillionInputTokens?: number; perMillionOutputTokens?: number }>;
  };
}

MajkChatResponse

interface MajkChatResponse {
  text:               string;            // Final assistant text
  stepsTaken:         number;            // Number of tool-use iterations
  toolsUsed:          string[];          // Tool names called this turn
  tokenUsage?:        TokenUsageEvent;   // Token counts (undefined if provider omits them)
  completeMessages?:  Message[];         // Full wire-level history (set when includeFullHistory: true)
}

MajkChat instance methods

| Method | Description | |--------|-------------| | chat(message) | Send a message; returns MajkChatResponse | | stream(message, opts?) | Yield StreamEvent objects in real time; see Streaming | | getHistory() | Current conversation as Message[] | | clearHistory() | Reset in-memory history (does not delete persisted session) | | getSessionId() | Active session ID, if any | | saveSession(id?) | Explicitly persist; returns session ID used | | loadSession(id) | Replace history from a stored session | | listSessions() | All sessions in the configured store | | getTokenStats() | Aggregate TokenStats across all chat() calls | | resetTokenStats() | Zero out aggregate counters | | destroy() | Tear down MCP connections and cleanup |


License

MIT