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

kortyx

v0.25.0

Published

TypeScript framework for production AI agents with explicit workflows, provider-agnostic models, streaming, interrupts, and runtime persistence.

Readme

kortyx

npm version CI License TypeScript Node

Kortyx is a TypeScript framework for building production AI agents with explicit workflows, provider-agnostic models, typed hooks, streaming, interrupts, and runtime persistence.

Use kortyx as the main package. It re-exports the public server/runtime APIs from the supporting @kortyx/* packages so application code can stay focused on workflows, nodes, providers, and UI transport.

Install

pnpm add kortyx @kortyx/google @kortyx/react
npm install kortyx @kortyx/google @kortyx/react

Run Studio locally

Kortyx includes the local Studio command. With Docker Desktop running:

npx kortyx studio start

This starts the self-hosted Studio, API, and Postgres stack, then prints the sign-in and server-side telemetry variables for this SDK project. Re-running the command is safe and preserves credentials and data.

Studio is a source-available preview under Elastic License 2.0; the Kortyx framework and CLI remain Apache-2.0. See the self-hosted preview guide for backup, upgrade, security, and limitation details.

npx kortyx studio status
npx kortyx studio logs
npx kortyx studio credentials
npx kortyx studio credentials --rotate
npx kortyx studio stop

For an operator-managed installation with external PostgreSQL, follow the server deployment guide.

Quickstart

Create a workflow:

// src/workflows/general-chat.workflow.ts
import { defineWorkflow } from "kortyx";
import { chatNode } from "@/nodes/chat.node";

export const generalChatWorkflow = defineWorkflow({
  id: "general-chat",
  version: "1.0.0",
  description: "Single-node chat workflow.",
  nodes: {
    chat: {
      run: chatNode,
      params: {
        temperature: 0.3,
      },
    },
  },
  edges: [
    ["__start__", "chat"],
    ["chat", "__end__"],
  ],
});

Add a server-side node:

// src/nodes/chat.node.ts
import { google } from "@kortyx/google";
import { useReason } from "kortyx";

type ChatParams = {
  temperature?: number;
};

export const chatNode = async ({
  input,
  params,
}: {
  input: unknown;
  params: ChatParams;
}) => {
  const result = await useReason({
    id: "chat",
    model: google("gemini-2.5-flash"),
    system: "You are a concise assistant.",
    input: String(input ?? ""),
    temperature: params.temperature ?? 0.3,
    stream: true,
    emit: true,
  });

  return {
    data: { text: result.text },
  };
};

Wire the agent:

// src/lib/agent.ts
import { createAgent } from "kortyx";
import { generalChatWorkflow } from "@/workflows/general-chat.workflow";

export const agent = createAgent({
  workflows: [generalChatWorkflow],
  defaultWorkflowId: "general-chat",
});

Expose it through a Next.js API route:

import { createChatRouteHandler } from "kortyx";
import { agent } from "@/lib/agent";

export const runtime = "nodejs";
export const dynamic = "force-dynamic";

const handleChat = createChatRouteHandler({ agent });

export async function POST(request: Request): Promise<Response> {
  return handleChat(request);
}

Consume the stream from React:

"use client";

import { createRouteChatTransport, useChat } from "@kortyx/react";

export function Chat() {
  const chat = useChat({
    transport: createRouteChatTransport({ endpoint: "/api/chat" }),
  });

  return (
    <form
      onSubmit={(event) => {
        event.preventDefault();
        const form = new FormData(event.currentTarget);
        chat.send(String(form.get("message") ?? ""));
        event.currentTarget.reset();
      }}
    >
      {chat.messages.map((message) => (
        <p key={message.id}>{message.content}</p>
      ))}
      {chat.streamContentPieces.map((piece) =>
        piece.type === "text" ? (
          <span key={piece.id}>{piece.content}</span>
        ) : null,
      )}
      <input name="message" />
      <button type="submit" disabled={chat.isStreaming}>
        Send
      </button>
    </form>
  );
}

Set a provider key and run your app:

GOOGLE_API_KEY=your_key_here pnpm dev

API Groups

| Area | Exports | | --- | --- | | Agent | createAgent, createChatRouteHandler, streamChatFromRoute | | Workflows | defineWorkflow, loadWorkflow, validateWorkflow | | Hooks | useReason, useInterrupt, useStructuredData, useNodeState, useWorkflowState, useRuntimeContext | | Runtime | workflow registries, node registry, in-memory/Redis framework adapters | | Providers | provider contracts and registry helpers from @kortyx/providers | | Streams | SSE helpers, stream readers, collectors, structured reducers |

Provider Packages

Install only the provider integrations your app needs.

| Provider | Package | Factory | Default environment variable | | --- | --- | --- | --- | | Google Gemini | @kortyx/google | google(...) | GOOGLE_API_KEY or GEMINI_API_KEY | | OpenAI | @kortyx/openai | openai(...) | OPENAI_API_KEY | | Anthropic | @kortyx/anthropic | anthropic(...) | ANTHROPIC_API_KEY | | DeepSeek | @kortyx/deepseek | deepseek(...) | DEEPSEEK_API_KEY | | Groq | @kortyx/groq | groq(...) | GROQ_API_KEY | | Mistral | @kortyx/mistral | mistral(...) | MISTRAL_API_KEY |

Documentation

Studio

The self-hosted Kortyx Studio preview provides run, session, workflow, interrupt, payload, timing, token, and cost inspection for telemetry emitted by Kortyx SDK applications.

Telemetry

Configure server-side Studio telemetry with createKortyxTelemetryAdapter:

import { createAgent } from "kortyx";
import { createKortyxTelemetryAdapter } from "@kortyx/telemetry";

const telemetry = createKortyxTelemetryAdapter({
  endpoint: process.env.KORTYX_TELEMETRY_API_URL!,
  apiKey: process.env.KORTYX_TELEMETRY_API_KEY!,
  environment:
    process.env.KORTYX_TELEMETRY_ENVIRONMENT ?? "development",
  service: {
    name: process.env.KORTYX_TELEMETRY_SERVICE_NAME ?? "support-api",
    deploymentRef: process.env.GIT_SHA,
  },
  maxQueueSize: 1_000,
});

const agent = createAgent({ workflows, telemetry });

Delivery is best-effort and non-blocking: events receive idempotent IDs, are batched in a bounded in-memory queue, and transient network/429/5xx failures retry with exponential backoff and jitter. Use telemetry.flush() during graceful shutdown; inspect getDroppedEventCount() and getPermanentDeliveryFailureCount() for delivery health. Prompt and output content is excluded by default. Enable only the sides you intend to persist with captureContent: true or { input: true, output: true }.

interrupt.expired is intentionally API-derived from the durable expiresAt sent in interrupt.created; the SDK does not run an unreliable local TTL timer. run.cancelled records aborted active executions. Forward request.signal in custom routes; createChatRouteHandler forwards it automatically. Children, models and tools inherit the live signal. Use useAbortSignal() for custom node I/O.

Applications that own visible conversation history can use createChatRouteHandler({ onTurnAccepted, onResponseFinalized }) to write a pending user turn before execution and a parsed assistant message after the response's checkpoint decision. The hooks require sessionId and clientTurnId; @kortyx/react sends the latter from the user message ID. onResponseFinalized includes status, checkpointId, and a message with ordered text, structured, interrupt, and error pieces. Configure disconnect: "continue" with onExecution to finish an ordinary run after the browser closes its stream. The default remains request cancellation. createCheckpointRouteHandler additionally exposes onForked and onRolledBack for app-owned transcript updates. Hook delivery is in-process; crash recovery and cross-store transactions remain application concerns. See Background Continuation.

Interrupt telemetry keeps structural fields (kind, interactionMode, optionCount, schemaId, and schemaVersion) even when content capture is off. Questions and static option labels use output-content capture; submitted responses use input-content capture. Option values and resume capability tokens are never emitted.

License

Apache-2.0. See LICENSE.

Error handling

Use serializeFailure for safe structured diagnostics and DomainError for explicitly approved domain details that must survive child checkpoint restore. Cancellation, execution limits and human interrupts remain control flow. Applications own retry and schema correction policy. See the error handling guide.

Throw ordinary Error instances when a workflow cannot continue; configured tracing records them automatically before the run fails. Use reportError(error, {severity?, metadata?, tags?}) only for handled errors when application code deliberately continues with a fallback. Reporting is best-effort observation and never changes workflow control flow.

Shared tools

await useTool({tool, input}) executes a typed plain tool immediately inside a workflow node, without a model call or MCP transport. Pass the same definition to useReason({tools: [tool]}) for model-driven selection and arguments. Tool definitions can provide outcomes.denialCodes, classifyResult and classifyError; permission enforcement remains application-owned. Direct calls preserve result/error identity and do not cache executions.

Studio records individual durations and separate success, denial, fault and cancellation outcomes; cached native and child calls appear as reuse with their original scope. Tool faults automatically include error type and message (up to 256/8192 characters), without extra call-site wiring. Observations exclude raw inputs/results, exception objects, causes and stacks. Error messages are exported verbatim and can contain sensitive text; an optional tool telemetry.error(error) callback can replace them with {type, message} or return null to suppress them. Throwing projections suppress diagnostics and never alter execution. Observer and delivery failures cannot change execution. kortyx topology push discovers attached tools from source, marks dynamic attachments unresolved, and makes available tools visible before traffic.

Configured Studio/OpenTelemetry tracing captures thrown workflow/model errors automatically, including bounded type, message, stack and cause details. Studio also records manual reportError calls as handled errors without failing the span. Prompts, outputs and raw provider responses remain excluded by default. Diagnostics are verbatim and may contain sensitive text; the optional trace adapter error(error) override can return {type, message, stack?, cause?} or null to replace or suppress them without changing execution. Client-facing failure contracts remain sanitized and unchanged.