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

@japan-ai-inc/studio-sdk

v0.2.0-beta.1

Published

TypeScript SDK for Japan AI Studio platform APIs

Downloads

119

Readme

@japan-ai-inc/studio-sdk

TypeScript SDK for the Japan AI Studio API. Provides typed, ergonomic access to Agents, Custom Objects, Storage, Workflows, and the Pages Rendering Service (PRS) platform.

Installation

Node.js 22 or later is required for the Node SDK and CLI.

npm install @japan-ai-inc/studio-sdk

CLI (jai-studio)

Install the published CLI globally:

npm install --global @japan-ai-inc/studio-sdk

Configure a profile

# Interactive prompt (recommended — key never appears in shell history)
jai-studio config set my-project

# Piped from a secret manager (CI/automation)
vault read -field=key secret/jai | jai-studio config set my-project --key-stdin

# Environment variable
JAI_STUDIO_API_KEY=pak_... jai-studio config set my-project

Security: Avoid passing API keys as CLI arguments — they are visible in shell history and process listings. Use the interactive prompt, --key-stdin, or the JAI_STUDIO_API_KEY environment variable instead.

Supported Usage Modes

This limited beta is primarily for developing Studio Apps locally. Production Studio App hosting runs through the Pages Rendering Service (PRS).

| Mode | Runtime | Authentication | Available modules | Supported use | | ------------------ | ------------------------------ | -------------------------------------------------------------------------------- | --------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Direct development | Node.js 22 or later | Developer-owned Project API key | agents, members, objects, storage, workflows; Pages via CLI | Local development and explicitly controlled development automation. Arbitrary external production hosting or integration is not part of the beta support contract. | | PRS browser | Browser inside PRS | PRS session; no API key | agents, objects, storage, platform | Browser code for a PRS-hosted Studio Page. | | PRS server runtime | Node.js 22 or later inside PRS | Platform-managed Project API key injected into server-only runtime configuration | agents, members, objects, storage, workflows | Server code within PRS hosting. This is not an external integration mode. |

Only Node.js 22 or later is verified for the Node SDK and CLI. Browser support is limited to code running inside PRS.

Direct Development with a Project API Key

Use a developer-owned Project API key for local server-side development or explicitly controlled development automation. The API technically accepts valid Project API keys, but that capability does not create a supported arbitrary external production-hosting contract.

Project API keys are secret-equivalent. They must never enter browser bundles, client-side JavaScript, browser-public environment variables, logs, or source control.

import { createClient } from "@japan-ai-inc/studio-sdk";

const studio = createClient({
  baseUrl: "https://api.japan-ai.co.jp",
  apiKey: "pak_your_api_key", // Get this from Studio → Settings → API Keys
});

// All modules available: agents, members, objects, storage, workflows
const agents = await studio.agents.list();
const result = await studio.agents.invoke("agent-id", { prompt: "Hello" });

Canonical Agent UUIDs are sent directly to /chat/v2; the SDK does not make a separate Agent detail request to translate them to labels. Non-UUID identifiers retain the legacy label-resolution path.

Available modules: agents, members, objects, storage, workflows

PRS Browser — No API Key

For browser code deployed as a Japan AI Studio Page, PRS provides the current session. Do not configure or read a Project API key in browser code.

import { createClient } from "@japan-ai-inc/studio-sdk";

// On PRS, baseUrl can be empty — fetch() uses relative paths.
// Auth is handled by PRS session cookies automatically.
const studio = createClient({ baseUrl: "" });

// agents.invoke() auto-detects PRS and routes through /api/agent-chat
const result = await studio.agents.invoke("550e8400-e29b-41d4-a716-446655440000", { prompt: "Hello" });

// Platform module — PRS-only features
const context = await studio.platform.waitForReady();
console.log(context.orgName, context.userName);

await studio.platform.openTask(data, "Review this");

Available modules: agents (auto-routes via PRS), objects, storage, platform

PRS Server Runtime — Platform-Managed Key

PRS may inject a platform-managed Project API key into server-only runtime configuration for a hosted app. Server code may use that value with the normal Node client. Never return it from a runtime-config endpoint or forward it to browser code.

