@diskd-ai/sdk

中文文档
7.10.0发布于 2026/11/12Apache-2.0TypeScriptES Module构建来源已验证近 30 天下载 8132

SDK for DiskD platform APIs (Drive, LLM Router, Agent Hub, MCP Hub, Telegram Userbot, Web Navigator)

原始使用文档
文档目录47 节
  1. Installation
  2. Install / build (repo)
  3. Authentication
  4. External clients (OAuth2)
  5. Internal services (API key)
  6. Environment variables
  7. Gateway Decision
  8. Drive API
  9. Path operations
  10. Upload (buffer + stream)
  11. Download (streaming)
  12. File metadata, disk usage, tools
  13. Sessions
  14. Crontab scheduler
  15. Email provider operations
  16. Messages Store API
  17. Mailboxes
  18. Folders
  19. Messages
  20. Attachments
  21. Auth modes
  22. Routines API
  23. Operatives API
  24. Operative files (Drive knowledge sources)
  25. Operative skills
  26. Operative MCP tools
  27. Calendar API
  28. Attendees
  29. Note links
  30. Attachments
  31. Event metadata
  32. Settings
  33. Drive Database API
  34. Drive Repository (CRUD pattern)
  35. TypeORM Driver ( diskd.os.datasource() )
  36. Installation
  37. Usage
  38. Transaction mapping
  39. Limitations (v1)
  40. LLM Router API
  41. Agent Hub API
  42. MCP Hub API
  43. Telegram Userbot API
  44. Web Navigator API
  45. Web quickstart (Vite + PKCE)
  46. Publishing a new version
  47. Docs and examples

@diskd-ai/sdk

Unified TypeScript SDK for the Upgraide platform APIs.

All services are accessible via the diskd factory, which provides a consistent diskd.auth.* + namespaced service pattern across diskd.os.*, diskd.platform.*, and diskd.utils.*:

import { diskd } from '@diskd-ai/sdk';

const auth = diskd.auth.apiKey({ workspaceId: '...' });

const drive      = diskd.os.drive({ version: 'v1', auth });
const sessions   = diskd.platform.sessions({
  auth,
  scope: { scopeType: 'project', projectId: 'proj-1' },
});
const crontab    = diskd.platform.crontab({
  auth,
  scope: { scopeType: 'project', projectId: 'proj-1' },
});
const db         = diskd.os.database({ auth, dbName: '...', schema: { ... } });
const ds         = diskd.os.datasource({ auth, dbName: '...', entities: [...] });
const llm        = diskd.os.llm({ auth });
const agents     = diskd.os.agents({ auth, workspaceId: '...' }); // deprecated, unrouted
const mcp        = diskd.os.mcp({ auth, workspaceId: '...' });
const messages   = diskd.os.messagesStore({ auth });
const routines   = diskd.platform.routines({ auth });
const operatives = diskd.platform.operatives({ auth });
const calendar   = diskd.platform.calendar({ auth });
const tg         = diskd.utils.tgUserBot({ auth, workspaceId: '...' });
const webNav     = diskd.utils.webNavigator({ auth, workspaceId: '...' });

Installation

  1. Configure .npmrc in your project (or ~/.npmrc):

    @diskd:registry=https://gitlab.iosya.com/api/v4/projects/80/packages/npm/
    //gitlab.iosya.com/api/v4/projects/80/packages/npm/:_authToken=${NPM_TOKEN}
    
  2. Set NPM_TOKEN to a GitLab personal access token with read_api scope:

    export NPM_TOKEN=glpat-xxxxxxxxxxxx
    
  3. Install:

    bun add @diskd-ai/sdk
    

Install / build (repo)

cd mono/platform-api
bun install
bun run build

Authentication

The SDK supports two authentication modes via the AuthModule interface.

External clients (OAuth2)

Use diskd.auth.credentials() for OAuth2 service-account or PKCE browser flows:

import { diskd } from '@diskd-ai/sdk';

const auth = await diskd.auth.credentials({
  scopes: [
    'openid',
    'drive:read',
    'drive:write',
    'sessions:read',
    'sessions:write',
    'crontab:read',
    'crontab:write',
  ],
  keyfilePath: 'credentials.json',
});

const drive = diskd.os.drive({ version: 'v1', auth });
const sessions = diskd.platform.sessions({
  auth,
  scope: { scopeType: 'project', projectId: 'proj-1' },
});
const crontab = diskd.platform.crontab({
  auth,
  scope: { scopeType: 'project', projectId: 'proj-1' },
});

OAuth2 scopes

