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

@metrio-ai/client

v1.6.4

Published

The official Metrio AI SDK for JavaScript and TypeScript

Readme

@metrio-ai/client

The official JavaScript/TypeScript SDK for MetrioAI — native fetch (no HTTP dependencies), automatic retries, and full type definitions. The metrio CLI now ships separately as @metrio-ai/cli. If you installed the old CLI globally, run npm rm -g @metrio-ai/client before npm i -g @metrio-ai/cli.

Installation

npm install @metrio-ai/client   # or: yarn add / pnpm add @metrio-ai/client

Quick Start

import { MetrioAI } from '@metrio-ai/client';
// CommonJS: const { MetrioAI } = require('@metrio-ai/client');

const client = new MetrioAI({
  apiKey: 'your-api-key',             // or set METRIOAI_API_KEY in the environment
  // Optional:
  baseUrl: 'https://api.metrio.ai',   // default (or METRIOAI_BASE_URL)
  maxRetries: 3,                      // retry attempts for failed requests
  retryDelay: 500                     // base delay between retries (ms)
});

const response = await client.chatCompletion({
  promptId: 1,
  messages: [
    { role: 'system', content: { type: 'text', text: 'You are a helpful assistant.' } },
    { role: 'user', content: { type: 'text', text: 'What is the capital of France?' } }
  ]
});
console.log(response.response);

Providers and Models

const providers = await client.providers();
console.log(providers.providers);        // ['openai', 'anthropic', 'gemini', 'xai', ...]

const models = await client.models('openai');
console.log(models.models);              // ['gpt-3.5-turbo', 'gpt-4', ...]

Chat Completion

const response = await client.chatCompletion({
  promptId: 1,
  messages: messages,
  // Optional parameters
  variables: [{ name: 'customVar', value: 'customValue' }],
  tag: 'custom-tag',       // Version tag to pin (defaults to active version)
  timezone: 'Asia/Taipei', // IANA timezone — adds local time + offset + weekday context
  tags: ['test']           // Request-level tags recorded on the run (see "Request Tags")
});

console.log(response.response);             // The AI-generated response
console.log(response.inputTokens);          // Number of input tokens
console.log(response.outputTokens);         // Number of output tokens
console.log(response.cacheReadTokens);      // Tokens read from prompt cache (if applicable)
console.log(response.cacheCreationTokens);  // Tokens written to prompt cache (if applicable)
console.log(response.elapsedTime);          // Time taken in milliseconds
console.log(response.elapsedTimeFormatted); // Human-readable elapsed time (e.g., "1.5s")
console.log(response.logId);                // Log ID for conversation history
console.log(response.taskId);               // Task ID for agent orchestration tracking

Note: chatCompletion no longer requires projectId — your project is determined by the API key. The field is still accepted for backward compatibility but is silently ignored.

Request Tags

Both chatCompletion and workflowCompletion accept an optional tags: string[]. Tags are recorded on the run so logs can later be filtered by tag — for example, tag automated traffic with ['test'] to keep it out of your real-usage metrics.

Validation (enforced client-side before the request is sent, mirroring the API):

  • At most 5 tags per request.
  • Each tag is 1–32 characters matching ^[a-z0-9][a-z0-9_-]{0,31}$.
  • An empty array is treated as no tags and the field is omitted from the request.

An invalid tag throws a MetrioApiError and no request is sent.

Streaming Responses

const completion = await client.chatCompletion({
  promptId: 1,
  messages: messages,
  stream: true
}, (chunk) => {
  console.log('Received chunk:', chunk.chunk);   // called for each chunk
});

console.log('Final response:', completion.response);  // complete response when done

The SDK handles both SSE-formatted chunks (parsed as JSON with full metadata) and plain text lines, and combines all chunks into the final response.

MCP Tools Requiring Approval

For MCP (Model Context Protocol) tools that need human approval before execution, list them in toolsRequiringApproval. When approval is needed the response includes a logId; make a follow-up call with the user's approval message and that logId, and MetrioAI retrieves the stored conversation history and continues execution.