import { createClient } from "@japan-ai-inc/studio-sdk";

const apiKey = process.env["JAI_PROJECT_API_KEY"];
if (!apiKey) {
  throw new Error("Missing PRS server runtime API key");
}

const studio = createClient({
  baseUrl: "https://api.japan-ai.co.jp",
  apiKey,
});

This key lifecycle is owned by PRS hosting. It is not a credential model for arbitrary external production deployment.

Available modules (PRS server runtime): agents, members, objects, storage, workflows. The platform module is for PRS browser integration; Pages deployment remains a development/control-plane operation rather than a hosted app server-runtime capability.

Distributable Studio App Checklist

Before handing a Studio Page app to the publish/install workflow:

  • Keep source-project resource IDs out of source code and browser assets.
  • Put supported development-time Agent, Workflow, and Custom Object IDs in root .env* files under stable, descriptive server-side keys.
  • Never put Project API keys or source-project IDs in NEXT_PUBLIC_*, VITE_*, REACT_APP_*, or PUBLIC_* values.
  • Keep storage configuration as logical paths or prefixes rather than storage UUIDs.
  • Initialize browser SDK code with createClient({ baseUrl: "" }); only server-runtime code may consume a platform-managed key.
  • Activate the SDK version only after a compatible PRS runtime is deployed.

How PRS detection works:

  • The SDK checks for window.JapanAI (injected by PRS at runtime)
  • When detected, agents.invoke() routes through PRS's /api/agent-chat instead of /chat/v2
  • Other PRS-enabled modules (objects, storage) go through PRS's /api/v1/* proxy
  • No code changes needed — the same SDK calls work in both environments

Limited-Beta Support

The beta audience is invited Studio App developers from existing Japan AI customers and partners. Contact your Japan AI account representative for access, support, or suspected security issues. Public package availability by itself does not expand support entitlement.

createClient(options)

| Option | Type | Required | Description | | ----------- | -------------- | -------- | --------------------------------------------------------------------------------- | | baseUrl | string | Yes | Studio API base URL (empty in PRS browser); with apiKey, HTTPS except loopback | | apiKey | string | No | Project API key for server-side modes; never used in PRS browser | | invokeUrl | string | No | Custom agent invoke URL; with apiKey, it must have the same origin as baseUrl | | timeoutMs | number | No | Default timeout (default: 30s) | | fetch | typeof fetch | No | Custom fetch implementation |

Returns a StudioClient with lazy-initialized module accessors: objects, agents, members, workflows, storage, platform.


Agents

Chat with AI agents, manage rooms, and retrieve message history.

List Agents

const { agents, totalCount, nextCursor } = await studio.agents.list({
  limit: 10,
  search: "support",
});

Get Agent

const agent = await studio.agents.get("agent-id");
// agent.id, agent.name, agent.label, agent.description, agent.enabled

Invoke (Non-Streaming)

const result = await studio.agents.invoke("550e8400-e29b-41d4-a716-446655440000", {
  prompt: "Summarize this document",
  sessionId: "room-id", // optional — continues conversation
  model: "gpt-4o", // optional
  temperature: 0.7, // optional
  systemPrompt: "...", // optional
  isEphemeral: true, // optional — don't persist to history
});

console.log(result.text); // AI response
console.log(result.sessionId); // Room ID for follow-ups
console.log(result.status); // "succeeded" | "failed"
console.log(result.references); // Source references (if any)

Invoke (Streaming)

for await (const event of studio.agents.invokeStream("550e8400-e29b-41d4-a716-446655440000", { prompt: "Write a haiku" })) {
  if (event.type === "delta") {
    process.stdout.write(event.content); // Stream text chunks
  }
  if (event.type === "error") {
    console.error(event.text);
  }
}

Rooms & Message History

// Create a new chat room
const room = await studio.agents.createRoom("agent-id");
console.log(room.roomId);

// Get messages in a room
const { messages, nextCursor } = await studio.agents.getMessages("room-id", {
  limit: 20,
  cursor: "next-page-cursor",
});
// messages[0].id, .role, .content, .createdAt

