@hue-run/sdk 0.16.0 → 0.18.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/CLI.md +18 -11
- package/EVALUATIONS.md +53 -2
- package/README.md +295 -45
- package/dist/attachment.d.ts +82 -0
- package/dist/attachment.js +225 -0
- package/dist/blobs.d.ts +73 -17
- package/dist/blobs.js +247 -96
- package/dist/cli/eval-direct.d.ts +3 -2
- package/dist/cli/eval-direct.js +49 -6
- package/dist/client.d.ts +21 -1
- package/dist/client.js +217 -32
- package/dist/evals/client.d.ts +14 -1
- package/dist/evals/client.js +73 -9
- package/dist/evals/files.d.ts +11 -1
- package/dist/evals/files.js +172 -20
- package/dist/evals/media.d.ts +217 -0
- package/dist/evals/media.js +907 -0
- package/dist/evals/runner.js +130 -106
- package/dist/evals/types.d.ts +48 -0
- package/dist/evals.d.ts +2 -0
- package/dist/evals.js +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/inline-files.d.ts +160 -28
- package/dist/inline-files.js +809 -86
- package/dist/json-edits.d.ts +24 -0
- package/dist/json-edits.js +187 -0
- package/dist/privacy.d.ts +14 -1
- package/dist/privacy.js +86 -25
- package/dist/safety.d.ts +7 -2
- package/dist/safety.js +22 -4
- package/dist/snapshot.d.ts +7 -4
- package/dist/snapshot.js +177 -38
- package/dist/transport.d.ts +18 -0
- package/dist/transport.js +544 -38
- package/dist/types.d.ts +28 -7
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/** How deep in a value an attachment's part may sit and still be uploaded: Hue reads media parts
|
|
2
|
+
* within ten levels of the value's root. */
|
|
3
|
+
export declare const MAX_ATTACHMENT_DEPTH = 10;
|
|
4
|
+
/** Options for {@link Attachment}. Give exactly one of `data` and `path`. */
|
|
5
|
+
export interface AttachmentOptions {
|
|
6
|
+
/** The file's bytes (a `Buffer`, typed array or `ArrayBuffer`), or a string holding them as
|
|
7
|
+
* base64 or a `data:` URL. */
|
|
8
|
+
data?: ArrayBufferView | ArrayBuffer | string;
|
|
9
|
+
/** A file to read when the attachment is created (Node.js file system). */
|
|
10
|
+
path?: string;
|
|
11
|
+
/** The file's media type, such as `image/png` or `application/pdf`. */
|
|
12
|
+
contentType: string;
|
|
13
|
+
/** The file name, recorded when `captureContent` is true. */
|
|
14
|
+
name?: string;
|
|
15
|
+
}
|
|
16
|
+
/** What an attachment holds, read by the client and transport only. */
|
|
17
|
+
export interface AttachmentState {
|
|
18
|
+
/** The file's bytes; undefined for an attachment that could not be read or is invalid. */
|
|
19
|
+
bytes?: Uint8Array;
|
|
20
|
+
sha256?: string;
|
|
21
|
+
contentType: string;
|
|
22
|
+
/** The name, when it is a valid media name. */
|
|
23
|
+
name?: string;
|
|
24
|
+
/** A name was given and is not a valid media name: it is omitted and counted. */
|
|
25
|
+
invalidName: boolean;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* A file placed in a recorded value, the equivalent of Langfuse's `LangfuseMedia` and
|
|
29
|
+
* Braintrust's `Attachment`. Put it anywhere in a `withSpan()` input or output, in `model()`
|
|
30
|
+
* messages, in a tool's arguments or result, in `recordMessages()` messages or in a span
|
|
31
|
+
* attribute passed to `withSpan()`. Hue records it at that position as a GenAI `blob` part that
|
|
32
|
+
* names the file by digest (`{ type: "blob", mime_type, modality, sha256, size, name }`) and
|
|
33
|
+
* uploads its bytes during export, listed under the record's `hue.blobs`.
|
|
34
|
+
*
|
|
35
|
+
* The bytes are read and hashed when the attachment is created; `data` and `path` are limited to
|
|
36
|
+
* 25 MiB. Creating one never throws: an attachment without exactly one of `data` and `path`, with
|
|
37
|
+
* unreadable data, larger data or an invalid `contentType` records `null` where it is placed and
|
|
38
|
+
* counts an instrumentation failure; an invalid `name` alone is omitted and counted.
|
|
39
|
+
*/
|
|
40
|
+
export declare class Attachment {
|
|
41
|
+
/** The media type given. */
|
|
42
|
+
readonly contentType: string;
|
|
43
|
+
/** The name given, when it is a valid media name. */
|
|
44
|
+
readonly name: string | undefined;
|
|
45
|
+
/** Lowercase hex SHA-256 of the bytes; undefined for an invalid attachment. */
|
|
46
|
+
readonly sha256: string | undefined;
|
|
47
|
+
/** Size of the bytes; undefined for an invalid attachment. */
|
|
48
|
+
readonly size: number | undefined;
|
|
49
|
+
constructor(options: AttachmentOptions);
|
|
50
|
+
}
|
|
51
|
+
/** The modality a media type implies, as Hue reads a part's media type. */
|
|
52
|
+
export declare function modalityOf(contentType: string): string;
|
|
53
|
+
/** An attachment's digest-form GenAI part, the name only with content capture; undefined for an
|
|
54
|
+
* invalid attachment. */
|
|
55
|
+
export declare function attachmentPart(state: AttachmentState, captureContent: boolean): Record<string, unknown> | undefined;
|
|
56
|
+
/** One attachment as a value is encoded: its state, its part (undefined when invalid), and the
|
|
57
|
+
* JSON pointer and depth of its position. */
|
|
58
|
+
export interface PlacedAttachment {
|
|
59
|
+
state: AttachmentState;
|
|
60
|
+
part?: Record<string, unknown>;
|
|
61
|
+
pointer: string;
|
|
62
|
+
depth: number;
|
|
63
|
+
}
|
|
64
|
+
/** Whether a value holds an attachment anywhere, within the encoder's bounds. */
|
|
65
|
+
export declare function holdsAttachment(value: unknown): boolean;
|
|
66
|
+
/** A JSON pointer's reference token. */
|
|
67
|
+
export declare function pointerSegment(segment: string): string;
|
|
68
|
+
/** The digest parts in `value` (within {@link MAX_ATTACHMENT_DEPTH}) that are the `files`' own
|
|
69
|
+
* parts: an object with `type` `blob`, no `content`, and the file's `sha256`, `size` and
|
|
70
|
+
* `mime_type`. Each found part takes one file of that digest, in the value's order; a part's
|
|
71
|
+
* `name`, when valid, is the one it carries. */
|
|
72
|
+
export declare function locateParts<T extends {
|
|
73
|
+
digest: {
|
|
74
|
+
sha256: string;
|
|
75
|
+
size: number;
|
|
76
|
+
};
|
|
77
|
+
contentType: string;
|
|
78
|
+
}>(value: unknown, files: readonly T[]): {
|
|
79
|
+
file: T;
|
|
80
|
+
pointer: string;
|
|
81
|
+
name?: string;
|
|
82
|
+
}[];
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { closeSync, fstatSync, openSync, readSync } from "node:fs";
|
|
3
|
+
import { types as utilTypes } from "node:util";
|
|
4
|
+
import { MAX_FILE_DATA_BYTES } from "./config.js";
|
|
5
|
+
import { inlineFileBytes, isMediaContentType, isMediaName } from "./inline-files.js";
|
|
6
|
+
/** How deep in a value an attachment's part may sit and still be uploaded: Hue reads media parts
|
|
7
|
+
* within ten levels of the value's root. */
|
|
8
|
+
export const MAX_ATTACHMENT_DEPTH = 10;
|
|
9
|
+
const states = new WeakMap();
|
|
10
|
+
/** A strict base64 string: alphabet characters only, padded to a multiple of four. */
|
|
11
|
+
const BASE64 = /^[A-Za-z0-9+/]*={0,2}$/;
|
|
12
|
+
/** The bytes of a string attachment: a `data:` URL by its own encoding, else strict base64. */
|
|
13
|
+
function stringBytes(text) {
|
|
14
|
+
if (text.startsWith("data:")) {
|
|
15
|
+
const decoded = inlineFileBytes(text);
|
|
16
|
+
return decoded.decoded ? decoded.bytes : undefined;
|
|
17
|
+
}
|
|
18
|
+
if (text.length % 4 !== 0 || !BASE64.test(text))
|
|
19
|
+
return undefined;
|
|
20
|
+
return Buffer.from(text, "base64");
|
|
21
|
+
}
|
|
22
|
+
/** A file's bytes, read through one descriptor and never past the limit. */
|
|
23
|
+
function fileBytes(path) {
|
|
24
|
+
const descriptor = openSync(path, "r");
|
|
25
|
+
try {
|
|
26
|
+
if (fstatSync(descriptor).size > MAX_FILE_DATA_BYTES)
|
|
27
|
+
return undefined;
|
|
28
|
+
// Read to its end, which may lie past its size as stated (a file that grew, a pipe), but
|
|
29
|
+
// never more than one byte past the limit.
|
|
30
|
+
const parts = [];
|
|
31
|
+
let total = 0;
|
|
32
|
+
for (;;) {
|
|
33
|
+
const room = Math.min(1 << 20, MAX_FILE_DATA_BYTES + 1 - total);
|
|
34
|
+
if (room <= 0)
|
|
35
|
+
return undefined;
|
|
36
|
+
const buffer = Buffer.alloc(room);
|
|
37
|
+
const read = readSync(descriptor, buffer, 0, room, null);
|
|
38
|
+
if (read === 0)
|
|
39
|
+
break;
|
|
40
|
+
parts.push(buffer.subarray(0, read));
|
|
41
|
+
total += read;
|
|
42
|
+
}
|
|
43
|
+
return Buffer.concat(parts, total);
|
|
44
|
+
}
|
|
45
|
+
finally {
|
|
46
|
+
closeSync(descriptor);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function attachmentBytes(options) {
|
|
50
|
+
const { data, path } = options;
|
|
51
|
+
if ((data === undefined) === (path === undefined))
|
|
52
|
+
return undefined;
|
|
53
|
+
let bytes;
|
|
54
|
+
if (typeof path === "string")
|
|
55
|
+
bytes = path ? fileBytes(path) : undefined;
|
|
56
|
+
else if (typeof data === "string") {
|
|
57
|
+
if (data.length > 4 * MAX_FILE_DATA_BYTES)
|
|
58
|
+
return undefined;
|
|
59
|
+
bytes = stringBytes(data);
|
|
60
|
+
}
|
|
61
|
+
// A copy, so a later change to the caller's buffer cannot change what the digest describes;
|
|
62
|
+
// bytes over the limit are refused before anything is copied.
|
|
63
|
+
else if (ArrayBuffer.isView(data)) {
|
|
64
|
+
if (data.byteLength > MAX_FILE_DATA_BYTES)
|
|
65
|
+
return undefined;
|
|
66
|
+
bytes = new Uint8Array(data.buffer, data.byteOffset, data.byteLength).slice();
|
|
67
|
+
}
|
|
68
|
+
else if (data instanceof ArrayBuffer) {
|
|
69
|
+
if (data.byteLength > MAX_FILE_DATA_BYTES)
|
|
70
|
+
return undefined;
|
|
71
|
+
bytes = new Uint8Array(data.slice(0));
|
|
72
|
+
}
|
|
73
|
+
return bytes && bytes.byteLength <= MAX_FILE_DATA_BYTES ? bytes : undefined;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* A file placed in a recorded value, the equivalent of Langfuse's `LangfuseMedia` and
|
|
77
|
+
* Braintrust's `Attachment`. Put it anywhere in a `withSpan()` input or output, in `model()`
|
|
78
|
+
* messages, in a tool's arguments or result, in `recordMessages()` messages or in a span
|
|
79
|
+
* attribute passed to `withSpan()`. Hue records it at that position as a GenAI `blob` part that
|
|
80
|
+
* names the file by digest (`{ type: "blob", mime_type, modality, sha256, size, name }`) and
|
|
81
|
+
* uploads its bytes during export, listed under the record's `hue.blobs`.
|
|
82
|
+
*
|
|
83
|
+
* The bytes are read and hashed when the attachment is created; `data` and `path` are limited to
|
|
84
|
+
* 25 MiB. Creating one never throws: an attachment without exactly one of `data` and `path`, with
|
|
85
|
+
* unreadable data, larger data or an invalid `contentType` records `null` where it is placed and
|
|
86
|
+
* counts an instrumentation failure; an invalid `name` alone is omitted and counted.
|
|
87
|
+
*/
|
|
88
|
+
export class Attachment {
|
|
89
|
+
/** The media type given. */
|
|
90
|
+
contentType;
|
|
91
|
+
/** The name given, when it is a valid media name. */
|
|
92
|
+
name;
|
|
93
|
+
/** Lowercase hex SHA-256 of the bytes; undefined for an invalid attachment. */
|
|
94
|
+
sha256;
|
|
95
|
+
/** Size of the bytes; undefined for an invalid attachment. */
|
|
96
|
+
size;
|
|
97
|
+
constructor(options) {
|
|
98
|
+
const contentType = options?.contentType;
|
|
99
|
+
const name = options?.name;
|
|
100
|
+
let bytes;
|
|
101
|
+
try {
|
|
102
|
+
bytes = isMediaContentType(contentType) ? attachmentBytes(options) : undefined;
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
// An unreadable path, or data that is not bytes: the attachment is invalid.
|
|
106
|
+
bytes = undefined;
|
|
107
|
+
}
|
|
108
|
+
const sha256 = bytes ? createHash("sha256").update(bytes).digest("hex") : undefined;
|
|
109
|
+
this.contentType = typeof contentType === "string" ? contentType : "";
|
|
110
|
+
this.name = isMediaName(name) ? name : undefined;
|
|
111
|
+
this.sha256 = sha256;
|
|
112
|
+
this.size = bytes?.byteLength;
|
|
113
|
+
states.set(this, {
|
|
114
|
+
...(bytes ? { bytes, sha256 } : {}),
|
|
115
|
+
contentType: this.contentType,
|
|
116
|
+
...(this.name === undefined ? {} : { name: this.name }),
|
|
117
|
+
invalidName: name !== undefined && name !== null && this.name === undefined,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/** @internal The attachment's bytes and validated fields; undefined for anything else. */
|
|
122
|
+
export function attachmentState(value) {
|
|
123
|
+
return value instanceof Attachment ? states.get(value) : undefined;
|
|
124
|
+
}
|
|
125
|
+
/** The modality a media type implies, as Hue reads a part's media type. */
|
|
126
|
+
export function modalityOf(contentType) {
|
|
127
|
+
const type = contentType.split(";", 1)[0].trim().toLowerCase();
|
|
128
|
+
if (type.startsWith("image/"))
|
|
129
|
+
return "image";
|
|
130
|
+
if (type.startsWith("audio/"))
|
|
131
|
+
return "audio";
|
|
132
|
+
if (type.startsWith("video/"))
|
|
133
|
+
return "video";
|
|
134
|
+
if (type.startsWith("text/") ||
|
|
135
|
+
type === "application/pdf" ||
|
|
136
|
+
type === "application/rtf" ||
|
|
137
|
+
type === "application/msword" ||
|
|
138
|
+
type === "application/vnd.ms-excel" ||
|
|
139
|
+
type === "application/vnd.ms-powerpoint" ||
|
|
140
|
+
type.startsWith("application/vnd.openxmlformats-officedocument."))
|
|
141
|
+
return "document";
|
|
142
|
+
return "file";
|
|
143
|
+
}
|
|
144
|
+
/** An attachment's digest-form GenAI part, the name only with content capture; undefined for an
|
|
145
|
+
* invalid attachment. */
|
|
146
|
+
export function attachmentPart(state, captureContent) {
|
|
147
|
+
if (!state.bytes || !state.sha256)
|
|
148
|
+
return undefined;
|
|
149
|
+
return {
|
|
150
|
+
type: "blob",
|
|
151
|
+
mime_type: state.contentType,
|
|
152
|
+
modality: modalityOf(state.contentType),
|
|
153
|
+
sha256: state.sha256,
|
|
154
|
+
size: state.bytes.byteLength,
|
|
155
|
+
...(captureContent && state.name !== undefined ? { name: state.name } : {}),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
/** Whether a value holds an attachment anywhere, within the encoder's bounds. */
|
|
159
|
+
export function holdsAttachment(value) {
|
|
160
|
+
let nodes = 0;
|
|
161
|
+
const visit = (item, depth) => {
|
|
162
|
+
// Proxies and accessors are never touched: reading them could run application code.
|
|
163
|
+
if (item === null || typeof item !== "object" || utilTypes.isProxy(item))
|
|
164
|
+
return false;
|
|
165
|
+
if (item instanceof Attachment)
|
|
166
|
+
return true;
|
|
167
|
+
if (depth > 32 || ++nodes > 65_536)
|
|
168
|
+
return false;
|
|
169
|
+
const prototype = Object.getPrototypeOf(item);
|
|
170
|
+
if (!Array.isArray(item) && prototype !== Object.prototype && prototype !== null)
|
|
171
|
+
return false;
|
|
172
|
+
return Object.keys(item).some((key) => {
|
|
173
|
+
const descriptor = Object.getOwnPropertyDescriptor(item, key);
|
|
174
|
+
return (descriptor !== undefined && "value" in descriptor && visit(descriptor.value, depth + 1));
|
|
175
|
+
});
|
|
176
|
+
};
|
|
177
|
+
try {
|
|
178
|
+
return visit(value, 0);
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
/** A JSON pointer's reference token. */
|
|
185
|
+
export function pointerSegment(segment) {
|
|
186
|
+
return segment.replaceAll("~", "~0").replaceAll("/", "~1");
|
|
187
|
+
}
|
|
188
|
+
/** The digest parts in `value` (within {@link MAX_ATTACHMENT_DEPTH}) that are the `files`' own
|
|
189
|
+
* parts: an object with `type` `blob`, no `content`, and the file's `sha256`, `size` and
|
|
190
|
+
* `mime_type`. Each found part takes one file of that digest, in the value's order; a part's
|
|
191
|
+
* `name`, when valid, is the one it carries. */
|
|
192
|
+
export function locateParts(value, files) {
|
|
193
|
+
const pending = [...files];
|
|
194
|
+
const found = [];
|
|
195
|
+
let nodes = 0;
|
|
196
|
+
const visit = (node, pointer, depth) => {
|
|
197
|
+
if (!pending.length || depth > MAX_ATTACHMENT_DEPTH || ++nodes > 1_048_576)
|
|
198
|
+
return;
|
|
199
|
+
if (Array.isArray(node)) {
|
|
200
|
+
node.forEach((item, index) => visit(item, `${pointer}/${index}`, depth + 1));
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (node === null || typeof node !== "object")
|
|
204
|
+
return;
|
|
205
|
+
const part = node;
|
|
206
|
+
if (part.type === "blob" && !Object.hasOwn(part, "content")) {
|
|
207
|
+
const index = pending.findIndex((file) => part.sha256 === file.digest.sha256 &&
|
|
208
|
+
part.size === file.digest.size &&
|
|
209
|
+
part.mime_type === file.contentType);
|
|
210
|
+
if (index !== -1) {
|
|
211
|
+
const [file] = pending.splice(index, 1);
|
|
212
|
+
found.push({
|
|
213
|
+
file: file,
|
|
214
|
+
pointer,
|
|
215
|
+
...(isMediaName(part.name) ? { name: part.name } : {}),
|
|
216
|
+
});
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
for (const [key, item] of Object.entries(part))
|
|
221
|
+
visit(item, `${pointer}/${pointerSegment(key)}`, depth + 1);
|
|
222
|
+
};
|
|
223
|
+
visit(value, "", 0);
|
|
224
|
+
return found;
|
|
225
|
+
}
|
package/dist/blobs.d.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
+
import { type HrTime } from "@opentelemetry/api";
|
|
2
|
+
import { type MediaDigestMemo, type FileDigest } from "./inline-files.js";
|
|
1
3
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* as it was before uploads existed: cut to the inline limit and listed under `hue.truncated` (an
|
|
7
|
-
* inline file as its digest), reported as a warning and counted.
|
|
4
|
+
* Large values and binary media travel apart from their span through reservation, presigned PUT,
|
|
5
|
+
* and completion. Text keeps a 16 KiB prefix. Binary media keeps digest metadata on its part.
|
|
6
|
+
* Successful uploads are listed in `hue.blobs`; failures keep inline fallback or file metadata,
|
|
7
|
+
* are reported as warnings, and are counted.
|
|
8
8
|
*/
|
|
9
9
|
/** The span attribute listing the values the SDK uploaded apart from the span: one compact JSON
|
|
10
|
-
* object per value, `{"key", "sha256", "size", "content_type"}`,
|
|
10
|
+
* object per value, `{"key", "sha256", "size", "content_type"}`, with an optional `name`. */
|
|
11
11
|
export declare const BLOBS_KEY = "hue.blobs";
|
|
12
12
|
/** The largest value one upload may hold (`Hue-Max-Blob-Bytes`): a larger value is cut inline. */
|
|
13
13
|
export declare const MAX_BLOB_BYTES = 1000000000;
|
|
@@ -24,7 +24,7 @@ export declare const MAX_BLOB_ENTRIES = 64;
|
|
|
24
24
|
export declare const MAX_HELD_BLOB_BYTES: number;
|
|
25
25
|
/** How long one export may spend uploading its values, on top of its requests. */
|
|
26
26
|
export declare function uploadBudgetMillis(timeoutMillis: number): number;
|
|
27
|
-
/** Why
|
|
27
|
+
/** Why content kept its fallback instead of being uploaded. */
|
|
28
28
|
export type FallbackReason = "unsupported" | "failed" | "tooLarge" | "budget";
|
|
29
29
|
/** The fixed description of each fallback, as an export issue reports it. */
|
|
30
30
|
export declare const FALLBACK_MESSAGES: Record<FallbackReason, string>;
|
|
@@ -38,7 +38,37 @@ export interface OffloadCandidate {
|
|
|
38
38
|
export interface OffloadCollector {
|
|
39
39
|
candidates: OffloadCandidate[];
|
|
40
40
|
cut: string[];
|
|
41
|
+
attachments?: MediaAttachment[];
|
|
42
|
+
/** The span's own attributes admission inspected for media within the record's allowance:
|
|
43
|
+
* export inspects only these. Absent for a record admission did not copy, and for a log record,
|
|
44
|
+
* which export inspects within its own allowance. */
|
|
45
|
+
inspected?: ReadonlySet<string> | undefined;
|
|
46
|
+
/** Set for a log record: its values over the inline limit are cut, never uploaded whole; only
|
|
47
|
+
* the media in its inspected attributes and its body upload, with the attachments placed in
|
|
48
|
+
* its body (`attachments`, keyed `body#<pointer>`). */
|
|
49
|
+
log?: boolean;
|
|
50
|
+
/** A log record's structured body, redacted whole, whose media upload under `body#<pointer>`. */
|
|
51
|
+
body?: {
|
|
52
|
+
value: unknown;
|
|
53
|
+
};
|
|
54
|
+
/** The strings of a log record's body the queue cut when the record was admitted: never read
|
|
55
|
+
* as files, since their bytes are not the file's. */
|
|
56
|
+
bodyCut?: Set<string>;
|
|
41
57
|
}
|
|
58
|
+
export interface MediaAttachment {
|
|
59
|
+
bytes: Uint8Array;
|
|
60
|
+
digest: FileDigest;
|
|
61
|
+
contentType: string;
|
|
62
|
+
name?: string;
|
|
63
|
+
eventTime?: HrTime;
|
|
64
|
+
/** For an attachment placed in a value: the record's attribute (or `body`) that holds its part. */
|
|
65
|
+
attribute?: string;
|
|
66
|
+
/** For a placed attachment found in its value: its `hue.blobs` key, `<attribute>#<pointer>`. */
|
|
67
|
+
key?: string;
|
|
68
|
+
}
|
|
69
|
+
/** The `hue.blobs` key of a staged file: its part's for an attachment placed in a value, else
|
|
70
|
+
* its `hue.file` event's. */
|
|
71
|
+
export declare function mediaKey(file: MediaAttachment): string;
|
|
42
72
|
/** The outcome of one export's uploads, reported once the export's records are placed. */
|
|
43
73
|
export interface OffloadTally {
|
|
44
74
|
uploaded: number;
|
|
@@ -48,6 +78,8 @@ export interface OffloadTally {
|
|
|
48
78
|
}>;
|
|
49
79
|
}
|
|
50
80
|
export declare function newTally(): OffloadTally;
|
|
81
|
+
export declare function countFallback(tally: OffloadTally, reason: FallbackReason, traceId: string): void;
|
|
82
|
+
/** A media type accepted by the upload contract. */
|
|
51
83
|
export declare function isBlobContentType(value: unknown): value is string;
|
|
52
84
|
/** The media type an uploaded text is stored under: JSON when it reads as a JSON object or array
|
|
53
85
|
* (a structured value's text), plain UTF-8 text otherwise. Both are previewed as text. */
|
|
@@ -62,6 +94,8 @@ export declare function digestOf(value: string | Uint8Array): Promise<{
|
|
|
62
94
|
sha256: string;
|
|
63
95
|
size: number;
|
|
64
96
|
}>;
|
|
97
|
+
/** The `hue.blobs` element for one uploaded value. */
|
|
98
|
+
export declare function blobEntry(key: string, sha256: string, size: number, contentType: string, name?: string): string;
|
|
65
99
|
/** What one export's uploads share: their deadline, and the export's hold for a receiver that
|
|
66
100
|
* limited the rate (`wait` resolves false when the hold may not take `millis` more). */
|
|
67
101
|
export interface UploadContext {
|
|
@@ -100,6 +134,7 @@ export declare class BlobUploader {
|
|
|
100
134
|
private running;
|
|
101
135
|
private waiting;
|
|
102
136
|
private requests;
|
|
137
|
+
private outcomes;
|
|
103
138
|
constructor(options: UploaderOptions);
|
|
104
139
|
/** Whether a value over the inline limit is worth holding whole for upload now. */
|
|
105
140
|
available(): boolean;
|
|
@@ -114,12 +149,12 @@ export declare class BlobUploader {
|
|
|
114
149
|
private pause;
|
|
115
150
|
/**
|
|
116
151
|
* Stores `value` under `key` of the trace: reserved, uploaded unless Hue already holds it, and
|
|
117
|
-
* completed.
|
|
152
|
+
* completed. Only a fresh upload takes a {@link limit} slot: another reference to bytes the
|
|
153
|
+
* trace is already storing shares that upload's outcome without one, so repeated files never
|
|
154
|
+
* hold the slots new files need. The caller must not hold a slot.
|
|
118
155
|
*/
|
|
119
|
-
store(traceId: string, key: string, value: string | Uint8Array, contentType: string, digest:
|
|
120
|
-
|
|
121
|
-
size: number;
|
|
122
|
-
}, upload: UploadContext): Promise<Outcome>;
|
|
156
|
+
store(traceId: string, key: string, value: string | Uint8Array, contentType: string, digest: FileDigest, upload: UploadContext): Promise<Outcome>;
|
|
157
|
+
private storeFresh;
|
|
123
158
|
private jsonHeaders;
|
|
124
159
|
/** Reserves the value: Hue answers that it holds it already, or a presigned PUT. A rate limit
|
|
125
160
|
* is waited out within the export's hold, a transient refusal retried once. */
|
|
@@ -145,20 +180,41 @@ export interface OffloadOptions {
|
|
|
145
180
|
valueBytes: number;
|
|
146
181
|
upload: UploadContext;
|
|
147
182
|
tally: OffloadTally;
|
|
183
|
+
memo?: MediaDigestMemo;
|
|
148
184
|
}
|
|
149
185
|
/**
|
|
150
|
-
* A span's own attributes with each candidate placed:
|
|
151
|
-
*
|
|
152
|
-
*
|
|
186
|
+
* A span's own attributes with each candidate placed: binary media becomes a digest, while
|
|
187
|
+
* text media over 64 KiB retains a 16 KiB prefix after upload or a digest on failure.
|
|
188
|
+
* A value still over the inline limit is uploaded and replaced by its first
|
|
153
189
|
* 16 KiB (or cut to the limit), each upload listed under `hue.blobs`, each whole value cut or
|
|
154
190
|
* uploaded listed under `hue.truncated`. Every fallback is counted in the tally.
|
|
155
191
|
*/
|
|
156
192
|
export declare function offloadAttributes(attributes: Record<string, unknown>, collector: OffloadCollector, options: OffloadOptions): Promise<Record<string, unknown>>;
|
|
193
|
+
/**
|
|
194
|
+
* A record's own attributes, and a log record's body, with each candidate placed (see
|
|
195
|
+
* {@link offloadAttributes}). A file is uploaded and placed in its digest form (a text file in a
|
|
196
|
+
* provider part keeps a 16 KiB prefix), its `hue.blobs` entry keyed `<attribute>#<pointer>` or, in
|
|
197
|
+
* a log record's body, `body#<pointer>`. A file that is not uploaded keeps its bytes inline, except
|
|
198
|
+
* in a span's own recorded messages, where it keeps its digest as before. A log record's values
|
|
199
|
+
* over the inline limit are cut, never uploaded whole, and a body still over it is replaced by the
|
|
200
|
+
* receiver's marker, as before.
|
|
201
|
+
*/
|
|
202
|
+
export declare function offloadRecord(attributes: Record<string, unknown>, collector: OffloadCollector, options: OffloadOptions): Promise<{
|
|
203
|
+
attributes: Record<string, unknown>;
|
|
204
|
+
body?: unknown;
|
|
205
|
+
}>;
|
|
157
206
|
/**
|
|
158
207
|
* The span's own attributes with every candidate placed as it was before uploads existed, for an
|
|
159
208
|
* upload step that failed unexpectedly: a message's large inline files as their digest, a value
|
|
160
209
|
* over the inline limit cut to it (bytes replaced by the receiver's marker) and listed under
|
|
161
210
|
* `hue.truncated`.
|
|
162
211
|
*/
|
|
163
|
-
export declare function fallbackAttributes(attributes: Record<string, unknown>, collector: OffloadCollector, valueBytes: number, tally: OffloadTally, traceId: string): Record<string, unknown>;
|
|
212
|
+
export declare function fallbackAttributes(attributes: Record<string, unknown>, collector: OffloadCollector, valueBytes: number, tally: OffloadTally, traceId: string, memo?: MediaDigestMemo): Record<string, unknown>;
|
|
213
|
+
/** A record's attributes, and a log record's body, placed as {@link fallbackAttributes} places
|
|
214
|
+
* them: a log record's files keep their bytes inline, and its body over the inline limit is
|
|
215
|
+
* replaced by the receiver's marker, as before. */
|
|
216
|
+
export declare function fallbackRecord(attributes: Record<string, unknown>, collector: OffloadCollector, valueBytes: number, tally: OffloadTally, traceId: string, memo?: MediaDigestMemo): {
|
|
217
|
+
attributes: Record<string, unknown>;
|
|
218
|
+
body?: unknown;
|
|
219
|
+
};
|
|
164
220
|
export {};
|