// Step 1: Initial request with tools requiring approval
const initialResponse = await client.chatCompletion({
  promptId: 1,
  messages: [{ role: 'user', content: { type: 'text', text: 'Send a message to the team on Slack about the deployment' } }],
  toolsRequiringApproval: ['send-slack-message', 'post-to-channel']
});

// Step 2: After the user approves, continue with the logId
const approvalResponse = await client.chatCompletion({
  promptId: 1,
  messages: [{ role: 'user', content: { type: 'text', text: 'Approved. Please proceed.' } }],
  logId: initialResponse.logId
});

Multimodal Input

Messages support text, images, PDFs, and binary content:

const completion = await client.chatCompletion({
  promptId: 1,
  messages: [
    {
      role: 'user',
      content: {
        type: 'image',                 // or 'pdf' with mime 'application/pdf'
        mime: 'image/jpeg',
        data: 'base64encodedimagedata...'
      }
    }
  ]
});

Workflow Completion

Run a workflow by its slug. This is parallel to chatCompletion — same base URL, same auth, same messages shape — but it executes a multi-node workflow and returns a structured reply plus a per-node trace. It is non-streaming.

const result = await client.workflowCompletion({
  workflowId: 'groceriesfriends-order',           // required — the workflow slug
  messages: [
    { role: 'user', content: { type: 'text', text: '我要訂 5 包麵粉' } }
  ],
  // Optional
  version: 3,                                     // pin a workflow version; omit to run the active version
  channel: { User: { name: '王小明', customer_id: '44785' } }, // surfaced as $channel
  variables: { region: 'TW' },                    // surfaced as $variables
  timezone: 'Asia/Taipei',
  tags: ['test'],                                 // see "Request Tags"
  // trace: 'full',                               // per-node detail level ('path' | 'full')
  idempotencyKey: 'line-msg-123',                 // e.g. the platform message ID; a retry with the same key
                                                  // (within 24h) replays the first result instead of re-running
});

// Prefer the structured `reply` object over re-parsing `response`:
console.log(result.reply.responseType);   // 'text' | 'struct' | 'none'
console.log(result.reply.content);        // text content (responseType === 'text')
console.log(result.reply.structContent);  // Unified Block Message (responseType === 'struct')
console.log(result.version);              // the workflow version actually executed
console.log(result.cost);                 // total cost of the run
console.log(result.llmCalls);             // number of LLM calls
console.log(result.elapsedTime);          // elapsed time in ms
console.log(result.nodeTrace);            // per-node observability trace
console.log(result.logId);                // workflow_runs.id (-1 if persistence failed)
console.log(result.taskId);
console.log(result.terminalNodeId);       // the node id the run ended on
console.log(result.tags);                 // request tags ∪ node run_tags actually recorded on the run
console.log(result.state);                // whatever the workflow's own "state" nodes wrote

Non-2xx responses throw a MetrioApiError with the HTTP status preserved, so callers can distinguish a missing workflow (404) from other request errors. A disabled workflow is not an error at all — the request succeeds (200) with skipped: true and a benign reply of responseType: "none" (its reason names the disabled workflow); no DAG runs and no workflow_runs row is recorded:

try {
  const result = await client.workflowCompletion({ workflowId: 'maybe-disabled', messages });
  if (result.skipped) {
    // workflow exists but is disabled — 200 response, reply.responseType === 'none'
  }
} catch (err) {
  if (err instanceof MetrioApiError && err.statusCode === 404) {
    // workflow doesn't exist in this project
  }
}

Agents

Run Metrio agents with client.agents.runs. Ids are numbers; previousRunId, maxTurns, tags, variables and timezone are optional.

const messages: MessageV2[] = [{ role: 'user', content: { type: 'text', text: 'Summarize today' } }];

// Sync (default): waits for the run to finish
const run = await client.agents.runs.create(7, { messages });
console.log(run.status, run.output);

// Async: returns the queued run; poll with get
const queued = await client.agents.runs.create(7, { messages, mode: 'async' });
let current = queued;
while (current.status === 'queued' || current.status === 'running') {
  await new Promise((r) => setTimeout(r, 1000));
  current = await client.agents.runs.get(7, queued.runId);
}

