fix(createblogpost): convert to proper AI SDK tool

- Wrap function with tool() from 'ai' package
- Add jsonSchema() for input validation
- Rename export from createBlogPost to createBlogPostTool
- Update package.json with proper tpmjs.tools format
- Bump version to 0.3.0

Fixes executor error: 'Cannot destructure property title of t'
This commit is contained in:
Ajax Davis 2025-12-12 10:38:46 +10:00
parent 196cbacf62
commit db482855d3
3 changed files with 165 additions and 124 deletions

View file

@ -1,6 +1,6 @@
{
"name": "@tpmjs/createblogpost",
"version": "0.2.0",
"version": "0.3.0",
"description": "A tool for creating structured blog posts with AI-generated content",
"type": "module",
"keywords": ["tpmjs-tool", "blog", "content", "ai", "writing"],
@ -34,63 +34,66 @@
"license": "MIT",
"tpmjs": {
"category": "text-analysis",
"description": "Creates structured blog posts with customizable frontmatter, content sections, and SEO metadata. Supports multiple output formats including Markdown and MDX.",
"parameters": [
"frameworks": ["vercel-ai"],
"tools": [
{
"name": "title",
"type": "string",
"description": "The title of the blog post",
"required": true
},
{
"name": "author",
"type": "string",
"description": "The author of the blog post",
"required": true
},
{
"name": "content",
"type": "string",
"description": "The main content of the blog post",
"required": true
},
{
"name": "tags",
"type": "string[]",
"description": "Array of tags for categorization",
"required": false,
"default": []
},
{
"name": "format",
"type": "'markdown' | 'mdx'",
"description": "Output format for the blog post",
"required": false,
"default": "markdown"
},
{
"name": "excerpt",
"type": "string",
"description": "Short excerpt or summary of the post",
"required": false
"exportName": "createBlogPostTool",
"description": "Creates structured blog posts with customizable frontmatter, content sections, and SEO metadata. Supports multiple output formats including Markdown and MDX.",
"parameters": [
{
"name": "title",
"type": "string",
"description": "The title of the blog post",
"required": true
},
{
"name": "author",
"type": "string",
"description": "The author of the blog post",
"required": true
},
{
"name": "content",
"type": "string",
"description": "The main content of the blog post",
"required": true
},
{
"name": "tags",
"type": "string[]",
"description": "Array of tags for categorization",
"required": false
},
{
"name": "format",
"type": "'markdown' | 'mdx'",
"description": "Output format for the blog post",
"required": false
},
{
"name": "excerpt",
"type": "string",
"description": "Short excerpt or summary of the post",
"required": false
}
],
"returns": {
"type": "BlogPost",
"description": "A structured blog post object with frontmatter, content, and metadata including slug, wordCount, readingTime, and formattedOutput"
},
"aiAgent": {
"useCase": "Use this tool when users need to generate blog posts, articles, or structured content with proper frontmatter and metadata. Ideal for content management systems, static site generators, and documentation sites.",
"limitations": "Does not include AI content generation - you must provide the content. Only formats and structures existing content.",
"examples": [
"Create a blog post about TypeScript best practices",
"Generate a tutorial post with code examples",
"Format an article with SEO metadata"
]
}
}
],
"returns": {
"type": "BlogPost",
"description": "A structured blog post object with frontmatter, content, and metadata including slug, wordCount, readingTime, and formattedOutput"
},
"frameworks": ["vercel-ai", "langchain"],
"aiAgent": {
"useCase": "Use this tool when users need to generate blog posts, articles, or structured content with proper frontmatter and metadata. Ideal for content management systems, static site generators, and documentation sites.",
"limitations": "Does not include AI content generation - you must provide the content. Only formats and structures existing content.",
"examples": [
"Create a blog post about TypeScript best practices",
"Generate a tutorial post with code examples",
"Format an article with SEO metadata"
]
}
]
},
"dependencies": {
"zod": "^4.1.13"
"ai": "6.0.0-beta.124"
}
}

View file

