artifacty 0.1.2 → 0.2.0
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/AGENTS.md +2 -2
- package/README.md +17 -8
- package/docs/artifact-schema-v1.md +27 -5
- package/docs/integrations.md +21 -4
- package/docs/sarif-csv-artifact-plan.md +44 -65
- package/package.json +1 -1
- package/src/cli.js +18 -4
- package/src/client/editor.js +76 -4
- package/src/lib/converters.js +429 -44
- package/src/lib/installer.js +58 -4
- package/src/lib/render.js +404 -2
- package/src/lib/storage.js +52 -3
- package/src/mcp-server.js +2 -2
- package/src/server.js +41 -2
package/src/lib/converters.js
CHANGED
|
@@ -2,7 +2,8 @@ import path from "node:path";
|
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
import { ARTIFACT_TYPES, contentTypeForFormat, normalizeFormat } from "./storage.js";
|
|
4
4
|
|
|
5
|
-
const
|
|
5
|
+
const CONTINUATION_AGENTS = new Set(["codex", "copilot", "cursor"]);
|
|
6
|
+
const KNOWN_AGENTS = new Set(["auto", "artifacty", "claude", "codex", "copilot", "cursor", "gemini", "generic"]);
|
|
6
7
|
|
|
7
8
|
export function convertAgentArtifact(input = {}) {
|
|
8
9
|
const originalAgent = normalizeAgent(input.agent || input.sourceAgent || input.source_agent || "auto");
|
|
@@ -12,12 +13,21 @@ export function convertAgentArtifact(input = {}) {
|
|
|
12
13
|
const sourcePath = optionalString(input.sourcePath || input.path || input.file);
|
|
13
14
|
const fileName = optionalString(input.fileName || input.filename || (sourcePath ? path.basename(sourcePath) : ""));
|
|
14
15
|
const explicitContentType = optionalString(input.contentType || input.mimeType || input.mime_type);
|
|
15
|
-
|
|
16
|
+
let content = String(decoded.content ?? input.content ?? payload ?? "");
|
|
16
17
|
const format = normalizeFormat(
|
|
17
18
|
input.format ||
|
|
18
19
|
decoded.format ||
|
|
19
20
|
detectFormat({ content, contentType: explicitContentType || decoded.contentType, fileName })
|
|
20
21
|
);
|
|
22
|
+
const media = normalizeMediaContent({
|
|
23
|
+
content,
|
|
24
|
+
format,
|
|
25
|
+
contentType: explicitContentType || decoded.contentType,
|
|
26
|
+
fileName
|
|
27
|
+
});
|
|
28
|
+
if (media) {
|
|
29
|
+
content = media.content;
|
|
30
|
+
}
|
|
21
31
|
const sourceAgent = optionalString(input.sourceAgent || input.source_agent || decoded.sourceAgent) ||
|
|
22
32
|
(originalAgent === "auto" ? detectAgent(parsed, fileName) : originalAgent);
|
|
23
33
|
|
|
@@ -26,7 +36,7 @@ export function convertAgentArtifact(input = {}) {
|
|
|
26
36
|
optionalString(decoded.title) ||
|
|
27
37
|
inferTitle({ content, format, fileName, sourceAgent });
|
|
28
38
|
|
|
29
|
-
const contentType = explicitContentType || decoded.contentType || contentTypeForFormat(format);
|
|
39
|
+
const contentType = explicitContentType || decoded.contentType || media?.mimeType || contentTypeForFormat(format);
|
|
30
40
|
const artifactType = normalizeArtifactType(
|
|
31
41
|
input.artifactType ||
|
|
32
42
|
input.artifact_type ||
|
|
@@ -52,6 +62,7 @@ export function convertAgentArtifact(input = {}) {
|
|
|
52
62
|
metadata: {
|
|
53
63
|
...normalizeMetadata(decoded.metadata),
|
|
54
64
|
...normalizeMetadata(input.metadata),
|
|
65
|
+
...mediaMetadata(media),
|
|
55
66
|
artifactyImport: {
|
|
56
67
|
converter: "agent-artifact-v1",
|
|
57
68
|
originalAgent,
|
|
@@ -71,9 +82,21 @@ export function detectFormat({ content = "", contentType = "", fileName = "" } =
|
|
|
71
82
|
if (type.includes("vnd.ant.code") || type.includes("source-code")) {
|
|
72
83
|
return "code";
|
|
73
84
|
}
|
|
85
|
+
if (type.includes("sarif")) {
|
|
86
|
+
return "sarif";
|
|
87
|
+
}
|
|
88
|
+
if (type.includes("csv")) {
|
|
89
|
+
return "csv";
|
|
90
|
+
}
|
|
74
91
|
if (type.includes("svg")) {
|
|
75
92
|
return "svg";
|
|
76
93
|
}
|
|
94
|
+
if (isSupportedImageMime(type)) {
|
|
95
|
+
return "image";
|
|
96
|
+
}
|
|
97
|
+
if (isSupportedVideoMime(type)) {
|
|
98
|
+
return "video";
|
|
99
|
+
}
|
|
77
100
|
if (type.includes("vnd.ant.mermaid") || type.includes("mermaid")) {
|
|
78
101
|
return "mermaid";
|
|
79
102
|
}
|
|
@@ -93,7 +116,21 @@ export function detectFormat({ content = "", contentType = "", fileName = "" } =
|
|
|
93
116
|
return "text";
|
|
94
117
|
}
|
|
95
118
|
|
|
96
|
-
const
|
|
119
|
+
const lowerName = optionalString(fileName).toLowerCase();
|
|
120
|
+
if (lowerName.endsWith(".sarif") || lowerName.endsWith(".sarif.json")) {
|
|
121
|
+
return "sarif";
|
|
122
|
+
}
|
|
123
|
+
if (lowerName.endsWith(".csv")) {
|
|
124
|
+
return "csv";
|
|
125
|
+
}
|
|
126
|
+
if (isImageFileName(lowerName)) {
|
|
127
|
+
return "image";
|
|
128
|
+
}
|
|
129
|
+
if (isVideoFileName(lowerName)) {
|
|
130
|
+
return "video";
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const extension = path.extname(lowerName);
|
|
97
134
|
if (extension === ".html" || extension === ".htm") {
|
|
98
135
|
return "html";
|
|
99
136
|
}
|
|
@@ -129,12 +166,24 @@ export function detectFormat({ content = "", contentType = "", fileName = "" } =
|
|
|
129
166
|
if (looksLikeReact(trimmed)) {
|
|
130
167
|
return "react";
|
|
131
168
|
}
|
|
169
|
+
if (looksLikeMediaDataUrl(trimmed, "image")) {
|
|
170
|
+
return "image";
|
|
171
|
+
}
|
|
172
|
+
if (looksLikeMediaDataUrl(trimmed, "video")) {
|
|
173
|
+
return "video";
|
|
174
|
+
}
|
|
132
175
|
if (/^<!doctype html/i.test(trimmed) || /^<html[\s>]/i.test(trimmed)) {
|
|
133
176
|
return "html";
|
|
134
177
|
}
|
|
178
|
+
if (looksLikeSarif(trimmed)) {
|
|
179
|
+
return "sarif";
|
|
180
|
+
}
|
|
135
181
|
if (looksLikeJson(trimmed)) {
|
|
136
182
|
return "json";
|
|
137
183
|
}
|
|
184
|
+
if (looksLikeCsv(trimmed)) {
|
|
185
|
+
return "csv";
|
|
186
|
+
}
|
|
138
187
|
if (/^#{1,3}\s+\S/m.test(trimmed) || /^[-*]\s+\S/m.test(trimmed)) {
|
|
139
188
|
return "markdown";
|
|
140
189
|
}
|
|
@@ -160,13 +209,28 @@ function decodePayload(parsed, agent) {
|
|
|
160
209
|
function decodeObjectPayload(value, agent) {
|
|
161
210
|
const artifactObject = value.artifact && typeof value.artifact === "object" ? value.artifact : value;
|
|
162
211
|
|
|
163
|
-
if (Array.isArray(artifactObject.files) || artifactObject.bundle) {
|
|
212
|
+
if (Array.isArray(artifactObject.files) || Array.isArray(artifactObject.assets) || Array.isArray(artifactObject.media) || Array.isArray(artifactObject.attachments) || artifactObject.bundle) {
|
|
164
213
|
return decodeBundlePayload(artifactObject, agent);
|
|
165
214
|
}
|
|
166
215
|
|
|
167
|
-
const
|
|
168
|
-
if (
|
|
169
|
-
return
|
|
216
|
+
const continuation = decodeContinuationPayload(artifactObject, agent);
|
|
217
|
+
if (continuation) {
|
|
218
|
+
return continuation;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (isSarifObject(artifactObject)) {
|
|
222
|
+
return {
|
|
223
|
+
content: JSON.stringify(artifactObject, null, 2),
|
|
224
|
+
format: "sarif",
|
|
225
|
+
contentType: "application/sarif+json; charset=utf-8",
|
|
226
|
+
title: artifactObject.title || artifactObject.name || "SARIF report",
|
|
227
|
+
sourceAgent: artifactObject.sourceAgent || artifactObject.source_agent || artifactObject.agent || (agent === "auto" ? undefined : agent),
|
|
228
|
+
artifactType: artifactObject.artifactType || artifactObject.artifact_type || "analysis-report",
|
|
229
|
+
tags: artifactObject.tags,
|
|
230
|
+
metadata: {
|
|
231
|
+
originalPayloadShape: "sarif"
|
|
232
|
+
}
|
|
233
|
+
};
|
|
170
234
|
}
|
|
171
235
|
|
|
172
236
|
if (typeof artifactObject.content === "string" && !Array.isArray(artifactObject.content)) {
|
|
@@ -208,6 +272,22 @@ function decodeObjectPayload(value, agent) {
|
|
|
208
272
|
return objectContent(artifactObject, "react", artifactObject.react || artifactObject.jsx, "react");
|
|
209
273
|
}
|
|
210
274
|
|
|
275
|
+
if (artifactObject.image !== undefined || artifactObject.screenshot !== undefined) {
|
|
276
|
+
return mediaObjectContent(artifactObject, "image", artifactObject.image ?? artifactObject.screenshot);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (artifactObject.video !== undefined || artifactObject.recording !== undefined || artifactObject.demo !== undefined) {
|
|
280
|
+
return mediaObjectContent(artifactObject, "video", artifactObject.video ?? artifactObject.recording ?? artifactObject.demo);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (typeof artifactObject.sarif === "string") {
|
|
284
|
+
return objectContent(artifactObject, "sarif", artifactObject.sarif, "sarif");
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (typeof artifactObject.csv === "string") {
|
|
288
|
+
return objectContent(artifactObject, "csv", artifactObject.csv, "csv");
|
|
289
|
+
}
|
|
290
|
+
|
|
211
291
|
if (typeof artifactObject.html === "string") {
|
|
212
292
|
return objectContent(artifactObject, "html", artifactObject.html, "html");
|
|
213
293
|
}
|
|
@@ -312,10 +392,38 @@ function objectContent(object, shape, content, format) {
|
|
|
312
392
|
};
|
|
313
393
|
}
|
|
314
394
|
|
|
395
|
+
function mediaObjectContent(object, format, mediaValue) {
|
|
396
|
+
const sourceContentType = object.contentType || object.content_type || object.mimeType || object.mime_type || object.type;
|
|
397
|
+
const content = typeof mediaValue === "string"
|
|
398
|
+
? mediaValue
|
|
399
|
+
: optionalString(mediaValue?.data || mediaValue?.base64 || mediaValue?.content || mediaValue?.url);
|
|
400
|
+
const contentType = optionalString(mediaValue?.mimeType || mediaValue?.mime_type || sourceContentType);
|
|
401
|
+
return {
|
|
402
|
+
content,
|
|
403
|
+
format,
|
|
404
|
+
contentType,
|
|
405
|
+
title: object.title || object.name,
|
|
406
|
+
sourceAgent: object.sourceAgent || object.source_agent || object.agent,
|
|
407
|
+
artifactType: object.artifactType || object.artifact_type || "asset",
|
|
408
|
+
tags: object.tags,
|
|
409
|
+
metadata: {
|
|
410
|
+
originalPayloadShape: format,
|
|
411
|
+
originalId: object.id,
|
|
412
|
+
originalContentType: sourceContentType || contentType
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
|
|
315
417
|
function decodeBundlePayload(object, agent) {
|
|
316
418
|
const bundle = object.bundle && typeof object.bundle === "object" ? object.bundle : object;
|
|
317
419
|
const files = Array.isArray(bundle.files) ? bundle.files : [];
|
|
318
|
-
const
|
|
420
|
+
const assets = normalizeBundleAssets(firstPresent(bundle.assets, bundle.media, bundle.attachments));
|
|
421
|
+
const parts = normalizeBundleParts(bundle.parts, assets, optionalString(bundle.summary || object.summary || bundle.text));
|
|
422
|
+
const sourceAgent = object.sourceAgent || object.source_agent || object.agent || agent;
|
|
423
|
+
const continuationAgent = detectContinuationAgent({ agent: sourceAgent }, "auto");
|
|
424
|
+
const continuationMetadata = continuationAgent
|
|
425
|
+
? collectContinuationMetadata(continuationAgent, { agent: sourceAgent }, bundle, object)
|
|
426
|
+
: {};
|
|
319
427
|
const normalizedFiles = files.map((file, index) => {
|
|
320
428
|
const content = typeof file.content === "string" ? file.content : "";
|
|
321
429
|
return {
|
|
@@ -335,8 +443,11 @@ function decodeBundlePayload(object, agent) {
|
|
|
335
443
|
schemaVersion: 1,
|
|
336
444
|
artifactType: "bundle",
|
|
337
445
|
title: object.title || bundle.title || "Artifact bundle",
|
|
446
|
+
text: optionalString(bundle.text || bundle.summary || object.summary),
|
|
447
|
+
parts,
|
|
448
|
+
assets,
|
|
338
449
|
files: normalizedFiles,
|
|
339
|
-
...bundleDocumentMetadata(
|
|
450
|
+
...bundleDocumentMetadata(continuationMetadata)
|
|
340
451
|
}, null, 2),
|
|
341
452
|
format: "json",
|
|
342
453
|
contentType: "application/vnd.artifacty.bundle+json; charset=utf-8",
|
|
@@ -347,37 +458,83 @@ function decodeBundlePayload(object, agent) {
|
|
|
347
458
|
metadata: {
|
|
348
459
|
originalPayloadShape: "artifact-bundle",
|
|
349
460
|
fileCount: normalizedFiles.length,
|
|
461
|
+
assetCount: assets.length,
|
|
350
462
|
bundlePolicy: "text-files-stored-inline-in-bundle-json",
|
|
351
|
-
|
|
463
|
+
assetPolicy: assets.length > 0 ? "base64-assets-stored-inline-in-bundle-json" : undefined,
|
|
464
|
+
...continuationMetadata
|
|
352
465
|
}
|
|
353
466
|
};
|
|
354
467
|
}
|
|
355
468
|
|
|
356
|
-
function
|
|
357
|
-
if (!
|
|
469
|
+
function normalizeBundleAssets(value) {
|
|
470
|
+
if (!Array.isArray(value)) {
|
|
471
|
+
return [];
|
|
472
|
+
}
|
|
473
|
+
return value.map((asset, index) => {
|
|
474
|
+
if (!asset || typeof asset !== "object") {
|
|
475
|
+
return null;
|
|
476
|
+
}
|
|
477
|
+
const rawData = optionalString(asset.data || asset.base64 || asset.content || asset.url);
|
|
478
|
+
const dataUrl = parseDataUrl(rawData);
|
|
479
|
+
const mimeType = dataUrl?.mimeType ||
|
|
480
|
+
mediaMimeTypeFromContentType(asset.mimeType || asset.mime_type || asset.contentType || asset.content_type || asset.type, "image") ||
|
|
481
|
+
mediaMimeTypeFromContentType(asset.mimeType || asset.mime_type || asset.contentType || asset.content_type || asset.type, "video") ||
|
|
482
|
+
mediaMimeTypeFromFileName(asset.fileName || asset.filename || asset.name || asset.path) ||
|
|
483
|
+
"application/octet-stream";
|
|
484
|
+
const data = dataUrl?.base64 || normalizeBase64(rawData);
|
|
485
|
+
if (!data) {
|
|
486
|
+
return null;
|
|
487
|
+
}
|
|
488
|
+
return pruneEmpty({
|
|
489
|
+
id: optionalString(asset.id) || `asset-${index + 1}`,
|
|
490
|
+
name: optionalString(asset.name || asset.fileName || asset.filename || asset.path),
|
|
491
|
+
role: optionalString(asset.role || asset.kind || asset.type),
|
|
492
|
+
mimeType,
|
|
493
|
+
encoding: "base64",
|
|
494
|
+
data,
|
|
495
|
+
sizeBytes: Buffer.byteLength(data, "base64"),
|
|
496
|
+
sha256: createHash("sha256").update(Buffer.from(data, "base64")).digest("hex"),
|
|
497
|
+
caption: optionalString(asset.caption || asset.alt || asset.description)
|
|
498
|
+
});
|
|
499
|
+
}).filter(Boolean);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function normalizeBundleParts(value, assets, text) {
|
|
503
|
+
if (Array.isArray(value) && value.length > 0) {
|
|
504
|
+
return value;
|
|
505
|
+
}
|
|
506
|
+
return [
|
|
507
|
+
text ? { type: "text", text } : null,
|
|
508
|
+
...assets.map((asset) => ({ type: "asset-ref", assetId: asset.id, mimeType: asset.mimeType }))
|
|
509
|
+
].filter(Boolean);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function decodeContinuationPayload(object, agent) {
|
|
513
|
+
const sourceAgent = detectContinuationAgent(object, agent);
|
|
514
|
+
if (!sourceAgent || hasDirectContentField(object)) {
|
|
358
515
|
return null;
|
|
359
516
|
}
|
|
360
517
|
|
|
361
|
-
const artifactType =
|
|
518
|
+
const artifactType = inferContinuationType(object);
|
|
362
519
|
if (!artifactType) {
|
|
363
520
|
return null;
|
|
364
521
|
}
|
|
365
522
|
|
|
366
|
-
const metadata =
|
|
523
|
+
const metadata = collectContinuationMetadata(sourceAgent, { agent }, object);
|
|
367
524
|
return {
|
|
368
|
-
content:
|
|
525
|
+
content: renderContinuationMarkdown(sourceAgent, object, artifactType, metadata),
|
|
369
526
|
format: "markdown",
|
|
370
527
|
contentType: "text/markdown; charset=utf-8",
|
|
371
|
-
title: object.title || object.name ||
|
|
372
|
-
sourceAgent
|
|
528
|
+
title: object.title || object.name || titleForContinuationType(sourceAgent, artifactType),
|
|
529
|
+
sourceAgent,
|
|
373
530
|
artifactType,
|
|
374
531
|
tags: uniqueStrings([
|
|
375
532
|
artifactType,
|
|
376
|
-
|
|
533
|
+
sourceAgent,
|
|
377
534
|
...normalizeTags(object.tags)
|
|
378
535
|
]),
|
|
379
536
|
metadata: {
|
|
380
|
-
originalPayloadShape:
|
|
537
|
+
originalPayloadShape: `${sourceAgent}-continuation`,
|
|
381
538
|
continuationKind: artifactType,
|
|
382
539
|
...metadata
|
|
383
540
|
}
|
|
@@ -394,10 +551,17 @@ function hasDirectContentField(object) {
|
|
|
394
551
|
typeof object.svg === "string" ||
|
|
395
552
|
typeof object.mermaid === "string" ||
|
|
396
553
|
typeof object.react === "string" ||
|
|
397
|
-
typeof object.jsx === "string"
|
|
554
|
+
typeof object.jsx === "string" ||
|
|
555
|
+
typeof object.sarif === "string" ||
|
|
556
|
+
typeof object.csv === "string" ||
|
|
557
|
+
object.image !== undefined ||
|
|
558
|
+
object.screenshot !== undefined ||
|
|
559
|
+
object.video !== undefined ||
|
|
560
|
+
object.recording !== undefined ||
|
|
561
|
+
object.demo !== undefined;
|
|
398
562
|
}
|
|
399
563
|
|
|
400
|
-
function
|
|
564
|
+
function detectContinuationAgent(object, agent) {
|
|
401
565
|
const explicit = normalizeAgent(
|
|
402
566
|
object.sourceAgent ||
|
|
403
567
|
object.source_agent ||
|
|
@@ -407,10 +571,10 @@ function isCodexPayload(object, agent) {
|
|
|
407
571
|
object.created_by ||
|
|
408
572
|
agent
|
|
409
573
|
);
|
|
410
|
-
return explicit
|
|
574
|
+
return CONTINUATION_AGENTS.has(explicit) ? explicit : "";
|
|
411
575
|
}
|
|
412
576
|
|
|
413
|
-
function
|
|
577
|
+
function inferContinuationType(object) {
|
|
414
578
|
const explicit = optionalString(object.artifactType || object.artifact_type || object.kind || object.category || object.purpose || object.type).toLowerCase();
|
|
415
579
|
if (explicit.includes("review")) {
|
|
416
580
|
return "code-review";
|
|
@@ -449,11 +613,8 @@ function inferCodexContinuationType(object) {
|
|
|
449
613
|
return null;
|
|
450
614
|
}
|
|
451
615
|
|
|
452
|
-
function
|
|
616
|
+
function collectContinuationMetadata(sourceAgent, ...sources) {
|
|
453
617
|
const objects = sources.filter((item) => item && typeof item === "object");
|
|
454
|
-
if (!objects.some((item) => isCodexPayload(item, "auto"))) {
|
|
455
|
-
return {};
|
|
456
|
-
}
|
|
457
618
|
const source = Object.assign({}, ...objects);
|
|
458
619
|
const continuation = pruneEmpty({
|
|
459
620
|
summary: optionalString(source.summary),
|
|
@@ -471,21 +632,36 @@ function collectCodexContinuationMetadata(...sources) {
|
|
|
471
632
|
residualRisk: optionalString(source.residualRisk || source.residual_risk)
|
|
472
633
|
});
|
|
473
634
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
635
|
+
if (Object.keys(continuation).length === 0) {
|
|
636
|
+
return {};
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
const metadata = { continuation };
|
|
640
|
+
metadata[`${sourceAgent}Continuation`] = continuation;
|
|
641
|
+
return metadata;
|
|
477
642
|
}
|
|
478
643
|
|
|
479
644
|
function bundleDocumentMetadata(metadata) {
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
645
|
+
const result = {};
|
|
646
|
+
if (metadata.continuation) {
|
|
647
|
+
result.continuation = metadata.continuation;
|
|
648
|
+
}
|
|
649
|
+
if (metadata.codexContinuation) {
|
|
650
|
+
result.codexContinuation = metadata.codexContinuation;
|
|
651
|
+
}
|
|
652
|
+
if (metadata.copilotContinuation) {
|
|
653
|
+
result.copilotContinuation = metadata.copilotContinuation;
|
|
654
|
+
}
|
|
655
|
+
if (metadata.cursorContinuation) {
|
|
656
|
+
result.cursorContinuation = metadata.cursorContinuation;
|
|
657
|
+
}
|
|
658
|
+
return result;
|
|
483
659
|
}
|
|
484
660
|
|
|
485
|
-
function
|
|
486
|
-
const continuation = metadata.
|
|
487
|
-
const title = object.title || object.name ||
|
|
488
|
-
const lines = [`# ${title}`, "", `Source:
|
|
661
|
+
function renderContinuationMarkdown(sourceAgent, object, artifactType, metadata) {
|
|
662
|
+
const continuation = metadata.continuation || metadata[`${sourceAgent}Continuation`] || {};
|
|
663
|
+
const title = object.title || object.name || titleForContinuationType(sourceAgent, artifactType);
|
|
664
|
+
const lines = [`# ${title}`, "", `Source: ${displayAgentName(sourceAgent)}`, `Artifact type: ${artifactType}`, ""];
|
|
489
665
|
|
|
490
666
|
addTextSection(lines, "Summary", continuation.summary);
|
|
491
667
|
addTextSection(lines, "Goal", continuation.goal);
|
|
@@ -507,17 +683,31 @@ function renderCodexContinuationMarkdown(object, artifactType, metadata) {
|
|
|
507
683
|
return lines.join("\n").replace(/\n{3,}/g, "\n\n").trimEnd();
|
|
508
684
|
}
|
|
509
685
|
|
|
510
|
-
function
|
|
686
|
+
function titleForContinuationType(sourceAgent, artifactType) {
|
|
687
|
+
const displayName = displayAgentName(sourceAgent);
|
|
511
688
|
if (artifactType === "code-review") {
|
|
512
|
-
return
|
|
689
|
+
return `${displayName} Code Review`;
|
|
513
690
|
}
|
|
514
691
|
if (artifactType === "test-report") {
|
|
515
|
-
return
|
|
692
|
+
return `${displayName} Verification Report`;
|
|
516
693
|
}
|
|
517
694
|
if (artifactType === "diff-walkthrough") {
|
|
518
|
-
return
|
|
695
|
+
return `${displayName} Diff Walkthrough`;
|
|
696
|
+
}
|
|
697
|
+
return `${displayName} Handoff`;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
function displayAgentName(sourceAgent) {
|
|
701
|
+
if (sourceAgent === "copilot") {
|
|
702
|
+
return "GitHub Copilot";
|
|
519
703
|
}
|
|
520
|
-
|
|
704
|
+
if (sourceAgent === "cursor") {
|
|
705
|
+
return "Cursor";
|
|
706
|
+
}
|
|
707
|
+
if (sourceAgent === "codex") {
|
|
708
|
+
return "Codex";
|
|
709
|
+
}
|
|
710
|
+
return sourceAgent || "Agent";
|
|
521
711
|
}
|
|
522
712
|
|
|
523
713
|
function addTextSection(lines, title, value) {
|
|
@@ -683,6 +873,12 @@ function detectAgent(parsed, fileName) {
|
|
|
683
873
|
if (lowerName.includes("codex")) {
|
|
684
874
|
return "codex";
|
|
685
875
|
}
|
|
876
|
+
if (lowerName.includes("copilot")) {
|
|
877
|
+
return "copilot";
|
|
878
|
+
}
|
|
879
|
+
if (lowerName.includes("cursor")) {
|
|
880
|
+
return "cursor";
|
|
881
|
+
}
|
|
686
882
|
return "generic";
|
|
687
883
|
}
|
|
688
884
|
|
|
@@ -706,6 +902,18 @@ function safeFormat(value) {
|
|
|
706
902
|
if (normalized === "code" || normalized.includes("vnd.ant.code") || normalized.includes("source-code")) {
|
|
707
903
|
return "code";
|
|
708
904
|
}
|
|
905
|
+
if (normalized === "sarif" || normalized.includes("sarif")) {
|
|
906
|
+
return "sarif";
|
|
907
|
+
}
|
|
908
|
+
if (normalized === "csv" || normalized.includes("csv")) {
|
|
909
|
+
return "csv";
|
|
910
|
+
}
|
|
911
|
+
if (isSupportedImageMime(normalized) || normalized === "image") {
|
|
912
|
+
return "image";
|
|
913
|
+
}
|
|
914
|
+
if (isSupportedVideoMime(normalized) || normalized === "video") {
|
|
915
|
+
return "video";
|
|
916
|
+
}
|
|
709
917
|
if (normalized === "html" || normalized.includes("html")) {
|
|
710
918
|
return "html";
|
|
711
919
|
}
|
|
@@ -859,7 +1067,7 @@ function createBundleDocument({ title, text, assets, parts }) {
|
|
|
859
1067
|
};
|
|
860
1068
|
}
|
|
861
1069
|
|
|
862
|
-
function inferArtifactType({ format, fileName, metadata }) {
|
|
1070
|
+
function inferArtifactType({ format, content, fileName, metadata }) {
|
|
863
1071
|
if (metadata?.originalPayloadShape === "artifact-bundle") {
|
|
864
1072
|
return "bundle";
|
|
865
1073
|
}
|
|
@@ -875,6 +1083,18 @@ function inferArtifactType({ format, fileName, metadata }) {
|
|
|
875
1083
|
if (format === "code") {
|
|
876
1084
|
return "snippet";
|
|
877
1085
|
}
|
|
1086
|
+
if (format === "sarif") {
|
|
1087
|
+
return "analysis-report";
|
|
1088
|
+
}
|
|
1089
|
+
if (format === "csv") {
|
|
1090
|
+
return looksLikeAnalysisCsv(content) ||
|
|
1091
|
+
/findings?|security|review|scan/i.test(optionalString(fileName))
|
|
1092
|
+
? "analysis-report"
|
|
1093
|
+
: "table";
|
|
1094
|
+
}
|
|
1095
|
+
if (format === "image" || format === "video") {
|
|
1096
|
+
return "asset";
|
|
1097
|
+
}
|
|
878
1098
|
const lowerName = optionalString(fileName).toLowerCase();
|
|
879
1099
|
if (lowerName.includes("handoff")) {
|
|
880
1100
|
return "handoff";
|
|
@@ -888,6 +1108,14 @@ function inferArtifactType({ format, fileName, metadata }) {
|
|
|
888
1108
|
return "document";
|
|
889
1109
|
}
|
|
890
1110
|
|
|
1111
|
+
function looksLikeAnalysisCsv(content) {
|
|
1112
|
+
const [header = ""] = optionalString(content).split(/\r?\n/, 1);
|
|
1113
|
+
const normalized = header.toLowerCase();
|
|
1114
|
+
return normalized.includes("severity") &&
|
|
1115
|
+
(normalized.includes("message") || normalized.includes("description")) &&
|
|
1116
|
+
(normalized.includes("file") || normalized.includes("path") || normalized.includes("rule"));
|
|
1117
|
+
}
|
|
1118
|
+
|
|
891
1119
|
function normalizeArtifactType(value) {
|
|
892
1120
|
const normalized = optionalString(value).toLowerCase();
|
|
893
1121
|
if (!normalized) {
|
|
@@ -928,6 +1156,157 @@ function looksLikeJson(value) {
|
|
|
928
1156
|
(trimmed.startsWith("[") && trimmed.endsWith("]"));
|
|
929
1157
|
}
|
|
930
1158
|
|
|
1159
|
+
function looksLikeSarif(value) {
|
|
1160
|
+
if (!looksLikeJson(value)) {
|
|
1161
|
+
return false;
|
|
1162
|
+
}
|
|
1163
|
+
try {
|
|
1164
|
+
return isSarifObject(JSON.parse(optionalString(value)));
|
|
1165
|
+
} catch {
|
|
1166
|
+
return false;
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
function isSarifObject(value) {
|
|
1171
|
+
return value &&
|
|
1172
|
+
typeof value === "object" &&
|
|
1173
|
+
!Array.isArray(value) &&
|
|
1174
|
+
Array.isArray(value.runs) &&
|
|
1175
|
+
(typeof value.version === "string" || optionalString(value.$schema).toLowerCase().includes("sarif"));
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
function looksLikeCsv(value) {
|
|
1179
|
+
const lines = optionalString(value)
|
|
1180
|
+
.split(/\r?\n/)
|
|
1181
|
+
.map((line) => line.trimEnd())
|
|
1182
|
+
.filter(Boolean)
|
|
1183
|
+
.slice(0, 5);
|
|
1184
|
+
if (lines.length < 2 || lines[0].trimStart().startsWith("|")) {
|
|
1185
|
+
return false;
|
|
1186
|
+
}
|
|
1187
|
+
const counts = lines.map(csvFieldCount);
|
|
1188
|
+
return counts[0] > 1 && counts.every((count) => count === counts[0]);
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
function csvFieldCount(line) {
|
|
1192
|
+
let count = 1;
|
|
1193
|
+
let inQuotes = false;
|
|
1194
|
+
for (let index = 0; index < line.length; index += 1) {
|
|
1195
|
+
const char = line[index];
|
|
1196
|
+
if (char === "\"") {
|
|
1197
|
+
if (inQuotes && line[index + 1] === "\"") {
|
|
1198
|
+
index += 1;
|
|
1199
|
+
} else {
|
|
1200
|
+
inQuotes = !inQuotes;
|
|
1201
|
+
}
|
|
1202
|
+
} else if (char === "," && !inQuotes) {
|
|
1203
|
+
count += 1;
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
return count;
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
function looksLikeMediaDataUrl(value, kind) {
|
|
1210
|
+
const match = /^data:([^;,]+);base64,/i.exec(optionalString(value));
|
|
1211
|
+
if (!match) {
|
|
1212
|
+
return false;
|
|
1213
|
+
}
|
|
1214
|
+
const mimeType = match[1].toLowerCase();
|
|
1215
|
+
return kind === "image" ? isSupportedImageMime(mimeType) : isSupportedVideoMime(mimeType);
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
function normalizeMediaContent({ content, format, contentType, fileName }) {
|
|
1219
|
+
if (format !== "image" && format !== "video") {
|
|
1220
|
+
return null;
|
|
1221
|
+
}
|
|
1222
|
+
const dataUrl = parseDataUrl(content);
|
|
1223
|
+
const mimeType = dataUrl?.mimeType ||
|
|
1224
|
+
mediaMimeTypeFromContentType(contentType, format) ||
|
|
1225
|
+
mediaMimeTypeFromFileName(fileName) ||
|
|
1226
|
+
"";
|
|
1227
|
+
const normalized = dataUrl?.base64 || normalizeBase64(content);
|
|
1228
|
+
if (!normalized) {
|
|
1229
|
+
return null;
|
|
1230
|
+
}
|
|
1231
|
+
return {
|
|
1232
|
+
content: normalized,
|
|
1233
|
+
mimeType,
|
|
1234
|
+
encoding: "base64",
|
|
1235
|
+
originalEncoding: dataUrl ? "data-url" : "base64"
|
|
1236
|
+
};
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
function mediaMetadata(media) {
|
|
1240
|
+
if (!media) {
|
|
1241
|
+
return {};
|
|
1242
|
+
}
|
|
1243
|
+
return {
|
|
1244
|
+
mimeType: media.mimeType || undefined,
|
|
1245
|
+
encoding: media.encoding,
|
|
1246
|
+
mediaPolicy: "base64-media-stored-inline; /raw decodes bytes for preview and download",
|
|
1247
|
+
originalEncoding: media.originalEncoding
|
|
1248
|
+
};
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
function parseDataUrl(value) {
|
|
1252
|
+
const match = /^data:([^;,]+);base64,([A-Za-z0-9+/=_-]+)$/i.exec(optionalString(value).replace(/\s+/g, ""));
|
|
1253
|
+
if (!match) {
|
|
1254
|
+
return null;
|
|
1255
|
+
}
|
|
1256
|
+
return {
|
|
1257
|
+
mimeType: match[1].toLowerCase(),
|
|
1258
|
+
base64: normalizeBase64(match[2])
|
|
1259
|
+
};
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
function normalizeBase64(value) {
|
|
1263
|
+
const normalized = optionalString(value).replace(/\s+/g, "").replaceAll("-", "+").replaceAll("_", "/");
|
|
1264
|
+
if (!normalized || !/^[A-Za-z0-9+/]+={0,2}$/.test(normalized)) {
|
|
1265
|
+
return "";
|
|
1266
|
+
}
|
|
1267
|
+
return normalized;
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
function mediaMimeTypeFromContentType(contentType, format) {
|
|
1271
|
+
const normalized = optionalString(contentType).toLowerCase().split(";")[0];
|
|
1272
|
+
if (format === "image" && isSupportedImageMime(normalized)) {
|
|
1273
|
+
return normalized;
|
|
1274
|
+
}
|
|
1275
|
+
if (format === "video" && isSupportedVideoMime(normalized)) {
|
|
1276
|
+
return normalized;
|
|
1277
|
+
}
|
|
1278
|
+
return "";
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
function mediaMimeTypeFromFileName(fileName) {
|
|
1282
|
+
const extension = path.extname(optionalString(fileName).toLowerCase());
|
|
1283
|
+
return {
|
|
1284
|
+
".png": "image/png",
|
|
1285
|
+
".jpg": "image/jpeg",
|
|
1286
|
+
".jpeg": "image/jpeg",
|
|
1287
|
+
".gif": "image/gif",
|
|
1288
|
+
".webp": "image/webp",
|
|
1289
|
+
".mp4": "video/mp4",
|
|
1290
|
+
".webm": "video/webm"
|
|
1291
|
+
}[extension] || "";
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
function isImageFileName(fileName) {
|
|
1295
|
+
return isSupportedImageMime(mediaMimeTypeFromFileName(fileName));
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
function isVideoFileName(fileName) {
|
|
1299
|
+
return isSupportedVideoMime(mediaMimeTypeFromFileName(fileName));
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
function isSupportedImageMime(value) {
|
|
1303
|
+
return new Set(["image/png", "image/jpeg", "image/gif", "image/webp"]).has(optionalString(value).toLowerCase().split(";")[0]);
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
function isSupportedVideoMime(value) {
|
|
1307
|
+
return new Set(["video/mp4", "video/webm"]).has(optionalString(value).toLowerCase().split(";")[0]);
|
|
1308
|
+
}
|
|
1309
|
+
|
|
931
1310
|
function looksLikeMermaid(value) {
|
|
932
1311
|
const firstMeaningfulLine = optionalString(value)
|
|
933
1312
|
.split(/\r?\n/)
|
|
@@ -989,6 +1368,12 @@ function normalizeAgent(value) {
|
|
|
989
1368
|
if (normalized === "gemini-cli" || normalized === "google") {
|
|
990
1369
|
return "gemini";
|
|
991
1370
|
}
|
|
1371
|
+
if (normalized === "github-copilot" || normalized === "copilot-chat" || normalized === "vscode-copilot" || normalized === "vs-code-copilot") {
|
|
1372
|
+
return "copilot";
|
|
1373
|
+
}
|
|
1374
|
+
if (normalized === "cursor-ai") {
|
|
1375
|
+
return "cursor";
|
|
1376
|
+
}
|
|
992
1377
|
if (normalized === "openai" || normalized === "chatgpt") {
|
|
993
1378
|
return "codex";
|
|
994
1379
|
}
|