@corbits/artifacts

中文文档
0.4.0发布于 2026/11/01LGPL-2.1-onlyTypeScriptES Module近 30 天下载 3965

Artifacts, versions and uploads with a pluggable ContentStore, mountable onto any Interchange host. Requires @intx 0.4.0 or newer.

原始使用文档
文档目录15 节
  1. Why @corbits/artifacts?
  2. Install
  3. Quickstart
  4. Where it fits
  5. Reference
  6. createArtifactRoutes(deps)
  7. createWorkflowArtifactRoutes(deps)
  8. runArtifactMigrations(config, { schema })
  9. @corbits/artifacts/sidecar-bundle
  10. Using with Interchange
  11. Upgrading from 0.1
  12. Upgrading from 0.2
  13. Agent tools
  14. Hosts
  15. License

@corbits/artifacts

Versioned documents and files for Interchange agents and their users: a Corbits hub module that mounts grant-gated Hono routes on @intx/hub-api, keeps versions in Postgres and bytes in a pluggable ContentStore, and ships agent tools for the sidecar.

Why @corbits/artifacts?

  1. Every version is kept. Each revision of a document or uploaded file is a new version with its own content, bytes and SHA-256 digest. expectedVersion turns a revise into a compare-and-set.
  2. Mounts like any hub route. createArtifactRoutes returns a Hono<TenantEnv> sub-app. Reads are confined to the caller's tenant, and writes run through the hub's requireGrant.
  3. Agents write through the same store. A run-scoped route set and a sidecar tool pack let a workflow run create, read and revise the artifacts its users see.
  4. Migrations that replay safely. Every SQL file is idempotent and runs on every boot under an advisory lock, so several replicas can start at once.

It ships no UI and no object store: the host renders artifacts and brings its own ContentStore for large files.

Install

bun add @corbits/artifacts \
  @intx/agent @intx/authz @intx/db @intx/hub-api @intx/types \
  drizzle-orm hono hono-openapi postgres

Add @intx/agent only if an agent uses the sidecar tools. Runs on Node >= 24.

Quickstart

With DATABASE_URL pointing at a hub database that has run runArtifactMigrations (see Using with Interchange):

import {
  createArtifact,
  createArtifactDb,
  getArtifact,
} from "@corbits/artifacts";

const { db, close } = createArtifactDb(process.env.DATABASE_URL!);

const created = await db.transaction((tx) =>
  createArtifact(tx, {
    scope: { tenantId: "acme", principalId: "alice" },
    ownerPrincipalId: "alice",
    kind: "document",
    title: "Notes",
    content: "Hello",
    source: { origin: "manual" },
  }),
);
console.log(await getArtifact(db, created.id));
await close();

acme and alice must be a tenant and principal in the hub. It prints the artifact at version 1.

Where it fits

Interchange runs AI agents as principals: accounts with their own identity, permissions and credentials. Its hub is the multi-tenant control plane that holds tenants, principals and grants (permissions a principal holds on a resource); its sidecar is the agent runtime.

Reference

createArtifactRoutes(deps)

depsTypeWhat the host provides
dbArtifactDbThe hub's drizzle handle, for example from createDB.
contentStoreContentStoreWhere file bytes go. InlineContentStore keeps them in Postgres; implement the port for object storage.
requireGrantRequireGrantFrom @intx/hub-api's createRequireGrant.
onArtifactCreated(tx, row, scope) => Promise<void>Optional. Runs in the creating transaction for any other creation side effect. The creator's write and archive grants are minted for you.
decorate(tenantId, rows) => Promise<void>Optional. Adds display-only fields to serialized rows. It must not change which rows are returned.
uploadPolicyUploadPolicyOptional. MIME types POST /artifacts/upload accepts. Defaults to ARTIFACT_UPLOAD_POLICY.
countSegmentsArtifactCountSegmentsOptional. Named predicates for GET /artifacts/counts.
RouteGrantDoes
GET /artifactsnone (tenant-scoped)Lists artifacts, paginated. Empty 200 with no principal.
GET /artifacts/countsnone (tenant-scoped)Counts per countSegments bucket, plus all.
POST /artifactscreate on artifact:*Creates a text or URL artifact at version 1.
POST /artifacts/uploadcreate on artifact:*Uploads one or more files (multipart/form-data), one artifact each.
GET /artifacts/:idnone (tenant-scoped)The latest version.
GET /artifacts/:id/versionsnone (tenant-scoped)Version history.
GET /artifacts/:id/versions/:versionnone (tenant-scoped)One version, with its content.
POST /artifacts/:id/versionswrite on artifact:<id>Adds a version from JSON, or from a new file for an uploaded file.
POST /artifacts/:id/archive, unarchivearchive on artifact:<id>Archives or restores.
GET /artifacts/:id/download?version=Nnone (tenant-scoped)The bytes of a version, latest by default.
GET /artifacts/:id/previewnone (tenant-scoped)A text/html artifact under a sandboxing CSP; 415 otherwise.