The SDK forwards scopes unchanged to the token request. Request the scopes that the public APIS gateway route of every client you call declares (required_scopes in apis-service config/routes.yaml). The gateway compares the token against every scope the route declares, so request the full set for each route:

SDK clientGateway routeScopes
diskd.os.drive, diskd.os.messagesStore/v1/os/drivedrive:read drive:write
diskd.os.database/v1/os/databasedatabase:read database:write
diskd.os.email/v1/os/emailemail:read email:write
diskd.os.llm/v1/os/llmllm:invoke llm:models:read
diskd.os.mcp, diskd.os.mcpTools/v1/os/mcpmcp:read mcp:execute
diskd.platform.sessions/v1/platform/sessionssessions:read sessions:write
diskd.platform.crontab/v1/platform/crontabcrontab:read crontab:write
diskd.platform.operatives/v1/platform/operativesoperatives:read operatives:write
diskd.platform.projects, diskd.platform.notes/v1/platform/projectsprojects:read projects:write
diskd.platform.routines, diskd.platform.routineRuns/v1/platform/routinesroutines:read routines:write
diskd.platform.events/v1/platform/eventsevents:write
diskd.platform.calendar/v1/platform/calendarcalendar:read calendar:write
diskd.platform.contacts/v1/platform/contactscontacts:read contacts:write
diskd.platform.inbox/v1/os/drive, /v1/os/emaildrive:read drive:write email:read email:write
diskd.utils.tgUserBot/v1/utils/tg-userbottg-userbot:read tg-userbot:write
diskd.utils.webNavigator/v1/utils/web-navigatorweb-navigator:read web-navigator:write

Keep openid when you need an OIDC sign-in (browser PKCE flow); it is harmless for client-credentials keyfiles.

Rollout status (2026-10-05): the gateway does not enforce scopes yet. It logs each OAuth2 request's token_scopes and whether they satisfy the route (scopes_satisfied). OAuth clients registered before the iam-service scope rollout only allow openid, so Hydra answers invalid_scope when they request route scopes. SDK/CLI credentials pick up the route scopes the next time credentials are fetched through the app's credentials flow (the workspace keeps the same client id and secret, so an existing credentials.json keeps working afterwards); self-service OAuth apps are reconciled by iam-service on startup.

Internal services (API key)

Use diskd.auth.apiKey() for service-to-service communication within the cluster:

import { diskd } from '@diskd-ai/sdk';

const auth = diskd.auth.apiKey({ workspaceId: process.env.WORKSPACE_ID! });

const drive = diskd.os.drive({ version: 'v1', auth });

diskd.auth.apiKey() reads APIS_API_KEY from the environment and fails fast when either APIS_API_KEY or APIS_BASE_URL is missing.

Both auth modes produce identical client instances.

Environment variables

All resource APIs resolve from the centralized gateway base URL:

Env VariableDefault
APIS_BASE_URLhttps://apis.diskd.local:8080
APIS_API_KEYnone

The gateway is the single resource entrypoint. Public gateway URLs follow the versioned convention https://apis.example/v1/{namespace}/{module}. The SDK derives API paths from the same namespace structure as the public SDK surface and lets the gateway handle API orchestration and auth strategy.

Derived default paths:

  • /v1/os/drive
  • /v1/os/database
  • /v1/os/llm
  • /v1/os/agents (deprecated Agent Hub client; apis-service has no such route)
  • /v1/os/mcp
  • /v1/platform/sessions
  • /v1/platform/crontab
  • /v1/platform/operatives
  • /v1/platform/projects
  • /v1/platform/routines
  • /v1/platform/events
  • /v1/platform/calendar
  • /v1/utils/tg-userbot
  • /v1/utils/web-navigator

You can still override a client with an explicit url, but the default mode is the centralized gateway.

Gateway Decision

This SDK does not treat resource APIs as independently-discovered hosts. The canonical model is one centralized apis gateway behind APIS_BASE_URL.

That means:

  • no per-service default env vars such as LLM_ROUTER_BASE_URL, AGENT_HUB_BASE_URL, or MCP_HUB_BASE_URL
  • resource clients derive their route from APIS_BASE_URL plus a namespace-derived path prefix
  • the gateway is responsible for request routing, API orchestration, and auth-strategy handling

Per-client url remains available only as an explicit override.

Drive API

Path operations

