@ethisyscore/extension-runtime 1.136.0 → 1.137.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/MockPluginStorage-BM3bq99B.d.cts +35 -0
- package/dist/MockPluginStorage-BndTX2RR.d.ts +35 -0
- package/dist/{bridge-client-RJOl8WlU.d.ts → bridge-client-CkxYcZcm.d.ts} +1 -1
- package/dist/{bridge-client-D9EYFq1l.d.cts → bridge-client-DkogtCvn.d.cts} +1 -1
- package/dist/{bridge-envelopes-M5a42rAy.d.cts → bridge-envelopes-BcKu-nQm.d.cts} +72 -1
- package/dist/{bridge-envelopes-M5a42rAy.d.ts → bridge-envelopes-BcKu-nQm.d.ts} +72 -1
- package/dist/host/index.cjs +57 -2
- package/dist/host/index.cjs.map +1 -1
- package/dist/host/index.d.cts +3 -3
- package/dist/host/index.d.ts +3 -3
- package/dist/host/index.js +57 -2
- package/dist/host/index.js.map +1 -1
- package/dist/mock-host/cli.cjs +273 -7
- package/dist/mock-host/cli.cjs.map +1 -1
- package/dist/mock-host/cli.d.cts +10 -3
- package/dist/mock-host/cli.d.ts +10 -3
- package/dist/mock-host/cli.js +272 -8
- package/dist/mock-host/cli.js.map +1 -1
- package/dist/mock-host/index.cjs +236 -1
- package/dist/mock-host/index.cjs.map +1 -1
- package/dist/mock-host/index.d.cts +14 -4
- package/dist/mock-host/index.d.ts +14 -4
- package/dist/mock-host/index.js +236 -2
- package/dist/mock-host/index.js.map +1 -1
- package/dist/plugin/index.cjs +378 -100
- package/dist/plugin/index.cjs.map +1 -1
- package/dist/plugin/index.d.cts +171 -6
- package/dist/plugin/index.d.ts +171 -6
- package/dist/plugin/index.js +366 -101
- package/dist/plugin/index.js.map +1 -1
- package/dist/{transport-UaoDRdm3.d.cts → transport-D1lE-5nt.d.cts} +30 -3
- package/dist/{transport-CFzJQiFG.d.ts → transport-INVtDo2s.d.ts} +30 -3
- package/package.json +1 -1
package/dist/mock-host/cli.d.cts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { SduiNode } from '@ethisyscore/protocol';
|
|
2
|
-
import {
|
|
3
|
-
import '../
|
|
2
|
+
import { i as WorkerCtor, M as McpHttpClient, l as WorkerRemoteDomTransport } from '../transport-D1lE-5nt.cjs';
|
|
3
|
+
import { M as MockPluginStorage } from '../MockPluginStorage-BM3bq99B.cjs';
|
|
4
|
+
import '../bridge-envelopes-BcKu-nQm.cjs';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* `npx mock-host <dir>` — standalone declarative-mock-host dev server.
|
|
@@ -144,6 +145,12 @@ interface BootContractBHostOptions {
|
|
|
144
145
|
*/
|
|
145
146
|
mcpClient?: McpHttpClient;
|
|
146
147
|
}
|
|
148
|
+
/**
|
|
149
|
+
* Where the default mock client's `uploadToStorage` writes. Exported so a test or a dev script can
|
|
150
|
+
* read back what a surface uploaded.
|
|
151
|
+
*/
|
|
152
|
+
declare const defaultMockPluginStorage: MockPluginStorage;
|
|
153
|
+
declare const defaultMockMcpClient: McpHttpClient;
|
|
147
154
|
/**
|
|
148
155
|
* Spawn the in-process worker that drives the Contract B mock host.
|
|
149
156
|
*
|
|
@@ -171,4 +178,4 @@ declare function run(args: string[]): Promise<{
|
|
|
171
178
|
close: () => Promise<void>;
|
|
172
179
|
}>;
|
|
173
180
|
|
|
174
|
-
export { type BootContractBHostOptions, type ParsedArgs, type RenderMode, type ResourceMap, type ToolResultMap, bootContractBHost, loadResources, loadTools, normaliseEntry, parseArgs, renderEntryModule, renderIndexHtml, renderWorkerBundleEntryModule, renderWorkerBundleIndexHtml, run };
|
|
181
|
+
export { type BootContractBHostOptions, type ParsedArgs, type RenderMode, type ResourceMap, type ToolResultMap, bootContractBHost, defaultMockMcpClient, defaultMockPluginStorage, loadResources, loadTools, normaliseEntry, parseArgs, renderEntryModule, renderIndexHtml, renderWorkerBundleEntryModule, renderWorkerBundleIndexHtml, run };
|
package/dist/mock-host/cli.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { SduiNode } from '@ethisyscore/protocol';
|
|
2
|
-
import {
|
|
3
|
-
import '../
|
|
2
|
+
import { i as WorkerCtor, M as McpHttpClient, l as WorkerRemoteDomTransport } from '../transport-INVtDo2s.js';
|
|
3
|
+
import { M as MockPluginStorage } from '../MockPluginStorage-BndTX2RR.js';
|
|
4
|
+
import '../bridge-envelopes-BcKu-nQm.js';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* `npx mock-host <dir>` — standalone declarative-mock-host dev server.
|
|
@@ -144,6 +145,12 @@ interface BootContractBHostOptions {
|
|
|
144
145
|
*/
|
|
145
146
|
mcpClient?: McpHttpClient;
|
|
146
147
|
}
|
|
148
|
+
/**
|
|
149
|
+
* Where the default mock client's `uploadToStorage` writes. Exported so a test or a dev script can
|
|
150
|
+
* read back what a surface uploaded.
|
|
151
|
+
*/
|
|
152
|
+
declare const defaultMockPluginStorage: MockPluginStorage;
|
|
153
|
+
declare const defaultMockMcpClient: McpHttpClient;
|
|
147
154
|
/**
|
|
148
155
|
* Spawn the in-process worker that drives the Contract B mock host.
|
|
149
156
|
*
|
|
@@ -171,4 +178,4 @@ declare function run(args: string[]): Promise<{
|
|
|
171
178
|
close: () => Promise<void>;
|
|
172
179
|
}>;
|
|
173
180
|
|
|
174
|
-
export { type BootContractBHostOptions, type ParsedArgs, type RenderMode, type ResourceMap, type ToolResultMap, bootContractBHost, loadResources, loadTools, normaliseEntry, parseArgs, renderEntryModule, renderIndexHtml, renderWorkerBundleEntryModule, renderWorkerBundleIndexHtml, run };
|
|
181
|
+
export { type BootContractBHostOptions, type ParsedArgs, type RenderMode, type ResourceMap, type ToolResultMap, bootContractBHost, defaultMockMcpClient, defaultMockPluginStorage, loadResources, loadTools, normaliseEntry, parseArgs, renderEntryModule, renderIndexHtml, renderWorkerBundleEntryModule, renderWorkerBundleIndexHtml, run };
|
package/dist/mock-host/cli.js
CHANGED
|
@@ -47,6 +47,29 @@ var MCP_ERROR_CODES = [
|
|
|
47
47
|
function isMcpErrorCode(value) {
|
|
48
48
|
return typeof value === "string" && MCP_ERROR_CODES.includes(value);
|
|
49
49
|
}
|
|
50
|
+
var RETRYABLE = ["rate_limited", "timeout", "unavailable"];
|
|
51
|
+
function isRetryableMcpErrorCode(code) {
|
|
52
|
+
return RETRYABLE.includes(code);
|
|
53
|
+
}
|
|
54
|
+
var McpToolError = class extends Error {
|
|
55
|
+
/** Machine-readable classification. `internal` when the host sent none. */
|
|
56
|
+
code;
|
|
57
|
+
/**
|
|
58
|
+
* Marks the instance for {@link isMcpToolError}. A structural marker rather than a prototype
|
|
59
|
+
* check because the error can be constructed in one bundle and inspected in another, where
|
|
60
|
+
* `instanceof` compares two different class objects and answers false.
|
|
61
|
+
*/
|
|
62
|
+
isMcpToolError = true;
|
|
63
|
+
constructor(message, code = "internal") {
|
|
64
|
+
super(message);
|
|
65
|
+
this.name = "McpHostError";
|
|
66
|
+
this.code = code;
|
|
67
|
+
}
|
|
68
|
+
/** True when this failure is worth retrying unchanged. */
|
|
69
|
+
get retryable() {
|
|
70
|
+
return isRetryableMcpErrorCode(this.code);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
50
73
|
function mcpErrorCodeFromHttpStatus(status) {
|
|
51
74
|
switch (status) {
|
|
52
75
|
case 401:
|
|
@@ -413,6 +436,9 @@ var WorkerRemoteDomTransport = class {
|
|
|
413
436
|
case "ethisys:mcp:uploadDocument":
|
|
414
437
|
this.dispatchMcp(message, "ethisys:mcp:uploadDocument:result", (m) => this.handleUploadDocument(m));
|
|
415
438
|
return;
|
|
439
|
+
case "ethisys:mcp:uploadToStorage":
|
|
440
|
+
this.dispatchMcp(message, "ethisys:mcp:uploadToStorage:result", (m) => this.handleUploadToStorage(m));
|
|
441
|
+
return;
|
|
416
442
|
case "ethisys:mcp:abort":
|
|
417
443
|
this.inFlightAborts.get(message.id ?? "")?.abort();
|
|
418
444
|
return;
|
|
@@ -650,11 +676,63 @@ var WorkerRemoteDomTransport = class {
|
|
|
650
676
|
this.replyError(message.id, "ethisys:mcp:uploadDocument:result", err);
|
|
651
677
|
}
|
|
652
678
|
}
|
|
653
|
-
|
|
679
|
+
async handleUploadToStorage(message) {
|
|
680
|
+
const resultType = "ethisys:mcp:uploadToStorage:result";
|
|
681
|
+
let token;
|
|
682
|
+
try {
|
|
683
|
+
token = await this.capabilityTokenProvider();
|
|
684
|
+
} catch (err) {
|
|
685
|
+
this.replyError(message.id, resultType, err, true);
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
if (this.disposed) {
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
try {
|
|
692
|
+
const result = await this.mcpClient.fetch({
|
|
693
|
+
kind: "uploadToStorage",
|
|
694
|
+
meta: message.meta,
|
|
695
|
+
buffer: message.buffer,
|
|
696
|
+
capabilityToken: token,
|
|
697
|
+
signal: this.requestSignal(message.id)
|
|
698
|
+
});
|
|
699
|
+
this.reportVersionSkew(result);
|
|
700
|
+
this.safePostMessage({
|
|
701
|
+
id: message.id,
|
|
702
|
+
type: resultType,
|
|
703
|
+
ok: result.ok,
|
|
704
|
+
data: result.data,
|
|
705
|
+
error: result.error,
|
|
706
|
+
code: classifyHostResponse(result),
|
|
707
|
+
// The HTTP status, on failure only. A bare number carries no host internals, and it
|
|
708
|
+
// is the only way the plugin can tell 413 (file too large) from 507 (quota) from 503
|
|
709
|
+
// (storage lock busy): the closed MCP code folds all three into `internal`/`unavailable`.
|
|
710
|
+
...!result.ok && typeof result.status === "number" ? { status: result.status } : {}
|
|
711
|
+
});
|
|
712
|
+
} catch (err) {
|
|
713
|
+
this.replyError(message.id, resultType, err, true);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
replyError(id, type, err, includeStatus = false) {
|
|
654
717
|
const message = err instanceof Error ? err.message : "MCP request failed";
|
|
655
|
-
|
|
718
|
+
const status = includeStatus ? statusOf(err) : void 0;
|
|
719
|
+
this.safePostMessage({
|
|
720
|
+
id,
|
|
721
|
+
type,
|
|
722
|
+
ok: false,
|
|
723
|
+
error: message,
|
|
724
|
+
code: classifyHostError(err),
|
|
725
|
+
...status !== void 0 ? { status } : {}
|
|
726
|
+
});
|
|
656
727
|
}
|
|
657
728
|
};
|
|
729
|
+
function statusOf(err) {
|
|
730
|
+
if (err === null || typeof err !== "object") {
|
|
731
|
+
return void 0;
|
|
732
|
+
}
|
|
733
|
+
const candidate = err;
|
|
734
|
+
return typeof candidate.status === "number" ? candidate.status : typeof candidate.statusCode === "number" ? candidate.statusCode : void 0;
|
|
735
|
+
}
|
|
658
736
|
|
|
659
737
|
// src/host/worker/component-registry.ts
|
|
660
738
|
var CONTRACT_B_PRIMITIVES = [
|
|
@@ -678,6 +756,180 @@ var CONTRACT_B_PRIMITIVES = [
|
|
|
678
756
|
];
|
|
679
757
|
new Set(CONTRACT_B_PRIMITIVES);
|
|
680
758
|
|
|
759
|
+
// src/plugin/pluginStorageUpload.ts
|
|
760
|
+
var PLUGIN_STORAGE_UPLOAD_MAX_BYTES = 30 * 1024 * 1024;
|
|
761
|
+
var PLUGIN_STORAGE_PREFIX_MAX_LENGTH = 84;
|
|
762
|
+
var PLUGIN_STORAGE_PATH_MAX_LENGTH = 128;
|
|
763
|
+
var PluginStorageUploadError = class extends McpToolError {
|
|
764
|
+
/** What went wrong, in terms a caller branches on. */
|
|
765
|
+
reason;
|
|
766
|
+
/** HTTP status from the server, when the host surfaced one. */
|
|
767
|
+
status;
|
|
768
|
+
/** Structural marker for {@link isPluginStorageUploadError}. */
|
|
769
|
+
isPluginStorageUploadError = true;
|
|
770
|
+
constructor(message, reason, options = {}) {
|
|
771
|
+
super(message, options.code ?? codeForReason(reason, options.status));
|
|
772
|
+
this.name = "PluginStorageUploadError";
|
|
773
|
+
this.reason = reason;
|
|
774
|
+
this.status = options.status;
|
|
775
|
+
}
|
|
776
|
+
};
|
|
777
|
+
function validatePluginStoragePrefix(raw) {
|
|
778
|
+
if (typeof raw !== "string" || raw.trim().length === 0) {
|
|
779
|
+
return { ok: false, error: "A storage path prefix is required." };
|
|
780
|
+
}
|
|
781
|
+
if (raw.length > PLUGIN_STORAGE_PREFIX_MAX_LENGTH) {
|
|
782
|
+
return {
|
|
783
|
+
ok: false,
|
|
784
|
+
error: `The storage path prefix must be at most ${PLUGIN_STORAGE_PREFIX_MAX_LENGTH} characters, so the stored path fits ${PLUGIN_STORAGE_PATH_MAX_LENGTH}.`
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
if (!/^[A-Za-z0-9_\-/]+$/.test(raw)) {
|
|
788
|
+
return {
|
|
789
|
+
ok: false,
|
|
790
|
+
error: "The storage path prefix may contain only letters, digits, '-', '_' and '/' separators."
|
|
791
|
+
};
|
|
792
|
+
}
|
|
793
|
+
if (raw.split("/").some((segment) => segment.length === 0)) {
|
|
794
|
+
return {
|
|
795
|
+
ok: false,
|
|
796
|
+
error: "The storage path prefix must not start or end with '/' or contain an empty segment."
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
return { ok: true, prefix: raw };
|
|
800
|
+
}
|
|
801
|
+
var PLUGIN_STORAGE_UPLOAD_CONTENT_TYPES = Object.freeze({
|
|
802
|
+
pdf: ["application/pdf"],
|
|
803
|
+
png: ["image/png"],
|
|
804
|
+
jpg: ["image/jpeg", "image/pjpeg", "image/jpg"],
|
|
805
|
+
jpeg: ["image/jpeg", "image/pjpeg", "image/jpg"],
|
|
806
|
+
gif: ["image/gif"],
|
|
807
|
+
webp: ["image/webp"],
|
|
808
|
+
doc: ["application/msword"],
|
|
809
|
+
docx: ["application/vnd.openxmlformats-officedocument.wordprocessingml.document"],
|
|
810
|
+
xls: ["application/vnd.ms-excel"],
|
|
811
|
+
xlsx: ["application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"],
|
|
812
|
+
ppt: ["application/vnd.ms-powerpoint"],
|
|
813
|
+
pptx: ["application/vnd.openxmlformats-officedocument.presentationml.presentation"],
|
|
814
|
+
odt: ["application/vnd.oasis.opendocument.text"],
|
|
815
|
+
ods: ["application/vnd.oasis.opendocument.spreadsheet"],
|
|
816
|
+
odp: ["application/vnd.oasis.opendocument.presentation"],
|
|
817
|
+
txt: ["text/plain"],
|
|
818
|
+
csv: ["text/csv", "application/vnd.ms-excel", "application/csv", "text/plain"],
|
|
819
|
+
zip: ["application/zip", "application/x-zip-compressed", "application/x-zip"],
|
|
820
|
+
mp3: ["audio/mpeg", "audio/mp3"],
|
|
821
|
+
m4a: ["audio/mp4", "audio/x-m4a"],
|
|
822
|
+
mp4: ["video/mp4"]
|
|
823
|
+
});
|
|
824
|
+
var OCTET_STREAM = "application/octet-stream";
|
|
825
|
+
function pluginStorageSafeExtension(fileName) {
|
|
826
|
+
if (typeof fileName !== "string" || fileName.trim().length === 0) {
|
|
827
|
+
return "";
|
|
828
|
+
}
|
|
829
|
+
const leaf = fileName.split(/[\\/]/).pop() ?? "";
|
|
830
|
+
const dot = leaf.lastIndexOf(".");
|
|
831
|
+
if (dot < 0) {
|
|
832
|
+
return "";
|
|
833
|
+
}
|
|
834
|
+
const body = leaf.slice(dot + 1);
|
|
835
|
+
return /^[A-Za-z0-9]{1,10}$/.test(body) ? `.${body.toLowerCase()}` : "";
|
|
836
|
+
}
|
|
837
|
+
function resolvePluginStorageContentType(fileName, declaredContentType) {
|
|
838
|
+
const extension = pluginStorageSafeExtension(fileName).slice(1);
|
|
839
|
+
const allowed = extension.length > 0 && Object.prototype.hasOwnProperty.call(PLUGIN_STORAGE_UPLOAD_CONTENT_TYPES, extension) ? PLUGIN_STORAGE_UPLOAD_CONTENT_TYPES[extension] : void 0;
|
|
840
|
+
if (allowed === void 0) {
|
|
841
|
+
return {
|
|
842
|
+
ok: false,
|
|
843
|
+
error: "This file type is not allowed. Allowed: " + Object.keys(PLUGIN_STORAGE_UPLOAD_CONTENT_TYPES).sort().join(", ") + "."
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
const declared = mediaTypeOf(declaredContentType);
|
|
847
|
+
if (declared === void 0 || declared === OCTET_STREAM || allowed.includes(declared)) {
|
|
848
|
+
return { ok: true, contentType: allowed[0] };
|
|
849
|
+
}
|
|
850
|
+
return {
|
|
851
|
+
ok: false,
|
|
852
|
+
error: `The declared content type '${declared}' does not match the file extension '.${extension}'.`
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
function mediaTypeOf(raw) {
|
|
856
|
+
if (typeof raw !== "string") {
|
|
857
|
+
return void 0;
|
|
858
|
+
}
|
|
859
|
+
const mediaType = raw.split(";")[0].trim().toLowerCase();
|
|
860
|
+
return /^[a-z0-9!#$&^_.+-]+\/[a-z0-9!#$&^_.+-]+$/.test(mediaType) ? mediaType : void 0;
|
|
861
|
+
}
|
|
862
|
+
function codeForReason(reason, status) {
|
|
863
|
+
if (status !== void 0) {
|
|
864
|
+
return mcpErrorCodeFromHttpStatus(status);
|
|
865
|
+
}
|
|
866
|
+
switch (reason) {
|
|
867
|
+
case "invalid_prefix":
|
|
868
|
+
case "too_large":
|
|
869
|
+
case "unsupported_type":
|
|
870
|
+
return "invalid_request";
|
|
871
|
+
case "timeout":
|
|
872
|
+
return "timeout";
|
|
873
|
+
case "unauthorized":
|
|
874
|
+
return "unauthorized";
|
|
875
|
+
case "forbidden":
|
|
876
|
+
return "forbidden";
|
|
877
|
+
case "busy":
|
|
878
|
+
return "unavailable";
|
|
879
|
+
case "aborted":
|
|
880
|
+
return "timeout";
|
|
881
|
+
default:
|
|
882
|
+
return "internal";
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
// src/mock-host/MockPluginStorage.ts
|
|
887
|
+
var MockPluginStorage = class {
|
|
888
|
+
files = /* @__PURE__ */ new Map();
|
|
889
|
+
/** Store `buffer` under a new path below `meta.pathPrefix`. */
|
|
890
|
+
store(meta, buffer) {
|
|
891
|
+
const prefix = validatePluginStoragePrefix(meta.pathPrefix);
|
|
892
|
+
if (!prefix.ok) {
|
|
893
|
+
throw new PluginStorageUploadError(prefix.error, "invalid_prefix", { status: 400 });
|
|
894
|
+
}
|
|
895
|
+
if (buffer.byteLength > PLUGIN_STORAGE_UPLOAD_MAX_BYTES) {
|
|
896
|
+
throw new PluginStorageUploadError(
|
|
897
|
+
`The upload exceeds the ${PLUGIN_STORAGE_UPLOAD_MAX_BYTES}-byte limit.`,
|
|
898
|
+
"too_large",
|
|
899
|
+
{ status: 413 }
|
|
900
|
+
);
|
|
901
|
+
}
|
|
902
|
+
const fileName = meta.fileName && meta.fileName.length > 0 ? meta.fileName : "upload.bin";
|
|
903
|
+
const type = resolvePluginStorageContentType(fileName, meta.contentType);
|
|
904
|
+
if (!type.ok) {
|
|
905
|
+
throw new PluginStorageUploadError(type.error, "unsupported_type", { status: 415 });
|
|
906
|
+
}
|
|
907
|
+
const path2 = `${prefix.prefix}/${randomGuidN()}${pluginStorageSafeExtension(fileName)}`;
|
|
908
|
+
const bytes = new Uint8Array(buffer.slice(0));
|
|
909
|
+
this.files.set(path2, { path: path2, fileName, contentType: type.contentType, bytes });
|
|
910
|
+
return { path: path2, fileName, contentType: type.contentType, sizeBytes: bytes.byteLength };
|
|
911
|
+
}
|
|
912
|
+
/** The stored file at `path`, or `undefined`. */
|
|
913
|
+
get(path2) {
|
|
914
|
+
return this.files.get(path2);
|
|
915
|
+
}
|
|
916
|
+
/** Every stored file, in upload order. */
|
|
917
|
+
list() {
|
|
918
|
+
return [...this.files.values()];
|
|
919
|
+
}
|
|
920
|
+
/** Forget every stored file. */
|
|
921
|
+
clear() {
|
|
922
|
+
this.files.clear();
|
|
923
|
+
}
|
|
924
|
+
};
|
|
925
|
+
function randomGuidN() {
|
|
926
|
+
const bytes = new Uint8Array(16);
|
|
927
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
928
|
+
bytes[6] = bytes[6] & 15 | 64;
|
|
929
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
930
|
+
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
931
|
+
}
|
|
932
|
+
|
|
681
933
|
// src/mock-host/cli.ts
|
|
682
934
|
async function loadResources(dir) {
|
|
683
935
|
const absDir = path.resolve(dir);
|
|
@@ -923,6 +1175,7 @@ import {
|
|
|
923
1175
|
WorkerRemoteDomTransport,
|
|
924
1176
|
SemanticComponentRegistry,
|
|
925
1177
|
} from "@ethisyscore/extension-runtime/host";
|
|
1178
|
+
import { MockPluginStorage } from "@ethisyscore/extension-runtime/mock-host";
|
|
926
1179
|
|
|
927
1180
|
const PRIMITIVES = ${safePrimitives};
|
|
928
1181
|
const bundleUrl = ${safeUrl};
|
|
@@ -938,12 +1191,18 @@ const registry = SemanticComponentRegistry.fromMap(componentMap);
|
|
|
938
1191
|
|
|
939
1192
|
// Mock-host MCP client: no platform, so every tool/resource call resolves
|
|
940
1193
|
// to a friendly placeholder. Authors who need real MCP traffic should run
|
|
941
|
-
// against the full platform host.
|
|
1194
|
+
// against the full platform host. uploadToStorage writes into an in-memory
|
|
1195
|
+
// plugin storage and returns a server-style {prefix}/{guid}{ext} path; an
|
|
1196
|
+
// invalid prefix or an over-size body throws with status 400 / 413, which the
|
|
1197
|
+
// transport forwards to the plugin as a typed error.
|
|
1198
|
+
const pluginStorage = new MockPluginStorage();
|
|
942
1199
|
const mcpClient = {
|
|
943
|
-
fetch: async (req) =>
|
|
944
|
-
ok: true,
|
|
945
|
-
|
|
946
|
-
|
|
1200
|
+
fetch: async (req) => req.kind === "uploadToStorage"
|
|
1201
|
+
? { ok: true, data: pluginStorage.store(req.meta, req.buffer) }
|
|
1202
|
+
: {
|
|
1203
|
+
ok: true,
|
|
1204
|
+
data: { mockHost: true, kind: req.kind, name: req.name ?? req.uri },
|
|
1205
|
+
},
|
|
947
1206
|
};
|
|
948
1207
|
|
|
949
1208
|
// The capability token never crosses the worker boundary \u2014 supply a marker
|
|
@@ -966,10 +1225,15 @@ transport.onRemoteDom(() => {
|
|
|
966
1225
|
// devtools (e.g., to terminate the worker between iteration cycles).
|
|
967
1226
|
window.__MOCK_HOST_TRANSPORT__ = transport;
|
|
968
1227
|
window.__MOCK_HOST_REGISTRY__ = registry;
|
|
1228
|
+
window.__MOCK_HOST_PLUGIN_STORAGE__ = pluginStorage;
|
|
969
1229
|
`;
|
|
970
1230
|
}
|
|
1231
|
+
var defaultMockPluginStorage = new MockPluginStorage();
|
|
971
1232
|
var defaultMockMcpClient = {
|
|
972
1233
|
fetch: async (req) => {
|
|
1234
|
+
if (req.kind === "uploadToStorage") {
|
|
1235
|
+
return { ok: true, data: defaultMockPluginStorage.store(req.meta, req.buffer) };
|
|
1236
|
+
}
|
|
973
1237
|
if (req.kind === "uploadDocument") {
|
|
974
1238
|
return {
|
|
975
1239
|
ok: true,
|
|
@@ -1151,6 +1415,6 @@ async function run(args) {
|
|
|
1151
1415
|
return { url, close: () => server.close() };
|
|
1152
1416
|
}
|
|
1153
1417
|
|
|
1154
|
-
export { bootContractBHost, loadResources, loadTools, normaliseEntry, parseArgs, renderEntryModule, renderIndexHtml, renderWorkerBundleEntryModule, renderWorkerBundleIndexHtml, run };
|
|
1418
|
+
export { bootContractBHost, defaultMockMcpClient, defaultMockPluginStorage, loadResources, loadTools, normaliseEntry, parseArgs, renderEntryModule, renderIndexHtml, renderWorkerBundleEntryModule, renderWorkerBundleIndexHtml, run };
|
|
1155
1419
|
//# sourceMappingURL=cli.js.map
|
|
1156
1420
|
//# sourceMappingURL=cli.js.map
|