Skip to content

Commit c2de344

Browse files
authored
Add Assembly Instructions compiler (#445)
* Add prompt Assembly compiler * Use Assembly Instructions command naming * Add companion release changeset
1 parent 5f529a5 commit c2de344

9 files changed

Lines changed: 1094 additions & 2 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@transloadit/mcp-server': patch
3+
'transloadit': patch
4+
---
5+
6+
Release mcp-server and transloadit alongside @transloadit/node.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@transloadit/node': minor
3+
'@transloadit/utils': minor
4+
---
5+
6+
Add a shared Assembly Instructions compiler and expose prompt-to-Assembly-Instructions helpers in
7+
the Node SDK and CLI.

packages/node/src/Transloadit.ts

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import type { Readable } from 'node:stream'
22

3+
import type {
4+
CompileAssemblyInstructionsOptions,
5+
CompileAssemblyInstructionsResult,
6+
} from '@transloadit/utils'
37
import type { Delays, Headers, OptionsOfJSONResponseBody, RetryOptions } from 'got'
48

59
import type { TransloaditErrorResponseBody } from './ApiError.ts'
@@ -43,6 +47,7 @@ import { access, stat } from 'node:fs/promises'
4347
import { basename } from 'node:path'
4448
import { setTimeout as delay } from 'node:timers/promises'
4549

50+
import { compileAssemblyInstructionsFromPrompt } from '@transloadit/utils'
4651
import { getSignedSmartCdnUrl, signParamsSync } from '@transloadit/utils/node'
4752
import debug from 'debug'
4853
import FormData from 'form-data'
@@ -62,6 +67,14 @@ import PaginationStream from './PaginationStream.ts'
6267
import PollingTimeoutError from './PollingTimeoutError.ts'
6368
import { sendTusRequest } from './tus.ts'
6469

70+
export type {
71+
CompileAssemblyInstructionsAttempt,
72+
CompileAssemblyInstructionsClient,
73+
CompileAssemblyInstructionsLintIssue,
74+
CompileAssemblyInstructionsMessage,
75+
CompileAssemblyInstructionsResult,
76+
} from '@transloadit/utils'
77+
6578
export type { AssemblyStatus } from './alphalib/types/assemblyStatus.ts'
6679
export type {
6780
Base64Strategy,
@@ -81,6 +94,11 @@ export type {
8194
RobotParamHelp,
8295
} from './robots.ts'
8396

97+
export {
98+
buildCompileAssemblyInstructionsSystemPrompt,
99+
CompileAssemblyInstructionsError,
100+
parseCompileAssemblyInstructionsResponse,
101+
} from '@transloadit/utils'
84102
// See https://github.com/sindresorhus/got/tree/v11.8.6?tab=readme-ov-file#errors
85103
// Expose relevant errors
86104
export {
@@ -274,6 +292,13 @@ export interface SmartCDNUrlOptions {
274292
expiresAt?: number
275293
}
276294

295+
export type CompileAssemblyInstructionsFromPromptOptions = Omit<
296+
CompileAssemblyInstructionsOptions,
297+
'client'
298+
> & {
299+
timeout?: number
300+
}
301+
277302
export type Fields = Record<string, string | number>
278303

279304
// A special promise that lets the user immediately get the assembly ID (synchronously before the request is sent)
@@ -313,6 +338,37 @@ function checkResult<T>(result: T | { error: string }): asserts result is T {
313338
}
314339
}
315340

341+
function getResultBilledChargeUsd(result: unknown): number | undefined {
342+
if (typeof result !== 'object' || result === null || Array.isArray(result)) {
343+
return undefined
344+
}
345+
346+
if (!('meta' in result)) {
347+
return undefined
348+
}
349+
350+
const { meta } = result
351+
if (typeof meta !== 'object' || meta === null || Array.isArray(meta)) {
352+
return undefined
353+
}
354+
355+
if (!('billed_charge_usd' in meta)) {
356+
return undefined
357+
}
358+
359+
const { billed_charge_usd: billedChargeUsd } = meta
360+
return typeof billedChargeUsd === 'number' ? billedChargeUsd : undefined
361+
}
362+
363+
async function fetchJson(url: string): Promise<unknown> {
364+
const response = await fetch(url)
365+
if (!response.ok) {
366+
throw new Error(`Failed to fetch AI response: ${response.status} ${response.statusText}`)
367+
}
368+
369+
return await response.json()
370+
}
371+
316372
type AuthKeySecret = {
317373
authKey: string
318374
authSecret: string
@@ -552,6 +608,56 @@ export class Transloadit {
552608
})
553609
}
554610

611+
/**
612+
* Compile a natural-language prompt into validated Assembly Instructions.
613+
*
614+
* This creates a zero-upload /ai/chat Assembly, validates the structured response,
615+
* and lints the generated instructions locally before returning them.
616+
*/
617+
async compileAssemblyInstructionsFromPrompt(
618+
options: CompileAssemblyInstructionsFromPromptOptions,
619+
): Promise<CompileAssemblyInstructionsResult> {
620+
const timeout = options.timeout ?? 5 * 60 * 1000
621+
622+
return await compileAssemblyInstructionsFromPrompt({
623+
...options,
624+
mcpServerUrl: options.mcpServerUrl ?? `${this._endpoint}/mcp`,
625+
client: {
626+
runAssemblyInstructionsCompiler: async ({ aiStep }) => {
627+
const assembly = await this.createAssembly({
628+
params: {
629+
steps: {
630+
ai: aiStep,
631+
},
632+
},
633+
waitForCompletion: true,
634+
expectedUploads: 0,
635+
timeout,
636+
})
637+
638+
const result = assembly.results?.ai?.[0]
639+
const resultUrl = result?.url
640+
if (!resultUrl) {
641+
throw new Error('No AI response in Assembly results.')
642+
}
643+
644+
return {
645+
response: await fetchJson(resultUrl),
646+
assemblyUrl: assembly.assembly_ssl_url ?? assembly.assembly_url ?? undefined,
647+
billedChargeUsd: getResultBilledChargeUsd(result),
648+
usageBytes: assembly.bytes_usage ?? undefined,
649+
}
650+
},
651+
lintAssemblyInstructions: async (instructionsJson) => {
652+
const lintResult = await this.lintAssemblyInstructions({
653+
assemblyInstructions: instructionsJson,
654+
})
655+
return lintResult.issues
656+
},
657+
},
658+
})
659+
}
660+
555661
/**
556662
* Mint a short-lived bearer token via POST /token.
557663
*

0 commit comments

Comments
 (0)