With no principal, every route except the list and counts answers 403. Another tenant's artifact, or an unknown id, answers 404. Set a request-body limit on the host for the upload and file-revise routes; they buffer the body before checking MAX_UPLOAD_BYTES.

Every write reports contentSha256, the hex sha256 stored with the new version: over the UTF-8 text, or over the file's bytes for an upload. POST /artifacts and POST /artifacts/upload carry it on each artifact, and POST /artifacts/:id/versions returns it beside artifactId and version. It is null only for a version written before digests existed.

createWorkflowArtifactRoutes(deps)

Run-scoped routes for a workflow run, which authenticates with a bearer token and an x-workflow-run-address header instead of a session.

depsTypeWhat the host provides
dbArtifactDbSame as above.
contentStoreContentStoreSame as above.
resolveRunScopeWorkflowRunResolver(bearerToken, runAddress) => ResolvedWorkflowRunScope | null. null answers 401.
agentTokenAgentTokenAuthOptional. Accepts an agent's own hub token as a second way in.
uploadPolicyUploadPolicyOptional. MIME types POST /artifacts/binary accepts. Defaults to ARTIFACT_UPLOAD_POLICY.
maxBinaryBytesnumberOptional. Byte ceiling for POST /artifacts/binary. Defaults to MAX_UPLOAD_BYTES.
maxContentCharsnumberOptional. Character ceiling on the resulting content — full text or edited text — for POST /artifacts and PATCH /artifacts/:id/versions. Defaults to 64,000.
beforeWriteWorkflowBeforeWriteOptional. Checks each text create (POST /artifacts) and revision (PATCH /artifacts/:id) before it is written. Omitted, every valid write lands.

beforeWrite({ scope, kind, write, content }) gets the run scope, the artifact kind, write as { op: "create" } or { op: "revise", artifactId }, and the content the version will hold after any edits. It returns one of:

  • { action: "allow" } writes as usual.
  • { action: "refuse", code, message } answers 422 with { code, message }, and the agent's tool error is message. code must match /^[a-z0-9_]+$/; message must be 1 to 1,000 characters.
  • { action: "redirect", artifactId, expectedVersion? } turns a create into a revision of that artifact, which must be of the same kind and in the run's tenant, answering 200 with its { id, version }. The revision takes the create's title. A missing, archived or other-tenant artifact answers 404, and a stale expectedVersion answers 409 telling the agent to read the existing artifact and revise it.

Any other decision, a redirect returned for a revision, or a redirect to an artifact of another kind answers 500 and writes nothing. Binary uploads and linked files are not checked.

A create's check is not serialized: two concurrent creates can both be allowed, so a policy like "one plan per tenant" can still end with two plans when runs race. A revision's check runs inside the transaction holding the artifact's row lock, so it should be fast and must not write that artifact.

Every write (POST /artifacts, POST /artifacts/binary, POST /artifacts/link-file, PATCH /artifacts/:id) answers { data: { id, version, contentSha256 } }, with the same digest the tenant routes report. For link-file the digest covers the stored preview text (empty when none is given), not the workspace file at path.

runArtifactMigrations(config, { schema })

Takes the same DBConfig and schema as Interchange's runMigrations. schema holds the host's tenant and principal tables; this package's tables always go in artifacts.

@corbits/artifacts/sidecar-bundle

artifacts is an @intx/agent tool pack of three tools. They call the run-scoped routes through the agent's hub credential.

  • artifact_write creates an artifact when no id is given, from content or a workspace file path. With an id it revises, either with the full text or with edits: exact passages, each of which must appear once, or nothing is written. expectedVersion refuses the write if a newer version exists. It returns { id, version, contentSha256 }.
  • artifact_read returns the content, a past version with version, or a range with offset and limit.
  • artifact_search lists the newest artifacts, filtered by kind and by a query matched against title and content.

Using with Interchange

Run the migrations after Interchange's, mount both route sets, grant create on artifact:* to principals that create artifacts, and give agents the tool pack.

import { timeWindowEvaluator } from "@intx/authz";
import { createDB, createGrantStore, runMigrations } from "@intx/db";
import { createRequireGrant } from "@intx/hub-api";
import {
  createArtifactRoutes,
  InlineContentStore,
  runArtifactMigrations,
} from "@corbits/artifacts";

const dbConfig = {
  host: "localhost",
  port: 5432,
  user: "postgres",
  password: "postgres",
  database: "interchange",
};

await runMigrations(dbConfig, { schema: "public" });
await runArtifactMigrations(dbConfig, { schema: "public" });

const { db } = createDB(dbConfig);
const requireGrant = createRequireGrant({
  grantStore: createGrantStore(db),
  conditionRegistry: { time_window: timeWindowEvaluator },
});

export const artifactRoutes = createArtifactRoutes({
  db,
  contentStore: InlineContentStore,
  requireGrant,
});

Mount artifactRoutes on the hub app at /api, behind the hub's auth and tenant middleware.

