@promptev/client 1.1.0 → 1.2.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/dist/cjs/index.cjs +33 -9
- package/dist/esm/index.d.ts +12 -2
- package/dist/esm/index.js +33 -9
- package/package.json +1 -1
- package/readme.md +22 -0
package/dist/cjs/index.cjs
CHANGED
|
@@ -305,8 +305,8 @@ class PromptevClient {
|
|
|
305
305
|
* @param agentSlug The agent's slug, as shown on its page in Promptev. Only
|
|
306
306
|
* PUBLISHED agents in the key's own project can be reached; anything else
|
|
307
307
|
* answers 404 ({@link NotFoundError}).
|
|
308
|
-
* @param message What to say.
|
|
309
|
-
* {@link MAX_MESSAGE_CHARS} characters.
|
|
308
|
+
* @param message What to say. Non-empty after trimming unless
|
|
309
|
+
* `options.files` are sent, at most {@link MAX_MESSAGE_CHARS} characters.
|
|
310
310
|
*
|
|
311
311
|
* @example Stream the answer
|
|
312
312
|
* ```ts
|
|
@@ -333,10 +333,11 @@ class PromptevClient {
|
|
|
333
333
|
throw new ValidationError("Name the agent to talk to.", undefined, undefined, "agentSlug");
|
|
334
334
|
}
|
|
335
335
|
const text = String(message ?? "").trim();
|
|
336
|
-
|
|
336
|
+
const files = options?.files ?? [];
|
|
337
|
+
if (!text && files.length === 0) {
|
|
337
338
|
// The server answers 422 for this; refusing here saves the round trip
|
|
338
339
|
// AND the api_call the workspace would still have been metered for.
|
|
339
|
-
throw new ValidationError("Say something to the agent.", undefined, undefined, "message");
|
|
340
|
+
throw new ValidationError("Say something to the agent, or attach a file.", undefined, undefined, "message");
|
|
340
341
|
}
|
|
341
342
|
if (text.length > exports.MAX_MESSAGE_CHARS) {
|
|
342
343
|
throw new ValidationError(`A message can be at most ${exports.MAX_MESSAGE_CHARS} characters.`, undefined, undefined, "message");
|
|
@@ -359,11 +360,7 @@ class PromptevClient {
|
|
|
359
360
|
// POST could run the agent, and its tools, a second time.
|
|
360
361
|
const resp = await this.fetchOnce(url, {
|
|
361
362
|
method: "POST",
|
|
362
|
-
|
|
363
|
-
"Content-Type": "application/json",
|
|
364
|
-
Accept: "text/event-stream",
|
|
365
|
-
}),
|
|
366
|
-
body: JSON.stringify(payload),
|
|
363
|
+
...chatBody(payload, files, (extra) => this.authHeaders(extra)),
|
|
367
364
|
signal: options?.signal,
|
|
368
365
|
});
|
|
369
366
|
if (resp.status >= 400) {
|
|
@@ -536,6 +533,33 @@ class PromptevClient {
|
|
|
536
533
|
}
|
|
537
534
|
}
|
|
538
535
|
exports.PromptevClient = PromptevClient;
|
|
536
|
+
/**
|
|
537
|
+
* The body of a chat call: JSON when there are no files; MULTIPART when there
|
|
538
|
+
* are, the JSON in a `payload` part and each file in a `files` part. Count and
|
|
539
|
+
* size are judged here, before the wire.
|
|
540
|
+
*/
|
|
541
|
+
function chatBody(payload, files, headers) {
|
|
542
|
+
if (files.length === 0) {
|
|
543
|
+
return {
|
|
544
|
+
headers: headers({ "Content-Type": "application/json", Accept: "text/event-stream" }),
|
|
545
|
+
body: JSON.stringify(payload),
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
if (files.length > exports.MAX_FILES_PER_RUN) {
|
|
549
|
+
throw new ValidationError(`At most ${exports.MAX_FILES_PER_RUN} files can be sent with one call.`, undefined, undefined, "files");
|
|
550
|
+
}
|
|
551
|
+
const form = new FormData();
|
|
552
|
+
form.append("payload", JSON.stringify(payload));
|
|
553
|
+
for (const file of files) {
|
|
554
|
+
const [name, blob] = toBlob(file);
|
|
555
|
+
if (blob.size > exports.MAX_UPLOAD_BYTES) {
|
|
556
|
+
throw new ValidationError(`${name}: larger than ${exports.MAX_UPLOAD_BYTES / (1024 * 1024)} MB — add bigger files to the agent's knowledge instead.`, undefined, undefined, "files");
|
|
557
|
+
}
|
|
558
|
+
form.append("files", blob, name);
|
|
559
|
+
}
|
|
560
|
+
// NO Content-Type — fetch sets the multipart boundary itself.
|
|
561
|
+
return { headers: headers({ Accept: "text/event-stream" }), body: form };
|
|
562
|
+
}
|
|
539
563
|
/** One upload input, as a named Blob — the only shape `FormData` accepts. */
|
|
540
564
|
function toBlob(file) {
|
|
541
565
|
if (typeof Blob !== "undefined" && file instanceof Blob) {
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -127,6 +127,16 @@ export interface ChatOptions {
|
|
|
127
127
|
* keyed by tool id (shown on the tool's page) — see {@link ToolInput}.
|
|
128
128
|
*/
|
|
129
129
|
toolInputs?: Record<string, ToolInput>;
|
|
130
|
+
/**
|
|
131
|
+
* Files for the agent to read on this turn, sent in the same call: at most
|
|
132
|
+
* {@link MAX_FILES_PER_RUN}, each at most 25 MB. Accepted only when the
|
|
133
|
+
* agent's "Chat attachments" is on (409 otherwise), and only the types the
|
|
134
|
+
* promptev chat takes (415 otherwise). An image is shown to a model that can
|
|
135
|
+
* see, and read into text for one that cannot. A document is read before
|
|
136
|
+
* the answer starts, so a long scan makes a slower first event. With files,
|
|
137
|
+
* `message` may be empty.
|
|
138
|
+
*/
|
|
139
|
+
files?: UploadInput[];
|
|
130
140
|
/** Abort the turn from your own signal (in addition to the client timeout). */
|
|
131
141
|
signal?: AbortSignal;
|
|
132
142
|
}
|
|
@@ -451,8 +461,8 @@ export declare class PromptevClient {
|
|
|
451
461
|
* @param agentSlug The agent's slug, as shown on its page in Promptev. Only
|
|
452
462
|
* PUBLISHED agents in the key's own project can be reached; anything else
|
|
453
463
|
* answers 404 ({@link NotFoundError}).
|
|
454
|
-
* @param message What to say.
|
|
455
|
-
* {@link MAX_MESSAGE_CHARS} characters.
|
|
464
|
+
* @param message What to say. Non-empty after trimming unless
|
|
465
|
+
* `options.files` are sent, at most {@link MAX_MESSAGE_CHARS} characters.
|
|
456
466
|
*
|
|
457
467
|
* @example Stream the answer
|
|
458
468
|
* ```ts
|
package/dist/esm/index.js
CHANGED
|
@@ -294,8 +294,8 @@ export class PromptevClient {
|
|
|
294
294
|
* @param agentSlug The agent's slug, as shown on its page in Promptev. Only
|
|
295
295
|
* PUBLISHED agents in the key's own project can be reached; anything else
|
|
296
296
|
* answers 404 ({@link NotFoundError}).
|
|
297
|
-
* @param message What to say.
|
|
298
|
-
* {@link MAX_MESSAGE_CHARS} characters.
|
|
297
|
+
* @param message What to say. Non-empty after trimming unless
|
|
298
|
+
* `options.files` are sent, at most {@link MAX_MESSAGE_CHARS} characters.
|
|
299
299
|
*
|
|
300
300
|
* @example Stream the answer
|
|
301
301
|
* ```ts
|
|
@@ -322,10 +322,11 @@ export class PromptevClient {
|
|
|
322
322
|
throw new ValidationError("Name the agent to talk to.", undefined, undefined, "agentSlug");
|
|
323
323
|
}
|
|
324
324
|
const text = String(message ?? "").trim();
|
|
325
|
-
|
|
325
|
+
const files = options?.files ?? [];
|
|
326
|
+
if (!text && files.length === 0) {
|
|
326
327
|
// The server answers 422 for this; refusing here saves the round trip
|
|
327
328
|
// AND the api_call the workspace would still have been metered for.
|
|
328
|
-
throw new ValidationError("Say something to the agent.", undefined, undefined, "message");
|
|
329
|
+
throw new ValidationError("Say something to the agent, or attach a file.", undefined, undefined, "message");
|
|
329
330
|
}
|
|
330
331
|
if (text.length > MAX_MESSAGE_CHARS) {
|
|
331
332
|
throw new ValidationError(`A message can be at most ${MAX_MESSAGE_CHARS} characters.`, undefined, undefined, "message");
|
|
@@ -348,11 +349,7 @@ export class PromptevClient {
|
|
|
348
349
|
// POST could run the agent, and its tools, a second time.
|
|
349
350
|
const resp = await this.fetchOnce(url, {
|
|
350
351
|
method: "POST",
|
|
351
|
-
|
|
352
|
-
"Content-Type": "application/json",
|
|
353
|
-
Accept: "text/event-stream",
|
|
354
|
-
}),
|
|
355
|
-
body: JSON.stringify(payload),
|
|
352
|
+
...chatBody(payload, files, (extra) => this.authHeaders(extra)),
|
|
356
353
|
signal: options?.signal,
|
|
357
354
|
});
|
|
358
355
|
if (resp.status >= 400) {
|
|
@@ -524,6 +521,33 @@ export class PromptevClient {
|
|
|
524
521
|
return JSON.parse(text);
|
|
525
522
|
}
|
|
526
523
|
}
|
|
524
|
+
/**
|
|
525
|
+
* The body of a chat call: JSON when there are no files; MULTIPART when there
|
|
526
|
+
* are, the JSON in a `payload` part and each file in a `files` part. Count and
|
|
527
|
+
* size are judged here, before the wire.
|
|
528
|
+
*/
|
|
529
|
+
function chatBody(payload, files, headers) {
|
|
530
|
+
if (files.length === 0) {
|
|
531
|
+
return {
|
|
532
|
+
headers: headers({ "Content-Type": "application/json", Accept: "text/event-stream" }),
|
|
533
|
+
body: JSON.stringify(payload),
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
if (files.length > MAX_FILES_PER_RUN) {
|
|
537
|
+
throw new ValidationError(`At most ${MAX_FILES_PER_RUN} files can be sent with one call.`, undefined, undefined, "files");
|
|
538
|
+
}
|
|
539
|
+
const form = new FormData();
|
|
540
|
+
form.append("payload", JSON.stringify(payload));
|
|
541
|
+
for (const file of files) {
|
|
542
|
+
const [name, blob] = toBlob(file);
|
|
543
|
+
if (blob.size > MAX_UPLOAD_BYTES) {
|
|
544
|
+
throw new ValidationError(`${name}: larger than ${MAX_UPLOAD_BYTES / (1024 * 1024)} MB — add bigger files to the agent's knowledge instead.`, undefined, undefined, "files");
|
|
545
|
+
}
|
|
546
|
+
form.append("files", blob, name);
|
|
547
|
+
}
|
|
548
|
+
// NO Content-Type — fetch sets the multipart boundary itself.
|
|
549
|
+
return { headers: headers({ Accept: "text/event-stream" }), body: form };
|
|
550
|
+
}
|
|
527
551
|
/** One upload input, as a named Blob — the only shape `FormData` accepts. */
|
|
528
552
|
function toBlob(file) {
|
|
529
553
|
if (typeof Blob !== "undefined" && file instanceof Blob) {
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -105,6 +105,28 @@ Passing `conversationId` is worth doing for a second reason: a tool set to **Ask
|
|
|
105
105
|
|
|
106
106
|
A thread belongs to the agent, to this API, **and** to the `onBehalfOf.subject` it was opened under. Keep the subject the same across a conversation's turns, and never reuse an id from the app, the portal or an embed — any mismatch answers `404`, not a fresh thread.
|
|
107
107
|
|
|
108
|
+
## Sending files with a message
|
|
109
|
+
|
|
110
|
+
Pass `files` and the agent reads them on that turn, in the same call. This is the paperclip of the promptev chat: the agent takes files only when **Chat attachments** is switched on in its builder, and only the types that chat takes.
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
import { readFile } from "node:fs/promises";
|
|
114
|
+
|
|
115
|
+
for await (const ev of client.chat("support-agent", "What is the total on this receipt?", {
|
|
116
|
+
files: [{ name: "receipt.png", content: await readFile("receipt.png") }], // or a File / Blob
|
|
117
|
+
})) {
|
|
118
|
+
if (ev.type === "token") process.stdout.write(ev.content);
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Up to **five files per call**, 25 MB each. An image is shown to a model that can see, and read into text for one that cannot. A document is read before the answer starts, so a long scan makes a slower first event. A call may carry files and an empty message.
|
|
123
|
+
|
|
124
|
+
| Status | When |
|
|
125
|
+
|---|---|
|
|
126
|
+
| 409 | The agent's Chat attachments is off |
|
|
127
|
+
| 415 | A file type the agent cannot take |
|
|
128
|
+
| 413 | The workspace is out of storage |
|
|
129
|
+
|
|
108
130
|
## Changing the model or a tool's values for one call
|
|
109
131
|
|
|
110
132
|
Both options are optional. Leave them out and the agent runs exactly as it was published. Nothing a call sends is stored: the next call runs the published agent again.
|