From 622dfdbfb09b9a7b8418c2b79dab458b0d3f73eb Mon Sep 17 00:00:00 2001 From: Andrii Arsenin Date: Tue, 15 Sep 2026 10:37:22 +0300 Subject: [PATCH] feat(scenario): let context.downloadFile save data: URLs as run artifacts Scenario steps can now persist in-script generated content (e.g. an obtained VC's JWT) as a run file via context.downloadFile('data:...'), without needing a network fetch. Bumps to 1.8.0. Co-Authored-By: Claude Sonnet 5 --- CHANGELOG.md | 12 ++++++++++++ docs/scenario.md | 2 ++ package.json | 2 +- server/src/common/file-downloader.ts | 16 ++++++++++++++++ 4 files changed, 31 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7eb1852..8998ae4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ All notable changes to this project will be documented in this file. +## 1.8.0 - 2026-09-15 + +Changes since 1.7.1: + +### Added + +- `context.downloadFile()` now accepts `data:` URIs, so scenario steps can save in-script generated content (e.g. a credential JWT) as a run artifact without a network fetch. + +### Changed + +- Documented the full scenario step `context` file API (`getScenarioFiles`, `downloadFile`) in `docs/scenario.md`. + ## 1.7.1 - 2026-04-21 Changes since 1.7.0: diff --git a/docs/scenario.md b/docs/scenario.md index e08f7a9..b631d01 100644 --- a/docs/scenario.md +++ b/docs/scenario.md @@ -36,6 +36,8 @@ Available in scope: - `context.dumpDom(selector?)`: simplified DOM snapshot - `context.log(...args)`, `context.warn(...args)`, `context.error(...args)`: structured step logs - `context.runSnippet(name, ...args)`: execute stored snippet code with the same context +- `context.getScenarioFiles(opts?)`: list files uploaded to the scenario (`limit`, `offset`) +- `context.downloadFile(url, opts?)`: fetch `url` and save the result as a run artifact (requires a real scenario run — throws when invoked ad hoc, e.g. via the `exec_code` MCP tool). `opts` may include `method`, `headers`, `body`, `filename`. `url` also accepts a `data:` URI (`data:;base64,` or `data:,`) to save content generated in-script (e.g. a credential JWT) directly, without an actual network fetch. - `console`: proxied to run logs (`log`, `warn`, `error`, etc.) `validateCode` additionally receives `result` in scope, which is the value returned by `execCode`. diff --git a/package.json b/package.json index 16aba2e..c6007fe 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "liqa", - "version": "1.7.1", + "version": "1.8.0", "private": true, "workspaces": [ "server", diff --git a/server/src/common/file-downloader.ts b/server/src/common/file-downloader.ts index eeb11aa..0ab9dc2 100644 --- a/server/src/common/file-downloader.ts +++ b/server/src/common/file-downloader.ts @@ -5,6 +5,18 @@ import { URL } from "url"; const MAX_FILE_SIZE = 100 * 1024 * 1024; // 100 MB const TIMEOUT_MS = 30000; // 30 seconds +function decodeDataUrl(urlStr: string): Buffer { + const match = /^data:([^,]*),(.*)$/s.exec(urlStr); + if (!match) { + throw new Error("Malformed data: URL"); + } + const [, meta, data] = match; + if (meta.endsWith(";base64")) { + return Buffer.from(data, "base64"); + } + return Buffer.from(decodeURIComponent(data), "utf-8"); +} + export async function downloadFile( urlStr: string, options?: { @@ -13,6 +25,10 @@ export async function downloadFile( body?: string; }, ): Promise { + if (urlStr.startsWith("data:")) { + return decodeDataUrl(urlStr); + } + const url = new URL(urlStr); const protocol = url.protocol === "https:" ? https : http; const method = options?.method ?? "GET";