@absolutejs/artifacts 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +90 -0
- package/README.md +128 -0
- package/dist/index.js +296 -0
- package/dist/manifest.js +5999 -0
- package/dist/manifest.json +100 -0
- package/dist/src/index.d.ts +14 -0
- package/dist/src/manifest.d.ts +9 -0
- package/dist/src/registry.d.ts +17 -0
- package/dist/src/renderers.d.ts +17 -0
- package/dist/src/service.d.ts +31 -0
- package/dist/src/store.d.ts +8 -0
- package/dist/src/tools.d.ts +17 -0
- package/dist/src/types.d.ts +55 -0
- package/package.json +58 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Business Source License 1.1
|
|
2
|
+
|
|
3
|
+
**Licensor:** Alex Kahn
|
|
4
|
+
|
|
5
|
+
**Licensed Work:** @absolutejs/artifacts (https://github.com/absolutejs/artifacts)
|
|
6
|
+
|
|
7
|
+
**Change Date:** July 14, 2030
|
|
8
|
+
|
|
9
|
+
**Change License:** Apache License, Version 2.0
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Terms
|
|
14
|
+
|
|
15
|
+
The Licensor hereby grants you the right to copy, modify, create derivative
|
|
16
|
+
works, redistribute, and make non-production use of the Licensed Work. The
|
|
17
|
+
Licensor may make an Additional Use Grant, permitting limited production use.
|
|
18
|
+
|
|
19
|
+
### Additional Use Grant
|
|
20
|
+
|
|
21
|
+
You may use the Licensed Work in production, provided your use does not include
|
|
22
|
+
any of the following:
|
|
23
|
+
|
|
24
|
+
1. **Offering a Competing Service.** You may not offer the Licensed Work, or
|
|
25
|
+
any derivative or substantial portion of it, to third parties as a hosted or
|
|
26
|
+
managed AI artifact-generation, document/page-creation, editing, rendering,
|
|
27
|
+
publishing, or artifact-management service (including, but not limited to,
|
|
28
|
+
services like Canva, Gamma, Adobe Express, Notion AI, Webflow AI, or v0).
|
|
29
|
+
This includes any product whose primary value to its users is the
|
|
30
|
+
functionality the Licensed Work provides.
|
|
31
|
+
|
|
32
|
+
2. **Resale or Redistribution as a Standalone Product.** You may not sell,
|
|
33
|
+
license, or distribute the Licensed Work, or any derivative or fork of it,
|
|
34
|
+
as a standalone commercial product.
|
|
35
|
+
|
|
36
|
+
3. **Removal of Attribution.** Any derivative work, fork, or redistribution of
|
|
37
|
+
the Licensed Work must prominently credit AbsoluteJS and include a link to
|
|
38
|
+
the original project repository (https://github.com/absolutejs/artifacts).
|
|
39
|
+
|
|
40
|
+
For clarity, the following uses are expressly permitted:
|
|
41
|
+
|
|
42
|
+
- Using the Licensed Work to build and operate your own applications, websites,
|
|
43
|
+
internal tools, or SaaS products (whether commercial or non-commercial), so
|
|
44
|
+
long as the Licensed Work itself is not the primary product you are selling.
|
|
45
|
+
- Using the Licensed Work as a dependency in commercial software you build and
|
|
46
|
+
sell, as long as the software is not itself a competing managed service of
|
|
47
|
+
the kind described in clause 1.
|
|
48
|
+
- Providing consulting, development, or professional services to clients using
|
|
49
|
+
the Licensed Work.
|
|
50
|
+
- Forking and modifying the Licensed Work for your own internal use, provided
|
|
51
|
+
attribution is maintained.
|
|
52
|
+
|
|
53
|
+
### Change Date and Change License
|
|
54
|
+
|
|
55
|
+
On the Change Date specified above, or on such other date as the Licensor may
|
|
56
|
+
specify by written notice, the Licensed Work will be made available under the
|
|
57
|
+
Change License (Apache License, Version 2.0). Until the Change Date, the terms
|
|
58
|
+
of this Business Source License 1.1 apply.
|
|
59
|
+
|
|
60
|
+
### Trademark
|
|
61
|
+
|
|
62
|
+
This license does not grant you any rights to use the "AbsoluteJS" or
|
|
63
|
+
"@absolutejs" name, logo, or any related trademarks. Forks and derivative works
|
|
64
|
+
must not be named or branded in a manner that suggests endorsement by or
|
|
65
|
+
affiliation with AbsoluteJS or the Licensor.
|
|
66
|
+
|
|
67
|
+
### Notices
|
|
68
|
+
|
|
69
|
+
You must not remove or obscure any licensing, copyright, or other notices
|
|
70
|
+
included in the Licensed Work.
|
|
71
|
+
|
|
72
|
+
### No Warranty
|
|
73
|
+
|
|
74
|
+
THE LICENSED WORK IS PROVIDED "AS IS". THE LICENSOR HEREBY DISCLAIMS ALL
|
|
75
|
+
WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF
|
|
76
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO
|
|
77
|
+
EVENT SHALL THE LICENSOR BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY,
|
|
78
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR
|
|
79
|
+
IN CONNECTION WITH THE LICENSED WORK OR THE USE OR OTHER DEALINGS IN THE
|
|
80
|
+
LICENSED WORK.
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Contact
|
|
85
|
+
|
|
86
|
+
For commercial licensing inquiries or additional permissions, contact:
|
|
87
|
+
|
|
88
|
+
- **Alex Kahn**
|
|
89
|
+
- alexkahndev@gmail.com
|
|
90
|
+
- alexkahndev.github.io
|
package/README.md
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# @absolutejs/artifacts
|
|
2
|
+
|
|
3
|
+
The typed lifecycle for things an AI makes.
|
|
4
|
+
|
|
5
|
+
An AI-generated page, report, plan, email, deck, or image should not disappear
|
|
6
|
+
into a chat transcript or become an unvalidated blob. It should have a kind,
|
|
7
|
+
structured content, ownership, provenance, revisions, capabilities, renderers,
|
|
8
|
+
and an explicit publication lifecycle.
|
|
9
|
+
|
|
10
|
+
`@absolutejs/artifacts` provides those contracts without owning your database,
|
|
11
|
+
routes, authorization, UI, or hosting.
|
|
12
|
+
|
|
13
|
+
## What it owns
|
|
14
|
+
|
|
15
|
+
- Structured artifact-kind schemas and runtime validation
|
|
16
|
+
- Draft, published, and archived lifecycle states
|
|
17
|
+
- Optimistic revisions that prevent lost edits
|
|
18
|
+
- Storage, renderer, and publisher interfaces
|
|
19
|
+
- An in-memory store for development and tests
|
|
20
|
+
- Owner-bound lifecycle tools structurally compatible with AI tool maps
|
|
21
|
+
- Provenance fields for model, tool, trace, and source entities
|
|
22
|
+
|
|
23
|
+
Your application retains authorization, durable persistence, public tokens,
|
|
24
|
+
URLs, notifications, analytics, submissions, and product-specific rendering.
|
|
25
|
+
|
|
26
|
+
## Define kinds once
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import { Type } from "@sinclair/typebox";
|
|
30
|
+
import {
|
|
31
|
+
createArtifactService,
|
|
32
|
+
createMemoryArtifactStore,
|
|
33
|
+
defineArtifactRegistry,
|
|
34
|
+
} from "@absolutejs/artifacts";
|
|
35
|
+
|
|
36
|
+
const registry = defineArtifactRegistry({
|
|
37
|
+
page: {
|
|
38
|
+
capabilities: ["archive", "edit", "preview", "publish"],
|
|
39
|
+
content: Type.Object({
|
|
40
|
+
blocks: Type.Array(
|
|
41
|
+
Type.Union([
|
|
42
|
+
Type.Object({ heading: Type.String(), type: Type.Literal("hero") }),
|
|
43
|
+
Type.Object({ body: Type.String(), type: Type.Literal("text") }),
|
|
44
|
+
]),
|
|
45
|
+
),
|
|
46
|
+
theme: Type.Union([Type.Literal("dark"), Type.Literal("light")]),
|
|
47
|
+
}),
|
|
48
|
+
label: "Page",
|
|
49
|
+
schemaVersion: 1,
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const artifacts = createArtifactService({
|
|
54
|
+
registry,
|
|
55
|
+
store: createMemoryArtifactStore(),
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const page = await artifacts.create("owner-123", {
|
|
59
|
+
content: {
|
|
60
|
+
blocks: [{ heading: "A real page", type: "hero" }],
|
|
61
|
+
theme: "light",
|
|
62
|
+
},
|
|
63
|
+
createdBy: "agent",
|
|
64
|
+
kind: "page",
|
|
65
|
+
provenance: { model: "your-model", tool: "create_page" },
|
|
66
|
+
title: "Launch page",
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Compose publishing and rendering
|
|
71
|
+
|
|
72
|
+
Publishing is an adapter because public access is a host policy:
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
const artifacts = createArtifactService({
|
|
76
|
+
publisher: {
|
|
77
|
+
publish: async (artifact, { idempotencyKey }) =>
|
|
78
|
+
mintPublicTokenAndUrl(artifact, idempotencyKey),
|
|
79
|
+
unpublish: async (artifact, { idempotencyKey }) =>
|
|
80
|
+
revokePublicAccess(artifact, idempotencyKey),
|
|
81
|
+
},
|
|
82
|
+
registry,
|
|
83
|
+
store: postgresArtifactStore,
|
|
84
|
+
});
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Renderers are independently registered by artifact kind and output format:
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
const renderers = createArtifactRendererRegistry([
|
|
91
|
+
{
|
|
92
|
+
format: "html",
|
|
93
|
+
kind: "page",
|
|
94
|
+
render: async (artifact) => ({
|
|
95
|
+
body: renderSafePage(artifact.content),
|
|
96
|
+
mediaType: "text/html; charset=utf-8",
|
|
97
|
+
}),
|
|
98
|
+
},
|
|
99
|
+
]);
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The package never treats generated HTML or JavaScript as trusted executable
|
|
103
|
+
content. Applications should define structured content schemas and render them
|
|
104
|
+
through controlled adapters.
|
|
105
|
+
|
|
106
|
+
## AI tools
|
|
107
|
+
|
|
108
|
+
`createArtifactTools` binds create, list, get, update, publish, and unpublish
|
|
109
|
+
operations to one owner. The returned definitions use TypeBox inputs and the
|
|
110
|
+
same `{ description, input, handler }` shape used by `@absolutejs/ai`.
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
const tools = createArtifactTools({
|
|
114
|
+
createdBy: "agent",
|
|
115
|
+
ownerId: member.id,
|
|
116
|
+
service: artifacts,
|
|
117
|
+
});
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Only expose the publication tool where the user explicitly controls public
|
|
121
|
+
access.
|
|
122
|
+
|
|
123
|
+
## License
|
|
124
|
+
|
|
125
|
+
Business Source License 1.1 — free for your own products, applications, and
|
|
126
|
+
internal use; you may not offer it as a competing hosted AI artifact-generation,
|
|
127
|
+
editing, rendering, publishing, or artifact-management service. Converts to
|
|
128
|
+
Apache 2.0 on July 14, 2030. See [LICENSE](./LICENSE).
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/registry.ts
|
|
3
|
+
import { Value } from "@sinclair/typebox/value";
|
|
4
|
+
|
|
5
|
+
// src/types.ts
|
|
6
|
+
var ARTIFACT_STATUSES = ["draft", "published", "archived"];
|
|
7
|
+
|
|
8
|
+
class ArtifactError extends Error {
|
|
9
|
+
code;
|
|
10
|
+
constructor(code, message) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = "ArtifactError";
|
|
13
|
+
this.code = code;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// src/registry.ts
|
|
18
|
+
var defineArtifactRegistry = (definitions) => ({
|
|
19
|
+
definitions,
|
|
20
|
+
kindNames: Object.keys(definitions),
|
|
21
|
+
parse: (kind, content) => {
|
|
22
|
+
const definition = definitions[kind];
|
|
23
|
+
if (!definition) {
|
|
24
|
+
throw new ArtifactError("unknown_kind", `Unknown artifact kind: ${kind}`);
|
|
25
|
+
}
|
|
26
|
+
if (!Value.Check(definition.content, content)) {
|
|
27
|
+
const issue = [...Value.Errors(definition.content, content)][0];
|
|
28
|
+
const detail = issue ? `${issue.path || "/"}: ${issue.message}` : "invalid content";
|
|
29
|
+
throw new ArtifactError("invalid_content", `Invalid ${kind} artifact content (${detail})`);
|
|
30
|
+
}
|
|
31
|
+
return content;
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
// src/renderers.ts
|
|
35
|
+
var createArtifactRendererRegistry = (initial = []) => {
|
|
36
|
+
const key = (kind, format) => `${kind}:${format}`;
|
|
37
|
+
const renderers = new Map(initial.map((renderer) => [key(renderer.kind, renderer.format), renderer]));
|
|
38
|
+
return {
|
|
39
|
+
formatsFor: (kind) => [...renderers.values()].filter((renderer) => renderer.kind === kind).map((renderer) => renderer.format),
|
|
40
|
+
register: (renderer) => {
|
|
41
|
+
renderers.set(key(renderer.kind, renderer.format), renderer);
|
|
42
|
+
},
|
|
43
|
+
render: async (artifact, format) => {
|
|
44
|
+
const renderer = renderers.get(key(artifact.kind, format));
|
|
45
|
+
if (!renderer) {
|
|
46
|
+
throw new ArtifactError("renderer_unavailable", `No ${format} renderer is registered for ${artifact.kind}`);
|
|
47
|
+
}
|
|
48
|
+
return renderer.render(artifact);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
// src/service.ts
|
|
53
|
+
var requireCapability = (artifact, capability) => {
|
|
54
|
+
if (!artifact.capabilities.includes(capability)) {
|
|
55
|
+
throw new ArtifactError("unsupported_capability", `${artifact.kind} artifacts do not support ${capability}`);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
var createArtifactService = (options) => {
|
|
59
|
+
const now = () => (options.clock ?? (() => new Date))().toISOString();
|
|
60
|
+
const idFactory = options.idFactory ?? (() => crypto.randomUUID());
|
|
61
|
+
const get = async (ownerId, artifactId) => {
|
|
62
|
+
const artifact = await options.store.get(ownerId, artifactId);
|
|
63
|
+
if (!artifact) {
|
|
64
|
+
throw new ArtifactError("not_found", "Artifact not found");
|
|
65
|
+
}
|
|
66
|
+
return artifact;
|
|
67
|
+
};
|
|
68
|
+
const saveRevision = async (artifact, expectedRevision) => {
|
|
69
|
+
const saved = await options.store.save(artifact, expectedRevision);
|
|
70
|
+
if (!saved) {
|
|
71
|
+
throw new ArtifactError("conflict", "Artifact changed since it was opened; reload before saving");
|
|
72
|
+
}
|
|
73
|
+
return artifact;
|
|
74
|
+
};
|
|
75
|
+
return {
|
|
76
|
+
archive: async (ownerId, artifactId) => {
|
|
77
|
+
const current = await get(ownerId, artifactId);
|
|
78
|
+
requireCapability(current, "archive");
|
|
79
|
+
return saveRevision({
|
|
80
|
+
...current,
|
|
81
|
+
revision: current.revision + 1,
|
|
82
|
+
status: "archived",
|
|
83
|
+
updatedAt: now()
|
|
84
|
+
}, current.revision);
|
|
85
|
+
},
|
|
86
|
+
create: async (ownerId, input) => {
|
|
87
|
+
const definition = options.registry.definitions[input.kind];
|
|
88
|
+
if (!definition) {
|
|
89
|
+
throw new ArtifactError("unknown_kind", `Unknown artifact kind: ${input.kind}`);
|
|
90
|
+
}
|
|
91
|
+
const content = options.registry.parse(input.kind, input.content);
|
|
92
|
+
const timestamp = now();
|
|
93
|
+
const artifact = {
|
|
94
|
+
capabilities: definition.capabilities ?? ["archive", "edit", "preview"],
|
|
95
|
+
content,
|
|
96
|
+
createdAt: timestamp,
|
|
97
|
+
createdBy: input.createdBy,
|
|
98
|
+
id: idFactory(),
|
|
99
|
+
kind: input.kind,
|
|
100
|
+
metadata: input.metadata ?? {},
|
|
101
|
+
ownerId,
|
|
102
|
+
provenance: input.provenance,
|
|
103
|
+
revision: 1,
|
|
104
|
+
schemaVersion: definition.schemaVersion ?? 1,
|
|
105
|
+
status: "draft",
|
|
106
|
+
title: input.title.trim(),
|
|
107
|
+
updatedAt: timestamp
|
|
108
|
+
};
|
|
109
|
+
await options.store.create(artifact);
|
|
110
|
+
return artifact;
|
|
111
|
+
},
|
|
112
|
+
get,
|
|
113
|
+
list: (ownerId, query) => options.store.list(ownerId, query),
|
|
114
|
+
publish: async (ownerId, artifactId) => {
|
|
115
|
+
const current = await get(ownerId, artifactId);
|
|
116
|
+
requireCapability(current, "publish");
|
|
117
|
+
if (!options.publisher) {
|
|
118
|
+
throw new ArtifactError("publisher_unavailable", "No artifact publisher is configured");
|
|
119
|
+
}
|
|
120
|
+
const result = await options.publisher.publish(current, {
|
|
121
|
+
idempotencyKey: `artifact:${current.id}:publish:${current.revision + 1}`
|
|
122
|
+
});
|
|
123
|
+
const publishedAt = now();
|
|
124
|
+
const publication = {
|
|
125
|
+
id: result.id,
|
|
126
|
+
publishedAt,
|
|
127
|
+
url: result.url
|
|
128
|
+
};
|
|
129
|
+
return saveRevision({
|
|
130
|
+
...current,
|
|
131
|
+
publication,
|
|
132
|
+
revision: current.revision + 1,
|
|
133
|
+
status: "published",
|
|
134
|
+
updatedAt: publishedAt
|
|
135
|
+
}, current.revision);
|
|
136
|
+
},
|
|
137
|
+
unpublish: async (ownerId, artifactId) => {
|
|
138
|
+
const current = await get(ownerId, artifactId);
|
|
139
|
+
requireCapability(current, "publish");
|
|
140
|
+
if (!options.publisher) {
|
|
141
|
+
throw new ArtifactError("publisher_unavailable", "No artifact publisher is configured");
|
|
142
|
+
}
|
|
143
|
+
await options.publisher.unpublish(current, {
|
|
144
|
+
idempotencyKey: `artifact:${current.id}:unpublish:${current.revision + 1}`
|
|
145
|
+
});
|
|
146
|
+
return saveRevision({
|
|
147
|
+
...current,
|
|
148
|
+
publication: undefined,
|
|
149
|
+
revision: current.revision + 1,
|
|
150
|
+
status: "draft",
|
|
151
|
+
updatedAt: now()
|
|
152
|
+
}, current.revision);
|
|
153
|
+
},
|
|
154
|
+
update: async (ownerId, artifactId, input) => {
|
|
155
|
+
const current = await get(ownerId, artifactId);
|
|
156
|
+
requireCapability(current, "edit");
|
|
157
|
+
const expectedRevision = input.expectedRevision ?? current.revision;
|
|
158
|
+
const content = input.content === undefined ? current.content : options.registry.parse(current.kind, input.content);
|
|
159
|
+
return saveRevision({
|
|
160
|
+
...current,
|
|
161
|
+
content,
|
|
162
|
+
metadata: input.metadata ?? current.metadata,
|
|
163
|
+
revision: current.revision + 1,
|
|
164
|
+
title: input.title?.trim() || current.title,
|
|
165
|
+
updatedAt: now()
|
|
166
|
+
}, expectedRevision);
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
};
|
|
170
|
+
// src/store.ts
|
|
171
|
+
var clone = (value) => structuredClone(value);
|
|
172
|
+
var createMemoryArtifactStore = (initial = []) => {
|
|
173
|
+
const records = new Map(initial.map((record) => [record.id, clone(record)]));
|
|
174
|
+
return {
|
|
175
|
+
create: async (record) => {
|
|
176
|
+
if (records.has(record.id))
|
|
177
|
+
throw new Error(`Duplicate artifact id: ${record.id}`);
|
|
178
|
+
records.set(record.id, clone(record));
|
|
179
|
+
},
|
|
180
|
+
get: async (ownerId, artifactId) => {
|
|
181
|
+
const record = records.get(artifactId);
|
|
182
|
+
return record?.ownerId === ownerId ? clone(record) : null;
|
|
183
|
+
},
|
|
184
|
+
list: async (ownerId, query = {}) => [...records.values()].filter((record) => record.ownerId === ownerId && (!query.kind || record.kind === query.kind) && (!query.status || record.status === query.status)).sort((left, right) => right.updatedAt.localeCompare(left.updatedAt)).slice(0, query.limit ?? Number.POSITIVE_INFINITY).map(clone),
|
|
185
|
+
save: async (record, expectedRevision) => {
|
|
186
|
+
const current = records.get(record.id);
|
|
187
|
+
if (!current || current.ownerId !== record.ownerId || current.revision !== expectedRevision) {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
records.set(record.id, clone(record));
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
};
|
|
195
|
+
// src/tools.ts
|
|
196
|
+
import { Type } from "@sinclair/typebox";
|
|
197
|
+
var record = (input) => input && typeof input === "object" && !Array.isArray(input) ? input : {};
|
|
198
|
+
var stringValue = (input, key) => typeof input[key] === "string" ? input[key] : undefined;
|
|
199
|
+
var createArtifactTools = (options) => ({
|
|
200
|
+
artifact_create: {
|
|
201
|
+
description: "Create a private, typed artifact. The kind must be registered by the host; publishing is a separate explicit action.",
|
|
202
|
+
handler: async (raw) => {
|
|
203
|
+
const input = record(raw);
|
|
204
|
+
const kind = stringValue(input, "kind");
|
|
205
|
+
const title = stringValue(input, "title");
|
|
206
|
+
if (!kind || !title || input.content === undefined) {
|
|
207
|
+
return "Provide kind, title, and content.";
|
|
208
|
+
}
|
|
209
|
+
const artifact = await options.service.create(options.ownerId, {
|
|
210
|
+
content: input.content,
|
|
211
|
+
createdBy: options.createdBy,
|
|
212
|
+
kind,
|
|
213
|
+
title
|
|
214
|
+
});
|
|
215
|
+
return JSON.stringify(artifact);
|
|
216
|
+
},
|
|
217
|
+
input: Type.Object({
|
|
218
|
+
content: Type.Unknown(),
|
|
219
|
+
kind: Type.String({ minLength: 1 }),
|
|
220
|
+
title: Type.String({ minLength: 1 })
|
|
221
|
+
})
|
|
222
|
+
},
|
|
223
|
+
artifact_get: {
|
|
224
|
+
annotations: { readOnlyHint: true },
|
|
225
|
+
description: "Open one artifact owned by the current user.",
|
|
226
|
+
handler: async (raw) => {
|
|
227
|
+
const artifactId = stringValue(record(raw), "artifactId");
|
|
228
|
+
if (!artifactId)
|
|
229
|
+
return "Provide artifactId.";
|
|
230
|
+
return JSON.stringify(await options.service.get(options.ownerId, artifactId));
|
|
231
|
+
},
|
|
232
|
+
input: Type.Object({ artifactId: Type.String({ minLength: 1 }) })
|
|
233
|
+
},
|
|
234
|
+
artifact_list: {
|
|
235
|
+
annotations: { readOnlyHint: true },
|
|
236
|
+
description: "List artifacts owned by the current user.",
|
|
237
|
+
handler: async (raw) => {
|
|
238
|
+
const input = record(raw);
|
|
239
|
+
const status = stringValue(input, "status");
|
|
240
|
+
return JSON.stringify(await options.service.list(options.ownerId, {
|
|
241
|
+
kind: stringValue(input, "kind"),
|
|
242
|
+
status: ARTIFACT_STATUSES.find((candidate) => candidate === status)
|
|
243
|
+
}));
|
|
244
|
+
},
|
|
245
|
+
input: Type.Object({
|
|
246
|
+
kind: Type.Optional(Type.String()),
|
|
247
|
+
status: Type.Optional(Type.Union(ARTIFACT_STATUSES.map((status) => Type.Literal(status))))
|
|
248
|
+
})
|
|
249
|
+
},
|
|
250
|
+
artifact_publish: {
|
|
251
|
+
description: "Publish or unpublish an artifact. Hosts should expose this tool only when the user explicitly controls public access.",
|
|
252
|
+
handler: async (raw) => {
|
|
253
|
+
const input = record(raw);
|
|
254
|
+
const artifactId = stringValue(input, "artifactId");
|
|
255
|
+
if (!artifactId || typeof input.published !== "boolean") {
|
|
256
|
+
return "Provide artifactId and published.";
|
|
257
|
+
}
|
|
258
|
+
const artifact = input.published ? await options.service.publish(options.ownerId, artifactId) : await options.service.unpublish(options.ownerId, artifactId);
|
|
259
|
+
return JSON.stringify(artifact);
|
|
260
|
+
},
|
|
261
|
+
input: Type.Object({
|
|
262
|
+
artifactId: Type.String({ minLength: 1 }),
|
|
263
|
+
published: Type.Boolean()
|
|
264
|
+
})
|
|
265
|
+
},
|
|
266
|
+
artifact_update: {
|
|
267
|
+
description: "Update an artifact's title or structured content with optional optimistic revision protection.",
|
|
268
|
+
handler: async (raw) => {
|
|
269
|
+
const input = record(raw);
|
|
270
|
+
const artifactId = stringValue(input, "artifactId");
|
|
271
|
+
if (!artifactId)
|
|
272
|
+
return "Provide artifactId.";
|
|
273
|
+
const artifact = await options.service.update(options.ownerId, artifactId, {
|
|
274
|
+
content: input.content,
|
|
275
|
+
expectedRevision: typeof input.expectedRevision === "number" ? input.expectedRevision : undefined,
|
|
276
|
+
title: stringValue(input, "title")
|
|
277
|
+
});
|
|
278
|
+
return JSON.stringify(artifact);
|
|
279
|
+
},
|
|
280
|
+
input: Type.Object({
|
|
281
|
+
artifactId: Type.String({ minLength: 1 }),
|
|
282
|
+
content: Type.Optional(Type.Unknown()),
|
|
283
|
+
expectedRevision: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
284
|
+
title: Type.Optional(Type.String({ minLength: 1 }))
|
|
285
|
+
})
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
export {
|
|
289
|
+
defineArtifactRegistry,
|
|
290
|
+
createMemoryArtifactStore,
|
|
291
|
+
createArtifactTools,
|
|
292
|
+
createArtifactService,
|
|
293
|
+
createArtifactRendererRegistry,
|
|
294
|
+
ArtifactError,
|
|
295
|
+
ARTIFACT_STATUSES
|
|
296
|
+
};
|