@olares/lares-core 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -0
- package/brand/identity.d.ts +9 -0
- package/brand/identity.js +82 -0
- package/brand/manifest.js +15 -0
- package/drive/download.js +40 -0
- package/drive/execute.js +143 -0
- package/drive/files-path.js +55 -0
- package/drive/ls.js +126 -0
- package/drive/paths.js +320 -0
- package/drive/tools.js +206 -0
- package/drive/url-download.js +314 -0
- package/files/deliverables.js +32 -0
- package/files/disposition.js +20 -0
- package/files/filename.js +22 -0
- package/files/intake.js +217 -0
- package/files/limits.js +1 -0
- package/files/markdown.js +88 -0
- package/files/mention.js +64 -0
- package/files/preview-cache.js +100 -0
- package/files/preview-groups.js +21 -0
- package/files/preview-workspace.js +342 -0
- package/files/preview.js +305 -0
- package/files/upload-client.js +40 -0
- package/files/upload-http.js +22 -0
- package/files/upload.js +104 -0
- package/i18n/chat-model-switch.js +41 -0
- package/i18n/chat-model.js +40 -0
- package/i18n/files.js +39 -0
- package/i18n/mobile.js +115 -0
- package/i18n/preview.js +57 -0
- package/i18n/search.js +33 -0
- package/i18n/t.js +22 -0
- package/i18n/voice.js +70 -0
- package/icons/mark.js +22 -0
- package/larepass/chat.js +134 -0
- package/larepass/context-provenance.js +65 -0
- package/larepass/host.js +80 -0
- package/larepass/mux.js +194 -0
- package/larepass/questions.js +16 -0
- package/larepass/rpc.js +60 -0
- package/larepass/runtime.js +395 -0
- package/larepass/session-cache.js +104 -0
- package/larepass/settings.js +100 -0
- package/larepass/stage-copy.js +16 -0
- package/larepass/tool-row.js +132 -0
- package/larepass/transcript.js +332 -0
- package/media/ffmpeg-run.js +117 -0
- package/media/router-images.js +63 -0
- package/olares/entrance.js +45 -0
- package/olares/identity.js +190 -0
- package/olares/session-identity.js +48 -0
- package/olares/trusted-host.js +37 -0
- package/package.json +111 -0
- package/router/catalog-sync.js +87 -0
- package/router/catalog.d.ts +15 -0
- package/router/catalog.js +73 -0
- package/router/gateway.d.ts +4 -0
- package/router/gateway.js +21 -0
- package/router/models-client.js +39 -0
- package/router/models.d.ts +16 -0
- package/router/models.js +133 -0
- package/router/search.js +142 -0
- package/router/session-model.js +143 -0
- package/router/shim.js +134 -0
- package/router/stt.js +200 -0
- package/search/client.js +38 -0
- package/search/config.js +98 -0
- package/search/menu.js +22 -0
- package/tools/async.js +34 -0
- package/tools/crypto-uuid.js +26 -0
- package/tools/http.js +123 -0
- package/tools/json-file.js +18 -0
- package/voice/client.js +90 -0
- package/voice/config.js +59 -0
- package/voice/languages.js +29 -0
- package/voice/recorder.js +43 -0
- package/voice/service.js +83 -0
- package/workspace/env.js +13 -0
- package/workspace/home.js +9 -0
- package/workspace/path.js +80 -0
- package/workspace/seed.js +13 -0
- package/workspace/session.js +42 -0
package/drive/paths.js
ADDED
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An Olares files path (`<fileType>/<extend>/<subPath…>`) names one object on
|
|
3
|
+
* the user's files backend. Preview serves it in place; drive_fetch copies it
|
|
4
|
+
* into the session workspace only when a later edit or transcode needs a local
|
|
5
|
+
* file. A produced-file chip is rendered from `presentCall` before the tool
|
|
6
|
+
* runs, so the path in the arguments is the path the UI opens.
|
|
7
|
+
*/
|
|
8
|
+
import { posixBasename as basename, posixExtname as extname, sanitizeFilename } from "../files/filename.js";
|
|
9
|
+
import { isFilesPath, parseFilesPath } from "./files-path.js";
|
|
10
|
+
|
|
11
|
+
export { isFilesPath, parseFilesPath } from "./files-path.js";
|
|
12
|
+
|
|
13
|
+
const DEFAULT_DIRECTORY = "downloads";
|
|
14
|
+
|
|
15
|
+
const DATA_NAMES = new Map([
|
|
16
|
+
["image/jpeg", "download.jpg"],
|
|
17
|
+
["image/jpg", "download.jpg"],
|
|
18
|
+
["image/png", "download.png"],
|
|
19
|
+
["image/gif", "download.gif"],
|
|
20
|
+
["image/webp", "download.webp"],
|
|
21
|
+
["image/bmp", "download.bmp"],
|
|
22
|
+
["video/mp4", "download.mp4"],
|
|
23
|
+
["video/webm", "download.webm"],
|
|
24
|
+
["video/quicktime", "download.mov"],
|
|
25
|
+
["audio/mpeg", "download.mp3"],
|
|
26
|
+
["audio/wav", "download.wav"],
|
|
27
|
+
["audio/ogg", "download.ogg"],
|
|
28
|
+
["audio/mp4", "download.m4a"],
|
|
29
|
+
["audio/aac", "download.aac"],
|
|
30
|
+
["audio/flac", "download.flac"],
|
|
31
|
+
["application/pdf", "download.pdf"],
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
function dataUrlFilename(mediaType) {
|
|
35
|
+
return DATA_NAMES.get(String(mediaType ?? "").split(";", 1)[0].trim().toLowerCase()) ?? null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const DRIVE_TRANSFER_TIMEOUT_MS = 30 * 60 * 1000;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Default media-loading contract. A pasted or requested file must land on the
|
|
42
|
+
* conversation preview without extra user wording. Knowledge / Wise / yt-dlp
|
|
43
|
+
* may land in Olares Files; that path is previewable as-is. Copying into the
|
|
44
|
+
* workspace is only for edit or transcode.
|
|
45
|
+
*/
|
|
46
|
+
export const DRIVE_IMPORT_PROMPT = [
|
|
47
|
+
"When the user pastes, shares, or asks to find, download, show, or play an image, video, audio,",
|
|
48
|
+
"document, or other file, load it into this conversation so it previews under the reply. That is",
|
|
49
|
+
"the default: they do not need to say preview in chat, don't just give a link, or don't open Files.",
|
|
50
|
+
"Never reply with only a hyperlink, and never tell them to open Olares Files instead. Size is never",
|
|
51
|
+
"a reason to skip preview.",
|
|
52
|
+
"Olares files paths (drive/…, sync/…, cache/…, external/…, and the cloud-account namespaces)",
|
|
53
|
+
"are previewable in this app. Any file under drive/Home is included — Documents, Pictures, Music,",
|
|
54
|
+
"Movies, Downloads, Code, and the rest — not only Downloads. After knowledge / Wise / yt-dlp /",
|
|
55
|
+
"torrent lands a file there, workspace_publish that files path immediately and name it in",
|
|
56
|
+
"markdown inline code. Do not copy it into the workspace only to preview it.",
|
|
57
|
+
"Direct public HTTP(S) file URLs and data: URLs use url_fetch; never curl, wget, or shell.",
|
|
58
|
+
"Give destination a meaningful filename with the correct extension whenever the URL path lacks one.",
|
|
59
|
+
"drive_fetch copies one files-backend file into the workspace when a later edit or transcode",
|
|
60
|
+
"(for example ffmpeg_encode) needs it there. url_fetch and ffmpeg_encode already publish their",
|
|
61
|
+
"workspace output. workspace_publish is for a file that already exists in the workspace or on",
|
|
62
|
+
"the files backend that those tools did not produce. Do not call it after drive_fetch, url_fetch,",
|
|
63
|
+
"or ffmpeg_encode. A subagent's own tool calls belong to its child turn: when a subagent reports",
|
|
64
|
+
"a final path, the parent must call workspace_publish on that path before replying.",
|
|
65
|
+
"Never bypass a url_fetch rejection or failure with curl, wget, shell, Python, Node, a browser, or",
|
|
66
|
+
"another network client. A non-public host refusal is a security boundary: report that the URL",
|
|
67
|
+
"cannot be fetched and ask for a public URL or Olares Files path instead of trying it directly.",
|
|
68
|
+
"Use ffmpeg_encode to generate or transcode H.264 video, including burning SRT, VTT, ASS, or SSA",
|
|
69
|
+
"subtitles into an input video. Do not transcode merely so a file can be previewed: webm, mp4,",
|
|
70
|
+
"images, and audio preview as-is from a files path or after url_fetch. Do not run ffmpeg or ffprobe",
|
|
71
|
+
"in the shell for those jobs. Report the encoder and speed from the tool result.",
|
|
72
|
+
"Name every returned workspace or Olares files path in markdown inline code so the UI can open it.",
|
|
73
|
+
"The conversation renders produced images, video, and audio right below the reply, so put those",
|
|
74
|
+
"mentions in the closing sentences and end the reply there: never name a produced file mid-reply",
|
|
75
|
+
"and then continue with more prose, alternatives, or follow-up questions, which would strand the",
|
|
76
|
+
"player far below the path.",
|
|
77
|
+
].join(" ");
|
|
78
|
+
|
|
79
|
+
function parseDestination(value, source) {
|
|
80
|
+
const name = sanitizeFilename(basename(source));
|
|
81
|
+
const raw = String(value ?? "").trim().replace(/\\/g, "/");
|
|
82
|
+
if (raw === "") return `${DEFAULT_DIRECTORY}/${name}`;
|
|
83
|
+
const trailing = raw.endsWith("/");
|
|
84
|
+
const segments = raw.split("/").filter((part) => part !== "");
|
|
85
|
+
if (raw.startsWith("/") || segments.some((part) => part === "." || part === "..")) {
|
|
86
|
+
throw new Error(`"${value}" must be a relative path inside the workspace`);
|
|
87
|
+
}
|
|
88
|
+
if (segments.length === 0) throw new Error("destination must name a path inside the workspace");
|
|
89
|
+
return [...segments, ...(trailing ? [name] : [])].join("/");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* @returns the validated source and the workspace-relative destination.
|
|
94
|
+
* @throws when either argument cannot name one fetchable file.
|
|
95
|
+
*/
|
|
96
|
+
export function resolveFetch(args) {
|
|
97
|
+
const source = parseFilesPath(args?.path);
|
|
98
|
+
return { source, destination: parseDestination(args?.destination, source) };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* The same resolution for the pure presenters, which also run on replayed logs:
|
|
103
|
+
* arguments that no longer resolve render as a plain card instead of throwing.
|
|
104
|
+
*/
|
|
105
|
+
export function describeFetch(args) {
|
|
106
|
+
try {
|
|
107
|
+
return resolveFetch(args);
|
|
108
|
+
} catch {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function parseDataSource(value) {
|
|
114
|
+
const raw = String(value ?? "").trim();
|
|
115
|
+
if (!raw.toLowerCase().startsWith("data:")) return null;
|
|
116
|
+
const comma = raw.indexOf(",");
|
|
117
|
+
if (comma < 5) throw new Error("data URL is missing a payload");
|
|
118
|
+
const meta = raw.slice(5, comma);
|
|
119
|
+
const parts = meta.split(";").map((part) => part.trim()).filter(Boolean);
|
|
120
|
+
const mediaType = (parts.find((part) => part.includes("/")) || "application/octet-stream")
|
|
121
|
+
.split(";", 1)[0]
|
|
122
|
+
.trim()
|
|
123
|
+
.toLowerCase() || "application/octet-stream";
|
|
124
|
+
return { source: "data:", mediaType };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function parseUrlSource(value) {
|
|
128
|
+
const data = parseDataSource(value);
|
|
129
|
+
if (data) return data;
|
|
130
|
+
const raw = String(value ?? "").trim();
|
|
131
|
+
if (raw === "" || raw.length > 4_096 || raw.includes("\0")) {
|
|
132
|
+
throw new Error("url is required and must be at most 4096 characters");
|
|
133
|
+
}
|
|
134
|
+
let url;
|
|
135
|
+
try {
|
|
136
|
+
url = new URL(raw);
|
|
137
|
+
} catch {
|
|
138
|
+
throw new Error(`"${raw}" is not a valid URL`);
|
|
139
|
+
}
|
|
140
|
+
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || !url.hostname) {
|
|
141
|
+
throw new Error("url_fetch accepts only public HTTP(S) URLs without embedded credentials");
|
|
142
|
+
}
|
|
143
|
+
url.hash = "";
|
|
144
|
+
return { source: url.href, mediaType: null };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function urlFilename(source) {
|
|
148
|
+
const url = new URL(source);
|
|
149
|
+
let name;
|
|
150
|
+
try {
|
|
151
|
+
name = decodeURIComponent(basename(url.pathname));
|
|
152
|
+
} catch {
|
|
153
|
+
name = basename(url.pathname);
|
|
154
|
+
}
|
|
155
|
+
const safe = sanitizeFilename(name || "download");
|
|
156
|
+
return extname(safe) === "" ? null : safe;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function resolveUrlFetch(args) {
|
|
160
|
+
const parsed = parseUrlSource(args?.url);
|
|
161
|
+
if (parsed.source === "data:") {
|
|
162
|
+
const inferred = dataUrlFilename(parsed.mediaType);
|
|
163
|
+
if ((args?.destination === undefined || String(args.destination).trim() === "") && inferred === null) {
|
|
164
|
+
throw new Error("destination with a file extension is required when the data URL has no known media type");
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
kind: "data",
|
|
168
|
+
source: "data:",
|
|
169
|
+
destination: parseDestination(args?.destination, inferred ?? "download.bin"),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
const inferred = urlFilename(parsed.source);
|
|
173
|
+
if ((args?.destination === undefined || String(args.destination).trim() === "") && inferred === null) {
|
|
174
|
+
throw new Error("destination with a file extension is required when the URL path has no extension");
|
|
175
|
+
}
|
|
176
|
+
return {
|
|
177
|
+
kind: "http",
|
|
178
|
+
source: parsed.source,
|
|
179
|
+
destination: parseDestination(args?.destination, inferred ?? "download.bin"),
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function describeUrlFetch(args) {
|
|
184
|
+
try {
|
|
185
|
+
return resolveUrlFetch(args);
|
|
186
|
+
} catch {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function resolveWorkspacePublish(args) {
|
|
192
|
+
const raw = String(args?.path ?? "").trim().replace(/\\/g, "/");
|
|
193
|
+
if (isFilesPath(raw)) {
|
|
194
|
+
return { path: parseFilesPath(raw), origin: "files" };
|
|
195
|
+
}
|
|
196
|
+
const segments = raw.split("/");
|
|
197
|
+
if (
|
|
198
|
+
raw === ""
|
|
199
|
+
|| raw.startsWith("/")
|
|
200
|
+
|| raw.endsWith("/")
|
|
201
|
+
|| raw.includes("\0")
|
|
202
|
+
|| segments.some((part) => part === "" || part === "." || part === "..")
|
|
203
|
+
) {
|
|
204
|
+
throw new Error("path must name one existing file inside the workspace");
|
|
205
|
+
}
|
|
206
|
+
return { path: segments.join("/"), origin: "workspace" };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function describeWorkspacePublish(args) {
|
|
210
|
+
try {
|
|
211
|
+
return resolveWorkspacePublish(args);
|
|
212
|
+
} catch {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const PATTERN = "testsrc2";
|
|
218
|
+
const SUBTITLE_EXT = new Set([".ass", ".srt", ".ssa", ".vtt"]);
|
|
219
|
+
const DEFAULT_ENCODE_DIRECTORY = "outputs";
|
|
220
|
+
const PATTERN_RATE = 30;
|
|
221
|
+
|
|
222
|
+
function parseWorkspaceRelative(value, label) {
|
|
223
|
+
const raw = String(value ?? "").trim().replace(/\\/g, "/");
|
|
224
|
+
const segments = raw.split("/");
|
|
225
|
+
if (
|
|
226
|
+
raw === ""
|
|
227
|
+
|| raw.startsWith("/")
|
|
228
|
+
|| raw.endsWith("/")
|
|
229
|
+
|| raw.includes("\0")
|
|
230
|
+
|| segments.some((part) => part === "" || part === "." || part === "..")
|
|
231
|
+
) {
|
|
232
|
+
throw new Error(`${label} must name one file inside the workspace`);
|
|
233
|
+
}
|
|
234
|
+
return segments.join("/");
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function evenDim(value, fallback, label) {
|
|
238
|
+
if (value === undefined || value === null || value === "") return fallback;
|
|
239
|
+
const n = Number(value);
|
|
240
|
+
if (!Number.isInteger(n) || n < 16 || n > 3840) {
|
|
241
|
+
throw new Error(`${label} must be an integer from 16 to 3840`);
|
|
242
|
+
}
|
|
243
|
+
return n - (n % 2);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function parseDuration(value, required) {
|
|
247
|
+
if (value === undefined || value === null || value === "") {
|
|
248
|
+
if (required) throw new Error("duration is required when generating a pattern");
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
const n = Number(value);
|
|
252
|
+
if (!Number.isFinite(n) || n < 0.1 || n > 600) {
|
|
253
|
+
throw new Error("duration must be between 0.1 and 600 seconds");
|
|
254
|
+
}
|
|
255
|
+
return n;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function lavfiSource(width, height) {
|
|
259
|
+
return `${PATTERN}=size=${width}x${height}:rate=${PATTERN_RATE}`;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function defaultEncodeDestination(args, input, pattern) {
|
|
263
|
+
if (args?.destination !== undefined && String(args.destination).trim() !== "") {
|
|
264
|
+
return parseWorkspaceRelative(args.destination, "destination");
|
|
265
|
+
}
|
|
266
|
+
if (input) {
|
|
267
|
+
const name = basename(input);
|
|
268
|
+
const stem = extname(name) === "" ? name : name.slice(0, -extname(name).length);
|
|
269
|
+
return `${DEFAULT_ENCODE_DIRECTORY}/${stem}.mp4`;
|
|
270
|
+
}
|
|
271
|
+
return `${DEFAULT_ENCODE_DIRECTORY}/${pattern}.mp4`;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export function resolveFfmpegEncode(args) {
|
|
275
|
+
const inputRaw = args?.input === undefined || args?.input === null || String(args.input).trim() === ""
|
|
276
|
+
? ""
|
|
277
|
+
: parseWorkspaceRelative(args.input, "input");
|
|
278
|
+
const subtitlesRaw = args?.subtitles === undefined
|
|
279
|
+
|| args?.subtitles === null
|
|
280
|
+
|| String(args.subtitles).trim() === ""
|
|
281
|
+
? ""
|
|
282
|
+
: parseWorkspaceRelative(args.subtitles, "subtitles");
|
|
283
|
+
const patternRaw = String(args?.pattern ?? "").trim().toLowerCase();
|
|
284
|
+
const pattern = patternRaw === "" ? "" : patternRaw;
|
|
285
|
+
if (Boolean(inputRaw) === Boolean(pattern)) {
|
|
286
|
+
throw new Error("ffmpeg_encode needs exactly one of input or pattern");
|
|
287
|
+
}
|
|
288
|
+
if (pattern && pattern !== PATTERN) {
|
|
289
|
+
throw new Error(`pattern must be ${PATTERN}`);
|
|
290
|
+
}
|
|
291
|
+
if (subtitlesRaw && !inputRaw) {
|
|
292
|
+
throw new Error("subtitles require an input video");
|
|
293
|
+
}
|
|
294
|
+
if (subtitlesRaw && !SUBTITLE_EXT.has(extname(subtitlesRaw).toLowerCase())) {
|
|
295
|
+
throw new Error("subtitles must end in .ass, .srt, .ssa, or .vtt");
|
|
296
|
+
}
|
|
297
|
+
const width = evenDim(args?.width, 1280, "width");
|
|
298
|
+
const height = evenDim(args?.height, 720, "height");
|
|
299
|
+
const destination = defaultEncodeDestination(args, inputRaw, pattern);
|
|
300
|
+
if (extname(destination).toLowerCase() !== ".mp4") {
|
|
301
|
+
throw new Error("destination must end in .mp4");
|
|
302
|
+
}
|
|
303
|
+
return {
|
|
304
|
+
input: inputRaw || null,
|
|
305
|
+
subtitles: subtitlesRaw || null,
|
|
306
|
+
pattern: pattern || null,
|
|
307
|
+
lavfi: pattern ? lavfiSource(width, height) : null,
|
|
308
|
+
duration: parseDuration(args?.duration, Boolean(pattern)),
|
|
309
|
+
destination,
|
|
310
|
+
overwrite: args?.overwrite === true,
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export function describeFfmpegEncode(args) {
|
|
315
|
+
try {
|
|
316
|
+
return resolveFfmpegEncode(args);
|
|
317
|
+
} catch {
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
}
|
package/drive/tools.js
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import {
|
|
2
|
+
executeDriveFetch,
|
|
3
|
+
executeFfmpegEncode,
|
|
4
|
+
executeUrlFetch,
|
|
5
|
+
executeWorkspacePublish,
|
|
6
|
+
presentDriveFetch,
|
|
7
|
+
presentFfmpegEncode,
|
|
8
|
+
presentUrlFetch,
|
|
9
|
+
presentWorkspacePublish,
|
|
10
|
+
} from "./execute.js";
|
|
11
|
+
import { DRIVE_TRANSFER_TIMEOUT_MS } from "./paths.js";
|
|
12
|
+
|
|
13
|
+
export function driveFetchDefinition(download) {
|
|
14
|
+
return {
|
|
15
|
+
name: "drive_fetch",
|
|
16
|
+
description:
|
|
17
|
+
"Copy one file from the Olares files backend (drive/…, sync/…, external/…, cloud accounts) into"
|
|
18
|
+
+ " the session workspace when a later edit or transcode needs it there. Preview of a files path"
|
|
19
|
+
+ " does not require this — workspace_publish the drive/… path instead. Size is not a reason to skip.",
|
|
20
|
+
parameters: {
|
|
21
|
+
path: {
|
|
22
|
+
type: "string",
|
|
23
|
+
required: true,
|
|
24
|
+
description: "Olares files path of a single file, e.g. drive/Home/Documents/clip.webm.",
|
|
25
|
+
},
|
|
26
|
+
destination: {
|
|
27
|
+
type: "string",
|
|
28
|
+
description:
|
|
29
|
+
"Workspace-relative target. A trailing slash names a directory to fetch into;"
|
|
30
|
+
+ " omit to use downloads/ and the source file name.",
|
|
31
|
+
},
|
|
32
|
+
overwrite: {
|
|
33
|
+
type: "boolean",
|
|
34
|
+
description: "Replace an existing destination file. Without it an existing path fails the call.",
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
output: {
|
|
38
|
+
schema: {
|
|
39
|
+
type: "object",
|
|
40
|
+
additionalProperties: false,
|
|
41
|
+
properties: {
|
|
42
|
+
path: { type: "string", required: true },
|
|
43
|
+
bytes: { type: "integer", required: true },
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
render: (args, value) => [{
|
|
47
|
+
type: "text",
|
|
48
|
+
text: `Fetched ${args.path} into ${value.path} (${value.bytes} bytes).`,
|
|
49
|
+
}],
|
|
50
|
+
},
|
|
51
|
+
timeoutMs: DRIVE_TRANSFER_TIMEOUT_MS,
|
|
52
|
+
execute: (args, exec) => executeDriveFetch(args, exec, download),
|
|
53
|
+
presentCall: presentDriveFetch,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function urlFetchDefinition(download) {
|
|
58
|
+
return {
|
|
59
|
+
name: "url_fetch",
|
|
60
|
+
description:
|
|
61
|
+
"Download one public HTTP(S) URL, or a data: URL / base64 payload, into the session workspace"
|
|
62
|
+
+ " and publish it for preview. This is the default for a pasted or requested online file; do"
|
|
63
|
+
+ " not answer with only the URL. Use after web search, or when olares-cli router / FlowStudio"
|
|
64
|
+
+ " returns a URL or inline bytes. Size is not a reason to skip.",
|
|
65
|
+
parameters: {
|
|
66
|
+
url: {
|
|
67
|
+
type: "string",
|
|
68
|
+
required: true,
|
|
69
|
+
description: "Direct public HTTP(S) file URL, or a data: URL (wrap raw base64 as data:<mediaType>;base64,...). Private/internal hosts and unsafe redirects are refused.",
|
|
70
|
+
},
|
|
71
|
+
destination: {
|
|
72
|
+
type: "string",
|
|
73
|
+
description:
|
|
74
|
+
"Workspace-relative target path. Required with a meaningful extension when the URL path"
|
|
75
|
+
+ " has none, e.g. downloads/portrait.jpg.",
|
|
76
|
+
},
|
|
77
|
+
overwrite: {
|
|
78
|
+
type: "boolean",
|
|
79
|
+
description: "Replace an existing destination file. Without it an existing path fails the call.",
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
output: {
|
|
83
|
+
schema: {
|
|
84
|
+
type: "object",
|
|
85
|
+
additionalProperties: false,
|
|
86
|
+
properties: {
|
|
87
|
+
path: { type: "string", required: true },
|
|
88
|
+
bytes: { type: "integer", required: true },
|
|
89
|
+
mediaType: { type: "string", required: true },
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
render: (_args, value) => [{
|
|
93
|
+
type: "text",
|
|
94
|
+
text: `Downloaded ${value.path} (${value.bytes} bytes, ${value.mediaType}).`,
|
|
95
|
+
}],
|
|
96
|
+
},
|
|
97
|
+
timeoutMs: DRIVE_TRANSFER_TIMEOUT_MS,
|
|
98
|
+
execute: (args, exec) => executeUrlFetch(args, exec, download),
|
|
99
|
+
presentCall: presentUrlFetch,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function workspacePublishDefinition(statFile) {
|
|
104
|
+
return {
|
|
105
|
+
name: "workspace_publish",
|
|
106
|
+
description:
|
|
107
|
+
"Publish one existing file as a produced artifact so the UI can preview it. Path is either"
|
|
108
|
+
+ " workspace-relative or an Olares files path (drive/Home/Documents/clip.webm). After knowledge"
|
|
109
|
+
+ " / Wise / yt-dlp lands in Files, call this immediately on that drive/… path — the user does"
|
|
110
|
+
+ " not need to request preview. Also use after a skill writes a local file. Do not use it after"
|
|
111
|
+
+ " drive_fetch, url_fetch, or ffmpeg_encode, which already publish.",
|
|
112
|
+
parameters: {
|
|
113
|
+
path: {
|
|
114
|
+
type: "string",
|
|
115
|
+
required: true,
|
|
116
|
+
description:
|
|
117
|
+
"Workspace-relative path of one existing regular file, or an Olares files path such as"
|
|
118
|
+
+ " drive/Home/Documents/clip.webm.",
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
output: {
|
|
122
|
+
schema: {
|
|
123
|
+
type: "object",
|
|
124
|
+
additionalProperties: false,
|
|
125
|
+
properties: {
|
|
126
|
+
path: { type: "string", required: true },
|
|
127
|
+
bytes: { type: "integer", required: true },
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
render: (_args, value) => [{
|
|
131
|
+
type: "text",
|
|
132
|
+
text: `Published ${value.path} (${value.bytes} bytes).`,
|
|
133
|
+
}],
|
|
134
|
+
},
|
|
135
|
+
execute: (args, exec) => executeWorkspacePublish(args, exec, statFile),
|
|
136
|
+
presentCall: presentWorkspacePublish,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function ffmpegEncodeDefinition(encode) {
|
|
141
|
+
return {
|
|
142
|
+
name: "ffmpeg_encode",
|
|
143
|
+
description:
|
|
144
|
+
"Generate, transcode, or burn subtitles into one H.264 video in the session workspace and publish"
|
|
145
|
+
+ " it for preview."
|
|
146
|
+
+ " Use this instead of shell ffmpeg for ordinary encodes. The host uses libx264;"
|
|
147
|
+
+ " do not pass encoder flags.",
|
|
148
|
+
parameters: {
|
|
149
|
+
destination: {
|
|
150
|
+
type: "string",
|
|
151
|
+
description:
|
|
152
|
+
"Workspace-relative browser-previewable .mp4 output. Omit to use outputs/<name>.mp4.",
|
|
153
|
+
},
|
|
154
|
+
input: {
|
|
155
|
+
type: "string",
|
|
156
|
+
description: "Workspace-relative video to transcode. Mutually exclusive with pattern.",
|
|
157
|
+
},
|
|
158
|
+
subtitles: {
|
|
159
|
+
type: "string",
|
|
160
|
+
description:
|
|
161
|
+
"Workspace-relative .srt, .vtt, .ass, or .ssa file to burn into input."
|
|
162
|
+
+ " Requires input; the host supplies a readable CJK-capable style.",
|
|
163
|
+
},
|
|
164
|
+
pattern: {
|
|
165
|
+
type: "string",
|
|
166
|
+
description: "Synthetic source testsrc2. Mutually exclusive with input.",
|
|
167
|
+
},
|
|
168
|
+
duration: {
|
|
169
|
+
type: "number",
|
|
170
|
+
description: "Seconds to write. Required for pattern; optional trim for input.",
|
|
171
|
+
},
|
|
172
|
+
width: {
|
|
173
|
+
type: "integer",
|
|
174
|
+
description: "Pattern frame width. Defaults to 1280.",
|
|
175
|
+
},
|
|
176
|
+
height: {
|
|
177
|
+
type: "integer",
|
|
178
|
+
description: "Pattern frame height. Defaults to 720.",
|
|
179
|
+
},
|
|
180
|
+
overwrite: {
|
|
181
|
+
type: "boolean",
|
|
182
|
+
description: "Replace an existing destination file. Without it an existing path fails the call.",
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
output: {
|
|
186
|
+
schema: {
|
|
187
|
+
type: "object",
|
|
188
|
+
additionalProperties: false,
|
|
189
|
+
properties: {
|
|
190
|
+
path: { type: "string", required: true },
|
|
191
|
+
bytes: { type: "integer", required: true },
|
|
192
|
+
encoder: { type: "string", required: true },
|
|
193
|
+
speed: { type: "string", required: true },
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
render: (_args, value) => [{
|
|
197
|
+
type: "text",
|
|
198
|
+
text: `Encoded ${value.path} with ${value.encoder}`
|
|
199
|
+
+ `${value.speed ? ` at ${value.speed}` : ""} (${value.bytes} bytes).`,
|
|
200
|
+
}],
|
|
201
|
+
},
|
|
202
|
+
timeoutMs: DRIVE_TRANSFER_TIMEOUT_MS,
|
|
203
|
+
execute: (args, exec) => executeFfmpegEncode(args, exec, encode),
|
|
204
|
+
presentCall: presentFfmpegEncode,
|
|
205
|
+
};
|
|
206
|
+
}
|