@xandreed/core 0.13.0 → 0.14.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/README.md +1 -1
- package/dist/conformance.functions.d.ts +34 -0
- package/dist/conformance.functions.js +25 -0
- package/dist/domain/bash.entity.d.ts +82 -0
- package/dist/domain/bash.entity.functions.d.ts +17 -0
- package/dist/domain/bash.entity.functions.js +26 -0
- package/dist/domain/bash.entity.functions.test.d.ts +1 -0
- package/dist/domain/bash.entity.js +30 -0
- package/dist/domain/file-system.entity.d.ts +91 -0
- package/dist/domain/file-system.entity.functions.d.ts +18 -0
- package/dist/domain/file-system.entity.functions.js +30 -0
- package/dist/domain/file-system.entity.js +37 -0
- package/dist/domain/loop-event.entity.d.ts +9 -0
- package/dist/domain/media.entity.d.ts +79 -0
- package/dist/domain/media.entity.functions.d.ts +76 -0
- package/dist/domain/media.entity.functions.js +110 -0
- package/dist/domain/media.entity.js +38 -0
- package/dist/domain/media.entity.test.d.ts +1 -0
- package/dist/domain/message.entity.d.ts +157 -5
- package/dist/domain/message.entity.js +8 -4
- package/dist/domain/model-catalog.entity.d.ts +2 -0
- package/dist/domain/model-catalog.entity.js +2 -1
- package/dist/domain/token-usage.entity.d.ts +45 -1
- package/dist/domain/token-usage.entity.functions.d.ts +30 -1
- package/dist/domain/token-usage.entity.functions.js +24 -4
- package/dist/domain/token-usage.entity.js +19 -2
- package/dist/domain/token-usage.entity.test.d.ts +1 -0
- package/dist/harness/capability.entity.functions.js +2 -1
- package/dist/harness/plugin.adapter.d.ts +3 -0
- package/dist/harness/plugin.adapter.js +7 -1
- package/dist/harness/skill-files.entity.d.ts +101 -0
- package/dist/harness/skill-files.entity.functions.d.ts +58 -0
- package/dist/harness/skill-files.entity.functions.js +150 -0
- package/dist/harness/skill-files.entity.functions.test.d.ts +1 -0
- package/dist/harness/skill-files.entity.js +25 -0
- package/dist/index.d.ts +48 -8
- package/dist/index.js +67 -6
- package/dist/loop/mapping.d.ts +12 -6
- package/dist/loop/mapping.js +30 -6
- package/dist/loop/stepContext.d.ts +8 -0
- package/dist/loop/stepContext.js +1 -1
- package/dist/loop/traceContent.d.ts +29 -0
- package/dist/loop/traceContent.js +4 -0
- package/dist/loop/traceContent.test.d.ts +1 -0
- package/dist/memory/memory-log.entity.d.ts +152 -14
- package/dist/memory/memory-log.entity.functions.d.ts +35 -5
- package/dist/memory/memory-log.entity.functions.js +59 -20
- package/dist/memory/memory-log.entity.js +19 -6
- package/dist/memory/memory-session.js +10 -6
- package/dist/memory/memory.conformance.d.ts +8 -1
- package/dist/memory/memory.conformance.js +32 -0
- package/dist/models/auth-store.conformance.d.ts +13 -0
- package/dist/models/auth-store.conformance.js +73 -0
- package/dist/models/auth-store.conformance.test.d.ts +1 -0
- package/dist/models/model-catalog.conformance.d.ts +15 -0
- package/dist/models/model-catalog.conformance.js +27 -0
- package/dist/models/model-catalog.conformance.test.d.ts +1 -0
- package/dist/models/settings-store.conformance.d.ts +26 -0
- package/dist/models/settings-store.conformance.js +80 -0
- package/dist/models/settings-store.conformance.test.d.ts +1 -0
- package/dist/ports/activity.port.d.ts +35 -0
- package/dist/ports/activity.port.js +7 -0
- package/dist/ports/auth-store.port.d.ts +7 -5
- package/dist/ports/bash.port.d.ts +22 -0
- package/dist/ports/bash.port.js +4 -0
- package/dist/ports/capability.port.d.ts +11 -1
- package/dist/ports/file-system.port.d.ts +44 -15
- package/dist/ports/file-system.port.js +1 -7
- package/dist/ports/memory.port.d.ts +13 -1
- package/dist/ports/model-catalog.port.d.ts +4 -1
- package/dist/ports/run-context.port.d.ts +6 -1
- package/dist/ports/session-log.port.d.ts +4 -1
- package/dist/ports/sessions.port.d.ts +35 -6
- package/dist/ports/settings-store.port.d.ts +11 -7
- package/dist/ports/step-loop.port.d.ts +30 -2
- package/dist/ports/tasks.port.d.ts +5 -1
- package/dist/ports/turn-admission.port.d.ts +8 -2
- package/dist/ports/turn-events.port.d.ts +8 -1
- package/dist/ports/turn-scope.port.d.ts +19 -1
- package/dist/ports/turn.port.d.ts +70 -5
- package/dist/ports/utility-llm.port.d.ts +2 -0
- package/dist/session/activity.entity.d.ts +42 -0
- package/dist/session/activity.entity.functions.d.ts +21 -0
- package/dist/session/activity.entity.functions.js +23 -0
- package/dist/session/activity.entity.functions.test.d.ts +1 -0
- package/dist/session/activity.entity.js +15 -0
- package/dist/session/chassis.js +2 -1
- package/dist/session/session-event.entity.d.ts +68 -6
- package/dist/session/session-event.entity.functions.d.ts +34 -2
- package/dist/session/session-event.entity.functions.js +54 -16
- package/dist/session/session-event.entity.js +19 -2
- package/dist/session/session-log.conformance.d.ts +4 -3
- package/dist/session/session-log.conformance.js +16 -14
- package/dist/session/session-log.recovery.conformance.d.ts +40 -0
- package/dist/session/session-log.recovery.conformance.js +143 -0
- package/dist/session/session-log.recovery.conformance.test.d.ts +1 -0
- package/dist/session/sessions.conformance.d.ts +79 -0
- package/dist/session/sessions.conformance.js +355 -0
- package/dist/session/sessions.entity.d.ts +36 -3
- package/dist/session/sessions.entity.functions.d.ts +42 -0
- package/dist/session/sessions.entity.functions.js +26 -0
- package/dist/session/sessions.entity.js +17 -4
- package/dist/session/sessions.entity.test.d.ts +1 -0
- package/dist/session/tasks.conformance.d.ts +70 -0
- package/dist/session/tasks.conformance.js +213 -0
- package/dist/session/tasks.conformance.test.d.ts +1 -0
- package/dist/turn/model-request.entity.d.ts +4 -0
- package/dist/turn/model-request.entity.js +7 -2
- package/dist/turn/step-loop.conformance.d.ts +4 -1
- package/dist/turn/step-loop.conformance.js +73 -7
- package/dist/turn/tool-registry.conformance.d.ts +38 -0
- package/dist/turn/tool-registry.conformance.js +206 -0
- package/dist/turn/turn-bus.js +4 -3
- package/dist/turn/turn-event.entity.d.ts +134 -34
- package/dist/turn/turn-event.entity.functions.d.ts +11 -0
- package/dist/turn/turn-event.entity.functions.js +9 -1
- package/dist/turn/turn-event.entity.js +76 -3
- package/dist/turn/turn-event.entity.test.d.ts +1 -0
- package/dist/turn/turn-lifecycle.d.ts +46 -8
- package/dist/turn/turn-lifecycle.js +33 -3
- package/dist/turn/turn-lifecycle.test.d.ts +1 -0
- package/dist/turn/turn-run.d.ts +15 -3
- package/dist/turn/turn-run.js +37 -9
- package/dist/turn/turn.adapter.d.ts +6 -5
- package/dist/turn/turn.adapter.js +15 -3
- package/dist/turn/turn.conformance.d.ts +11 -2
- package/dist/turn/turn.conformance.js +63 -2
- package/dist/turn/user-message.entity.d.ts +14 -4
- package/dist/turn/user-message.entity.functions.d.ts +21 -0
- package/dist/turn/user-message.entity.functions.js +24 -0
- package/dist/turn/user-message.entity.js +6 -8
- package/dist/turn/user-message.entity.test.d.ts +1 -0
- package/dist/util/glob.d.ts +30 -0
- package/dist/util/glob.js +91 -0
- package/dist/util/glob.test.d.ts +1 -0
- package/dist/workspace/bash.conformance.d.ts +37 -0
- package/dist/workspace/bash.conformance.js +120 -0
- package/dist/workspace/bash.conformance.test.d.ts +1 -0
- package/dist/workspace/file-system.conformance.d.ts +38 -0
- package/dist/workspace/file-system.conformance.js +254 -0
- package/dist/workspace/file-system.conformance.test.d.ts +1 -0
- package/dist/workspace/file-system.memory.adapter.d.ts +23 -0
- package/dist/workspace/file-system.memory.adapter.js +132 -0
- package/package.json +1 -1
- package/dist/harness/skill-files.d.ts +0 -17
- package/dist/harness/skill-files.js +0 -25
- package/dist/ports/shell.port.d.ts +0 -32
- package/dist/ports/shell.port.js +0 -16
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { Effect, Option, Schema } from "effect";
|
|
2
|
+
import { AttachmentRejected, ImageMediaType, MEDIA_LIMITS, MediaFingerprint } from "./media.entity.js";
|
|
3
|
+
export const IMAGE_TOKEN_ESTIMATE = 1600, base64Length = (bytes) => Math.ceil(bytes / 3) * 4;
|
|
4
|
+
const byteAt = (bytes, index) => bytes[index] ?? 0, u16be = (bytes, index) => byteAt(bytes, index) << 8 | byteAt(bytes, index + 1), u16le = (bytes, index) => byteAt(bytes, index) | byteAt(bytes, index + 1) << 8, u24le = (bytes, index) => byteAt(bytes, index) | byteAt(bytes, index + 1) << 8 | byteAt(bytes, index + 2) << 16, u32be = (bytes, index) => byteAt(bytes, index) * 16777216 + (byteAt(bytes, index + 1) << 16 | byteAt(bytes, index + 2) << 8 | byteAt(bytes, index + 3)), ascii = (text) => Array.from(text, (char) => char.charCodeAt(0)), startsWith = (bytes, offset, signature) => bytes.length >= offset + signature.length && signature.every((byte, index) => bytes[offset + index] === byte), PNG = [137, 80, 78, 71, 13, 10, 26, 10], JPEG = [255, 216, 255];
|
|
5
|
+
export const sniffImageType = (bytes) => startsWith(bytes, 0, PNG) ? Option.some("image/png") : startsWith(bytes, 0, JPEG) ? Option.some("image/jpeg") : startsWith(bytes, 0, ascii("GIF87a")) || startsWith(bytes, 0, ascii("GIF89a")) ? Option.some("image/gif") : startsWith(bytes, 0, ascii("RIFF")) && startsWith(bytes, 8, ascii("WEBP")) ? Option.some("image/webp") : Option.none();
|
|
6
|
+
const sized = (width, height) => width > 0 && height > 0 ? Option.some({ width, height }) : Option.none(), JPEG_FRAMES = new Set([192, 193, 194, 195, 197, 198, 199, 201, 202, 203, 205, 206, 207]), JPEG_MAX_SEGMENTS = 4096, jpegSize = (bytes, offset, segments) => {
|
|
7
|
+
if (segments > JPEG_MAX_SEGMENTS || offset + 4 > bytes.length || byteAt(bytes, offset) !== 255)
|
|
8
|
+
return Option.none();
|
|
9
|
+
const marker = byteAt(bytes, offset + 1);
|
|
10
|
+
if (marker === 255)
|
|
11
|
+
return jpegSize(bytes, offset + 1, segments + 1);
|
|
12
|
+
if (marker === 216 || marker === 1 || marker >= 208 && marker <= 215)
|
|
13
|
+
return jpegSize(bytes, offset + 2, segments + 1);
|
|
14
|
+
if (JPEG_FRAMES.has(marker))
|
|
15
|
+
return offset + 9 <= bytes.length ? sized(u16be(bytes, offset + 7), u16be(bytes, offset + 5)) : Option.none();
|
|
16
|
+
const length = u16be(bytes, offset + 2);
|
|
17
|
+
return marker === 217 || marker === 218 || length < 2 ? Option.none() : jpegSize(bytes, offset + 2 + length, segments + 1);
|
|
18
|
+
}, webpSize = (bytes) => {
|
|
19
|
+
if (bytes.length < 30)
|
|
20
|
+
return Option.none();
|
|
21
|
+
if (startsWith(bytes, 12, ascii("VP8 ")))
|
|
22
|
+
return startsWith(bytes, 23, [157, 1, 42]) ? sized(u16le(bytes, 26) & 16383, u16le(bytes, 28) & 16383) : Option.none();
|
|
23
|
+
if (startsWith(bytes, 12, ascii("VP8L"))) {
|
|
24
|
+
const [b0, b1, b2, b3] = [byteAt(bytes, 21), byteAt(bytes, 22), byteAt(bytes, 23), byteAt(bytes, 24)];
|
|
25
|
+
return byteAt(bytes, 20) === 47 ? sized(1 + ((b1 & 63) << 8 | b0), 1 + ((b3 & 15) << 10 | b2 << 2 | (b1 & 192) >> 6)) : Option.none();
|
|
26
|
+
}
|
|
27
|
+
return startsWith(bytes, 12, ascii("VP8X")) ? sized(1 + u24le(bytes, 24), 1 + u24le(bytes, 27)) : Option.none();
|
|
28
|
+
};
|
|
29
|
+
export const imageSizeOf = (bytes, mediaType) => {
|
|
30
|
+
if (mediaType === "image/png")
|
|
31
|
+
return bytes.length >= 24 && startsWith(bytes, 12, ascii("IHDR")) ? sized(u32be(bytes, 16), u32be(bytes, 20)) : Option.none();
|
|
32
|
+
if (mediaType === "image/gif")
|
|
33
|
+
return bytes.length >= 10 ? sized(u16le(bytes, 6), u16le(bytes, 8)) : Option.none();
|
|
34
|
+
if (mediaType === "image/webp")
|
|
35
|
+
return webpSize(bytes);
|
|
36
|
+
return jpegSize(bytes, 2, 0);
|
|
37
|
+
};
|
|
38
|
+
const hex8 = (value) => value.toString(16).padStart(8, "0");
|
|
39
|
+
export const mediaFingerprintOf = (bytes) => {
|
|
40
|
+
const fnv = bytes.reduce((hash, byte) => Math.imul(hash ^ byte, 16777619) >>> 0, 2166136261), mixed = bytes.reduce((hash, byte) => {
|
|
41
|
+
const product = Math.imul(hash ^ byte, 1540483477);
|
|
42
|
+
return (product ^ product >>> 15) >>> 0;
|
|
43
|
+
}, 2538058380);
|
|
44
|
+
return MediaFingerprint.make(`${hex8(fnv)}${hex8(mixed)}${bytes.length.toString(16)}`);
|
|
45
|
+
};
|
|
46
|
+
const rejected = (reason, message) => new AttachmentRejected({ reason, message }), described = (description) => ({
|
|
47
|
+
...description.name === void 0 ? {} : { name: description.name },
|
|
48
|
+
...description.alt === void 0 ? {} : { alt: description.alt }
|
|
49
|
+
}), encodeBase64 = Schema.encodeSync(Schema.Uint8ArrayFromBase64), decodeBase64 = Schema.decodeUnknownOption(Schema.Uint8ArrayFromBase64);
|
|
50
|
+
export const mediaFromBytes = (bytes, description = {}) => {
|
|
51
|
+
if (bytes.length === 0)
|
|
52
|
+
return Effect.fail(rejected("Empty", "the image has no bytes"));
|
|
53
|
+
if (bytes.length > MEDIA_LIMITS.imageBytes)
|
|
54
|
+
return Effect.fail(rejected("TooLarge", `the image has ${bytes.length} bytes, more than ${MEDIA_LIMITS.imageBytes}`));
|
|
55
|
+
return Option.match(sniffImageType(bytes), {
|
|
56
|
+
onNone: () => Effect.fail(rejected("UnsupportedType", "the bytes are not a PNG, JPEG, GIF or WebP image")),
|
|
57
|
+
onSome: (mediaType) => Option.match(imageSizeOf(bytes, mediaType), {
|
|
58
|
+
onNone: () => Effect.fail(rejected("Unreadable", `the ${mediaType} header does not give the image's size`)),
|
|
59
|
+
onSome: (size) => size.width > MEDIA_LIMITS.maxPixels || size.height > MEDIA_LIMITS.maxPixels ? Effect.fail(rejected("TooManyPixels", `the image is ${size.width}x${size.height} pixels, more than ${MEDIA_LIMITS.maxPixels} a side`)) : Effect.succeed({
|
|
60
|
+
type: "media",
|
|
61
|
+
mediaType,
|
|
62
|
+
data: encodeBase64(bytes),
|
|
63
|
+
fingerprint: mediaFingerprintOf(bytes),
|
|
64
|
+
bytes: bytes.length,
|
|
65
|
+
...described(description)
|
|
66
|
+
})
|
|
67
|
+
})
|
|
68
|
+
});
|
|
69
|
+
}, mediaFromBase64 = (data, description = {}) => {
|
|
70
|
+
const stripped = data.replace(/^data:[^,]*;base64,/i, "").replace(/\s+/g, "");
|
|
71
|
+
if (stripped.length === 0)
|
|
72
|
+
return Effect.fail(rejected("Empty", "the image has no data"));
|
|
73
|
+
if (stripped.length > base64Length(MEDIA_LIMITS.imageBytes))
|
|
74
|
+
return Effect.fail(rejected("TooLarge", `the image's base64 has ${stripped.length} characters, more than ${base64Length(MEDIA_LIMITS.imageBytes)}`));
|
|
75
|
+
return Option.match(decodeBase64(stripped), {
|
|
76
|
+
onNone: () => Effect.fail(rejected("InvalidBase64", "the image's data is not base64")),
|
|
77
|
+
onSome: (bytes) => mediaFromBytes(bytes, description)
|
|
78
|
+
});
|
|
79
|
+
}, normalizeMedia = (media) => mediaFromBase64(media.data, { ...media.name === void 0 ? {} : { name: media.name }, ...media.alt === void 0 ? {} : { alt: media.alt } });
|
|
80
|
+
const sizeText = (bytes) => bytes < 1024 ? `${bytes} B` : bytes < 1048576 ? `${Math.round(bytes / 1024)} KB` : `${(bytes / 1048576).toFixed(1)} MB`;
|
|
81
|
+
export const mediaLabel = (media) => `[image omitted: ${[media.mediaType, sizeText(media.bytes), ...media.name === void 0 ? [] : [media.name]].join(", ")}${media.alt === void 0 ? "" : `: ${media.alt}`}]`;
|
|
82
|
+
const IMAGE_TYPES = ImageMediaType.literals, isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
83
|
+
export const isMediaValue = (value) => isRecord(value) && value.type === "media" && typeof value.mediaType === "string" && IMAGE_TYPES.includes(value.mediaType) && typeof value.data === "string" && typeof value.fingerprint === "string" && typeof value.bytes === "number";
|
|
84
|
+
const slotsAt = (value, path) => isMediaValue(value) ? [{ path, fingerprint: value.fingerprint, mediaType: value.mediaType, bytes: value.bytes }] : Array.isArray(value) ? value.flatMap((item, index) => slotsAt(item, [...path, index])) : isRecord(value) ? Object.keys(value).sort().flatMap((key) => slotsAt(value[key], [...path, key])) : [];
|
|
85
|
+
export const mediaSlotsOf = (value) => slotsAt(value, []), mediaAt = (value, slot) => slot.path.reduce((at, key) => Option.flatMap(at, (current) => typeof key === "number" ? Array.isArray(current) ? Option.fromNullishOr(current[key]) : Option.none() : isRecord(current) && Object.hasOwn(current, key) ? Option.fromNullishOr(current[key]) : Option.none()), Option.some(value)).pipe(Option.filter(isMediaValue), Option.filter((media) => media.fingerprint === slot.fingerprint && media.mediaType === slot.mediaType && media.bytes === slot.bytes));
|
|
86
|
+
const unavailable = (reason) => `[image unavailable: ${reason}]`, noTally = { slots: [], labels: [], fingerprints: new Set, chars: 0 };
|
|
87
|
+
export const toolResultMedia = (encoded, slots) => Effect.reduce(slots, () => noTally, (tally, slot) => Option.match(mediaAt(encoded, slot), {
|
|
88
|
+
onNone: () => Effect.succeed({ ...tally, labels: [...tally.labels, unavailable("the result holds no such image")] }),
|
|
89
|
+
onSome: (media) => tally.fingerprints.has(media.fingerprint) ? Effect.succeed(tally) : normalizeMedia(media).pipe(Effect.match({
|
|
90
|
+
onFailure: (error) => ({ ...tally, labels: [...tally.labels, unavailable(error.message)] }),
|
|
91
|
+
onSuccess: (checked) => {
|
|
92
|
+
if (checked.data !== media.data || checked.fingerprint !== media.fingerprint || checked.mediaType !== media.mediaType || checked.bytes !== media.bytes)
|
|
93
|
+
return { ...tally, labels: [...tally.labels, unavailable("its type, size or fingerprint is not what its data gives; build it with mediaFromBytes")] };
|
|
94
|
+
const fingerprints = new Set([...tally.fingerprints, media.fingerprint]);
|
|
95
|
+
return tally.slots.length >= MEDIA_LIMITS.toolResultImages || tally.chars + media.data.length > MEDIA_LIMITS.requestChars ? { ...tally, fingerprints, labels: [...tally.labels, mediaLabel(media)] } : { ...tally, fingerprints, slots: [...tally.slots, slot], chars: tally.chars + media.data.length };
|
|
96
|
+
}
|
|
97
|
+
}))
|
|
98
|
+
})).pipe(Effect.map(({ slots: sent, labels }) => ({ slots: sent, labels }))), elideMediaData = (value) => {
|
|
99
|
+
if (isMediaValue(value))
|
|
100
|
+
return { ...value, data: value.fingerprint };
|
|
101
|
+
if (Array.isArray(value)) {
|
|
102
|
+
const mapped = value.map(elideMediaData);
|
|
103
|
+
return mapped.every((item, index) => item === value[index]) ? value : mapped;
|
|
104
|
+
}
|
|
105
|
+
if (isRecord(value)) {
|
|
106
|
+
const entries = Object.keys(value).map((key) => [key, value[key], elideMediaData(value[key])]);
|
|
107
|
+
return entries.every(([, before, after]) => before === after) ? value : Object.fromEntries(entries.map(([key, , after]) => [key, after]));
|
|
108
|
+
}
|
|
109
|
+
return value;
|
|
110
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
export const MEDIA_LIMITS = {
|
|
3
|
+
imageBytes: 3932160,
|
|
4
|
+
maxPixels: 8000,
|
|
5
|
+
attachments: 8,
|
|
6
|
+
requestImages: 8,
|
|
7
|
+
requestChars: 20000000,
|
|
8
|
+
toolResultImages: 4
|
|
9
|
+
}, ImageMediaType = Schema.Literals(["image/png", "image/jpeg", "image/gif", "image/webp"]), MediaFingerprint = Schema.String.check(Schema.isPattern(/^[0-9a-f]{16,}$/)).pipe(Schema.brand("MediaFingerprint")), Media = Schema.Struct({
|
|
10
|
+
type: Schema.Literal("media"),
|
|
11
|
+
mediaType: ImageMediaType,
|
|
12
|
+
data: Schema.String.check(Schema.isNonEmpty(), Schema.isMaxLength(Math.ceil(MEDIA_LIMITS.imageBytes / 3) * 4), Schema.isBase64()),
|
|
13
|
+
fingerprint: MediaFingerprint,
|
|
14
|
+
bytes: Schema.Int.check(Schema.isGreaterThan(0), Schema.isLessThanOrEqualTo(MEDIA_LIMITS.imageBytes)),
|
|
15
|
+
name: Schema.optionalKey(Schema.String),
|
|
16
|
+
alt: Schema.optionalKey(Schema.String)
|
|
17
|
+
}), MediaSlot = Schema.Struct({
|
|
18
|
+
path: Schema.Array(Schema.Union([Schema.String, Schema.Int])),
|
|
19
|
+
fingerprint: MediaFingerprint,
|
|
20
|
+
mediaType: ImageMediaType,
|
|
21
|
+
bytes: Schema.Int
|
|
22
|
+
}), AttachmentRejectedReason = Schema.Literals([
|
|
23
|
+
"Empty",
|
|
24
|
+
"InvalidBase64",
|
|
25
|
+
"UnsupportedType",
|
|
26
|
+
"TooLarge",
|
|
27
|
+
"TooManyPixels",
|
|
28
|
+
"Unreadable",
|
|
29
|
+
"TooMany",
|
|
30
|
+
"TooLargeTogether"
|
|
31
|
+
]);
|
|
32
|
+
|
|
33
|
+
export class AttachmentRejected extends Schema.TaggedError()("AttachmentRejected", {
|
|
34
|
+
reason: AttachmentRejectedReason,
|
|
35
|
+
index: Schema.optionalKey(Schema.Int),
|
|
36
|
+
message: Schema.String
|
|
37
|
+
}) {
|
|
38
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -48,12 +48,59 @@ export declare const ToolResultPart: Schema.Struct<{
|
|
|
48
48
|
readonly output: Schema.Unknown;
|
|
49
49
|
readonly providerOptions: Schema.optional<Schema.Unknown>;
|
|
50
50
|
readonly isError: Schema.optional<Schema.Boolean>;
|
|
51
|
+
/**
|
|
52
|
+
* Images sent with the result, after `output`'s text: memory's render of
|
|
53
|
+
* the images a result's slots name (`LogBody.ToolResult.media`). Only a
|
|
54
|
+
* rendered message has them; the loop's own tail keeps a result's images
|
|
55
|
+
* inside `output`, as the tool encoded them.
|
|
56
|
+
*/
|
|
57
|
+
readonly media: Schema.optionalKey<Schema.$Array<Schema.Struct<{
|
|
58
|
+
readonly type: Schema.Literal<"media">;
|
|
59
|
+
readonly mediaType: Schema.Literals<readonly ["image/png", "image/jpeg", "image/gif", "image/webp"]>;
|
|
60
|
+
readonly data: Schema.String;
|
|
61
|
+
readonly fingerprint: Schema.brand<Schema.String, "MediaFingerprint">;
|
|
62
|
+
readonly bytes: Schema.Int;
|
|
63
|
+
readonly name: Schema.optionalKey<Schema.String>;
|
|
64
|
+
readonly alt: Schema.optionalKey<Schema.String>;
|
|
65
|
+
}>>>;
|
|
51
66
|
}>;
|
|
52
67
|
export type ToolResultPart = typeof ToolResultPart.Type;
|
|
53
|
-
/**
|
|
68
|
+
/** One part of a multi-part user message: text, or an image sent inline. */
|
|
69
|
+
export declare const UserContentPart: Schema.Union<readonly [Schema.Struct<{
|
|
70
|
+
readonly type: Schema.Literal<"text">;
|
|
71
|
+
readonly text: Schema.String;
|
|
72
|
+
readonly providerOptions: Schema.optional<Schema.Unknown>;
|
|
73
|
+
}>, Schema.Struct<{
|
|
74
|
+
readonly type: Schema.Literal<"media">;
|
|
75
|
+
readonly mediaType: Schema.Literals<readonly ["image/png", "image/jpeg", "image/gif", "image/webp"]>;
|
|
76
|
+
readonly data: Schema.String;
|
|
77
|
+
readonly fingerprint: Schema.brand<Schema.String, "MediaFingerprint">;
|
|
78
|
+
readonly bytes: Schema.Int;
|
|
79
|
+
readonly name: Schema.optionalKey<Schema.String>;
|
|
80
|
+
readonly alt: Schema.optionalKey<Schema.String>;
|
|
81
|
+
}>]>;
|
|
82
|
+
export type UserContentPart = typeof UserContentPart.Type;
|
|
83
|
+
/**
|
|
84
|
+
* A model-visible message in the user role (the user's own words, turn
|
|
85
|
+
* context, host notes): text, or parts when it carries images (a user's
|
|
86
|
+
* attachments). Narrow with `typeof content === "string"` before reading it
|
|
87
|
+
* as text.
|
|
88
|
+
*/
|
|
54
89
|
export declare const UserRoleMessage: Schema.Struct<{
|
|
55
90
|
readonly role: Schema.Literal<"user">;
|
|
56
|
-
readonly content: Schema.String
|
|
91
|
+
readonly content: Schema.Union<readonly [Schema.String, Schema.$Array<Schema.Union<readonly [Schema.Struct<{
|
|
92
|
+
readonly type: Schema.Literal<"text">;
|
|
93
|
+
readonly text: Schema.String;
|
|
94
|
+
readonly providerOptions: Schema.optional<Schema.Unknown>;
|
|
95
|
+
}>, Schema.Struct<{
|
|
96
|
+
readonly type: Schema.Literal<"media">;
|
|
97
|
+
readonly mediaType: Schema.Literals<readonly ["image/png", "image/jpeg", "image/gif", "image/webp"]>;
|
|
98
|
+
readonly data: Schema.String;
|
|
99
|
+
readonly fingerprint: Schema.brand<Schema.String, "MediaFingerprint">;
|
|
100
|
+
readonly bytes: Schema.Int;
|
|
101
|
+
readonly name: Schema.optionalKey<Schema.String>;
|
|
102
|
+
readonly alt: Schema.optionalKey<Schema.String>;
|
|
103
|
+
}>]>>]>;
|
|
57
104
|
readonly providerOptions: Schema.optional<Schema.Unknown>;
|
|
58
105
|
}>;
|
|
59
106
|
export type UserRoleMessage = typeof UserRoleMessage.Type;
|
|
@@ -87,6 +134,21 @@ export declare const ToolMessage: Schema.Struct<{
|
|
|
87
134
|
readonly output: Schema.Unknown;
|
|
88
135
|
readonly providerOptions: Schema.optional<Schema.Unknown>;
|
|
89
136
|
readonly isError: Schema.optional<Schema.Boolean>;
|
|
137
|
+
/**
|
|
138
|
+
* Images sent with the result, after `output`'s text: memory's render of
|
|
139
|
+
* the images a result's slots name (`LogBody.ToolResult.media`). Only a
|
|
140
|
+
* rendered message has them; the loop's own tail keeps a result's images
|
|
141
|
+
* inside `output`, as the tool encoded them.
|
|
142
|
+
*/
|
|
143
|
+
readonly media: Schema.optionalKey<Schema.$Array<Schema.Struct<{
|
|
144
|
+
readonly type: Schema.Literal<"media">;
|
|
145
|
+
readonly mediaType: Schema.Literals<readonly ["image/png", "image/jpeg", "image/gif", "image/webp"]>;
|
|
146
|
+
readonly data: Schema.String;
|
|
147
|
+
readonly fingerprint: Schema.brand<Schema.String, "MediaFingerprint">;
|
|
148
|
+
readonly bytes: Schema.Int;
|
|
149
|
+
readonly name: Schema.optionalKey<Schema.String>;
|
|
150
|
+
readonly alt: Schema.optionalKey<Schema.String>;
|
|
151
|
+
}>>>;
|
|
90
152
|
}>>;
|
|
91
153
|
readonly providerOptions: Schema.optional<Schema.Unknown>;
|
|
92
154
|
}>;
|
|
@@ -94,7 +156,19 @@ export type ToolMessage = typeof ToolMessage.Type;
|
|
|
94
156
|
/** The single conversation unit — persisted as-is, one row per entry. */
|
|
95
157
|
export declare const AgentMessage: Schema.Union<readonly [Schema.Struct<{
|
|
96
158
|
readonly role: Schema.Literal<"user">;
|
|
97
|
-
readonly content: Schema.String
|
|
159
|
+
readonly content: Schema.Union<readonly [Schema.String, Schema.$Array<Schema.Union<readonly [Schema.Struct<{
|
|
160
|
+
readonly type: Schema.Literal<"text">;
|
|
161
|
+
readonly text: Schema.String;
|
|
162
|
+
readonly providerOptions: Schema.optional<Schema.Unknown>;
|
|
163
|
+
}>, Schema.Struct<{
|
|
164
|
+
readonly type: Schema.Literal<"media">;
|
|
165
|
+
readonly mediaType: Schema.Literals<readonly ["image/png", "image/jpeg", "image/gif", "image/webp"]>;
|
|
166
|
+
readonly data: Schema.String;
|
|
167
|
+
readonly fingerprint: Schema.brand<Schema.String, "MediaFingerprint">;
|
|
168
|
+
readonly bytes: Schema.Int;
|
|
169
|
+
readonly name: Schema.optionalKey<Schema.String>;
|
|
170
|
+
readonly alt: Schema.optionalKey<Schema.String>;
|
|
171
|
+
}>]>>]>;
|
|
98
172
|
readonly providerOptions: Schema.optional<Schema.Unknown>;
|
|
99
173
|
}>, Schema.Struct<{
|
|
100
174
|
readonly role: Schema.Literal<"assistant">;
|
|
@@ -124,6 +198,21 @@ export declare const AgentMessage: Schema.Union<readonly [Schema.Struct<{
|
|
|
124
198
|
readonly output: Schema.Unknown;
|
|
125
199
|
readonly providerOptions: Schema.optional<Schema.Unknown>;
|
|
126
200
|
readonly isError: Schema.optional<Schema.Boolean>;
|
|
201
|
+
/**
|
|
202
|
+
* Images sent with the result, after `output`'s text: memory's render of
|
|
203
|
+
* the images a result's slots name (`LogBody.ToolResult.media`). Only a
|
|
204
|
+
* rendered message has them; the loop's own tail keeps a result's images
|
|
205
|
+
* inside `output`, as the tool encoded them.
|
|
206
|
+
*/
|
|
207
|
+
readonly media: Schema.optionalKey<Schema.$Array<Schema.Struct<{
|
|
208
|
+
readonly type: Schema.Literal<"media">;
|
|
209
|
+
readonly mediaType: Schema.Literals<readonly ["image/png", "image/jpeg", "image/gif", "image/webp"]>;
|
|
210
|
+
readonly data: Schema.String;
|
|
211
|
+
readonly fingerprint: Schema.brand<Schema.String, "MediaFingerprint">;
|
|
212
|
+
readonly bytes: Schema.Int;
|
|
213
|
+
readonly name: Schema.optionalKey<Schema.String>;
|
|
214
|
+
readonly alt: Schema.optionalKey<Schema.String>;
|
|
215
|
+
}>>>;
|
|
127
216
|
}>>;
|
|
128
217
|
readonly providerOptions: Schema.optional<Schema.Unknown>;
|
|
129
218
|
}>]>;
|
|
@@ -137,7 +226,19 @@ export declare const AgentResult: Schema.Struct<{
|
|
|
137
226
|
readonly finalText: Schema.String;
|
|
138
227
|
readonly messages: Schema.$Array<Schema.Union<readonly [Schema.Struct<{
|
|
139
228
|
readonly role: Schema.Literal<"user">;
|
|
140
|
-
readonly content: Schema.String
|
|
229
|
+
readonly content: Schema.Union<readonly [Schema.String, Schema.$Array<Schema.Union<readonly [Schema.Struct<{
|
|
230
|
+
readonly type: Schema.Literal<"text">;
|
|
231
|
+
readonly text: Schema.String;
|
|
232
|
+
readonly providerOptions: Schema.optional<Schema.Unknown>;
|
|
233
|
+
}>, Schema.Struct<{
|
|
234
|
+
readonly type: Schema.Literal<"media">;
|
|
235
|
+
readonly mediaType: Schema.Literals<readonly ["image/png", "image/jpeg", "image/gif", "image/webp"]>;
|
|
236
|
+
readonly data: Schema.String;
|
|
237
|
+
readonly fingerprint: Schema.brand<Schema.String, "MediaFingerprint">;
|
|
238
|
+
readonly bytes: Schema.Int;
|
|
239
|
+
readonly name: Schema.optionalKey<Schema.String>;
|
|
240
|
+
readonly alt: Schema.optionalKey<Schema.String>;
|
|
241
|
+
}>]>>]>;
|
|
141
242
|
readonly providerOptions: Schema.optional<Schema.Unknown>;
|
|
142
243
|
}>, Schema.Struct<{
|
|
143
244
|
readonly role: Schema.Literal<"assistant">;
|
|
@@ -167,12 +268,39 @@ export declare const AgentResult: Schema.Struct<{
|
|
|
167
268
|
readonly output: Schema.Unknown;
|
|
168
269
|
readonly providerOptions: Schema.optional<Schema.Unknown>;
|
|
169
270
|
readonly isError: Schema.optional<Schema.Boolean>;
|
|
271
|
+
/**
|
|
272
|
+
* Images sent with the result, after `output`'s text: memory's render of
|
|
273
|
+
* the images a result's slots name (`LogBody.ToolResult.media`). Only a
|
|
274
|
+
* rendered message has them; the loop's own tail keeps a result's images
|
|
275
|
+
* inside `output`, as the tool encoded them.
|
|
276
|
+
*/
|
|
277
|
+
readonly media: Schema.optionalKey<Schema.$Array<Schema.Struct<{
|
|
278
|
+
readonly type: Schema.Literal<"media">;
|
|
279
|
+
readonly mediaType: Schema.Literals<readonly ["image/png", "image/jpeg", "image/gif", "image/webp"]>;
|
|
280
|
+
readonly data: Schema.String;
|
|
281
|
+
readonly fingerprint: Schema.brand<Schema.String, "MediaFingerprint">;
|
|
282
|
+
readonly bytes: Schema.Int;
|
|
283
|
+
readonly name: Schema.optionalKey<Schema.String>;
|
|
284
|
+
readonly alt: Schema.optionalKey<Schema.String>;
|
|
285
|
+
}>>>;
|
|
170
286
|
}>>;
|
|
171
287
|
readonly providerOptions: Schema.optional<Schema.Unknown>;
|
|
172
288
|
}>]>>;
|
|
173
289
|
readonly newTail: Schema.$Array<Schema.Union<readonly [Schema.Struct<{
|
|
174
290
|
readonly role: Schema.Literal<"user">;
|
|
175
|
-
readonly content: Schema.String
|
|
291
|
+
readonly content: Schema.Union<readonly [Schema.String, Schema.$Array<Schema.Union<readonly [Schema.Struct<{
|
|
292
|
+
readonly type: Schema.Literal<"text">;
|
|
293
|
+
readonly text: Schema.String;
|
|
294
|
+
readonly providerOptions: Schema.optional<Schema.Unknown>;
|
|
295
|
+
}>, Schema.Struct<{
|
|
296
|
+
readonly type: Schema.Literal<"media">;
|
|
297
|
+
readonly mediaType: Schema.Literals<readonly ["image/png", "image/jpeg", "image/gif", "image/webp"]>;
|
|
298
|
+
readonly data: Schema.String;
|
|
299
|
+
readonly fingerprint: Schema.brand<Schema.String, "MediaFingerprint">;
|
|
300
|
+
readonly bytes: Schema.Int;
|
|
301
|
+
readonly name: Schema.optionalKey<Schema.String>;
|
|
302
|
+
readonly alt: Schema.optionalKey<Schema.String>;
|
|
303
|
+
}>]>>]>;
|
|
176
304
|
readonly providerOptions: Schema.optional<Schema.Unknown>;
|
|
177
305
|
}>, Schema.Struct<{
|
|
178
306
|
readonly role: Schema.Literal<"assistant">;
|
|
@@ -202,6 +330,21 @@ export declare const AgentResult: Schema.Struct<{
|
|
|
202
330
|
readonly output: Schema.Unknown;
|
|
203
331
|
readonly providerOptions: Schema.optional<Schema.Unknown>;
|
|
204
332
|
readonly isError: Schema.optional<Schema.Boolean>;
|
|
333
|
+
/**
|
|
334
|
+
* Images sent with the result, after `output`'s text: memory's render of
|
|
335
|
+
* the images a result's slots name (`LogBody.ToolResult.media`). Only a
|
|
336
|
+
* rendered message has them; the loop's own tail keeps a result's images
|
|
337
|
+
* inside `output`, as the tool encoded them.
|
|
338
|
+
*/
|
|
339
|
+
readonly media: Schema.optionalKey<Schema.$Array<Schema.Struct<{
|
|
340
|
+
readonly type: Schema.Literal<"media">;
|
|
341
|
+
readonly mediaType: Schema.Literals<readonly ["image/png", "image/jpeg", "image/gif", "image/webp"]>;
|
|
342
|
+
readonly data: Schema.String;
|
|
343
|
+
readonly fingerprint: Schema.brand<Schema.String, "MediaFingerprint">;
|
|
344
|
+
readonly bytes: Schema.Int;
|
|
345
|
+
readonly name: Schema.optionalKey<Schema.String>;
|
|
346
|
+
readonly alt: Schema.optionalKey<Schema.String>;
|
|
347
|
+
}>>>;
|
|
205
348
|
}>>;
|
|
206
349
|
readonly providerOptions: Schema.optional<Schema.Unknown>;
|
|
207
350
|
}>]>>;
|
|
@@ -209,6 +352,15 @@ export declare const AgentResult: Schema.Struct<{
|
|
|
209
352
|
* degenerate-loop breaker stopped it, so `finalText` is NOT a deliverable. */
|
|
210
353
|
readonly outcome: Schema.Literals<readonly ["ok", "partial"]>;
|
|
211
354
|
readonly reason: Schema.Literals<readonly ["completed", "step-cap", "degenerate-loop"]>;
|
|
355
|
+
/** The run's provider calls summed (its assistant messages' usage). */
|
|
356
|
+
readonly usage: Schema.Struct<{
|
|
357
|
+
readonly inputTokens: Schema.Number;
|
|
358
|
+
readonly outputTokens: Schema.Number;
|
|
359
|
+
readonly totalTokens: Schema.Number;
|
|
360
|
+
readonly cacheReadTokens: Schema.Number;
|
|
361
|
+
readonly cacheWriteTokens: Schema.withDecodingDefaultKey<Schema.Number, never>;
|
|
362
|
+
readonly reasoningTokens: Schema.withDecodingDefaultKey<Schema.Number, never>;
|
|
363
|
+
}>;
|
|
212
364
|
}>;
|
|
213
365
|
export type AgentResult = typeof AgentResult.Type;
|
|
214
366
|
export {};
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Schema } from "effect";
|
|
2
|
+
import { Media } from "./media.entity.js";
|
|
3
|
+
import { TokenUsage } from "./token-usage.entity.js";
|
|
2
4
|
export const ConversationId = Schema.String.check(Schema.isUUID()).pipe(Schema.brand("ConversationId")), ToolCallId = Schema.String.pipe(Schema.brand("ToolCallId"));
|
|
3
5
|
|
|
4
6
|
export class Checkpoint extends Schema.Class("Checkpoint")({
|
|
@@ -29,10 +31,11 @@ export const TextPart = Schema.Struct({
|
|
|
29
31
|
toolName: Schema.String,
|
|
30
32
|
output: Schema.Unknown,
|
|
31
33
|
providerOptions: Schema.optional(Schema.Unknown),
|
|
32
|
-
isError: Schema.optional(Schema.Boolean)
|
|
33
|
-
|
|
34
|
+
isError: Schema.optional(Schema.Boolean),
|
|
35
|
+
media: Schema.optionalKey(Schema.Array(Media))
|
|
36
|
+
}), UserContentPart = Schema.Union([TextPart, Media]), UserRoleMessage = Schema.Struct({
|
|
34
37
|
role: Schema.Literal("user"),
|
|
35
|
-
content: Schema.String,
|
|
38
|
+
content: Schema.Union([Schema.String, Schema.Array(UserContentPart)]),
|
|
36
39
|
providerOptions: Schema.optional(Schema.Unknown)
|
|
37
40
|
}), AssistantMessage = Schema.Struct({
|
|
38
41
|
role: Schema.Literal("assistant"),
|
|
@@ -47,5 +50,6 @@ export const TextPart = Schema.Struct({
|
|
|
47
50
|
messages: Schema.Array(AgentMessage),
|
|
48
51
|
newTail: Schema.Array(AgentMessage),
|
|
49
52
|
outcome: Schema.Literals(["ok", "partial"]),
|
|
50
|
-
reason: Schema.Literals(["completed", "step-cap", "degenerate-loop"])
|
|
53
|
+
reason: Schema.Literals(["completed", "step-cap", "degenerate-loop"]),
|
|
54
|
+
usage: TokenUsage
|
|
51
55
|
});
|
|
@@ -6,5 +6,7 @@ export declare const ModelCatalogEntry: Schema.Struct<{
|
|
|
6
6
|
readonly label: Schema.optional<Schema.String>;
|
|
7
7
|
readonly provider: Schema.String;
|
|
8
8
|
readonly credential: Schema.Literals<readonly ["api_key", "oauth", "local"]>;
|
|
9
|
+
/** What the model reads: text, and images when its route sends them (a host can grey out an attach button). Absent: not stated. */
|
|
10
|
+
readonly input: Schema.optionalKey<Schema.$Array<Schema.Literals<readonly ["text", "image"]>>>;
|
|
9
11
|
}>;
|
|
10
12
|
export type ModelCatalogEntry = typeof ModelCatalogEntry.Type;
|
|
@@ -3,5 +3,6 @@ export const ModelCatalogEntry = Schema.Struct({
|
|
|
3
3
|
selection: Schema.String,
|
|
4
4
|
label: Schema.optional(Schema.String),
|
|
5
5
|
provider: Schema.String,
|
|
6
|
-
credential: Schema.Literals(["api_key", "oauth", "local"])
|
|
6
|
+
credential: Schema.Literals(["api_key", "oauth", "local"]),
|
|
7
|
+
input: Schema.optionalKey(Schema.Array(Schema.Literals(["text", "image"])))
|
|
7
8
|
});
|
|
@@ -1,9 +1,53 @@
|
|
|
1
1
|
import { Schema } from "effect";
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* Billed tokens for one model call (or a sum of them). The invariants
|
|
4
|
+
* (`tokenUsage` and `extractUsage` hold them):
|
|
5
|
+
* - `inputTokens` is the full input: uncached input, cache reads and cache
|
|
6
|
+
* writes (`uncachedInputTokens(u)` is the rest);
|
|
7
|
+
* - `outputTokens` is the full output, reasoning included;
|
|
8
|
+
* - `totalTokens = inputTokens + outputTokens`.
|
|
9
|
+
* `cacheWriteTokens` and `reasoningTokens` are missing from records written
|
|
10
|
+
* before 0.14.0; those decode with 0.
|
|
11
|
+
*/
|
|
3
12
|
export declare const TokenUsage: Schema.Struct<{
|
|
4
13
|
readonly inputTokens: Schema.Number;
|
|
5
14
|
readonly outputTokens: Schema.Number;
|
|
6
15
|
readonly totalTokens: Schema.Number;
|
|
7
16
|
readonly cacheReadTokens: Schema.Number;
|
|
17
|
+
readonly cacheWriteTokens: Schema.withDecodingDefaultKey<Schema.Number, never>;
|
|
18
|
+
readonly reasoningTokens: Schema.withDecodingDefaultKey<Schema.Number, never>;
|
|
8
19
|
}>;
|
|
9
20
|
export type TokenUsage = typeof TokenUsage.Type;
|
|
21
|
+
/**
|
|
22
|
+
* One usage split into the five kinds a call is billed by. Each is at least
|
|
23
|
+
* 0, and for a usage that holds the invariants they sum to `totalTokens`:
|
|
24
|
+
* `input` is the uncached input, `output` the output without reasoning.
|
|
25
|
+
*/
|
|
26
|
+
export declare const UsageBreakdown: Schema.Struct<{
|
|
27
|
+
readonly input: Schema.Number;
|
|
28
|
+
readonly cacheRead: Schema.Number;
|
|
29
|
+
readonly cacheWrite: Schema.Number;
|
|
30
|
+
readonly reasoning: Schema.Number;
|
|
31
|
+
readonly output: Schema.Number;
|
|
32
|
+
}>;
|
|
33
|
+
export type UsageBreakdown = typeof UsageBreakdown.Type;
|
|
34
|
+
/**
|
|
35
|
+
* A request's token budget (`budgetTokens`): an integer of at least 1. It is
|
|
36
|
+
* the target memory compaction keeps each request under (system prompt,
|
|
37
|
+
* tool schemas and messages, estimated), not a provider limit.
|
|
38
|
+
*/
|
|
39
|
+
export declare const BudgetTokens: Schema.Int;
|
|
40
|
+
export type BudgetTokens = typeof BudgetTokens.Type;
|
|
41
|
+
/**
|
|
42
|
+
* A model's prices, per million tokens (in the host's currency, usually
|
|
43
|
+
* USD). `reasoning` is optional: reasoning tokens are priced as output
|
|
44
|
+
* without it.
|
|
45
|
+
*/
|
|
46
|
+
export declare const ModelPrice: Schema.Struct<{
|
|
47
|
+
readonly input: Schema.Finite;
|
|
48
|
+
readonly output: Schema.Finite;
|
|
49
|
+
readonly cacheRead: Schema.Finite;
|
|
50
|
+
readonly cacheWrite: Schema.Finite;
|
|
51
|
+
readonly reasoning: Schema.optionalKey<Schema.Finite>;
|
|
52
|
+
}>;
|
|
53
|
+
export type ModelPrice = typeof ModelPrice.Type;
|
|
@@ -1,3 +1,32 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { BudgetTokens } from "./token-usage.entity.js";
|
|
2
|
+
import type { ModelPrice, TokenUsage, UsageBreakdown } from "./token-usage.entity.js";
|
|
3
|
+
/** Whether a value is a valid token budget: an integer of at least 1 (`BudgetTokens`). */
|
|
4
|
+
export declare const isBudgetTokens: (value: unknown) => value is BudgetTokens;
|
|
2
5
|
export declare const zeroUsage: TokenUsage;
|
|
6
|
+
/**
|
|
7
|
+
* A usage from a call's counts, with the invariants held: the input covers
|
|
8
|
+
* its cache reads and writes, the output covers its reasoning (a provider
|
|
9
|
+
* reporting more reasoning than output gets output = reasoning), the total
|
|
10
|
+
* is input plus output, and no count is negative.
|
|
11
|
+
*/
|
|
12
|
+
export declare const tokenUsage: (counts: {
|
|
13
|
+
readonly inputTokens: number;
|
|
14
|
+
readonly outputTokens: number;
|
|
15
|
+
readonly cacheReadTokens?: number;
|
|
16
|
+
readonly cacheWriteTokens?: number;
|
|
17
|
+
readonly reasoningTokens?: number;
|
|
18
|
+
}) => TokenUsage;
|
|
3
19
|
export declare const addUsage: (a: TokenUsage, b: TokenUsage) => TokenUsage;
|
|
20
|
+
/** The sum of many usages; `zeroUsage` for none. */
|
|
21
|
+
export declare const sumUsage: (usages: ReadonlyArray<TokenUsage>) => TokenUsage;
|
|
22
|
+
/** The input neither read from nor written to a cache: input minus both, at least 0. */
|
|
23
|
+
export declare const uncachedInputTokens: (usage: TokenUsage) => number;
|
|
24
|
+
/** The five billed kinds of a usage, each at least 0 (see `UsageBreakdown`). */
|
|
25
|
+
export declare const usageBreakdown: (usage: TokenUsage) => UsageBreakdown;
|
|
26
|
+
/**
|
|
27
|
+
* What a usage costs at `price` (per million tokens): each kind of
|
|
28
|
+
* `usageBreakdown` at its price, reasoning at `price.reasoning` or else
|
|
29
|
+
* the output price. Cache writes have one price (a provider's different
|
|
30
|
+
* write durations are not told apart).
|
|
31
|
+
*/
|
|
32
|
+
export declare const costOf: (usage: TokenUsage, price: ModelPrice) => number;
|
|
@@ -1,11 +1,31 @@
|
|
|
1
|
-
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import { BudgetTokens } from "./token-usage.entity.js";
|
|
3
|
+
export const isBudgetTokens = Schema.is(BudgetTokens), zeroUsage = {
|
|
2
4
|
inputTokens: 0,
|
|
3
5
|
outputTokens: 0,
|
|
4
6
|
totalTokens: 0,
|
|
5
|
-
cacheReadTokens: 0
|
|
7
|
+
cacheReadTokens: 0,
|
|
8
|
+
cacheWriteTokens: 0,
|
|
9
|
+
reasoningTokens: 0
|
|
10
|
+
};
|
|
11
|
+
const count = (value) => Math.max(0, value ?? 0);
|
|
12
|
+
export const tokenUsage = (counts) => {
|
|
13
|
+
const cacheReadTokens = count(counts.cacheReadTokens), cacheWriteTokens = count(counts.cacheWriteTokens), reasoningTokens = count(counts.reasoningTokens), inputTokens = Math.max(count(counts.inputTokens), cacheReadTokens + cacheWriteTokens), outputTokens = Math.max(count(counts.outputTokens), reasoningTokens);
|
|
14
|
+
return { inputTokens, outputTokens, totalTokens: inputTokens + outputTokens, cacheReadTokens, cacheWriteTokens, reasoningTokens };
|
|
6
15
|
}, addUsage = (a, b) => ({
|
|
7
16
|
inputTokens: a.inputTokens + b.inputTokens,
|
|
8
17
|
outputTokens: a.outputTokens + b.outputTokens,
|
|
9
18
|
totalTokens: a.totalTokens + b.totalTokens,
|
|
10
|
-
cacheReadTokens: a.cacheReadTokens + b.cacheReadTokens
|
|
11
|
-
|
|
19
|
+
cacheReadTokens: a.cacheReadTokens + b.cacheReadTokens,
|
|
20
|
+
cacheWriteTokens: a.cacheWriteTokens + b.cacheWriteTokens,
|
|
21
|
+
reasoningTokens: a.reasoningTokens + b.reasoningTokens
|
|
22
|
+
}), sumUsage = (usages) => usages.reduce(addUsage, zeroUsage), uncachedInputTokens = (usage) => Math.max(0, usage.inputTokens - usage.cacheReadTokens - usage.cacheWriteTokens), usageBreakdown = (usage) => ({
|
|
23
|
+
input: uncachedInputTokens(usage),
|
|
24
|
+
cacheRead: Math.max(0, usage.cacheReadTokens),
|
|
25
|
+
cacheWrite: Math.max(0, usage.cacheWriteTokens),
|
|
26
|
+
reasoning: Math.max(0, usage.reasoningTokens),
|
|
27
|
+
output: Math.max(0, usage.outputTokens - usage.reasoningTokens)
|
|
28
|
+
}), costOf = (usage, price) => {
|
|
29
|
+
const kinds = usageBreakdown(usage);
|
|
30
|
+
return (kinds.input * price.input + kinds.cacheRead * price.cacheRead + kinds.cacheWrite * price.cacheWrite + kinds.reasoning * (price.reasoning ?? price.output) + kinds.output * price.output) / 1e6;
|
|
31
|
+
};
|
|
@@ -1,7 +1,24 @@
|
|
|
1
|
-
import { Schema } from "effect";
|
|
1
|
+
import { Effect, Schema } from "effect";
|
|
2
|
+
const LaterCount = Schema.Number.pipe(Schema.withDecodingDefaultKey(Effect.succeed(0)));
|
|
2
3
|
export const TokenUsage = Schema.Struct({
|
|
3
4
|
inputTokens: Schema.Number,
|
|
4
5
|
outputTokens: Schema.Number,
|
|
5
6
|
totalTokens: Schema.Number,
|
|
6
|
-
cacheReadTokens: Schema.Number
|
|
7
|
+
cacheReadTokens: Schema.Number,
|
|
8
|
+
cacheWriteTokens: LaterCount,
|
|
9
|
+
reasoningTokens: LaterCount
|
|
10
|
+
}), UsageBreakdown = Schema.Struct({
|
|
11
|
+
input: Schema.Number,
|
|
12
|
+
cacheRead: Schema.Number,
|
|
13
|
+
cacheWrite: Schema.Number,
|
|
14
|
+
reasoning: Schema.Number,
|
|
15
|
+
output: Schema.Number
|
|
16
|
+
}), BudgetTokens = Schema.Int.pipe(Schema.check(Schema.isGreaterThanOrEqualTo(1)));
|
|
17
|
+
const Price = Schema.Finite.pipe(Schema.check(Schema.isGreaterThanOrEqualTo(0)));
|
|
18
|
+
export const ModelPrice = Schema.Struct({
|
|
19
|
+
input: Price,
|
|
20
|
+
output: Price,
|
|
21
|
+
cacheRead: Price,
|
|
22
|
+
cacheWrite: Price,
|
|
23
|
+
reasoning: Schema.optionalKey(Price)
|
|
7
24
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -18,7 +18,8 @@ export const defaultAnnotations = {
|
|
|
18
18
|
compact: Option.fromNullishOr(view.compact),
|
|
19
19
|
subjects: view.subjects ?? (() => []),
|
|
20
20
|
artifacts: view.artifacts ?? (() => []),
|
|
21
|
-
digest: Option.fromNullishOr(view.digest)
|
|
21
|
+
digest: Option.fromNullishOr(view.digest),
|
|
22
|
+
media: Option.fromNullishOr(view.media)
|
|
22
23
|
})),
|
|
23
24
|
annotations: { ...defaultAnnotations, ...definition.annotations }
|
|
24
25
|
}), defineSkill = (skill) => ({
|
|
@@ -5,6 +5,9 @@ import type { TypedPlugin } from "./plugin.entity.js";
|
|
|
5
5
|
* `build` (the graph's) and `live` (a host's own composition) share one
|
|
6
6
|
* decode: the options merged over the defaults, extra keys refused — so a
|
|
7
7
|
* graph caller, whose options already hold the defaults, decodes the same.
|
|
8
|
+
* A `build` whose layer fails with a `HarnessError` fails with that error's
|
|
9
|
+
* code, as `live` does (and names the plugin when the error does not); any
|
|
10
|
+
* other failure is `plugin.activation`, and a defect `plugin.defect`.
|
|
8
11
|
*/
|
|
9
12
|
export declare const definePlugin: <A extends Readonly<Record<string, unknown>>, I, Out, E, In>(definition: {
|
|
10
13
|
readonly id: string;
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { Context, Effect, Layer, Schema } from "effect";
|
|
2
2
|
import { HarnessError, PLUGIN_API_VERSION } from "./plugin.entity.js";
|
|
3
|
+
const isHarnessError = Schema.is(Schema.Struct({
|
|
4
|
+
_tag: Schema.Literal("HarnessError"),
|
|
5
|
+
code: Schema.String,
|
|
6
|
+
message: Schema.String,
|
|
7
|
+
plugin: Schema.optional(Schema.String)
|
|
8
|
+
}));
|
|
3
9
|
export const definePlugin = (definition) => {
|
|
4
10
|
const decodeOptions = (options) => Schema.decodeUnknownEffect(definition.config)(typeof options === "object" && options !== null ? { ...definition.defaults, ...options } : options ?? definition.defaults, { onExcessProperty: "error" });
|
|
5
11
|
return {
|
|
@@ -14,7 +20,7 @@ export const definePlugin = (definition) => {
|
|
|
14
20
|
schema: definition.config,
|
|
15
21
|
config: definition.config,
|
|
16
22
|
defaults: definition.defaults,
|
|
17
|
-
build: (options, services) => decodeOptions(options).pipe(Effect.flatMap((config) => Layer.build(Layer.fresh(definition.layer(config))).pipe(Effect.provide(Context.makeUnsafe(services.mapUnsafe)), Effect.map((built) => Context.makeUnsafe(Context.omit(Layer.CurrentMemoMap)(built).mapUnsafe)))), Effect.mapError((error) => new HarnessError({ code: "plugin.activation", plugin: definition.id, message: String(error) })), Effect.catchDefect((error) => Effect.fail(new HarnessError({ code: "plugin.defect", plugin: definition.id, message: String(error) })))),
|
|
23
|
+
build: (options, services) => decodeOptions(options).pipe(Effect.flatMap((config) => Layer.build(Layer.fresh(definition.layer(config))).pipe(Effect.provide(Context.makeUnsafe(services.mapUnsafe)), Effect.map((built) => Context.makeUnsafe(Context.omit(Layer.CurrentMemoMap)(built).mapUnsafe)))), Effect.mapError((error) => isHarnessError(error) ? new HarnessError({ code: error.code, message: error.message, plugin: error.plugin ?? definition.id }) : new HarnessError({ code: "plugin.activation", plugin: definition.id, message: String(error) })), Effect.catchDefect((error) => Effect.fail(new HarnessError({ code: "plugin.defect", plugin: definition.id, message: String(error) })))),
|
|
18
24
|
live: (options) => Layer.unwrap(decodeOptions(options).pipe(Effect.mapError((error) => new HarnessError({ code: "config.options", plugin: definition.id, message: String(error) })), Effect.map(definition.layer)))
|
|
19
25
|
};
|
|
20
26
|
};
|