1const CODEX_ARTIFACT_TEMPLATE_KINDS = [
2 "document",
3 "presentation",
4 "spreadsheet",
5 "site",
6 "google-docs",
7 "google-slides",
8 "google-sheets",
9 "image",
10 "email",
11 "slack",
12] as const;
13
14export type CodexArtifactTemplateKind = (typeof CODEX_ARTIFACT_TEMPLATE_KINDS)[number];
15
16const CODEX_ARTIFACT_TEMPLATE_GALLERY_KINDS = ["imagegen", "product-design"] as const;
17
18export type CodexArtifactTemplateGalleryKind =
19 (typeof CODEX_ARTIFACT_TEMPLATE_GALLERY_KINDS)[number];
20
21export interface CodexArtifactTemplate {
22 readonly artifactKind: CodexArtifactTemplateKind;
23 readonly displayName: string;
24 readonly galleryKind?: CodexArtifactTemplateGalleryKind;
25 readonly skillDirectory: string;
26 readonly skillName: string;
27}
28
29const CODEX_ARTIFACT_TEMPLATE_LABEL_BY_KIND = {
30 document: "Document template",
31 presentation: "Presentation template",
32 spreadsheet: "Spreadsheet template",
33 site: "Site template",
34 "google-docs": "Google Doc template",
35 "google-slides": "Google Slides template",
36 "google-sheets": "Google Sheet template",
37 image: "Image template",
38 email: "Email template",
39 slack: "Slack template",
40} satisfies Record<CodexArtifactTemplateKind, string>;
41
42export type CodexArtifactTemplateAttributes = Readonly<Record<string, string | null | undefined>>;
43
44const WINDOWS_DRIVE_PATH_REGEX = /^[A-Za-z]:[\\/]/;
45const WINDOWS_UNC_PATH_REGEX = /^(?:\\\\[^\\]+\\[^\\]+|\/\/[^/]+\/[^/]+)/;
46
47function isCodexArtifactTemplateKind(value: unknown): value is CodexArtifactTemplateKind {
48 return CODEX_ARTIFACT_TEMPLATE_KINDS.some((kind) => kind === value);
49}
50
51function isCodexArtifactTemplateGalleryKind(
52 value: unknown,
53): value is CodexArtifactTemplateGalleryKind {
54 return CODEX_ARTIFACT_TEMPLATE_GALLERY_KINDS.some((kind) => kind === value);
55}
56
57function isAbsoluteSkillDirectory(value: string): boolean {
58 return (
59 (value.startsWith("/") && !value.startsWith("//")) ||
60 WINDOWS_DRIVE_PATH_REGEX.test(value) ||
61 WINDOWS_UNC_PATH_REGEX.test(value)
62 );
63}
64
65/** Mirrors the Codex result-card schema so malformed directives remain literal Markdown. */
66export function resolveCodexArtifactTemplate(
67 attributes: CodexArtifactTemplateAttributes | null | undefined,
68): CodexArtifactTemplate | null {
69 const artifactKind = attributes?.artifact_kind;
70 const displayNameValue = attributes?.display_name;
71 const displayName = typeof displayNameValue === "string" ? displayNameValue.trim() : undefined;
72 const galleryKind = attributes?.gallery_kind;
73 const skillDirectory = attributes?.skill_directory;
74 const skillName = attributes?.skill_name;
75
76 if (
77 !isCodexArtifactTemplateKind(artifactKind) ||
78 !displayName ||
79 typeof skillDirectory !== "string" ||
80 !isAbsoluteSkillDirectory(skillDirectory) ||
81 typeof skillName !== "string" ||
82 !skillName.startsWith("artifact-template-") ||
83 (galleryKind !== undefined && !isCodexArtifactTemplateGalleryKind(galleryKind))
84 ) {
85 return null;
86 }
87
88 return {
89 artifactKind,
90 displayName,
91 ...(galleryKind === undefined ? {} : { galleryKind }),
92 skillDirectory,
93 skillName,
94 };
95}
96
97const USE_PROMPT_BY_KIND: Record<CodexArtifactTemplateKind, (skill: string) => string> = {
98 document: (skill) => `Create a document using this ${skill} about…`,
99 presentation: (skill) => `Create a presentation using the ${skill} template about…`,
100 spreadsheet: (skill) => `Create a spreadsheet using this ${skill} about…`,
101 site: (skill) => `Create a Site using this ${skill} about…`,
102 "google-docs": (skill) => `Create a Google Doc using this ${skill} about…`,
103 "google-slides": (skill) => `Create a Google Slides presentation using this ${skill} about…`,
104 "google-sheets": (skill) => `Create a Google Sheet using this ${skill} about…`,
105 image: (skill) => `Create an image using this ${skill} of…`,
106 email: (skill) => `Draft an email using this ${skill} about…`,
107 slack: (skill) => `Draft a Slack message using this ${skill} about…`,
108};
109
110export function codexArtifactTemplateUsePrompt(template: CodexArtifactTemplate): string {
111 return USE_PROMPT_BY_KIND[template.artifactKind](`$${template.skillName}`);
112}
113
114export function codexArtifactTemplatePresentationLabel(kind: CodexArtifactTemplateKind): string {
115 return CODEX_ARTIFACT_TEMPLATE_LABEL_BY_KIND[kind];
116}
117
118export function appendCodexArtifactTemplateUsePrompt(
119 draft: string,
120 template: CodexArtifactTemplate,
121): string {
122 const prompt = codexArtifactTemplateUsePrompt(template);
123 const trimmedDraft = draft.trimEnd();
124 const promptStart = trimmedDraft.length - prompt.length;
125 const alreadyEndsWithPrompt =
126 promptStart >= 0 &&
127 trimmedDraft.slice(promptStart) === prompt &&
128 (promptStart === 0 || /\s/.test(trimmedDraft[promptStart - 1] ?? ""));
129
130 if (alreadyEndsWithPrompt) {
131 return draft;
132 }
133
134 const needsLeadingSpace = draft.length > 0 && !/\s$/.test(draft);
135 return `${draft}${needsLeadingSpace ? " " : ""}${prompt}`;
136}