export type WorkflowReferenceFormat = 'raw' | 'ai-xml' | 'string' | 'multimodal' | (string & {}) export type WorkflowReferencePath = string | string[] export interface WorkflowInputIdentity { id?: string name: string type?: string } export interface WorkflowReferenceOptions { /** * A string selects one field, and a flat string array selects one compound * field path (for example, ['user', 'email'] encodes as user[email]). * Use a nested array when you need multiple path query parameters. */ path?: WorkflowReferencePath | WorkflowReferencePath[] format?: WorkflowReferenceFormat pathFormats?: Record } function normalizePaths(path: WorkflowReferenceOptions['path']): WorkflowReferencePath[] { if (path === undefined) return [] if (typeof path === 'string') return [path] if (Array.isArray(path) && path.every(segment => typeof segment === 'string')) return [path as string[]] return path as WorkflowReferencePath[] } function encodePath(path: WorkflowReferencePath): string { if (typeof path === 'string') return path const [head, ...tail] = path if (!head) throw new Error('Reference path arrays must include at least one segment.') return `${head}${tail.map(segment => `[${segment}]`).join('')}` } function encodeQueryPart(value: string): string { return encodeURIComponent(value) } function buildReferenceUri( protocol: 'input' | 'step', identifier: string, options: WorkflowReferenceOptions = {}, ): string { const params: string[] = [] for (const path of normalizePaths(options.path)) { params.push(`path=${encodeQueryPart(encodePath(path))}`) } if (options.format) params.push(`format=${encodeQueryPart(options.format)}`) for (const [path, format] of Object.entries(options.pathFormats ?? {})) { params.push(`${encodeQueryPart(`format[${path}]`)}=${encodeQueryPart(format)}`) } const query = params.length > 0 ? `?${params.join('&')}` : '' return `${protocol}://${encodeURIComponent(identifier)}${query}` } function wrap(uri: string): string { return `{{${uri}}}` } /** * Reference a workflow input from an action config field. * * File inputs default to `format: 'raw'`. Without it the file never reaches the model: the run * still reports `completed` with `error: null`, and the step simply sees nothing. Pass an explicit * `format` to override. Referencing an input by bare id or name cannot detect the type, so pass the * input object returned by `createWorkflowInput` when the input is a file. */ export function inputRef( variable: WorkflowInputIdentity | string, options: WorkflowReferenceOptions = {}, ): string { const identifier = typeof variable === 'string' ? variable : variable.id || variable.name if (!identifier) throw new Error('inputRef requires a variable id or name.') const isFileInput = typeof variable !== 'string' && variable.type === 'file' const resolved = isFileInput && options.format === undefined ? { ...options, format: 'raw' as const } : options return wrap(buildReferenceUri('input', identifier, resolved)) } export function inputRefByName( name: string, options: WorkflowReferenceOptions = {}, ): string { if (!name) throw new Error('inputRefByName requires a non-empty name.') return inputRef(name, options) } export function stepRef( step: { id: string } | string, options: WorkflowReferenceOptions = {}, ): string { const id = typeof step === 'string' ? step : step.id if (!id) throw new Error('stepRef requires a step id.') return wrap(buildReferenceUri('step', id, options)) }