Serve TeX plugin ABI over CultNet
This commit is contained in:
parent
db53acf276
commit
ae7e2fa2a4
6 changed files with 127 additions and 15 deletions
34
package-lock.json
generated
34
package-lock.json
generated
|
|
@ -10,12 +10,40 @@
|
|||
"license": "MPL-2.0",
|
||||
"workspaces": [
|
||||
"plugins/*"
|
||||
]
|
||||
],
|
||||
"dependencies": {
|
||||
"@msgpack/msgpack": "^3.1.3",
|
||||
"cultnet-ts": "file:../CultLib/packages/cultnet-ts"
|
||||
}
|
||||
},
|
||||
"../CultLib/packages/cultnet-ts": {
|
||||
"version": "0.1.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@msgpack/msgpack": "^3.1.3",
|
||||
"ajv": "^8.17.1",
|
||||
"cultcache-ts": "file:../cultcache-ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.0.0",
|
||||
"json-schema-to-typescript": "^15.0.4",
|
||||
"typescript": "^5.9.0",
|
||||
"zod": "^3.25.76"
|
||||
}
|
||||
},
|
||||
"node_modules/@gamecult/eve-plugin-tex": {
|
||||
"resolved": "plugins/eve-plugin-tex",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@msgpack/msgpack": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-3.1.3.tgz",
|
||||
"integrity": "sha512-47XIizs9XZXvuJgoaJUIE2lFoID8ugvc0jzSHP+Ptfk8nTbnR8g788wv48N03Kx0UkAv559HWRQ3yzOgzlRNUA==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "8.3.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
|
||||
|
|
@ -25,6 +53,10 @@
|
|||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/cultnet-ts": {
|
||||
"resolved": "../CultLib/packages/cultnet-ts",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/katex": {
|
||||
"version": "0.16.47",
|
||||
"resolved": "https://registry.npmjs.org/katex/-/katex-0.16.47.tgz",
|
||||
|
|
|
|||
|
|
@ -7,5 +7,9 @@
|
|||
"scripts": {
|
||||
"test": "C:\\Progra~1\\nodejs\\node.exe --test plugins/*/test/*.test.mjs",
|
||||
"witness:tex": "C:\\Progra~1\\nodejs\\node.exe scripts/run-tex-witness.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@msgpack/msgpack": "^3.1.3",
|
||||
"cultnet-ts": "file:../CultLib/packages/cultnet-ts"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,11 +7,12 @@
|
|||
"runtime": {
|
||||
"invocationModel": "executable-sidecar",
|
||||
"contract": "gamecult.eve.plugin_abi.v1",
|
||||
"transports": ["stdio"],
|
||||
"transports": ["cultnet-rudp", "stdio"],
|
||||
"authority": ["renderer-independent", "no-provider-state-mutation", "provider-accepts-or-denies-commands"],
|
||||
"sidecar": {
|
||||
"processKind": "long-running-daemon",
|
||||
"protocol": "stdio-ndjson",
|
||||
"protocol": "cultnet-operation-v0",
|
||||
"debugProtocol": "stdio-ndjson",
|
||||
"requestSchema": "gamecult.eve.plugin_abi.request.v1",
|
||||
"responseSchema": "gamecult.eve.plugin_abi.response.v1",
|
||||
"operations": ["describe", "validate", "project", "lower", "measure", "apply"],
|
||||
|
|
@ -25,4 +26,3 @@
|
|||
"commands": [],
|
||||
"fixtures": ["tex-math-basic"]
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,11 +14,12 @@
|
|||
"runtime": {
|
||||
"invocationModel": "executable-sidecar",
|
||||
"contract": "gamecult.eve.plugin_abi.v1",
|
||||
"transports": ["stdio"],
|
||||
"transports": ["cultnet-rudp", "stdio"],
|
||||
"authority": ["renderer-independent", "no-provider-state-mutation", "provider-accepts-or-denies-commands"],
|
||||
"sidecar": {
|
||||
"processKind": "long-running-daemon",
|
||||
"protocol": "stdio-ndjson",
|
||||
"protocol": "cultnet-operation-v0",
|
||||
"debugProtocol": "stdio-ndjson",
|
||||
"requestSchema": "gamecult.eve.plugin_abi.request.v1",
|
||||
"responseSchema": "gamecult.eve.plugin_abi.response.v1",
|
||||
"operations": ["describe", "validate", "project", "lower", "measure", "apply"],
|
||||
|
|
@ -37,4 +38,3 @@
|
|||
"optionalCapabilities": ["tex.inline", "tex.block", "tex.scene-placement"]
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
61
plugins/eve-plugin-tex/src/cultnet-sidecar.mjs
Normal file
61
plugins/eve-plugin-tex/src/cultnet-sidecar.mjs
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
import { Buffer } from "node:buffer";
|
||||
import { decode, encode } from "@msgpack/msgpack";
|
||||
import { startCultNetOperationServer } from "cultnet-ts";
|
||||
import { dispatch, pluginId } from "./plugin.mjs";
|
||||
|
||||
const host = process.env.EVE_PLUGIN_HOST || "127.0.0.1";
|
||||
const port = Number(process.env.EVE_PLUGIN_PORT || 0);
|
||||
const server = await startCultNetOperationServer({
|
||||
runtimeId: "eve-plugin-tex",
|
||||
host,
|
||||
port,
|
||||
handler: request => {
|
||||
if (request.serviceId !== pluginId) throw new Error(`Expected service ${pluginId}.`);
|
||||
if (request.payloadSchema !== "gamecult.eve.plugin_abi.request.v1") throw new Error("Unsupported plugin payload schema.");
|
||||
const abiRequest = decode(Buffer.from(request.payload, "base64"));
|
||||
let abiResponse;
|
||||
try {
|
||||
abiResponse = {
|
||||
schema: "gamecult.eve.plugin_abi.response.v1",
|
||||
pluginId,
|
||||
operation: abiRequest.operation,
|
||||
requestId: abiRequest.requestId,
|
||||
status: "accepted",
|
||||
output: dispatch(abiRequest),
|
||||
};
|
||||
} catch (error) {
|
||||
abiResponse = {
|
||||
schema: "gamecult.eve.plugin_abi.response.v1",
|
||||
pluginId,
|
||||
operation: abiRequest?.operation || request.operation,
|
||||
requestId: abiRequest?.requestId || request.messageId,
|
||||
status: "rejected",
|
||||
output: {},
|
||||
diagnostics: [{ message: error instanceof Error ? error.message : String(error) }],
|
||||
};
|
||||
}
|
||||
return {
|
||||
schemaVersion: "cultnet.operation_response.v0",
|
||||
messageId: request.messageId,
|
||||
serviceId: pluginId,
|
||||
operation: request.operation,
|
||||
status: abiResponse.status,
|
||||
payloadSchema: abiResponse.schema,
|
||||
payloadEncoding: "messagepack-base64",
|
||||
payload: Buffer.from(encode(abiResponse)).toString("base64"),
|
||||
diagnostics: (abiResponse.diagnostics || []).map(item => item.message),
|
||||
sourceRuntimeId: "eve-plugin-tex",
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
process.stdout.write(`${JSON.stringify({ schema: "gamecult.eve.plugin_endpoint.v1", pluginId, endpoint: server.endpoint })}\n`);
|
||||
const stop = async () => {
|
||||
await server.close();
|
||||
process.exit(0);
|
||||
};
|
||||
process.once("SIGINT", stop);
|
||||
process.once("SIGTERM", stop);
|
||||
await new Promise(() => {});
|
||||
|
|
@ -3,11 +3,19 @@ import { spawn } from "node:child_process";
|
|||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import readline from "node:readline";
|
||||
import { Buffer } from "node:buffer";
|
||||
import { decode, encode } from "@msgpack/msgpack";
|
||||
import { invokeCultNetOperation } from "cultnet-ts";
|
||||
|
||||
const root = path.resolve(import.meta.dirname, "..");
|
||||
const fixture = JSON.parse(fs.readFileSync(path.join(root, "plugins/eve-plugin-tex/plugin-abi-fixture.json"), "utf8"));
|
||||
const child = spawn(process.execPath, [path.join(root, "plugins/eve-plugin-tex/src/sidecar.mjs")], { cwd: root, stdio: ["pipe", "pipe", "inherit"] });
|
||||
const child = spawn(process.execPath, [path.join(root, "plugins/eve-plugin-tex/src/cultnet-sidecar.mjs")], { cwd: root, stdio: ["ignore", "pipe", "inherit"] });
|
||||
const responses = readline.createInterface({ input: child.stdout })[Symbol.asyncIterator]();
|
||||
const endpointLine = await responses.next();
|
||||
assert.equal(endpointLine.done, false);
|
||||
const endpointAdvertisement = JSON.parse(endpointLine.value);
|
||||
assert.equal(endpointAdvertisement.pluginId, fixture.pluginId);
|
||||
const endpoint = endpointAdvertisement.endpoint;
|
||||
const startedAt = performance.now();
|
||||
const operationWitnesses = [];
|
||||
|
||||
|
|
@ -16,10 +24,18 @@ try {
|
|||
const operation = fixture.operations[index];
|
||||
const requestId = `tex-witness-${index}`;
|
||||
const operationStartedAt = performance.now();
|
||||
child.stdin.write(`${JSON.stringify({ schema: fixture.requestSchema, pluginId: fixture.pluginId, operation: operation.operation, requestId, input: operation.input })}\n`);
|
||||
const next = await responses.next();
|
||||
assert.equal(next.done, false);
|
||||
const response = JSON.parse(next.value);
|
||||
const abiRequest = { schema: fixture.requestSchema, pluginId: fixture.pluginId, operation: operation.operation, requestId, input: operation.input };
|
||||
const envelope = await invokeCultNetOperation(endpoint, {
|
||||
schemaVersion: "cultnet.operation_request.v0",
|
||||
messageId: requestId,
|
||||
serviceId: fixture.pluginId,
|
||||
operation: operation.operation,
|
||||
payloadSchema: fixture.requestSchema,
|
||||
payloadEncoding: "messagepack-base64",
|
||||
payload: Buffer.from(encode(abiRequest)).toString("base64"),
|
||||
sourceRuntimeId: "eve-conformance",
|
||||
}, { runtimeId: "eve-conformance" });
|
||||
const response = decode(Buffer.from(envelope.payload, "base64"));
|
||||
assert.equal(response.schema, fixture.responseSchema);
|
||||
assert.equal(response.pluginId, fixture.pluginId);
|
||||
assert.equal(response.operation, operation.operation);
|
||||
|
|
@ -35,7 +51,6 @@ try {
|
|||
});
|
||||
}
|
||||
} finally {
|
||||
child.stdin.end();
|
||||
child.kill();
|
||||
}
|
||||
|
||||
|
|
@ -47,13 +62,13 @@ const witness = {
|
|||
pluginId: "tex.math",
|
||||
ownerRepo: "EvePlugins",
|
||||
status: "pass",
|
||||
transport: "stdio-ndjson",
|
||||
transport: "cultnet-operation-v0+rudp",
|
||||
generatedAtUtc: new Date().toISOString(),
|
||||
durationMs: Number((performance.now() - startedAt).toFixed(3)),
|
||||
operations: operationWitnesses,
|
||||
advertisementPath: "plugins/eve-plugin-tex/advertisement.json",
|
||||
fixturePath: "plugins/eve-plugin-tex/plugin-abi-fixture.json",
|
||||
executable: { artifact: "plugins/eve-plugin-tex/src/sidecar.mjs", command: "node" },
|
||||
executable: { artifact: "plugins/eve-plugin-tex/src/cultnet-sidecar.mjs", command: "node" },
|
||||
diagnostics: [],
|
||||
authority: "plugin-sidecar-owns-tex-semantics-provider-retains-source-and-command-acceptance",
|
||||
};
|
||||
|
|
|
|||
Reference in a new issue