6bf8bebf51
CI / Test and Build (push) Failing after 1s
CI / Migrate Dev DB (push) Has been skipped
CI / Migrate DB (push) Has been skipped
CodeQL / Analyze actions (push) Has been cancelled
CodeQL / Analyze javascript-typescript (push) Has been cancelled
CI / Detect Version (push) Has been cancelled
CI / Detect Desktop Changes (push) Has been cancelled
CI / Build AMD64 (blacksmith-2vcpu-ubuntu-2404, ./docker/cron.Dockerfile, ubuntu-latest, ghcr.io/simstudioai/cron) (push) Has been cancelled
CI / Build AMD64 (blacksmith-2vcpu-ubuntu-2404, ./docker/db.Dockerfile, ECR_MIGRATIONS, ubuntu-latest, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (blacksmith-4vcpu-ubuntu-2404, ./docker/pii.Dockerfile, ECR_PII, ubuntu-latest, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (blacksmith-4vcpu-ubuntu-2404, ./docker/realtime.Dockerfile, ECR_REALTIME, ubuntu-latest, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build AMD64 (blacksmith-8vcpu-ubuntu-2404, ./docker/app.Dockerfile, ECR_APP, linux-x64-8-core, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/cron.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/cron) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/db.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/pii.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/realtime.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-8vcpu-ubuntu-2404-arm, ./docker/app.Dockerfile, linux-arm64-8-core, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
Helm Chart / Lint, test, and validate chart (push) Has been cancelled
Helm Chart / Chart version bumped (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
CI / Build Dev ECR (blacksmith-8vcpu-ubuntu-2404, ./docker/app.Dockerfile, ECR_APP, linux-x64-8-core) (push) Has been cancelled
CI / Promote Images (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/cron) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build Dev ECR (blacksmith-2vcpu-ubuntu-2404, ./docker/db.Dockerfile, ECR_MIGRATIONS, ubuntu-latest) (push) Has been cancelled
CI / Build Dev ECR (blacksmith-4vcpu-ubuntu-2404, ./docker/pii.Dockerfile, ECR_PII, ubuntu-latest) (push) Has been cancelled
CI / Build Dev ECR (blacksmith-4vcpu-ubuntu-2404, ./docker/realtime.Dockerfile, ECR_REALTIME, ubuntu-latest) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Check Desktop Signing Secrets (push) Has been cancelled
CI / Desktop Release (push) Has been cancelled
CI / Create Desktop Prerelease (push) Has been cancelled
CI / Desktop Prerelease Build (push) Has been cancelled
CI / Publish Desktop Prerelease (push) Has been cancelled
CI / Prune Desktop Prereleases (push) Has been cancelled
Helm Chart / Install on kind and run helm test (push) Has been cancelled
152 lines
5.1 KiB
TypeScript
152 lines
5.1 KiB
TypeScript
import { isPlainRecord } from '@sim/utils/object'
|
|
import type { ResolvedSecretInputPath } from '@/executor/utils/resolved-secret-trace-registry'
|
|
|
|
interface ModelBoundFileInputOptions {
|
|
includeInlineBase64?: boolean
|
|
includeName?: boolean
|
|
parseSerializedFile?: boolean
|
|
}
|
|
|
|
interface PreferredModelBoundFileInputPathOptions extends ModelBoundFileInputOptions {
|
|
file: unknown
|
|
filePath: unknown
|
|
fileInputPath: ResolvedSecretInputPath
|
|
filePathInputPath: ResolvedSecretInputPath
|
|
prefer: 'file' | 'path'
|
|
}
|
|
|
|
function selectFileRecordInputPaths(
|
|
input: Record<string, unknown>,
|
|
rootPath: ResolvedSecretInputPath,
|
|
options: ModelBoundFileInputOptions
|
|
): ResolvedSecretInputPath[] {
|
|
const paths: ResolvedSecretInputPath[] = []
|
|
const hasSource = Boolean(input.base64 || input.key || input.path || input.url)
|
|
|
|
if (options.includeInlineBase64 && input.base64) {
|
|
paths.push([...rootPath, 'base64'])
|
|
}
|
|
if (typeof input.path === 'string' && isInlineDataUrl(input.path)) {
|
|
paths.push([...rootPath, 'path'])
|
|
}
|
|
if (typeof input.url === 'string' && isInlineDataUrl(input.url)) {
|
|
paths.push([...rootPath, 'url'])
|
|
}
|
|
|
|
if (hasSource && options.includeName && input.name !== undefined) {
|
|
paths.push([...rootPath, 'name'])
|
|
}
|
|
return paths
|
|
}
|
|
|
|
function isInlineDataUrl(value: string): boolean {
|
|
return /^data:[^,]*;base64,/iu.test(value.trim())
|
|
}
|
|
|
|
/**
|
|
* Selects inline file content and explicitly requested model-visible metadata. Storage keys,
|
|
* paths, and remote URLs are locators: provenance on a locator says nothing about the fetched
|
|
* bytes, which are authorized independently at the owning file boundary.
|
|
*/
|
|
export function selectModelBoundFileInputPaths(
|
|
input: unknown,
|
|
rootPath: ResolvedSecretInputPath,
|
|
options: ModelBoundFileInputOptions = {}
|
|
): ResolvedSecretInputPath[] {
|
|
if (Array.isArray(input)) {
|
|
return input.flatMap((entry, index) =>
|
|
selectModelBoundFileInputPaths(entry, [...rootPath, String(index)], options)
|
|
)
|
|
}
|
|
if (typeof input === 'string') {
|
|
if (options.parseSerializedFile) {
|
|
try {
|
|
const parsed = JSON.parse(input)
|
|
return selectModelBoundFileInputPaths(parsed, rootPath, {
|
|
...options,
|
|
parseSerializedFile: false,
|
|
}).length > 0
|
|
? [rootPath]
|
|
: []
|
|
} catch {
|
|
return isInlineDataUrl(input) ? [rootPath] : []
|
|
}
|
|
}
|
|
return isInlineDataUrl(input) ? [rootPath] : []
|
|
}
|
|
if (!isPlainRecord(input)) return []
|
|
return selectFileRecordInputPaths(input, rootPath, options)
|
|
}
|
|
|
|
function selectFilePath(input: unknown): string | undefined {
|
|
if (typeof input !== 'string' || input === 'null' || input.trim() === '') return undefined
|
|
return input.trim()
|
|
}
|
|
|
|
/** Mirrors file-vs-path precedence while selecting exact resolver input paths. */
|
|
export function selectPreferredModelBoundFileInputPaths(
|
|
options: PreferredModelBoundFileInputPathOptions
|
|
): ResolvedSecretInputPath[] {
|
|
const hasFile = isPlainRecord(options.file)
|
|
const filePath = selectFilePath(options.filePath)
|
|
|
|
if (options.prefer === 'file' && hasFile) {
|
|
return selectModelBoundFileInputPaths(options.file, options.fileInputPath, options)
|
|
}
|
|
if (filePath !== undefined) {
|
|
return isInlineDataUrl(filePath) ? [options.filePathInputPath] : []
|
|
}
|
|
if (options.prefer === 'path' && hasFile) {
|
|
return selectModelBoundFileInputPaths(options.file, options.fileInputPath, options)
|
|
}
|
|
return []
|
|
}
|
|
|
|
/** Selects only file names that are serialized into model-visible requests. */
|
|
export function selectModelVisibleFileNames(input: unknown): unknown {
|
|
if (Array.isArray(input)) return input.map(selectModelVisibleFileNames)
|
|
if (!isPlainRecord(input)) return undefined
|
|
return input.name !== undefined ? { name: input.name } : {}
|
|
}
|
|
|
|
function haveExactKeys(value: Record<string, unknown>, expected: readonly string[]): boolean {
|
|
const keys = Object.keys(value)
|
|
return keys.length === expected.length && expected.every((key) => Object.hasOwn(value, key))
|
|
}
|
|
|
|
/** Reapplies projected file names while preserving every locator and inline-content field. */
|
|
export function applyProjectedModelVisibleFileNames(
|
|
original: unknown,
|
|
projected: unknown
|
|
): unknown {
|
|
if (Array.isArray(original)) {
|
|
if (!Array.isArray(projected) || projected.length !== original.length) {
|
|
throw new Error('Projected file names do not match the original files')
|
|
}
|
|
return original.map((entry, index) =>
|
|
applyProjectedModelVisibleFileNames(entry, projected[index])
|
|
)
|
|
}
|
|
|
|
if (!isPlainRecord(original)) {
|
|
if (projected !== undefined) {
|
|
throw new Error('Projected file name does not match the original file')
|
|
}
|
|
return original
|
|
}
|
|
if (!isPlainRecord(projected)) {
|
|
throw new Error('Projected file name is invalid')
|
|
}
|
|
|
|
if (original.name === undefined) {
|
|
if (!haveExactKeys(projected, [])) {
|
|
throw new Error('Projected file name does not match the original file')
|
|
}
|
|
return { ...original }
|
|
}
|
|
if (!haveExactKeys(projected, ['name']) || typeof projected.name !== 'string') {
|
|
throw new Error('Projected file name is invalid')
|
|
}
|
|
return { ...original, name: projected.name }
|
|
}
|