From 680ed4cee96c2930799fa3d114a5a71d4d292aca Mon Sep 17 00:00:00 2001 From: Gilad Resisi Date: Wed, 8 Jul 2026 16:43:20 +0700 Subject: [PATCH 1/2] fix(tiktok): default content_posting_method to DIRECT_POST The agent (chat / MCP) had to pick content_posting_method with no guidance: the setting is a bare required enum with no description or default, and TikTok's agent rules said nothing about it. For a video post "UPLOAD" reads like the obvious choice - but UPLOAD does not publish, it only drops the video into the user's TikTok app inbox, where it must be completed manually within ~24h or it is discarded. Users hit this as "post marked successful but never published". Two layers: - TikTok @Rules text now tells the agent to use DIRECT_POST unless the user explicitly asks to review/edit in the app first, and explains that UPLOAD does not publish. - A PROVIDER_SETTINGS_DEFAULTS map + buildSettings() helper fills content_posting_method with DIRECT_POST when the key is omitted, in both validation and creation, so an omitted value can never land on UPLOAD by accident. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../chat/tools/integration.schedule.post.ts | 48 +++++++++++++++---- .../integrations/social/tiktok.provider.ts | 4 +- 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/libraries/nestjs-libraries/src/chat/tools/integration.schedule.post.ts b/libraries/nestjs-libraries/src/chat/tools/integration.schedule.post.ts index 19433a7fae..fdc9df735b 100644 --- a/libraries/nestjs-libraries/src/chat/tools/integration.schedule.post.ts +++ b/libraries/nestjs-libraries/src/chat/tools/integration.schedule.post.ts @@ -16,6 +16,38 @@ import { const validUrlExtension = new ValidUrlExtension(); const validUrlPath = new ValidUrlPath(); +// Settings the agent tends to guess when the schema gives it no guidance — +// fill them explicitly so an omitted key never lands on a surprising value +// (e.g. TikTok UPLOAD sends the post to the user's app inbox instead of +// publishing it). +export const PROVIDER_SETTINGS_DEFAULTS: Record> = + { + tiktok: { content_posting_method: 'DIRECT_POST' }, + }; + +export const buildSettings = ( + providerIdentifier: string, + settings: { key: string; value: any }[], + base: AllProvidersSettings +): AllProvidersSettings => { + const merged = settings.reduce( + (acc: AllProvidersSettings, s: { key: string; value: any }) => ({ + ...acc, + [s.key]: s.value, + }), + base + ); + + const defaults = PROVIDER_SETTINGS_DEFAULTS[providerIdentifier] || {}; + for (const [key, value] of Object.entries(defaults)) { + if ((merged as any)[key] === undefined || (merged as any)[key] === '') { + (merged as any)[key] = value; + } + } + + return merged; +}; + // Same URL validation as MediaDto (valid.url.path) - each attachment must // point to an allowed upload domain and a supported file extension. const attachmentUrl = z @@ -147,11 +179,9 @@ If the tools return errors, you would need to rerun it with the right parameters // Same server-side validation as the dashboard / public API // (settings DTO + media checkValidity + empty / too-long content). - const settings = platform.settings.reduce( - (acc: AllProvidersSettings, s: { key: string; value: any }) => ({ - ...acc, - [s.key]: s.value, - }), + const settings = buildSettings( + integrations[platform.integrationId]?.providerIdentifier || '', + platform.settings, {} as AllProvidersSettings ); @@ -216,11 +246,9 @@ If the tools return errors, you would need to rerun it with the right parameters { integration, group: makeId(10), - settings: post.settings.reduce( - (acc: AllProvidersSettings, s: { key: string; value: any }) => ({ - ...acc, - [s.key]: s.value, - }), + settings: buildSettings( + integration.providerIdentifier, + post.settings, { __type: integration.providerIdentifier, } as AllProvidersSettings diff --git a/libraries/nestjs-libraries/src/integrations/social/tiktok.provider.ts b/libraries/nestjs-libraries/src/integrations/social/tiktok.provider.ts index 87be989289..89a2bbf259 100644 --- a/libraries/nestjs-libraries/src/integrations/social/tiktok.provider.ts +++ b/libraries/nestjs-libraries/src/integrations/social/tiktok.provider.ts @@ -19,7 +19,9 @@ import { Integration } from '@prisma/client'; import { Rules } from '@gitroom/nestjs-libraries/chat/rules.description.decorator'; @Rules( - 'TikTok can have one video or one picture or multiple pictures, it cannot be without an attachment' + 'TikTok can have one video or one picture or multiple pictures, it cannot be without an attachment. ' + + 'For the "content_posting_method" setting always use "DIRECT_POST" unless the user explicitly asks to review or edit the post inside the TikTok app before publishing. ' + + '"UPLOAD" does NOT publish the post: it only sends it to the user\'s TikTok app inbox, where they must manually complete it within 24 hours or it is discarded.' ) export class TiktokProvider extends SocialAbstract implements SocialProvider { identifier = 'tiktok'; From fdb1e417cd0bacb2438df129e6376c988ec6bb8e Mon Sep 17 00:00:00 2001 From: Gilad Resisi Date: Fri, 10 Jul 2026 10:27:19 +0700 Subject: [PATCH 2/2] docs(tiktok): describe content_posting_method in the generated schema `content_posting_method` was exposed to schema consumers as a bare `enum: ["DIRECT_POST", "UPLOAD"]` with no description, so an LLM asked to "upload this video to TikTok" would reasonably pick the value literally named UPLOAD - which never publishes, it only drops the media into the user's TikTok app inbox where it expires after 24 hours. Annotate the field with `@JSONSchema({ description })`. This is metadata only: it is read by class-validator-jsonschema when generating the schema and registers no validator, so `@IsIn([...])` still rejects a missing or invalid value and the field stays required. Verified against the generated schema: `description` is present, `enum` is unchanged, and `content_posting_method` remains in `required`. The annotation reaches every `getValidationSchemas()` consumer: - `integrationSchema` MCP tool (internal web agent + external MCP agents) - `GET /public/v1/integration-settings/:id` (public API, and the CLI's `postiz integrations:settings`) The dashboard is unaffected - its React form hardcodes DIRECT_POST. For agents this layers on top of the @Rules text in the previous commit: the settings default only fills an omitted key, and since the field is required an agent nearly always sends *something*, so the prose rule and this description are what steer it away from UPLOAD. For public API callers that read the schema but not `rules`, this is the only in-band signal. Precedent for the decorator: whop.dto.ts, discord.dto.ts, listmonk.dto.ts. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../src/dtos/posts/providers-settings/tiktok.dto.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/libraries/nestjs-libraries/src/dtos/posts/providers-settings/tiktok.dto.ts b/libraries/nestjs-libraries/src/dtos/posts/providers-settings/tiktok.dto.ts index 8417315f86..d36ccffe93 100644 --- a/libraries/nestjs-libraries/src/dtos/posts/providers-settings/tiktok.dto.ts +++ b/libraries/nestjs-libraries/src/dtos/posts/providers-settings/tiktok.dto.ts @@ -1,6 +1,7 @@ import { IsBoolean, ValidateIf, IsIn, IsString, MaxLength, IsOptional } from 'class-validator'; +import { JSONSchema } from 'class-validator-jsonschema'; export class TikTokDto { @ValidateIf((p) => p.title) @@ -44,5 +45,12 @@ export class TikTokDto { @IsIn(['DIRECT_POST', 'UPLOAD']) @IsString() + @JSONSchema({ + description: + 'Required. Use "DIRECT_POST" to actually publish the post to TikTok. ' + + '"UPLOAD" does NOT publish: it only sends the media to the user\'s TikTok app inbox, ' + + 'where they must manually finish and publish it within 24 hours or it is discarded. ' + + 'Only use "UPLOAD" when the user explicitly asks to review or edit the post inside the TikTok app before publishing.', + }) content_posting_method: 'DIRECT_POST' | 'UPLOAD'; }