railcode 0.1.18 → 0.1.19
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/index.js +36 -36
- package/dist/manifest.js +11 -4
- package/package.json +1 -1
- package/static/sdk.js +46 -19
package/dist/index.js
CHANGED
|
@@ -4382,8 +4382,9 @@ class CliError extends Error {
|
|
|
4382
4382
|
// dev — local development server (`railcode dev`)
|
|
4383
4383
|
//
|
|
4384
4384
|
// Serves the app on a single loopback origin and emulates the /_api/* data plane:
|
|
4385
|
-
// identity is synthetic, KV + files live on local disk. llm/sql/
|
|
4386
|
-
// to the real instance
|
|
4385
|
+
// identity is synthetic, KV + files live on local disk. llm/sql/queries/connectors/email
|
|
4386
|
+
// proxy to the real instance as the signed-in user (their token + org grants) via the
|
|
4387
|
+
// org/user-scoped routes — no deploy needed. Mirrors railcode-core's dev.
|
|
4387
4388
|
// ---------------------------------------------------------------------------
|
|
4388
4389
|
const DEFAULT_DEV_PORT = 7331;
|
|
4389
4390
|
const DEFAULT_ASSET_PORT = 5173;
|
|
@@ -4922,8 +4923,10 @@ async function handleLocalApi(ctx, req, res, url) {
|
|
|
4922
4923
|
path === "/llm/generate" ||
|
|
4923
4924
|
path === "/llm/stream" ||
|
|
4924
4925
|
path === "/llm/providers" ||
|
|
4926
|
+
path === "/email" ||
|
|
4927
|
+
path === "/email/send" ||
|
|
4925
4928
|
path === "/service-connectors" ||
|
|
4926
|
-
path
|
|
4929
|
+
path.startsWith("/service-connectors/")) {
|
|
4927
4930
|
await forwardCompute(ctx, req, res, path, url);
|
|
4928
4931
|
return;
|
|
4929
4932
|
}
|
|
@@ -5113,27 +5116,29 @@ function devCreds(config) {
|
|
|
5113
5116
|
return null;
|
|
5114
5117
|
return { apiUrl: normalizeApiOrigin(apiUrl), token, orgUuid };
|
|
5115
5118
|
}
|
|
5116
|
-
//
|
|
5117
|
-
//
|
|
5118
|
-
// a
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5119
|
+
// Map a local-dev /_api compute path to the real org/user-scoped upstream URL. Local
|
|
5120
|
+
// dev invokes compute as the signed-in user (their personal token + org grants), NOT
|
|
5121
|
+
// as a deployed app — so no `railcode deploy` is needed first. The app slug still
|
|
5122
|
+
// namespaces local KV/files, but plays no part here. Deployed apps keep their own
|
|
5123
|
+
// bearer app-scoped routes (.../apps/{app}/...); this is deliberately the user plane.
|
|
5124
|
+
export function devComputeTarget(creds, path, search) {
|
|
5125
|
+
const org = `${creds.apiUrl}/api/organizations/${creds.orgUuid}`;
|
|
5126
|
+
if (path === "/connections")
|
|
5127
|
+
return `${org}/data/connections${search}`;
|
|
5128
|
+
if (path === "/sql")
|
|
5129
|
+
return `${org}/data/sql${search}`;
|
|
5130
|
+
if (path === "/queries" || path.startsWith("/queries/"))
|
|
5131
|
+
return `${org}/data${path}${search}`;
|
|
5132
|
+
if (path === "/service-connectors" || path.startsWith("/service-connectors/")) {
|
|
5133
|
+
return `${org}/data${path}${search}`;
|
|
5134
|
+
}
|
|
5135
|
+
if (path === "/llm/providers" || path === "/llm/generate" || path === "/llm/stream") {
|
|
5136
|
+
return `${org}${path}${search}`;
|
|
5137
|
+
}
|
|
5138
|
+
if (path === "/email" || path === "/email/send") {
|
|
5139
|
+
return `${org}${path}${search}`;
|
|
5140
|
+
}
|
|
5141
|
+
return null;
|
|
5137
5142
|
}
|
|
5138
5143
|
export function devUpstreamAuthFallback(status, degradeOk) {
|
|
5139
5144
|
if (status !== 401)
|
|
@@ -5164,19 +5169,13 @@ async function forwardCompute(ctx, req, res, path, url) {
|
|
|
5164
5169
|
return sendJson(res, 200, []);
|
|
5165
5170
|
return sendJson(res, 503, {
|
|
5166
5171
|
error: "not_logged_in",
|
|
5167
|
-
message: "Run `railcode login` to use LLM
|
|
5172
|
+
message: "Run `railcode login` to use LLM, SQL, saved queries, connectors, and email in dev.",
|
|
5168
5173
|
});
|
|
5169
5174
|
}
|
|
5170
|
-
const
|
|
5171
|
-
if (!
|
|
5172
|
-
|
|
5173
|
-
return sendJson(res, 200, []);
|
|
5174
|
-
return sendJson(res, 503, {
|
|
5175
|
-
error: "app_unavailable",
|
|
5176
|
-
message: "Deploy this app once before using LLM, SQL, saved queries, or connectors in dev.",
|
|
5177
|
-
});
|
|
5175
|
+
const target = devComputeTarget(creds, path, url.search);
|
|
5176
|
+
if (!target) {
|
|
5177
|
+
return sendJson(res, 404, { detail: "not found" });
|
|
5178
5178
|
}
|
|
5179
|
-
const target = `${creds.apiUrl}/api/organizations/${creds.orgUuid}/apps/${appUuid}${path}${url.search}`;
|
|
5180
5179
|
const body = req.method === "POST" ? await readBody(req) : undefined;
|
|
5181
5180
|
const headers = {
|
|
5182
5181
|
Authorization: `Bearer ${creds.token}`,
|
|
@@ -5409,10 +5408,11 @@ function printDevBanner(ctx, config) {
|
|
|
5409
5408
|
const creds = devCreds(config);
|
|
5410
5409
|
if (creds) {
|
|
5411
5410
|
console.log(` account: ${config.email ?? "?"} @ ${creds.apiUrl}`);
|
|
5412
|
-
console.log(`
|
|
5411
|
+
console.log(` compute: llm/sql/queries/connectors/email → real instance (REAL spend + data)`);
|
|
5412
|
+
console.log(` runs as: you — your saved token + org permissions (no deploy needed)`);
|
|
5413
5413
|
}
|
|
5414
5414
|
else {
|
|
5415
|
-
console.log(` account: not logged in — llm/sql return 503, connectors empty`);
|
|
5415
|
+
console.log(` account: not logged in — llm/sql/email return 503, connectors empty`);
|
|
5416
5416
|
}
|
|
5417
5417
|
console.log("");
|
|
5418
5418
|
console.log(" Ctrl-C to stop.");
|
package/dist/manifest.js
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
// - GET /v1/balance
|
|
14
14
|
// - GET /v1/customers/*
|
|
15
15
|
// llm: true
|
|
16
|
+
// email: true
|
|
16
17
|
// adhoc_sql:
|
|
17
18
|
// - warehouse
|
|
18
19
|
//
|
|
@@ -30,7 +31,7 @@
|
|
|
30
31
|
// test/manifest-fixtures.json (also run by
|
|
31
32
|
// backend/tests/test_manifest_parse_fixtures.py) keeps the two parsers pinned.
|
|
32
33
|
export const APP_MANIFEST_NAME = "manifest.yaml";
|
|
33
|
-
const ALLOWED_KEYS = ["run_as", "saved_queries", "connectors", "llm", "adhoc_sql"];
|
|
34
|
+
const ALLOWED_KEYS = ["run_as", "saved_queries", "connectors", "llm", "email", "adhoc_sql"];
|
|
34
35
|
const HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
|
|
35
36
|
const SAVED_QUERY_NAME = /^[a-z0-9_]{1,80}$/;
|
|
36
37
|
// PyYAML's YAML 1.1 plain-scalar resolver: a bare (unquoted) scalar matching one
|
|
@@ -322,7 +323,7 @@ function listString(s, key, num) {
|
|
|
322
323
|
throw new ManifestParseError(`Empty list item under "${key}"`, num);
|
|
323
324
|
return value;
|
|
324
325
|
}
|
|
325
|
-
function parseBool(s, num) {
|
|
326
|
+
function parseBool(s, key, num) {
|
|
326
327
|
// A quoted scalar is a STRING even if it spells a boolean — as on the server.
|
|
327
328
|
if (!s.quoted) {
|
|
328
329
|
if (YAML_TRUE.test(s.value))
|
|
@@ -330,7 +331,7 @@ function parseBool(s, num) {
|
|
|
330
331
|
if (YAML_FALSE.test(s.value))
|
|
331
332
|
return false;
|
|
332
333
|
}
|
|
333
|
-
throw new ManifestParseError(`"
|
|
334
|
+
throw new ManifestParseError(`"${key}" must be true or false (got "${s.value}")`, num);
|
|
334
335
|
}
|
|
335
336
|
// Strip the optional document markers: one leading "---" and one trailing "..."
|
|
336
337
|
// are no-ops. Anything more is a multi-document stream, which the server
|
|
@@ -382,6 +383,7 @@ export function parseManifestYaml(source) {
|
|
|
382
383
|
saved_queries: [],
|
|
383
384
|
connectors: {},
|
|
384
385
|
llm: false,
|
|
386
|
+
email: false,
|
|
385
387
|
adhoc_sql: [],
|
|
386
388
|
};
|
|
387
389
|
for (const [key, entry] of entries) {
|
|
@@ -393,7 +395,10 @@ export function parseManifestYaml(source) {
|
|
|
393
395
|
doc.run_as = value;
|
|
394
396
|
}
|
|
395
397
|
else if (key === "llm") {
|
|
396
|
-
doc.llm = parseBool(inlineScalar(entry, key), entry.keyLine.num);
|
|
398
|
+
doc.llm = parseBool(inlineScalar(entry, key), key, entry.keyLine.num);
|
|
399
|
+
}
|
|
400
|
+
else if (key === "email") {
|
|
401
|
+
doc.email = parseBool(inlineScalar(entry, key), key, entry.keyLine.num);
|
|
397
402
|
}
|
|
398
403
|
else if (key === "connectors") {
|
|
399
404
|
doc.connectors = parseConnectors(entry);
|
|
@@ -644,6 +649,8 @@ export function documentOperations(doc) {
|
|
|
644
649
|
}
|
|
645
650
|
if (doc.llm)
|
|
646
651
|
ops.push({ type: "llm", id: "*", label: "LLM access" });
|
|
652
|
+
if (doc.email)
|
|
653
|
+
ops.push({ type: "email", id: "*", label: "email sending" });
|
|
647
654
|
for (const name of doc.adhoc_sql) {
|
|
648
655
|
ops.push({ type: "connector", id: name, label: `ad-hoc SQL on "${name}"` });
|
|
649
656
|
}
|
package/package.json
CHANGED
package/static/sdk.js
CHANGED
|
@@ -175,8 +175,8 @@
|
|
|
175
175
|
}
|
|
176
176
|
return shorten(String(res), 48);
|
|
177
177
|
}
|
|
178
|
-
function track(kind,
|
|
179
|
-
const entry = { kind, label:
|
|
178
|
+
function track(kind, label3, thunk) {
|
|
179
|
+
const entry = { kind, label: label3, status: "pending", t0: performance.now() };
|
|
180
180
|
Inspector.add(entry);
|
|
181
181
|
return thunk().then(
|
|
182
182
|
(res) => {
|
|
@@ -195,8 +195,8 @@
|
|
|
195
195
|
}
|
|
196
196
|
);
|
|
197
197
|
}
|
|
198
|
-
function trackStream(kind,
|
|
199
|
-
const entry = { kind, label:
|
|
198
|
+
function trackStream(kind, label3, streamFactory) {
|
|
199
|
+
const entry = { kind, label: label3, status: "pending", t0: performance.now() };
|
|
200
200
|
Inspector.add(entry);
|
|
201
201
|
return async function* () {
|
|
202
202
|
let textLen = 0;
|
|
@@ -234,10 +234,10 @@
|
|
|
234
234
|
};
|
|
235
235
|
var runSQL = (engine, connection, query2, params) => {
|
|
236
236
|
const trimmed = shorten(query2.replace(/\s+/g, " ").trim(), QUERY_LABEL_MAX);
|
|
237
|
-
const
|
|
237
|
+
const label3 = `${engine ?? "data"}(${q(connection)}).runSQL(${q(trimmed)}${params && params.length ? `, ${shorten(JSON.stringify(params), PARAMS_LABEL_MAX)}` : ""})`;
|
|
238
238
|
return track(
|
|
239
239
|
"sql",
|
|
240
|
-
|
|
240
|
+
label3,
|
|
241
241
|
() => call("POST", "/sql", {
|
|
242
242
|
// engine is advisory; omit it for the generic data() namespace so the backend
|
|
243
243
|
// dispatches on the connection's stored kind. postgres() pins engine="postgres".
|
|
@@ -387,6 +387,32 @@
|
|
|
387
387
|
return resp.markdown;
|
|
388
388
|
});
|
|
389
389
|
|
|
390
|
+
// ../sdk/src/email.ts
|
|
391
|
+
function label(opts) {
|
|
392
|
+
const to = Array.isArray(opts.to) ? `${opts.to.length} recipients` : opts.to;
|
|
393
|
+
return `email.send(${q(shorten(`${to}: ${opts.subject}`, 70))})`;
|
|
394
|
+
}
|
|
395
|
+
function body(opts) {
|
|
396
|
+
const out = {
|
|
397
|
+
to: opts.to,
|
|
398
|
+
subject: opts.subject
|
|
399
|
+
};
|
|
400
|
+
if (opts.html !== void 0) out.html = opts.html;
|
|
401
|
+
if (opts.text !== void 0) out.text = opts.text;
|
|
402
|
+
if (opts.cc !== void 0) out.cc = opts.cc;
|
|
403
|
+
if (opts.bcc !== void 0) out.bcc = opts.bcc;
|
|
404
|
+
if (opts.replyTo !== void 0) out.replyTo = opts.replyTo;
|
|
405
|
+
return out;
|
|
406
|
+
}
|
|
407
|
+
function send2(opts) {
|
|
408
|
+
return track(
|
|
409
|
+
"email",
|
|
410
|
+
label(opts),
|
|
411
|
+
() => call("POST", "/email/send", { body: body(opts) })
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
var email = { send: send2 };
|
|
415
|
+
|
|
390
416
|
// ../sdk/src/files.ts
|
|
391
417
|
function contentTypeOf(data2, explicit) {
|
|
392
418
|
if (explicit) return explicit;
|
|
@@ -431,12 +457,12 @@
|
|
|
431
457
|
var appUsers = () => track("app-users", "appUsers()", () => call("GET", "/app-users"));
|
|
432
458
|
|
|
433
459
|
// ../sdk/src/llm.ts
|
|
434
|
-
function
|
|
460
|
+
function label2(method, input, opts = {}) {
|
|
435
461
|
const preview = typeof input === "string" ? shorten(input.replace(/\s+/g, " ").trim(), 70) : `${input.length} message${input.length === 1 ? "" : "s"}`;
|
|
436
462
|
const output = opts.output?.type === "json" ? " \u2192 json" : "";
|
|
437
463
|
return `llm.${method}(${q(preview)})${output}`;
|
|
438
464
|
}
|
|
439
|
-
function
|
|
465
|
+
function body2(input, opts = {}) {
|
|
440
466
|
const out = Array.isArray(input) ? { messages: input } : { input };
|
|
441
467
|
if (opts.model !== void 0) out.model = opts.model;
|
|
442
468
|
if (opts.provider !== void 0) out.provider = opts.provider;
|
|
@@ -452,7 +478,7 @@
|
|
|
452
478
|
method: "POST",
|
|
453
479
|
credentials: "include",
|
|
454
480
|
headers: { "Content-Type": "application/json" },
|
|
455
|
-
body: JSON.stringify(
|
|
481
|
+
body: JSON.stringify(body2(input, opts))
|
|
456
482
|
});
|
|
457
483
|
if (!resp.ok) throw new ApiError(resp.status, await resp.text());
|
|
458
484
|
return await resp.json();
|
|
@@ -465,7 +491,7 @@
|
|
|
465
491
|
method: "POST",
|
|
466
492
|
credentials: "include",
|
|
467
493
|
headers: { "Content-Type": "application/json" },
|
|
468
|
-
body: JSON.stringify(
|
|
494
|
+
body: JSON.stringify(body2(input, opts))
|
|
469
495
|
});
|
|
470
496
|
if (!resp.ok) throw new ApiError(resp.status, await resp.text());
|
|
471
497
|
if (!resp.body) throw new Error("LLM stream response has no body.");
|
|
@@ -493,10 +519,10 @@
|
|
|
493
519
|
}
|
|
494
520
|
}
|
|
495
521
|
function generate(input, opts = {}) {
|
|
496
|
-
return track("llm",
|
|
522
|
+
return track("llm", label2("generate", input, opts), () => doGenerate(input, opts));
|
|
497
523
|
}
|
|
498
524
|
function stream(input, opts = {}) {
|
|
499
|
-
return trackStream("llm",
|
|
525
|
+
return trackStream("llm", label2("stream", input, opts), () => doStream(input, opts));
|
|
500
526
|
}
|
|
501
527
|
var llm = { generate, stream };
|
|
502
528
|
var llmProviders = () => track("llm", "llmProviders()", () => call("GET", "/llm/providers"));
|
|
@@ -504,10 +530,10 @@
|
|
|
504
530
|
// ../sdk/src/queries.ts
|
|
505
531
|
var PARAMS_LABEL_MAX2 = 40;
|
|
506
532
|
var query = (name, params) => {
|
|
507
|
-
const
|
|
533
|
+
const label3 = `query(${q(name)}${params && Object.keys(params).length ? `, ${shorten(JSON.stringify(params), PARAMS_LABEL_MAX2)}` : ""})`;
|
|
508
534
|
return track(
|
|
509
535
|
"sql",
|
|
510
|
-
|
|
536
|
+
label3,
|
|
511
537
|
() => call("POST", `/queries/${encPath(name)}`, {
|
|
512
538
|
body: { params: params || {} }
|
|
513
539
|
}).then(toSqlRows)
|
|
@@ -518,22 +544,22 @@
|
|
|
518
544
|
// ../sdk/src/service-connectors.ts
|
|
519
545
|
var PATH_LABEL_MAX = 60;
|
|
520
546
|
var toResponse = (env) => {
|
|
521
|
-
const
|
|
547
|
+
const body3 = env.body ?? "";
|
|
522
548
|
return {
|
|
523
549
|
status: env.status,
|
|
524
550
|
ok: env.ok,
|
|
525
551
|
headers: env.headers || {},
|
|
526
552
|
truncated: Boolean(env.truncated),
|
|
527
|
-
text: () => Promise.resolve(
|
|
528
|
-
json: () => Promise.resolve(JSON.parse(
|
|
553
|
+
text: () => Promise.resolve(body3),
|
|
554
|
+
json: () => Promise.resolve(JSON.parse(body3))
|
|
529
555
|
};
|
|
530
556
|
};
|
|
531
557
|
var request = (name, path, opts) => {
|
|
532
558
|
const method = (opts?.method || "GET").toUpperCase();
|
|
533
|
-
const
|
|
559
|
+
const label3 = `connector(${q(name)}).fetch(${q(method)} ${q(shorten(path, PATH_LABEL_MAX))})`;
|
|
534
560
|
return track(
|
|
535
561
|
"connector",
|
|
536
|
-
|
|
562
|
+
label3,
|
|
537
563
|
() => call("POST", "/service-connectors/request", {
|
|
538
564
|
body: { connector: name, method, path, body: opts?.body }
|
|
539
565
|
}).then(toResponse)
|
|
@@ -555,6 +581,7 @@
|
|
|
555
581
|
target.me = me;
|
|
556
582
|
target.appUsers = appUsers;
|
|
557
583
|
target.designSystem = designSystem;
|
|
584
|
+
target.email = email;
|
|
558
585
|
target.llm = llm;
|
|
559
586
|
target.llmProviders = llmProviders;
|
|
560
587
|
target.data = data;
|