For agents, mount createWorkflowArtifactRoutes({ db, contentStore: InlineContentStore, resolveRunScope }) at /api/workflow-artifacts. resolveRunScope is the host's (bearerToken, runAddress) lookup that returns the run's tenant and principal from the hub's workflow runs, or null. The sidecar tools call /api/workflow-artifacts. Add them to an agent and bind its hub credential to the agent's hub token when you deploy it:

import { defineAgent, type InferencePreference } from "@intx/agent";
import { artifacts } from "@corbits/artifacts/sidecar-bundle";

export function buildAssistant(sources: readonly InferencePreference[]) {
  return defineAgent({
    id: "assistant",
    systemPrompt: "You help the team read and write shared artifacts.",
    capabilities: [],
    inference: { sources },
    tools: [artifacts],
  });
}

Upgrading from 0.1

  • mountArtifacts(app, opts) is now createArtifactRoutes(deps), and mountWorkflowArtifacts(app, opts) is now createWorkflowArtifactRoutes(deps). Mount both with app.route.
  • POST /artifacts and POST /artifacts/upload need create on artifact:*. The upgrade grants it to every principal that has already created an artifact in its tenant; grant it to new principals yourself.
  • runArtifactMigrations(db) is now runArtifactMigrations(dbConfig, { schema }). Existing 0.1.0 data upgrades in place on the first boot.
  • That boot drops the 0.1.0 migration ledger. You cannot roll back to 0.1.0, and 0.1.0 and 0.2.0 replicas must not share a database.
  • The drizzle tables, the web_site helpers, SKILL_DRAFT_KIND, windowContent and the mail-attachment routes and helpers are removed. The mail_attachment_ref table is dropped.
  • Node 24 or newer is required.

Upgrading from 0.2

Separated by surface: what changed for AGENTS (the sidecar tool pack) and what changed for HOSTS (the routes and exports).

Agent tools

The sidecar tool pack collapsed its five finer-grained tools into three. artifact_write now covers both create and link (revise via edits/content is unchanged), and its { artifactId, version } result is the same.

0.2 tool0.3How
artifact_createartifact_writeCall it with no artifactId, passing title, kind, content.
artifact_link_fileartifact_writeCall it with no artifactId, passing a workspace path.
artifact_read_chunkartifact_readPass the range as offset/limit instead of start/end.
artifact_listartifact_searchResults are ordered most-recently-updated first.
artifact_find_by_titleartifact_searchNot a drop-in — see the caveat below.

artifact_find_by_title is not a drop-in. In 0.2 it matched the title exactly, matched the title only, and returned at most one { artifactId, version } (or null). In 0.3 artifact_search does a case-insensitive substring (ILIKE) match on both the title and the content, and returns a list of matches. A caller that needs the old exact-title, single-result semantics must post-filter the results (compare title case-insensitively and take the first exact match).

Search results are capped at 50 artifacts per call. artifact_search accepts a cursor property (plus limit, capped at 50) to page through all matches; pass back the previous call's nextCursor (keeping the same query and kind) until none is returned.

Hosts

The roles are unchanged, but two routes and one export were removed:

  • GET /artifacts/find → GET /artifacts?query=…, where query is the same case-insensitive substring (ILIKE) match on title and content.
  • GET /artifacts/:id/chunk → GET /artifacts/:id/read?offset&limit.
  • readArtifactChunk is removed. findArtifactByTitle and linkFileArtifact remain exported. README.md's Reference section describes the current routes and tools.

Two error classes are now exported from the package entry: VersionConflictError answers 409 when an expectedVersion is stale, and ArtifactEditError answers 400 when a revise is refused. Catch them to surface precise failures to your users.

License

LGPL-2.1-only

版本与发布记录

@corbits/artifacts 共发布 4 个版本,首次发布于 2026/09/25;过去 12 个月发布了 4 个版本。

版本发布日期距今
0.4.0latest2026/11/019 天前
0.3.02026/10/2021 天前
0.2.02026/09/281 个月前
0.1.02026/09/251 个月前

关于 @corbits/artifacts 的常见问题

@corbits/artifacts 是什么?

Artifacts, versions and uploads with a pluggable ContentStore, mountable onto any Interchange host. Requires @intx 0.4.0 or newer.

如何安装 @corbits/artifacts?

在项目目录运行 npm install @corbits/artifacts,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:hono(^4.12.32)、@intx/db(^0.5.0)、postgres(^3.4.9)、@intx/types(^0.5.0)、drizzle-orm(^0.45.2)、hono-openapi(^1.2.0)、@intx/hub-api(^0.5.0)、@intx/hub-common(0.5.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@corbits/artifacts 支持 TypeScript 吗?

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

@corbits/artifacts 支持 ESM 还是 CommonJS?

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

@corbits/artifacts 需要什么版本的 Node.js?

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

@corbits/artifacts 可以商用吗?

@corbits/artifacts 使用 LGPL-2.1-only 许可。弱 Copyleft:作为库被调用时通常不要求开源你自己的代码,但修改库本身需以 LGPL 开源;前端打包合并后的适用边界存在不同理解。以上为概括,不构成法律意见,请以许可原文为准。

@corbits/artifacts 的下载量有多少?

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

@corbits/artifacts 还在维护吗?

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