@ -1,17 +1,12 @@
/**
* Blog Post Creation Tool for TPMJS
* Creates structured blog posts with frontmatter and metadata
*
* This is a proper AI SDK v6 tool that can be used with streamText()
* Uses jsonSchema() to avoid Zod 4 JSON Schema conversion issues with OpenAI
*/
export interface BlogPostOptions {
title: string;
author: string;
content: string;
tags?: string[];
format?: 'markdown' | 'mdx';
excerpt?: string;
publishDate?: Date;
}
import { jsonSchema, tool } from 'ai';
export interface BlogPost {
frontmatter: {
@ -28,6 +23,18 @@ export interface BlogPost {
formattedOutput: string;
}
/**
* Input type for Create Blog Post Tool
*/
type CreateBlogPostInput = {
title: string;
author: string;
content: string;
tags?: string[];
format?: 'markdown' | 'mdx';
excerpt?: string;
};
/**
* Creates a slug from a title
*/
@ -84,64 +91,95 @@ function formatFrontmatter(
}
/**
* Create Blog Post Tool
* Creates a structured blog post with frontmatter and metadata
*
* This is a proper AI SDK v6 tool that can be used with streamText()
*/
export async function createBlogPost(options: BlogPostOptions): Promise<BlogPost> {
const {
title,
author,
content,
tags = [],
format = 'markdown',
excerpt,
publishDate = new Date(),
} = options;
export const createBlogPostTool = tool({
description:
'Creates a structured blog post with frontmatter, metadata, slug, word count, and reading time. Outputs in Markdown or MDX format.',
inputSchema: jsonSchema<CreateBlogPostInput>({
type: 'object',
properties: {
title: {
type: 'string',
description: 'The title of the blog post',
},
author: {
type: 'string',
description: 'The author of the blog post',
},
content: {
type: 'string',
description: 'The main content of the blog post',
},
tags: {
type: 'array',
items: { type: 'string' },
description: 'Array of tags for categorization',
},
format: {
type: 'string',
enum: ['markdown', 'mdx'],
description: 'Output format for the blog post (default: markdown)',
},
excerpt: {
type: 'string',
description: 'Short excerpt or summary of the post',
},
},
required: ['title', 'author', 'content'],
additionalProperties: false,
}),
async execute({ title, author, content, tags = [], format = 'markdown', excerpt }) {
// Validate required fields
if (!title || title.trim().length === 0) {
throw new Error('Title is required');
}
// Validate required fields
if (!title || title.trim().length === 0) {
throw new Error('Title is required');
}
if (!author || author.trim().length === 0) {
throw new Error('Author is required');
}
if (!author || author.trim().length === 0) {
throw new Error('Author is required');
}
if (!content || content.trim().length === 0) {
throw new Error('Content is required');
}
if (!content || content.trim().length === 0) {
throw new Error('Content is required');
}
// Calculate metadata
const slug = createSlug(title);
const wordCount = countWords(content);
const readingTime = calculateReadingTime(wordCount);
const publishDate = new Date();
// Calculate metadata
const slug = createSlug(title);
const wordCount = countWords(content);
const readingTime = calculateReadingTime(wordCount);
// Build frontmatter
const frontmatter: BlogPost['frontmatter'] = {
title,
author,
date: publishDate.toISOString().split('T')[0] || '',
tags,
slug,
wordCount,
readingTime,
};
// Build frontmatter
const frontmatter: BlogPost['frontmatter'] = {
title,
author,
date: publishDate.toISOString().split('T')[0] || '',
tags,
slug,
wordCount,
readingTime,
};
if (excerpt) {
frontmatter.excerpt = excerpt;
}
if (excerpt) {
frontmatter.excerpt = excerpt;
}
// Format the complete blog post
const formattedFrontmatter = formatFrontmatter(frontmatter, format);
const formattedOutput = `${formattedFrontmatter}\n\n${content}`;
// Format the complete blog post
const formattedFrontmatter = formatFrontmatter(frontmatter, format);
const formattedOutput = `${formattedFrontmatter}\n\n${content}`;
return {
frontmatter,
content,
formattedOutput,
};
}
return {
frontmatter,
content,
formattedOutput,
};
},
});
/**
* Export default for convenience
*/
export default createBlogPost;
export default createBlogPostTool;