Skip to content

Commit 1d48289

Browse files
committed
Mothership block pudate
1 parent fce1024 commit 1d48289

4 files changed

Lines changed: 124 additions & 128 deletions

File tree

apps/sim/blocks/blocks/mothership.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { Rocket } from 'lucide-react'
21
import type { BlockConfig } from '@/blocks/types'
2+
import { Blimp } from '@/components/emcn'
33
import type { ToolResponse } from '@/tools/types'
44

55
interface MothershipResponse extends ToolResponse {
@@ -27,7 +27,7 @@ export const MothershipBlock: BlockConfig<MothershipResponse> = {
2727
`,
2828
category: 'blocks',
2929
bgColor: '#802FDE',
30-
icon: Rocket,
30+
icon: Blimp,
3131
subBlocks: [
3232
{
3333
id: 'messages',

apps/sim/executor/handlers/agent/agent-handler.ts

Lines changed: 3 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ import {
1212
validateModelProvider,
1313
validateSkillsAllowed,
1414
} from '@/ee/access-control/utils/permission-check'
15-
import { AGENT, BlockType, DEFAULTS, REFERENCE, stripCustomToolPrefix } from '@/executor/constants'
15+
import { AGENT, BlockType, DEFAULTS, stripCustomToolPrefix } from '@/executor/constants'
1616
import { memoryService } from '@/executor/handlers/agent/memory'
17+
import { parseResponseFormat } from '@/executor/handlers/shared/response-format'
1718
import {
1819
buildLoadSkillTool,
1920
buildSkillsSystemPromptSection,
@@ -55,7 +56,7 @@ export class AgentBlockHandler implements BlockHandler {
5556

5657
await this.validateToolPermissions(ctx, filteredInputs.tools || [])
5758

58-
const responseFormat = this.parseResponseFormat(filteredInputs.responseFormat)
59+
const responseFormat = parseResponseFormat(filteredInputs.responseFormat)
5960
const model = filteredInputs.model || AGENT.DEFAULT_MODEL
6061

6162
await validateModelProvider(ctx.userId, model, ctx)
@@ -112,55 +113,6 @@ export class AgentBlockHandler implements BlockHandler {
112113
return result
113114
}
114115

115-
private parseResponseFormat(responseFormat?: string | object): any {
116-
if (!responseFormat || responseFormat === '') return undefined
117-
118-
if (typeof responseFormat === 'object' && responseFormat !== null) {
119-
const formatObj = responseFormat as any
120-
if (!formatObj.schema && !formatObj.name) {
121-
return {
122-
name: 'response_schema',
123-
schema: responseFormat,
124-
strict: true,
125-
}
126-
}
127-
return responseFormat
128-
}
129-
130-
if (typeof responseFormat === 'string') {
131-
const trimmedValue = responseFormat.trim()
132-
133-
if (trimmedValue.startsWith(REFERENCE.START) && trimmedValue.includes(REFERENCE.END)) {
134-
return undefined
135-
}
136-
137-
try {
138-
const parsed = JSON.parse(trimmedValue)
139-
140-
if (parsed && typeof parsed === 'object' && !parsed.schema && !parsed.name) {
141-
return {
142-
name: 'response_schema',
143-
schema: parsed,
144-
strict: true,
145-
}
146-
}
147-
return parsed
148-
} catch (error: any) {
149-
logger.warn('Failed to parse response format as JSON, using default behavior:', {
150-
error: error.message,
151-
value: trimmedValue,
152-
})
153-
return undefined
154-
}
155-
}
156-
157-
logger.warn('Unexpected response format type, using default behavior:', {
158-
type: typeof responseFormat,
159-
value: responseFormat,
160-
})
161-
return undefined
162-
}
163-
164116
private async validateToolPermissions(ctx: ExecutionContext, tools: ToolInput[]): Promise<void> {
165117
if (!Array.isArray(tools) || tools.length === 0) return
166118

Lines changed: 8 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import { createLogger } from '@sim/logger'
22
import type { BlockOutput } from '@/blocks/types'
33
import { BlockType } from '@/executor/constants'
4+
import {
5+
parseResponseFormat,
6+
processStructuredResponse,
7+
resolveMessages,
8+
} from '@/executor/handlers/shared/response-format'
49
import type { BlockHandler, ExecutionContext } from '@/executor/types'
510
import { buildAPIUrl, buildAuthHeaders, extractAPIErrorMessage } from '@/executor/utils/http'
611
import type { SerializedBlock } from '@/serializer/types'
@@ -24,8 +29,8 @@ export class MothershipBlockHandler implements BlockHandler {
2429
block: SerializedBlock,
2530
inputs: Record<string, any>
2631
): Promise<BlockOutput> {
27-
const messages = this.resolveMessages(inputs)
28-
const responseFormat = this.parseResponseFormat(inputs.responseFormat)
32+
const messages = resolveMessages(inputs.messages)
33+
const responseFormat = parseResponseFormat(inputs.responseFormat)
2934

3035
const memoryType = inputs.memoryType || 'none'
3136
const chatId =
@@ -68,7 +73,7 @@ export class MothershipBlockHandler implements BlockHandler {
6873
const result = await response.json()
6974

7075
if (responseFormat && result.content) {
71-
return this.processStructuredResponse(result)
76+
return processStructuredResponse(result, 'mothership')
7277
}
7378

7479
return {
@@ -77,76 +82,4 @@ export class MothershipBlockHandler implements BlockHandler {
7782
tokens: result.tokens || {},
7883
}
7984
}
80-
81-
private resolveMessages(
82-
inputs: Record<string, any>
83-
): Array<{ role: string; content: string }> {
84-
const raw = inputs.messages
85-
if (!raw) {
86-
throw new Error('Messages input is required for the Mothership block')
87-
}
88-
89-
let messages: unknown[]
90-
if (typeof raw === 'string') {
91-
try {
92-
messages = JSON.parse(raw)
93-
} catch {
94-
throw new Error('Messages must be a valid JSON array')
95-
}
96-
} else if (Array.isArray(raw)) {
97-
messages = raw
98-
} else {
99-
throw new Error('Messages must be an array of {role, content} objects')
100-
}
101-
102-
return messages.map((msg: any, i: number) => {
103-
if (!msg.role || typeof msg.content !== 'string') {
104-
throw new Error(
105-
`Message at index ${i} must have "role" (string) and "content" (string)`
106-
)
107-
}
108-
return { role: String(msg.role), content: msg.content }
109-
})
110-
}
111-
112-
private parseResponseFormat(responseFormat?: string | object): any {
113-
if (!responseFormat || responseFormat === '') return undefined
114-
115-
if (typeof responseFormat === 'object') return responseFormat
116-
117-
if (typeof responseFormat === 'string') {
118-
const trimmed = responseFormat.trim()
119-
if (!trimmed) return undefined
120-
if (trimmed.startsWith('<') || trimmed.startsWith('{{')) return undefined
121-
try {
122-
return JSON.parse(trimmed)
123-
} catch {
124-
logger.warn('Failed to parse responseFormat as JSON', {
125-
preview: trimmed.slice(0, 100),
126-
})
127-
return undefined
128-
}
129-
}
130-
131-
return undefined
132-
}
133-
134-
private processStructuredResponse(result: any): BlockOutput {
135-
const content = result.content
136-
try {
137-
const parsed = JSON.parse(content.trim())
138-
return {
139-
...parsed,
140-
model: result.model || 'mothership',
141-
tokens: result.tokens || {},
142-
}
143-
} catch {
144-
logger.warn('Failed to parse structured response, returning raw content')
145-
return {
146-
content,
147-
model: result.model || 'mothership',
148-
tokens: result.tokens || {},
149-
}
150-
}
151-
}
15285
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import { createLogger } from '@sim/logger'
2+
import type { BlockOutput } from '@/blocks/types'
3+
import { REFERENCE } from '@/executor/constants'
4+
5+
const logger = createLogger('SharedResponseFormat')
6+
7+
/**
8+
* Parse a raw responseFormat value (string or object) into a usable schema.
9+
*
10+
* Handles:
11+
* - Empty / falsy → undefined
12+
* - Already an object → wraps bare schemas with `{ name, schema, strict }`
13+
* - JSON string → parsed, then same wrapping logic
14+
* - Unresolved block references (`<block.field>`) → undefined
15+
*/
16+
export function parseResponseFormat(responseFormat?: string | object): any {
17+
if (!responseFormat || responseFormat === '') return undefined
18+
19+
if (typeof responseFormat === 'object' && responseFormat !== null) {
20+
const formatObj = responseFormat as any
21+
if (!formatObj.schema && !formatObj.name) {
22+
return { name: 'response_schema', schema: responseFormat, strict: true }
23+
}
24+
return responseFormat
25+
}
26+
27+
if (typeof responseFormat === 'string') {
28+
const trimmed = responseFormat.trim()
29+
if (!trimmed) return undefined
30+
if (trimmed.startsWith(REFERENCE.START) && trimmed.includes(REFERENCE.END)) {
31+
return undefined
32+
}
33+
try {
34+
const parsed = JSON.parse(trimmed)
35+
if (parsed && typeof parsed === 'object' && !parsed.schema && !parsed.name) {
36+
return { name: 'response_schema', schema: parsed, strict: true }
37+
}
38+
return parsed
39+
} catch (error: any) {
40+
logger.warn('Failed to parse response format as JSON', {
41+
error: error.message,
42+
preview: trimmed.slice(0, 100),
43+
})
44+
return undefined
45+
}
46+
}
47+
48+
return undefined
49+
}
50+
51+
/**
52+
* Validate and extract messages from a raw input value.
53+
*
54+
* Accepts a JSON string or an array. Each entry must have
55+
* `role` (string) and `content` (string).
56+
*/
57+
export function resolveMessages(
58+
raw: unknown
59+
): Array<{ role: string; content: string }> {
60+
if (!raw) {
61+
throw new Error('Messages input is required')
62+
}
63+
64+
let messages: unknown[]
65+
if (typeof raw === 'string') {
66+
try {
67+
messages = JSON.parse(raw)
68+
} catch {
69+
throw new Error('Messages must be a valid JSON array')
70+
}
71+
} else if (Array.isArray(raw)) {
72+
messages = raw
73+
} else {
74+
throw new Error('Messages must be an array of {role, content} objects')
75+
}
76+
77+
return messages.map((msg: any, i: number) => {
78+
if (!msg.role || typeof msg.content !== 'string') {
79+
throw new Error(
80+
`Message at index ${i} must have "role" (string) and "content" (string)`
81+
)
82+
}
83+
return { role: String(msg.role), content: msg.content }
84+
})
85+
}
86+
87+
/**
88+
* Try to parse the LLM response content as structured JSON and spread
89+
* the fields into the block output. Falls back to returning raw content.
90+
*/
91+
export function processStructuredResponse(
92+
result: { content?: string; model?: string; tokens?: any },
93+
defaultModel: string
94+
): BlockOutput {
95+
const content = result.content ?? ''
96+
try {
97+
const parsed = JSON.parse(content.trim())
98+
return {
99+
...parsed,
100+
model: result.model || defaultModel,
101+
tokens: result.tokens || {},
102+
}
103+
} catch {
104+
logger.warn('Failed to parse structured response, returning raw content')
105+
return {
106+
content,
107+
model: result.model || defaultModel,
108+
tokens: result.tokens || {},
109+
}
110+
}
111+
}

0 commit comments

Comments
 (0)