@qilin-agent/attachment 0.0.0-stage → 3.1.3
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/LICENSE +21 -0
- package/README.i18n.yaml +67 -0
- package/README.md +137 -2
- package/README.zh.md +138 -0
- package/lib/index.js +344 -0
- package/lib/types/admission.d.ts +26 -0
- package/lib/types/admission.js +52 -0
- package/lib/types/brand.d.ts +19 -0
- package/lib/types/brand.js +18 -0
- package/lib/types/error.d.ts +42 -0
- package/lib/types/error.js +72 -0
- package/lib/types/index.d.ts +132 -0
- package/lib/types/index.js +162 -0
- package/lib/types/request-projection.d.ts +28 -0
- package/lib/types/request-projection.js +49 -0
- package/lib/types/types.d.ts +155 -0
- package/lib/types/types.js +3 -0
- package/package.json +42 -3
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/** Durable attachment storage seam (`ctx.attachments`). @module @qilin-agent/attachment */
|
|
2
|
+
import { Context, Service } from '@qilin-agent/kylin';
|
|
3
|
+
import { AttachmentError } from './error.ts';
|
|
4
|
+
import type { AdmittedPromptContentPart, AttachmentAdmissionPart, EncodedFileAttachment, FileAttachmentRef, ImageAttachmentLimits, ImageAttachmentRef, ImageRequestTarget, RequestImageAttachment, SaveFileAttachment, SaveFileStreamAttachment, SaveImageAttachment, StoredImageAttachment } from './types.ts';
|
|
5
|
+
export { AttachmentId, ImageVariantId } from './brand.ts';
|
|
6
|
+
export { AttachmentError, isAttachmentError, isImageAdmissionError } from './error.ts';
|
|
7
|
+
export type { AttachmentErrorCode, ImageAdmissionErrorCode } from './error.ts';
|
|
8
|
+
export { admitEncodedFile, admitEncodedImages } from './admission.ts';
|
|
9
|
+
export { longEdgeDimensions, requestImageDimensions } from './request-projection.ts';
|
|
10
|
+
export type { ProjectedDimensions } from './request-projection.ts';
|
|
11
|
+
export type { AttachmentId as AttachmentIdType, AdmittedPromptContentPart, AttachmentAdmissionPart, EncodedFileAttachment, EncodedImageAttachment, FileAttachmentRef, ImageAttachmentLimits, ImageAttachmentRef, ImageRequestTarget, ImageMediaType, PromptContentPart, RequestImageAttachment, SaveFileAttachment, SaveFileStreamAttachment, SaveImageAttachment, StoredImageAttachment, } from './types.ts';
|
|
12
|
+
declare module '@qilin-agent/kylin' {
|
|
13
|
+
interface Context {
|
|
14
|
+
attachments: AttachmentStore;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/** Immutable binary attachment service. Implementations validate bytes before publishing a reference. */
|
|
18
|
+
export declare abstract class AttachmentStore extends Service {
|
|
19
|
+
constructor(ctx: Context);
|
|
20
|
+
/** Deployment-resolved image policy used by authoritative and fast-path validation. */
|
|
21
|
+
abstract readonly imageLimits: ImageAttachmentLimits;
|
|
22
|
+
/**
|
|
23
|
+
* Validate one image without persisting it.
|
|
24
|
+
* Batch callers validate every member before saving any member.
|
|
25
|
+
* @param input - encoded bytes, declared media type, and optional display name.
|
|
26
|
+
* @returns completion after the encoded raster has been fully decoded.
|
|
27
|
+
*/
|
|
28
|
+
abstract validateImage(input: SaveImageAttachment): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Validate one ordered image batch before committing any member.
|
|
31
|
+
* Validation failures start no writes; storage failures return no partial
|
|
32
|
+
* references, although already published content-addressed objects may stay
|
|
33
|
+
* unreachable until a future retention policy collects them.
|
|
34
|
+
* @param inputs - encoded images in their owning message order.
|
|
35
|
+
* @returns durable references in the exact input order.
|
|
36
|
+
*/
|
|
37
|
+
protected validateImageBatch(inputs: readonly SaveImageAttachment[]): void;
|
|
38
|
+
/**
|
|
39
|
+
* Validate and durably commit one ordered image batch.
|
|
40
|
+
* @param inputs - encoded images in owning-message order.
|
|
41
|
+
* @returns durable normalized attachment references in the same order after every member succeeds.
|
|
42
|
+
*/
|
|
43
|
+
saveImages(inputs: readonly SaveImageAttachment[]): Promise<readonly ImageAttachmentRef[]>;
|
|
44
|
+
/**
|
|
45
|
+
* Admit one Host prompt and replace each uploaded image with its durable reference.
|
|
46
|
+
* Text and durable file references pass through unchanged. A prompt without image parts performs no storage operation.
|
|
47
|
+
* @param content - prompt parts in message order after file receipt resolution.
|
|
48
|
+
* @returns admitted prompt parts in the same order as `content`.
|
|
49
|
+
* @throws AttachmentError when the image batch is refused.
|
|
50
|
+
*/
|
|
51
|
+
admitPromptContent(content: readonly AttachmentAdmissionPart[]): Promise<AdmittedPromptContentPart[]>;
|
|
52
|
+
/**
|
|
53
|
+
* Decode and durably commit one canonical base64 file upload.
|
|
54
|
+
* @param input - canonical base64 bytes and optional display name.
|
|
55
|
+
* @returns the durable content-addressed file reference.
|
|
56
|
+
* @throws AttachmentError when the encoding or storage operation is refused.
|
|
57
|
+
*/
|
|
58
|
+
admitEncodedFile(input: EncodedFileAttachment): Promise<FileAttachmentRef>;
|
|
59
|
+
/**
|
|
60
|
+
* Identify a failure emitted by this attachment capability by its stable code.
|
|
61
|
+
* @param error - value caught from an attachment operation.
|
|
62
|
+
* @returns whether the value is an attachment failure.
|
|
63
|
+
*/
|
|
64
|
+
isAttachmentError(error: unknown): error is AttachmentError;
|
|
65
|
+
/**
|
|
66
|
+
* Validate and durably commit one image before its owning session event is appended.
|
|
67
|
+
* The returned reference describes the persisted normalized image. When
|
|
68
|
+
* normalization reduces the raster, its `originalDimensions` records the
|
|
69
|
+
* orientation-applied input dimensions.
|
|
70
|
+
* @param input - encoded bytes, declared media type, and optional display name.
|
|
71
|
+
* @returns the durable content-addressed normalized image reference.
|
|
72
|
+
*/
|
|
73
|
+
abstract saveImage(input: SaveImageAttachment): Promise<ImageAttachmentRef>;
|
|
74
|
+
/**
|
|
75
|
+
* Read one image and verify that bytes still match the recorded reference.
|
|
76
|
+
* @param ref - durable reference from the session log.
|
|
77
|
+
* @param signal - optional cancellation for backend read and verification work.
|
|
78
|
+
* @returns the verified bytes and normalized attachment reference.
|
|
79
|
+
* @throws the signal reason when aborted, or a storage error when verification fails.
|
|
80
|
+
*/
|
|
81
|
+
abstract readImage(ref: ImageAttachmentRef, signal?: AbortSignal): Promise<StoredImageAttachment>;
|
|
82
|
+
/**
|
|
83
|
+
* Locate the provider-owned normalized object in the harness host filesystem.
|
|
84
|
+
* @param ref - durable normalized attachment reference.
|
|
85
|
+
* @returns an absolute host path, or undefined when this backend is not host-file-backed.
|
|
86
|
+
* @throws an AttachmentError when the durable reference is invalid.
|
|
87
|
+
*/
|
|
88
|
+
imageHostPath(ref: ImageAttachmentRef): string | undefined;
|
|
89
|
+
/**
|
|
90
|
+
* Durably commit one file byte-for-byte before its owning session event is
|
|
91
|
+
* appended. Files carry no admission limits: any byte content and length is
|
|
92
|
+
* accepted, and the stored object is the exact submitted bytes. Backends
|
|
93
|
+
* without verbatim file storage keep this default rejection.
|
|
94
|
+
* @param input - exact bytes and optional display name.
|
|
95
|
+
* @returns the durable content-addressed file reference.
|
|
96
|
+
*/
|
|
97
|
+
saveFile(input: SaveFileAttachment): Promise<FileAttachmentRef>;
|
|
98
|
+
/**
|
|
99
|
+
* Durably commit one file byte-for-byte from bounded chunks. Providers must
|
|
100
|
+
* apply backpressure and must not collect the complete file in memory.
|
|
101
|
+
* Backends without streamed verbatim storage keep this default rejection.
|
|
102
|
+
* @param input - ordered exact bytes, optional cancellation, and display name.
|
|
103
|
+
* @returns the durable content-addressed file reference.
|
|
104
|
+
*/
|
|
105
|
+
saveFileStream(input: SaveFileStreamAttachment): Promise<FileAttachmentRef>;
|
|
106
|
+
/**
|
|
107
|
+
* Read and verify one verbatim stored file as bounded chunks. Providers must
|
|
108
|
+
* not collect the complete file in memory. Backends without verbatim file
|
|
109
|
+
* reads keep this default rejection.
|
|
110
|
+
* @param ref - durable reference from the session log.
|
|
111
|
+
* @param signal - optional cancellation for backend reads and verification work.
|
|
112
|
+
* @returns exact file bytes in order; integrity failures reject the iteration.
|
|
113
|
+
*/
|
|
114
|
+
readFileStream(ref: FileAttachmentRef, signal?: AbortSignal): AsyncIterable<Uint8Array>;
|
|
115
|
+
/**
|
|
116
|
+
* Locate the verbatim stored file object in the harness host filesystem.
|
|
117
|
+
* @param ref - durable file reference.
|
|
118
|
+
* @returns an absolute host path, or undefined when this backend is not host-file-backed.
|
|
119
|
+
* @throws an AttachmentError when the durable reference is invalid.
|
|
120
|
+
*/
|
|
121
|
+
fileHostPath(ref: FileAttachmentRef): string | undefined;
|
|
122
|
+
/**
|
|
123
|
+
* Generate or read one deterministic model-request version from the stored normalized image.
|
|
124
|
+
* @param ref - durable provider-independent normalized attachment reference.
|
|
125
|
+
* @param target - route-chosen dimensions and byte target; an unmet byte target yields the smallest ladder output.
|
|
126
|
+
* @param signal - optional cancellation.
|
|
127
|
+
* @returns request bytes and the cache/upload identity covering every transform input.
|
|
128
|
+
*/
|
|
129
|
+
readImageRequest(ref: ImageAttachmentRef, target: ImageRequestTarget, signal?: AbortSignal): Promise<RequestImageAttachment>;
|
|
130
|
+
}
|
|
131
|
+
export default AttachmentStore;
|
|
132
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/** Durable attachment storage seam (`ctx.attachments`). @module @qilin-agent/attachment */
|
|
2
|
+
import { Service } from '@qilin-agent/kylin';
|
|
3
|
+
import { admitEncodedFile as admitFileInput, admitEncodedImages } from "./admission.js";
|
|
4
|
+
import { AttachmentError, isAttachmentError as matchesAttachmentError } from "./error.js";
|
|
5
|
+
export { AttachmentId, ImageVariantId } from "./brand.js";
|
|
6
|
+
export { AttachmentError, isAttachmentError, isImageAdmissionError } from "./error.js";
|
|
7
|
+
export { admitEncodedFile, admitEncodedImages } from "./admission.js";
|
|
8
|
+
export { longEdgeDimensions, requestImageDimensions } from "./request-projection.js";
|
|
9
|
+
/** Immutable binary attachment service. Implementations validate bytes before publishing a reference. */
|
|
10
|
+
export class AttachmentStore extends Service {
|
|
11
|
+
constructor(ctx) {
|
|
12
|
+
super(ctx, 'attachments');
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Validate one ordered image batch before committing any member.
|
|
16
|
+
* Validation failures start no writes; storage failures return no partial
|
|
17
|
+
* references, although already published content-addressed objects may stay
|
|
18
|
+
* unreachable until a future retention policy collects them.
|
|
19
|
+
* @param inputs - encoded images in their owning message order.
|
|
20
|
+
* @returns durable references in the exact input order.
|
|
21
|
+
*/
|
|
22
|
+
validateImageBatch(inputs) {
|
|
23
|
+
const { maxImagesPerMessage, maxMessageImageBytes, mediaTypes } = this.imageLimits;
|
|
24
|
+
if (inputs.length > maxImagesPerMessage) {
|
|
25
|
+
throw new AttachmentError('Image batch exceeds the configured image-count limit.', 'TOO_MANY_IMAGES');
|
|
26
|
+
}
|
|
27
|
+
const totalBytes = inputs.reduce((sum, input) => sum + input.data.byteLength, 0);
|
|
28
|
+
if (totalBytes > maxMessageImageBytes) {
|
|
29
|
+
throw new AttachmentError('Image batch exceeds the configured aggregate image-byte limit.', 'IMAGES_TOO_LARGE');
|
|
30
|
+
}
|
|
31
|
+
for (const input of inputs) {
|
|
32
|
+
if (!mediaTypes.includes(input.mediaType)) {
|
|
33
|
+
throw new AttachmentError(`Image type ${input.mediaType} is not accepted by this deployment.`, 'UNSUPPORTED_IMAGE_TYPE');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Validate and durably commit one ordered image batch.
|
|
39
|
+
* @param inputs - encoded images in owning-message order.
|
|
40
|
+
* @returns durable normalized attachment references in the same order after every member succeeds.
|
|
41
|
+
*/
|
|
42
|
+
async saveImages(inputs) {
|
|
43
|
+
this.validateImageBatch(inputs);
|
|
44
|
+
for (const input of inputs)
|
|
45
|
+
await this.validateImage(input);
|
|
46
|
+
const refs = [];
|
|
47
|
+
for (const input of inputs)
|
|
48
|
+
refs.push(await this.saveImage(input));
|
|
49
|
+
return refs;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Admit one Host prompt and replace each uploaded image with its durable reference.
|
|
53
|
+
* Text and durable file references pass through unchanged. A prompt without image parts performs no storage operation.
|
|
54
|
+
* @param content - prompt parts in message order after file receipt resolution.
|
|
55
|
+
* @returns admitted prompt parts in the same order as `content`.
|
|
56
|
+
* @throws AttachmentError when the image batch is refused.
|
|
57
|
+
*/
|
|
58
|
+
async admitPromptContent(content) {
|
|
59
|
+
if (content.every(part => part.type !== 'image')) {
|
|
60
|
+
return content.map(part => part.type === 'text'
|
|
61
|
+
? { type: 'text', text: part.text }
|
|
62
|
+
: { type: 'file', attachment: part.attachment });
|
|
63
|
+
}
|
|
64
|
+
const refs = await admitEncodedImages(this, content.filter(part => part.type === 'image'));
|
|
65
|
+
let next = 0;
|
|
66
|
+
return content.map((part) => {
|
|
67
|
+
if (part.type === 'text')
|
|
68
|
+
return { type: 'text', text: part.text };
|
|
69
|
+
if (part.type === 'file')
|
|
70
|
+
return { type: 'file', attachment: part.attachment };
|
|
71
|
+
return { type: 'image', attachment: refs[next++] };
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Decode and durably commit one canonical base64 file upload.
|
|
76
|
+
* @param input - canonical base64 bytes and optional display name.
|
|
77
|
+
* @returns the durable content-addressed file reference.
|
|
78
|
+
* @throws AttachmentError when the encoding or storage operation is refused.
|
|
79
|
+
*/
|
|
80
|
+
admitEncodedFile(input) {
|
|
81
|
+
return admitFileInput(this, input);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Identify a failure emitted by this attachment capability by its stable code.
|
|
85
|
+
* @param error - value caught from an attachment operation.
|
|
86
|
+
* @returns whether the value is an attachment failure.
|
|
87
|
+
*/
|
|
88
|
+
isAttachmentError(error) {
|
|
89
|
+
return matchesAttachmentError(error);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Locate the provider-owned normalized object in the harness host filesystem.
|
|
93
|
+
* @param ref - durable normalized attachment reference.
|
|
94
|
+
* @returns an absolute host path, or undefined when this backend is not host-file-backed.
|
|
95
|
+
* @throws an AttachmentError when the durable reference is invalid.
|
|
96
|
+
*/
|
|
97
|
+
imageHostPath(ref) {
|
|
98
|
+
void ref;
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Durably commit one file byte-for-byte before its owning session event is
|
|
103
|
+
* appended. Files carry no admission limits: any byte content and length is
|
|
104
|
+
* accepted, and the stored object is the exact submitted bytes. Backends
|
|
105
|
+
* without verbatim file storage keep this default rejection.
|
|
106
|
+
* @param input - exact bytes and optional display name.
|
|
107
|
+
* @returns the durable content-addressed file reference.
|
|
108
|
+
*/
|
|
109
|
+
saveFile(input) {
|
|
110
|
+
void input;
|
|
111
|
+
return Promise.reject(new AttachmentError('The mounted attachment provider cannot store verbatim files.', 'ATTACHMENT_FILES_UNSUPPORTED'));
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Durably commit one file byte-for-byte from bounded chunks. Providers must
|
|
115
|
+
* apply backpressure and must not collect the complete file in memory.
|
|
116
|
+
* Backends without streamed verbatim storage keep this default rejection.
|
|
117
|
+
* @param input - ordered exact bytes, optional cancellation, and display name.
|
|
118
|
+
* @returns the durable content-addressed file reference.
|
|
119
|
+
*/
|
|
120
|
+
saveFileStream(input) {
|
|
121
|
+
void input;
|
|
122
|
+
return Promise.reject(new AttachmentError('The mounted attachment provider cannot stream verbatim files.', 'ATTACHMENT_FILES_UNSUPPORTED'));
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Read and verify one verbatim stored file as bounded chunks. Providers must
|
|
126
|
+
* not collect the complete file in memory. Backends without verbatim file
|
|
127
|
+
* reads keep this default rejection.
|
|
128
|
+
* @param ref - durable reference from the session log.
|
|
129
|
+
* @param signal - optional cancellation for backend reads and verification work.
|
|
130
|
+
* @returns exact file bytes in order; integrity failures reject the iteration.
|
|
131
|
+
*/
|
|
132
|
+
async *readFileStream(ref, signal) {
|
|
133
|
+
signal?.throwIfAborted();
|
|
134
|
+
void ref;
|
|
135
|
+
await Promise.reject(new AttachmentError('The mounted attachment provider cannot read verbatim files.', 'ATTACHMENT_FILES_UNSUPPORTED'));
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Locate the verbatim stored file object in the harness host filesystem.
|
|
139
|
+
* @param ref - durable file reference.
|
|
140
|
+
* @returns an absolute host path, or undefined when this backend is not host-file-backed.
|
|
141
|
+
* @throws an AttachmentError when the durable reference is invalid.
|
|
142
|
+
*/
|
|
143
|
+
fileHostPath(ref) {
|
|
144
|
+
void ref;
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Generate or read one deterministic model-request version from the stored normalized image.
|
|
149
|
+
* @param ref - durable provider-independent normalized attachment reference.
|
|
150
|
+
* @param target - route-chosen dimensions and byte target; an unmet byte target yields the smallest ladder output.
|
|
151
|
+
* @param signal - optional cancellation.
|
|
152
|
+
* @returns request bytes and the cache/upload identity covering every transform input.
|
|
153
|
+
*/
|
|
154
|
+
readImageRequest(ref, target, signal) {
|
|
155
|
+
signal?.throwIfAborted();
|
|
156
|
+
void ref;
|
|
157
|
+
void target;
|
|
158
|
+
return Promise.reject(new AttachmentError('The mounted attachment provider cannot derive model-request images.', 'ATTACHMENT_PROJECTION_UNSUPPORTED'));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
export default AttachmentStore;
|
|
162
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure request-projection geometry shared by model routes and provider-side
|
|
3
|
+
* request pricing. @module @qilin-agent/attachment/request-projection
|
|
4
|
+
*/
|
|
5
|
+
/** Integer width and height of one projected image. */
|
|
6
|
+
export interface ProjectedDimensions {
|
|
7
|
+
width: number;
|
|
8
|
+
height: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Compute aspect-preserving integer dimensions within a hard total-pixel budget.
|
|
12
|
+
* @param width - positive source width.
|
|
13
|
+
* @param height - positive source height.
|
|
14
|
+
* @param maxPixels - positive width-times-height cap.
|
|
15
|
+
* @returns inward-rounded dimensions; small images are not enlarged.
|
|
16
|
+
*/
|
|
17
|
+
export declare function requestImageDimensions(width: number, height: number, maxPixels: number): ProjectedDimensions;
|
|
18
|
+
/**
|
|
19
|
+
* Compute aspect-preserving integer dimensions with an exact long edge; the
|
|
20
|
+
* short edge rounds to the nearest pixel, as an encoder resize by the
|
|
21
|
+
* long edge alone.
|
|
22
|
+
* @param width - positive source width.
|
|
23
|
+
* @param height - positive source height.
|
|
24
|
+
* @param longEdge - positive target for the longer source edge.
|
|
25
|
+
* @returns the target dimensions; a long edge at or above the source returns the source unchanged.
|
|
26
|
+
*/
|
|
27
|
+
export declare function longEdgeDimensions(width: number, height: number, longEdge: number): ProjectedDimensions;
|
|
28
|
+
//# sourceMappingURL=request-projection.d.ts.map
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure request-projection geometry shared by model routes and provider-side
|
|
3
|
+
* request pricing. @module @qilin-agent/attachment/request-projection
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Compute aspect-preserving integer dimensions within a hard total-pixel budget.
|
|
7
|
+
* @param width - positive source width.
|
|
8
|
+
* @param height - positive source height.
|
|
9
|
+
* @param maxPixels - positive width-times-height cap.
|
|
10
|
+
* @returns inward-rounded dimensions; small images are not enlarged.
|
|
11
|
+
*/
|
|
12
|
+
export function requestImageDimensions(width, height, maxPixels) {
|
|
13
|
+
const scale = Math.min(1, Math.sqrt(maxPixels / (width * height)));
|
|
14
|
+
if (scale === 1)
|
|
15
|
+
return { width, height };
|
|
16
|
+
if (width >= height) {
|
|
17
|
+
let projectedWidth = Math.max(1, Math.floor(width * scale));
|
|
18
|
+
let projectedHeight = Math.max(1, Math.round(projectedWidth * height / width));
|
|
19
|
+
while (projectedWidth * projectedHeight > maxPixels && projectedWidth > 1) {
|
|
20
|
+
projectedWidth -= 1;
|
|
21
|
+
projectedHeight = Math.max(1, Math.round(projectedWidth * height / width));
|
|
22
|
+
}
|
|
23
|
+
return { width: projectedWidth, height: projectedHeight };
|
|
24
|
+
}
|
|
25
|
+
let projectedHeight = Math.max(1, Math.floor(height * scale));
|
|
26
|
+
let projectedWidth = Math.max(1, Math.round(projectedHeight * width / height));
|
|
27
|
+
while (projectedWidth * projectedHeight > maxPixels && projectedHeight > 1) {
|
|
28
|
+
projectedHeight -= 1;
|
|
29
|
+
projectedWidth = Math.max(1, Math.round(projectedHeight * width / height));
|
|
30
|
+
}
|
|
31
|
+
return { width: projectedWidth, height: projectedHeight };
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Compute aspect-preserving integer dimensions with an exact long edge; the
|
|
35
|
+
* short edge rounds to the nearest pixel, as an encoder resize by the
|
|
36
|
+
* long edge alone.
|
|
37
|
+
* @param width - positive source width.
|
|
38
|
+
* @param height - positive source height.
|
|
39
|
+
* @param longEdge - positive target for the longer source edge.
|
|
40
|
+
* @returns the target dimensions; a long edge at or above the source returns the source unchanged.
|
|
41
|
+
*/
|
|
42
|
+
export function longEdgeDimensions(width, height, longEdge) {
|
|
43
|
+
if (longEdge >= Math.max(width, height))
|
|
44
|
+
return { width, height };
|
|
45
|
+
return width >= height
|
|
46
|
+
? { width: longEdge, height: Math.max(1, Math.round(longEdge * height / width)) }
|
|
47
|
+
: { width: Math.max(1, Math.round(longEdge * width / height)), height: longEdge };
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=request-projection.js.map
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/** Durable attachment vocabulary. @module @qilin-agent/attachment/types */
|
|
2
|
+
import type { AttachmentId, ImageVariantId } from './brand.ts';
|
|
3
|
+
export type { AttachmentId } from './brand.ts';
|
|
4
|
+
/** Raster image formats accepted by the version-one attachment path. */
|
|
5
|
+
export type ImageMediaType = 'image/png' | 'image/jpeg' | 'image/webp' | 'image/gif';
|
|
6
|
+
/** Durable, serializable reference to one immutable normalized image. */
|
|
7
|
+
export interface ImageAttachmentRef {
|
|
8
|
+
/** Opaque storage identifier; never a filesystem path or bearer URL. */
|
|
9
|
+
attachmentId: AttachmentId;
|
|
10
|
+
/** Media type verified from the stored bytes. */
|
|
11
|
+
mediaType: ImageMediaType;
|
|
12
|
+
/** Exact encoded byte length. */
|
|
13
|
+
bytes: number;
|
|
14
|
+
/** Intrinsic encoded width in pixels. */
|
|
15
|
+
width: number;
|
|
16
|
+
/** Intrinsic encoded height in pixels. */
|
|
17
|
+
height: number;
|
|
18
|
+
/** Optional display name stripped of local path information. */
|
|
19
|
+
name?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Input dimensions after applying EXIF orientation and before normalization
|
|
22
|
+
* scaling. Present only when normalization reduced the image.
|
|
23
|
+
*/
|
|
24
|
+
originalDimensions?: {
|
|
25
|
+
width: number;
|
|
26
|
+
height: number;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Durable, serializable reference to one verbatim stored file. Files are
|
|
31
|
+
* stored byte-for-byte with no normalization; `attachmentId` is the sha256
|
|
32
|
+
* digest of exactly those bytes.
|
|
33
|
+
*/
|
|
34
|
+
export interface FileAttachmentRef {
|
|
35
|
+
/** Opaque content-addressed storage identifier; never a filesystem path or bearer URL. */
|
|
36
|
+
attachmentId: AttachmentId;
|
|
37
|
+
/** Sanitized display filename, also the stored object's leaf name. */
|
|
38
|
+
name: string;
|
|
39
|
+
/** Exact byte length. */
|
|
40
|
+
bytes: number;
|
|
41
|
+
}
|
|
42
|
+
/** Base64-encoded file upload accompanying one wire request. */
|
|
43
|
+
export interface EncodedFileAttachment {
|
|
44
|
+
/** Canonical base64 encoding of the file bytes. */
|
|
45
|
+
data: string;
|
|
46
|
+
/** Optional display name; it is never interpreted as a path. */
|
|
47
|
+
name?: string;
|
|
48
|
+
}
|
|
49
|
+
/** Request to durably commit one file verbatim. */
|
|
50
|
+
export interface SaveFileAttachment {
|
|
51
|
+
data: Uint8Array;
|
|
52
|
+
/** Optional browser/provider display name; it is never interpreted as a path. */
|
|
53
|
+
name?: string;
|
|
54
|
+
}
|
|
55
|
+
/** Request to durably commit one file from bounded byte chunks. */
|
|
56
|
+
export interface SaveFileStreamAttachment {
|
|
57
|
+
/** Exact file bytes in order; providers must not retain the complete sequence in memory. */
|
|
58
|
+
data: AsyncIterable<Uint8Array>;
|
|
59
|
+
/** Optional cancellation for source reads and storage writes. */
|
|
60
|
+
signal?: AbortSignal;
|
|
61
|
+
/** Optional browser/provider display name; it is never interpreted as a path. */
|
|
62
|
+
name?: string;
|
|
63
|
+
}
|
|
64
|
+
/** Deployment-resolved limits used by upload admission and request buffering. */
|
|
65
|
+
export interface ImageAttachmentLimits {
|
|
66
|
+
maxImageBytes: number;
|
|
67
|
+
maxImagesPerMessage: number;
|
|
68
|
+
maxMessageImageBytes: number;
|
|
69
|
+
maxImagePixels: number;
|
|
70
|
+
/** Maximum intrinsic width and maximum intrinsic height in pixels for one image. */
|
|
71
|
+
maxImageDimension: number;
|
|
72
|
+
mediaTypes: readonly ImageMediaType[];
|
|
73
|
+
}
|
|
74
|
+
/** Base64-encoded image upload accompanying one wire request. */
|
|
75
|
+
export interface EncodedImageAttachment {
|
|
76
|
+
/** Declared media type, verified against the decoded bytes during admission. */
|
|
77
|
+
mediaType: ImageMediaType;
|
|
78
|
+
/** Canonical base64 encoding of the image bytes. */
|
|
79
|
+
data: string;
|
|
80
|
+
/** Optional display name; it is never interpreted as a path. */
|
|
81
|
+
name?: string;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Browser-submitted prompt content accepted by Host prompt endpoints; the
|
|
85
|
+
* accepting Host promotes image parts to durable references through
|
|
86
|
+
* `ctx.attachments.admitPromptContent()` before any message is created, so a wire caller can
|
|
87
|
+
* never cite an attachment it did not upload.
|
|
88
|
+
*/
|
|
89
|
+
export type PromptContentPart = {
|
|
90
|
+
readonly type: 'text';
|
|
91
|
+
readonly text: string;
|
|
92
|
+
} | {
|
|
93
|
+
readonly type: 'image';
|
|
94
|
+
readonly mediaType: ImageMediaType;
|
|
95
|
+
readonly data: string;
|
|
96
|
+
readonly name?: string;
|
|
97
|
+
};
|
|
98
|
+
/** Host prompt content whose file receipts are resolved and whose image bytes await admission. */
|
|
99
|
+
export type AttachmentAdmissionPart = PromptContentPart | {
|
|
100
|
+
readonly type: 'file';
|
|
101
|
+
readonly attachment: FileAttachmentRef;
|
|
102
|
+
};
|
|
103
|
+
/** Host-admitted prompt content with every attachment represented by its durable reference. */
|
|
104
|
+
export type AdmittedPromptContentPart = {
|
|
105
|
+
readonly type: 'text';
|
|
106
|
+
readonly text: string;
|
|
107
|
+
} | {
|
|
108
|
+
readonly type: 'image';
|
|
109
|
+
readonly attachment: ImageAttachmentRef;
|
|
110
|
+
} | {
|
|
111
|
+
readonly type: 'file';
|
|
112
|
+
readonly attachment: FileAttachmentRef;
|
|
113
|
+
};
|
|
114
|
+
/** Request to validate and durably commit one image. */
|
|
115
|
+
export interface SaveImageAttachment {
|
|
116
|
+
data: Uint8Array;
|
|
117
|
+
/** Caller-declared media type, checked against fully decoded bytes. */
|
|
118
|
+
mediaType: ImageMediaType;
|
|
119
|
+
/** Optional browser/provider display name; it is never interpreted as a path. */
|
|
120
|
+
name?: string;
|
|
121
|
+
}
|
|
122
|
+
/** Stored image bytes returned after reference and digest verification. */
|
|
123
|
+
export interface StoredImageAttachment {
|
|
124
|
+
ref: ImageAttachmentRef;
|
|
125
|
+
data: Uint8Array;
|
|
126
|
+
}
|
|
127
|
+
/** Deterministic request-image target selected by one exact model route for one attachment. */
|
|
128
|
+
export interface ImageRequestTarget {
|
|
129
|
+
/** Target width in pixels; a target above the source keeps the source width. */
|
|
130
|
+
width: number;
|
|
131
|
+
/** Target height in pixels; a target above the source keeps the source height. */
|
|
132
|
+
height: number;
|
|
133
|
+
/** Encoded-byte target before base64 expansion or Files API upload; the smallest quality-ladder output is kept when no quality fits. */
|
|
134
|
+
maxBytes: number;
|
|
135
|
+
}
|
|
136
|
+
/** Cached request version derived from one provider-independent normalized attachment. */
|
|
137
|
+
export interface RequestImageAttachment {
|
|
138
|
+
/** Cache and upload-index key over the attachment id, policy, and fixed encoder parameters. */
|
|
139
|
+
variantId: ImageVariantId;
|
|
140
|
+
/** Durable normalized attachment from which this request version was derived. */
|
|
141
|
+
attachment: ImageAttachmentRef;
|
|
142
|
+
/** Encoded request bytes. */
|
|
143
|
+
data: Uint8Array;
|
|
144
|
+
mediaType: ImageMediaType;
|
|
145
|
+
bytes: number;
|
|
146
|
+
width: number;
|
|
147
|
+
height: number;
|
|
148
|
+
/** Provider-compatible sample depth proven after request encoding. */
|
|
149
|
+
depth: 'uchar';
|
|
150
|
+
/** Provider-compatible color space proven after request encoding. */
|
|
151
|
+
space: 'srgb';
|
|
152
|
+
/** Whether the encoded request version retains an alpha channel. */
|
|
153
|
+
hasAlpha: boolean;
|
|
154
|
+
}
|
|
155
|
+
//# sourceMappingURL=types.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,45 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qilin-agent/attachment",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"description": "Durable immutable attachment storage seam for the QiLin",
|
|
4
|
+
"version": "3.1.3",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/kkutysllb/QiLin.git",
|
|
11
|
+
"directory": "packages/attachment/attachment"
|
|
12
|
+
},
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": "^22.19.0 || >=24.0.0"
|
|
15
|
+
},
|
|
16
|
+
"type": "module",
|
|
17
|
+
"main": "lib/index.js",
|
|
18
|
+
"types": "lib/types/index.d.ts",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./lib/types/index.d.ts",
|
|
22
|
+
"default": "./lib/index.js"
|
|
23
|
+
},
|
|
24
|
+
"./types": {
|
|
25
|
+
"types": "./lib/types/types.d.ts",
|
|
26
|
+
"default": "./lib/types/types.js"
|
|
27
|
+
},
|
|
28
|
+
"./src/*": "./src/*",
|
|
29
|
+
"./package.json": "./package.json"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"lib/index.js",
|
|
33
|
+
"lib/types/**/*.js",
|
|
34
|
+
"lib/types/**/*.d.ts"
|
|
35
|
+
],
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"@qilin-agent/brand": "3.1.3",
|
|
39
|
+
"@qilin-agent/kylin": "~4.0.3"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@qilin-agent/brand": "3.1.3",
|
|
43
|
+
"@qilin-agent/kylin": "~4.0.3"
|
|
44
|
+
}
|
|
6
45
|
}
|