PRS Behavior

When running inside the Pages Rendering Service (PRS), invoke() and invokeStream() automatically route through /api/agent-chat instead of /chat/v2. The SDK detects PRS via window.JapanAI and classifies the first argument after trimming it:

  • A canonical 8-4-4-4-12 hexadecimal UUID (case-insensitive, with no version/variant restriction) is normalized to lowercase and sent as agent_id. PRS resolves and authorizes that ID from the authenticated Page's release-managed runtime metadata. An ID not declared by that Page is rejected; it does not fall back to label routing.
  • Any other non-empty string is sent as the legacy agent_label. For example, "agent-id" is a label, not a stable Agent ID.
  • An empty or whitespace-only string sends neither field, allowing PRS to use its configured fallback Agent.

SDK-generated requests never contain both identity fields. A legacy Agent whose label is itself a canonical UUID must use the low-level studio.platform.chat({ agentLabel }) API to force label routing; that path does not use release-managed Agent ID resolution or its version/runtime pinning. Outside PRS, the existing Agent lookup and invocation path remains unchanged.

The compatible PRS agent_id contract must be deployed before an SDK version using UUID routing is released or activated. An older PRS may ignore agent_id and invoke its fallback Agent instead.


Objects

Manage custom object schemas and their records.

Object Definitions

// List all objects
const { data } = await studio.objects.list();

// Create an object
const obj = await studio.objects.create({ objectName: "contacts" });

// Get / Update / Delete
const obj = await studio.objects.get("objectId");
await studio.objects.update("objectId", { displayName: "Contacts" });
await studio.objects.delete("objectId");

Records (via define<T>())

define<T>() returns a typed handle for record operations on a specific object.

type Contact = { name: string; email: string; company?: string };
const contacts = studio.objects.define<Contact>("contacts");

// CRUD
const record = await contacts.create({ name: "Acme", email: "[email protected]" });
const fetched = await contacts.get("record-id");
const updated = await contacts.update("record-id", { company: "Acme Inc" });
await contacts.delete("record-id");

// List with pagination
const page = await contacts.list({ limit: 20, cursor: "next-cursor" });
// page.data        → RecordItem<Contact>[]
// page.hasNextPage → boolean
// page.cursor      → string | null

// Search with filters
const results = await contacts.search({
  filters: [{ field: "name", operator: "contains", value: "Acme" }],
  sort: [{ field: "createdAt", direction: "desc" }],
  limit: 50,
});

// Bulk create (up to 1000 records)
const created = await contacts.bulkCreate([
  { name: "Alice", email: "[email protected]" },
  { name: "Bob", email: "[email protected]" },
]);

Fields

const contacts = studio.objects.define<Contact>("contacts");

// List fields
const fields = await contacts.listFields();

// Create a field
const field = await contacts.createField({
  fieldName: "phone",
  displayName: "Phone Number",
  dataType: "phone",
  fieldNum: 5,
});

// Update / Delete
await contacts.updateField("fieldId", { displayName: "Mobile" });
await contacts.deleteField("fieldId");

Search Filter Operators

| Operator | Description | | ------------------------- | ----------------------- | | eq | Equal | | ne | Not equal | | gt / gte | Greater than / or equal | | lt / lte | Less than / or equal | | contains | String contains | | startswith / endswith | String prefix/suffix | | in / notin | Value in/not in array | | isnull / isnotnull | Null check | | exists | Field exists | | regex | Regex match | | between | Range check | | jsoncontains | JSON field contains |


Storage

Upload, download, and manage files and folders.

List Files

const { files, folders, totalCount, nextCursor } = await studio.storage.list({
  path: "/documents",
  recursive: true,
  limit: 50,
  orderBy: "createdAt",
  orderDirection: "desc",
});

Upload Files

// Single file
const result = await studio.storage.upload(file, { path: "/uploads" });

// Multiple files (up to 20)
const result = await studio.storage.upload([file1, file2, file3], {
  path: "/uploads",
});
// result.files → StorageFile[]

// Atomically replace files with matching names in the target path.
// Existing rows keep the same fileId; non-conflicting files are created.
const replaced = await studio.storage.upload(file, {
  path: "/uploads",
  conflictResolution: "replace",
});