await drive.init();
const entries = await drive.list({ path: '/' });
const dir = await drive.create({ dirName: 'my-folder' });
await drive.rename({ inode: dir.inode, newName: 'renamed-folder' });
await drive.delete({ inodes: [dir.inode], recursive: true });
const resolved = await drive.resolve({ inodes: ['inode1', 'inode2'] });
await drive.updateMetadata({ inode: 'abc', metadata: { key: 'value' } });
await drive.updateAttributes({ inode: 'abc', attributes: ['pinned'] });

Upload (buffer + stream)

// Buffer upload
const result = await drive.upload.file({
  name: 'hello.txt',
  data: new TextEncoder().encode('Hello, world!'),
  mimeType: 'text/plain',
  onProgress: (uploaded, total) => console.log(`${uploaded}/${total}`),
});

// Stream upload (large files)
const result = await drive.upload.file({
  name: 'large.bin',
  stream: readableStream,
  size: 1_000_000_000,
  sha256Root: 'precomputed-hex',
});

Download (streaming)

const file = await drive.download.file({
  inode: 'abc123',
  onProgress: (downloaded, total) => console.log(`${downloaded}/${total}`),
});
await file.stream.pipeTo(writableStream);

File metadata, disk usage, tools

const meta = await drive.files.metadata({ inode: 'abc' });
const usage = await drive.diskUsage();
const ls = await drive.tools.ls({ path: '/', recursive: true });
const grep = await drive.tools.grep({ pattern: 'TODO' });

Sessions

The SDK exposes these session methods on diskd.platform.sessions({ auth, scope }):

  • start
  • open
  • save
  • list
  • delete
  • message
const session = await sessions.start({
  title: 'Deployment help',
});

await session.append([
  sessions.message({ role: 'user', content: 'How do I deploy to production?' }),
]);

const sessionList = await sessions.list();

Crontab scheduler

The SDK exposes these scheduler methods on diskd.platform.crontab({ auth, scope, timezone? }). If timezone is omitted, the SDK uses the caller runtime timezone by default.

  • save
  • get
  • getStatus
  • listJobs
  • runJob
  • createJob
await crontab.createJob({
  job: {
    jobId: '01JABCD2FGH3JK4MNP5QRST6VW',
    enabled: true,
    schedule: {
      minute: '*/5',
      hour: '*',
      dayOfMonth: '*',
      month: '*',
      dayOfWeek: '*',
    },
    request: {
      method: 'POST',
      url: 'https://example.internal/hooks/sync',
      payload: {
        kind: 'json',
        value: { source: 'sdk-example' },
      },
    },
  },
});

const status = await crontab.getStatus();

See examples/node/drive-upload-download.ts, examples/node/drive-session-external.ts, and examples/node/drive-crontab.ts.

Email provider operations

diskd.os.email({ auth }) uses /v1/os/email to submit durable provider commands and wait for workspace-scoped results. It supports account listing, five-minute sync enrollment/status, body and attachment hydration, flags, deletion, and independent SMTP/IMAP connection probes. Mail content remains in Drive; send mail through the existing Drive Exchange Outbox contract.

diskd.platform.inbox({ auth }) uses these workers automatically. For a custom worker gateway, replace the removed mcpUrl option with emailUrl. Existing Inbox methods and message locators are unchanged. Internal consumers can supply emailAuth when workers and Drive require different service credentials. The worker credential is then sent only to the email endpoint. A client deadline does not cancel an already accepted command.

Messages Store API

