@elevasis/sdk 0.3.3 → 0.4.1
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/cli.cjs +200 -76
- package/dist/index.d.ts +76 -3
- package/dist/index.js +132 -1
- package/dist/worker/index.js +132 -0
- package/package.json +4 -4
- package/dist/server/index.js +0 -174
package/dist/cli.cjs
CHANGED
|
@@ -39746,9 +39746,16 @@ var ResourceRegistry = class {
|
|
|
39746
39746
|
}
|
|
39747
39747
|
/**
|
|
39748
39748
|
* Pre-serialized organization data cache
|
|
39749
|
-
* Computed once at construction,
|
|
39749
|
+
* Computed once at construction for static orgs, updated incrementally for runtime orgs
|
|
39750
39750
|
*/
|
|
39751
39751
|
serializedCache;
|
|
39752
|
+
/**
|
|
39753
|
+
* Per-resource remote configuration (external deployments)
|
|
39754
|
+
* Key: "orgName/resourceId", Value: RemoteOrgConfig for that resource.
|
|
39755
|
+
* Tracks which individual resources were added at runtime via deploy pipeline.
|
|
39756
|
+
* Static and remote resources coexist in the same org.
|
|
39757
|
+
*/
|
|
39758
|
+
remoteResources = /* @__PURE__ */ new Map();
|
|
39752
39759
|
/**
|
|
39753
39760
|
* Validates registry on construction
|
|
39754
39761
|
* - Checks for duplicate resourceIds within organizations
|
|
@@ -39848,6 +39855,130 @@ var ResourceRegistry = class {
|
|
|
39848
39855
|
return this.registry;
|
|
39849
39856
|
}
|
|
39850
39857
|
// ============================================================================
|
|
39858
|
+
// Runtime Organization Registration (External Deployments)
|
|
39859
|
+
// ============================================================================
|
|
39860
|
+
/**
|
|
39861
|
+
* Register external resources at runtime
|
|
39862
|
+
*
|
|
39863
|
+
* Called during deploy pipeline when an external developer deploys their bundle.
|
|
39864
|
+
* Merges the incoming stub definitions into the org's registry and stores
|
|
39865
|
+
* per-resource remote config for worker thread execution branching.
|
|
39866
|
+
*
|
|
39867
|
+
* Static and remote resources coexist in the same org. If the org already
|
|
39868
|
+
* has static resources, the incoming remote resources are merged alongside them.
|
|
39869
|
+
* If redeploying (some resources already registered as remote for this org),
|
|
39870
|
+
* the previous remote resources are unregistered first.
|
|
39871
|
+
*
|
|
39872
|
+
* @param orgName - Organization name (used as registry key)
|
|
39873
|
+
* @param org - Stub resource definitions (workflows/agents with placeholder handlers)
|
|
39874
|
+
* @param remote - Remote configuration (bundle path, deployment ID, env vars)
|
|
39875
|
+
* @throws Error if incoming resourceId conflicts with a static resource
|
|
39876
|
+
* @throws Error if incoming deployment contains duplicate resourceIds
|
|
39877
|
+
*/
|
|
39878
|
+
registerOrganization(orgName, org, remote) {
|
|
39879
|
+
const incomingWorkflowIds = (org.workflows ?? []).map((w) => w.config.resourceId);
|
|
39880
|
+
const incomingAgentIds = (org.agents ?? []).map((a) => a.config.resourceId);
|
|
39881
|
+
const incomingIds = [...incomingWorkflowIds, ...incomingAgentIds];
|
|
39882
|
+
const seen = /* @__PURE__ */ new Set();
|
|
39883
|
+
for (const id of incomingIds) {
|
|
39884
|
+
if (seen.has(id)) {
|
|
39885
|
+
throw new Error(
|
|
39886
|
+
`Duplicate resource ID '${id}' in deployment. Each resource must have a unique ID.`
|
|
39887
|
+
);
|
|
39888
|
+
}
|
|
39889
|
+
seen.add(id);
|
|
39890
|
+
}
|
|
39891
|
+
if (this.isRemote(orgName)) {
|
|
39892
|
+
this.unregisterOrganization(orgName);
|
|
39893
|
+
}
|
|
39894
|
+
const existingOrg = this.registry[orgName];
|
|
39895
|
+
if (existingOrg) {
|
|
39896
|
+
const staticWorkflowIds = new Set((existingOrg.workflows ?? []).map((w) => w.config.resourceId));
|
|
39897
|
+
const staticAgentIds = new Set((existingOrg.agents ?? []).map((a) => a.config.resourceId));
|
|
39898
|
+
for (const id of incomingIds) {
|
|
39899
|
+
if (staticWorkflowIds.has(id) || staticAgentIds.has(id)) {
|
|
39900
|
+
throw new Error(
|
|
39901
|
+
`Resource '${id}' already exists in '${orgName}' as an internal resource. External deployments cannot override internal resources.`
|
|
39902
|
+
);
|
|
39903
|
+
}
|
|
39904
|
+
}
|
|
39905
|
+
}
|
|
39906
|
+
if (existingOrg) {
|
|
39907
|
+
existingOrg.workflows = [...existingOrg.workflows ?? [], ...org.workflows ?? []];
|
|
39908
|
+
existingOrg.agents = [...existingOrg.agents ?? [], ...org.agents ?? []];
|
|
39909
|
+
} else {
|
|
39910
|
+
this.registry[orgName] = org;
|
|
39911
|
+
}
|
|
39912
|
+
for (const id of incomingIds) {
|
|
39913
|
+
this.remoteResources.set(`${orgName}/${id}`, remote);
|
|
39914
|
+
}
|
|
39915
|
+
this.serializedCache.set(orgName, serializeOrganization(this.registry[orgName]));
|
|
39916
|
+
}
|
|
39917
|
+
/**
|
|
39918
|
+
* Unregister runtime-registered resources for an organization
|
|
39919
|
+
*
|
|
39920
|
+
* Removes only resources that were registered at runtime (via registerOrganization).
|
|
39921
|
+
* Static resources loaded at startup are preserved. If the org still has static
|
|
39922
|
+
* resources after removal, the serialization cache is rebuilt. If no resources
|
|
39923
|
+
* remain, the org is fully removed from the registry.
|
|
39924
|
+
* No-op if the org has no remote resources.
|
|
39925
|
+
*
|
|
39926
|
+
* @param orgName - Organization name to unregister remote resources from
|
|
39927
|
+
*/
|
|
39928
|
+
unregisterOrganization(orgName) {
|
|
39929
|
+
const prefix = `${orgName}/`;
|
|
39930
|
+
const remoteIds = /* @__PURE__ */ new Set();
|
|
39931
|
+
for (const key of this.remoteResources.keys()) {
|
|
39932
|
+
if (key.startsWith(prefix)) {
|
|
39933
|
+
remoteIds.add(key.slice(prefix.length));
|
|
39934
|
+
this.remoteResources.delete(key);
|
|
39935
|
+
}
|
|
39936
|
+
}
|
|
39937
|
+
if (remoteIds.size === 0) return;
|
|
39938
|
+
const orgResources = this.registry[orgName];
|
|
39939
|
+
if (!orgResources) return;
|
|
39940
|
+
orgResources.workflows = (orgResources.workflows ?? []).filter(
|
|
39941
|
+
(w) => !remoteIds.has(w.config.resourceId)
|
|
39942
|
+
);
|
|
39943
|
+
orgResources.agents = (orgResources.agents ?? []).filter(
|
|
39944
|
+
(a) => !remoteIds.has(a.config.resourceId)
|
|
39945
|
+
);
|
|
39946
|
+
const remaining = (orgResources.workflows?.length ?? 0) + (orgResources.agents?.length ?? 0) + (orgResources.triggers?.length ?? 0) + (orgResources.integrations?.length ?? 0) + (orgResources.externalResources?.length ?? 0) + (orgResources.humanCheckpoints?.length ?? 0);
|
|
39947
|
+
if (remaining > 0) {
|
|
39948
|
+
this.serializedCache.set(orgName, serializeOrganization(orgResources));
|
|
39949
|
+
} else {
|
|
39950
|
+
delete this.registry[orgName];
|
|
39951
|
+
this.serializedCache.delete(orgName);
|
|
39952
|
+
}
|
|
39953
|
+
}
|
|
39954
|
+
/**
|
|
39955
|
+
* Get remote configuration for a specific resource
|
|
39956
|
+
*
|
|
39957
|
+
* Returns the RemoteOrgConfig if the resource was registered at runtime,
|
|
39958
|
+
* or null if it's a static resource or doesn't exist.
|
|
39959
|
+
* Used by the execution coordinator to branch between local and worker execution.
|
|
39960
|
+
*
|
|
39961
|
+
* @param orgName - Organization name
|
|
39962
|
+
* @param resourceId - Resource ID
|
|
39963
|
+
* @returns Remote config or null
|
|
39964
|
+
*/
|
|
39965
|
+
getRemoteConfig(orgName, resourceId) {
|
|
39966
|
+
return this.remoteResources.get(`${orgName}/${resourceId}`) ?? null;
|
|
39967
|
+
}
|
|
39968
|
+
/**
|
|
39969
|
+
* Check if an organization has any remote (externally deployed) resources
|
|
39970
|
+
*
|
|
39971
|
+
* @param orgName - Organization name
|
|
39972
|
+
* @returns true if the org has at least one runtime-registered resource
|
|
39973
|
+
*/
|
|
39974
|
+
isRemote(orgName) {
|
|
39975
|
+
const prefix = `${orgName}/`;
|
|
39976
|
+
for (const key of this.remoteResources.keys()) {
|
|
39977
|
+
if (key.startsWith(prefix)) return true;
|
|
39978
|
+
}
|
|
39979
|
+
return false;
|
|
39980
|
+
}
|
|
39981
|
+
// ============================================================================
|
|
39851
39982
|
// Resource Manifest Accessors
|
|
39852
39983
|
// ============================================================================
|
|
39853
39984
|
/**
|
|
@@ -40044,20 +40175,11 @@ function wrapAction(commandName, fn) {
|
|
|
40044
40175
|
// src/cli/commands/check.ts
|
|
40045
40176
|
var import_meta = {};
|
|
40046
40177
|
function registerCheckCommand(program3) {
|
|
40047
|
-
program3.command("check").description("Validate project resources against the ResourceRegistry\n Example: elevasis check --
|
|
40048
|
-
const configPath = options.config ?? "./elevasis.config.ts";
|
|
40178
|
+
program3.command("check").description("Validate project resources against the ResourceRegistry\n Example: elevasis check --entry ./src/index.ts").option("--entry <path>", "Path to entry file (default: ./src/index.ts)").action(wrapAction("check", async (options) => {
|
|
40049
40179
|
const entryPath = options.entry ?? "./src/index.ts";
|
|
40050
40180
|
const spinner = ora("Validating resources...").start();
|
|
40051
40181
|
try {
|
|
40052
40182
|
const jiti = (0, import_jiti.createJiti)(import_meta.url);
|
|
40053
|
-
const configModule = await jiti.import((0, import_path.resolve)(configPath));
|
|
40054
|
-
const config3 = configModule.default;
|
|
40055
|
-
if (!config3?.organization) {
|
|
40056
|
-
spinner.fail('Invalid config: missing "organization" field');
|
|
40057
|
-
console.error(source_default.gray(` Config file: ${(0, import_path.resolve)(configPath)}`));
|
|
40058
|
-
console.error(source_default.gray(' Expected: export default { organization: "your-org-name" }'));
|
|
40059
|
-
throw new Error("Invalid config");
|
|
40060
|
-
}
|
|
40061
40183
|
const entryModule = await jiti.import((0, import_path.resolve)(entryPath));
|
|
40062
40184
|
const org = entryModule.default;
|
|
40063
40185
|
if (!org) {
|
|
@@ -40066,7 +40188,7 @@ function registerCheckCommand(program3) {
|
|
|
40066
40188
|
console.error(source_default.gray(" Expected: export default { workflows: [...], agents: [...] }"));
|
|
40067
40189
|
throw new Error("Invalid entry");
|
|
40068
40190
|
}
|
|
40069
|
-
new ResourceRegistry({
|
|
40191
|
+
new ResourceRegistry({ _check: org });
|
|
40070
40192
|
const workflowCount = org.workflows?.length ?? 0;
|
|
40071
40193
|
const agentCount = org.agents?.length ?? 0;
|
|
40072
40194
|
const totalCount = workflowCount + agentCount;
|
|
@@ -40117,27 +40239,73 @@ function resolveEnvironment() {
|
|
|
40117
40239
|
return process.env.NODE_ENV === "development" ? "development" : "production";
|
|
40118
40240
|
}
|
|
40119
40241
|
|
|
40242
|
+
// src/cli/api-client.ts
|
|
40243
|
+
function getApiKey() {
|
|
40244
|
+
const key = process.env.ELEVASIS_API_KEY;
|
|
40245
|
+
if (!key) {
|
|
40246
|
+
throw new Error(
|
|
40247
|
+
"ELEVASIS_API_KEY environment variable is required.\nSet it in your .env file: ELEVASIS_API_KEY=sk_..."
|
|
40248
|
+
);
|
|
40249
|
+
}
|
|
40250
|
+
return key;
|
|
40251
|
+
}
|
|
40252
|
+
async function apiGet(endpoint, apiUrl = resolveApiUrl()) {
|
|
40253
|
+
const response = await fetch(`${apiUrl}${endpoint}`, {
|
|
40254
|
+
headers: { Authorization: `Bearer ${getApiKey()}` }
|
|
40255
|
+
});
|
|
40256
|
+
if (!response.ok) {
|
|
40257
|
+
const errorText = await response.text();
|
|
40258
|
+
throw new Error(`API request failed (${response.status}): ${errorText}`);
|
|
40259
|
+
}
|
|
40260
|
+
if (response.status === 204) {
|
|
40261
|
+
return {};
|
|
40262
|
+
}
|
|
40263
|
+
return response.json();
|
|
40264
|
+
}
|
|
40265
|
+
async function apiPost(endpoint, body, apiUrl = resolveApiUrl()) {
|
|
40266
|
+
const response = await fetch(`${apiUrl}${endpoint}`, {
|
|
40267
|
+
method: "POST",
|
|
40268
|
+
headers: {
|
|
40269
|
+
Authorization: `Bearer ${getApiKey()}`,
|
|
40270
|
+
"Content-Type": "application/json"
|
|
40271
|
+
},
|
|
40272
|
+
body: JSON.stringify(body)
|
|
40273
|
+
});
|
|
40274
|
+
if (!response.ok) {
|
|
40275
|
+
const errorText = await response.text();
|
|
40276
|
+
throw new Error(`API request failed (${response.status}): ${errorText}`);
|
|
40277
|
+
}
|
|
40278
|
+
if (response.status === 204) {
|
|
40279
|
+
return {};
|
|
40280
|
+
}
|
|
40281
|
+
return response.json();
|
|
40282
|
+
}
|
|
40283
|
+
|
|
40120
40284
|
// src/cli/commands/deploy.ts
|
|
40121
40285
|
var import_meta2 = {};
|
|
40122
40286
|
function registerDeployCommand(program3) {
|
|
40123
|
-
program3.command("deploy").description("Validate, bundle, upload, and deploy project resources\n Example: elevasis deploy --api-url http://localhost:5170").option("--api-url <url>", "API URL").option("--
|
|
40287
|
+
program3.command("deploy").description("Validate, bundle, upload, and deploy project resources\n Example: elevasis deploy --api-url http://localhost:5170").option("--api-url <url>", "API URL").option("--entry <path>", "Path to entry file (default: ./src/index.ts)").action(wrapAction("deploy", async (options) => {
|
|
40124
40288
|
const startTime = Date.now();
|
|
40125
40289
|
const apiUrl = resolveApiUrl(options.apiUrl);
|
|
40126
40290
|
const env2 = resolveEnvironment();
|
|
40127
|
-
const configPath = options.config ?? "./elevasis.config.ts";
|
|
40128
40291
|
const entryPath = options.entry ?? "./src/index.ts";
|
|
40292
|
+
const authSpinner = ora("Authenticating...").start();
|
|
40293
|
+
let orgName;
|
|
40294
|
+
try {
|
|
40295
|
+
const me = await apiGet("/api/external/me", apiUrl);
|
|
40296
|
+
orgName = me.organizationName;
|
|
40297
|
+
authSpinner.succeed(
|
|
40298
|
+
source_default.green("Authenticating...") + source_default.white(" done") + source_default.gray(` (${orgName})`)
|
|
40299
|
+
);
|
|
40300
|
+
} catch (error46) {
|
|
40301
|
+
authSpinner.fail(source_default.red("Authentication failed"));
|
|
40302
|
+
console.error(source_default.gray(" Check your ELEVASIS_API_KEY and API URL."));
|
|
40303
|
+
throw error46;
|
|
40304
|
+
}
|
|
40129
40305
|
const validateSpinner = ora("Validating...").start();
|
|
40130
|
-
let config3;
|
|
40131
40306
|
let org;
|
|
40132
40307
|
try {
|
|
40133
40308
|
const jiti = (0, import_jiti2.createJiti)(import_meta2.url);
|
|
40134
|
-
const configModule = await jiti.import((0, import_path2.resolve)(configPath));
|
|
40135
|
-
config3 = configModule.default;
|
|
40136
|
-
if (!config3?.organization) {
|
|
40137
|
-
validateSpinner.fail('Invalid config: missing "organization" field');
|
|
40138
|
-
console.error(source_default.gray(` Config file: ${(0, import_path2.resolve)(configPath)}`));
|
|
40139
|
-
throw new Error('Invalid config: missing "organization" field');
|
|
40140
|
-
}
|
|
40141
40309
|
const entryModule = await jiti.import((0, import_path2.resolve)(entryPath));
|
|
40142
40310
|
org = entryModule.default;
|
|
40143
40311
|
if (!org) {
|
|
@@ -40145,7 +40313,7 @@ function registerDeployCommand(program3) {
|
|
|
40145
40313
|
console.error(source_default.gray(` Entry file: ${(0, import_path2.resolve)(entryPath)}`));
|
|
40146
40314
|
throw new Error("Invalid entry: no default export found");
|
|
40147
40315
|
}
|
|
40148
|
-
new ResourceRegistry({ [
|
|
40316
|
+
new ResourceRegistry({ [orgName]: org });
|
|
40149
40317
|
const workflowCount = org.workflows?.length ?? 0;
|
|
40150
40318
|
const agentCount = org.agents?.length ?? 0;
|
|
40151
40319
|
const totalCount = workflowCount + agentCount;
|
|
@@ -40153,7 +40321,7 @@ function registerDeployCommand(program3) {
|
|
|
40153
40321
|
source_default.green("Validating...") + source_default.white(" done") + source_default.gray(` (${totalCount} resource${totalCount !== 1 ? "s" : ""}, 0 errors)`)
|
|
40154
40322
|
);
|
|
40155
40323
|
console.log("");
|
|
40156
|
-
console.log(source_default.gray(` Org: ${
|
|
40324
|
+
console.log(source_default.gray(` Org: ${orgName}`));
|
|
40157
40325
|
console.log(source_default.gray(` Target: ${apiUrl} (${env2})`));
|
|
40158
40326
|
console.log("");
|
|
40159
40327
|
for (const w of org.workflows ?? []) {
|
|
@@ -40233,13 +40401,13 @@ function registerDeployCommand(program3) {
|
|
|
40233
40401
|
console.log("");
|
|
40234
40402
|
}
|
|
40235
40403
|
const bundleSpinner = ora("Bundling...").start();
|
|
40236
|
-
const wrapperPath = (0, import_path2.resolve)("
|
|
40404
|
+
const wrapperPath = (0, import_path2.resolve)("__elevasis_worker.ts");
|
|
40237
40405
|
const bundleOutfile = (0, import_path2.resolve)("dist/bundle.js");
|
|
40238
40406
|
try {
|
|
40239
40407
|
const entryImport = entryPath.replace(/\.ts$/, ".js");
|
|
40240
40408
|
const wrapperContent = `import org from ${JSON.stringify(entryImport)}
|
|
40241
|
-
import {
|
|
40242
|
-
|
|
40409
|
+
import { startWorker } from '@elevasis/sdk/worker'
|
|
40410
|
+
startWorker(org)
|
|
40243
40411
|
`;
|
|
40244
40412
|
await (0, import_promises.writeFile)(wrapperPath, wrapperContent, "utf-8");
|
|
40245
40413
|
await (0, import_promises.mkdir)((0, import_path2.resolve)("dist"), { recursive: true });
|
|
@@ -40329,48 +40497,6 @@ startServer(org)
|
|
|
40329
40497
|
}));
|
|
40330
40498
|
}
|
|
40331
40499
|
|
|
40332
|
-
// src/cli/api-client.ts
|
|
40333
|
-
function getApiKey() {
|
|
40334
|
-
const key = process.env.ELEVASIS_API_KEY;
|
|
40335
|
-
if (!key) {
|
|
40336
|
-
throw new Error(
|
|
40337
|
-
"ELEVASIS_API_KEY environment variable is required.\nSet it in your .env file: ELEVASIS_API_KEY=sk_..."
|
|
40338
|
-
);
|
|
40339
|
-
}
|
|
40340
|
-
return key;
|
|
40341
|
-
}
|
|
40342
|
-
async function apiGet(endpoint, apiUrl = resolveApiUrl()) {
|
|
40343
|
-
const response = await fetch(`${apiUrl}${endpoint}`, {
|
|
40344
|
-
headers: { Authorization: `Bearer ${getApiKey()}` }
|
|
40345
|
-
});
|
|
40346
|
-
if (!response.ok) {
|
|
40347
|
-
const errorText = await response.text();
|
|
40348
|
-
throw new Error(`API request failed (${response.status}): ${errorText}`);
|
|
40349
|
-
}
|
|
40350
|
-
if (response.status === 204) {
|
|
40351
|
-
return {};
|
|
40352
|
-
}
|
|
40353
|
-
return response.json();
|
|
40354
|
-
}
|
|
40355
|
-
async function apiPost(endpoint, body, apiUrl = resolveApiUrl()) {
|
|
40356
|
-
const response = await fetch(`${apiUrl}${endpoint}`, {
|
|
40357
|
-
method: "POST",
|
|
40358
|
-
headers: {
|
|
40359
|
-
Authorization: `Bearer ${getApiKey()}`,
|
|
40360
|
-
"Content-Type": "application/json"
|
|
40361
|
-
},
|
|
40362
|
-
body: JSON.stringify(body)
|
|
40363
|
-
});
|
|
40364
|
-
if (!response.ok) {
|
|
40365
|
-
const errorText = await response.text();
|
|
40366
|
-
throw new Error(`API request failed (${response.status}): ${errorText}`);
|
|
40367
|
-
}
|
|
40368
|
-
if (response.status === 204) {
|
|
40369
|
-
return {};
|
|
40370
|
-
}
|
|
40371
|
-
return response.json();
|
|
40372
|
-
}
|
|
40373
|
-
|
|
40374
40500
|
// src/cli/commands/exec.ts
|
|
40375
40501
|
function registerExecCommand(program3) {
|
|
40376
40502
|
program3.command("exec <resourceId>").description(`Execute a deployed resource
|
|
@@ -40703,7 +40829,7 @@ var import_path3 = require("path");
|
|
|
40703
40829
|
var import_promises2 = require("fs/promises");
|
|
40704
40830
|
|
|
40705
40831
|
// src/cli/version.ts
|
|
40706
|
-
var SDK_VERSION = "0.
|
|
40832
|
+
var SDK_VERSION = "0.4.1";
|
|
40707
40833
|
|
|
40708
40834
|
// src/cli/commands/init.ts
|
|
40709
40835
|
var SCAFFOLD_FILES = [
|
|
@@ -40740,7 +40866,7 @@ function registerInitCommand(program3) {
|
|
|
40740
40866
|
}
|
|
40741
40867
|
await (0, import_promises2.mkdir)((0, import_path3.resolve)(targetDir, "src"), { recursive: true });
|
|
40742
40868
|
const files = {
|
|
40743
|
-
"elevasis.config.ts": configTemplate(
|
|
40869
|
+
"elevasis.config.ts": configTemplate(),
|
|
40744
40870
|
"package.json": packageJsonTemplate(orgSlug),
|
|
40745
40871
|
"pnpm-workspace.yaml": pnpmWorkspaceTemplate(),
|
|
40746
40872
|
"tsconfig.json": tsconfigTemplate(),
|
|
@@ -40768,12 +40894,10 @@ function toSlug(name) {
|
|
|
40768
40894
|
const slug = name.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/^[^a-z]+/, "").replace(/-+/g, "-").replace(/-$/, "");
|
|
40769
40895
|
return slug.length >= 3 ? slug : "my-project";
|
|
40770
40896
|
}
|
|
40771
|
-
function configTemplate(
|
|
40897
|
+
function configTemplate() {
|
|
40772
40898
|
return `import type { ElevasConfig } from '@elevasis/sdk'
|
|
40773
40899
|
|
|
40774
|
-
export default {
|
|
40775
|
-
organization: '${organization}',
|
|
40776
|
-
} satisfies ElevasConfig
|
|
40900
|
+
export default {} satisfies ElevasConfig
|
|
40777
40901
|
`;
|
|
40778
40902
|
}
|
|
40779
40903
|
function packageJsonTemplate(organization) {
|
|
@@ -40866,7 +40990,7 @@ elevasis executions <resourceId>
|
|
|
40866
40990
|
|
|
40867
40991
|
## Project Structure
|
|
40868
40992
|
|
|
40869
|
-
- \`elevasis.config.ts\` --
|
|
40993
|
+
- \`elevasis.config.ts\` -- Project config (optional settings)
|
|
40870
40994
|
- \`src/index.ts\` -- Resource definitions (workflows, agents)
|
|
40871
40995
|
- \`.env\` -- API key and environment variables
|
|
40872
40996
|
`;
|
package/dist/index.d.ts
CHANGED
|
@@ -1054,6 +1054,22 @@ interface IterationContext {
|
|
|
1054
1054
|
* - Command View data generation
|
|
1055
1055
|
*/
|
|
1056
1056
|
|
|
1057
|
+
/**
|
|
1058
|
+
* Configuration for a remotely-deployed organization
|
|
1059
|
+
*
|
|
1060
|
+
* Stored alongside runtime-registered organizations to support
|
|
1061
|
+
* worker thread execution branching and credential management.
|
|
1062
|
+
*/
|
|
1063
|
+
interface RemoteOrgConfig {
|
|
1064
|
+
/** Path to the esbuild bundle on disk */
|
|
1065
|
+
bundlePath: string;
|
|
1066
|
+
/** Deployment record ID */
|
|
1067
|
+
deploymentId: string;
|
|
1068
|
+
/** Developer-provided environment variables injected into the worker */
|
|
1069
|
+
envVars?: Record<string, string>;
|
|
1070
|
+
/** Platform tool name -> credential name mapping */
|
|
1071
|
+
toolCredentials?: Record<string, string>;
|
|
1072
|
+
}
|
|
1057
1073
|
/**
|
|
1058
1074
|
* Organization-specific resource collection
|
|
1059
1075
|
*
|
|
@@ -1084,9 +1100,16 @@ declare class ResourceRegistry {
|
|
|
1084
1100
|
private registry;
|
|
1085
1101
|
/**
|
|
1086
1102
|
* Pre-serialized organization data cache
|
|
1087
|
-
* Computed once at construction,
|
|
1103
|
+
* Computed once at construction for static orgs, updated incrementally for runtime orgs
|
|
1088
1104
|
*/
|
|
1089
1105
|
private serializedCache;
|
|
1106
|
+
/**
|
|
1107
|
+
* Per-resource remote configuration (external deployments)
|
|
1108
|
+
* Key: "orgName/resourceId", Value: RemoteOrgConfig for that resource.
|
|
1109
|
+
* Tracks which individual resources were added at runtime via deploy pipeline.
|
|
1110
|
+
* Static and remote resources coexist in the same org.
|
|
1111
|
+
*/
|
|
1112
|
+
private remoteResources;
|
|
1090
1113
|
constructor(registry: OrganizationRegistry);
|
|
1091
1114
|
/**
|
|
1092
1115
|
* Validates registry on construction
|
|
@@ -1122,6 +1145,56 @@ declare class ResourceRegistry {
|
|
|
1122
1145
|
* NOTE: For debugging only - returns raw registry data
|
|
1123
1146
|
*/
|
|
1124
1147
|
listAllResources(): OrganizationRegistry;
|
|
1148
|
+
/**
|
|
1149
|
+
* Register external resources at runtime
|
|
1150
|
+
*
|
|
1151
|
+
* Called during deploy pipeline when an external developer deploys their bundle.
|
|
1152
|
+
* Merges the incoming stub definitions into the org's registry and stores
|
|
1153
|
+
* per-resource remote config for worker thread execution branching.
|
|
1154
|
+
*
|
|
1155
|
+
* Static and remote resources coexist in the same org. If the org already
|
|
1156
|
+
* has static resources, the incoming remote resources are merged alongside them.
|
|
1157
|
+
* If redeploying (some resources already registered as remote for this org),
|
|
1158
|
+
* the previous remote resources are unregistered first.
|
|
1159
|
+
*
|
|
1160
|
+
* @param orgName - Organization name (used as registry key)
|
|
1161
|
+
* @param org - Stub resource definitions (workflows/agents with placeholder handlers)
|
|
1162
|
+
* @param remote - Remote configuration (bundle path, deployment ID, env vars)
|
|
1163
|
+
* @throws Error if incoming resourceId conflicts with a static resource
|
|
1164
|
+
* @throws Error if incoming deployment contains duplicate resourceIds
|
|
1165
|
+
*/
|
|
1166
|
+
registerOrganization(orgName: string, org: OrganizationResources, remote: RemoteOrgConfig): void;
|
|
1167
|
+
/**
|
|
1168
|
+
* Unregister runtime-registered resources for an organization
|
|
1169
|
+
*
|
|
1170
|
+
* Removes only resources that were registered at runtime (via registerOrganization).
|
|
1171
|
+
* Static resources loaded at startup are preserved. If the org still has static
|
|
1172
|
+
* resources after removal, the serialization cache is rebuilt. If no resources
|
|
1173
|
+
* remain, the org is fully removed from the registry.
|
|
1174
|
+
* No-op if the org has no remote resources.
|
|
1175
|
+
*
|
|
1176
|
+
* @param orgName - Organization name to unregister remote resources from
|
|
1177
|
+
*/
|
|
1178
|
+
unregisterOrganization(orgName: string): void;
|
|
1179
|
+
/**
|
|
1180
|
+
* Get remote configuration for a specific resource
|
|
1181
|
+
*
|
|
1182
|
+
* Returns the RemoteOrgConfig if the resource was registered at runtime,
|
|
1183
|
+
* or null if it's a static resource or doesn't exist.
|
|
1184
|
+
* Used by the execution coordinator to branch between local and worker execution.
|
|
1185
|
+
*
|
|
1186
|
+
* @param orgName - Organization name
|
|
1187
|
+
* @param resourceId - Resource ID
|
|
1188
|
+
* @returns Remote config or null
|
|
1189
|
+
*/
|
|
1190
|
+
getRemoteConfig(orgName: string, resourceId: string): RemoteOrgConfig | null;
|
|
1191
|
+
/**
|
|
1192
|
+
* Check if an organization has any remote (externally deployed) resources
|
|
1193
|
+
*
|
|
1194
|
+
* @param orgName - Organization name
|
|
1195
|
+
* @returns true if the org has at least one runtime-registered resource
|
|
1196
|
+
*/
|
|
1197
|
+
isRemote(orgName: string): boolean;
|
|
1125
1198
|
/**
|
|
1126
1199
|
* Get triggers for an organization
|
|
1127
1200
|
* @param organizationName - Organization name
|
|
@@ -1837,10 +1910,10 @@ declare class RegistryValidationError extends Error {
|
|
|
1837
1910
|
|
|
1838
1911
|
/**
|
|
1839
1912
|
* Project configuration for an external developer project.
|
|
1840
|
-
* Defined in
|
|
1913
|
+
* Defined in elevasis.config.ts at the project root.
|
|
1914
|
+
* Organization is derived from the ELEVASIS_API_KEY -- not configured here.
|
|
1841
1915
|
*/
|
|
1842
1916
|
interface ElevasConfig {
|
|
1843
|
-
organization: string;
|
|
1844
1917
|
defaultStatus?: ResourceStatus;
|
|
1845
1918
|
dev?: {
|
|
1846
1919
|
port?: number;
|
package/dist/index.js
CHANGED
|
@@ -3162,9 +3162,16 @@ var ResourceRegistry = class {
|
|
|
3162
3162
|
}
|
|
3163
3163
|
/**
|
|
3164
3164
|
* Pre-serialized organization data cache
|
|
3165
|
-
* Computed once at construction,
|
|
3165
|
+
* Computed once at construction for static orgs, updated incrementally for runtime orgs
|
|
3166
3166
|
*/
|
|
3167
3167
|
serializedCache;
|
|
3168
|
+
/**
|
|
3169
|
+
* Per-resource remote configuration (external deployments)
|
|
3170
|
+
* Key: "orgName/resourceId", Value: RemoteOrgConfig for that resource.
|
|
3171
|
+
* Tracks which individual resources were added at runtime via deploy pipeline.
|
|
3172
|
+
* Static and remote resources coexist in the same org.
|
|
3173
|
+
*/
|
|
3174
|
+
remoteResources = /* @__PURE__ */ new Map();
|
|
3168
3175
|
/**
|
|
3169
3176
|
* Validates registry on construction
|
|
3170
3177
|
* - Checks for duplicate resourceIds within organizations
|
|
@@ -3264,6 +3271,130 @@ var ResourceRegistry = class {
|
|
|
3264
3271
|
return this.registry;
|
|
3265
3272
|
}
|
|
3266
3273
|
// ============================================================================
|
|
3274
|
+
// Runtime Organization Registration (External Deployments)
|
|
3275
|
+
// ============================================================================
|
|
3276
|
+
/**
|
|
3277
|
+
* Register external resources at runtime
|
|
3278
|
+
*
|
|
3279
|
+
* Called during deploy pipeline when an external developer deploys their bundle.
|
|
3280
|
+
* Merges the incoming stub definitions into the org's registry and stores
|
|
3281
|
+
* per-resource remote config for worker thread execution branching.
|
|
3282
|
+
*
|
|
3283
|
+
* Static and remote resources coexist in the same org. If the org already
|
|
3284
|
+
* has static resources, the incoming remote resources are merged alongside them.
|
|
3285
|
+
* If redeploying (some resources already registered as remote for this org),
|
|
3286
|
+
* the previous remote resources are unregistered first.
|
|
3287
|
+
*
|
|
3288
|
+
* @param orgName - Organization name (used as registry key)
|
|
3289
|
+
* @param org - Stub resource definitions (workflows/agents with placeholder handlers)
|
|
3290
|
+
* @param remote - Remote configuration (bundle path, deployment ID, env vars)
|
|
3291
|
+
* @throws Error if incoming resourceId conflicts with a static resource
|
|
3292
|
+
* @throws Error if incoming deployment contains duplicate resourceIds
|
|
3293
|
+
*/
|
|
3294
|
+
registerOrganization(orgName, org, remote) {
|
|
3295
|
+
const incomingWorkflowIds = (org.workflows ?? []).map((w) => w.config.resourceId);
|
|
3296
|
+
const incomingAgentIds = (org.agents ?? []).map((a) => a.config.resourceId);
|
|
3297
|
+
const incomingIds = [...incomingWorkflowIds, ...incomingAgentIds];
|
|
3298
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3299
|
+
for (const id of incomingIds) {
|
|
3300
|
+
if (seen.has(id)) {
|
|
3301
|
+
throw new Error(
|
|
3302
|
+
`Duplicate resource ID '${id}' in deployment. Each resource must have a unique ID.`
|
|
3303
|
+
);
|
|
3304
|
+
}
|
|
3305
|
+
seen.add(id);
|
|
3306
|
+
}
|
|
3307
|
+
if (this.isRemote(orgName)) {
|
|
3308
|
+
this.unregisterOrganization(orgName);
|
|
3309
|
+
}
|
|
3310
|
+
const existingOrg = this.registry[orgName];
|
|
3311
|
+
if (existingOrg) {
|
|
3312
|
+
const staticWorkflowIds = new Set((existingOrg.workflows ?? []).map((w) => w.config.resourceId));
|
|
3313
|
+
const staticAgentIds = new Set((existingOrg.agents ?? []).map((a) => a.config.resourceId));
|
|
3314
|
+
for (const id of incomingIds) {
|
|
3315
|
+
if (staticWorkflowIds.has(id) || staticAgentIds.has(id)) {
|
|
3316
|
+
throw new Error(
|
|
3317
|
+
`Resource '${id}' already exists in '${orgName}' as an internal resource. External deployments cannot override internal resources.`
|
|
3318
|
+
);
|
|
3319
|
+
}
|
|
3320
|
+
}
|
|
3321
|
+
}
|
|
3322
|
+
if (existingOrg) {
|
|
3323
|
+
existingOrg.workflows = [...existingOrg.workflows ?? [], ...org.workflows ?? []];
|
|
3324
|
+
existingOrg.agents = [...existingOrg.agents ?? [], ...org.agents ?? []];
|
|
3325
|
+
} else {
|
|
3326
|
+
this.registry[orgName] = org;
|
|
3327
|
+
}
|
|
3328
|
+
for (const id of incomingIds) {
|
|
3329
|
+
this.remoteResources.set(`${orgName}/${id}`, remote);
|
|
3330
|
+
}
|
|
3331
|
+
this.serializedCache.set(orgName, serializeOrganization(this.registry[orgName]));
|
|
3332
|
+
}
|
|
3333
|
+
/**
|
|
3334
|
+
* Unregister runtime-registered resources for an organization
|
|
3335
|
+
*
|
|
3336
|
+
* Removes only resources that were registered at runtime (via registerOrganization).
|
|
3337
|
+
* Static resources loaded at startup are preserved. If the org still has static
|
|
3338
|
+
* resources after removal, the serialization cache is rebuilt. If no resources
|
|
3339
|
+
* remain, the org is fully removed from the registry.
|
|
3340
|
+
* No-op if the org has no remote resources.
|
|
3341
|
+
*
|
|
3342
|
+
* @param orgName - Organization name to unregister remote resources from
|
|
3343
|
+
*/
|
|
3344
|
+
unregisterOrganization(orgName) {
|
|
3345
|
+
const prefix = `${orgName}/`;
|
|
3346
|
+
const remoteIds = /* @__PURE__ */ new Set();
|
|
3347
|
+
for (const key of this.remoteResources.keys()) {
|
|
3348
|
+
if (key.startsWith(prefix)) {
|
|
3349
|
+
remoteIds.add(key.slice(prefix.length));
|
|
3350
|
+
this.remoteResources.delete(key);
|
|
3351
|
+
}
|
|
3352
|
+
}
|
|
3353
|
+
if (remoteIds.size === 0) return;
|
|
3354
|
+
const orgResources = this.registry[orgName];
|
|
3355
|
+
if (!orgResources) return;
|
|
3356
|
+
orgResources.workflows = (orgResources.workflows ?? []).filter(
|
|
3357
|
+
(w) => !remoteIds.has(w.config.resourceId)
|
|
3358
|
+
);
|
|
3359
|
+
orgResources.agents = (orgResources.agents ?? []).filter(
|
|
3360
|
+
(a) => !remoteIds.has(a.config.resourceId)
|
|
3361
|
+
);
|
|
3362
|
+
const remaining = (orgResources.workflows?.length ?? 0) + (orgResources.agents?.length ?? 0) + (orgResources.triggers?.length ?? 0) + (orgResources.integrations?.length ?? 0) + (orgResources.externalResources?.length ?? 0) + (orgResources.humanCheckpoints?.length ?? 0);
|
|
3363
|
+
if (remaining > 0) {
|
|
3364
|
+
this.serializedCache.set(orgName, serializeOrganization(orgResources));
|
|
3365
|
+
} else {
|
|
3366
|
+
delete this.registry[orgName];
|
|
3367
|
+
this.serializedCache.delete(orgName);
|
|
3368
|
+
}
|
|
3369
|
+
}
|
|
3370
|
+
/**
|
|
3371
|
+
* Get remote configuration for a specific resource
|
|
3372
|
+
*
|
|
3373
|
+
* Returns the RemoteOrgConfig if the resource was registered at runtime,
|
|
3374
|
+
* or null if it's a static resource or doesn't exist.
|
|
3375
|
+
* Used by the execution coordinator to branch between local and worker execution.
|
|
3376
|
+
*
|
|
3377
|
+
* @param orgName - Organization name
|
|
3378
|
+
* @param resourceId - Resource ID
|
|
3379
|
+
* @returns Remote config or null
|
|
3380
|
+
*/
|
|
3381
|
+
getRemoteConfig(orgName, resourceId) {
|
|
3382
|
+
return this.remoteResources.get(`${orgName}/${resourceId}`) ?? null;
|
|
3383
|
+
}
|
|
3384
|
+
/**
|
|
3385
|
+
* Check if an organization has any remote (externally deployed) resources
|
|
3386
|
+
*
|
|
3387
|
+
* @param orgName - Organization name
|
|
3388
|
+
* @returns true if the org has at least one runtime-registered resource
|
|
3389
|
+
*/
|
|
3390
|
+
isRemote(orgName) {
|
|
3391
|
+
const prefix = `${orgName}/`;
|
|
3392
|
+
for (const key of this.remoteResources.keys()) {
|
|
3393
|
+
if (key.startsWith(prefix)) return true;
|
|
3394
|
+
}
|
|
3395
|
+
return false;
|
|
3396
|
+
}
|
|
3397
|
+
// ============================================================================
|
|
3267
3398
|
// Resource Manifest Accessors
|
|
3268
3399
|
// ============================================================================
|
|
3269
3400
|
/**
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { parentPort } from 'worker_threads';
|
|
2
|
+
|
|
3
|
+
// src/worker/index.ts
|
|
4
|
+
function resolveNext(next, data) {
|
|
5
|
+
if (next === null) return null;
|
|
6
|
+
if (next.type === "linear") return next.target;
|
|
7
|
+
for (const route of next.routes) {
|
|
8
|
+
if (route.condition(data)) return route.target;
|
|
9
|
+
}
|
|
10
|
+
return next.default;
|
|
11
|
+
}
|
|
12
|
+
async function executeWorkflow(workflow, input) {
|
|
13
|
+
const logs = [];
|
|
14
|
+
const origLog = console.log;
|
|
15
|
+
const origWarn = console.warn;
|
|
16
|
+
const origError = console.error;
|
|
17
|
+
const capture = (level, orig) => (...args) => {
|
|
18
|
+
logs.push({ level, message: args.map(String).join(" ") });
|
|
19
|
+
orig(...args);
|
|
20
|
+
};
|
|
21
|
+
console.log = capture("info", origLog);
|
|
22
|
+
console.warn = capture("warn", origWarn);
|
|
23
|
+
console.error = capture("error", origError);
|
|
24
|
+
try {
|
|
25
|
+
let currentData = workflow.contract.inputSchema ? workflow.contract.inputSchema.parse(input) : input;
|
|
26
|
+
let stepId = workflow.entryPoint;
|
|
27
|
+
while (stepId !== null) {
|
|
28
|
+
const step = workflow.steps[stepId];
|
|
29
|
+
if (!step) {
|
|
30
|
+
throw new Error(`Step '${stepId}' not found in workflow '${workflow.config.resourceId}'`);
|
|
31
|
+
}
|
|
32
|
+
const stepInput = step.inputSchema.parse(currentData);
|
|
33
|
+
const rawOutput = await step.handler(stepInput, {
|
|
34
|
+
executionId: "",
|
|
35
|
+
organizationId: "",
|
|
36
|
+
organizationName: "",
|
|
37
|
+
resourceId: workflow.config.resourceId,
|
|
38
|
+
executionDepth: 0,
|
|
39
|
+
store: /* @__PURE__ */ new Map(),
|
|
40
|
+
logger: {
|
|
41
|
+
debug: (msg) => console.log(`[debug] ${msg}`),
|
|
42
|
+
info: (msg) => console.log(`[info] ${msg}`),
|
|
43
|
+
warn: (msg) => console.warn(`[warn] ${msg}`),
|
|
44
|
+
error: (msg) => console.error(`[error] ${msg}`)
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
currentData = step.outputSchema.parse(rawOutput);
|
|
48
|
+
stepId = resolveNext(step.next, currentData);
|
|
49
|
+
}
|
|
50
|
+
if (workflow.contract.outputSchema) {
|
|
51
|
+
currentData = workflow.contract.outputSchema.parse(currentData);
|
|
52
|
+
}
|
|
53
|
+
return { output: currentData, logs };
|
|
54
|
+
} finally {
|
|
55
|
+
console.log = origLog;
|
|
56
|
+
console.warn = origWarn;
|
|
57
|
+
console.error = origError;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function startWorker(org) {
|
|
61
|
+
const workflows = new Map(
|
|
62
|
+
(org.workflows ?? []).map((w) => [w.config.resourceId, w])
|
|
63
|
+
);
|
|
64
|
+
const agents = new Map(
|
|
65
|
+
(org.agents ?? []).map((a) => [a.config.resourceId, a])
|
|
66
|
+
);
|
|
67
|
+
console.log(`[SDK-WORKER] Worker started with ${workflows.size} workflow(s), ${agents.size} agent(s)`);
|
|
68
|
+
parentPort.on("message", async (msg) => {
|
|
69
|
+
if (msg.type === "manifest") {
|
|
70
|
+
const workflowList = (org.workflows ?? []).map((w) => w.config.resourceId);
|
|
71
|
+
const agentList = (org.agents ?? []).map((a) => a.config.resourceId);
|
|
72
|
+
console.log(`[SDK-WORKER] Manifest requested: workflows=[${workflowList.join(", ")}], agents=[${agentList.join(", ")}]`);
|
|
73
|
+
parentPort.postMessage({
|
|
74
|
+
type: "manifest",
|
|
75
|
+
workflows: (org.workflows ?? []).map((w) => ({
|
|
76
|
+
resourceId: w.config.resourceId,
|
|
77
|
+
name: w.config.name,
|
|
78
|
+
type: w.config.type,
|
|
79
|
+
status: w.config.status,
|
|
80
|
+
description: w.config.description,
|
|
81
|
+
version: w.config.version
|
|
82
|
+
})),
|
|
83
|
+
agents: (org.agents ?? []).map((a) => ({
|
|
84
|
+
resourceId: a.config.resourceId,
|
|
85
|
+
name: a.config.name,
|
|
86
|
+
type: a.config.type,
|
|
87
|
+
status: a.config.status,
|
|
88
|
+
description: a.config.description,
|
|
89
|
+
version: a.config.version
|
|
90
|
+
}))
|
|
91
|
+
});
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (msg.type === "execute") {
|
|
95
|
+
const { resourceId, executionId, input } = msg;
|
|
96
|
+
console.log(`[SDK-WORKER] Execute request: resourceId=${resourceId}, executionId=${executionId}`);
|
|
97
|
+
const workflow = workflows.get(resourceId);
|
|
98
|
+
if (workflow) {
|
|
99
|
+
try {
|
|
100
|
+
console.log(`[SDK-WORKER] Running workflow '${resourceId}' (${Object.keys(workflow.steps).length} steps)`);
|
|
101
|
+
const startTime = Date.now();
|
|
102
|
+
const { output, logs } = await executeWorkflow(workflow, input);
|
|
103
|
+
console.log(`[SDK-WORKER] Workflow '${resourceId}' completed (${Date.now() - startTime}ms)`);
|
|
104
|
+
parentPort.postMessage({ type: "result", status: "completed", output, logs });
|
|
105
|
+
} catch (err) {
|
|
106
|
+
console.error(`[SDK-WORKER] Workflow '${resourceId}' failed: ${String(err)}`);
|
|
107
|
+
parentPort.postMessage({ type: "result", status: "failed", error: String(err), logs: [] });
|
|
108
|
+
}
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (agents.has(resourceId)) {
|
|
112
|
+
console.error(`[SDK-WORKER] Agent execution not supported: ${resourceId}`);
|
|
113
|
+
parentPort.postMessage({
|
|
114
|
+
type: "result",
|
|
115
|
+
status: "failed",
|
|
116
|
+
error: "Agent execution not yet supported in worker runtime",
|
|
117
|
+
logs: []
|
|
118
|
+
});
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
console.error(`[SDK-WORKER] Resource not found: ${resourceId}`);
|
|
122
|
+
parentPort.postMessage({
|
|
123
|
+
type: "result",
|
|
124
|
+
status: "failed",
|
|
125
|
+
error: `Resource not found: ${resourceId}`,
|
|
126
|
+
logs: []
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export { startWorker };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elevasis/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "SDK for building Elevasis organization resources",
|
|
5
5
|
"comment:bin": "IMPORTANT: This package shares the 'elevasis' binary name with @repo/cli. They never conflict because @elevasis/sdk must NEVER be added as a dependency of any workspace package (apps/*, packages/*, organizations/*). Workspace projects use @repo/cli for the 'elevasis' binary. External developers (outside the workspace) get this SDK's binary via npm install.",
|
|
6
6
|
"type": "module",
|
|
@@ -13,14 +13,14 @@
|
|
|
13
13
|
"types": "./dist/index.d.ts",
|
|
14
14
|
"import": "./dist/index.js"
|
|
15
15
|
},
|
|
16
|
-
"./
|
|
17
|
-
"import": "./dist/
|
|
16
|
+
"./worker": {
|
|
17
|
+
"import": "./dist/worker/index.js"
|
|
18
18
|
}
|
|
19
19
|
},
|
|
20
20
|
"files": [
|
|
21
21
|
"dist/index.js",
|
|
22
22
|
"dist/index.d.ts",
|
|
23
|
-
"dist/
|
|
23
|
+
"dist/worker/index.js",
|
|
24
24
|
"dist/cli.cjs"
|
|
25
25
|
],
|
|
26
26
|
"scripts": {
|
package/dist/server/index.js
DELETED
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
import { createServer } from 'http';
|
|
2
|
-
|
|
3
|
-
// src/server/index.ts
|
|
4
|
-
function readBody(req) {
|
|
5
|
-
return new Promise((resolve, reject) => {
|
|
6
|
-
const chunks = [];
|
|
7
|
-
req.on("data", (chunk) => chunks.push(chunk));
|
|
8
|
-
req.on("end", () => resolve(Buffer.concat(chunks).toString()));
|
|
9
|
-
req.on("error", reject);
|
|
10
|
-
});
|
|
11
|
-
}
|
|
12
|
-
function json(res, status, data) {
|
|
13
|
-
res.writeHead(status, { "Content-Type": "application/json" });
|
|
14
|
-
res.end(JSON.stringify(data));
|
|
15
|
-
}
|
|
16
|
-
function resolveNext(next, data) {
|
|
17
|
-
if (next === null) return null;
|
|
18
|
-
if (next.type === "linear") return next.target;
|
|
19
|
-
for (const route of next.routes) {
|
|
20
|
-
if (route.condition(data)) return route.target;
|
|
21
|
-
}
|
|
22
|
-
return next.default;
|
|
23
|
-
}
|
|
24
|
-
async function executeWorkflow(workflow, input, context) {
|
|
25
|
-
let currentData = workflow.contract.inputSchema.parse(input);
|
|
26
|
-
let stepId = workflow.entryPoint;
|
|
27
|
-
while (stepId !== null) {
|
|
28
|
-
if (context.signal?.aborted) {
|
|
29
|
-
throw new Error("Execution cancelled");
|
|
30
|
-
}
|
|
31
|
-
const step = workflow.steps[stepId];
|
|
32
|
-
if (!step) {
|
|
33
|
-
throw new Error(`Step '${stepId}' not found in workflow '${workflow.config.resourceId}'`);
|
|
34
|
-
}
|
|
35
|
-
const stepInput = step.inputSchema.parse(currentData);
|
|
36
|
-
const rawOutput = await step.handler(stepInput, context);
|
|
37
|
-
currentData = step.outputSchema.parse(rawOutput);
|
|
38
|
-
stepId = resolveNext(step.next, currentData);
|
|
39
|
-
}
|
|
40
|
-
if (workflow.contract.outputSchema) {
|
|
41
|
-
currentData = workflow.contract.outputSchema.parse(currentData);
|
|
42
|
-
}
|
|
43
|
-
return currentData;
|
|
44
|
-
}
|
|
45
|
-
function withLogCapture(fn) {
|
|
46
|
-
const logs = [];
|
|
47
|
-
const orig = { log: console.log, warn: console.warn, error: console.error };
|
|
48
|
-
const capture = (level, origFn) => (...args) => {
|
|
49
|
-
logs.push({ level, message: args.map(String).join(" "), timestamp: Date.now() });
|
|
50
|
-
origFn(...args);
|
|
51
|
-
};
|
|
52
|
-
console.log = capture("info", orig.log);
|
|
53
|
-
console.warn = capture("warn", orig.warn);
|
|
54
|
-
console.error = capture("error", orig.error);
|
|
55
|
-
return fn().then((result) => ({ result, logs })).finally(() => {
|
|
56
|
-
console.log = orig.log;
|
|
57
|
-
console.warn = orig.warn;
|
|
58
|
-
console.error = orig.error;
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
function startServer(org) {
|
|
62
|
-
const port = parseInt(process.env.PORT || "3000");
|
|
63
|
-
const workflows = new Map(
|
|
64
|
-
(org.workflows ?? []).map((w) => [w.config.resourceId, w])
|
|
65
|
-
);
|
|
66
|
-
const agents = new Map(
|
|
67
|
-
(org.agents ?? []).map((a) => [a.config.resourceId, a])
|
|
68
|
-
);
|
|
69
|
-
const controllers = /* @__PURE__ */ new Map();
|
|
70
|
-
const server = createServer(async (req, res) => {
|
|
71
|
-
try {
|
|
72
|
-
if (req.url === "/health" && req.method === "GET") {
|
|
73
|
-
res.writeHead(200).end("ok");
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
if (req.url === "/manifest" && req.method === "GET") {
|
|
77
|
-
json(res, 200, {
|
|
78
|
-
workflows: (org.workflows ?? []).map((w) => ({
|
|
79
|
-
resourceId: w.config.resourceId,
|
|
80
|
-
name: w.config.name,
|
|
81
|
-
type: w.config.type,
|
|
82
|
-
status: w.config.status,
|
|
83
|
-
description: w.config.description,
|
|
84
|
-
version: w.config.version
|
|
85
|
-
})),
|
|
86
|
-
agents: (org.agents ?? []).map((a) => ({
|
|
87
|
-
resourceId: a.config.resourceId,
|
|
88
|
-
name: a.config.name,
|
|
89
|
-
type: a.config.type,
|
|
90
|
-
status: a.config.status,
|
|
91
|
-
description: a.config.description,
|
|
92
|
-
version: a.config.version
|
|
93
|
-
}))
|
|
94
|
-
});
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
if (req.url === "/execute" && req.method === "POST") {
|
|
98
|
-
const body = JSON.parse(await readBody(req));
|
|
99
|
-
const { resourceId, executionId, input } = body;
|
|
100
|
-
const workflow = workflows.get(resourceId);
|
|
101
|
-
if (workflow) {
|
|
102
|
-
const controller = new AbortController();
|
|
103
|
-
controllers.set(executionId, controller);
|
|
104
|
-
const deadline = req.headers["x-elevasis-deadline"];
|
|
105
|
-
const timer = deadline ? setTimeout(() => controller.abort(), Number(deadline) - Date.now()) : void 0;
|
|
106
|
-
const context = {
|
|
107
|
-
executionId,
|
|
108
|
-
organizationId: "",
|
|
109
|
-
organizationName: "",
|
|
110
|
-
resourceId,
|
|
111
|
-
executionDepth: 0,
|
|
112
|
-
store: /* @__PURE__ */ new Map(),
|
|
113
|
-
signal: controller.signal,
|
|
114
|
-
logger: {
|
|
115
|
-
debug: (msg) => console.log(`[debug] ${msg}`),
|
|
116
|
-
info: (msg) => console.log(`[info] ${msg}`),
|
|
117
|
-
warn: (msg) => console.warn(`[warn] ${msg}`),
|
|
118
|
-
error: (msg) => console.error(`[error] ${msg}`)
|
|
119
|
-
}
|
|
120
|
-
};
|
|
121
|
-
try {
|
|
122
|
-
const { result: output, logs } = await withLogCapture(
|
|
123
|
-
() => executeWorkflow(workflow, input, context)
|
|
124
|
-
);
|
|
125
|
-
json(res, 200, { status: "completed", output, logs });
|
|
126
|
-
} catch (err) {
|
|
127
|
-
const { logs } = await withLogCapture(async () => {
|
|
128
|
-
});
|
|
129
|
-
json(res, 500, { status: "failed", error: String(err), logs });
|
|
130
|
-
} finally {
|
|
131
|
-
controllers.delete(executionId);
|
|
132
|
-
if (timer) clearTimeout(timer);
|
|
133
|
-
}
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
const agent = agents.get(resourceId);
|
|
137
|
-
if (agent) {
|
|
138
|
-
json(res, 501, {
|
|
139
|
-
status: "failed",
|
|
140
|
-
error: `Agent execution is not supported in SDK server v0.3.0. Resource '${resourceId}' is an agent.`,
|
|
141
|
-
logs: []
|
|
142
|
-
});
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
json(res, 404, { error: `Resource not found: ${resourceId}` });
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
|
-
if (req.url === "/cancel" && req.method === "POST") {
|
|
149
|
-
const body = JSON.parse(await readBody(req));
|
|
150
|
-
const { executionId } = body;
|
|
151
|
-
const controller = controllers.get(executionId);
|
|
152
|
-
if (controller) {
|
|
153
|
-
controller.abort();
|
|
154
|
-
controllers.delete(executionId);
|
|
155
|
-
json(res, 200, { cancelled: true });
|
|
156
|
-
} else {
|
|
157
|
-
json(res, 404, { error: `No running execution: ${executionId}` });
|
|
158
|
-
}
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
res.writeHead(404).end();
|
|
162
|
-
} catch (err) {
|
|
163
|
-
console.error("Unhandled server error:", err);
|
|
164
|
-
if (!res.headersSent) {
|
|
165
|
-
json(res, 500, { error: "Internal server error" });
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
});
|
|
169
|
-
server.listen(port, () => {
|
|
170
|
-
console.log(`Elevasis SDK server listening on port ${port}`);
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
export { startServer };
|