@ixo/oracle-runtime-workers 0.36.0 → 0.36.1
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ixo/oracle-runtime-workers",
|
|
3
|
-
"version": "0.36.
|
|
3
|
+
"version": "0.36.1",
|
|
4
4
|
"description": "QiForge oracle runtime for Cloudflare Workers — Durable Object per user (SQLite in WASM over DO storage), Matrix E2EE gateway Durable Object, Hono HTTP/SSE shell.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"publishConfig": {
|
|
@@ -67,9 +67,9 @@
|
|
|
67
67
|
"protobufjs": "^7.6.5",
|
|
68
68
|
"@ixo/topic-protocol": "1.0.0-rc.8",
|
|
69
69
|
"survey-core": "2.3.11",
|
|
70
|
-
"@ixo/ucan": "2.3.0",
|
|
71
70
|
"@ixo/common": "1.7.2",
|
|
72
|
-
"@ixo/ixo-transaction": "0.2.0"
|
|
71
|
+
"@ixo/ixo-transaction": "0.2.0",
|
|
72
|
+
"@ixo/ucan": "2.3.0"
|
|
73
73
|
},
|
|
74
74
|
"devDependencies": {
|
|
75
75
|
"@cloudflare/vitest-pool-workers": "^0.22.0",
|
|
@@ -443,9 +443,7 @@ export async function prepareAttachments(
|
|
|
443
443
|
}> = [];
|
|
444
444
|
for (const attachment of params.attachments) {
|
|
445
445
|
const kind = classifyAttachment(attachment);
|
|
446
|
-
const strategy = routeAttachment(kind, params.caps
|
|
447
|
-
channelFile: Boolean(attachment.channelRef),
|
|
448
|
-
});
|
|
446
|
+
const strategy = routeAttachment(kind, params.caps);
|
|
449
447
|
deps.logger.log(
|
|
450
448
|
`[attachments] "${attachment.filename}" (${attachment.mimetype}) kind=${kind} model=${params.model} → ${strategy}`,
|
|
451
449
|
);
|
package/src/attachments/route.ts
CHANGED
|
@@ -18,22 +18,11 @@ export type AttachmentStrategy =
|
|
|
18
18
|
/**
|
|
19
19
|
* Decide, for one attachment, how to get it to the selected model — the single
|
|
20
20
|
* cost-aware, capability-aware routing rule. Pure and total.
|
|
21
|
-
*
|
|
22
|
-
* `channelFile`: a file from a chat channel (WhatsApp) never goes as raw
|
|
23
|
-
* bytes. Its message is replayed to the model on every later turn and kept in
|
|
24
|
-
* the user's checkpoint, so a native PDF or photo there is base64 the size of
|
|
25
|
-
* the file, counted as tokens at chars/4 and held several times over in the
|
|
26
|
-
* object's 128 MB isolate (a 600 KB PDF reset the isolate on every attempt).
|
|
27
|
-
* The helper model turns it into text instead; tools still read the file
|
|
28
|
-
* itself through `ctx.session.channel.attachments`.
|
|
29
21
|
*/
|
|
30
22
|
export function routeAttachment(
|
|
31
23
|
kind: AttachmentKind,
|
|
32
24
|
caps: ModelInputCapabilities,
|
|
33
|
-
options: { channelFile?: boolean } = {},
|
|
34
25
|
): AttachmentStrategy {
|
|
35
|
-
if (options.channelFile)
|
|
36
|
-
return kind === 'text' ? 'parse-local' : 'model-extract';
|
|
37
26
|
switch (kind) {
|
|
38
27
|
case 'text':
|
|
39
28
|
return 'parse-local';
|
package/src/attachments/view.ts
CHANGED
|
@@ -129,11 +129,7 @@ export async function viewAttachment(
|
|
|
129
129
|
): Promise<AttachmentView> {
|
|
130
130
|
const attachment = toInput(meta);
|
|
131
131
|
const kind = classifyAttachment(attachment);
|
|
132
|
-
|
|
133
|
-
// them in the history again (see `routeAttachment`).
|
|
134
|
-
const strategy = routeAttachment(kind, deps.caps, {
|
|
135
|
-
channelFile: Boolean(attachment.channelRef),
|
|
136
|
-
});
|
|
132
|
+
const strategy = routeAttachment(kind, deps.caps);
|
|
137
133
|
if (strategy === 'send-native') {
|
|
138
134
|
const { bytes, mimetype, sniffed } = await loadAttachmentBytes(
|
|
139
135
|
attachment,
|
package/src/delivery/prompt.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SENDABLE_FILE_TYPES } from '../plugins/sandbox/send-file';
|
|
1
2
|
import type { DeliveryProfile } from './types';
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -41,7 +42,7 @@ export function renderSurfaceSection(
|
|
|
41
42
|
`- Ordinary answers stay in chat, including explanations, comparisons, short plans and step-by-step instructions. No headings or horizontal rules. Lists up to ${limits.maxListItems} items, code up to ${limits.maxCodeLines} lines and **bold** for the key fact are fine in chat. Write a small comparison as one short line per item, not a table.`,
|
|
42
43
|
...(files
|
|
43
44
|
? [
|
|
44
|
-
`- Deliver every file meant for the user
|
|
45
|
+
`- Deliver every file meant for the user with \`send_file\` (${SENDABLE_FILE_TYPES} only): ${deliversAttachments(surface) ? 'the user receives the file itself here, as an attachment' : 'a link to the file is posted here for you'}. When the user asks for a file in a particular format, build it yourself in the sandbox and send it this way. Do not send a file you downloaded from the internet unless the user asked for that exact file. Never paste a download link, and do not describe how to open or download it. Do not send helper scripts or intermediate files. Call it once per file, after the sandbox call that saved the file has finished.`,
|
|
45
46
|
]
|
|
46
47
|
: []),
|
|
47
48
|
artifacts
|
package/src/delivery/types.ts
CHANGED
|
@@ -93,7 +93,7 @@ export interface ReplyFile {
|
|
|
93
93
|
url: string;
|
|
94
94
|
/** Sanitised, from the artefact path, with its extension. */
|
|
95
95
|
filename: string;
|
|
96
|
-
/** From the extension's allow-list
|
|
96
|
+
/** From the extension's allow-list: `send_file` refuses any other type. */
|
|
97
97
|
mimetype: string;
|
|
98
98
|
/** Size, when the store reported it. */
|
|
99
99
|
bytes?: number;
|
|
@@ -39,17 +39,18 @@ export const SendFileArgs = z.object({
|
|
|
39
39
|
.describe('A short line shown with the file, at most 1,024 characters.'),
|
|
40
40
|
});
|
|
41
41
|
|
|
42
|
-
/**
|
|
42
|
+
/**
|
|
43
|
+
* What `send_file` sends, by extension, with its MIME type: the types a chat
|
|
44
|
+
* gateway delivers. A gateway refuses any other (the legacy `.doc`, `.xls`
|
|
45
|
+
* and `.ppt` included), so the tool refuses it before signing anything.
|
|
46
|
+
*/
|
|
43
47
|
const FILE_MIME: ReadonlyMap<string, string> = new Map([
|
|
44
48
|
['pdf', 'application/pdf'],
|
|
45
|
-
['doc', 'application/msword'],
|
|
46
49
|
[
|
|
47
50
|
'docx',
|
|
48
51
|
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
49
52
|
],
|
|
50
|
-
['xls', 'application/vnd.ms-excel'],
|
|
51
53
|
['xlsx', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'],
|
|
52
|
-
['ppt', 'application/vnd.ms-powerpoint'],
|
|
53
54
|
[
|
|
54
55
|
'pptx',
|
|
55
56
|
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
|
@@ -63,11 +64,15 @@ const FILE_MIME: ReadonlyMap<string, string> = new Map([
|
|
|
63
64
|
['csv', 'text/csv'],
|
|
64
65
|
]);
|
|
65
66
|
|
|
66
|
-
/** The
|
|
67
|
-
export
|
|
67
|
+
/** The types in `FILE_MIME`, as the tool description and the prompt name them. */
|
|
68
|
+
export const SENDABLE_FILE_TYPES =
|
|
69
|
+
'PDF, Word (.docx), Excel (.xlsx), PowerPoint (.pptx), text, CSV, Markdown, PNG or JPEG';
|
|
70
|
+
|
|
71
|
+
/** The MIME type of a filename `send_file` may send; `undefined` for any other. */
|
|
72
|
+
export function fileMimetype(filename: string): string | undefined {
|
|
68
73
|
const dot = filename.lastIndexOf('.');
|
|
69
74
|
const extension = dot >= 0 ? filename.slice(dot + 1).toLowerCase() : '';
|
|
70
|
-
return FILE_MIME.get(extension)
|
|
75
|
+
return FILE_MIME.get(extension);
|
|
71
76
|
}
|
|
72
77
|
|
|
73
78
|
/**
|
|
@@ -191,6 +196,10 @@ function notFoundError(path: string): string {
|
|
|
191
196
|
return `No stored file at \`${path}\`. A file is stored once the sandbox call that wrote it under \`/workspace/data/output/\` has finished. Check the path with \`artifact_list\`, or write the file first, then call \`send_file\` again.`;
|
|
192
197
|
}
|
|
193
198
|
|
|
199
|
+
function unsupportedTypeError(path: string): string {
|
|
200
|
+
return `Cannot send \`${path}\`. This chat can only receive ${SENDABLE_FILE_TYPES} files. Convert the file, or describe its contents instead.`;
|
|
201
|
+
}
|
|
202
|
+
|
|
194
203
|
function failedError(path: string): string {
|
|
195
204
|
return `Could not prepare \`${path}\` for sending. Try once more; if it fails again, tell the user the file could not be sent. Do not offer a link.`;
|
|
196
205
|
}
|
|
@@ -209,6 +218,11 @@ async function signFile(
|
|
|
209
218
|
logger.warn(`[send_file] ${args.path}: ${kind}`);
|
|
210
219
|
return { error };
|
|
211
220
|
};
|
|
221
|
+
// Checked before signing: a file of another type is never signed.
|
|
222
|
+
const filename = deliveredFilename(args.path);
|
|
223
|
+
const mimetype = fileMimetype(filename);
|
|
224
|
+
if (!mimetype)
|
|
225
|
+
return failed('unsupported type', unsupportedTypeError(args.path));
|
|
212
226
|
let signed: Record<string, unknown> | undefined;
|
|
213
227
|
try {
|
|
214
228
|
signed = resultObject(await presign.invoke({ path: args.path }));
|
|
@@ -227,14 +241,13 @@ async function signFile(
|
|
|
227
241
|
const url = httpsUrl(signed?.downloadUrl) ?? httpsUrl(signed?.url);
|
|
228
242
|
if (!url) return failed('no usable link returned', failedError(args.path));
|
|
229
243
|
|
|
230
|
-
const filename = deliveredFilename(args.path);
|
|
231
244
|
const { expiresIn, path, size } = signed ?? {};
|
|
232
245
|
return {
|
|
233
246
|
path: typeof path === 'string' && path ? path : args.path,
|
|
234
247
|
file: {
|
|
235
248
|
url,
|
|
236
249
|
filename,
|
|
237
|
-
mimetype
|
|
250
|
+
mimetype,
|
|
238
251
|
...(typeof size === 'number' ? { bytes: size } : {}),
|
|
239
252
|
...(args.caption ? { caption: args.caption } : {}),
|
|
240
253
|
...(typeof expiresIn === 'number'
|
|
@@ -294,7 +307,7 @@ export function buildSendFileTool(
|
|
|
294
307
|
},
|
|
295
308
|
{
|
|
296
309
|
name: SEND_FILE_TOOL,
|
|
297
|
-
description: `Send the user a file ${attachments ? 'as an attachment in this chat' : 'as a link posted in this room'}. Use it for every file meant for the user: a
|
|
310
|
+
description: `Send the user a file ${attachments ? 'as an attachment in this chat' : 'as a link posted in this room'}. Use it for every file meant for the user; it sends ${SENDABLE_FILE_TYPES} files only. Build the file yourself in the sandbox: do not send a file you downloaded from the internet unless the user asked for that exact file. Not for helper scripts or intermediate files. \`path\` is the stored path, for example \`/output/report.docx\`: save the file under \`/workspace/data/output/\` and let that sandbox call finish first. \`caption\` is an optional short line shown with the file. ${attachments ? 'The user receives the file itself' : 'The link is posted for you'}, so never paste a download link and do not explain how to open it. For several files, call it once per file, in the order they should arrive. If it returns an error, follow it, or tell the user the file could not be sent.`,
|
|
298
311
|
schema: SendFileArgs,
|
|
299
312
|
effect: 'read',
|
|
300
313
|
},
|