diff --git a/package-lock.json b/package-lock.json index 1378275..f43a7e0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,7 @@ "grpc-tools": "^1.12.4", "json5": "^2.2.3", "nostr-tools": "^2.3.2", - "openagents-grpc-proto": "https://github.com/OpenAgentsInc/openagents-grpc-proto/releases/download/v0.9/openagents_grpc_proto-JAVASCRIPT.tgz", + "openagents-grpc-proto": "https://github.com/OpenAgentsInc/openagents-grpc-proto/releases/download/v0.10.0/openagents_grpc_proto-JAVASCRIPT.tgz", "ts-protoc-gen": "^0.15.0", "uuidv4": "^6.2.13", "ws": "^8.16.0" @@ -1850,9 +1850,9 @@ } }, "node_modules/openagents-grpc-proto": { - "version": "0.9", - "resolved": "https://github.com/OpenAgentsInc/openagents-grpc-proto/releases/download/v0.9/openagents_grpc_proto-JAVASCRIPT.tgz", - "integrity": "sha512-VFjXKqqgTXD+LhWybYXC2GbJMhxlS8MO+KAurYo2s4Z/aFe35GE006dOkebW3r4CiwfU9g16tvxnswXCbLLiaw==", + "version": "0.10.0", + "resolved": "https://github.com/OpenAgentsInc/openagents-grpc-proto/releases/download/v0.10.0/openagents_grpc_proto-JAVASCRIPT.tgz", + "integrity": "sha512-KoggLDLuC68hMPKr5Pq5Q/mT0jZ+aXYxQtW2lC3uLNee32m9GwQm9IajVx4U6zFo0wi12zMRbjzizaXvrHOPDw==", "dependencies": { "@grpc/grpc-js": "^1.10.6", "@protobuf-ts/plugin": "^2.9.4" diff --git a/package.json b/package.json index b0eb0f4..876535c 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "grpc-tools": "^1.12.4", "json5": "^2.2.3", "nostr-tools": "^2.3.2", - "openagents-grpc-proto": "https://github.com/OpenAgentsInc/openagents-grpc-proto/releases/download/v0.9/openagents_grpc_proto-JAVASCRIPT.tgz", + "openagents-grpc-proto": "https://github.com/OpenAgentsInc/openagents-grpc-proto/releases/download/v0.10.0/openagents_grpc_proto-JAVASCRIPT.tgz", "ts-protoc-gen": "^0.15.0", "uuidv4": "^6.2.13", "ws": "^8.16.0" diff --git a/src/runtime/ExtismJob.ts b/src/runtime/ExtismJob.ts index b6eb25e..66125ed 100644 --- a/src/runtime/ExtismJob.ts +++ b/src/runtime/ExtismJob.ts @@ -20,13 +20,15 @@ export default class ExtismJob { looping: boolean = false; jobId: string; hostFunctions: { [key: string]: ExtismFunction }; - executionQueue: Promise = Promise.resolve(undefined); + executionQueue: Promise = Promise.resolve(undefined); + allowedHosts: string[] = ["*"]; constructor( jobId: string, mainPluginPath: string, expiration: number, - hostFunctions: { [key: string]: ExtismFunction } + hostFunctions: { [key: string]: ExtismFunction }, + allowedHosts?: string[] ) { // TODO: check origins this.mainPluginPath = mainPluginPath; @@ -34,16 +36,20 @@ export default class ExtismJob { this.jobId = jobId; this.hostFunctions = hostFunctions; + if(allowedHosts!==undefined) this.allowedHosts = allowedHosts; + // autodestruct - setTimeout(async () => { - await this.destroy(); - },1000*60*5); + setTimeout( + async () => { + await this.destroy(); + }, + 1000 * 60 * 5 + ); } async init() { if (this.initialized) throw new Error("Already initialized"); this.main = await this._loadFragment(this.mainPluginPath); - this.initialized = true; for (const plugin of [this.main, ...this.dependencies]) { @@ -64,7 +70,7 @@ export default class ExtismJob { functions: { "extism:host/user": this.hostFunctions, }, - allowedHosts:["*"] + allowedHosts: this.allowedHosts, }; const plugin = await Extism.createPlugin(path, options); let meta: any; @@ -86,7 +92,7 @@ export default class ExtismJob { return fragment; } - async _callPlugin(plugin: Extism.Plugin, method: string, jobId?: string, inputData?: string){ + async _callPlugin(plugin: Extism.Plugin, method: string, jobId?: string, inputData?: string) { return await this._callPluginMaybe(plugin, method, jobId, inputData, true); } async _callPluginMaybe( @@ -97,41 +103,42 @@ export default class ExtismJob { required?: boolean ): Promise { if (!this.initialized) throw new Error("Not initialized"); - const q=this.executionQueue.then(async () => { - try{ - const fexists = async (name) => { - return await plugin.functionExists(name); - }; - let out: Extism.PluginOutput; - if (jobId && (await fexists(method + "ForJob"))) { - out = await plugin.call( - method + "ForJob", - JSON.stringify({ - jobId: jobId, - args: inputData, - }) - ); - } else if (await fexists(method)) { - out = await plugin.call(method, inputData); - } else { - if (required) { - throw new Error("No method " + method + " found in plugin " + this); + const q = this.executionQueue + .then(async () => { + try { + const fexists = async (name) => { + return await plugin.functionExists(name); + }; + let out: Extism.PluginOutput; + if (jobId && (await fexists(method + "ForJob"))) { + out = await plugin.call( + method + "ForJob", + JSON.stringify({ + jobId: jobId, + args: inputData, + }) + ); + } else if (await fexists(method)) { + out = await plugin.call(method, inputData); } else { - return ""; + if (required) { + throw new Error("No method " + method + " found in plugin " + this); + } else { + return ""; + } } + return out ? out.text() : undefined; + } catch (e) { + console.error("Error calling plugin in execution queue", e); + throw e; } - return out?out.text():undefined; - }catch(e){ - console.error("Error calling plugin in execution queue",e); + }) + .catch((e) => { + console.error("Error in execution queue", e); throw e; - } - }).catch((e)=>{ - console.error("Error in execution queue",e); - throw e; - }); - - - this.executionQueue= q; + }); + + this.executionQueue = q; return await q; } @@ -147,26 +154,26 @@ export default class ExtismJob { async run(inputData: string): Promise { if (!this.initialized) throw new Error("Not initialized"); - try{ + try { return await this._callPlugin(this.main.plugin, "run", this.jobId, inputData); - }catch(e){ - console.error("Error running plugin",e); + } catch (e) { + console.error("Error running plugin", e); throw e; } } // async loop() { - // if (!this.initialized) throw new Error("Not initialized"); - - // if (this.looping) return; - // this.looping = true; - // await this._callPluginMaybe(this.main.plugin, "loop", this.jobId, "{}") - // .finally(() => { - // this.looping = false; - // }) - // .catch((e) => { - // console.error(e); - // }); + // if (!this.initialized) throw new Error("Not initialized"); + + // if (this.looping) return; + // this.looping = true; + // await this._callPluginMaybe(this.main.plugin, "loop", this.jobId, "{}") + // .finally(() => { + // this.looping = false; + // }) + // .catch((e) => { + // console.error(e); + // }); // } private destructing: boolean = false; diff --git a/src/runtime/JobManager.ts b/src/runtime/JobManager.ts index 95b74b5..a89dbee 100644 --- a/src/runtime/JobManager.ts +++ b/src/runtime/JobManager.ts @@ -52,6 +52,7 @@ export default class JobManager { filterByRunOn: "openagents/extism-runtime", wait: 60000, excludeId: this.failedJobs.map((j) => j.jobId), + filterByBids: [], }) ).response; @@ -72,6 +73,17 @@ export default class JobManager { const expiration = Math.min(Date.now() + maxExecutionTime, job.expiration); const pluginMainSHAHash = Crypto.createHash("sha256").update(pluginMain).digest("hex"); + let allowedHosts = undefined; + for(const p of job.param){ + if (p.key == "allow-host") { + if (!allowedHosts) allowedHosts = []; + allowedHosts.push(...p.value); + } else if (p.key == "allow-hosts") { + if (!allowedHosts) allowedHosts = []; + allowedHosts.push(...p.value); + } + } + if(this.secrets){ const secrets0 = this.secrets.namespace(pluginMainSHAHash); const secrets1 = this.secrets.namespace(pluginMain); @@ -101,7 +113,7 @@ export default class JobManager { } } console.log("Running plugin", pluginMain); - const plugin = new ExtismJob(job.id, pluginMain, expiration, mergedHostFunctions); + const plugin = new ExtismJob(job.id, pluginMain, expiration, mergedHostFunctions, allowedHosts); const res = (await this.conn.acceptJob({ jobId: job.id })).response; console.log("Accepted job", res); // console.log("Initializing plugin", pluginMain, pluginMainSHAHash,inputData); diff --git a/src/runtime/PluginRepo.ts b/src/runtime/PluginRepo.ts index 4b9a860..3424d5a 100644 --- a/src/runtime/PluginRepo.ts +++ b/src/runtime/PluginRepo.ts @@ -73,10 +73,22 @@ export default class PluginRepo{ const miniTemplate = plugin["mini-template"]; const main = miniTemplate.main; const input=miniTemplate.input; + const allowedHosts = miniTemplate["allowed-hosts"]; + + if(!main || !input) throw new Error("Invalid mini-template"); const escapeString = (str)=>{ return str.replace(/"/g,`\\"`); }; + + let allowedHostsTemplate=""; + if(allowedHosts){ + for(const host of allowedHosts){ + allowedHostsTemplate+=`["param", "allow-host", "${escapeString(host)}"],\n`; + } + } + + template = ` { "kind": {{{meta.kind}}}, @@ -84,6 +96,7 @@ export default class PluginRepo{ "tags": [ ["param","run-on", "openagents/extism-runtime" ], ["param","main","${escapeString(main)}"], + ${allowedHostsTemplate.trim()} ["i", "${escapeString(input)}", "text", ""], ["expiration", "{{{sys.expiration_timestamp_seconds}}}"] ], @@ -91,6 +104,7 @@ export default class PluginRepo{ } `; } + if(!template) throw new Error("Invalid plugin template"); if(!sockets) throw new Error("Invalid plugin sockets"); if(!meta) throw new Error("Invalid plugin meta");