Download

// Download file content
const response = await studio.storage.download("file-id");
const blob = await response.blob();

// Get a signed download URL
const { downloadUrl, expiresAt } = await studio.storage.getDownloadUrl("file-id", { expiresInMinutes: 60 });

// Download an entire folder as a zip
const response = await studio.storage.downloadFolder("folder-id", {
  maxSizeMb: 100,
});

File Metadata & Deletion

const metadata = await studio.storage.getMetadata("file-id");
// metadata.fileId, .fileName, .path, .mimeType, .sizeBytes

await studio.storage.deleteFile("file-id");
// { fileId, message }

Folders

// Create a folder
const folder = await studio.storage.createFolder({
  folderName: "reports",
  path: "/documents",
});

// List folder contents
const contents = await studio.storage.listFolderContents("folder-id", {
  recursive: true,
  limit: 100,
});

// Delete a folder
const result = await studio.storage.deleteFolder("folder-id");
// result.deletedCount — number of items deleted

Resumable Upload (Large Files)

For large files, use the two-step prepare → finalize flow:

// 1. Prepare — get a signed upload URL
const prepared = await studio.storage.prepareUpload({
  fileName: "large-video.mp4",
  fileSize: 500_000_000,
  mimeType: "video/mp4",
  path: "/media",
});

// 2. Upload directly to storage (using the signed URL)
await fetch(prepared.uploadUrl, {
  method: "PUT",
  headers: prepared.requiredHeaders,
  body: fileContent,
});

// 3. Finalize — register the file in Studio
const file = await studio.storage.finalizeUpload({
  storageId: prepared.storageId,
  fileName: "large-video.mp4",
  fileSize: 500_000_000,
  mimeType: "video/mp4",
});

To atomically replace an existing file with the same name and path, pass conflictResolution: "replace" to prepareUpload() and send the returned replaceIntent back unchanged when finalizing:

const prepared = await studio.storage.prepareUpload({
  fileName: "large-video.mp4",
  fileSize: 500_000_000,
  mimeType: "video/mp4",
  path: "/media",
  conflictResolution: "replace",
});

await fetch(prepared.uploadUrl, {
  method: "PUT",
  headers: prepared.requiredHeaders,
  body: fileContent,
});

const file = await studio.storage.finalizeUpload({
  storageId: prepared.storageId,
  replaceIntent: prepared.replaceIntent,
  fileName: "large-video.mp4",
  fileSize: 500_000_000,
  mimeType: "video/mp4",
  path: "/media",
});

Workflows

Trigger and monitor workflow runs.

List Workflows

const { workflows, totalCount, nextCursor } = await studio.workflows.list({
  limit: 10,
});

By default only workflows with a published snapshot are returned. Use status to include drafts:

// Draft-only workflows (no published snapshot)
const drafts = await studio.workflows.list({ status: "draft" });

// Everything — each row carries isPublished
const all = await studio.workflows.list({ status: "all" });
for (const wf of all.workflows) {
  console.log(wf.name, wf.isPublished);
}

Get Workflow

const workflow = await studio.workflows.get("workflow-id");
// workflow.id, .name, .description, .enabled, .isPublished

Run a Workflow

// Fire-and-forget
const { runId, status } = await studio.workflows.run("workflow-id", {
  inputs: { documentUrl: "https://..." },
});

// Poll for result
const result = await studio.workflows.getRunResult(runId);
// result.status: "RUNNING" | "SUCCEEDED" | "FAILED"
// result.outputs: Record<string, unknown>

Run and Wait

Blocks until the workflow completes (default timeout: 5 minutes):

const result = await studio.workflows.runAndWait("workflow-id", {
  inputs: { query: "market trends" },
  timeout: 120, // seconds
});

if (result.status === "SUCCEEDED") {
  console.log(result.outputs);
} else {
  console.error(result.error);
}

Members

List the members of the current project.

Direct development may use a Project API key. PRS-hosted page apps require PRS to allow-list GET /api/v1/project-members before this module can be used in browser mode without an API key.

List Members

