| 1 | const 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 | |
| 14 | export type CodexArtifactTemplateKind = (typeof CODEX_ARTIFACT_TEMPLATE_KINDS)[number]; |
| 15 | |
| 16 | const CODEX_ARTIFACT_TEMPLATE_GALLERY_KINDS = ["imagegen", "product-design"] as const; |
| 17 | |
| 18 | export type CodexArtifactTemplateGalleryKind = |
| 19 | (typeof CODEX_ARTIFACT_TEMPLATE_GALLERY_KINDS)[number]; |
| 20 | |
| 21 | export interface CodexArtifactTemplate { |
| 22 | readonly artifactKind: CodexArtifactTemplateKind; |
| 23 | readonly displayName: string; |
| 24 | readonly galleryKind?: CodexArtifactTemplateGalleryKind; |
| 25 | readonly skillDirectory: string; |
| 26 | readonly skillName: string; |
| 27 | } |
| 28 | |
| 29 | const 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 | |
| 42 | export type CodexArtifactTemplateAttributes = Readonly<Record<string, string | null | undefined>>; |
| 43 | |
| 44 | const WINDOWS_DRIVE_PATH_REGEX = /^[A-Za-z]:[\\/]/; |
| 45 | const WINDOWS_UNC_PATH_REGEX = /^(?:\\\\[^\\]+\\[^\\]+|\/\/[^/]+\/[^/]+)/; |
| 46 | |
| 47 | function isCodexArtifactTemplateKind(value: unknown): value is CodexArtifactTemplateKind { |
| 48 | return CODEX_ARTIFACT_TEMPLATE_KINDS.some((kind) => kind === value); |
| 49 | } |
| 50 | |
| 51 | function isCodexArtifactTemplateGalleryKind( |
| 52 | value: unknown, |
| 53 | ): value is CodexArtifactTemplateGalleryKind { |
| 54 | return CODEX_ARTIFACT_TEMPLATE_GALLERY_KINDS.some((kind) => kind === value); |
| 55 | } |
| 56 | |
| 57 | function 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. */ |
| 66 | export 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 | |
| 97 | const 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 | |
| 110 | export function codexArtifactTemplateUsePrompt(template: CodexArtifactTemplate): string { |
| 111 | return USE_PROMPT_BY_KIND[template.artifactKind](`$${template.skillName}`); |
| 112 | } |
| 113 | |
| 114 | export function codexArtifactTemplatePresentationLabel(kind: CodexArtifactTemplateKind): string { |
| 115 | return CODEX_ARTIFACT_TEMPLATE_LABEL_BY_KIND[kind]; |
| 116 | } |
| 117 | |
| 118 | export 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 | } |