Channel-agnostic message storage on top of Drive. Each workspace mailbox is one SQLite file under /Mailboxes/<mailboxId>.mailbox; attachment bytes live in Drive under /Mailboxes/<mailboxId>/<per-message-folder>/. The wire is the messages_store/* JSON-RPC namespace served by drive.

Use it to persist email (IMAP / JMAP), Telegram, WhatsApp, or any other channel where messages live in folders inside per-account mailboxes. Message payload is opaque JSON; the store never inspects it.

For email read/unread changes, use diskd.platform.inbox({ auth }).markRead(...). It resolves the persisted provider identity, queues a shared email worker command, and verifies the provider flags and a fresh Drive mirror read before returning. Provider flags take precedence over legacy isRead and isFlagged booleans. A client configured with contentMode: 'stored-only' rejects mark-read because it has no provider mutation boundary. The public method and argument shape are unchanged; no stored-data migration is required.

The client exposes four boundaries -- mailboxes, folders, messages, attachments -- via a functional scoping pattern: each level returns a client that captures its identifiers in a closure, so callers never repeat (mailboxId, folderId, externalId) on every call.

const messagesStore = diskd.os.messagesStore({ auth });
const mailbox = messagesStore.mailbox({ mailboxId: 'gmail-acme' });
const folder  = mailbox.folder({ folderId: 'INBOX' });
const message = folder.message({ externalId: 'imap-uid-1001' });

Mailboxes

// Allocate the mailbox SQLite file under /Mailboxes/.
// metadata is opaque JSON, stashed on the underlying drive_databases record.
const created = await messagesStore.createMailbox({
  mailboxId: 'gmail-acme',
  displayName: '[email protected]',
  metadata: { protocol: 'imap', host: 'imap.gmail.com' },
});
// → { mailboxId, dbInode, drivePath: '/Mailboxes/gmail-acme.mailbox' }

// Bind a mailbox-scoped client; subsequent calls don't repeat mailboxId.
const mailbox = messagesStore.mailbox({ mailboxId: 'gmail-acme' });

// Idempotent SQLite-schema bootstrap. Required before any folder/message ops.
await mailbox.init();

// Workspace-scoped enumeration.
const all = await messagesStore.listMailboxes();
//   → readonly { mailboxId, displayName, dbInode, recordCount,
//                sizeBytes, updatedAt }[]

// Cascade-delete (mailbox file + per-mailbox attachment subtree).
await mailbox.delete();

Folders

folderId is opaque (caller-chosen) -- IMAP folder name, JMAP id, Telegram chat_id, etc. Folder metadata is the natural place for protocol-specific sync state (UIDVALIDITY/UIDNEXT/HIGHESTMODSEQ for IMAP, JMAP state, Telegram pts, etc.). The store never reads it.

// Idempotent upsert. created=true on first call, false thereafter.
await mailbox.upsertFolder({
  folderId: 'INBOX',
  displayName: 'Inbox',
  metadata: { uidvalidity: 12345, uidnext: 1101 },
});

// List folders in this mailbox.
const folders = await mailbox.listFolders();

// Bind a folder-scoped client.
const folder = mailbox.folder({ folderId: 'INBOX' });

// Update display name / sync metadata via the scoped client (folderId implicit).
await folder.upsert({
  displayName: 'Inbox',
  metadata: { uidvalidity: 12345, uidnext: 1200 },
});

// Read one folder.
const summary = await folder.get();
//   → { folderId, displayName, metadata, messageCount, updatedAt }

// Cascade-delete folder + messages + per-message attachments.
const result = await folder.delete();
//   → { folderId, deleted, deletedMessageCount }

Messages

externalId is the caller's idempotency key within the folder (IMAP UID stringified, Telegram message_id, JMAP id, ...). payload is opaque JSON the store never inspects.

A successful upsertBatch response means the batch is durable in S3 -- the SQLite head is committed before the call returns.

// Bulk insert-or-update by externalId.
const ub = await folder.upsertBatch({
  items: [
    {
      externalId: 'imap-uid-1001',
      payload: {
        subject: 'Welcome to upgraide',
        from: '[email protected]',
        receivedAt: '2026-04-28T13:21:32Z',
        labels: ['inbox', 'unread'],
      },
    },
    { externalId: 'imap-uid-1002', payload: { subject: 'Your weekly digest' } },
  ],
});
//   → { inserted: 2, updated: 0 }

// Cursor-paginated read.
let cursor: string | null = null;
do {
  const page = await folder.listMessages({ limit: 100, cursor: cursor ?? undefined });
  for (const m of page.items) {
    // m.externalId, m.payload, m.createdAt, m.updatedAt
  }
  cursor = page.nextCursor;
} while (cursor);

// Single-message lookup.
const msg = await folder.getMessage({ externalId: 'imap-uid-1001' });

// Bulk delete; missing ids are silently skipped.
const del = await folder.deleteBatch({
  externalIds: ['imap-uid-1001', 'imap-uid-1002'],
});
//   → { deleted: 2 }

Attachments

Attachments follow Drive's upload-intent contract (start → PUT bytes → commit) but are scoped to a single message. The per-message Drive folder is created lazily on the first uploadStart call.

const message = folder.message({ externalId: 'imap-uid-1001' });

// 1. Begin upload -- get an intent + presigned URL.
const intent = await message.attachments.uploadStart({
  attachmentId: 'att-1',
  filename: 'invoice.pdf',
  contentType: 'application/pdf',
  sizeBytes: 12345,
});

// 2. PUT the bytes to intent.uploadUrl with header X-Upload-Intent-Id.
//    (See Drive upload examples for streaming/buffer modes.)
const putRes = await fetch(intent.uploadUrl, {
  method: 'PUT',
  headers: { 'X-Upload-Intent-Id': intent.intentId, 'Content-Type': 'application/pdf' },
  body: pdfBytes,
});
const { etag } = (await putRes.json()) as { etag: string };

// 3. Commit -- registers the attachment row.
await message.attachments.uploadCommit({
  attachmentId: 'att-1',
  intentId: intent.intentId,
  etag,
});

// Enumerate this message's attachments.
const list = await message.attachments.list();

// Presigned download URL (with explicit expiresAt).
const dl = await message.attachments.downloadUrl({ attachmentId: 'att-1' });

// Delete the attachment row + its Drive file.
await message.attachments.delete({ attachmentId: 'att-1' });

Auth modes

Same as the rest of the SDK -- both work:

// API key (internal services / Tilt / port-forward).
const auth = diskd.auth.apiKey({ workspaceId: 'ws-...' });

// OAuth2 client-credentials (external clients); Messages Store calls /v1/os/drive.
const auth = await diskd.auth.credentials({
  scopes: ['openid', 'drive:read', 'drive:write'],
  keyfilePath: '.agents/credentials.json',
});

const messagesStore = diskd.os.messagesStore({ auth });

Workspace identity is always auth-derived (X-Workspace-Id header from API key, ext.workspace_id claim from OAuth JWT) -- never sent on the wire as a parameter. See examples/node/messages-store-example.ts for a full end-to-end walk-through.

Routines API

REST client for managing routines (automated workflows) scoped to profile or project:

const routines = diskd.platform.routines({ auth });

// List routines in a scope
const all = await routines.list({ scope: 'workspace' });
const projectRoutines = await routines.list({ scope: 'project', projectName: 'my-project' });

// Get by slug
const routine = await routines.get({ slug: 'daily-summary', scope: 'workspace' });

// Create
const created = await routines.create({
  name: 'Daily Summary',
  scope: 'workspace',
  operativeSlug: 'research-agent',
  triggerType: 'rhythm',
  trigger: { cron: '0 9 * * *' },
  steps: [{ id: 'step-1', name: 'Summarize', action: 'summarize', order: 0 }],
});

// Update
const updated = await routines.update(
  'daily-summary',
  { status: 'paused' },
  { scopeType: 'workspace' },
);

// Delete
await routines.delete({ slug: 'daily-summary', scope: 'workspace' });

Operatives API

REST client for managing operatives (AI agents) with attached files, skills, and MCP tools:

const ops = diskd.platform.operatives({ auth });

// List operatives in a project
const list = await ops.list({ projectId: 'proj-1' });

// Get by id or slug
const operative = await ops.get('op-01');
const bySlug = await ops.getBySlug({ projectId: 'proj-1', slug: 'research-agent' });

// Create
const created = await ops.create({
  projectId: 'proj-1',
  name: 'Research Agent',
  engine: 'deep',
  engineProvider: 'anthropic',
  engineModel: 'claude-4',
});

// Update
await ops.update('op-01', {
  orders: 'You are a research assistant focused on academic papers.',
  fileAccess: 'selected',
});

// Delete
await ops.delete('op-01');

Operative files (Drive knowledge sources)

Attach Drive files from the operative's project chroot as knowledge sources:

// Attach files (paths relative to project chroot)
await ops.files.add('op-01', { paths: ['/docs/knowledge-base', '/docs/readme.md'] });

// List attached files
const files = await ops.files.list('op-01');

// Detach a file
await ops.files.remove('op-01', files[0].id);

Operative skills

// Attach skills
await ops.skills.add('op-01', { refIds: ['web-search', 'code-review'] });

// List attached skills
const skills = await ops.skills.list('op-01');

// Detach a skill
await ops.skills.remove('op-01', skills[0].id);

Operative MCP tools

// Attach MCP tools
await ops.tools.add('op-01', { selectors: ['github/search_repos', 'slack/send_message'] });

// List attached tools
const tools = await ops.tools.list('op-01');

// Detach a tool
await ops.tools.remove('op-01', tools[0].id);

Calendar API

REST client for workspace calendar management -- events, attendees, note links, attachments, and settings:

const calendar = diskd.platform.calendar({ auth });

// Accounts and events
const accounts = await calendar.listAccounts();
const events = await calendar.listEvents({
  startAfter: '2026-03-01T00:00:00Z',
  startBefore: '2026-03-31T23:59:59Z',
});

// Event CRUD
const event = await calendar.createEvent({
  calendarId: accounts[0].calendars[0].id,
  title: 'Sprint Planning',
  startAt: '2026-03-25T10:00:00Z',
  endAt: '2026-03-25T11:00:00Z',
});

await calendar.updateEvent(event.id, {
  title: 'Sprint Planning (updated)',
  metadata: { timeBlockCategory: 'meeting' },
});

await calendar.deleteEvent(event.id);

Attendees

const attendee = await calendar.attendees.add(event.id, {
  email: '[email protected]',
  role: 'required',
});

await calendar.attendees.updateRsvp(event.id, attendee.id, 'yes');
await calendar.attendees.remove(event.id, attendee.id);
const link = await calendar.noteLinks.add(event.id, {
  noteDiskPath: '/Projects/sprint/notes/planning.md',
  title: 'Planning Notes',
  linkType: 'context',
});

await calendar.noteLinks.remove(event.id, link.id);

Attachments

const attachment = await calendar.attachments.add(event.id, {
  type: 'url',
  title: 'Meeting Recording',
  url: 'https://meet.example.com/recording/123',
});

await calendar.attachments.remove(event.id, attachment.id);

Event metadata

Events support an extensible metadata JSONB field for cross-domain data:

await calendar.updateEvent(event.id, {
  metadata: {
    timeBlockCategory: 'focus',
    linkedNotes: [
      { noteDiskPath: '/docs/spec.md', title: 'Spec', linkType: 'context' },
    ],
  },
});

Settings

const settings = await calendar.getSettings();
await calendar.updateSettings({
  weekStartDay: 0,
  defaultView: 'month',
  timezone: 'America/New_York',
});

Drive Database API

JSON-RPC client for Drive's SQLite-backed database operations:

const drive = diskd.os.drive({ version: 'v1', auth });

// Create a database with schema
const db = await drive.db.create({
  name: 'myapp.workspace-123.main',
  schema: {
    users: {
      id:    { type: 'TEXT', primaryKey: true },
      name:  { type: 'TEXT', notNull: true },
      email: { type: 'TEXT', notNull: true },
    },
  },
});

// Insert rows
await drive.db.insert({
  name: 'myapp.workspace-123.main',
  table: 'users',
  rows: [{ id: '1', name: 'Alice', email: '[email protected]' }],
});

// Query with parameters
const result = await drive.db.query({
  name: 'myapp.workspace-123.main',
  sql: 'SELECT * FROM users WHERE id = ?',
  parameters: ['1'],
});

// Commit, metadata, drop, resolve
await drive.db.commit({ name: 'myapp.workspace-123.main' });
const meta = await drive.db.metadata({ name: 'myapp.workspace-123.main' });
const resolved = await drive.db.resolveByInode({ dbInode: db.dbInode });

Drive Repository (CRUD pattern)

Higher-level database + table-scoped repository pattern -- ideal for services that use Drive DB as their persistence layer:

// Create database with schema
const db = diskd.os.database({
  auth,
  dbName: 'shop.workspace-123.main',
  dbType: 'database',
  schema: {
    users:  { id: { type: 'TEXT', primaryKey: true }, name: { type: 'TEXT', notNull: true } },
    orders: { id: { type: 'TEXT', primaryKey: true }, user_id: { type: 'TEXT' }, total: { type: 'INTEGER' } },
  },
});

await db.ensureCreated();

// Get table-scoped repositories
const users = db.repository('users');
const orders = db.repository('orders');

// Insert
await users.insert([{ id: 'u1', name: 'Alice' }, { id: 'u2', name: 'Bob' }]);

// Find with where, orderBy, limit, offset
const results = await users.find({
  where: { name: 'Alice' },
  orderBy: { column: 'name', direction: 'ASC' },
  limit: 10,
});

// Find one (returns null if not found)
const alice = await users.findOne({ id: 'u1' });

// Count
const total = await orders.count();
const pending = await orders.count({ status: 'pending' });

// Update
await orders.update({ where: { id: 'o1' }, set: { status: 'shipped' } });

// Delete
await orders.deleteRows({ status: 'cancelled' });

// Raw SQL at database level for joins and complex queries
const summary = await db.query(`
  SELECT u.name, SUM(o.total) AS revenue
  FROM users u JOIN orders o ON o.user_id = u.id
  GROUP BY u.id ORDER BY revenue DESC
`);

// Commit and metadata (database-level operations)
await db.commit();
const meta = await db.metadata();

See examples/node/drive-db-repository-example.ts.

TypeORM Driver (diskd.os.datasource())

Use TypeORM entities, relations, and repositories against Drive DB. SQL is routed through Drive DB JSON-RPC, and TypeORM's transaction lifecycle maps to Drive DB's commit/rollback semantics. Requires typeorm as a peer dependency.

Installation

npm install @diskd-ai/sdk typeorm

Usage

import { diskd } from '@diskd-ai/sdk';
import { Entity, PrimaryColumn, Column } from 'typeorm';

// Define entities
@Entity({ name: 'users' })
class User {
  @PrimaryColumn({ type: 'varchar', length: 26 })
  id!: string;

  @Column({ type: 'varchar' })
  name!: string;

  @Column({ type: 'varchar' })
  email!: string;
}

// Create DataSource backed by Drive DB
process.env.APIS_BASE_URL ??= 'https://apis.diskd.local:8080';

const auth = diskd.auth.apiKey({ workspaceId: 'workspace-123' });

const ds = diskd.os.datasource({
  auth,
  url: `${process.env.APIS_BASE_URL}/v1/os/database/api/v1`,
  dbName: 'shop.workspace-123.main',
  entities: [User],
  synchronize: true,
});

await ds.initialize();

// Standard TypeORM repository operations
const userRepo = ds.getRepository(User);
await userRepo.save({ id: 'u1', name: 'Alice', email: '[email protected]' });

const alice = await userRepo.findOneBy({ id: 'u1' });
const users = await userRepo.find({ order: { name: 'ASC' } });

// Persist to S3 (flush WAL)
await ds.driver.commit();

// Rollback discards uncommitted changes (revert to last commit)
await ds.driver.driveRollback();

Transaction mapping

TypeORM operationDrive DB action
BEGIN TRANSACTIONNo-op (writes auto-accumulate in WAL)
COMMITdrive.db.commit() -- flush WAL to S3
ROLLBACKdrive.db.rollback() -- discard WAL

Limitations (v1)

  • No nested transactions / savepoints (deferred to v2)
  • Affected row count returns 0 (each JSON-RPC call is a separate SQLite connection; works fine for ULID-based entities)
  • Schema introspection from live database is limited; synchronize: true generates DDL directly

See examples/node/typeorm-drive-example.ts and docs/typeorm-driver-design.md.

LLM Router API

JSON-RPC 2.0 + NDJSON streaming for multi-provider LLM completions:

const llm = diskd.os.llm({ auth });

// Non-streaming completion
const result = await llm.completions.create({
  provider: 'openai', model: 'gpt-4o-mini',
  messages: [{ role: 'user', content: 'Hello' }],
  maxTokens: 64,
});

// Streaming
for await (const chunk of llm.completions.stream(params)) {
  process.stdout.write(chunk.choices[0]?.delta?.content ?? '');
}

// Models, embeddings, OCR, audio transcription
const models = await llm.models.listAll();
const embeddings = await llm.embeddings.create({ provider: 'openai', model: 'text-embedding-3-small', input: ['text'] });

See examples/node/llm-router-example.ts.

Agent Hub API

Deprecated and unrouted. Agent Hub is deprecated, and apis-service declares no /v1/os/agents route, so diskd.os.agents (and createAgentHubClient) cannot reach it through the gateway. The client stays exported only to avoid a breaking change. Agent turns run on pi-agent-service, reached through the app-service sessions API (/api/sessions); this SDK has no client for that API yet. diskd.platform.sessions is the Drive session store and does not invoke agents.

SSE streaming with StreamProtocolHandler + StreamProtocolFetcher for agent invocation:

import { diskd, StreamProtocolHandler } from '@diskd-ai/sdk';

const agents = diskd.os.agents({ auth, workspaceId: '...' });

// List agents and models
const agentList = await agents.agents.list();
const models = await agents.agents.getSupportedModels('assistant');
const billing = await agents.billing.getAliases();

// Invoke with fluent stream handling
const handler = new StreamProtocolHandler()
  .on('response.output_text.delta', (e) => process.stdout.write(e.delta))
  .on('response.completed', (e) => console.log('done', e.response.usage))
  .on('response.failed', (e) => console.error(e.response.error.message))
  .on('error', (e) => console.error(e.message));

const stream = await agents.invoke({
  agentName: 'assistant',
  query: 'Hello',
  agentOptions: { maxTokens: 256 },
});

stream.map((event) => handler.handle(event))
  .stop(() => console.log('stream closed'))
  .catch((err) => console.error(err));

Stream protocol events include text deltas, function calls/results, content parts (images, files, audio), web/file search lifecycle, external sources, plan updates, notifications, and error/completion signals.

See examples/node/agent-hub-example.ts.

MCP Hub API

REST client for MCP server catalog, registry, and integrations:

const mcp = diskd.os.mcp({ auth, workspaceId: '...' });

// Catalog
const catalog = await mcp.catalog.list({ search: 'github' });
const details = await mcp.catalog.getServerDetails(serverId);

// Registry (installed servers)
const registry = await mcp.registry.list();
const added = await mcp.registry.addServer({ catalogServerId: '...' });
await mcp.registry.toggleTool(serverId, toolId, false);
const logs = await mcp.registry.getServerLogs(serverId, { limit: 10 });
await mcp.registry.deleteServer(serverId);

// Env vars, connection settings, remote servers
await mcp.registry.upsertEnvVar(serverId, { key: 'TOKEN', value: '...' });
await mcp.registry.addRemoteServer({ name: 'My MCP', url: '...', authType: 'pat' });

See examples/node/mcp-hub-example.ts.

Telegram Userbot API

REST client for Telegram channel resolution, importing, and message retrieval:

const tg = diskd.utils.tgUserBot({ auth, workspaceId: '...' });

// Resolve channel (public, no auth required)
const resolved = await tg.channels.resolve('durov');

// Channel operations
const channels = await tg.channels.list();
await tg.channels.add({ channelIdentifier: '@mychannel', limit: 1000 });
await tg.channels.sync({ telegramId: -1001234567890 });

// Messages and stats
const messages = await tg.channels.getMessages(channelId, { limit: 50, searchText: 'keyword' });
const stats = await tg.channels.getStats(channelId);
const status = await tg.channels.getStatus(channelId);

// Tasks
const tasks = await tg.tasks.list();
await tg.tasks.cancel(taskUuid);

See examples/node/tg-userbot-example.ts.

Web Navigator API

REST client for URL resolution and web scraping jobs:

const webNav = diskd.utils.webNavigator({ auth, workspaceId: '...' });

// Resolve URL metadata
const meta = await webNav.resolve({ url: 'https://example.com' });

// Submit scrape job
const job = await webNav.scrape.submit({ url: 'https://example.com', depth: 1, maxPages: 10 });
const status = await webNav.scrape.getStatus(job.jobId);
const result = await webNav.scrape.getResult(job.jobId);
await webNav.scrape.cancel(job.jobId);

See examples/node/web-navigator-example.ts.

Web quickstart (Vite + PKCE)

Use @diskd-ai/sdk/browser and a standard OAuth2 Authorization Code + PKCE redirect.

Runnable example: examples/web/ (see examples/README.md).

Publishing a new version

  1. Bump the version in package.json:

    npm version patch   # 0.3.0 -> 0.3.1
    npm version minor   # 0.3.0 -> 0.4.0
    npm version major   # 0.3.0 -> 1.0.0
    
  2. Push the commit and tag:

    git push gitlab main --tags
    
  3. The GitLab CI pipeline triggers on v*.*.* tags and automatically:

    • Builds the project
    • Runs unit tests and typecheck
    • Publishes to the GitLab Package Registry
  4. Verify at: https://gitlab.iosya.com/upgraide-v2/platform-api/-/packages

Docs and examples

  • Quickstart: docs/sdk-quickstart.md
  • Examples: examples/README.md
  • Design: docs/drive-session-sdk-design.md

版本与发布记录

@diskd-ai/sdk 共发布 63 个版本,首次发布于 2026/04/29;过去 12 个月发布了 63 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
7.10.0latest2026/11/12今天
7.9.02026/11/039 天前
7.8.22026/10/2815 天前
7.8.12026/10/2716 天前
7.8.02026/10/121 个月前
7.7.02026/10/111 个月前

关于 @diskd-ai/sdk 的常见问题

@diskd-ai/sdk 是什么?

SDK for DiskD platform APIs (Drive, LLM Router, Agent Hub, MCP Hub, Telegram Userbot, Web Navigator)

如何安装 @diskd-ai/sdk?

在项目目录运行 npm install @diskd-ai/sdk,或使用 pnpm add / yarn add / bun add。

@diskd-ai/sdk 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@diskd-ai/sdk 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@diskd-ai/sdk 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22.0.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@diskd-ai/sdk 可以商用吗?

@diskd-ai/sdk 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@diskd-ai/sdk 的下载量有多少?

根据 npm 官方下载统计,@diskd-ai/sdk 在 2026/10/13 – 2026/11/11 这 30 天内被下载 8,132 次,日均约 271 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@diskd-ai/sdk 还在维护吗?

最新版本 7.10.0 发布于 2026/11/12(今天);至今共发布 63 个版本,其中过去 12 个月发布了 63 个;首次发布于 2026/04/29。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。