@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
package/dist/evals/runner.js
CHANGED
|
@@ -520,9 +520,10 @@ export async function runExperiment(options) {
|
|
|
520
520
|
// copies of its inputs, its work, the evaluator's downloads and the staged outputs.
|
|
521
521
|
// A retry gets its own, so an agent a dead run left behind cannot write into it.
|
|
522
522
|
const worldDirectoryOf = (itemId, attempt) => join(filesRoot, `world-case-${uuid(itemId)}${attempt ? `-${uuid(attempt)}` : ""}`);
|
|
523
|
+
const directDirectoryOf = (itemId) => join(filesRoot, `case-${uuid(itemId)}`);
|
|
523
524
|
const runCase = async (item, files) => {
|
|
524
525
|
const file = `case-${uuid(item.id)}`;
|
|
525
|
-
const directDirectory =
|
|
526
|
+
const directDirectory = directDirectoryOf(item.id);
|
|
526
527
|
const caseDirectoryOf = (frozen) => frozen.environmentVersionId ? files.directory : directDirectory;
|
|
527
528
|
let checkpoint;
|
|
528
529
|
try {
|
|
@@ -605,122 +606,139 @@ export async function runExperiment(options) {
|
|
|
605
606
|
const outputDirectory = join(caseDirectory, "work");
|
|
606
607
|
await mkdir(outputDirectory, { recursive: true, mode: 0o700 });
|
|
607
608
|
const failureSequenceBefore = options.hue.transport.getFailureSequence();
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
await store.write(file, { stage: "running", executionId: execution.id, ...attempt });
|
|
623
|
-
let state = "succeeded";
|
|
624
|
-
let output;
|
|
625
|
-
let generated;
|
|
626
|
-
let targetError;
|
|
627
|
-
try {
|
|
628
|
-
const result = await options.target(targetInputs, {
|
|
629
|
-
config: targetConfig,
|
|
630
|
-
item: structuredClone(frozenCase),
|
|
631
|
-
span,
|
|
632
|
-
executionId: execution.id,
|
|
633
|
-
files: structuredClone(inputFiles),
|
|
634
|
-
outputDirectory,
|
|
609
|
+
let outcome;
|
|
610
|
+
try {
|
|
611
|
+
checkpoint = await options.hue.withSpan("hue.experiment.case", async (span) => {
|
|
612
|
+
const start = {
|
|
613
|
+
stage: "starting",
|
|
614
|
+
startKey: randomUUID(),
|
|
615
|
+
traceExternalId: span.traceId,
|
|
616
|
+
...(retryOf ? { previousExecutionId: retryOf } : {}),
|
|
617
|
+
};
|
|
618
|
+
await store.write(file, start);
|
|
619
|
+
const execution = await options.client.startExecution(experiment.id, item.id, {
|
|
620
|
+
idempotencyKey: start.startKey,
|
|
621
|
+
traceExternalId: span.traceId,
|
|
622
|
+
...retry(retryOf),
|
|
635
623
|
});
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
// resumed run to replace instead of being scored as cancelled.
|
|
648
|
-
if (error instanceof TargetCancelledError && options.signal?.aborted) {
|
|
649
|
-
options.hue.recordError(span.span, error);
|
|
650
|
-
await store.write(file, {
|
|
651
|
-
stage: "interrupted",
|
|
624
|
+
const attempt = retryOf ? { attempt: retryOf } : {};
|
|
625
|
+
await store.write(file, { stage: "running", executionId: execution.id, ...attempt });
|
|
626
|
+
let state = "succeeded";
|
|
627
|
+
let output;
|
|
628
|
+
let generated;
|
|
629
|
+
let targetError;
|
|
630
|
+
try {
|
|
631
|
+
const result = await options.target(targetInputs, {
|
|
632
|
+
config: targetConfig,
|
|
633
|
+
item: structuredClone(frozenCase),
|
|
634
|
+
span,
|
|
652
635
|
executionId: execution.id,
|
|
653
|
-
|
|
636
|
+
files: structuredClone(inputFiles),
|
|
637
|
+
outputDirectory,
|
|
654
638
|
});
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
// Output validation is outside the target catch: a successful target never becomes an agent error because upload serialization failed.
|
|
662
|
-
if (output !== undefined) {
|
|
663
|
-
try {
|
|
664
|
-
output = json(output);
|
|
639
|
+
if (result instanceof TargetResult) {
|
|
640
|
+
output = result.output;
|
|
641
|
+
generated = result.files;
|
|
642
|
+
}
|
|
643
|
+
else
|
|
644
|
+
output = result;
|
|
665
645
|
}
|
|
666
646
|
catch (error) {
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
//
|
|
670
|
-
|
|
671
|
-
if (
|
|
647
|
+
if (error instanceof TargetOutcomeUncertainError)
|
|
648
|
+
throw error;
|
|
649
|
+
// A stop the caller asked for is no outcome: the execution stays open for the
|
|
650
|
+
// resumed run to replace instead of being scored as cancelled.
|
|
651
|
+
if (error instanceof TargetCancelledError && options.signal?.aborted) {
|
|
652
|
+
options.hue.recordError(span.span, error);
|
|
672
653
|
await store.write(file, {
|
|
673
|
-
stage: "
|
|
654
|
+
stage: "interrupted",
|
|
674
655
|
executionId: execution.id,
|
|
656
|
+
...attempt,
|
|
675
657
|
});
|
|
676
|
-
throw new OutcomeSerializationError(execution.id);
|
|
677
|
-
}
|
|
678
|
-
state = "error";
|
|
679
|
-
output = undefined;
|
|
680
|
-
targetError = new OutputTooLargeError(limit);
|
|
681
|
-
options.hue.recordError(span.span, targetError);
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
|
-
if (output !== undefined)
|
|
685
|
-
span.setOutput(output);
|
|
686
|
-
let staged = [];
|
|
687
|
-
// An empty declared list (a direct case answered only through stdout, or
|
|
688
|
-
// `withFiles(output, [])`) means no generated files, not a missing-files error.
|
|
689
|
-
if (generated !== undefined && generated.length > 0 && state === "succeeded") {
|
|
690
|
-
try {
|
|
691
|
-
staged = await stageOutputFiles(generated, join(caseDirectory, "outputs"));
|
|
692
|
-
}
|
|
693
|
-
catch (error) {
|
|
694
|
-
// Files the target declared but did not deliver are its own failure; the
|
|
695
|
-
// outcome is still saved instead of leaving the execution uncertain.
|
|
696
|
-
if (!(error instanceof OutputFileError))
|
|
697
658
|
throw error;
|
|
698
|
-
|
|
659
|
+
}
|
|
660
|
+
state = error instanceof TargetCancelledError ? "cancelled" : "error";
|
|
699
661
|
targetError = error;
|
|
700
662
|
options.hue.recordError(span.span, error);
|
|
701
663
|
}
|
|
664
|
+
// Output validation is outside the target catch: a successful target never becomes an agent error because upload serialization failed.
|
|
665
|
+
if (output !== undefined) {
|
|
666
|
+
try {
|
|
667
|
+
output = json(output);
|
|
668
|
+
}
|
|
669
|
+
catch (error) {
|
|
670
|
+
// An output over the size, count or depth bounds is the target's own failure, as
|
|
671
|
+
// a file it cannot deliver is: the case completes as an error and the run goes
|
|
672
|
+
// on. Output that is not JSON at all still stops the run for inspection.
|
|
673
|
+
const limit = error instanceof RangeError ? outputLimits.get(error.message) : undefined;
|
|
674
|
+
if (limit === undefined) {
|
|
675
|
+
await store.write(file, {
|
|
676
|
+
stage: "serialization_failed",
|
|
677
|
+
executionId: execution.id,
|
|
678
|
+
});
|
|
679
|
+
throw new OutcomeSerializationError(execution.id);
|
|
680
|
+
}
|
|
681
|
+
state = "error";
|
|
682
|
+
output = undefined;
|
|
683
|
+
targetError = new OutputTooLargeError(limit);
|
|
684
|
+
options.hue.recordError(span.span, targetError);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
if (output !== undefined)
|
|
688
|
+
span.setOutput(output);
|
|
689
|
+
let staged = [];
|
|
690
|
+
// An empty declared list (a direct case answered only through stdout, or
|
|
691
|
+
// `withFiles(output, [])`) means no generated files, not a missing-files error.
|
|
692
|
+
if (generated !== undefined && generated.length > 0 && state === "succeeded") {
|
|
693
|
+
try {
|
|
694
|
+
staged = await stageOutputFiles(generated, join(caseDirectory, "outputs"));
|
|
695
|
+
}
|
|
696
|
+
catch (error) {
|
|
697
|
+
// Files the target declared but did not deliver are its own failure; the
|
|
698
|
+
// outcome is still saved instead of leaving the execution uncertain.
|
|
699
|
+
if (!(error instanceof OutputFileError))
|
|
700
|
+
throw error;
|
|
701
|
+
state = "error";
|
|
702
|
+
targetError = error;
|
|
703
|
+
options.hue.recordError(span.span, error);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
const uploading = {
|
|
707
|
+
stage: "uploading",
|
|
708
|
+
executionId: execution.id,
|
|
709
|
+
trace: { traceId: span.traceId, spanId: span.spanId },
|
|
710
|
+
state,
|
|
711
|
+
hasOutput: output !== undefined,
|
|
712
|
+
...(options.persistResultContent && output !== undefined ? { output } : {}),
|
|
713
|
+
...(state === "error" ? { error: errorPayload(targetError) } : {}),
|
|
714
|
+
files: staged,
|
|
715
|
+
...attempt,
|
|
716
|
+
};
|
|
717
|
+
// Without persisted result content a restart cannot reconstruct the outcome; the
|
|
718
|
+
// saved stage then reports the execution as uncertain instead of guessing.
|
|
719
|
+
files.keepOutputs = true;
|
|
720
|
+
await store.write(file, uploading);
|
|
721
|
+
outcome = uploading;
|
|
722
|
+
return prepare(file, uploading, frozenCase, caseDirectory, output);
|
|
723
|
+
}, {
|
|
724
|
+
parentContext: ROOT_CONTEXT,
|
|
725
|
+
input: frozenCase.inputs,
|
|
726
|
+
attributes: { "hue.experiment.id": experiment.id, "hue.dataset.case.id": item.id },
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
catch (error) {
|
|
730
|
+
// A failure after the outcome was saved (fetching the scorers' files, uploading the
|
|
731
|
+
// target's files): the root has ended, so the count of spans the case ended is saved with
|
|
732
|
+
// the outcome, and a resume holds the trace's receipt to it before completing.
|
|
733
|
+
const spans = outcome?.trace
|
|
734
|
+
? options.hue.transport.spansEnded?.(outcome.trace.traceId)
|
|
735
|
+
: undefined;
|
|
736
|
+
if (outcome?.trace && spans !== undefined) {
|
|
737
|
+
outcome.trace.spans = spans;
|
|
738
|
+
await store.write(file, outcome);
|
|
702
739
|
}
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
executionId: execution.id,
|
|
706
|
-
trace: { traceId: span.traceId, spanId: span.spanId },
|
|
707
|
-
state,
|
|
708
|
-
hasOutput: output !== undefined,
|
|
709
|
-
...(options.persistResultContent && output !== undefined ? { output } : {}),
|
|
710
|
-
...(state === "error" ? { error: errorPayload(targetError) } : {}),
|
|
711
|
-
files: staged,
|
|
712
|
-
...attempt,
|
|
713
|
-
};
|
|
714
|
-
// Without persisted result content a restart cannot reconstruct the outcome; the
|
|
715
|
-
// saved stage then reports the execution as uncertain instead of guessing.
|
|
716
|
-
files.keepOutputs = true;
|
|
717
|
-
await store.write(file, uploading);
|
|
718
|
-
return prepare(file, uploading, frozenCase, caseDirectory, output);
|
|
719
|
-
}, {
|
|
720
|
-
parentContext: ROOT_CONTEXT,
|
|
721
|
-
input: frozenCase.inputs,
|
|
722
|
-
attributes: { "hue.experiment.id": experiment.id, "hue.dataset.case.id": item.id },
|
|
723
|
-
});
|
|
740
|
+
throw error;
|
|
741
|
+
}
|
|
724
742
|
files.keepOutputs = false;
|
|
725
743
|
// End root before waiting for both OTLP signals. Export failure leaves the prepared checkpoint intact.
|
|
726
744
|
let exportError;
|
|
@@ -888,9 +906,14 @@ export async function runExperiment(options) {
|
|
|
888
906
|
await pool(items, concurrency, async (item) => {
|
|
889
907
|
// A world case's directory goes however the case ends, a forced exit included. An
|
|
890
908
|
// interrupted upload keeps only the staged outputs it resumes from; inputs are downloaded
|
|
891
|
-
// anew for scoring. A direct case
|
|
909
|
+
// anew for scoring. A direct case's directory stays for inspection, except the scorers'
|
|
910
|
+
// copies of evaluator-only files, which go too, so no later case's target finds them.
|
|
892
911
|
const files = { keepOutputs: true, directory: worldDirectoryOf(item.id) };
|
|
893
|
-
const
|
|
912
|
+
const evaluatorInputs = join(directDirectoryOf(item.id), "evaluator-inputs");
|
|
913
|
+
const untrack = onForcedExit(() => {
|
|
914
|
+
releaseWorldFilesSync(files.directory, files.keepOutputs);
|
|
915
|
+
rmSync(evaluatorInputs, { recursive: true, force: true });
|
|
916
|
+
});
|
|
894
917
|
try {
|
|
895
918
|
await runCase(item, files);
|
|
896
919
|
await releaseWorldFiles(files.directory, false);
|
|
@@ -900,6 +923,7 @@ export async function runExperiment(options) {
|
|
|
900
923
|
throw error;
|
|
901
924
|
}
|
|
902
925
|
finally {
|
|
926
|
+
await removeCaseFiles(evaluatorInputs);
|
|
903
927
|
untrack();
|
|
904
928
|
}
|
|
905
929
|
});
|
package/dist/evals/types.d.ts
CHANGED
|
@@ -172,7 +172,53 @@ export interface CaseWrite {
|
|
|
172
172
|
metadata?: Record<string, JsonValue>;
|
|
173
173
|
/** Immutable simulated-world version selected for this case. */
|
|
174
174
|
environmentVersionId?: string | null;
|
|
175
|
+
/** Input files pinned to the case, at most 32: artifacts this project already holds, or bytes
|
|
176
|
+
* the client uploads first. Roles other than `org_template` and `evaluator_reference` reach the
|
|
177
|
+
* target; those two reach only scorers. */
|
|
178
|
+
files?: CaseFileWrite[];
|
|
175
179
|
}
|
|
180
|
+
/** A pinned input file as the case API takes it. */
|
|
181
|
+
export interface CaseFileReference {
|
|
182
|
+
/** A ready artifact of this project. */
|
|
183
|
+
artifactId: string;
|
|
184
|
+
/** Who receives the file. */
|
|
185
|
+
role: CaseFile["role"];
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
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 once from `path`. An uploaded file
|
|
190
|
+
* must have one of `outputContentTypes`, a well-formed name of 1–255 UTF-16 code units without
|
|
191
|
+
* path separators or control characters, and 1 byte to 25 MiB.
|
|
192
|
+
*/
|
|
193
|
+
export type CaseFileWrite = (CaseFileReference & {
|
|
194
|
+
/** Not set on an artifact reference: the artifact keeps its own name. */
|
|
195
|
+
filename?: undefined;
|
|
196
|
+
/** Not set on an artifact reference: the artifact keeps its own type. */
|
|
197
|
+
contentType?: undefined;
|
|
198
|
+
/** Not set in this form. */
|
|
199
|
+
bytes?: undefined;
|
|
200
|
+
/** Not set in this form. */
|
|
201
|
+
path?: undefined;
|
|
202
|
+
}) | ({
|
|
203
|
+
/** Who receives the file. */
|
|
204
|
+
role: CaseFile["role"];
|
|
205
|
+
/** File name Hue stores. */
|
|
206
|
+
filename: string;
|
|
207
|
+
/** One of `outputContentTypes`, the media types Hue stores as artifacts. */
|
|
208
|
+
contentType: string;
|
|
209
|
+
/** Not set when the file is given as bytes or a path. */
|
|
210
|
+
artifactId?: undefined;
|
|
211
|
+
} & ({
|
|
212
|
+
/** The file's bytes. */
|
|
213
|
+
bytes: Uint8Array;
|
|
214
|
+
/** Not set in this form. */
|
|
215
|
+
path?: undefined;
|
|
216
|
+
} | {
|
|
217
|
+
/** Path of the file on this machine. */
|
|
218
|
+
path: string;
|
|
219
|
+
/** Not set in this form. */
|
|
220
|
+
bytes?: undefined;
|
|
221
|
+
}));
|
|
176
222
|
/** A typed metric a scorer declares and must report exactly once per scored result. */
|
|
177
223
|
export type MetricDefinition = {
|
|
178
224
|
/** Metric name. */
|
|
@@ -851,11 +897,13 @@ export type OutputFile = {
|
|
|
851
897
|
/** Path of the generated file on this machine. */
|
|
852
898
|
path: string;
|
|
853
899
|
/** Not used when `path` is given. */
|
|
900
|
+
/** Not set in this form. */
|
|
854
901
|
bytes?: undefined;
|
|
855
902
|
} | {
|
|
856
903
|
/** Generated bytes held in memory. */
|
|
857
904
|
bytes: Uint8Array;
|
|
858
905
|
/** Not used when `bytes` is given. */
|
|
906
|
+
/** Not set in this form. */
|
|
859
907
|
path?: undefined;
|
|
860
908
|
});
|
|
861
909
|
/** A target's saved outcome when it produced files. Create it with `withFiles`. */
|
package/dist/evals.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export { ArtifactSizeError, createEvaluationClient, EvaluationClient, HueApiError, isTransientApiError, } from "./evals/client.js";
|
|
2
2
|
export type { EvaluationClientOptions } from "./evals/client.js";
|
|
3
|
+
export { MediaFetchError, rehydrateMedia } from "./evals/media.js";
|
|
4
|
+
export type { MediaClient, MediaFetchErrorReason, MediaFetchOptions, MediaFetchTarget, MediaFile, MediaResolution, MediaResolveFailure, MediaResolveOptions, MediaSourceFile, MediaWriteOptions, } from "./evals/media.js";
|
|
3
5
|
export { runExperiment, rescore, UncertainExecutionError, OutcomeSerializationError, TargetCancelledError, TargetOutcomeUncertainError, TargetTimeoutError, TraceExportUnacknowledgedError, } from "./evals/runner.js";
|
|
4
6
|
export type { RunExperimentOptions, RunExperimentTargetContext, RescoreOptions, RunnerReport, TelemetryIssueCount, TelemetryNotAccepted, TracePending, } from "./evals/runner.js";
|
|
5
7
|
export { TargetResult, withFiles } from "./evals/types.js";
|
package/dist/evals.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { ArtifactSizeError, createEvaluationClient, EvaluationClient, HueApiError, isTransientApiError, } from "./evals/client.js";
|
|
2
|
+
export { MediaFetchError, rehydrateMedia } from "./evals/media.js";
|
|
2
3
|
export { runExperiment, rescore, UncertainExecutionError, OutcomeSerializationError, TargetCancelledError, TargetOutcomeUncertainError, TargetTimeoutError, TraceExportUnacknowledgedError, } from "./evals/runner.js";
|
|
3
4
|
export { TargetResult, withFiles } from "./evals/types.js";
|
|
4
5
|
export { CaseFileError, outputContentTypes, outputFileLimits, OutputFileError, safeFilename, targetFileRoles, } from "./evals/files.js";
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { createHue, createHueSafe, HueClient, HueConnectionError, type ExistingHueProviders, } from "./client.js";
|
|
2
|
+
export { Attachment, type AttachmentOptions } from "./attachment.js";
|
|
2
3
|
export { createHueTransport, HueTransport, HueExportError } from "./transport.js";
|
|
3
4
|
export type { RecordValue } from "./transport.js";
|
|
4
5
|
export { hueExperimentalTelemetry } from "./experimental-telemetry.js";
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { createHue, createHueSafe, HueClient, HueConnectionError, } from "./client.js";
|
|
2
|
+
export { Attachment } from "./attachment.js";
|
|
2
3
|
export { createHueTransport, HueTransport, HueExportError } from "./transport.js";
|
|
3
4
|
export { hueExperimentalTelemetry } from "./experimental-telemetry.js";
|
|
4
5
|
export { contentPrefixes } from "./privacy.js";
|
package/dist/inline-files.d.ts
CHANGED
|
@@ -1,11 +1,26 @@
|
|
|
1
|
-
|
|
1
|
+
import { type JsonEdit } from "./json-edits.js";
|
|
2
|
+
/** Text media above this decoded byte limit is offloaded; binary media always uses a digest. */
|
|
2
3
|
export declare const INLINE_FILE_LIMIT: number;
|
|
3
|
-
/** The longest message text, in UTF-
|
|
4
|
+
/** The longest message text, in UTF-16 code units, inspected for inline files. */
|
|
4
5
|
export declare const MAX_INLINE_FILE_TEXT: number;
|
|
5
6
|
/** Message text one record inspects for inline files in all, in UTF-16 code units. */
|
|
6
7
|
export declare const INLINE_FILE_TEXT_PER_RECORD: number;
|
|
7
8
|
/** Whether an attribute is one of the recorded message attributes that can inline files. */
|
|
8
9
|
export declare function isMessageKey(key: string): boolean;
|
|
10
|
+
/** How an attribute's text is read for files: JSON by convention, metadata, an OpenLLMetry parts
|
|
11
|
+
* list, or a URL that is the file. */
|
|
12
|
+
export type MediaReading = "json" | "metadata" | "parts" | "url";
|
|
13
|
+
/** How an attribute's text is read for files, when it is read at all. */
|
|
14
|
+
export declare function readingOf(key: string): MediaReading | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* The attributes of a record that hold literal text: OpenInference's `input.value` and
|
|
17
|
+
* `output.value` when the record declares `input.mime_type` / `output.mime_type` as `text/plain`.
|
|
18
|
+
* Hue reads no JSON, and so no file, in them; neither does the SDK.
|
|
19
|
+
*/
|
|
20
|
+
export declare function literalKeys(attributes: unknown): ReadonlySet<string>;
|
|
21
|
+
/** Whether a span attribute is one whose value the SDK inspects for inline media: the attributes
|
|
22
|
+
* Hue's reader reads files from. */
|
|
23
|
+
export declare function isMediaKey(key: string): boolean;
|
|
9
24
|
/** An inline file's bytes, whether they were decoded from the part's text (base64 or
|
|
10
25
|
* percent-escapes) rather than taken as its UTF-8, and the media type its `data:` URL names. */
|
|
11
26
|
export interface InlineFileBytes {
|
|
@@ -20,38 +35,155 @@ export interface InlineFileBytes {
|
|
|
20
35
|
* `data:` URL whose data does not decode is taken as UTF-8 too.
|
|
21
36
|
*/
|
|
22
37
|
export declare function inlineFileBytes(content: string): InlineFileBytes;
|
|
23
|
-
/**
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
*
|
|
31
|
-
*/
|
|
32
|
-
export
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
|
|
38
|
+
/** JSON text a placed file sits in: its edits, at JSON pointers relative to `at`, the pointer of
|
|
39
|
+
* the text in the recorded value ("" for the value itself). */
|
|
40
|
+
export interface MediaDocument {
|
|
41
|
+
at: string;
|
|
42
|
+
edits: JsonEdit[];
|
|
43
|
+
}
|
|
44
|
+
/** A string inside a recorded value whose JSON the walk read (a tool call's arguments, a tool
|
|
45
|
+
* result): written again once a file inside it is placed, every byte the placement does not
|
|
46
|
+
* change kept as recorded. */
|
|
47
|
+
export interface EncodedValue extends MediaDocument {
|
|
48
|
+
owner: Record<string, unknown>;
|
|
49
|
+
key: string;
|
|
50
|
+
value: unknown;
|
|
51
|
+
dirty: boolean;
|
|
52
|
+
/** The string as recorded. */
|
|
53
|
+
text: string;
|
|
54
|
+
/** The JSON text the string sits in: an encoded string's, else the recorded value's. */
|
|
55
|
+
parent: MediaDocument;
|
|
56
|
+
}
|
|
57
|
+
/** A part and the exact leaf that contains its bytes. Nested provider metadata stays in place. */
|
|
39
58
|
export interface InlineFilePart {
|
|
59
|
+
/** The part that carries the digest: the provider part, or for OpenAI assistant audio the
|
|
60
|
+
* message. Unused for a whole string. */
|
|
40
61
|
part: Record<string, unknown>;
|
|
41
|
-
|
|
62
|
+
owner: Record<string, unknown> | unknown[];
|
|
63
|
+
key: string | number;
|
|
42
64
|
content: string;
|
|
43
65
|
pointer: string;
|
|
66
|
+
partPointer: string;
|
|
67
|
+
contentType?: string;
|
|
68
|
+
name?: string;
|
|
69
|
+
base64?: boolean;
|
|
70
|
+
/** A whole string that is a `data:` URL: replaced by a GenAI `blob` part in digest form. */
|
|
71
|
+
whole?: boolean;
|
|
72
|
+
/** The JSON-encoded strings the file sits in, outermost first: no `hue.blobs` key can address
|
|
73
|
+
* inside a string, so such a file is placed by its digest. */
|
|
74
|
+
encoded?: EncodedValue[];
|
|
75
|
+
/** The JSON text the file sits in, which records its placement as edits. */
|
|
76
|
+
document?: MediaDocument;
|
|
44
77
|
}
|
|
78
|
+
export declare function isMediaName(value: unknown): value is string;
|
|
79
|
+
/** Whether the receiver can preview these bytes as text. */
|
|
80
|
+
export declare function isTextMediaType(contentType: string): boolean;
|
|
81
|
+
/** Cheap shared prefilter before bounded parsing of the attributes inspected for media. */
|
|
82
|
+
export declare function mayInlineFiles(key: string, value: unknown): boolean;
|
|
83
|
+
/** Whether text may hold inline media at all: cheap, before it is parsed or walked. */
|
|
84
|
+
export declare function mayHoldMedia(value: unknown): value is string;
|
|
85
|
+
/** A printable MIME type with optional parameters, accepted by the upload contract. */
|
|
86
|
+
export declare function isMediaContentType(value: unknown): value is string;
|
|
87
|
+
/** Detect the content pointer, MIME, name, and encoding contract of a provider media part. */
|
|
88
|
+
export declare function contentPath(part: Record<string, unknown>, pointer?: string): InlineFilePart | undefined;
|
|
89
|
+
/** The modality a media type implies, as Hue's reader names it. */
|
|
90
|
+
export declare function modalityOf(mediaType: string): "image" | "audio" | "video" | "document" | "file";
|
|
91
|
+
/** How deep Hue's reader looks for files in a recorded value: a part more than ten levels below
|
|
92
|
+
* the value's root (an encoded string's JSON counting as a level) is not read as a file, so it is
|
|
93
|
+
* not inspected either. */
|
|
94
|
+
export declare const MAX_MEDIA_DEPTH = 10;
|
|
45
95
|
/**
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* caller may replace their content in place.
|
|
96
|
+
* Whether Hue's reader reads JSON text as the value it encodes, and so reads files in it: nested
|
|
97
|
+
* at most 32 levels, with no number JavaScript cannot hold exactly (an integer past 2^53, or one
|
|
98
|
+
* past the double range). Hue keeps any other text as it is, so the SDK leaves it as recorded.
|
|
50
99
|
*/
|
|
51
|
-
export declare function
|
|
52
|
-
/** The
|
|
53
|
-
*
|
|
54
|
-
|
|
100
|
+
export declare function readerParses(value: unknown): boolean;
|
|
101
|
+
/** The files a recorded value holds, the box holding the value (so a whole-string file at its
|
|
102
|
+
* root can be replaced), the JSON-encoded strings the walk read, innermost first, to write again
|
|
103
|
+
* once a file inside one is placed, and the value's own JSON text when it was read from text. */
|
|
104
|
+
export interface MediaValue {
|
|
105
|
+
box: {
|
|
106
|
+
value: unknown;
|
|
107
|
+
};
|
|
108
|
+
files: InlineFilePart[];
|
|
109
|
+
encoded: EncodedValue[];
|
|
110
|
+
/** The value's JSON text as recorded, edited in place when its files are placed. */
|
|
111
|
+
source?: string;
|
|
112
|
+
document: MediaDocument;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Every file a recorded value carries for upload, in reading order, as Hue's reader finds them
|
|
116
|
+
* (`forEachMediaPart`): a provider part with inline bytes, an OpenAI assistant message's audio,
|
|
117
|
+
* a whole string that is a typed `data:` URL, and the files inside a tool call's or tool result's
|
|
118
|
+
* JSON-encoded string. A part whose bytes are not inline (a remote URL, a file id) is left whole.
|
|
119
|
+
* The caller applies the decoded text threshold.
|
|
120
|
+
*/
|
|
121
|
+
export declare function mediaIn(value: unknown, source?: string): MediaValue;
|
|
122
|
+
/**
|
|
123
|
+
* An attribute's recorded text read for files as Hue's reader reads that attribute: JSON by its
|
|
124
|
+
* convention; metadata as JSON when it reads as an object or array, otherwise as the text itself;
|
|
125
|
+
* an OpenLLMetry content as the parts list it encodes (any other content stays text); an
|
|
126
|
+
* OpenInference image URL as the text itself. Undefined for text that reading finds nothing in.
|
|
127
|
+
* Throws for JSON that does not parse or nests too deep.
|
|
128
|
+
*/
|
|
129
|
+
export declare function readMediaText(key: string, text: string): MediaValue | undefined;
|
|
130
|
+
/** The recorded value's text once its files are placed: its JSON text as recorded with only the
|
|
131
|
+
* placed files' values (or their parts' members) replaced, each JSON-encoded string a placed file
|
|
132
|
+
* sits in written again the same way, innermost first. */
|
|
133
|
+
export declare function placedMediaText(media: MediaValue): string;
|
|
134
|
+
/** Each JSON-encoded string a placed file sits in, written again, innermost first: the string as
|
|
135
|
+
* recorded with only its placed files changed. */
|
|
136
|
+
export declare function reencodeMedia(media: MediaValue): void;
|
|
137
|
+
/** Whether a found file has bytes to place: nonempty, and a whole string's `data:` URL decodes. */
|
|
138
|
+
export declare function placeable(file: InlineFilePart, decoded: InlineFileBytes): boolean;
|
|
139
|
+
/** 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 and a
|
|
141
|
+
* file inside a JSON-encoded string always take the digest form. */
|
|
142
|
+
export declare function keepsPrefix(file: InlineFilePart, textual: boolean): boolean;
|
|
143
|
+
export declare function mediaBytes(file: InlineFilePart): InlineFileBytes;
|
|
144
|
+
export declare function mediaContentType(file: InlineFilePart, decoded: InlineFileBytes): string;
|
|
145
|
+
export interface FileDigest {
|
|
55
146
|
sha256: string;
|
|
56
147
|
size: number;
|
|
57
|
-
}
|
|
148
|
+
}
|
|
149
|
+
/** Bounded payload identity memo, shared by admission and export, cleared at shutdown. */
|
|
150
|
+
export declare class MediaDigestMemo {
|
|
151
|
+
private entries;
|
|
152
|
+
private units;
|
|
153
|
+
clear(): void;
|
|
154
|
+
digest(file: InlineFilePart, decoded?: InlineFileBytes, traceId?: string): FileDigest;
|
|
155
|
+
bytes(bytes: Uint8Array, traceId: string): FileDigest;
|
|
156
|
+
}
|
|
157
|
+
/** Digest placement removes only the leaf and adds identity to the provider part (the message,
|
|
158
|
+
* for OpenAI assistant audio); a whole string becomes a GenAI `blob` part in digest form. */
|
|
159
|
+
export declare function digestPart(file: InlineFilePart, digest: FileDigest): void;
|
|
160
|
+
/** Prefix placement keeps the first 16 KiB of a text file's content in place. */
|
|
161
|
+
export declare function prefixPart(file: InlineFilePart, prefix: string): void;
|
|
162
|
+
/**
|
|
163
|
+
* A span's own attribute with its inline media replaced by their digest, as export places media
|
|
164
|
+
* it does not upload. Only the recorded message attributes (`isMessageKey`) shrink without an
|
|
165
|
+
* upload; every other inspected attribute keeps its bytes inline unless they upload, so `anyKey`
|
|
166
|
+
* asks for the digest form of any inspected attribute (what the queue is charged for a value it
|
|
167
|
+
* holds whole for upload).
|
|
168
|
+
*/
|
|
169
|
+
export declare function hashInlineFiles(key: string, value: unknown, count?: {
|
|
170
|
+
files: number;
|
|
171
|
+
}, memo?: MediaDigestMemo, traceId?: string, anyKey?: boolean): unknown;
|
|
172
|
+
/** Nested message attributes retain small media and digest larger media without upload state. */
|
|
173
|
+
export declare function hashNestedInlineFiles(key: string, value: unknown, count?: {
|
|
174
|
+
files: number;
|
|
175
|
+
}): unknown;
|
|
176
|
+
/**
|
|
177
|
+
* Each file a recorded value holds replaced by its digest, as export places a file it does not
|
|
178
|
+
* upload: binary media of any size (`nested`: over the inline limit) and text media over the
|
|
179
|
+
* inline limit. Whether any was replaced; `count` counts them.
|
|
180
|
+
*/
|
|
181
|
+
export declare function digestMedia(media: MediaValue, nested: boolean, count?: {
|
|
182
|
+
files: number;
|
|
183
|
+
}, memo?: MediaDigestMemo, traceId?: string): boolean;
|
|
184
|
+
/**
|
|
185
|
+
* The UTF-8 size of a structured value's JSON once each file the SDK may upload from it, within
|
|
186
|
+
* the inspection limits, is placed in its digest form: whether a log body over the value cap fits
|
|
187
|
+
* it once its media upload. Mutates `value`, a copy the caller owns.
|
|
188
|
+
*/
|
|
189
|
+
export declare function digestFormBytes(value: unknown): number;
|