From db482855d3e097b3312eb51bdfaf7f778dab1701 Mon Sep 17 00:00:00 2001 From: Ajax Davis Date: Fri, 12 Dec 2025 10:38:46 +1000 Subject: [PATCH] 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' --- packages/tools/createBlogPost/package.json | 113 +++++++-------- packages/tools/createBlogPost/src/index.ts | 152 +++++++++++++-------- pnpm-lock.yaml | 24 ++-- 3 files changed, 165 insertions(+), 124 deletions(-) diff --git a/packages/tools/createBlogPost/package.json b/packages/tools/createBlogPost/package.json index 6998613..5ed9bb1 100644 --- a/packages/tools/createBlogPost/package.json +++ b/packages/tools/createBlogPost/package.json @@ -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" } } diff --git a/packages/tools/createBlogPost/src/index.ts b/packages/tools/createBlogPost/src/index.ts index 6c3b4e9..1e6dd6f 100644 --- a/packages/tools/createBlogPost/src/index.ts +++ b/packages/tools/createBlogPost/src/index.ts @@ -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 { - 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({ + 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; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d1fc1d7..6fa3013 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -131,10 +131,10 @@ importers: version: 10.4.22(postcss@8.5.6) eslint: specifier: ^9.39.1 - version: 9.39.1(jiti@2.6.1) + version: 9.39.1(jiti@1.21.7) eslint-config-next: specifier: ^16.0.4 - version: 16.0.4(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + version: 16.0.4(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) postcss: specifier: ^8.5.1 version: 8.5.6 @@ -255,10 +255,10 @@ importers: version: 10.4.22(postcss@8.5.6) eslint: specifier: ^9.39.1 - version: 9.39.1(jiti@1.21.7) + version: 9.39.1(jiti@2.6.1) eslint-config-next: specifier: ^16.0.4 - version: 16.0.4(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + version: 16.0.4(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) postcss: specifier: ^8.5.1 version: 8.5.6 @@ -544,9 +544,9 @@ importers: packages/tools/createBlogPost: dependencies: - zod: - specifier: ^4.1.13 - version: 4.1.13 + ai: + specifier: 6.0.0-beta.124 + version: 6.0.0-beta.124(effect@3.18.4)(zod@4.1.13) devDependencies: '@tpmjs/tsconfig': specifier: workspace:* @@ -9442,7 +9442,7 @@ snapshots: '@next/eslint-plugin-next': 16.0.4 eslint: 9.39.1(jiti@1.21.7) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(eslint@9.39.1(jiti@1.21.7)))(eslint@9.39.1(jiti@1.21.7)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@1.21.7)) eslint-plugin-import: 2.32.0(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@1.21.7)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.1(jiti@1.21.7)) eslint-plugin-react: 7.37.5(eslint@9.39.1(jiti@1.21.7)) @@ -9485,7 +9485,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(eslint@9.39.1(jiti@1.21.7)))(eslint@9.39.1(jiti@1.21.7)): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@1.21.7)): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.3 @@ -9525,13 +9525,13 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(eslint@9.39.1(jiti@1.21.7)))(eslint@9.39.1(jiti@1.21.7)))(eslint@9.39.1(jiti@1.21.7)): + eslint-module-utils@2.12.1(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@1.21.7)): dependencies: debug: 3.2.7 optionalDependencies: eslint: 9.39.1(jiti@1.21.7) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(eslint@9.39.1(jiti@1.21.7)))(eslint@9.39.1(jiti@1.21.7)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@1.21.7)) transitivePeerDependencies: - supports-color @@ -9585,7 +9585,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.39.1(jiti@1.21.7) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(eslint@9.39.1(jiti@1.21.7)))(eslint@9.39.1(jiti@1.21.7)))(eslint@9.39.1(jiti@1.21.7)) + eslint-module-utils: 2.12.1(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@1.21.7)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3