mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-06 03:00:16 -04:00
Mothership block pudate
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { Rocket } from 'lucide-react'
|
||||
import type { BlockConfig } from '@/blocks/types'
|
||||
import { Blimp } from '@/components/emcn'
|
||||
import type { ToolResponse } from '@/tools/types'
|
||||
|
||||
interface MothershipResponse extends ToolResponse {
|
||||
@@ -27,7 +27,7 @@ export const MothershipBlock: BlockConfig<MothershipResponse> = {
|
||||
`,
|
||||
category: 'blocks',
|
||||
bgColor: '#802FDE',
|
||||
icon: Rocket,
|
||||
icon: Blimp,
|
||||
subBlocks: [
|
||||
{
|
||||
id: 'messages',
|
||||
|
||||
@@ -12,8 +12,9 @@ import {
|
||||
validateModelProvider,
|
||||
validateSkillsAllowed,
|
||||
} from '@/ee/access-control/utils/permission-check'
|
||||
import { AGENT, BlockType, DEFAULTS, REFERENCE, stripCustomToolPrefix } from '@/executor/constants'
|
||||
import { AGENT, BlockType, DEFAULTS, stripCustomToolPrefix } from '@/executor/constants'
|
||||
import { memoryService } from '@/executor/handlers/agent/memory'
|
||||
import { parseResponseFormat } from '@/executor/handlers/shared/response-format'
|
||||
import {
|
||||
buildLoadSkillTool,
|
||||
buildSkillsSystemPromptSection,
|
||||
@@ -55,7 +56,7 @@ export class AgentBlockHandler implements BlockHandler {
|
||||
|
||||
await this.validateToolPermissions(ctx, filteredInputs.tools || [])
|
||||
|
||||
const responseFormat = this.parseResponseFormat(filteredInputs.responseFormat)
|
||||
const responseFormat = parseResponseFormat(filteredInputs.responseFormat)
|
||||
const model = filteredInputs.model || AGENT.DEFAULT_MODEL
|
||||
|
||||
await validateModelProvider(ctx.userId, model, ctx)
|
||||
@@ -112,55 +113,6 @@ export class AgentBlockHandler implements BlockHandler {
|
||||
return result
|
||||
}
|
||||
|
||||
private parseResponseFormat(responseFormat?: string | object): any {
|
||||
if (!responseFormat || responseFormat === '') return undefined
|
||||
|
||||
if (typeof responseFormat === 'object' && responseFormat !== null) {
|
||||
const formatObj = responseFormat as any
|
||||
if (!formatObj.schema && !formatObj.name) {
|
||||
return {
|
||||
name: 'response_schema',
|
||||
schema: responseFormat,
|
||||
strict: true,
|
||||
}
|
||||
}
|
||||
return responseFormat
|
||||
}
|
||||
|
||||
if (typeof responseFormat === 'string') {
|
||||
const trimmedValue = responseFormat.trim()
|
||||
|
||||
if (trimmedValue.startsWith(REFERENCE.START) && trimmedValue.includes(REFERENCE.END)) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(trimmedValue)
|
||||
|
||||
if (parsed && typeof parsed === 'object' && !parsed.schema && !parsed.name) {
|
||||
return {
|
||||
name: 'response_schema',
|
||||
schema: parsed,
|
||||
strict: true,
|
||||
}
|
||||
}
|
||||
return parsed
|
||||
} catch (error: any) {
|
||||
logger.warn('Failed to parse response format as JSON, using default behavior:', {
|
||||
error: error.message,
|
||||
value: trimmedValue,
|
||||
})
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
logger.warn('Unexpected response format type, using default behavior:', {
|
||||
type: typeof responseFormat,
|
||||
value: responseFormat,
|
||||
})
|
||||
return undefined
|
||||
}
|
||||
|
||||
private async validateToolPermissions(ctx: ExecutionContext, tools: ToolInput[]): Promise<void> {
|
||||
if (!Array.isArray(tools) || tools.length === 0) return
|
||||
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { BlockOutput } from '@/blocks/types'
|
||||
import { BlockType } from '@/executor/constants'
|
||||
import {
|
||||
parseResponseFormat,
|
||||
processStructuredResponse,
|
||||
resolveMessages,
|
||||
} from '@/executor/handlers/shared/response-format'
|
||||
import type { BlockHandler, ExecutionContext } from '@/executor/types'
|
||||
import { buildAPIUrl, buildAuthHeaders, extractAPIErrorMessage } from '@/executor/utils/http'
|
||||
import type { SerializedBlock } from '@/serializer/types'
|
||||
@@ -24,8 +29,8 @@ export class MothershipBlockHandler implements BlockHandler {
|
||||
block: SerializedBlock,
|
||||
inputs: Record<string, any>
|
||||
): Promise<BlockOutput> {
|
||||
const messages = this.resolveMessages(inputs)
|
||||
const responseFormat = this.parseResponseFormat(inputs.responseFormat)
|
||||
const messages = resolveMessages(inputs.messages)
|
||||
const responseFormat = parseResponseFormat(inputs.responseFormat)
|
||||
|
||||
const memoryType = inputs.memoryType || 'none'
|
||||
const chatId =
|
||||
@@ -68,7 +73,7 @@ export class MothershipBlockHandler implements BlockHandler {
|
||||
const result = await response.json()
|
||||
|
||||
if (responseFormat && result.content) {
|
||||
return this.processStructuredResponse(result)
|
||||
return processStructuredResponse(result, 'mothership')
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -77,76 +82,4 @@ export class MothershipBlockHandler implements BlockHandler {
|
||||
tokens: result.tokens || {},
|
||||
}
|
||||
}
|
||||
|
||||
private resolveMessages(
|
||||
inputs: Record<string, any>
|
||||
): Array<{ role: string; content: string }> {
|
||||
const raw = inputs.messages
|
||||
if (!raw) {
|
||||
throw new Error('Messages input is required for the Mothership block')
|
||||
}
|
||||
|
||||
let messages: unknown[]
|
||||
if (typeof raw === 'string') {
|
||||
try {
|
||||
messages = JSON.parse(raw)
|
||||
} catch {
|
||||
throw new Error('Messages must be a valid JSON array')
|
||||
}
|
||||
} else if (Array.isArray(raw)) {
|
||||
messages = raw
|
||||
} else {
|
||||
throw new Error('Messages must be an array of {role, content} objects')
|
||||
}
|
||||
|
||||
return messages.map((msg: any, i: number) => {
|
||||
if (!msg.role || typeof msg.content !== 'string') {
|
||||
throw new Error(
|
||||
`Message at index ${i} must have "role" (string) and "content" (string)`
|
||||
)
|
||||
}
|
||||
return { role: String(msg.role), content: msg.content }
|
||||
})
|
||||
}
|
||||
|
||||
private parseResponseFormat(responseFormat?: string | object): any {
|
||||
if (!responseFormat || responseFormat === '') return undefined
|
||||
|
||||
if (typeof responseFormat === 'object') return responseFormat
|
||||
|
||||
if (typeof responseFormat === 'string') {
|
||||
const trimmed = responseFormat.trim()
|
||||
if (!trimmed) return undefined
|
||||
if (trimmed.startsWith('<') || trimmed.startsWith('{{')) return undefined
|
||||
try {
|
||||
return JSON.parse(trimmed)
|
||||
} catch {
|
||||
logger.warn('Failed to parse responseFormat as JSON', {
|
||||
preview: trimmed.slice(0, 100),
|
||||
})
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
private processStructuredResponse(result: any): BlockOutput {
|
||||
const content = result.content
|
||||
try {
|
||||
const parsed = JSON.parse(content.trim())
|
||||
return {
|
||||
...parsed,
|
||||
model: result.model || 'mothership',
|
||||
tokens: result.tokens || {},
|
||||
}
|
||||
} catch {
|
||||
logger.warn('Failed to parse structured response, returning raw content')
|
||||
return {
|
||||
content,
|
||||
model: result.model || 'mothership',
|
||||
tokens: result.tokens || {},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
111
apps/sim/executor/handlers/shared/response-format.ts
Normal file
111
apps/sim/executor/handlers/shared/response-format.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type { BlockOutput } from '@/blocks/types'
|
||||
import { REFERENCE } from '@/executor/constants'
|
||||
|
||||
const logger = createLogger('SharedResponseFormat')
|
||||
|
||||
/**
|
||||
* Parse a raw responseFormat value (string or object) into a usable schema.
|
||||
*
|
||||
* Handles:
|
||||
* - Empty / falsy → undefined
|
||||
* - Already an object → wraps bare schemas with `{ name, schema, strict }`
|
||||
* - JSON string → parsed, then same wrapping logic
|
||||
* - Unresolved block references (`<block.field>`) → undefined
|
||||
*/
|
||||
export function parseResponseFormat(responseFormat?: string | object): any {
|
||||
if (!responseFormat || responseFormat === '') return undefined
|
||||
|
||||
if (typeof responseFormat === 'object' && responseFormat !== null) {
|
||||
const formatObj = responseFormat as any
|
||||
if (!formatObj.schema && !formatObj.name) {
|
||||
return { name: 'response_schema', schema: responseFormat, strict: true }
|
||||
}
|
||||
return responseFormat
|
||||
}
|
||||
|
||||
if (typeof responseFormat === 'string') {
|
||||
const trimmed = responseFormat.trim()
|
||||
if (!trimmed) return undefined
|
||||
if (trimmed.startsWith(REFERENCE.START) && trimmed.includes(REFERENCE.END)) {
|
||||
return undefined
|
||||
}
|
||||
try {
|
||||
const parsed = JSON.parse(trimmed)
|
||||
if (parsed && typeof parsed === 'object' && !parsed.schema && !parsed.name) {
|
||||
return { name: 'response_schema', schema: parsed, strict: true }
|
||||
}
|
||||
return parsed
|
||||
} catch (error: any) {
|
||||
logger.warn('Failed to parse response format as JSON', {
|
||||
error: error.message,
|
||||
preview: trimmed.slice(0, 100),
|
||||
})
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate and extract messages from a raw input value.
|
||||
*
|
||||
* Accepts a JSON string or an array. Each entry must have
|
||||
* `role` (string) and `content` (string).
|
||||
*/
|
||||
export function resolveMessages(
|
||||
raw: unknown
|
||||
): Array<{ role: string; content: string }> {
|
||||
if (!raw) {
|
||||
throw new Error('Messages input is required')
|
||||
}
|
||||
|
||||
let messages: unknown[]
|
||||
if (typeof raw === 'string') {
|
||||
try {
|
||||
messages = JSON.parse(raw)
|
||||
} catch {
|
||||
throw new Error('Messages must be a valid JSON array')
|
||||
}
|
||||
} else if (Array.isArray(raw)) {
|
||||
messages = raw
|
||||
} else {
|
||||
throw new Error('Messages must be an array of {role, content} objects')
|
||||
}
|
||||
|
||||
return messages.map((msg: any, i: number) => {
|
||||
if (!msg.role || typeof msg.content !== 'string') {
|
||||
throw new Error(
|
||||
`Message at index ${i} must have "role" (string) and "content" (string)`
|
||||
)
|
||||
}
|
||||
return { role: String(msg.role), content: msg.content }
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to parse the LLM response content as structured JSON and spread
|
||||
* the fields into the block output. Falls back to returning raw content.
|
||||
*/
|
||||
export function processStructuredResponse(
|
||||
result: { content?: string; model?: string; tokens?: any },
|
||||
defaultModel: string
|
||||
): BlockOutput {
|
||||
const content = result.content ?? ''
|
||||
try {
|
||||
const parsed = JSON.parse(content.trim())
|
||||
return {
|
||||
...parsed,
|
||||
model: result.model || defaultModel,
|
||||
tokens: result.tokens || {},
|
||||
}
|
||||
} catch {
|
||||
logger.warn('Failed to parse structured response, returning raw content')
|
||||
return {
|
||||
content,
|
||||
model: result.model || defaultModel,
|
||||
tokens: result.tokens || {},
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user