@hue-run/sdk 0.18.0 → 0.18.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/CLI.md +130 -8
- package/EVALUATIONS.md +7 -4
- package/README.md +43 -6
- package/dist/blobs.js +1 -1
- package/dist/cli/import.d.ts +35 -0
- package/dist/cli/import.js +692 -0
- package/dist/cli/langfuse.d.ts +309 -0
- package/dist/cli/langfuse.js +1075 -0
- package/dist/evals/client.d.ts +7 -2
- package/dist/evals/client.js +20 -4
- package/dist/evals/files.js +100 -12
- package/dist/evals/media.d.ts +5 -1
- package/dist/evals/media.js +15 -3
- package/dist/evals/types.d.ts +4 -3
- package/dist/inline-files.d.ts +12 -3
- package/dist/inline-files.js +12 -9
- package/dist/setup/cli.js +7 -3
- package/dist/transport.d.ts +3 -0
- package/dist/transport.js +11 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/evals/client.d.ts
CHANGED
|
@@ -79,6 +79,7 @@ export declare class EvaluationClient {
|
|
|
79
79
|
}): Promise<Uint8Array>;
|
|
80
80
|
/** Stage bytes at the storage capability Hue issued. The Hue key is never sent to storage. */
|
|
81
81
|
uploadArtifactBytes(upload: ArtifactUpload, bytes: Uint8Array, contentType: string): Promise<void>;
|
|
82
|
+
private putArtifact;
|
|
82
83
|
private page;
|
|
83
84
|
private registryPage;
|
|
84
85
|
/** Reads the current project to confirm the key and origin. */
|
|
@@ -106,10 +107,14 @@ export declare class EvaluationClient {
|
|
|
106
107
|
* `files` given as bytes or a path is checked against the artifact policy and uploaded as a
|
|
107
108
|
* verified artifact first, under a key derived from the version, the case's `externalKey`, the
|
|
108
109
|
* role and the bytes, so sending the same case again reuses the artifact. Every file and the
|
|
109
|
-
* case itself are checked before the first upload.
|
|
110
|
+
* case itself are checked before the first upload. A file given by `path` is hashed a slice at
|
|
111
|
+
* a time when it is checked and read again when it is uploaded, one file at a time, so no more
|
|
112
|
+
* than one file is held in memory; only bytes that match the checked SHA-256 are sent.
|
|
110
113
|
*
|
|
111
114
|
* @throws TypeError or RangeError for `files` outside the artifact policy, or a case Hue would
|
|
112
|
-
* refuse for its fields or size, before any request.
|
|
115
|
+
* refuse for its fields or size, before any request. TypeError when a file given by `path`
|
|
116
|
+
* changed after it was checked, or can no longer be read, before any of its bytes are sent: it
|
|
117
|
+
* is not staged and the case is not written.
|
|
113
118
|
*/
|
|
114
119
|
addCase(id: string, input: CaseWrite): Promise<{
|
|
115
120
|
/** The stored case. */
|
package/dist/evals/client.js
CHANGED
|
@@ -402,17 +402,29 @@ export class EvaluationClient {
|
|
|
402
402
|
}
|
|
403
403
|
}
|
|
404
404
|
/** Stage bytes at the storage capability Hue issued. The Hue key is never sent to storage. */
|
|
405
|
-
|
|
405
|
+
uploadArtifactBytes(upload, bytes, contentType) {
|
|
406
|
+
return this.putArtifact(upload, contentType, bytes);
|
|
407
|
+
}
|
|
408
|
+
/** @internal Stage `byteSize` bytes read from `body` a slice at a time, without the copy a
|
|
409
|
+
* byte body is given; the request declares their length, as it does for bytes. */
|
|
410
|
+
uploadArtifactStream(upload, body, byteSize, contentType) {
|
|
411
|
+
return this.putArtifact(upload, contentType, body, byteSize);
|
|
412
|
+
}
|
|
413
|
+
async putArtifact(upload, contentType, body, byteSize) {
|
|
406
414
|
const uploadUrl = signedUploadUrl(upload.uploadUrl);
|
|
407
415
|
if (upload.method !== "PUT")
|
|
408
416
|
throw new HueApiError();
|
|
409
417
|
const headers = signedUploadHeaders(upload.headers, contentType);
|
|
418
|
+
// Storage refuses a body of unknown length, which a stream would otherwise be sent as.
|
|
419
|
+
if (byteSize !== undefined)
|
|
420
|
+
headers["content-length"] = String(byteSize);
|
|
410
421
|
let response;
|
|
411
422
|
try {
|
|
412
423
|
response = await fetch(uploadUrl, {
|
|
413
424
|
method: "PUT",
|
|
414
425
|
headers,
|
|
415
|
-
body
|
|
426
|
+
body,
|
|
427
|
+
...(byteSize !== undefined ? { duplex: "half" } : {}),
|
|
416
428
|
redirect: "error",
|
|
417
429
|
signal: AbortSignal.timeout(Math.max(this.timeoutMillis, 120_000)),
|
|
418
430
|
});
|
|
@@ -480,10 +492,14 @@ export class EvaluationClient {
|
|
|
480
492
|
* `files` given as bytes or a path is checked against the artifact policy and uploaded as a
|
|
481
493
|
* verified artifact first, under a key derived from the version, the case's `externalKey`, the
|
|
482
494
|
* role and the bytes, so sending the same case again reuses the artifact. Every file and the
|
|
483
|
-
* case itself are checked before the first upload.
|
|
495
|
+
* case itself are checked before the first upload. A file given by `path` is hashed a slice at
|
|
496
|
+
* a time when it is checked and read again when it is uploaded, one file at a time, so no more
|
|
497
|
+
* than one file is held in memory; only bytes that match the checked SHA-256 are sent.
|
|
484
498
|
*
|
|
485
499
|
* @throws TypeError or RangeError for `files` outside the artifact policy, or a case Hue would
|
|
486
|
-
* refuse for its fields or size, before any request.
|
|
500
|
+
* refuse for its fields or size, before any request. TypeError when a file given by `path`
|
|
501
|
+
* changed after it was checked, or can no longer be read, before any of its bytes are sent: it
|
|
502
|
+
* is not staged and the case is not written.
|
|
487
503
|
*/
|
|
488
504
|
async addCase(id, input) {
|
|
489
505
|
// The canonical spelling: the reservation keys of uploaded files name the version.
|
package/dist/evals/files.js
CHANGED
|
@@ -426,15 +426,22 @@ export async function uploadOutputFiles(client, executionId, files, save, timing
|
|
|
426
426
|
}
|
|
427
427
|
/** Reserve, stage and verify one artifact; its ID once Hue verified it. Replaying the key settles
|
|
428
428
|
* on the same artifact, including one Hue finished verifying after an earlier attempt stopped. */
|
|
429
|
-
async function publishArtifact(client, declared,
|
|
429
|
+
async function publishArtifact(client, declared, body, label, timing) {
|
|
430
430
|
const reserved = await client.reserveArtifact(declared);
|
|
431
431
|
let state = reserved.state;
|
|
432
432
|
if (state !== "ready") {
|
|
433
433
|
// A verification already running holds the uploaded bytes; Hue refuses another upload then.
|
|
434
434
|
if (reserved.copyState !== "acknowledged" && state !== "verifying") {
|
|
435
|
+
// A checked file is read again, whole, only now and only if it still has the bytes its
|
|
436
|
+
// check hashed: a file that changed sends nothing.
|
|
437
|
+
const bytes = body instanceof Uint8Array ? body : await checkedBytes(body);
|
|
435
438
|
const upload = await client.requestArtifactUpload(reserved.id);
|
|
436
439
|
try {
|
|
437
|
-
|
|
440
|
+
if (body instanceof Uint8Array)
|
|
441
|
+
await client.uploadArtifactBytes(upload, bytes, declared.contentType);
|
|
442
|
+
// Sent from the write's own buffer a slice at a time, never copied whole.
|
|
443
|
+
else
|
|
444
|
+
await client.uploadArtifactStream(upload, slicesOf(bytes), bytes.byteLength, declared.contentType);
|
|
438
445
|
}
|
|
439
446
|
catch {
|
|
440
447
|
// A lost staging acknowledgement or an immutable object already present can only be
|
|
@@ -447,6 +454,59 @@ async function publishArtifact(client, declared, bytes, label, timing) {
|
|
|
447
454
|
throw new Error(`${label} was not verified by Hue (${state})`);
|
|
448
455
|
return reserved.id;
|
|
449
456
|
}
|
|
457
|
+
/** How much of a case file given by path its check reads at once, to hash it. */
|
|
458
|
+
const CASE_FILE_SLICE = 1024 * 1024;
|
|
459
|
+
/** Bytes as a stream of 1 MiB views of them, made as the request reads them. */
|
|
460
|
+
function slicesOf(bytes) {
|
|
461
|
+
let offset = 0;
|
|
462
|
+
return new ReadableStream({
|
|
463
|
+
pull(controller) {
|
|
464
|
+
if (offset >= bytes.byteLength)
|
|
465
|
+
return controller.close();
|
|
466
|
+
const end = Math.min(bytes.byteLength, offset + CASE_FILE_SLICE);
|
|
467
|
+
controller.enqueue(bytes.subarray(offset, end));
|
|
468
|
+
offset = end;
|
|
469
|
+
},
|
|
470
|
+
}, { highWaterMark: 0 });
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* The bytes of a checked case file, read whole when it is uploaded: case files are uploaded one
|
|
474
|
+
* at a time, into one buffer the write reuses, so no more than one is held in memory. Only bytes
|
|
475
|
+
* with the checked size and SHA-256 are returned, so the bytes sent are the bytes the check
|
|
476
|
+
* hashed; a file that changed after its check, or can no longer be read, is refused with a
|
|
477
|
+
* `TypeError` before anything is sent. The bytes are valid until the next file is read.
|
|
478
|
+
*/
|
|
479
|
+
async function checkedBytes(file) {
|
|
480
|
+
const changed = () => new TypeError(`${file.label} changed after it was checked`);
|
|
481
|
+
const unreadable = (error) => new TypeError(`${file.label} could not be read`, { cause: error });
|
|
482
|
+
const handle = await open(file.path, "r").catch((error) => {
|
|
483
|
+
throw unreadable(error);
|
|
484
|
+
});
|
|
485
|
+
try {
|
|
486
|
+
const read = (buffer, offset) => handle
|
|
487
|
+
.read(buffer, offset, buffer.byteLength - offset, null)
|
|
488
|
+
.then(({ bytesRead }) => bytesRead)
|
|
489
|
+
.catch((error) => {
|
|
490
|
+
throw unreadable(error);
|
|
491
|
+
});
|
|
492
|
+
// Never more than its checked size and one byte, the byte that shows it grew.
|
|
493
|
+
if ((file.scratch.buffer?.byteLength ?? 0) < file.byteSize)
|
|
494
|
+
file.scratch.buffer = Buffer.allocUnsafe(file.byteSize);
|
|
495
|
+
const bytes = file.scratch.buffer.subarray(0, file.byteSize);
|
|
496
|
+
for (let offset = 0; offset < bytes.byteLength;) {
|
|
497
|
+
const bytesRead = await read(bytes, offset);
|
|
498
|
+
if (!bytesRead)
|
|
499
|
+
throw changed();
|
|
500
|
+
offset += bytesRead;
|
|
501
|
+
}
|
|
502
|
+
if ((await read(Buffer.alloc(1), 0)) || sha256(bytes) !== file.sha256)
|
|
503
|
+
throw changed();
|
|
504
|
+
return bytes;
|
|
505
|
+
}
|
|
506
|
+
finally {
|
|
507
|
+
await handle.close().catch(() => undefined);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
450
510
|
/** Roles a pinned case input can carry. */
|
|
451
511
|
const caseFileRoles = [
|
|
452
512
|
...targetFileRoles,
|
|
@@ -466,8 +526,9 @@ function caseFileName(name) {
|
|
|
466
526
|
throw new TypeError("A case file needs a well-formed filename of 1–255 UTF-16 code units without path separators or control characters");
|
|
467
527
|
return name;
|
|
468
528
|
}
|
|
469
|
-
/**
|
|
470
|
-
|
|
529
|
+
/** A case file given by path, checked: a regular file within the size limit, hashed a slice at a
|
|
530
|
+
* time so it is never held whole. Its emptiness is the caller's check, as for bytes. */
|
|
531
|
+
async function checkedFile(path, label, scratch) {
|
|
471
532
|
if (typeof path !== "string" || !path)
|
|
472
533
|
throw new TypeError(`${label} needs bytes or a path`);
|
|
473
534
|
// A path with no file behind it, or one that cannot be read, is refused as any invalid file.
|
|
@@ -479,9 +540,32 @@ async function caseFileBytes(path, label) {
|
|
|
479
540
|
throw new TypeError(`${label} is not a regular file`);
|
|
480
541
|
if (info.size > outputFileLimits.bytes)
|
|
481
542
|
throw new RangeError(`${label} exceeds 25 MiB`);
|
|
482
|
-
|
|
543
|
+
const handle = await open(path, "r").catch((error) => {
|
|
483
544
|
throw unreadable(error);
|
|
484
545
|
});
|
|
546
|
+
try {
|
|
547
|
+
const hash = createHash("sha256");
|
|
548
|
+
const slice = Buffer.allocUnsafe(CASE_FILE_SLICE);
|
|
549
|
+
let byteSize = 0;
|
|
550
|
+
for (;;) {
|
|
551
|
+
const { bytesRead } = await handle
|
|
552
|
+
.read(slice, 0, slice.byteLength, null)
|
|
553
|
+
.catch((error) => {
|
|
554
|
+
throw unreadable(error);
|
|
555
|
+
});
|
|
556
|
+
if (!bytesRead)
|
|
557
|
+
break;
|
|
558
|
+
byteSize += bytesRead;
|
|
559
|
+
// A file that grew past the limit since its size was read is refused, not read whole.
|
|
560
|
+
if (byteSize > outputFileLimits.bytes)
|
|
561
|
+
throw new RangeError(`${label} exceeds 25 MiB`);
|
|
562
|
+
hash.update(slice.subarray(0, bytesRead));
|
|
563
|
+
}
|
|
564
|
+
return { path, label, byteSize, sha256: hash.digest("hex"), scratch };
|
|
565
|
+
}
|
|
566
|
+
finally {
|
|
567
|
+
await handle.close().catch(() => undefined);
|
|
568
|
+
}
|
|
485
569
|
}
|
|
486
570
|
/** A stand-in for an artifact a case file is uploaded as, the length of a real ID. */
|
|
487
571
|
const STAND_IN_ID = "00000000-0000-4000-8000-000000000000";
|
|
@@ -502,6 +586,7 @@ export async function caseFileReferences(client, versionId, input, beforeUpload
|
|
|
502
586
|
throw new RangeError(`A case pins at most ${MAX_CASE_FILES} files`);
|
|
503
587
|
const prepared = [];
|
|
504
588
|
const seen = new Set();
|
|
589
|
+
const scratch = {};
|
|
505
590
|
for (const [index, file] of files.entries()) {
|
|
506
591
|
const label = `Case file ${index + 1}`;
|
|
507
592
|
if (!file || typeof file !== "object")
|
|
@@ -530,18 +615,21 @@ export async function caseFileReferences(client, versionId, input, beforeUpload
|
|
|
530
615
|
throw new TypeError(`${label} names bytes and a path; give one`);
|
|
531
616
|
if (file.bytes !== undefined && !(file.bytes instanceof Uint8Array))
|
|
532
617
|
throw new TypeError(`${label} bytes must be a Uint8Array`);
|
|
533
|
-
|
|
534
|
-
|
|
618
|
+
// Bytes are kept as given; a path is kept as its size and digest, and read again to upload.
|
|
619
|
+
const body = file.bytes ?? (await checkedFile(file.path, label, scratch));
|
|
620
|
+
const byteSize = body instanceof Uint8Array ? body.byteLength : body.byteSize;
|
|
621
|
+
if (!byteSize)
|
|
535
622
|
throw new RangeError(`${label} is empty`);
|
|
536
|
-
if (
|
|
623
|
+
if (byteSize > outputFileLimits.bytes)
|
|
537
624
|
throw new RangeError(`${label} exceeds 25 MiB`);
|
|
538
|
-
const digest = sha256(
|
|
625
|
+
const digest = body instanceof Uint8Array ? sha256(body) : body.sha256;
|
|
539
626
|
prepared.push({
|
|
540
627
|
role: file.role,
|
|
541
628
|
filename,
|
|
542
629
|
contentType: file.contentType,
|
|
543
|
-
|
|
630
|
+
byteSize,
|
|
544
631
|
sha256: digest,
|
|
632
|
+
body,
|
|
545
633
|
});
|
|
546
634
|
identity = `file:${digest}:${file.contentType}:${filename}`;
|
|
547
635
|
}
|
|
@@ -574,9 +662,9 @@ export async function caseFileReferences(client, versionId, input, beforeUpload
|
|
|
574
662
|
idempotencyKey: `hue-sdk:case:${versionId}:${caseKey}:${item.role}:${item.sha256}:${sha256(Buffer.from(item.filename)).slice(0, 16)}:${sha256(Buffer.from(item.contentType)).slice(0, 16)}`,
|
|
575
663
|
filename: item.filename,
|
|
576
664
|
contentType: item.contentType,
|
|
577
|
-
byteSize: item.
|
|
665
|
+
byteSize: item.byteSize,
|
|
578
666
|
sha256: item.sha256,
|
|
579
|
-
}, item.
|
|
667
|
+
}, item.body, `Case file ${item.filename}`, timing);
|
|
580
668
|
pin({ artifactId, role: item.role });
|
|
581
669
|
}
|
|
582
670
|
return references;
|
package/dist/evals/media.d.ts
CHANGED
|
@@ -38,8 +38,12 @@ export declare class MediaFetchError extends Error {
|
|
|
38
38
|
/** HTTP status of the answer that failed, when there was one. */
|
|
39
39
|
status?: number | undefined,
|
|
40
40
|
/** Seconds Hue asked the caller to wait, on `unavailable`, when it said. */
|
|
41
|
-
retryAfterSeconds?: number | undefined,
|
|
41
|
+
retryAfterSeconds?: number | undefined,
|
|
42
|
+
/** `cause`, the error behind a failure; `snapshot`, set when a snapshot's copy was read, so a
|
|
43
|
+
* `forbidden` message names the Read and write key it needs. */
|
|
44
|
+
options?: {
|
|
42
45
|
cause?: unknown;
|
|
46
|
+
snapshot?: boolean;
|
|
43
47
|
});
|
|
44
48
|
}
|
|
45
49
|
/** One recorded file, downloaded and checked against its digest. */
|
package/dist/evals/media.js
CHANGED
|
@@ -11,6 +11,8 @@ const messages = {
|
|
|
11
11
|
digest_mismatch: "The downloaded bytes do not match the requested SHA-256",
|
|
12
12
|
failed: "The media request failed",
|
|
13
13
|
};
|
|
14
|
+
/** A snapshot's copies take only a Read and write key, where a trace's files take a Read key too. */
|
|
15
|
+
const snapshotForbidden = "The key cannot read a snapshot's files; use a Read and write key";
|
|
14
16
|
/** Thrown by {@link MediaClient.fetch}. The message is fixed and never includes response text. */
|
|
15
17
|
export class MediaFetchError extends Error {
|
|
16
18
|
reason;
|
|
@@ -22,8 +24,16 @@ export class MediaFetchError extends Error {
|
|
|
22
24
|
/** HTTP status of the answer that failed, when there was one. */
|
|
23
25
|
status,
|
|
24
26
|
/** Seconds Hue asked the caller to wait, on `unavailable`, when it said. */
|
|
25
|
-
retryAfterSeconds,
|
|
26
|
-
|
|
27
|
+
retryAfterSeconds,
|
|
28
|
+
/** `cause`, the error behind a failure; `snapshot`, set when a snapshot's copy was read, so a
|
|
29
|
+
* `forbidden` message names the Read and write key it needs. */
|
|
30
|
+
options) {
|
|
31
|
+
const { snapshot, ...rest } = options ?? {};
|
|
32
|
+
super(reason === "failed" && status
|
|
33
|
+
? `${messages.failed} (HTTP ${status})`
|
|
34
|
+
: reason === "forbidden" && snapshot
|
|
35
|
+
? snapshotForbidden
|
|
36
|
+
: messages[reason], rest);
|
|
27
37
|
this.reason = reason;
|
|
28
38
|
this.status = status;
|
|
29
39
|
this.retryAfterSeconds = retryAfterSeconds;
|
|
@@ -354,7 +364,9 @@ export class MediaClient {
|
|
|
354
364
|
}
|
|
355
365
|
if (status === 401 || status === 403) {
|
|
356
366
|
await response.body?.cancel().catch(() => undefined);
|
|
357
|
-
throw new MediaFetchError("forbidden", status
|
|
367
|
+
throw new MediaFetchError("forbidden", status, undefined, {
|
|
368
|
+
snapshot: path.startsWith("/trace-snapshots/"),
|
|
369
|
+
});
|
|
358
370
|
}
|
|
359
371
|
if (!response.ok) {
|
|
360
372
|
await response.body?.cancel().catch(() => undefined);
|
package/dist/evals/types.d.ts
CHANGED
|
@@ -186,9 +186,10 @@ export interface CaseFileReference {
|
|
|
186
186
|
}
|
|
187
187
|
/**
|
|
188
188
|
* A pinned input file for {@link CaseWrite.files}: a {@link CaseFileReference}, or a file the
|
|
189
|
-
* client uploads as a verified artifact, from `bytes` or read
|
|
190
|
-
*
|
|
191
|
-
*
|
|
189
|
+
* client uploads as a verified artifact, from `bytes` or read from `path`: hashed a slice at a
|
|
190
|
+
* time when the case is checked, then read again as it is uploaded, one file at a time. An
|
|
191
|
+
* uploaded file must have one of `outputContentTypes`, a well-formed name of 1–255 UTF-16 code
|
|
192
|
+
* units without path separators or control characters, and 1 byte to 25 MiB.
|
|
192
193
|
*/
|
|
193
194
|
export type CaseFileWrite = (CaseFileReference & {
|
|
194
195
|
/** Not set on an artifact reference: the artifact keeps its own name. */
|
package/dist/inline-files.d.ts
CHANGED
|
@@ -27,6 +27,9 @@ export interface InlineFileBytes {
|
|
|
27
27
|
bytes: Buffer;
|
|
28
28
|
decoded: boolean;
|
|
29
29
|
urlMediaType?: string;
|
|
30
|
+
/** The bytes were decoded from text that reads as base64 by its alphabet alone, not from a
|
|
31
|
+
* `data:` URL or a field its convention declares base64. */
|
|
32
|
+
bareBase64?: boolean;
|
|
30
33
|
}
|
|
31
34
|
/**
|
|
32
35
|
* The bytes an inline file part carries, the file's own bytes whatever its media type: a `data:`
|
|
@@ -137,9 +140,15 @@ export declare function reencodeMedia(media: MediaValue): void;
|
|
|
137
140
|
/** Whether a found file has bytes to place: nonempty, and a whole string's `data:` URL decodes. */
|
|
138
141
|
export declare function placeable(file: InlineFilePart, decoded: InlineFileBytes): boolean;
|
|
139
142
|
/** Whether an uploaded file keeps a 16 KiB prefix of its content in place (text media in a
|
|
140
|
-
* provider part, whose entry names the content) rather than its digest. A whole string
|
|
141
|
-
*
|
|
142
|
-
|
|
143
|
+
* provider part, whose entry names the content) rather than its digest. A whole string, a file
|
|
144
|
+
* inside a JSON-encoded string, and a text file a part holds as bare base64 (a GenAI `blob`,
|
|
145
|
+
* Vertex `media` or AI SDK part, whose content is not the text itself) take the digest form. */
|
|
146
|
+
export declare function keepsPrefix(file: InlineFilePart, textual: boolean, decoded: InlineFileBytes): boolean;
|
|
147
|
+
/** A file's bytes: a field its convention declares base64 decoded, any other content read as
|
|
148
|
+
* Hue's reader reads it, whatever the part's media type (`inlineFileBytes`): a `data:` URL by its
|
|
149
|
+
* own encoding, text that reads as base64 decoded (what the GenAI conventions, and
|
|
150
|
+
* `client.media.resolve`, write in a `blob` or `media` part for every media type), anything else
|
|
151
|
+
* as its UTF-8. */
|
|
143
152
|
export declare function mediaBytes(file: InlineFilePart): InlineFileBytes;
|
|
144
153
|
export declare function mediaContentType(file: InlineFilePart, decoded: InlineFileBytes): string;
|
|
145
154
|
export interface FileDigest {
|
package/dist/inline-files.js
CHANGED
|
@@ -140,6 +140,7 @@ export function inlineFileBytes(content) {
|
|
|
140
140
|
bytes: decoded ?? Buffer.from(content, "utf8"),
|
|
141
141
|
decoded: decoded !== undefined,
|
|
142
142
|
...(mediaType ? { urlMediaType: mediaType } : {}),
|
|
143
|
+
...(decoded !== undefined && !url ? { bareBase64: true } : {}),
|
|
143
144
|
};
|
|
144
145
|
}
|
|
145
146
|
const audioTypes = {
|
|
@@ -711,18 +712,20 @@ export function placeable(file, decoded) {
|
|
|
711
712
|
return decoded.bytes.byteLength > 0 && (!file.whole || decoded.decoded);
|
|
712
713
|
}
|
|
713
714
|
/** Whether an uploaded file keeps a 16 KiB prefix of its content in place (text media in a
|
|
714
|
-
* provider part, whose entry names the content) rather than its digest. A whole string
|
|
715
|
-
*
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
715
|
+
* provider part, whose entry names the content) rather than its digest. A whole string, a file
|
|
716
|
+
* inside a JSON-encoded string, and a text file a part holds as bare base64 (a GenAI `blob`,
|
|
717
|
+
* Vertex `media` or AI SDK part, whose content is not the text itself) take the digest form. */
|
|
718
|
+
export function keepsPrefix(file, textual, decoded) {
|
|
719
|
+
return textual && !file.whole && !file.encoded?.length && !decoded.bareBase64;
|
|
720
|
+
}
|
|
721
|
+
/** A file's bytes: a field its convention declares base64 decoded, any other content read as
|
|
722
|
+
* Hue's reader reads it, whatever the part's media type (`inlineFileBytes`): a `data:` URL by its
|
|
723
|
+
* own encoding, text that reads as base64 decoded (what the GenAI conventions, and
|
|
724
|
+
* `client.media.resolve`, write in a `blob` or `media` part for every media type), anything else
|
|
725
|
+
* as its UTF-8. */
|
|
719
726
|
export function mediaBytes(file) {
|
|
720
727
|
if (file.base64)
|
|
721
728
|
return { bytes: Buffer.from(file.content, "base64"), decoded: true };
|
|
722
|
-
if (isMediaContentType(file.contentType) &&
|
|
723
|
-
isTextMediaType(file.contentType) &&
|
|
724
|
-
!file.content.startsWith("data:"))
|
|
725
|
-
return { bytes: Buffer.from(file.content, "utf8"), decoded: false };
|
|
726
729
|
return inlineFileBytes(file.content);
|
|
727
730
|
}
|
|
728
731
|
export function mediaContentType(file, decoded) {
|
package/dist/setup/cli.js
CHANGED
|
@@ -24,6 +24,10 @@ const extensions = new Map([
|
|
|
24
24
|
"mcp",
|
|
25
25
|
async () => (await loadCommand("mcp", () => import("../cli/mcp.js"))).runMcpCommand(process.argv.slice(3)),
|
|
26
26
|
],
|
|
27
|
+
[
|
|
28
|
+
"import",
|
|
29
|
+
async () => (await loadCommand("import", () => import("../cli/import.js"))).runImportCommand(process.argv.slice(3)),
|
|
30
|
+
],
|
|
27
31
|
[
|
|
28
32
|
"listen",
|
|
29
33
|
async () => (await loadCommand("listen", () => import("../cli/listen.js"))).runListenCommand(process.argv.slice(3)),
|
|
@@ -70,7 +74,7 @@ async function main() {
|
|
|
70
74
|
}
|
|
71
75
|
catch {
|
|
72
76
|
if (!agentRequested) {
|
|
73
|
-
process.stderr.write("Usage: hue <setup|resume|status|claim|login|eval|mcp|listen> [--agent|--format human|plain|jsonl] [--project PATH] [--origin URL] [--restart]\n");
|
|
77
|
+
process.stderr.write("Usage: hue <setup|resume|status|claim|login|eval|mcp|listen|import> [--agent|--format human|plain|jsonl] [--project PATH] [--origin URL] [--restart]\n");
|
|
74
78
|
return 2;
|
|
75
79
|
}
|
|
76
80
|
const event = {
|
|
@@ -101,7 +105,7 @@ async function main() {
|
|
|
101
105
|
process.stdout.write(`${renderJsonlEvent(event)}\n`);
|
|
102
106
|
return 2;
|
|
103
107
|
}
|
|
104
|
-
process.stdout.write("Usage: hue <setup|resume|status|claim|login|eval|mcp|listen> [--agent|--format human|plain|jsonl] [--project PATH] [--origin URL] [--restart]\n");
|
|
108
|
+
process.stdout.write("Usage: hue <setup|resume|status|claim|login|eval|mcp|listen|import> [--agent|--format human|plain|jsonl] [--project PATH] [--origin URL] [--restart]\n");
|
|
105
109
|
return 0;
|
|
106
110
|
}
|
|
107
111
|
const command = parsed.positionals[0];
|
|
@@ -133,7 +137,7 @@ async function main() {
|
|
|
133
137
|
process.stdout.write(`${renderJsonlEvent(event)}\n`);
|
|
134
138
|
}
|
|
135
139
|
else
|
|
136
|
-
process.stderr.write("Usage: hue <setup|resume|status|claim|login|eval|mcp|listen> [--agent|--format human|plain|jsonl] [--project PATH] [--origin URL] [--restart]\n");
|
|
140
|
+
process.stderr.write("Usage: hue <setup|resume|status|claim|login|eval|mcp|listen|import> [--agent|--format human|plain|jsonl] [--project PATH] [--origin URL] [--restart]\n");
|
|
137
141
|
return 2;
|
|
138
142
|
}
|
|
139
143
|
const mode = selectSetupOutputMode({
|
package/dist/transport.d.ts
CHANGED
|
@@ -57,6 +57,9 @@ export declare class HueTransport {
|
|
|
57
57
|
* over the request limit), reported on the trace's root span as `hue.sdk.dropped_records` when
|
|
58
58
|
* the root is exported, so Hue reads the trace as incomplete by that many records. */
|
|
59
59
|
private droppedByTrace;
|
|
60
|
+
/** Whether a trace's root carries `hue.sdk.dropped_records`; off for a replay of recorded
|
|
61
|
+
* spans, which must send the same root on every run. */
|
|
62
|
+
private marksDroppedRecords;
|
|
60
63
|
private instrumentationFailures;
|
|
61
64
|
private diagnosticPending;
|
|
62
65
|
private lastDiagnosticAt;
|
package/dist/transport.js
CHANGED
|
@@ -184,6 +184,9 @@ export class HueTransport {
|
|
|
184
184
|
* over the request limit), reported on the trace's root span as `hue.sdk.dropped_records` when
|
|
185
185
|
* the root is exported, so Hue reads the trace as incomplete by that many records. */
|
|
186
186
|
droppedByTrace = new Map();
|
|
187
|
+
/** Whether a trace's root carries `hue.sdk.dropped_records`; off for a replay of recorded
|
|
188
|
+
* spans, which must send the same root on every run. */
|
|
189
|
+
marksDroppedRecords = true;
|
|
187
190
|
instrumentationFailures = 0;
|
|
188
191
|
diagnosticPending = false;
|
|
189
192
|
lastDiagnosticAt = -Infinity;
|
|
@@ -822,13 +825,20 @@ export class HueTransport {
|
|
|
822
825
|
* (`consumeDroppedRecords`): a root written again under retry says the same, and a root whose
|
|
823
826
|
* request failed has not told Hue, so its count is not forgotten with it. */
|
|
824
827
|
withDroppedRecords(span) {
|
|
825
|
-
if (span.parentSpanContext)
|
|
828
|
+
if (span.parentSpanContext || !this.marksDroppedRecords)
|
|
826
829
|
return span;
|
|
827
830
|
const count = this.droppedByTrace.get(span.spanContext().traceId);
|
|
828
831
|
if (!count)
|
|
829
832
|
return span;
|
|
830
833
|
return { ...span, attributes: { ...span.attributes, [DROPPED_RECORDS_KEY]: count } };
|
|
831
834
|
}
|
|
835
|
+
/** @internal For an import that replays recorded spans: roots never carry the count of
|
|
836
|
+
* records this run did not deliver, so a later run that delivers them sends each root as it
|
|
837
|
+
* was first sent, which Hue keeps as an exact replay. The losses are still counted and
|
|
838
|
+
* reported. */
|
|
839
|
+
omitDroppedRecords() {
|
|
840
|
+
this.marksDroppedRecords = false;
|
|
841
|
+
}
|
|
832
842
|
/** @internal Exporter callback: these counts reached Hue on their traces' roots. Only what a
|
|
833
843
|
* root carried is forgotten: a record lost while its request was in flight is still counted. */
|
|
834
844
|
consumeDroppedRecords(counts) {
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** Package version shared by the instrumentation scope and the export User-Agent. */
|
|
2
|
-
export declare const sdkVersion = "0.18.
|
|
2
|
+
export declare const sdkVersion = "0.18.1";
|
package/dist/version.js
CHANGED