// get also lists every sub-agent run below it (summary only; get a child's output by its own ids)
for (const child of current.children ?? []) {
  console.log(child.agentId, child.runId, child.status, child.cost);
}

// Streaming: ends when the server closes the stream
for await (const ev of client.agents.runs.createStream(7, { messages })) {
  if (ev.event === 'text_delta') process.stdout.write(ev.data.text);
  else if (ev.event === 'final') console.log('\n', ev.data.output);
  else if (ev.event === 'error') console.error(ev.data.error);
}

When a run stops at interrupted, approve or reject its pending_approvals (each decision needs the entry's fingerprint):

const decisions = (run.pending_approvals ?? []).map((p): AgentApprovalDecision => ({
  call_id: p.call_id,
  fingerprint: p.fingerprint,
  decision: 'approve',          // or 'reject', with an optional `reason`
}));
const resumed = await client.agents.runs.approve(7, run.runId, { decisions });
// or stream the resumed run: client.agents.runs.approveStream(7, run.runId, { decisions })

Resume a dropped stream with after set to the id of the last event you received:

let lastId;
try {
  for await (const ev of client.agents.runs.stream(7, run.runId)) {
    lastId = ev.id ?? lastId;
  }
} catch {
  for await (const ev of client.agents.runs.stream(7, run.runId, { after: lastId })) {
    // continues after lastId (omit `after` if no event arrived yet)
  }
}

The SDK never retries run creation (create, createStream) or the approve methods. Pass idempotencyKey and retry yourself: a completed run replays, an in-flight one returns 409, and a failed run releases the key so a retry runs the agent again. client.agents.runs.cancel(agentId, runId) cancels a run (a retried cancel may get 409 if the first attempt succeeded; check status in error.responseData).

Evaluate a Prompt

Test a prompt with specific model settings:

const evalResponse = await client.evaluate({
  projectId: 'test-project',
  promptId: 1,
  modelProvider: 'openai',
  modelName: 'gpt-3.5-turbo',
  modelSettings: {
    temperature: 0.7,
    maxTokens: 1000,
    topP: 0.9,
    topK: 40,
    frequencyPenalty: 0,
    presencePenalty: 0
  },
  messages: [
    { role: 'user', content: { type: 'text', text: 'What is the capital of France?' } }
  ],
  outputFormat: 'json'   // optional
});

Error Handling

API failures throw a MetrioApiError with detailed error information:

import { MetrioAI, MetrioApiError } from '@metrio-ai/client';

try {
  const response = await client.chatCompletion({ promptId: 1, messages });
} catch (error) {
  if (error instanceof MetrioApiError) {
    console.error('API Error:', error.message);
    console.error('Status Code:', error.statusCode);
    console.error('Response Data:', error.responseData);
    console.error('Request Data:', error.requestData);
  }
}

Retryable failures (network errors, timeouts, 5xx server errors) are retried automatically; tune with the maxRetries (default: 3) and retryDelay (default: 500 ms) client options.

TypeScript Support

The SDK is fully written in TypeScript and exports types for every parameter and response:

| Type | Description | |------|-------------| | MetrioAIOptions | Configuration options for the client | | RunParams | Parameters for chat completion requests | | EvalParams | Parameters for evaluation requests | | RunResponse | Response from chat completion or evaluation | | StreamChunk | A chunk from a streaming response | | Message | A single message in a conversation | | MessageContent | Content of a message (text or file) | | ProvidersResponse | Response from the providers endpoint | | ModelsResponse | Response from the models endpoint | | ModelSettings | Model configuration settings | | Variable | Variable for template substitution |

import { MetrioAI, RunParams, RunResponse } from '@metrio-ai/client';

const params: RunParams = {
  promptId: 1,
  messages: [{ role: 'user', content: { type: 'text', text: 'Hello' } }],
  timezone: 'Asia/Taipei'
};
const response: RunResponse = await client.chatCompletion(params);

Requirements

  • Node.js 18.0.0 or higher (for the native fetch API)

License

This SDK is licensed under the BSD 3-Clause License.

Support

For questions or issues, please contact [email protected].