@elizaos/plugin-elizacloud 1.5.19 → 1.7.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser/index.browser.js +3 -3
- package/dist/browser/index.browser.js.map +9 -7
- package/dist/cjs/index.node.cjs +557 -44
- package/dist/cjs/index.node.js.map +13 -7
- package/dist/database/adapter.d.ts +27 -0
- package/dist/database/direct-adapter.d.ts +31 -0
- package/dist/database/index.d.ts +14 -0
- package/dist/database/schema.d.ts +11 -0
- package/dist/database/types.d.ts +22 -0
- package/dist/index.browser.d.ts +16 -2
- package/dist/index.d.ts +11 -0
- package/dist/node/index.node.js +557 -44
- package/dist/node/index.node.js.map +13 -7
- package/dist/storage/index.d.ts +8 -0
- package/dist/storage/service.d.ts +62 -0
- package/dist/storage/types.d.ts +44 -0
- package/dist/utils/config.d.ts +7 -0
- package/package.json +11 -1
package/dist/node/index.node.js
CHANGED
|
@@ -18,7 +18,7 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
|
18
18
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
19
19
|
|
|
20
20
|
// src/index.ts
|
|
21
|
-
import { logger as
|
|
21
|
+
import { logger as logger13, ModelType as ModelType6 } from "@elizaos/core";
|
|
22
22
|
|
|
23
23
|
// src/init.ts
|
|
24
24
|
import { logger as logger2 } from "@elizaos/core";
|
|
@@ -76,6 +76,9 @@ function getLargeModel(runtime) {
|
|
|
76
76
|
function getImageDescriptionModel(runtime) {
|
|
77
77
|
return getSetting(runtime, "ELIZAOS_CLOUD_IMAGE_DESCRIPTION_MODEL", "gpt-4o-mini") ?? "gpt-4o-mini";
|
|
78
78
|
}
|
|
79
|
+
function getImageGenerationModel(runtime) {
|
|
80
|
+
return getSetting(runtime, "ELIZAOS_CLOUD_IMAGE_GENERATION_MODEL", "openai/gpt-5-nano") ?? "openai/gpt-5-nano";
|
|
81
|
+
}
|
|
79
82
|
function getExperimentalTelemetry(runtime) {
|
|
80
83
|
const setting = getSetting(runtime, "ELIZAOS_CLOUD_EXPERIMENTAL_TELEMETRY", "false");
|
|
81
84
|
const normalizedSetting = String(setting).toLowerCase();
|
|
@@ -234,6 +237,31 @@ function getJsonRepairFunction() {
|
|
|
234
237
|
}
|
|
235
238
|
};
|
|
236
239
|
}
|
|
240
|
+
function detectAudioMimeType(buffer) {
|
|
241
|
+
if (buffer.length < 12) {
|
|
242
|
+
return "application/octet-stream";
|
|
243
|
+
}
|
|
244
|
+
if (buffer[0] === 82 && buffer[1] === 73 && buffer[2] === 70 && buffer[3] === 70 && buffer[8] === 87 && buffer[9] === 65 && buffer[10] === 86 && buffer[11] === 69) {
|
|
245
|
+
return "audio/wav";
|
|
246
|
+
}
|
|
247
|
+
if (buffer[0] === 73 && buffer[1] === 68 && buffer[2] === 51 || buffer[0] === 255 && (buffer[1] & 224) === 224) {
|
|
248
|
+
return "audio/mpeg";
|
|
249
|
+
}
|
|
250
|
+
if (buffer[0] === 79 && buffer[1] === 103 && buffer[2] === 103 && buffer[3] === 83) {
|
|
251
|
+
return "audio/ogg";
|
|
252
|
+
}
|
|
253
|
+
if (buffer[0] === 102 && buffer[1] === 76 && buffer[2] === 97 && buffer[3] === 67) {
|
|
254
|
+
return "audio/flac";
|
|
255
|
+
}
|
|
256
|
+
if (buffer[4] === 102 && buffer[5] === 116 && buffer[6] === 121 && buffer[7] === 112) {
|
|
257
|
+
return "audio/mp4";
|
|
258
|
+
}
|
|
259
|
+
if (buffer[0] === 26 && buffer[1] === 69 && buffer[2] === 223 && buffer[3] === 163) {
|
|
260
|
+
return "audio/webm";
|
|
261
|
+
}
|
|
262
|
+
logger4.warn("Could not detect audio format from buffer, using generic binary type");
|
|
263
|
+
return "application/octet-stream";
|
|
264
|
+
}
|
|
237
265
|
async function webStreamToNodeStream(webStream) {
|
|
238
266
|
try {
|
|
239
267
|
const { Readable } = await import("node:stream");
|
|
@@ -409,7 +437,7 @@ async function handleImageGeneration(runtime, params) {
|
|
|
409
437
|
const numImages = params.n || 1;
|
|
410
438
|
const size = params.size || "1024x1024";
|
|
411
439
|
const prompt = params.prompt;
|
|
412
|
-
const modelName =
|
|
440
|
+
const modelName = getImageGenerationModel(runtime);
|
|
413
441
|
logger7.log(`[ELIZAOS_CLOUD] Using IMAGE model: ${modelName}`);
|
|
414
442
|
const baseURL = getBaseURL(runtime);
|
|
415
443
|
const aspectRatioMap = {
|
|
@@ -517,8 +545,89 @@ async function handleImageDescription(runtime, params) {
|
|
|
517
545
|
};
|
|
518
546
|
}
|
|
519
547
|
}
|
|
520
|
-
// src/models/
|
|
548
|
+
// src/models/transcription.ts
|
|
521
549
|
import { logger as logger8 } from "@elizaos/core";
|
|
550
|
+
async function handleTranscription(runtime, input) {
|
|
551
|
+
let modelName = getSetting(runtime, "ELIZAOS_CLOUD_TRANSCRIPTION_MODEL", "gpt-4o-mini-transcribe");
|
|
552
|
+
logger8.log(`[ELIZAOS_CLOUD] Using TRANSCRIPTION model: ${modelName}`);
|
|
553
|
+
const baseURL = getBaseURL(runtime);
|
|
554
|
+
let blob;
|
|
555
|
+
let extraParams = null;
|
|
556
|
+
if (input instanceof Blob || input instanceof File) {
|
|
557
|
+
blob = input;
|
|
558
|
+
} else if (Buffer.isBuffer(input)) {
|
|
559
|
+
const detectedMimeType = detectAudioMimeType(input);
|
|
560
|
+
logger8.debug(`Auto-detected audio MIME type: ${detectedMimeType}`);
|
|
561
|
+
blob = new Blob([input], { type: detectedMimeType });
|
|
562
|
+
} else if (typeof input === "object" && input !== null && "audio" in input && input.audio != null) {
|
|
563
|
+
const params = input;
|
|
564
|
+
if (!(params.audio instanceof Blob) && !(params.audio instanceof File) && !Buffer.isBuffer(params.audio)) {
|
|
565
|
+
throw new Error("TRANSCRIPTION param 'audio' must be a Blob/File/Buffer.");
|
|
566
|
+
}
|
|
567
|
+
if (Buffer.isBuffer(params.audio)) {
|
|
568
|
+
let mimeType = params.mimeType;
|
|
569
|
+
if (!mimeType) {
|
|
570
|
+
mimeType = detectAudioMimeType(params.audio);
|
|
571
|
+
logger8.debug(`Auto-detected audio MIME type: ${mimeType}`);
|
|
572
|
+
} else {
|
|
573
|
+
logger8.debug(`Using provided MIME type: ${mimeType}`);
|
|
574
|
+
}
|
|
575
|
+
blob = new Blob([params.audio], { type: mimeType });
|
|
576
|
+
} else {
|
|
577
|
+
blob = params.audio;
|
|
578
|
+
}
|
|
579
|
+
extraParams = params;
|
|
580
|
+
if (typeof params.model === "string" && params.model) {
|
|
581
|
+
modelName = params.model;
|
|
582
|
+
}
|
|
583
|
+
} else {
|
|
584
|
+
throw new Error("TRANSCRIPTION expects a Blob/File/Buffer or an object { audio: Blob/File/Buffer, mimeType?, language?, response_format?, timestampGranularities?, prompt?, temperature?, model? }");
|
|
585
|
+
}
|
|
586
|
+
const mime = blob.type || "audio/webm";
|
|
587
|
+
const filename = blob.name || (mime.includes("mp3") || mime.includes("mpeg") ? "recording.mp3" : mime.includes("ogg") ? "recording.ogg" : mime.includes("wav") ? "recording.wav" : mime.includes("webm") ? "recording.webm" : "recording.bin");
|
|
588
|
+
const formData = new FormData;
|
|
589
|
+
formData.append("file", blob, filename);
|
|
590
|
+
formData.append("model", String(modelName));
|
|
591
|
+
if (extraParams) {
|
|
592
|
+
if (typeof extraParams.language === "string") {
|
|
593
|
+
formData.append("language", String(extraParams.language));
|
|
594
|
+
}
|
|
595
|
+
if (typeof extraParams.response_format === "string") {
|
|
596
|
+
formData.append("response_format", String(extraParams.response_format));
|
|
597
|
+
}
|
|
598
|
+
if (typeof extraParams.prompt === "string") {
|
|
599
|
+
formData.append("prompt", String(extraParams.prompt));
|
|
600
|
+
}
|
|
601
|
+
if (typeof extraParams.temperature === "number") {
|
|
602
|
+
formData.append("temperature", String(extraParams.temperature));
|
|
603
|
+
}
|
|
604
|
+
if (Array.isArray(extraParams.timestampGranularities)) {
|
|
605
|
+
for (const g of extraParams.timestampGranularities) {
|
|
606
|
+
formData.append("timestamp_granularities[]", String(g));
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
try {
|
|
611
|
+
const response = await fetch(`${baseURL}/audio/transcriptions`, {
|
|
612
|
+
method: "POST",
|
|
613
|
+
headers: {
|
|
614
|
+
...getAuthHeader(runtime)
|
|
615
|
+
},
|
|
616
|
+
body: formData
|
|
617
|
+
});
|
|
618
|
+
if (!response.ok) {
|
|
619
|
+
throw new Error(`Failed to transcribe audio: ${response.status} ${response.statusText}`);
|
|
620
|
+
}
|
|
621
|
+
const data = await response.json();
|
|
622
|
+
return data.text || "";
|
|
623
|
+
} catch (error) {
|
|
624
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
625
|
+
logger8.error(`TRANSCRIPTION error: ${message}`);
|
|
626
|
+
throw error;
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
// src/models/speech.ts
|
|
630
|
+
import { logger as logger9 } from "@elizaos/core";
|
|
522
631
|
async function fetchTextToSpeech(runtime, options) {
|
|
523
632
|
const defaultModel = getSetting(runtime, "ELIZAOS_CLOUD_TTS_MODEL", "gpt-4o-mini-tts");
|
|
524
633
|
const defaultVoice = getSetting(runtime, "ELIZAOS_CLOUD_TTS_VOICE", "nova");
|
|
@@ -560,10 +669,367 @@ async function fetchTextToSpeech(runtime, options) {
|
|
|
560
669
|
throw new Error(`Failed to fetch speech from ElizaOS Cloud TTS: ${message}`);
|
|
561
670
|
}
|
|
562
671
|
}
|
|
672
|
+
async function handleTextToSpeech(runtime, input) {
|
|
673
|
+
const options = typeof input === "string" ? { text: input } : input;
|
|
674
|
+
const resolvedModel = options.model || getSetting(runtime, "ELIZAOS_CLOUD_TTS_MODEL", "gpt-4o-mini-tts");
|
|
675
|
+
logger9.log(`[ELIZAOS_CLOUD] Using TEXT_TO_SPEECH model: ${resolvedModel}`);
|
|
676
|
+
try {
|
|
677
|
+
const speechStream = await fetchTextToSpeech(runtime, options);
|
|
678
|
+
return speechStream;
|
|
679
|
+
} catch (error) {
|
|
680
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
681
|
+
logger9.error(`Error in TEXT_TO_SPEECH: ${message}`);
|
|
682
|
+
throw error;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
// src/models/tokenization.ts
|
|
686
|
+
import { ModelType as ModelType5 } from "@elizaos/core";
|
|
687
|
+
import { encodingForModel } from "js-tiktoken";
|
|
688
|
+
async function tokenizeText(model, prompt) {
|
|
689
|
+
const modelName = model === ModelType5.TEXT_SMALL ? process.env.ELIZAOS_CLOUD_SMALL_MODEL ?? process.env.SMALL_MODEL ?? "gpt-5-nano" : process.env.LARGE_MODEL ?? "gpt-5-mini";
|
|
690
|
+
const tokens = encodingForModel(modelName).encode(prompt);
|
|
691
|
+
return tokens;
|
|
692
|
+
}
|
|
693
|
+
async function detokenizeText(model, tokens) {
|
|
694
|
+
const modelName = model === ModelType5.TEXT_SMALL ? process.env.ELIZAOS_CLOUD_SMALL_MODEL ?? process.env.SMALL_MODEL ?? "gpt-5-nano" : process.env.ELIZAOS_CLOUD_LARGE_MODEL ?? process.env.LARGE_MODEL ?? "gpt-5-mini";
|
|
695
|
+
return encodingForModel(modelName).decode(tokens);
|
|
696
|
+
}
|
|
697
|
+
async function handleTokenizerEncode(_runtime, { prompt, modelType = ModelType5.TEXT_LARGE }) {
|
|
698
|
+
return await tokenizeText(modelType ?? ModelType5.TEXT_LARGE, prompt);
|
|
699
|
+
}
|
|
700
|
+
async function handleTokenizerDecode(_runtime, { tokens, modelType = ModelType5.TEXT_LARGE }) {
|
|
701
|
+
return await detokenizeText(modelType ?? ModelType5.TEXT_LARGE, tokens);
|
|
702
|
+
}
|
|
703
|
+
// src/database/adapter.ts
|
|
704
|
+
import { logger as logger10 } from "@elizaos/core";
|
|
705
|
+
import pluginSql from "@elizaos/plugin-sql/node";
|
|
706
|
+
var DEFAULT_CLOUD_URL = "https://www.elizacloud.ai";
|
|
707
|
+
async function createCloudDatabaseAdapter(config) {
|
|
708
|
+
const baseUrl = config.baseUrl || DEFAULT_CLOUD_URL;
|
|
709
|
+
logger10.info({ src: "plugin:elizacloud", agentId: config.agentId }, "Provisioning cloud database");
|
|
710
|
+
const response = await provisionCloudDatabase(config.apiKey, baseUrl, config.agentId);
|
|
711
|
+
if (!response.success || !response.connectionUrl) {
|
|
712
|
+
logger10.error({
|
|
713
|
+
src: "plugin:elizacloud",
|
|
714
|
+
error: response.error,
|
|
715
|
+
agentId: config.agentId
|
|
716
|
+
}, "Failed to provision cloud database");
|
|
717
|
+
return null;
|
|
718
|
+
}
|
|
719
|
+
logger10.info({ src: "plugin:elizacloud", agentId: config.agentId }, "Cloud database provisioned successfully");
|
|
720
|
+
const adapter = pluginSql.createDatabaseAdapter({ postgresUrl: response.connectionUrl }, config.agentId);
|
|
721
|
+
logger10.info({ src: "plugin:elizacloud", agentId: config.agentId }, "Cloud database adapter created using PostgreSQL connection");
|
|
722
|
+
return adapter;
|
|
723
|
+
}
|
|
724
|
+
async function provisionCloudDatabase(apiKey, baseUrl, agentId) {
|
|
725
|
+
try {
|
|
726
|
+
const response = await fetch(`${baseUrl}/api/v1/database/provision`, {
|
|
727
|
+
method: "POST",
|
|
728
|
+
headers: {
|
|
729
|
+
Authorization: `Bearer ${apiKey}`,
|
|
730
|
+
"Content-Type": "application/json"
|
|
731
|
+
},
|
|
732
|
+
body: JSON.stringify({
|
|
733
|
+
agentId,
|
|
734
|
+
type: "postgresql"
|
|
735
|
+
})
|
|
736
|
+
});
|
|
737
|
+
if (!response.ok) {
|
|
738
|
+
const errorText = await response.text();
|
|
739
|
+
return {
|
|
740
|
+
success: false,
|
|
741
|
+
error: `Cloud database provisioning failed: ${response.status} ${errorText}`
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
const data = await response.json();
|
|
745
|
+
return {
|
|
746
|
+
success: true,
|
|
747
|
+
connectionUrl: data.connectionUrl,
|
|
748
|
+
expiresAt: data.expiresAt
|
|
749
|
+
};
|
|
750
|
+
} catch (error) {
|
|
751
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
752
|
+
return {
|
|
753
|
+
success: false,
|
|
754
|
+
error: `Network error during database provisioning: ${message}`
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
class CloudDatabaseAdapter {
|
|
760
|
+
config;
|
|
761
|
+
adapter = null;
|
|
762
|
+
constructor(config) {
|
|
763
|
+
this.config = config;
|
|
764
|
+
}
|
|
765
|
+
async initialize() {
|
|
766
|
+
if (this.adapter) {
|
|
767
|
+
return this.adapter;
|
|
768
|
+
}
|
|
769
|
+
this.adapter = await createCloudDatabaseAdapter(this.config);
|
|
770
|
+
return this.adapter;
|
|
771
|
+
}
|
|
772
|
+
getAdapter() {
|
|
773
|
+
return this.adapter;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// src/storage/service.ts
|
|
778
|
+
import { logger as logger11 } from "@elizaos/core";
|
|
779
|
+
var DEFAULT_CLOUD_URL2 = "https://www.elizacloud.ai";
|
|
780
|
+
var STORAGE_ENDPOINT = "/api/v1/storage/files";
|
|
781
|
+
function createCloudStorageService(config) {
|
|
782
|
+
return new CloudStorageService(config);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
class CloudStorageService {
|
|
786
|
+
apiKey;
|
|
787
|
+
baseUrl;
|
|
788
|
+
constructor(config) {
|
|
789
|
+
this.apiKey = config.apiKey;
|
|
790
|
+
this.baseUrl = config.baseUrl || DEFAULT_CLOUD_URL2;
|
|
791
|
+
}
|
|
792
|
+
async upload(file, options = {}) {
|
|
793
|
+
try {
|
|
794
|
+
const formData = new FormData;
|
|
795
|
+
let blob;
|
|
796
|
+
if (Buffer.isBuffer(file)) {
|
|
797
|
+
blob = new Blob([file], {
|
|
798
|
+
type: options.contentType || "application/octet-stream"
|
|
799
|
+
});
|
|
800
|
+
} else {
|
|
801
|
+
blob = file;
|
|
802
|
+
}
|
|
803
|
+
const filename = options.filename || (file instanceof File ? file.name : "file") || "upload";
|
|
804
|
+
formData.append("file", blob, filename);
|
|
805
|
+
if (options.metadata) {
|
|
806
|
+
formData.append("metadata", JSON.stringify(options.metadata));
|
|
807
|
+
}
|
|
808
|
+
const response = await fetch(`${this.baseUrl}${STORAGE_ENDPOINT}`, {
|
|
809
|
+
method: "POST",
|
|
810
|
+
headers: {
|
|
811
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
812
|
+
},
|
|
813
|
+
body: formData
|
|
814
|
+
});
|
|
815
|
+
if (!response.ok) {
|
|
816
|
+
const errorData = await response.json().catch(() => ({}));
|
|
817
|
+
if (response.status === 402) {
|
|
818
|
+
return {
|
|
819
|
+
success: false,
|
|
820
|
+
error: `Insufficient credits. Required: ${errorData.required || "unknown"}, Available: ${errorData.available || "unknown"}. Top up at ${errorData.topUpUrl || "/dashboard/billing"}`
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
return {
|
|
824
|
+
success: false,
|
|
825
|
+
error: `Upload failed: ${response.status} ${errorData.error || "Unknown error"}`
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
const data = await response.json();
|
|
829
|
+
logger11.info({ src: "plugin:elizacloud", cost: data.cost, remaining: data.creditsRemaining }, "Storage upload successful");
|
|
830
|
+
return {
|
|
831
|
+
success: true,
|
|
832
|
+
id: data.id,
|
|
833
|
+
url: data.url,
|
|
834
|
+
pathname: data.pathname,
|
|
835
|
+
contentType: data.contentType,
|
|
836
|
+
size: data.size,
|
|
837
|
+
cost: data.cost,
|
|
838
|
+
creditsRemaining: data.creditsRemaining
|
|
839
|
+
};
|
|
840
|
+
} catch (error) {
|
|
841
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
842
|
+
logger11.error({ src: "plugin:elizacloud", error }, "Storage upload failed");
|
|
843
|
+
return {
|
|
844
|
+
success: false,
|
|
845
|
+
error: `Upload error: ${message}`
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
async download(id, url) {
|
|
850
|
+
if (url) {
|
|
851
|
+
try {
|
|
852
|
+
const response = await fetch(url);
|
|
853
|
+
if (!response.ok) {
|
|
854
|
+
logger11.error({ src: "plugin:elizacloud", status: response.status, url }, "Storage direct download failed");
|
|
855
|
+
return null;
|
|
856
|
+
}
|
|
857
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
858
|
+
return Buffer.from(arrayBuffer);
|
|
859
|
+
} catch (error) {
|
|
860
|
+
logger11.error({ src: "plugin:elizacloud", error }, "Storage direct download error");
|
|
861
|
+
return null;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
try {
|
|
865
|
+
const response = await fetch(`${this.baseUrl}${STORAGE_ENDPOINT}/${id}?download=true`, {
|
|
866
|
+
headers: {
|
|
867
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
868
|
+
},
|
|
869
|
+
redirect: "follow"
|
|
870
|
+
});
|
|
871
|
+
if (!response.ok) {
|
|
872
|
+
logger11.error({ src: "plugin:elizacloud", status: response.status }, "Storage download failed");
|
|
873
|
+
return null;
|
|
874
|
+
}
|
|
875
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
876
|
+
return Buffer.from(arrayBuffer);
|
|
877
|
+
} catch (error) {
|
|
878
|
+
logger11.error({ src: "plugin:elizacloud", error }, "Storage download error");
|
|
879
|
+
return null;
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
async list(options = {}) {
|
|
883
|
+
try {
|
|
884
|
+
const params = new URLSearchParams;
|
|
885
|
+
if (options.prefix)
|
|
886
|
+
params.set("prefix", options.prefix);
|
|
887
|
+
if (options.limit)
|
|
888
|
+
params.set("limit", String(options.limit));
|
|
889
|
+
if (options.cursor)
|
|
890
|
+
params.set("cursor", options.cursor);
|
|
891
|
+
const response = await fetch(`${this.baseUrl}${STORAGE_ENDPOINT}?${params.toString()}`, {
|
|
892
|
+
headers: {
|
|
893
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
894
|
+
}
|
|
895
|
+
});
|
|
896
|
+
if (!response.ok) {
|
|
897
|
+
logger11.error({ src: "plugin:elizacloud", status: response.status }, "Storage list failed");
|
|
898
|
+
return { items: [], hasMore: false };
|
|
899
|
+
}
|
|
900
|
+
const data = await response.json();
|
|
901
|
+
return {
|
|
902
|
+
items: data.items || [],
|
|
903
|
+
cursor: data.cursor,
|
|
904
|
+
hasMore: data.hasMore || false
|
|
905
|
+
};
|
|
906
|
+
} catch (error) {
|
|
907
|
+
logger11.error({ src: "plugin:elizacloud", error }, "Storage list error");
|
|
908
|
+
return { items: [], hasMore: false };
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
async delete(id, url) {
|
|
912
|
+
if (!url) {
|
|
913
|
+
logger11.error({ src: "plugin:elizacloud" }, "Storage delete requires file URL");
|
|
914
|
+
return false;
|
|
915
|
+
}
|
|
916
|
+
try {
|
|
917
|
+
const params = new URLSearchParams({ url });
|
|
918
|
+
const response = await fetch(`${this.baseUrl}${STORAGE_ENDPOINT}/${id}?${params.toString()}`, {
|
|
919
|
+
method: "DELETE",
|
|
920
|
+
headers: {
|
|
921
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
922
|
+
}
|
|
923
|
+
});
|
|
924
|
+
if (!response.ok) {
|
|
925
|
+
const errorData = await response.json().catch(() => ({}));
|
|
926
|
+
logger11.error({ src: "plugin:elizacloud", status: response.status, error: errorData.error }, "Storage delete failed");
|
|
927
|
+
return false;
|
|
928
|
+
}
|
|
929
|
+
return true;
|
|
930
|
+
} catch (error) {
|
|
931
|
+
logger11.error({ src: "plugin:elizacloud", error }, "Storage delete error");
|
|
932
|
+
return false;
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
async getStats() {
|
|
936
|
+
try {
|
|
937
|
+
const response = await fetch(`${this.baseUrl}${STORAGE_ENDPOINT}?stats=true`, {
|
|
938
|
+
headers: {
|
|
939
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
940
|
+
}
|
|
941
|
+
});
|
|
942
|
+
if (!response.ok) {
|
|
943
|
+
return null;
|
|
944
|
+
}
|
|
945
|
+
const data = await response.json();
|
|
946
|
+
return {
|
|
947
|
+
totalFiles: data.stats?.totalFiles || 0,
|
|
948
|
+
totalSize: data.stats?.totalSize || 0,
|
|
949
|
+
totalSizeGB: data.stats?.totalSizeGB || 0,
|
|
950
|
+
pricing: data.pricing || {}
|
|
951
|
+
};
|
|
952
|
+
} catch (error) {
|
|
953
|
+
logger11.error({ src: "plugin:elizacloud", error }, "Storage stats error");
|
|
954
|
+
return null;
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
// src/database/direct-adapter.ts
|
|
959
|
+
import { logger as logger12 } from "@elizaos/core";
|
|
960
|
+
import pluginSql2 from "@elizaos/plugin-sql/node";
|
|
961
|
+
function createDatabaseAdapter(config, agentId) {
|
|
962
|
+
const adapter = pluginSql2.createDatabaseAdapter({ postgresUrl: config.postgresUrl }, agentId);
|
|
963
|
+
logger12.info({ src: "plugin:elizacloud", agentId }, "Direct database adapter created");
|
|
964
|
+
return adapter;
|
|
965
|
+
}
|
|
966
|
+
async function createDirectDatabaseAdapter(config, agentId) {
|
|
967
|
+
return createDatabaseAdapter(config, agentId);
|
|
968
|
+
}
|
|
969
|
+
// src/database/schema.ts
|
|
970
|
+
import pluginSql3 from "@elizaos/plugin-sql/node";
|
|
971
|
+
var {
|
|
972
|
+
agentTable,
|
|
973
|
+
roomTable,
|
|
974
|
+
participantTable,
|
|
975
|
+
memoryTable,
|
|
976
|
+
embeddingTable,
|
|
977
|
+
entityTable,
|
|
978
|
+
relationshipTable,
|
|
979
|
+
componentTable,
|
|
980
|
+
taskTable,
|
|
981
|
+
logTable,
|
|
982
|
+
cacheTable,
|
|
983
|
+
worldTable,
|
|
984
|
+
serverTable,
|
|
985
|
+
messageTable,
|
|
986
|
+
messageServerTable,
|
|
987
|
+
messageServerAgentsTable,
|
|
988
|
+
channelTable,
|
|
989
|
+
channelParticipantsTable
|
|
990
|
+
} = pluginSql3.schema;
|
|
991
|
+
var serverAgentsTable = serverTable;
|
|
563
992
|
// src/index.ts
|
|
993
|
+
var cloudStorageInstance = null;
|
|
994
|
+
function getCloudStorage() {
|
|
995
|
+
return cloudStorageInstance;
|
|
996
|
+
}
|
|
997
|
+
async function initializeCloudDatabase(runtime) {
|
|
998
|
+
const apiKey = getApiKey(runtime);
|
|
999
|
+
const baseUrl = getBaseURL(runtime);
|
|
1000
|
+
if (!apiKey) {
|
|
1001
|
+
logger13.warn({ src: "plugin:elizacloud" }, "Cloud database enabled but no API key found - skipping database initialization");
|
|
1002
|
+
return;
|
|
1003
|
+
}
|
|
1004
|
+
logger13.info({ src: "plugin:elizacloud", agentId: runtime.agentId }, "Initializing cloud database");
|
|
1005
|
+
const adapter = await createCloudDatabaseAdapter({
|
|
1006
|
+
apiKey,
|
|
1007
|
+
baseUrl,
|
|
1008
|
+
agentId: runtime.agentId
|
|
1009
|
+
});
|
|
1010
|
+
if (adapter) {
|
|
1011
|
+
runtime.registerDatabaseAdapter(adapter);
|
|
1012
|
+
logger13.info({ src: "plugin:elizacloud", agentId: runtime.agentId }, "Cloud database adapter registered successfully");
|
|
1013
|
+
} else {
|
|
1014
|
+
logger13.error({ src: "plugin:elizacloud", agentId: runtime.agentId }, "Failed to initialize cloud database adapter");
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
function initializeCloudStorage(runtime) {
|
|
1018
|
+
const apiKey = getApiKey(runtime);
|
|
1019
|
+
const baseUrl = getBaseURL(runtime);
|
|
1020
|
+
if (!apiKey) {
|
|
1021
|
+
logger13.warn({ src: "plugin:elizacloud" }, "No API key found - cloud storage will not be available");
|
|
1022
|
+
return;
|
|
1023
|
+
}
|
|
1024
|
+
cloudStorageInstance = new CloudStorageService({
|
|
1025
|
+
apiKey,
|
|
1026
|
+
baseUrl
|
|
1027
|
+
});
|
|
1028
|
+
logger13.info({ src: "plugin:elizacloud", agentId: runtime.agentId }, "Cloud storage service initialized");
|
|
1029
|
+
}
|
|
564
1030
|
var elizaOSCloudPlugin = {
|
|
565
1031
|
name: "elizaOSCloud",
|
|
566
|
-
description: "ElizaOS Cloud plugin -
|
|
1032
|
+
description: "ElizaOS Cloud plugin - Complete AI, storage, and database solution. Provides multi-model inference (GPT-4, Claude, Gemini), embeddings, image generation, transcription, TTS, managed PostgreSQL database, and cloud file storage. A single plugin that replaces all other AI and database plugins.",
|
|
567
1033
|
config: {
|
|
568
1034
|
ELIZAOS_CLOUD_API_KEY: process.env.ELIZAOS_CLOUD_API_KEY,
|
|
569
1035
|
ELIZAOS_CLOUD_BASE_URL: process.env.ELIZAOS_CLOUD_BASE_URL,
|
|
@@ -577,19 +1043,39 @@ var elizaOSCloudPlugin = {
|
|
|
577
1043
|
ELIZAOS_CLOUD_EMBEDDING_DIMENSIONS: process.env.ELIZAOS_CLOUD_EMBEDDING_DIMENSIONS,
|
|
578
1044
|
ELIZAOS_CLOUD_IMAGE_DESCRIPTION_MODEL: process.env.ELIZAOS_CLOUD_IMAGE_DESCRIPTION_MODEL,
|
|
579
1045
|
ELIZAOS_CLOUD_IMAGE_DESCRIPTION_MAX_TOKENS: process.env.ELIZAOS_CLOUD_IMAGE_DESCRIPTION_MAX_TOKENS,
|
|
1046
|
+
ELIZAOS_CLOUD_IMAGE_GENERATION_MODEL: process.env.ELIZAOS_CLOUD_IMAGE_GENERATION_MODEL,
|
|
1047
|
+
ELIZAOS_CLOUD_TTS_MODEL: process.env.ELIZAOS_CLOUD_TTS_MODEL,
|
|
1048
|
+
ELIZAOS_CLOUD_TTS_VOICE: process.env.ELIZAOS_CLOUD_TTS_VOICE,
|
|
1049
|
+
ELIZAOS_CLOUD_TRANSCRIPTION_MODEL: process.env.ELIZAOS_CLOUD_TRANSCRIPTION_MODEL,
|
|
1050
|
+
ELIZAOS_CLOUD_DATABASE: process.env.ELIZAOS_CLOUD_DATABASE,
|
|
1051
|
+
ELIZAOS_CLOUD_STORAGE: process.env.ELIZAOS_CLOUD_STORAGE,
|
|
580
1052
|
ELIZAOS_CLOUD_EXPERIMENTAL_TELEMETRY: process.env.ELIZAOS_CLOUD_EXPERIMENTAL_TELEMETRY
|
|
581
1053
|
},
|
|
1054
|
+
priority: -1,
|
|
582
1055
|
async init(config, runtime) {
|
|
583
1056
|
initializeOpenAI(config, runtime);
|
|
1057
|
+
if (!isBrowser()) {
|
|
1058
|
+
initializeCloudStorage(runtime);
|
|
1059
|
+
}
|
|
1060
|
+
const cloudDatabaseEnabled = runtime.getSetting("ELIZAOS_CLOUD_DATABASE") === "true" || process.env.ELIZAOS_CLOUD_DATABASE === "true";
|
|
1061
|
+
if (cloudDatabaseEnabled && !isBrowser()) {
|
|
1062
|
+
await initializeCloudDatabase(runtime);
|
|
1063
|
+
}
|
|
584
1064
|
},
|
|
585
1065
|
models: {
|
|
586
|
-
[
|
|
587
|
-
[
|
|
588
|
-
[
|
|
589
|
-
[
|
|
590
|
-
[
|
|
591
|
-
[
|
|
592
|
-
[
|
|
1066
|
+
[ModelType6.TEXT_SMALL]: handleTextSmall,
|
|
1067
|
+
[ModelType6.TEXT_LARGE]: handleTextLarge,
|
|
1068
|
+
[ModelType6.TEXT_REASONING_SMALL]: handleTextSmall,
|
|
1069
|
+
[ModelType6.TEXT_REASONING_LARGE]: handleTextLarge,
|
|
1070
|
+
[ModelType6.OBJECT_SMALL]: handleObjectSmall,
|
|
1071
|
+
[ModelType6.OBJECT_LARGE]: handleObjectLarge,
|
|
1072
|
+
[ModelType6.TEXT_EMBEDDING]: handleTextEmbedding,
|
|
1073
|
+
[ModelType6.TEXT_TOKENIZER_ENCODE]: handleTokenizerEncode,
|
|
1074
|
+
[ModelType6.TEXT_TOKENIZER_DECODE]: handleTokenizerDecode,
|
|
1075
|
+
[ModelType6.IMAGE]: handleImageGeneration,
|
|
1076
|
+
[ModelType6.IMAGE_DESCRIPTION]: handleImageDescription,
|
|
1077
|
+
[ModelType6.TRANSCRIPTION]: handleTranscription,
|
|
1078
|
+
[ModelType6.TEXT_TO_SPEECH]: handleTextToSpeech
|
|
593
1079
|
},
|
|
594
1080
|
tests: [
|
|
595
1081
|
{
|
|
@@ -605,7 +1091,7 @@ var elizaOSCloudPlugin = {
|
|
|
605
1091
|
}
|
|
606
1092
|
});
|
|
607
1093
|
const data = await response.json();
|
|
608
|
-
|
|
1094
|
+
logger13.log({ data: data?.data?.length ?? "N/A" }, "Models Available");
|
|
609
1095
|
if (!response.ok) {
|
|
610
1096
|
throw new Error(`Failed to validate OpenAI API key: ${response.statusText}`);
|
|
611
1097
|
}
|
|
@@ -615,13 +1101,13 @@ var elizaOSCloudPlugin = {
|
|
|
615
1101
|
name: "ELIZAOS_CLOUD_test_text_embedding",
|
|
616
1102
|
fn: async (runtime) => {
|
|
617
1103
|
try {
|
|
618
|
-
const embedding = await runtime.useModel(
|
|
1104
|
+
const embedding = await runtime.useModel(ModelType6.TEXT_EMBEDDING, {
|
|
619
1105
|
text: "Hello, world!"
|
|
620
1106
|
});
|
|
621
|
-
|
|
1107
|
+
logger13.log({ embedding }, "embedding");
|
|
622
1108
|
} catch (error) {
|
|
623
1109
|
const message = error instanceof Error ? error.message : String(error);
|
|
624
|
-
|
|
1110
|
+
logger13.error(`Error in test_text_embedding: ${message}`);
|
|
625
1111
|
throw error;
|
|
626
1112
|
}
|
|
627
1113
|
}
|
|
@@ -630,16 +1116,16 @@ var elizaOSCloudPlugin = {
|
|
|
630
1116
|
name: "ELIZAOS_CLOUD_test_text_large",
|
|
631
1117
|
fn: async (runtime) => {
|
|
632
1118
|
try {
|
|
633
|
-
const text = await runtime.useModel(
|
|
1119
|
+
const text = await runtime.useModel(ModelType6.TEXT_LARGE, {
|
|
634
1120
|
prompt: "What is the nature of reality in 10 words?"
|
|
635
1121
|
});
|
|
636
1122
|
if (text.length === 0) {
|
|
637
1123
|
throw new Error("Failed to generate text");
|
|
638
1124
|
}
|
|
639
|
-
|
|
1125
|
+
logger13.log({ text }, "generated with test_text_large");
|
|
640
1126
|
} catch (error) {
|
|
641
1127
|
const message = error instanceof Error ? error.message : String(error);
|
|
642
|
-
|
|
1128
|
+
logger13.error(`Error in test_text_large: ${message}`);
|
|
643
1129
|
throw error;
|
|
644
1130
|
}
|
|
645
1131
|
}
|
|
@@ -648,16 +1134,16 @@ var elizaOSCloudPlugin = {
|
|
|
648
1134
|
name: "ELIZAOS_CLOUD_test_text_small",
|
|
649
1135
|
fn: async (runtime) => {
|
|
650
1136
|
try {
|
|
651
|
-
const text = await runtime.useModel(
|
|
1137
|
+
const text = await runtime.useModel(ModelType6.TEXT_SMALL, {
|
|
652
1138
|
prompt: "What is the nature of reality in 10 words?"
|
|
653
1139
|
});
|
|
654
1140
|
if (text.length === 0) {
|
|
655
1141
|
throw new Error("Failed to generate text");
|
|
656
1142
|
}
|
|
657
|
-
|
|
1143
|
+
logger13.log({ text }, "generated with test_text_small");
|
|
658
1144
|
} catch (error) {
|
|
659
1145
|
const message = error instanceof Error ? error.message : String(error);
|
|
660
|
-
|
|
1146
|
+
logger13.error(`Error in test_text_small: ${message}`);
|
|
661
1147
|
throw error;
|
|
662
1148
|
}
|
|
663
1149
|
}
|
|
@@ -665,17 +1151,17 @@ var elizaOSCloudPlugin = {
|
|
|
665
1151
|
{
|
|
666
1152
|
name: "ELIZAOS_CLOUD_test_image_generation",
|
|
667
1153
|
fn: async (runtime) => {
|
|
668
|
-
|
|
1154
|
+
logger13.log("ELIZAOS_CLOUD_test_image_generation");
|
|
669
1155
|
try {
|
|
670
|
-
const image = await runtime.useModel(
|
|
1156
|
+
const image = await runtime.useModel(ModelType6.IMAGE, {
|
|
671
1157
|
prompt: "A beautiful sunset over a calm ocean",
|
|
672
1158
|
n: 1,
|
|
673
1159
|
size: "1024x1024"
|
|
674
1160
|
});
|
|
675
|
-
|
|
1161
|
+
logger13.log({ image }, "generated with test_image_generation");
|
|
676
1162
|
} catch (error) {
|
|
677
1163
|
const message = error instanceof Error ? error.message : String(error);
|
|
678
|
-
|
|
1164
|
+
logger13.error(`Error in test_image_generation: ${message}`);
|
|
679
1165
|
throw error;
|
|
680
1166
|
}
|
|
681
1167
|
}
|
|
@@ -684,36 +1170,36 @@ var elizaOSCloudPlugin = {
|
|
|
684
1170
|
name: "image-description",
|
|
685
1171
|
fn: async (runtime) => {
|
|
686
1172
|
try {
|
|
687
|
-
|
|
1173
|
+
logger13.log("ELIZAOS_CLOUD_test_image_description");
|
|
688
1174
|
try {
|
|
689
|
-
const result = await runtime.useModel(
|
|
1175
|
+
const result = await runtime.useModel(ModelType6.IMAGE_DESCRIPTION, "https://upload.wikimedia.org/wikipedia/commons/thumb/1/1c/Vitalik_Buterin_TechCrunch_London_2015_%28cropped%29.jpg/537px-Vitalik_Buterin_TechCrunch_London_2015_%28cropped%29.jpg");
|
|
690
1176
|
if (result && typeof result === "object" && "title" in result && "description" in result) {
|
|
691
|
-
|
|
1177
|
+
logger13.log({ result }, "Image description");
|
|
692
1178
|
} else {
|
|
693
|
-
|
|
1179
|
+
logger13.error("Invalid image description result format:", result);
|
|
694
1180
|
}
|
|
695
1181
|
} catch (e) {
|
|
696
1182
|
const message = e instanceof Error ? e.message : String(e);
|
|
697
|
-
|
|
1183
|
+
logger13.error(`Error in image description test: ${message}`);
|
|
698
1184
|
}
|
|
699
1185
|
} catch (e) {
|
|
700
1186
|
const message = e instanceof Error ? e.message : String(e);
|
|
701
|
-
|
|
1187
|
+
logger13.error(`Error in ELIZAOS_CLOUD_test_image_description: ${message}`);
|
|
702
1188
|
}
|
|
703
1189
|
}
|
|
704
1190
|
},
|
|
705
1191
|
{
|
|
706
1192
|
name: "ELIZAOS_CLOUD_test_transcription",
|
|
707
1193
|
fn: async (runtime) => {
|
|
708
|
-
|
|
1194
|
+
logger13.log("ELIZAOS_CLOUD_test_transcription");
|
|
709
1195
|
try {
|
|
710
1196
|
const response = await fetch("https://upload.wikimedia.org/wikipedia/en/4/40/Chris_Benoit_Voice_Message.ogg");
|
|
711
1197
|
const arrayBuffer = await response.arrayBuffer();
|
|
712
|
-
const transcription = await runtime.useModel(
|
|
713
|
-
|
|
1198
|
+
const transcription = await runtime.useModel(ModelType6.TRANSCRIPTION, Buffer.from(new Uint8Array(arrayBuffer)));
|
|
1199
|
+
logger13.log({ transcription }, "generated with test_transcription");
|
|
714
1200
|
} catch (error) {
|
|
715
1201
|
const message = error instanceof Error ? error.message : String(error);
|
|
716
|
-
|
|
1202
|
+
logger13.error(`Error in test_transcription: ${message}`);
|
|
717
1203
|
throw error;
|
|
718
1204
|
}
|
|
719
1205
|
}
|
|
@@ -722,23 +1208,23 @@ var elizaOSCloudPlugin = {
|
|
|
722
1208
|
name: "ELIZAOS_CLOUD_test_text_tokenizer_encode",
|
|
723
1209
|
fn: async (runtime) => {
|
|
724
1210
|
const prompt = "Hello tokenizer encode!";
|
|
725
|
-
const tokens = await runtime.useModel(
|
|
1211
|
+
const tokens = await runtime.useModel(ModelType6.TEXT_TOKENIZER_ENCODE, { prompt });
|
|
726
1212
|
if (!Array.isArray(tokens) || tokens.length === 0) {
|
|
727
1213
|
throw new Error("Failed to tokenize text: expected non-empty array of tokens");
|
|
728
1214
|
}
|
|
729
|
-
|
|
1215
|
+
logger13.log({ tokens }, "Tokenized output");
|
|
730
1216
|
}
|
|
731
1217
|
},
|
|
732
1218
|
{
|
|
733
1219
|
name: "ELIZAOS_CLOUD_test_text_tokenizer_decode",
|
|
734
1220
|
fn: async (runtime) => {
|
|
735
1221
|
const prompt = "Hello tokenizer decode!";
|
|
736
|
-
const tokens = await runtime.useModel(
|
|
737
|
-
const decodedText = await runtime.useModel(
|
|
1222
|
+
const tokens = await runtime.useModel(ModelType6.TEXT_TOKENIZER_ENCODE, { prompt });
|
|
1223
|
+
const decodedText = await runtime.useModel(ModelType6.TEXT_TOKENIZER_DECODE, { tokens });
|
|
738
1224
|
if (decodedText !== prompt) {
|
|
739
1225
|
throw new Error(`Decoded text does not match original. Expected "${prompt}", got "${decodedText}"`);
|
|
740
1226
|
}
|
|
741
|
-
|
|
1227
|
+
logger13.log({ decodedText }, "Decoded text");
|
|
742
1228
|
}
|
|
743
1229
|
},
|
|
744
1230
|
{
|
|
@@ -751,10 +1237,10 @@ var elizaOSCloudPlugin = {
|
|
|
751
1237
|
if (!response) {
|
|
752
1238
|
throw new Error("Failed to generate speech");
|
|
753
1239
|
}
|
|
754
|
-
|
|
1240
|
+
logger13.log("Generated speech successfully");
|
|
755
1241
|
} catch (error) {
|
|
756
1242
|
const message = error instanceof Error ? error.message : String(error);
|
|
757
|
-
|
|
1243
|
+
logger13.error(`Error in ELIZAOS_CLOUD_test_text_to_speech: ${message}`);
|
|
758
1244
|
throw error;
|
|
759
1245
|
}
|
|
760
1246
|
}
|
|
@@ -765,8 +1251,35 @@ var elizaOSCloudPlugin = {
|
|
|
765
1251
|
};
|
|
766
1252
|
var src_default = elizaOSCloudPlugin;
|
|
767
1253
|
export {
|
|
1254
|
+
worldTable,
|
|
1255
|
+
taskTable,
|
|
1256
|
+
serverTable,
|
|
1257
|
+
serverAgentsTable,
|
|
1258
|
+
roomTable,
|
|
1259
|
+
relationshipTable,
|
|
1260
|
+
pluginSql3 as pluginSql,
|
|
1261
|
+
participantTable,
|
|
1262
|
+
messageTable,
|
|
1263
|
+
messageServerTable,
|
|
1264
|
+
messageServerAgentsTable,
|
|
1265
|
+
memoryTable,
|
|
1266
|
+
logTable,
|
|
1267
|
+
getCloudStorage,
|
|
1268
|
+
entityTable,
|
|
1269
|
+
embeddingTable,
|
|
768
1270
|
elizaOSCloudPlugin,
|
|
769
|
-
src_default as default
|
|
1271
|
+
src_default as default,
|
|
1272
|
+
createDirectDatabaseAdapter,
|
|
1273
|
+
createDatabaseAdapter,
|
|
1274
|
+
createCloudStorageService,
|
|
1275
|
+
createCloudDatabaseAdapter,
|
|
1276
|
+
componentTable,
|
|
1277
|
+
channelTable,
|
|
1278
|
+
channelParticipantsTable,
|
|
1279
|
+
cacheTable,
|
|
1280
|
+
agentTable,
|
|
1281
|
+
CloudStorageService,
|
|
1282
|
+
CloudDatabaseAdapter
|
|
770
1283
|
};
|
|
771
1284
|
|
|
772
|
-
//# debugId=
|
|
1285
|
+
//# debugId=1EA438C51CA2A03164756E2164756E21
|