dist / index.js
dist / index.js
#!/usr/bin/env node
'use strict';
var sdk = require('@lmstudio/sdk');
var zod = require('zod');
var path = require('path');
var fs = require('fs');
var crypto = require('crypto');
var url = require('url');
var fs$1 = require('node:fs');
var path$1 = require('node:path');
var child_process = require('child_process');
var os = require('os');
var promises = require('node:fs/promises');
var http = require('http');
require('node:crypto');
var util = require('util');
var os$1 = require('node:os');
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
}) : x)(function(x) {
if (typeof require !== "undefined") return require.apply(this, arguments);
throw Error('Dynamic require of "' + x + '" is not supported');
});
var __commonJS = (cb, mod) => function __require2() {
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
__defProp(target, "default", { value: mod, enumerable: true }) ,
mod
));
// node_modules/flatbuffers/js/constants.js
var require_constants = __commonJS({
"node_modules/flatbuffers/js/constants.js"(exports$1) {
Object.defineProperty(exports$1, "__esModule", { value: true });
exports$1.SIZE_PREFIX_LENGTH = exports$1.FILE_IDENTIFIER_LENGTH = exports$1.SIZEOF_INT = exports$1.SIZEOF_SHORT = void 0;
exports$1.SIZEOF_SHORT = 2;
exports$1.SIZEOF_INT = 4;
exports$1.FILE_IDENTIFIER_LENGTH = 4;
exports$1.SIZE_PREFIX_LENGTH = 4;
}
});
// node_modules/flatbuffers/js/utils.js
var require_utils = __commonJS({
"node_modules/flatbuffers/js/utils.js"(exports$1) {
Object.defineProperty(exports$1, "__esModule", { value: true });
exports$1.isLittleEndian = exports$1.float64 = exports$1.float32 = exports$1.int32 = void 0;
exports$1.int32 = new Int32Array(2);
exports$1.float32 = new Float32Array(exports$1.int32.buffer);
exports$1.float64 = new Float64Array(exports$1.int32.buffer);
exports$1.isLittleEndian = new Uint16Array(new Uint8Array([1, 0]).buffer)[0] === 1;
}
});
// node_modules/flatbuffers/js/encoding.js
var require_encoding = __commonJS({
"node_modules/flatbuffers/js/encoding.js"(exports$1) {
Object.defineProperty(exports$1, "__esModule", { value: true });
exports$1.Encoding = void 0;
var Encoding;
(function(Encoding2) {
Encoding2[Encoding2["UTF8_BYTES"] = 1] = "UTF8_BYTES";
Encoding2[Encoding2["UTF16_STRING"] = 2] = "UTF16_STRING";
})(Encoding || (exports$1.Encoding = Encoding = {}));
}
});
// node_modules/flatbuffers/js/byte-buffer.js
var require_byte_buffer = __commonJS({
"node_modules/flatbuffers/js/byte-buffer.js"(exports$1) {
Object.defineProperty(exports$1, "__esModule", { value: true });
exports$1.ByteBuffer = void 0;
var constants_js_1 = require_constants();
var encoding_js_1 = require_encoding();
var utils_js_1 = require_utils();
var ByteBuffer2 = class _ByteBuffer {
/**
* Create a new ByteBuffer with a given array of bytes (`Uint8Array`)
*/
constructor(bytes_) {
this.bytes_ = bytes_;
this.position_ = 0;
this.text_decoder_ = new TextDecoder();
}
/**
* Create and allocate a new ByteBuffer with a given size.
*/
static allocate(byte_size) {
return new _ByteBuffer(new Uint8Array(byte_size));
}
clear() {
this.position_ = 0;
}
/**
* Get the underlying `Uint8Array`.
*/
bytes() {
return this.bytes_;
}
/**
* Get the buffer's position.
*/
position() {
return this.position_;
}
/**
* Set the buffer's position.
*/
setPosition(position) {
this.position_ = position;
}
/**
* Get the buffer's capacity.
*/
capacity() {
return this.bytes_.length;
}
readInt8(offset) {
return this.readUint8(offset) << 24 >> 24;
}
readUint8(offset) {
return this.bytes_[offset];
}
readInt16(offset) {
return this.readUint16(offset) << 16 >> 16;
}
readUint16(offset) {
return this.bytes_[offset] | this.bytes_[offset + 1] << 8;
}
readInt32(offset) {
return this.bytes_[offset] | this.bytes_[offset + 1] << 8 | this.bytes_[offset + 2] << 16 | this.bytes_[offset + 3] << 24;
}
readUint32(offset) {
return this.readInt32(offset) >>> 0;
}
readInt64(offset) {
return BigInt.asIntN(64, BigInt(this.readUint32(offset)) + (BigInt(this.readUint32(offset + 4)) << BigInt(32)));
}
readUint64(offset) {
return BigInt.asUintN(64, BigInt(this.readUint32(offset)) + (BigInt(this.readUint32(offset + 4)) << BigInt(32)));
}
readFloat32(offset) {
utils_js_1.int32[0] = this.readInt32(offset);
return utils_js_1.float32[0];
}
readFloat64(offset) {
utils_js_1.int32[utils_js_1.isLittleEndian ? 0 : 1] = this.readInt32(offset);
utils_js_1.int32[utils_js_1.isLittleEndian ? 1 : 0] = this.readInt32(offset + 4);
return utils_js_1.float64[0];
}
writeInt8(offset, value) {
this.bytes_[offset] = value;
}
writeUint8(offset, value) {
this.bytes_[offset] = value;
}
writeInt16(offset, value) {
this.bytes_[offset] = value;
this.bytes_[offset + 1] = value >> 8;
}
writeUint16(offset, value) {
this.bytes_[offset] = value;
this.bytes_[offset + 1] = value >> 8;
}
writeInt32(offset, value) {
this.bytes_[offset] = value;
this.bytes_[offset + 1] = value >> 8;
this.bytes_[offset + 2] = value >> 16;
this.bytes_[offset + 3] = value >> 24;
}
writeUint32(offset, value) {
this.bytes_[offset] = value;
this.bytes_[offset + 1] = value >> 8;
this.bytes_[offset + 2] = value >> 16;
this.bytes_[offset + 3] = value >> 24;
}
writeInt64(offset, value) {
this.writeInt32(offset, Number(BigInt.asIntN(32, value)));
this.writeInt32(offset + 4, Number(BigInt.asIntN(32, value >> BigInt(32))));
}
writeUint64(offset, value) {
this.writeUint32(offset, Number(BigInt.asUintN(32, value)));
this.writeUint32(offset + 4, Number(BigInt.asUintN(32, value >> BigInt(32))));
}
writeFloat32(offset, value) {
utils_js_1.float32[0] = value;
this.writeInt32(offset, utils_js_1.int32[0]);
}
writeFloat64(offset, value) {
utils_js_1.float64[0] = value;
this.writeInt32(offset, utils_js_1.int32[utils_js_1.isLittleEndian ? 0 : 1]);
this.writeInt32(offset + 4, utils_js_1.int32[utils_js_1.isLittleEndian ? 1 : 0]);
}
/**
* Return the file identifier. Behavior is undefined for FlatBuffers whose
* schema does not include a file_identifier (likely points at padding or the
* start of a the root vtable).
*/
getBufferIdentifier() {
if (this.bytes_.length < this.position_ + constants_js_1.SIZEOF_INT + constants_js_1.FILE_IDENTIFIER_LENGTH) {
throw new Error("FlatBuffers: ByteBuffer is too short to contain an identifier.");
}
let result = "";
for (let i = 0; i < constants_js_1.FILE_IDENTIFIER_LENGTH; i++) {
result += String.fromCharCode(this.readInt8(this.position_ + constants_js_1.SIZEOF_INT + i));
}
return result;
}
/**
* Look up a field in the vtable, return an offset into the object, or 0 if the
* field is not present.
*/
__offset(bb_pos, vtable_offset) {
const vtable = bb_pos - this.readInt32(bb_pos);
return vtable_offset < this.readInt16(vtable) ? this.readInt16(vtable + vtable_offset) : 0;
}
/**
* Initialize any Table-derived type to point to the union at the given offset.
*/
__union(t, offset) {
t.bb_pos = offset + this.readInt32(offset);
t.bb = this;
return t;
}
/**
* Create a JavaScript string from UTF-8 data stored inside the FlatBuffer.
* This allocates a new string and converts to wide chars upon each access.
*
* To avoid the conversion to string, pass Encoding.UTF8_BYTES as the
* "optionalEncoding" argument. This is useful for avoiding conversion when
* the data will just be packaged back up in another FlatBuffer later on.
*
* @param offset
* @param opt_encoding Defaults to UTF16_STRING
*/
__string(offset, opt_encoding) {
offset += this.readInt32(offset);
const length = this.readInt32(offset);
offset += constants_js_1.SIZEOF_INT;
const utf8bytes = this.bytes_.subarray(offset, offset + length);
if (opt_encoding === encoding_js_1.Encoding.UTF8_BYTES)
return utf8bytes;
else
return this.text_decoder_.decode(utf8bytes);
}
/**
* Handle unions that can contain string as its member, if a Table-derived type then initialize it,
* if a string then return a new one
*
* WARNING: strings are immutable in JS so we can't change the string that the user gave us, this
* makes the behaviour of __union_with_string different compared to __union
*/
__union_with_string(o, offset) {
if (typeof o === "string") {
return this.__string(offset);
}
return this.__union(o, offset);
}
/**
* Retrieve the relative offset stored at "offset"
*/
__indirect(offset) {
return offset + this.readInt32(offset);
}
/**
* Get the start of data of a vector whose offset is stored at "offset" in this object.
*/
__vector(offset) {
return offset + this.readInt32(offset) + constants_js_1.SIZEOF_INT;
}
/**
* Get the length of a vector whose offset is stored at "offset" in this object.
*/
__vector_len(offset) {
return this.readInt32(offset + this.readInt32(offset));
}
__has_identifier(ident) {
if (ident.length != constants_js_1.FILE_IDENTIFIER_LENGTH) {
throw new Error("FlatBuffers: file identifier must be length " + constants_js_1.FILE_IDENTIFIER_LENGTH);
}
for (let i = 0; i < constants_js_1.FILE_IDENTIFIER_LENGTH; i++) {
if (ident.charCodeAt(i) != this.readInt8(this.position() + constants_js_1.SIZEOF_INT + i)) {
return false;
}
}
return true;
}
/**
* A helper function for generating list for obj api
*/
createScalarList(listAccessor, listLength) {
const ret = [];
for (let i = 0; i < listLength; ++i) {
const val = listAccessor(i);
if (val !== null) {
ret.push(val);
}
}
return ret;
}
/**
* A helper function for generating list for obj api
* @param listAccessor function that accepts an index and return data at that index
* @param listLength listLength
* @param res result list
*/
createObjList(listAccessor, listLength) {
const ret = [];
for (let i = 0; i < listLength; ++i) {
const val = listAccessor(i);
if (val !== null) {
ret.push(val.unpack());
}
}
return ret;
}
};
exports$1.ByteBuffer = ByteBuffer2;
}
});
// node_modules/flatbuffers/js/builder.js
var require_builder = __commonJS({
"node_modules/flatbuffers/js/builder.js"(exports$1) {
Object.defineProperty(exports$1, "__esModule", { value: true });
exports$1.Builder = void 0;
var byte_buffer_js_1 = require_byte_buffer();
var constants_js_1 = require_constants();
var Builder = class _Builder {
/**
* Create a FlatBufferBuilder.
*/
constructor(opt_initial_size) {
this.minalign = 1;
this.vtable = null;
this.vtable_in_use = 0;
this.isNested = false;
this.object_start = 0;
this.vtables = [];
this.vector_num_elems = 0;
this.force_defaults = false;
this.string_maps = null;
this.text_encoder = new TextEncoder();
let initial_size;
if (!opt_initial_size) {
initial_size = 1024;
} else {
initial_size = opt_initial_size;
}
this.bb = byte_buffer_js_1.ByteBuffer.allocate(initial_size);
this.space = initial_size;
}
clear() {
this.bb.clear();
this.space = this.bb.capacity();
this.minalign = 1;
this.vtable = null;
this.vtable_in_use = 0;
this.isNested = false;
this.object_start = 0;
this.vtables = [];
this.vector_num_elems = 0;
this.force_defaults = false;
this.string_maps = null;
}
/**
* In order to save space, fields that are set to their default value
* don't get serialized into the buffer. Forcing defaults provides a
* way to manually disable this optimization.
*
* @param forceDefaults true always serializes default values
*/
forceDefaults(forceDefaults) {
this.force_defaults = forceDefaults;
}
/**
* Get the ByteBuffer representing the FlatBuffer. Only call this after you've
* called finish(). The actual data starts at the ByteBuffer's current position,
* not necessarily at 0.
*/
dataBuffer() {
return this.bb;
}
/**
* Get the bytes representing the FlatBuffer. Only call this after you've
* called finish().
*/
asUint8Array() {
return this.bb.bytes().subarray(this.bb.position(), this.bb.position() + this.offset());
}
/**
* Prepare to write an element of `size` after `additional_bytes` have been
* written, e.g. if you write a string, you need to align such the int length
* field is aligned to 4 bytes, and the string data follows it directly. If all
* you need to do is alignment, `additional_bytes` will be 0.
*
* @param size This is the of the new element to write
* @param additional_bytes The padding size
*/
prep(size, additional_bytes) {
if (size > this.minalign) {
this.minalign = size;
}
const align_size = ~(this.bb.capacity() - this.space + additional_bytes) + 1 & size - 1;
while (this.space < align_size + size + additional_bytes) {
const old_buf_size = this.bb.capacity();
this.bb = _Builder.growByteBuffer(this.bb);
this.space += this.bb.capacity() - old_buf_size;
}
this.pad(align_size);
}
pad(byte_size) {
for (let i = 0; i < byte_size; i++) {
this.bb.writeInt8(--this.space, 0);
}
}
writeInt8(value) {
this.bb.writeInt8(this.space -= 1, value);
}
writeInt16(value) {
this.bb.writeInt16(this.space -= 2, value);
}
writeInt32(value) {
this.bb.writeInt32(this.space -= 4, value);
}
writeInt64(value) {
this.bb.writeInt64(this.space -= 8, value);
}
writeFloat32(value) {
this.bb.writeFloat32(this.space -= 4, value);
}
writeFloat64(value) {
this.bb.writeFloat64(this.space -= 8, value);
}
/**
* Add an `int8` to the buffer, properly aligned, and grows the buffer (if necessary).
* @param value The `int8` to add the buffer.
*/
addInt8(value) {
this.prep(1, 0);
this.writeInt8(value);
}
/**
* Add an `int16` to the buffer, properly aligned, and grows the buffer (if necessary).
* @param value The `int16` to add the buffer.
*/
addInt16(value) {
this.prep(2, 0);
this.writeInt16(value);
}
/**
* Add an `int32` to the buffer, properly aligned, and grows the buffer (if necessary).
* @param value The `int32` to add the buffer.
*/
addInt32(value) {
this.prep(4, 0);
this.writeInt32(value);
}
/**
* Add an `int64` to the buffer, properly aligned, and grows the buffer (if necessary).
* @param value The `int64` to add the buffer.
*/
addInt64(value) {
this.prep(8, 0);
this.writeInt64(value);
}
/**
* Add a `float32` to the buffer, properly aligned, and grows the buffer (if necessary).
* @param value The `float32` to add the buffer.
*/
addFloat32(value) {
this.prep(4, 0);
this.writeFloat32(value);
}
/**
* Add a `float64` to the buffer, properly aligned, and grows the buffer (if necessary).
* @param value The `float64` to add the buffer.
*/
addFloat64(value) {
this.prep(8, 0);
this.writeFloat64(value);
}
addFieldInt8(voffset, value, defaultValue) {
if (this.force_defaults || value != defaultValue) {
this.addInt8(value);
this.slot(voffset);
}
}
addFieldInt16(voffset, value, defaultValue) {
if (this.force_defaults || value != defaultValue) {
this.addInt16(value);
this.slot(voffset);
}
}
addFieldInt32(voffset, value, defaultValue) {
if (this.force_defaults || value != defaultValue) {
this.addInt32(value);
this.slot(voffset);
}
}
addFieldInt64(voffset, value, defaultValue) {
if (this.force_defaults || value !== defaultValue) {
this.addInt64(value);
this.slot(voffset);
}
}
addFieldFloat32(voffset, value, defaultValue) {
if (this.force_defaults || value != defaultValue) {
this.addFloat32(value);
this.slot(voffset);
}
}
addFieldFloat64(voffset, value, defaultValue) {
if (this.force_defaults || value != defaultValue) {
this.addFloat64(value);
this.slot(voffset);
}
}
addFieldOffset(voffset, value, defaultValue) {
if (this.force_defaults || value != defaultValue) {
this.addOffset(value);
this.slot(voffset);
}
}
/**
* Structs are stored inline, so nothing additional is being added. `d` is always 0.
*/
addFieldStruct(voffset, value, defaultValue) {
if (value != defaultValue) {
this.nested(value);
this.slot(voffset);
}
}
/**
* Structures are always stored inline, they need to be created right
* where they're used. You'll get this assertion failure if you
* created it elsewhere.
*/
nested(obj) {
if (obj != this.offset()) {
throw new TypeError("FlatBuffers: struct must be serialized inline.");
}
}
/**
* Should not be creating any other object, string or vector
* while an object is being constructed
*/
notNested() {
if (this.isNested) {
throw new TypeError("FlatBuffers: object serialization must not be nested.");
}
}
/**
* Set the current vtable at `voffset` to the current location in the buffer.
*/
slot(voffset) {
if (this.vtable !== null)
this.vtable[voffset] = this.offset();
}
/**
* @returns Offset relative to the end of the buffer.
*/
offset() {
return this.bb.capacity() - this.space;
}
/**
* Doubles the size of the backing ByteBuffer and copies the old data towards
* the end of the new buffer (since we build the buffer backwards).
*
* @param bb The current buffer with the existing data
* @returns A new byte buffer with the old data copied
* to it. The data is located at the end of the buffer.
*
* uint8Array.set() formally takes {Array<number>|ArrayBufferView}, so to pass
* it a uint8Array we need to suppress the type check:
* @suppress {checkTypes}
*/
static growByteBuffer(bb) {
const old_buf_size = bb.capacity();
if (old_buf_size & 3221225472) {
throw new Error("FlatBuffers: cannot grow buffer beyond 2 gigabytes.");
}
const new_buf_size = old_buf_size << 1;
const nbb = byte_buffer_js_1.ByteBuffer.allocate(new_buf_size);
nbb.setPosition(new_buf_size - old_buf_size);
nbb.bytes().set(bb.bytes(), new_buf_size - old_buf_size);
return nbb;
}
/**
* Adds on offset, relative to where it will be written.
*
* @param offset The offset to add.
*/
addOffset(offset) {
this.prep(constants_js_1.SIZEOF_INT, 0);
this.writeInt32(this.offset() - offset + constants_js_1.SIZEOF_INT);
}
/**
* Start encoding a new object in the buffer. Users will not usually need to
* call this directly. The FlatBuffers compiler will generate helper methods
* that call this method internally.
*/
startObject(numfields) {
this.notNested();
if (this.vtable == null) {
this.vtable = [];
}
this.vtable_in_use = numfields;
for (let i = 0; i < numfields; i++) {
this.vtable[i] = 0;
}
this.isNested = true;
this.object_start = this.offset();
}
/**
* Finish off writing the object that is under construction.
*
* @returns The offset to the object inside `dataBuffer`
*/
endObject() {
if (this.vtable == null || !this.isNested) {
throw new Error("FlatBuffers: endObject called without startObject");
}
this.addInt32(0);
const vtableloc = this.offset();
let i = this.vtable_in_use - 1;
for (; i >= 0 && this.vtable[i] == 0; i--) {
}
const trimmed_size = i + 1;
for (; i >= 0; i--) {
this.addInt16(this.vtable[i] != 0 ? vtableloc - this.vtable[i] : 0);
}
const standard_fields = 2;
this.addInt16(vtableloc - this.object_start);
const len = (trimmed_size + standard_fields) * constants_js_1.SIZEOF_SHORT;
this.addInt16(len);
let existing_vtable = 0;
const vt1 = this.space;
outer_loop: for (i = 0; i < this.vtables.length; i++) {
const vt2 = this.bb.capacity() - this.vtables[i];
if (len == this.bb.readInt16(vt2)) {
for (let j = constants_js_1.SIZEOF_SHORT; j < len; j += constants_js_1.SIZEOF_SHORT) {
if (this.bb.readInt16(vt1 + j) != this.bb.readInt16(vt2 + j)) {
continue outer_loop;
}
}
existing_vtable = this.vtables[i];
break;
}
}
if (existing_vtable) {
this.space = this.bb.capacity() - vtableloc;
this.bb.writeInt32(this.space, existing_vtable - vtableloc);
} else {
this.vtables.push(this.offset());
this.bb.writeInt32(this.bb.capacity() - vtableloc, this.offset() - vtableloc);
}
this.isNested = false;
return vtableloc;
}
/**
* Finalize a buffer, poiting to the given `root_table`.
*/
finish(root_table, opt_file_identifier, opt_size_prefix) {
const size_prefix = opt_size_prefix ? constants_js_1.SIZE_PREFIX_LENGTH : 0;
if (opt_file_identifier) {
const file_identifier = opt_file_identifier;
this.prep(this.minalign, constants_js_1.SIZEOF_INT + constants_js_1.FILE_IDENTIFIER_LENGTH + size_prefix);
if (file_identifier.length != constants_js_1.FILE_IDENTIFIER_LENGTH) {
throw new TypeError("FlatBuffers: file identifier must be length " + constants_js_1.FILE_IDENTIFIER_LENGTH);
}
for (let i = constants_js_1.FILE_IDENTIFIER_LENGTH - 1; i >= 0; i--) {
this.writeInt8(file_identifier.charCodeAt(i));
}
}
this.prep(this.minalign, constants_js_1.SIZEOF_INT + size_prefix);
this.addOffset(root_table);
if (size_prefix) {
this.addInt32(this.bb.capacity() - this.space);
}
this.bb.setPosition(this.space);
}
/**
* Finalize a size prefixed buffer, pointing to the given `root_table`.
*/
finishSizePrefixed(root_table, opt_file_identifier) {
this.finish(root_table, opt_file_identifier, true);
}
/**
* This checks a required field has been set in a given table that has
* just been constructed.
*/
requiredField(table, field) {
const table_start = this.bb.capacity() - table;
const vtable_start = table_start - this.bb.readInt32(table_start);
const ok = field < this.bb.readInt16(vtable_start) && this.bb.readInt16(vtable_start + field) != 0;
if (!ok) {
throw new TypeError("FlatBuffers: field " + field + " must be set");
}
}
/**
* Start a new array/vector of objects. Users usually will not call
* this directly. The FlatBuffers compiler will create a start/end
* method for vector types in generated code.
*
* @param elem_size The size of each element in the array
* @param num_elems The number of elements in the array
* @param alignment The alignment of the array
*/
startVector(elem_size, num_elems, alignment) {
this.notNested();
this.vector_num_elems = num_elems;
this.prep(constants_js_1.SIZEOF_INT, elem_size * num_elems);
this.prep(alignment, elem_size * num_elems);
}
/**
* Finish off the creation of an array and all its elements. The array must be
* created with `startVector`.
*
* @returns The offset at which the newly created array
* starts.
*/
endVector() {
this.writeInt32(this.vector_num_elems);
return this.offset();
}
/**
* Encode the string `s` in the buffer using UTF-8. If the string passed has
* already been seen, we return the offset of the already written string
*
* @param s The string to encode
* @return The offset in the buffer where the encoded string starts
*/
createSharedString(s) {
if (!s) {
return 0;
}
if (!this.string_maps) {
this.string_maps = /* @__PURE__ */ new Map();
}
if (this.string_maps.has(s)) {
return this.string_maps.get(s);
}
const offset = this.createString(s);
this.string_maps.set(s, offset);
return offset;
}
/**
* Encode the string `s` in the buffer using UTF-8. If a Uint8Array is passed
* instead of a string, it is assumed to contain valid UTF-8 encoded data.
*
* @param s The string to encode
* @return The offset in the buffer where the encoded string starts
*/
createString(s) {
if (s === null || s === void 0) {
return 0;
}
let utf8;
if (s instanceof Uint8Array) {
utf8 = s;
} else {
utf8 = this.text_encoder.encode(s);
}
this.addInt8(0);
this.startVector(1, utf8.length, 1);
this.bb.setPosition(this.space -= utf8.length);
this.bb.bytes().set(utf8, this.space);
return this.endVector();
}
/**
* Create a byte vector.
*
* @param v The bytes to add
* @returns The offset in the buffer where the byte vector starts
*/
createByteVector(v) {
if (v === null || v === void 0) {
return 0;
}
this.startVector(1, v.length, 1);
this.bb.setPosition(this.space -= v.length);
this.bb.bytes().set(v, this.space);
return this.endVector();
}
/**
* A helper function to pack an object
*
* @returns offset of obj
*/
createObjectOffset(obj) {
if (obj === null) {
return 0;
}
if (typeof obj === "string") {
return this.createString(obj);
} else {
return obj.pack(this);
}
}
/**
* A helper function to pack a list of object
*
* @returns list of offsets of each non null object
*/
createObjectOffsetList(list) {
const ret = [];
for (let i = 0; i < list.length; ++i) {
const val = list[i];
if (val !== null) {
ret.push(this.createObjectOffset(val));
} else {
throw new TypeError("FlatBuffers: Argument for createObjectOffsetList cannot contain null.");
}
}
return ret;
}
createStructOffsetList(list, startFunc) {
startFunc(this, list.length);
this.createObjectOffsetList(list.slice().reverse());
return this.endVector();
}
};
exports$1.Builder = Builder;
}
});
// node_modules/flatbuffers/js/flatbuffers.js
var require_flatbuffers = __commonJS({
"node_modules/flatbuffers/js/flatbuffers.js"(exports$1) {
Object.defineProperty(exports$1, "__esModule", { value: true });
exports$1.Encoding = exports$1.ByteBuffer = exports$1.Builder = exports$1.isLittleEndian = exports$1.int32 = exports$1.float64 = exports$1.float32 = exports$1.SIZE_PREFIX_LENGTH = exports$1.SIZEOF_SHORT = exports$1.SIZEOF_INT = exports$1.FILE_IDENTIFIER_LENGTH = void 0;
var constants_js_1 = require_constants();
Object.defineProperty(exports$1, "FILE_IDENTIFIER_LENGTH", { enumerable: true, get: function() {
return constants_js_1.FILE_IDENTIFIER_LENGTH;
} });
Object.defineProperty(exports$1, "SIZEOF_INT", { enumerable: true, get: function() {
return constants_js_1.SIZEOF_INT;
} });
Object.defineProperty(exports$1, "SIZEOF_SHORT", { enumerable: true, get: function() {
return constants_js_1.SIZEOF_SHORT;
} });
Object.defineProperty(exports$1, "SIZE_PREFIX_LENGTH", { enumerable: true, get: function() {
return constants_js_1.SIZE_PREFIX_LENGTH;
} });
var utils_js_1 = require_utils();
Object.defineProperty(exports$1, "float32", { enumerable: true, get: function() {
return utils_js_1.float32;
} });
Object.defineProperty(exports$1, "float64", { enumerable: true, get: function() {
return utils_js_1.float64;
} });
Object.defineProperty(exports$1, "int32", { enumerable: true, get: function() {
return utils_js_1.int32;
} });
Object.defineProperty(exports$1, "isLittleEndian", { enumerable: true, get: function() {
return utils_js_1.isLittleEndian;
} });
var builder_js_1 = require_builder();
Object.defineProperty(exports$1, "Builder", { enumerable: true, get: function() {
return builder_js_1.Builder;
} });
var byte_buffer_js_1 = require_byte_buffer();
Object.defineProperty(exports$1, "ByteBuffer", { enumerable: true, get: function() {
return byte_buffer_js_1.ByteBuffer;
} });
var encoding_js_1 = require_encoding();
Object.defineProperty(exports$1, "Encoding", { enumerable: true, get: function() {
return encoding_js_1.Encoding;
} });
}
});
// node_modules/exifr/dist/full.umd.js
var require_full_umd = __commonJS({
"node_modules/exifr/dist/full.umd.js"(exports$1, module) {
!(function(e, t) {
"object" == typeof exports$1 && "undefined" != typeof module ? t(exports$1) : "function" == typeof define && define.amd ? define("exifr", ["exports"], t) : t((e = "undefined" != typeof globalThis ? globalThis : e || self).exifr = {});
})(exports$1, (function(e) {
var t = "undefined" != typeof self ? self : global;
const i = "undefined" != typeof navigator, n = i && "undefined" == typeof HTMLImageElement, s = !("undefined" == typeof global || "undefined" == typeof process || !process.versions || !process.versions.node), r = t.Buffer, a = t.BigInt, o = !!r, l = (e2) => e2;
function h(e2, t2 = l) {
if (s) try {
return "function" == typeof __require ? Promise.resolve(t2(__require(e2))) : import(
/* webpackIgnore: true */
e2
).then(t2);
} catch (t3) {
console.warn(`Couldn't load ${e2}`);
}
}
let u = t.fetch;
const c = (e2) => u = e2;
if (!t.fetch) {
const e2 = h("http", ((e3) => e3)), t2 = h("https", ((e3) => e3)), i2 = (n2, { headers: s2 } = {}) => new Promise((async (r2, a2) => {
let { port: o2, hostname: l2, pathname: h2, protocol: u2, search: c2 } = new URL(n2);
const f2 = { method: "GET", hostname: l2, path: encodeURI(h2) + c2, headers: s2 };
"" !== o2 && (f2.port = Number(o2));
const d2 = ("https:" === u2 ? await t2 : await e2).request(f2, ((e3) => {
if (301 === e3.statusCode || 302 === e3.statusCode) {
let t3 = new URL(e3.headers.location, n2).toString();
return i2(t3, { headers: s2 }).then(r2).catch(a2);
}
r2({ status: e3.statusCode, arrayBuffer: () => new Promise(((t3) => {
let i3 = [];
e3.on("data", ((e4) => i3.push(e4))), e3.on("end", (() => t3(Buffer.concat(i3))));
})) });
}));
d2.on("error", a2), d2.end();
}));
c(i2);
}
function f(e2, t2, i2) {
return t2 in e2 ? Object.defineProperty(e2, t2, { value: i2, enumerable: true, configurable: true, writable: true }) : e2[t2] = i2, e2;
}
const d = (e2) => g(e2) ? void 0 : e2, p = (e2) => void 0 !== e2;
function g(e2) {
return void 0 === e2 || (e2 instanceof Map ? 0 === e2.size : 0 === Object.values(e2).filter(p).length);
}
function m(e2) {
let t2 = new Error(e2);
throw delete t2.stack, t2;
}
function S(e2) {
return "" === (e2 = (function(e3) {
for (; e3.endsWith("\0"); ) e3 = e3.slice(0, -1);
return e3;
})(e2).trim()) ? void 0 : e2;
}
function C(e2) {
let t2 = (function(e3) {
let t3 = 0;
return e3.ifd0.enabled && (t3 += 1024), e3.exif.enabled && (t3 += 2048), e3.makerNote && (t3 += 2048), e3.userComment && (t3 += 1024), e3.gps.enabled && (t3 += 512), e3.interop.enabled && (t3 += 100), e3.ifd1.enabled && (t3 += 1024), t3 + 2048;
})(e2);
return e2.jfif.enabled && (t2 += 50), e2.xmp.enabled && (t2 += 2e4), e2.iptc.enabled && (t2 += 14e3), e2.icc.enabled && (t2 += 6e3), t2;
}
const y = (e2) => String.fromCharCode.apply(null, e2), b = "undefined" != typeof TextDecoder ? new TextDecoder("utf-8") : void 0;
function P(e2) {
return b ? b.decode(e2) : o ? Buffer.from(e2).toString("utf8") : decodeURIComponent(escape(y(e2)));
}
class I {
static from(e2, t2) {
return e2 instanceof this && e2.le === t2 ? e2 : new I(e2, void 0, void 0, t2);
}
constructor(e2, t2 = 0, i2, n2) {
if ("boolean" == typeof n2 && (this.le = n2), Array.isArray(e2) && (e2 = new Uint8Array(e2)), 0 === e2) this.byteOffset = 0, this.byteLength = 0;
else if (e2 instanceof ArrayBuffer) {
void 0 === i2 && (i2 = e2.byteLength - t2);
let n3 = new DataView(e2, t2, i2);
this._swapDataView(n3);
} else if (e2 instanceof Uint8Array || e2 instanceof DataView || e2 instanceof I) {
void 0 === i2 && (i2 = e2.byteLength - t2), (t2 += e2.byteOffset) + i2 > e2.byteOffset + e2.byteLength && m("Creating view outside of available memory in ArrayBuffer");
let n3 = new DataView(e2.buffer, t2, i2);
this._swapDataView(n3);
} else if ("number" == typeof e2) {
let t3 = new DataView(new ArrayBuffer(e2));
this._swapDataView(t3);
} else m("Invalid input argument for BufferView: " + e2);
}
_swapArrayBuffer(e2) {
this._swapDataView(new DataView(e2));
}
_swapBuffer(e2) {
this._swapDataView(new DataView(e2.buffer, e2.byteOffset, e2.byteLength));
}
_swapDataView(e2) {
this.dataView = e2, this.buffer = e2.buffer, this.byteOffset = e2.byteOffset, this.byteLength = e2.byteLength;
}
_lengthToEnd(e2) {
return this.byteLength - e2;
}
set(e2, t2, i2 = I) {
return e2 instanceof DataView || e2 instanceof I ? e2 = new Uint8Array(e2.buffer, e2.byteOffset, e2.byteLength) : e2 instanceof ArrayBuffer && (e2 = new Uint8Array(e2)), e2 instanceof Uint8Array || m("BufferView.set(): Invalid data argument."), this.toUint8().set(e2, t2), new i2(this, t2, e2.byteLength);
}
subarray(e2, t2) {
return t2 = t2 || this._lengthToEnd(e2), new I(this, e2, t2);
}
toUint8() {
return new Uint8Array(this.buffer, this.byteOffset, this.byteLength);
}
getUint8Array(e2, t2) {
return new Uint8Array(this.buffer, this.byteOffset + e2, t2);
}
getString(e2 = 0, t2 = this.byteLength) {
return P(this.getUint8Array(e2, t2));
}
getLatin1String(e2 = 0, t2 = this.byteLength) {
let i2 = this.getUint8Array(e2, t2);
return y(i2);
}
getUnicodeString(e2 = 0, t2 = this.byteLength) {
const i2 = [];
for (let n2 = 0; n2 < t2 && e2 + n2 < this.byteLength; n2 += 2) i2.push(this.getUint16(e2 + n2));
return y(i2);
}
getInt8(e2) {
return this.dataView.getInt8(e2);
}
getUint8(e2) {
return this.dataView.getUint8(e2);
}
getInt16(e2, t2 = this.le) {
return this.dataView.getInt16(e2, t2);
}
getInt32(e2, t2 = this.le) {
return this.dataView.getInt32(e2, t2);
}
getUint16(e2, t2 = this.le) {
return this.dataView.getUint16(e2, t2);
}
getUint32(e2, t2 = this.le) {
return this.dataView.getUint32(e2, t2);
}
getFloat32(e2, t2 = this.le) {
return this.dataView.getFloat32(e2, t2);
}
getFloat64(e2, t2 = this.le) {
return this.dataView.getFloat64(e2, t2);
}
getFloat(e2, t2 = this.le) {
return this.dataView.getFloat32(e2, t2);
}
getDouble(e2, t2 = this.le) {
return this.dataView.getFloat64(e2, t2);
}
getUintBytes(e2, t2, i2) {
switch (t2) {
case 1:
return this.getUint8(e2, i2);
case 2:
return this.getUint16(e2, i2);
case 4:
return this.getUint32(e2, i2);
case 8:
return this.getUint64 && this.getUint64(e2, i2);
}
}
getUint(e2, t2, i2) {
switch (t2) {
case 8:
return this.getUint8(e2, i2);
case 16:
return this.getUint16(e2, i2);
case 32:
return this.getUint32(e2, i2);
case 64:
return this.getUint64 && this.getUint64(e2, i2);
}
}
toString(e2) {
return this.dataView.toString(e2, this.constructor.name);
}
ensureChunk() {
}
}
function k(e2, t2) {
m(`${e2} '${t2}' was not loaded, try using full build of exifr.`);
}
class w extends Map {
constructor(e2) {
super(), this.kind = e2;
}
get(e2, t2) {
return this.has(e2) || k(this.kind, e2), t2 && (e2 in t2 || (function(e3, t3) {
m(`Unknown ${e3} '${t3}'.`);
})(this.kind, e2), t2[e2].enabled || k(this.kind, e2)), super.get(e2);
}
keyList() {
return Array.from(this.keys());
}
}
var T = new w("file parser"), A = new w("segment parser"), D = new w("file reader");
const O = "Invalid input argument";
function x(e2, t2) {
return "string" == typeof e2 ? v(e2, t2) : i && !n && e2 instanceof HTMLImageElement ? v(e2.src, t2) : e2 instanceof Uint8Array || e2 instanceof ArrayBuffer || e2 instanceof DataView ? new I(e2) : i && e2 instanceof Blob ? M(e2, t2, "blob", U) : void m(O);
}
function v(e2, t2) {
return (n2 = e2).startsWith("data:") || n2.length > 1e4 ? R(e2, t2, "base64") : s && e2.includes("://") ? M(e2, t2, "url", L) : s ? R(e2, t2, "fs") : i ? M(e2, t2, "url", L) : void m(O);
var n2;
}
async function M(e2, t2, i2, n2) {
return D.has(i2) ? R(e2, t2, i2) : n2 ? (async function(e3, t3) {
let i3 = await t3(e3);
return new I(i3);
})(e2, n2) : void m(`Parser ${i2} is not loaded`);
}
async function R(e2, t2, i2) {
let n2 = new (D.get(i2))(e2, t2);
return await n2.read(), n2;
}
const L = (e2) => u(e2).then(((e3) => e3.arrayBuffer())), U = (e2) => new Promise(((t2, i2) => {
let n2 = new FileReader();
n2.onloadend = () => t2(n2.result || new ArrayBuffer()), n2.onerror = i2, n2.readAsArrayBuffer(e2);
}));
class F extends Map {
get tagKeys() {
return this.allKeys || (this.allKeys = Array.from(this.keys())), this.allKeys;
}
get tagValues() {
return this.allValues || (this.allValues = Array.from(this.values())), this.allValues;
}
}
function B(e2, t2, i2) {
let n2 = new F();
for (let [e3, t3] of i2) n2.set(e3, t3);
if (Array.isArray(t2)) for (let i3 of t2) e2.set(i3, n2);
else e2.set(t2, n2);
return n2;
}
function E(e2, t2, i2) {
let n2, s2 = e2.get(t2);
for (n2 of i2) s2.set(n2[0], n2[1]);
}
const N = /* @__PURE__ */ new Map(), G = /* @__PURE__ */ new Map(), V = /* @__PURE__ */ new Map(), z4 = 37500, H = 37510, j = 700, W = 33723, K = 34675, X = 34665, _ = 34853, Y = 40965, $ = ["chunked", "firstChunkSize", "firstChunkSizeNode", "firstChunkSizeBrowser", "chunkSize", "chunkLimit"], J = ["jfif", "xmp", "icc", "iptc", "ihdr"], q = ["tiff", ...J], Q = ["ifd0", "ifd1", "exif", "gps", "interop"], Z = [...q, ...Q], ee = ["makerNote", "userComment"], te = ["translateKeys", "translateValues", "reviveValues", "multiSegment"], ie = [...te, "sanitize", "mergeOutput", "silentErrors"];
class ne {
get translate() {
return this.translateKeys || this.translateValues || this.reviveValues;
}
}
class se extends ne {
get needed() {
return this.enabled || this.deps.size > 0;
}
constructor(e2, t2, i2, n2) {
if (super(), f(this, "enabled", false), f(this, "skip", /* @__PURE__ */ new Set()), f(this, "pick", /* @__PURE__ */ new Set()), f(this, "deps", /* @__PURE__ */ new Set()), f(this, "translateKeys", false), f(this, "translateValues", false), f(this, "reviveValues", false), this.key = e2, this.enabled = t2, this.parse = this.enabled, this.applyInheritables(n2), this.canBeFiltered = Q.includes(e2), this.canBeFiltered && (this.dict = N.get(e2)), void 0 !== i2) if (Array.isArray(i2)) this.parse = this.enabled = true, this.canBeFiltered && i2.length > 0 && this.translateTagSet(i2, this.pick);
else if ("object" == typeof i2) {
if (this.enabled = true, this.parse = false !== i2.parse, this.canBeFiltered) {
let { pick: e3, skip: t3 } = i2;
e3 && e3.length > 0 && this.translateTagSet(e3, this.pick), t3 && t3.length > 0 && this.translateTagSet(t3, this.skip);
}
this.applyInheritables(i2);
} else true === i2 || false === i2 ? this.parse = this.enabled = i2 : m(`Invalid options argument: ${i2}`);
}
applyInheritables(e2) {
let t2, i2;
for (t2 of te) i2 = e2[t2], void 0 !== i2 && (this[t2] = i2);
}
translateTagSet(e2, t2) {
if (this.dict) {
let i2, n2, { tagKeys: s2, tagValues: r2 } = this.dict;
for (i2 of e2) "string" == typeof i2 ? (n2 = r2.indexOf(i2), -1 === n2 && (n2 = s2.indexOf(Number(i2))), -1 !== n2 && t2.add(Number(s2[n2]))) : t2.add(i2);
} else for (let i2 of e2) t2.add(i2);
}
finalizeFilters() {
!this.enabled && this.deps.size > 0 ? (this.enabled = true, ue(this.pick, this.deps)) : this.enabled && this.pick.size > 0 && ue(this.pick, this.deps);
}
}
var re = { jfif: false, tiff: true, xmp: false, icc: false, iptc: false, ifd0: true, ifd1: false, exif: true, gps: true, interop: false, ihdr: void 0, makerNote: false, userComment: false, multiSegment: false, skip: [], pick: [], translateKeys: true, translateValues: true, reviveValues: true, sanitize: true, mergeOutput: true, silentErrors: true, chunked: true, firstChunkSize: void 0, firstChunkSizeNode: 512, firstChunkSizeBrowser: 65536, chunkSize: 65536, chunkLimit: 5 }, ae = /* @__PURE__ */ new Map();
class oe extends ne {
static useCached(e2) {
let t2 = ae.get(e2);
return void 0 !== t2 || (t2 = new this(e2), ae.set(e2, t2)), t2;
}
constructor(e2) {
super(), true === e2 ? this.setupFromTrue() : void 0 === e2 ? this.setupFromUndefined() : Array.isArray(e2) ? this.setupFromArray(e2) : "object" == typeof e2 ? this.setupFromObject(e2) : m(`Invalid options argument ${e2}`), void 0 === this.firstChunkSize && (this.firstChunkSize = i ? this.firstChunkSizeBrowser : this.firstChunkSizeNode), this.mergeOutput && (this.ifd1.enabled = false), this.filterNestedSegmentTags(), this.traverseTiffDependencyTree(), this.checkLoadedPlugins();
}
setupFromUndefined() {
let e2;
for (e2 of $) this[e2] = re[e2];
for (e2 of ie) this[e2] = re[e2];
for (e2 of ee) this[e2] = re[e2];
for (e2 of Z) this[e2] = new se(e2, re[e2], void 0, this);
}
setupFromTrue() {
let e2;
for (e2 of $) this[e2] = re[e2];
for (e2 of ie) this[e2] = re[e2];
for (e2 of ee) this[e2] = true;
for (e2 of Z) this[e2] = new se(e2, true, void 0, this);
}
setupFromArray(e2) {
let t2;
for (t2 of $) this[t2] = re[t2];
for (t2 of ie) this[t2] = re[t2];
for (t2 of ee) this[t2] = re[t2];
for (t2 of Z) this[t2] = new se(t2, false, void 0, this);
this.setupGlobalFilters(e2, void 0, Q);
}
setupFromObject(e2) {
let t2;
for (t2 of (Q.ifd0 = Q.ifd0 || Q.image, Q.ifd1 = Q.ifd1 || Q.thumbnail, Object.assign(this, e2), $)) this[t2] = he(e2[t2], re[t2]);
for (t2 of ie) this[t2] = he(e2[t2], re[t2]);
for (t2 of ee) this[t2] = he(e2[t2], re[t2]);
for (t2 of q) this[t2] = new se(t2, re[t2], e2[t2], this);
for (t2 of Q) this[t2] = new se(t2, re[t2], e2[t2], this.tiff);
this.setupGlobalFilters(e2.pick, e2.skip, Q, Z), true === e2.tiff ? this.batchEnableWithBool(Q, true) : false === e2.tiff ? this.batchEnableWithUserValue(Q, e2) : Array.isArray(e2.tiff) ? this.setupGlobalFilters(e2.tiff, void 0, Q) : "object" == typeof e2.tiff && this.setupGlobalFilters(e2.tiff.pick, e2.tiff.skip, Q);
}
batchEnableWithBool(e2, t2) {
for (let i2 of e2) this[i2].enabled = t2;
}
batchEnableWithUserValue(e2, t2) {
for (let i2 of e2) {
let e3 = t2[i2];
this[i2].enabled = false !== e3 && void 0 !== e3;
}
}
setupGlobalFilters(e2, t2, i2, n2 = i2) {
if (e2 && e2.length) {
for (let e3 of n2) this[e3].enabled = false;
let t3 = le(e2, i2);
for (let [e3, i3] of t3) ue(this[e3].pick, i3), this[e3].enabled = true;
} else if (t2 && t2.length) {
let e3 = le(t2, i2);
for (let [t3, i3] of e3) ue(this[t3].skip, i3);
}
}
filterNestedSegmentTags() {
let { ifd0: e2, exif: t2, xmp: i2, iptc: n2, icc: s2 } = this;
this.makerNote ? t2.deps.add(z4) : t2.skip.add(z4), this.userComment ? t2.deps.add(H) : t2.skip.add(H), i2.enabled || e2.skip.add(j), n2.enabled || e2.skip.add(W), s2.enabled || e2.skip.add(K);
}
traverseTiffDependencyTree() {
let { ifd0: e2, exif: t2, gps: i2, interop: n2 } = this;
n2.needed && (t2.deps.add(Y), e2.deps.add(Y)), t2.needed && e2.deps.add(X), i2.needed && e2.deps.add(_), this.tiff.enabled = Q.some(((e3) => true === this[e3].enabled)) || this.makerNote || this.userComment;
for (let e3 of Q) this[e3].finalizeFilters();
}
get onlyTiff() {
return !J.map(((e2) => this[e2].enabled)).some(((e2) => true === e2)) && this.tiff.enabled;
}
checkLoadedPlugins() {
for (let e2 of q) this[e2].enabled && !A.has(e2) && k("segment parser", e2);
}
}
function le(e2, t2) {
let i2, n2, s2, r2, a2 = [];
for (s2 of t2) {
for (r2 of (i2 = N.get(s2), n2 = [], i2)) (e2.includes(r2[0]) || e2.includes(r2[1])) && n2.push(r2[0]);
n2.length && a2.push([s2, n2]);
}
return a2;
}
function he(e2, t2) {
return void 0 !== e2 ? e2 : void 0 !== t2 ? t2 : void 0;
}
function ue(e2, t2) {
for (let i2 of t2) e2.add(i2);
}
f(oe, "default", re);
class ce {
constructor(e2) {
f(this, "parsers", {}), f(this, "output", {}), f(this, "errors", []), f(this, "pushToErrors", ((e3) => this.errors.push(e3))), this.options = oe.useCached(e2);
}
async read(e2) {
this.file = await x(e2, this.options);
}
setup() {
if (this.fileParser) return;
let { file: e2 } = this, t2 = e2.getUint16(0);
for (let [i2, n2] of T) if (n2.canHandle(e2, t2)) return this.fileParser = new n2(this.options, this.file, this.parsers), e2[i2] = true;
this.file.close && this.file.close(), m("Unknown file format");
}
async parse() {
let { output: e2, errors: t2 } = this;
return this.setup(), this.options.silentErrors ? (await this.executeParsers().catch(this.pushToErrors), t2.push(...this.fileParser.errors)) : await this.executeParsers(), this.file.close && this.file.close(), this.options.silentErrors && t2.length > 0 && (e2.errors = t2), d(e2);
}
async executeParsers() {
let { output: e2 } = this;
await this.fileParser.parse();
let t2 = Object.values(this.parsers).map((async (t3) => {
let i2 = await t3.parse();
t3.assignToOutput(e2, i2);
}));
this.options.silentErrors && (t2 = t2.map(((e3) => e3.catch(this.pushToErrors)))), await Promise.all(t2);
}
async extractThumbnail() {
this.setup();
let { options: e2, file: t2 } = this, i2 = A.get("tiff", e2);
var n2;
if (t2.tiff ? n2 = { start: 0, type: "tiff" } : t2.jpeg && (n2 = await this.fileParser.getOrFindSegment("tiff")), void 0 === n2) return;
let s2 = await this.fileParser.ensureSegmentChunk(n2), r2 = this.parsers.tiff = new i2(s2, e2, t2), a2 = await r2.extractThumbnail();
return t2.close && t2.close(), a2;
}
}
async function fe(e2, t2) {
let i2 = new ce(t2);
return await i2.read(e2), i2.parse();
}
var de = Object.freeze({ __proto__: null, parse: fe, Exifr: ce, fileParsers: T, segmentParsers: A, fileReaders: D, tagKeys: N, tagValues: G, tagRevivers: V, createDictionary: B, extendDictionary: E, fetchUrlAsArrayBuffer: L, readBlobAsArrayBuffer: U, chunkedProps: $, otherSegments: J, segments: q, tiffBlocks: Q, segmentsAndBlocks: Z, tiffExtractables: ee, inheritables: te, allFormatters: ie, Options: oe });
class pe {
constructor(e2, t2, i2) {
f(this, "errors", []), f(this, "ensureSegmentChunk", (async (e3) => {
let t3 = e3.start, i3 = e3.size || 65536;
if (this.file.chunked) if (this.file.available(t3, i3)) e3.chunk = this.file.subarray(t3, i3);
else try {
e3.chunk = await this.file.readChunk(t3, i3);
} catch (t4) {
m(`Couldn't read segment: ${JSON.stringify(e3)}. ${t4.message}`);
}
else this.file.byteLength > t3 + i3 ? e3.chunk = this.file.subarray(t3, i3) : void 0 === e3.size ? e3.chunk = this.file.subarray(t3) : m("Segment unreachable: " + JSON.stringify(e3));
return e3.chunk;
})), this.extendOptions && this.extendOptions(e2), this.options = e2, this.file = t2, this.parsers = i2;
}
injectSegment(e2, t2) {
this.options[e2].enabled && this.createParser(e2, t2);
}
createParser(e2, t2) {
let i2 = new (A.get(e2))(t2, this.options, this.file);
return this.parsers[e2] = i2;
}
createParsers(e2) {
for (let t2 of e2) {
let { type: e3, chunk: i2 } = t2, n2 = this.options[e3];
if (n2 && n2.enabled) {
let t3 = this.parsers[e3];
t3 && t3.append || t3 || this.createParser(e3, i2);
}
}
}
async readSegments(e2) {
let t2 = e2.map(this.ensureSegmentChunk);
await Promise.all(t2);
}
}
class ge {
static findPosition(e2, t2) {
let i2 = e2.getUint16(t2 + 2) + 2, n2 = "function" == typeof this.headerLength ? this.headerLength(e2, t2, i2) : this.headerLength, s2 = t2 + n2, r2 = i2 - n2;
return { offset: t2, length: i2, headerLength: n2, start: s2, size: r2, end: s2 + r2 };
}
static parse(e2, t2 = {}) {
return new this(e2, new oe({ [this.type]: t2 }), e2).parse();
}
normalizeInput(e2) {
return e2 instanceof I ? e2 : new I(e2);
}
constructor(e2, t2 = {}, i2) {
f(this, "errors", []), f(this, "raw", /* @__PURE__ */ new Map()), f(this, "handleError", ((e3) => {
if (!this.options.silentErrors) throw e3;
this.errors.push(e3.message);
})), this.chunk = this.normalizeInput(e2), this.file = i2, this.type = this.constructor.type, this.globalOptions = this.options = t2, this.localOptions = t2[this.type], this.canTranslate = this.localOptions && this.localOptions.translate;
}
translate() {
this.canTranslate && (this.translated = this.translateBlock(this.raw, this.type));
}
get output() {
return this.translated ? this.translated : this.raw ? Object.fromEntries(this.raw) : void 0;
}
translateBlock(e2, t2) {
let i2 = V.get(t2), n2 = G.get(t2), s2 = N.get(t2), r2 = this.options[t2], a2 = r2.reviveValues && !!i2, o2 = r2.translateValues && !!n2, l2 = r2.translateKeys && !!s2, h2 = {};
for (let [t3, r3] of e2) a2 && i2.has(t3) ? r3 = i2.get(t3)(r3) : o2 && n2.has(t3) && (r3 = this.translateValue(r3, n2.get(t3))), l2 && s2.has(t3) && (t3 = s2.get(t3) || t3), h2[t3] = r3;
return h2;
}
translateValue(e2, t2) {
return t2[e2] || t2.DEFAULT || e2;
}
assignToOutput(e2, t2) {
this.assignObjectToOutput(e2, this.constructor.type, t2);
}
assignObjectToOutput(e2, t2, i2) {
if (this.globalOptions.mergeOutput) return Object.assign(e2, i2);
e2[t2] ? Object.assign(e2[t2], i2) : e2[t2] = i2;
}
}
f(ge, "headerLength", 4), f(ge, "type", void 0), f(ge, "multiSegment", false), f(ge, "canHandle", (() => false));
function me(e2) {
return 192 === e2 || 194 === e2 || 196 === e2 || 219 === e2 || 221 === e2 || 218 === e2 || 254 === e2;
}
function Se(e2) {
return e2 >= 224 && e2 <= 239;
}
function Ce(e2, t2, i2) {
for (let [n2, s2] of A) if (s2.canHandle(e2, t2, i2)) return n2;
}
class ye extends pe {
constructor(...e2) {
super(...e2), f(this, "appSegments", []), f(this, "jpegSegments", []), f(this, "unknownSegments", []);
}
static canHandle(e2, t2) {
return 65496 === t2;
}
async parse() {
await this.findAppSegments(), await this.readSegments(this.appSegments), this.mergeMultiSegments(), this.createParsers(this.mergedAppSegments || this.appSegments);
}
setupSegmentFinderArgs(e2) {
true === e2 ? (this.findAll = true, this.wanted = new Set(A.keyList())) : (e2 = void 0 === e2 ? A.keyList().filter(((e3) => this.options[e3].enabled)) : e2.filter(((e3) => this.options[e3].enabled && A.has(e3))), this.findAll = false, this.remaining = new Set(e2), this.wanted = new Set(e2)), this.unfinishedMultiSegment = false;
}
async findAppSegments(e2 = 0, t2) {
this.setupSegmentFinderArgs(t2);
let { file: i2, findAll: n2, wanted: s2, remaining: r2 } = this;
if (!n2 && this.file.chunked && (n2 = Array.from(s2).some(((e3) => {
let t3 = A.get(e3), i3 = this.options[e3];
return t3.multiSegment && i3.multiSegment;
})), n2 && await this.file.readWhole()), e2 = this.findAppSegmentsInRange(e2, i2.byteLength), !this.options.onlyTiff && i2.chunked) {
let t3 = false;
for (; r2.size > 0 && !t3 && (i2.canReadNextChunk || this.unfinishedMultiSegment); ) {
let { nextChunkOffset: n3 } = i2, s3 = this.appSegments.some(((e3) => !this.file.available(e3.offset || e3.start, e3.length || e3.size)));
if (t3 = e2 > n3 && !s3 ? !await i2.readNextChunk(e2) : !await i2.readNextChunk(n3), void 0 === (e2 = this.findAppSegmentsInRange(e2, i2.byteLength))) return;
}
}
}
findAppSegmentsInRange(e2, t2) {
t2 -= 2;
let i2, n2, s2, r2, a2, o2, { file: l2, findAll: h2, wanted: u2, remaining: c2, options: f2 } = this;
for (; e2 < t2; e2++) if (255 === l2.getUint8(e2)) {
if (i2 = l2.getUint8(e2 + 1), Se(i2)) {
if (n2 = l2.getUint16(e2 + 2), s2 = Ce(l2, e2, n2), s2 && u2.has(s2) && (r2 = A.get(s2), a2 = r2.findPosition(l2, e2), o2 = f2[s2], a2.type = s2, this.appSegments.push(a2), !h2 && (r2.multiSegment && o2.multiSegment ? (this.unfinishedMultiSegment = a2.chunkNumber < a2.chunkCount, this.unfinishedMultiSegment || c2.delete(s2)) : c2.delete(s2), 0 === c2.size))) break;
f2.recordUnknownSegments && (a2 = ge.findPosition(l2, e2), a2.marker = i2, this.unknownSegments.push(a2)), e2 += n2 + 1;
} else if (me(i2)) {
if (n2 = l2.getUint16(e2 + 2), 218 === i2 && false !== f2.stopAfterSos) return;
f2.recordJpegSegments && this.jpegSegments.push({ offset: e2, length: n2, marker: i2 }), e2 += n2 + 1;
}
}
return e2;
}
mergeMultiSegments() {
if (!this.appSegments.some(((e3) => e3.multiSegment))) return;
let e2 = (function(e3, t2) {
let i2, n2, s2, r2 = /* @__PURE__ */ new Map();
for (let a2 = 0; a2 < e3.length; a2++) i2 = e3[a2], n2 = i2[t2], r2.has(n2) ? s2 = r2.get(n2) : r2.set(n2, s2 = []), s2.push(i2);
return Array.from(r2);
})(this.appSegments, "type");
this.mergedAppSegments = e2.map((([e3, t2]) => {
let i2 = A.get(e3, this.options);
if (i2.handleMultiSegments) {
return { type: e3, chunk: i2.handleMultiSegments(t2) };
}
return t2[0];
}));
}
getSegment(e2) {
return this.appSegments.find(((t2) => t2.type === e2));
}
async getOrFindSegment(e2) {
let t2 = this.getSegment(e2);
return void 0 === t2 && (await this.findAppSegments(0, [e2]), t2 = this.getSegment(e2)), t2;
}
}
f(ye, "type", "jpeg"), T.set("jpeg", ye);
const be = [void 0, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8, 4];
class Pe extends ge {
parseHeader() {
var e2 = this.chunk.getUint16();
18761 === e2 ? this.le = true : 19789 === e2 && (this.le = false), this.chunk.le = this.le, this.headerParsed = true;
}
parseTags(e2, t2, i2 = /* @__PURE__ */ new Map()) {
let { pick: n2, skip: s2 } = this.options[t2];
n2 = new Set(n2);
let r2 = n2.size > 0, a2 = 0 === s2.size, o2 = this.chunk.getUint16(e2);
e2 += 2;
for (let l2 = 0; l2 < o2; l2++) {
let o3 = this.chunk.getUint16(e2);
if (r2) {
if (n2.has(o3) && (i2.set(o3, this.parseTag(e2, o3, t2)), n2.delete(o3), 0 === n2.size)) break;
} else !a2 && s2.has(o3) || i2.set(o3, this.parseTag(e2, o3, t2));
e2 += 12;
}
return i2;
}
parseTag(e2, t2, i2) {
let { chunk: n2 } = this, s2 = n2.getUint16(e2 + 2), r2 = n2.getUint32(e2 + 4), a2 = be[s2];
if (a2 * r2 <= 4 ? e2 += 8 : e2 = n2.getUint32(e2 + 8), (s2 < 1 || s2 > 13) && m(`Invalid TIFF value type. block: ${i2.toUpperCase()}, tag: ${t2.toString(16)}, type: ${s2}, offset ${e2}`), e2 > n2.byteLength && m(`Invalid TIFF value offset. block: ${i2.toUpperCase()}, tag: ${t2.toString(16)}, type: ${s2}, offset ${e2} is outside of chunk size ${n2.byteLength}`), 1 === s2) return n2.getUint8Array(e2, r2);
if (2 === s2) return S(n2.getString(e2, r2));
if (7 === s2) return n2.getUint8Array(e2, r2);
if (1 === r2) return this.parseTagValue(s2, e2);
{
let t3 = new ((function(e3) {
switch (e3) {
case 1:
return Uint8Array;
case 3:
return Uint16Array;
case 4:
return Uint32Array;
case 5:
return Array;
case 6:
return Int8Array;
case 8:
return Int16Array;
case 9:
return Int32Array;
case 10:
return Array;
case 11:
return Float32Array;
case 12:
return Float64Array;
default:
return Array;
}
})(s2))(r2), i3 = a2;
for (let n3 = 0; n3 < r2; n3++) t3[n3] = this.parseTagValue(s2, e2), e2 += i3;
return t3;
}
}
parseTagValue(e2, t2) {
let { chunk: i2 } = this;
switch (e2) {
case 1:
return i2.getUint8(t2);
case 3:
return i2.getUint16(t2);
case 4:
return i2.getUint32(t2);
case 5:
return i2.getUint32(t2) / i2.getUint32(t2 + 4);
case 6:
return i2.getInt8(t2);
case 8:
return i2.getInt16(t2);
case 9:
return i2.getInt32(t2);
case 10:
return i2.getInt32(t2) / i2.getInt32(t2 + 4);
case 11:
return i2.getFloat(t2);
case 12:
return i2.getDouble(t2);
case 13:
return i2.getUint32(t2);
default:
m(`Invalid tiff type ${e2}`);
}
}
}
class Ie extends Pe {
static canHandle(e2, t2) {
return 225 === e2.getUint8(t2 + 1) && 1165519206 === e2.getUint32(t2 + 4) && 0 === e2.getUint16(t2 + 8);
}
async parse() {
this.parseHeader();
let { options: e2 } = this;
return e2.ifd0.enabled && await this.parseIfd0Block(), e2.exif.enabled && await this.safeParse("parseExifBlock"), e2.gps.enabled && await this.safeParse("parseGpsBlock"), e2.interop.enabled && await this.safeParse("parseInteropBlock"), e2.ifd1.enabled && await this.safeParse("parseThumbnailBlock"), this.createOutput();
}
safeParse(e2) {
let t2 = this[e2]();
return void 0 !== t2.catch && (t2 = t2.catch(this.handleError)), t2;
}
findIfd0Offset() {
void 0 === this.ifd0Offset && (this.ifd0Offset = this.chunk.getUint32(4));
}
findIfd1Offset() {
if (void 0 === this.ifd1Offset) {
this.findIfd0Offset();
let e2 = this.chunk.getUint16(this.ifd0Offset), t2 = this.ifd0Offset + 2 + 12 * e2;
this.ifd1Offset = this.chunk.getUint32(t2);
}
}
parseBlock(e2, t2) {
let i2 = /* @__PURE__ */ new Map();
return this[t2] = i2, this.parseTags(e2, t2, i2), i2;
}
async parseIfd0Block() {
if (this.ifd0) return;
let { file: e2 } = this;
this.findIfd0Offset(), this.ifd0Offset < 8 && m("Malformed EXIF data"), !e2.chunked && this.ifd0Offset > e2.byteLength && m(`IFD0 offset points to outside of file.
this.ifd0Offset: ${this.ifd0Offset}, file.byteLength: ${e2.byteLength}`), e2.tiff && await e2.ensureChunk(this.ifd0Offset, C(this.options));
let t2 = this.parseBlock(this.ifd0Offset, "ifd0");
return 0 !== t2.size ? (this.exifOffset = t2.get(X), this.interopOffset = t2.get(Y), this.gpsOffset = t2.get(_), this.xmp = t2.get(j), this.iptc = t2.get(W), this.icc = t2.get(K), this.options.sanitize && (t2.delete(X), t2.delete(Y), t2.delete(_), t2.delete(j), t2.delete(W), t2.delete(K)), t2) : void 0;
}
async parseExifBlock() {
if (this.exif) return;
if (this.ifd0 || await this.parseIfd0Block(), void 0 === this.exifOffset) return;
this.file.tiff && await this.file.ensureChunk(this.exifOffset, C(this.options));
let e2 = this.parseBlock(this.exifOffset, "exif");
return this.interopOffset || (this.interopOffset = e2.get(Y)), this.makerNote = e2.get(z4), this.userComment = e2.get(H), this.options.sanitize && (e2.delete(Y), e2.delete(z4), e2.delete(H)), this.unpack(e2, 41728), this.unpack(e2, 41729), e2;
}
unpack(e2, t2) {
let i2 = e2.get(t2);
i2 && 1 === i2.length && e2.set(t2, i2[0]);
}
async parseGpsBlock() {
if (this.gps) return;
if (this.ifd0 || await this.parseIfd0Block(), void 0 === this.gpsOffset) return;
let e2 = this.parseBlock(this.gpsOffset, "gps");
return e2 && e2.has(2) && e2.has(4) && (e2.set("latitude", ke(...e2.get(2), e2.get(1))), e2.set("longitude", ke(...e2.get(4), e2.get(3)))), e2;
}
async parseInteropBlock() {
if (!this.interop && (this.ifd0 || await this.parseIfd0Block(), void 0 !== this.interopOffset || this.exif || await this.parseExifBlock(), void 0 !== this.interopOffset)) return this.parseBlock(this.interopOffset, "interop");
}
async parseThumbnailBlock(e2 = false) {
if (!this.ifd1 && !this.ifd1Parsed && (!this.options.mergeOutput || e2)) return this.findIfd1Offset(), this.ifd1Offset > 0 && (this.parseBlock(this.ifd1Offset, "ifd1"), this.ifd1Parsed = true), this.ifd1;
}
async extractThumbnail() {
if (this.headerParsed || this.parseHeader(), this.ifd1Parsed || await this.parseThumbnailBlock(true), void 0 === this.ifd1) return;
let e2 = this.ifd1.get(513), t2 = this.ifd1.get(514);
return this.chunk.getUint8Array(e2, t2);
}
get image() {
return this.ifd0;
}
get thumbnail() {
return this.ifd1;
}
createOutput() {
let e2, t2, i2, n2 = {};
for (t2 of Q) if (e2 = this[t2], !g(e2)) if (i2 = this.canTranslate ? this.translateBlock(e2, t2) : Object.fromEntries(e2), this.options.mergeOutput) {
if ("ifd1" === t2) continue;
Object.assign(n2, i2);
} else n2[t2] = i2;
return this.makerNote && (n2.makerNote = this.makerNote), this.userComment && (n2.userComment = this.userComment), n2;
}
assignToOutput(e2, t2) {
if (this.globalOptions.mergeOutput) Object.assign(e2, t2);
else for (let [i2, n2] of Object.entries(t2)) this.assignObjectToOutput(e2, i2, n2);
}
}
function ke(e2, t2, i2, n2) {
var s2 = e2 + t2 / 60 + i2 / 3600;
return "S" !== n2 && "W" !== n2 || (s2 *= -1), s2;
}
f(Ie, "type", "tiff"), f(Ie, "headerLength", 10), A.set("tiff", Ie);
var we = Object.freeze({ __proto__: null, default: de, Exifr: ce, fileParsers: T, segmentParsers: A, fileReaders: D, tagKeys: N, tagValues: G, tagRevivers: V, createDictionary: B, extendDictionary: E, fetchUrlAsArrayBuffer: L, readBlobAsArrayBuffer: U, chunkedProps: $, otherSegments: J, segments: q, tiffBlocks: Q, segmentsAndBlocks: Z, tiffExtractables: ee, inheritables: te, allFormatters: ie, Options: oe, parse: fe });
const Te = { ifd0: false, ifd1: false, exif: false, gps: false, interop: false, sanitize: false, reviveValues: true, translateKeys: false, translateValues: false, mergeOutput: false }, Ae = Object.assign({}, Te, { firstChunkSize: 4e4, gps: [1, 2, 3, 4] });
async function De(e2) {
let t2 = new ce(Ae);
await t2.read(e2);
let i2 = await t2.parse();
if (i2 && i2.gps) {
let { latitude: e3, longitude: t3 } = i2.gps;
return { latitude: e3, longitude: t3 };
}
}
const Oe = Object.assign({}, Te, { tiff: false, ifd1: true, mergeOutput: false });
async function xe(e2) {
let t2 = new ce(Oe);
await t2.read(e2);
let i2 = await t2.extractThumbnail();
return i2 && o ? r.from(i2) : i2;
}
async function ve(e2) {
let t2 = await this.thumbnail(e2);
if (void 0 !== t2) {
let e3 = new Blob([t2]);
return URL.createObjectURL(e3);
}
}
const Me = Object.assign({}, Te, { firstChunkSize: 4e4, ifd0: [274] });
async function Re(e2) {
let t2 = new ce(Me);
await t2.read(e2);
let i2 = await t2.parse();
if (i2 && i2.ifd0) return i2.ifd0[274];
}
const Le = Object.freeze({ 1: { dimensionSwapped: false, scaleX: 1, scaleY: 1, deg: 0, rad: 0 }, 2: { dimensionSwapped: false, scaleX: -1, scaleY: 1, deg: 0, rad: 0 }, 3: { dimensionSwapped: false, scaleX: 1, scaleY: 1, deg: 180, rad: 180 * Math.PI / 180 }, 4: { dimensionSwapped: false, scaleX: -1, scaleY: 1, deg: 180, rad: 180 * Math.PI / 180 }, 5: { dimensionSwapped: true, scaleX: 1, scaleY: -1, deg: 90, rad: 90 * Math.PI / 180 }, 6: { dimensionSwapped: true, scaleX: 1, scaleY: 1, deg: 90, rad: 90 * Math.PI / 180 }, 7: { dimensionSwapped: true, scaleX: 1, scaleY: -1, deg: 270, rad: 270 * Math.PI / 180 }, 8: { dimensionSwapped: true, scaleX: 1, scaleY: 1, deg: 270, rad: 270 * Math.PI / 180 } });
if (e.rotateCanvas = true, e.rotateCss = true, "object" == typeof navigator) {
let t2 = navigator.userAgent;
if (t2.includes("iPad") || t2.includes("iPhone")) {
let i2 = t2.match(/OS (\d+)_(\d+)/);
if (i2) {
let [, t3, n2] = i2, s2 = Number(t3) + 0.1 * Number(n2);
e.rotateCanvas = s2 < 13.4, e.rotateCss = false;
}
} else if (t2.includes("OS X 10")) {
let [, i2] = t2.match(/OS X 10[_.](\d+)/);
e.rotateCanvas = e.rotateCss = Number(i2) < 15;
}
if (t2.includes("Chrome/")) {
let [, i2] = t2.match(/Chrome\/(\d+)/);
e.rotateCanvas = e.rotateCss = Number(i2) < 81;
} else if (t2.includes("Firefox/")) {
let [, i2] = t2.match(/Firefox\/(\d+)/);
e.rotateCanvas = e.rotateCss = Number(i2) < 77;
}
}
async function Ue(t2) {
let i2 = await Re(t2);
return Object.assign({ canvas: e.rotateCanvas, css: e.rotateCss }, Le[i2]);
}
class Fe extends I {
constructor(...e2) {
super(...e2), f(this, "ranges", new Be()), 0 !== this.byteLength && this.ranges.add(0, this.byteLength);
}
_tryExtend(e2, t2, i2) {
if (0 === e2 && 0 === this.byteLength && i2) {
let e3 = new DataView(i2.buffer || i2, i2.byteOffset, i2.byteLength);
this._swapDataView(e3);
} else {
let i3 = e2 + t2;
if (i3 > this.byteLength) {
let { dataView: e3 } = this._extend(i3);
this._swapDataView(e3);
}
}
}
_extend(e2) {
let t2;
t2 = o ? r.allocUnsafe(e2) : new Uint8Array(e2);
let i2 = new DataView(t2.buffer, t2.byteOffset, t2.byteLength);
return t2.set(new Uint8Array(this.buffer, this.byteOffset, this.byteLength), 0), { uintView: t2, dataView: i2 };
}
subarray(e2, t2, i2 = false) {
return t2 = t2 || this._lengthToEnd(e2), i2 && this._tryExtend(e2, t2), this.ranges.add(e2, t2), super.subarray(e2, t2);
}
set(e2, t2, i2 = false) {
i2 && this._tryExtend(t2, e2.byteLength, e2);
let n2 = super.set(e2, t2);
return this.ranges.add(t2, n2.byteLength), n2;
}
async ensureChunk(e2, t2) {
this.chunked && (this.ranges.available(e2, t2) || await this.readChunk(e2, t2));
}
available(e2, t2) {
return this.ranges.available(e2, t2);
}
}
class Be {
constructor() {
f(this, "list", []);
}
get length() {
return this.list.length;
}
add(e2, t2, i2 = 0) {
let n2 = e2 + t2, s2 = this.list.filter(((t3) => Ee(e2, t3.offset, n2) || Ee(e2, t3.end, n2)));
if (s2.length > 0) {
e2 = Math.min(e2, ...s2.map(((e3) => e3.offset))), n2 = Math.max(n2, ...s2.map(((e3) => e3.end))), t2 = n2 - e2;
let i3 = s2.shift();
i3.offset = e2, i3.length = t2, i3.end = n2, this.list = this.list.filter(((e3) => !s2.includes(e3)));
} else this.list.push({ offset: e2, length: t2, end: n2 });
}
available(e2, t2) {
let i2 = e2 + t2;
return this.list.some(((t3) => t3.offset <= e2 && i2 <= t3.end));
}
}
function Ee(e2, t2, i2) {
return e2 <= t2 && t2 <= i2;
}
class Ne extends Fe {
constructor(e2, t2) {
super(0), f(this, "chunksRead", 0), this.input = e2, this.options = t2;
}
async readWhole() {
this.chunked = false, await this.readChunk(this.nextChunkOffset);
}
async readChunked() {
this.chunked = true, await this.readChunk(0, this.options.firstChunkSize);
}
async readNextChunk(e2 = this.nextChunkOffset) {
if (this.fullyRead) return this.chunksRead++, false;
let t2 = this.options.chunkSize, i2 = await this.readChunk(e2, t2);
return !!i2 && i2.byteLength === t2;
}
async readChunk(e2, t2) {
if (this.chunksRead++, 0 !== (t2 = this.safeWrapAddress(e2, t2))) return this._readChunk(e2, t2);
}
safeWrapAddress(e2, t2) {
return void 0 !== this.size && e2 + t2 > this.size ? Math.max(0, this.size - e2) : t2;
}
get nextChunkOffset() {
if (0 !== this.ranges.list.length) return this.ranges.list[0].length;
}
get canReadNextChunk() {
return this.chunksRead < this.options.chunkLimit;
}
get fullyRead() {
return void 0 !== this.size && this.nextChunkOffset === this.size;
}
read() {
return this.options.chunked ? this.readChunked() : this.readWhole();
}
close() {
}
}
D.set("blob", class extends Ne {
async readWhole() {
this.chunked = false;
let e2 = await U(this.input);
this._swapArrayBuffer(e2);
}
readChunked() {
return this.chunked = true, this.size = this.input.size, super.readChunked();
}
async _readChunk(e2, t2) {
let i2 = t2 ? e2 + t2 : void 0, n2 = this.input.slice(e2, i2), s2 = await U(n2);
return this.set(s2, e2, true);
}
});
var Ge = Object.freeze({ __proto__: null, default: we, Exifr: ce, fileParsers: T, segmentParsers: A, fileReaders: D, tagKeys: N, tagValues: G, tagRevivers: V, createDictionary: B, extendDictionary: E, fetchUrlAsArrayBuffer: L, readBlobAsArrayBuffer: U, chunkedProps: $, otherSegments: J, segments: q, tiffBlocks: Q, segmentsAndBlocks: Z, tiffExtractables: ee, inheritables: te, allFormatters: ie, Options: oe, parse: fe, gpsOnlyOptions: Ae, gps: De, thumbnailOnlyOptions: Oe, thumbnail: xe, thumbnailUrl: ve, orientationOnlyOptions: Me, orientation: Re, rotations: Le, get rotateCanvas() {
return e.rotateCanvas;
}, get rotateCss() {
return e.rotateCss;
}, rotation: Ue });
D.set("url", class extends Ne {
async readWhole() {
this.chunked = false;
let e2 = await L(this.input);
e2 instanceof ArrayBuffer ? this._swapArrayBuffer(e2) : e2 instanceof Uint8Array && this._swapBuffer(e2);
}
async _readChunk(e2, t2) {
let i2 = t2 ? e2 + t2 - 1 : void 0, n2 = this.options.httpHeaders || {};
(e2 || i2) && (n2.range = `bytes=${[e2, i2].join("-")}`);
let s2 = await u(this.input, { headers: n2 }), r2 = await s2.arrayBuffer(), a2 = r2.byteLength;
if (416 !== s2.status) return a2 !== t2 && (this.size = e2 + a2), this.set(r2, e2, true);
}
});
I.prototype.getUint64 = function(e2) {
let t2 = this.getUint32(e2), i2 = this.getUint32(e2 + 4);
return t2 < 1048575 ? t2 << 32 | i2 : void 0 !== typeof a ? (console.warn("Using BigInt because of type 64uint but JS can only handle 53b numbers."), a(t2) << a(32) | a(i2)) : void m("Trying to read 64b value but JS can only handle 53b numbers.");
};
class Ve extends pe {
parseBoxes(e2 = 0) {
let t2 = [];
for (; e2 < this.file.byteLength - 4; ) {
let i2 = this.parseBoxHead(e2);
if (t2.push(i2), 0 === i2.length) break;
e2 += i2.length;
}
return t2;
}
parseSubBoxes(e2) {
e2.boxes = this.parseBoxes(e2.start);
}
findBox(e2, t2) {
return void 0 === e2.boxes && this.parseSubBoxes(e2), e2.boxes.find(((e3) => e3.kind === t2));
}
parseBoxHead(e2) {
let t2 = this.file.getUint32(e2), i2 = this.file.getString(e2 + 4, 4), n2 = e2 + 8;
return 1 === t2 && (t2 = this.file.getUint64(e2 + 8), n2 += 8), { offset: e2, length: t2, kind: i2, start: n2 };
}
parseBoxFullHead(e2) {
if (void 0 !== e2.version) return;
let t2 = this.file.getUint32(e2.start);
e2.version = t2 >> 24, e2.start += 4;
}
}
class ze extends Ve {
static canHandle(e2, t2) {
if (0 !== t2) return false;
let i2 = e2.getUint16(2);
if (i2 > 50) return false;
let n2 = 16, s2 = [];
for (; n2 < i2; ) s2.push(e2.getString(n2, 4)), n2 += 4;
return s2.includes(this.type);
}
async parse() {
let e2 = this.file.getUint32(0), t2 = this.parseBoxHead(e2);
for (; "meta" !== t2.kind; ) e2 += t2.length, await this.file.ensureChunk(e2, 16), t2 = this.parseBoxHead(e2);
await this.file.ensureChunk(t2.offset, t2.length), this.parseBoxFullHead(t2), this.parseSubBoxes(t2), this.options.icc.enabled && await this.findIcc(t2), this.options.tiff.enabled && await this.findExif(t2);
}
async registerSegment(e2, t2, i2) {
await this.file.ensureChunk(t2, i2);
let n2 = this.file.subarray(t2, i2);
this.createParser(e2, n2);
}
async findIcc(e2) {
let t2 = this.findBox(e2, "iprp");
if (void 0 === t2) return;
let i2 = this.findBox(t2, "ipco");
if (void 0 === i2) return;
let n2 = this.findBox(i2, "colr");
void 0 !== n2 && await this.registerSegment("icc", n2.offset + 12, n2.length);
}
async findExif(e2) {
let t2 = this.findBox(e2, "iinf");
if (void 0 === t2) return;
let i2 = this.findBox(e2, "iloc");
if (void 0 === i2) return;
let n2 = this.findExifLocIdInIinf(t2), s2 = this.findExtentInIloc(i2, n2);
if (void 0 === s2) return;
let [r2, a2] = s2;
await this.file.ensureChunk(r2, a2);
let o2 = 4 + this.file.getUint32(r2);
r2 += o2, a2 -= o2, await this.registerSegment("tiff", r2, a2);
}
findExifLocIdInIinf(e2) {
this.parseBoxFullHead(e2);
let t2, i2, n2, s2, r2 = e2.start, a2 = this.file.getUint16(r2);
for (r2 += 2; a2--; ) {
if (t2 = this.parseBoxHead(r2), this.parseBoxFullHead(t2), i2 = t2.start, t2.version >= 2 && (n2 = 3 === t2.version ? 4 : 2, s2 = this.file.getString(i2 + n2 + 2, 4), "Exif" === s2)) return this.file.getUintBytes(i2, n2);
r2 += t2.length;
}
}
get8bits(e2) {
let t2 = this.file.getUint8(e2);
return [t2 >> 4, 15 & t2];
}
findExtentInIloc(e2, t2) {
this.parseBoxFullHead(e2);
let i2 = e2.start, [n2, s2] = this.get8bits(i2++), [r2, a2] = this.get8bits(i2++), o2 = 2 === e2.version ? 4 : 2, l2 = 1 === e2.version || 2 === e2.version ? 2 : 0, h2 = a2 + n2 + s2, u2 = 2 === e2.version ? 4 : 2, c2 = this.file.getUintBytes(i2, u2);
for (i2 += u2; c2--; ) {
let e3 = this.file.getUintBytes(i2, o2);
i2 += o2 + l2 + 2 + r2;
let u3 = this.file.getUint16(i2);
if (i2 += 2, e3 === t2) return u3 > 1 && console.warn("ILOC box has more than one extent but we're only processing one\nPlease create an issue at https://github.com/MikeKovarik/exifr with this file"), [this.file.getUintBytes(i2 + a2, n2), this.file.getUintBytes(i2 + a2 + n2, s2)];
i2 += u3 * h2;
}
}
}
class He extends ze {
}
f(He, "type", "heic");
class je extends ze {
}
f(je, "type", "avif"), T.set("heic", He), T.set("avif", je), B(N, ["ifd0", "ifd1"], [[256, "ImageWidth"], [257, "ImageHeight"], [258, "BitsPerSample"], [259, "Compression"], [262, "PhotometricInterpretation"], [270, "ImageDescription"], [271, "Make"], [272, "Model"], [273, "StripOffsets"], [274, "Orientation"], [277, "SamplesPerPixel"], [278, "RowsPerStrip"], [279, "StripByteCounts"], [282, "XResolution"], [283, "YResolution"], [284, "PlanarConfiguration"], [296, "ResolutionUnit"], [301, "TransferFunction"], [305, "Software"], [306, "ModifyDate"], [315, "Artist"], [316, "HostComputer"], [317, "Predictor"], [318, "WhitePoint"], [319, "PrimaryChromaticities"], [513, "ThumbnailOffset"], [514, "ThumbnailLength"], [529, "YCbCrCoefficients"], [530, "YCbCrSubSampling"], [531, "YCbCrPositioning"], [532, "ReferenceBlackWhite"], [700, "ApplicationNotes"], [33432, "Copyright"], [33723, "IPTC"], [34665, "ExifIFD"], [34675, "ICC"], [34853, "GpsIFD"], [330, "SubIFD"], [40965, "InteropIFD"], [40091, "XPTitle"], [40092, "XPComment"], [40093, "XPAuthor"], [40094, "XPKeywords"], [40095, "XPSubject"]]), B(N, "exif", [[33434, "ExposureTime"], [33437, "FNumber"], [34850, "ExposureProgram"], [34852, "SpectralSensitivity"], [34855, "ISO"], [34858, "TimeZoneOffset"], [34859, "SelfTimerMode"], [34864, "SensitivityType"], [34865, "StandardOutputSensitivity"], [34866, "RecommendedExposureIndex"], [34867, "ISOSpeed"], [34868, "ISOSpeedLatitudeyyy"], [34869, "ISOSpeedLatitudezzz"], [36864, "ExifVersion"], [36867, "DateTimeOriginal"], [36868, "CreateDate"], [36873, "GooglePlusUploadCode"], [36880, "OffsetTime"], [36881, "OffsetTimeOriginal"], [36882, "OffsetTimeDigitized"], [37121, "ComponentsConfiguration"], [37122, "CompressedBitsPerPixel"], [37377, "ShutterSpeedValue"], [37378, "ApertureValue"], [37379, "BrightnessValue"], [37380, "ExposureCompensation"], [37381, "MaxApertureValue"], [37382, "SubjectDistance"], [37383, "MeteringMode"], [37384, "LightSource"], [37385, "Flash"], [37386, "FocalLength"], [37393, "ImageNumber"], [37394, "SecurityClassification"], [37395, "ImageHistory"], [37396, "SubjectArea"], [37500, "MakerNote"], [37510, "UserComment"], [37520, "SubSecTime"], [37521, "SubSecTimeOriginal"], [37522, "SubSecTimeDigitized"], [37888, "AmbientTemperature"], [37889, "Humidity"], [37890, "Pressure"], [37891, "WaterDepth"], [37892, "Acceleration"], [37893, "CameraElevationAngle"], [40960, "FlashpixVersion"], [40961, "ColorSpace"], [40962, "ExifImageWidth"], [40963, "ExifImageHeight"], [40964, "RelatedSoundFile"], [41483, "FlashEnergy"], [41486, "FocalPlaneXResolution"], [41487, "FocalPlaneYResolution"], [41488, "FocalPlaneResolutionUnit"], [41492, "SubjectLocation"], [41493, "ExposureIndex"], [41495, "SensingMethod"], [41728, "FileSource"], [41729, "SceneType"], [41730, "CFAPattern"], [41985, "CustomRendered"], [41986, "ExposureMode"], [41987, "WhiteBalance"], [41988, "DigitalZoomRatio"], [41989, "FocalLengthIn35mmFormat"], [41990, "SceneCaptureType"], [41991, "GainControl"], [41992, "Contrast"], [41993, "Saturation"], [41994, "Sharpness"], [41996, "SubjectDistanceRange"], [42016, "ImageUniqueID"], [42032, "OwnerName"], [42033, "SerialNumber"], [42034, "LensInfo"], [42035, "LensMake"], [42036, "LensModel"], [42037, "LensSerialNumber"], [42080, "CompositeImage"], [42081, "CompositeImageCount"], [42082, "CompositeImageExposureTimes"], [42240, "Gamma"], [59932, "Padding"], [59933, "OffsetSchema"], [65e3, "OwnerName"], [65001, "SerialNumber"], [65002, "Lens"], [65100, "RawFile"], [65101, "Converter"], [65102, "WhiteBalance"], [65105, "Exposure"], [65106, "Shadows"], [65107, "Brightness"], [65108, "Contrast"], [65109, "Saturation"], [65110, "Sharpness"], [65111, "Smoothness"], [65112, "MoireFilter"], [40965, "InteropIFD"]]), B(N, "gps", [[0, "GPSVersionID"], [1, "GPSLatitudeRef"], [2, "GPSLatitude"], [3, "GPSLongitudeRef"], [4, "GPSLongitude"], [5, "GPSAltitudeRef"], [6, "GPSAltitude"], [7, "GPSTimeStamp"], [8, "GPSSatellites"], [9, "GPSStatus"], [10, "GPSMeasureMode"], [11, "GPSDOP"], [12, "GPSSpeedRef"], [13, "GPSSpeed"], [14, "GPSTrackRef"], [15, "GPSTrack"], [16, "GPSImgDirectionRef"], [17, "GPSImgDirection"], [18, "GPSMapDatum"], [19, "GPSDestLatitudeRef"], [20, "GPSDestLatitude"], [21, "GPSDestLongitudeRef"], [22, "GPSDestLongitude"], [23, "GPSDestBearingRef"], [24, "GPSDestBearing"], [25, "GPSDestDistanceRef"], [26, "GPSDestDistance"], [27, "GPSProcessingMethod"], [28, "GPSAreaInformation"], [29, "GPSDateStamp"], [30, "GPSDifferential"], [31, "GPSHPositioningError"]]), B(G, ["ifd0", "ifd1"], [[274, { 1: "Horizontal (normal)", 2: "Mirror horizontal", 3: "Rotate 180", 4: "Mirror vertical", 5: "Mirror horizontal and rotate 270 CW", 6: "Rotate 90 CW", 7: "Mirror horizontal and rotate 90 CW", 8: "Rotate 270 CW" }], [296, { 1: "None", 2: "inches", 3: "cm" }]]);
let We = B(G, "exif", [[34850, { 0: "Not defined", 1: "Manual", 2: "Normal program", 3: "Aperture priority", 4: "Shutter priority", 5: "Creative program", 6: "Action program", 7: "Portrait mode", 8: "Landscape mode" }], [37121, { 0: "-", 1: "Y", 2: "Cb", 3: "Cr", 4: "R", 5: "G", 6: "B" }], [37383, { 0: "Unknown", 1: "Average", 2: "CenterWeightedAverage", 3: "Spot", 4: "MultiSpot", 5: "Pattern", 6: "Partial", 255: "Other" }], [37384, { 0: "Unknown", 1: "Daylight", 2: "Fluorescent", 3: "Tungsten (incandescent light)", 4: "Flash", 9: "Fine weather", 10: "Cloudy weather", 11: "Shade", 12: "Daylight fluorescent (D 5700 - 7100K)", 13: "Day white fluorescent (N 4600 - 5400K)", 14: "Cool white fluorescent (W 3900 - 4500K)", 15: "White fluorescent (WW 3200 - 3700K)", 17: "Standard light A", 18: "Standard light B", 19: "Standard light C", 20: "D55", 21: "D65", 22: "D75", 23: "D50", 24: "ISO studio tungsten", 255: "Other" }], [37385, { 0: "Flash did not fire", 1: "Flash fired", 5: "Strobe return light not detected", 7: "Strobe return light detected", 9: "Flash fired, compulsory flash mode", 13: "Flash fired, compulsory flash mode, return light not detected", 15: "Flash fired, compulsory flash mode, return light detected", 16: "Flash did not fire, compulsory flash mode", 24: "Flash did not fire, auto mode", 25: "Flash fired, auto mode", 29: "Flash fired, auto mode, return light not detected", 31: "Flash fired, auto mode, return light detected", 32: "No flash function", 65: "Flash fired, red-eye reduction mode", 69: "Flash fired, red-eye reduction mode, return light not detected", 71: "Flash fired, red-eye reduction mode, return light detected", 73: "Flash fired, compulsory flash mode, red-eye reduction mode", 77: "Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected", 79: "Flash fired, compulsory flash mode, red-eye reduction mode, return light detected", 89: "Flash fired, auto mode, red-eye reduction mode", 93: "Flash fired, auto mode, return light not detected, red-eye reduction mode", 95: "Flash fired, auto mode, return light detected, red-eye reduction mode" }], [41495, { 1: "Not defined", 2: "One-chip color area sensor", 3: "Two-chip color area sensor", 4: "Three-chip color area sensor", 5: "Color sequential area sensor", 7: "Trilinear sensor", 8: "Color sequential linear sensor" }], [41728, { 1: "Film Scanner", 2: "Reflection Print Scanner", 3: "Digital Camera" }], [41729, { 1: "Directly photographed" }], [41985, { 0: "Normal", 1: "Custom", 2: "HDR (no original saved)", 3: "HDR (original saved)", 4: "Original (for HDR)", 6: "Panorama", 7: "Portrait HDR", 8: "Portrait" }], [41986, { 0: "Auto", 1: "Manual", 2: "Auto bracket" }], [41987, { 0: "Auto", 1: "Manual" }], [41990, { 0: "Standard", 1: "Landscape", 2: "Portrait", 3: "Night", 4: "Other" }], [41991, { 0: "None", 1: "Low gain up", 2: "High gain up", 3: "Low gain down", 4: "High gain down" }], [41996, { 0: "Unknown", 1: "Macro", 2: "Close", 3: "Distant" }], [42080, { 0: "Unknown", 1: "Not a Composite Image", 2: "General Composite Image", 3: "Composite Image Captured While Shooting" }]]);
const Ke = { 1: "No absolute unit of measurement", 2: "Inch", 3: "Centimeter" };
We.set(37392, Ke), We.set(41488, Ke);
const Xe = { 0: "Normal", 1: "Low", 2: "High" };
function _e(e2) {
return "object" == typeof e2 && void 0 !== e2.length ? e2[0] : e2;
}
function Ye(e2) {
let t2 = Array.from(e2).slice(1);
return t2[1] > 15 && (t2 = t2.map(((e3) => String.fromCharCode(e3)))), "0" !== t2[2] && 0 !== t2[2] || t2.pop(), t2.join(".");
}
function $e(e2) {
if ("string" == typeof e2) {
var [t2, i2, n2, s2, r2, a2] = e2.trim().split(/[-: ]/g).map(Number), o2 = new Date(t2, i2 - 1, n2);
return Number.isNaN(s2) || Number.isNaN(r2) || Number.isNaN(a2) || (o2.setHours(s2), o2.setMinutes(r2), o2.setSeconds(a2)), Number.isNaN(+o2) ? e2 : o2;
}
}
function Je(e2) {
if ("string" == typeof e2) return e2;
let t2 = [];
if (0 === e2[1] && 0 === e2[e2.length - 1]) for (let i2 = 0; i2 < e2.length; i2 += 2) t2.push(qe(e2[i2 + 1], e2[i2]));
else for (let i2 = 0; i2 < e2.length; i2 += 2) t2.push(qe(e2[i2], e2[i2 + 1]));
return S(String.fromCodePoint(...t2));
}
function qe(e2, t2) {
return e2 << 8 | t2;
}
We.set(41992, Xe), We.set(41993, Xe), We.set(41994, Xe), B(V, ["ifd0", "ifd1"], [[50827, function(e2) {
return "string" != typeof e2 ? P(e2) : e2;
}], [306, $e], [40091, Je], [40092, Je], [40093, Je], [40094, Je], [40095, Je]]), B(V, "exif", [[40960, Ye], [36864, Ye], [36867, $e], [36868, $e], [40962, _e], [40963, _e]]), B(V, "gps", [[0, (e2) => Array.from(e2).join(".")], [7, (e2) => Array.from(e2).join(":")]]);
const Qe = "http://ns.adobe.com/", Ze = "http://ns.adobe.com/xmp/extension/";
class et extends ge {
static canHandle(e2, t2) {
return 225 === e2.getUint8(t2 + 1) && 1752462448 === e2.getUint32(t2 + 4) && e2.getString(t2 + 4, Qe.length) === Qe;
}
static headerLength(e2, t2) {
return e2.getString(t2 + 4, Ze.length) === Ze ? 79 : 4 + "http://ns.adobe.com/xap/1.0/".length + 1;
}
static findPosition(e2, t2) {
let i2 = super.findPosition(e2, t2);
return i2.multiSegment = i2.extended = 79 === i2.headerLength, i2.multiSegment ? (i2.chunkCount = e2.getUint8(t2 + 72), i2.chunkNumber = e2.getUint8(t2 + 76), 0 !== e2.getUint8(t2 + 77) && i2.chunkNumber++) : (i2.chunkCount = 1 / 0, i2.chunkNumber = -1), i2;
}
static handleMultiSegments(e2) {
return e2.map(((e3) => e3.chunk.getString())).join("");
}
normalizeInput(e2) {
return "string" == typeof e2 ? e2 : I.from(e2).getString();
}
parse(e2 = this.chunk) {
if (!this.localOptions.parse) return e2;
e2 = (function(e3) {
let t3 = {}, i3 = {};
for (let e4 of ut) t3[e4] = [], i3[e4] = 0;
return e3.replace(ct, ((e4, n3, s2) => {
if ("<" === n3) {
let n4 = ++i3[s2];
return t3[s2].push(n4), `${e4}#${n4}`;
}
return `${e4}#${t3[s2].pop()}`;
}));
})(e2);
let t2 = nt.findAll(e2, "rdf", "Description");
0 === t2.length && t2.push(new nt("rdf", "Description", void 0, e2));
let i2, n2 = {};
for (let e3 of t2) for (let t3 of e3.properties) i2 = ot(t3.ns, n2), st(t3, i2);
return (function(e3) {
let t3;
for (let i3 in e3) t3 = e3[i3] = d(e3[i3]), void 0 === t3 && delete e3[i3];
return d(e3);
})(n2);
}
assignToOutput(e2, t2) {
if (this.localOptions.parse) for (let [i2, n2] of Object.entries(t2)) switch (i2) {
case "tiff":
this.assignObjectToOutput(e2, "ifd0", n2);
break;
case "exif":
this.assignObjectToOutput(e2, "exif", n2);
break;
case "xmlns":
break;
default:
this.assignObjectToOutput(e2, i2, n2);
}
else e2.xmp = t2;
}
}
f(et, "type", "xmp"), f(et, "multiSegment", true), A.set("xmp", et);
class tt {
static findAll(e2) {
return lt(e2, /([a-zA-Z0-9-]+):([a-zA-Z0-9-]+)=("[^"]*"|'[^']*')/gm).map(tt.unpackMatch);
}
static unpackMatch(e2) {
let t2 = e2[1], i2 = e2[2], n2 = e2[3].slice(1, -1);
return n2 = ht(n2), new tt(t2, i2, n2);
}
constructor(e2, t2, i2) {
this.ns = e2, this.name = t2, this.value = i2;
}
serialize() {
return this.value;
}
}
const it = "[\\w\\d-]+";
class nt {
static findAll(e2, t2, i2) {
if (void 0 !== t2 || void 0 !== i2) {
t2 = t2 || it, i2 = i2 || it;
var n2 = new RegExp(`<(${t2}):(${i2})(#\\d+)?((\\s+?[\\w\\d-:]+=("[^"]*"|'[^']*'))*\\s*)(\\/>|>([\\s\\S]*?)<\\/\\1:\\2\\3>)`, "gm");
} else n2 = /<([\w\d-]+):([\w\d-]+)(#\d+)?((\s+?[\w\d-:]+=("[^"]*"|'[^']*'))*\s*)(\/>|>([\s\S]*?)<\/\1:\2\3>)/gm;
return lt(e2, n2).map(nt.unpackMatch);
}
static unpackMatch(e2) {
let t2 = e2[1], i2 = e2[2], n2 = e2[4], s2 = e2[8];
return new nt(t2, i2, n2, s2);
}
constructor(e2, t2, i2, n2) {
this.ns = e2, this.name = t2, this.attrString = i2, this.innerXml = n2, this.attrs = tt.findAll(i2), this.children = nt.findAll(n2), this.value = 0 === this.children.length ? ht(n2) : void 0, this.properties = [...this.attrs, ...this.children];
}
get isPrimitive() {
return void 0 !== this.value && 0 === this.attrs.length && 0 === this.children.length;
}
get isListContainer() {
return 1 === this.children.length && this.children[0].isList;
}
get isList() {
let { ns: e2, name: t2 } = this;
return "rdf" === e2 && ("Seq" === t2 || "Bag" === t2 || "Alt" === t2);
}
get isListItem() {
return "rdf" === this.ns && "li" === this.name;
}
serialize() {
if (0 === this.properties.length && void 0 === this.value) return;
if (this.isPrimitive) return this.value;
if (this.isListContainer) return this.children[0].serialize();
if (this.isList) return at(this.children.map(rt));
if (this.isListItem && 1 === this.children.length && 0 === this.attrs.length) return this.children[0].serialize();
let e2 = {};
for (let t2 of this.properties) st(t2, e2);
return void 0 !== this.value && (e2.value = this.value), d(e2);
}
}
function st(e2, t2) {
let i2 = e2.serialize();
void 0 !== i2 && (t2[e2.name] = i2);
}
var rt = (e2) => e2.serialize(), at = (e2) => 1 === e2.length ? e2[0] : e2, ot = (e2, t2) => t2[e2] ? t2[e2] : t2[e2] = {};
function lt(e2, t2) {
let i2, n2 = [];
if (!e2) return n2;
for (; null !== (i2 = t2.exec(e2)); ) n2.push(i2);
return n2;
}
function ht(e2) {
if ((function(e3) {
return null == e3 || "null" === e3 || "undefined" === e3 || "" === e3 || "" === e3.trim();
})(e2)) return;
let t2 = Number(e2);
if (!Number.isNaN(t2)) return t2;
let i2 = e2.toLowerCase();
return "true" === i2 || "false" !== i2 && e2.trim();
}
const ut = ["rdf:li", "rdf:Seq", "rdf:Bag", "rdf:Alt", "rdf:Description"], ct = new RegExp(`(<|\\/)(${ut.join("|")})`, "g");
var ft = Object.freeze({ __proto__: null, default: Ge, Exifr: ce, fileParsers: T, segmentParsers: A, fileReaders: D, tagKeys: N, tagValues: G, tagRevivers: V, createDictionary: B, extendDictionary: E, fetchUrlAsArrayBuffer: L, readBlobAsArrayBuffer: U, chunkedProps: $, otherSegments: J, segments: q, tiffBlocks: Q, segmentsAndBlocks: Z, tiffExtractables: ee, inheritables: te, allFormatters: ie, Options: oe, parse: fe, gpsOnlyOptions: Ae, gps: De, thumbnailOnlyOptions: Oe, thumbnail: xe, thumbnailUrl: ve, orientationOnlyOptions: Me, orientation: Re, rotations: Le, get rotateCanvas() {
return e.rotateCanvas;
}, get rotateCss() {
return e.rotateCss;
}, rotation: Ue });
const dt = ["xmp", "icc", "iptc", "tiff"], pt = () => {
};
async function gt(e2, t2, i2) {
let n2 = i2[e2];
return n2.enabled = true, n2.parse = true, A.get(e2).parse(t2, n2);
}
let mt = h("fs", ((e2) => e2.promises));
D.set("fs", class extends Ne {
async readWhole() {
this.chunked = false, this.fs = await mt;
let e2 = await this.fs.readFile(this.input);
this._swapBuffer(e2);
}
async readChunked() {
this.chunked = true, this.fs = await mt, await this.open(), await this.readChunk(0, this.options.firstChunkSize);
}
async open() {
void 0 === this.fh && (this.fh = await this.fs.open(this.input, "r"), this.size = (await this.fh.stat(this.input)).size);
}
async _readChunk(e2, t2) {
void 0 === this.fh && await this.open(), e2 + t2 > this.size && (t2 = this.size - e2);
var i2 = this.subarray(e2, t2, true);
return await this.fh.read(i2.dataView, 0, t2, e2), i2;
}
async close() {
if (this.fh) {
let e2 = this.fh;
this.fh = void 0, await e2.close();
}
}
});
D.set("base64", class extends Ne {
constructor(...e2) {
super(...e2), this.input = this.input.replace(/^data:([^;]+);base64,/gim, ""), this.size = this.input.length / 4 * 3, this.input.endsWith("==") ? this.size -= 2 : this.input.endsWith("=") && (this.size -= 1);
}
async _readChunk(e2, t2) {
let i2, n2, s2 = this.input;
void 0 === e2 ? (e2 = 0, i2 = 0, n2 = 0) : (i2 = 4 * Math.floor(e2 / 3), n2 = e2 - i2 / 4 * 3), void 0 === t2 && (t2 = this.size);
let a2 = e2 + t2, l2 = i2 + 4 * Math.ceil(a2 / 3);
s2 = s2.slice(i2, l2);
let h2 = Math.min(t2, this.size - e2);
if (o) {
let t3 = r.from(s2, "base64").slice(n2, n2 + h2);
return this.set(t3, e2, true);
}
{
let t3 = this.subarray(e2, h2, true), i3 = atob(s2), r2 = t3.toUint8();
for (let e3 = 0; e3 < h2; e3++) r2[e3] = i3.charCodeAt(n2 + e3);
return t3;
}
}
});
class St extends pe {
static canHandle(e2, t2) {
return 18761 === t2 || 19789 === t2;
}
extendOptions(e2) {
let { ifd0: t2, xmp: i2, iptc: n2, icc: s2 } = e2;
i2.enabled && t2.deps.add(j), n2.enabled && t2.deps.add(W), s2.enabled && t2.deps.add(K), t2.finalizeFilters();
}
async parse() {
let { tiff: e2, xmp: t2, iptc: i2, icc: n2 } = this.options;
if (e2.enabled || t2.enabled || i2.enabled || n2.enabled) {
let e3 = Math.max(C(this.options), this.options.chunkSize);
await this.file.ensureChunk(0, e3), this.createParser("tiff", this.file), this.parsers.tiff.parseHeader(), await this.parsers.tiff.parseIfd0Block(), this.adaptTiffPropAsSegment("xmp"), this.adaptTiffPropAsSegment("iptc"), this.adaptTiffPropAsSegment("icc");
}
}
adaptTiffPropAsSegment(e2) {
if (this.parsers.tiff[e2]) {
let t2 = this.parsers.tiff[e2];
this.injectSegment(e2, t2);
}
}
}
f(St, "type", "tiff"), T.set("tiff", St);
let Ct = h("zlib");
const yt = "XML:com.adobe.xmp", bt = "ihdr", Pt = "iccp", It = "text", kt = "itxt", wt = [bt, Pt, It, kt, "exif"];
class Tt extends pe {
constructor(...e2) {
super(...e2), f(this, "catchError", ((e3) => this.errors.push(e3))), f(this, "metaChunks", []), f(this, "unknownChunks", []);
}
static canHandle(e2, t2) {
return 35152 === t2 && 2303741511 === e2.getUint32(0) && 218765834 === e2.getUint32(4);
}
async parse() {
let { file: e2 } = this;
await this.findPngChunksInRange("\x89PNG\r\n\n".length, e2.byteLength), await this.readSegments(this.metaChunks), this.findIhdr(), this.parseTextChunks(), await this.findExif().catch(this.catchError), await this.findXmp().catch(this.catchError), await this.findIcc().catch(this.catchError);
}
async findPngChunksInRange(e2, t2) {
let { file: i2 } = this;
for (; e2 < t2; ) {
let t3 = i2.getUint32(e2), n2 = i2.getUint32(e2 + 4), s2 = i2.getString(e2 + 4, 4).toLowerCase(), r2 = t3 + 4 + 4 + 4, a2 = { type: s2, offset: e2, length: r2, start: e2 + 4 + 4, size: t3, marker: n2 };
wt.includes(s2) ? this.metaChunks.push(a2) : this.unknownChunks.push(a2), e2 += r2;
}
}
parseTextChunks() {
let e2 = this.metaChunks.filter(((e3) => e3.type === It));
for (let t2 of e2) {
let [e3, i2] = this.file.getString(t2.start, t2.size).split("\0");
this.injectKeyValToIhdr(e3, i2);
}
}
injectKeyValToIhdr(e2, t2) {
let i2 = this.parsers.ihdr;
i2 && i2.raw.set(e2, t2);
}
findIhdr() {
let e2 = this.metaChunks.find(((e3) => e3.type === bt));
e2 && false !== this.options.ihdr.enabled && this.createParser(bt, e2.chunk);
}
async findExif() {
let e2 = this.metaChunks.find(((e3) => "exif" === e3.type));
e2 && this.injectSegment("tiff", e2.chunk);
}
async findXmp() {
let e2 = this.metaChunks.filter(((e3) => e3.type === kt));
for (let t2 of e2) {
t2.chunk.getString(0, yt.length) === yt && this.injectSegment("xmp", t2.chunk);
}
}
async findIcc() {
let e2 = this.metaChunks.find(((e3) => e3.type === Pt));
if (!e2) return;
let { chunk: t2 } = e2, i2 = t2.getUint8Array(0, 81), n2 = 0;
for (; n2 < 80 && 0 !== i2[n2]; ) n2++;
let r2 = n2 + 2, a2 = t2.getString(0, n2);
if (this.injectKeyValToIhdr("ProfileName", a2), s) {
let e3 = await Ct, i3 = t2.getUint8Array(r2);
i3 = e3.inflateSync(i3), this.injectSegment("icc", i3);
}
}
}
f(Tt, "type", "png"), T.set("png", Tt), B(N, "interop", [[1, "InteropIndex"], [2, "InteropVersion"], [4096, "RelatedImageFileFormat"], [4097, "RelatedImageWidth"], [4098, "RelatedImageHeight"]]), E(N, "ifd0", [[11, "ProcessingSoftware"], [254, "SubfileType"], [255, "OldSubfileType"], [263, "Thresholding"], [264, "CellWidth"], [265, "CellLength"], [266, "FillOrder"], [269, "DocumentName"], [280, "MinSampleValue"], [281, "MaxSampleValue"], [285, "PageName"], [286, "XPosition"], [287, "YPosition"], [290, "GrayResponseUnit"], [297, "PageNumber"], [321, "HalftoneHints"], [322, "TileWidth"], [323, "TileLength"], [332, "InkSet"], [337, "TargetPrinter"], [18246, "Rating"], [18249, "RatingPercent"], [33550, "PixelScale"], [34264, "ModelTransform"], [34377, "PhotoshopSettings"], [50706, "DNGVersion"], [50707, "DNGBackwardVersion"], [50708, "UniqueCameraModel"], [50709, "LocalizedCameraModel"], [50736, "DNGLensInfo"], [50739, "ShadowScale"], [50740, "DNGPrivateData"], [33920, "IntergraphMatrix"], [33922, "ModelTiePoint"], [34118, "SEMInfo"], [34735, "GeoTiffDirectory"], [34736, "GeoTiffDoubleParams"], [34737, "GeoTiffAsciiParams"], [50341, "PrintIM"], [50721, "ColorMatrix1"], [50722, "ColorMatrix2"], [50723, "CameraCalibration1"], [50724, "CameraCalibration2"], [50725, "ReductionMatrix1"], [50726, "ReductionMatrix2"], [50727, "AnalogBalance"], [50728, "AsShotNeutral"], [50729, "AsShotWhiteXY"], [50730, "BaselineExposure"], [50731, "BaselineNoise"], [50732, "BaselineSharpness"], [50734, "LinearResponseLimit"], [50735, "CameraSerialNumber"], [50741, "MakerNoteSafety"], [50778, "CalibrationIlluminant1"], [50779, "CalibrationIlluminant2"], [50781, "RawDataUniqueID"], [50827, "OriginalRawFileName"], [50828, "OriginalRawFileData"], [50831, "AsShotICCProfile"], [50832, "AsShotPreProfileMatrix"], [50833, "CurrentICCProfile"], [50834, "CurrentPreProfileMatrix"], [50879, "ColorimetricReference"], [50885, "SRawType"], [50898, "PanasonicTitle"], [50899, "PanasonicTitle2"], [50931, "CameraCalibrationSig"], [50932, "ProfileCalibrationSig"], [50933, "ProfileIFD"], [50934, "AsShotProfileName"], [50936, "ProfileName"], [50937, "ProfileHueSatMapDims"], [50938, "ProfileHueSatMapData1"], [50939, "ProfileHueSatMapData2"], [50940, "ProfileToneCurve"], [50941, "ProfileEmbedPolicy"], [50942, "ProfileCopyright"], [50964, "ForwardMatrix1"], [50965, "ForwardMatrix2"], [50966, "PreviewApplicationName"], [50967, "PreviewApplicationVersion"], [50968, "PreviewSettingsName"], [50969, "PreviewSettingsDigest"], [50970, "PreviewColorSpace"], [50971, "PreviewDateTime"], [50972, "RawImageDigest"], [50973, "OriginalRawFileDigest"], [50981, "ProfileLookTableDims"], [50982, "ProfileLookTableData"], [51043, "TimeCodes"], [51044, "FrameRate"], [51058, "TStop"], [51081, "ReelName"], [51089, "OriginalDefaultFinalSize"], [51090, "OriginalBestQualitySize"], [51091, "OriginalDefaultCropSize"], [51105, "CameraLabel"], [51107, "ProfileHueSatMapEncoding"], [51108, "ProfileLookTableEncoding"], [51109, "BaselineExposureOffset"], [51110, "DefaultBlackRender"], [51111, "NewRawImageDigest"], [51112, "RawToPreviewGain"]]);
let At = [[273, "StripOffsets"], [279, "StripByteCounts"], [288, "FreeOffsets"], [289, "FreeByteCounts"], [291, "GrayResponseCurve"], [292, "T4Options"], [293, "T6Options"], [300, "ColorResponseUnit"], [320, "ColorMap"], [324, "TileOffsets"], [325, "TileByteCounts"], [326, "BadFaxLines"], [327, "CleanFaxData"], [328, "ConsecutiveBadFaxLines"], [330, "SubIFD"], [333, "InkNames"], [334, "NumberofInks"], [336, "DotRange"], [338, "ExtraSamples"], [339, "SampleFormat"], [340, "SMinSampleValue"], [341, "SMaxSampleValue"], [342, "TransferRange"], [343, "ClipPath"], [344, "XClipPathUnits"], [345, "YClipPathUnits"], [346, "Indexed"], [347, "JPEGTables"], [351, "OPIProxy"], [400, "GlobalParametersIFD"], [401, "ProfileType"], [402, "FaxProfile"], [403, "CodingMethods"], [404, "VersionYear"], [405, "ModeNumber"], [433, "Decode"], [434, "DefaultImageColor"], [435, "T82Options"], [437, "JPEGTables"], [512, "JPEGProc"], [515, "JPEGRestartInterval"], [517, "JPEGLosslessPredictors"], [518, "JPEGPointTransforms"], [519, "JPEGQTables"], [520, "JPEGDCTables"], [521, "JPEGACTables"], [559, "StripRowCounts"], [999, "USPTOMiscellaneous"], [18247, "XP_DIP_XML"], [18248, "StitchInfo"], [28672, "SonyRawFileType"], [28688, "SonyToneCurve"], [28721, "VignettingCorrection"], [28722, "VignettingCorrParams"], [28724, "ChromaticAberrationCorrection"], [28725, "ChromaticAberrationCorrParams"], [28726, "DistortionCorrection"], [28727, "DistortionCorrParams"], [29895, "SonyCropTopLeft"], [29896, "SonyCropSize"], [32781, "ImageID"], [32931, "WangTag1"], [32932, "WangAnnotation"], [32933, "WangTag3"], [32934, "WangTag4"], [32953, "ImageReferencePoints"], [32954, "RegionXformTackPoint"], [32955, "WarpQuadrilateral"], [32956, "AffineTransformMat"], [32995, "Matteing"], [32996, "DataType"], [32997, "ImageDepth"], [32998, "TileDepth"], [33300, "ImageFullWidth"], [33301, "ImageFullHeight"], [33302, "TextureFormat"], [33303, "WrapModes"], [33304, "FovCot"], [33305, "MatrixWorldToScreen"], [33306, "MatrixWorldToCamera"], [33405, "Model2"], [33421, "CFARepeatPatternDim"], [33422, "CFAPattern2"], [33423, "BatteryLevel"], [33424, "KodakIFD"], [33445, "MDFileTag"], [33446, "MDScalePixel"], [33447, "MDColorTable"], [33448, "MDLabName"], [33449, "MDSampleInfo"], [33450, "MDPrepDate"], [33451, "MDPrepTime"], [33452, "MDFileUnits"], [33589, "AdventScale"], [33590, "AdventRevision"], [33628, "UIC1Tag"], [33629, "UIC2Tag"], [33630, "UIC3Tag"], [33631, "UIC4Tag"], [33918, "IntergraphPacketData"], [33919, "IntergraphFlagRegisters"], [33921, "INGRReserved"], [34016, "Site"], [34017, "ColorSequence"], [34018, "IT8Header"], [34019, "RasterPadding"], [34020, "BitsPerRunLength"], [34021, "BitsPerExtendedRunLength"], [34022, "ColorTable"], [34023, "ImageColorIndicator"], [34024, "BackgroundColorIndicator"], [34025, "ImageColorValue"], [34026, "BackgroundColorValue"], [34027, "PixelIntensityRange"], [34028, "TransparencyIndicator"], [34029, "ColorCharacterization"], [34030, "HCUsage"], [34031, "TrapIndicator"], [34032, "CMYKEquivalent"], [34152, "AFCP_IPTC"], [34232, "PixelMagicJBIGOptions"], [34263, "JPLCartoIFD"], [34306, "WB_GRGBLevels"], [34310, "LeafData"], [34687, "TIFF_FXExtensions"], [34688, "MultiProfiles"], [34689, "SharedData"], [34690, "T88Options"], [34732, "ImageLayer"], [34750, "JBIGOptions"], [34856, "Opto-ElectricConvFactor"], [34857, "Interlace"], [34908, "FaxRecvParams"], [34909, "FaxSubAddress"], [34910, "FaxRecvTime"], [34929, "FedexEDR"], [34954, "LeafSubIFD"], [37387, "FlashEnergy"], [37388, "SpatialFrequencyResponse"], [37389, "Noise"], [37390, "FocalPlaneXResolution"], [37391, "FocalPlaneYResolution"], [37392, "FocalPlaneResolutionUnit"], [37397, "ExposureIndex"], [37398, "TIFF-EPStandardID"], [37399, "SensingMethod"], [37434, "CIP3DataFile"], [37435, "CIP3Sheet"], [37436, "CIP3Side"], [37439, "StoNits"], [37679, "MSDocumentText"], [37680, "MSPropertySetStorage"], [37681, "MSDocumentTextPosition"], [37724, "ImageSourceData"], [40965, "InteropIFD"], [40976, "SamsungRawPointersOffset"], [40977, "SamsungRawPointersLength"], [41217, "SamsungRawByteOrder"], [41218, "SamsungRawUnknown"], [41484, "SpatialFrequencyResponse"], [41485, "Noise"], [41489, "ImageNumber"], [41490, "SecurityClassification"], [41491, "ImageHistory"], [41494, "TIFF-EPStandardID"], [41995, "DeviceSettingDescription"], [42112, "GDALMetadata"], [42113, "GDALNoData"], [44992, "ExpandSoftware"], [44993, "ExpandLens"], [44994, "ExpandFilm"], [44995, "ExpandFilterLens"], [44996, "ExpandScanner"], [44997, "ExpandFlashLamp"], [46275, "HasselbladRawImage"], [48129, "PixelFormat"], [48130, "Transformation"], [48131, "Uncompressed"], [48132, "ImageType"], [48256, "ImageWidth"], [48257, "ImageHeight"], [48258, "WidthResolution"], [48259, "HeightResolution"], [48320, "ImageOffset"], [48321, "ImageByteCount"], [48322, "AlphaOffset"], [48323, "AlphaByteCount"], [48324, "ImageDataDiscard"], [48325, "AlphaDataDiscard"], [50215, "OceScanjobDesc"], [50216, "OceApplicationSelector"], [50217, "OceIDNumber"], [50218, "OceImageLogic"], [50255, "Annotations"], [50459, "HasselbladExif"], [50547, "OriginalFileName"], [50560, "USPTOOriginalContentType"], [50656, "CR2CFAPattern"], [50710, "CFAPlaneColor"], [50711, "CFALayout"], [50712, "LinearizationTable"], [50713, "BlackLevelRepeatDim"], [50714, "BlackLevel"], [50715, "BlackLevelDeltaH"], [50716, "BlackLevelDeltaV"], [50717, "WhiteLevel"], [50718, "DefaultScale"], [50719, "DefaultCropOrigin"], [50720, "DefaultCropSize"], [50733, "BayerGreenSplit"], [50737, "ChromaBlurRadius"], [50738, "AntiAliasStrength"], [50752, "RawImageSegmentation"], [50780, "BestQualityScale"], [50784, "AliasLayerMetadata"], [50829, "ActiveArea"], [50830, "MaskedAreas"], [50935, "NoiseReductionApplied"], [50974, "SubTileBlockSize"], [50975, "RowInterleaveFactor"], [51008, "OpcodeList1"], [51009, "OpcodeList2"], [51022, "OpcodeList3"], [51041, "NoiseProfile"], [51114, "CacheVersion"], [51125, "DefaultUserCrop"], [51157, "NikonNEFInfo"], [65024, "KdcIFD"]];
E(N, "ifd0", At), E(N, "exif", At), B(G, "gps", [[23, { M: "Magnetic North", T: "True North" }], [25, { K: "Kilometers", M: "Miles", N: "Nautical Miles" }]]);
class Dt extends ge {
static canHandle(e2, t2) {
return 224 === e2.getUint8(t2 + 1) && 1246120262 === e2.getUint32(t2 + 4) && 0 === e2.getUint8(t2 + 8);
}
parse() {
return this.parseTags(), this.translate(), this.output;
}
parseTags() {
this.raw = /* @__PURE__ */ new Map([[0, this.chunk.getUint16(0)], [2, this.chunk.getUint8(2)], [3, this.chunk.getUint16(3)], [5, this.chunk.getUint16(5)], [7, this.chunk.getUint8(7)], [8, this.chunk.getUint8(8)]]);
}
}
f(Dt, "type", "jfif"), f(Dt, "headerLength", 9), A.set("jfif", Dt), B(N, "jfif", [[0, "JFIFVersion"], [2, "ResolutionUnit"], [3, "XResolution"], [5, "YResolution"], [7, "ThumbnailWidth"], [8, "ThumbnailHeight"]]);
class Ot extends ge {
parse() {
return this.parseTags(), this.translate(), this.output;
}
parseTags() {
this.raw = new Map([[0, this.chunk.getUint32(0)], [4, this.chunk.getUint32(4)], [8, this.chunk.getUint8(8)], [9, this.chunk.getUint8(9)], [10, this.chunk.getUint8(10)], [11, this.chunk.getUint8(11)], [12, this.chunk.getUint8(12)], ...Array.from(this.raw)]);
}
}
f(Ot, "type", "ihdr"), A.set("ihdr", Ot), B(N, "ihdr", [[0, "ImageWidth"], [4, "ImageHeight"], [8, "BitDepth"], [9, "ColorType"], [10, "Compression"], [11, "Filter"], [12, "Interlace"]]), B(G, "ihdr", [[9, { 0: "Grayscale", 2: "RGB", 3: "Palette", 4: "Grayscale with Alpha", 6: "RGB with Alpha", DEFAULT: "Unknown" }], [10, { 0: "Deflate/Inflate", DEFAULT: "Unknown" }], [11, { 0: "Adaptive", DEFAULT: "Unknown" }], [12, { 0: "Noninterlaced", 1: "Adam7 Interlace", DEFAULT: "Unknown" }]]);
const xt = "\0\0\0\0";
class vt extends ge {
static canHandle(e2, t2) {
return 226 === e2.getUint8(t2 + 1) && 1229144927 === e2.getUint32(t2 + 4);
}
static findPosition(e2, t2) {
let i2 = super.findPosition(e2, t2);
return i2.chunkNumber = e2.getUint8(t2 + 16), i2.chunkCount = e2.getUint8(t2 + 17), i2.multiSegment = i2.chunkCount > 1, i2;
}
static handleMultiSegments(e2) {
return (function(e3) {
let t2 = (function(e4) {
let t3 = e4[0].constructor, i2 = 0;
for (let t4 of e4) i2 += t4.length;
let n2 = new t3(i2), s2 = 0;
for (let t4 of e4) n2.set(t4, s2), s2 += t4.length;
return n2;
})(e3.map(((e4) => e4.chunk.toUint8())));
return new I(t2);
})(e2);
}
parse() {
return this.raw = /* @__PURE__ */ new Map(), this.parseHeader(), this.parseTags(), this.translate(), this.output;
}
parseHeader() {
let { raw: e2 } = this;
this.chunk.byteLength < 84 && m("ICC header is too short");
for (let [t2, i2] of Object.entries(Mt)) {
t2 = parseInt(t2, 10);
let n2 = i2(this.chunk, t2);
n2 !== xt && e2.set(t2, n2);
}
}
parseTags() {
let e2, t2, i2, n2, s2, { raw: r2 } = this, a2 = this.chunk.getUint32(128), o2 = 132, l2 = this.chunk.byteLength;
for (; a2--; ) {
if (e2 = this.chunk.getString(o2, 4), t2 = this.chunk.getUint32(o2 + 4), i2 = this.chunk.getUint32(o2 + 8), n2 = this.chunk.getString(t2, 4), t2 + i2 > l2) return void console.warn("reached the end of the first ICC chunk. Enable options.tiff.multiSegment to read all ICC segments.");
s2 = this.parseTag(n2, t2, i2), void 0 !== s2 && s2 !== xt && r2.set(e2, s2), o2 += 12;
}
}
parseTag(e2, t2, i2) {
switch (e2) {
case "desc":
return this.parseDesc(t2);
case "mluc":
return this.parseMluc(t2);
case "text":
return this.parseText(t2, i2);
case "sig ":
return this.parseSig(t2);
}
if (!(t2 + i2 > this.chunk.byteLength)) return this.chunk.getUint8Array(t2, i2);
}
parseDesc(e2) {
let t2 = this.chunk.getUint32(e2 + 8) - 1;
return S(this.chunk.getString(e2 + 12, t2));
}
parseText(e2, t2) {
return S(this.chunk.getString(e2 + 8, t2 - 8));
}
parseSig(e2) {
return S(this.chunk.getString(e2 + 8, 4));
}
parseMluc(e2) {
let { chunk: t2 } = this, i2 = t2.getUint32(e2 + 8), n2 = t2.getUint32(e2 + 12), s2 = e2 + 16, r2 = [];
for (let a2 = 0; a2 < i2; a2++) {
let i3 = t2.getString(s2 + 0, 2), a3 = t2.getString(s2 + 2, 2), o2 = t2.getUint32(s2 + 4), l2 = t2.getUint32(s2 + 8) + e2, h2 = S(t2.getUnicodeString(l2, o2));
r2.push({ lang: i3, country: a3, text: h2 }), s2 += n2;
}
return 1 === i2 ? r2[0].text : r2;
}
translateValue(e2, t2) {
return "string" == typeof e2 ? t2[e2] || t2[e2.toLowerCase()] || e2 : t2[e2] || e2;
}
}
f(vt, "type", "icc"), f(vt, "multiSegment", true), f(vt, "headerLength", 18);
const Mt = { 4: Rt, 8: function(e2, t2) {
return [e2.getUint8(t2), e2.getUint8(t2 + 1) >> 4, e2.getUint8(t2 + 1) % 16].map(((e3) => e3.toString(10))).join(".");
}, 12: Rt, 16: Rt, 20: Rt, 24: function(e2, t2) {
const i2 = e2.getUint16(t2), n2 = e2.getUint16(t2 + 2) - 1, s2 = e2.getUint16(t2 + 4), r2 = e2.getUint16(t2 + 6), a2 = e2.getUint16(t2 + 8), o2 = e2.getUint16(t2 + 10);
return new Date(Date.UTC(i2, n2, s2, r2, a2, o2));
}, 36: Rt, 40: Rt, 48: Rt, 52: Rt, 64: (e2, t2) => e2.getUint32(t2), 80: Rt };
function Rt(e2, t2) {
return S(e2.getString(t2, 4));
}
A.set("icc", vt), B(N, "icc", [[4, "ProfileCMMType"], [8, "ProfileVersion"], [12, "ProfileClass"], [16, "ColorSpaceData"], [20, "ProfileConnectionSpace"], [24, "ProfileDateTime"], [36, "ProfileFileSignature"], [40, "PrimaryPlatform"], [44, "CMMFlags"], [48, "DeviceManufacturer"], [52, "DeviceModel"], [56, "DeviceAttributes"], [64, "RenderingIntent"], [68, "ConnectionSpaceIlluminant"], [80, "ProfileCreator"], [84, "ProfileID"], ["Header", "ProfileHeader"], ["MS00", "WCSProfiles"], ["bTRC", "BlueTRC"], ["bXYZ", "BlueMatrixColumn"], ["bfd", "UCRBG"], ["bkpt", "MediaBlackPoint"], ["calt", "CalibrationDateTime"], ["chad", "ChromaticAdaptation"], ["chrm", "Chromaticity"], ["ciis", "ColorimetricIntentImageState"], ["clot", "ColorantTableOut"], ["clro", "ColorantOrder"], ["clrt", "ColorantTable"], ["cprt", "ProfileCopyright"], ["crdi", "CRDInfo"], ["desc", "ProfileDescription"], ["devs", "DeviceSettings"], ["dmdd", "DeviceModelDesc"], ["dmnd", "DeviceMfgDesc"], ["dscm", "ProfileDescriptionML"], ["fpce", "FocalPlaneColorimetryEstimates"], ["gTRC", "GreenTRC"], ["gXYZ", "GreenMatrixColumn"], ["gamt", "Gamut"], ["kTRC", "GrayTRC"], ["lumi", "Luminance"], ["meas", "Measurement"], ["meta", "Metadata"], ["mmod", "MakeAndModel"], ["ncl2", "NamedColor2"], ["ncol", "NamedColor"], ["ndin", "NativeDisplayInfo"], ["pre0", "Preview0"], ["pre1", "Preview1"], ["pre2", "Preview2"], ["ps2i", "PS2RenderingIntent"], ["ps2s", "PostScript2CSA"], ["psd0", "PostScript2CRD0"], ["psd1", "PostScript2CRD1"], ["psd2", "PostScript2CRD2"], ["psd3", "PostScript2CRD3"], ["pseq", "ProfileSequenceDesc"], ["psid", "ProfileSequenceIdentifier"], ["psvm", "PS2CRDVMSize"], ["rTRC", "RedTRC"], ["rXYZ", "RedMatrixColumn"], ["resp", "OutputResponse"], ["rhoc", "ReflectionHardcopyOrigColorimetry"], ["rig0", "PerceptualRenderingIntentGamut"], ["rig2", "SaturationRenderingIntentGamut"], ["rpoc", "ReflectionPrintOutputColorimetry"], ["sape", "SceneAppearanceEstimates"], ["scoe", "SceneColorimetryEstimates"], ["scrd", "ScreeningDesc"], ["scrn", "Screening"], ["targ", "CharTarget"], ["tech", "Technology"], ["vcgt", "VideoCardGamma"], ["view", "ViewingConditions"], ["vued", "ViewingCondDesc"], ["wtpt", "MediaWhitePoint"]]);
const Lt = { "4d2p": "Erdt Systems", AAMA: "Aamazing Technologies", ACER: "Acer", ACLT: "Acolyte Color Research", ACTI: "Actix Sytems", ADAR: "Adara Technology", ADBE: "Adobe", ADI: "ADI Systems", AGFA: "Agfa Graphics", ALMD: "Alps Electric", ALPS: "Alps Electric", ALWN: "Alwan Color Expertise", AMTI: "Amiable Technologies", AOC: "AOC International", APAG: "Apago", APPL: "Apple Computer", AST: "AST", "AT&T": "AT&T", BAEL: "BARBIERI electronic", BRCO: "Barco NV", BRKP: "Breakpoint", BROT: "Brother", BULL: "Bull", BUS: "Bus Computer Systems", "C-IT": "C-Itoh", CAMR: "Intel", CANO: "Canon", CARR: "Carroll Touch", CASI: "Casio", CBUS: "Colorbus PL", CEL: "Crossfield", CELx: "Crossfield", CGS: "CGS Publishing Technologies International", CHM: "Rochester Robotics", CIGL: "Colour Imaging Group, London", CITI: "Citizen", CL00: "Candela", CLIQ: "Color IQ", CMCO: "Chromaco", CMiX: "CHROMiX", COLO: "Colorgraphic Communications", COMP: "Compaq", COMp: "Compeq/Focus Technology", CONR: "Conrac Display Products", CORD: "Cordata Technologies", CPQ: "Compaq", CPRO: "ColorPro", CRN: "Cornerstone", CTX: "CTX International", CVIS: "ColorVision", CWC: "Fujitsu Laboratories", DARI: "Darius Technology", DATA: "Dataproducts", DCP: "Dry Creek Photo", DCRC: "Digital Contents Resource Center, Chung-Ang University", DELL: "Dell Computer", DIC: "Dainippon Ink and Chemicals", DICO: "Diconix", DIGI: "Digital", "DL&C": "Digital Light & Color", DPLG: "Doppelganger", DS: "Dainippon Screen", DSOL: "DOOSOL", DUPN: "DuPont", EPSO: "Epson", ESKO: "Esko-Graphics", ETRI: "Electronics and Telecommunications Research Institute", EVER: "Everex Systems", EXAC: "ExactCODE", Eizo: "Eizo", FALC: "Falco Data Products", FF: "Fuji Photo Film", FFEI: "FujiFilm Electronic Imaging", FNRD: "Fnord Software", FORA: "Fora", FORE: "Forefront Technology", FP: "Fujitsu", FPA: "WayTech Development", FUJI: "Fujitsu", FX: "Fuji Xerox", GCC: "GCC Technologies", GGSL: "Global Graphics Software", GMB: "Gretagmacbeth", GMG: "GMG", GOLD: "GoldStar Technology", GOOG: "Google", GPRT: "Giantprint", GTMB: "Gretagmacbeth", GVC: "WayTech Development", GW2K: "Sony", HCI: "HCI", HDM: "Heidelberger Druckmaschinen", HERM: "Hermes", HITA: "Hitachi America", HP: "Hewlett-Packard", HTC: "Hitachi", HiTi: "HiTi Digital", IBM: "IBM", IDNT: "Scitex", IEC: "Hewlett-Packard", IIYA: "Iiyama North America", IKEG: "Ikegami Electronics", IMAG: "Image Systems", IMI: "Ingram Micro", INTC: "Intel", INTL: "N/A (INTL)", INTR: "Intra Electronics", IOCO: "Iocomm International Technology", IPS: "InfoPrint Solutions Company", IRIS: "Scitex", ISL: "Ichikawa Soft Laboratory", ITNL: "N/A (ITNL)", IVM: "IVM", IWAT: "Iwatsu Electric", Idnt: "Scitex", Inca: "Inca Digital Printers", Iris: "Scitex", JPEG: "Joint Photographic Experts Group", JSFT: "Jetsoft Development", JVC: "JVC Information Products", KART: "Scitex", KFC: "KFC Computek Components", KLH: "KLH Computers", KMHD: "Konica Minolta", KNCA: "Konica", KODA: "Kodak", KYOC: "Kyocera", Kart: "Scitex", LCAG: "Leica", LCCD: "Leeds Colour", LDAK: "Left Dakota", LEAD: "Leading Technology", LEXM: "Lexmark International", LINK: "Link Computer", LINO: "Linotronic", LITE: "Lite-On", Leaf: "Leaf", Lino: "Linotronic", MAGC: "Mag Computronic", MAGI: "MAG Innovision", MANN: "Mannesmann", MICN: "Micron Technology", MICR: "Microtek", MICV: "Microvitec", MINO: "Minolta", MITS: "Mitsubishi Electronics America", MITs: "Mitsuba", MNLT: "Minolta", MODG: "Modgraph", MONI: "Monitronix", MONS: "Monaco Systems", MORS: "Morse Technology", MOTI: "Motive Systems", MSFT: "Microsoft", MUTO: "MUTOH INDUSTRIES", Mits: "Mitsubishi Electric", NANA: "NANAO", NEC: "NEC", NEXP: "NexPress Solutions", NISS: "Nissei Sangyo America", NKON: "Nikon", NONE: "none", OCE: "Oce Technologies", OCEC: "OceColor", OKI: "Oki", OKID: "Okidata", OKIP: "Okidata", OLIV: "Olivetti", OLYM: "Olympus", ONYX: "Onyx Graphics", OPTI: "Optiquest", PACK: "Packard Bell", PANA: "Matsushita Electric Industrial", PANT: "Pantone", PBN: "Packard Bell", PFU: "PFU", PHIL: "Philips Consumer Electronics", PNTX: "HOYA", POne: "Phase One A/S", PREM: "Premier Computer Innovations", PRIN: "Princeton Graphic Systems", PRIP: "Princeton Publishing Labs", QLUX: "Hong Kong", QMS: "QMS", QPCD: "QPcard AB", QUAD: "QuadLaser", QUME: "Qume", RADI: "Radius", RDDx: "Integrated Color Solutions", RDG: "Roland DG", REDM: "REDMS Group", RELI: "Relisys", RGMS: "Rolf Gierling Multitools", RICO: "Ricoh", RNLD: "Edmund Ronald", ROYA: "Royal", RPC: "Ricoh Printing Systems", RTL: "Royal Information Electronics", SAMP: "Sampo", SAMS: "Samsung", SANT: "Jaime Santana Pomares", SCIT: "Scitex", SCRN: "Dainippon Screen", SDP: "Scitex", SEC: "Samsung", SEIK: "Seiko Instruments", SEIk: "Seikosha", SGUY: "ScanGuy.com", SHAR: "Sharp Laboratories", SICC: "International Color Consortium", SONY: "Sony", SPCL: "SpectraCal", STAR: "Star", STC: "Sampo Technology", Scit: "Scitex", Sdp: "Scitex", Sony: "Sony", TALO: "Talon Technology", TAND: "Tandy", TATU: "Tatung", TAXA: "TAXAN America", TDS: "Tokyo Denshi Sekei", TECO: "TECO Information Systems", TEGR: "Tegra", TEKT: "Tektronix", TI: "Texas Instruments", TMKR: "TypeMaker", TOSB: "Toshiba", TOSH: "Toshiba", TOTK: "TOTOKU ELECTRIC", TRIU: "Triumph", TSBT: "Toshiba", TTX: "TTX Computer Products", TVM: "TVM Professional Monitor", TW: "TW Casper", ULSX: "Ulead Systems", UNIS: "Unisys", UTZF: "Utz Fehlau & Sohn", VARI: "Varityper", VIEW: "Viewsonic", VISL: "Visual communication", VIVO: "Vivo Mobile Communication", WANG: "Wang", WLBR: "Wilbur Imaging", WTG2: "Ware To Go", WYSE: "WYSE Technology", XERX: "Xerox", XRIT: "X-Rite", ZRAN: "Zoran", Zebr: "Zebra Technologies", appl: "Apple Computer", bICC: "basICColor", berg: "bergdesign", ceyd: "Integrated Color Solutions", clsp: "MacDermid ColorSpan", ds: "Dainippon Screen", dupn: "DuPont", ffei: "FujiFilm Electronic Imaging", flux: "FluxData", iris: "Scitex", kart: "Scitex", lcms: "Little CMS", lino: "Linotronic", none: "none", ob4d: "Erdt Systems", obic: "Medigraph", quby: "Qubyx Sarl", scit: "Scitex", scrn: "Dainippon Screen", sdp: "Scitex", siwi: "SIWI GRAFIKA", yxym: "YxyMaster" }, Ut = { scnr: "Scanner", mntr: "Monitor", prtr: "Printer", link: "Device Link", abst: "Abstract", spac: "Color Space Conversion Profile", nmcl: "Named Color", cenc: "ColorEncodingSpace profile", mid: "MultiplexIdentification profile", mlnk: "MultiplexLink profile", mvis: "MultiplexVisualization profile", nkpf: "Nikon Input Device Profile (NON-STANDARD!)" };
B(G, "icc", [[4, Lt], [12, Ut], [40, Object.assign({}, Lt, Ut)], [48, Lt], [80, Lt], [64, { 0: "Perceptual", 1: "Relative Colorimetric", 2: "Saturation", 3: "Absolute Colorimetric" }], ["tech", { amd: "Active Matrix Display", crt: "Cathode Ray Tube Display", kpcd: "Photo CD", pmd: "Passive Matrix Display", dcam: "Digital Camera", dcpj: "Digital Cinema Projector", dmpc: "Digital Motion Picture Camera", dsub: "Dye Sublimation Printer", epho: "Electrophotographic Printer", esta: "Electrostatic Printer", flex: "Flexography", fprn: "Film Writer", fscn: "Film Scanner", grav: "Gravure", ijet: "Ink Jet Printer", imgs: "Photo Image Setter", mpfr: "Motion Picture Film Recorder", mpfs: "Motion Picture Film Scanner", offs: "Offset Lithography", pjtv: "Projection Television", rpho: "Photographic Paper Printer", rscn: "Reflective Scanner", silk: "Silkscreen", twax: "Thermal Wax Printer", vidc: "Video Camera", vidm: "Video Monitor" }]]);
class Ft extends ge {
static canHandle(e2, t2, i2) {
return 237 === e2.getUint8(t2 + 1) && "Photoshop" === e2.getString(t2 + 4, 9) && void 0 !== this.containsIptc8bim(e2, t2, i2);
}
static headerLength(e2, t2, i2) {
let n2, s2 = this.containsIptc8bim(e2, t2, i2);
if (void 0 !== s2) return n2 = e2.getUint8(t2 + s2 + 7), n2 % 2 != 0 && (n2 += 1), 0 === n2 && (n2 = 4), s2 + 8 + n2;
}
static containsIptc8bim(e2, t2, i2) {
for (let n2 = 0; n2 < i2; n2++) if (this.isIptcSegmentHead(e2, t2 + n2)) return n2;
}
static isIptcSegmentHead(e2, t2) {
return 56 === e2.getUint8(t2) && 943868237 === e2.getUint32(t2) && 1028 === e2.getUint16(t2 + 4);
}
parse() {
let { raw: e2 } = this, t2 = this.chunk.byteLength - 1, i2 = false;
for (let n2 = 0; n2 < t2; n2++) if (28 === this.chunk.getUint8(n2) && 2 === this.chunk.getUint8(n2 + 1)) {
i2 = true;
let t3 = this.chunk.getUint16(n2 + 3), s2 = this.chunk.getUint8(n2 + 2), r2 = this.chunk.getLatin1String(n2 + 5, t3);
e2.set(s2, this.pluralizeValue(e2.get(s2), r2)), n2 += 4 + t3;
} else if (i2) break;
return this.translate(), this.output;
}
pluralizeValue(e2, t2) {
return void 0 !== e2 ? e2 instanceof Array ? (e2.push(t2), e2) : [e2, t2] : t2;
}
}
f(Ft, "type", "iptc"), f(Ft, "translateValues", false), f(Ft, "reviveValues", false), A.set("iptc", Ft), B(N, "iptc", [[0, "ApplicationRecordVersion"], [3, "ObjectTypeReference"], [4, "ObjectAttributeReference"], [5, "ObjectName"], [7, "EditStatus"], [8, "EditorialUpdate"], [10, "Urgency"], [12, "SubjectReference"], [15, "Category"], [20, "SupplementalCategories"], [22, "FixtureIdentifier"], [25, "Keywords"], [26, "ContentLocationCode"], [27, "ContentLocationName"], [30, "ReleaseDate"], [35, "ReleaseTime"], [37, "ExpirationDate"], [38, "ExpirationTime"], [40, "SpecialInstructions"], [42, "ActionAdvised"], [45, "ReferenceService"], [47, "ReferenceDate"], [50, "ReferenceNumber"], [55, "DateCreated"], [60, "TimeCreated"], [62, "DigitalCreationDate"], [63, "DigitalCreationTime"], [65, "OriginatingProgram"], [70, "ProgramVersion"], [75, "ObjectCycle"], [80, "Byline"], [85, "BylineTitle"], [90, "City"], [92, "Sublocation"], [95, "State"], [100, "CountryCode"], [101, "Country"], [103, "OriginalTransmissionReference"], [105, "Headline"], [110, "Credit"], [115, "Source"], [116, "CopyrightNotice"], [118, "Contact"], [120, "Caption"], [121, "LocalCaption"], [122, "Writer"], [125, "RasterizedCaption"], [130, "ImageType"], [131, "ImageOrientation"], [135, "LanguageIdentifier"], [150, "AudioType"], [151, "AudioSamplingRate"], [152, "AudioSamplingResolution"], [153, "AudioDuration"], [154, "AudioOutcue"], [184, "JobID"], [185, "MasterDocumentID"], [186, "ShortDocumentID"], [187, "UniqueDocumentID"], [188, "OwnerID"], [200, "ObjectPreviewFileFormat"], [201, "ObjectPreviewFileVersion"], [202, "ObjectPreviewData"], [221, "Prefs"], [225, "ClassifyState"], [228, "SimilarityIndex"], [230, "DocumentNotes"], [231, "DocumentHistory"], [232, "ExifCameraInfo"], [255, "CatalogSets"]]), B(G, "iptc", [[10, { 0: "0 (reserved)", 1: "1 (most urgent)", 2: "2", 3: "3", 4: "4", 5: "5 (normal urgency)", 6: "6", 7: "7", 8: "8 (least urgent)", 9: "9 (user-defined priority)" }], [75, { a: "Morning", b: "Both Morning and Evening", p: "Evening" }], [131, { L: "Landscape", P: "Portrait", S: "Square" }]]), e.Exifr = ce, e.Options = oe, e.allFormatters = ie, e.chunkedProps = $, e.createDictionary = B, e.default = ft, e.extendDictionary = E, e.fetchUrlAsArrayBuffer = L, e.fileParsers = T, e.fileReaders = D, e.gps = De, e.gpsOnlyOptions = Ae, e.inheritables = te, e.orientation = Re, e.orientationOnlyOptions = Me, e.otherSegments = J, e.parse = fe, e.readBlobAsArrayBuffer = U, e.rotation = Ue, e.rotations = Le, e.segmentParsers = A, e.segments = q, e.segmentsAndBlocks = Z, e.sidecar = async function(e2, t2, i2) {
let n2 = new oe(t2);
n2.chunked = false, void 0 === i2 && "string" == typeof e2 && (i2 = (function(e3) {
let t3 = e3.toLowerCase().split(".").pop();
if (/* @__PURE__ */ (function(e4) {
return "exif" === e4 || "tiff" === e4 || "tif" === e4;
})(t3)) return "tiff";
if (dt.includes(t3)) return t3;
})(e2));
let s2 = await x(e2, n2);
if (i2) {
if (dt.includes(i2)) return gt(i2, s2, n2);
m("Invalid segment type");
} else {
if ((function(e3) {
let t3 = e3.getString(0, 50).trim();
return t3.includes("<?xpacket") || t3.includes("<x:");
})(s2)) return gt("xmp", s2, n2);
for (let [e3] of A) {
if (!dt.includes(e3)) continue;
let t3 = await gt(e3, s2, n2).catch(pt);
if (t3) return t3;
}
m("Unknown file format");
}
}, e.tagKeys = N, e.tagRevivers = V, e.tagValues = G, e.thumbnail = xe, e.thumbnailOnlyOptions = Oe, e.thumbnailUrl = ve, e.tiffBlocks = Q, e.tiffExtractables = ee, Object.defineProperty(e, "__esModule", { value: true });
}));
}
});
function getModuleDir() {
try {
return __dirname;
} catch {
return process.cwd();
}
}
function readJsonFile(filePath) {
try {
const raw = fs$1.readFileSync(filePath, "utf8");
return JSON.parse(raw);
} catch {
return null;
}
}
function findUpwards(startDir, fileName, maxDepth = 8) {
let dir = startDir;
for (let i = 0; i < maxDepth; i++) {
const candidate = path$1.join(dir, fileName);
if (fs$1.existsSync(candidate)) return candidate;
const parent = path$1.dirname(dir);
if (parent === dir) break;
dir = parent;
}
return null;
}
function getPluginMeta() {
const moduleDir = getModuleDir();
const packageJsonPath = findUpwards(moduleDir, "package.json");
const manifestJsonPath = findUpwards(moduleDir, "manifest.json");
const packageJson = packageJsonPath ? readJsonFile(packageJsonPath) : null;
const manifestJson = manifestJsonPath ? readJsonFile(manifestJsonPath) : null;
const version = String(packageJson?.version || "unknown");
const owner = String(manifestJson?.owner || "").trim();
const name = String(manifestJson?.name || "").trim();
const revisionsUrl = owner && name ? `https://raw.githubusercontent.com/${owner}/${name}-docs/main/docs/CHANGELOG.md` : "https://raw.githubusercontent.com";
const pluginIdentifier = owner && name ? `${owner}/${name}` : name ? name : "unknown";
return { version, owner, name, revisionsUrl, pluginIdentifier };
}
function formatToolMetaBlock(meta = getPluginMeta()) {
return `Plugin-Identifier: ${meta.pluginIdentifier}
Plugin version: ${meta.version}`;
}
// src/capabilities.ts
var cachedSelfPluginIdentifier;
function getSelfPluginIdentifier() {
if (cachedSelfPluginIdentifier !== void 0)
return cachedSelfPluginIdentifier;
try {
const pluginIdentifier = getPluginMeta().pluginIdentifier;
if (pluginIdentifier && pluginIdentifier !== "unknown") {
cachedSelfPluginIdentifier = pluginIdentifier;
return cachedSelfPluginIdentifier;
}
} catch (err) {
console.warn(
"[Capabilities] Failed to resolve pluginIdentifier from manifest.json.",
err
);
cachedSelfPluginIdentifier = null;
return cachedSelfPluginIdentifier;
}
console.warn(
"[Capabilities] manifest.json did not contain valid owner/name."
);
cachedSelfPluginIdentifier = null;
return cachedSelfPluginIdentifier;
}
sdk.createConfigSchematics().field(
"model",
"string",
{
displayName: "Agent Model",
subtitle: "Enter the vision model to use as orchestrator. Default: Qwen3.6 35B \u0410\u0417\u0412.",
placeholder: "qwen/qwen3.6-35b-a3b"
},
"qwen/qwen3.6-35b-a3b"
).field(
"visionPromotionPersistent",
"boolean",
{
displayName: "Vision Promotion: Persistent",
subtitle: "ON: promote up to 5 attachments + 4 variants every turn. OFF: promote only when new.",
engineDoesNotSupport: true
},
false
).field(
"logRequests",
"boolean",
{
displayName: "Debug: Log requests/response",
subtitle: "Logs full request/response JSON; may include sensitive data.",
engineDoesNotSupport: true
},
false
).field(
"debugPromotion",
"boolean",
{
displayName: "Debug: Media promotion",
subtitle: "Verbose logs for media state, previews and cleanup.",
engineDoesNotSupport: true
},
false
).field(
"debugChunks",
"boolean",
{
displayName: "Debug: Stream chunk logs",
subtitle: "Log raw streaming chunks to console (verbose).",
engineDoesNotSupport: true
},
false
).build();
sdk.createConfigSchematics().field(
"baseUrl",
"string",
{
displayName: "LM Studio API base-URL",
subtitle: "Local LM Studio server base-URL. Default: http://127.0.0.1:1234/v1",
placeholder: "http://127.0.0.1:1234/v1"
},
"http://127.0.0.1:1234/v1"
).field(
"apiKey",
"string",
{
displayName: "(Optional) API Key",
subtitle: "Only needed if your LM Studio server requires authentication.",
isProtected: true,
placeholder: "sk-..."
},
""
).field(
"PREVIEW_IN_CHAT",
"boolean",
{
displayName: "Simple Previews in Chat",
subtitle: "When enabled, tool responses include client-based image previews. Not recommended for advanced functionality.",
engineDoesNotSupport: false
},
false
).field(
"unloadAgentModelDuringRender",
"boolean",
{
displayName: "Unload Agent Model During Render",
subtitle: "When enabled, unloads the agent model from VRAM before long renders (image2image, edit, text2video, image2video). Only applies to local LM Studio instances.",
engineDoesNotSupport: false
},
true
).field(
"DRAW_THINGS_HOST",
"string",
{
displayName: "Draw Things Host",
subtitle: "Hostname or IP of the Draw Things backend server.",
placeholder: "127.0.0.1"
},
"127.0.0.1"
).field(
"DRAW_THINGS_HTTP_PORT",
"numeric",
{
displayName: "Draw Things HTTP Port",
subtitle: "HTTP API port (default: 7860)."
},
7860
).field(
"DRAW_THINGS_GRPC_PORT",
"numeric",
{
displayName: "Draw Things gRPC Port",
subtitle: "gRPC port (default: 7859)."
},
7859
).field(
"embedPngMetadata",
"boolean",
{
displayName: "Embed Metadata in PNGs",
subtitle: "Write generation parameters (prompt, model, seed, LoRAs, sources) into saved PNGs as XMP metadata. Compatible with draw-things-index and find-image.",
engineDoesNotSupport: false
},
true
).field(
"customConfigsPath",
"string",
{
displayName: "Custom Configs Path",
subtitle: "Path to custom_configs.json from Draw Things. Change to: `none` to disable.",
placeholder: "~/Library/Containers/com.liuliu.draw-things/Data/Documents/Models/custom_configs.json",
engineDoesNotSupport: false
},
"~/Library/Containers/com.liuliu.draw-things/Data/Documents/Models/custom_configs.json"
).field(
"HTTP_SERVER_PORT",
"numeric",
{
displayName: "Local HTTP Server Port",
subtitle: "Port for serving generated images over localhost (default: 54760).",
engineDoesNotSupport: true
},
54760
).build();
// src/helpers/projectUriResolver.ts
__toESM(require_flatbuffers());
// src/interfaces/thumbnail-history-node.ts
__toESM(require_flatbuffers());
function localTimestamp(d = /* @__PURE__ */ new Date()) {
const pad = (n, len = 2) => String(n).padStart(len, "0");
const year = d.getFullYear();
const month = pad(d.getMonth() + 1);
const day = pad(d.getDate());
const hours = pad(d.getHours());
const minutes = pad(d.getMinutes());
const seconds = pad(d.getSeconds());
const millis = pad(d.getMilliseconds(), 3);
const tzOffset = -d.getTimezoneOffset();
const tzSign = tzOffset >= 0 ? "+" : "-";
const tzHours = pad(Math.floor(Math.abs(tzOffset) / 60));
const tzMins = pad(Math.abs(tzOffset) % 60);
return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}.${millis}${tzSign}${tzHours}:${tzMins}`;
}
async function readState$1(chatWd) {
const p = path.join(chatWd, "chat_media_state.json");
try {
const raw = await fs.promises.readFile(p, "utf-8");
const json = JSON.parse(raw);
return normalizeState(json);
} catch {
return {
attachments: [],
variants: [],
pictures: [],
images: [],
counters: {}
};
}
}
async function writeStateAtomic(chatWd, state) {
const tmp = path.join(chatWd, "chat_media_state.json.tmp");
const dst = path.join(chatWd, "chat_media_state.json");
if (Array.isArray(state.attachments) && state.attachments.length > 1) {
state.attachments.sort((a, b) => (a.a ?? 0) - (b.a ?? 0));
}
if (Array.isArray(state.variants) && state.variants.length > 1) {
state.variants.sort((a, b) => (a.v ?? 0) - (b.v ?? 0));
}
if (Array.isArray(state.pictures) && state.pictures.length > 1) {
state.pictures.sort((a, b) => (a.p ?? 0) - (b.p ?? 0));
}
if (Array.isArray(state.images) && state.images.length > 1) {
state.images.sort((a, b) => (a.i ?? 0) - (b.i ?? 0));
}
let pretty = JSON.stringify(state, null, 2);
pretty = pretty.replace(
/"(lastPromotedAttachmentAs|lastPromotedVariantVs|lastPromotedImageIs|lastPixelPromotedAttachmentAs|lastPixelPromotedVariantVs|lastPixelPromotedImageIs|injectedMarkdown)":\s*\[\s*\n([\s\S]*?)\n\s*\]/g,
(match, key, content) => {
const items = content.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
return `"${key}": [${items.join(", ")}]`;
}
);
await fs.promises.writeFile(tmp, pretty, "utf-8");
await fs.promises.rename(tmp, dst);
}
function normalizeState(s) {
let lastPromotedAttachmentAs;
if (Array.isArray(s?.lastPromotedAttachmentAs)) {
lastPromotedAttachmentAs = s.lastPromotedAttachmentAs.filter(
(x) => typeof x === "number" && Number.isFinite(x)
);
} else if (Array.isArray(s?.lastPromotedAttachmentNs)) {
lastPromotedAttachmentAs = s.lastPromotedAttachmentNs.filter(
(x) => typeof x === "number" && Number.isFinite(x)
);
} else if (typeof s?.lastPromotedAttachmentN === "number") {
lastPromotedAttachmentAs = [s.lastPromotedAttachmentN];
} else if (typeof s?.lastPromotedAttachmentA === "number") {
lastPromotedAttachmentAs = [s.lastPromotedAttachmentA];
}
let lastPromotedVariantVs;
if (Array.isArray(s?.lastPromotedVariantVs)) {
lastPromotedVariantVs = s.lastPromotedVariantVs.filter(
(x) => typeof x === "number" && Number.isFinite(x)
);
}
let lastPromotedImageIs;
if (Array.isArray(s?.lastPromotedImageIs)) {
lastPromotedImageIs = s.lastPromotedImageIs.filter(
(x) => typeof x === "number" && Number.isFinite(x)
);
}
let lastPixelPromotedAttachmentAs;
if (Array.isArray(s?.lastPixelPromotedAttachmentAs)) {
lastPixelPromotedAttachmentAs = s.lastPixelPromotedAttachmentAs.filter(
(x) => typeof x === "number" && Number.isFinite(x)
);
} else if (Array.isArray(s?.lastPixelPromotedAttachmentNs)) {
lastPixelPromotedAttachmentAs = s.lastPixelPromotedAttachmentNs.filter(
(x) => typeof x === "number" && Number.isFinite(x)
);
}
let lastPixelPromotedVariantVs;
if (Array.isArray(s?.lastPixelPromotedVariantVs)) {
lastPixelPromotedVariantVs = s.lastPixelPromotedVariantVs.filter(
(x) => typeof x === "number" && Number.isFinite(x)
);
}
let lastPixelPromotedImageIs;
if (Array.isArray(s?.lastPixelPromotedImageIs)) {
lastPixelPromotedImageIs = s.lastPixelPromotedImageIs.filter(
(x) => typeof x === "number" && Number.isFinite(x)
);
}
const normalizeNumberArray = (v) => {
if (!Array.isArray(v)) return void 0;
const a = v.filter((x) => typeof x === "number" && Number.isFinite(x)).map((x) => Math.floor(x)).filter((x) => x > 0);
if (!a.length) return void 0;
return Array.from(new Set(a)).sort((x, y) => x - y);
};
const pendingReviewPromotion = (() => {
const pr = s?.pendingReviewPromotion;
if (!pr || typeof pr !== "object") return void 0;
const requestedAt = typeof pr.requestedAt === "string" && pr.requestedAt.trim() ? String(pr.requestedAt) : "";
const targets = pr.targets && typeof pr.targets === "object" ? pr.targets : null;
if (!requestedAt || !targets) return void 0;
const a = normalizeNumberArray(targets.a);
const v = normalizeNumberArray(targets.v);
const i = normalizeNumberArray(targets.i);
const p = normalizeNumberArray(targets.p);
if (!a && !v && !i && !p) return void 0;
const ttlMs = typeof pr.ttlMs === "number" && Number.isFinite(pr.ttlMs) && pr.ttlMs > 0 ? pr.ttlMs : void 0;
return {
requestedAt,
requestedByToolCallId: typeof pr.requestedByToolCallId === "string" && pr.requestedByToolCallId.trim() ? String(pr.requestedByToolCallId) : void 0,
reason: typeof pr.reason === "string" && pr.reason.trim() ? String(pr.reason) : void 0,
ttlMs,
targets: {
a,
v,
i,
p
}
};
})();
const pendingSequenceReview = (() => {
const ps = s?.pendingSequenceReview;
if (!ps || typeof ps !== "object") return void 0;
const requestedAt = typeof ps.requestedAt === "string" && ps.requestedAt.trim() ? String(ps.requestedAt) : "";
const movAbs = typeof ps.movAbs === "string" && ps.movAbs.trim() ? String(ps.movAbs) : "";
const variant = typeof ps.variant === "number" && Number.isFinite(ps.variant) ? Math.floor(ps.variant) : 0;
if (!requestedAt || !movAbs || variant <= 0) return void 0;
const fps = typeof ps.fps === "number" && Number.isFinite(ps.fps) && ps.fps > 0 ? ps.fps : 2;
const ttlMs = typeof ps.ttlMs === "number" && Number.isFinite(ps.ttlMs) && ps.ttlMs > 0 ? ps.ttlMs : void 0;
return {
requestedAt,
movAbs,
variant,
variantLabel: typeof ps.variantLabel === "string" && ps.variantLabel.trim() ? String(ps.variantLabel) : void 0,
fps,
ttlMs
};
})();
const n = {
attachments: Array.isArray(s?.attachments) ? s.attachments : [],
variants: Array.isArray(s?.variants) ? s.variants : [],
pictures: Array.isArray(s?.pictures) ? s.pictures : [],
images: Array.isArray(s?.images) ? s.images : [],
pendingReviewPromotion,
pendingSequenceReview,
lastEvent: s?.lastEvent,
lastCanvasNotation: typeof s?.lastCanvasNotation === "string" ? s.lastCanvasNotation : void 0,
lastCanvasAt: typeof s?.lastCanvasAt === "string" ? s.lastCanvasAt : void 0,
counters: typeof s?.counters === "object" && s?.counters ? s.counters : {},
injectedMarkdown: Array.isArray(s?.injectedMarkdown) ? s.injectedMarkdown : void 0,
injectedContent: Array.isArray(s?.injectedContent) ? s.injectedContent.filter((x) => typeof x === "string" && x.trim()) : void 0,
lastVariantsTs: typeof s?.lastVariantsTs === "string" ? s.lastVariantsTs : void 0,
lastPromotedTs: typeof s?.lastPromotedTs === "string" ? s.lastPromotedTs : void 0,
lastPromotedAttachmentAs,
// Keep deprecated field for backward compat during transition
lastPromotedAttachmentA: typeof s?.lastPromotedAttachmentA === "number" ? s.lastPromotedAttachmentA : typeof s?.lastPromotedAttachmentN === "number" ? s.lastPromotedAttachmentN : void 0,
lastPromotedVariantVs,
lastPromotedImageIs,
lastPixelPromotedAt: typeof s?.lastPixelPromotedAt === "string" ? s.lastPixelPromotedAt : void 0,
lastPixelPromotedAttachmentAs,
lastPixelPromotedVariantVs,
lastPixelPromotedImageIs,
forcePixelPromotionNextTurn: typeof s?.forcePixelPromotionNextTurn === "boolean" ? s.forcePixelPromotionNextTurn : void 0,
forcePixelPromotionSetAt: typeof s?.forcePixelPromotionSetAt === "string" ? s.forcePixelPromotionSetAt : void 0,
forcePixelPromotionReason: typeof s?.forcePixelPromotionReason === "string" ? s.forcePixelPromotionReason : void 0,
lastSsotMessageCount: typeof s?.lastSsotMessageCount === "number" && Number.isFinite(s.lastSsotMessageCount) ? s.lastSsotMessageCount : void 0
};
if (n.attachments.length > 1) {
n.attachments.sort((a, b) => (a.a ?? 0) - (b.a ?? 0));
}
if (n.variants.length > 1) {
n.variants.sort((a, b) => (a.v ?? 0) - (b.v ?? 0));
}
if (n.pictures.length > 1) {
n.pictures.sort((a, b) => (a.p ?? 0) - (b.p ?? 0));
}
if (n.images.length > 1) {
n.images.sort((a, b) => (a.i ?? 0) - (b.i ?? 0));
}
return n;
}
function normalizeString(val) {
return typeof val === "string" ? String(val) : void 0;
}
function normalizeNumber(val) {
return typeof val === "number" && Number.isFinite(val) ? val : void 0;
}
var COMMON_MEDIA_KEYS = /* @__PURE__ */ new Set([
"filename",
"preview",
"sourceTool",
"pluginId",
"sourceUrl",
"title",
"confidence",
"width",
"height",
"pageUrl",
"turnId",
"createdAt",
"kind",
"v",
"p",
"i"
]);
function buildMediaRecord(input, index, indexField, createdAt, existingIndex) {
const base = {
filename: input.filename,
preview: input.preview,
sourceTool: normalizeString(input.sourceTool),
pluginId: normalizeString(input.pluginId),
sourceUrl: normalizeString(input.sourceUrl),
title: normalizeString(input.title),
confidence: normalizeString(input.confidence),
width: normalizeNumber(input.width),
height: normalizeNumber(input.height),
pageUrl: normalizeString(input.pageUrl),
turnId: normalizeNumber(input.turnId),
createdAt: input.createdAt ?? createdAt
};
if (input.kind === "tool_result" || input.kind === "generated") {
base.kind = input.kind;
}
for (const key of Object.keys(input)) {
if (!COMMON_MEDIA_KEYS.has(key) && input[key] != null) {
base[key] = input[key];
}
}
base[indexField] = existingIndex ?? index;
return base;
}
function upgradeExistingRecord(existing, incoming, indexField) {
const incomingIndex = incoming[indexField];
if (existing[indexField] == null && typeof incomingIndex === "number") {
existing[indexField] = incomingIndex;
}
const fieldsToUpgrade = [
"sourceTool",
"pluginId",
"sourceUrl",
"title",
"confidence",
"pageUrl",
"kind"
];
for (const field of fieldsToUpgrade) {
if (existing[field] == null && incoming[field] != null) {
existing[field] = incoming[field];
}
}
if (existing.width == null && typeof incoming.width === "number") {
existing.width = incoming.width;
}
if (existing.height == null && typeof incoming.height === "number") {
existing.height = incoming.height;
}
if (existing.turnId == null && typeof incoming.turnId === "number") {
existing.turnId = incoming.turnId;
}
for (const key of Object.keys(incoming)) {
if (!COMMON_MEDIA_KEYS.has(key) && existing[key] == null && incoming[key] != null) {
existing[key] = incoming[key];
}
}
}
function appendMediaItems(state, items, config) {
if (!items.length) return { changed: false, state, records: [] };
const {
stateArrayKey,
counterKey,
indexField,
eventType,
getDedupeKey,
filter
} = config;
const existing = Array.isArray(
state[stateArrayKey]
) ? [...state[stateArrayKey]] : [];
const maxExistingIndex = existing.reduce((max, r) => {
const idx = r[indexField];
return Math.max(max, typeof idx === "number" && Number.isFinite(idx) ? idx : 0);
}, 0);
const counterIndex = state.counters[counterKey] ?? 1;
const baseIndex = Math.max(1, counterIndex, maxExistingIndex + 1);
const createdAt = localTimestamp();
let newOnes = items.map(
(it, idx) => buildMediaRecord(
it,
baseIndex + idx,
indexField,
createdAt,
it[indexField]
)
);
if (filter) {
newOnes = newOnes.filter(filter);
}
const existingByKey = /* @__PURE__ */ new Map();
for (const item of existing) {
const key = getDedupeKey(item);
if (key) existingByKey.set(key, item);
}
const all = [...existing];
for (const r of newOnes) {
const key = getDedupeKey(r);
const existingItem = key ? existingByKey.get(key) : void 0;
if (!existingItem) {
all.push(r);
if (key) existingByKey.set(key, r);
} else {
upgradeExistingRecord(
existingItem,
r,
indexField
);
}
}
all.sort((a, b) => {
const aIdx = a[indexField] ?? 0;
const bIdx = b[indexField] ?? 0;
return aIdx - bIdx || String(a.createdAt || "").localeCompare(String(b.createdAt || ""));
});
const changed = JSON.stringify(all) !== JSON.stringify(state[stateArrayKey] || []);
if (changed) {
state[stateArrayKey] = all;
const lastIndex = all.at(-1)?.[indexField];
state.counters[counterKey] = (typeof lastIndex === "number" ? lastIndex : baseIndex - 1) + 1;
state.lastEvent = { type: eventType, at: localTimestamp() };
}
return { changed, state, records: newOnes };
}
var IMAGE_APPEND_CONFIG = {
stateArrayKey: "images",
counterKey: "nextImageI",
indexField: "i",
eventType: "images",
getDedupeKey: (item) => `${item.filename}|${item.preview}`
};
function appendImages(state, items) {
return appendMediaItems(
state,
items,
IMAGE_APPEND_CONFIG
);
}
function resolveProjectRootFrom(startDir) {
try {
{
let dir = startDir;
for (let i = 0; i < 50; i++) {
try {
if (fs.existsSync(path.join(dir, "manifest.json"))) return dir;
} catch {
}
const parent = path.dirname(dir);
if (parent === dir) break;
dir = parent;
}
}
{
let dir = startDir;
for (let i = 0; i < 50; i++) {
try {
if (fs.existsSync(path.join(dir, "package.json"))) return dir;
} catch {
}
const parent = path.dirname(dir);
if (parent === dir) break;
dir = parent;
}
}
{
let dir = startDir;
for (let i = 0; i < 30; i++) {
const base = path.basename(dir);
if (base === "dist" || base === "src") return path.dirname(dir);
const parent = path.dirname(dir);
if (parent === dir) break;
dir = parent;
}
}
} catch {
}
return startDir;
}
function getProjectRoot() {
try {
if (fs.existsSync(path.join(process.cwd(), "manifest.json"))) {
return process.cwd();
}
} catch {
}
try {
const filePath = typeof __filename !== "undefined" && __filename ? __filename : process.argv && process.argv[1] ? process.argv[1] : process.cwd();
const moduleDir = path.dirname(filePath);
return resolveProjectRootFrom(moduleDir);
} catch {
return resolveProjectRootFrom(process.cwd());
}
}
function getLogsDir() {
return path.join(getProjectRoot(), "logs");
}
var cachedPluginLogFilename;
function getPluginLogFilename() {
if (cachedPluginLogFilename !== void 0) return cachedPluginLogFilename;
try {
const raw = fs.readFileSync(path.join(getProjectRoot(), "manifest.json"), "utf-8");
const name = JSON.parse(raw)?.name;
if (typeof name === "string" && name.trim()) {
cachedPluginLogFilename = `${name.trim()}-plugin.log`;
return cachedPluginLogFilename;
}
} catch {
}
cachedPluginLogFilename = "generate-image-plugin.log";
return cachedPluginLogFilename;
}
function getActiveChatContext(opts) {
return null;
}
var lmstudioHome = null;
function findLMStudioHome() {
if (lmstudioHome !== null) {
return lmstudioHome;
}
const resolvedHomeDir = fs.realpathSync(os.homedir());
const pointerFilePath = path.join(resolvedHomeDir, ".lmstudio-home-pointer");
if (fs.existsSync(pointerFilePath)) {
const candidate = fs.readFileSync(pointerFilePath, "utf-8").trim();
try {
if (candidate && fs.existsSync(candidate)) {
const hasConversations = fs.existsSync(path.join(candidate, "conversations"));
const hasUserFiles = fs.existsSync(path.join(candidate, "user-files"));
if (hasConversations || hasUserFiles) {
lmstudioHome = candidate;
return lmstudioHome;
}
}
} catch {
}
}
const dotHome = path.join(resolvedHomeDir, ".lmstudio");
const cacheHome = path.join(resolvedHomeDir, ".cache", "lm-studio");
const looksValid = (p) => {
try {
if (!fs.existsSync(p)) return false;
const conv = path.join(p, "conversations");
const files = path.join(p, "user-files");
return fs.existsSync(conv) || fs.existsSync(files);
} catch {
return false;
}
};
if (looksValid(dotHome)) {
lmstudioHome = dotHome;
try {
fs.writeFileSync(pointerFilePath, lmstudioHome, "utf-8");
} catch {
}
return lmstudioHome;
}
if (looksValid(cacheHome)) {
lmstudioHome = cacheHome;
try {
fs.writeFileSync(pointerFilePath, lmstudioHome, "utf-8");
} catch {
}
return lmstudioHome;
}
const home = dotHome;
lmstudioHome = home;
try {
fs.writeFileSync(pointerFilePath, lmstudioHome, "utf-8");
} catch {
}
return lmstudioHome;
}
function getLMStudioWorkingDir(chatId) {
const home = findLMStudioHome();
return path.join(home, "working-directories", chatId);
}
async function getLMStudioFileMetadata(fileIdentifier) {
try {
const home = findLMStudioHome() || path.join(os.homedir(), ".lmstudio");
const metadataPath = path.join(
home,
"user-files",
`${fileIdentifier}.metadata.json`
);
if (!fs.existsSync(metadataPath)) return null;
const raw = await fs.promises.readFile(metadataPath, "utf-8");
const meta = JSON.parse(raw);
if (typeof meta.originalName !== "string" || typeof meta.fileIdentifier !== "string") {
return null;
}
return meta;
} catch {
return null;
}
}
async function getOriginalFileName(fileIdentifier) {
const meta = await getLMStudioFileMetadata(fileIdentifier);
return meta?.originalName ?? null;
}
// src/services/drawthingsLimits.ts
var drawthingsLimits = {
// Limits for RENDERED output (requested_effective)
min: 256,
// Minimum dimension per side
maxWidth: 2048,
// Maximum render width
maxHeight: 2048,
// Preview generation for ALL MediaTypes (attachment, variant, image, picture).
// w + h ≤ previewMaxSum; one preview file serves: display in chat, vision promotion,
// analyse_image, detect_object.
previewMaxSum: 1792,
previewQuality: 80,
previewFormat: "jpeg"
};
// src/services/toolParams/variants.ts
function extractVariantUrisFromContent(raw) {
const candidates = [];
const text = typeof raw === "string" ? raw : JSON.stringify(raw);
const uriRegex = /file:\/\/[^\s"')]+generated-image-[^\s"')]*-v(\d+)\.png/gi;
let match;
while ((match = uriRegex.exec(text)) !== null) {
const uri = match[0];
const variantNum = parseInt(match[1], 10);
const filename = uri.split("/").pop() ?? "";
candidates.push({
filename,
index: variantNum
});
}
return candidates;
}
function extractGenerateImageResult(raw) {
const candidates = [];
if (raw && typeof raw === "object") {
const obj = raw;
if (Array.isArray(obj.filenames)) {
for (let i = 0; i < obj.filenames.length; i++) {
const fn = obj.filenames[i];
if (typeof fn === "string") {
candidates.push({ filename: fn, index: i + 1 });
}
}
}
if (Array.isArray(obj.content)) {
for (const item of obj.content) {
if (item && typeof item === "object" && "text" in item) {
const nested = extractVariantUrisFromContent(item.text);
candidates.push(...nested);
}
}
}
}
if (candidates.length === 0) {
return extractVariantUrisFromContent(raw);
}
return candidates;
}
var selfPlugin = getSelfPluginIdentifier() ?? "unknown";
var VARIANT_FULL_CONFIG = {
mediaType: "variant",
allow: "all",
ssotJoin: {
source: "conversation.json",
messageRole: "assistant",
jsonPath: "content",
// Regex scan for file:// URIs
extractFromSource: extractVariantUrisFromContent
},
scan: {
trigger: "both",
scope: "all-turns"
},
harvesting: {
defaultExtractor: extractGenerateImageResult,
tools: {
[`${selfPlugin}/generate_image`]: {
extractCandidates: extractGenerateImageResult,
actions: {
generatePreview: true,
visionPromotion: {
metadata: true,
pixel: true
}
}
}
}
},
actions: {
generatePreview: true,
visionPromotion: {
metadata: true,
// Labels (v1, v2) ALWAYS included
pixel: true
// Base64 pixels in rolling window
}
},
injectMdInAgentResponse: {
format: "none",
// Toggle-dependent: !PREVIEW_IN_CHAT
itemTemplate: "",
labelGenerator: (item, i) => `v${item.index ?? i + 1}`
},
preview: {
generate: true,
namingPattern: "preview-{basename}.jpg",
format: drawthingsLimits.previewFormat,
mimeType: "image/jpeg",
maxSum: drawthingsLimits.previewMaxSum,
quality: drawthingsLimits.previewQuality,
outputDir: "."
},
toggles: {
toggles: []
}
};
// src/helpers/imageUtils.ts
var loadedSharp2 = void 0;
var loadedJimp2 = void 0;
var libLogged = false;
var loggedFns = /* @__PURE__ */ new Set();
function logOnce(msg) {
if (loggedFns.has(msg)) return;
loggedFns.add(msg);
try {
console.debug(msg);
} catch {
}
}
async function tryLoadSharp2() {
if (loadedSharp2 !== void 0) return loadedSharp2;
try {
const mod = await import('sharp');
loadedSharp2 = mod?.default || mod;
if (!libLogged) {
try {
console.debug(`[imageUtils] using lib=sharp`);
libLogged = true;
} catch {
}
}
return loadedSharp2;
} catch {
loadedSharp2 = null;
return null;
}
}
async function tryLoadJimp2() {
if (loadedJimp2 !== void 0) return loadedJimp2;
try {
const mod = await import('jimp');
const candidate = mod && (mod.default || mod.Jimp || mod);
loadedJimp2 = candidate;
if (!libLogged) {
try {
console.debug(`[imageUtils] using lib=jimp`);
libLogged = true;
} catch {
}
}
return loadedJimp2;
} catch {
loadedJimp2 = null;
return null;
}
}
function jimpHasFn(obj, name) {
try {
return obj && typeof obj[name] === "function";
} catch {
return false;
}
}
async function jimpResizeCompat(img, w, h) {
if (!jimpHasFn(img, "resize")) {
throw new Error("Jimp resize not available");
}
let lastError;
try {
await img.resize({ w, h });
return;
} catch (e) {
lastError = e;
}
try {
await img.resize(w, h);
return;
} catch (e) {
lastError = e;
}
throw new Error(
`Jimp resize failed for both API variants: ${lastError?.message || String(lastError)}`
);
}
async function jimpAutoRotate(img) {
try {
if (jimpHasFn(img, "exifRotate")) {
await img.exifRotate();
} else if (jimpHasFn(img, "rotate")) {
}
} catch (e) {
try {
console.error(`[imageUtils] Jimp EXIF rotation failed: ${String(e)}`);
} catch {
}
}
}
async function jimpGetBufferCompat(img, mime, options) {
let lastError;
try {
if (jimpHasFn(img, "getBufferAsync")) {
return await img.getBufferAsync(mime, options);
}
} catch (e) {
lastError = e;
}
try {
if (jimpHasFn(img, "getBuffer")) {
return await img.getBuffer(mime, options);
}
} catch (e) {
lastError = e;
}
throw new Error(
`Jimp getBuffer failed for both API variants (mime=${mime}): ${lastError?.message || String(lastError)}`
);
}
async function getSize(buffer) {
const sharp = await tryLoadSharp2();
if (sharp) {
logOnce(`[imageUtils.getSize] using Sharp`);
const meta = await sharp(buffer).rotate().metadata();
return { width: meta.width || 0, height: meta.height || 0 };
}
const Jimp = await tryLoadJimp2();
if (Jimp && typeof Jimp.read === "function") {
logOnce(`[imageUtils.getSize] using Jimp`);
const img = await Jimp.read(buffer);
await jimpAutoRotate(img);
const w = typeof img.getWidth === "function" ? img.getWidth() : typeof img.width === "number" ? img.width : img.bitmap?.width || 0;
const h = typeof img.getHeight === "function" ? img.getHeight() : typeof img.height === "number" ? img.height : img.bitmap?.height || 0;
return { width: w || 0, height: h || 0 };
}
return { width: 0, height: 0 };
}
async function resizeAndEncode(buffer, format, quality, width, _maxBytes) {
const sharp = await tryLoadSharp2();
if (sharp) {
logOnce(`[imageUtils.resizeAndEncode] using Sharp`);
const pipeline = sharp(buffer).rotate().resize({ width, fit: "inside", withoutEnlargement: false });
const { data, info } = await (format === "jpeg" ? pipeline.jpeg({
quality: clampQuality(quality),
mozjpeg: true,
chromaSubsampling: "4:2:0",
progressive: true
}) : pipeline.webp({ quality: clampQuality(quality), effort: 4 })).toBuffer({ resolveWithObject: true });
const outW = typeof info.width === "number" ? info.width : width;
const outH = typeof info.height === "number" ? info.height : Math.round(width * 0.75);
return { data, width: outW, height: outH };
}
const Jimp = await tryLoadJimp2();
if (Jimp && typeof Jimp.read === "function") {
logOnce(`[imageUtils.resizeAndEncode] using Jimp`);
const img = await Jimp.read(buffer);
await jimpAutoRotate(img);
const origW = typeof img.getWidth === "function" ? img.getWidth() : typeof img.width === "number" ? img.width : img.bitmap?.width || 0;
const origH = typeof img.getHeight === "function" ? img.getHeight() : typeof img.height === "number" ? img.height : img.bitmap?.height || 0;
const scale = width / Math.max(1, origW);
const outW = Math.max(1, Math.round(origW * scale));
const outH = Math.max(1, Math.round(origH * scale));
await jimpResizeCompat(img, outW, outH);
const q = clampQuality(quality);
{
const data = await jimpGetBufferCompat(img, "image/jpeg", { quality: q });
return { data, width: outW, height: outH };
}
}
return { data: buffer, width, height: Math.round(width * 0.75) };
}
async function resizeAndEncodeByHeight(buffer, format, quality, maxHeight) {
const sharp = await tryLoadSharp2();
if (sharp) {
logOnce(`[imageUtils.resizeAndEncodeByHeight] using Sharp`);
const pipeline = sharp(buffer).rotate().resize({ height: maxHeight, fit: "inside", withoutEnlargement: false });
const { data, info } = await (format === "jpeg" ? pipeline.jpeg({
quality: clampQuality(quality),
mozjpeg: true,
chromaSubsampling: "4:2:0",
progressive: true
}) : pipeline.webp({ quality: clampQuality(quality), effort: 4 })).toBuffer({ resolveWithObject: true });
const outW = typeof info.width === "number" ? info.width : Math.round(maxHeight * 1.33);
const outH = typeof info.height === "number" ? info.height : maxHeight;
return { data, width: outW, height: outH };
}
const Jimp = await tryLoadJimp2();
if (Jimp && typeof Jimp.read === "function") {
logOnce(`[imageUtils.resizeAndEncodeByHeight] using Jimp`);
const img = await Jimp.read(buffer);
await jimpAutoRotate(img);
const origW = typeof img.getWidth === "function" ? img.getWidth() : typeof img.width === "number" ? img.width : img.bitmap?.width || 0;
const origH = typeof img.getHeight === "function" ? img.getHeight() : typeof img.height === "number" ? img.height : img.bitmap?.height || 0;
const scale = maxHeight / Math.max(1, origH);
const outH = Math.max(1, Math.round(maxHeight));
const outW = Math.max(1, Math.round(origW * scale));
await jimpResizeCompat(img, outW, outH);
const q = clampQuality(quality);
{
const data = await jimpGetBufferCompat(img, "image/jpeg", { quality: q });
return { data, width: outW, height: outH };
}
}
return {
data: buffer,
width: Math.round(maxHeight * 1.33),
height: maxHeight
};
}
async function resizeAndEncodeBySum(buffer, format, quality, maxSum) {
const sharp = await tryLoadSharp2();
const calcDims = (origW, origH) => {
let w = Math.max(1, Math.round(origW));
let h = Math.max(1, Math.round(origH));
const currentSum = w + h;
if (currentSum > maxSum) {
const scale = maxSum / currentSum;
w = Math.max(1, Math.round(w * scale));
h = Math.max(1, Math.round(h * scale));
}
return { w, h };
};
if (sharp) {
logOnce(`[imageUtils.resizeAndEncodeBySum] using Sharp`);
const metadata = await sharp(buffer).metadata();
const rotatesDimensions = metadata.orientation !== void 0 && metadata.orientation >= 5 && metadata.orientation <= 8;
const origW = rotatesDimensions ? metadata.height ?? 640 : metadata.width ?? 640;
const origH = rotatesDimensions ? metadata.width ?? 640 : metadata.height ?? 640;
const { w, h } = calcDims(origW, origH);
const pipeline = sharp(buffer).rotate().resize({ width: w, height: h, fit: "inside", withoutEnlargement: true });
const { data, info } = await (format === "jpeg" ? pipeline.jpeg({
quality: clampQuality(quality),
mozjpeg: true,
chromaSubsampling: "4:2:0",
progressive: true
}) : pipeline.webp({ quality: clampQuality(quality), effort: 4 })).toBuffer({ resolveWithObject: true });
const outW = typeof info.width === "number" ? info.width : w;
const outH = typeof info.height === "number" ? info.height : h;
return { data, width: outW, height: outH };
}
const Jimp = await tryLoadJimp2();
if (Jimp && typeof Jimp.read === "function") {
logOnce(`[imageUtils.resizeAndEncodeBySum] using Jimp`);
const img = await Jimp.read(buffer);
await jimpAutoRotate(img);
const origW = typeof img.getWidth === "function" ? img.getWidth() : typeof img.width === "number" ? img.width : img.bitmap?.width || 640;
const origH = typeof img.getHeight === "function" ? img.getHeight() : typeof img.height === "number" ? img.height : img.bitmap?.height || 640;
const { w, h } = calcDims(origW, origH);
await jimpResizeCompat(img, w, h);
const q = clampQuality(quality);
{
const data = await jimpGetBufferCompat(img, "image/jpeg", { quality: q });
return { data, width: w, height: h };
}
}
return {
data: buffer,
width: Math.round(maxSum / 2),
height: Math.round(maxSum / 2)
};
}
function clampQuality(q) {
if (!Number.isFinite(q)) return 80;
q = Math.round(q);
if (q < 1) q = 1;
if (q > 100) q = 100;
return q;
}
// src/media-promotion-core/image.ts
function getDefaultPreviewOptions() {
return {
maxDim: drawthingsLimits.previewMaxSum,
quality: drawthingsLimits.previewQuality,
mode: "sum",
maxSum: drawthingsLimits.previewMaxSum
};
}
function isAllowedOriginalExt(p) {
return /(\.(png|jpe?g|webp|mov))$/i.test(p);
}
function previewFilenameFrom(originalFilename) {
const hasPrefix = originalFilename.toLowerCase().startsWith("preview-");
const base = hasPrefix ? originalFilename : `preview-${originalFilename}`;
return base.replace(/\.(png|jpg|jpeg|webp|gif)$/i, ".jpg").replace(/ /g, "_");
}
async function encodeJpegPreviewFromBuffer(srcBuf, opts) {
const q = Math.max(1, Math.min(100, opts.quality));
if (opts.mode === "height") {
const targetH = Math.max(1, Math.round(opts.maxDim));
const { data: data2, width: width2, height: height2 } = await resizeAndEncodeByHeight(
srcBuf,
"jpeg",
q,
targetH
);
return { data: data2, width: width2, height: height2 };
}
if (opts.mode === "sum" && opts.maxSum) {
const { data: data2, width: width2, height: height2 } = await resizeAndEncodeBySum(
srcBuf,
"jpeg",
q,
opts.maxSum
);
return { data: data2, width: width2, height: height2 };
}
const maxW = Math.max(1, Math.round(opts.maxDim));
const { data, width, height } = await resizeAndEncode(
srcBuf,
"jpeg",
q,
maxW);
return { data, width, height };
}
async function encodeJpegPreview(srcAbs, dstAbs, opts) {
const srcBuf = await fs.promises.readFile(srcAbs);
const { data } = await encodeJpegPreviewFromBuffer(srcBuf, opts);
await fs.promises.writeFile(dstAbs, data);
}
function isPreviewOptions(x) {
return typeof x === "object" && x !== null && "maxDim" in x && typeof x.maxDim === "number";
}
function normalizeToPreviewOptions(input) {
if (isPreviewOptions(input)) return input;
return {
maxDim: input.maxWidth ?? 640,
quality: input.quality ?? 80,
mode: input.maxSum ? "sum" : "width",
maxSum: input.maxSum
};
}
async function generatePreview(srcAbs, chatWd, optsInput, options) {
const debug = options?.debug ?? false;
const opts = normalizeToPreviewOptions(optsInput);
if (!fs.existsSync(srcAbs)) {
if (debug) console.warn(`[Preview] Source not found: ${srcAbs}`);
return null;
}
const originalFilename = path.basename(srcAbs);
const previewFilename = options?.customFilename ?? previewFilenameFrom(originalFilename);
const previewAbs = path.join(chatWd, previewFilename);
if (!options?.force && fs.existsSync(previewAbs)) {
if (debug) console.info(`[Preview] Exists, skipping: ${previewFilename}`);
return previewFilename;
}
try {
await encodeJpegPreview(srcAbs, previewAbs, opts);
if (debug) console.info(`[Preview] Generated: ${previewFilename}`);
return previewFilename;
} catch (e) {
if (debug)
console.warn(
`[Preview] Failed for ${originalFilename}:`,
e.message
);
throw e;
}
}
async function generatePreviewFromBuffer(srcBuf, chatWd, originalFilename, optsInput, options) {
const debug = false;
const opts = normalizeToPreviewOptions(optsInput);
const previewFilename = options?.customFilename ?? previewFilenameFrom(originalFilename);
const previewAbs = path.join(chatWd, previewFilename);
if (fs.existsSync(previewAbs)) {
try {
const existingData = await fs.promises.readFile(previewAbs);
return {
previewFilename,
previewAbs,
data: existingData,
width: 0,
// Unknown for existing file
height: 0
};
} catch {
}
}
try {
const { data, width, height } = await encodeJpegPreviewFromBuffer(
srcBuf,
opts
);
await fs.promises.writeFile(previewAbs, data);
if (debug)
;
return {
previewFilename,
previewAbs,
data,
width,
height
};
} catch (e) {
throw e;
}
}
function findConversationPath(chatWd) {
const chatId = path.basename(chatWd);
const lmHome = findLMStudioHome() || path.join(os.homedir(), ".lmstudio");
const conversationsDir = path.join(lmHome, "conversations");
const candidates = [
path.join(conversationsDir, `${chatId}.conversation.json`),
path.join(chatWd, ".conversation.json"),
path.join(chatWd, "conversation.json")
];
for (const p of candidates) {
try {
fs.accessSync(p, fs.constants.F_OK);
return p;
} catch {
}
}
return void 0;
}
async function readConversation(chatWd) {
const p = findConversationPath(chatWd);
if (!p) return void 0;
const maxAttempts = 5;
const retryDelayMs = 50;
for (let attempt = 0; attempt < maxAttempts; attempt++) {
try {
const raw = await fs.promises.readFile(p, "utf-8");
const json = JSON.parse(raw);
return { json, path: p };
} catch {
if (attempt < maxAttempts - 1) {
await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
}
}
}
return void 0;
}
function findMessagesArray(json) {
if (!json || typeof json !== "object") return void 0;
const obj = json;
const candidates = [
obj.messages,
obj.conversation?.messages,
obj.chat?.messages,
obj.history,
obj.turns,
obj.items
];
for (const arr of candidates) {
if (Array.isArray(arr) && arr.length > 0) {
return arr;
}
}
return void 0;
}
function resolveMessageVersion(raw) {
if (!raw || typeof raw !== "object") return raw;
const obj = raw;
const versions = obj.versions;
if (!Array.isArray(versions) || versions.length === 0) {
return raw;
}
const selRaw = obj.currentlySelected;
const sel = typeof selRaw === "number" && Number.isFinite(selRaw) ? selRaw : 0;
if (sel >= 0 && sel < versions.length) {
return versions[sel];
}
return versions[versions.length - 1];
}
function getMessageRole(msg) {
if (!msg || typeof msg !== "object") return "unknown";
const obj = msg;
if (obj.type === "contentBlock") {
const arr = obj.content;
if (Array.isArray(arr)) {
for (const it of arr) {
if (!it || typeof it !== "object") continue;
const t = it.type;
if (t === "toolCallRequest" || t === "toolCallResult") {
return "tool";
}
}
}
}
const role = obj.role ?? obj.author ?? obj.sender;
if (typeof role === "string") return role.toLowerCase();
const type = obj.type ?? obj.messageType;
if (typeof type === "string") {
if (type === "user" || type === "user_message") return "user";
if (type === "assistant" || type === "assistant_message")
return "assistant";
if (type === "system") return "system";
if (type === "tool" || type === "tool_result") return "tool";
}
return "unknown";
}
function parseMessages(json) {
const messages = findMessagesArray(json);
if (!messages) return [];
const result = [];
for (let i = 0; i < messages.length; i++) {
const raw = messages[i];
const resolved = resolveMessageVersion(raw);
const role = getMessageRole(resolved);
result.push({
index: i,
turnId: i + 1,
// 1-based
role,
content: resolved,
raw
});
}
return result;
}
function extractUserAttachments(msg, lmHome) {
const result = [];
const home = findLMStudioHome() || path.join(os.homedir(), ".lmstudio");
const userFilesDir = path.join(home, "user-files");
const content = msg.content;
if (!content || typeof content !== "object") return result;
const collectFromObject = (obj) => {
if (!obj || typeof obj !== "object") return;
if (Array.isArray(obj)) {
for (const item of obj) {
collectFromObject(item);
}
return;
}
const o = obj;
const fileId = o.fileIdentifier ?? o.file_identifier ?? o.identifier;
const fileType = o.fileType ?? o.file_type ?? o.type;
if (typeof fileId === "string" && fileId.trim()) {
if (fileType === "image" || /\.(png|jpg|jpeg|webp|gif|bmp|tiff?)$/i.test(fileId)) {
result.push(path.join(userFilesDir, fileId));
}
}
const filePath = o.path ?? o.filePath ?? o.file_path ?? o.uri ?? o.url;
if (typeof filePath === "string" && filePath.trim()) {
let resolved = filePath;
if (filePath.startsWith("file://")) {
try {
resolved = decodeURIComponent(filePath.replace(/^file:\/\//, ""));
} catch {
resolved = filePath.replace(/^file:\/\//, "");
}
}
if (/\.(png|jpg|jpeg|webp|gif|bmp|tiff?)$/i.test(resolved)) {
result.push(resolved);
}
}
for (const key of Object.keys(o)) {
if (key !== "content" || !Array.isArray(o[key])) {
collectFromObject(o[key]);
}
}
};
const contentArray = content.content;
if (Array.isArray(contentArray)) {
for (const part of contentArray) {
collectFromObject(part);
}
} else {
collectFromObject(content);
}
const files = content.files;
if (Array.isArray(files)) {
for (const f of files) {
collectFromObject(f);
}
}
const attachments = content.attachments;
if (Array.isArray(attachments)) {
for (const a of attachments) {
collectFromObject(a);
}
}
return result;
}
function extractPendingAttachments(json, lmHome) {
const result = [];
const home = findLMStudioHome() || path.join(os.homedir(), ".lmstudio");
const userFilesDir = path.join(home, "user-files");
if (!json || typeof json !== "object") return result;
const files = json.clientInputFiles;
if (!Array.isArray(files)) return result;
for (const f of files) {
if (!f || typeof f !== "object") continue;
const fo = f;
const id = fo.fileIdentifier;
const type = fo.fileType;
if (typeof id === "string" && id.trim() && type === "image") {
result.push(path.join(userFilesDir, id));
}
}
return result;
}
function buildConversationWideRequestMetadata(messages, debug) {
const metaByKey = /* @__PURE__ */ new Map();
let toolRequestCount = 0;
const remember = (key, meta) => {
const k = typeof key === "string" || typeof key === "number" ? String(key) : "";
if (!k) return;
const prev = metaByKey.get(k) ?? {};
metaByKey.set(k, {
pluginId: meta.pluginId ?? prev.pluginId,
toolName: meta.toolName ?? prev.toolName
});
};
if (debug) {
console.info(
`[MediaScanner] buildConversationWideRequestMetadata: scanning ${messages.length} messages`
);
}
for (const msg of messages) {
const content = msg.content;
if (!content || typeof content !== "object") continue;
const obj = content;
if (obj.type === "contentBlock" && Array.isArray(obj.content)) {
for (const item of obj.content) {
if (!item || typeof item !== "object") continue;
const bo = item;
if (bo.type !== "toolCallRequest") continue;
const pluginId = typeof bo.pluginIdentifier === "string" ? bo.pluginIdentifier : void 0;
const toolName = typeof bo.name === "string" ? bo.name : void 0;
const callId = bo.callId ?? bo.toolCallId ?? bo.id;
const reqId = bo.toolCallRequestId ?? bo.requestId;
toolRequestCount++;
if (debug) {
console.info(
`[MediaScanner] Request metadata (Case1): callId=${callId} reqId=${reqId} tool=${toolName} plugin=${pluginId ?? "(none)"}`
);
}
remember(callId, { pluginId, toolName });
remember(reqId, { pluginId, toolName });
}
}
if (Array.isArray(obj.content)) {
for (const item of obj.content) {
if (!item || typeof item !== "object") continue;
const it = item;
if (it.type === "contentBlock" && Array.isArray(it.content)) {
for (const bi of it.content) {
if (!bi || typeof bi !== "object") continue;
const bo = bi;
if (bo.type !== "toolCallRequest") continue;
const pluginId = typeof bo.pluginIdentifier === "string" ? bo.pluginIdentifier : void 0;
const toolName = typeof bo.name === "string" ? bo.name : void 0;
const callId = bo.callId ?? bo.toolCallId ?? bo.id;
const reqId = bo.toolCallRequestId ?? bo.requestId;
toolRequestCount++;
if (debug) {
console.info(
`[MediaScanner] Request metadata (Case2): callId=${callId} reqId=${reqId} tool=${toolName} plugin=${pluginId ?? "(none)"}`
);
}
remember(callId, { pluginId, toolName });
remember(reqId, { pluginId, toolName });
}
}
}
}
if (Array.isArray(obj.steps)) {
for (const step of obj.steps) {
if (!step || typeof step !== "object") continue;
const st = step;
if (st.type === "contentBlock" && Array.isArray(st.content)) {
for (const bi of st.content) {
if (!bi || typeof bi !== "object") continue;
const bo = bi;
if (bo.type !== "toolCallRequest") continue;
const pluginId = typeof bo.pluginIdentifier === "string" ? bo.pluginIdentifier : void 0;
const toolName = typeof bo.name === "string" ? bo.name : void 0;
const callId = bo.callId ?? bo.toolCallId ?? bo.id;
const reqId = bo.toolCallRequestId ?? bo.requestId;
toolRequestCount++;
if (debug) {
console.info(
`[MediaScanner] Request metadata (Case3-steps): callId=${callId} reqId=${reqId} tool=${toolName} plugin=${pluginId ?? "(none)"}`
);
}
remember(callId, { pluginId, toolName });
remember(reqId, { pluginId, toolName });
}
}
}
}
}
if (debug) {
console.info(
`[MediaScanner] buildConversationWideRequestMetadata: found ${toolRequestCount} toolCallRequests, ${metaByKey.size} unique keys`
);
}
return metaByKey;
}
async function scanMedia(chatWd, mediaType, options) {
const { scope, debug = false } = options;
if (!chatWd) {
return { candidates: [] };
}
const conv = await readConversation(chatWd);
if (!conv) {
if (debug) {
console.info(`[MediaScanner] No conversation.json found for ${chatWd}`);
}
return { candidates: [] };
}
const messages = parseMessages(conv.json);
if (debug) {
console.info(
`[MediaScanner] Parsed ${messages.length} messages from ${conv.path}`
);
}
const requestMetaByKey = buildConversationWideRequestMetadata(
messages,
debug
);
if (debug && requestMetaByKey.size > 0) {
console.info(
`[MediaScanner] Collected ${requestMetaByKey.size} request metadata entries`
);
}
const candidates = [];
const orderedMessages = scope === "last" ? [...messages].reverse() : messages;
for (const msg of orderedMessages) {
let foundInThisMessage = [];
{
foundInThisMessage = scanAttachmentsInMessage(msg, conv.json);
}
if (foundInThisMessage.length > 0) {
candidates.push(...foundInThisMessage);
if (scope === "last") {
break;
}
}
}
if (scope === "all") {
const pending = extractPendingAttachments(conv.json);
const toAppend = [];
for (const p of pending) {
const exists = candidates.some((c) => c.identifier === p);
if (exists) continue;
toAppend.push({
kind: "attachment",
identifier: p,
turnId: 0
// Pending = before any turn
});
}
if (toAppend.length) {
candidates.push(...toAppend);
}
}
if (debug) {
console.info(
`[MediaScanner] FINAL: Found ${candidates.length} ${mediaType} candidates (scope: ${scope})`
);
if (candidates.length > 0) {
for (const c of candidates.slice(0, 3)) {
console.info(
`[MediaScanner] candidate: id=${c.identifier?.slice(
0,
50
)} pluginId=${c.pluginId} tool=${c.sourceTool}`
);
}
}
}
return {
candidates,
conversationPath: conv.path
};
}
function scanAttachmentsInMessage(msg, conversationJson, debug) {
if (msg.role !== "user") return [];
const attachments = extractUserAttachments(msg);
return attachments.map((absPath) => ({
kind: "attachment",
identifier: absPath,
turnId: msg.turnId
}));
}
async function findAllMedia(chatWd, mediaType, debug = false) {
return scanMedia(chatWd, mediaType, { scope: "all", debug });
}
// src/services/mediaScanner/legacyAdapters.ts
async function findAllAttachmentsLegacy(chatWd, debug) {
if (!chatWd) return { found: [], turnIdByAbs: {} };
const result = await findAllMedia(chatWd, "attachment", debug);
const found = [];
const turnIdByAbs = {};
for (const c of result.candidates) {
const abs = c.identifier;
if (!found.includes(abs)) {
found.push(abs);
}
if (turnIdByAbs[abs] === void 0) {
turnIdByAbs[abs] = c.turnId;
}
}
return { found, turnIdByAbs };
}
async function pathExists2(p) {
try {
await fs.promises.access(p, fs.constants.F_OK);
return true;
} catch {
return false;
}
}
async function importAttachmentBatch(chatWd, state, sourcePaths, turnIdByOriginAbs, previewOpts, maxPreviewAttachments = 0, debug = false) {
const normalizeAbs = (p) => {
try {
return path.resolve(p);
} catch {
return p;
}
};
const normalizedSource = sourcePaths.filter((p) => typeof p === "string" && p.trim().length > 0).map(normalizeAbs);
const normalizeTurnIdMap = (m) => {
if (!m) return {};
const out = {};
for (const [k, v] of Object.entries(m)) {
if (typeof v === "number" && Number.isFinite(v)) {
out[normalizeAbs(k)] = v;
}
}
return out;
};
const normalizedTurnIdByAbs = normalizeTurnIdMap(turnIdByOriginAbs);
const normalizedSourceDeduped = [];
{
const seen = /* @__PURE__ */ new Set();
for (const p of normalizedSource) {
if (!seen.has(p)) {
seen.add(p);
normalizedSourceDeduped.push(p);
}
}
}
if (normalizedSourceDeduped.length === 0) {
if (debug)
console.info(
"Batch import: No new attachments in current turn; keeping existing state."
);
return { changed: false };
}
try {
const current = Array.isArray(state.attachments) ? state.attachments : [];
const currentOrigins = current.map(
(a) => a && typeof a.originAbs === "string" ? String(a.originAbs) : ""
).filter((p) => p.trim().length > 0).map(normalizeAbs);
const same = currentOrigins.length === normalizedSourceDeduped.length && currentOrigins.every((p, i) => p === normalizedSourceDeduped[i]);
if (same && current.length > 0) {
let allOk = true;
for (let i = 0; i < current.length; i++) {
const a = current[i];
const pv = a && typeof a.preview === "string" ? String(a.preview) : "";
if (i < Math.max(0, Math.floor(maxPreviewAttachments))) {
if (!pv) {
allOk = false;
break;
}
const pvAbs = path.join(chatWd, pv);
const okPv = await pathExists2(pvAbs);
if (!okPv) {
allOk = false;
break;
}
}
}
if (allOk) {
try {
let anyMetaChanged = false;
const nextAttachments = current.map((a) => {
const originAbs = a && typeof a.originAbs === "string" ? String(a.originAbs) : "";
const key = originAbs ? normalizeAbs(originAbs) : "";
const nextTurnId = key ? normalizedTurnIdByAbs[key] : void 0;
let updated = a;
if (typeof nextTurnId === "number" && a && a.turnId !== nextTurnId) {
anyMetaChanged = true;
updated = { ...updated, turnId: nextTurnId };
}
if (!updated.preview && originAbs) {
const candidate = previewFilenameFrom(path.basename(originAbs));
if (fs.existsSync(path.join(chatWd, candidate))) {
anyMetaChanged = true;
updated = { ...updated, preview: candidate };
}
}
return updated;
});
if (anyMetaChanged) {
state.attachments = nextAttachments;
await writeStateAtomic(chatWd, state);
if (debug)
console.info(
"Batch import: updated attachment turnId metadata (idempotent, no re-import)."
);
return { changed: false, metadataChanged: true };
}
} catch (e) {
if (debug)
console.warn(
"Batch import: turnId metadata update failed; continuing idempotent skip:",
e.message
);
}
if (debug)
console.info(
"Batch import: SSOT matches current state; skipping re-import (idempotent)."
);
return { changed: false };
}
}
} catch (e) {
if (debug)
console.warn(
"Batch import: idempotence check failed; continuing with import:",
e.message
);
}
const existingByOrigin = /* @__PURE__ */ new Map();
for (const a of state.attachments || []) {
if (a && typeof a.originAbs === "string") {
existingByOrigin.set(normalizeAbs(a.originAbs), a);
}
}
const usedAs = /* @__PURE__ */ new Set();
for (const a of existingByOrigin.values()) {
const av = a?.a;
if (typeof av === "number" && Number.isFinite(av) && av > 0) {
usedAs.add(av);
}
}
const ensureNextA = () => {
const current = state?.counters?.nextAttachmentA;
if (typeof current === "number" && Number.isFinite(current) && current > 0) {
return Math.floor(current);
}
const maxExisting = usedAs.size ? Math.max(...Array.from(usedAs)) : 0;
return maxExisting + 1;
};
let nextA = ensureNextA();
const allocateA = () => {
while (usedAs.has(nextA)) nextA++;
const a = nextA;
usedAs.add(a);
nextA++;
return a;
};
const imported = [];
for (let i = 0; i < normalizedSourceDeduped.length; i++) {
const abs = normalizedSourceDeduped[i];
if (!await pathExists2(abs)) {
if (debug)
console.warn(`Batch import: source not found, skipping: ${abs}`);
continue;
}
if (!isAllowedOriginalExt(abs)) {
if (debug)
console.warn(`Batch import: extension not allowed, skipping: ${abs}`);
continue;
}
const existing = existingByOrigin.get(normalizeAbs(abs));
if (existing) {
if (existing.preview) {
const previewAbs = path.join(chatWd, existing.preview);
if (!await pathExists2(previewAbs)) {
if (debug)
console.info(
`Batch import: regenerating missing preview for a${existing.a}`
);
await generatePreview(abs, chatWd, previewOpts, {
customFilename: existing.preview,
force: true,
debug
});
}
}
const nextTurnId = normalizedTurnIdByAbs[normalizeAbs(abs)];
let existingWidth = existing.width;
let existingHeight = existing.height;
if (existingWidth == null || existingHeight == null) {
try {
const origBuf = await fs.promises.readFile(abs);
const dims = await getSize(origBuf);
if (dims.width > 0 && dims.height > 0) {
existingWidth = dims.width;
existingHeight = dims.height;
}
} catch (e) {
if (debug)
console.warn(
`Batch import: failed to measure dims for a${existing.a}:`,
e.message
);
}
}
imported.push({
...existing,
// Keep stable `a`
a: typeof existing.a === "number" && Number.isFinite(existing.a) && existing.a > 0 ? existing.a : allocateA(),
turnId: typeof nextTurnId === "number" ? nextTurnId : existing.turnId,
width: existingWidth,
height: existingHeight
});
if (debug)
console.info(
`Batch import: reusing existing attachment as a${existing.a}: ${path.basename(abs)}`
);
continue;
}
const origin = path.basename(abs);
let previewName = void 0;
if (i < Math.max(0, Math.floor(maxPreviewAttachments))) {
previewName = await generatePreview(abs, chatWd, previewOpts, { debug }) ?? void 0;
}
if (!previewName) {
const candidate = previewFilenameFrom(path.basename(abs));
if (fs.existsSync(path.join(chatWd, candidate))) {
previewName = candidate;
}
}
let originalName = void 0;
const lmHome = findLMStudioHome();
const userFilesDir = path.join(lmHome, "user-files");
if (abs.startsWith(userFilesDir)) {
const fileIdentifier = path.basename(abs);
const resolvedName = await getOriginalFileName(fileIdentifier);
if (!resolvedName || !resolvedName.trim()) {
throw new Error(
`Missing originalName in LM Studio metadata for fileIdentifier='${fileIdentifier}' (abs='${abs}')`
);
}
originalName = resolvedName;
if (debug)
console.info(
`Resolved original filename: ${fileIdentifier} \u2192 ${originalName}`
);
} else {
originalName = path.basename(abs);
}
let origWidth;
let origHeight;
try {
const origBuf = await fs.promises.readFile(abs);
const dims = await getSize(origBuf);
if (dims.width > 0 && dims.height > 0) {
origWidth = dims.width;
origHeight = dims.height;
}
} catch (e) {
if (debug)
console.warn(
`Batch import: failed to measure dims for new attachment ${path.basename(abs)}:`,
e.message
);
}
imported.push({
origin,
originAbs: abs,
originalName,
turnId: normalizedTurnIdByAbs[normalizeAbs(abs)],
preview: previewName,
width: origWidth,
height: origHeight,
createdAt: localTimestamp(),
a: allocateA()
// Stable, monotonically increasing id
});
if (debug)
console.info(
`Batch import: new attachment as a${imported[imported.length - 1].a}: ${path.basename(abs)}`
);
}
if (imported.length === 0) {
if (debug) console.warn("Batch import: no valid attachments imported");
return { changed: false };
}
state.attachments = imported;
state.counters.nextAttachmentA = nextA;
state.lastEvent = { type: "attachment", at: localTimestamp() };
await writeStateAtomic(chatWd, state);
if (debug)
console.info(
`Batch imported ${imported.length} attachment(s) from SSOT (replaced array)`
);
return { changed: true };
}
// src/helpers/attachmentSync.ts
var DEFAULT_PREVIEW_OPTS = getDefaultPreviewOptions();
async function syncAttachmentsToState(workingDir, debug = false, maxPreviewAttachments = 0, previewOpts) {
const state = await readState$1(workingDir);
const { found, turnIdByAbs } = await findAllAttachmentsLegacy(
workingDir,
debug
);
const opts = maxPreviewAttachments > 0 ? DEFAULT_PREVIEW_OPTS : { maxDim: 0, quality: 0 };
const result = await importAttachmentBatch(
workingDir,
state,
found,
turnIdByAbs,
opts,
maxPreviewAttachments,
debug
);
return { changed: result.changed };
}
function buildPaths() {
const logsDir = getLogsDir();
const filePath = path.resolve(logsDir, getPluginLogFilename().replace(/\.log$/, ".audit.jsonl"));
return { logsDir, filePath };
}
function localTimestamp2() {
try {
return (/* @__PURE__ */ new Date()).toLocaleString(void 0, {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: false,
timeZoneName: "short"
});
} catch {
return (/* @__PURE__ */ new Date()).toString();
}
}
function buildAuditLogger({
backend,
mode,
requestId: providedRequestId
}) {
const requestId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
const entry = {
timestamp: localTimestamp2(),
requestId,
backend,
mode
};
function setChatId(id) {
if (typeof id === "string" && id.trim().length > 0) entry.chat_id = id;
}
function setUserRequest(req) {
entry.user_request = req;
}
function setRenderTarget(target) {
entry.render_target = target;
}
function setInputs(inputs) {
entry.inputs = inputs;
}
function setOutput(output) {
entry.output = { ...entry.output, ...output };
}
function setError(err) {
let message = "unknown error";
let status = void 0;
if (typeof err === "string") message = err;
else if (err && typeof err === "object") {
const anyErr = err;
message = anyErr.message || JSON.stringify(anyErr);
if (typeof anyErr.status === "number") status = anyErr.status;
}
entry.error = status ? { message, status } : { message };
}
async function write() {
try {
const { logsDir, filePath } = buildPaths();
await fs.promises.mkdir(logsDir, { recursive: true });
const block = JSON.stringify(entry, null, 2) + "\n\n";
await fs.promises.appendFile(filePath, block, { encoding: "utf8" });
} catch (e) {
console.error(
"auditLog write failed:",
e instanceof Error ? e.message : String(e)
);
}
}
return {
requestId,
setChatId,
setUserRequest,
setRenderTarget,
setInputs,
setOutput,
setError,
write
};
}
// src/helpers/cameraImageMetadata.ts
var import_exifr = __toESM(require_full_umd());
var EXIF_FIELDS = [
"DateTimeOriginal",
"CreateDate",
"ModifyDate",
"Make",
"Model",
"ImageWidth",
"ImageHeight",
"ExifImageWidth",
"ExifImageHeight",
"GPSLatitude",
"GPSLatitudeRef",
"GPSLongitude",
"GPSLongitudeRef",
"LensModel",
"ExposureTime",
"FNumber",
"ISO",
"ISOSpeedRatings",
"ExposureCompensation",
"FocalLength",
"FocalLengthIn35mmFormat",
"ExposureProgram",
"MeteringMode",
"WhiteBalance",
"Flash",
"Orientation",
"ExposureMode"
];
function nonEmptyString(value) {
return typeof value === "string" && value.trim() ? value.trim() : void 0;
}
function finitePositiveNumber(value) {
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
}
function finiteNumber(value) {
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
}
function scalarString(value) {
if (typeof value === "string") return nonEmptyString(value);
if (typeof value === "number" && Number.isFinite(value)) return String(value);
return void 0;
}
function finiteCoordinate(value) {
return typeof value === "number" && Number.isFinite(value) && value >= -180 && value <= 180 ? value : void 0;
}
function decimalCoordinate(value, reference) {
const decimal = Array.isArray(value) ? value.length === 3 && value.every((part) => typeof part === "number" && Number.isFinite(part)) ? value[0] + value[1] / 60 + value[2] / 3600 : void 0 : finiteCoordinate(value);
if (decimal === void 0) return void 0;
const direction = nonEmptyString(reference)?.toUpperCase();
return direction === "S" || direction === "W" ? -decimal : decimal;
}
function isoTimestamp(value) {
if (!(value instanceof Date) || Number.isNaN(value.getTime())) return void 0;
return value.toISOString();
}
async function readCameraImageMetadata(input) {
try {
const bytes = typeof input === "string" ? await promises.readFile(input) : input;
const fields = await import_exifr.default.parse(bytes, { pick: EXIF_FIELDS });
if (!fields) return {};
const make = nonEmptyString(fields.Make);
const model = nonEmptyString(fields.Model);
const camera = [make, model].filter((value, index, values) => value && values.indexOf(value) === index).join(" ") || void 0;
return {
capturedAt: isoTimestamp(fields.DateTimeOriginal) ?? isoTimestamp(fields.CreateDate) ?? isoTimestamp(fields.ModifyDate),
camera,
width: finitePositiveNumber(fields.ExifImageWidth) ?? finitePositiveNumber(fields.ImageWidth),
height: finitePositiveNumber(fields.ExifImageHeight) ?? finitePositiveNumber(fields.ImageHeight),
latitude: decimalCoordinate(fields.GPSLatitude, fields.GPSLatitudeRef),
longitude: decimalCoordinate(fields.GPSLongitude, fields.GPSLongitudeRef),
lensModel: nonEmptyString(fields.LensModel),
exposureTime: scalarString(fields.ExposureTime),
fNumber: finitePositiveNumber(fields.FNumber),
iso: finitePositiveNumber(fields.ISO) ?? finitePositiveNumber(fields.ISOSpeedRatings),
exposureCompensation: finiteNumber(fields.ExposureCompensation),
focalLength: finitePositiveNumber(fields.FocalLength),
focalLength35mm: finitePositiveNumber(fields.FocalLengthIn35mmFormat),
exposureProgram: scalarString(fields.ExposureProgram),
meteringMode: scalarString(fields.MeteringMode),
whiteBalance: scalarString(fields.WhiteBalance),
flash: scalarString(fields.Flash),
orientation: scalarString(fields.Orientation),
exposureMode: scalarString(fields.ExposureMode)
};
} catch {
return {};
}
}
var DEFAULT_HOST = "127.0.0.1";
function envPort() {
const v = process.env.HTTP_SERVER_PORT;
if (v == null || String(v).trim() === "") return void 0;
const n = Number(v);
return Number.isInteger(n) && n >= 1024 && n <= 65535 ? n : void 0;
}
async function healthCheck(port, host = DEFAULT_HOST) {
return new Promise((resolve) => {
const req = http.get(
{ host, port, path: "/__healthz", timeout: 600 },
(res) => {
try {
const ok = (res.statusCode || 0) === 200 && String(res.headers["x-mcp-image-server"]) === "1";
res.resume();
res.once("end", () => resolve(ok));
} catch {
resolve(false);
}
}
);
req.on("timeout", () => {
try {
req.destroy();
} catch {
}
resolve(false);
});
req.on("error", () => resolve(false));
});
}
function toHttpOriginalUrl(fileName, baseUrl, chatId) {
if (chatId) {
return `${baseUrl.replace(/\/$/, "")}/${encodeURIComponent(
chatId
)}/${encodeURIComponent(fileName)}`;
}
return `${baseUrl.replace(/\/$/, "")}/${encodeURIComponent(fileName)}`;
}
function toHttpPreviewUrl(fileName, baseUrl, chatId) {
if (chatId) {
return `${baseUrl.replace(/\/$/, "")}/${encodeURIComponent(
chatId
)}/${encodeURIComponent(fileName)}`;
}
return `${baseUrl.replace(/\/$/, "")}/previews/${encodeURIComponent(
fileName
)}`;
}
async function getHealthyServerBaseUrl(host = DEFAULT_HOST) {
try {
const fixedPort = envPort();
if (fixedPort == null) return "";
const ok = await healthCheck(fixedPort, host).catch(() => false);
if (ok) return `http://127.0.0.1:${fixedPort}`;
return "";
} catch {
return "";
}
}
function scoreToConfidence(score) {
if (score >= 2) return "high";
if (score === 1) return "medium";
return "low";
}
async function sleep(ms) {
return new Promise((r) => setTimeout(r, ms));
}
async function resolveActiveLMStudioChatId(opts) {
const retries = 4;
const delayMs = 200;
const recentSec = 120;
try {
const home = findLMStudioHome();
const convDir = path.join(home, "conversations");
if (!fs.existsSync(convDir)) {
return {
ok: false,
reason: `LM Studio conversations dir not found: ${convDir}`
};
}
let chosenPath = null;
let mtimeMs = 0;
for (let attempt = 0; attempt < Math.max(1, retries); attempt++) {
const entries = await fs.promises.readdir(convDir).catch(() => []);
const convFiles = entries.filter((f) => f.endsWith(".conversation.json")).map((f) => path.join(convDir, f));
if (convFiles.length === 0) {
if (attempt < retries - 1) {
await sleep(delayMs);
continue;
}
return { ok: false, reason: "No conversation files found" };
}
const withTimes = convFiles.map((p) => {
try {
const s = fs.statSync(p);
return s.isFile() ? { p, t: s.mtimeMs } : null;
} catch {
return null;
}
}).filter(Boolean);
if (withTimes.length === 0) {
if (attempt < retries - 1) {
await sleep(delayMs);
continue;
}
return { ok: false, reason: "No readable conversation files" };
}
withTimes.sort((a, b) => b.t - a.t);
chosenPath = withTimes[0].p;
mtimeMs = withTimes[0].t;
try {
const raw = await fs.promises.readFile(chosenPath, "utf8");
JSON.parse(raw);
break;
} catch {
if (attempt < retries - 1) {
await sleep(delayMs);
continue;
}
break;
}
}
if (!chosenPath)
return { ok: false, reason: "Failed to pick conversation" };
const chatId = path.basename(chosenPath).replace(/\.conversation\.json$/i, "");
let score = 0;
let reason = [];
if (mtimeMs > 0) {
const ageSec = (Date.now() - mtimeMs) / 1e3;
if (ageSec <= recentSec) {
score += 1;
reason.push(`recent:${Math.round(ageSec)}s`);
} else {
reason.push(`stale:${Math.round(ageSec)}s`);
}
}
try {
const raw = await fs.promises.readFile(chosenPath, "utf8");
JSON.parse(raw);
score += 1;
reason.push("parse_ok");
} catch {
reason.push("parse_uncertain");
}
return {
ok: true,
chatId,
filePath: chosenPath,
mtimeMs,
confidence: scoreToConfidence(score),
reason: reason.join(",")
};
} catch (e) {
return { ok: false, reason: e?.message || String(e) };
}
}
// src/helpers/resolveImg2ImgSourceLMStudio.ts
var LM_HOME = path.join(os.homedir(), ".lmstudio");
try {
const h = findLMStudioHome();
if (h && typeof h === "string") LM_HOME = h;
} catch {
}
util.promisify(child_process.exec);
// src/interfaces/control.ts
__toESM(require_flatbuffers());
// src/interfaces/generation-configuration.ts
__toESM(require_flatbuffers());
// src/interfaces/lo-ra.ts
__toESM(require_flatbuffers());
// src/interfaces/tensor-history-node.ts
__toESM(require_flatbuffers());
// src/interfaces/text-history-node.ts
__toESM(require_flatbuffers());
var TOOL_MIN_RENDER_DIM = drawthingsLimits.min;
var TOOL_MAX_WIDTH = drawthingsLimits.maxWidth;
var TOOL_MAX_HEIGHT = drawthingsLimits.maxHeight;
var TOOL_MAX_PREVIEW_W = drawthingsLimits.maxWidth;
var ZOOM_TOOL_MAX_DIM = 2048;
function normalizeQualityToInt(q, def) {
let n = typeof q === "string" ? parseFloat(q) : typeof q === "number" ? q : def;
if (!Number.isFinite(n)) n = def;
if (n > 0 && n <= 1) n = n * 100;
n = Math.round(n);
if (n < 1) n = 1;
if (n > 100) n = 100;
return n;
}
var GenerateToolParamsSchemaBase = zod.z.object({
prompt: zod.z.string().optional(),
negative_prompt: zod.z.string().optional(),
width: zod.z.coerce.number().int().min(TOOL_MIN_RENDER_DIM, `width must be >= ${TOOL_MIN_RENDER_DIM}`).max(TOOL_MAX_WIDTH, `width must be <= ${TOOL_MAX_WIDTH}`).optional(),
height: zod.z.coerce.number().int().min(TOOL_MIN_RENDER_DIM, `height must be >= ${TOOL_MIN_RENDER_DIM}`).max(TOOL_MAX_HEIGHT, `height must be <= ${TOOL_MAX_HEIGHT}`).optional(),
steps: zod.z.coerce.number().int().min(1, "steps must be >= 1").max(50, "steps must be <= 50").optional(),
seed: zod.z.coerce.number().int().optional(),
guidance_scale: zod.z.coerce.number().min(0, "guidance_scale must be >= 0").max(50, "guidance_scale must be <= 50").optional(),
model: zod.z.string().optional(),
sampler: zod.z.union([zod.z.string(), zod.z.number()]).optional(),
numFrames: zod.z.coerce.number().int().min(1, "numFrames must be >= 1").max(641, "numFrames must be <= 641").optional(),
random_string: zod.z.string().optional(),
// preview controls (validation only; defaults applied by caller)
previewFormat: zod.z.enum(["jpeg", "webp"], {
errorMap: () => ({ message: "previewFormat must be 'jpeg' or 'webp'" })
}).optional(),
previewMaxWidth: zod.z.coerce.number().int().min(128).max(TOOL_MAX_PREVIEW_W).optional(),
previewMinWidth: zod.z.coerce.number().int().min(128).max(TOOL_MAX_PREVIEW_W).optional(),
previewMaxBytes: zod.z.coerce.number().int().min(2e3).max(2e5).optional(),
previewQuality: zod.z.union([zod.z.coerce.number(), zod.z.string()]).transform((v) => normalizeQualityToInt(v, NaN)).optional(),
previewMinQuality: zod.z.union([zod.z.coerce.number(), zod.z.string()]).transform((v) => normalizeQualityToInt(v, NaN)).optional(),
previewQualityStep: zod.z.coerce.number().int().min(1).max(20).optional(),
previewScaleStep: zod.z.coerce.number().min(0.5).max(0.98).optional(),
previewInChat: zod.z.coerce.boolean().optional(),
alt: zod.z.string().max(120).optional(),
// saving
saveOriginal: zod.z.coerce.boolean().optional(),
saveDir: zod.z.string().optional()
}).passthrough();
GenerateToolParamsSchemaBase.superRefine((d, ctx) => {
if (d.previewMinWidth !== void 0 && d.previewMaxWidth !== void 0 && d.previewMinWidth > d.previewMaxWidth) {
ctx.addIssue({
code: "custom",
path: ["previewMinWidth"],
message: "previewMinWidth must be <= previewMaxWidth"
});
}
if (d.previewMinQuality !== void 0 && d.previewQuality !== void 0 && d.previewMinQuality > d.previewQuality) {
ctx.addIssue({
code: "custom",
path: ["previewMinQuality"],
message: "previewMinQuality must be <= previewQuality"
});
}
});
zod.z.union([
zod.z.string().transform(
(s) => s.split(/[\s,]+/).map((x) => parseInt(x.trim(), 10)).filter((n) => Number.isInteger(n) && n >= 1)
),
zod.z.coerce.number().int().min(1).transform((n) => [n]),
zod.z.array(zod.z.coerce.number().int().min(1))
]).optional();
function normalizeSourceToken(raw) {
let s = String(raw ?? "").trim();
if (!s) return "";
s = s.replace(/^[\[{(]+/, "").replace(/[\]})]+$/, "");
s = s.replace(/^['"`]+|['"`]+$/g, "");
s = s.replace(/[;:.!?]+$/, "");
s = s.trim();
if (/^a$/i.test(s)) return "a1";
if (/^v$/i.test(s)) return "v1";
if (/^p$/i.test(s)) return "p1";
if (/^i$/i.test(s)) return "i1";
return s;
}
var SourceNotation = zod.z.preprocess(
(v) => typeof v === "string" ? normalizeSourceToken(v) : v,
zod.z.string().trim().regex(
/^([avpi]|[avpi]?[1-9]\d*)$/i,
"Source notation: 'a1', 'v2', 'p1', 'i3', or digit when unambiguous"
)
);
var SourceNotationList = zod.z.union([
// String form: split by comma/space and validate each part
zod.z.string().transform(
(s) => s.split(/[\s,]+/).map((x) => normalizeSourceToken(x)).filter((x) => x.length > 0)
),
// Array form: validate each element
zod.z.array(SourceNotation)
]).refine(
(arr) => arr.every((x) => /^([avpi]|[avpi]?[1-9]\d*)$/i.test(String(x))),
"moodboard contains invalid source notation(s)"
);
var GenerateToolParamsShapeMinimal = {
prompt: zod.z.string().optional().describe(
"Image description (mode: 'text2image') OR description of desired changes (mode: 'image2image')."
),
width: zod.z.coerce.number().int().min(TOOL_MIN_RENDER_DIM).max(TOOL_MAX_WIDTH).optional().describe(`Width in pixels (max ${TOOL_MAX_WIDTH}). Has sensible default.`),
height: zod.z.coerce.number().int().min(TOOL_MIN_RENDER_DIM).max(TOOL_MAX_HEIGHT).optional().describe(`Height in pixels (max ${TOOL_MAX_HEIGHT}). Has sensible default.`),
imageFormat: zod.z.enum(["square", "landscape", "portrait", "16:9"]).optional().describe("Aspect ratio shorthand. Override if context suggests. '16:9' yields 1024\xD7576 (video-optimized)."),
quality: zod.z.enum(["low", "medium", "high", "auto"]).optional().describe("Quality preset (affects steps). Default is balanced."),
// model preset selection — default 'auto' uses built-in settings
model: zod.z.string().optional().describe("Model preset. 'ltx' selects LTX-2 for video generation. Default 'auto' selects best model for the mode."),
// number of images to generate in one call
variants: zod.z.coerce.number().int().min(1).max(4).optional().describe("Number of images (1-4). Default is 1."),
// number of video frames
numFrames: zod.z.coerce.number().int().min(1, "numFrames must be >= 1").max(641, "numFrames must be <= 641").optional().describe("Number of video frames. Must be a multiple of 32 (or multiple of 32 + 1). Default: 1 (image). Silently ignored for non-video modes."),
// image2image controls (only allow selecting a prior variant)
mode: zod.z.enum(["text2image", "image2image", "edit", "text2video", "image2video", "refine"]).optional().describe("Generation mode. 'text2video'/'image2video' require a video-capable model (e.g. 'ltx'). Required when sources exist."),
// Primary source for image2image/edit
canvas: zod.z.union([
SourceNotation,
zod.z.array(SourceNotation).min(1).max(1).transform((arr) => arr[0])
]).optional().describe(
"Primary source image. Notation: 'a1', 'v2', 'p1', 'i3'. Digit-only allowed only when unambiguous. Tolerates single-item array input."
),
// Additional style references for image2image/edit modes (gRPC only for image2image)
moodboard: SourceNotationList.optional().describe(
"Additional style references for image2image/edit modes. Array of source notations (same format as canvas). Note: moodboard for image2image requires gRPC transport."
)
};
zod.z.object(GenerateToolParamsShapeMinimal).strict();
zod.z.union([zod.z.number(), zod.z.string()]).optional();
var cropSideOrNullField = zod.z.union([zod.z.number(), zod.z.string()]).nullable();
var cropSideOverrideArray = zod.z.array(cropSideOrNullField).optional();
var cropSideOrArrayBase = zod.z.preprocess(
(val) => {
if (typeof val === "string") {
const t = val.trim();
if (t.startsWith("[")) {
try {
const parsed = JSON.parse(t);
if (Array.isArray(parsed)) {
return parsed.map((el) => {
if (typeof el === "string") {
if (el.trim().toLowerCase() === "null") return null;
const n = Number(el.trim());
if (!isNaN(n)) return n;
}
return el;
});
}
} catch {
}
}
}
return val;
},
zod.z.union([zod.z.number(), zod.z.string(), cropSideOverrideArray.unwrap()])
);
cropSideOrArrayBase.optional();
var detectLabelBase = zod.z.preprocess(
(val) => {
if (typeof val === "string") {
const t = val.trim();
if (t.startsWith("[")) {
try {
const parsed = JSON.parse(t);
if (Array.isArray(parsed)) {
return parsed.map((el) => String(el).trim()).filter((s) => s.length > 0);
}
} catch {
}
}
}
return val;
},
zod.z.union([
zod.z.string().transform(
(s) => s.split(/\s*,\s*/).map((x) => x.trim()).filter((x) => x.length > 0)
),
zod.z.array(zod.z.string().min(1))
])
);
var CropToolParamsShape = {
canvas: zod.z.union([
SourceNotation,
zod.z.array(SourceNotation).min(1).max(1).transform((arr) => arr[0])
]).optional().describe(
"Source image. Notation: 'a1', 'v2', 'p1', 'i3'. Tolerates single-item array input."
),
cropLeft: zod.z.union([zod.z.number(), zod.z.string(), zod.z.array(zod.z.union([zod.z.number(), zod.z.string()]).nullable())]).optional().describe(
"Amount to remove from the left side. Scalar (single-region): number or px-string, e.g. '120px'. Array (multi-region, parallel to detectLabel): one entry per label; null = keep detection value. Default unit: % (0\u201399)."
),
cropRight: zod.z.union([zod.z.number(), zod.z.string(), zod.z.array(zod.z.union([zod.z.number(), zod.z.string()]).nullable())]).optional().describe(
"Amount to remove from the right side. Scalar or per-box array (null = keep detection value). Default unit: % (0\u201399)."
),
cropTop: zod.z.union([zod.z.number(), zod.z.string(), zod.z.array(zod.z.union([zod.z.number(), zod.z.string()]).nullable())]).optional().describe(
"Amount to remove from the top. Scalar or per-box array (null = keep detection value). Default unit: % (0\u201399)."
),
cropBottom: zod.z.union([zod.z.number(), zod.z.string(), zod.z.array(zod.z.union([zod.z.number(), zod.z.string()]).nullable())]).optional().describe(
"Amount to remove from the bottom. Scalar or per-box array (null = keep detection value). Default unit: % (0\u201399)."
),
imageFormat: zod.z.enum(["square", "landscape", "portrait", "16:9"]).optional().describe(
"Target aspect ratio. Only active when explicitly set; omitting it crops with the given sides only (no AR enforcement). Ignored when all 4 crop sides are explicitly given. Unspecified axes are centred; single-side anchors are honoured. 'square'=1:1, 'landscape'=4:3, 'portrait'=3:4, '16:9'=16:9."
),
detectLabel: detectLabelBase.optional().describe(
`Label (or array of labels) of detected objects to mask/crop (requires a prior detect_object run). Array form enables multi-region: one bbox per entry, parallel to detectIndex and per-box crop overrides. String form: comma-separated labels, e.g. "cat, dog". Multi-word labels are supported. canvas may be the original source (e.g. 'a1') or the detect_object result (e.g. 'i3').`
),
detectIndex: zod.z.union([
zod.z.string().transform((s) => (s.match(/\d+/g) ?? []).map(Number)),
zod.z.coerce.number().int().min(0).transform((n) => [n]),
zod.z.array(zod.z.coerce.number().int().min(0))
]).optional().describe(
"Zero-based index (or array of indices, parallel to detectLabel) to select among multiple detections with the same label. Default 0 for each entry."
),
frameAdjust: zod.z.union([zod.z.number(), zod.z.string()]).optional().describe(
"Expand (positive) or shrink (negative) each detection bounding box. Applied as the final step after all overrides. Number: percent of bbox diagonal. String: value + optional 'px' suffix, e.g. '20px' or '-10%'. Applies to all regions uniformly."
)
};
zod.z.object(CropToolParamsShape).strict();
var ZoomInToolParamsShape = {
prompt: zod.z.string().optional().describe("Description of desired changes to apply during re-rendering. Default: empty (preserve content)."),
width: zod.z.coerce.number().int().min(TOOL_MIN_RENDER_DIM).max(ZOOM_TOOL_MAX_DIM).optional().describe(`Output width in pixels (max ${ZOOM_TOOL_MAX_DIM}). Has sensible default.`),
height: zod.z.coerce.number().int().min(TOOL_MIN_RENDER_DIM).max(ZOOM_TOOL_MAX_DIM).optional().describe(`Output height in pixels (max ${ZOOM_TOOL_MAX_DIM}). Has sensible default.`),
quality: zod.z.enum(["low", "medium", "high", "auto"]).optional().describe("Quality preset (affects steps). Default is balanced."),
model: zod.z.string().optional().describe("Model preset for image2image re-rendering. Default 'auto'."),
canvas: zod.z.union([
SourceNotation,
zod.z.array(SourceNotation).min(1).max(1).transform((arr) => arr[0])
]).optional().describe(
"Source image. Notation: 'a1', 'v2', 'p1', 'i3'. Tolerates single-item array input."
),
detectLabel: zod.z.string().optional().describe(
"Crop to the bounding box of a detected object by label (requires a prior detect_object run). canvas may be the original source (e.g. 'a1') or the detect_object result (e.g. 'i3')."
),
detectIndex: zod.z.coerce.number().int().min(0).optional().describe("Zero-based index to select among multiple detections with the same label. Default 0."),
frameAdjust: zod.z.union([zod.z.number(), zod.z.string()]).optional().describe(
"Expand (positive) or shrink (negative) the detection bounding box before cropping. Number: percent of min(W,H). String: value + optional 'px' suffix, e.g. '20px' or '-10%'."
),
imageFormat: zod.z.enum(["square", "landscape", "portrait", "16:9"]).optional().describe(
"Target aspect ratio for the render output. Only active when explicitly set. 'square'=1:1, 'landscape'=4:3, 'portrait'=3:4, '16:9'=16:9."
)
};
zod.z.object(ZoomInToolParamsShape).strict();
var InpaintToolParamsShape = {
prompt: zod.z.string().optional().describe("Description of desired changes to apply during re-rendering. Default: empty (preserve content)."),
width: zod.z.coerce.number().int().min(TOOL_MIN_RENDER_DIM).max(ZOOM_TOOL_MAX_DIM).optional().describe(`Output width in pixels (max ${ZOOM_TOOL_MAX_DIM}). Has sensible default.`),
height: zod.z.coerce.number().int().min(TOOL_MIN_RENDER_DIM).max(ZOOM_TOOL_MAX_DIM).optional().describe(`Output height in pixels (max ${ZOOM_TOOL_MAX_DIM}). Has sensible default.`),
quality: zod.z.enum(["low", "medium", "high", "auto"]).optional().describe("Quality preset (affects steps). Default is balanced."),
model: zod.z.string().optional().describe("Model preset for image2image re-rendering. Default 'auto'."),
canvas: zod.z.union([
SourceNotation,
zod.z.array(SourceNotation).min(1).max(1).transform((arr) => arr[0])
]).optional().describe(
"Source image. Notation: 'a1', 'v2', 'p1', 'i3'. Tolerates single-item array input."
),
detectLabel: detectLabelBase.optional().describe(
`Label (or array of labels) of detected objects to inpaint. Preferred: array, e.g. ["left eye", "right eye"]. Also accepts comma-separated string, e.g. "left eye, right eye". Multi-word labels are supported. Each entry selects one detection bounding box. Requires a prior detect_object run. canvas may be the source (e.g. 'a1') or the detect_object result (e.g. 'i3').`
),
detectIndex: zod.z.union([
zod.z.string().transform((s) => (s.match(/\d+/g) ?? []).map(Number)),
zod.z.coerce.number().int().min(0).transform((n) => [n]),
zod.z.array(zod.z.coerce.number().int().min(0))
]).optional().describe(
"Zero-based index (or array of indices, parallel to detectLabel) to select among multiple detections with the same label. Default 0 for each entry."
),
frameAdjust: zod.z.union([zod.z.number(), zod.z.string()]).optional().describe(
"Expand (positive) or shrink (negative) each detection bounding box. Applied as the final step after all per-box overrides. Applies uniformly to all regions. Number: percent of bbox diagonal. String: value + optional 'px' suffix, e.g. '20px' or '-10%'."
),
cropLeft: cropSideOrArrayBase.optional().describe(
"Override the left crop side for the detected region(s). Scalar = same for all; array (parallel to detectLabel, null = keep detection value) for per-box control. Default unit: % (0\u201399) or append 'px'."
),
cropRight: cropSideOrArrayBase.optional().describe(
"Override the right crop side. Scalar or per-box array (null = keep detection value). Default unit: %."
),
cropTop: cropSideOrArrayBase.optional().describe(
"Override the top crop side. Scalar or per-box array (null = keep detection value). Default unit: %."
),
cropBottom: cropSideOrArrayBase.optional().describe(
"Override the bottom crop side. Scalar or per-box array (null = keep detection value). Default unit: %."
)
};
zod.z.object(InpaintToolParamsShape).strict();
var OutpaintToolParamsShape = {
prompt: zod.z.string().optional().describe("Description of desired changes to apply during re-rendering. Default: empty (preserve content)."),
width: zod.z.coerce.number().int().min(TOOL_MIN_RENDER_DIM).max(ZOOM_TOOL_MAX_DIM).optional().describe(`Output width in pixels (max ${ZOOM_TOOL_MAX_DIM}). Has sensible default.`),
height: zod.z.coerce.number().int().min(TOOL_MIN_RENDER_DIM).max(ZOOM_TOOL_MAX_DIM).optional().describe(`Output height in pixels (max ${ZOOM_TOOL_MAX_DIM}). Has sensible default.`),
quality: zod.z.enum(["low", "medium", "high", "auto"]).optional().describe("Quality preset (affects steps). Default is balanced."),
model: zod.z.string().optional().describe("Model preset for image2image re-rendering. Default 'auto'."),
canvas: zod.z.union([
SourceNotation,
zod.z.array(SourceNotation).min(1).max(1).transform((arr) => arr[0])
]).optional().describe(
"Source image. Notation: 'a1', 'v2', 'p1', 'i3'. Tolerates single-item array input."
),
detectLabel: detectLabelBase.optional().describe(
`Label (or array of labels) of detected objects to outpaint. Preferred: array, e.g. ["left eye", "right eye"]. Also accepts comma-separated string, e.g. "left eye, right eye". Multi-word labels are supported. Each entry selects one detection bounding box. Requires a prior detect_object run. canvas may be the source (e.g. 'a1') or the detect_object result (e.g. 'i3').`
),
detectIndex: zod.z.union([
zod.z.string().transform((s) => (s.match(/\d+/g) ?? []).map(Number)),
zod.z.coerce.number().int().min(0).transform((n) => [n]),
zod.z.array(zod.z.coerce.number().int().min(0))
]).optional().describe(
"Zero-based index (or array of indices, parallel to detectLabel) to select among multiple detections with the same label. Default 0 for each entry."
),
frameAdjust: zod.z.union([zod.z.number(), zod.z.string()]).optional().describe(
"Expand (positive) or shrink (negative) each detection bounding box. Applied as the final step after all per-box overrides. Applies uniformly to all regions. Number: percent of bbox diagonal. String: value + optional 'px' suffix, e.g. '20px' or '-10%'."
),
cropLeft: cropSideOrArrayBase.optional().describe(
"Override the left crop side for the detected region(s). Scalar = same for all; array (parallel to detectLabel, null = keep detection value) for per-box control. Default unit: % (0\u201399) or append 'px'."
),
cropRight: cropSideOrArrayBase.optional().describe(
"Override the right crop side. Scalar or per-box array (null = keep detection value). Default unit: %."
),
cropTop: cropSideOrArrayBase.optional().describe(
"Override the top crop side. Scalar or per-box array (null = keep detection value). Default unit: %."
),
cropBottom: cropSideOrArrayBase.optional().describe(
"Override the bottom crop side. Scalar or per-box array (null = keep detection value). Default unit: %."
)
};
zod.z.object(OutpaintToolParamsShape).strict();
var UpscaleToolParamsShape = {
prompt: zod.z.string().optional().describe("Description of desired changes to apply during re-rendering. Default: empty (preserve content)."),
width: zod.z.coerce.number().int().min(TOOL_MIN_RENDER_DIM).max(ZOOM_TOOL_MAX_DIM).optional().describe(`Output width in pixels (max ${ZOOM_TOOL_MAX_DIM}). Has sensible default.`),
height: zod.z.coerce.number().int().min(TOOL_MIN_RENDER_DIM).max(ZOOM_TOOL_MAX_DIM).optional().describe(`Output height in pixels (max ${ZOOM_TOOL_MAX_DIM}). Has sensible default.`),
scaleFactor: zod.z.number().positive().optional().describe("Scale factor applied to the canvas dimensions. E.g. 2 doubles the resolution. Mutually exclusive with width/height."),
quality: zod.z.enum(["low", "medium", "high", "auto"]).optional().describe("Quality preset (affects steps). Default is balanced."),
model: zod.z.string().optional().describe("Model preset for image2image re-rendering. Default 'auto'."),
canvas: zod.z.union([
SourceNotation,
zod.z.array(SourceNotation).min(1).max(1).transform((arr) => arr[0])
]).optional().describe(
"Source image. Notation: 'a1', 'v2', 'p1', 'i3'. Tolerates single-item array input."
),
imageFormat: zod.z.string().optional().describe("Target image format / aspect ratio preset (e.g. '16:9', 'portrait'). Resolved to concrete pixel dimensions.")
};
zod.z.object(UpscaleToolParamsShape).strict();
var RefineToolParamsShape = {
canvas: zod.z.union([
SourceNotation,
zod.z.array(SourceNotation).min(1).max(1).transform((arr) => arr[0])
]).optional().describe(
"Source image. Notation: 'a1', 'v2', 'p1', 'i3'. Tolerates single-item array input."
),
model: zod.z.string().describe(
"Required. Model preset to use for refinement. model: z-image produces a polished, refined look. model: krea or model: krea2 uses Krea 2 Turbo. model: qwen-image or model: flux produces a more natural, organic look."
),
width: zod.z.coerce.number().int().min(TOOL_MIN_RENDER_DIM).max(ZOOM_TOOL_MAX_DIM).optional().describe(`Output width in pixels (max ${ZOOM_TOOL_MAX_DIM}). Defaults to canvas width.`),
height: zod.z.coerce.number().int().min(TOOL_MIN_RENDER_DIM).max(ZOOM_TOOL_MAX_DIM).optional().describe(`Output height in pixels (max ${ZOOM_TOOL_MAX_DIM}). Defaults to canvas height.`),
imageFormat: zod.z.string().optional().describe("Target image format / aspect ratio preset (e.g. '16:9', 'portrait'). Resolved to concrete pixel dimensions.")
};
zod.z.object(RefineToolParamsShape).strict();
zod.z.union([
// String form: split by comma/space
zod.z.string().transform(
(s) => s.split(/[\s,]+/).map((x) => x.trim()).filter((x) => x.length > 0)
),
// Array form: pass through
zod.z.array(zod.z.string())
]).refine((arr) => arr.length >= 1, "targets must contain at least one notation").refine((arr) => arr.length <= 32, "targets must contain at most 32 notations");
({
variant: zod.z.coerce.string().describe(
"Reference to the video to review. Use standard media notation: vN for variants, iN for images, pN for pictures (e.g. v1, i3). A bare integer N is also accepted and treated as vN. Must correspond to a generated video."
),
fps: zod.z.coerce.number().min(0.1).max(30).optional().default(2).describe(
"Frame sampling rate in fps. Default: 2. Higher values send more frames to the model."
)
});
function cssColorToRgbaInt(color){const named={pink:[255,105,180],red:[255,0,0],green:[0,128,0],lime:[0,255,0],blue:[0,0,255],yellow:[255,255,0],cyan:[0,255,255],magenta:[255,0,255],white:[255,255,255],black:[0,0,0],orange:[255,165,0],purple:[128,0,128]};const lower=color.trim().toLowerCase();if(named[lower]){const[r,g,b]=named[lower];return ((r&255)<<24|(g&255)<<16|(b&255)<<8|255)>>>0}const rgb3=lower.match(/^#([0-9a-f])([0-9a-f])([0-9a-f])$/);if(rgb3){const r=parseInt(rgb3[1]+rgb3[1],16);const g=parseInt(rgb3[2]+rgb3[2],16);const b=parseInt(rgb3[3]+rgb3[3],16);return ((r&255)<<24|(g&255)<<16|(b&255)<<8|255)>>>0}const rgb6=lower.match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/);if(rgb6){const r=parseInt(rgb6[1],16);const g=parseInt(rgb6[2],16);const b=parseInt(rgb6[3],16);return ((r&255)<<24|(g&255)<<16|(b&255)<<8|255)>>>0}return ((255&255)<<24|(105&255)<<16|(180&255)<<8|255)>>>0}async function drawBboxesOnImage(buffer,bboxes,options){const sourceDims=options?.sourceDims;const usePalette=options?.palette!==false;const lineWeight=options?.lineWeight??2;const singleColorInt=usePalette?0:cssColorToRgbaInt(options?.color??"pink");const requireFn=typeof require!=="undefined"?require:(await import('module')).createRequire(__filename);const jimpMod=requireFn("jimp");const Jimp=jimpMod.Jimp??jimpMod.default??jimpMod;if(!Jimp||typeof Jimp.read!=="function"){throw new Error("drawBboxesOnImage: Jimp.read not available")}const img=await Jimp.read(buffer);const imgW=typeof img.getWidth==="function"?img.getWidth():typeof img.width==="number"?img.width:img.bitmap?.width||0;const imgH=typeof img.getHeight==="function"?img.getHeight():typeof img.height==="number"?img.height:img.bitmap?.height||0;const scaleX=sourceDims&&sourceDims.width>0?imgW/sourceDims.width:1;const scaleY=sourceDims&&sourceDims.height>0?imgH/sourceDims.height:1;const palette=[[255,59,48,255],[52,199,89,255],[0,122,255,255],[255,159,10,255],[191,90,242,255],[255,214,10,255]];for(let bi=0;bi<bboxes.length;bi++){let colorInt;if(usePalette){const[r,g,b,a]=palette[bi%palette.length];colorInt=((r&255)<<24|(g&255)<<16|(b&255)<<8|a&255)>>>0;}else {colorInt=singleColorInt;}const[bx1,by1,bx2,by2]=bboxes[bi];const x1=Math.max(0,Math.min(imgW-1,Math.round(bx1*scaleX)));const y1=Math.max(0,Math.min(imgH-1,Math.round(by1*scaleY)));const x2=Math.max(0,Math.min(imgW-1,Math.round(bx2*scaleX)));const y2=Math.max(0,Math.min(imgH-1,Math.round(by2*scaleY)));for(let t=0;t<lineWeight;t++){for(let x=x1;x<=x2;x++){if(y1+t<imgH)img.setPixelColor(colorInt,x,y1+t);if(y2-t>=0)img.setPixelColor(colorInt,x,y2-t);}for(let y=y1;y<=y2;y++){if(x1+t<imgW)img.setPixelColor(colorInt,x1+t,y);if(x2-t>=0)img.setPixelColor(colorInt,x2-t,y);}}}const bufResult=typeof img.getBufferAsync==="function"?img.getBufferAsync("image/png"):img.getBuffer("image/png");if(bufResult&&typeof bufResult.then==="function"){return bufResult}return new Promise((resolve,reject)=>img.getBuffer("image/png",(err,data)=>err?reject(err):resolve(data)))}
const CRC_TABLE=(()=>{const table=new Int32Array(256);for(let n=0;n<256;n++){let crc=n;for(let bit=0;bit<8;bit++){crc=crc&1?0xedb88320^crc>>>1:crc>>>1;}table[n]=crc;}return table})();function crc32(data){let crc=0xffffffff;for(let index=0;index<data.length;index++){crc=CRC_TABLE[(crc^data[index])&255]^crc>>>8;}return (crc^0xffffffff)>>>0}function normPath(absolutePath){const home=os$1.homedir();return absolutePath.startsWith(home)?`~${absolutePath.slice(home.length)}`:absolutePath}function escapeXml(value){return value.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function getPngDimensions(pngBuffer){if(pngBuffer.length<24||!pngBuffer.subarray(0,8).equals(Buffer.from([137,80,78,71,13,10,26,10]))||pngBuffer.toString("ascii",12,16)!=="IHDR"){return undefined}return {width:pngBuffer.readUInt32BE(16),height:pngBuffer.readUInt32BE(20)}}function buildXmpString(params){const createdAt=escapeXml(params.createdAt??new Date().toISOString());const metadata={};if(params.prompt!=null)metadata["c"]=params.prompt;if(params.model)metadata["model"]=params.model;if(typeof params.width==="number"&&typeof params.height==="number"){metadata["size"]=`${params.width}x${params.height}`;}if(params.sources?.length)metadata["sources"]=params.sources.map(normPath);if(params.mode)metadata["mode"]=params.mode;if(params.analysis)metadata["analysis"]=params.analysis;metadata["generated_by"]=params.generatedBy??"ceveyne/analyse-image";const description=[];if(params.prompt)description.push(params.prompt);const details=[];if(typeof params.width==="number"&&typeof params.height==="number")details.push(`Size: ${params.width}x${params.height}`);if(params.model)details.push(`Model: ${params.model}`);if(details.length)description.push(details.join(", "));if(params.sources?.length)description.push(`Source: ${params.sources.map(normPath).join(", ")}`);return [`<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 6.0.0">`,` <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">`,` <rdf:Description rdf:about=""`,` xmlns:dc="http://purl.org/dc/elements/1.1/"`,` xmlns:xmp="http://ns.adobe.com/xap/1.0/"`,` xmlns:exif="http://ns.adobe.com/exif/1.0/">`,` <dc:description><rdf:Alt><rdf:li xml:lang="x-default">${escapeXml(description.join("\n"))}</rdf:li></rdf:Alt></dc:description>`,` <xmp:CreatorTool>${escapeXml(params.creatorTool??params.generatedBy??"ceveyne/analyse-image")}</xmp:CreatorTool>`,` <xmp:CreateDate>${createdAt}</xmp:CreateDate>`,` <exif:UserComment><rdf:Alt><rdf:li xml:lang="x-default">${JSON.stringify(metadata)}</rdf:li></rdf:Alt></exif:UserComment>`,` </rdf:Description>`,` </rdf:RDF>`,`</x:xmpmeta>`].join("\n")}function buildITxtChunk(text){const data=Buffer.concat([Buffer.from("XML:com.adobe.xmp","utf8"),Buffer.from([0,0,0,0,0]),Buffer.from(text,"utf8")]);const typeAndData=Buffer.concat([Buffer.from("iTXt","ascii"),data]);const chunk=Buffer.allocUnsafe(12+data.length);chunk.writeUInt32BE(data.length,0);chunk.write("iTXt",4,"ascii");data.copy(chunk,8);chunk.writeUInt32BE(crc32(typeAndData),8+data.length);return chunk}function injectXmpIntoBuffer(pngBuffer,params){const dimensions=getPngDimensions(pngBuffer);if(!dimensions)return pngBuffer;const populatedParams={...params,width:params.width??dimensions.width,height:params.height??dimensions.height};const chunk=buildITxtChunk(buildXmpString(populatedParams));const insertAt=8+25;return Buffer.concat([pngBuffer.subarray(0,insertAt),chunk,pngBuffer.subarray(insertAt)])}
const JSON_FENCE_RE=/```(?:json)?\s*([\s\S]*?)```/i;const ITEM_RE=/\{\s*"bbox_2d":\s*\[(\d+),\s*(\d+),\s*(\d+),\s*(\d+)\],\s*"label":\s*"([^"]+)"\s*\}/gi;const JSON_FORMAT=" Output JSON only — a JSON array where each element has"+" 'bbox_2d' ([x1, y1, x2, y2] as integers normalized 0–1000) and 'label' (a string)."+" No prose, no markdown, no explanation.";const LABEL_FORMAT_RULE="\n\nLABEL FORMAT RULE (mandatory):"+"\n- Labels must be concise and specific: 2–4 words maximum."+"\n- No commas or punctuation inside a label (no ',', '.', ';', ':', '/') — downstream tools split labels on commas."+"\n- Examples: 'plugin list', 'plugin name', 'human face', 'left hand', 'red car', 'fluffy owl toy'";function normalizeLmApiRoot(baseUrl){return String(baseUrl||"").trim().replace(/\/(api\/v1|v1)\/?$/i,"").replace(/\/+$/,"")}function authHeaders(apiKey,contentType=false){const headers={};if(contentType)headers["Content-Type"]="application/json";if(apiKey?.trim())headers.Authorization=`Bearer ${apiKey.trim()}`;return headers}function logVisionRequestMetadata(metadata){const line=`[LmStudioVisionAnalyzer] /api/v1/chat request ${JSON.stringify(metadata)}`;console.info(line);try{const logsDir=getLogsDir();if(!fs.existsSync(logsDir))fs.mkdirSync(logsDir,{recursive:true});fs.appendFileSync(path.join(logsDir,"user-docs-plugin.log"),`${new Date().toISOString()} - ${line}
`,"utf8");}catch{}}function hasLoadedInstances(modelInfo){return Array.isArray(modelInfo?.loaded_instances)&&modelInfo.loaded_instances.length>0}async function getVisionModelState(baseUrl,apiKey,modelKey){const apiRoot=normalizeLmApiRoot(baseUrl);if(!apiRoot)return {loaded:false};const normalizedModelKey=modelKey.trim().toLowerCase();const controller=new AbortController;const timeout=setTimeout(()=>controller.abort(),5e3);try{const response=await fetch(`${apiRoot}/api/v1/models`,{headers:authHeaders(apiKey),signal:controller.signal});if(!response.ok)return {loaded:false};const data=await response.json();const models=Array.isArray(data)?data:Array.isArray(data?.models)?data.models:Array.isArray(data?.data)?data.data:[];const modelInfo=models.find(entry=>{const key=String(entry?.key||entry?.id||"").trim().toLowerCase();return key===normalizedModelKey});if(!modelInfo)return {loaded:false};return {loaded:hasLoadedInstances(modelInfo),modelKey:String(modelInfo?.key||modelInfo?.id||"").trim()||undefined}}catch{return {loaded:false}}finally{clearTimeout(timeout);}}async function loadVisionInstanceViaApi(baseUrl,apiKey,modelKey){const apiRoot=normalizeLmApiRoot(baseUrl);if(!apiRoot){return {ok:false,error:"Vision API base URL is empty."}}const controller=new AbortController;const timeout=setTimeout(()=>controller.abort(),6e5);try{const response=await fetch(`${apiRoot}/api/v1/models/load`,{method:"POST",headers:authHeaders(apiKey,true),body:JSON.stringify({model:modelKey,echo_load_config:true}),signal:controller.signal});const text=await response.text().catch(()=>"");let data=null;if(text.trim()){try{data=JSON.parse(text);}catch{data={raw:text};}}const apiError=data?.error?.message||data?.error||data?.message;if(!response.ok||apiError){const detail=apiError||text||`${response.status} ${response.statusText}`;return {ok:false,error:`Vision API could not load '${modelKey}' via /api/v1/models/load. This can happen when there are not enough system resources available. Error: ${detail}`}}return {ok:true}}catch(error){const detail=error?.name==="AbortError"?"request timed out after 600000 ms":error?.message||String(error);return {ok:false,error:`Vision API could not load '${modelKey}' via /api/v1/models/load. This can happen when there are not enough system resources available. Error: ${detail}`}}finally{clearTimeout(timeout);}}async function ensureLmStudioVisionInstanceReady(config){const modelKey=String(config.modelKey||"").trim();if(!modelKey){return {ok:false,error:"Vision API mode is active, but Qwen3-VL model key is empty."}}const initialState=await getVisionModelState(config.baseUrl,config.apiKey,modelKey);if(initialState.loaded){return {ok:true,loaded:false}}try{config.status?.(`Loading ${modelKey}...`);}catch{}const loadResult=await loadVisionInstanceViaApi(config.baseUrl,config.apiKey,modelKey);if(!loadResult.ok)return loadResult;const loadedState=await getVisionModelState(config.baseUrl,config.apiKey,modelKey);if(!loadedState.loaded){return {ok:false,error:`Vision API loaded '${modelKey}' via /api/v1/models/load, but /api/v1/models did not report it as loaded.`}}if(loadedState.modelKey?.trim().toLowerCase()!==modelKey.toLowerCase()){return {ok:false,error:`Vision API loaded a model, but /api/v1/models reports '${loadedState.modelKey||"unknown model"}' instead of '${modelKey}'.`}}return {ok:true,loaded:true}}function mimeFromPath(filePath){const ext=path.extname(filePath).toLowerCase();if(ext===".jpg"||ext===".jpeg")return "image/jpeg";if(ext===".webp")return "image/webp";if(ext===".gif")return "image/gif";return "image/png"}function readUInt24LE(buffer,offset){return buffer[offset]|buffer[offset+1]<<8|buffer[offset+2]<<16}function readPngDimensions(buffer){if(buffer.length<24)return null;if(buffer.toString("ascii",1,4)!=="PNG")return null;return {width:buffer.readUInt32BE(16),height:buffer.readUInt32BE(20)}}function readGifDimensions(buffer){if(buffer.length<10)return null;const signature=buffer.toString("ascii",0,6);if(signature!=="GIF87a"&&signature!=="GIF89a")return null;return {width:buffer.readUInt16LE(6),height:buffer.readUInt16LE(8)}}function readWebpDimensions(buffer){if(buffer.length<30)return null;if(buffer.toString("ascii",0,4)!=="RIFF"||buffer.toString("ascii",8,12)!=="WEBP"){return null}const chunkType=buffer.toString("ascii",12,16);if(chunkType==="VP8X"&&buffer.length>=30){return {width:readUInt24LE(buffer,24)+1,height:readUInt24LE(buffer,27)+1}}if(chunkType==="VP8L"&&buffer.length>=25&&buffer[20]===47){const bits=buffer.readUInt32LE(21);return {width:(bits&16383)+1,height:(bits>>14&16383)+1}}if(chunkType==="VP8 "&&buffer.length>=30){return {width:buffer.readUInt16LE(26)&16383,height:buffer.readUInt16LE(28)&16383}}return null}function readJpegDimensions(buffer){if(buffer.length<4||buffer[0]!==255||buffer[1]!==216)return null;let offset=2;while(offset+9<buffer.length){if(buffer[offset]!==255){offset+=1;continue}while(offset<buffer.length&&buffer[offset]===255)offset+=1;const marker=buffer[offset];offset+=1;if(marker===217||marker===218)break;if(offset+2>buffer.length)break;const segmentLength=buffer.readUInt16BE(offset);if(segmentLength<2||offset+segmentLength>buffer.length)break;const isStartOfFrame=marker>=192&&marker<=195||marker>=197&&marker<=199||marker>=201&&marker<=203||marker>=205&&marker<=207;if(isStartOfFrame&&segmentLength>=7){return {height:buffer.readUInt16BE(offset+3),width:buffer.readUInt16BE(offset+5)}}offset+=segmentLength;}return null}async function readImageDimensions(filePath){const buffer=await fs.promises.readFile(filePath);const dimensions=readPngDimensions(buffer)||readJpegDimensions(buffer)||readWebpDimensions(buffer)||readGifDimensions(buffer);if(!dimensions||dimensions.width<=0||dimensions.height<=0){throw new Error(`Could not determine image dimensions for ${filePath}`)}return dimensions}function extractMessageText(data){const output=Array.isArray(data?.output)?data.output:[];const pieces=[];for(const item of output){if(item?.type!=="message")continue;const content=item?.content;if(typeof content==="string"){pieces.push(content);}else if(Array.isArray(content)){for(const part of content){if(typeof part==="string"){pieces.push(part);}else if(typeof part?.text==="string"){pieces.push(part.text);}else if(typeof part?.content==="string"){pieces.push(part.content);}}}}if(pieces.length===0&&typeof data?.text==="string"){pieces.push(data.text);}if(pieces.length===0&&typeof data?.content==="string"){pieces.push(data.content);}return pieces.join("\n").trim()}function buildDetectPrompt(task,odPrompt){const label=String(task||"").trim();if(label){return `Detect all instances of '${label}' in the image.`+LABEL_FORMAT_RULE+JSON_FORMAT}const instruction=String(odPrompt||"").trim();if(!instruction){throw new Error("No OD prompt available: odPrompt not set and DETECT_OD_PROMPT env var not set")}return instruction+LABEL_FORMAT_RULE+JSON_FORMAT}function bboxToCrop(bbox,width,height){const[x1,y1,x2,y2]=bbox;return {cropLeft:x1/width*100,cropRight:(width-x2)/width*100,cropTop:y1/height*100,cropBottom:(height-y2)/height*100}}function parseQwen3VlDetectionOutput(text,width,height){const objects=[];const seen=new Set;const fenceMatch=JSON_FENCE_RE.exec(text);const jsonText=fenceMatch?fenceMatch[1].trim():text.trim();let items=null;try{const parsed=JSON.parse(jsonText);items=Array.isArray(parsed)?parsed:[parsed];}catch{const recovered=[];ITEM_RE.lastIndex=0;for(const match of text.matchAll(ITEM_RE)){recovered.push({bbox_2d:[Number(match[1]),Number(match[2]),Number(match[3]),Number(match[4])],label:match[5]});}items=recovered.length>0?recovered:[];}for(const item of items){if(!item||typeof item!=="object")continue;const bbox=item.bbox_2d;const label=String(item.label||"");if(!Array.isArray(bbox)||bbox.length!==4)continue;const[nx1,ny1,nx2,ny2]=bbox.map(value=>Number(value));if(![nx1,ny1,nx2,ny2].every(value=>Number.isFinite(value)&&value>=0&&value<=1e3)){continue}if(nx2<=nx1||ny2<=ny1)continue;if(nx1<10&&ny1<10&&nx2>990&&ny2>990)continue;const dedupKey=`${Math.round(nx1)}:${Math.round(ny1)}:${Math.round(nx2)}:${Math.round(ny2)}:${label}`;if(seen.has(dedupKey))continue;seen.add(dedupKey);const pixelBbox=[nx1/1e3*width,ny1/1e3*height,nx2/1e3*width,ny2/1e3*height];objects.push({label,bbox:pixelBbox,...bboxToCrop(pixelBbox,width,height)});}return objects}async function chatOnce(item,prompt,config){const apiRoot=normalizeLmApiRoot(config.baseUrl);if(!apiRoot){throw new Error("Vision API base URL is empty")}const endpoint=`${apiRoot}/api/v1/chat`;const timeoutMs=config.timeoutMs??18e4;const model=config.model||"vision-capability-priming";const buf=await fs.promises.readFile(item.filePath);const dataUrl=`data:${mimeFromPath(item.filePath)};base64,${buf.toString("base64")}`;const payload={model,input:[{type:"text",content:prompt},{type:"image",data_url:dataUrl}],store:false};if(typeof config.maxTokens==="number"&&Number.isFinite(config.maxTokens)&&config.maxTokens>0){payload.max_output_tokens=Math.floor(config.maxTokens);}if(typeof config.temperature==="number"&&Number.isFinite(config.temperature)){payload.temperature=config.temperature;}logVisionRequestMetadata({configuredBaseUrl:config.baseUrl,apiRoot,endpoint,model,store:payload.store,max_output_tokens:payload.max_output_tokens??null,temperature:payload.temperature??null,promptChars:prompt.length,imageBytes:buf.byteLength,inputTypes:Array.isArray(payload.input)?payload.input.map(part=>part.type):[],payloadKeys:Object.keys(payload)});const headers=authHeaders(config.apiKey,true);const controller=new AbortController;const timeout=setTimeout(()=>controller.abort(),timeoutMs);const startedAt=Date.now();let data;try{const resp=await fetch(endpoint,{method:"POST",headers,body:JSON.stringify(payload),signal:controller.signal});clearTimeout(timeout);if(!resp.ok){const detail=await resp.text().catch(()=>"(no body)");throw new Error(`Vision API ${resp.status}: ${detail}`)}data=await resp.json();}catch(error){clearTimeout(timeout);if(error?.name==="AbortError"){throw new Error(`Vision API timed out after ${timeoutMs}ms`)}throw new Error(`Vision API failed: ${error?.message||String(error)}`)}return {text:extractMessageText(data),elapsedMs:Date.now()-startedAt,bytes:buf.byteLength,modelInstanceId:typeof data?.model_instance_id==="string"?data.model_instance_id:""}}async function analyzeLmStudioVisionBatch(items,config){if(!items.length){return {results:[],totalInferenceTimeMs:0,backend:"vision-api"}}const results=[];let totalInferenceTimeMs=0;for(const item of items){console.info(`[LmStudioVisionAnalyzer] /api/v1/chat start mode=analyze id=${item.id} timeoutMs=${config.timeoutMs??18e4}`);const response=await chatOnce(item,config.prompt||"Describe the image.",config);console.info(`[LmStudioVisionAnalyzer] /api/v1/chat ok mode=analyze id=${item.id} bytes=${response.bytes} elapsedMs=${response.elapsedMs} modelInstance=${response.modelInstanceId||"?"}`);results.push({id:item.id,text:response.text,inferenceTimeMs:response.elapsedMs});totalInferenceTimeMs+=response.elapsedMs;}return {results,totalInferenceTimeMs,backend:"vision-api"}}async function detectLmStudioVisionBatch(items,config){if(!items.length){return {results:[],totalInferenceTimeMs:0,backend:"vision-api"}}const prompt=buildDetectPrompt(config.task,config.odPrompt);const results=[];let totalInferenceTimeMs=0;for(const item of items){const{width,height}=await readImageDimensions(item.filePath);console.info(`[LmStudioVisionAnalyzer] /api/v1/chat start mode=detect id=${item.id} timeoutMs=${config.timeoutMs??12e4}`);const response=await chatOnce(item,prompt,{baseUrl:config.baseUrl,apiKey:config.apiKey,model:config.model||"vision-capability-priming",maxTokens:config.maxTokens,temperature:config.temperature,timeoutMs:config.timeoutMs??12e4});const objects=parseQwen3VlDetectionOutput(response.text,width,height);console.info(`[LmStudioVisionAnalyzer] /api/v1/chat ok mode=detect id=${item.id} objects=${objects.length} bytes=${response.bytes} elapsedMs=${response.elapsedMs} modelInstance=${response.modelInstanceId||"?"}`);results.push({id:item.id,objects,imageWidth:width,imageHeight:height,inferenceTimeMs:response.elapsedMs});totalInferenceTimeMs+=response.elapsedMs;}return {results,totalInferenceTimeMs,backend:"vision-api"}}
function reportToolStatus(ctx,message){try{if(typeof ctx.status==="function"){ctx.status(stripTrailingStatusPunctuation(message));}}catch{}}function stripTrailingStatusPunctuation(message){return message.trim().replace(/\.{1,3}$/,"")}function formatToolStep(step,totalSteps,message){const pct=totalSteps>0?Math.round(step/(totalSteps+1)*100):0;const prefix=`Step ${step}/${totalSteps} (${pct}%)`;return message?`${stripTrailingStatusPunctuation(message)} - ${prefix}`:prefix}function reportToolStep(ctx,step,totalSteps,message){reportToolStatus(ctx,formatToolStep(step,totalSteps,message));}
const defaultPluginSettings={PREVIEW_IN_CHAT:true,HTTP_SERVER_PORT:54760,embeddingBaseUrl:"http://127.0.0.1:1234/v1",embeddingApiKey:"",qwen3VlModelPath:"qwen/qwen3-vl-8b",visionPrompt:"",embedPngMetadata:true,serverMaxTokens:768,serverTemperature:.7,qwen3VlOdPrompt:["Detect objects in the image with strict hierarchical prioritization.","","PRIORITY 1 (CRITICAL - MUST DETECT FIRST):",'- You MUST detect "human face" (highest priority if a person is present)','- You MUST detect "person" (if no face is clearly visible or if the person is the main subject)',"","PRIORITY 2 (MAIN SUBJECT / HERO ELEMENT):","- The most visually prominent object or subject that is NOT part of the background.","- Use specific, concrete labels (e.g., 'red car', 'fluffy owl toy').","- Avoid generic terms like 'object' or 'thing'.","","PRIORITY 3 (CONTEXTUAL BACKGROUND ELEMENTS):","- Only detect background elements if they are significant to the scene composition OR if the main subject is interacting with them.","- Do not detect minor or redundant background details.","","PRIORITY 4 (FOCUSSED MAIN SUBJECT / HERO ELEMENT):","- All visible body parts (hands, feet, arms, legs).","- Elements of the face, as far as clearly detectable and focussed on close-ups: nose, mouth, left and right eyes, eyebrows and ears","- anatomical details, as far as recognizable as \\\"focussed\\\" or \\\"prominent\\\" (e.g., 'iris', 'pupil', 'eyelid')","","RULES:","- Maximum 16 objects total.","- Each bounding box must be unique and non-redundant.","- For clothing, name the specific garment (e.g., 'tank top', 'jeans').","- For body parts, qualify by position (e.g., 'left hand').","- NEVER prioritize background elements over the main subject or human face.","- NEVER prioritize anatomical details over general concepts unless they are solely focussed (e.g. only detect 'eyes' unless 'human face' is the dominant part of the image)","- If the main subject is a person, focus on the person and their immediate interactions. Ignore background elements unless they are directly involved in the interaction.","- If NO person or face is visible, ALWAYS detect Priority 2 and Priority 3 subjects regardless."].join("\n"),detectMaxTokens:2048,detectTemperature:.3,includeGenerationMetadata:true};const globalConfigSchematics=sdk.createConfigSchematics().field("PREVIEW_IN_CHAT","boolean",{displayName:"Previews in Chat",subtitle:"When enabled, tool responses include inline image previews. Recommended for local models without vision capability.",engineDoesNotSupport:false},defaultPluginSettings.PREVIEW_IN_CHAT).field("HTTP_SERVER_PORT","numeric",{displayName:"Local HTTP Server Port",subtitle:"Port for serving generated images over localhost (default: 54760).",engineDoesNotSupport:true},defaultPluginSettings.HTTP_SERVER_PORT).field("embeddingBaseUrl","string",{displayName:"Vision API Base URL",subtitle:"OpenAI-compatible /v1 URL. Vision tools use the same server root and call LM Studio's internal /api/v1 vision endpoints. Separate from the agent API.",placeholder:"http://127.0.0.1:1234/v1",engineDoesNotSupport:false},defaultPluginSettings.embeddingBaseUrl).field("embeddingApiKey","string",{displayName:"Vision API Key",subtitle:"Optional key for the Qwen3-VL vision backend. Separate from the agent API key.",isProtected:true,placeholder:"sk-...",engineDoesNotSupport:false},defaultPluginSettings.embeddingApiKey).field("qwen3VlModelPath","string",{displayName:"Qwen3-VL Model",subtitle:"LM Studio model key for the Qwen3-VL Vision API backend, for example qwen/qwen3-vl-8b. This is not a filesystem path.",placeholder:"qwen/qwen3-vl-8b",engineDoesNotSupport:false},defaultPluginSettings.qwen3VlModelPath).field("visionPrompt","string",{displayName:"Vision Prompt",subtitle:"Default prompt sent to the vision model when the agent does not supply one. Leave empty to disable automatic visual description.",placeholder:"Analyze this image based strictly on what is directly visible. Do not infer, assume, or complete information that is not present.",isParagraph:true},defaultPluginSettings.visionPrompt).field("embedPngMetadata","boolean",{displayName:"Embed Metadata in PNGs",subtitle:"Write analysis provenance, detected objects, and bounding boxes into saved PNGs as Draw Things-compatible XMP metadata.",engineDoesNotSupport:false},defaultPluginSettings.embedPngMetadata).field("includeGenerationMetadata","boolean",{displayName:"Include Generation Metadata",subtitle:"When enabled, Draw Things generation parameters (prompt, model, sampler, seed, ...) embedded in PNG files are appended to each analysis result.",engineDoesNotSupport:false},defaultPluginSettings.includeGenerationMetadata).field("serverMaxTokens","numeric",{displayName:"Vision API: Max Tokens",subtitle:"Maximum response length in tokens (1-4096). Default: 768.",engineDoesNotSupport:true},defaultPluginSettings.serverMaxTokens).field("serverTemperature","numeric",{displayName:"Vision API: Temperature",subtitle:"Sampling temperature (0.0-2.0). Default: 0.7.",engineDoesNotSupport:true},defaultPluginSettings.serverTemperature).field("qwen3VlOdPrompt","string",{displayName:"Qwen3-VL: Object Detection Prompt",subtitle:"Instruction sent to Qwen3-VL for default object detection. Leave empty to use the built-in default.",placeholder:"",isParagraph:true,engineDoesNotSupport:false},defaultPluginSettings.qwen3VlOdPrompt).field("detectMaxTokens","numeric",{displayName:"Vision API Detect: Max Tokens",subtitle:"Maximum response length in tokens for object detection (1-4096). Default: 2048.",engineDoesNotSupport:true},defaultPluginSettings.detectMaxTokens).field("detectTemperature","numeric",{displayName:"Vision API Detect: Temperature",subtitle:"Sampling temperature for object detection (0.0-2.0). Default: 0.3.",engineDoesNotSupport:true},defaultPluginSettings.detectTemperature).build();
function isoStampCompact$1(){const d=new Date;const year=d.getUTCFullYear();const month=String(d.getUTCMonth()+1).padStart(2,"0");const day=String(d.getUTCDate()).padStart(2,"0");const hours=String(d.getUTCHours()).padStart(2,"0");const minutes=String(d.getUTCMinutes()).padStart(2,"0");const seconds=String(d.getUTCSeconds()).padStart(2,"0");const millis=String(d.getUTCMilliseconds()).padStart(3,"0");return `${year}${month}${day}T${hours}${minutes}${seconds}${millis}Z`}function parsePrefixedNotation$1(s){const t=String(s||"").trim().toLowerCase();const m=t.match(/^([avip])(\d+)$/);if(!m)return null;const idx=Math.max(1,parseInt(m[2],10));const pool=m[1]==="a"?"attachment":m[1]==="v"?"variant":m[1]==="i"?"image":"picture";return {pool,index:idx}}function formatPluginMeta$2(){return formatToolMetaBlock()}function getGlobalConfig$2(ctl){const ctlAny=ctl;const getter=ctlAny.getGlobalPluginConfig||ctlAny.getGlobalConfig;if(!getter)return null;try{return getter.call(ctl,globalConfigSchematics)}catch{return null}}function getGlobalString$2(gcfg,key,fallback){try{const value=gcfg?.get(key);return typeof value==="string"?value:fallback}catch{return fallback}}function getGlobalNumber$2(gcfg,key,fallback){try{const value=gcfg?.get(key);return typeof value==="number"&&Number.isFinite(value)?value:fallback}catch{return fallback}}function getGlobalBoolean$1(gcfg,key,fallback){try{const value=gcfg?.get(key);return typeof value==="boolean"?value:fallback}catch{return fallback}}function applyFrameAdjust(bbox,frameAdjust,imgW,imgH){const[x1,y1,x2,y2]=bbox;const diag=Math.hypot(x2-x1,y2-y1);let d_px;if(typeof frameAdjust==="string"){const m=String(frameAdjust).trim().match(/^([+-]?\d+(?:\.\d+)?)\s*(%|px)?$/i);if(!m)return bbox;const val=parseFloat(m[1]);d_px=m[2]?.toLowerCase()==="px"?val:val/100*diag;}else {d_px=frameAdjust/100*diag;}return [Math.max(0,Math.round(x1-d_px)),Math.max(0,Math.round(y1-d_px)),Math.min(imgW-1,Math.round(x2+d_px)),Math.min(imgH-1,Math.round(y2+d_px))]}const FlexibleTargetsList$2=zod.z.union([zod.z.string().transform(s=>(s.match(/[aivp]\d+/gi)??[]).map(x=>x.toLowerCase())),zod.z.array(zod.z.string())]).refine(arr=>arr.length>=1,"targets must contain at least one notation").refine(arr=>arr.length<=16,"targets must contain at most 16 notations");const AnnotateImageParamsShape={targets:FlexibleTargetsList$2.optional().describe("One or more image notations to process. Each notation is a letter followed by a number: "+"a=attachment (a1, a2, …), i=generated image (i1, i2, …), v=variant (v1, v2, …), p=picture (p1, p2, …). "+'Pass via the targets field, e.g. annotate_image({"targets":["a1", "i3"]}). '+"Omit when there is exactly one image — it will be selected automatically."),task:zod.z.string().optional().default("").describe("What to detect. Omit for full-image general object detection. "+"Use natural language to target specific subjects (e.g. 'all faces and hands', 'the dog', 'cars and bicycles'). "+"Not used on correction calls — detections are loaded from state."),color:zod.z.string().optional().describe("Box color for all detected objects. CSS color name or hex, e.g. 'pink', 'red', '#FF0000'. Default: pink."),lineWeight:zod.z.coerce.number().int().min(1).max(50).optional().describe("Line thickness in pixels for all bounding boxes. Default: 5."),frameAdjust:zod.z.union([zod.z.number(),zod.z.string()]).optional().describe("Expand (positive) or shrink (negative) bounding boxes before drawing. "+"Number: percent of box diagonal (e.g. 5 = +5 %). "+"String: value + optional 'px' or '%' suffix, e.g. '10px', '-5%'. "+"Without detectLabel: applied to all boxes. With detectLabel: applied to the selected box only. Default: 5."),detectLabel:zod.z.preprocess(val=>{if(typeof val==="string"){const t=val.trim();if(t.startsWith("[")){try{const parsed=JSON.parse(t);if(Array.isArray(parsed)){return parsed.map(el=>String(el).trim()).filter(s=>s.length>0)}}catch{}}}return val},zod.z.union([zod.z.string().transform(s=>s.split(/\s*,\s*/).map(x=>x.trim()).filter(x=>x.length>0)),zod.z.array(zod.z.string().min(1))])).optional().describe("On a correction call: label(s) to match (case-insensitive). "+"Single string, comma-separated list ('left eye, right eye'), or JSON array. "+"When set, ONLY the matching detection(s) are drawn — all others are omitted. "+"Single label with no detectIndex: auto-expands to ALL detections for that label (Option A). "+"canvas may be the original source (e.g. a1) or the previous annotate_image result (e.g. i3)."),detectIndex:zod.z.union([zod.z.string().transform(s=>(s.match(/\d+/g)??[]).map(Number)),zod.z.coerce.number().int().min(0).transform(n=>[n]),zod.z.array(zod.z.coerce.number().int().min(0))]).optional().describe("Zero-based index or list of indices, parallel to detectLabel. "+"indices[li] ?? indices[0] ?? 0 for missing entries. Default: 0. "+"Single label + multiple indices draws that label at each specified occurrence (Option B). "+"Bracket notation accepted: '[2, 4, 7]'."),x1:zod.z.preprocess(val=>{if(typeof val==="string"&&val.trim().startsWith("[")){try{return JSON.parse(val)}catch{}}return val},zod.z.union([zod.z.number(),zod.z.array(zod.z.union([zod.z.number(),zod.z.null()]))])).optional().describe("Manual left edge(s) in original image pixels. "+"Scalar: applies to all selected detections. Array (parallel to detectLabel): null = keep stored value. "+"E.g. x1=[null,50,null] moves only the second detection's left edge."),y1:zod.z.preprocess(val=>{if(typeof val==="string"&&val.trim().startsWith("[")){try{return JSON.parse(val)}catch{}}return val},zod.z.union([zod.z.number(),zod.z.array(zod.z.union([zod.z.number(),zod.z.null()]))])).optional().describe("Manual top edge(s) in original image pixels. Scalar or array (null = keep stored). See x1."),x2:zod.z.preprocess(val=>{if(typeof val==="string"&&val.trim().startsWith("[")){try{return JSON.parse(val)}catch{}}return val},zod.z.union([zod.z.number(),zod.z.array(zod.z.union([zod.z.number(),zod.z.null()]))])).optional().describe("Manual right edge(s) in original image pixels. Scalar or array (null = keep stored). See x1."),y2:zod.z.preprocess(val=>{if(typeof val==="string"&&val.trim().startsWith("[")){try{return JSON.parse(val)}catch{}}return val},zod.z.union([zod.z.number(),zod.z.array(zod.z.union([zod.z.number(),zod.z.null()]))])).optional().describe("Manual bottom edge(s) in original image pixels. Scalar or array (null = keep stored). See x1.")};function resolveCoordOverride(override,index){if(override===undefined)return undefined;if(Array.isArray(override)){const entry=override[index];if(entry===null||entry===undefined)return undefined;return entry}return override}function expandDetectIndices(detections,label){const lower=label.toLowerCase();let count=0;for(const d of detections){if(d.label.toLowerCase()===lower)count++;}return count>1?Array.from({length:count},(_,i)=>i):[0]}function createAnnotateImageTool(ctl){return sdk.tool({name:"annotate_image",description:`Highlights specific areas or elements on images. If well described, these elements are precisely framed with bounding boxes in the chosen color.
Detections are saved to state for later correction, refinement, or re-drawing with adjusted labels and edge positions.
--- Correction call (omit task) ---
Redraws from stored detections without inference. canvas may be the original source (e.g. a1) or a previous annotate_image result (e.g. i3).
Without detectLabel: ALL stored detections are drawn with the given color/lineWeight/frameAdjust.
With detectLabel: ONLY the matching detection(s) are drawn.
- Option A — single label, no detectIndex: auto-expands to ALL detections for that label.
detectLabel="face" with 8 stored faces → draws all 8 faces.
- Option B — single label, multiple detectIndex: draws that label at each specified occurrence.
detectLabel="face", detectIndex="[2,4,7]" → draws face #2, #4, #7.
- Multi-label — comma-separated or array: one detection per label.
detectLabel="left eye, right eye" → draws both eyes.
Adjusting box edges (x1/y1/x2/y2):
- Scalar: applies the same value to all selected boxes.
detectLabel="face, hand, dog", y2=300 → all three boxes get bottom edge at y=300.
- Array (parallel to detectLabel, null = keep stored value): per-box override.
detectLabel="face, hand, dog", y2=[null, null, 240] → only dog's bottom edge moves to y=240.
- Partial: omitted axes always keep the stored value.
detectLabel="face", y2=240 → only the bottom edge changes, x1/y1/x2 unchanged.
--- Re-detect with new prompt ---
Pass task to force fresh inference even on an already-annotated image. Replaces stored detections.
Parameters:
- targets: Image notation(s), e.g. ["a1"]. Omit when exactly one image is available.
- task: What to detect (natural language). Omit on a correction call. Providing task always triggers fresh inference.
- color: Box color (CSS name or hex). Default: pink.
- lineWeight: Line thickness in pixels. Default: 5.
- frameAdjust: Expand (+) or shrink (−) boxes as % of box diagonal or absolute px. Default: 5.
- detectLabel: Label(s) to match (case-insensitive). Comma-separated or array. When set, draws ONLY matching detections.
- detectIndex: Index or list of indices, parallel to detectLabel. Bracket notation '[2,4,7]' accepted. Default: 0.
- x1/y1/x2/y2: Box edge override(s) in original image pixels. Scalar or array parallel to detectLabel (null = keep stored).
${formatPluginMeta$2()}`,parameters:AnnotateImageParamsShape,implementation:async(args,ctx)=>{try{let rawTargets=[];if(Array.isArray(args?.targets)){rawTargets=args.targets.map(s=>String(s).trim()).filter(Boolean);}else if(typeof args?.targets==="string"&&args.targets.trim()){const trimmed=args.targets.trim();if(trimmed.startsWith("[")){try{const parsed=JSON.parse(trimmed);rawTargets=Array.isArray(parsed)?parsed.map(s=>String(s).trim()).filter(Boolean):trimmed.split(/[\s,]+/).map(s=>s.trim()).filter(Boolean);}catch{rawTargets=trimmed.split(/[\s,]+/).map(s=>s.trim()).filter(Boolean);}}else {rawTargets=trimmed.split(/[\s,]+/).map(s=>s.trim()).filter(Boolean);}}const taskArg=typeof args?.task==="string"&&args.task.trim()?args.task.trim():"";const globalColor=typeof args?.color==="string"&&args.color.trim()?args.color.trim():"pink";const globalLineWeight=typeof args?.lineWeight==="number"?Math.max(1,Math.min(50,Math.round(args.lineWeight))):5;const globalFrameAdjust=args?.frameAdjust!==undefined?args.frameAdjust:5;let globalLabels;{const dlRaw=args?.detectLabel;if(Array.isArray(dlRaw)){const arr=dlRaw.map(s=>String(s).trim()).filter(Boolean);if(arr.length>0)globalLabels=arr;}else if(typeof dlRaw==="string"&&dlRaw.trim()){globalLabels=dlRaw.split(/\s*,\s*/).map(x=>x.trim()).filter(Boolean);}}let globalIndices=[];{const diRaw=args?.detectIndex;if(Array.isArray(diRaw)){globalIndices=diRaw.map(n=>typeof n==="number"?Math.floor(n):parseInt(String(n),10)).filter(n=>!isNaN(n)&&n>=0);}else if(typeof diRaw==="string"&&diRaw.trim()){globalIndices=(diRaw.match(/\d+/g)??[]).map(Number);}else if(typeof diRaw==="number"&&diRaw>=0){globalIndices=[Math.floor(diRaw)];}}const rawX1=args?.x1;const rawY1=args?.y1;const rawX2=args?.x2;const rawY2=args?.y2;function normaliseCoord(raw){if(raw===undefined||raw===null)return undefined;if(typeof raw==="number")return raw;if(Array.isArray(raw))return raw.map(v=>v===null||v===undefined?null:Number(v));if(typeof raw==="string"){const t=raw.trim();if(t.startsWith("[")){try{const p=JSON.parse(t);if(Array.isArray(p))return p.map(v=>v===null||v===undefined?null:Number(v))}catch{}}const n=Number(t);return isNaN(n)?undefined:n}return undefined}const manualX1=normaliseCoord(rawX1);const manualY1=normaliseCoord(rawY1);const manualX2=normaliseCoord(rawX2);const manualY2=normaliseCoord(rawY2);const hasAnyManualCoord=manualX1!==undefined||manualY1!==undefined||manualX2!==undefined||manualY2!==undefined;console.log("[annotate_image] invoked",{targets:rawTargets,task:taskArg,color:globalColor,lineWeight:globalLineWeight,frameAdjust:globalFrameAdjust,detectLabels:globalLabels,detectIndices:globalIndices,manualCoords:{x1:manualX1,y1:manualY1,x2:manualX2,y2:manualY2}});let currentLmChatId=null;let currentLmWorkingDir=null;try{const chatCtx=await getActiveChatContext();if(chatCtx?.chatId)currentLmChatId=chatCtx.chatId;if(chatCtx?.workingDir)currentLmWorkingDir=chatCtx.workingDir;}catch{}if(!currentLmChatId){try{const resolved=await resolveActiveLMStudioChatId();if(resolved?.ok)currentLmChatId=resolved.chatId;}catch{}}const primaryOutDir=currentLmWorkingDir||(currentLmChatId?getLMStudioWorkingDir(currentLmChatId):undefined);if(!primaryOutDir){return {content:[{type:"text",text:"annotate_image failed: could not resolve LM Studio chat working directory."}],isError:true}}await fs.promises.mkdir(primaryOutDir,{recursive:true}).catch(()=>{});try{await syncAttachmentsToState(primaryOutDir,false,Number.MAX_SAFE_INTEGER);}catch(e){console.warn("[annotate_image] attachment sync failed (non-fatal):",e?.message??e);}const st=await readState$1(primaryOutDir);const attachments=Array.isArray(st?.attachments)?st.attachments:[];const pictures=Array.isArray(st?.pictures)?st.pictures:[];const imageRecords=Array.isArray(st?.images)?st.images:[];const variantRecords=Array.isArray(st?.variants)?st.variants:[];async function resolvePreviewBuf(notation){const pref=parsePrefixedNotation$1(notation);if(!pref)throw new Error(`Invalid notation: ${notation}`);if(pref.pool==="attachment"){const rec=attachments.find(a=>a?.a===pref.index);const r=rec&&typeof rec.preview==="string"?rec.preview:"";if(!r)throw new Error(`Preview for a${pref.index} not found.`);return fs.promises.readFile(path.join(primaryOutDir,r))}if(pref.pool==="image"){const rec=imageRecords.find(r=>r?.i===pref.index);const r=rec&&typeof rec.preview==="string"?rec.preview:"";if(!r)throw new Error(`Preview for i${pref.index} not found.`);return fs.promises.readFile(path.join(primaryOutDir,r))}if(pref.pool==="variant"){const rec=variantRecords.find(v=>v?.v===pref.index);const r=rec&&typeof rec.preview==="string"?rec.preview:"";if(!r)throw new Error(`Preview for v${pref.index} not found.`);return fs.promises.readFile(path.join(primaryOutDir,r))}const rec=pictures.find(p=>p?.p===pref.index);const r=rec&&typeof rec.preview==="string"?rec.preview:"";if(!r)throw new Error(`Preview for p${pref.index} not found.`);return fs.promises.readFile(path.join(primaryOutDir,r))}async function resolveOriginalBuf(notation,fallback){try{const pref=parsePrefixedNotation$1(notation);if(!pref)return fallback;if(pref.pool==="attachment"){const rec=attachments.find(a=>a?.a===pref.index);const abs=rec&&typeof rec.originAbs==="string"?rec.originAbs:"";if(!abs)return fallback;return await fs.promises.readFile(abs)}let rec;if(pref.pool==="image")rec=imageRecords.find(r=>r?.i===pref.index);else if(pref.pool==="variant")rec=variantRecords.find(v=>v?.v===pref.index);else rec=pictures.find(p=>p?.p===pref.index);const fn=rec&&typeof rec.filename==="string"?rec.filename:"";if(!fn)return fallback;return await fs.promises.readFile(path.join(primaryOutDir,fn))}catch{return fallback}}let autoId=null;if(rawTargets.length===0){const total=attachments.length+variantRecords.length+imageRecords.length+pictures.length;if(total===0){return {content:[{type:"text",text:"No source image available."}],isError:true}}if(total>1){return {content:[{type:"text",text:"Ambiguous source — specify targets explicitly."}],isError:true}}if(attachments.length===1)autoId=`a${typeof attachments[0]?.a==="number"?attachments[0].a:1}`;else if(variantRecords.length===1)autoId=`v${typeof variantRecords[0]?.v==="number"?variantRecords[0].v:1}`;else if(imageRecords.length===1)autoId=`i${typeof imageRecords[0]?.i==="number"?imageRecords[0].i:1}`;else autoId=`p${pictures[0]?.p??1}`;rawTargets=[autoId];}const forceDetect=taskArg.length>0;const resolvedEntries=[];for(const rawId of rawTargets){let drawSourceId=rawId;let stateRec=null;const pref=parsePrefixedNotation$1(rawId);if(pref?.pool==="image"){const imgRec=imageRecords.find(r=>r?.i===pref.index);if(imgRec&&Array.isArray(imgRec.detections)&&imgRec.detections.length>0&&typeof imgRec.imageWidth==="number"){stateRec=imgRec;drawSourceId=typeof imgRec.detectSource==="string"&&imgRec.detectSource?imgRec.detectSource:rawId;}}if(!stateRec){const prior=[...imageRecords].reverse().find(r=>r?.detectSource===rawId&&Array.isArray(r.detections)&&r.detections.length>0&&typeof r.imageWidth==="number");if(prior){stateRec=prior;drawSourceId=rawId;}}try{if(stateRec&&!forceDetect){const preview=await resolvePreviewBuf(drawSourceId).catch(()=>null);const origBuf=preview?await resolveOriginalBuf(drawSourceId,preview):Buffer.alloc(0);resolvedEntries.push({mode:"redraw",id:drawSourceId,origBuf,task:typeof stateRec.task==="string"?stateRec.task:taskArg,detections:stateRec.detections,imageWidth:stateRec.imageWidth,imageHeight:stateRec.imageHeight,analysisMetadata:stateRec.analysisMetadata});}else {const previewBuf=await resolvePreviewBuf(rawId);const origBuf=await resolveOriginalBuf(rawId,previewBuf);resolvedEntries.push({mode:"detect",id:rawId,previewBuf,origBuf});}}catch(e){return {content:[{type:"text",text:String(e?.message||e)}],isError:true}}}const detectEntries=resolvedEntries.filter(e=>e.mode==="detect");const progressTotalSteps=detectEntries.length>0?detectEntries.length+resolvedEntries.length+4:resolvedEntries.length+3;let batchResult=null;const globalConfig=getGlobalConfig$2(ctl);const embedPngMetadata=getGlobalBoolean$1(globalConfig,"embedPngMetadata",defaultPluginSettings.embedPngMetadata);let visionModelKey="";let detectionConfig=null;if(detectEntries.length>0){const visionBaseUrl=getGlobalString$2(globalConfig,"embeddingBaseUrl",process.env.LMSTUDIO_VISION_API_BASE_URL||defaultPluginSettings.embeddingBaseUrl);const visionApiKey=getGlobalString$2(globalConfig,"embeddingApiKey",process.env.LMSTUDIO_VISION_API_KEY||defaultPluginSettings.embeddingApiKey);visionModelKey=getGlobalString$2(globalConfig,"qwen3VlModelPath",process.env.LMSTUDIO_VISION_MODEL_KEY||defaultPluginSettings.qwen3VlModelPath);const envDetectMaxTokens=Number.parseInt(process.env.DETECT_MAX_TOKENS||"",10);const envDetectTemperature=Number.parseFloat(process.env.DETECT_TEMPERATURE||"");const configuredDetectMaxTokens=Math.floor(getGlobalNumber$2(globalConfig,"detectMaxTokens",Number.isFinite(envDetectMaxTokens)&&envDetectMaxTokens>0?envDetectMaxTokens:defaultPluginSettings.detectMaxTokens));const configuredDetectTemperature=getGlobalNumber$2(globalConfig,"detectTemperature",Number.isFinite(envDetectTemperature)?envDetectTemperature:defaultPluginSettings.detectTemperature);detectionConfig={task:taskArg,odPrompt:getGlobalString$2(globalConfig,"qwen3VlOdPrompt",process.env.DETECT_OD_PROMPT||defaultPluginSettings.qwen3VlOdPrompt)||undefined,maxTokens:configuredDetectMaxTokens,temperature:configuredDetectTemperature,timeoutMs:12e4};const tmpPaths=[];const detectionItems=[];for(const entry of detectEntries){const tmpPath=path.join(primaryOutDir,`_tmp_annotate_src_${entry.id}_${Date.now()}.png`);await fs.promises.writeFile(tmpPath,entry.previewBuf);tmpPaths.push(tmpPath);detectionItems.push({id:entry.id,filePath:tmpPath});}try{reportToolStatus(ctx,`Detecting objects in ${detectEntries.length} image${detectEntries.length===1?"":"s"}...`);reportToolStep(ctx,1,progressTotalSteps,`Preparing ${detectEntries.length} image${detectEntries.length===1?"":"s"} for annotation detection...`);const ready=await ensureLmStudioVisionInstanceReady({baseUrl:visionBaseUrl,apiKey:visionApiKey,modelKey:visionModelKey,status:message=>{try{ctx.status(message);}catch{}}});if(!ready.ok){throw new Error(ready.error)}batchResult={results:[],totalInferenceTimeMs:0,backend:"vision-api"};for(let idx=0;idx<detectionItems.length;idx++){const item=detectionItems[idx];reportToolStep(ctx,idx+2,progressTotalSteps,`Detecting objects in ${item.id} (${idx+1}/${detectionItems.length})...`);const singleResult=await detectLmStudioVisionBatch([item],{...detectionConfig,baseUrl:visionBaseUrl,apiKey:visionApiKey,model:visionModelKey});batchResult.results.push(...singleResult.results);batchResult.totalInferenceTimeMs+=singleResult.totalInferenceTimeMs;batchResult.backend=singleResult.backend;}try{const totalObjects=batchResult.results.reduce((s,r)=>s+(r.objects?.length??0),0);const ms=Math.round(batchResult.totalInferenceTimeMs);reportToolStep(ctx,detectEntries.length+2,progressTotalSteps,`${totalObjects} object${totalObjects===1?"":"s"} found (${ms}ms); drawing boxes...`);}catch{}}finally{for(const tp of tmpPaths)await fs.promises.unlink(tp).catch(()=>{});}if(!batchResult||!batchResult.results.length){return {content:[{type:"text",text:"annotate_image: no results returned from detection API."}],isError:true}}}else {reportToolStatus(ctx,`Redrawing ${resolvedEntries.length} annotated image${resolvedEntries.length===1?"":"s"} from stored detections...`);reportToolStep(ctx,1,progressTotalSteps,`Redrawing ${resolvedEntries.length} annotated image${resolvedEntries.length===1?"":"s"} from stored detections...`);}const variantPreviewSpec=VARIANT_FULL_CONFIG.preview;const stamp=isoStampCompact$1();let nextI=Math.max(1,st.counters?.nextImageI??1);const imageRecordsForState=[];const resultEntries=[];const httpBase=await getHealthyServerBaseUrl();let detectResultIdx=0;let resolvedIdx=0;const drawBaseStep=detectEntries.length>0?detectEntries.length+3:2;for(const entry of resolvedEntries){reportToolStep(ctx,drawBaseStep+resolvedIdx,progressTotalSteps,`Drawing annotation for ${entry.id} (${resolvedIdx+1}/${resolvedEntries.length})...`);resolvedIdx++;let rawBboxes;let imgW;let imgH;let detObjects;let isRedraw;let entryTask;let inferenceTimeMs=0;let bboxesAlreadyAdjusted=false;if(entry.mode==="redraw"){imgW=entry.imageWidth;imgH=entry.imageHeight;isRedraw=true;entryTask=entry.task;if(globalLabels!==undefined&&globalLabels.length>0){let labels=[...globalLabels];let indices=[...globalIndices];if(labels.length===1&&indices.length===0){const allIndices=expandDetectIndices(entry.detections,labels[0]);if(allIndices.length>1){labels=Array(allIndices.length).fill(labels[0]);indices=allIndices;}}if(labels.length===1&&indices.length>1){labels=Array(indices.length).fill(labels[0]);}const detByLabel=new Map;for(const d of entry.detections){const key=d.label.toLowerCase();if(!detByLabel.has(key))detByLabel.set(key,[]);detByLabel.get(key).push(d);}const resolvedBoxes=[];for(let li=0;li<labels.length;li++){const label=labels[li];const idx=indices[li]??indices[0]??0;const selectedDet=detByLabel.get(label.toLowerCase())?.[idx];if(!selectedDet){const available=[...new Set(entry.detections.map(d=>d.label))].join(", ");return {content:[{type:"text",text:`annotate_image: label '${label}' (index ${idx}) not found in stored detections. Available: ${available||"(none)"}`}],isError:true}}const ox1=hasAnyManualCoord?resolveCoordOverride(manualX1,li):undefined;const oy1=hasAnyManualCoord?resolveCoordOverride(manualY1,li):undefined;const ox2=hasAnyManualCoord?resolveCoordOverride(manualX2,li):undefined;const oy2=hasAnyManualCoord?resolveCoordOverride(manualY2,li):undefined;const bbox=[ox1??selectedDet.bbox.x1,oy1??selectedDet.bbox.y1,ox2??selectedDet.bbox.x2,oy2??selectedDet.bbox.y2];resolvedBoxes.push({det:selectedDet,bbox});}rawBboxes=resolvedBoxes.map(({bbox})=>applyFrameAdjust(bbox,globalFrameAdjust,imgW,imgH));detObjects=resolvedBoxes.map(({det,bbox})=>({...det,bbox:{x1:bbox[0],y1:bbox[1],x2:bbox[2],y2:bbox[3]}}));bboxesAlreadyAdjusted=true;}else if(globalIndices.length>0){const selected=[];for(const idx of globalIndices){const det=entry.detections[idx];if(!det){return {content:[{type:"text",text:`annotate_image: detectIndex ${idx} out of range (${entry.detections.length} stored detections).`}],isError:true}}selected.push(det);}rawBboxes=selected.map(d=>[d.bbox.x1,d.bbox.y1,d.bbox.x2,d.bbox.y2]);detObjects=selected;bboxesAlreadyAdjusted=false;}else {rawBboxes=entry.detections.map(d=>[d.bbox.x1,d.bbox.y1,d.bbox.x2,d.bbox.y2]);detObjects=entry.detections;}}else {const detResult=batchResult.results[detectResultIdx++];rawBboxes=detResult.objects.map(o=>o.bbox);imgW=detResult.imageWidth;imgH=detResult.imageHeight;detObjects=detResult.objects.map(o=>({label:o.label,bbox:{x1:o.bbox[0],y1:o.bbox[1],x2:o.bbox[2],y2:o.bbox[3]},crop:{cropLeft:o.cropLeft,cropRight:o.cropRight,cropTop:o.cropTop,cropBottom:o.cropBottom}}));isRedraw=false;entryTask=taskArg;inferenceTimeMs=detResult.inferenceTimeMs??0;}const adjustedBboxes=bboxesAlreadyAdjusted?rawBboxes:rawBboxes.map(bbox=>applyFrameAdjust(bbox,globalFrameAdjust,imgW,imgH));const annotatedBuf=await drawBboxesOnImage(entry.origBuf,adjustedBboxes,{sourceDims:{width:imgW,height:imgH},palette:false,color:globalColor,lineWeight:globalLineWeight});const inheritedInference=entry.mode==="redraw"?entry.analysisMetadata?.inference:undefined;const analysis={schema:"ceveyne.image-analysis/v1",tool:"annotate_image",sourceNotation:entry.id,inference:entry.mode==="redraw"?inheritedInference?{...inheritedInference,reused:true}:undefined:{model:visionModelKey,...entryTask?{query:entryTask}:{},detectorPromptSha256:crypto.createHash("sha256").update(detectionConfig?.odPrompt??"").digest("hex"),maxTokens:detectionConfig?.maxTokens,temperature:detectionConfig?.temperature},render:{color:globalColor,lineWeight:globalLineWeight,frameAdjust:globalFrameAdjust},detections:detObjects.map((detection,index)=>({label:detection.label,bbox:{x1:adjustedBboxes[index][0],y1:adjustedBboxes[index][1],x2:adjustedBboxes[index][2],y2:adjustedBboxes[index][3]}}))};const savedBuffer=embedPngMetadata?injectXmpIntoBuffer(annotatedBuf,{...entryTask?{prompt:entryTask}:{},...analysis.inference?.model?{model:analysis.inference.model}:{},mode:isRedraw?"image_annotation_redraw":"image_annotation",generatedBy:`${getSelfPluginIdentifier()}/annotate_image`,creatorTool:`${getSelfPluginIdentifier()}/annotate_image`,analysis}):annotatedBuf;const currentI=nextI++;const baseName=`image-${stamp}-i${currentI}`;const savedPath=path.join(primaryOutDir,`${baseName}.png`);await fs.promises.writeFile(savedPath,savedBuffer);const savedFileUrl=url.pathToFileURL(savedPath).toString();const savedSize=savedBuffer.length;let preview=null;try{const p=await generatePreviewFromBuffer(savedBuffer,primaryOutDir,`${baseName}.png`,variantPreviewSpec);preview={ok:true,filePath:p.previewAbs,fileName:p.previewFilename,fileUrl:url.pathToFileURL(p.previewAbs).toString(),size_bytes:p.data.length,width:p.width,height:p.height,mimeType:"image/jpeg",dataBase64:p.data.toString("base64")};}catch(e){console.warn(`[annotate_image] preview generation failed for ${entry.id}:`,String(e));}const httpOriginal=httpBase?toHttpOriginalUrl(`${baseName}.png`,httpBase,currentLmChatId||undefined):"";const httpPreview=(()=>{if(!httpBase||!currentLmChatId||!preview?.fileName)return "";return toHttpPreviewUrl(preview.fileName,httpBase,currentLmChatId)})();imageRecordsForState.push({filename:`${baseName}.png`,preview:preview?`preview-${baseName}.jpg`:undefined,i:currentI,sourceTool:`${getSelfPluginIdentifier()}/annotate_image`,detectSource:entry.id,task:entryTask,annotateColor:globalColor,annotateLineWeight:globalLineWeight,annotateFrameAdjust:globalFrameAdjust,imageWidth:imgW,imageHeight:imgH,analysisMetadata:analysis,detections:detObjects.map(d=>({label:d.label,bbox:{x1:d.bbox.x1,y1:d.bbox.y1,x2:d.bbox.x2,y2:d.bbox.y2},crop:d.crop??{}}))});resultEntries.push({id:entry.id,i:currentI,isRedraw,task:entryTask,detObjects,imageWidth:imgW,imageHeight:imgH,savedPath,savedFileUrl,savedSize,preview,httpOriginal,httpPreview,inferenceTimeMs});}reportToolStep(ctx,progressTotalSteps-1,progressTotalSteps,"Updating image state and audit log...");try{const stateForUpdate=await readState$1(primaryOutDir);const appendResult=appendImages(stateForUpdate,imageRecordsForState);if(appendResult.changed){await writeStateAtomic(primaryOutDir,stateForUpdate);}}catch(e){console.warn("[annotate_image] state update failed:",String(e));}try{const audit=buildAuditLogger({backend:"annotate_image",mode:"annotate_image",requestId:undefined});if(currentLmChatId)audit.setChatId(currentLmChatId);audit.setUserRequest({targets:rawTargets,task:taskArg,color:globalColor,lineWeight:globalLineWeight,frameAdjust:globalFrameAdjust});audit.setOutput({images:resultEntries.map(r=>({id:r.id,i:r.i,redraw:r.isRedraw,detections:r.detObjects.length,path:r.savedPath,url:r.savedFileUrl,bytes:r.savedSize,...r.httpOriginal?{http_url:r.httpOriginal}:{},...r.preview?{preview_path:r.preview.filePath,preview_url:r.preview.fileUrl}:{},...r.httpPreview?{http_preview_url:r.httpPreview}:{}}))});await audit.write();}catch{}reportToolStep(ctx,progressTotalSteps,progressTotalSteps,"Assembling annotation result...");const summaries=resultEntries.map(r=>({tool:"annotate_image",source:r.id,i:r.i,redraw:r.isRedraw,color:globalColor,lineWeight:globalLineWeight,frameAdjust:globalFrameAdjust,...r.inferenceTimeMs>0?{inferenceTimeMs:r.inferenceTimeMs}:{},detections:r.detObjects.map(d=>({label:d.label,bbox:{x1:d.bbox.x1,y1:d.bbox.y1,x2:d.bbox.x2,y2:d.bbox.y2}}))}));const envPreviewRaw=process.env["PREVIEW_IN_CHAT"];const previewInChat=envPreviewRaw===undefined?true:envPreviewRaw==="1"||envPreviewRaw.toLowerCase()==="true";const resultNotations=resultEntries.map(r=>`i${r.i}`);const targetsJson=JSON.stringify(resultNotations);const reviewHintFalse=`Carefully examine the preview to make absolutely sure that the object detection matches your intent. Registered as ${resultNotations.join(", ")}. Use review_image({"targets":${targetsJson}}) to review, or annotate_image({"targets":${targetsJson}}) to apply corrections.`;const reviewHintTrue=`Carefully examine the preview to make absolutely sure that the object detection matches your intent. This is an image file. Present the image to the user by using the markdown above. Registered as ${resultNotations.join(", ")}. Use review_image({"targets":${targetsJson}}) to review, or annotate_image({"targets":${targetsJson}}) to apply corrections.`;const content=[];for(const r of resultEntries){const fallbackPreviewUrl=r.preview?.fileUrl||r.savedFileUrl;if(previewInChat&&r.preview){const fname=String(r.preview.fileName||"");content.push({type:"image",fileName:fname,mimeType:r.preview.mimeType,markdown:``,$hint:reviewHintTrue});}}if(batchResult&&batchResult.totalInferenceTimeMs>0){content.push({type:"text",text:`Total inference time: ${Math.round(batchResult.totalInferenceTimeMs)}ms`});}content.push({type:"text",text:JSON.stringify(summaries.length===1?summaries[0]:summaries),...previewInChat?{}:{$hint:reviewHintFalse}});return {content}}catch(error){return {content:[{type:"text",text:`annotate_image failed: ${error.message||String(error)}`}],isError:true}}}})}
async function readState(chatWd){const p=path.join(chatWd,"chat_media_state.json");try{const raw=await fs.promises.readFile(p,"utf-8");const json=JSON.parse(raw);return {attachments:Array.isArray(json?.attachments)?json.attachments:[],variants:Array.isArray(json?.variants)?json.variants:[],pictures:Array.isArray(json?.pictures)?json.pictures:[],images:Array.isArray(json?.images)?json.images:[],counters:json?.counters||{}}}catch{return {attachments:[],variants:[],pictures:[],images:[],counters:{}}}}
function readPngGenerationMeta(filePath){let buf;try{buf=fs.readFileSync(filePath);}catch{return null}if(buf.length<8||buf[0]!==137||buf[1]!==80||buf[2]!==78||buf[3]!==71){return null}let offset=8;while(offset+12<=buf.length){const chunkLen=buf.readUInt32BE(offset);const chunkType=buf.toString("ascii",offset+4,offset+8);const dataStart=offset+8;const dataEnd=dataStart+chunkLen;if(dataEnd+4>buf.length)break;if(chunkType==="IEND")break;if(chunkType==="iTXt"){const data=buf.slice(dataStart,dataEnd);const kwEnd=data.indexOf(0);if(kwEnd>=0&&data.toString("ascii",0,kwEnd)==="XML:com.adobe.xmp"){const comprFlag=data[kwEnd+1];if(comprFlag!==0){offset=dataEnd+4;continue}let pos=kwEnd+3;while(pos<data.length&&data[pos]!==0)pos++;pos++;while(pos<data.length&&data[pos]!==0)pos++;pos++;const xmpText=data.toString("utf8",pos);return extractMetaFromXmp(xmpText)}}offset=dataEnd+4;}return null}function extractMetaFromXmp(xmp){const match=xmp.match(/<exif:UserComment>[\s\S]*?<rdf:li[^>]*>([\s\S]*?)<\/rdf:li>/);if(!match)return null;let raw;try{const jsonText=match[1].trim().replace(/"/g,'"').replace(/'/g,"'").replace(/</g,"<").replace(/>/g,">").replace(/&/g,"&");raw=JSON.parse(jsonText);}catch{return null}const loras=Array.isArray(raw.lora)?raw.lora.filter(l=>l&&typeof l.model==="string").map(l=>({file:String(l.model),weight:typeof l.weight==="number"?l.weight:undefined})):undefined;const sources=Array.isArray(raw.sources)?raw.sources.filter(s=>typeof s==="string"):undefined;return {prompt:typeof raw.c==="string"&&raw.c?raw.c:undefined,negativePrompt:typeof raw.uc==="string"&&raw.uc?raw.uc:undefined,model:typeof raw.model==="string"&&raw.model?raw.model:undefined,sampler:typeof raw.sampler==="string"&&raw.sampler?raw.sampler:undefined,steps:typeof raw.steps==="number"?raw.steps:undefined,guidanceScale:typeof raw.scale==="number"?raw.scale:undefined,seed:typeof raw.seed==="number"?raw.seed:undefined,seedMode:typeof raw.seed_mode==="string"&&raw.seed_mode?raw.seed_mode:undefined,shift:typeof raw.shift==="number"?raw.shift:undefined,size:typeof raw.size==="string"&&raw.size?raw.size:undefined,strength:typeof raw.strength==="number"?raw.strength:undefined,loras:loras?.length?loras:undefined,sources:sources?.length?sources:undefined,mode:typeof raw.mode==="string"&&raw.mode?raw.mode:undefined,generatedBy:typeof raw.generated_by==="string"&&raw.generated_by?raw.generated_by:undefined}}function formatGenerationMeta(meta){const lines=[" GENERATION METADATA:"];if(meta.prompt)lines.push(` Prompt: ${meta.prompt}`);if(meta.negativePrompt)lines.push(` Negative Prompt: ${meta.negativePrompt}`);if(meta.model)lines.push(` Model: ${meta.model}`);const techParts=[];if(meta.sampler)techParts.push(`Sampler: ${meta.sampler}`);if(typeof meta.steps==="number")techParts.push(`Steps: ${meta.steps}`);if(typeof meta.guidanceScale==="number")techParts.push(`Guidance Scale: ${meta.guidanceScale}`);if(typeof meta.seed==="number")techParts.push(`Seed: ${meta.seed}`);if(techParts.length>0)lines.push(` ${techParts.join(" ")}`);if(meta.size)lines.push(` Size: ${meta.size}`);if(typeof meta.strength==="number"&&meta.strength!==1){lines.push(` Strength: ${meta.strength}`);}if(meta.loras&&meta.loras.length>0){const loraStr=meta.loras.map(l=>l.weight!=null?`${l.file} (${l.weight})`:l.file).join(", ");lines.push(` LoRA: ${loraStr}`);}if(meta.sources&&meta.sources.length>0){lines.push(` Source(s): ${meta.sources.join(", ")}`);}return lines.join("\n")}
function formatPluginMeta$1(){return formatToolMetaBlock()}function getGlobalConfig$1(ctl){const ctlAny=ctl;const getter=ctlAny.getGlobalPluginConfig||ctlAny.getGlobalConfig;if(!getter)return null;try{return getter.call(ctl,globalConfigSchematics)}catch{return null}}function getGlobalString$1(gcfg,key,fallback){try{const value=gcfg?.get(key);return typeof value==="string"?value:fallback}catch{return fallback}}function getGlobalNumber$1(gcfg,key,fallback){try{const value=gcfg?.get(key);return typeof value==="number"&&Number.isFinite(value)?value:fallback}catch{return fallback}}const FlexibleTargetsList$1=zod.z.union([zod.z.string().transform(s=>(s.match(/[aivp]\d+/gi)??[]).map(x=>x.toLowerCase())),zod.z.array(zod.z.string())]).refine(arr=>arr.length>=1,"targets must contain at least one notation").refine(arr=>arr.length<=16,"targets must contain at most 16 notations");const AnalyseImageParamsShape={targets:FlexibleTargetsList$1,prompt:zod.z.string().optional().describe("Optional prompt for the vision model. Empty = model default.")};function parseTargets(targets){const parsed={a:[],v:[],i:[],p:[]};const invalid=[];for(const raw of targets){const s=typeof raw==="string"?raw.trim():"";const m=/^([avip])(\d+)$/i.exec(s);if(!m){invalid.push(String(raw));continue}const kind=m[1].toLowerCase();const n=parseInt(m[2],10);if(!Number.isFinite(n)||n<=0){invalid.push(String(raw));continue}parsed[kind].push(n);}Object.keys(parsed).forEach(k=>{parsed[k]=Array.from(new Set(parsed[k])).sort((a,b)=>a-b);});return {parsed,invalid}}function getAvailable(state){const availableA=(state.attachments||[]).map(x=>typeof x?.a==="number"?x.a:undefined).filter(x=>typeof x==="number"&&x>0).sort((a,b)=>a-b);const availableV=(state.variants||[]).map(x=>typeof x?.v==="number"?x.v:undefined).filter(x=>typeof x==="number"&&x>0).sort((a,b)=>a-b);const availableI=(state.images||[]).map(x=>typeof x?.i==="number"?x.i:undefined).filter(x=>typeof x==="number"&&x>0).sort((a,b)=>a-b);const availableP=(state.pictures||[]).map(x=>typeof x?.p==="number"?x.p:undefined).filter(x=>typeof x==="number"&&x>0).sort((a,b)=>a-b);return {availableA,availableV,availableI,availableP}}async function ensurePreviewExists(chatWd,previewRel){const pAbs=path.join(chatWd,previewRel);await fs.promises.access(pAbs,fs.constants.F_OK);}function classifyVisionError(errMsg){if(/\b503\b/.test(errMsg)){return "The Vision API is reachable, but the configured vision model is not available for inference. Check the configured vision model key and loaded model state."}if(/aborted|aborterror|timed out|timeout/i.test(errMsg)){return "The Vision API request timed out before the model returned."}if(/ECONNREFUSED|ENOTFOUND|ECONNRESET|network socket|fetch failed/i.test(errMsg)){return "The Vision API is not reachable. Check the configured vision/embedding API base URL and try again."}return /Vision API/i.test(errMsg)?errMsg:`Vision API error: ${errMsg}`}function analyzeTimeoutMs(itemCount){return Math.min(6e5,Math.max(18e4,itemCount*6e4))}function createAnalyseImageTool(ctl){return sdk.tool({name:"analyse_image",description:`Inspect existing media items (images, pictures, variants, attachments) for generation metadata and, optionally, visual content.
PRIMARY use — generation metadata:
Call this tool whenever the user asks how an image was generated, what settings were used, or wants to reuse generation parameters (prompt, model, sampler, seed, steps, guidance scale, LoRA, source images, …). Those parameters are embedded in the PNG file and are returned automatically — no vision prompt needed.
SECONDARY use — visual description (on demand only):
Only request a visual description when the user explicitly asks you to describe or analyze the image content. Pass a prompt in the 'prompt' parameter. Without a prompt, no vision model is invoked and no description is returned.
Parameters:
- targets: Field in the JSON argument object. Pass a JSON array of notations, e.g. analyse_image({"targets":["a1", "v2"]}). Notation: aN=attachment, vN=variant, iN=image, pN=picture.
- prompt: (optional) Vision prompt for visual description — omit unless explicitly requested.
${formatPluginMeta$1()}`,parameters:AnalyseImageParamsShape,implementation:async(args,ctx)=>{try{const strict=false;let targets;if(Array.isArray(args?.targets)){targets=args.targets;}else if(typeof args?.targets==="string"&&args.targets.trim().startsWith("[")){try{const parsed=JSON.parse(args.targets.trim());targets=Array.isArray(parsed)?parsed.map(s=>String(s).trim()).filter(Boolean):[];}catch{targets=[];}}else {targets=[];}const prompt=typeof args?.prompt==="string"?args.prompt:"";const globalConfig=getGlobalConfig$1(ctl);const configuredVisionPrompt=getGlobalString$1(globalConfig,"visionPrompt",process.env.VISION_PROMPT||defaultPluginSettings.visionPrompt);const effectivePrompt=(prompt||configuredVisionPrompt||"").trim();const{parsed,invalid:invalidRaw}=parseTargets(targets);const workingDir=ctl.getWorkingDirectory();if(typeof workingDir!=="string"||!workingDir.trim()){return "analyse_image failed: working directory not available."}const chatWd=workingDir;try{await syncAttachmentsToState(chatWd,false,Number.MAX_SAFE_INTEGER);}catch(syncErr){console.warn("[analyse_image] attachment sync failed (non-fatal):",syncErr?.message??syncErr);}const state=await readState(chatWd);const{availableA,availableV,availableI,availableP}=getAvailable(state);if(invalidRaw.length>0&&strict);const analysisItems=[];const originalFilePaths=new Map;const displayNames=new Map;const missingNotations=new Set;const missingDetails=[];const addItem=async(notation,rec,previewField,originalAbsPath,displayName)=>{if(!rec){missingNotations.add(notation);missingDetails.push(notation);return false}const previewRel=typeof rec[previewField]==="string"?String(rec[previewField]):"";if(!previewRel.trim()){missingNotations.add(notation);missingDetails.push(`${notation} (missing preview)`);return false}try{await ensurePreviewExists(chatWd,previewRel);analysisItems.push({id:notation,filePath:path.join(chatWd,previewRel)});if(originalAbsPath){originalFilePaths.set(notation,originalAbsPath);}const dn=displayName||(originalAbsPath?path.basename(originalAbsPath):undefined);if(dn){displayNames.set(notation,dn);}return true}catch{missingNotations.add(notation);missingDetails.push(`${notation} (preview file missing)`);return false}};for(const n of parsed.a){const rec=(state.attachments||[]).find(x=>x?.a===n);const origAbs=rec?.originAbs??(rec?.filename?path.join(chatWd,rec.filename):undefined);const origName=typeof rec?.originalName==="string"&&rec.originalName?rec.originalName:undefined;await addItem(`a${n}`,rec,"preview",origAbs,origName);}for(const n of parsed.v){const rec=(state.variants||[]).find(x=>x?.v===n);const origAbs=rec?.filename?path.join(chatWd,rec.filename):undefined;await addItem(`v${n}`,rec,"preview",origAbs);}for(const n of parsed.i){const rec=(state.images||[]).find(x=>x?.i===n);const origAbs=rec?.filename?path.join(chatWd,rec.filename):undefined;await addItem(`i${n}`,rec,"preview",origAbs);}for(const n of parsed.p){const rec=(state.pictures||[]).find(x=>x?.p===n);const origAbs=rec?.filename?path.join(chatWd,rec.filename):undefined;await addItem(`p${n}`,rec,"preview",origAbs);}if(missingDetails.length>0&&strict);if(analysisItems.length===0){const hint=`Available: `+`a=[${availableA.map(x=>`a${x}`).join(", ")||"(none)"}] `+`v=[${availableV.map(x=>`v${x}`).join(", ")||"(none)"}] `+`i=[${availableI.map(x=>`i${x}`).join(", ")||"(none)"}] `+`p=[${availableP.map(x=>`p${x}`).join(", ")||"(none)"}]`;return `analyse_image: no valid targets found. ${hint}`}let visionError=null;const visionResults=new Map;let totalInferenceTimeMs=null;if(effectivePrompt){const envServerMaxTokens=Number.parseInt(process.env.SERVER_MAX_TOKENS||"",10);const envServerTemperature=Number.parseFloat(process.env.SERVER_TEMPERATURE||"");const configuredMaxTokens=Math.floor(getGlobalNumber$1(globalConfig,"serverMaxTokens",Number.isFinite(envServerMaxTokens)&&envServerMaxTokens>0?envServerMaxTokens:defaultPluginSettings.serverMaxTokens));const configuredTemperature=getGlobalNumber$1(globalConfig,"serverTemperature",Number.isFinite(envServerTemperature)?envServerTemperature:defaultPluginSettings.serverTemperature);const lmStudioConfig={baseUrl:getGlobalString$1(globalConfig,"embeddingBaseUrl",process.env.LMSTUDIO_VISION_API_BASE_URL||defaultPluginSettings.embeddingBaseUrl),apiKey:getGlobalString$1(globalConfig,"embeddingApiKey",process.env.LMSTUDIO_VISION_API_KEY||defaultPluginSettings.embeddingApiKey),model:getGlobalString$1(globalConfig,"qwen3VlModelPath",process.env.LMSTUDIO_VISION_MODEL_KEY||defaultPluginSettings.qwen3VlModelPath),prompt:effectivePrompt,maxTokens:configuredMaxTokens,temperature:configuredTemperature,timeoutMs:analyzeTimeoutMs(1)};try{const totalSteps=analysisItems.length+2;reportToolStatus(ctx,`Analyzing ${analysisItems.length} image${analysisItems.length===1?"":"s"}...`);reportToolStep(ctx,1,totalSteps,`Preparing ${analysisItems.length} image${analysisItems.length===1?"":"s"} for visual analysis...`);const ready=await ensureLmStudioVisionInstanceReady({baseUrl:lmStudioConfig.baseUrl,apiKey:lmStudioConfig.apiKey,modelKey:lmStudioConfig.model||"",status:message=>{try{ctx.status(message);}catch{}}});if(!ready.ok){throw new Error(ready.error)}let totalMs=0;for(let idx=0;idx<analysisItems.length;idx++){const item=analysisItems[idx];reportToolStep(ctx,idx+2,totalSteps,`Analyzing ${item.id} (${idx+1}/${analysisItems.length})...`);const batchResult=await analyzeLmStudioVisionBatch([item],lmStudioConfig);for(const r of batchResult.results){visionResults.set(r.id,r.text.trim()||"(no description)");}totalMs+=batchResult.totalInferenceTimeMs;}totalInferenceTimeMs=totalMs;reportToolStep(ctx,totalSteps,totalSteps,"Formatting analysis results...");}catch(e){visionError=classifyVisionError(e.message||String(e));}}const includeGenMeta=process.env.INCLUDE_GENERATION_METADATA!=="false";const lines=[];lines.push(`Analysis results (${analysisItems.length} image${analysisItems.length!==1?"s":""}):`);lines.push("");if(visionError){lines.push(`Note: Visual analysis unavailable — ${visionError}`);lines.push("");}for(const item of analysisItems){const{id}=item;const displayName=displayNames.get(id);const origPath=originalFilePaths.get(id);const header=displayName?`${id} — ${displayName}`:id;lines.push(`- ${header}`);if(effectivePrompt){if(visionError){lines.push(` Visual: (not available)`);}else {lines.push(` Visual: ${visionResults.get(id)??"(no description)"}`);}}if(includeGenMeta){if(origPath&&origPath.toLowerCase().endsWith(".png")){const meta=readPngGenerationMeta(origPath);if(meta){lines.push(formatGenerationMeta(meta));}else {lines.push(` (No embedded generation metadata)`);}}else if(origPath&&/\.jpe?g$/i.test(origPath)){const metadata=await readCameraImageMetadata(origPath);if(Object.keys(metadata).length>0){lines.push(` EXIF JSON: ${JSON.stringify(metadata)}`);}else {lines.push(` (No embedded EXIF metadata)`);}}else if(origPath){lines.push(` (No embedded generation metadata — not a PNG file)`);}}lines.push("");}if(totalInferenceTimeMs!==null){lines.push(`Total inference time: ${Math.round(totalInferenceTimeMs)}ms`);}try{const statusSuffix=visionError?" (vision unavailable)":" successfully";ctx.status(`Analyzed ${analysisItems.length} image${analysisItems.length!==1?"s":""}${statusSuffix}`);}catch{}return lines.join("\n")}catch(e){return `analyse_image failed: ${String(e?.message||e)}`}}})}
function isoStampCompact(){const d=new Date;const year=d.getUTCFullYear();const month=String(d.getUTCMonth()+1).padStart(2,"0");const day=String(d.getUTCDate()).padStart(2,"0");const hours=String(d.getUTCHours()).padStart(2,"0");const minutes=String(d.getUTCMinutes()).padStart(2,"0");const seconds=String(d.getUTCSeconds()).padStart(2,"0");const millis=String(d.getUTCMilliseconds()).padStart(3,"0");return `${year}${month}${day}T${hours}${minutes}${seconds}${millis}Z`}function parsePrefixedNotation(s){const t=String(s||"").trim().toLowerCase();const m=t.match(/^([avip])(\d+)$/);if(!m)return null;const idx=Math.max(1,parseInt(m[2],10));const pool=m[1]==="a"?"attachment":m[1]==="v"?"variant":m[1]==="i"?"image":"picture";return {pool,index:idx}}function formatPluginMeta(){try{const cwd=process.cwd();const pkg=JSON.parse(fs.readFileSync(path.join(cwd,"package.json"),"utf-8"));const mf=JSON.parse(fs.readFileSync(path.join(cwd,"manifest.json"),"utf-8"));const id=mf?.owner&&mf?.name?`${mf.owner}/${mf.name}`:pkg?.name||"ceveyne/analyse-image";return `Plugin-Identifier: ${id}
Plugin version: ${pkg?.version||""}`}catch{return "Plugin-Identifier: ceveyne/analyse-image"}}function getGlobalConfig(ctl){const ctlAny=ctl;const getter=ctlAny.getGlobalPluginConfig||ctlAny.getGlobalConfig;if(!getter)return null;try{return getter.call(ctl,globalConfigSchematics)}catch{return null}}function getGlobalString(gcfg,key,fallback){try{const value=gcfg?.get(key);return typeof value==="string"?value:fallback}catch{return fallback}}function getGlobalNumber(gcfg,key,fallback){try{const value=gcfg?.get(key);return typeof value==="number"&&Number.isFinite(value)?value:fallback}catch{return fallback}}function getGlobalBoolean(gcfg,key,fallback){try{const value=gcfg?.get(key);return typeof value==="boolean"?value:fallback}catch{return fallback}}const FlexibleTargetsList=zod.z.union([zod.z.string().transform(s=>(s.match(/[aivp]\d+/gi)??[]).map(x=>x.toLowerCase())),zod.z.array(zod.z.string())]).refine(arr=>arr.length>=1,"targets must contain at least one notation").refine(arr=>arr.length<=16,"targets must contain at most 16 notations");const DetectObjectParamsShape={targets:FlexibleTargetsList.optional().describe("One or more image notations to process. Each notation is a letter followed by a number: "+"a=attachment (a1, a2, …), i=generated image (i1, i2, …), v=variant (v1, v2, …), p=picture (p1, p2, …). "+'Pass as a JSON array, e.g. ["a1", "i3"]. '+"Omit when there is exactly one image — it will be selected automatically."),task:zod.z.string().optional().default("").describe("What to detect. Omit for full-image general object detection. "+"Use natural language to target specific subjects (e.g. 'all faces and hands', 'the dog', 'cars and bicycles').")};function createDetectObjectTool(ctl){return sdk.tool({name:"detect_object",description:`Detect objects in one or more images and draw colored bounding boxes on each result.
For each source image, returns a new annotated image (saved as iN) with bounding boxes for each detected object, plus a JSON summary with labels, coordinates, and crop percentages. Uses Qwen3-VL for detection.
Parameters:
- targets: JSON array of image notations, e.g. ["a1", "i3"]. Notation: a=attachment, i=generated image, v=variant, p=picture. Omit when there is exactly one image.
- task: What to detect. Omit for full-image general object detection. Use natural language to target specific subjects (e.g. 'all faces and hands', 'the dog', 'cars and bicycles').
${formatPluginMeta()}`,parameters:DetectObjectParamsShape,implementation:async(args,ctx)=>{try{let rawTargets=[];if(Array.isArray(args?.targets)){rawTargets=args.targets.map(s=>String(s).trim()).filter(Boolean);}else if(typeof args?.targets==="string"&&args.targets.trim()){const trimmed=args.targets.trim();if(trimmed.startsWith("[")){try{const parsed=JSON.parse(trimmed);if(Array.isArray(parsed)){rawTargets=parsed.map(s=>String(s).trim()).filter(Boolean);}else {rawTargets=trimmed.split(/[\s,]+/).map(s=>s.trim()).filter(Boolean);}}catch{rawTargets=trimmed.split(/[\s,]+/).map(s=>s.trim()).filter(Boolean);}}else {rawTargets=trimmed.split(/[\s,]+/).map(s=>s.trim()).filter(Boolean);}}const task=typeof args?.task==="string"&&args.task.trim()?args.task.trim():"";console.log("[detect_object] invoked",{targets:rawTargets,task});let currentLmChatId=null;let currentLmWorkingDir=null;try{const chatCtx=await getActiveChatContext();if(chatCtx?.chatId)currentLmChatId=chatCtx.chatId;if(chatCtx?.workingDir)currentLmWorkingDir=chatCtx.workingDir;}catch{}if(!currentLmChatId){try{const resolved=await resolveActiveLMStudioChatId();if(resolved?.ok)currentLmChatId=resolved.chatId;}catch{}}const primaryOutDir=currentLmWorkingDir||(currentLmChatId?getLMStudioWorkingDir(currentLmChatId):undefined);if(!primaryOutDir){console.error("[detect_object] could not resolve working directory");return {content:[{type:"text",text:"detect_object failed: could not resolve LM Studio chat working directory."}],isError:true}}console.log("[detect_object] primaryOutDir:",primaryOutDir);await fs.promises.mkdir(primaryOutDir,{recursive:true}).catch(()=>{});console.log("[detect_object] syncing attachments...");try{await syncAttachmentsToState(primaryOutDir,false,Number.MAX_SAFE_INTEGER);}catch(e){console.warn("[detect_object] attachment sync failed (non-fatal):",e?.message??e);}console.log("[detect_object] attachment sync done");console.log("[detect_object] reading state...");const st=await readState$1(primaryOutDir);const attachments=Array.isArray(st?.attachments)?st.attachments:[];const pictures=Array.isArray(st?.pictures)?st.pictures:[];const imageRecords=Array.isArray(st?.images)?st.images:[];const images=imageRecords.filter(r=>r&&typeof r.filename==="string").sort((a,b)=>(a.i||0)-(b.i||0)).map(r=>({i:r.i||1,path:path.join(primaryOutDir,r.filename)}));const variantRecords=Array.isArray(st?.variants)?st.variants:[];const variants=variantRecords.filter(v=>v&&typeof v.filename==="string").map(v=>({v:v.v||1,path:path.join(primaryOutDir,v.filename)}));console.log("[detect_object] state:",{attachments:attachments.length,images:images.length,variants:variants.length,pictures:pictures.length});const sourceEntries=[];async function resolveOneBuf(rawCanvas){const pref=parsePrefixedNotation(rawCanvas);if(!pref)throw new Error(`Invalid canvas notation: ${rawCanvas}`);if(pref.pool==="attachment"){const rec=attachments.find(a=>a?.a===pref.index);const previewRel=rec&&typeof rec.preview==="string"?rec.preview:"";if(!previewRel)throw new Error(`Preview for attachment a${pref.index} not found.`);return fs.promises.readFile(path.join(primaryOutDir,previewRel))}else if(pref.pool==="image"){const rec=imageRecords.find(r=>r?.i===pref.index);const previewRel=rec&&typeof rec.preview==="string"?rec.preview:"";if(!previewRel)throw new Error(`Preview for image i${pref.index} not found.`);return fs.promises.readFile(path.join(primaryOutDir,previewRel))}else if(pref.pool==="variant"){const rec=variantRecords.find(v=>v?.v===pref.index);const previewRel=rec&&typeof rec.preview==="string"?rec.preview:"";if(!previewRel)throw new Error(`Preview for variant v${pref.index} not found.`);return fs.promises.readFile(path.join(primaryOutDir,previewRel))}else {const rec=pictures.find(p=>p?.p===pref.index);const previewRel=rec&&typeof rec.preview==="string"?rec.preview:"";if(!previewRel)throw new Error(`Preview for picture p${pref.index} not found.`);return fs.promises.readFile(path.join(primaryOutDir,previewRel))}}async function resolveOriginalBuf(rawCanvas,previewFallback){try{const pref=parsePrefixedNotation(rawCanvas);if(!pref)return previewFallback;if(pref.pool==="attachment"){const rec=attachments.find(a=>a?.a===pref.index);const originAbs=rec&&typeof rec.originAbs==="string"?rec.originAbs:"";if(!originAbs)return previewFallback;return await fs.promises.readFile(originAbs)}let rec;if(pref.pool==="image")rec=imageRecords.find(r=>r?.i===pref.index);else if(pref.pool==="variant")rec=variantRecords.find(v=>v?.v===pref.index);else rec=pictures.find(p=>p?.p===pref.index);const filename=rec&&typeof rec.filename==="string"?rec.filename:"";if(!filename)return previewFallback;return await fs.promises.readFile(path.join(primaryOutDir,filename))}catch{return previewFallback}}try{if(rawTargets.length>0){for(const t of rawTargets){const buf=await resolveOneBuf(t);const origBuf=await resolveOriginalBuf(t,buf);sourceEntries.push({id:t,buf,origBuf});}}else {const total=attachments.length+variantRecords.length+imageRecords.length+pictures.length;if(total===0)throw new Error("No source image available.");if(total>1)throw new Error("Ambiguous source — specify targets explicitly.");let buf;let id;if(attachments.length===1){const rec=attachments[0];const previewRel=rec&&typeof rec.preview==="string"?rec.preview:"";if(!previewRel)throw new Error("Preview for attachment not found.");buf=await fs.promises.readFile(path.join(primaryOutDir,previewRel));id=`a${typeof rec.a==="number"?rec.a:1}`;}else if(variantRecords.length===1){const rec=variantRecords[0];const previewRel=rec&&typeof rec.preview==="string"?rec.preview:"";if(!previewRel)throw new Error("Preview for variant not found.");buf=await fs.promises.readFile(path.join(primaryOutDir,previewRel));id=`v${typeof rec.v==="number"?rec.v:1}`;}else if(imageRecords.length===1){const rec=imageRecords[0];const previewRel=rec&&typeof rec.preview==="string"?rec.preview:"";if(!previewRel)throw new Error("Preview for image not found.");buf=await fs.promises.readFile(path.join(primaryOutDir,previewRel));id=`i${typeof rec.i==="number"?rec.i:1}`;}else {const rec=pictures[0];const previewRel=rec&&typeof rec.preview==="string"?rec.preview:"";if(!previewRel)throw new Error("Preview for picture not found.");buf=await fs.promises.readFile(path.join(primaryOutDir,previewRel));id=`p${rec.p??1}`;}const origBuf=await resolveOriginalBuf(id,buf);sourceEntries.push({id,buf,origBuf});}}catch(e){return {content:[{type:"text",text:String(e?.message||e)}],isError:true}}console.log("[detect_object] sources resolved:",sourceEntries.map(s=>s.id));const globalConfig=getGlobalConfig(ctl);const embedPngMetadata=getGlobalBoolean(globalConfig,"embedPngMetadata",defaultPluginSettings.embedPngMetadata);const visionBaseUrl=getGlobalString(globalConfig,"embeddingBaseUrl",process.env.LMSTUDIO_VISION_API_BASE_URL||defaultPluginSettings.embeddingBaseUrl);const visionApiKey=getGlobalString(globalConfig,"embeddingApiKey",process.env.LMSTUDIO_VISION_API_KEY||defaultPluginSettings.embeddingApiKey);const visionModelKey=getGlobalString(globalConfig,"qwen3VlModelPath",process.env.LMSTUDIO_VISION_MODEL_KEY||defaultPluginSettings.qwen3VlModelPath);const envDetectMaxTokens=Number.parseInt(process.env.DETECT_MAX_TOKENS||"",10);const envDetectTemperature=Number.parseFloat(process.env.DETECT_TEMPERATURE||"");const configuredDetectMaxTokens=Math.floor(getGlobalNumber(globalConfig,"detectMaxTokens",Number.isFinite(envDetectMaxTokens)&&envDetectMaxTokens>0?envDetectMaxTokens:defaultPluginSettings.detectMaxTokens));const configuredDetectTemperature=getGlobalNumber(globalConfig,"detectTemperature",Number.isFinite(envDetectTemperature)?envDetectTemperature:defaultPluginSettings.detectTemperature);const detectionConfig={task,odPrompt:getGlobalString(globalConfig,"qwen3VlOdPrompt",process.env.DETECT_OD_PROMPT||defaultPluginSettings.qwen3VlOdPrompt)||undefined,maxTokens:configuredDetectMaxTokens,temperature:configuredDetectTemperature,timeoutMs:12e4};const tmpPaths=[];const detectionItems=[];for(const entry of sourceEntries){const tmpPath=path.join(primaryOutDir,`_tmp_detect_src_${entry.id}_${Date.now()}.png`);await fs.promises.writeFile(tmpPath,entry.buf);tmpPaths.push(tmpPath);detectionItems.push({id:entry.id,filePath:tmpPath});}console.log("[detect_object] calling detection API for",detectionItems.length,"items");const progressTotalSteps=sourceEntries.length*2+4;let batchResult={results:[],totalInferenceTimeMs:0,backend:"vision-api"};try{reportToolStatus(ctx,`Detecting objects in ${sourceEntries.length} image${sourceEntries.length===1?"":"s"}...`);reportToolStep(ctx,1,progressTotalSteps,`Preparing ${sourceEntries.length} image${sourceEntries.length===1?"":"s"} for object detection...`);const ready=await ensureLmStudioVisionInstanceReady({baseUrl:visionBaseUrl,apiKey:visionApiKey,modelKey:visionModelKey,status:message=>{try{ctx.status(message);}catch{}}});if(!ready.ok){throw new Error(ready.error)}for(let idx=0;idx<detectionItems.length;idx++){const item=detectionItems[idx];reportToolStep(ctx,idx+2,progressTotalSteps,`Detecting objects in ${item.id} (${idx+1}/${detectionItems.length})...`);const singleResult=await detectLmStudioVisionBatch([item],{...detectionConfig,baseUrl:visionBaseUrl,apiKey:visionApiKey,model:visionModelKey});batchResult.results.push(...singleResult.results);batchResult.totalInferenceTimeMs+=singleResult.totalInferenceTimeMs;batchResult.backend=singleResult.backend;}console.log("[detect_object] detection API returned:",{results:batchResult.results.length,totalMs:batchResult.totalInferenceTimeMs});try{const totalObjects=batchResult.results.reduce((s,r)=>s+(r.objects?.length??0),0);const ms=Math.round(batchResult.totalInferenceTimeMs);reportToolStep(ctx,sourceEntries.length+2,progressTotalSteps,`${totalObjects} object${totalObjects===1?"":"s"} found across ${batchResult.results.length} image${batchResult.results.length===1?"":"s"} (${ms}ms); drawing bounding boxes...`);}catch{}}finally{for(const tp of tmpPaths)await fs.promises.unlink(tp).catch(()=>{});}if(!batchResult.results.length){return {content:[{type:"text",text:"detect_object: no results returned from detection API."}],isError:true}}const variantPreviewSpec=VARIANT_FULL_CONFIG.preview;const stamp=isoStampCompact();let nextI=Math.max(1,st.counters?.nextImageI??1);const imageRecordsForState=[];const resultEntries=[];const httpBase=await getHealthyServerBaseUrl();for(let idx=0;idx<batchResult.results.length;idx++){const detResult=batchResult.results[idx];const sourceId=sourceEntries[idx]?.id??`canvas${idx+1}`;const origBuf=sourceEntries[idx].origBuf;const currentI=nextI++;reportToolStep(ctx,sourceEntries.length+3+idx,progressTotalSteps,`Drawing boxes for ${sourceId} (${idx+1}/${batchResult.results.length})...`);const bboxes=detResult.objects.map(o=>o.bbox);console.log(`[detect_object] drawing ${bboxes.length} bboxes for ${sourceId}...`);const annotatedBuf=await drawBboxesOnImage(origBuf,bboxes,{sourceDims:{width:detResult.imageWidth,height:detResult.imageHeight},palette:true});const analysis={schema:"ceveyne.image-analysis/v1",tool:"detect_object",sourceNotation:sourceId,inference:{model:visionModelKey,...task?{query:task}:{},detectorPromptSha256:crypto.createHash("sha256").update(detectionConfig.odPrompt??"").digest("hex"),maxTokens:configuredDetectMaxTokens,temperature:configuredDetectTemperature},render:{palette:true},detections:detResult.objects.map(object=>({label:object.label,bbox:{x1:object.bbox[0],y1:object.bbox[1],x2:object.bbox[2],y2:object.bbox[3]}}))};const savedBuffer=embedPngMetadata?injectXmpIntoBuffer(annotatedBuf,{...task?{prompt:task}:{},model:visionModelKey,mode:"object_detection",generatedBy:`${getSelfPluginIdentifier()}/detect_object`,creatorTool:`${getSelfPluginIdentifier()}/detect_object`,analysis}):annotatedBuf;const baseName=`image-${stamp}-i${currentI}`;const savedPath=path.join(primaryOutDir,`${baseName}.png`);await fs.promises.writeFile(savedPath,savedBuffer);const savedFileUrl=url.pathToFileURL(savedPath).toString();const savedSize=savedBuffer.length;console.log(`[detect_object] annotated image written: ${savedPath} (${savedSize} bytes)`);let preview=null;try{const p=await generatePreviewFromBuffer(savedBuffer,primaryOutDir,`${baseName}.png`,variantPreviewSpec);preview={ok:true,filePath:p.previewAbs,fileName:p.previewFilename,fileUrl:url.pathToFileURL(p.previewAbs).toString(),size_bytes:p.data.length,width:p.width,height:p.height,mimeType:"image/jpeg",dataBase64:p.data.toString("base64")};}catch(e){console.warn(`[detect_object] preview generation failed for ${sourceId}:`,String(e));}const httpOriginal=httpBase?toHttpOriginalUrl(`${baseName}.png`,httpBase,currentLmChatId||undefined):"";const httpPreview=(()=>{if(!httpBase||!currentLmChatId||!preview?.fileName)return "";return toHttpPreviewUrl(preview.fileName,httpBase,currentLmChatId)})();imageRecordsForState.push({filename:`${baseName}.png`,preview:preview?`preview-${baseName}.jpg`:undefined,i:currentI,sourceTool:`${getSelfPluginIdentifier()}/detect_object`,detectSource:sourceId,task,imageWidth:detResult.imageWidth,imageHeight:detResult.imageHeight,analysisMetadata:analysis,detections:detResult.objects.map(o=>({label:o.label,bbox:{x1:o.bbox[0],y1:o.bbox[1],x2:o.bbox[2],y2:o.bbox[3]},crop:{cropLeft:o.cropLeft,cropRight:o.cropRight,cropTop:o.cropTop,cropBottom:o.cropBottom}}))});resultEntries.push({id:sourceId,i:currentI,detResult,savedPath,savedFileUrl,savedSize,preview,httpOriginal,httpPreview});}console.log("[detect_object] updating state...");reportToolStep(ctx,progressTotalSteps-1,progressTotalSteps,"Updating image state and audit log...");try{const stateForUpdate=await readState$1(primaryOutDir);const appendResult=appendImages(stateForUpdate,imageRecordsForState);if(appendResult.changed){await writeStateAtomic(primaryOutDir,stateForUpdate);console.log("[detect_object] state written, nextImageI:",stateForUpdate.counters?.nextImageI);}}catch(e){console.warn("[detect_object] state update failed:",String(e));}try{const audit=buildAuditLogger({backend:"detect_object",mode:"detect_object",requestId:undefined});if(currentLmChatId)audit.setChatId(currentLmChatId);audit.setUserRequest({targets:rawTargets,task});audit.setOutput({images:resultEntries.map(r=>({id:r.id,i:r.i,detections:r.detResult.objects.length,path:r.savedPath,url:r.savedFileUrl,bytes:r.savedSize,...r.httpOriginal?{http_url:r.httpOriginal}:{},...r.preview?{preview_path:r.preview.filePath,preview_url:r.preview.fileUrl}:{},...r.httpPreview?{http_preview_url:r.httpPreview}:{}}))});await audit.write();}catch(e){console.warn("[detect_object] audit logging failed:",String(e));}const envPreviewRaw=process.env["PREVIEW_IN_CHAT"];const previewInChat=envPreviewRaw===undefined?true:envPreviewRaw==="1"||envPreviewRaw.toLowerCase()==="true";const summaries=resultEntries.map(r=>({tool:"detect_object",source:r.id,i:r.i,imageWidth:r.detResult.imageWidth,imageHeight:r.detResult.imageHeight,inferenceTimeMs:r.detResult.inferenceTimeMs,detections:r.detResult.objects.map(o=>({label:o.label,bbox:{x1:o.bbox[0],y1:o.bbox[1],x2:o.bbox[2],y2:o.bbox[3]},crop:{left:{pct:o.cropLeft,px:Math.round(o.cropLeft/100*r.detResult.imageWidth)},right:{pct:o.cropRight,px:Math.round(o.cropRight/100*r.detResult.imageWidth)},top:{pct:o.cropTop,px:Math.round(o.cropTop/100*r.detResult.imageHeight)},bottom:{pct:o.cropBottom,px:Math.round(o.cropBottom/100*r.detResult.imageHeight)}},crop_tool_hint:"Pass crop.left.pct as cropLeft, crop.right.pct as cropRight, crop.top.pct as cropTop, crop.bottom.pct as cropBottom to the crop tool."}))}));const reviewHint="Carefully examine the preview to make absolutely sure that the object detection matches your intent.";const content=[];reportToolStep(ctx,progressTotalSteps,progressTotalSteps,"Assembling detection result...");for(const r of resultEntries){const fallbackPreviewUrl=r.preview?.fileUrl||r.savedFileUrl;const previewLine=`Preview i${r.i}: ${r.httpPreview?r.httpPreview:fallbackPreviewUrl}`;const originalLine=`Original i${r.i}: ${r.httpOriginal?r.httpOriginal:r.savedFileUrl}`;if(previewInChat&&r.preview){const fname=String(r.preview.fileName||"");content.push({type:"image",fileName:fname,mimeType:r.preview.mimeType,markdown:``,$hint:"This is an image file. Present the image to the user by using the markdown above."});}}const totalMs=Math.round(batchResult.totalInferenceTimeMs);if(totalMs>0){content.push({type:"text",text:`Total inference time: ${totalMs}ms`});}content.push({type:"text",text:JSON.stringify(summaries.length===1?summaries[0]:summaries),...previewInChat?{}:{$hint:reviewHint}});return {content}}catch(error){return {content:[{type:"text",text:`detect_object failed: ${error.message||String(error)}`}],isError:true}}}})}
async function toolsProvider(ctl){try{const getter=ctl.getGlobalPluginConfig||ctl.getGlobalConfig;const gcfg=getter?getter.call(ctl,globalConfigSchematics):null;if(gcfg){const embeddingBaseUrl=gcfg.get("embeddingBaseUrl");if(typeof embeddingBaseUrl==="string"&&embeddingBaseUrl.trim()){process.env.LMSTUDIO_VISION_API_BASE_URL=embeddingBaseUrl.trim();}const embeddingApiKey=gcfg.get("embeddingApiKey");if(typeof embeddingApiKey==="string"){process.env.LMSTUDIO_VISION_API_KEY=embeddingApiKey;}const prompt=gcfg.get("visionPrompt");if(typeof prompt==="string"){process.env.VISION_PROMPT=prompt;}const inclMeta=gcfg.get("includeGenerationMetadata");if(typeof inclMeta==="boolean"){process.env.INCLUDE_GENERATION_METADATA=inclMeta?"true":"false";}const previewInChat=gcfg.get("PREVIEW_IN_CHAT");if(typeof previewInChat==="boolean"){process.env.PREVIEW_IN_CHAT=previewInChat?"true":"false";}const httpPort=gcfg.get("HTTP_SERVER_PORT");if(typeof httpPort==="number"&&Number.isFinite(httpPort)&&httpPort>0){process.env.HTTP_SERVER_PORT=String(Math.floor(httpPort));}const visionMaxTokens=gcfg.get("serverMaxTokens");if(typeof visionMaxTokens==="number"&&Number.isFinite(visionMaxTokens)&&visionMaxTokens>0){process.env.SERVER_MAX_TOKENS=String(Math.floor(visionMaxTokens));}const visionTemperature=gcfg.get("serverTemperature");if(typeof visionTemperature==="number"&&Number.isFinite(visionTemperature)){process.env.SERVER_TEMPERATURE=String(visionTemperature);}const qwen3VlModelPath=gcfg.get("qwen3VlModelPath");if(typeof qwen3VlModelPath==="string"){process.env.LMSTUDIO_VISION_MODEL_KEY=qwen3VlModelPath;}const qwen3VlOdPrompt=gcfg.get("qwen3VlOdPrompt");if(typeof qwen3VlOdPrompt==="string"){process.env.DETECT_OD_PROMPT=qwen3VlOdPrompt;}const detectMaxTokens=gcfg.get("detectMaxTokens");if(typeof detectMaxTokens==="number"&&Number.isFinite(detectMaxTokens)&&detectMaxTokens>0){process.env.DETECT_MAX_TOKENS=String(Math.floor(detectMaxTokens));}const detectTemperature=gcfg.get("detectTemperature");if(typeof detectTemperature==="number"&&Number.isFinite(detectTemperature)){process.env.DETECT_TEMPERATURE=String(detectTemperature);}}}catch{}const tools=[];tools.push(createAnalyseImageTool(ctl));tools.push(createDetectObjectTool(ctl));tools.push(createAnnotateImageTool(ctl));return tools}
async function main(context){context.withGlobalConfigSchematics(globalConfigSchematics).withToolsProvider(toolsProvider);}
exports.main = main;
#!/usr/bin/env node
'use strict';
var sdk = require('@lmstudio/sdk');
var zod = require('zod');
var path = require('path');
var fs = require('fs');
var crypto = require('crypto');
var url = require('url');
var fs$1 = require('node:fs');
var path$1 = require('node:path');
var child_process = require('child_process');
var os = require('os');
var promises = require('node:fs/promises');
var http = require('http');
require('node:crypto');
var util = require('util');
var os$1 = require('node:os');
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
}) : x)(function(x) {
if (typeof require !== "undefined") return require.apply(this, arguments);
throw Error('Dynamic require of "' + x + '" is not supported');
});
var __commonJS = (cb, mod) => function __require2() {
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
__defProp(target, "default", { value: mod, enumerable: true }) ,
mod
));
// node_modules/flatbuffers/js/constants.js
var require_constants = __commonJS({
"node_modules/flatbuffers/js/constants.js"(exports$1) {
Object.defineProperty(exports$1, "__esModule", { value: true });
exports$1.SIZE_PREFIX_LENGTH = exports$1.FILE_IDENTIFIER_LENGTH = exports$1.SIZEOF_INT = exports$1.SIZEOF_SHORT = void 0;
exports$1.SIZEOF_SHORT = 2;
exports$1.SIZEOF_INT = 4;
exports$1.FILE_IDENTIFIER_LENGTH = 4;
exports$1.SIZE_PREFIX_LENGTH = 4;
}
});
// node_modules/flatbuffers/js/utils.js
var require_utils = __commonJS({
"node_modules/flatbuffers/js/utils.js"(exports$1) {
Object.defineProperty(exports$1, "__esModule", { value: true });
exports$1.isLittleEndian = exports$1.float64 = exports$1.float32 = exports$1.int32 = void 0;
exports$1.int32 = new Int32Array(2);
exports$1.float32 = new Float32Array(exports$1.int32.buffer);
exports$1.float64 = new Float64Array(exports$1.int32.buffer);
exports$1.isLittleEndian = new Uint16Array(new Uint8Array([1, 0]).buffer)[0] === 1;
}
});
// node_modules/flatbuffers/js/encoding.js
var require_encoding = __commonJS({
"node_modules/flatbuffers/js/encoding.js"(exports$1) {
Object.defineProperty(exports$1, "__esModule", { value: true });
exports$1.Encoding = void 0;
var Encoding;
(function(Encoding2) {
Encoding2[Encoding2["UTF8_BYTES"] = 1] = "UTF8_BYTES";
Encoding2[Encoding2["UTF16_STRING"] = 2] = "UTF16_STRING";
})(Encoding || (exports$1.Encoding = Encoding = {}));
}
});
// node_modules/flatbuffers/js/byte-buffer.js
var require_byte_buffer = __commonJS({
"node_modules/flatbuffers/js/byte-buffer.js"(exports$1) {
Object.defineProperty(exports$1, "__esModule", { value: true });
exports$1.ByteBuffer = void 0;
var constants_js_1 = require_constants();
var encoding_js_1 = require_encoding();
var utils_js_1 = require_utils();
var ByteBuffer2 = class _ByteBuffer {
/**
* Create a new ByteBuffer with a given array of bytes (`Uint8Array`)
*/
constructor(bytes_) {
this.bytes_ = bytes_;
this.position_ = 0;
this.text_decoder_ = new TextDecoder();
}
/**
* Create and allocate a new ByteBuffer with a given size.
*/
static allocate(byte_size) {
return new _ByteBuffer(new Uint8Array(byte_size));
}
clear() {
this.position_ = 0;
}
/**
* Get the underlying `Uint8Array`.
*/
bytes() {
return this.bytes_;
}
/**
* Get the buffer's position.
*/
position() {
return this.position_;
}
/**
* Set the buffer's position.
*/
setPosition(position) {
this.position_ = position;
}
/**
* Get the buffer's capacity.
*/
capacity() {
return this.bytes_.length;
}
readInt8(offset) {
return this.readUint8(offset) << 24 >> 24;
}
readUint8(offset) {
return this.bytes_[offset];
}
readInt16(offset) {
return this.readUint16(offset) << 16 >> 16;
}
readUint16(offset) {
return this.bytes_[offset] | this.bytes_[offset + 1] << 8;
}
readInt32(offset) {
return this.bytes_[offset] | this.bytes_[offset + 1] << 8 | this.bytes_[offset + 2] << 16 | this.bytes_[offset + 3] << 24;
}
readUint32(offset) {
return this.readInt32(offset) >>> 0;
}
readInt64(offset) {
return BigInt.asIntN(64, BigInt(this.readUint32(offset)) + (BigInt(this.readUint32(offset + 4)) << BigInt(32)));
}
readUint64(offset) {
return BigInt.asUintN(64, BigInt(this.readUint32(offset)) + (BigInt(this.readUint32(offset + 4)) << BigInt(32)));
}
readFloat32(offset) {
utils_js_1.int32[0] = this.readInt32(offset);
return utils_js_1.float32[0];
}
readFloat64(offset) {
utils_js_1.int32[utils_js_1.isLittleEndian ? 0 : 1] = this.readInt32(offset);
utils_js_1.int32[utils_js_1.isLittleEndian ? 1 : 0] = this.readInt32(offset + 4);
return utils_js_1.float64[0];
}
writeInt8(offset, value) {
this.bytes_[offset] = value;
}
writeUint8(offset, value) {
this.bytes_[offset] = value;
}
writeInt16(offset, value) {
this.bytes_[offset] = value;
this.bytes_[offset + 1] = value >> 8;
}
writeUint16(offset, value) {
this.bytes_[offset] = value;
this.bytes_[offset + 1] = value >> 8;
}
writeInt32(offset, value) {
this.bytes_[offset] = value;
this.bytes_[offset + 1] = value >> 8;
this.bytes_[offset + 2] = value >> 16;
this.bytes_[offset + 3] = value >> 24;
}
writeUint32(offset, value) {
this.bytes_[offset] = value;
this.bytes_[offset + 1] = value >> 8;
this.bytes_[offset + 2] = value >> 16;
this.bytes_[offset + 3] = value >> 24;
}
writeInt64(offset, value) {
this.writeInt32(offset, Number(BigInt.asIntN(32, value)));
this.writeInt32(offset + 4, Number(BigInt.asIntN(32, value >> BigInt(32))));
}
writeUint64(offset, value) {
this.writeUint32(offset, Number(BigInt.asUintN(32, value)));
this.writeUint32(offset + 4, Number(BigInt.asUintN(32, value >> BigInt(32))));
}
writeFloat32(offset, value) {
utils_js_1.float32[0] = value;
this.writeInt32(offset, utils_js_1.int32[0]);
}
writeFloat64(offset, value) {
utils_js_1.float64[0] = value;
this.writeInt32(offset, utils_js_1.int32[utils_js_1.isLittleEndian ? 0 : 1]);
this.writeInt32(offset + 4, utils_js_1.int32[utils_js_1.isLittleEndian ? 1 : 0]);
}
/**
* Return the file identifier. Behavior is undefined for FlatBuffers whose
* schema does not include a file_identifier (likely points at padding or the
* start of a the root vtable).
*/
getBufferIdentifier() {
if (this.bytes_.length < this.position_ + constants_js_1.SIZEOF_INT + constants_js_1.FILE_IDENTIFIER_LENGTH) {
throw new Error("FlatBuffers: ByteBuffer is too short to contain an identifier.");
}
let result = "";
for (let i = 0; i < constants_js_1.FILE_IDENTIFIER_LENGTH; i++) {
result += String.fromCharCode(this.readInt8(this.position_ + constants_js_1.SIZEOF_INT + i));
}
return result;
}
/**
* Look up a field in the vtable, return an offset into the object, or 0 if the
* field is not present.
*/
__offset(bb_pos, vtable_offset) {
const vtable = bb_pos - this.readInt32(bb_pos);
return vtable_offset < this.readInt16(vtable) ? this.readInt16(vtable + vtable_offset) : 0;
}
/**
* Initialize any Table-derived type to point to the union at the given offset.
*/
__union(t, offset) {
t.bb_pos = offset + this.readInt32(offset);
t.bb = this;
return t;
}
/**
* Create a JavaScript string from UTF-8 data stored inside the FlatBuffer.
* This allocates a new string and converts to wide chars upon each access.
*
* To avoid the conversion to string, pass Encoding.UTF8_BYTES as the
* "optionalEncoding" argument. This is useful for avoiding conversion when
* the data will just be packaged back up in another FlatBuffer later on.
*
* @param offset
* @param opt_encoding Defaults to UTF16_STRING
*/
__string(offset, opt_encoding) {
offset += this.readInt32(offset);
const length = this.readInt32(offset);
offset += constants_js_1.SIZEOF_INT;
const utf8bytes = this.bytes_.subarray(offset, offset + length);
if (opt_encoding === encoding_js_1.Encoding.UTF8_BYTES)
return utf8bytes;
else
return this.text_decoder_.decode(utf8bytes);
}
/**
* Handle unions that can contain string as its member, if a Table-derived type then initialize it,
* if a string then return a new one
*
* WARNING: strings are immutable in JS so we can't change the string that the user gave us, this
* makes the behaviour of __union_with_string different compared to __union
*/
__union_with_string(o, offset) {
if (typeof o === "string") {
return this.__string(offset);
}
return this.__union(o, offset);
}
/**
* Retrieve the relative offset stored at "offset"
*/
__indirect(offset) {
return offset + this.readInt32(offset);
}
/**
* Get the start of data of a vector whose offset is stored at "offset" in this object.
*/
__vector(offset) {
return offset + this.readInt32(offset) + constants_js_1.SIZEOF_INT;
}
/**
* Get the length of a vector whose offset is stored at "offset" in this object.
*/
__vector_len(offset) {
return this.readInt32(offset + this.readInt32(offset));
}
__has_identifier(ident) {
if (ident.length != constants_js_1.FILE_IDENTIFIER_LENGTH) {
throw new Error("FlatBuffers: file identifier must be length " + constants_js_1.FILE_IDENTIFIER_LENGTH);
}
for (let i = 0; i < constants_js_1.FILE_IDENTIFIER_LENGTH; i++) {
if (ident.charCodeAt(i) != this.readInt8(this.position() + constants_js_1.SIZEOF_INT + i)) {
return false;
}
}
return true;
}
/**
* A helper function for generating list for obj api
*/
createScalarList(listAccessor, listLength) {
const ret = [];
for (let i = 0; i < listLength; ++i) {
const val = listAccessor(i);
if (val !== null) {
ret.push(val);
}
}
return ret;
}
/**
* A helper function for generating list for obj api
* @param listAccessor function that accepts an index and return data at that index
* @param listLength listLength
* @param res result list
*/
createObjList(listAccessor, listLength) {
const ret = [];
for (let i = 0; i < listLength; ++i) {
const val = listAccessor(i);
if (val !== null) {
ret.push(val.unpack());
}
}
return ret;
}
};
exports$1.ByteBuffer = ByteBuffer2;
}
});
// node_modules/flatbuffers/js/builder.js
var require_builder = __commonJS({
"node_modules/flatbuffers/js/builder.js"(exports$1) {
Object.defineProperty(exports$1, "__esModule", { value: true });
exports$1.Builder = void 0;
var byte_buffer_js_1 = require_byte_buffer();
var constants_js_1 = require_constants();
var Builder = class _Builder {
/**
* Create a FlatBufferBuilder.
*/
constructor(opt_initial_size) {
this.minalign = 1;
this.vtable = null;
this.vtable_in_use = 0;
this.isNested = false;
this.object_start = 0;
this.vtables = [];
this.vector_num_elems = 0;
this.force_defaults = false;
this.string_maps = null;
this.text_encoder = new TextEncoder();
let initial_size;
if (!opt_initial_size) {
initial_size = 1024;
} else {
initial_size = opt_initial_size;
}
this.bb = byte_buffer_js_1.ByteBuffer.allocate(initial_size);
this.space = initial_size;
}
clear() {
this.bb.clear();
this.space = this.bb.capacity();
this.minalign = 1;
this.vtable = null;
this.vtable_in_use = 0;
this.isNested = false;
this.object_start = 0;
this.vtables = [];
this.vector_num_elems = 0;
this.force_defaults = false;
this.string_maps = null;
}
/**
* In order to save space, fields that are set to their default value
* don't get serialized into the buffer. Forcing defaults provides a
* way to manually disable this optimization.
*
* @param forceDefaults true always serializes default values
*/
forceDefaults(forceDefaults) {
this.force_defaults = forceDefaults;
}
/**
* Get the ByteBuffer representing the FlatBuffer. Only call this after you've
* called finish(). The actual data starts at the ByteBuffer's current position,
* not necessarily at 0.
*/
dataBuffer() {
return this.bb;
}
/**
* Get the bytes representing the FlatBuffer. Only call this after you've
* called finish().
*/
asUint8Array() {
return this.bb.bytes().subarray(this.bb.position(), this.bb.position() + this.offset());
}
/**
* Prepare to write an element of `size` after `additional_bytes` have been
* written, e.g. if you write a string, you need to align such the int length
* field is aligned to 4 bytes, and the string data follows it directly. If all
* you need to do is alignment, `additional_bytes` will be 0.
*
* @param size This is the of the new element to write
* @param additional_bytes The padding size
*/
prep(size, additional_bytes) {
if (size > this.minalign) {
this.minalign = size;
}
const align_size = ~(this.bb.capacity() - this.space + additional_bytes) + 1 & size - 1;
while (this.space < align_size + size + additional_bytes) {
const old_buf_size = this.bb.capacity();
this.bb = _Builder.growByteBuffer(this.bb);
this.space += this.bb.capacity() - old_buf_size;
}
this.pad(align_size);
}
pad(byte_size) {
for (let i = 0; i < byte_size; i++) {
this.bb.writeInt8(--this.space, 0);
}
}
writeInt8(value) {
this.bb.writeInt8(this.space -= 1, value);
}
writeInt16(value) {
this.bb.writeInt16(this.space -= 2, value);
}
writeInt32(value) {
this.bb.writeInt32(this.space -= 4, value);
}
writeInt64(value) {
this.bb.writeInt64(this.space -= 8, value);
}
writeFloat32(value) {
this.bb.writeFloat32(this.space -= 4, value);
}
writeFloat64(value) {
this.bb.writeFloat64(this.space -= 8, value);
}
/**
* Add an `int8` to the buffer, properly aligned, and grows the buffer (if necessary).
* @param value The `int8` to add the buffer.
*/
addInt8(value) {
this.prep(1, 0);
this.writeInt8(value);
}
/**
* Add an `int16` to the buffer, properly aligned, and grows the buffer (if necessary).
* @param value The `int16` to add the buffer.
*/
addInt16(value) {
this.prep(2, 0);
this.writeInt16(value);
}
/**
* Add an `int32` to the buffer, properly aligned, and grows the buffer (if necessary).
* @param value The `int32` to add the buffer.
*/
addInt32(value) {
this.prep(4, 0);
this.writeInt32(value);
}
/**
* Add an `int64` to the buffer, properly aligned, and grows the buffer (if necessary).
* @param value The `int64` to add the buffer.
*/
addInt64(value) {
this.prep(8, 0);
this.writeInt64(value);
}
/**
* Add a `float32` to the buffer, properly aligned, and grows the buffer (if necessary).
* @param value The `float32` to add the buffer.
*/
addFloat32(value) {
this.prep(4, 0);
this.writeFloat32(value);
}
/**
* Add a `float64` to the buffer, properly aligned, and grows the buffer (if necessary).
* @param value The `float64` to add the buffer.
*/
addFloat64(value) {
this.prep(8, 0);
this.writeFloat64(value);
}
addFieldInt8(voffset, value, defaultValue) {
if (this.force_defaults || value != defaultValue) {
this.addInt8(value);
this.slot(voffset);
}
}
addFieldInt16(voffset, value, defaultValue) {
if (this.force_defaults || value != defaultValue) {
this.addInt16(value);
this.slot(voffset);
}
}
addFieldInt32(voffset, value, defaultValue) {
if (this.force_defaults || value != defaultValue) {
this.addInt32(value);
this.slot(voffset);
}
}
addFieldInt64(voffset, value, defaultValue) {
if (this.force_defaults || value !== defaultValue) {
this.addInt64(value);
this.slot(voffset);
}
}
addFieldFloat32(voffset, value, defaultValue) {
if (this.force_defaults || value != defaultValue) {
this.addFloat32(value);
this.slot(voffset);
}
}
addFieldFloat64(voffset, value, defaultValue) {
if (this.force_defaults || value != defaultValue) {
this.addFloat64(value);
this.slot(voffset);
}
}
addFieldOffset(voffset, value, defaultValue) {
if (this.force_defaults || value != defaultValue) {
this.addOffset(value);
this.slot(voffset);
}
}
/**
* Structs are stored inline, so nothing additional is being added. `d` is always 0.
*/
addFieldStruct(voffset, value, defaultValue) {
if (value != defaultValue) {
this.nested(value);
this.slot(voffset);
}
}
/**
* Structures are always stored inline, they need to be created right
* where they're used. You'll get this assertion failure if you
* created it elsewhere.
*/
nested(obj) {
if (obj != this.offset()) {
throw new TypeError("FlatBuffers: struct must be serialized inline.");
}
}
/**
* Should not be creating any other object, string or vector
* while an object is being constructed
*/
notNested() {
if (this.isNested) {
throw new TypeError("FlatBuffers: object serialization must not be nested.");
}
}
/**
* Set the current vtable at `voffset` to the current location in the buffer.
*/
slot(voffset) {
if (this.vtable !== null)
this.vtable[voffset] = this.offset();
}
/**
* @returns Offset relative to the end of the buffer.
*/
offset() {
return this.bb.capacity() - this.space;
}
/**
* Doubles the size of the backing ByteBuffer and copies the old data towards
* the end of the new buffer (since we build the buffer backwards).
*
* @param bb The current buffer with the existing data
* @returns A new byte buffer with the old data copied
* to it. The data is located at the end of the buffer.
*
* uint8Array.set() formally takes {Array<number>|ArrayBufferView}, so to pass
* it a uint8Array we need to suppress the type check:
* @suppress {checkTypes}
*/
static growByteBuffer(bb) {
const old_buf_size = bb.capacity();
if (old_buf_size & 3221225472) {
throw new Error("FlatBuffers: cannot grow buffer beyond 2 gigabytes.");
}
const new_buf_size = old_buf_size << 1;
const nbb = byte_buffer_js_1.ByteBuffer.allocate(new_buf_size);
nbb.setPosition(new_buf_size - old_buf_size);
nbb.bytes().set(bb.bytes(), new_buf_size - old_buf_size);
return nbb;
}
/**
* Adds on offset, relative to where it will be written.
*
* @param offset The offset to add.
*/
addOffset(offset) {
this.prep(constants_js_1.SIZEOF_INT, 0);
this.writeInt32(this.offset() - offset + constants_js_1.SIZEOF_INT);
}
/**
* Start encoding a new object in the buffer. Users will not usually need to
* call this directly. The FlatBuffers compiler will generate helper methods
* that call this method internally.
*/
startObject(numfields) {
this.notNested();
if (this.vtable == null) {
this.vtable = [];
}
this.vtable_in_use = numfields;
for (let i = 0; i < numfields; i++) {
this.vtable[i] = 0;
}
this.isNested = true;
this.object_start = this.offset();
}
/**
* Finish off writing the object that is under construction.
*
* @returns The offset to the object inside `dataBuffer`
*/
endObject() {
if (this.vtable == null || !this.isNested) {
throw new Error("FlatBuffers: endObject called without startObject");
}
this.addInt32(0);
const vtableloc = this.offset();
let i = this.vtable_in_use - 1;
for (; i >= 0 && this.vtable[i] == 0; i--) {
}
const trimmed_size = i + 1;
for (; i >= 0; i--) {
this.addInt16(this.vtable[i] != 0 ? vtableloc - this.vtable[i] : 0);
}
const standard_fields = 2;
this.addInt16(vtableloc - this.object_start);
const len = (trimmed_size + standard_fields) * constants_js_1.SIZEOF_SHORT;
this.addInt16(len);
let existing_vtable = 0;
const vt1 = this.space;
outer_loop: for (i = 0; i < this.vtables.length; i++) {
const vt2 = this.bb.capacity() - this.vtables[i];
if (len == this.bb.readInt16(vt2)) {
for (let j = constants_js_1.SIZEOF_SHORT; j < len; j += constants_js_1.SIZEOF_SHORT) {
if (this.bb.readInt16(vt1 + j) != this.bb.readInt16(vt2 + j)) {
continue outer_loop;
}
}
existing_vtable = this.vtables[i];
break;
}
}
if (existing_vtable) {
this.space = this.bb.capacity() - vtableloc;
this.bb.writeInt32(this.space, existing_vtable - vtableloc);
} else {
this.vtables.push(this.offset());
this.bb.writeInt32(this.bb.capacity() - vtableloc, this.offset() - vtableloc);
}
this.isNested = false;
return vtableloc;
}
/**
* Finalize a buffer, poiting to the given `root_table`.
*/
finish(root_table, opt_file_identifier, opt_size_prefix) {
const size_prefix = opt_size_prefix ? constants_js_1.SIZE_PREFIX_LENGTH : 0;
if (opt_file_identifier) {
const file_identifier = opt_file_identifier;
this.prep(this.minalign, constants_js_1.SIZEOF_INT + constants_js_1.FILE_IDENTIFIER_LENGTH + size_prefix);
if (file_identifier.length != constants_js_1.FILE_IDENTIFIER_LENGTH) {
throw new TypeError("FlatBuffers: file identifier must be length " + constants_js_1.FILE_IDENTIFIER_LENGTH);
}
for (let i = constants_js_1.FILE_IDENTIFIER_LENGTH - 1; i >= 0; i--) {
this.writeInt8(file_identifier.charCodeAt(i));
}
}
this.prep(this.minalign, constants_js_1.SIZEOF_INT + size_prefix);
this.addOffset(root_table);
if (size_prefix) {
this.addInt32(this.bb.capacity() - this.space);
}
this.bb.setPosition(this.space);
}
/**
* Finalize a size prefixed buffer, pointing to the given `root_table`.
*/
finishSizePrefixed(root_table, opt_file_identifier) {
this.finish(root_table, opt_file_identifier, true);
}
/**
* This checks a required field has been set in a given table that has
* just been constructed.
*/
requiredField(table, field) {
const table_start = this.bb.capacity() - table;
const vtable_start = table_start - this.bb.readInt32(table_start);
const ok = field < this.bb.readInt16(vtable_start) && this.bb.readInt16(vtable_start + field) != 0;
if (!ok) {
throw new TypeError("FlatBuffers: field " + field + " must be set");
}
}
/**
* Start a new array/vector of objects. Users usually will not call
* this directly. The FlatBuffers compiler will create a start/end
* method for vector types in generated code.
*
* @param elem_size The size of each element in the array
* @param num_elems The number of elements in the array
* @param alignment The alignment of the array
*/
startVector(elem_size, num_elems, alignment) {
this.notNested();
this.vector_num_elems = num_elems;
this.prep(constants_js_1.SIZEOF_INT, elem_size * num_elems);
this.prep(alignment, elem_size * num_elems);
}
/**
* Finish off the creation of an array and all its elements. The array must be
* created with `startVector`.
*
* @returns The offset at which the newly created array
* starts.
*/
endVector() {
this.writeInt32(this.vector_num_elems);
return this.offset();
}
/**
* Encode the string `s` in the buffer using UTF-8. If the string passed has
* already been seen, we return the offset of the already written string
*
* @param s The string to encode
* @return The offset in the buffer where the encoded string starts
*/
createSharedString(s) {
if (!s) {
return 0;
}
if (!this.string_maps) {
this.string_maps = /* @__PURE__ */ new Map();
}
if (this.string_maps.has(s)) {
return this.string_maps.get(s);
}
const offset = this.createString(s);
this.string_maps.set(s, offset);
return offset;
}
/**
* Encode the string `s` in the buffer using UTF-8. If a Uint8Array is passed
* instead of a string, it is assumed to contain valid UTF-8 encoded data.
*
* @param s The string to encode
* @return The offset in the buffer where the encoded string starts
*/
createString(s) {
if (s === null || s === void 0) {
return 0;
}
let utf8;
if (s instanceof Uint8Array) {
utf8 = s;
} else {
utf8 = this.text_encoder.encode(s);
}
this.addInt8(0);
this.startVector(1, utf8.length, 1);
this.bb.setPosition(this.space -= utf8.length);
this.bb.bytes().set(utf8, this.space);
return this.endVector();
}
/**
* Create a byte vector.
*
* @param v The bytes to add
* @returns The offset in the buffer where the byte vector starts
*/
createByteVector(v) {
if (v === null || v === void 0) {
return 0;
}
this.startVector(1, v.length, 1);
this.bb.setPosition(this.space -= v.length);
this.bb.bytes().set(v, this.space);
return this.endVector();
}
/**
* A helper function to pack an object
*
* @returns offset of obj
*/
createObjectOffset(obj) {
if (obj === null) {
return 0;
}
if (typeof obj === "string") {
return this.createString(obj);
} else {
return obj.pack(this);
}
}
/**
* A helper function to pack a list of object
*
* @returns list of offsets of each non null object
*/
createObjectOffsetList(list) {
const ret = [];
for (let i = 0; i < list.length; ++i) {
const val = list[i];
if (val !== null) {
ret.push(this.createObjectOffset(val));
} else {
throw new TypeError("FlatBuffers: Argument for createObjectOffsetList cannot contain null.");
}
}
return ret;
}
createStructOffsetList(list, startFunc) {
startFunc(this, list.length);
this.createObjectOffsetList(list.slice().reverse());
return this.endVector();
}
};
exports$1.Builder = Builder;
}
});
// node_modules/flatbuffers/js/flatbuffers.js
var require_flatbuffers = __commonJS({
"node_modules/flatbuffers/js/flatbuffers.js"(exports$1) {
Object.defineProperty(exports$1, "__esModule", { value: true });
exports$1.Encoding = exports$1.ByteBuffer = exports$1.Builder = exports$1.isLittleEndian = exports$1.int32 = exports$1.float64 = exports$1.float32 = exports$1.SIZE_PREFIX_LENGTH = exports$1.SIZEOF_SHORT = exports$1.SIZEOF_INT = exports$1.FILE_IDENTIFIER_LENGTH = void 0;
var constants_js_1 = require_constants();
Object.defineProperty(exports$1, "FILE_IDENTIFIER_LENGTH", { enumerable: true, get: function() {
return constants_js_1.FILE_IDENTIFIER_LENGTH;
} });
Object.defineProperty(exports$1, "SIZEOF_INT", { enumerable: true, get: function() {
return constants_js_1.SIZEOF_INT;
} });
Object.defineProperty(exports$1, "SIZEOF_SHORT", { enumerable: true, get: function() {
return constants_js_1.SIZEOF_SHORT;
} });
Object.defineProperty(exports$1, "SIZE_PREFIX_LENGTH", { enumerable: true, get: function() {
return constants_js_1.SIZE_PREFIX_LENGTH;
} });
var utils_js_1 = require_utils();
Object.defineProperty(exports$1, "float32", { enumerable: true, get: function() {
return utils_js_1.float32;
} });
Object.defineProperty(exports$1, "float64", { enumerable: true, get: function() {
return utils_js_1.float64;
} });
Object.defineProperty(exports$1, "int32", { enumerable: true, get: function() {
return utils_js_1.int32;
} });
Object.defineProperty(exports$1, "isLittleEndian", { enumerable: true, get: function() {
return utils_js_1.isLittleEndian;
} });
var builder_js_1 = require_builder();
Object.defineProperty(exports$1, "Builder", { enumerable: true, get: function() {
return builder_js_1.Builder;
} });
var byte_buffer_js_1 = require_byte_buffer();
Object.defineProperty(exports$1, "ByteBuffer", { enumerable: true, get: function() {
return byte_buffer_js_1.ByteBuffer;
} });
var encoding_js_1 = require_encoding();
Object.defineProperty(exports$1, "Encoding", { enumerable: true, get: function() {
return encoding_js_1.Encoding;
} });
}
});
// node_modules/exifr/dist/full.umd.js
var require_full_umd = __commonJS({
"node_modules/exifr/dist/full.umd.js"(exports$1, module) {
!(function(e, t) {
"object" == typeof exports$1 && "undefined" != typeof module ? t(exports$1) : "function" == typeof define && define.amd ? define("exifr", ["exports"], t) : t((e = "undefined" != typeof globalThis ? globalThis : e || self).exifr = {});
})(exports$1, (function(e) {
var t = "undefined" != typeof self ? self : global;
const i = "undefined" != typeof navigator, n = i && "undefined" == typeof HTMLImageElement, s = !("undefined" == typeof global || "undefined" == typeof process || !process.versions || !process.versions.node), r = t.Buffer, a = t.BigInt, o = !!r, l = (e2) => e2;
function h(e2, t2 = l) {
if (s) try {
return "function" == typeof __require ? Promise.resolve(t2(__require(e2))) : import(
/* webpackIgnore: true */
e2
).then(t2);
} catch (t3) {
console.warn(`Couldn't load ${e2}`);
}
}
let u = t.fetch;
const c = (e2) => u = e2;
if (!t.fetch) {
const e2 = h("http", ((e3) => e3)), t2 = h("https", ((e3) => e3)), i2 = (n2, { headers: s2 } = {}) => new Promise((async (r2, a2) => {
let { port: o2, hostname: l2, pathname: h2, protocol: u2, search: c2 } = new URL(n2);
const f2 = { method: "GET", hostname: l2, path: encodeURI(h2) + c2, headers: s2 };
"" !== o2 && (f2.port = Number(o2));
const d2 = ("https:" === u2 ? await t2 : await e2).request(f2, ((e3) => {
if (301 === e3.statusCode || 302 === e3.statusCode) {
let t3 = new URL(e3.headers.location, n2).toString();
return i2(t3, { headers: s2 }).then(r2).catch(a2);
}
r2({ status: e3.statusCode, arrayBuffer: () => new Promise(((t3) => {
let i3 = [];
e3.on("data", ((e4) => i3.push(e4))), e3.on("end", (() => t3(Buffer.concat(i3))));
})) });
}));
d2.on("error", a2), d2.end();
}));
c(i2);
}
function f(e2, t2, i2) {
return t2 in e2 ? Object.defineProperty(e2, t2, { value: i2, enumerable: true, configurable: true, writable: true }) : e2[t2] = i2, e2;
}
const d = (e2) => g(e2) ? void 0 : e2, p = (e2) => void 0 !== e2;
function g(e2) {
return void 0 === e2 || (e2 instanceof Map ? 0 === e2.size : 0 === Object.values(e2).filter(p).length);
}
function m(e2) {
let t2 = new Error(e2);
throw delete t2.stack, t2;
}
function S(e2) {
return "" === (e2 = (function(e3) {
for (; e3.endsWith("\0"); ) e3 = e3.slice(0, -1);
return e3;
})(e2).trim()) ? void 0 : e2;
}
function C(e2) {
let t2 = (function(e3) {
let t3 = 0;
return e3.ifd0.enabled && (t3 += 1024), e3.exif.enabled && (t3 += 2048), e3.makerNote && (t3 += 2048), e3.userComment && (t3 += 1024), e3.gps.enabled && (t3 += 512), e3.interop.enabled && (t3 += 100), e3.ifd1.enabled && (t3 += 1024), t3 + 2048;
})(e2);
return e2.jfif.enabled && (t2 += 50), e2.xmp.enabled && (t2 += 2e4), e2.iptc.enabled && (t2 += 14e3), e2.icc.enabled && (t2 += 6e3), t2;
}
const y = (e2) => String.fromCharCode.apply(null, e2), b = "undefined" != typeof TextDecoder ? new TextDecoder("utf-8") : void 0;
function P(e2) {
return b ? b.decode(e2) : o ? Buffer.from(e2).toString("utf8") : decodeURIComponent(escape(y(e2)));
}
class I {
static from(e2, t2) {
return e2 instanceof this && e2.le === t2 ? e2 : new I(e2, void 0, void 0, t2);
}
constructor(e2, t2 = 0, i2, n2) {
if ("boolean" == typeof n2 && (this.le = n2), Array.isArray(e2) && (e2 = new Uint8Array(e2)), 0 === e2) this.byteOffset = 0, this.byteLength = 0;
else if (e2 instanceof ArrayBuffer) {
void 0 === i2 && (i2 = e2.byteLength - t2);
let n3 = new DataView(e2, t2, i2);
this._swapDataView(n3);
} else if (e2 instanceof Uint8Array || e2 instanceof DataView || e2 instanceof I) {
void 0 === i2 && (i2 = e2.byteLength - t2), (t2 += e2.byteOffset) + i2 > e2.byteOffset + e2.byteLength && m("Creating view outside of available memory in ArrayBuffer");
let n3 = new DataView(e2.buffer, t2, i2);
this._swapDataView(n3);
} else if ("number" == typeof e2) {
let t3 = new DataView(new ArrayBuffer(e2));
this._swapDataView(t3);
} else m("Invalid input argument for BufferView: " + e2);
}
_swapArrayBuffer(e2) {
this._swapDataView(new DataView(e2));
}
_swapBuffer(e2) {
this._swapDataView(new DataView(e2.buffer, e2.byteOffset, e2.byteLength));
}
_swapDataView(e2) {
this.dataView = e2, this.buffer = e2.buffer, this.byteOffset = e2.byteOffset, this.byteLength = e2.byteLength;
}
_lengthToEnd(e2) {
return this.byteLength - e2;
}
set(e2, t2, i2 = I) {
return e2 instanceof DataView || e2 instanceof I ? e2 = new Uint8Array(e2.buffer, e2.byteOffset, e2.byteLength) : e2 instanceof ArrayBuffer && (e2 = new Uint8Array(e2)), e2 instanceof Uint8Array || m("BufferView.set(): Invalid data argument."), this.toUint8().set(e2, t2), new i2(this, t2, e2.byteLength);
}
subarray(e2, t2) {
return t2 = t2 || this._lengthToEnd(e2), new I(this, e2, t2);
}
toUint8() {
return new Uint8Array(this.buffer, this.byteOffset, this.byteLength);
}
getUint8Array(e2, t2) {
return new Uint8Array(this.buffer, this.byteOffset + e2, t2);
}
getString(e2 = 0, t2 = this.byteLength) {
return P(this.getUint8Array(e2, t2));
}
getLatin1String(e2 = 0, t2 = this.byteLength) {
let i2 = this.getUint8Array(e2, t2);
return y(i2);
}
getUnicodeString(e2 = 0, t2 = this.byteLength) {
const i2 = [];
for (let n2 = 0; n2 < t2 && e2 + n2 < this.byteLength; n2 += 2) i2.push(this.getUint16(e2 + n2));
return y(i2);
}
getInt8(e2) {
return this.dataView.getInt8(e2);
}
getUint8(e2) {
return this.dataView.getUint8(e2);
}
getInt16(e2, t2 = this.le) {
return this.dataView.getInt16(e2, t2);
}
getInt32(e2, t2 = this.le) {
return this.dataView.getInt32(e2, t2);
}
getUint16(e2, t2 = this.le) {
return this.dataView.getUint16(e2, t2);
}
getUint32(e2, t2 = this.le) {
return this.dataView.getUint32(e2, t2);
}
getFloat32(e2, t2 = this.le) {
return this.dataView.getFloat32(e2, t2);
}
getFloat64(e2, t2 = this.le) {
return this.dataView.getFloat64(e2, t2);
}
getFloat(e2, t2 = this.le) {
return this.dataView.getFloat32(e2, t2);
}
getDouble(e2, t2 = this.le) {
return this.dataView.getFloat64(e2, t2);
}
getUintBytes(e2, t2, i2) {
switch (t2) {
case 1:
return this.getUint8(e2, i2);
case 2:
return this.getUint16(e2, i2);
case 4:
return this.getUint32(e2, i2);
case 8:
return this.getUint64 && this.getUint64(e2, i2);
}
}
getUint(e2, t2, i2) {
switch (t2) {
case 8:
return this.getUint8(e2, i2);
case 16:
return this.getUint16(e2, i2);
case 32:
return this.getUint32(e2, i2);
case 64:
return this.getUint64 && this.getUint64(e2, i2);
}
}
toString(e2) {
return this.dataView.toString(e2, this.constructor.name);
}
ensureChunk() {
}
}
function k(e2, t2) {
m(`${e2} '${t2}' was not loaded, try using full build of exifr.`);
}
class w extends Map {
constructor(e2) {
super(), this.kind = e2;
}
get(e2, t2) {
return this.has(e2) || k(this.kind, e2), t2 && (e2 in t2 || (function(e3, t3) {
m(`Unknown ${e3} '${t3}'.`);
})(this.kind, e2), t2[e2].enabled || k(this.kind, e2)), super.get(e2);
}
keyList() {
return Array.from(this.keys());
}
}
var T = new w("file parser"), A = new w("segment parser"), D = new w("file reader");
const O = "Invalid input argument";
function x(e2, t2) {
return "string" == typeof e2 ? v(e2, t2) : i && !n && e2 instanceof HTMLImageElement ? v(e2.src, t2) : e2 instanceof Uint8Array || e2 instanceof ArrayBuffer || e2 instanceof DataView ? new I(e2) : i && e2 instanceof Blob ? M(e2, t2, "blob", U) : void m(O);
}
function v(e2, t2) {
return (n2 = e2).startsWith("data:") || n2.length > 1e4 ? R(e2, t2, "base64") : s && e2.includes("://") ? M(e2, t2, "url", L) : s ? R(e2, t2, "fs") : i ? M(e2, t2, "url", L) : void m(O);
var n2;
}
async function M(e2, t2, i2, n2) {
return D.has(i2) ? R(e2, t2, i2) : n2 ? (async function(e3, t3) {
let i3 = await t3(e3);
return new I(i3);
})(e2, n2) : void m(`Parser ${i2} is not loaded`);
}
async function R(e2, t2, i2) {
let n2 = new (D.get(i2))(e2, t2);
return await n2.read(), n2;
}
const L = (e2) => u(e2).then(((e3) => e3.arrayBuffer())), U = (e2) => new Promise(((t2, i2) => {
let n2 = new FileReader();
n2.onloadend = () => t2(n2.result || new ArrayBuffer()), n2.onerror = i2, n2.readAsArrayBuffer(e2);
}));
class F extends Map {
get tagKeys() {
return this.allKeys || (this.allKeys = Array.from(this.keys())), this.allKeys;
}
get tagValues() {
return this.allValues || (this.allValues = Array.from(this.values())), this.allValues;
}
}
function B(e2, t2, i2) {
let n2 = new F();
for (let [e3, t3] of i2) n2.set(e3, t3);
if (Array.isArray(t2)) for (let i3 of t2) e2.set(i3, n2);
else e2.set(t2, n2);
return n2;
}
function E(e2, t2, i2) {
let n2, s2 = e2.get(t2);
for (n2 of i2) s2.set(n2[0], n2[1]);
}
const N = /* @__PURE__ */ new Map(), G = /* @__PURE__ */ new Map(), V = /* @__PURE__ */ new Map(), z4 = 37500, H = 37510, j = 700, W = 33723, K = 34675, X = 34665, _ = 34853, Y = 40965, $ = ["chunked", "firstChunkSize", "firstChunkSizeNode", "firstChunkSizeBrowser", "chunkSize", "chunkLimit"], J = ["jfif", "xmp", "icc", "iptc", "ihdr"], q = ["tiff", ...J], Q = ["ifd0", "ifd1", "exif", "gps", "interop"], Z = [...q, ...Q], ee = ["makerNote", "userComment"], te = ["translateKeys", "translateValues", "reviveValues", "multiSegment"], ie = [...te, "sanitize", "mergeOutput", "silentErrors"];
class ne {
get translate() {
return this.translateKeys || this.translateValues || this.reviveValues;
}
}
class se extends ne {
get needed() {
return this.enabled || this.deps.size > 0;
}
constructor(e2, t2, i2, n2) {
if (super(), f(this, "enabled", false), f(this, "skip", /* @__PURE__ */ new Set()), f(this, "pick", /* @__PURE__ */ new Set()), f(this, "deps", /* @__PURE__ */ new Set()), f(this, "translateKeys", false), f(this, "translateValues", false), f(this, "reviveValues", false), this.key = e2, this.enabled = t2, this.parse = this.enabled, this.applyInheritables(n2), this.canBeFiltered = Q.includes(e2), this.canBeFiltered && (this.dict = N.get(e2)), void 0 !== i2) if (Array.isArray(i2)) this.parse = this.enabled = true, this.canBeFiltered && i2.length > 0 && this.translateTagSet(i2, this.pick);
else if ("object" == typeof i2) {
if (this.enabled = true, this.parse = false !== i2.parse, this.canBeFiltered) {
let { pick: e3, skip: t3 } = i2;
e3 && e3.length > 0 && this.translateTagSet(e3, this.pick), t3 && t3.length > 0 && this.translateTagSet(t3, this.skip);
}
this.applyInheritables(i2);
} else true === i2 || false === i2 ? this.parse = this.enabled = i2 : m(`Invalid options argument: ${i2}`);
}
applyInheritables(e2) {
let t2, i2;
for (t2 of te) i2 = e2[t2], void 0 !== i2 && (this[t2] = i2);
}
translateTagSet(e2, t2) {
if (this.dict) {
let i2, n2, { tagKeys: s2, tagValues: r2 } = this.dict;
for (i2 of e2) "string" == typeof i2 ? (n2 = r2.indexOf(i2), -1 === n2 && (n2 = s2.indexOf(Number(i2))), -1 !== n2 && t2.add(Number(s2[n2]))) : t2.add(i2);
} else for (let i2 of e2) t2.add(i2);
}
finalizeFilters() {
!this.enabled && this.deps.size > 0 ? (this.enabled = true, ue(this.pick, this.deps)) : this.enabled && this.pick.size > 0 && ue(this.pick, this.deps);
}
}
var re = { jfif: false, tiff: true, xmp: false, icc: false, iptc: false, ifd0: true, ifd1: false, exif: true, gps: true, interop: false, ihdr: void 0, makerNote: false, userComment: false, multiSegment: false, skip: [], pick: [], translateKeys: true, translateValues: true, reviveValues: true, sanitize: true, mergeOutput: true, silentErrors: true, chunked: true, firstChunkSize: void 0, firstChunkSizeNode: 512, firstChunkSizeBrowser: 65536, chunkSize: 65536, chunkLimit: 5 }, ae = /* @__PURE__ */ new Map();
class oe extends ne {
static useCached(e2) {
let t2 = ae.get(e2);
return void 0 !== t2 || (t2 = new this(e2), ae.set(e2, t2)), t2;
}
constructor(e2) {
super(), true === e2 ? this.setupFromTrue() : void 0 === e2 ? this.setupFromUndefined() : Array.isArray(e2) ? this.setupFromArray(e2) : "object" == typeof e2 ? this.setupFromObject(e2) : m(`Invalid options argument ${e2}`), void 0 === this.firstChunkSize && (this.firstChunkSize = i ? this.firstChunkSizeBrowser : this.firstChunkSizeNode), this.mergeOutput && (this.ifd1.enabled = false), this.filterNestedSegmentTags(), this.traverseTiffDependencyTree(), this.checkLoadedPlugins();
}
setupFromUndefined() {
let e2;
for (e2 of $) this[e2] = re[e2];
for (e2 of ie) this[e2] = re[e2];
for (e2 of ee) this[e2] = re[e2];
for (e2 of Z) this[e2] = new se(e2, re[e2], void 0, this);
}
setupFromTrue() {
let e2;
for (e2 of $) this[e2] = re[e2];
for (e2 of ie) this[e2] = re[e2];
for (e2 of ee) this[e2] = true;
for (e2 of Z) this[e2] = new se(e2, true, void 0, this);
}
setupFromArray(e2) {
let t2;
for (t2 of $) this[t2] = re[t2];
for (t2 of ie) this[t2] = re[t2];
for (t2 of ee) this[t2] = re[t2];
for (t2 of Z) this[t2] = new se(t2, false, void 0, this);
this.setupGlobalFilters(e2, void 0, Q);
}
setupFromObject(e2) {
let t2;
for (t2 of (Q.ifd0 = Q.ifd0 || Q.image, Q.ifd1 = Q.ifd1 || Q.thumbnail, Object.assign(this, e2), $)) this[t2] = he(e2[t2], re[t2]);
for (t2 of ie) this[t2] = he(e2[t2], re[t2]);
for (t2 of ee) this[t2] = he(e2[t2], re[t2]);
for (t2 of q) this[t2] = new se(t2, re[t2], e2[t2], this);
for (t2 of Q) this[t2] = new se(t2, re[t2], e2[t2], this.tiff);
this.setupGlobalFilters(e2.pick, e2.skip, Q, Z), true === e2.tiff ? this.batchEnableWithBool(Q, true) : false === e2.tiff ? this.batchEnableWithUserValue(Q, e2) : Array.isArray(e2.tiff) ? this.setupGlobalFilters(e2.tiff, void 0, Q) : "object" == typeof e2.tiff && this.setupGlobalFilters(e2.tiff.pick, e2.tiff.skip, Q);
}
batchEnableWithBool(e2, t2) {
for (let i2 of e2) this[i2].enabled = t2;
}
batchEnableWithUserValue(e2, t2) {
for (let i2 of e2) {
let e3 = t2[i2];
this[i2].enabled = false !== e3 && void 0 !== e3;
}
}
setupGlobalFilters(e2, t2, i2, n2 = i2) {
if (e2 && e2.length) {
for (let e3 of n2) this[e3].enabled = false;
let t3 = le(e2, i2);
for (let [e3, i3] of t3) ue(this[e3].pick, i3), this[e3].enabled = true;
} else if (t2 && t2.length) {
let e3 = le(t2, i2);
for (let [t3, i3] of e3) ue(this[t3].skip, i3);
}
}
filterNestedSegmentTags() {
let { ifd0: e2, exif: t2, xmp: i2, iptc: n2, icc: s2 } = this;
this.makerNote ? t2.deps.add(z4) : t2.skip.add(z4), this.userComment ? t2.deps.add(H) : t2.skip.add(H), i2.enabled || e2.skip.add(j), n2.enabled || e2.skip.add(W), s2.enabled || e2.skip.add(K);
}
traverseTiffDependencyTree() {
let { ifd0: e2, exif: t2, gps: i2, interop: n2 } = this;
n2.needed && (t2.deps.add(Y), e2.deps.add(Y)), t2.needed && e2.deps.add(X), i2.needed && e2.deps.add(_), this.tiff.enabled = Q.some(((e3) => true === this[e3].enabled)) || this.makerNote || this.userComment;
for (let e3 of Q) this[e3].finalizeFilters();
}
get onlyTiff() {
return !J.map(((e2) => this[e2].enabled)).some(((e2) => true === e2)) && this.tiff.enabled;
}
checkLoadedPlugins() {
for (let e2 of q) this[e2].enabled && !A.has(e2) && k("segment parser", e2);
}
}
function le(e2, t2) {
let i2, n2, s2, r2, a2 = [];
for (s2 of t2) {
for (r2 of (i2 = N.get(s2), n2 = [], i2)) (e2.includes(r2[0]) || e2.includes(r2[1])) && n2.push(r2[0]);
n2.length && a2.push([s2, n2]);
}
return a2;
}
function he(e2, t2) {
return void 0 !== e2 ? e2 : void 0 !== t2 ? t2 : void 0;
}
function ue(e2, t2) {
for (let i2 of t2) e2.add(i2);
}
f(oe, "default", re);
class ce {
constructor(e2) {
f(this, "parsers", {}), f(this, "output", {}), f(this, "errors", []), f(this, "pushToErrors", ((e3) => this.errors.push(e3))), this.options = oe.useCached(e2);
}
async read(e2) {
this.file = await x(e2, this.options);
}
setup() {
if (this.fileParser) return;
let { file: e2 } = this, t2 = e2.getUint16(0);
for (let [i2, n2] of T) if (n2.canHandle(e2, t2)) return this.fileParser = new n2(this.options, this.file, this.parsers), e2[i2] = true;
this.file.close && this.file.close(), m("Unknown file format");
}
async parse() {
let { output: e2, errors: t2 } = this;
return this.setup(), this.options.silentErrors ? (await this.executeParsers().catch(this.pushToErrors), t2.push(...this.fileParser.errors)) : await this.executeParsers(), this.file.close && this.file.close(), this.options.silentErrors && t2.length > 0 && (e2.errors = t2), d(e2);
}
async executeParsers() {
let { output: e2 } = this;
await this.fileParser.parse();
let t2 = Object.values(this.parsers).map((async (t3) => {
let i2 = await t3.parse();
t3.assignToOutput(e2, i2);
}));
this.options.silentErrors && (t2 = t2.map(((e3) => e3.catch(this.pushToErrors)))), await Promise.all(t2);
}
async extractThumbnail() {
this.setup();
let { options: e2, file: t2 } = this, i2 = A.get("tiff", e2);
var n2;
if (t2.tiff ? n2 = { start: 0, type: "tiff" } : t2.jpeg && (n2 = await this.fileParser.getOrFindSegment("tiff")), void 0 === n2) return;
let s2 = await this.fileParser.ensureSegmentChunk(n2), r2 = this.parsers.tiff = new i2(s2, e2, t2), a2 = await r2.extractThumbnail();
return t2.close && t2.close(), a2;
}
}
async function fe(e2, t2) {
let i2 = new ce(t2);
return await i2.read(e2), i2.parse();
}
var de = Object.freeze({ __proto__: null, parse: fe, Exifr: ce, fileParsers: T, segmentParsers: A, fileReaders: D, tagKeys: N, tagValues: G, tagRevivers: V, createDictionary: B, extendDictionary: E, fetchUrlAsArrayBuffer: L, readBlobAsArrayBuffer: U, chunkedProps: $, otherSegments: J, segments: q, tiffBlocks: Q, segmentsAndBlocks: Z, tiffExtractables: ee, inheritables: te, allFormatters: ie, Options: oe });
class pe {
constructor(e2, t2, i2) {
f(this, "errors", []), f(this, "ensureSegmentChunk", (async (e3) => {
let t3 = e3.start, i3 = e3.size || 65536;
if (this.file.chunked) if (this.file.available(t3, i3)) e3.chunk = this.file.subarray(t3, i3);
else try {
e3.chunk = await this.file.readChunk(t3, i3);
} catch (t4) {
m(`Couldn't read segment: ${JSON.stringify(e3)}. ${t4.message}`);
}
else this.file.byteLength > t3 + i3 ? e3.chunk = this.file.subarray(t3, i3) : void 0 === e3.size ? e3.chunk = this.file.subarray(t3) : m("Segment unreachable: " + JSON.stringify(e3));
return e3.chunk;
})), this.extendOptions && this.extendOptions(e2), this.options = e2, this.file = t2, this.parsers = i2;
}
injectSegment(e2, t2) {
this.options[e2].enabled && this.createParser(e2, t2);
}
createParser(e2, t2) {
let i2 = new (A.get(e2))(t2, this.options, this.file);
return this.parsers[e2] = i2;
}
createParsers(e2) {
for (let t2 of e2) {
let { type: e3, chunk: i2 } = t2, n2 = this.options[e3];
if (n2 && n2.enabled) {
let t3 = this.parsers[e3];
t3 && t3.append || t3 || this.createParser(e3, i2);
}
}
}
async readSegments(e2) {
let t2 = e2.map(this.ensureSegmentChunk);
await Promise.all(t2);
}
}
class ge {
static findPosition(e2, t2) {
let i2 = e2.getUint16(t2 + 2) + 2, n2 = "function" == typeof this.headerLength ? this.headerLength(e2, t2, i2) : this.headerLength, s2 = t2 + n2, r2 = i2 - n2;
return { offset: t2, length: i2, headerLength: n2, start: s2, size: r2, end: s2 + r2 };
}
static parse(e2, t2 = {}) {
return new this(e2, new oe({ [this.type]: t2 }), e2).parse();
}
normalizeInput(e2) {
return e2 instanceof I ? e2 : new I(e2);
}
constructor(e2, t2 = {}, i2) {
f(this, "errors", []), f(this, "raw", /* @__PURE__ */ new Map()), f(this, "handleError", ((e3) => {
if (!this.options.silentErrors) throw e3;
this.errors.push(e3.message);
})), this.chunk = this.normalizeInput(e2), this.file = i2, this.type = this.constructor.type, this.globalOptions = this.options = t2, this.localOptions = t2[this.type], this.canTranslate = this.localOptions && this.localOptions.translate;
}
translate() {
this.canTranslate && (this.translated = this.translateBlock(this.raw, this.type));
}
get output() {
return this.translated ? this.translated : this.raw ? Object.fromEntries(this.raw) : void 0;
}
translateBlock(e2, t2) {
let i2 = V.get(t2), n2 = G.get(t2), s2 = N.get(t2), r2 = this.options[t2], a2 = r2.reviveValues && !!i2, o2 = r2.translateValues && !!n2, l2 = r2.translateKeys && !!s2, h2 = {};
for (let [t3, r3] of e2) a2 && i2.has(t3) ? r3 = i2.get(t3)(r3) : o2 && n2.has(t3) && (r3 = this.translateValue(r3, n2.get(t3))), l2 && s2.has(t3) && (t3 = s2.get(t3) || t3), h2[t3] = r3;
return h2;
}
translateValue(e2, t2) {
return t2[e2] || t2.DEFAULT || e2;
}
assignToOutput(e2, t2) {
this.assignObjectToOutput(e2, this.constructor.type, t2);
}
assignObjectToOutput(e2, t2, i2) {
if (this.globalOptions.mergeOutput) return Object.assign(e2, i2);
e2[t2] ? Object.assign(e2[t2], i2) : e2[t2] = i2;
}
}
f(ge, "headerLength", 4), f(ge, "type", void 0), f(ge, "multiSegment", false), f(ge, "canHandle", (() => false));
function me(e2) {
return 192 === e2 || 194 === e2 || 196 === e2 || 219 === e2 || 221 === e2 || 218 === e2 || 254 === e2;
}
function Se(e2) {
return e2 >= 224 && e2 <= 239;
}
function Ce(e2, t2, i2) {
for (let [n2, s2] of A) if (s2.canHandle(e2, t2, i2)) return n2;
}
class ye extends pe {
constructor(...e2) {
super(...e2), f(this, "appSegments", []), f(this, "jpegSegments", []), f(this, "unknownSegments", []);
}
static canHandle(e2, t2) {
return 65496 === t2;
}
async parse() {
await this.findAppSegments(), await this.readSegments(this.appSegments), this.mergeMultiSegments(), this.createParsers(this.mergedAppSegments || this.appSegments);
}
setupSegmentFinderArgs(e2) {
true === e2 ? (this.findAll = true, this.wanted = new Set(A.keyList())) : (e2 = void 0 === e2 ? A.keyList().filter(((e3) => this.options[e3].enabled)) : e2.filter(((e3) => this.options[e3].enabled && A.has(e3))), this.findAll = false, this.remaining = new Set(e2), this.wanted = new Set(e2)), this.unfinishedMultiSegment = false;
}
async findAppSegments(e2 = 0, t2) {
this.setupSegmentFinderArgs(t2);
let { file: i2, findAll: n2, wanted: s2, remaining: r2 } = this;
if (!n2 && this.file.chunked && (n2 = Array.from(s2).some(((e3) => {
let t3 = A.get(e3), i3 = this.options[e3];
return t3.multiSegment && i3.multiSegment;
})), n2 && await this.file.readWhole()), e2 = this.findAppSegmentsInRange(e2, i2.byteLength), !this.options.onlyTiff && i2.chunked) {
let t3 = false;
for (; r2.size > 0 && !t3 && (i2.canReadNextChunk || this.unfinishedMultiSegment); ) {
let { nextChunkOffset: n3 } = i2, s3 = this.appSegments.some(((e3) => !this.file.available(e3.offset || e3.start, e3.length || e3.size)));
if (t3 = e2 > n3 && !s3 ? !await i2.readNextChunk(e2) : !await i2.readNextChunk(n3), void 0 === (e2 = this.findAppSegmentsInRange(e2, i2.byteLength))) return;
}
}
}
findAppSegmentsInRange(e2, t2) {
t2 -= 2;
let i2, n2, s2, r2, a2, o2, { file: l2, findAll: h2, wanted: u2, remaining: c2, options: f2 } = this;
for (; e2 < t2; e2++) if (255 === l2.getUint8(e2)) {
if (i2 = l2.getUint8(e2 + 1), Se(i2)) {
if (n2 = l2.getUint16(e2 + 2), s2 = Ce(l2, e2, n2), s2 && u2.has(s2) && (r2 = A.get(s2), a2 = r2.findPosition(l2, e2), o2 = f2[s2], a2.type = s2, this.appSegments.push(a2), !h2 && (r2.multiSegment && o2.multiSegment ? (this.unfinishedMultiSegment = a2.chunkNumber < a2.chunkCount, this.unfinishedMultiSegment || c2.delete(s2)) : c2.delete(s2), 0 === c2.size))) break;
f2.recordUnknownSegments && (a2 = ge.findPosition(l2, e2), a2.marker = i2, this.unknownSegments.push(a2)), e2 += n2 + 1;
} else if (me(i2)) {
if (n2 = l2.getUint16(e2 + 2), 218 === i2 && false !== f2.stopAfterSos) return;
f2.recordJpegSegments && this.jpegSegments.push({ offset: e2, length: n2, marker: i2 }), e2 += n2 + 1;
}
}
return e2;
}
mergeMultiSegments() {
if (!this.appSegments.some(((e3) => e3.multiSegment))) return;
let e2 = (function(e3, t2) {
let i2, n2, s2, r2 = /* @__PURE__ */ new Map();
for (let a2 = 0; a2 < e3.length; a2++) i2 = e3[a2], n2 = i2[t2], r2.has(n2) ? s2 = r2.get(n2) : r2.set(n2, s2 = []), s2.push(i2);
return Array.from(r2);
})(this.appSegments, "type");
this.mergedAppSegments = e2.map((([e3, t2]) => {
let i2 = A.get(e3, this.options);
if (i2.handleMultiSegments) {
return { type: e3, chunk: i2.handleMultiSegments(t2) };
}
return t2[0];
}));
}
getSegment(e2) {
return this.appSegments.find(((t2) => t2.type === e2));
}
async getOrFindSegment(e2) {
let t2 = this.getSegment(e2);
return void 0 === t2 && (await this.findAppSegments(0, [e2]), t2 = this.getSegment(e2)), t2;
}
}
f(ye, "type", "jpeg"), T.set("jpeg", ye);
const be = [void 0, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8, 4];
class Pe extends ge {
parseHeader() {
var e2 = this.chunk.getUint16();
18761 === e2 ? this.le = true : 19789 === e2 && (this.le = false), this.chunk.le = this.le, this.headerParsed = true;
}
parseTags(e2, t2, i2 = /* @__PURE__ */ new Map()) {
let { pick: n2, skip: s2 } = this.options[t2];
n2 = new Set(n2);
let r2 = n2.size > 0, a2 = 0 === s2.size, o2 = this.chunk.getUint16(e2);
e2 += 2;
for (let l2 = 0; l2 < o2; l2++) {
let o3 = this.chunk.getUint16(e2);
if (r2) {
if (n2.has(o3) && (i2.set(o3, this.parseTag(e2, o3, t2)), n2.delete(o3), 0 === n2.size)) break;
} else !a2 && s2.has(o3) || i2.set(o3, this.parseTag(e2, o3, t2));
e2 += 12;
}
return i2;
}
parseTag(e2, t2, i2) {
let { chunk: n2 } = this, s2 = n2.getUint16(e2 + 2), r2 = n2.getUint32(e2 + 4), a2 = be[s2];
if (a2 * r2 <= 4 ? e2 += 8 : e2 = n2.getUint32(e2 + 8), (s2 < 1 || s2 > 13) && m(`Invalid TIFF value type. block: ${i2.toUpperCase()}, tag: ${t2.toString(16)}, type: ${s2}, offset ${e2}`), e2 > n2.byteLength && m(`Invalid TIFF value offset. block: ${i2.toUpperCase()}, tag: ${t2.toString(16)}, type: ${s2}, offset ${e2} is outside of chunk size ${n2.byteLength}`), 1 === s2) return n2.getUint8Array(e2, r2);
if (2 === s2) return S(n2.getString(e2, r2));
if (7 === s2) return n2.getUint8Array(e2, r2);
if (1 === r2) return this.parseTagValue(s2, e2);
{
let t3 = new ((function(e3) {
switch (e3) {
case 1:
return Uint8Array;
case 3:
return Uint16Array;
case 4:
return Uint32Array;
case 5:
return Array;
case 6:
return Int8Array;
case 8:
return Int16Array;
case 9:
return Int32Array;
case 10:
return Array;
case 11:
return Float32Array;
case 12:
return Float64Array;
default:
return Array;
}
})(s2))(r2), i3 = a2;
for (let n3 = 0; n3 < r2; n3++) t3[n3] = this.parseTagValue(s2, e2), e2 += i3;
return t3;
}
}
parseTagValue(e2, t2) {
let { chunk: i2 } = this;
switch (e2) {
case 1:
return i2.getUint8(t2);
case 3:
return i2.getUint16(t2);
case 4:
return i2.getUint32(t2);
case 5:
return i2.getUint32(t2) / i2.getUint32(t2 + 4);
case 6:
return i2.getInt8(t2);
case 8:
return i2.getInt16(t2);
case 9:
return i2.getInt32(t2);
case 10:
return i2.getInt32(t2) / i2.getInt32(t2 + 4);
case 11:
return i2.getFloat(t2);
case 12:
return i2.getDouble(t2);
case 13:
return i2.getUint32(t2);
default:
m(`Invalid tiff type ${e2}`);
}
}
}
class Ie extends Pe {
static canHandle(e2, t2) {
return 225 === e2.getUint8(t2 + 1) && 1165519206 === e2.getUint32(t2 + 4) && 0 === e2.getUint16(t2 + 8);
}
async parse() {
this.parseHeader();
let { options: e2 } = this;
return e2.ifd0.enabled && await this.parseIfd0Block(), e2.exif.enabled && await this.safeParse("parseExifBlock"), e2.gps.enabled && await this.safeParse("parseGpsBlock"), e2.interop.enabled && await this.safeParse("parseInteropBlock"), e2.ifd1.enabled && await this.safeParse("parseThumbnailBlock"), this.createOutput();
}
safeParse(e2) {
let t2 = this[e2]();
return void 0 !== t2.catch && (t2 = t2.catch(this.handleError)), t2;
}
findIfd0Offset() {
void 0 === this.ifd0Offset && (this.ifd0Offset = this.chunk.getUint32(4));
}
findIfd1Offset() {
if (void 0 === this.ifd1Offset) {
this.findIfd0Offset();
let e2 = this.chunk.getUint16(this.ifd0Offset), t2 = this.ifd0Offset + 2 + 12 * e2;
this.ifd1Offset = this.chunk.getUint32(t2);
}
}
parseBlock(e2, t2) {
let i2 = /* @__PURE__ */ new Map();
return this[t2] = i2, this.parseTags(e2, t2, i2), i2;
}
async parseIfd0Block() {
if (this.ifd0) return;
let { file: e2 } = this;
this.findIfd0Offset(), this.ifd0Offset < 8 && m("Malformed EXIF data"), !e2.chunked && this.ifd0Offset > e2.byteLength && m(`IFD0 offset points to outside of file.
this.ifd0Offset: ${this.ifd0Offset}, file.byteLength: ${e2.byteLength}`), e2.tiff && await e2.ensureChunk(this.ifd0Offset, C(this.options));
let t2 = this.parseBlock(this.ifd0Offset, "ifd0");
return 0 !== t2.size ? (this.exifOffset = t2.get(X), this.interopOffset = t2.get(Y), this.gpsOffset = t2.get(_), this.xmp = t2.get(j), this.iptc = t2.get(W), this.icc = t2.get(K), this.options.sanitize && (t2.delete(X), t2.delete(Y), t2.delete(_), t2.delete(j), t2.delete(W), t2.delete(K)), t2) : void 0;
}
async parseExifBlock() {
if (this.exif) return;
if (this.ifd0 || await this.parseIfd0Block(), void 0 === this.exifOffset) return;
this.file.tiff && await this.file.ensureChunk(this.exifOffset, C(this.options));
let e2 = this.parseBlock(this.exifOffset, "exif");
return this.interopOffset || (this.interopOffset = e2.get(Y)), this.makerNote = e2.get(z4), this.userComment = e2.get(H), this.options.sanitize && (e2.delete(Y), e2.delete(z4), e2.delete(H)), this.unpack(e2, 41728), this.unpack(e2, 41729), e2;
}
unpack(e2, t2) {
let i2 = e2.get(t2);
i2 && 1 === i2.length && e2.set(t2, i2[0]);
}
async parseGpsBlock() {
if (this.gps) return;
if (this.ifd0 || await this.parseIfd0Block(), void 0 === this.gpsOffset) return;
let e2 = this.parseBlock(this.gpsOffset, "gps");
return e2 && e2.has(2) && e2.has(4) && (e2.set("latitude", ke(...e2.get(2), e2.get(1))), e2.set("longitude", ke(...e2.get(4), e2.get(3)))), e2;
}
async parseInteropBlock() {
if (!this.interop && (this.ifd0 || await this.parseIfd0Block(), void 0 !== this.interopOffset || this.exif || await this.parseExifBlock(), void 0 !== this.interopOffset)) return this.parseBlock(this.interopOffset, "interop");
}
async parseThumbnailBlock(e2 = false) {
if (!this.ifd1 && !this.ifd1Parsed && (!this.options.mergeOutput || e2)) return this.findIfd1Offset(), this.ifd1Offset > 0 && (this.parseBlock(this.ifd1Offset, "ifd1"), this.ifd1Parsed = true), this.ifd1;
}
async extractThumbnail() {
if (this.headerParsed || this.parseHeader(), this.ifd1Parsed || await this.parseThumbnailBlock(true), void 0 === this.ifd1) return;
let e2 = this.ifd1.get(513), t2 = this.ifd1.get(514);
return this.chunk.getUint8Array(e2, t2);
}
get image() {
return this.ifd0;
}
get thumbnail() {
return this.ifd1;
}
createOutput() {
let e2, t2, i2, n2 = {};
for (t2 of Q) if (e2 = this[t2], !g(e2)) if (i2 = this.canTranslate ? this.translateBlock(e2, t2) : Object.fromEntries(e2), this.options.mergeOutput) {
if ("ifd1" === t2) continue;
Object.assign(n2, i2);
} else n2[t2] = i2;
return this.makerNote && (n2.makerNote = this.makerNote), this.userComment && (n2.userComment = this.userComment), n2;
}
assignToOutput(e2, t2) {
if (this.globalOptions.mergeOutput) Object.assign(e2, t2);
else for (let [i2, n2] of Object.entries(t2)) this.assignObjectToOutput(e2, i2, n2);
}
}
function ke(e2, t2, i2, n2) {
var s2 = e2 + t2 / 60 + i2 / 3600;
return "S" !== n2 && "W" !== n2 || (s2 *= -1), s2;
}
f(Ie, "type", "tiff"), f(Ie, "headerLength", 10), A.set("tiff", Ie);
var we = Object.freeze({ __proto__: null, default: de, Exifr: ce, fileParsers: T, segmentParsers: A, fileReaders: D, tagKeys: N, tagValues: G, tagRevivers: V, createDictionary: B, extendDictionary: E, fetchUrlAsArrayBuffer: L, readBlobAsArrayBuffer: U, chunkedProps: $, otherSegments: J, segments: q, tiffBlocks: Q, segmentsAndBlocks: Z, tiffExtractables: ee, inheritables: te, allFormatters: ie, Options: oe, parse: fe });
const Te = { ifd0: false, ifd1: false, exif: false, gps: false, interop: false, sanitize: false, reviveValues: true, translateKeys: false, translateValues: false, mergeOutput: false }, Ae = Object.assign({}, Te, { firstChunkSize: 4e4, gps: [1, 2, 3, 4] });
async function De(e2) {
let t2 = new ce(Ae);
await t2.read(e2);
let i2 = await t2.parse();
if (i2 && i2.gps) {
let { latitude: e3, longitude: t3 } = i2.gps;
return { latitude: e3, longitude: t3 };
}
}
const Oe = Object.assign({}, Te, { tiff: false, ifd1: true, mergeOutput: false });
async function xe(e2) {
let t2 = new ce(Oe);
await t2.read(e2);
let i2 = await t2.extractThumbnail();
return i2 && o ? r.from(i2) : i2;
}
async function ve(e2) {
let t2 = await this.thumbnail(e2);
if (void 0 !== t2) {
let e3 = new Blob([t2]);
return URL.createObjectURL(e3);
}
}
const Me = Object.assign({}, Te, { firstChunkSize: 4e4, ifd0: [274] });
async function Re(e2) {
let t2 = new ce(Me);
await t2.read(e2);
let i2 = await t2.parse();
if (i2 && i2.ifd0) return i2.ifd0[274];
}
const Le = Object.freeze({ 1: { dimensionSwapped: false, scaleX: 1, scaleY: 1, deg: 0, rad: 0 }, 2: { dimensionSwapped: false, scaleX: -1, scaleY: 1, deg: 0, rad: 0 }, 3: { dimensionSwapped: false, scaleX: 1, scaleY: 1, deg: 180, rad: 180 * Math.PI / 180 }, 4: { dimensionSwapped: false, scaleX: -1, scaleY: 1, deg: 180, rad: 180 * Math.PI / 180 }, 5: { dimensionSwapped: true, scaleX: 1, scaleY: -1, deg: 90, rad: 90 * Math.PI / 180 }, 6: { dimensionSwapped: true, scaleX: 1, scaleY: 1, deg: 90, rad: 90 * Math.PI / 180 }, 7: { dimensionSwapped: true, scaleX: 1, scaleY: -1, deg: 270, rad: 270 * Math.PI / 180 }, 8: { dimensionSwapped: true, scaleX: 1, scaleY: 1, deg: 270, rad: 270 * Math.PI / 180 } });
if (e.rotateCanvas = true, e.rotateCss = true, "object" == typeof navigator) {
let t2 = navigator.userAgent;
if (t2.includes("iPad") || t2.includes("iPhone")) {
let i2 = t2.match(/OS (\d+)_(\d+)/);
if (i2) {
let [, t3, n2] = i2, s2 = Number(t3) + 0.1 * Number(n2);
e.rotateCanvas = s2 < 13.4, e.rotateCss = false;
}
} else if (t2.includes("OS X 10")) {
let [, i2] = t2.match(/OS X 10[_.](\d+)/);
e.rotateCanvas = e.rotateCss = Number(i2) < 15;
}
if (t2.includes("Chrome/")) {
let [, i2] = t2.match(/Chrome\/(\d+)/);
e.rotateCanvas = e.rotateCss = Number(i2) < 81;
} else if (t2.includes("Firefox/")) {
let [, i2] = t2.match(/Firefox\/(\d+)/);
e.rotateCanvas = e.rotateCss = Number(i2) < 77;
}
}
async function Ue(t2) {
let i2 = await Re(t2);
return Object.assign({ canvas: e.rotateCanvas, css: e.rotateCss }, Le[i2]);
}
class Fe extends I {
constructor(...e2) {
super(...e2), f(this, "ranges", new Be()), 0 !== this.byteLength && this.ranges.add(0, this.byteLength);
}
_tryExtend(e2, t2, i2) {
if (0 === e2 && 0 === this.byteLength && i2) {
let e3 = new DataView(i2.buffer || i2, i2.byteOffset, i2.byteLength);
this._swapDataView(e3);
} else {
let i3 = e2 + t2;
if (i3 > this.byteLength) {
let { dataView: e3 } = this._extend(i3);
this._swapDataView(e3);
}
}
}
_extend(e2) {
let t2;
t2 = o ? r.allocUnsafe(e2) : new Uint8Array(e2);
let i2 = new DataView(t2.buffer, t2.byteOffset, t2.byteLength);
return t2.set(new Uint8Array(this.buffer, this.byteOffset, this.byteLength), 0), { uintView: t2, dataView: i2 };
}
subarray(e2, t2, i2 = false) {
return t2 = t2 || this._lengthToEnd(e2), i2 && this._tryExtend(e2, t2), this.ranges.add(e2, t2), super.subarray(e2, t2);
}
set(e2, t2, i2 = false) {
i2 && this._tryExtend(t2, e2.byteLength, e2);
let n2 = super.set(e2, t2);
return this.ranges.add(t2, n2.byteLength), n2;
}
async ensureChunk(e2, t2) {
this.chunked && (this.ranges.available(e2, t2) || await this.readChunk(e2, t2));
}
available(e2, t2) {
return this.ranges.available(e2, t2);
}
}
class Be {
constructor() {
f(this, "list", []);
}
get length() {
return this.list.length;
}
add(e2, t2, i2 = 0) {
let n2 = e2 + t2, s2 = this.list.filter(((t3) => Ee(e2, t3.offset, n2) || Ee(e2, t3.end, n2)));
if (s2.length > 0) {
e2 = Math.min(e2, ...s2.map(((e3) => e3.offset))), n2 = Math.max(n2, ...s2.map(((e3) => e3.end))), t2 = n2 - e2;
let i3 = s2.shift();
i3.offset = e2, i3.length = t2, i3.end = n2, this.list = this.list.filter(((e3) => !s2.includes(e3)));
} else this.list.push({ offset: e2, length: t2, end: n2 });
}
available(e2, t2) {
let i2 = e2 + t2;
return this.list.some(((t3) => t3.offset <= e2 && i2 <= t3.end));
}
}
function Ee(e2, t2, i2) {
return e2 <= t2 && t2 <= i2;
}
class Ne extends Fe {
constructor(e2, t2) {
super(0), f(this, "chunksRead", 0), this.input = e2, this.options = t2;
}
async readWhole() {
this.chunked = false, await this.readChunk(this.nextChunkOffset);
}
async readChunked() {
this.chunked = true, await this.readChunk(0, this.options.firstChunkSize);
}
async readNextChunk(e2 = this.nextChunkOffset) {
if (this.fullyRead) return this.chunksRead++, false;
let t2 = this.options.chunkSize, i2 = await this.readChunk(e2, t2);
return !!i2 && i2.byteLength === t2;
}
async readChunk(e2, t2) {
if (this.chunksRead++, 0 !== (t2 = this.safeWrapAddress(e2, t2))) return this._readChunk(e2, t2);
}
safeWrapAddress(e2, t2) {
return void 0 !== this.size && e2 + t2 > this.size ? Math.max(0, this.size - e2) : t2;
}
get nextChunkOffset() {
if (0 !== this.ranges.list.length) return this.ranges.list[0].length;
}
get canReadNextChunk() {
return this.chunksRead < this.options.chunkLimit;
}
get fullyRead() {
return void 0 !== this.size && this.nextChunkOffset === this.size;
}
read() {
return this.options.chunked ? this.readChunked() : this.readWhole();
}
close() {
}
}
D.set("blob", class extends Ne {
async readWhole() {
this.chunked = false;
let e2 = await U(this.input);
this._swapArrayBuffer(e2);
}
readChunked() {
return this.chunked = true, this.size = this.input.size, super.readChunked();
}
async _readChunk(e2, t2) {
let i2 = t2 ? e2 + t2 : void 0, n2 = this.input.slice(e2, i2), s2 = await U(n2);
return this.set(s2, e2, true);
}
});
var Ge = Object.freeze({ __proto__: null, default: we, Exifr: ce, fileParsers: T, segmentParsers: A, fileReaders: D, tagKeys: N, tagValues: G, tagRevivers: V, createDictionary: B, extendDictionary: E, fetchUrlAsArrayBuffer: L, readBlobAsArrayBuffer: U, chunkedProps: $, otherSegments: J, segments: q, tiffBlocks: Q, segmentsAndBlocks: Z, tiffExtractables: ee, inheritables: te, allFormatters: ie, Options: oe, parse: fe, gpsOnlyOptions: Ae, gps: De, thumbnailOnlyOptions: Oe, thumbnail: xe, thumbnailUrl: ve, orientationOnlyOptions: Me, orientation: Re, rotations: Le, get rotateCanvas() {
return e.rotateCanvas;
}, get rotateCss() {
return e.rotateCss;
}, rotation: Ue });
D.set("url", class extends Ne {
async readWhole() {
this.chunked = false;
let e2 = await L(this.input);
e2 instanceof ArrayBuffer ? this._swapArrayBuffer(e2) : e2 instanceof Uint8Array && this._swapBuffer(e2);
}
async _readChunk(e2, t2) {
let i2 = t2 ? e2 + t2 - 1 : void 0, n2 = this.options.httpHeaders || {};
(e2 || i2) && (n2.range = `bytes=${[e2, i2].join("-")}`);
let s2 = await u(this.input, { headers: n2 }), r2 = await s2.arrayBuffer(), a2 = r2.byteLength;
if (416 !== s2.status) return a2 !== t2 && (this.size = e2 + a2), this.set(r2, e2, true);
}
});
I.prototype.getUint64 = function(e2) {
let t2 = this.getUint32(e2), i2 = this.getUint32(e2 + 4);
return t2 < 1048575 ? t2 << 32 | i2 : void 0 !== typeof a ? (console.warn("Using BigInt because of type 64uint but JS can only handle 53b numbers."), a(t2) << a(32) | a(i2)) : void m("Trying to read 64b value but JS can only handle 53b numbers.");
};
class Ve extends pe {
parseBoxes(e2 = 0) {
let t2 = [];
for (; e2 < this.file.byteLength - 4; ) {
let i2 = this.parseBoxHead(e2);
if (t2.push(i2), 0 === i2.length) break;
e2 += i2.length;
}
return t2;
}
parseSubBoxes(e2) {
e2.boxes = this.parseBoxes(e2.start);
}
findBox(e2, t2) {
return void 0 === e2.boxes && this.parseSubBoxes(e2), e2.boxes.find(((e3) => e3.kind === t2));
}
parseBoxHead(e2) {
let t2 = this.file.getUint32(e2), i2 = this.file.getString(e2 + 4, 4), n2 = e2 + 8;
return 1 === t2 && (t2 = this.file.getUint64(e2 + 8), n2 += 8), { offset: e2, length: t2, kind: i2, start: n2 };
}
parseBoxFullHead(e2) {
if (void 0 !== e2.version) return;
let t2 = this.file.getUint32(e2.start);
e2.version = t2 >> 24, e2.start += 4;
}
}
class ze extends Ve {
static canHandle(e2, t2) {
if (0 !== t2) return false;
let i2 = e2.getUint16(2);
if (i2 > 50) return false;
let n2 = 16, s2 = [];
for (; n2 < i2; ) s2.push(e2.getString(n2, 4)), n2 += 4;
return s2.includes(this.type);
}
async parse() {
let e2 = this.file.getUint32(0), t2 = this.parseBoxHead(e2);
for (; "meta" !== t2.kind; ) e2 += t2.length, await this.file.ensureChunk(e2, 16), t2 = this.parseBoxHead(e2);
await this.file.ensureChunk(t2.offset, t2.length), this.parseBoxFullHead(t2), this.parseSubBoxes(t2), this.options.icc.enabled && await this.findIcc(t2), this.options.tiff.enabled && await this.findExif(t2);
}
async registerSegment(e2, t2, i2) {
await this.file.ensureChunk(t2, i2);
let n2 = this.file.subarray(t2, i2);
this.createParser(e2, n2);
}
async findIcc(e2) {
let t2 = this.findBox(e2, "iprp");
if (void 0 === t2) return;
let i2 = this.findBox(t2, "ipco");
if (void 0 === i2) return;
let n2 = this.findBox(i2, "colr");
void 0 !== n2 && await this.registerSegment("icc", n2.offset + 12, n2.length);
}
async findExif(e2) {
let t2 = this.findBox(e2, "iinf");
if (void 0 === t2) return;
let i2 = this.findBox(e2, "iloc");
if (void 0 === i2) return;
let n2 = this.findExifLocIdInIinf(t2), s2 = this.findExtentInIloc(i2, n2);
if (void 0 === s2) return;
let [r2, a2] = s2;
await this.file.ensureChunk(r2, a2);
let o2 = 4 + this.file.getUint32(r2);
r2 += o2, a2 -= o2, await this.registerSegment("tiff", r2, a2);
}
findExifLocIdInIinf(e2) {
this.parseBoxFullHead(e2);
let t2, i2, n2, s2, r2 = e2.start, a2 = this.file.getUint16(r2);
for (r2 += 2; a2--; ) {
if (t2 = this.parseBoxHead(r2), this.parseBoxFullHead(t2), i2 = t2.start, t2.version >= 2 && (n2 = 3 === t2.version ? 4 : 2, s2 = this.file.getString(i2 + n2 + 2, 4), "Exif" === s2)) return this.file.getUintBytes(i2, n2);
r2 += t2.length;
}
}
get8bits(e2) {
let t2 = this.file.getUint8(e2);
return [t2 >> 4, 15 & t2];
}
findExtentInIloc(e2, t2) {
this.parseBoxFullHead(e2);
let i2 = e2.start, [n2, s2] = this.get8bits(i2++), [r2, a2] = this.get8bits(i2++), o2 = 2 === e2.version ? 4 : 2, l2 = 1 === e2.version || 2 === e2.version ? 2 : 0, h2 = a2 + n2 + s2, u2 = 2 === e2.version ? 4 : 2, c2 = this.file.getUintBytes(i2, u2);
for (i2 += u2; c2--; ) {
let e3 = this.file.getUintBytes(i2, o2);
i2 += o2 + l2 + 2 + r2;
let u3 = this.file.getUint16(i2);
if (i2 += 2, e3 === t2) return u3 > 1 && console.warn("ILOC box has more than one extent but we're only processing one\nPlease create an issue at https://github.com/MikeKovarik/exifr with this file"), [this.file.getUintBytes(i2 + a2, n2), this.file.getUintBytes(i2 + a2 + n2, s2)];
i2 += u3 * h2;
}
}
}
class He extends ze {
}
f(He, "type", "heic");
class je extends ze {
}
f(je, "type", "avif"), T.set("heic", He), T.set("avif", je), B(N, ["ifd0", "ifd1"], [[256, "ImageWidth"], [257, "ImageHeight"], [258, "BitsPerSample"], [259, "Compression"], [262, "PhotometricInterpretation"], [270, "ImageDescription"], [271, "Make"], [272, "Model"], [273, "StripOffsets"], [274, "Orientation"], [277, "SamplesPerPixel"], [278, "RowsPerStrip"], [279, "StripByteCounts"], [282, "XResolution"], [283, "YResolution"], [284, "PlanarConfiguration"], [296, "ResolutionUnit"], [301, "TransferFunction"], [305, "Software"], [306, "ModifyDate"], [315, "Artist"], [316, "HostComputer"], [317, "Predictor"], [318, "WhitePoint"], [319, "PrimaryChromaticities"], [513, "ThumbnailOffset"], [514, "ThumbnailLength"], [529, "YCbCrCoefficients"], [530, "YCbCrSubSampling"], [531, "YCbCrPositioning"], [532, "ReferenceBlackWhite"], [700, "ApplicationNotes"], [33432, "Copyright"], [33723, "IPTC"], [34665, "ExifIFD"], [34675, "ICC"], [34853, "GpsIFD"], [330, "SubIFD"], [40965, "InteropIFD"], [40091, "XPTitle"], [40092, "XPComment"], [40093, "XPAuthor"], [40094, "XPKeywords"], [40095, "XPSubject"]]), B(N, "exif", [[33434, "ExposureTime"], [33437, "FNumber"], [34850, "ExposureProgram"], [34852, "SpectralSensitivity"], [34855, "ISO"], [34858, "TimeZoneOffset"], [34859, "SelfTimerMode"], [34864, "SensitivityType"], [34865, "StandardOutputSensitivity"], [34866, "RecommendedExposureIndex"], [34867, "ISOSpeed"], [34868, "ISOSpeedLatitudeyyy"], [34869, "ISOSpeedLatitudezzz"], [36864, "ExifVersion"], [36867, "DateTimeOriginal"], [36868, "CreateDate"], [36873, "GooglePlusUploadCode"], [36880, "OffsetTime"], [36881, "OffsetTimeOriginal"], [36882, "OffsetTimeDigitized"], [37121, "ComponentsConfiguration"], [37122, "CompressedBitsPerPixel"], [37377, "ShutterSpeedValue"], [37378, "ApertureValue"], [37379, "BrightnessValue"], [37380, "ExposureCompensation"], [37381, "MaxApertureValue"], [37382, "SubjectDistance"], [37383, "MeteringMode"], [37384, "LightSource"], [37385, "Flash"], [37386, "FocalLength"], [37393, "ImageNumber"], [37394, "SecurityClassification"], [37395, "ImageHistory"], [37396, "SubjectArea"], [37500, "MakerNote"], [37510, "UserComment"], [37520, "SubSecTime"], [37521, "SubSecTimeOriginal"], [37522, "SubSecTimeDigitized"], [37888, "AmbientTemperature"], [37889, "Humidity"], [37890, "Pressure"], [37891, "WaterDepth"], [37892, "Acceleration"], [37893, "CameraElevationAngle"], [40960, "FlashpixVersion"], [40961, "ColorSpace"], [40962, "ExifImageWidth"], [40963, "ExifImageHeight"], [40964, "RelatedSoundFile"], [41483, "FlashEnergy"], [41486, "FocalPlaneXResolution"], [41487, "FocalPlaneYResolution"], [41488, "FocalPlaneResolutionUnit"], [41492, "SubjectLocation"], [41493, "ExposureIndex"], [41495, "SensingMethod"], [41728, "FileSource"], [41729, "SceneType"], [41730, "CFAPattern"], [41985, "CustomRendered"], [41986, "ExposureMode"], [41987, "WhiteBalance"], [41988, "DigitalZoomRatio"], [41989, "FocalLengthIn35mmFormat"], [41990, "SceneCaptureType"], [41991, "GainControl"], [41992, "Contrast"], [41993, "Saturation"], [41994, "Sharpness"], [41996, "SubjectDistanceRange"], [42016, "ImageUniqueID"], [42032, "OwnerName"], [42033, "SerialNumber"], [42034, "LensInfo"], [42035, "LensMake"], [42036, "LensModel"], [42037, "LensSerialNumber"], [42080, "CompositeImage"], [42081, "CompositeImageCount"], [42082, "CompositeImageExposureTimes"], [42240, "Gamma"], [59932, "Padding"], [59933, "OffsetSchema"], [65e3, "OwnerName"], [65001, "SerialNumber"], [65002, "Lens"], [65100, "RawFile"], [65101, "Converter"], [65102, "WhiteBalance"], [65105, "Exposure"], [65106, "Shadows"], [65107, "Brightness"], [65108, "Contrast"], [65109, "Saturation"], [65110, "Sharpness"], [65111, "Smoothness"], [65112, "MoireFilter"], [40965, "InteropIFD"]]), B(N, "gps", [[0, "GPSVersionID"], [1, "GPSLatitudeRef"], [2, "GPSLatitude"], [3, "GPSLongitudeRef"], [4, "GPSLongitude"], [5, "GPSAltitudeRef"], [6, "GPSAltitude"], [7, "GPSTimeStamp"], [8, "GPSSatellites"], [9, "GPSStatus"], [10, "GPSMeasureMode"], [11, "GPSDOP"], [12, "GPSSpeedRef"], [13, "GPSSpeed"], [14, "GPSTrackRef"], [15, "GPSTrack"], [16, "GPSImgDirectionRef"], [17, "GPSImgDirection"], [18, "GPSMapDatum"], [19, "GPSDestLatitudeRef"], [20, "GPSDestLatitude"], [21, "GPSDestLongitudeRef"], [22, "GPSDestLongitude"], [23, "GPSDestBearingRef"], [24, "GPSDestBearing"], [25, "GPSDestDistanceRef"], [26, "GPSDestDistance"], [27, "GPSProcessingMethod"], [28, "GPSAreaInformation"], [29, "GPSDateStamp"], [30, "GPSDifferential"], [31, "GPSHPositioningError"]]), B(G, ["ifd0", "ifd1"], [[274, { 1: "Horizontal (normal)", 2: "Mirror horizontal", 3: "Rotate 180", 4: "Mirror vertical", 5: "Mirror horizontal and rotate 270 CW", 6: "Rotate 90 CW", 7: "Mirror horizontal and rotate 90 CW", 8: "Rotate 270 CW" }], [296, { 1: "None", 2: "inches", 3: "cm" }]]);
let We = B(G, "exif", [[34850, { 0: "Not defined", 1: "Manual", 2: "Normal program", 3: "Aperture priority", 4: "Shutter priority", 5: "Creative program", 6: "Action program", 7: "Portrait mode", 8: "Landscape mode" }], [37121, { 0: "-", 1: "Y", 2: "Cb", 3: "Cr", 4: "R", 5: "G", 6: "B" }], [37383, { 0: "Unknown", 1: "Average", 2: "CenterWeightedAverage", 3: "Spot", 4: "MultiSpot", 5: "Pattern", 6: "Partial", 255: "Other" }], [37384, { 0: "Unknown", 1: "Daylight", 2: "Fluorescent", 3: "Tungsten (incandescent light)", 4: "Flash", 9: "Fine weather", 10: "Cloudy weather", 11: "Shade", 12: "Daylight fluorescent (D 5700 - 7100K)", 13: "Day white fluorescent (N 4600 - 5400K)", 14: "Cool white fluorescent (W 3900 - 4500K)", 15: "White fluorescent (WW 3200 - 3700K)", 17: "Standard light A", 18: "Standard light B", 19: "Standard light C", 20: "D55", 21: "D65", 22: "D75", 23: "D50", 24: "ISO studio tungsten", 255: "Other" }], [37385, { 0: "Flash did not fire", 1: "Flash fired", 5: "Strobe return light not detected", 7: "Strobe return light detected", 9: "Flash fired, compulsory flash mode", 13: "Flash fired, compulsory flash mode, return light not detected", 15: "Flash fired, compulsory flash mode, return light detected", 16: "Flash did not fire, compulsory flash mode", 24: "Flash did not fire, auto mode", 25: "Flash fired, auto mode", 29: "Flash fired, auto mode, return light not detected", 31: "Flash fired, auto mode, return light detected", 32: "No flash function", 65: "Flash fired, red-eye reduction mode", 69: "Flash fired, red-eye reduction mode, return light not detected", 71: "Flash fired, red-eye reduction mode, return light detected", 73: "Flash fired, compulsory flash mode, red-eye reduction mode", 77: "Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected", 79: "Flash fired, compulsory flash mode, red-eye reduction mode, return light detected", 89: "Flash fired, auto mode, red-eye reduction mode", 93: "Flash fired, auto mode, return light not detected, red-eye reduction mode", 95: "Flash fired, auto mode, return light detected, red-eye reduction mode" }], [41495, { 1: "Not defined", 2: "One-chip color area sensor", 3: "Two-chip color area sensor", 4: "Three-chip color area sensor", 5: "Color sequential area sensor", 7: "Trilinear sensor", 8: "Color sequential linear sensor" }], [41728, { 1: "Film Scanner", 2: "Reflection Print Scanner", 3: "Digital Camera" }], [41729, { 1: "Directly photographed" }], [41985, { 0: "Normal", 1: "Custom", 2: "HDR (no original saved)", 3: "HDR (original saved)", 4: "Original (for HDR)", 6: "Panorama", 7: "Portrait HDR", 8: "Portrait" }], [41986, { 0: "Auto", 1: "Manual", 2: "Auto bracket" }], [41987, { 0: "Auto", 1: "Manual" }], [41990, { 0: "Standard", 1: "Landscape", 2: "Portrait", 3: "Night", 4: "Other" }], [41991, { 0: "None", 1: "Low gain up", 2: "High gain up", 3: "Low gain down", 4: "High gain down" }], [41996, { 0: "Unknown", 1: "Macro", 2: "Close", 3: "Distant" }], [42080, { 0: "Unknown", 1: "Not a Composite Image", 2: "General Composite Image", 3: "Composite Image Captured While Shooting" }]]);
const Ke = { 1: "No absolute unit of measurement", 2: "Inch", 3: "Centimeter" };
We.set(37392, Ke), We.set(41488, Ke);
const Xe = { 0: "Normal", 1: "Low", 2: "High" };
function _e(e2) {
return "object" == typeof e2 && void 0 !== e2.length ? e2[0] : e2;
}
function Ye(e2) {
let t2 = Array.from(e2).slice(1);
return t2[1] > 15 && (t2 = t2.map(((e3) => String.fromCharCode(e3)))), "0" !== t2[2] && 0 !== t2[2] || t2.pop(), t2.join(".");
}
function $e(e2) {
if ("string" == typeof e2) {
var [t2, i2, n2, s2, r2, a2] = e2.trim().split(/[-: ]/g).map(Number), o2 = new Date(t2, i2 - 1, n2);
return Number.isNaN(s2) || Number.isNaN(r2) || Number.isNaN(a2) || (o2.setHours(s2), o2.setMinutes(r2), o2.setSeconds(a2)), Number.isNaN(+o2) ? e2 : o2;
}
}
function Je(e2) {
if ("string" == typeof e2) return e2;
let t2 = [];
if (0 === e2[1] && 0 === e2[e2.length - 1]) for (let i2 = 0; i2 < e2.length; i2 += 2) t2.push(qe(e2[i2 + 1], e2[i2]));
else for (let i2 = 0; i2 < e2.length; i2 += 2) t2.push(qe(e2[i2], e2[i2 + 1]));
return S(String.fromCodePoint(...t2));
}
function qe(e2, t2) {
return e2 << 8 | t2;
}
We.set(41992, Xe), We.set(41993, Xe), We.set(41994, Xe), B(V, ["ifd0", "ifd1"], [[50827, function(e2) {
return "string" != typeof e2 ? P(e2) : e2;
}], [306, $e], [40091, Je], [40092, Je], [40093, Je], [40094, Je], [40095, Je]]), B(V, "exif", [[40960, Ye], [36864, Ye], [36867, $e], [36868, $e], [40962, _e], [40963, _e]]), B(V, "gps", [[0, (e2) => Array.from(e2).join(".")], [7, (e2) => Array.from(e2).join(":")]]);
const Qe = "http://ns.adobe.com/", Ze = "http://ns.adobe.com/xmp/extension/";
class et extends ge {
static canHandle(e2, t2) {
return 225 === e2.getUint8(t2 + 1) && 1752462448 === e2.getUint32(t2 + 4) && e2.getString(t2 + 4, Qe.length) === Qe;
}
static headerLength(e2, t2) {
return e2.getString(t2 + 4, Ze.length) === Ze ? 79 : 4 + "http://ns.adobe.com/xap/1.0/".length + 1;
}
static findPosition(e2, t2) {
let i2 = super.findPosition(e2, t2);
return i2.multiSegment = i2.extended = 79 === i2.headerLength, i2.multiSegment ? (i2.chunkCount = e2.getUint8(t2 + 72), i2.chunkNumber = e2.getUint8(t2 + 76), 0 !== e2.getUint8(t2 + 77) && i2.chunkNumber++) : (i2.chunkCount = 1 / 0, i2.chunkNumber = -1), i2;
}
static handleMultiSegments(e2) {
return e2.map(((e3) => e3.chunk.getString())).join("");
}
normalizeInput(e2) {
return "string" == typeof e2 ? e2 : I.from(e2).getString();
}
parse(e2 = this.chunk) {
if (!this.localOptions.parse) return e2;
e2 = (function(e3) {
let t3 = {}, i3 = {};
for (let e4 of ut) t3[e4] = [], i3[e4] = 0;
return e3.replace(ct, ((e4, n3, s2) => {
if ("<" === n3) {
let n4 = ++i3[s2];
return t3[s2].push(n4), `${e4}#${n4}`;
}
return `${e4}#${t3[s2].pop()}`;
}));
})(e2);
let t2 = nt.findAll(e2, "rdf", "Description");
0 === t2.length && t2.push(new nt("rdf", "Description", void 0, e2));
let i2, n2 = {};
for (let e3 of t2) for (let t3 of e3.properties) i2 = ot(t3.ns, n2), st(t3, i2);
return (function(e3) {
let t3;
for (let i3 in e3) t3 = e3[i3] = d(e3[i3]), void 0 === t3 && delete e3[i3];
return d(e3);
})(n2);
}
assignToOutput(e2, t2) {
if (this.localOptions.parse) for (let [i2, n2] of Object.entries(t2)) switch (i2) {
case "tiff":
this.assignObjectToOutput(e2, "ifd0", n2);
break;
case "exif":
this.assignObjectToOutput(e2, "exif", n2);
break;
case "xmlns":
break;
default:
this.assignObjectToOutput(e2, i2, n2);
}
else e2.xmp = t2;
}
}
f(et, "type", "xmp"), f(et, "multiSegment", true), A.set("xmp", et);
class tt {
static findAll(e2) {
return lt(e2, /([a-zA-Z0-9-]+):([a-zA-Z0-9-]+)=("[^"]*"|'[^']*')/gm).map(tt.unpackMatch);
}
static unpackMatch(e2) {
let t2 = e2[1], i2 = e2[2], n2 = e2[3].slice(1, -1);
return n2 = ht(n2), new tt(t2, i2, n2);
}
constructor(e2, t2, i2) {
this.ns = e2, this.name = t2, this.value = i2;
}
serialize() {
return this.value;
}
}
const it = "[\\w\\d-]+";
class nt {
static findAll(e2, t2, i2) {
if (void 0 !== t2 || void 0 !== i2) {
t2 = t2 || it, i2 = i2 || it;
var n2 = new RegExp(`<(${t2}):(${i2})(#\\d+)?((\\s+?[\\w\\d-:]+=("[^"]*"|'[^']*'))*\\s*)(\\/>|>([\\s\\S]*?)<\\/\\1:\\2\\3>)`, "gm");
} else n2 = /<([\w\d-]+):([\w\d-]+)(#\d+)?((\s+?[\w\d-:]+=("[^"]*"|'[^']*'))*\s*)(\/>|>([\s\S]*?)<\/\1:\2\3>)/gm;
return lt(e2, n2).map(nt.unpackMatch);
}
static unpackMatch(e2) {
let t2 = e2[1], i2 = e2[2], n2 = e2[4], s2 = e2[8];
return new nt(t2, i2, n2, s2);
}
constructor(e2, t2, i2, n2) {
this.ns = e2, this.name = t2, this.attrString = i2, this.innerXml = n2, this.attrs = tt.findAll(i2), this.children = nt.findAll(n2), this.value = 0 === this.children.length ? ht(n2) : void 0, this.properties = [...this.attrs, ...this.children];
}
get isPrimitive() {
return void 0 !== this.value && 0 === this.attrs.length && 0 === this.children.length;
}
get isListContainer() {
return 1 === this.children.length && this.children[0].isList;
}
get isList() {
let { ns: e2, name: t2 } = this;
return "rdf" === e2 && ("Seq" === t2 || "Bag" === t2 || "Alt" === t2);
}
get isListItem() {
return "rdf" === this.ns && "li" === this.name;
}
serialize() {
if (0 === this.properties.length && void 0 === this.value) return;
if (this.isPrimitive) return this.value;
if (this.isListContainer) return this.children[0].serialize();
if (this.isList) return at(this.children.map(rt));
if (this.isListItem && 1 === this.children.length && 0 === this.attrs.length) return this.children[0].serialize();
let e2 = {};
for (let t2 of this.properties) st(t2, e2);
return void 0 !== this.value && (e2.value = this.value), d(e2);
}
}
function st(e2, t2) {
let i2 = e2.serialize();
void 0 !== i2 && (t2[e2.name] = i2);
}
var rt = (e2) => e2.serialize(), at = (e2) => 1 === e2.length ? e2[0] : e2, ot = (e2, t2) => t2[e2] ? t2[e2] : t2[e2] = {};
function lt(e2, t2) {
let i2, n2 = [];
if (!e2) return n2;
for (; null !== (i2 = t2.exec(e2)); ) n2.push(i2);
return n2;
}
function ht(e2) {
if ((function(e3) {
return null == e3 || "null" === e3 || "undefined" === e3 || "" === e3 || "" === e3.trim();
})(e2)) return;
let t2 = Number(e2);
if (!Number.isNaN(t2)) return t2;
let i2 = e2.toLowerCase();
return "true" === i2 || "false" !== i2 && e2.trim();
}
const ut = ["rdf:li", "rdf:Seq", "rdf:Bag", "rdf:Alt", "rdf:Description"], ct = new RegExp(`(<|\\/)(${ut.join("|")})`, "g");
var ft = Object.freeze({ __proto__: null, default: Ge, Exifr: ce, fileParsers: T, segmentParsers: A, fileReaders: D, tagKeys: N, tagValues: G, tagRevivers: V, createDictionary: B, extendDictionary: E, fetchUrlAsArrayBuffer: L, readBlobAsArrayBuffer: U, chunkedProps: $, otherSegments: J, segments: q, tiffBlocks: Q, segmentsAndBlocks: Z, tiffExtractables: ee, inheritables: te, allFormatters: ie, Options: oe, parse: fe, gpsOnlyOptions: Ae, gps: De, thumbnailOnlyOptions: Oe, thumbnail: xe, thumbnailUrl: ve, orientationOnlyOptions: Me, orientation: Re, rotations: Le, get rotateCanvas() {
return e.rotateCanvas;
}, get rotateCss() {
return e.rotateCss;
}, rotation: Ue });
const dt = ["xmp", "icc", "iptc", "tiff"], pt = () => {
};
async function gt(e2, t2, i2) {
let n2 = i2[e2];
return n2.enabled = true, n2.parse = true, A.get(e2).parse(t2, n2);
}
let mt = h("fs", ((e2) => e2.promises));
D.set("fs", class extends Ne {
async readWhole() {
this.chunked = false, this.fs = await mt;
let e2 = await this.fs.readFile(this.input);
this._swapBuffer(e2);
}
async readChunked() {
this.chunked = true, this.fs = await mt, await this.open(), await this.readChunk(0, this.options.firstChunkSize);
}
async open() {
void 0 === this.fh && (this.fh = await this.fs.open(this.input, "r"), this.size = (await this.fh.stat(this.input)).size);
}
async _readChunk(e2, t2) {
void 0 === this.fh && await this.open(), e2 + t2 > this.size && (t2 = this.size - e2);
var i2 = this.subarray(e2, t2, true);
return await this.fh.read(i2.dataView, 0, t2, e2), i2;
}
async close() {
if (this.fh) {
let e2 = this.fh;
this.fh = void 0, await e2.close();
}
}
});
D.set("base64", class extends Ne {
constructor(...e2) {
super(...e2), this.input = this.input.replace(/^data:([^;]+);base64,/gim, ""), this.size = this.input.length / 4 * 3, this.input.endsWith("==") ? this.size -= 2 : this.input.endsWith("=") && (this.size -= 1);
}
async _readChunk(e2, t2) {
let i2, n2, s2 = this.input;
void 0 === e2 ? (e2 = 0, i2 = 0, n2 = 0) : (i2 = 4 * Math.floor(e2 / 3), n2 = e2 - i2 / 4 * 3), void 0 === t2 && (t2 = this.size);
let a2 = e2 + t2, l2 = i2 + 4 * Math.ceil(a2 / 3);
s2 = s2.slice(i2, l2);
let h2 = Math.min(t2, this.size - e2);
if (o) {
let t3 = r.from(s2, "base64").slice(n2, n2 + h2);
return this.set(t3, e2, true);
}
{
let t3 = this.subarray(e2, h2, true), i3 = atob(s2), r2 = t3.toUint8();
for (let e3 = 0; e3 < h2; e3++) r2[e3] = i3.charCodeAt(n2 + e3);
return t3;
}
}
});
class St extends pe {
static canHandle(e2, t2) {
return 18761 === t2 || 19789 === t2;
}
extendOptions(e2) {
let { ifd0: t2, xmp: i2, iptc: n2, icc: s2 } = e2;
i2.enabled && t2.deps.add(j), n2.enabled && t2.deps.add(W), s2.enabled && t2.deps.add(K), t2.finalizeFilters();
}
async parse() {
let { tiff: e2, xmp: t2, iptc: i2, icc: n2 } = this.options;
if (e2.enabled || t2.enabled || i2.enabled || n2.enabled) {
let e3 = Math.max(C(this.options), this.options.chunkSize);
await this.file.ensureChunk(0, e3), this.createParser("tiff", this.file), this.parsers.tiff.parseHeader(), await this.parsers.tiff.parseIfd0Block(), this.adaptTiffPropAsSegment("xmp"), this.adaptTiffPropAsSegment("iptc"), this.adaptTiffPropAsSegment("icc");
}
}
adaptTiffPropAsSegment(e2) {
if (this.parsers.tiff[e2]) {
let t2 = this.parsers.tiff[e2];
this.injectSegment(e2, t2);
}
}
}
f(St, "type", "tiff"), T.set("tiff", St);
let Ct = h("zlib");
const yt = "XML:com.adobe.xmp", bt = "ihdr", Pt = "iccp", It = "text", kt = "itxt", wt = [bt, Pt, It, kt, "exif"];
class Tt extends pe {
constructor(...e2) {
super(...e2), f(this, "catchError", ((e3) => this.errors.push(e3))), f(this, "metaChunks", []), f(this, "unknownChunks", []);
}
static canHandle(e2, t2) {
return 35152 === t2 && 2303741511 === e2.getUint32(0) && 218765834 === e2.getUint32(4);
}
async parse() {
let { file: e2 } = this;
await this.findPngChunksInRange("\x89PNG\r\n\n".length, e2.byteLength), await this.readSegments(this.metaChunks), this.findIhdr(), this.parseTextChunks(), await this.findExif().catch(this.catchError), await this.findXmp().catch(this.catchError), await this.findIcc().catch(this.catchError);
}
async findPngChunksInRange(e2, t2) {
let { file: i2 } = this;
for (; e2 < t2; ) {
let t3 = i2.getUint32(e2), n2 = i2.getUint32(e2 + 4), s2 = i2.getString(e2 + 4, 4).toLowerCase(), r2 = t3 + 4 + 4 + 4, a2 = { type: s2, offset: e2, length: r2, start: e2 + 4 + 4, size: t3, marker: n2 };
wt.includes(s2) ? this.metaChunks.push(a2) : this.unknownChunks.push(a2), e2 += r2;
}
}
parseTextChunks() {
let e2 = this.metaChunks.filter(((e3) => e3.type === It));
for (let t2 of e2) {
let [e3, i2] = this.file.getString(t2.start, t2.size).split("\0");
this.injectKeyValToIhdr(e3, i2);
}
}
injectKeyValToIhdr(e2, t2) {
let i2 = this.parsers.ihdr;
i2 && i2.raw.set(e2, t2);
}
findIhdr() {
let e2 = this.metaChunks.find(((e3) => e3.type === bt));
e2 && false !== this.options.ihdr.enabled && this.createParser(bt, e2.chunk);
}
async findExif() {
let e2 = this.metaChunks.find(((e3) => "exif" === e3.type));
e2 && this.injectSegment("tiff", e2.chunk);
}
async findXmp() {
let e2 = this.metaChunks.filter(((e3) => e3.type === kt));
for (let t2 of e2) {
t2.chunk.getString(0, yt.length) === yt && this.injectSegment("xmp", t2.chunk);
}
}
async findIcc() {
let e2 = this.metaChunks.find(((e3) => e3.type === Pt));
if (!e2) return;
let { chunk: t2 } = e2, i2 = t2.getUint8Array(0, 81), n2 = 0;
for (; n2 < 80 && 0 !== i2[n2]; ) n2++;
let r2 = n2 + 2, a2 = t2.getString(0, n2);
if (this.injectKeyValToIhdr("ProfileName", a2), s) {
let e3 = await Ct, i3 = t2.getUint8Array(r2);
i3 = e3.inflateSync(i3), this.injectSegment("icc", i3);
}
}
}
f(Tt, "type", "png"), T.set("png", Tt), B(N, "interop", [[1, "InteropIndex"], [2, "InteropVersion"], [4096, "RelatedImageFileFormat"], [4097, "RelatedImageWidth"], [4098, "RelatedImageHeight"]]), E(N, "ifd0", [[11, "ProcessingSoftware"], [254, "SubfileType"], [255, "OldSubfileType"], [263, "Thresholding"], [264, "CellWidth"], [265, "CellLength"], [266, "FillOrder"], [269, "DocumentName"], [280, "MinSampleValue"], [281, "MaxSampleValue"], [285, "PageName"], [286, "XPosition"], [287, "YPosition"], [290, "GrayResponseUnit"], [297, "PageNumber"], [321, "HalftoneHints"], [322, "TileWidth"], [323, "TileLength"], [332, "InkSet"], [337, "TargetPrinter"], [18246, "Rating"], [18249, "RatingPercent"], [33550, "PixelScale"], [34264, "ModelTransform"], [34377, "PhotoshopSettings"], [50706, "DNGVersion"], [50707, "DNGBackwardVersion"], [50708, "UniqueCameraModel"], [50709, "LocalizedCameraModel"], [50736, "DNGLensInfo"], [50739, "ShadowScale"], [50740, "DNGPrivateData"], [33920, "IntergraphMatrix"], [33922, "ModelTiePoint"], [34118, "SEMInfo"], [34735, "GeoTiffDirectory"], [34736, "GeoTiffDoubleParams"], [34737, "GeoTiffAsciiParams"], [50341, "PrintIM"], [50721, "ColorMatrix1"], [50722, "ColorMatrix2"], [50723, "CameraCalibration1"], [50724, "CameraCalibration2"], [50725, "ReductionMatrix1"], [50726, "ReductionMatrix2"], [50727, "AnalogBalance"], [50728, "AsShotNeutral"], [50729, "AsShotWhiteXY"], [50730, "BaselineExposure"], [50731, "BaselineNoise"], [50732, "BaselineSharpness"], [50734, "LinearResponseLimit"], [50735, "CameraSerialNumber"], [50741, "MakerNoteSafety"], [50778, "CalibrationIlluminant1"], [50779, "CalibrationIlluminant2"], [50781, "RawDataUniqueID"], [50827, "OriginalRawFileName"], [50828, "OriginalRawFileData"], [50831, "AsShotICCProfile"], [50832, "AsShotPreProfileMatrix"], [50833, "CurrentICCProfile"], [50834, "CurrentPreProfileMatrix"], [50879, "ColorimetricReference"], [50885, "SRawType"], [50898, "PanasonicTitle"], [50899, "PanasonicTitle2"], [50931, "CameraCalibrationSig"], [50932, "ProfileCalibrationSig"], [50933, "ProfileIFD"], [50934, "AsShotProfileName"], [50936, "ProfileName"], [50937, "ProfileHueSatMapDims"], [50938, "ProfileHueSatMapData1"], [50939, "ProfileHueSatMapData2"], [50940, "ProfileToneCurve"], [50941, "ProfileEmbedPolicy"], [50942, "ProfileCopyright"], [50964, "ForwardMatrix1"], [50965, "ForwardMatrix2"], [50966, "PreviewApplicationName"], [50967, "PreviewApplicationVersion"], [50968, "PreviewSettingsName"], [50969, "PreviewSettingsDigest"], [50970, "PreviewColorSpace"], [50971, "PreviewDateTime"], [50972, "RawImageDigest"], [50973, "OriginalRawFileDigest"], [50981, "ProfileLookTableDims"], [50982, "ProfileLookTableData"], [51043, "TimeCodes"], [51044, "FrameRate"], [51058, "TStop"], [51081, "ReelName"], [51089, "OriginalDefaultFinalSize"], [51090, "OriginalBestQualitySize"], [51091, "OriginalDefaultCropSize"], [51105, "CameraLabel"], [51107, "ProfileHueSatMapEncoding"], [51108, "ProfileLookTableEncoding"], [51109, "BaselineExposureOffset"], [51110, "DefaultBlackRender"], [51111, "NewRawImageDigest"], [51112, "RawToPreviewGain"]]);
let At = [[273, "StripOffsets"], [279, "StripByteCounts"], [288, "FreeOffsets"], [289, "FreeByteCounts"], [291, "GrayResponseCurve"], [292, "T4Options"], [293, "T6Options"], [300, "ColorResponseUnit"], [320, "ColorMap"], [324, "TileOffsets"], [325, "TileByteCounts"], [326, "BadFaxLines"], [327, "CleanFaxData"], [328, "ConsecutiveBadFaxLines"], [330, "SubIFD"], [333, "InkNames"], [334, "NumberofInks"], [336, "DotRange"], [338, "ExtraSamples"], [339, "SampleFormat"], [340, "SMinSampleValue"], [341, "SMaxSampleValue"], [342, "TransferRange"], [343, "ClipPath"], [344, "XClipPathUnits"], [345, "YClipPathUnits"], [346, "Indexed"], [347, "JPEGTables"], [351, "OPIProxy"], [400, "GlobalParametersIFD"], [401, "ProfileType"], [402, "FaxProfile"], [403, "CodingMethods"], [404, "VersionYear"], [405, "ModeNumber"], [433, "Decode"], [434, "DefaultImageColor"], [435, "T82Options"], [437, "JPEGTables"], [512, "JPEGProc"], [515, "JPEGRestartInterval"], [517, "JPEGLosslessPredictors"], [518, "JPEGPointTransforms"], [519, "JPEGQTables"], [520, "JPEGDCTables"], [521, "JPEGACTables"], [559, "StripRowCounts"], [999, "USPTOMiscellaneous"], [18247, "XP_DIP_XML"], [18248, "StitchInfo"], [28672, "SonyRawFileType"], [28688, "SonyToneCurve"], [28721, "VignettingCorrection"], [28722, "VignettingCorrParams"], [28724, "ChromaticAberrationCorrection"], [28725, "ChromaticAberrationCorrParams"], [28726, "DistortionCorrection"], [28727, "DistortionCorrParams"], [29895, "SonyCropTopLeft"], [29896, "SonyCropSize"], [32781, "ImageID"], [32931, "WangTag1"], [32932, "WangAnnotation"], [32933, "WangTag3"], [32934, "WangTag4"], [32953, "ImageReferencePoints"], [32954, "RegionXformTackPoint"], [32955, "WarpQuadrilateral"], [32956, "AffineTransformMat"], [32995, "Matteing"], [32996, "DataType"], [32997, "ImageDepth"], [32998, "TileDepth"], [33300, "ImageFullWidth"], [33301, "ImageFullHeight"], [33302, "TextureFormat"], [33303, "WrapModes"], [33304, "FovCot"], [33305, "MatrixWorldToScreen"], [33306, "MatrixWorldToCamera"], [33405, "Model2"], [33421, "CFARepeatPatternDim"], [33422, "CFAPattern2"], [33423, "BatteryLevel"], [33424, "KodakIFD"], [33445, "MDFileTag"], [33446, "MDScalePixel"], [33447, "MDColorTable"], [33448, "MDLabName"], [33449, "MDSampleInfo"], [33450, "MDPrepDate"], [33451, "MDPrepTime"], [33452, "MDFileUnits"], [33589, "AdventScale"], [33590, "AdventRevision"], [33628, "UIC1Tag"], [33629, "UIC2Tag"], [33630, "UIC3Tag"], [33631, "UIC4Tag"], [33918, "IntergraphPacketData"], [33919, "IntergraphFlagRegisters"], [33921, "INGRReserved"], [34016, "Site"], [34017, "ColorSequence"], [34018, "IT8Header"], [34019, "RasterPadding"], [34020, "BitsPerRunLength"], [34021, "BitsPerExtendedRunLength"], [34022, "ColorTable"], [34023, "ImageColorIndicator"], [34024, "BackgroundColorIndicator"], [34025, "ImageColorValue"], [34026, "BackgroundColorValue"], [34027, "PixelIntensityRange"], [34028, "TransparencyIndicator"], [34029, "ColorCharacterization"], [34030, "HCUsage"], [34031, "TrapIndicator"], [34032, "CMYKEquivalent"], [34152, "AFCP_IPTC"], [34232, "PixelMagicJBIGOptions"], [34263, "JPLCartoIFD"], [34306, "WB_GRGBLevels"], [34310, "LeafData"], [34687, "TIFF_FXExtensions"], [34688, "MultiProfiles"], [34689, "SharedData"], [34690, "T88Options"], [34732, "ImageLayer"], [34750, "JBIGOptions"], [34856, "Opto-ElectricConvFactor"], [34857, "Interlace"], [34908, "FaxRecvParams"], [34909, "FaxSubAddress"], [34910, "FaxRecvTime"], [34929, "FedexEDR"], [34954, "LeafSubIFD"], [37387, "FlashEnergy"], [37388, "SpatialFrequencyResponse"], [37389, "Noise"], [37390, "FocalPlaneXResolution"], [37391, "FocalPlaneYResolution"], [37392, "FocalPlaneResolutionUnit"], [37397, "ExposureIndex"], [37398, "TIFF-EPStandardID"], [37399, "SensingMethod"], [37434, "CIP3DataFile"], [37435, "CIP3Sheet"], [37436, "CIP3Side"], [37439, "StoNits"], [37679, "MSDocumentText"], [37680, "MSPropertySetStorage"], [37681, "MSDocumentTextPosition"], [37724, "ImageSourceData"], [40965, "InteropIFD"], [40976, "SamsungRawPointersOffset"], [40977, "SamsungRawPointersLength"], [41217, "SamsungRawByteOrder"], [41218, "SamsungRawUnknown"], [41484, "SpatialFrequencyResponse"], [41485, "Noise"], [41489, "ImageNumber"], [41490, "SecurityClassification"], [41491, "ImageHistory"], [41494, "TIFF-EPStandardID"], [41995, "DeviceSettingDescription"], [42112, "GDALMetadata"], [42113, "GDALNoData"], [44992, "ExpandSoftware"], [44993, "ExpandLens"], [44994, "ExpandFilm"], [44995, "ExpandFilterLens"], [44996, "ExpandScanner"], [44997, "ExpandFlashLamp"], [46275, "HasselbladRawImage"], [48129, "PixelFormat"], [48130, "Transformation"], [48131, "Uncompressed"], [48132, "ImageType"], [48256, "ImageWidth"], [48257, "ImageHeight"], [48258, "WidthResolution"], [48259, "HeightResolution"], [48320, "ImageOffset"], [48321, "ImageByteCount"], [48322, "AlphaOffset"], [48323, "AlphaByteCount"], [48324, "ImageDataDiscard"], [48325, "AlphaDataDiscard"], [50215, "OceScanjobDesc"], [50216, "OceApplicationSelector"], [50217, "OceIDNumber"], [50218, "OceImageLogic"], [50255, "Annotations"], [50459, "HasselbladExif"], [50547, "OriginalFileName"], [50560, "USPTOOriginalContentType"], [50656, "CR2CFAPattern"], [50710, "CFAPlaneColor"], [50711, "CFALayout"], [50712, "LinearizationTable"], [50713, "BlackLevelRepeatDim"], [50714, "BlackLevel"], [50715, "BlackLevelDeltaH"], [50716, "BlackLevelDeltaV"], [50717, "WhiteLevel"], [50718, "DefaultScale"], [50719, "DefaultCropOrigin"], [50720, "DefaultCropSize"], [50733, "BayerGreenSplit"], [50737, "ChromaBlurRadius"], [50738, "AntiAliasStrength"], [50752, "RawImageSegmentation"], [50780, "BestQualityScale"], [50784, "AliasLayerMetadata"], [50829, "ActiveArea"], [50830, "MaskedAreas"], [50935, "NoiseReductionApplied"], [50974, "SubTileBlockSize"], [50975, "RowInterleaveFactor"], [51008, "OpcodeList1"], [51009, "OpcodeList2"], [51022, "OpcodeList3"], [51041, "NoiseProfile"], [51114, "CacheVersion"], [51125, "DefaultUserCrop"], [51157, "NikonNEFInfo"], [65024, "KdcIFD"]];
E(N, "ifd0", At), E(N, "exif", At), B(G, "gps", [[23, { M: "Magnetic North", T: "True North" }], [25, { K: "Kilometers", M: "Miles", N: "Nautical Miles" }]]);
class Dt extends ge {
static canHandle(e2, t2) {
return 224 === e2.getUint8(t2 + 1) && 1246120262 === e2.getUint32(t2 + 4) && 0 === e2.getUint8(t2 + 8);
}
parse() {
return this.parseTags(), this.translate(), this.output;
}
parseTags() {
this.raw = /* @__PURE__ */ new Map([[0, this.chunk.getUint16(0)], [2, this.chunk.getUint8(2)], [3, this.chunk.getUint16(3)], [5, this.chunk.getUint16(5)], [7, this.chunk.getUint8(7)], [8, this.chunk.getUint8(8)]]);
}
}
f(Dt, "type", "jfif"), f(Dt, "headerLength", 9), A.set("jfif", Dt), B(N, "jfif", [[0, "JFIFVersion"], [2, "ResolutionUnit"], [3, "XResolution"], [5, "YResolution"], [7, "ThumbnailWidth"], [8, "ThumbnailHeight"]]);
class Ot extends ge {
parse() {
return this.parseTags(), this.translate(), this.output;
}
parseTags() {
this.raw = new Map([[0, this.chunk.getUint32(0)], [4, this.chunk.getUint32(4)], [8, this.chunk.getUint8(8)], [9, this.chunk.getUint8(9)], [10, this.chunk.getUint8(10)], [11, this.chunk.getUint8(11)], [12, this.chunk.getUint8(12)], ...Array.from(this.raw)]);
}
}
f(Ot, "type", "ihdr"), A.set("ihdr", Ot), B(N, "ihdr", [[0, "ImageWidth"], [4, "ImageHeight"], [8, "BitDepth"], [9, "ColorType"], [10, "Compression"], [11, "Filter"], [12, "Interlace"]]), B(G, "ihdr", [[9, { 0: "Grayscale", 2: "RGB", 3: "Palette", 4: "Grayscale with Alpha", 6: "RGB with Alpha", DEFAULT: "Unknown" }], [10, { 0: "Deflate/Inflate", DEFAULT: "Unknown" }], [11, { 0: "Adaptive", DEFAULT: "Unknown" }], [12, { 0: "Noninterlaced", 1: "Adam7 Interlace", DEFAULT: "Unknown" }]]);
const xt = "\0\0\0\0";
class vt extends ge {
static canHandle(e2, t2) {
return 226 === e2.getUint8(t2 + 1) && 1229144927 === e2.getUint32(t2 + 4);
}
static findPosition(e2, t2) {
let i2 = super.findPosition(e2, t2);
return i2.chunkNumber = e2.getUint8(t2 + 16), i2.chunkCount = e2.getUint8(t2 + 17), i2.multiSegment = i2.chunkCount > 1, i2;
}
static handleMultiSegments(e2) {
return (function(e3) {
let t2 = (function(e4) {
let t3 = e4[0].constructor, i2 = 0;
for (let t4 of e4) i2 += t4.length;
let n2 = new t3(i2), s2 = 0;
for (let t4 of e4) n2.set(t4, s2), s2 += t4.length;
return n2;
})(e3.map(((e4) => e4.chunk.toUint8())));
return new I(t2);
})(e2);
}
parse() {
return this.raw = /* @__PURE__ */ new Map(), this.parseHeader(), this.parseTags(), this.translate(), this.output;
}
parseHeader() {
let { raw: e2 } = this;
this.chunk.byteLength < 84 && m("ICC header is too short");
for (let [t2, i2] of Object.entries(Mt)) {
t2 = parseInt(t2, 10);
let n2 = i2(this.chunk, t2);
n2 !== xt && e2.set(t2, n2);
}
}
parseTags() {
let e2, t2, i2, n2, s2, { raw: r2 } = this, a2 = this.chunk.getUint32(128), o2 = 132, l2 = this.chunk.byteLength;
for (; a2--; ) {
if (e2 = this.chunk.getString(o2, 4), t2 = this.chunk.getUint32(o2 + 4), i2 = this.chunk.getUint32(o2 + 8), n2 = this.chunk.getString(t2, 4), t2 + i2 > l2) return void console.warn("reached the end of the first ICC chunk. Enable options.tiff.multiSegment to read all ICC segments.");
s2 = this.parseTag(n2, t2, i2), void 0 !== s2 && s2 !== xt && r2.set(e2, s2), o2 += 12;
}
}
parseTag(e2, t2, i2) {
switch (e2) {
case "desc":
return this.parseDesc(t2);
case "mluc":
return this.parseMluc(t2);
case "text":
return this.parseText(t2, i2);
case "sig ":
return this.parseSig(t2);
}
if (!(t2 + i2 > this.chunk.byteLength)) return this.chunk.getUint8Array(t2, i2);
}
parseDesc(e2) {
let t2 = this.chunk.getUint32(e2 + 8) - 1;
return S(this.chunk.getString(e2 + 12, t2));
}
parseText(e2, t2) {
return S(this.chunk.getString(e2 + 8, t2 - 8));
}
parseSig(e2) {
return S(this.chunk.getString(e2 + 8, 4));
}
parseMluc(e2) {
let { chunk: t2 } = this, i2 = t2.getUint32(e2 + 8), n2 = t2.getUint32(e2 + 12), s2 = e2 + 16, r2 = [];
for (let a2 = 0; a2 < i2; a2++) {
let i3 = t2.getString(s2 + 0, 2), a3 = t2.getString(s2 + 2, 2), o2 = t2.getUint32(s2 + 4), l2 = t2.getUint32(s2 + 8) + e2, h2 = S(t2.getUnicodeString(l2, o2));
r2.push({ lang: i3, country: a3, text: h2 }), s2 += n2;
}
return 1 === i2 ? r2[0].text : r2;
}
translateValue(e2, t2) {
return "string" == typeof e2 ? t2[e2] || t2[e2.toLowerCase()] || e2 : t2[e2] || e2;
}
}
f(vt, "type", "icc"), f(vt, "multiSegment", true), f(vt, "headerLength", 18);
const Mt = { 4: Rt, 8: function(e2, t2) {
return [e2.getUint8(t2), e2.getUint8(t2 + 1) >> 4, e2.getUint8(t2 + 1) % 16].map(((e3) => e3.toString(10))).join(".");
}, 12: Rt, 16: Rt, 20: Rt, 24: function(e2, t2) {
const i2 = e2.getUint16(t2), n2 = e2.getUint16(t2 + 2) - 1, s2 = e2.getUint16(t2 + 4), r2 = e2.getUint16(t2 + 6), a2 = e2.getUint16(t2 + 8), o2 = e2.getUint16(t2 + 10);
return new Date(Date.UTC(i2, n2, s2, r2, a2, o2));
}, 36: Rt, 40: Rt, 48: Rt, 52: Rt, 64: (e2, t2) => e2.getUint32(t2), 80: Rt };
function Rt(e2, t2) {
return S(e2.getString(t2, 4));
}
A.set("icc", vt), B(N, "icc", [[4, "ProfileCMMType"], [8, "ProfileVersion"], [12, "ProfileClass"], [16, "ColorSpaceData"], [20, "ProfileConnectionSpace"], [24, "ProfileDateTime"], [36, "ProfileFileSignature"], [40, "PrimaryPlatform"], [44, "CMMFlags"], [48, "DeviceManufacturer"], [52, "DeviceModel"], [56, "DeviceAttributes"], [64, "RenderingIntent"], [68, "ConnectionSpaceIlluminant"], [80, "ProfileCreator"], [84, "ProfileID"], ["Header", "ProfileHeader"], ["MS00", "WCSProfiles"], ["bTRC", "BlueTRC"], ["bXYZ", "BlueMatrixColumn"], ["bfd", "UCRBG"], ["bkpt", "MediaBlackPoint"], ["calt", "CalibrationDateTime"], ["chad", "ChromaticAdaptation"], ["chrm", "Chromaticity"], ["ciis", "ColorimetricIntentImageState"], ["clot", "ColorantTableOut"], ["clro", "ColorantOrder"], ["clrt", "ColorantTable"], ["cprt", "ProfileCopyright"], ["crdi", "CRDInfo"], ["desc", "ProfileDescription"], ["devs", "DeviceSettings"], ["dmdd", "DeviceModelDesc"], ["dmnd", "DeviceMfgDesc"], ["dscm", "ProfileDescriptionML"], ["fpce", "FocalPlaneColorimetryEstimates"], ["gTRC", "GreenTRC"], ["gXYZ", "GreenMatrixColumn"], ["gamt", "Gamut"], ["kTRC", "GrayTRC"], ["lumi", "Luminance"], ["meas", "Measurement"], ["meta", "Metadata"], ["mmod", "MakeAndModel"], ["ncl2", "NamedColor2"], ["ncol", "NamedColor"], ["ndin", "NativeDisplayInfo"], ["pre0", "Preview0"], ["pre1", "Preview1"], ["pre2", "Preview2"], ["ps2i", "PS2RenderingIntent"], ["ps2s", "PostScript2CSA"], ["psd0", "PostScript2CRD0"], ["psd1", "PostScript2CRD1"], ["psd2", "PostScript2CRD2"], ["psd3", "PostScript2CRD3"], ["pseq", "ProfileSequenceDesc"], ["psid", "ProfileSequenceIdentifier"], ["psvm", "PS2CRDVMSize"], ["rTRC", "RedTRC"], ["rXYZ", "RedMatrixColumn"], ["resp", "OutputResponse"], ["rhoc", "ReflectionHardcopyOrigColorimetry"], ["rig0", "PerceptualRenderingIntentGamut"], ["rig2", "SaturationRenderingIntentGamut"], ["rpoc", "ReflectionPrintOutputColorimetry"], ["sape", "SceneAppearanceEstimates"], ["scoe", "SceneColorimetryEstimates"], ["scrd", "ScreeningDesc"], ["scrn", "Screening"], ["targ", "CharTarget"], ["tech", "Technology"], ["vcgt", "VideoCardGamma"], ["view", "ViewingConditions"], ["vued", "ViewingCondDesc"], ["wtpt", "MediaWhitePoint"]]);
const Lt = { "4d2p": "Erdt Systems", AAMA: "Aamazing Technologies", ACER: "Acer", ACLT: "Acolyte Color Research", ACTI: "Actix Sytems", ADAR: "Adara Technology", ADBE: "Adobe", ADI: "ADI Systems", AGFA: "Agfa Graphics", ALMD: "Alps Electric", ALPS: "Alps Electric", ALWN: "Alwan Color Expertise", AMTI: "Amiable Technologies", AOC: "AOC International", APAG: "Apago", APPL: "Apple Computer", AST: "AST", "AT&T": "AT&T", BAEL: "BARBIERI electronic", BRCO: "Barco NV", BRKP: "Breakpoint", BROT: "Brother", BULL: "Bull", BUS: "Bus Computer Systems", "C-IT": "C-Itoh", CAMR: "Intel", CANO: "Canon", CARR: "Carroll Touch", CASI: "Casio", CBUS: "Colorbus PL", CEL: "Crossfield", CELx: "Crossfield", CGS: "CGS Publishing Technologies International", CHM: "Rochester Robotics", CIGL: "Colour Imaging Group, London", CITI: "Citizen", CL00: "Candela", CLIQ: "Color IQ", CMCO: "Chromaco", CMiX: "CHROMiX", COLO: "Colorgraphic Communications", COMP: "Compaq", COMp: "Compeq/Focus Technology", CONR: "Conrac Display Products", CORD: "Cordata Technologies", CPQ: "Compaq", CPRO: "ColorPro", CRN: "Cornerstone", CTX: "CTX International", CVIS: "ColorVision", CWC: "Fujitsu Laboratories", DARI: "Darius Technology", DATA: "Dataproducts", DCP: "Dry Creek Photo", DCRC: "Digital Contents Resource Center, Chung-Ang University", DELL: "Dell Computer", DIC: "Dainippon Ink and Chemicals", DICO: "Diconix", DIGI: "Digital", "DL&C": "Digital Light & Color", DPLG: "Doppelganger", DS: "Dainippon Screen", DSOL: "DOOSOL", DUPN: "DuPont", EPSO: "Epson", ESKO: "Esko-Graphics", ETRI: "Electronics and Telecommunications Research Institute", EVER: "Everex Systems", EXAC: "ExactCODE", Eizo: "Eizo", FALC: "Falco Data Products", FF: "Fuji Photo Film", FFEI: "FujiFilm Electronic Imaging", FNRD: "Fnord Software", FORA: "Fora", FORE: "Forefront Technology", FP: "Fujitsu", FPA: "WayTech Development", FUJI: "Fujitsu", FX: "Fuji Xerox", GCC: "GCC Technologies", GGSL: "Global Graphics Software", GMB: "Gretagmacbeth", GMG: "GMG", GOLD: "GoldStar Technology", GOOG: "Google", GPRT: "Giantprint", GTMB: "Gretagmacbeth", GVC: "WayTech Development", GW2K: "Sony", HCI: "HCI", HDM: "Heidelberger Druckmaschinen", HERM: "Hermes", HITA: "Hitachi America", HP: "Hewlett-Packard", HTC: "Hitachi", HiTi: "HiTi Digital", IBM: "IBM", IDNT: "Scitex", IEC: "Hewlett-Packard", IIYA: "Iiyama North America", IKEG: "Ikegami Electronics", IMAG: "Image Systems", IMI: "Ingram Micro", INTC: "Intel", INTL: "N/A (INTL)", INTR: "Intra Electronics", IOCO: "Iocomm International Technology", IPS: "InfoPrint Solutions Company", IRIS: "Scitex", ISL: "Ichikawa Soft Laboratory", ITNL: "N/A (ITNL)", IVM: "IVM", IWAT: "Iwatsu Electric", Idnt: "Scitex", Inca: "Inca Digital Printers", Iris: "Scitex", JPEG: "Joint Photographic Experts Group", JSFT: "Jetsoft Development", JVC: "JVC Information Products", KART: "Scitex", KFC: "KFC Computek Components", KLH: "KLH Computers", KMHD: "Konica Minolta", KNCA: "Konica", KODA: "Kodak", KYOC: "Kyocera", Kart: "Scitex", LCAG: "Leica", LCCD: "Leeds Colour", LDAK: "Left Dakota", LEAD: "Leading Technology", LEXM: "Lexmark International", LINK: "Link Computer", LINO: "Linotronic", LITE: "Lite-On", Leaf: "Leaf", Lino: "Linotronic", MAGC: "Mag Computronic", MAGI: "MAG Innovision", MANN: "Mannesmann", MICN: "Micron Technology", MICR: "Microtek", MICV: "Microvitec", MINO: "Minolta", MITS: "Mitsubishi Electronics America", MITs: "Mitsuba", MNLT: "Minolta", MODG: "Modgraph", MONI: "Monitronix", MONS: "Monaco Systems", MORS: "Morse Technology", MOTI: "Motive Systems", MSFT: "Microsoft", MUTO: "MUTOH INDUSTRIES", Mits: "Mitsubishi Electric", NANA: "NANAO", NEC: "NEC", NEXP: "NexPress Solutions", NISS: "Nissei Sangyo America", NKON: "Nikon", NONE: "none", OCE: "Oce Technologies", OCEC: "OceColor", OKI: "Oki", OKID: "Okidata", OKIP: "Okidata", OLIV: "Olivetti", OLYM: "Olympus", ONYX: "Onyx Graphics", OPTI: "Optiquest", PACK: "Packard Bell", PANA: "Matsushita Electric Industrial", PANT: "Pantone", PBN: "Packard Bell", PFU: "PFU", PHIL: "Philips Consumer Electronics", PNTX: "HOYA", POne: "Phase One A/S", PREM: "Premier Computer Innovations", PRIN: "Princeton Graphic Systems", PRIP: "Princeton Publishing Labs", QLUX: "Hong Kong", QMS: "QMS", QPCD: "QPcard AB", QUAD: "QuadLaser", QUME: "Qume", RADI: "Radius", RDDx: "Integrated Color Solutions", RDG: "Roland DG", REDM: "REDMS Group", RELI: "Relisys", RGMS: "Rolf Gierling Multitools", RICO: "Ricoh", RNLD: "Edmund Ronald", ROYA: "Royal", RPC: "Ricoh Printing Systems", RTL: "Royal Information Electronics", SAMP: "Sampo", SAMS: "Samsung", SANT: "Jaime Santana Pomares", SCIT: "Scitex", SCRN: "Dainippon Screen", SDP: "Scitex", SEC: "Samsung", SEIK: "Seiko Instruments", SEIk: "Seikosha", SGUY: "ScanGuy.com", SHAR: "Sharp Laboratories", SICC: "International Color Consortium", SONY: "Sony", SPCL: "SpectraCal", STAR: "Star", STC: "Sampo Technology", Scit: "Scitex", Sdp: "Scitex", Sony: "Sony", TALO: "Talon Technology", TAND: "Tandy", TATU: "Tatung", TAXA: "TAXAN America", TDS: "Tokyo Denshi Sekei", TECO: "TECO Information Systems", TEGR: "Tegra", TEKT: "Tektronix", TI: "Texas Instruments", TMKR: "TypeMaker", TOSB: "Toshiba", TOSH: "Toshiba", TOTK: "TOTOKU ELECTRIC", TRIU: "Triumph", TSBT: "Toshiba", TTX: "TTX Computer Products", TVM: "TVM Professional Monitor", TW: "TW Casper", ULSX: "Ulead Systems", UNIS: "Unisys", UTZF: "Utz Fehlau & Sohn", VARI: "Varityper", VIEW: "Viewsonic", VISL: "Visual communication", VIVO: "Vivo Mobile Communication", WANG: "Wang", WLBR: "Wilbur Imaging", WTG2: "Ware To Go", WYSE: "WYSE Technology", XERX: "Xerox", XRIT: "X-Rite", ZRAN: "Zoran", Zebr: "Zebra Technologies", appl: "Apple Computer", bICC: "basICColor", berg: "bergdesign", ceyd: "Integrated Color Solutions", clsp: "MacDermid ColorSpan", ds: "Dainippon Screen", dupn: "DuPont", ffei: "FujiFilm Electronic Imaging", flux: "FluxData", iris: "Scitex", kart: "Scitex", lcms: "Little CMS", lino: "Linotronic", none: "none", ob4d: "Erdt Systems", obic: "Medigraph", quby: "Qubyx Sarl", scit: "Scitex", scrn: "Dainippon Screen", sdp: "Scitex", siwi: "SIWI GRAFIKA", yxym: "YxyMaster" }, Ut = { scnr: "Scanner", mntr: "Monitor", prtr: "Printer", link: "Device Link", abst: "Abstract", spac: "Color Space Conversion Profile", nmcl: "Named Color", cenc: "ColorEncodingSpace profile", mid: "MultiplexIdentification profile", mlnk: "MultiplexLink profile", mvis: "MultiplexVisualization profile", nkpf: "Nikon Input Device Profile (NON-STANDARD!)" };
B(G, "icc", [[4, Lt], [12, Ut], [40, Object.assign({}, Lt, Ut)], [48, Lt], [80, Lt], [64, { 0: "Perceptual", 1: "Relative Colorimetric", 2: "Saturation", 3: "Absolute Colorimetric" }], ["tech", { amd: "Active Matrix Display", crt: "Cathode Ray Tube Display", kpcd: "Photo CD", pmd: "Passive Matrix Display", dcam: "Digital Camera", dcpj: "Digital Cinema Projector", dmpc: "Digital Motion Picture Camera", dsub: "Dye Sublimation Printer", epho: "Electrophotographic Printer", esta: "Electrostatic Printer", flex: "Flexography", fprn: "Film Writer", fscn: "Film Scanner", grav: "Gravure", ijet: "Ink Jet Printer", imgs: "Photo Image Setter", mpfr: "Motion Picture Film Recorder", mpfs: "Motion Picture Film Scanner", offs: "Offset Lithography", pjtv: "Projection Television", rpho: "Photographic Paper Printer", rscn: "Reflective Scanner", silk: "Silkscreen", twax: "Thermal Wax Printer", vidc: "Video Camera", vidm: "Video Monitor" }]]);
class Ft extends ge {
static canHandle(e2, t2, i2) {
return 237 === e2.getUint8(t2 + 1) && "Photoshop" === e2.getString(t2 + 4, 9) && void 0 !== this.containsIptc8bim(e2, t2, i2);
}
static headerLength(e2, t2, i2) {
let n2, s2 = this.containsIptc8bim(e2, t2, i2);
if (void 0 !== s2) return n2 = e2.getUint8(t2 + s2 + 7), n2 % 2 != 0 && (n2 += 1), 0 === n2 && (n2 = 4), s2 + 8 + n2;
}
static containsIptc8bim(e2, t2, i2) {
for (let n2 = 0; n2 < i2; n2++) if (this.isIptcSegmentHead(e2, t2 + n2)) return n2;
}
static isIptcSegmentHead(e2, t2) {
return 56 === e2.getUint8(t2) && 943868237 === e2.getUint32(t2) && 1028 === e2.getUint16(t2 + 4);
}
parse() {
let { raw: e2 } = this, t2 = this.chunk.byteLength - 1, i2 = false;
for (let n2 = 0; n2 < t2; n2++) if (28 === this.chunk.getUint8(n2) && 2 === this.chunk.getUint8(n2 + 1)) {
i2 = true;
let t3 = this.chunk.getUint16(n2 + 3), s2 = this.chunk.getUint8(n2 + 2), r2 = this.chunk.getLatin1String(n2 + 5, t3);
e2.set(s2, this.pluralizeValue(e2.get(s2), r2)), n2 += 4 + t3;
} else if (i2) break;
return this.translate(), this.output;
}
pluralizeValue(e2, t2) {
return void 0 !== e2 ? e2 instanceof Array ? (e2.push(t2), e2) : [e2, t2] : t2;
}
}
f(Ft, "type", "iptc"), f(Ft, "translateValues", false), f(Ft, "reviveValues", false), A.set("iptc", Ft), B(N, "iptc", [[0, "ApplicationRecordVersion"], [3, "ObjectTypeReference"], [4, "ObjectAttributeReference"], [5, "ObjectName"], [7, "EditStatus"], [8, "EditorialUpdate"], [10, "Urgency"], [12, "SubjectReference"], [15, "Category"], [20, "SupplementalCategories"], [22, "FixtureIdentifier"], [25, "Keywords"], [26, "ContentLocationCode"], [27, "ContentLocationName"], [30, "ReleaseDate"], [35, "ReleaseTime"], [37, "ExpirationDate"], [38, "ExpirationTime"], [40, "SpecialInstructions"], [42, "ActionAdvised"], [45, "ReferenceService"], [47, "ReferenceDate"], [50, "ReferenceNumber"], [55, "DateCreated"], [60, "TimeCreated"], [62, "DigitalCreationDate"], [63, "DigitalCreationTime"], [65, "OriginatingProgram"], [70, "ProgramVersion"], [75, "ObjectCycle"], [80, "Byline"], [85, "BylineTitle"], [90, "City"], [92, "Sublocation"], [95, "State"], [100, "CountryCode"], [101, "Country"], [103, "OriginalTransmissionReference"], [105, "Headline"], [110, "Credit"], [115, "Source"], [116, "CopyrightNotice"], [118, "Contact"], [120, "Caption"], [121, "LocalCaption"], [122, "Writer"], [125, "RasterizedCaption"], [130, "ImageType"], [131, "ImageOrientation"], [135, "LanguageIdentifier"], [150, "AudioType"], [151, "AudioSamplingRate"], [152, "AudioSamplingResolution"], [153, "AudioDuration"], [154, "AudioOutcue"], [184, "JobID"], [185, "MasterDocumentID"], [186, "ShortDocumentID"], [187, "UniqueDocumentID"], [188, "OwnerID"], [200, "ObjectPreviewFileFormat"], [201, "ObjectPreviewFileVersion"], [202, "ObjectPreviewData"], [221, "Prefs"], [225, "ClassifyState"], [228, "SimilarityIndex"], [230, "DocumentNotes"], [231, "DocumentHistory"], [232, "ExifCameraInfo"], [255, "CatalogSets"]]), B(G, "iptc", [[10, { 0: "0 (reserved)", 1: "1 (most urgent)", 2: "2", 3: "3", 4: "4", 5: "5 (normal urgency)", 6: "6", 7: "7", 8: "8 (least urgent)", 9: "9 (user-defined priority)" }], [75, { a: "Morning", b: "Both Morning and Evening", p: "Evening" }], [131, { L: "Landscape", P: "Portrait", S: "Square" }]]), e.Exifr = ce, e.Options = oe, e.allFormatters = ie, e.chunkedProps = $, e.createDictionary = B, e.default = ft, e.extendDictionary = E, e.fetchUrlAsArrayBuffer = L, e.fileParsers = T, e.fileReaders = D, e.gps = De, e.gpsOnlyOptions = Ae, e.inheritables = te, e.orientation = Re, e.orientationOnlyOptions = Me, e.otherSegments = J, e.parse = fe, e.readBlobAsArrayBuffer = U, e.rotation = Ue, e.rotations = Le, e.segmentParsers = A, e.segments = q, e.segmentsAndBlocks = Z, e.sidecar = async function(e2, t2, i2) {
let n2 = new oe(t2);
n2.chunked = false, void 0 === i2 && "string" == typeof e2 && (i2 = (function(e3) {
let t3 = e3.toLowerCase().split(".").pop();
if (/* @__PURE__ */ (function(e4) {
return "exif" === e4 || "tiff" === e4 || "tif" === e4;
})(t3)) return "tiff";
if (dt.includes(t3)) return t3;
})(e2));
let s2 = await x(e2, n2);
if (i2) {
if (dt.includes(i2)) return gt(i2, s2, n2);
m("Invalid segment type");
} else {
if ((function(e3) {
let t3 = e3.getString(0, 50).trim();
return t3.includes("<?xpacket") || t3.includes("<x:");
})(s2)) return gt("xmp", s2, n2);
for (let [e3] of A) {
if (!dt.includes(e3)) continue;
let t3 = await gt(e3, s2, n2).catch(pt);
if (t3) return t3;
}
m("Unknown file format");
}
}, e.tagKeys = N, e.tagRevivers = V, e.tagValues = G, e.thumbnail = xe, e.thumbnailOnlyOptions = Oe, e.thumbnailUrl = ve, e.tiffBlocks = Q, e.tiffExtractables = ee, Object.defineProperty(e, "__esModule", { value: true });
}));
}
});
function getModuleDir() {
try {
return __dirname;
} catch {
return process.cwd();
}
}
function readJsonFile(filePath) {
try {
const raw = fs$1.readFileSync(filePath, "utf8");
return JSON.parse(raw);
} catch {
return null;
}
}
function findUpwards(startDir, fileName, maxDepth = 8) {
let dir = startDir;
for (let i = 0; i < maxDepth; i++) {
const candidate = path$1.join(dir, fileName);
if (fs$1.existsSync(candidate)) return candidate;
const parent = path$1.dirname(dir);
if (parent === dir) break;
dir = parent;
}
return null;
}
function getPluginMeta() {
const moduleDir = getModuleDir();
const packageJsonPath = findUpwards(moduleDir, "package.json");
const manifestJsonPath = findUpwards(moduleDir, "manifest.json");
const packageJson = packageJsonPath ? readJsonFile(packageJsonPath) : null;
const manifestJson = manifestJsonPath ? readJsonFile(manifestJsonPath) : null;
const version = String(packageJson?.version || "unknown");
const owner = String(manifestJson?.owner || "").trim();
const name = String(manifestJson?.name || "").trim();
const revisionsUrl = owner && name ? `https://raw.githubusercontent.com/${owner}/${name}-docs/main/docs/CHANGELOG.md` : "https://raw.githubusercontent.com";
const pluginIdentifier = owner && name ? `${owner}/${name}` : name ? name : "unknown";
return { version, owner, name, revisionsUrl, pluginIdentifier };
}
function formatToolMetaBlock(meta = getPluginMeta()) {
return `Plugin-Identifier: ${meta.pluginIdentifier}
Plugin version: ${meta.version}`;
}
// src/capabilities.ts
var cachedSelfPluginIdentifier;
function getSelfPluginIdentifier() {
if (cachedSelfPluginIdentifier !== void 0)
return cachedSelfPluginIdentifier;
try {
const pluginIdentifier = getPluginMeta().pluginIdentifier;
if (pluginIdentifier && pluginIdentifier !== "unknown") {
cachedSelfPluginIdentifier = pluginIdentifier;
return cachedSelfPluginIdentifier;
}
} catch (err) {
console.warn(
"[Capabilities] Failed to resolve pluginIdentifier from manifest.json.",
err
);
cachedSelfPluginIdentifier = null;
return cachedSelfPluginIdentifier;
}
console.warn(
"[Capabilities] manifest.json did not contain valid owner/name."
);
cachedSelfPluginIdentifier = null;
return cachedSelfPluginIdentifier;
}
sdk.createConfigSchematics().field(
"model",
"string",
{
displayName: "Agent Model",
subtitle: "Enter the vision model to use as orchestrator. Default: Qwen3.6 35B \u0410\u0417\u0412.",
placeholder: "qwen/qwen3.6-35b-a3b"
},
"qwen/qwen3.6-35b-a3b"
).field(
"visionPromotionPersistent",
"boolean",
{
displayName: "Vision Promotion: Persistent",
subtitle: "ON: promote up to 5 attachments + 4 variants every turn. OFF: promote only when new.",
engineDoesNotSupport: true
},
false
).field(
"logRequests",
"boolean",
{
displayName: "Debug: Log requests/response",
subtitle: "Logs full request/response JSON; may include sensitive data.",
engineDoesNotSupport: true
},
false
).field(
"debugPromotion",
"boolean",
{
displayName: "Debug: Media promotion",
subtitle: "Verbose logs for media state, previews and cleanup.",
engineDoesNotSupport: true
},
false
).field(
"debugChunks",
"boolean",
{
displayName: "Debug: Stream chunk logs",
subtitle: "Log raw streaming chunks to console (verbose).",
engineDoesNotSupport: true
},
false
).build();
sdk.createConfigSchematics().field(
"baseUrl",
"string",
{
displayName: "LM Studio API base-URL",
subtitle: "Local LM Studio server base-URL. Default: http://127.0.0.1:1234/v1",
placeholder: "http://127.0.0.1:1234/v1"
},
"http://127.0.0.1:1234/v1"
).field(
"apiKey",
"string",
{
displayName: "(Optional) API Key",
subtitle: "Only needed if your LM Studio server requires authentication.",
isProtected: true,
placeholder: "sk-..."
},
""
).field(
"PREVIEW_IN_CHAT",
"boolean",
{
displayName: "Simple Previews in Chat",
subtitle: "When enabled, tool responses include client-based image previews. Not recommended for advanced functionality.",
engineDoesNotSupport: false
},
false
).field(
"unloadAgentModelDuringRender",
"boolean",
{
displayName: "Unload Agent Model During Render",
subtitle: "When enabled, unloads the agent model from VRAM before long renders (image2image, edit, text2video, image2video). Only applies to local LM Studio instances.",
engineDoesNotSupport: false
},
true
).field(
"DRAW_THINGS_HOST",
"string",
{
displayName: "Draw Things Host",
subtitle: "Hostname or IP of the Draw Things backend server.",
placeholder: "127.0.0.1"
},
"127.0.0.1"
).field(
"DRAW_THINGS_HTTP_PORT",
"numeric",
{
displayName: "Draw Things HTTP Port",
subtitle: "HTTP API port (default: 7860)."
},
7860
).field(
"DRAW_THINGS_GRPC_PORT",
"numeric",
{
displayName: "Draw Things gRPC Port",
subtitle: "gRPC port (default: 7859)."
},
7859
).field(
"embedPngMetadata",
"boolean",
{
displayName: "Embed Metadata in PNGs",
subtitle: "Write generation parameters (prompt, model, seed, LoRAs, sources) into saved PNGs as XMP metadata. Compatible with draw-things-index and find-image.",
engineDoesNotSupport: false
},
true
).field(
"customConfigsPath",
"string",
{
displayName: "Custom Configs Path",
subtitle: "Path to custom_configs.json from Draw Things. Change to: `none` to disable.",
placeholder: "~/Library/Containers/com.liuliu.draw-things/Data/Documents/Models/custom_configs.json",
engineDoesNotSupport: false
},
"~/Library/Containers/com.liuliu.draw-things/Data/Documents/Models/custom_configs.json"
).field(
"HTTP_SERVER_PORT",
"numeric",
{
displayName: "Local HTTP Server Port",
subtitle: "Port for serving generated images over localhost (default: 54760).",
engineDoesNotSupport: true
},
54760
).build();
// src/helpers/projectUriResolver.ts
__toESM(require_flatbuffers());
// src/interfaces/thumbnail-history-node.ts
__toESM(require_flatbuffers());
function localTimestamp(d = /* @__PURE__ */ new Date()) {
const pad = (n, len = 2) => String(n).padStart(len, "0");
const year = d.getFullYear();
const month = pad(d.getMonth() + 1);
const day = pad(d.getDate());
const hours = pad(d.getHours());
const minutes = pad(d.getMinutes());
const seconds = pad(d.getSeconds());
const millis = pad(d.getMilliseconds(), 3);
const tzOffset = -d.getTimezoneOffset();
const tzSign = tzOffset >= 0 ? "+" : "-";
const tzHours = pad(Math.floor(Math.abs(tzOffset) / 60));
const tzMins = pad(Math.abs(tzOffset) % 60);
return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}.${millis}${tzSign}${tzHours}:${tzMins}`;
}
async function readState$1(chatWd) {
const p = path.join(chatWd, "chat_media_state.json");
try {
const raw = await fs.promises.readFile(p, "utf-8");
const json = JSON.parse(raw);
return normalizeState(json);
} catch {
return {
attachments: [],
variants: [],
pictures: [],
images: [],
counters: {}
};
}
}
async function writeStateAtomic(chatWd, state) {
const tmp = path.join(chatWd, "chat_media_state.json.tmp");
const dst = path.join(chatWd, "chat_media_state.json");
if (Array.isArray(state.attachments) && state.attachments.length > 1) {
state.attachments.sort((a, b) => (a.a ?? 0) - (b.a ?? 0));
}
if (Array.isArray(state.variants) && state.variants.length > 1) {
state.variants.sort((a, b) => (a.v ?? 0) - (b.v ?? 0));
}
if (Array.isArray(state.pictures) && state.pictures.length > 1) {
state.pictures.sort((a, b) => (a.p ?? 0) - (b.p ?? 0));
}
if (Array.isArray(state.images) && state.images.length > 1) {
state.images.sort((a, b) => (a.i ?? 0) - (b.i ?? 0));
}
let pretty = JSON.stringify(state, null, 2);
pretty = pretty.replace(
/"(lastPromotedAttachmentAs|lastPromotedVariantVs|lastPromotedImageIs|lastPixelPromotedAttachmentAs|lastPixelPromotedVariantVs|lastPixelPromotedImageIs|injectedMarkdown)":\s*\[\s*\n([\s\S]*?)\n\s*\]/g,
(match, key, content) => {
const items = content.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
return `"${key}": [${items.join(", ")}]`;
}
);
await fs.promises.writeFile(tmp, pretty, "utf-8");
await fs.promises.rename(tmp, dst);
}
function normalizeState(s) {
let lastPromotedAttachmentAs;
if (Array.isArray(s?.lastPromotedAttachmentAs)) {
lastPromotedAttachmentAs = s.lastPromotedAttachmentAs.filter(
(x) => typeof x === "number" && Number.isFinite(x)
);
} else if (Array.isArray(s?.lastPromotedAttachmentNs)) {
lastPromotedAttachmentAs = s.lastPromotedAttachmentNs.filter(
(x) => typeof x === "number" && Number.isFinite(x)
);
} else if (typeof s?.lastPromotedAttachmentN === "number") {
lastPromotedAttachmentAs = [s.lastPromotedAttachmentN];
} else if (typeof s?.lastPromotedAttachmentA === "number") {
lastPromotedAttachmentAs = [s.lastPromotedAttachmentA];
}
let lastPromotedVariantVs;
if (Array.isArray(s?.lastPromotedVariantVs)) {
lastPromotedVariantVs = s.lastPromotedVariantVs.filter(
(x) => typeof x === "number" && Number.isFinite(x)
);
}
let lastPromotedImageIs;
if (Array.isArray(s?.lastPromotedImageIs)) {
lastPromotedImageIs = s.lastPromotedImageIs.filter(
(x) => typeof x === "number" && Number.isFinite(x)
);
}
let lastPixelPromotedAttachmentAs;
if (Array.isArray(s?.lastPixelPromotedAttachmentAs)) {
lastPixelPromotedAttachmentAs = s.lastPixelPromotedAttachmentAs.filter(
(x) => typeof x === "number" && Number.isFinite(x)
);
} else if (Array.isArray(s?.lastPixelPromotedAttachmentNs)) {
lastPixelPromotedAttachmentAs = s.lastPixelPromotedAttachmentNs.filter(
(x) => typeof x === "number" && Number.isFinite(x)
);
}
let lastPixelPromotedVariantVs;
if (Array.isArray(s?.lastPixelPromotedVariantVs)) {
lastPixelPromotedVariantVs = s.lastPixelPromotedVariantVs.filter(
(x) => typeof x === "number" && Number.isFinite(x)
);
}
let lastPixelPromotedImageIs;
if (Array.isArray(s?.lastPixelPromotedImageIs)) {
lastPixelPromotedImageIs = s.lastPixelPromotedImageIs.filter(
(x) => typeof x === "number" && Number.isFinite(x)
);
}
const normalizeNumberArray = (v) => {
if (!Array.isArray(v)) return void 0;
const a = v.filter((x) => typeof x === "number" && Number.isFinite(x)).map((x) => Math.floor(x)).filter((x) => x > 0);
if (!a.length) return void 0;
return Array.from(new Set(a)).sort((x, y) => x - y);
};
const pendingReviewPromotion = (() => {
const pr = s?.pendingReviewPromotion;
if (!pr || typeof pr !== "object") return void 0;
const requestedAt = typeof pr.requestedAt === "string" && pr.requestedAt.trim() ? String(pr.requestedAt) : "";
const targets = pr.targets && typeof pr.targets === "object" ? pr.targets : null;
if (!requestedAt || !targets) return void 0;
const a = normalizeNumberArray(targets.a);
const v = normalizeNumberArray(targets.v);
const i = normalizeNumberArray(targets.i);
const p = normalizeNumberArray(targets.p);
if (!a && !v && !i && !p) return void 0;
const ttlMs = typeof pr.ttlMs === "number" && Number.isFinite(pr.ttlMs) && pr.ttlMs > 0 ? pr.ttlMs : void 0;
return {
requestedAt,
requestedByToolCallId: typeof pr.requestedByToolCallId === "string" && pr.requestedByToolCallId.trim() ? String(pr.requestedByToolCallId) : void 0,
reason: typeof pr.reason === "string" && pr.reason.trim() ? String(pr.reason) : void 0,
ttlMs,
targets: {
a,
v,
i,
p
}
};
})();
const pendingSequenceReview = (() => {
const ps = s?.pendingSequenceReview;
if (!ps || typeof ps !== "object") return void 0;
const requestedAt = typeof ps.requestedAt === "string" && ps.requestedAt.trim() ? String(ps.requestedAt) : "";
const movAbs = typeof ps.movAbs === "string" && ps.movAbs.trim() ? String(ps.movAbs) : "";
const variant = typeof ps.variant === "number" && Number.isFinite(ps.variant) ? Math.floor(ps.variant) : 0;
if (!requestedAt || !movAbs || variant <= 0) return void 0;
const fps = typeof ps.fps === "number" && Number.isFinite(ps.fps) && ps.fps > 0 ? ps.fps : 2;
const ttlMs = typeof ps.ttlMs === "number" && Number.isFinite(ps.ttlMs) && ps.ttlMs > 0 ? ps.ttlMs : void 0;
return {
requestedAt,
movAbs,
variant,
variantLabel: typeof ps.variantLabel === "string" && ps.variantLabel.trim() ? String(ps.variantLabel) : void 0,
fps,
ttlMs
};
})();
const n = {
attachments: Array.isArray(s?.attachments) ? s.attachments : [],
variants: Array.isArray(s?.variants) ? s.variants : [],
pictures: Array.isArray(s?.pictures) ? s.pictures : [],
images: Array.isArray(s?.images) ? s.images : [],
pendingReviewPromotion,
pendingSequenceReview,
lastEvent: s?.lastEvent,
lastCanvasNotation: typeof s?.lastCanvasNotation === "string" ? s.lastCanvasNotation : void 0,
lastCanvasAt: typeof s?.lastCanvasAt === "string" ? s.lastCanvasAt : void 0,
counters: typeof s?.counters === "object" && s?.counters ? s.counters : {},
injectedMarkdown: Array.isArray(s?.injectedMarkdown) ? s.injectedMarkdown : void 0,
injectedContent: Array.isArray(s?.injectedContent) ? s.injectedContent.filter((x) => typeof x === "string" && x.trim()) : void 0,
lastVariantsTs: typeof s?.lastVariantsTs === "string" ? s.lastVariantsTs : void 0,
lastPromotedTs: typeof s?.lastPromotedTs === "string" ? s.lastPromotedTs : void 0,
lastPromotedAttachmentAs,
// Keep deprecated field for backward compat during transition
lastPromotedAttachmentA: typeof s?.lastPromotedAttachmentA === "number" ? s.lastPromotedAttachmentA : typeof s?.lastPromotedAttachmentN === "number" ? s.lastPromotedAttachmentN : void 0,
lastPromotedVariantVs,
lastPromotedImageIs,
lastPixelPromotedAt: typeof s?.lastPixelPromotedAt === "string" ? s.lastPixelPromotedAt : void 0,
lastPixelPromotedAttachmentAs,
lastPixelPromotedVariantVs,
lastPixelPromotedImageIs,
forcePixelPromotionNextTurn: typeof s?.forcePixelPromotionNextTurn === "boolean" ? s.forcePixelPromotionNextTurn : void 0,
forcePixelPromotionSetAt: typeof s?.forcePixelPromotionSetAt === "string" ? s.forcePixelPromotionSetAt : void 0,
forcePixelPromotionReason: typeof s?.forcePixelPromotionReason === "string" ? s.forcePixelPromotionReason : void 0,
lastSsotMessageCount: typeof s?.lastSsotMessageCount === "number" && Number.isFinite(s.lastSsotMessageCount) ? s.lastSsotMessageCount : void 0
};
if (n.attachments.length > 1) {
n.attachments.sort((a, b) => (a.a ?? 0) - (b.a ?? 0));
}
if (n.variants.length > 1) {
n.variants.sort((a, b) => (a.v ?? 0) - (b.v ?? 0));
}
if (n.pictures.length > 1) {
n.pictures.sort((a, b) => (a.p ?? 0) - (b.p ?? 0));
}
if (n.images.length > 1) {
n.images.sort((a, b) => (a.i ?? 0) - (b.i ?? 0));
}
return n;
}
function normalizeString(val) {
return typeof val === "string" ? String(val) : void 0;
}
function normalizeNumber(val) {
return typeof val === "number" && Number.isFinite(val) ? val : void 0;
}
var COMMON_MEDIA_KEYS = /* @__PURE__ */ new Set([
"filename",
"preview",
"sourceTool",
"pluginId",
"sourceUrl",
"title",
"confidence",
"width",
"height",
"pageUrl",
"turnId",
"createdAt",
"kind",
"v",
"p",
"i"
]);
function buildMediaRecord(input, index, indexField, createdAt, existingIndex) {
const base = {
filename: input.filename,
preview: input.preview,
sourceTool: normalizeString(input.sourceTool),
pluginId: normalizeString(input.pluginId),
sourceUrl: normalizeString(input.sourceUrl),
title: normalizeString(input.title),
confidence: normalizeString(input.confidence),
width: normalizeNumber(input.width),
height: normalizeNumber(input.height),
pageUrl: normalizeString(input.pageUrl),
turnId: normalizeNumber(input.turnId),
createdAt: input.createdAt ?? createdAt
};
if (input.kind === "tool_result" || input.kind === "generated") {
base.kind = input.kind;
}
for (const key of Object.keys(input)) {
if (!COMMON_MEDIA_KEYS.has(key) && input[key] != null) {
base[key] = input[key];
}
}
base[indexField] = existingIndex ?? index;
return base;
}
function upgradeExistingRecord(existing, incoming, indexField) {
const incomingIndex = incoming[indexField];
if (existing[indexField] == null && typeof incomingIndex === "number") {
existing[indexField] = incomingIndex;
}
const fieldsToUpgrade = [
"sourceTool",
"pluginId",
"sourceUrl",
"title",
"confidence",
"pageUrl",
"kind"
];
for (const field of fieldsToUpgrade) {
if (existing[field] == null && incoming[field] != null) {
existing[field] = incoming[field];
}
}
if (existing.width == null && typeof incoming.width === "number") {
existing.width = incoming.width;
}
if (existing.height == null && typeof incoming.height === "number") {
existing.height = incoming.height;
}
if (existing.turnId == null && typeof incoming.turnId === "number") {
existing.turnId = incoming.turnId;
}
for (const key of Object.keys(incoming)) {
if (!COMMON_MEDIA_KEYS.has(key) && existing[key] == null && incoming[key] != null) {
existing[key] = incoming[key];
}
}
}
function appendMediaItems(state, items, config) {
if (!items.length) return { changed: false, state, records: [] };
const {
stateArrayKey,
counterKey,
indexField,
eventType,
getDedupeKey,
filter
} = config;
const existing = Array.isArray(
state[stateArrayKey]
) ? [...state[stateArrayKey]] : [];
const maxExistingIndex = existing.reduce((max, r) => {
const idx = r[indexField];
return Math.max(max, typeof idx === "number" && Number.isFinite(idx) ? idx : 0);
}, 0);
const counterIndex = state.counters[counterKey] ?? 1;
const baseIndex = Math.max(1, counterIndex, maxExistingIndex + 1);
const createdAt = localTimestamp();
let newOnes = items.map(
(it, idx) => buildMediaRecord(
it,
baseIndex + idx,
indexField,
createdAt,
it[indexField]
)
);
if (filter) {
newOnes = newOnes.filter(filter);
}
const existingByKey = /* @__PURE__ */ new Map();
for (const item of existing) {
const key = getDedupeKey(item);
if (key) existingByKey.set(key, item);
}
const all = [...existing];
for (const r of newOnes) {
const key = getDedupeKey(r);
const existingItem = key ? existingByKey.get(key) : void 0;
if (!existingItem) {
all.push(r);
if (key) existingByKey.set(key, r);
} else {
upgradeExistingRecord(
existingItem,
r,
indexField
);
}
}
all.sort((a, b) => {
const aIdx = a[indexField] ?? 0;
const bIdx = b[indexField] ?? 0;
return aIdx - bIdx || String(a.createdAt || "").localeCompare(String(b.createdAt || ""));
});
const changed = JSON.stringify(all) !== JSON.stringify(state[stateArrayKey] || []);
if (changed) {
state[stateArrayKey] = all;
const lastIndex = all.at(-1)?.[indexField];
state.counters[counterKey] = (typeof lastIndex === "number" ? lastIndex : baseIndex - 1) + 1;
state.lastEvent = { type: eventType, at: localTimestamp() };
}
return { changed, state, records: newOnes };
}
var IMAGE_APPEND_CONFIG = {
stateArrayKey: "images",
counterKey: "nextImageI",
indexField: "i",
eventType: "images",
getDedupeKey: (item) => `${item.filename}|${item.preview}`
};
function appendImages(state, items) {
return appendMediaItems(
state,
items,
IMAGE_APPEND_CONFIG
);
}
function resolveProjectRootFrom(startDir) {
try {
{
let dir = startDir;
for (let i = 0; i < 50; i++) {
try {
if (fs.existsSync(path.join(dir, "manifest.json"))) return dir;
} catch {
}
const parent = path.dirname(dir);
if (parent === dir) break;
dir = parent;
}
}
{
let dir = startDir;
for (let i = 0; i < 50; i++) {
try {
if (fs.existsSync(path.join(dir, "package.json"))) return dir;
} catch {
}
const parent = path.dirname(dir);
if (parent === dir) break;
dir = parent;
}
}
{
let dir = startDir;
for (let i = 0; i < 30; i++) {
const base = path.basename(dir);
if (base === "dist" || base === "src") return path.dirname(dir);
const parent = path.dirname(dir);
if (parent === dir) break;
dir = parent;
}
}
} catch {
}
return startDir;
}
function getProjectRoot() {
try {
if (fs.existsSync(path.join(process.cwd(), "manifest.json"))) {
return process.cwd();
}
} catch {
}
try {
const filePath = typeof __filename !== "undefined" && __filename ? __filename : process.argv && process.argv[1] ? process.argv[1] : process.cwd();
const moduleDir = path.dirname(filePath);
return resolveProjectRootFrom(moduleDir);
} catch {
return resolveProjectRootFrom(process.cwd());
}
}
function getLogsDir() {
return path.join(getProjectRoot(), "logs");
}
var cachedPluginLogFilename;
function getPluginLogFilename() {
if (cachedPluginLogFilename !== void 0) return cachedPluginLogFilename;
try {
const raw = fs.readFileSync(path.join(getProjectRoot(), "manifest.json"), "utf-8");
const name = JSON.parse(raw)?.name;
if (typeof name === "string" && name.trim()) {
cachedPluginLogFilename = `${name.trim()}-plugin.log`;
return cachedPluginLogFilename;
}
} catch {
}
cachedPluginLogFilename = "generate-image-plugin.log";
return cachedPluginLogFilename;
}
function getActiveChatContext(opts) {
return null;
}
var lmstudioHome = null;
function findLMStudioHome() {
if (lmstudioHome !== null) {
return lmstudioHome;
}
const resolvedHomeDir = fs.realpathSync(os.homedir());
const pointerFilePath = path.join(resolvedHomeDir, ".lmstudio-home-pointer");
if (fs.existsSync(pointerFilePath)) {
const candidate = fs.readFileSync(pointerFilePath, "utf-8").trim();
try {
if (candidate && fs.existsSync(candidate)) {
const hasConversations = fs.existsSync(path.join(candidate, "conversations"));
const hasUserFiles = fs.existsSync(path.join(candidate, "user-files"));
if (hasConversations || hasUserFiles) {
lmstudioHome = candidate;
return lmstudioHome;
}
}
} catch {
}
}
const dotHome = path.join(resolvedHomeDir, ".lmstudio");
const cacheHome = path.join(resolvedHomeDir, ".cache", "lm-studio");
const looksValid = (p) => {
try {
if (!fs.existsSync(p)) return false;
const conv = path.join(p, "conversations");
const files = path.join(p, "user-files");
return fs.existsSync(conv) || fs.existsSync(files);
} catch {
return false;
}
};
if (looksValid(dotHome)) {
lmstudioHome = dotHome;
try {
fs.writeFileSync(pointerFilePath, lmstudioHome, "utf-8");
} catch {
}
return lmstudioHome;
}
if (looksValid(cacheHome)) {
lmstudioHome = cacheHome;
try {
fs.writeFileSync(pointerFilePath, lmstudioHome, "utf-8");
} catch {
}
return lmstudioHome;
}
const home = dotHome;
lmstudioHome = home;
try {
fs.writeFileSync(pointerFilePath, lmstudioHome, "utf-8");
} catch {
}
return lmstudioHome;
}
function getLMStudioWorkingDir(chatId) {
const home = findLMStudioHome();
return path.join(home, "working-directories", chatId);
}
async function getLMStudioFileMetadata(fileIdentifier) {
try {
const home = findLMStudioHome() || path.join(os.homedir(), ".lmstudio");
const metadataPath = path.join(
home,
"user-files",
`${fileIdentifier}.metadata.json`
);
if (!fs.existsSync(metadataPath)) return null;
const raw = await fs.promises.readFile(metadataPath, "utf-8");
const meta = JSON.parse(raw);
if (typeof meta.originalName !== "string" || typeof meta.fileIdentifier !== "string") {
return null;
}
return meta;
} catch {
return null;
}
}
async function getOriginalFileName(fileIdentifier) {
const meta = await getLMStudioFileMetadata(fileIdentifier);
return meta?.originalName ?? null;
}
// src/services/drawthingsLimits.ts
var drawthingsLimits = {
// Limits for RENDERED output (requested_effective)
min: 256,
// Minimum dimension per side
maxWidth: 2048,
// Maximum render width
maxHeight: 2048,
// Preview generation for ALL MediaTypes (attachment, variant, image, picture).
// w + h ≤ previewMaxSum; one preview file serves: display in chat, vision promotion,
// analyse_image, detect_object.
previewMaxSum: 1792,
previewQuality: 80,
previewFormat: "jpeg"
};
// src/services/toolParams/variants.ts
function extractVariantUrisFromContent(raw) {
const candidates = [];
const text = typeof raw === "string" ? raw : JSON.stringify(raw);
const uriRegex = /file:\/\/[^\s"')]+generated-image-[^\s"')]*-v(\d+)\.png/gi;
let match;
while ((match = uriRegex.exec(text)) !== null) {
const uri = match[0];
const variantNum = parseInt(match[1], 10);
const filename = uri.split("/").pop() ?? "";
candidates.push({
filename,
index: variantNum
});
}
return candidates;
}
function extractGenerateImageResult(raw) {
const candidates = [];
if (raw && typeof raw === "object") {
const obj = raw;
if (Array.isArray(obj.filenames)) {
for (let i = 0; i < obj.filenames.length; i++) {
const fn = obj.filenames[i];
if (typeof fn === "string") {
candidates.push({ filename: fn, index: i + 1 });
}
}
}
if (Array.isArray(obj.content)) {
for (const item of obj.content) {
if (item && typeof item === "object" && "text" in item) {
const nested = extractVariantUrisFromContent(item.text);
candidates.push(...nested);
}
}
}
}
if (candidates.length === 0) {
return extractVariantUrisFromContent(raw);
}
return candidates;
}
var selfPlugin = getSelfPluginIdentifier() ?? "unknown";
var VARIANT_FULL_CONFIG = {
mediaType: "variant",
allow: "all",
ssotJoin: {
source: "conversation.json",
messageRole: "assistant",
jsonPath: "content",
// Regex scan for file:// URIs
extractFromSource: extractVariantUrisFromContent
},
scan: {
trigger: "both",
scope: "all-turns"
},
harvesting: {
defaultExtractor: extractGenerateImageResult,
tools: {
[`${selfPlugin}/generate_image`]: {
extractCandidates: extractGenerateImageResult,
actions: {
generatePreview: true,
visionPromotion: {
metadata: true,
pixel: true
}
}
}
}
},
actions: {
generatePreview: true,
visionPromotion: {
metadata: true,
// Labels (v1, v2) ALWAYS included
pixel: true
// Base64 pixels in rolling window
}
},
injectMdInAgentResponse: {
format: "none",
// Toggle-dependent: !PREVIEW_IN_CHAT
itemTemplate: "",
labelGenerator: (item, i) => `v${item.index ?? i + 1}`
},
preview: {
generate: true,
namingPattern: "preview-{basename}.jpg",
format: drawthingsLimits.previewFormat,
mimeType: "image/jpeg",
maxSum: drawthingsLimits.previewMaxSum,
quality: drawthingsLimits.previewQuality,
outputDir: "."
},
toggles: {
toggles: []
}
};
// src/helpers/imageUtils.ts
var loadedSharp2 = void 0;
var loadedJimp2 = void 0;
var libLogged = false;
var loggedFns = /* @__PURE__ */ new Set();
function logOnce(msg) {
if (loggedFns.has(msg)) return;
loggedFns.add(msg);
try {
console.debug(msg);
} catch {
}
}
async function tryLoadSharp2() {
if (loadedSharp2 !== void 0) return loadedSharp2;
try {
const mod = await import('sharp');
loadedSharp2 = mod?.default || mod;
if (!libLogged) {
try {
console.debug(`[imageUtils] using lib=sharp`);
libLogged = true;
} catch {
}
}
return loadedSharp2;
} catch {
loadedSharp2 = null;
return null;
}
}
async function tryLoadJimp2() {
if (loadedJimp2 !== void 0) return loadedJimp2;
try {
const mod = await import('jimp');
const candidate = mod && (mod.default || mod.Jimp || mod);
loadedJimp2 = candidate;
if (!libLogged) {
try {
console.debug(`[imageUtils] using lib=jimp`);
libLogged = true;
} catch {
}
}
return loadedJimp2;
} catch {
loadedJimp2 = null;
return null;
}
}
function jimpHasFn(obj, name) {
try {
return obj && typeof obj[name] === "function";
} catch {
return false;
}
}
async function jimpResizeCompat(img, w, h) {
if (!jimpHasFn(img, "resize")) {
throw new Error("Jimp resize not available");
}
let lastError;
try {
await img.resize({ w, h });
return;
} catch (e) {
lastError = e;
}
try {
await img.resize(w, h);
return;
} catch (e) {
lastError = e;
}
throw new Error(
`Jimp resize failed for both API variants: ${lastError?.message || String(lastError)}`
);
}
async function jimpAutoRotate(img) {
try {
if (jimpHasFn(img, "exifRotate")) {
await img.exifRotate();
} else if (jimpHasFn(img, "rotate")) {
}
} catch (e) {
try {
console.error(`[imageUtils] Jimp EXIF rotation failed: ${String(e)}`);
} catch {
}
}
}
async function jimpGetBufferCompat(img, mime, options) {
let lastError;
try {
if (jimpHasFn(img, "getBufferAsync")) {
return await img.getBufferAsync(mime, options);
}
} catch (e) {
lastError = e;
}
try {
if (jimpHasFn(img, "getBuffer")) {
return await img.getBuffer(mime, options);
}
} catch (e) {
lastError = e;
}
throw new Error(
`Jimp getBuffer failed for both API variants (mime=${mime}): ${lastError?.message || String(lastError)}`
);
}
async function getSize(buffer) {
const sharp = await tryLoadSharp2();
if (sharp) {
logOnce(`[imageUtils.getSize] using Sharp`);
const meta = await sharp(buffer).rotate().metadata();
return { width: meta.width || 0, height: meta.height || 0 };
}
const Jimp = await tryLoadJimp2();
if (Jimp && typeof Jimp.read === "function") {
logOnce(`[imageUtils.getSize] using Jimp`);
const img = await Jimp.read(buffer);
await jimpAutoRotate(img);
const w = typeof img.getWidth === "function" ? img.getWidth() : typeof img.width === "number" ? img.width : img.bitmap?.width || 0;
const h = typeof img.getHeight === "function" ? img.getHeight() : typeof img.height === "number" ? img.height : img.bitmap?.height || 0;
return { width: w || 0, height: h || 0 };
}
return { width: 0, height: 0 };
}
async function resizeAndEncode(buffer, format, quality, width, _maxBytes) {
const sharp = await tryLoadSharp2();
if (sharp) {
logOnce(`[imageUtils.resizeAndEncode] using Sharp`);
const pipeline = sharp(buffer).rotate().resize({ width, fit: "inside", withoutEnlargement: false });
const { data, info } = await (format === "jpeg" ? pipeline.jpeg({
quality: clampQuality(quality),
mozjpeg: true,
chromaSubsampling: "4:2:0",
progressive: true
}) : pipeline.webp({ quality: clampQuality(quality), effort: 4 })).toBuffer({ resolveWithObject: true });
const outW = typeof info.width === "number" ? info.width : width;
const outH = typeof info.height === "number" ? info.height : Math.round(width * 0.75);
return { data, width: outW, height: outH };
}
const Jimp = await tryLoadJimp2();
if (Jimp && typeof Jimp.read === "function") {
logOnce(`[imageUtils.resizeAndEncode] using Jimp`);
const img = await Jimp.read(buffer);
await jimpAutoRotate(img);
const origW = typeof img.getWidth === "function" ? img.getWidth() : typeof img.width === "number" ? img.width : img.bitmap?.width || 0;
const origH = typeof img.getHeight === "function" ? img.getHeight() : typeof img.height === "number" ? img.height : img.bitmap?.height || 0;
const scale = width / Math.max(1, origW);
const outW = Math.max(1, Math.round(origW * scale));
const outH = Math.max(1, Math.round(origH * scale));
await jimpResizeCompat(img, outW, outH);
const q = clampQuality(quality);
{
const data = await jimpGetBufferCompat(img, "image/jpeg", { quality: q });
return { data, width: outW, height: outH };
}
}
return { data: buffer, width, height: Math.round(width * 0.75) };
}
async function resizeAndEncodeByHeight(buffer, format, quality, maxHeight) {
const sharp = await tryLoadSharp2();
if (sharp) {
logOnce(`[imageUtils.resizeAndEncodeByHeight] using Sharp`);
const pipeline = sharp(buffer).rotate().resize({ height: maxHeight, fit: "inside", withoutEnlargement: false });
const { data, info } = await (format === "jpeg" ? pipeline.jpeg({
quality: clampQuality(quality),
mozjpeg: true,
chromaSubsampling: "4:2:0",
progressive: true
}) : pipeline.webp({ quality: clampQuality(quality), effort: 4 })).toBuffer({ resolveWithObject: true });
const outW = typeof info.width === "number" ? info.width : Math.round(maxHeight * 1.33);
const outH = typeof info.height === "number" ? info.height : maxHeight;
return { data, width: outW, height: outH };
}
const Jimp = await tryLoadJimp2();
if (Jimp && typeof Jimp.read === "function") {
logOnce(`[imageUtils.resizeAndEncodeByHeight] using Jimp`);
const img = await Jimp.read(buffer);
await jimpAutoRotate(img);
const origW = typeof img.getWidth === "function" ? img.getWidth() : typeof img.width === "number" ? img.width : img.bitmap?.width || 0;
const origH = typeof img.getHeight === "function" ? img.getHeight() : typeof img.height === "number" ? img.height : img.bitmap?.height || 0;
const scale = maxHeight / Math.max(1, origH);
const outH = Math.max(1, Math.round(maxHeight));
const outW = Math.max(1, Math.round(origW * scale));
await jimpResizeCompat(img, outW, outH);
const q = clampQuality(quality);
{
const data = await jimpGetBufferCompat(img, "image/jpeg", { quality: q });
return { data, width: outW, height: outH };
}
}
return {
data: buffer,
width: Math.round(maxHeight * 1.33),
height: maxHeight
};
}
async function resizeAndEncodeBySum(buffer, format, quality, maxSum) {
const sharp = await tryLoadSharp2();
const calcDims = (origW, origH) => {
let w = Math.max(1, Math.round(origW));
let h = Math.max(1, Math.round(origH));
const currentSum = w + h;
if (currentSum > maxSum) {
const scale = maxSum / currentSum;
w = Math.max(1, Math.round(w * scale));
h = Math.max(1, Math.round(h * scale));
}
return { w, h };
};
if (sharp) {
logOnce(`[imageUtils.resizeAndEncodeBySum] using Sharp`);
const metadata = await sharp(buffer).metadata();
const rotatesDimensions = metadata.orientation !== void 0 && metadata.orientation >= 5 && metadata.orientation <= 8;
const origW = rotatesDimensions ? metadata.height ?? 640 : metadata.width ?? 640;
const origH = rotatesDimensions ? metadata.width ?? 640 : metadata.height ?? 640;
const { w, h } = calcDims(origW, origH);
const pipeline = sharp(buffer).rotate().resize({ width: w, height: h, fit: "inside", withoutEnlargement: true });
const { data, info } = await (format === "jpeg" ? pipeline.jpeg({
quality: clampQuality(quality),
mozjpeg: true,
chromaSubsampling: "4:2:0",
progressive: true
}) : pipeline.webp({ quality: clampQuality(quality), effort: 4 })).toBuffer({ resolveWithObject: true });
const outW = typeof info.width === "number" ? info.width : w;
const outH = typeof info.height === "number" ? info.height : h;
return { data, width: outW, height: outH };
}
const Jimp = await tryLoadJimp2();
if (Jimp && typeof Jimp.read === "function") {
logOnce(`[imageUtils.resizeAndEncodeBySum] using Jimp`);
const img = await Jimp.read(buffer);
await jimpAutoRotate(img);
const origW = typeof img.getWidth === "function" ? img.getWidth() : typeof img.width === "number" ? img.width : img.bitmap?.width || 640;
const origH = typeof img.getHeight === "function" ? img.getHeight() : typeof img.height === "number" ? img.height : img.bitmap?.height || 640;
const { w, h } = calcDims(origW, origH);
await jimpResizeCompat(img, w, h);
const q = clampQuality(quality);
{
const data = await jimpGetBufferCompat(img, "image/jpeg", { quality: q });
return { data, width: w, height: h };
}
}
return {
data: buffer,
width: Math.round(maxSum / 2),
height: Math.round(maxSum / 2)
};
}
function clampQuality(q) {
if (!Number.isFinite(q)) return 80;
q = Math.round(q);
if (q < 1) q = 1;
if (q > 100) q = 100;
return q;
}
// src/media-promotion-core/image.ts
function getDefaultPreviewOptions() {
return {
maxDim: drawthingsLimits.previewMaxSum,
quality: drawthingsLimits.previewQuality,
mode: "sum",
maxSum: drawthingsLimits.previewMaxSum
};
}
function isAllowedOriginalExt(p) {
return /(\.(png|jpe?g|webp|mov))$/i.test(p);
}
function previewFilenameFrom(originalFilename) {
const hasPrefix = originalFilename.toLowerCase().startsWith("preview-");
const base = hasPrefix ? originalFilename : `preview-${originalFilename}`;
return base.replace(/\.(png|jpg|jpeg|webp|gif)$/i, ".jpg").replace(/ /g, "_");
}
async function encodeJpegPreviewFromBuffer(srcBuf, opts) {
const q = Math.max(1, Math.min(100, opts.quality));
if (opts.mode === "height") {
const targetH = Math.max(1, Math.round(opts.maxDim));
const { data: data2, width: width2, height: height2 } = await resizeAndEncodeByHeight(
srcBuf,
"jpeg",
q,
targetH
);
return { data: data2, width: width2, height: height2 };
}
if (opts.mode === "sum" && opts.maxSum) {
const { data: data2, width: width2, height: height2 } = await resizeAndEncodeBySum(
srcBuf,
"jpeg",
q,
opts.maxSum
);
return { data: data2, width: width2, height: height2 };
}
const maxW = Math.max(1, Math.round(opts.maxDim));
const { data, width, height } = await resizeAndEncode(
srcBuf,
"jpeg",
q,
maxW);
return { data, width, height };
}
async function encodeJpegPreview(srcAbs, dstAbs, opts) {
const srcBuf = await fs.promises.readFile(srcAbs);
const { data } = await encodeJpegPreviewFromBuffer(srcBuf, opts);
await fs.promises.writeFile(dstAbs, data);
}
function isPreviewOptions(x) {
return typeof x === "object" && x !== null && "maxDim" in x && typeof x.maxDim === "number";
}
function normalizeToPreviewOptions(input) {
if (isPreviewOptions(input)) return input;
return {
maxDim: input.maxWidth ?? 640,
quality: input.quality ?? 80,
mode: input.maxSum ? "sum" : "width",
maxSum: input.maxSum
};
}
async function generatePreview(srcAbs, chatWd, optsInput, options) {
const debug = options?.debug ?? false;
const opts = normalizeToPreviewOptions(optsInput);
if (!fs.existsSync(srcAbs)) {
if (debug) console.warn(`[Preview] Source not found: ${srcAbs}`);
return null;
}
const originalFilename = path.basename(srcAbs);
const previewFilename = options?.customFilename ?? previewFilenameFrom(originalFilename);
const previewAbs = path.join(chatWd, previewFilename);
if (!options?.force && fs.existsSync(previewAbs)) {
if (debug) console.info(`[Preview] Exists, skipping: ${previewFilename}`);
return previewFilename;
}
try {
await encodeJpegPreview(srcAbs, previewAbs, opts);
if (debug) console.info(`[Preview] Generated: ${previewFilename}`);
return previewFilename;
} catch (e) {
if (debug)
console.warn(
`[Preview] Failed for ${originalFilename}:`,
e.message
);
throw e;
}
}
async function generatePreviewFromBuffer(srcBuf, chatWd, originalFilename, optsInput, options) {
const debug = false;
const opts = normalizeToPreviewOptions(optsInput);
const previewFilename = options?.customFilename ?? previewFilenameFrom(originalFilename);
const previewAbs = path.join(chatWd, previewFilename);
if (fs.existsSync(previewAbs)) {
try {
const existingData = await fs.promises.readFile(previewAbs);
return {
previewFilename,
previewAbs,
data: existingData,
width: 0,
// Unknown for existing file
height: 0
};
} catch {
}
}
try {
const { data, width, height } = await encodeJpegPreviewFromBuffer(
srcBuf,
opts
);
await fs.promises.writeFile(previewAbs, data);
if (debug)
;
return {
previewFilename,
previewAbs,
data,
width,
height
};
} catch (e) {
throw e;
}
}
function findConversationPath(chatWd) {
const chatId = path.basename(chatWd);
const lmHome = findLMStudioHome() || path.join(os.homedir(), ".lmstudio");
const conversationsDir = path.join(lmHome, "conversations");
const candidates = [
path.join(conversationsDir, `${chatId}.conversation.json`),
path.join(chatWd, ".conversation.json"),
path.join(chatWd, "conversation.json")
];
for (const p of candidates) {
try {
fs.accessSync(p, fs.constants.F_OK);
return p;
} catch {
}
}
return void 0;
}
async function readConversation(chatWd) {
const p = findConversationPath(chatWd);
if (!p) return void 0;
const maxAttempts = 5;
const retryDelayMs = 50;
for (let attempt = 0; attempt < maxAttempts; attempt++) {
try {
const raw = await fs.promises.readFile(p, "utf-8");
const json = JSON.parse(raw);
return { json, path: p };
} catch {
if (attempt < maxAttempts - 1) {
await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
}
}
}
return void 0;
}
function findMessagesArray(json) {
if (!json || typeof json !== "object") return void 0;
const obj = json;
const candidates = [
obj.messages,
obj.conversation?.messages,
obj.chat?.messages,
obj.history,
obj.turns,
obj.items
];
for (const arr of candidates) {
if (Array.isArray(arr) && arr.length > 0) {
return arr;
}
}
return void 0;
}
function resolveMessageVersion(raw) {
if (!raw || typeof raw !== "object") return raw;
const obj = raw;
const versions = obj.versions;
if (!Array.isArray(versions) || versions.length === 0) {
return raw;
}
const selRaw = obj.currentlySelected;
const sel = typeof selRaw === "number" && Number.isFinite(selRaw) ? selRaw : 0;
if (sel >= 0 && sel < versions.length) {
return versions[sel];
}
return versions[versions.length - 1];
}
function getMessageRole(msg) {
if (!msg || typeof msg !== "object") return "unknown";
const obj = msg;
if (obj.type === "contentBlock") {
const arr = obj.content;
if (Array.isArray(arr)) {
for (const it of arr) {
if (!it || typeof it !== "object") continue;
const t = it.type;
if (t === "toolCallRequest" || t === "toolCallResult") {
return "tool";
}
}
}
}
const role = obj.role ?? obj.author ?? obj.sender;
if (typeof role === "string") return role.toLowerCase();
const type = obj.type ?? obj.messageType;
if (typeof type === "string") {
if (type === "user" || type === "user_message") return "user";
if (type === "assistant" || type === "assistant_message")
return "assistant";
if (type === "system") return "system";
if (type === "tool" || type === "tool_result") return "tool";
}
return "unknown";
}
function parseMessages(json) {
const messages = findMessagesArray(json);
if (!messages) return [];
const result = [];
for (let i = 0; i < messages.length; i++) {
const raw = messages[i];
const resolved = resolveMessageVersion(raw);
const role = getMessageRole(resolved);
result.push({
index: i,
turnId: i + 1,
// 1-based
role,
content: resolved,
raw
});
}
return result;
}
function extractUserAttachments(msg, lmHome) {
const result = [];
const home = findLMStudioHome() || path.join(os.homedir(), ".lmstudio");
const userFilesDir = path.join(home, "user-files");
const content = msg.content;
if (!content || typeof content !== "object") return result;
const collectFromObject = (obj) => {
if (!obj || typeof obj !== "object") return;
if (Array.isArray(obj)) {
for (const item of obj) {
collectFromObject(item);
}
return;
}
const o = obj;
const fileId = o.fileIdentifier ?? o.file_identifier ?? o.identifier;
const fileType = o.fileType ?? o.file_type ?? o.type;
if (typeof fileId === "string" && fileId.trim()) {
if (fileType === "image" || /\.(png|jpg|jpeg|webp|gif|bmp|tiff?)$/i.test(fileId)) {
result.push(path.join(userFilesDir, fileId));
}
}
const filePath = o.path ?? o.filePath ?? o.file_path ?? o.uri ?? o.url;
if (typeof filePath === "string" && filePath.trim()) {
let resolved = filePath;
if (filePath.startsWith("file://")) {
try {
resolved = decodeURIComponent(filePath.replace(/^file:\/\//, ""));
} catch {
resolved = filePath.replace(/^file:\/\//, "");
}
}
if (/\.(png|jpg|jpeg|webp|gif|bmp|tiff?)$/i.test(resolved)) {
result.push(resolved);
}
}
for (const key of Object.keys(o)) {
if (key !== "content" || !Array.isArray(o[key])) {
collectFromObject(o[key]);
}
}
};
const contentArray = content.content;
if (Array.isArray(contentArray)) {
for (const part of contentArray) {
collectFromObject(part);
}
} else {
collectFromObject(content);
}
const files = content.files;
if (Array.isArray(files)) {
for (const f of files) {
collectFromObject(f);
}
}
const attachments = content.attachments;
if (Array.isArray(attachments)) {
for (const a of attachments) {
collectFromObject(a);
}
}
return result;
}
function extractPendingAttachments(json, lmHome) {
const result = [];
const home = findLMStudioHome() || path.join(os.homedir(), ".lmstudio");
const userFilesDir = path.join(home, "user-files");
if (!json || typeof json !== "object") return result;
const files = json.clientInputFiles;
if (!Array.isArray(files)) return result;
for (const f of files) {
if (!f || typeof f !== "object") continue;
const fo = f;
const id = fo.fileIdentifier;
const type = fo.fileType;
if (typeof id === "string" && id.trim() && type === "image") {
result.push(path.join(userFilesDir, id));
}
}
return result;
}
function buildConversationWideRequestMetadata(messages, debug) {
const metaByKey = /* @__PURE__ */ new Map();
let toolRequestCount = 0;
const remember = (key, meta) => {
const k = typeof key === "string" || typeof key === "number" ? String(key) : "";
if (!k) return;
const prev = metaByKey.get(k) ?? {};
metaByKey.set(k, {
pluginId: meta.pluginId ?? prev.pluginId,
toolName: meta.toolName ?? prev.toolName
});
};
if (debug) {
console.info(
`[MediaScanner] buildConversationWideRequestMetadata: scanning ${messages.length} messages`
);
}
for (const msg of messages) {
const content = msg.content;
if (!content || typeof content !== "object") continue;
const obj = content;
if (obj.type === "contentBlock" && Array.isArray(obj.content)) {
for (const item of obj.content) {
if (!item || typeof item !== "object") continue;
const bo = item;
if (bo.type !== "toolCallRequest") continue;
const pluginId = typeof bo.pluginIdentifier === "string" ? bo.pluginIdentifier : void 0;
const toolName = typeof bo.name === "string" ? bo.name : void 0;
const callId = bo.callId ?? bo.toolCallId ?? bo.id;
const reqId = bo.toolCallRequestId ?? bo.requestId;
toolRequestCount++;
if (debug) {
console.info(
`[MediaScanner] Request metadata (Case1): callId=${callId} reqId=${reqId} tool=${toolName} plugin=${pluginId ?? "(none)"}`
);
}
remember(callId, { pluginId, toolName });
remember(reqId, { pluginId, toolName });
}
}
if (Array.isArray(obj.content)) {
for (const item of obj.content) {
if (!item || typeof item !== "object") continue;
const it = item;
if (it.type === "contentBlock" && Array.isArray(it.content)) {
for (const bi of it.content) {
if (!bi || typeof bi !== "object") continue;
const bo = bi;
if (bo.type !== "toolCallRequest") continue;
const pluginId = typeof bo.pluginIdentifier === "string" ? bo.pluginIdentifier : void 0;
const toolName = typeof bo.name === "string" ? bo.name : void 0;
const callId = bo.callId ?? bo.toolCallId ?? bo.id;
const reqId = bo.toolCallRequestId ?? bo.requestId;
toolRequestCount++;
if (debug) {
console.info(
`[MediaScanner] Request metadata (Case2): callId=${callId} reqId=${reqId} tool=${toolName} plugin=${pluginId ?? "(none)"}`
);
}
remember(callId, { pluginId, toolName });
remember(reqId, { pluginId, toolName });
}
}
}
}
if (Array.isArray(obj.steps)) {
for (const step of obj.steps) {
if (!step || typeof step !== "object") continue;
const st = step;
if (st.type === "contentBlock" && Array.isArray(st.content)) {
for (const bi of st.content) {
if (!bi || typeof bi !== "object") continue;
const bo = bi;
if (bo.type !== "toolCallRequest") continue;
const pluginId = typeof bo.pluginIdentifier === "string" ? bo.pluginIdentifier : void 0;
const toolName = typeof bo.name === "string" ? bo.name : void 0;
const callId = bo.callId ?? bo.toolCallId ?? bo.id;
const reqId = bo.toolCallRequestId ?? bo.requestId;
toolRequestCount++;
if (debug) {
console.info(
`[MediaScanner] Request metadata (Case3-steps): callId=${callId} reqId=${reqId} tool=${toolName} plugin=${pluginId ?? "(none)"}`
);
}
remember(callId, { pluginId, toolName });
remember(reqId, { pluginId, toolName });
}
}
}
}
}
if (debug) {
console.info(
`[MediaScanner] buildConversationWideRequestMetadata: found ${toolRequestCount} toolCallRequests, ${metaByKey.size} unique keys`
);
}
return metaByKey;
}
async function scanMedia(chatWd, mediaType, options) {
const { scope, debug = false } = options;
if (!chatWd) {
return { candidates: [] };
}
const conv = await readConversation(chatWd);
if (!conv) {
if (debug) {
console.info(`[MediaScanner] No conversation.json found for ${chatWd}`);
}
return { candidates: [] };
}
const messages = parseMessages(conv.json);
if (debug) {
console.info(
`[MediaScanner] Parsed ${messages.length} messages from ${conv.path}`
);
}
const requestMetaByKey = buildConversationWideRequestMetadata(
messages,
debug
);
if (debug && requestMetaByKey.size > 0) {
console.info(
`[MediaScanner] Collected ${requestMetaByKey.size} request metadata entries`
);
}
const candidates = [];
const orderedMessages = scope === "last" ? [...messages].reverse() : messages;
for (const msg of orderedMessages) {
let foundInThisMessage = [];
{
foundInThisMessage = scanAttachmentsInMessage(msg, conv.json);
}
if (foundInThisMessage.length > 0) {
candidates.push(...foundInThisMessage);
if (scope === "last") {
break;
}
}
}
if (scope === "all") {
const pending = extractPendingAttachments(conv.json);
const toAppend = [];
for (const p of pending) {
const exists = candidates.some((c) => c.identifier === p);
if (exists) continue;
toAppend.push({
kind: "attachment",
identifier: p,
turnId: 0
// Pending = before any turn
});
}
if (toAppend.length) {
candidates.push(...toAppend);
}
}
if (debug) {
console.info(
`[MediaScanner] FINAL: Found ${candidates.length} ${mediaType} candidates (scope: ${scope})`
);
if (candidates.length > 0) {
for (const c of candidates.slice(0, 3)) {
console.info(
`[MediaScanner] candidate: id=${c.identifier?.slice(
0,
50
)} pluginId=${c.pluginId} tool=${c.sourceTool}`
);
}
}
}
return {
candidates,
conversationPath: conv.path
};
}
function scanAttachmentsInMessage(msg, conversationJson, debug) {
if (msg.role !== "user") return [];
const attachments = extractUserAttachments(msg);
return attachments.map((absPath) => ({
kind: "attachment",
identifier: absPath,
turnId: msg.turnId
}));
}
async function findAllMedia(chatWd, mediaType, debug = false) {
return scanMedia(chatWd, mediaType, { scope: "all", debug });
}
// src/services/mediaScanner/legacyAdapters.ts
async function findAllAttachmentsLegacy(chatWd, debug) {
if (!chatWd) return { found: [], turnIdByAbs: {} };
const result = await findAllMedia(chatWd, "attachment", debug);
const found = [];
const turnIdByAbs = {};
for (const c of result.candidates) {
const abs = c.identifier;
if (!found.includes(abs)) {
found.push(abs);
}
if (turnIdByAbs[abs] === void 0) {
turnIdByAbs[abs] = c.turnId;
}
}
return { found, turnIdByAbs };
}
async function pathExists2(p) {
try {
await fs.promises.access(p, fs.constants.F_OK);
return true;
} catch {
return false;
}
}
async function importAttachmentBatch(chatWd, state, sourcePaths, turnIdByOriginAbs, previewOpts, maxPreviewAttachments = 0, debug = false) {
const normalizeAbs = (p) => {
try {
return path.resolve(p);
} catch {
return p;
}
};
const normalizedSource = sourcePaths.filter((p) => typeof p === "string" && p.trim().length > 0).map(normalizeAbs);
const normalizeTurnIdMap = (m) => {
if (!m) return {};
const out = {};
for (const [k, v] of Object.entries(m)) {
if (typeof v === "number" && Number.isFinite(v)) {
out[normalizeAbs(k)] = v;
}
}
return out;
};
const normalizedTurnIdByAbs = normalizeTurnIdMap(turnIdByOriginAbs);
const normalizedSourceDeduped = [];
{
const seen = /* @__PURE__ */ new Set();
for (const p of normalizedSource) {
if (!seen.has(p)) {
seen.add(p);
normalizedSourceDeduped.push(p);
}
}
}
if (normalizedSourceDeduped.length === 0) {
if (debug)
console.info(
"Batch import: No new attachments in current turn; keeping existing state."
);
return { changed: false };
}
try {
const current = Array.isArray(state.attachments) ? state.attachments : [];
const currentOrigins = current.map(
(a) => a && typeof a.originAbs === "string" ? String(a.originAbs) : ""
).filter((p) => p.trim().length > 0).map(normalizeAbs);
const same = currentOrigins.length === normalizedSourceDeduped.length && currentOrigins.every((p, i) => p === normalizedSourceDeduped[i]);
if (same && current.length > 0) {
let allOk = true;
for (let i = 0; i < current.length; i++) {
const a = current[i];
const pv = a && typeof a.preview === "string" ? String(a.preview) : "";
if (i < Math.max(0, Math.floor(maxPreviewAttachments))) {
if (!pv) {
allOk = false;
break;
}
const pvAbs = path.join(chatWd, pv);
const okPv = await pathExists2(pvAbs);
if (!okPv) {
allOk = false;
break;
}
}
}
if (allOk) {
try {
let anyMetaChanged = false;
const nextAttachments = current.map((a) => {
const originAbs = a && typeof a.originAbs === "string" ? String(a.originAbs) : "";
const key = originAbs ? normalizeAbs(originAbs) : "";
const nextTurnId = key ? normalizedTurnIdByAbs[key] : void 0;
let updated = a;
if (typeof nextTurnId === "number" && a && a.turnId !== nextTurnId) {
anyMetaChanged = true;
updated = { ...updated, turnId: nextTurnId };
}
if (!updated.preview && originAbs) {
const candidate = previewFilenameFrom(path.basename(originAbs));
if (fs.existsSync(path.join(chatWd, candidate))) {
anyMetaChanged = true;
updated = { ...updated, preview: candidate };
}
}
return updated;
});
if (anyMetaChanged) {
state.attachments = nextAttachments;
await writeStateAtomic(chatWd, state);
if (debug)
console.info(
"Batch import: updated attachment turnId metadata (idempotent, no re-import)."
);
return { changed: false, metadataChanged: true };
}
} catch (e) {
if (debug)
console.warn(
"Batch import: turnId metadata update failed; continuing idempotent skip:",
e.message
);
}
if (debug)
console.info(
"Batch import: SSOT matches current state; skipping re-import (idempotent)."
);
return { changed: false };
}
}
} catch (e) {
if (debug)
console.warn(
"Batch import: idempotence check failed; continuing with import:",
e.message
);
}
const existingByOrigin = /* @__PURE__ */ new Map();
for (const a of state.attachments || []) {
if (a && typeof a.originAbs === "string") {
existingByOrigin.set(normalizeAbs(a.originAbs), a);
}
}
const usedAs = /* @__PURE__ */ new Set();
for (const a of existingByOrigin.values()) {
const av = a?.a;
if (typeof av === "number" && Number.isFinite(av) && av > 0) {
usedAs.add(av);
}
}
const ensureNextA = () => {
const current = state?.counters?.nextAttachmentA;
if (typeof current === "number" && Number.isFinite(current) && current > 0) {
return Math.floor(current);
}
const maxExisting = usedAs.size ? Math.max(...Array.from(usedAs)) : 0;
return maxExisting + 1;
};
let nextA = ensureNextA();
const allocateA = () => {
while (usedAs.has(nextA)) nextA++;
const a = nextA;
usedAs.add(a);
nextA++;
return a;
};
const imported = [];
for (let i = 0; i < normalizedSourceDeduped.length; i++) {
const abs = normalizedSourceDeduped[i];
if (!await pathExists2(abs)) {
if (debug)
console.warn(`Batch import: source not found, skipping: ${abs}`);
continue;
}
if (!isAllowedOriginalExt(abs)) {
if (debug)
console.warn(`Batch import: extension not allowed, skipping: ${abs}`);
continue;
}
const existing = existingByOrigin.get(normalizeAbs(abs));
if (existing) {
if (existing.preview) {
const previewAbs = path.join(chatWd, existing.preview);
if (!await pathExists2(previewAbs)) {
if (debug)
console.info(
`Batch import: regenerating missing preview for a${existing.a}`
);
await generatePreview(abs, chatWd, previewOpts, {
customFilename: existing.preview,
force: true,
debug
});
}
}
const nextTurnId = normalizedTurnIdByAbs[normalizeAbs(abs)];
let existingWidth = existing.width;
let existingHeight = existing.height;
if (existingWidth == null || existingHeight == null) {
try {
const origBuf = await fs.promises.readFile(abs);
const dims = await getSize(origBuf);
if (dims.width > 0 && dims.height > 0) {
existingWidth = dims.width;
existingHeight = dims.height;
}
} catch (e) {
if (debug)
console.warn(
`Batch import: failed to measure dims for a${existing.a}:`,
e.message
);
}
}
imported.push({
...existing,
// Keep stable `a`
a: typeof existing.a === "number" && Number.isFinite(existing.a) && existing.a > 0 ? existing.a : allocateA(),
turnId: typeof nextTurnId === "number" ? nextTurnId : existing.turnId,
width: existingWidth,
height: existingHeight
});
if (debug)
console.info(
`Batch import: reusing existing attachment as a${existing.a}: ${path.basename(abs)}`
);
continue;
}
const origin = path.basename(abs);
let previewName = void 0;
if (i < Math.max(0, Math.floor(maxPreviewAttachments))) {
previewName = await generatePreview(abs, chatWd, previewOpts, { debug }) ?? void 0;
}
if (!previewName) {
const candidate = previewFilenameFrom(path.basename(abs));
if (fs.existsSync(path.join(chatWd, candidate))) {
previewName = candidate;
}
}
let originalName = void 0;
const lmHome = findLMStudioHome();
const userFilesDir = path.join(lmHome, "user-files");
if (abs.startsWith(userFilesDir)) {
const fileIdentifier = path.basename(abs);
const resolvedName = await getOriginalFileName(fileIdentifier);
if (!resolvedName || !resolvedName.trim()) {
throw new Error(
`Missing originalName in LM Studio metadata for fileIdentifier='${fileIdentifier}' (abs='${abs}')`
);
}
originalName = resolvedName;
if (debug)
console.info(
`Resolved original filename: ${fileIdentifier} \u2192 ${originalName}`
);
} else {
originalName = path.basename(abs);
}
let origWidth;
let origHeight;
try {
const origBuf = await fs.promises.readFile(abs);
const dims = await getSize(origBuf);
if (dims.width > 0 && dims.height > 0) {
origWidth = dims.width;
origHeight = dims.height;
}
} catch (e) {
if (debug)
console.warn(
`Batch import: failed to measure dims for new attachment ${path.basename(abs)}:`,
e.message
);
}
imported.push({
origin,
originAbs: abs,
originalName,
turnId: normalizedTurnIdByAbs[normalizeAbs(abs)],
preview: previewName,
width: origWidth,
height: origHeight,
createdAt: localTimestamp(),
a: allocateA()
// Stable, monotonically increasing id
});
if (debug)
console.info(
`Batch import: new attachment as a${imported[imported.length - 1].a}: ${path.basename(abs)}`
);
}
if (imported.length === 0) {
if (debug) console.warn("Batch import: no valid attachments imported");
return { changed: false };
}
state.attachments = imported;
state.counters.nextAttachmentA = nextA;
state.lastEvent = { type: "attachment", at: localTimestamp() };
await writeStateAtomic(chatWd, state);
if (debug)
console.info(
`Batch imported ${imported.length} attachment(s) from SSOT (replaced array)`
);
return { changed: true };
}
// src/helpers/attachmentSync.ts
var DEFAULT_PREVIEW_OPTS = getDefaultPreviewOptions();
async function syncAttachmentsToState(workingDir, debug = false, maxPreviewAttachments = 0, previewOpts) {
const state = await readState$1(workingDir);
const { found, turnIdByAbs } = await findAllAttachmentsLegacy(
workingDir,
debug
);
const opts = maxPreviewAttachments > 0 ? DEFAULT_PREVIEW_OPTS : { maxDim: 0, quality: 0 };
const result = await importAttachmentBatch(
workingDir,
state,
found,
turnIdByAbs,
opts,
maxPreviewAttachments,
debug
);
return { changed: result.changed };
}
function buildPaths() {
const logsDir = getLogsDir();
const filePath = path.resolve(logsDir, getPluginLogFilename().replace(/\.log$/, ".audit.jsonl"));
return { logsDir, filePath };
}
function localTimestamp2() {
try {
return (/* @__PURE__ */ new Date()).toLocaleString(void 0, {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: false,
timeZoneName: "short"
});
} catch {
return (/* @__PURE__ */ new Date()).toString();
}
}
function buildAuditLogger({
backend,
mode,
requestId: providedRequestId
}) {
const requestId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
const entry = {
timestamp: localTimestamp2(),
requestId,
backend,
mode
};
function setChatId(id) {
if (typeof id === "string" && id.trim().length > 0) entry.chat_id = id;
}
function setUserRequest(req) {
entry.user_request = req;
}
function setRenderTarget(target) {
entry.render_target = target;
}
function setInputs(inputs) {
entry.inputs = inputs;
}
function setOutput(output) {
entry.output = { ...entry.output, ...output };
}
function setError(err) {
let message = "unknown error";
let status = void 0;
if (typeof err === "string") message = err;
else if (err && typeof err === "object") {
const anyErr = err;
message = anyErr.message || JSON.stringify(anyErr);
if (typeof anyErr.status === "number") status = anyErr.status;
}
entry.error = status ? { message, status } : { message };
}
async function write() {
try {
const { logsDir, filePath } = buildPaths();
await fs.promises.mkdir(logsDir, { recursive: true });
const block = JSON.stringify(entry, null, 2) + "\n\n";
await fs.promises.appendFile(filePath, block, { encoding: "utf8" });
} catch (e) {
console.error(
"auditLog write failed:",
e instanceof Error ? e.message : String(e)
);
}
}
return {
requestId,
setChatId,
setUserRequest,
setRenderTarget,
setInputs,
setOutput,
setError,
write
};
}
// src/helpers/cameraImageMetadata.ts
var import_exifr = __toESM(require_full_umd());
var EXIF_FIELDS = [
"DateTimeOriginal",
"CreateDate",
"ModifyDate",
"Make",
"Model",
"ImageWidth",
"ImageHeight",
"ExifImageWidth",
"ExifImageHeight",
"GPSLatitude",
"GPSLatitudeRef",
"GPSLongitude",
"GPSLongitudeRef",
"LensModel",
"ExposureTime",
"FNumber",
"ISO",
"ISOSpeedRatings",
"ExposureCompensation",
"FocalLength",
"FocalLengthIn35mmFormat",
"ExposureProgram",
"MeteringMode",
"WhiteBalance",
"Flash",
"Orientation",
"ExposureMode"
];
function nonEmptyString(value) {
return typeof value === "string" && value.trim() ? value.trim() : void 0;
}
function finitePositiveNumber(value) {
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
}
function finiteNumber(value) {
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
}
function scalarString(value) {
if (typeof value === "string") return nonEmptyString(value);
if (typeof value === "number" && Number.isFinite(value)) return String(value);
return void 0;
}
function finiteCoordinate(value) {
return typeof value === "number" && Number.isFinite(value) && value >= -180 && value <= 180 ? value : void 0;
}
function decimalCoordinate(value, reference) {
const decimal = Array.isArray(value) ? value.length === 3 && value.every((part) => typeof part === "number" && Number.isFinite(part)) ? value[0] + value[1] / 60 + value[2] / 3600 : void 0 : finiteCoordinate(value);
if (decimal === void 0) return void 0;
const direction = nonEmptyString(reference)?.toUpperCase();
return direction === "S" || direction === "W" ? -decimal : decimal;
}
function isoTimestamp(value) {
if (!(value instanceof Date) || Number.isNaN(value.getTime())) return void 0;
return value.toISOString();
}
async function readCameraImageMetadata(input) {
try {
const bytes = typeof input === "string" ? await promises.readFile(input) : input;
const fields = await import_exifr.default.parse(bytes, { pick: EXIF_FIELDS });
if (!fields) return {};
const make = nonEmptyString(fields.Make);
const model = nonEmptyString(fields.Model);
const camera = [make, model].filter((value, index, values) => value && values.indexOf(value) === index).join(" ") || void 0;
return {
capturedAt: isoTimestamp(fields.DateTimeOriginal) ?? isoTimestamp(fields.CreateDate) ?? isoTimestamp(fields.ModifyDate),
camera,
width: finitePositiveNumber(fields.ExifImageWidth) ?? finitePositiveNumber(fields.ImageWidth),
height: finitePositiveNumber(fields.ExifImageHeight) ?? finitePositiveNumber(fields.ImageHeight),
latitude: decimalCoordinate(fields.GPSLatitude, fields.GPSLatitudeRef),
longitude: decimalCoordinate(fields.GPSLongitude, fields.GPSLongitudeRef),
lensModel: nonEmptyString(fields.LensModel),
exposureTime: scalarString(fields.ExposureTime),
fNumber: finitePositiveNumber(fields.FNumber),
iso: finitePositiveNumber(fields.ISO) ?? finitePositiveNumber(fields.ISOSpeedRatings),
exposureCompensation: finiteNumber(fields.ExposureCompensation),
focalLength: finitePositiveNumber(fields.FocalLength),
focalLength35mm: finitePositiveNumber(fields.FocalLengthIn35mmFormat),
exposureProgram: scalarString(fields.ExposureProgram),
meteringMode: scalarString(fields.MeteringMode),
whiteBalance: scalarString(fields.WhiteBalance),
flash: scalarString(fields.Flash),
orientation: scalarString(fields.Orientation),
exposureMode: scalarString(fields.ExposureMode)
};
} catch {
return {};
}
}
var DEFAULT_HOST = "127.0.0.1";
function envPort() {
const v = process.env.HTTP_SERVER_PORT;
if (v == null || String(v).trim() === "") return void 0;
const n = Number(v);
return Number.isInteger(n) && n >= 1024 && n <= 65535 ? n : void 0;
}
async function healthCheck(port, host = DEFAULT_HOST) {
return new Promise((resolve) => {
const req = http.get(
{ host, port, path: "/__healthz", timeout: 600 },
(res) => {
try {
const ok = (res.statusCode || 0) === 200 && String(res.headers["x-mcp-image-server"]) === "1";
res.resume();
res.once("end", () => resolve(ok));
} catch {
resolve(false);
}
}
);
req.on("timeout", () => {
try {
req.destroy();
} catch {
}
resolve(false);
});
req.on("error", () => resolve(false));
});
}
function toHttpOriginalUrl(fileName, baseUrl, chatId) {
if (chatId) {
return `${baseUrl.replace(/\/$/, "")}/${encodeURIComponent(
chatId
)}/${encodeURIComponent(fileName)}`;
}
return `${baseUrl.replace(/\/$/, "")}/${encodeURIComponent(fileName)}`;
}
function toHttpPreviewUrl(fileName, baseUrl, chatId) {
if (chatId) {
return `${baseUrl.replace(/\/$/, "")}/${encodeURIComponent(
chatId
)}/${encodeURIComponent(fileName)}`;
}
return `${baseUrl.replace(/\/$/, "")}/previews/${encodeURIComponent(
fileName
)}`;
}
async function getHealthyServerBaseUrl(host = DEFAULT_HOST) {
try {
const fixedPort = envPort();
if (fixedPort == null) return "";
const ok = await healthCheck(fixedPort, host).catch(() => false);
if (ok) return `http://127.0.0.1:${fixedPort}`;
return "";
} catch {
return "";
}
}
function scoreToConfidence(score) {
if (score >= 2) return "high";
if (score === 1) return "medium";
return "low";
}
async function sleep(ms) {
return new Promise((r) => setTimeout(r, ms));
}
async function resolveActiveLMStudioChatId(opts) {
const retries = 4;
const delayMs = 200;
const recentSec = 120;
try {
const home = findLMStudioHome();
const convDir = path.join(home, "conversations");
if (!fs.existsSync(convDir)) {
return {
ok: false,
reason: `LM Studio conversations dir not found: ${convDir}`
};
}
let chosenPath = null;
let mtimeMs = 0;
for (let attempt = 0; attempt < Math.max(1, retries); attempt++) {
const entries = await fs.promises.readdir(convDir).catch(() => []);
const convFiles = entries.filter((f) => f.endsWith(".conversation.json")).map((f) => path.join(convDir, f));
if (convFiles.length === 0) {
if (attempt < retries - 1) {
await sleep(delayMs);
continue;
}
return { ok: false, reason: "No conversation files found" };
}
const withTimes = convFiles.map((p) => {
try {
const s = fs.statSync(p);
return s.isFile() ? { p, t: s.mtimeMs } : null;
} catch {
return null;
}
}).filter(Boolean);
if (withTimes.length === 0) {
if (attempt < retries - 1) {
await sleep(delayMs);
continue;
}
return { ok: false, reason: "No readable conversation files" };
}
withTimes.sort((a, b) => b.t - a.t);
chosenPath = withTimes[0].p;
mtimeMs = withTimes[0].t;
try {
const raw = await fs.promises.readFile(chosenPath, "utf8");
JSON.parse(raw);
break;
} catch {
if (attempt < retries - 1) {
await sleep(delayMs);
continue;
}
break;
}
}
if (!chosenPath)
return { ok: false, reason: "Failed to pick conversation" };
const chatId = path.basename(chosenPath).replace(/\.conversation\.json$/i, "");
let score = 0;
let reason = [];
if (mtimeMs > 0) {
const ageSec = (Date.now() - mtimeMs) / 1e3;
if (ageSec <= recentSec) {
score += 1;
reason.push(`recent:${Math.round(ageSec)}s`);
} else {
reason.push(`stale:${Math.round(ageSec)}s`);
}
}
try {
const raw = await fs.promises.readFile(chosenPath, "utf8");
JSON.parse(raw);
score += 1;
reason.push("parse_ok");
} catch {
reason.push("parse_uncertain");
}
return {
ok: true,
chatId,
filePath: chosenPath,
mtimeMs,
confidence: scoreToConfidence(score),
reason: reason.join(",")
};
} catch (e) {
return { ok: false, reason: e?.message || String(e) };
}
}
// src/helpers/resolveImg2ImgSourceLMStudio.ts
var LM_HOME = path.join(os.homedir(), ".lmstudio");
try {
const h = findLMStudioHome();
if (h && typeof h === "string") LM_HOME = h;
} catch {
}
util.promisify(child_process.exec);
// src/interfaces/control.ts
__toESM(require_flatbuffers());
// src/interfaces/generation-configuration.ts
__toESM(require_flatbuffers());
// src/interfaces/lo-ra.ts
__toESM(require_flatbuffers());
// src/interfaces/tensor-history-node.ts
__toESM(require_flatbuffers());
// src/interfaces/text-history-node.ts
__toESM(require_flatbuffers());
var TOOL_MIN_RENDER_DIM = drawthingsLimits.min;
var TOOL_MAX_WIDTH = drawthingsLimits.maxWidth;
var TOOL_MAX_HEIGHT = drawthingsLimits.maxHeight;
var TOOL_MAX_PREVIEW_W = drawthingsLimits.maxWidth;
var ZOOM_TOOL_MAX_DIM = 2048;
function normalizeQualityToInt(q, def) {
let n = typeof q === "string" ? parseFloat(q) : typeof q === "number" ? q : def;
if (!Number.isFinite(n)) n = def;
if (n > 0 && n <= 1) n = n * 100;
n = Math.round(n);
if (n < 1) n = 1;
if (n > 100) n = 100;
return n;
}
var GenerateToolParamsSchemaBase = zod.z.object({
prompt: zod.z.string().optional(),
negative_prompt: zod.z.string().optional(),
width: zod.z.coerce.number().int().min(TOOL_MIN_RENDER_DIM, `width must be >= ${TOOL_MIN_RENDER_DIM}`).max(TOOL_MAX_WIDTH, `width must be <= ${TOOL_MAX_WIDTH}`).optional(),
height: zod.z.coerce.number().int().min(TOOL_MIN_RENDER_DIM, `height must be >= ${TOOL_MIN_RENDER_DIM}`).max(TOOL_MAX_HEIGHT, `height must be <= ${TOOL_MAX_HEIGHT}`).optional(),
steps: zod.z.coerce.number().int().min(1, "steps must be >= 1").max(50, "steps must be <= 50").optional(),
seed: zod.z.coerce.number().int().optional(),
guidance_scale: zod.z.coerce.number().min(0, "guidance_scale must be >= 0").max(50, "guidance_scale must be <= 50").optional(),
model: zod.z.string().optional(),
sampler: zod.z.union([zod.z.string(), zod.z.number()]).optional(),
numFrames: zod.z.coerce.number().int().min(1, "numFrames must be >= 1").max(641, "numFrames must be <= 641").optional(),
random_string: zod.z.string().optional(),
// preview controls (validation only; defaults applied by caller)
previewFormat: zod.z.enum(["jpeg", "webp"], {
errorMap: () => ({ message: "previewFormat must be 'jpeg' or 'webp'" })
}).optional(),
previewMaxWidth: zod.z.coerce.number().int().min(128).max(TOOL_MAX_PREVIEW_W).optional(),
previewMinWidth: zod.z.coerce.number().int().min(128).max(TOOL_MAX_PREVIEW_W).optional(),
previewMaxBytes: zod.z.coerce.number().int().min(2e3).max(2e5).optional(),
previewQuality: zod.z.union([zod.z.coerce.number(), zod.z.string()]).transform((v) => normalizeQualityToInt(v, NaN)).optional(),
previewMinQuality: zod.z.union([zod.z.coerce.number(), zod.z.string()]).transform((v) => normalizeQualityToInt(v, NaN)).optional(),
previewQualityStep: zod.z.coerce.number().int().min(1).max(20).optional(),
previewScaleStep: zod.z.coerce.number().min(0.5).max(0.98).optional(),
previewInChat: zod.z.coerce.boolean().optional(),
alt: zod.z.string().max(120).optional(),
// saving
saveOriginal: zod.z.coerce.boolean().optional(),
saveDir: zod.z.string().optional()
}).passthrough();
GenerateToolParamsSchemaBase.superRefine((d, ctx) => {
if (d.previewMinWidth !== void 0 && d.previewMaxWidth !== void 0 && d.previewMinWidth > d.previewMaxWidth) {
ctx.addIssue({
code: "custom",
path: ["previewMinWidth"],
message: "previewMinWidth must be <= previewMaxWidth"
});
}
if (d.previewMinQuality !== void 0 && d.previewQuality !== void 0 && d.previewMinQuality > d.previewQuality) {
ctx.addIssue({
code: "custom",
path: ["previewMinQuality"],
message: "previewMinQuality must be <= previewQuality"
});
}
});
zod.z.union([
zod.z.string().transform(
(s) => s.split(/[\s,]+/).map((x) => parseInt(x.trim(), 10)).filter((n) => Number.isInteger(n) && n >= 1)
),
zod.z.coerce.number().int().min(1).transform((n) => [n]),
zod.z.array(zod.z.coerce.number().int().min(1))
]).optional();
function normalizeSourceToken(raw) {
let s = String(raw ?? "").trim();
if (!s) return "";
s = s.replace(/^[\[{(]+/, "").replace(/[\]})]+$/, "");
s = s.replace(/^['"`]+|['"`]+$/g, "");
s = s.replace(/[;:.!?]+$/, "");
s = s.trim();
if (/^a$/i.test(s)) return "a1";
if (/^v$/i.test(s)) return "v1";
if (/^p$/i.test(s)) return "p1";
if (/^i$/i.test(s)) return "i1";
return s;
}
var SourceNotation = zod.z.preprocess(
(v) => typeof v === "string" ? normalizeSourceToken(v) : v,
zod.z.string().trim().regex(
/^([avpi]|[avpi]?[1-9]\d*)$/i,
"Source notation: 'a1', 'v2', 'p1', 'i3', or digit when unambiguous"
)
);
var SourceNotationList = zod.z.union([
// String form: split by comma/space and validate each part
zod.z.string().transform(
(s) => s.split(/[\s,]+/).map((x) => normalizeSourceToken(x)).filter((x) => x.length > 0)
),
// Array form: validate each element
zod.z.array(SourceNotation)
]).refine(
(arr) => arr.every((x) => /^([avpi]|[avpi]?[1-9]\d*)$/i.test(String(x))),
"moodboard contains invalid source notation(s)"
);
var GenerateToolParamsShapeMinimal = {
prompt: zod.z.string().optional().describe(
"Image description (mode: 'text2image') OR description of desired changes (mode: 'image2image')."
),
width: zod.z.coerce.number().int().min(TOOL_MIN_RENDER_DIM).max(TOOL_MAX_WIDTH).optional().describe(`Width in pixels (max ${TOOL_MAX_WIDTH}). Has sensible default.`),
height: zod.z.coerce.number().int().min(TOOL_MIN_RENDER_DIM).max(TOOL_MAX_HEIGHT).optional().describe(`Height in pixels (max ${TOOL_MAX_HEIGHT}). Has sensible default.`),
imageFormat: zod.z.enum(["square", "landscape", "portrait", "16:9"]).optional().describe("Aspect ratio shorthand. Override if context suggests. '16:9' yields 1024\xD7576 (video-optimized)."),
quality: zod.z.enum(["low", "medium", "high", "auto"]).optional().describe("Quality preset (affects steps). Default is balanced."),
// model preset selection — default 'auto' uses built-in settings
model: zod.z.string().optional().describe("Model preset. 'ltx' selects LTX-2 for video generation. Default 'auto' selects best model for the mode."),
// number of images to generate in one call
variants: zod.z.coerce.number().int().min(1).max(4).optional().describe("Number of images (1-4). Default is 1."),
// number of video frames
numFrames: zod.z.coerce.number().int().min(1, "numFrames must be >= 1").max(641, "numFrames must be <= 641").optional().describe("Number of video frames. Must be a multiple of 32 (or multiple of 32 + 1). Default: 1 (image). Silently ignored for non-video modes."),
// image2image controls (only allow selecting a prior variant)
mode: zod.z.enum(["text2image", "image2image", "edit", "text2video", "image2video", "refine"]).optional().describe("Generation mode. 'text2video'/'image2video' require a video-capable model (e.g. 'ltx'). Required when sources exist."),
// Primary source for image2image/edit
canvas: zod.z.union([
SourceNotation,
zod.z.array(SourceNotation).min(1).max(1).transform((arr) => arr[0])
]).optional().describe(
"Primary source image. Notation: 'a1', 'v2', 'p1', 'i3'. Digit-only allowed only when unambiguous. Tolerates single-item array input."
),
// Additional style references for image2image/edit modes (gRPC only for image2image)
moodboard: SourceNotationList.optional().describe(
"Additional style references for image2image/edit modes. Array of source notations (same format as canvas). Note: moodboard for image2image requires gRPC transport."
)
};
zod.z.object(GenerateToolParamsShapeMinimal).strict();
zod.z.union([zod.z.number(), zod.z.string()]).optional();
var cropSideOrNullField = zod.z.union([zod.z.number(), zod.z.string()]).nullable();
var cropSideOverrideArray = zod.z.array(cropSideOrNullField).optional();
var cropSideOrArrayBase = zod.z.preprocess(
(val) => {
if (typeof val === "string") {
const t = val.trim();
if (t.startsWith("[")) {
try {
const parsed = JSON.parse(t);
if (Array.isArray(parsed)) {
return parsed.map((el) => {
if (typeof el === "string") {
if (el.trim().toLowerCase() === "null") return null;
const n = Number(el.trim());
if (!isNaN(n)) return n;
}
return el;
});
}
} catch {
}
}
}
return val;
},
zod.z.union([zod.z.number(), zod.z.string(), cropSideOverrideArray.unwrap()])
);
cropSideOrArrayBase.optional();
var detectLabelBase = zod.z.preprocess(
(val) => {
if (typeof val === "string") {
const t = val.trim();
if (t.startsWith("[")) {
try {
const parsed = JSON.parse(t);
if (Array.isArray(parsed)) {
return parsed.map((el) => String(el).trim()).filter((s) => s.length > 0);
}
} catch {
}
}
}
return val;
},
zod.z.union([
zod.z.string().transform(
(s) => s.split(/\s*,\s*/).map((x) => x.trim()).filter((x) => x.length > 0)
),
zod.z.array(zod.z.string().min(1))
])
);
var CropToolParamsShape = {
canvas: zod.z.union([
SourceNotation,
zod.z.array(SourceNotation).min(1).max(1).transform((arr) => arr[0])
]).optional().describe(
"Source image. Notation: 'a1', 'v2', 'p1', 'i3'. Tolerates single-item array input."
),
cropLeft: zod.z.union([zod.z.number(), zod.z.string(), zod.z.array(zod.z.union([zod.z.number(), zod.z.string()]).nullable())]).optional().describe(
"Amount to remove from the left side. Scalar (single-region): number or px-string, e.g. '120px'. Array (multi-region, parallel to detectLabel): one entry per label; null = keep detection value. Default unit: % (0\u201399)."
),
cropRight: zod.z.union([zod.z.number(), zod.z.string(), zod.z.array(zod.z.union([zod.z.number(), zod.z.string()]).nullable())]).optional().describe(
"Amount to remove from the right side. Scalar or per-box array (null = keep detection value). Default unit: % (0\u201399)."
),
cropTop: zod.z.union([zod.z.number(), zod.z.string(), zod.z.array(zod.z.union([zod.z.number(), zod.z.string()]).nullable())]).optional().describe(
"Amount to remove from the top. Scalar or per-box array (null = keep detection value). Default unit: % (0\u201399)."
),
cropBottom: zod.z.union([zod.z.number(), zod.z.string(), zod.z.array(zod.z.union([zod.z.number(), zod.z.string()]).nullable())]).optional().describe(
"Amount to remove from the bottom. Scalar or per-box array (null = keep detection value). Default unit: % (0\u201399)."
),
imageFormat: zod.z.enum(["square", "landscape", "portrait", "16:9"]).optional().describe(
"Target aspect ratio. Only active when explicitly set; omitting it crops with the given sides only (no AR enforcement). Ignored when all 4 crop sides are explicitly given. Unspecified axes are centred; single-side anchors are honoured. 'square'=1:1, 'landscape'=4:3, 'portrait'=3:4, '16:9'=16:9."
),
detectLabel: detectLabelBase.optional().describe(
`Label (or array of labels) of detected objects to mask/crop (requires a prior detect_object run). Array form enables multi-region: one bbox per entry, parallel to detectIndex and per-box crop overrides. String form: comma-separated labels, e.g. "cat, dog". Multi-word labels are supported. canvas may be the original source (e.g. 'a1') or the detect_object result (e.g. 'i3').`
),
detectIndex: zod.z.union([
zod.z.string().transform((s) => (s.match(/\d+/g) ?? []).map(Number)),
zod.z.coerce.number().int().min(0).transform((n) => [n]),
zod.z.array(zod.z.coerce.number().int().min(0))
]).optional().describe(
"Zero-based index (or array of indices, parallel to detectLabel) to select among multiple detections with the same label. Default 0 for each entry."
),
frameAdjust: zod.z.union([zod.z.number(), zod.z.string()]).optional().describe(
"Expand (positive) or shrink (negative) each detection bounding box. Applied as the final step after all overrides. Number: percent of bbox diagonal. String: value + optional 'px' suffix, e.g. '20px' or '-10%'. Applies to all regions uniformly."
)
};
zod.z.object(CropToolParamsShape).strict();
var ZoomInToolParamsShape = {
prompt: zod.z.string().optional().describe("Description of desired changes to apply during re-rendering. Default: empty (preserve content)."),
width: zod.z.coerce.number().int().min(TOOL_MIN_RENDER_DIM).max(ZOOM_TOOL_MAX_DIM).optional().describe(`Output width in pixels (max ${ZOOM_TOOL_MAX_DIM}). Has sensible default.`),
height: zod.z.coerce.number().int().min(TOOL_MIN_RENDER_DIM).max(ZOOM_TOOL_MAX_DIM).optional().describe(`Output height in pixels (max ${ZOOM_TOOL_MAX_DIM}). Has sensible default.`),
quality: zod.z.enum(["low", "medium", "high", "auto"]).optional().describe("Quality preset (affects steps). Default is balanced."),
model: zod.z.string().optional().describe("Model preset for image2image re-rendering. Default 'auto'."),
canvas: zod.z.union([
SourceNotation,
zod.z.array(SourceNotation).min(1).max(1).transform((arr) => arr[0])
]).optional().describe(
"Source image. Notation: 'a1', 'v2', 'p1', 'i3'. Tolerates single-item array input."
),
detectLabel: zod.z.string().optional().describe(
"Crop to the bounding box of a detected object by label (requires a prior detect_object run). canvas may be the original source (e.g. 'a1') or the detect_object result (e.g. 'i3')."
),
detectIndex: zod.z.coerce.number().int().min(0).optional().describe("Zero-based index to select among multiple detections with the same label. Default 0."),
frameAdjust: zod.z.union([zod.z.number(), zod.z.string()]).optional().describe(
"Expand (positive) or shrink (negative) the detection bounding box before cropping. Number: percent of min(W,H). String: value + optional 'px' suffix, e.g. '20px' or '-10%'."
),
imageFormat: zod.z.enum(["square", "landscape", "portrait", "16:9"]).optional().describe(
"Target aspect ratio for the render output. Only active when explicitly set. 'square'=1:1, 'landscape'=4:3, 'portrait'=3:4, '16:9'=16:9."
)
};
zod.z.object(ZoomInToolParamsShape).strict();
var InpaintToolParamsShape = {
prompt: zod.z.string().optional().describe("Description of desired changes to apply during re-rendering. Default: empty (preserve content)."),
width: zod.z.coerce.number().int().min(TOOL_MIN_RENDER_DIM).max(ZOOM_TOOL_MAX_DIM).optional().describe(`Output width in pixels (max ${ZOOM_TOOL_MAX_DIM}). Has sensible default.`),
height: zod.z.coerce.number().int().min(TOOL_MIN_RENDER_DIM).max(ZOOM_TOOL_MAX_DIM).optional().describe(`Output height in pixels (max ${ZOOM_TOOL_MAX_DIM}). Has sensible default.`),
quality: zod.z.enum(["low", "medium", "high", "auto"]).optional().describe("Quality preset (affects steps). Default is balanced."),
model: zod.z.string().optional().describe("Model preset for image2image re-rendering. Default 'auto'."),
canvas: zod.z.union([
SourceNotation,
zod.z.array(SourceNotation).min(1).max(1).transform((arr) => arr[0])
]).optional().describe(
"Source image. Notation: 'a1', 'v2', 'p1', 'i3'. Tolerates single-item array input."
),
detectLabel: detectLabelBase.optional().describe(
`Label (or array of labels) of detected objects to inpaint. Preferred: array, e.g. ["left eye", "right eye"]. Also accepts comma-separated string, e.g. "left eye, right eye". Multi-word labels are supported. Each entry selects one detection bounding box. Requires a prior detect_object run. canvas may be the source (e.g. 'a1') or the detect_object result (e.g. 'i3').`
),
detectIndex: zod.z.union([
zod.z.string().transform((s) => (s.match(/\d+/g) ?? []).map(Number)),
zod.z.coerce.number().int().min(0).transform((n) => [n]),
zod.z.array(zod.z.coerce.number().int().min(0))
]).optional().describe(
"Zero-based index (or array of indices, parallel to detectLabel) to select among multiple detections with the same label. Default 0 for each entry."
),
frameAdjust: zod.z.union([zod.z.number(), zod.z.string()]).optional().describe(
"Expand (positive) or shrink (negative) each detection bounding box. Applied as the final step after all per-box overrides. Applies uniformly to all regions. Number: percent of bbox diagonal. String: value + optional 'px' suffix, e.g. '20px' or '-10%'."
),
cropLeft: cropSideOrArrayBase.optional().describe(
"Override the left crop side for the detected region(s). Scalar = same for all; array (parallel to detectLabel, null = keep detection value) for per-box control. Default unit: % (0\u201399) or append 'px'."
),
cropRight: cropSideOrArrayBase.optional().describe(
"Override the right crop side. Scalar or per-box array (null = keep detection value). Default unit: %."
),
cropTop: cropSideOrArrayBase.optional().describe(
"Override the top crop side. Scalar or per-box array (null = keep detection value). Default unit: %."
),
cropBottom: cropSideOrArrayBase.optional().describe(
"Override the bottom crop side. Scalar or per-box array (null = keep detection value). Default unit: %."
)
};
zod.z.object(InpaintToolParamsShape).strict();
var OutpaintToolParamsShape = {
prompt: zod.z.string().optional().describe("Description of desired changes to apply during re-rendering. Default: empty (preserve content)."),
width: zod.z.coerce.number().int().min(TOOL_MIN_RENDER_DIM).max(ZOOM_TOOL_MAX_DIM).optional().describe(`Output width in pixels (max ${ZOOM_TOOL_MAX_DIM}). Has sensible default.`),
height: zod.z.coerce.number().int().min(TOOL_MIN_RENDER_DIM).max(ZOOM_TOOL_MAX_DIM).optional().describe(`Output height in pixels (max ${ZOOM_TOOL_MAX_DIM}). Has sensible default.`),
quality: zod.z.enum(["low", "medium", "high", "auto"]).optional().describe("Quality preset (affects steps). Default is balanced."),
model: zod.z.string().optional().describe("Model preset for image2image re-rendering. Default 'auto'."),
canvas: zod.z.union([
SourceNotation,
zod.z.array(SourceNotation).min(1).max(1).transform((arr) => arr[0])
]).optional().describe(
"Source image. Notation: 'a1', 'v2', 'p1', 'i3'. Tolerates single-item array input."
),
detectLabel: detectLabelBase.optional().describe(
`Label (or array of labels) of detected objects to outpaint. Preferred: array, e.g. ["left eye", "right eye"]. Also accepts comma-separated string, e.g. "left eye, right eye". Multi-word labels are supported. Each entry selects one detection bounding box. Requires a prior detect_object run. canvas may be the source (e.g. 'a1') or the detect_object result (e.g. 'i3').`
),
detectIndex: zod.z.union([
zod.z.string().transform((s) => (s.match(/\d+/g) ?? []).map(Number)),
zod.z.coerce.number().int().min(0).transform((n) => [n]),
zod.z.array(zod.z.coerce.number().int().min(0))
]).optional().describe(
"Zero-based index (or array of indices, parallel to detectLabel) to select among multiple detections with the same label. Default 0 for each entry."
),
frameAdjust: zod.z.union([zod.z.number(), zod.z.string()]).optional().describe(
"Expand (positive) or shrink (negative) each detection bounding box. Applied as the final step after all per-box overrides. Applies uniformly to all regions. Number: percent of bbox diagonal. String: value + optional 'px' suffix, e.g. '20px' or '-10%'."
),
cropLeft: cropSideOrArrayBase.optional().describe(
"Override the left crop side for the detected region(s). Scalar = same for all; array (parallel to detectLabel, null = keep detection value) for per-box control. Default unit: % (0\u201399) or append 'px'."
),
cropRight: cropSideOrArrayBase.optional().describe(
"Override the right crop side. Scalar or per-box array (null = keep detection value). Default unit: %."
),
cropTop: cropSideOrArrayBase.optional().describe(
"Override the top crop side. Scalar or per-box array (null = keep detection value). Default unit: %."
),
cropBottom: cropSideOrArrayBase.optional().describe(
"Override the bottom crop side. Scalar or per-box array (null = keep detection value). Default unit: %."
)
};
zod.z.object(OutpaintToolParamsShape).strict();
var UpscaleToolParamsShape = {
prompt: zod.z.string().optional().describe("Description of desired changes to apply during re-rendering. Default: empty (preserve content)."),
width: zod.z.coerce.number().int().min(TOOL_MIN_RENDER_DIM).max(ZOOM_TOOL_MAX_DIM).optional().describe(`Output width in pixels (max ${ZOOM_TOOL_MAX_DIM}). Has sensible default.`),
height: zod.z.coerce.number().int().min(TOOL_MIN_RENDER_DIM).max(ZOOM_TOOL_MAX_DIM).optional().describe(`Output height in pixels (max ${ZOOM_TOOL_MAX_DIM}). Has sensible default.`),
scaleFactor: zod.z.number().positive().optional().describe("Scale factor applied to the canvas dimensions. E.g. 2 doubles the resolution. Mutually exclusive with width/height."),
quality: zod.z.enum(["low", "medium", "high", "auto"]).optional().describe("Quality preset (affects steps). Default is balanced."),
model: zod.z.string().optional().describe("Model preset for image2image re-rendering. Default 'auto'."),
canvas: zod.z.union([
SourceNotation,
zod.z.array(SourceNotation).min(1).max(1).transform((arr) => arr[0])
]).optional().describe(
"Source image. Notation: 'a1', 'v2', 'p1', 'i3'. Tolerates single-item array input."
),
imageFormat: zod.z.string().optional().describe("Target image format / aspect ratio preset (e.g. '16:9', 'portrait'). Resolved to concrete pixel dimensions.")
};
zod.z.object(UpscaleToolParamsShape).strict();
var RefineToolParamsShape = {
canvas: zod.z.union([
SourceNotation,
zod.z.array(SourceNotation).min(1).max(1).transform((arr) => arr[0])
]).optional().describe(
"Source image. Notation: 'a1', 'v2', 'p1', 'i3'. Tolerates single-item array input."
),
model: zod.z.string().describe(
"Required. Model preset to use for refinement. model: z-image produces a polished, refined look. model: krea or model: krea2 uses Krea 2 Turbo. model: qwen-image or model: flux produces a more natural, organic look."
),
width: zod.z.coerce.number().int().min(TOOL_MIN_RENDER_DIM).max(ZOOM_TOOL_MAX_DIM).optional().describe(`Output width in pixels (max ${ZOOM_TOOL_MAX_DIM}). Defaults to canvas width.`),
height: zod.z.coerce.number().int().min(TOOL_MIN_RENDER_DIM).max(ZOOM_TOOL_MAX_DIM).optional().describe(`Output height in pixels (max ${ZOOM_TOOL_MAX_DIM}). Defaults to canvas height.`),
imageFormat: zod.z.string().optional().describe("Target image format / aspect ratio preset (e.g. '16:9', 'portrait'). Resolved to concrete pixel dimensions.")
};
zod.z.object(RefineToolParamsShape).strict();
zod.z.union([
// String form: split by comma/space
zod.z.string().transform(
(s) => s.split(/[\s,]+/).map((x) => x.trim()).filter((x) => x.length > 0)
),
// Array form: pass through
zod.z.array(zod.z.string())
]).refine((arr) => arr.length >= 1, "targets must contain at least one notation").refine((arr) => arr.length <= 32, "targets must contain at most 32 notations");
({
variant: zod.z.coerce.string().describe(
"Reference to the video to review. Use standard media notation: vN for variants, iN for images, pN for pictures (e.g. v1, i3). A bare integer N is also accepted and treated as vN. Must correspond to a generated video."
),
fps: zod.z.coerce.number().min(0.1).max(30).optional().default(2).describe(
"Frame sampling rate in fps. Default: 2. Higher values send more frames to the model."
)
});
function cssColorToRgbaInt(color){const named={pink:[255,105,180],red:[255,0,0],green:[0,128,0],lime:[0,255,0],blue:[0,0,255],yellow:[255,255,0],cyan:[0,255,255],magenta:[255,0,255],white:[255,255,255],black:[0,0,0],orange:[255,165,0],purple:[128,0,128]};const lower=color.trim().toLowerCase();if(named[lower]){const[r,g,b]=named[lower];return ((r&255)<<24|(g&255)<<16|(b&255)<<8|255)>>>0}const rgb3=lower.match(/^#([0-9a-f])([0-9a-f])([0-9a-f])$/);if(rgb3){const r=parseInt(rgb3[1]+rgb3[1],16);const g=parseInt(rgb3[2]+rgb3[2],16);const b=parseInt(rgb3[3]+rgb3[3],16);return ((r&255)<<24|(g&255)<<16|(b&255)<<8|255)>>>0}const rgb6=lower.match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/);if(rgb6){const r=parseInt(rgb6[1],16);const g=parseInt(rgb6[2],16);const b=parseInt(rgb6[3],16);return ((r&255)<<24|(g&255)<<16|(b&255)<<8|255)>>>0}return ((255&255)<<24|(105&255)<<16|(180&255)<<8|255)>>>0}async function drawBboxesOnImage(buffer,bboxes,options){const sourceDims=options?.sourceDims;const usePalette=options?.palette!==false;const lineWeight=options?.lineWeight??2;const singleColorInt=usePalette?0:cssColorToRgbaInt(options?.color??"pink");const requireFn=typeof require!=="undefined"?require:(await import('module')).createRequire(__filename);const jimpMod=requireFn("jimp");const Jimp=jimpMod.Jimp??jimpMod.default??jimpMod;if(!Jimp||typeof Jimp.read!=="function"){throw new Error("drawBboxesOnImage: Jimp.read not available")}const img=await Jimp.read(buffer);const imgW=typeof img.getWidth==="function"?img.getWidth():typeof img.width==="number"?img.width:img.bitmap?.width||0;const imgH=typeof img.getHeight==="function"?img.getHeight():typeof img.height==="number"?img.height:img.bitmap?.height||0;const scaleX=sourceDims&&sourceDims.width>0?imgW/sourceDims.width:1;const scaleY=sourceDims&&sourceDims.height>0?imgH/sourceDims.height:1;const palette=[[255,59,48,255],[52,199,89,255],[0,122,255,255],[255,159,10,255],[191,90,242,255],[255,214,10,255]];for(let bi=0;bi<bboxes.length;bi++){let colorInt;if(usePalette){const[r,g,b,a]=palette[bi%palette.length];colorInt=((r&255)<<24|(g&255)<<16|(b&255)<<8|a&255)>>>0;}else {colorInt=singleColorInt;}const[bx1,by1,bx2,by2]=bboxes[bi];const x1=Math.max(0,Math.min(imgW-1,Math.round(bx1*scaleX)));const y1=Math.max(0,Math.min(imgH-1,Math.round(by1*scaleY)));const x2=Math.max(0,Math.min(imgW-1,Math.round(bx2*scaleX)));const y2=Math.max(0,Math.min(imgH-1,Math.round(by2*scaleY)));for(let t=0;t<lineWeight;t++){for(let x=x1;x<=x2;x++){if(y1+t<imgH)img.setPixelColor(colorInt,x,y1+t);if(y2-t>=0)img.setPixelColor(colorInt,x,y2-t);}for(let y=y1;y<=y2;y++){if(x1+t<imgW)img.setPixelColor(colorInt,x1+t,y);if(x2-t>=0)img.setPixelColor(colorInt,x2-t,y);}}}const bufResult=typeof img.getBufferAsync==="function"?img.getBufferAsync("image/png"):img.getBuffer("image/png");if(bufResult&&typeof bufResult.then==="function"){return bufResult}return new Promise((resolve,reject)=>img.getBuffer("image/png",(err,data)=>err?reject(err):resolve(data)))}
const CRC_TABLE=(()=>{const table=new Int32Array(256);for(let n=0;n<256;n++){let crc=n;for(let bit=0;bit<8;bit++){crc=crc&1?0xedb88320^crc>>>1:crc>>>1;}table[n]=crc;}return table})();function crc32(data){let crc=0xffffffff;for(let index=0;index<data.length;index++){crc=CRC_TABLE[(crc^data[index])&255]^crc>>>8;}return (crc^0xffffffff)>>>0}function normPath(absolutePath){const home=os$1.homedir();return absolutePath.startsWith(home)?`~${absolutePath.slice(home.length)}`:absolutePath}function escapeXml(value){return value.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function getPngDimensions(pngBuffer){if(pngBuffer.length<24||!pngBuffer.subarray(0,8).equals(Buffer.from([137,80,78,71,13,10,26,10]))||pngBuffer.toString("ascii",12,16)!=="IHDR"){return undefined}return {width:pngBuffer.readUInt32BE(16),height:pngBuffer.readUInt32BE(20)}}function buildXmpString(params){const createdAt=escapeXml(params.createdAt??new Date().toISOString());const metadata={};if(params.prompt!=null)metadata["c"]=params.prompt;if(params.model)metadata["model"]=params.model;if(typeof params.width==="number"&&typeof params.height==="number"){metadata["size"]=`${params.width}x${params.height}`;}if(params.sources?.length)metadata["sources"]=params.sources.map(normPath);if(params.mode)metadata["mode"]=params.mode;if(params.analysis)metadata["analysis"]=params.analysis;metadata["generated_by"]=params.generatedBy??"ceveyne/analyse-image";const description=[];if(params.prompt)description.push(params.prompt);const details=[];if(typeof params.width==="number"&&typeof params.height==="number")details.push(`Size: ${params.width}x${params.height}`);if(params.model)details.push(`Model: ${params.model}`);if(details.length)description.push(details.join(", "));if(params.sources?.length)description.push(`Source: ${params.sources.map(normPath).join(", ")}`);return [`<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 6.0.0">`,` <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">`,` <rdf:Description rdf:about=""`,` xmlns:dc="http://purl.org/dc/elements/1.1/"`,` xmlns:xmp="http://ns.adobe.com/xap/1.0/"`,` xmlns:exif="http://ns.adobe.com/exif/1.0/">`,` <dc:description><rdf:Alt><rdf:li xml:lang="x-default">${escapeXml(description.join("\n"))}</rdf:li></rdf:Alt></dc:description>`,` <xmp:CreatorTool>${escapeXml(params.creatorTool??params.generatedBy??"ceveyne/analyse-image")}</xmp:CreatorTool>`,` <xmp:CreateDate>${createdAt}</xmp:CreateDate>`,` <exif:UserComment><rdf:Alt><rdf:li xml:lang="x-default">${JSON.stringify(metadata)}</rdf:li></rdf:Alt></exif:UserComment>`,` </rdf:Description>`,` </rdf:RDF>`,`</x:xmpmeta>`].join("\n")}function buildITxtChunk(text){const data=Buffer.concat([Buffer.from("XML:com.adobe.xmp","utf8"),Buffer.from([0,0,0,0,0]),Buffer.from(text,"utf8")]);const typeAndData=Buffer.concat([Buffer.from("iTXt","ascii"),data]);const chunk=Buffer.allocUnsafe(12+data.length);chunk.writeUInt32BE(data.length,0);chunk.write("iTXt",4,"ascii");data.copy(chunk,8);chunk.writeUInt32BE(crc32(typeAndData),8+data.length);return chunk}function injectXmpIntoBuffer(pngBuffer,params){const dimensions=getPngDimensions(pngBuffer);if(!dimensions)return pngBuffer;const populatedParams={...params,width:params.width??dimensions.width,height:params.height??dimensions.height};const chunk=buildITxtChunk(buildXmpString(populatedParams));const insertAt=8+25;return Buffer.concat([pngBuffer.subarray(0,insertAt),chunk,pngBuffer.subarray(insertAt)])}
const JSON_FENCE_RE=/```(?:json)?\s*([\s\S]*?)```/i;const ITEM_RE=/\{\s*"bbox_2d":\s*\[(\d+),\s*(\d+),\s*(\d+),\s*(\d+)\],\s*"label":\s*"([^"]+)"\s*\}/gi;const JSON_FORMAT=" Output JSON only — a JSON array where each element has"+" 'bbox_2d' ([x1, y1, x2, y2] as integers normalized 0–1000) and 'label' (a string)."+" No prose, no markdown, no explanation.";const LABEL_FORMAT_RULE="\n\nLABEL FORMAT RULE (mandatory):"+"\n- Labels must be concise and specific: 2–4 words maximum."+"\n- No commas or punctuation inside a label (no ',', '.', ';', ':', '/') — downstream tools split labels on commas."+"\n- Examples: 'plugin list', 'plugin name', 'human face', 'left hand', 'red car', 'fluffy owl toy'";function normalizeLmApiRoot(baseUrl){return String(baseUrl||"").trim().replace(/\/(api\/v1|v1)\/?$/i,"").replace(/\/+$/,"")}function authHeaders(apiKey,contentType=false){const headers={};if(contentType)headers["Content-Type"]="application/json";if(apiKey?.trim())headers.Authorization=`Bearer ${apiKey.trim()}`;return headers}function logVisionRequestMetadata(metadata){const line=`[LmStudioVisionAnalyzer] /api/v1/chat request ${JSON.stringify(metadata)}`;console.info(line);try{const logsDir=getLogsDir();if(!fs.existsSync(logsDir))fs.mkdirSync(logsDir,{recursive:true});fs.appendFileSync(path.join(logsDir,"user-docs-plugin.log"),`${new Date().toISOString()} - ${line}
`,"utf8");}catch{}}function hasLoadedInstances(modelInfo){return Array.isArray(modelInfo?.loaded_instances)&&modelInfo.loaded_instances.length>0}async function getVisionModelState(baseUrl,apiKey,modelKey){const apiRoot=normalizeLmApiRoot(baseUrl);if(!apiRoot)return {loaded:false};const normalizedModelKey=modelKey.trim().toLowerCase();const controller=new AbortController;const timeout=setTimeout(()=>controller.abort(),5e3);try{const response=await fetch(`${apiRoot}/api/v1/models`,{headers:authHeaders(apiKey),signal:controller.signal});if(!response.ok)return {loaded:false};const data=await response.json();const models=Array.isArray(data)?data:Array.isArray(data?.models)?data.models:Array.isArray(data?.data)?data.data:[];const modelInfo=models.find(entry=>{const key=String(entry?.key||entry?.id||"").trim().toLowerCase();return key===normalizedModelKey});if(!modelInfo)return {loaded:false};return {loaded:hasLoadedInstances(modelInfo),modelKey:String(modelInfo?.key||modelInfo?.id||"").trim()||undefined}}catch{return {loaded:false}}finally{clearTimeout(timeout);}}async function loadVisionInstanceViaApi(baseUrl,apiKey,modelKey){const apiRoot=normalizeLmApiRoot(baseUrl);if(!apiRoot){return {ok:false,error:"Vision API base URL is empty."}}const controller=new AbortController;const timeout=setTimeout(()=>controller.abort(),6e5);try{const response=await fetch(`${apiRoot}/api/v1/models/load`,{method:"POST",headers:authHeaders(apiKey,true),body:JSON.stringify({model:modelKey,echo_load_config:true}),signal:controller.signal});const text=await response.text().catch(()=>"");let data=null;if(text.trim()){try{data=JSON.parse(text);}catch{data={raw:text};}}const apiError=data?.error?.message||data?.error||data?.message;if(!response.ok||apiError){const detail=apiError||text||`${response.status} ${response.statusText}`;return {ok:false,error:`Vision API could not load '${modelKey}' via /api/v1/models/load. This can happen when there are not enough system resources available. Error: ${detail}`}}return {ok:true}}catch(error){const detail=error?.name==="AbortError"?"request timed out after 600000 ms":error?.message||String(error);return {ok:false,error:`Vision API could not load '${modelKey}' via /api/v1/models/load. This can happen when there are not enough system resources available. Error: ${detail}`}}finally{clearTimeout(timeout);}}async function ensureLmStudioVisionInstanceReady(config){const modelKey=String(config.modelKey||"").trim();if(!modelKey){return {ok:false,error:"Vision API mode is active, but Qwen3-VL model key is empty."}}const initialState=await getVisionModelState(config.baseUrl,config.apiKey,modelKey);if(initialState.loaded){return {ok:true,loaded:false}}try{config.status?.(`Loading ${modelKey}...`);}catch{}const loadResult=await loadVisionInstanceViaApi(config.baseUrl,config.apiKey,modelKey);if(!loadResult.ok)return loadResult;const loadedState=await getVisionModelState(config.baseUrl,config.apiKey,modelKey);if(!loadedState.loaded){return {ok:false,error:`Vision API loaded '${modelKey}' via /api/v1/models/load, but /api/v1/models did not report it as loaded.`}}if(loadedState.modelKey?.trim().toLowerCase()!==modelKey.toLowerCase()){return {ok:false,error:`Vision API loaded a model, but /api/v1/models reports '${loadedState.modelKey||"unknown model"}' instead of '${modelKey}'.`}}return {ok:true,loaded:true}}function mimeFromPath(filePath){const ext=path.extname(filePath).toLowerCase();if(ext===".jpg"||ext===".jpeg")return "image/jpeg";if(ext===".webp")return "image/webp";if(ext===".gif")return "image/gif";return "image/png"}function readUInt24LE(buffer,offset){return buffer[offset]|buffer[offset+1]<<8|buffer[offset+2]<<16}function readPngDimensions(buffer){if(buffer.length<24)return null;if(buffer.toString("ascii",1,4)!=="PNG")return null;return {width:buffer.readUInt32BE(16),height:buffer.readUInt32BE(20)}}function readGifDimensions(buffer){if(buffer.length<10)return null;const signature=buffer.toString("ascii",0,6);if(signature!=="GIF87a"&&signature!=="GIF89a")return null;return {width:buffer.readUInt16LE(6),height:buffer.readUInt16LE(8)}}function readWebpDimensions(buffer){if(buffer.length<30)return null;if(buffer.toString("ascii",0,4)!=="RIFF"||buffer.toString("ascii",8,12)!=="WEBP"){return null}const chunkType=buffer.toString("ascii",12,16);if(chunkType==="VP8X"&&buffer.length>=30){return {width:readUInt24LE(buffer,24)+1,height:readUInt24LE(buffer,27)+1}}if(chunkType==="VP8L"&&buffer.length>=25&&buffer[20]===47){const bits=buffer.readUInt32LE(21);return {width:(bits&16383)+1,height:(bits>>14&16383)+1}}if(chunkType==="VP8 "&&buffer.length>=30){return {width:buffer.readUInt16LE(26)&16383,height:buffer.readUInt16LE(28)&16383}}return null}function readJpegDimensions(buffer){if(buffer.length<4||buffer[0]!==255||buffer[1]!==216)return null;let offset=2;while(offset+9<buffer.length){if(buffer[offset]!==255){offset+=1;continue}while(offset<buffer.length&&buffer[offset]===255)offset+=1;const marker=buffer[offset];offset+=1;if(marker===217||marker===218)break;if(offset+2>buffer.length)break;const segmentLength=buffer.readUInt16BE(offset);if(segmentLength<2||offset+segmentLength>buffer.length)break;const isStartOfFrame=marker>=192&&marker<=195||marker>=197&&marker<=199||marker>=201&&marker<=203||marker>=205&&marker<=207;if(isStartOfFrame&&segmentLength>=7){return {height:buffer.readUInt16BE(offset+3),width:buffer.readUInt16BE(offset+5)}}offset+=segmentLength;}return null}async function readImageDimensions(filePath){const buffer=await fs.promises.readFile(filePath);const dimensions=readPngDimensions(buffer)||readJpegDimensions(buffer)||readWebpDimensions(buffer)||readGifDimensions(buffer);if(!dimensions||dimensions.width<=0||dimensions.height<=0){throw new Error(`Could not determine image dimensions for ${filePath}`)}return dimensions}function extractMessageText(data){const output=Array.isArray(data?.output)?data.output:[];const pieces=[];for(const item of output){if(item?.type!=="message")continue;const content=item?.content;if(typeof content==="string"){pieces.push(content);}else if(Array.isArray(content)){for(const part of content){if(typeof part==="string"){pieces.push(part);}else if(typeof part?.text==="string"){pieces.push(part.text);}else if(typeof part?.content==="string"){pieces.push(part.content);}}}}if(pieces.length===0&&typeof data?.text==="string"){pieces.push(data.text);}if(pieces.length===0&&typeof data?.content==="string"){pieces.push(data.content);}return pieces.join("\n").trim()}function buildDetectPrompt(task,odPrompt){const label=String(task||"").trim();if(label){return `Detect all instances of '${label}' in the image.`+LABEL_FORMAT_RULE+JSON_FORMAT}const instruction=String(odPrompt||"").trim();if(!instruction){throw new Error("No OD prompt available: odPrompt not set and DETECT_OD_PROMPT env var not set")}return instruction+LABEL_FORMAT_RULE+JSON_FORMAT}function bboxToCrop(bbox,width,height){const[x1,y1,x2,y2]=bbox;return {cropLeft:x1/width*100,cropRight:(width-x2)/width*100,cropTop:y1/height*100,cropBottom:(height-y2)/height*100}}function parseQwen3VlDetectionOutput(text,width,height){const objects=[];const seen=new Set;const fenceMatch=JSON_FENCE_RE.exec(text);const jsonText=fenceMatch?fenceMatch[1].trim():text.trim();let items=null;try{const parsed=JSON.parse(jsonText);items=Array.isArray(parsed)?parsed:[parsed];}catch{const recovered=[];ITEM_RE.lastIndex=0;for(const match of text.matchAll(ITEM_RE)){recovered.push({bbox_2d:[Number(match[1]),Number(match[2]),Number(match[3]),Number(match[4])],label:match[5]});}items=recovered.length>0?recovered:[];}for(const item of items){if(!item||typeof item!=="object")continue;const bbox=item.bbox_2d;const label=String(item.label||"");if(!Array.isArray(bbox)||bbox.length!==4)continue;const[nx1,ny1,nx2,ny2]=bbox.map(value=>Number(value));if(![nx1,ny1,nx2,ny2].every(value=>Number.isFinite(value)&&value>=0&&value<=1e3)){continue}if(nx2<=nx1||ny2<=ny1)continue;if(nx1<10&&ny1<10&&nx2>990&&ny2>990)continue;const dedupKey=`${Math.round(nx1)}:${Math.round(ny1)}:${Math.round(nx2)}:${Math.round(ny2)}:${label}`;if(seen.has(dedupKey))continue;seen.add(dedupKey);const pixelBbox=[nx1/1e3*width,ny1/1e3*height,nx2/1e3*width,ny2/1e3*height];objects.push({label,bbox:pixelBbox,...bboxToCrop(pixelBbox,width,height)});}return objects}async function chatOnce(item,prompt,config){const apiRoot=normalizeLmApiRoot(config.baseUrl);if(!apiRoot){throw new Error("Vision API base URL is empty")}const endpoint=`${apiRoot}/api/v1/chat`;const timeoutMs=config.timeoutMs??18e4;const model=config.model||"vision-capability-priming";const buf=await fs.promises.readFile(item.filePath);const dataUrl=`data:${mimeFromPath(item.filePath)};base64,${buf.toString("base64")}`;const payload={model,input:[{type:"text",content:prompt},{type:"image",data_url:dataUrl}],store:false};if(typeof config.maxTokens==="number"&&Number.isFinite(config.maxTokens)&&config.maxTokens>0){payload.max_output_tokens=Math.floor(config.maxTokens);}if(typeof config.temperature==="number"&&Number.isFinite(config.temperature)){payload.temperature=config.temperature;}logVisionRequestMetadata({configuredBaseUrl:config.baseUrl,apiRoot,endpoint,model,store:payload.store,max_output_tokens:payload.max_output_tokens??null,temperature:payload.temperature??null,promptChars:prompt.length,imageBytes:buf.byteLength,inputTypes:Array.isArray(payload.input)?payload.input.map(part=>part.type):[],payloadKeys:Object.keys(payload)});const headers=authHeaders(config.apiKey,true);const controller=new AbortController;const timeout=setTimeout(()=>controller.abort(),timeoutMs);const startedAt=Date.now();let data;try{const resp=await fetch(endpoint,{method:"POST",headers,body:JSON.stringify(payload),signal:controller.signal});clearTimeout(timeout);if(!resp.ok){const detail=await resp.text().catch(()=>"(no body)");throw new Error(`Vision API ${resp.status}: ${detail}`)}data=await resp.json();}catch(error){clearTimeout(timeout);if(error?.name==="AbortError"){throw new Error(`Vision API timed out after ${timeoutMs}ms`)}throw new Error(`Vision API failed: ${error?.message||String(error)}`)}return {text:extractMessageText(data),elapsedMs:Date.now()-startedAt,bytes:buf.byteLength,modelInstanceId:typeof data?.model_instance_id==="string"?data.model_instance_id:""}}async function analyzeLmStudioVisionBatch(items,config){if(!items.length){return {results:[],totalInferenceTimeMs:0,backend:"vision-api"}}const results=[];let totalInferenceTimeMs=0;for(const item of items){console.info(`[LmStudioVisionAnalyzer] /api/v1/chat start mode=analyze id=${item.id} timeoutMs=${config.timeoutMs??18e4}`);const response=await chatOnce(item,config.prompt||"Describe the image.",config);console.info(`[LmStudioVisionAnalyzer] /api/v1/chat ok mode=analyze id=${item.id} bytes=${response.bytes} elapsedMs=${response.elapsedMs} modelInstance=${response.modelInstanceId||"?"}`);results.push({id:item.id,text:response.text,inferenceTimeMs:response.elapsedMs});totalInferenceTimeMs+=response.elapsedMs;}return {results,totalInferenceTimeMs,backend:"vision-api"}}async function detectLmStudioVisionBatch(items,config){if(!items.length){return {results:[],totalInferenceTimeMs:0,backend:"vision-api"}}const prompt=buildDetectPrompt(config.task,config.odPrompt);const results=[];let totalInferenceTimeMs=0;for(const item of items){const{width,height}=await readImageDimensions(item.filePath);console.info(`[LmStudioVisionAnalyzer] /api/v1/chat start mode=detect id=${item.id} timeoutMs=${config.timeoutMs??12e4}`);const response=await chatOnce(item,prompt,{baseUrl:config.baseUrl,apiKey:config.apiKey,model:config.model||"vision-capability-priming",maxTokens:config.maxTokens,temperature:config.temperature,timeoutMs:config.timeoutMs??12e4});const objects=parseQwen3VlDetectionOutput(response.text,width,height);console.info(`[LmStudioVisionAnalyzer] /api/v1/chat ok mode=detect id=${item.id} objects=${objects.length} bytes=${response.bytes} elapsedMs=${response.elapsedMs} modelInstance=${response.modelInstanceId||"?"}`);results.push({id:item.id,objects,imageWidth:width,imageHeight:height,inferenceTimeMs:response.elapsedMs});totalInferenceTimeMs+=response.elapsedMs;}return {results,totalInferenceTimeMs,backend:"vision-api"}}
function reportToolStatus(ctx,message){try{if(typeof ctx.status==="function"){ctx.status(stripTrailingStatusPunctuation(message));}}catch{}}function stripTrailingStatusPunctuation(message){return message.trim().replace(/\.{1,3}$/,"")}function formatToolStep(step,totalSteps,message){const pct=totalSteps>0?Math.round(step/(totalSteps+1)*100):0;const prefix=`Step ${step}/${totalSteps} (${pct}%)`;return message?`${stripTrailingStatusPunctuation(message)} - ${prefix}`:prefix}function reportToolStep(ctx,step,totalSteps,message){reportToolStatus(ctx,formatToolStep(step,totalSteps,message));}
const defaultPluginSettings={PREVIEW_IN_CHAT:true,HTTP_SERVER_PORT:54760,embeddingBaseUrl:"http://127.0.0.1:1234/v1",embeddingApiKey:"",qwen3VlModelPath:"qwen/qwen3-vl-8b",visionPrompt:"",embedPngMetadata:true,serverMaxTokens:768,serverTemperature:.7,qwen3VlOdPrompt:["Detect objects in the image with strict hierarchical prioritization.","","PRIORITY 1 (CRITICAL - MUST DETECT FIRST):",'- You MUST detect "human face" (highest priority if a person is present)','- You MUST detect "person" (if no face is clearly visible or if the person is the main subject)',"","PRIORITY 2 (MAIN SUBJECT / HERO ELEMENT):","- The most visually prominent object or subject that is NOT part of the background.","- Use specific, concrete labels (e.g., 'red car', 'fluffy owl toy').","- Avoid generic terms like 'object' or 'thing'.","","PRIORITY 3 (CONTEXTUAL BACKGROUND ELEMENTS):","- Only detect background elements if they are significant to the scene composition OR if the main subject is interacting with them.","- Do not detect minor or redundant background details.","","PRIORITY 4 (FOCUSSED MAIN SUBJECT / HERO ELEMENT):","- All visible body parts (hands, feet, arms, legs).","- Elements of the face, as far as clearly detectable and focussed on close-ups: nose, mouth, left and right eyes, eyebrows and ears","- anatomical details, as far as recognizable as \\\"focussed\\\" or \\\"prominent\\\" (e.g., 'iris', 'pupil', 'eyelid')","","RULES:","- Maximum 16 objects total.","- Each bounding box must be unique and non-redundant.","- For clothing, name the specific garment (e.g., 'tank top', 'jeans').","- For body parts, qualify by position (e.g., 'left hand').","- NEVER prioritize background elements over the main subject or human face.","- NEVER prioritize anatomical details over general concepts unless they are solely focussed (e.g. only detect 'eyes' unless 'human face' is the dominant part of the image)","- If the main subject is a person, focus on the person and their immediate interactions. Ignore background elements unless they are directly involved in the interaction.","- If NO person or face is visible, ALWAYS detect Priority 2 and Priority 3 subjects regardless."].join("\n"),detectMaxTokens:2048,detectTemperature:.3,includeGenerationMetadata:true};const globalConfigSchematics=sdk.createConfigSchematics().field("PREVIEW_IN_CHAT","boolean",{displayName:"Previews in Chat",subtitle:"When enabled, tool responses include inline image previews. Recommended for local models without vision capability.",engineDoesNotSupport:false},defaultPluginSettings.PREVIEW_IN_CHAT).field("HTTP_SERVER_PORT","numeric",{displayName:"Local HTTP Server Port",subtitle:"Port for serving generated images over localhost (default: 54760).",engineDoesNotSupport:true},defaultPluginSettings.HTTP_SERVER_PORT).field("embeddingBaseUrl","string",{displayName:"Vision API Base URL",subtitle:"OpenAI-compatible /v1 URL. Vision tools use the same server root and call LM Studio's internal /api/v1 vision endpoints. Separate from the agent API.",placeholder:"http://127.0.0.1:1234/v1",engineDoesNotSupport:false},defaultPluginSettings.embeddingBaseUrl).field("embeddingApiKey","string",{displayName:"Vision API Key",subtitle:"Optional key for the Qwen3-VL vision backend. Separate from the agent API key.",isProtected:true,placeholder:"sk-...",engineDoesNotSupport:false},defaultPluginSettings.embeddingApiKey).field("qwen3VlModelPath","string",{displayName:"Qwen3-VL Model",subtitle:"LM Studio model key for the Qwen3-VL Vision API backend, for example qwen/qwen3-vl-8b. This is not a filesystem path.",placeholder:"qwen/qwen3-vl-8b",engineDoesNotSupport:false},defaultPluginSettings.qwen3VlModelPath).field("visionPrompt","string",{displayName:"Vision Prompt",subtitle:"Default prompt sent to the vision model when the agent does not supply one. Leave empty to disable automatic visual description.",placeholder:"Analyze this image based strictly on what is directly visible. Do not infer, assume, or complete information that is not present.",isParagraph:true},defaultPluginSettings.visionPrompt).field("embedPngMetadata","boolean",{displayName:"Embed Metadata in PNGs",subtitle:"Write analysis provenance, detected objects, and bounding boxes into saved PNGs as Draw Things-compatible XMP metadata.",engineDoesNotSupport:false},defaultPluginSettings.embedPngMetadata).field("includeGenerationMetadata","boolean",{displayName:"Include Generation Metadata",subtitle:"When enabled, Draw Things generation parameters (prompt, model, sampler, seed, ...) embedded in PNG files are appended to each analysis result.",engineDoesNotSupport:false},defaultPluginSettings.includeGenerationMetadata).field("serverMaxTokens","numeric",{displayName:"Vision API: Max Tokens",subtitle:"Maximum response length in tokens (1-4096). Default: 768.",engineDoesNotSupport:true},defaultPluginSettings.serverMaxTokens).field("serverTemperature","numeric",{displayName:"Vision API: Temperature",subtitle:"Sampling temperature (0.0-2.0). Default: 0.7.",engineDoesNotSupport:true},defaultPluginSettings.serverTemperature).field("qwen3VlOdPrompt","string",{displayName:"Qwen3-VL: Object Detection Prompt",subtitle:"Instruction sent to Qwen3-VL for default object detection. Leave empty to use the built-in default.",placeholder:"",isParagraph:true,engineDoesNotSupport:false},defaultPluginSettings.qwen3VlOdPrompt).field("detectMaxTokens","numeric",{displayName:"Vision API Detect: Max Tokens",subtitle:"Maximum response length in tokens for object detection (1-4096). Default: 2048.",engineDoesNotSupport:true},defaultPluginSettings.detectMaxTokens).field("detectTemperature","numeric",{displayName:"Vision API Detect: Temperature",subtitle:"Sampling temperature for object detection (0.0-2.0). Default: 0.3.",engineDoesNotSupport:true},defaultPluginSettings.detectTemperature).build();
function isoStampCompact$1(){const d=new Date;const year=d.getUTCFullYear();const month=String(d.getUTCMonth()+1).padStart(2,"0");const day=String(d.getUTCDate()).padStart(2,"0");const hours=String(d.getUTCHours()).padStart(2,"0");const minutes=String(d.getUTCMinutes()).padStart(2,"0");const seconds=String(d.getUTCSeconds()).padStart(2,"0");const millis=String(d.getUTCMilliseconds()).padStart(3,"0");return `${year}${month}${day}T${hours}${minutes}${seconds}${millis}Z`}function parsePrefixedNotation$1(s){const t=String(s||"").trim().toLowerCase();const m=t.match(/^([avip])(\d+)$/);if(!m)return null;const idx=Math.max(1,parseInt(m[2],10));const pool=m[1]==="a"?"attachment":m[1]==="v"?"variant":m[1]==="i"?"image":"picture";return {pool,index:idx}}function formatPluginMeta$2(){return formatToolMetaBlock()}function getGlobalConfig$2(ctl){const ctlAny=ctl;const getter=ctlAny.getGlobalPluginConfig||ctlAny.getGlobalConfig;if(!getter)return null;try{return getter.call(ctl,globalConfigSchematics)}catch{return null}}function getGlobalString$2(gcfg,key,fallback){try{const value=gcfg?.get(key);return typeof value==="string"?value:fallback}catch{return fallback}}function getGlobalNumber$2(gcfg,key,fallback){try{const value=gcfg?.get(key);return typeof value==="number"&&Number.isFinite(value)?value:fallback}catch{return fallback}}function getGlobalBoolean$1(gcfg,key,fallback){try{const value=gcfg?.get(key);return typeof value==="boolean"?value:fallback}catch{return fallback}}function applyFrameAdjust(bbox,frameAdjust,imgW,imgH){const[x1,y1,x2,y2]=bbox;const diag=Math.hypot(x2-x1,y2-y1);let d_px;if(typeof frameAdjust==="string"){const m=String(frameAdjust).trim().match(/^([+-]?\d+(?:\.\d+)?)\s*(%|px)?$/i);if(!m)return bbox;const val=parseFloat(m[1]);d_px=m[2]?.toLowerCase()==="px"?val:val/100*diag;}else {d_px=frameAdjust/100*diag;}return [Math.max(0,Math.round(x1-d_px)),Math.max(0,Math.round(y1-d_px)),Math.min(imgW-1,Math.round(x2+d_px)),Math.min(imgH-1,Math.round(y2+d_px))]}const FlexibleTargetsList$2=zod.z.union([zod.z.string().transform(s=>(s.match(/[aivp]\d+/gi)??[]).map(x=>x.toLowerCase())),zod.z.array(zod.z.string())]).refine(arr=>arr.length>=1,"targets must contain at least one notation").refine(arr=>arr.length<=16,"targets must contain at most 16 notations");const AnnotateImageParamsShape={targets:FlexibleTargetsList$2.optional().describe("One or more image notations to process. Each notation is a letter followed by a number: "+"a=attachment (a1, a2, …), i=generated image (i1, i2, …), v=variant (v1, v2, …), p=picture (p1, p2, …). "+'Pass via the targets field, e.g. annotate_image({"targets":["a1", "i3"]}). '+"Omit when there is exactly one image — it will be selected automatically."),task:zod.z.string().optional().default("").describe("What to detect. Omit for full-image general object detection. "+"Use natural language to target specific subjects (e.g. 'all faces and hands', 'the dog', 'cars and bicycles'). "+"Not used on correction calls — detections are loaded from state."),color:zod.z.string().optional().describe("Box color for all detected objects. CSS color name or hex, e.g. 'pink', 'red', '#FF0000'. Default: pink."),lineWeight:zod.z.coerce.number().int().min(1).max(50).optional().describe("Line thickness in pixels for all bounding boxes. Default: 5."),frameAdjust:zod.z.union([zod.z.number(),zod.z.string()]).optional().describe("Expand (positive) or shrink (negative) bounding boxes before drawing. "+"Number: percent of box diagonal (e.g. 5 = +5 %). "+"String: value + optional 'px' or '%' suffix, e.g. '10px', '-5%'. "+"Without detectLabel: applied to all boxes. With detectLabel: applied to the selected box only. Default: 5."),detectLabel:zod.z.preprocess(val=>{if(typeof val==="string"){const t=val.trim();if(t.startsWith("[")){try{const parsed=JSON.parse(t);if(Array.isArray(parsed)){return parsed.map(el=>String(el).trim()).filter(s=>s.length>0)}}catch{}}}return val},zod.z.union([zod.z.string().transform(s=>s.split(/\s*,\s*/).map(x=>x.trim()).filter(x=>x.length>0)),zod.z.array(zod.z.string().min(1))])).optional().describe("On a correction call: label(s) to match (case-insensitive). "+"Single string, comma-separated list ('left eye, right eye'), or JSON array. "+"When set, ONLY the matching detection(s) are drawn — all others are omitted. "+"Single label with no detectIndex: auto-expands to ALL detections for that label (Option A). "+"canvas may be the original source (e.g. a1) or the previous annotate_image result (e.g. i3)."),detectIndex:zod.z.union([zod.z.string().transform(s=>(s.match(/\d+/g)??[]).map(Number)),zod.z.coerce.number().int().min(0).transform(n=>[n]),zod.z.array(zod.z.coerce.number().int().min(0))]).optional().describe("Zero-based index or list of indices, parallel to detectLabel. "+"indices[li] ?? indices[0] ?? 0 for missing entries. Default: 0. "+"Single label + multiple indices draws that label at each specified occurrence (Option B). "+"Bracket notation accepted: '[2, 4, 7]'."),x1:zod.z.preprocess(val=>{if(typeof val==="string"&&val.trim().startsWith("[")){try{return JSON.parse(val)}catch{}}return val},zod.z.union([zod.z.number(),zod.z.array(zod.z.union([zod.z.number(),zod.z.null()]))])).optional().describe("Manual left edge(s) in original image pixels. "+"Scalar: applies to all selected detections. Array (parallel to detectLabel): null = keep stored value. "+"E.g. x1=[null,50,null] moves only the second detection's left edge."),y1:zod.z.preprocess(val=>{if(typeof val==="string"&&val.trim().startsWith("[")){try{return JSON.parse(val)}catch{}}return val},zod.z.union([zod.z.number(),zod.z.array(zod.z.union([zod.z.number(),zod.z.null()]))])).optional().describe("Manual top edge(s) in original image pixels. Scalar or array (null = keep stored). See x1."),x2:zod.z.preprocess(val=>{if(typeof val==="string"&&val.trim().startsWith("[")){try{return JSON.parse(val)}catch{}}return val},zod.z.union([zod.z.number(),zod.z.array(zod.z.union([zod.z.number(),zod.z.null()]))])).optional().describe("Manual right edge(s) in original image pixels. Scalar or array (null = keep stored). See x1."),y2:zod.z.preprocess(val=>{if(typeof val==="string"&&val.trim().startsWith("[")){try{return JSON.parse(val)}catch{}}return val},zod.z.union([zod.z.number(),zod.z.array(zod.z.union([zod.z.number(),zod.z.null()]))])).optional().describe("Manual bottom edge(s) in original image pixels. Scalar or array (null = keep stored). See x1.")};function resolveCoordOverride(override,index){if(override===undefined)return undefined;if(Array.isArray(override)){const entry=override[index];if(entry===null||entry===undefined)return undefined;return entry}return override}function expandDetectIndices(detections,label){const lower=label.toLowerCase();let count=0;for(const d of detections){if(d.label.toLowerCase()===lower)count++;}return count>1?Array.from({length:count},(_,i)=>i):[0]}function createAnnotateImageTool(ctl){return sdk.tool({name:"annotate_image",description:`Highlights specific areas or elements on images. If well described, these elements are precisely framed with bounding boxes in the chosen color.
Detections are saved to state for later correction, refinement, or re-drawing with adjusted labels and edge positions.
--- Correction call (omit task) ---
Redraws from stored detections without inference. canvas may be the original source (e.g. a1) or a previous annotate_image result (e.g. i3).
Without detectLabel: ALL stored detections are drawn with the given color/lineWeight/frameAdjust.
With detectLabel: ONLY the matching detection(s) are drawn.
- Option A — single label, no detectIndex: auto-expands to ALL detections for that label.
detectLabel="face" with 8 stored faces → draws all 8 faces.
- Option B — single label, multiple detectIndex: draws that label at each specified occurrence.
detectLabel="face", detectIndex="[2,4,7]" → draws face #2, #4, #7.
- Multi-label — comma-separated or array: one detection per label.
detectLabel="left eye, right eye" → draws both eyes.
Adjusting box edges (x1/y1/x2/y2):
- Scalar: applies the same value to all selected boxes.
detectLabel="face, hand, dog", y2=300 → all three boxes get bottom edge at y=300.
- Array (parallel to detectLabel, null = keep stored value): per-box override.
detectLabel="face, hand, dog", y2=[null, null, 240] → only dog's bottom edge moves to y=240.
- Partial: omitted axes always keep the stored value.
detectLabel="face", y2=240 → only the bottom edge changes, x1/y1/x2 unchanged.
--- Re-detect with new prompt ---
Pass task to force fresh inference even on an already-annotated image. Replaces stored detections.
Parameters:
- targets: Image notation(s), e.g. ["a1"]. Omit when exactly one image is available.
- task: What to detect (natural language). Omit on a correction call. Providing task always triggers fresh inference.
- color: Box color (CSS name or hex). Default: pink.
- lineWeight: Line thickness in pixels. Default: 5.
- frameAdjust: Expand (+) or shrink (−) boxes as % of box diagonal or absolute px. Default: 5.
- detectLabel: Label(s) to match (case-insensitive). Comma-separated or array. When set, draws ONLY matching detections.
- detectIndex: Index or list of indices, parallel to detectLabel. Bracket notation '[2,4,7]' accepted. Default: 0.
- x1/y1/x2/y2: Box edge override(s) in original image pixels. Scalar or array parallel to detectLabel (null = keep stored).
${formatPluginMeta$2()}`,parameters:AnnotateImageParamsShape,implementation:async(args,ctx)=>{try{let rawTargets=[];if(Array.isArray(args?.targets)){rawTargets=args.targets.map(s=>String(s).trim()).filter(Boolean);}else if(typeof args?.targets==="string"&&args.targets.trim()){const trimmed=args.targets.trim();if(trimmed.startsWith("[")){try{const parsed=JSON.parse(trimmed);rawTargets=Array.isArray(parsed)?parsed.map(s=>String(s).trim()).filter(Boolean):trimmed.split(/[\s,]+/).map(s=>s.trim()).filter(Boolean);}catch{rawTargets=trimmed.split(/[\s,]+/).map(s=>s.trim()).filter(Boolean);}}else {rawTargets=trimmed.split(/[\s,]+/).map(s=>s.trim()).filter(Boolean);}}const taskArg=typeof args?.task==="string"&&args.task.trim()?args.task.trim():"";const globalColor=typeof args?.color==="string"&&args.color.trim()?args.color.trim():"pink";const globalLineWeight=typeof args?.lineWeight==="number"?Math.max(1,Math.min(50,Math.round(args.lineWeight))):5;const globalFrameAdjust=args?.frameAdjust!==undefined?args.frameAdjust:5;let globalLabels;{const dlRaw=args?.detectLabel;if(Array.isArray(dlRaw)){const arr=dlRaw.map(s=>String(s).trim()).filter(Boolean);if(arr.length>0)globalLabels=arr;}else if(typeof dlRaw==="string"&&dlRaw.trim()){globalLabels=dlRaw.split(/\s*,\s*/).map(x=>x.trim()).filter(Boolean);}}let globalIndices=[];{const diRaw=args?.detectIndex;if(Array.isArray(diRaw)){globalIndices=diRaw.map(n=>typeof n==="number"?Math.floor(n):parseInt(String(n),10)).filter(n=>!isNaN(n)&&n>=0);}else if(typeof diRaw==="string"&&diRaw.trim()){globalIndices=(diRaw.match(/\d+/g)??[]).map(Number);}else if(typeof diRaw==="number"&&diRaw>=0){globalIndices=[Math.floor(diRaw)];}}const rawX1=args?.x1;const rawY1=args?.y1;const rawX2=args?.x2;const rawY2=args?.y2;function normaliseCoord(raw){if(raw===undefined||raw===null)return undefined;if(typeof raw==="number")return raw;if(Array.isArray(raw))return raw.map(v=>v===null||v===undefined?null:Number(v));if(typeof raw==="string"){const t=raw.trim();if(t.startsWith("[")){try{const p=JSON.parse(t);if(Array.isArray(p))return p.map(v=>v===null||v===undefined?null:Number(v))}catch{}}const n=Number(t);return isNaN(n)?undefined:n}return undefined}const manualX1=normaliseCoord(rawX1);const manualY1=normaliseCoord(rawY1);const manualX2=normaliseCoord(rawX2);const manualY2=normaliseCoord(rawY2);const hasAnyManualCoord=manualX1!==undefined||manualY1!==undefined||manualX2!==undefined||manualY2!==undefined;console.log("[annotate_image] invoked",{targets:rawTargets,task:taskArg,color:globalColor,lineWeight:globalLineWeight,frameAdjust:globalFrameAdjust,detectLabels:globalLabels,detectIndices:globalIndices,manualCoords:{x1:manualX1,y1:manualY1,x2:manualX2,y2:manualY2}});let currentLmChatId=null;let currentLmWorkingDir=null;try{const chatCtx=await getActiveChatContext();if(chatCtx?.chatId)currentLmChatId=chatCtx.chatId;if(chatCtx?.workingDir)currentLmWorkingDir=chatCtx.workingDir;}catch{}if(!currentLmChatId){try{const resolved=await resolveActiveLMStudioChatId();if(resolved?.ok)currentLmChatId=resolved.chatId;}catch{}}const primaryOutDir=currentLmWorkingDir||(currentLmChatId?getLMStudioWorkingDir(currentLmChatId):undefined);if(!primaryOutDir){return {content:[{type:"text",text:"annotate_image failed: could not resolve LM Studio chat working directory."}],isError:true}}await fs.promises.mkdir(primaryOutDir,{recursive:true}).catch(()=>{});try{await syncAttachmentsToState(primaryOutDir,false,Number.MAX_SAFE_INTEGER);}catch(e){console.warn("[annotate_image] attachment sync failed (non-fatal):",e?.message??e);}const st=await readState$1(primaryOutDir);const attachments=Array.isArray(st?.attachments)?st.attachments:[];const pictures=Array.isArray(st?.pictures)?st.pictures:[];const imageRecords=Array.isArray(st?.images)?st.images:[];const variantRecords=Array.isArray(st?.variants)?st.variants:[];async function resolvePreviewBuf(notation){const pref=parsePrefixedNotation$1(notation);if(!pref)throw new Error(`Invalid notation: ${notation}`);if(pref.pool==="attachment"){const rec=attachments.find(a=>a?.a===pref.index);const r=rec&&typeof rec.preview==="string"?rec.preview:"";if(!r)throw new Error(`Preview for a${pref.index} not found.`);return fs.promises.readFile(path.join(primaryOutDir,r))}if(pref.pool==="image"){const rec=imageRecords.find(r=>r?.i===pref.index);const r=rec&&typeof rec.preview==="string"?rec.preview:"";if(!r)throw new Error(`Preview for i${pref.index} not found.`);return fs.promises.readFile(path.join(primaryOutDir,r))}if(pref.pool==="variant"){const rec=variantRecords.find(v=>v?.v===pref.index);const r=rec&&typeof rec.preview==="string"?rec.preview:"";if(!r)throw new Error(`Preview for v${pref.index} not found.`);return fs.promises.readFile(path.join(primaryOutDir,r))}const rec=pictures.find(p=>p?.p===pref.index);const r=rec&&typeof rec.preview==="string"?rec.preview:"";if(!r)throw new Error(`Preview for p${pref.index} not found.`);return fs.promises.readFile(path.join(primaryOutDir,r))}async function resolveOriginalBuf(notation,fallback){try{const pref=parsePrefixedNotation$1(notation);if(!pref)return fallback;if(pref.pool==="attachment"){const rec=attachments.find(a=>a?.a===pref.index);const abs=rec&&typeof rec.originAbs==="string"?rec.originAbs:"";if(!abs)return fallback;return await fs.promises.readFile(abs)}let rec;if(pref.pool==="image")rec=imageRecords.find(r=>r?.i===pref.index);else if(pref.pool==="variant")rec=variantRecords.find(v=>v?.v===pref.index);else rec=pictures.find(p=>p?.p===pref.index);const fn=rec&&typeof rec.filename==="string"?rec.filename:"";if(!fn)return fallback;return await fs.promises.readFile(path.join(primaryOutDir,fn))}catch{return fallback}}let autoId=null;if(rawTargets.length===0){const total=attachments.length+variantRecords.length+imageRecords.length+pictures.length;if(total===0){return {content:[{type:"text",text:"No source image available."}],isError:true}}if(total>1){return {content:[{type:"text",text:"Ambiguous source — specify targets explicitly."}],isError:true}}if(attachments.length===1)autoId=`a${typeof attachments[0]?.a==="number"?attachments[0].a:1}`;else if(variantRecords.length===1)autoId=`v${typeof variantRecords[0]?.v==="number"?variantRecords[0].v:1}`;else if(imageRecords.length===1)autoId=`i${typeof imageRecords[0]?.i==="number"?imageRecords[0].i:1}`;else autoId=`p${pictures[0]?.p??1}`;rawTargets=[autoId];}const forceDetect=taskArg.length>0;const resolvedEntries=[];for(const rawId of rawTargets){let drawSourceId=rawId;let stateRec=null;const pref=parsePrefixedNotation$1(rawId);if(pref?.pool==="image"){const imgRec=imageRecords.find(r=>r?.i===pref.index);if(imgRec&&Array.isArray(imgRec.detections)&&imgRec.detections.length>0&&typeof imgRec.imageWidth==="number"){stateRec=imgRec;drawSourceId=typeof imgRec.detectSource==="string"&&imgRec.detectSource?imgRec.detectSource:rawId;}}if(!stateRec){const prior=[...imageRecords].reverse().find(r=>r?.detectSource===rawId&&Array.isArray(r.detections)&&r.detections.length>0&&typeof r.imageWidth==="number");if(prior){stateRec=prior;drawSourceId=rawId;}}try{if(stateRec&&!forceDetect){const preview=await resolvePreviewBuf(drawSourceId).catch(()=>null);const origBuf=preview?await resolveOriginalBuf(drawSourceId,preview):Buffer.alloc(0);resolvedEntries.push({mode:"redraw",id:drawSourceId,origBuf,task:typeof stateRec.task==="string"?stateRec.task:taskArg,detections:stateRec.detections,imageWidth:stateRec.imageWidth,imageHeight:stateRec.imageHeight,analysisMetadata:stateRec.analysisMetadata});}else {const previewBuf=await resolvePreviewBuf(rawId);const origBuf=await resolveOriginalBuf(rawId,previewBuf);resolvedEntries.push({mode:"detect",id:rawId,previewBuf,origBuf});}}catch(e){return {content:[{type:"text",text:String(e?.message||e)}],isError:true}}}const detectEntries=resolvedEntries.filter(e=>e.mode==="detect");const progressTotalSteps=detectEntries.length>0?detectEntries.length+resolvedEntries.length+4:resolvedEntries.length+3;let batchResult=null;const globalConfig=getGlobalConfig$2(ctl);const embedPngMetadata=getGlobalBoolean$1(globalConfig,"embedPngMetadata",defaultPluginSettings.embedPngMetadata);let visionModelKey="";let detectionConfig=null;if(detectEntries.length>0){const visionBaseUrl=getGlobalString$2(globalConfig,"embeddingBaseUrl",process.env.LMSTUDIO_VISION_API_BASE_URL||defaultPluginSettings.embeddingBaseUrl);const visionApiKey=getGlobalString$2(globalConfig,"embeddingApiKey",process.env.LMSTUDIO_VISION_API_KEY||defaultPluginSettings.embeddingApiKey);visionModelKey=getGlobalString$2(globalConfig,"qwen3VlModelPath",process.env.LMSTUDIO_VISION_MODEL_KEY||defaultPluginSettings.qwen3VlModelPath);const envDetectMaxTokens=Number.parseInt(process.env.DETECT_MAX_TOKENS||"",10);const envDetectTemperature=Number.parseFloat(process.env.DETECT_TEMPERATURE||"");const configuredDetectMaxTokens=Math.floor(getGlobalNumber$2(globalConfig,"detectMaxTokens",Number.isFinite(envDetectMaxTokens)&&envDetectMaxTokens>0?envDetectMaxTokens:defaultPluginSettings.detectMaxTokens));const configuredDetectTemperature=getGlobalNumber$2(globalConfig,"detectTemperature",Number.isFinite(envDetectTemperature)?envDetectTemperature:defaultPluginSettings.detectTemperature);detectionConfig={task:taskArg,odPrompt:getGlobalString$2(globalConfig,"qwen3VlOdPrompt",process.env.DETECT_OD_PROMPT||defaultPluginSettings.qwen3VlOdPrompt)||undefined,maxTokens:configuredDetectMaxTokens,temperature:configuredDetectTemperature,timeoutMs:12e4};const tmpPaths=[];const detectionItems=[];for(const entry of detectEntries){const tmpPath=path.join(primaryOutDir,`_tmp_annotate_src_${entry.id}_${Date.now()}.png`);await fs.promises.writeFile(tmpPath,entry.previewBuf);tmpPaths.push(tmpPath);detectionItems.push({id:entry.id,filePath:tmpPath});}try{reportToolStatus(ctx,`Detecting objects in ${detectEntries.length} image${detectEntries.length===1?"":"s"}...`);reportToolStep(ctx,1,progressTotalSteps,`Preparing ${detectEntries.length} image${detectEntries.length===1?"":"s"} for annotation detection...`);const ready=await ensureLmStudioVisionInstanceReady({baseUrl:visionBaseUrl,apiKey:visionApiKey,modelKey:visionModelKey,status:message=>{try{ctx.status(message);}catch{}}});if(!ready.ok){throw new Error(ready.error)}batchResult={results:[],totalInferenceTimeMs:0,backend:"vision-api"};for(let idx=0;idx<detectionItems.length;idx++){const item=detectionItems[idx];reportToolStep(ctx,idx+2,progressTotalSteps,`Detecting objects in ${item.id} (${idx+1}/${detectionItems.length})...`);const singleResult=await detectLmStudioVisionBatch([item],{...detectionConfig,baseUrl:visionBaseUrl,apiKey:visionApiKey,model:visionModelKey});batchResult.results.push(...singleResult.results);batchResult.totalInferenceTimeMs+=singleResult.totalInferenceTimeMs;batchResult.backend=singleResult.backend;}try{const totalObjects=batchResult.results.reduce((s,r)=>s+(r.objects?.length??0),0);const ms=Math.round(batchResult.totalInferenceTimeMs);reportToolStep(ctx,detectEntries.length+2,progressTotalSteps,`${totalObjects} object${totalObjects===1?"":"s"} found (${ms}ms); drawing boxes...`);}catch{}}finally{for(const tp of tmpPaths)await fs.promises.unlink(tp).catch(()=>{});}if(!batchResult||!batchResult.results.length){return {content:[{type:"text",text:"annotate_image: no results returned from detection API."}],isError:true}}}else {reportToolStatus(ctx,`Redrawing ${resolvedEntries.length} annotated image${resolvedEntries.length===1?"":"s"} from stored detections...`);reportToolStep(ctx,1,progressTotalSteps,`Redrawing ${resolvedEntries.length} annotated image${resolvedEntries.length===1?"":"s"} from stored detections...`);}const variantPreviewSpec=VARIANT_FULL_CONFIG.preview;const stamp=isoStampCompact$1();let nextI=Math.max(1,st.counters?.nextImageI??1);const imageRecordsForState=[];const resultEntries=[];const httpBase=await getHealthyServerBaseUrl();let detectResultIdx=0;let resolvedIdx=0;const drawBaseStep=detectEntries.length>0?detectEntries.length+3:2;for(const entry of resolvedEntries){reportToolStep(ctx,drawBaseStep+resolvedIdx,progressTotalSteps,`Drawing annotation for ${entry.id} (${resolvedIdx+1}/${resolvedEntries.length})...`);resolvedIdx++;let rawBboxes;let imgW;let imgH;let detObjects;let isRedraw;let entryTask;let inferenceTimeMs=0;let bboxesAlreadyAdjusted=false;if(entry.mode==="redraw"){imgW=entry.imageWidth;imgH=entry.imageHeight;isRedraw=true;entryTask=entry.task;if(globalLabels!==undefined&&globalLabels.length>0){let labels=[...globalLabels];let indices=[...globalIndices];if(labels.length===1&&indices.length===0){const allIndices=expandDetectIndices(entry.detections,labels[0]);if(allIndices.length>1){labels=Array(allIndices.length).fill(labels[0]);indices=allIndices;}}if(labels.length===1&&indices.length>1){labels=Array(indices.length).fill(labels[0]);}const detByLabel=new Map;for(const d of entry.detections){const key=d.label.toLowerCase();if(!detByLabel.has(key))detByLabel.set(key,[]);detByLabel.get(key).push(d);}const resolvedBoxes=[];for(let li=0;li<labels.length;li++){const label=labels[li];const idx=indices[li]??indices[0]??0;const selectedDet=detByLabel.get(label.toLowerCase())?.[idx];if(!selectedDet){const available=[...new Set(entry.detections.map(d=>d.label))].join(", ");return {content:[{type:"text",text:`annotate_image: label '${label}' (index ${idx}) not found in stored detections. Available: ${available||"(none)"}`}],isError:true}}const ox1=hasAnyManualCoord?resolveCoordOverride(manualX1,li):undefined;const oy1=hasAnyManualCoord?resolveCoordOverride(manualY1,li):undefined;const ox2=hasAnyManualCoord?resolveCoordOverride(manualX2,li):undefined;const oy2=hasAnyManualCoord?resolveCoordOverride(manualY2,li):undefined;const bbox=[ox1??selectedDet.bbox.x1,oy1??selectedDet.bbox.y1,ox2??selectedDet.bbox.x2,oy2??selectedDet.bbox.y2];resolvedBoxes.push({det:selectedDet,bbox});}rawBboxes=resolvedBoxes.map(({bbox})=>applyFrameAdjust(bbox,globalFrameAdjust,imgW,imgH));detObjects=resolvedBoxes.map(({det,bbox})=>({...det,bbox:{x1:bbox[0],y1:bbox[1],x2:bbox[2],y2:bbox[3]}}));bboxesAlreadyAdjusted=true;}else if(globalIndices.length>0){const selected=[];for(const idx of globalIndices){const det=entry.detections[idx];if(!det){return {content:[{type:"text",text:`annotate_image: detectIndex ${idx} out of range (${entry.detections.length} stored detections).`}],isError:true}}selected.push(det);}rawBboxes=selected.map(d=>[d.bbox.x1,d.bbox.y1,d.bbox.x2,d.bbox.y2]);detObjects=selected;bboxesAlreadyAdjusted=false;}else {rawBboxes=entry.detections.map(d=>[d.bbox.x1,d.bbox.y1,d.bbox.x2,d.bbox.y2]);detObjects=entry.detections;}}else {const detResult=batchResult.results[detectResultIdx++];rawBboxes=detResult.objects.map(o=>o.bbox);imgW=detResult.imageWidth;imgH=detResult.imageHeight;detObjects=detResult.objects.map(o=>({label:o.label,bbox:{x1:o.bbox[0],y1:o.bbox[1],x2:o.bbox[2],y2:o.bbox[3]},crop:{cropLeft:o.cropLeft,cropRight:o.cropRight,cropTop:o.cropTop,cropBottom:o.cropBottom}}));isRedraw=false;entryTask=taskArg;inferenceTimeMs=detResult.inferenceTimeMs??0;}const adjustedBboxes=bboxesAlreadyAdjusted?rawBboxes:rawBboxes.map(bbox=>applyFrameAdjust(bbox,globalFrameAdjust,imgW,imgH));const annotatedBuf=await drawBboxesOnImage(entry.origBuf,adjustedBboxes,{sourceDims:{width:imgW,height:imgH},palette:false,color:globalColor,lineWeight:globalLineWeight});const inheritedInference=entry.mode==="redraw"?entry.analysisMetadata?.inference:undefined;const analysis={schema:"ceveyne.image-analysis/v1",tool:"annotate_image",sourceNotation:entry.id,inference:entry.mode==="redraw"?inheritedInference?{...inheritedInference,reused:true}:undefined:{model:visionModelKey,...entryTask?{query:entryTask}:{},detectorPromptSha256:crypto.createHash("sha256").update(detectionConfig?.odPrompt??"").digest("hex"),maxTokens:detectionConfig?.maxTokens,temperature:detectionConfig?.temperature},render:{color:globalColor,lineWeight:globalLineWeight,frameAdjust:globalFrameAdjust},detections:detObjects.map((detection,index)=>({label:detection.label,bbox:{x1:adjustedBboxes[index][0],y1:adjustedBboxes[index][1],x2:adjustedBboxes[index][2],y2:adjustedBboxes[index][3]}}))};const savedBuffer=embedPngMetadata?injectXmpIntoBuffer(annotatedBuf,{...entryTask?{prompt:entryTask}:{},...analysis.inference?.model?{model:analysis.inference.model}:{},mode:isRedraw?"image_annotation_redraw":"image_annotation",generatedBy:`${getSelfPluginIdentifier()}/annotate_image`,creatorTool:`${getSelfPluginIdentifier()}/annotate_image`,analysis}):annotatedBuf;const currentI=nextI++;const baseName=`image-${stamp}-i${currentI}`;const savedPath=path.join(primaryOutDir,`${baseName}.png`);await fs.promises.writeFile(savedPath,savedBuffer);const savedFileUrl=url.pathToFileURL(savedPath).toString();const savedSize=savedBuffer.length;let preview=null;try{const p=await generatePreviewFromBuffer(savedBuffer,primaryOutDir,`${baseName}.png`,variantPreviewSpec);preview={ok:true,filePath:p.previewAbs,fileName:p.previewFilename,fileUrl:url.pathToFileURL(p.previewAbs).toString(),size_bytes:p.data.length,width:p.width,height:p.height,mimeType:"image/jpeg",dataBase64:p.data.toString("base64")};}catch(e){console.warn(`[annotate_image] preview generation failed for ${entry.id}:`,String(e));}const httpOriginal=httpBase?toHttpOriginalUrl(`${baseName}.png`,httpBase,currentLmChatId||undefined):"";const httpPreview=(()=>{if(!httpBase||!currentLmChatId||!preview?.fileName)return "";return toHttpPreviewUrl(preview.fileName,httpBase,currentLmChatId)})();imageRecordsForState.push({filename:`${baseName}.png`,preview:preview?`preview-${baseName}.jpg`:undefined,i:currentI,sourceTool:`${getSelfPluginIdentifier()}/annotate_image`,detectSource:entry.id,task:entryTask,annotateColor:globalColor,annotateLineWeight:globalLineWeight,annotateFrameAdjust:globalFrameAdjust,imageWidth:imgW,imageHeight:imgH,analysisMetadata:analysis,detections:detObjects.map(d=>({label:d.label,bbox:{x1:d.bbox.x1,y1:d.bbox.y1,x2:d.bbox.x2,y2:d.bbox.y2},crop:d.crop??{}}))});resultEntries.push({id:entry.id,i:currentI,isRedraw,task:entryTask,detObjects,imageWidth:imgW,imageHeight:imgH,savedPath,savedFileUrl,savedSize,preview,httpOriginal,httpPreview,inferenceTimeMs});}reportToolStep(ctx,progressTotalSteps-1,progressTotalSteps,"Updating image state and audit log...");try{const stateForUpdate=await readState$1(primaryOutDir);const appendResult=appendImages(stateForUpdate,imageRecordsForState);if(appendResult.changed){await writeStateAtomic(primaryOutDir,stateForUpdate);}}catch(e){console.warn("[annotate_image] state update failed:",String(e));}try{const audit=buildAuditLogger({backend:"annotate_image",mode:"annotate_image",requestId:undefined});if(currentLmChatId)audit.setChatId(currentLmChatId);audit.setUserRequest({targets:rawTargets,task:taskArg,color:globalColor,lineWeight:globalLineWeight,frameAdjust:globalFrameAdjust});audit.setOutput({images:resultEntries.map(r=>({id:r.id,i:r.i,redraw:r.isRedraw,detections:r.detObjects.length,path:r.savedPath,url:r.savedFileUrl,bytes:r.savedSize,...r.httpOriginal?{http_url:r.httpOriginal}:{},...r.preview?{preview_path:r.preview.filePath,preview_url:r.preview.fileUrl}:{},...r.httpPreview?{http_preview_url:r.httpPreview}:{}}))});await audit.write();}catch{}reportToolStep(ctx,progressTotalSteps,progressTotalSteps,"Assembling annotation result...");const summaries=resultEntries.map(r=>({tool:"annotate_image",source:r.id,i:r.i,redraw:r.isRedraw,color:globalColor,lineWeight:globalLineWeight,frameAdjust:globalFrameAdjust,...r.inferenceTimeMs>0?{inferenceTimeMs:r.inferenceTimeMs}:{},detections:r.detObjects.map(d=>({label:d.label,bbox:{x1:d.bbox.x1,y1:d.bbox.y1,x2:d.bbox.x2,y2:d.bbox.y2}}))}));const envPreviewRaw=process.env["PREVIEW_IN_CHAT"];const previewInChat=envPreviewRaw===undefined?true:envPreviewRaw==="1"||envPreviewRaw.toLowerCase()==="true";const resultNotations=resultEntries.map(r=>`i${r.i}`);const targetsJson=JSON.stringify(resultNotations);const reviewHintFalse=`Carefully examine the preview to make absolutely sure that the object detection matches your intent. Registered as ${resultNotations.join(", ")}. Use review_image({"targets":${targetsJson}}) to review, or annotate_image({"targets":${targetsJson}}) to apply corrections.`;const reviewHintTrue=`Carefully examine the preview to make absolutely sure that the object detection matches your intent. This is an image file. Present the image to the user by using the markdown above. Registered as ${resultNotations.join(", ")}. Use review_image({"targets":${targetsJson}}) to review, or annotate_image({"targets":${targetsJson}}) to apply corrections.`;const content=[];for(const r of resultEntries){const fallbackPreviewUrl=r.preview?.fileUrl||r.savedFileUrl;if(previewInChat&&r.preview){const fname=String(r.preview.fileName||"");content.push({type:"image",fileName:fname,mimeType:r.preview.mimeType,markdown:``,$hint:reviewHintTrue});}}if(batchResult&&batchResult.totalInferenceTimeMs>0){content.push({type:"text",text:`Total inference time: ${Math.round(batchResult.totalInferenceTimeMs)}ms`});}content.push({type:"text",text:JSON.stringify(summaries.length===1?summaries[0]:summaries),...previewInChat?{}:{$hint:reviewHintFalse}});return {content}}catch(error){return {content:[{type:"text",text:`annotate_image failed: ${error.message||String(error)}`}],isError:true}}}})}
async function readState(chatWd){const p=path.join(chatWd,"chat_media_state.json");try{const raw=await fs.promises.readFile(p,"utf-8");const json=JSON.parse(raw);return {attachments:Array.isArray(json?.attachments)?json.attachments:[],variants:Array.isArray(json?.variants)?json.variants:[],pictures:Array.isArray(json?.pictures)?json.pictures:[],images:Array.isArray(json?.images)?json.images:[],counters:json?.counters||{}}}catch{return {attachments:[],variants:[],pictures:[],images:[],counters:{}}}}
function readPngGenerationMeta(filePath){let buf;try{buf=fs.readFileSync(filePath);}catch{return null}if(buf.length<8||buf[0]!==137||buf[1]!==80||buf[2]!==78||buf[3]!==71){return null}let offset=8;while(offset+12<=buf.length){const chunkLen=buf.readUInt32BE(offset);const chunkType=buf.toString("ascii",offset+4,offset+8);const dataStart=offset+8;const dataEnd=dataStart+chunkLen;if(dataEnd+4>buf.length)break;if(chunkType==="IEND")break;if(chunkType==="iTXt"){const data=buf.slice(dataStart,dataEnd);const kwEnd=data.indexOf(0);if(kwEnd>=0&&data.toString("ascii",0,kwEnd)==="XML:com.adobe.xmp"){const comprFlag=data[kwEnd+1];if(comprFlag!==0){offset=dataEnd+4;continue}let pos=kwEnd+3;while(pos<data.length&&data[pos]!==0)pos++;pos++;while(pos<data.length&&data[pos]!==0)pos++;pos++;const xmpText=data.toString("utf8",pos);return extractMetaFromXmp(xmpText)}}offset=dataEnd+4;}return null}function extractMetaFromXmp(xmp){const match=xmp.match(/<exif:UserComment>[\s\S]*?<rdf:li[^>]*>([\s\S]*?)<\/rdf:li>/);if(!match)return null;let raw;try{const jsonText=match[1].trim().replace(/"/g,'"').replace(/'/g,"'").replace(/</g,"<").replace(/>/g,">").replace(/&/g,"&");raw=JSON.parse(jsonText);}catch{return null}const loras=Array.isArray(raw.lora)?raw.lora.filter(l=>l&&typeof l.model==="string").map(l=>({file:String(l.model),weight:typeof l.weight==="number"?l.weight:undefined})):undefined;const sources=Array.isArray(raw.sources)?raw.sources.filter(s=>typeof s==="string"):undefined;return {prompt:typeof raw.c==="string"&&raw.c?raw.c:undefined,negativePrompt:typeof raw.uc==="string"&&raw.uc?raw.uc:undefined,model:typeof raw.model==="string"&&raw.model?raw.model:undefined,sampler:typeof raw.sampler==="string"&&raw.sampler?raw.sampler:undefined,steps:typeof raw.steps==="number"?raw.steps:undefined,guidanceScale:typeof raw.scale==="number"?raw.scale:undefined,seed:typeof raw.seed==="number"?raw.seed:undefined,seedMode:typeof raw.seed_mode==="string"&&raw.seed_mode?raw.seed_mode:undefined,shift:typeof raw.shift==="number"?raw.shift:undefined,size:typeof raw.size==="string"&&raw.size?raw.size:undefined,strength:typeof raw.strength==="number"?raw.strength:undefined,loras:loras?.length?loras:undefined,sources:sources?.length?sources:undefined,mode:typeof raw.mode==="string"&&raw.mode?raw.mode:undefined,generatedBy:typeof raw.generated_by==="string"&&raw.generated_by?raw.generated_by:undefined}}function formatGenerationMeta(meta){const lines=[" GENERATION METADATA:"];if(meta.prompt)lines.push(` Prompt: ${meta.prompt}`);if(meta.negativePrompt)lines.push(` Negative Prompt: ${meta.negativePrompt}`);if(meta.model)lines.push(` Model: ${meta.model}`);const techParts=[];if(meta.sampler)techParts.push(`Sampler: ${meta.sampler}`);if(typeof meta.steps==="number")techParts.push(`Steps: ${meta.steps}`);if(typeof meta.guidanceScale==="number")techParts.push(`Guidance Scale: ${meta.guidanceScale}`);if(typeof meta.seed==="number")techParts.push(`Seed: ${meta.seed}`);if(techParts.length>0)lines.push(` ${techParts.join(" ")}`);if(meta.size)lines.push(` Size: ${meta.size}`);if(typeof meta.strength==="number"&&meta.strength!==1){lines.push(` Strength: ${meta.strength}`);}if(meta.loras&&meta.loras.length>0){const loraStr=meta.loras.map(l=>l.weight!=null?`${l.file} (${l.weight})`:l.file).join(", ");lines.push(` LoRA: ${loraStr}`);}if(meta.sources&&meta.sources.length>0){lines.push(` Source(s): ${meta.sources.join(", ")}`);}return lines.join("\n")}
function formatPluginMeta$1(){return formatToolMetaBlock()}function getGlobalConfig$1(ctl){const ctlAny=ctl;const getter=ctlAny.getGlobalPluginConfig||ctlAny.getGlobalConfig;if(!getter)return null;try{return getter.call(ctl,globalConfigSchematics)}catch{return null}}function getGlobalString$1(gcfg,key,fallback){try{const value=gcfg?.get(key);return typeof value==="string"?value:fallback}catch{return fallback}}function getGlobalNumber$1(gcfg,key,fallback){try{const value=gcfg?.get(key);return typeof value==="number"&&Number.isFinite(value)?value:fallback}catch{return fallback}}const FlexibleTargetsList$1=zod.z.union([zod.z.string().transform(s=>(s.match(/[aivp]\d+/gi)??[]).map(x=>x.toLowerCase())),zod.z.array(zod.z.string())]).refine(arr=>arr.length>=1,"targets must contain at least one notation").refine(arr=>arr.length<=16,"targets must contain at most 16 notations");const AnalyseImageParamsShape={targets:FlexibleTargetsList$1,prompt:zod.z.string().optional().describe("Optional prompt for the vision model. Empty = model default.")};function parseTargets(targets){const parsed={a:[],v:[],i:[],p:[]};const invalid=[];for(const raw of targets){const s=typeof raw==="string"?raw.trim():"";const m=/^([avip])(\d+)$/i.exec(s);if(!m){invalid.push(String(raw));continue}const kind=m[1].toLowerCase();const n=parseInt(m[2],10);if(!Number.isFinite(n)||n<=0){invalid.push(String(raw));continue}parsed[kind].push(n);}Object.keys(parsed).forEach(k=>{parsed[k]=Array.from(new Set(parsed[k])).sort((a,b)=>a-b);});return {parsed,invalid}}function getAvailable(state){const availableA=(state.attachments||[]).map(x=>typeof x?.a==="number"?x.a:undefined).filter(x=>typeof x==="number"&&x>0).sort((a,b)=>a-b);const availableV=(state.variants||[]).map(x=>typeof x?.v==="number"?x.v:undefined).filter(x=>typeof x==="number"&&x>0).sort((a,b)=>a-b);const availableI=(state.images||[]).map(x=>typeof x?.i==="number"?x.i:undefined).filter(x=>typeof x==="number"&&x>0).sort((a,b)=>a-b);const availableP=(state.pictures||[]).map(x=>typeof x?.p==="number"?x.p:undefined).filter(x=>typeof x==="number"&&x>0).sort((a,b)=>a-b);return {availableA,availableV,availableI,availableP}}async function ensurePreviewExists(chatWd,previewRel){const pAbs=path.join(chatWd,previewRel);await fs.promises.access(pAbs,fs.constants.F_OK);}function classifyVisionError(errMsg){if(/\b503\b/.test(errMsg)){return "The Vision API is reachable, but the configured vision model is not available for inference. Check the configured vision model key and loaded model state."}if(/aborted|aborterror|timed out|timeout/i.test(errMsg)){return "The Vision API request timed out before the model returned."}if(/ECONNREFUSED|ENOTFOUND|ECONNRESET|network socket|fetch failed/i.test(errMsg)){return "The Vision API is not reachable. Check the configured vision/embedding API base URL and try again."}return /Vision API/i.test(errMsg)?errMsg:`Vision API error: ${errMsg}`}function analyzeTimeoutMs(itemCount){return Math.min(6e5,Math.max(18e4,itemCount*6e4))}function createAnalyseImageTool(ctl){return sdk.tool({name:"analyse_image",description:`Inspect existing media items (images, pictures, variants, attachments) for generation metadata and, optionally, visual content.
PRIMARY use — generation metadata:
Call this tool whenever the user asks how an image was generated, what settings were used, or wants to reuse generation parameters (prompt, model, sampler, seed, steps, guidance scale, LoRA, source images, …). Those parameters are embedded in the PNG file and are returned automatically — no vision prompt needed.
SECONDARY use — visual description (on demand only):
Only request a visual description when the user explicitly asks you to describe or analyze the image content. Pass a prompt in the 'prompt' parameter. Without a prompt, no vision model is invoked and no description is returned.
Parameters:
- targets: Field in the JSON argument object. Pass a JSON array of notations, e.g. analyse_image({"targets":["a1", "v2"]}). Notation: aN=attachment, vN=variant, iN=image, pN=picture.
- prompt: (optional) Vision prompt for visual description — omit unless explicitly requested.
${formatPluginMeta$1()}`,parameters:AnalyseImageParamsShape,implementation:async(args,ctx)=>{try{const strict=false;let targets;if(Array.isArray(args?.targets)){targets=args.targets;}else if(typeof args?.targets==="string"&&args.targets.trim().startsWith("[")){try{const parsed=JSON.parse(args.targets.trim());targets=Array.isArray(parsed)?parsed.map(s=>String(s).trim()).filter(Boolean):[];}catch{targets=[];}}else {targets=[];}const prompt=typeof args?.prompt==="string"?args.prompt:"";const globalConfig=getGlobalConfig$1(ctl);const configuredVisionPrompt=getGlobalString$1(globalConfig,"visionPrompt",process.env.VISION_PROMPT||defaultPluginSettings.visionPrompt);const effectivePrompt=(prompt||configuredVisionPrompt||"").trim();const{parsed,invalid:invalidRaw}=parseTargets(targets);const workingDir=ctl.getWorkingDirectory();if(typeof workingDir!=="string"||!workingDir.trim()){return "analyse_image failed: working directory not available."}const chatWd=workingDir;try{await syncAttachmentsToState(chatWd,false,Number.MAX_SAFE_INTEGER);}catch(syncErr){console.warn("[analyse_image] attachment sync failed (non-fatal):",syncErr?.message??syncErr);}const state=await readState(chatWd);const{availableA,availableV,availableI,availableP}=getAvailable(state);if(invalidRaw.length>0&&strict);const analysisItems=[];const originalFilePaths=new Map;const displayNames=new Map;const missingNotations=new Set;const missingDetails=[];const addItem=async(notation,rec,previewField,originalAbsPath,displayName)=>{if(!rec){missingNotations.add(notation);missingDetails.push(notation);return false}const previewRel=typeof rec[previewField]==="string"?String(rec[previewField]):"";if(!previewRel.trim()){missingNotations.add(notation);missingDetails.push(`${notation} (missing preview)`);return false}try{await ensurePreviewExists(chatWd,previewRel);analysisItems.push({id:notation,filePath:path.join(chatWd,previewRel)});if(originalAbsPath){originalFilePaths.set(notation,originalAbsPath);}const dn=displayName||(originalAbsPath?path.basename(originalAbsPath):undefined);if(dn){displayNames.set(notation,dn);}return true}catch{missingNotations.add(notation);missingDetails.push(`${notation} (preview file missing)`);return false}};for(const n of parsed.a){const rec=(state.attachments||[]).find(x=>x?.a===n);const origAbs=rec?.originAbs??(rec?.filename?path.join(chatWd,rec.filename):undefined);const origName=typeof rec?.originalName==="string"&&rec.originalName?rec.originalName:undefined;await addItem(`a${n}`,rec,"preview",origAbs,origName);}for(const n of parsed.v){const rec=(state.variants||[]).find(x=>x?.v===n);const origAbs=rec?.filename?path.join(chatWd,rec.filename):undefined;await addItem(`v${n}`,rec,"preview",origAbs);}for(const n of parsed.i){const rec=(state.images||[]).find(x=>x?.i===n);const origAbs=rec?.filename?path.join(chatWd,rec.filename):undefined;await addItem(`i${n}`,rec,"preview",origAbs);}for(const n of parsed.p){const rec=(state.pictures||[]).find(x=>x?.p===n);const origAbs=rec?.filename?path.join(chatWd,rec.filename):undefined;await addItem(`p${n}`,rec,"preview",origAbs);}if(missingDetails.length>0&&strict);if(analysisItems.length===0){const hint=`Available: `+`a=[${availableA.map(x=>`a${x}`).join(", ")||"(none)"}] `+`v=[${availableV.map(x=>`v${x}`).join(", ")||"(none)"}] `+`i=[${availableI.map(x=>`i${x}`).join(", ")||"(none)"}] `+`p=[${availableP.map(x=>`p${x}`).join(", ")||"(none)"}]`;return `analyse_image: no valid targets found. ${hint}`}let visionError=null;const visionResults=new Map;let totalInferenceTimeMs=null;if(effectivePrompt){const envServerMaxTokens=Number.parseInt(process.env.SERVER_MAX_TOKENS||"",10);const envServerTemperature=Number.parseFloat(process.env.SERVER_TEMPERATURE||"");const configuredMaxTokens=Math.floor(getGlobalNumber$1(globalConfig,"serverMaxTokens",Number.isFinite(envServerMaxTokens)&&envServerMaxTokens>0?envServerMaxTokens:defaultPluginSettings.serverMaxTokens));const configuredTemperature=getGlobalNumber$1(globalConfig,"serverTemperature",Number.isFinite(envServerTemperature)?envServerTemperature:defaultPluginSettings.serverTemperature);const lmStudioConfig={baseUrl:getGlobalString$1(globalConfig,"embeddingBaseUrl",process.env.LMSTUDIO_VISION_API_BASE_URL||defaultPluginSettings.embeddingBaseUrl),apiKey:getGlobalString$1(globalConfig,"embeddingApiKey",process.env.LMSTUDIO_VISION_API_KEY||defaultPluginSettings.embeddingApiKey),model:getGlobalString$1(globalConfig,"qwen3VlModelPath",process.env.LMSTUDIO_VISION_MODEL_KEY||defaultPluginSettings.qwen3VlModelPath),prompt:effectivePrompt,maxTokens:configuredMaxTokens,temperature:configuredTemperature,timeoutMs:analyzeTimeoutMs(1)};try{const totalSteps=analysisItems.length+2;reportToolStatus(ctx,`Analyzing ${analysisItems.length} image${analysisItems.length===1?"":"s"}...`);reportToolStep(ctx,1,totalSteps,`Preparing ${analysisItems.length} image${analysisItems.length===1?"":"s"} for visual analysis...`);const ready=await ensureLmStudioVisionInstanceReady({baseUrl:lmStudioConfig.baseUrl,apiKey:lmStudioConfig.apiKey,modelKey:lmStudioConfig.model||"",status:message=>{try{ctx.status(message);}catch{}}});if(!ready.ok){throw new Error(ready.error)}let totalMs=0;for(let idx=0;idx<analysisItems.length;idx++){const item=analysisItems[idx];reportToolStep(ctx,idx+2,totalSteps,`Analyzing ${item.id} (${idx+1}/${analysisItems.length})...`);const batchResult=await analyzeLmStudioVisionBatch([item],lmStudioConfig);for(const r of batchResult.results){visionResults.set(r.id,r.text.trim()||"(no description)");}totalMs+=batchResult.totalInferenceTimeMs;}totalInferenceTimeMs=totalMs;reportToolStep(ctx,totalSteps,totalSteps,"Formatting analysis results...");}catch(e){visionError=classifyVisionError(e.message||String(e));}}const includeGenMeta=process.env.INCLUDE_GENERATION_METADATA!=="false";const lines=[];lines.push(`Analysis results (${analysisItems.length} image${analysisItems.length!==1?"s":""}):`);lines.push("");if(visionError){lines.push(`Note: Visual analysis unavailable — ${visionError}`);lines.push("");}for(const item of analysisItems){const{id}=item;const displayName=displayNames.get(id);const origPath=originalFilePaths.get(id);const header=displayName?`${id} — ${displayName}`:id;lines.push(`- ${header}`);if(effectivePrompt){if(visionError){lines.push(` Visual: (not available)`);}else {lines.push(` Visual: ${visionResults.get(id)??"(no description)"}`);}}if(includeGenMeta){if(origPath&&origPath.toLowerCase().endsWith(".png")){const meta=readPngGenerationMeta(origPath);if(meta){lines.push(formatGenerationMeta(meta));}else {lines.push(` (No embedded generation metadata)`);}}else if(origPath&&/\.jpe?g$/i.test(origPath)){const metadata=await readCameraImageMetadata(origPath);if(Object.keys(metadata).length>0){lines.push(` EXIF JSON: ${JSON.stringify(metadata)}`);}else {lines.push(` (No embedded EXIF metadata)`);}}else if(origPath){lines.push(` (No embedded generation metadata — not a PNG file)`);}}lines.push("");}if(totalInferenceTimeMs!==null){lines.push(`Total inference time: ${Math.round(totalInferenceTimeMs)}ms`);}try{const statusSuffix=visionError?" (vision unavailable)":" successfully";ctx.status(`Analyzed ${analysisItems.length} image${analysisItems.length!==1?"s":""}${statusSuffix}`);}catch{}return lines.join("\n")}catch(e){return `analyse_image failed: ${String(e?.message||e)}`}}})}
function isoStampCompact(){const d=new Date;const year=d.getUTCFullYear();const month=String(d.getUTCMonth()+1).padStart(2,"0");const day=String(d.getUTCDate()).padStart(2,"0");const hours=String(d.getUTCHours()).padStart(2,"0");const minutes=String(d.getUTCMinutes()).padStart(2,"0");const seconds=String(d.getUTCSeconds()).padStart(2,"0");const millis=String(d.getUTCMilliseconds()).padStart(3,"0");return `${year}${month}${day}T${hours}${minutes}${seconds}${millis}Z`}function parsePrefixedNotation(s){const t=String(s||"").trim().toLowerCase();const m=t.match(/^([avip])(\d+)$/);if(!m)return null;const idx=Math.max(1,parseInt(m[2],10));const pool=m[1]==="a"?"attachment":m[1]==="v"?"variant":m[1]==="i"?"image":"picture";return {pool,index:idx}}function formatPluginMeta(){try{const cwd=process.cwd();const pkg=JSON.parse(fs.readFileSync(path.join(cwd,"package.json"),"utf-8"));const mf=JSON.parse(fs.readFileSync(path.join(cwd,"manifest.json"),"utf-8"));const id=mf?.owner&&mf?.name?`${mf.owner}/${mf.name}`:pkg?.name||"ceveyne/analyse-image";return `Plugin-Identifier: ${id}
Plugin version: ${pkg?.version||""}`}catch{return "Plugin-Identifier: ceveyne/analyse-image"}}function getGlobalConfig(ctl){const ctlAny=ctl;const getter=ctlAny.getGlobalPluginConfig||ctlAny.getGlobalConfig;if(!getter)return null;try{return getter.call(ctl,globalConfigSchematics)}catch{return null}}function getGlobalString(gcfg,key,fallback){try{const value=gcfg?.get(key);return typeof value==="string"?value:fallback}catch{return fallback}}function getGlobalNumber(gcfg,key,fallback){try{const value=gcfg?.get(key);return typeof value==="number"&&Number.isFinite(value)?value:fallback}catch{return fallback}}function getGlobalBoolean(gcfg,key,fallback){try{const value=gcfg?.get(key);return typeof value==="boolean"?value:fallback}catch{return fallback}}const FlexibleTargetsList=zod.z.union([zod.z.string().transform(s=>(s.match(/[aivp]\d+/gi)??[]).map(x=>x.toLowerCase())),zod.z.array(zod.z.string())]).refine(arr=>arr.length>=1,"targets must contain at least one notation").refine(arr=>arr.length<=16,"targets must contain at most 16 notations");const DetectObjectParamsShape={targets:FlexibleTargetsList.optional().describe("One or more image notations to process. Each notation is a letter followed by a number: "+"a=attachment (a1, a2, …), i=generated image (i1, i2, …), v=variant (v1, v2, …), p=picture (p1, p2, …). "+'Pass as a JSON array, e.g. ["a1", "i3"]. '+"Omit when there is exactly one image — it will be selected automatically."),task:zod.z.string().optional().default("").describe("What to detect. Omit for full-image general object detection. "+"Use natural language to target specific subjects (e.g. 'all faces and hands', 'the dog', 'cars and bicycles').")};function createDetectObjectTool(ctl){return sdk.tool({name:"detect_object",description:`Detect objects in one or more images and draw colored bounding boxes on each result.
For each source image, returns a new annotated image (saved as iN) with bounding boxes for each detected object, plus a JSON summary with labels, coordinates, and crop percentages. Uses Qwen3-VL for detection.
Parameters:
- targets: JSON array of image notations, e.g. ["a1", "i3"]. Notation: a=attachment, i=generated image, v=variant, p=picture. Omit when there is exactly one image.
- task: What to detect. Omit for full-image general object detection. Use natural language to target specific subjects (e.g. 'all faces and hands', 'the dog', 'cars and bicycles').
${formatPluginMeta()}`,parameters:DetectObjectParamsShape,implementation:async(args,ctx)=>{try{let rawTargets=[];if(Array.isArray(args?.targets)){rawTargets=args.targets.map(s=>String(s).trim()).filter(Boolean);}else if(typeof args?.targets==="string"&&args.targets.trim()){const trimmed=args.targets.trim();if(trimmed.startsWith("[")){try{const parsed=JSON.parse(trimmed);if(Array.isArray(parsed)){rawTargets=parsed.map(s=>String(s).trim()).filter(Boolean);}else {rawTargets=trimmed.split(/[\s,]+/).map(s=>s.trim()).filter(Boolean);}}catch{rawTargets=trimmed.split(/[\s,]+/).map(s=>s.trim()).filter(Boolean);}}else {rawTargets=trimmed.split(/[\s,]+/).map(s=>s.trim()).filter(Boolean);}}const task=typeof args?.task==="string"&&args.task.trim()?args.task.trim():"";console.log("[detect_object] invoked",{targets:rawTargets,task});let currentLmChatId=null;let currentLmWorkingDir=null;try{const chatCtx=await getActiveChatContext();if(chatCtx?.chatId)currentLmChatId=chatCtx.chatId;if(chatCtx?.workingDir)currentLmWorkingDir=chatCtx.workingDir;}catch{}if(!currentLmChatId){try{const resolved=await resolveActiveLMStudioChatId();if(resolved?.ok)currentLmChatId=resolved.chatId;}catch{}}const primaryOutDir=currentLmWorkingDir||(currentLmChatId?getLMStudioWorkingDir(currentLmChatId):undefined);if(!primaryOutDir){console.error("[detect_object] could not resolve working directory");return {content:[{type:"text",text:"detect_object failed: could not resolve LM Studio chat working directory."}],isError:true}}console.log("[detect_object] primaryOutDir:",primaryOutDir);await fs.promises.mkdir(primaryOutDir,{recursive:true}).catch(()=>{});console.log("[detect_object] syncing attachments...");try{await syncAttachmentsToState(primaryOutDir,false,Number.MAX_SAFE_INTEGER);}catch(e){console.warn("[detect_object] attachment sync failed (non-fatal):",e?.message??e);}console.log("[detect_object] attachment sync done");console.log("[detect_object] reading state...");const st=await readState$1(primaryOutDir);const attachments=Array.isArray(st?.attachments)?st.attachments:[];const pictures=Array.isArray(st?.pictures)?st.pictures:[];const imageRecords=Array.isArray(st?.images)?st.images:[];const images=imageRecords.filter(r=>r&&typeof r.filename==="string").sort((a,b)=>(a.i||0)-(b.i||0)).map(r=>({i:r.i||1,path:path.join(primaryOutDir,r.filename)}));const variantRecords=Array.isArray(st?.variants)?st.variants:[];const variants=variantRecords.filter(v=>v&&typeof v.filename==="string").map(v=>({v:v.v||1,path:path.join(primaryOutDir,v.filename)}));console.log("[detect_object] state:",{attachments:attachments.length,images:images.length,variants:variants.length,pictures:pictures.length});const sourceEntries=[];async function resolveOneBuf(rawCanvas){const pref=parsePrefixedNotation(rawCanvas);if(!pref)throw new Error(`Invalid canvas notation: ${rawCanvas}`);if(pref.pool==="attachment"){const rec=attachments.find(a=>a?.a===pref.index);const previewRel=rec&&typeof rec.preview==="string"?rec.preview:"";if(!previewRel)throw new Error(`Preview for attachment a${pref.index} not found.`);return fs.promises.readFile(path.join(primaryOutDir,previewRel))}else if(pref.pool==="image"){const rec=imageRecords.find(r=>r?.i===pref.index);const previewRel=rec&&typeof rec.preview==="string"?rec.preview:"";if(!previewRel)throw new Error(`Preview for image i${pref.index} not found.`);return fs.promises.readFile(path.join(primaryOutDir,previewRel))}else if(pref.pool==="variant"){const rec=variantRecords.find(v=>v?.v===pref.index);const previewRel=rec&&typeof rec.preview==="string"?rec.preview:"";if(!previewRel)throw new Error(`Preview for variant v${pref.index} not found.`);return fs.promises.readFile(path.join(primaryOutDir,previewRel))}else {const rec=pictures.find(p=>p?.p===pref.index);const previewRel=rec&&typeof rec.preview==="string"?rec.preview:"";if(!previewRel)throw new Error(`Preview for picture p${pref.index} not found.`);return fs.promises.readFile(path.join(primaryOutDir,previewRel))}}async function resolveOriginalBuf(rawCanvas,previewFallback){try{const pref=parsePrefixedNotation(rawCanvas);if(!pref)return previewFallback;if(pref.pool==="attachment"){const rec=attachments.find(a=>a?.a===pref.index);const originAbs=rec&&typeof rec.originAbs==="string"?rec.originAbs:"";if(!originAbs)return previewFallback;return await fs.promises.readFile(originAbs)}let rec;if(pref.pool==="image")rec=imageRecords.find(r=>r?.i===pref.index);else if(pref.pool==="variant")rec=variantRecords.find(v=>v?.v===pref.index);else rec=pictures.find(p=>p?.p===pref.index);const filename=rec&&typeof rec.filename==="string"?rec.filename:"";if(!filename)return previewFallback;return await fs.promises.readFile(path.join(primaryOutDir,filename))}catch{return previewFallback}}try{if(rawTargets.length>0){for(const t of rawTargets){const buf=await resolveOneBuf(t);const origBuf=await resolveOriginalBuf(t,buf);sourceEntries.push({id:t,buf,origBuf});}}else {const total=attachments.length+variantRecords.length+imageRecords.length+pictures.length;if(total===0)throw new Error("No source image available.");if(total>1)throw new Error("Ambiguous source — specify targets explicitly.");let buf;let id;if(attachments.length===1){const rec=attachments[0];const previewRel=rec&&typeof rec.preview==="string"?rec.preview:"";if(!previewRel)throw new Error("Preview for attachment not found.");buf=await fs.promises.readFile(path.join(primaryOutDir,previewRel));id=`a${typeof rec.a==="number"?rec.a:1}`;}else if(variantRecords.length===1){const rec=variantRecords[0];const previewRel=rec&&typeof rec.preview==="string"?rec.preview:"";if(!previewRel)throw new Error("Preview for variant not found.");buf=await fs.promises.readFile(path.join(primaryOutDir,previewRel));id=`v${typeof rec.v==="number"?rec.v:1}`;}else if(imageRecords.length===1){const rec=imageRecords[0];const previewRel=rec&&typeof rec.preview==="string"?rec.preview:"";if(!previewRel)throw new Error("Preview for image not found.");buf=await fs.promises.readFile(path.join(primaryOutDir,previewRel));id=`i${typeof rec.i==="number"?rec.i:1}`;}else {const rec=pictures[0];const previewRel=rec&&typeof rec.preview==="string"?rec.preview:"";if(!previewRel)throw new Error("Preview for picture not found.");buf=await fs.promises.readFile(path.join(primaryOutDir,previewRel));id=`p${rec.p??1}`;}const origBuf=await resolveOriginalBuf(id,buf);sourceEntries.push({id,buf,origBuf});}}catch(e){return {content:[{type:"text",text:String(e?.message||e)}],isError:true}}console.log("[detect_object] sources resolved:",sourceEntries.map(s=>s.id));const globalConfig=getGlobalConfig(ctl);const embedPngMetadata=getGlobalBoolean(globalConfig,"embedPngMetadata",defaultPluginSettings.embedPngMetadata);const visionBaseUrl=getGlobalString(globalConfig,"embeddingBaseUrl",process.env.LMSTUDIO_VISION_API_BASE_URL||defaultPluginSettings.embeddingBaseUrl);const visionApiKey=getGlobalString(globalConfig,"embeddingApiKey",process.env.LMSTUDIO_VISION_API_KEY||defaultPluginSettings.embeddingApiKey);const visionModelKey=getGlobalString(globalConfig,"qwen3VlModelPath",process.env.LMSTUDIO_VISION_MODEL_KEY||defaultPluginSettings.qwen3VlModelPath);const envDetectMaxTokens=Number.parseInt(process.env.DETECT_MAX_TOKENS||"",10);const envDetectTemperature=Number.parseFloat(process.env.DETECT_TEMPERATURE||"");const configuredDetectMaxTokens=Math.floor(getGlobalNumber(globalConfig,"detectMaxTokens",Number.isFinite(envDetectMaxTokens)&&envDetectMaxTokens>0?envDetectMaxTokens:defaultPluginSettings.detectMaxTokens));const configuredDetectTemperature=getGlobalNumber(globalConfig,"detectTemperature",Number.isFinite(envDetectTemperature)?envDetectTemperature:defaultPluginSettings.detectTemperature);const detectionConfig={task,odPrompt:getGlobalString(globalConfig,"qwen3VlOdPrompt",process.env.DETECT_OD_PROMPT||defaultPluginSettings.qwen3VlOdPrompt)||undefined,maxTokens:configuredDetectMaxTokens,temperature:configuredDetectTemperature,timeoutMs:12e4};const tmpPaths=[];const detectionItems=[];for(const entry of sourceEntries){const tmpPath=path.join(primaryOutDir,`_tmp_detect_src_${entry.id}_${Date.now()}.png`);await fs.promises.writeFile(tmpPath,entry.buf);tmpPaths.push(tmpPath);detectionItems.push({id:entry.id,filePath:tmpPath});}console.log("[detect_object] calling detection API for",detectionItems.length,"items");const progressTotalSteps=sourceEntries.length*2+4;let batchResult={results:[],totalInferenceTimeMs:0,backend:"vision-api"};try{reportToolStatus(ctx,`Detecting objects in ${sourceEntries.length} image${sourceEntries.length===1?"":"s"}...`);reportToolStep(ctx,1,progressTotalSteps,`Preparing ${sourceEntries.length} image${sourceEntries.length===1?"":"s"} for object detection...`);const ready=await ensureLmStudioVisionInstanceReady({baseUrl:visionBaseUrl,apiKey:visionApiKey,modelKey:visionModelKey,status:message=>{try{ctx.status(message);}catch{}}});if(!ready.ok){throw new Error(ready.error)}for(let idx=0;idx<detectionItems.length;idx++){const item=detectionItems[idx];reportToolStep(ctx,idx+2,progressTotalSteps,`Detecting objects in ${item.id} (${idx+1}/${detectionItems.length})...`);const singleResult=await detectLmStudioVisionBatch([item],{...detectionConfig,baseUrl:visionBaseUrl,apiKey:visionApiKey,model:visionModelKey});batchResult.results.push(...singleResult.results);batchResult.totalInferenceTimeMs+=singleResult.totalInferenceTimeMs;batchResult.backend=singleResult.backend;}console.log("[detect_object] detection API returned:",{results:batchResult.results.length,totalMs:batchResult.totalInferenceTimeMs});try{const totalObjects=batchResult.results.reduce((s,r)=>s+(r.objects?.length??0),0);const ms=Math.round(batchResult.totalInferenceTimeMs);reportToolStep(ctx,sourceEntries.length+2,progressTotalSteps,`${totalObjects} object${totalObjects===1?"":"s"} found across ${batchResult.results.length} image${batchResult.results.length===1?"":"s"} (${ms}ms); drawing bounding boxes...`);}catch{}}finally{for(const tp of tmpPaths)await fs.promises.unlink(tp).catch(()=>{});}if(!batchResult.results.length){return {content:[{type:"text",text:"detect_object: no results returned from detection API."}],isError:true}}const variantPreviewSpec=VARIANT_FULL_CONFIG.preview;const stamp=isoStampCompact();let nextI=Math.max(1,st.counters?.nextImageI??1);const imageRecordsForState=[];const resultEntries=[];const httpBase=await getHealthyServerBaseUrl();for(let idx=0;idx<batchResult.results.length;idx++){const detResult=batchResult.results[idx];const sourceId=sourceEntries[idx]?.id??`canvas${idx+1}`;const origBuf=sourceEntries[idx].origBuf;const currentI=nextI++;reportToolStep(ctx,sourceEntries.length+3+idx,progressTotalSteps,`Drawing boxes for ${sourceId} (${idx+1}/${batchResult.results.length})...`);const bboxes=detResult.objects.map(o=>o.bbox);console.log(`[detect_object] drawing ${bboxes.length} bboxes for ${sourceId}...`);const annotatedBuf=await drawBboxesOnImage(origBuf,bboxes,{sourceDims:{width:detResult.imageWidth,height:detResult.imageHeight},palette:true});const analysis={schema:"ceveyne.image-analysis/v1",tool:"detect_object",sourceNotation:sourceId,inference:{model:visionModelKey,...task?{query:task}:{},detectorPromptSha256:crypto.createHash("sha256").update(detectionConfig.odPrompt??"").digest("hex"),maxTokens:configuredDetectMaxTokens,temperature:configuredDetectTemperature},render:{palette:true},detections:detResult.objects.map(object=>({label:object.label,bbox:{x1:object.bbox[0],y1:object.bbox[1],x2:object.bbox[2],y2:object.bbox[3]}}))};const savedBuffer=embedPngMetadata?injectXmpIntoBuffer(annotatedBuf,{...task?{prompt:task}:{},model:visionModelKey,mode:"object_detection",generatedBy:`${getSelfPluginIdentifier()}/detect_object`,creatorTool:`${getSelfPluginIdentifier()}/detect_object`,analysis}):annotatedBuf;const baseName=`image-${stamp}-i${currentI}`;const savedPath=path.join(primaryOutDir,`${baseName}.png`);await fs.promises.writeFile(savedPath,savedBuffer);const savedFileUrl=url.pathToFileURL(savedPath).toString();const savedSize=savedBuffer.length;console.log(`[detect_object] annotated image written: ${savedPath} (${savedSize} bytes)`);let preview=null;try{const p=await generatePreviewFromBuffer(savedBuffer,primaryOutDir,`${baseName}.png`,variantPreviewSpec);preview={ok:true,filePath:p.previewAbs,fileName:p.previewFilename,fileUrl:url.pathToFileURL(p.previewAbs).toString(),size_bytes:p.data.length,width:p.width,height:p.height,mimeType:"image/jpeg",dataBase64:p.data.toString("base64")};}catch(e){console.warn(`[detect_object] preview generation failed for ${sourceId}:`,String(e));}const httpOriginal=httpBase?toHttpOriginalUrl(`${baseName}.png`,httpBase,currentLmChatId||undefined):"";const httpPreview=(()=>{if(!httpBase||!currentLmChatId||!preview?.fileName)return "";return toHttpPreviewUrl(preview.fileName,httpBase,currentLmChatId)})();imageRecordsForState.push({filename:`${baseName}.png`,preview:preview?`preview-${baseName}.jpg`:undefined,i:currentI,sourceTool:`${getSelfPluginIdentifier()}/detect_object`,detectSource:sourceId,task,imageWidth:detResult.imageWidth,imageHeight:detResult.imageHeight,analysisMetadata:analysis,detections:detResult.objects.map(o=>({label:o.label,bbox:{x1:o.bbox[0],y1:o.bbox[1],x2:o.bbox[2],y2:o.bbox[3]},crop:{cropLeft:o.cropLeft,cropRight:o.cropRight,cropTop:o.cropTop,cropBottom:o.cropBottom}}))});resultEntries.push({id:sourceId,i:currentI,detResult,savedPath,savedFileUrl,savedSize,preview,httpOriginal,httpPreview});}console.log("[detect_object] updating state...");reportToolStep(ctx,progressTotalSteps-1,progressTotalSteps,"Updating image state and audit log...");try{const stateForUpdate=await readState$1(primaryOutDir);const appendResult=appendImages(stateForUpdate,imageRecordsForState);if(appendResult.changed){await writeStateAtomic(primaryOutDir,stateForUpdate);console.log("[detect_object] state written, nextImageI:",stateForUpdate.counters?.nextImageI);}}catch(e){console.warn("[detect_object] state update failed:",String(e));}try{const audit=buildAuditLogger({backend:"detect_object",mode:"detect_object",requestId:undefined});if(currentLmChatId)audit.setChatId(currentLmChatId);audit.setUserRequest({targets:rawTargets,task});audit.setOutput({images:resultEntries.map(r=>({id:r.id,i:r.i,detections:r.detResult.objects.length,path:r.savedPath,url:r.savedFileUrl,bytes:r.savedSize,...r.httpOriginal?{http_url:r.httpOriginal}:{},...r.preview?{preview_path:r.preview.filePath,preview_url:r.preview.fileUrl}:{},...r.httpPreview?{http_preview_url:r.httpPreview}:{}}))});await audit.write();}catch(e){console.warn("[detect_object] audit logging failed:",String(e));}const envPreviewRaw=process.env["PREVIEW_IN_CHAT"];const previewInChat=envPreviewRaw===undefined?true:envPreviewRaw==="1"||envPreviewRaw.toLowerCase()==="true";const summaries=resultEntries.map(r=>({tool:"detect_object",source:r.id,i:r.i,imageWidth:r.detResult.imageWidth,imageHeight:r.detResult.imageHeight,inferenceTimeMs:r.detResult.inferenceTimeMs,detections:r.detResult.objects.map(o=>({label:o.label,bbox:{x1:o.bbox[0],y1:o.bbox[1],x2:o.bbox[2],y2:o.bbox[3]},crop:{left:{pct:o.cropLeft,px:Math.round(o.cropLeft/100*r.detResult.imageWidth)},right:{pct:o.cropRight,px:Math.round(o.cropRight/100*r.detResult.imageWidth)},top:{pct:o.cropTop,px:Math.round(o.cropTop/100*r.detResult.imageHeight)},bottom:{pct:o.cropBottom,px:Math.round(o.cropBottom/100*r.detResult.imageHeight)}},crop_tool_hint:"Pass crop.left.pct as cropLeft, crop.right.pct as cropRight, crop.top.pct as cropTop, crop.bottom.pct as cropBottom to the crop tool."}))}));const reviewHint="Carefully examine the preview to make absolutely sure that the object detection matches your intent.";const content=[];reportToolStep(ctx,progressTotalSteps,progressTotalSteps,"Assembling detection result...");for(const r of resultEntries){const fallbackPreviewUrl=r.preview?.fileUrl||r.savedFileUrl;const previewLine=`Preview i${r.i}: ${r.httpPreview?r.httpPreview:fallbackPreviewUrl}`;const originalLine=`Original i${r.i}: ${r.httpOriginal?r.httpOriginal:r.savedFileUrl}`;if(previewInChat&&r.preview){const fname=String(r.preview.fileName||"");content.push({type:"image",fileName:fname,mimeType:r.preview.mimeType,markdown:``,$hint:"This is an image file. Present the image to the user by using the markdown above."});}}const totalMs=Math.round(batchResult.totalInferenceTimeMs);if(totalMs>0){content.push({type:"text",text:`Total inference time: ${totalMs}ms`});}content.push({type:"text",text:JSON.stringify(summaries.length===1?summaries[0]:summaries),...previewInChat?{}:{$hint:reviewHint}});return {content}}catch(error){return {content:[{type:"text",text:`detect_object failed: ${error.message||String(error)}`}],isError:true}}}})}
async function toolsProvider(ctl){try{const getter=ctl.getGlobalPluginConfig||ctl.getGlobalConfig;const gcfg=getter?getter.call(ctl,globalConfigSchematics):null;if(gcfg){const embeddingBaseUrl=gcfg.get("embeddingBaseUrl");if(typeof embeddingBaseUrl==="string"&&embeddingBaseUrl.trim()){process.env.LMSTUDIO_VISION_API_BASE_URL=embeddingBaseUrl.trim();}const embeddingApiKey=gcfg.get("embeddingApiKey");if(typeof embeddingApiKey==="string"){process.env.LMSTUDIO_VISION_API_KEY=embeddingApiKey;}const prompt=gcfg.get("visionPrompt");if(typeof prompt==="string"){process.env.VISION_PROMPT=prompt;}const inclMeta=gcfg.get("includeGenerationMetadata");if(typeof inclMeta==="boolean"){process.env.INCLUDE_GENERATION_METADATA=inclMeta?"true":"false";}const previewInChat=gcfg.get("PREVIEW_IN_CHAT");if(typeof previewInChat==="boolean"){process.env.PREVIEW_IN_CHAT=previewInChat?"true":"false";}const httpPort=gcfg.get("HTTP_SERVER_PORT");if(typeof httpPort==="number"&&Number.isFinite(httpPort)&&httpPort>0){process.env.HTTP_SERVER_PORT=String(Math.floor(httpPort));}const visionMaxTokens=gcfg.get("serverMaxTokens");if(typeof visionMaxTokens==="number"&&Number.isFinite(visionMaxTokens)&&visionMaxTokens>0){process.env.SERVER_MAX_TOKENS=String(Math.floor(visionMaxTokens));}const visionTemperature=gcfg.get("serverTemperature");if(typeof visionTemperature==="number"&&Number.isFinite(visionTemperature)){process.env.SERVER_TEMPERATURE=String(visionTemperature);}const qwen3VlModelPath=gcfg.get("qwen3VlModelPath");if(typeof qwen3VlModelPath==="string"){process.env.LMSTUDIO_VISION_MODEL_KEY=qwen3VlModelPath;}const qwen3VlOdPrompt=gcfg.get("qwen3VlOdPrompt");if(typeof qwen3VlOdPrompt==="string"){process.env.DETECT_OD_PROMPT=qwen3VlOdPrompt;}const detectMaxTokens=gcfg.get("detectMaxTokens");if(typeof detectMaxTokens==="number"&&Number.isFinite(detectMaxTokens)&&detectMaxTokens>0){process.env.DETECT_MAX_TOKENS=String(Math.floor(detectMaxTokens));}const detectTemperature=gcfg.get("detectTemperature");if(typeof detectTemperature==="number"&&Number.isFinite(detectTemperature)){process.env.DETECT_TEMPERATURE=String(detectTemperature);}}}catch{}const tools=[];tools.push(createAnalyseImageTool(ctl));tools.push(createDetectObjectTool(ctl));tools.push(createAnnotateImageTool(ctl));return tools}
async function main(context){context.withGlobalConfigSchematics(globalConfigSchematics).withToolsProvider(toolsProvider);}
exports.main = main;