const { members, totalCount, nextCursor } = await studio.members.list({
  search: "ada",
  limit: 50,
  cursor: "next-page-cursor",
});
// members[0].id, .principalId, .principalType, .name, .email, .avatarUrl, .createdAt

Platform (PRS) — Beta

Beta: PRS is an internal runtime. This module's API may change between minor versions. Pin to an exact SDK version if you depend on it.

Browser-only module for apps running inside Japan AI's Pages Rendering Service. This module does not require baseUrl or apiKey — PRS injects authentication automatically.

Detect PRS

if (studio.platform.isAvailable()) {
  const context = studio.platform.getContext();
  console.log(context.orgName, context.userName);
}

Wait for PRS Ready

PRS injects window.JapanAI asynchronously. Use waitForReady() to wait:

const context = await studio.platform.waitForReady(5000); // timeout in ms
if (context) {
  // PRS is ready
  console.log(context.userId, context.projectId, context.pageId);
} else {
  // Not running on PRS, or timed out
}

Platform Context

getContext() returns:

type PlatformContext = {
  userId: string;
  email: string;
  userName: string;
  memberRole: string;
  orgId: string;
  orgName: string;
  projectId: string;
  pageId: string;
  customObjects: Record<string, string>; // name → objectId
};

Open Task

Delegate data to an agent task in the Studio UI:

const result = await studio.platform.openTask(
  [{ name: "John", email: "[email protected]" }], // data
  "Please review this contact", // user prompt
  {
    agentLabel: "review-agent",
    isTemporary: false,
    isEphemeral: false,
  }
);
console.log(result.roomId);

Chat (Low-Level PRS Proxy)

Direct access to PRS's /api/agent-chat endpoint (prefer studio.agents.invoke() instead — it auto-routes on PRS):

Use agentLabel here when you intentionally need the legacy label path, including for a label that is itself shaped like a canonical UUID. Label-only calls do not use release-managed Agent ID resolution or its version/runtime pinning.

const response = await studio.platform.chat({
  messages: [{ role: "user", content: "Hello" }],
  agentLabel: "support-agent",
  model: "gpt-4o",
  temperature: 0.7,
});
console.log(response.content, response.roomId);

Error Handling

Each module throws its own error class, all extending SdkApiError:

import { ObjectsApiError, AgentsApiError, StorageApiError, WorkflowsApiError } from "@japan-ai-inc/studio-sdk";

try {
  await studio.agents.invoke("agent-id", { prompt: "hello" });
} catch (err) {
  if (err instanceof AgentsApiError) {
    console.error(err.statusCode); // 401, 404, 500, etc.
    console.error(err.message); // Human-readable error
    console.error(err.responseBody); // Raw error response
  }
}

Timeouts & Cancellation

Default timeout is 30 seconds. Override per-request or globally:

// Global timeout
const studio = createClient({ baseUrl: "...", apiKey: "...", timeoutMs: 60000 });

// Per-request timeout
await contacts.list({ limit: 10 }, { timeoutMs: 5000 });

// Cancel with AbortController
const controller = new AbortController();
const promise = contacts.search({ filters: [...] }, { signal: controller.signal });
controller.abort();

Agent invoke has longer defaults: 5 minutes for invoke(), 10 minutes for invokeStream().


Versioning & Stability

This package follows Semantic Versioning.

Pre-1.0 policy (0.x.y): The SDK is in active development. Minor version bumps (0.2.0, 0.3.0) may contain breaking changes to method signatures, error shapes, or module structure. Patch versions within the same minor are backwards-compatible bug fixes and security patches.

Recommendation: Pin to an exact version in package.json until 1.0:

"@japan-ai-inc/studio-sdk": "0.2.0-beta.1"

What counts as a breaking change:

  • Removing or renaming an exported function, class, or type
  • Changing the signature of a public method (required params, return type)
  • Changing error class hierarchy or statusCode semantics
  • Removing a CLI command or changing its required arguments

What does NOT count as breaking:

  • Adding new optional parameters, methods, or exports
  • Improving error messages
  • Bug fixes to match documented behavior

See CHANGELOG.md for release history.