@talgent/computer 0.1.2 → 0.2.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/dist/computer.mjs +250 -137
- package/package.json +1 -1
package/dist/computer.mjs
CHANGED
|
@@ -4076,7 +4076,7 @@ function num(value) {
|
|
|
4076
4076
|
|
|
4077
4077
|
// src/run.ts
|
|
4078
4078
|
import { homedir as homedir2 } from "node:os";
|
|
4079
|
-
import { join as
|
|
4079
|
+
import { join as join15 } from "node:path";
|
|
4080
4080
|
|
|
4081
4081
|
// src/claude-code.ts
|
|
4082
4082
|
import { execFile } from "node:child_process";
|
|
@@ -5566,9 +5566,10 @@ var HEADER_VALUE = /^[\x21-\x7e]+$/;
|
|
|
5566
5566
|
var LOOPBACK = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "[::1]"]);
|
|
5567
5567
|
function parseCredentials(body) {
|
|
5568
5568
|
if (!isObject(body)) throw new InvalidCredentialsError("body must be an object");
|
|
5569
|
-
const { model, gateway } = body;
|
|
5569
|
+
const { model, gateway, api } = body;
|
|
5570
5570
|
const result = { gateway: parseGateway(gateway) };
|
|
5571
5571
|
if (model !== void 0 && model !== null) result.model = parseModel(model);
|
|
5572
|
+
if (api !== void 0 && api !== null) result.api = parseApi(api, result.gateway.url);
|
|
5572
5573
|
return result;
|
|
5573
5574
|
}
|
|
5574
5575
|
function parseModel(model) {
|
|
@@ -5579,6 +5580,12 @@ function parseModel(model) {
|
|
|
5579
5580
|
}
|
|
5580
5581
|
return { baseUrl: url.href, key: headerValue(model.key, "model.key") };
|
|
5581
5582
|
}
|
|
5583
|
+
function parseApi(api, gatewayUrl) {
|
|
5584
|
+
if (!isObject(api)) throw new InvalidCredentialsError("api must be an object");
|
|
5585
|
+
const url = parseUrl(api.url, "api.url");
|
|
5586
|
+
if (url.origin !== new URL(gatewayUrl).origin) throw new InvalidCredentialsError("api.url must lie on gateway.url's origin");
|
|
5587
|
+
return { url: url.href };
|
|
5588
|
+
}
|
|
5582
5589
|
function parseGateway(gateway) {
|
|
5583
5590
|
if (!isObject(gateway)) throw new InvalidCredentialsError("gateway must be an object");
|
|
5584
5591
|
const url = parseUrl(gateway.url, "gateway.url");
|
|
@@ -5924,12 +5931,12 @@ var Works = class {
|
|
|
5924
5931
|
}
|
|
5925
5932
|
// Opens or updates the Work's environment and answers its root.
|
|
5926
5933
|
async put(workId, body) {
|
|
5927
|
-
const { accessToken, environment, modelProxy, telemetry } = parseEnvironment(body);
|
|
5934
|
+
const { accessToken, environment, modelProxy, platformApi, telemetry } = parseEnvironment(body);
|
|
5928
5935
|
const root = this.root(workId);
|
|
5929
5936
|
await mkdir6(root, { recursive: true, mode: 448 });
|
|
5930
5937
|
await chmod5(root, 448);
|
|
5931
5938
|
await mkdir6(this.recordsDir, { recursive: true, mode: 448 });
|
|
5932
|
-
const stored = { tokenSha256: sha2562(accessToken), environment, modelProxy, telemetry };
|
|
5939
|
+
const stored = { tokenSha256: sha2562(accessToken), environment, modelProxy, platformApi, telemetry };
|
|
5933
5940
|
writeJsonAtomic(join9(this.recordsDir, `${workId}.json`), stored);
|
|
5934
5941
|
this.cache.set(workId, stored);
|
|
5935
5942
|
return root;
|
|
@@ -5968,7 +5975,7 @@ var Works = class {
|
|
|
5968
5975
|
const expected = Buffer.from(stored.tokenSha256, "hex");
|
|
5969
5976
|
const presented = Buffer.from(sha2562(token), "hex");
|
|
5970
5977
|
if (token === "" || !timingSafeEqual(expected, presented)) return { kind: "denied" };
|
|
5971
|
-
const work = { root: this.root(workId), environment: stored.environment, modelProxy: stored.modelProxy === true };
|
|
5978
|
+
const work = { root: this.root(workId), environment: stored.environment, modelProxy: stored.modelProxy, platformApi: stored.platformApi === true };
|
|
5972
5979
|
if (stored.telemetry !== void 0) work.telemetry = stored.telemetry;
|
|
5973
5980
|
return { kind: "ok", work };
|
|
5974
5981
|
}
|
|
@@ -5989,9 +5996,10 @@ var Works = class {
|
|
|
5989
5996
|
};
|
|
5990
5997
|
function parseEnvironment(body) {
|
|
5991
5998
|
if (typeof body !== "object" || body === null || Array.isArray(body)) throw new InvalidEnvironmentError("body must be an object");
|
|
5992
|
-
const { access_token: accessToken, environment = {}, model_proxy: modelProxy = false, telemetry: rawTelemetry } = body;
|
|
5999
|
+
const { access_token: accessToken, environment = {}, model_proxy: modelProxy = false, platform_api: platformApi = false, telemetry: rawTelemetry } = body;
|
|
5993
6000
|
if (typeof accessToken !== "string" || accessToken.trim() === "") throw new InvalidEnvironmentError("access_token required");
|
|
5994
6001
|
if (typeof modelProxy !== "boolean") throw new InvalidEnvironmentError("model_proxy must be a boolean");
|
|
6002
|
+
if (typeof platformApi !== "boolean") throw new InvalidEnvironmentError("platform_api must be a boolean");
|
|
5995
6003
|
if (typeof environment !== "object" || environment === null || Array.isArray(environment)) {
|
|
5996
6004
|
throw new InvalidEnvironmentError("environment must be an object");
|
|
5997
6005
|
}
|
|
@@ -6009,7 +6017,7 @@ function parseEnvironment(body) {
|
|
|
6009
6017
|
if (error instanceof InvalidTelemetryError) throw new InvalidEnvironmentError(error.message);
|
|
6010
6018
|
throw error;
|
|
6011
6019
|
}
|
|
6012
|
-
return { accessToken, environment: result, modelProxy, telemetry };
|
|
6020
|
+
return { accessToken, environment: result, modelProxy, platformApi, telemetry };
|
|
6013
6021
|
}
|
|
6014
6022
|
async function removeTree(path) {
|
|
6015
6023
|
try {
|
|
@@ -6487,8 +6495,10 @@ var ExecdService = class {
|
|
|
6487
6495
|
}
|
|
6488
6496
|
// The machine's environment, then the Work's, then what Talgent Computer
|
|
6489
6497
|
// owns: the machine's HOME, the Work root, the bundled Claude Code first on
|
|
6490
|
-
// PATH, the run's way to the local proxy and, when the platform named
|
|
6491
|
-
//
|
|
6498
|
+
// PATH, the run's way to the local proxy and, when the platform named them,
|
|
6499
|
+
// the platform's native API through the proxy and where its telemetry goes,
|
|
6500
|
+
// in place of the machine's. The API address carries the run's token in
|
|
6501
|
+
// its path, as a plain HTTP client presents nothing else. A Work on a Model
|
|
6492
6502
|
// Configuration reaches its model only through the proxy, with the run's
|
|
6493
6503
|
// token, whether or not its credentials were delivered yet: it never falls
|
|
6494
6504
|
// back to the machine's own login.
|
|
@@ -6496,12 +6506,14 @@ var ExecdService = class {
|
|
|
6496
6506
|
const { baseEnv, home, claudeBinDir: bin, proxyOrigin, platform = process.platform } = this.options;
|
|
6497
6507
|
const proxyUrl = `${proxyOrigin()}/w/${workId}`;
|
|
6498
6508
|
const model = work.modelProxy ? { ANTHROPIC_BASE_URL: `${proxyUrl}/model`, ANTHROPIC_AUTH_TOKEN: proxyToken } : {};
|
|
6509
|
+
const api = work.platformApi ? { TALGENT_API_ADDRESS: `${proxyUrl}/api/${proxyToken}` } : {};
|
|
6499
6510
|
const env = mergeEnvironment(platform, baseEnv, work.environment, {
|
|
6500
6511
|
HOME: home,
|
|
6501
6512
|
TALGENT_WORK_ROOT: work.root,
|
|
6502
6513
|
TALGENT_PROXY_URL: proxyUrl,
|
|
6503
6514
|
TALGENT_PROXY_TOKEN: proxyToken,
|
|
6504
|
-
...model
|
|
6515
|
+
...model,
|
|
6516
|
+
...api
|
|
6505
6517
|
});
|
|
6506
6518
|
if (bin !== void 0) prependPath(env, bin, platform);
|
|
6507
6519
|
if (work.modelProxy) deleteVariable(env, "ANTHROPIC_API_KEY", platform);
|
|
@@ -6862,42 +6874,7 @@ var ContentHandles = class {
|
|
|
6862
6874
|
// src/proxy/content-links.ts
|
|
6863
6875
|
import { Transform, pipeline as pipeline2 } from "node:stream";
|
|
6864
6876
|
|
|
6865
|
-
// src/proxy/
|
|
6866
|
-
var MAX_SOURCE_BYTES = 64 * 1024 * 1024;
|
|
6867
|
-
var MAX_REQUEST_BYTES = base64Length(MAX_SOURCE_BYTES) + 1024 * 1024;
|
|
6868
|
-
var TOOLS = /* @__PURE__ */ new Set(["publish_artifact", "replace_artifact"]);
|
|
6869
|
-
var INPUTS_DIR = "inputs";
|
|
6870
|
-
var completions = new Limit(2);
|
|
6871
|
-
var UNHELD_REQUEST_BYTES = 1024 * 1024;
|
|
6872
|
-
var INVALID_PARAMS = -32602;
|
|
6873
|
-
var INVALID_REQUEST = -32600;
|
|
6874
|
-
var UNREADABLE = "source_path names no readable file in the Work's workspace.";
|
|
6875
|
-
var TOO_LARGE = "The source file is larger than 64 MiB. Split it or compress it into a smaller archive.";
|
|
6876
|
-
var FROM_INPUTS = "source_path is under inputs/, the Work's read-only inputs. Publish a file outside inputs/.";
|
|
6877
|
-
var OVER_REQUEST = "The files published in this request exceed its size limit. Publish them in separate requests.";
|
|
6878
|
-
var NOT_FORWARDED = "Not forwarded: a publication in this batch was refused.";
|
|
6879
|
-
async function completeRequest(request, root) {
|
|
6880
|
-
const encoding = request.headers["content-encoding"];
|
|
6881
|
-
if (encoding !== void 0 && encoding !== "identity") return { kind: "pass", body: { chunks: [], rest: request } };
|
|
6882
|
-
const head = await readHead(request, UNHELD_REQUEST_BYTES);
|
|
6883
|
-
if (head.rest === void 0) {
|
|
6884
|
-
const body = Buffer.concat(head.chunks);
|
|
6885
|
-
const calls = publicationCalls(body);
|
|
6886
|
-
if (calls === void 0) return { kind: "pass", body: head };
|
|
6887
|
-
return completions.run(() => complete(calls, body, root));
|
|
6888
|
-
}
|
|
6889
|
-
const rest = head.rest;
|
|
6890
|
-
return completions.run(async () => {
|
|
6891
|
-
const read = head.chunks.reduce((size, chunk) => size + chunk.length, 0);
|
|
6892
|
-
const tail = await readHead(rest, MAX_REQUEST_BYTES - read);
|
|
6893
|
-
const chunks = [...head.chunks, ...tail.chunks];
|
|
6894
|
-
if (tail.rest !== void 0) return { kind: "pass", body: { chunks, rest: tail.rest } };
|
|
6895
|
-
const body = Buffer.concat(chunks);
|
|
6896
|
-
const calls = publicationCalls(body);
|
|
6897
|
-
if (calls === void 0) return { kind: "pass", body: { chunks: [body] } };
|
|
6898
|
-
return complete(calls, body, root);
|
|
6899
|
-
});
|
|
6900
|
-
}
|
|
6877
|
+
// src/proxy/request-body.ts
|
|
6901
6878
|
var BODY_IDLE_MS = 3e4;
|
|
6902
6879
|
function readHead(stream, limit, idleMs = BODY_IDLE_MS) {
|
|
6903
6880
|
return new Promise((resolve3, reject) => {
|
|
@@ -6948,71 +6925,6 @@ function readHead(stream, limit, idleMs = BODY_IDLE_MS) {
|
|
|
6948
6925
|
stream.resume();
|
|
6949
6926
|
});
|
|
6950
6927
|
}
|
|
6951
|
-
function publicationCalls(body) {
|
|
6952
|
-
let parsed;
|
|
6953
|
-
try {
|
|
6954
|
-
parsed = JSON.parse(body.toString("utf8"));
|
|
6955
|
-
} catch {
|
|
6956
|
-
return void 0;
|
|
6957
|
-
}
|
|
6958
|
-
const batch = Array.isArray(parsed);
|
|
6959
|
-
const messages = batch ? parsed : [parsed];
|
|
6960
|
-
const calls = messages.flatMap((message2) => {
|
|
6961
|
-
const args = argsOf(message2);
|
|
6962
|
-
return args === void 0 ? [] : [{ message: message2, args }];
|
|
6963
|
-
});
|
|
6964
|
-
return calls.length === 0 ? void 0 : { parsed, batch, messages, calls };
|
|
6965
|
-
}
|
|
6966
|
-
async function complete({ parsed, batch, messages, calls }, body, root) {
|
|
6967
|
-
let size = body.length;
|
|
6968
|
-
for (const { message: message2, args } of calls) {
|
|
6969
|
-
delete args.source_content;
|
|
6970
|
-
if (typeof args.source_path !== "string") continue;
|
|
6971
|
-
const source = await readSource(root, args.source_path, MAX_REQUEST_BYTES - size);
|
|
6972
|
-
if ("refusal" in source) return { kind: "answer", body: refusal(messages, batch, message2, source.refusal) };
|
|
6973
|
-
const content = source.content.toString("base64");
|
|
6974
|
-
args.source_content = content;
|
|
6975
|
-
size += content.length;
|
|
6976
|
-
}
|
|
6977
|
-
return { kind: "forward", body: Buffer.from(JSON.stringify(parsed)) };
|
|
6978
|
-
}
|
|
6979
|
-
function argsOf(message2) {
|
|
6980
|
-
if (!isObject(message2) || message2.method !== "tools/call" || !isObject(message2.params)) return void 0;
|
|
6981
|
-
const { name, arguments: args } = message2.params;
|
|
6982
|
-
return typeof name === "string" && TOOLS.has(name) && isObject(args) ? args : void 0;
|
|
6983
|
-
}
|
|
6984
|
-
async function readSource(root, sourcePath, budget) {
|
|
6985
|
-
if (sourcePath === "") return { refusal: UNREADABLE };
|
|
6986
|
-
const file = await openWorkFile(root, workspaceRelative(sourcePath) ?? sourcePath).catch(() => ({ status: 500 }));
|
|
6987
|
-
if ("status" in file) return { refusal: UNREADABLE };
|
|
6988
|
-
try {
|
|
6989
|
-
const inputs = await confined(root, INPUTS_DIR);
|
|
6990
|
-
if (inputs !== void 0 && within(inputs, file.path)) return { refusal: FROM_INPUTS };
|
|
6991
|
-
if (file.size > MAX_SOURCE_BYTES) return { refusal: TOO_LARGE };
|
|
6992
|
-
if (base64Length(file.size) > budget) return { refusal: OVER_REQUEST };
|
|
6993
|
-
const content = await readBounded(file.handle, MAX_SOURCE_BYTES);
|
|
6994
|
-
if (content === void 0) return { refusal: TOO_LARGE };
|
|
6995
|
-
if (base64Length(content.length) > budget) return { refusal: OVER_REQUEST };
|
|
6996
|
-
return { content };
|
|
6997
|
-
} catch {
|
|
6998
|
-
return { refusal: UNREADABLE };
|
|
6999
|
-
} finally {
|
|
7000
|
-
await file.handle.close().catch(() => {
|
|
7001
|
-
});
|
|
7002
|
-
}
|
|
7003
|
-
}
|
|
7004
|
-
function refusal(messages, batch, refused, reason2) {
|
|
7005
|
-
const answers = messages.flatMap((message2) => {
|
|
7006
|
-
if (!isObject(message2) || typeof message2.method !== "string" || !("id" in message2)) return [];
|
|
7007
|
-
const error = message2 === refused ? { code: INVALID_PARAMS, message: reason2 } : { code: INVALID_REQUEST, message: NOT_FORWARDED };
|
|
7008
|
-
return [{ jsonrpc: "2.0", id: message2.id, error }];
|
|
7009
|
-
});
|
|
7010
|
-
if (answers.length === 0) return void 0;
|
|
7011
|
-
return Buffer.from(JSON.stringify(batch ? answers : answers[0]));
|
|
7012
|
-
}
|
|
7013
|
-
function base64Length(bytes) {
|
|
7014
|
-
return 4 * Math.ceil(bytes / 3);
|
|
7015
|
-
}
|
|
7016
6928
|
|
|
7017
6929
|
// src/proxy/content-links.ts
|
|
7018
6930
|
var CONTENT_PATH = "/computer/v1/storage/content";
|
|
@@ -7094,9 +7006,170 @@ function escapeRegExp2(text2) {
|
|
|
7094
7006
|
return text2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
7095
7007
|
}
|
|
7096
7008
|
|
|
7009
|
+
// src/proxy/publication-source.ts
|
|
7010
|
+
import { lstat as lstat2, realpath as realpath3 } from "node:fs/promises";
|
|
7011
|
+
import { isAbsolute as isAbsolute2, join as join11, normalize, relative } from "node:path";
|
|
7012
|
+
var MAX_SOURCE_BYTES = 64 * 1024 * 1024;
|
|
7013
|
+
var MAX_REQUEST_BYTES = base64Length(MAX_SOURCE_BYTES) + 1024 * 1024;
|
|
7014
|
+
var UNHELD_REQUEST_BYTES = 1024 * 1024;
|
|
7015
|
+
var RESERVED_DIRS = ["inputs", "knowledge"];
|
|
7016
|
+
var completions = new Limit(2);
|
|
7017
|
+
function holdingCompletion(task) {
|
|
7018
|
+
return completions.run(task);
|
|
7019
|
+
}
|
|
7020
|
+
var noFile = (field) => `${field} names no file in the Work's workspace.`;
|
|
7021
|
+
var notOneFile = (field) => `${field} is not one regular file in the Work's workspace, reached without symbolic links.`;
|
|
7022
|
+
var TOO_LARGE = "The source file is larger than 64 MiB. Split it or compress it into a smaller archive.";
|
|
7023
|
+
var reserved = (field, dir) => `${field} is under ${dir}/, which the Work only reads. Publish a file outside inputs/ and knowledge/.`;
|
|
7024
|
+
var OVER_REQUEST = "The files published in this request exceed its size limit. Publish them in separate requests.";
|
|
7025
|
+
async function readSource(root, sourcePath, field, budget) {
|
|
7026
|
+
const invalid = (refusal2) => ({ refusal: refusal2, missing: false });
|
|
7027
|
+
if (sourcePath === "" || sourcePath.includes("\0")) return invalid(notOneFile(field));
|
|
7028
|
+
let rootReal;
|
|
7029
|
+
try {
|
|
7030
|
+
rootReal = await realpath3(root);
|
|
7031
|
+
} catch {
|
|
7032
|
+
return invalid(notOneFile(field));
|
|
7033
|
+
}
|
|
7034
|
+
const target = workspaceRelative(sourcePath) ?? sourcePath;
|
|
7035
|
+
const named = isAbsolute2(target) ? normalize(target) : join11(rootReal, target);
|
|
7036
|
+
const lexical = within(root, named) ? join11(rootReal, relative(root, named)) : named;
|
|
7037
|
+
if (!within(rootReal, lexical) || lexical === rootReal) return invalid(notOneFile(field));
|
|
7038
|
+
const file = await openWorkFile(root, lexical).catch(() => ({ status: 500 }));
|
|
7039
|
+
if ("status" in file) {
|
|
7040
|
+
const missing = file.status === 404 && await absent(lexical);
|
|
7041
|
+
return missing ? { refusal: noFile(field), missing: true } : invalid(notOneFile(field));
|
|
7042
|
+
}
|
|
7043
|
+
try {
|
|
7044
|
+
if (file.path !== lexical) return invalid(notOneFile(field));
|
|
7045
|
+
for (const dir of RESERVED_DIRS) {
|
|
7046
|
+
if (within(join11(rootReal, dir), file.path)) return invalid(reserved(field, dir));
|
|
7047
|
+
}
|
|
7048
|
+
if (file.size > MAX_SOURCE_BYTES) return invalid(TOO_LARGE);
|
|
7049
|
+
if (base64Length(file.size) > budget) return invalid(OVER_REQUEST);
|
|
7050
|
+
const content = await readBounded(file.handle, MAX_SOURCE_BYTES);
|
|
7051
|
+
if (content === void 0) return invalid(TOO_LARGE);
|
|
7052
|
+
if (base64Length(content.length) > budget) return invalid(OVER_REQUEST);
|
|
7053
|
+
return { content };
|
|
7054
|
+
} catch {
|
|
7055
|
+
return invalid(notOneFile(field));
|
|
7056
|
+
} finally {
|
|
7057
|
+
await file.handle.close().catch(() => {
|
|
7058
|
+
});
|
|
7059
|
+
}
|
|
7060
|
+
}
|
|
7061
|
+
async function absent(path) {
|
|
7062
|
+
try {
|
|
7063
|
+
await lstat2(path);
|
|
7064
|
+
return false;
|
|
7065
|
+
} catch {
|
|
7066
|
+
return true;
|
|
7067
|
+
}
|
|
7068
|
+
}
|
|
7069
|
+
function base64Length(bytes) {
|
|
7070
|
+
return 4 * Math.ceil(bytes / 3);
|
|
7071
|
+
}
|
|
7072
|
+
|
|
7073
|
+
// src/proxy/publication.ts
|
|
7074
|
+
var TOOLS = /* @__PURE__ */ new Set(["publish_artifact", "replace_artifact"]);
|
|
7075
|
+
var INVALID_PARAMS = -32602;
|
|
7076
|
+
var INVALID_REQUEST = -32600;
|
|
7077
|
+
var NOT_FORWARDED = "Not forwarded: a publication in this batch was refused.";
|
|
7078
|
+
async function completeRequest(request, root) {
|
|
7079
|
+
const encoding = request.headers["content-encoding"];
|
|
7080
|
+
if (encoding !== void 0 && encoding !== "identity") return { kind: "pass", body: { chunks: [], rest: request } };
|
|
7081
|
+
const head = await readHead(request, UNHELD_REQUEST_BYTES);
|
|
7082
|
+
if (head.rest === void 0) {
|
|
7083
|
+
const body = Buffer.concat(head.chunks);
|
|
7084
|
+
const calls = publicationCalls(body);
|
|
7085
|
+
if (calls === void 0) return { kind: "pass", body: head };
|
|
7086
|
+
return holdingCompletion(() => complete(calls, body, root));
|
|
7087
|
+
}
|
|
7088
|
+
const rest = head.rest;
|
|
7089
|
+
return holdingCompletion(async () => {
|
|
7090
|
+
const read = head.chunks.reduce((size, chunk) => size + chunk.length, 0);
|
|
7091
|
+
const tail = await readHead(rest, MAX_REQUEST_BYTES - read);
|
|
7092
|
+
const chunks = [...head.chunks, ...tail.chunks];
|
|
7093
|
+
if (tail.rest !== void 0) return { kind: "pass", body: { chunks, rest: tail.rest } };
|
|
7094
|
+
const body = Buffer.concat(chunks);
|
|
7095
|
+
const calls = publicationCalls(body);
|
|
7096
|
+
if (calls === void 0) return { kind: "pass", body: { chunks: [body] } };
|
|
7097
|
+
return complete(calls, body, root);
|
|
7098
|
+
});
|
|
7099
|
+
}
|
|
7100
|
+
function publicationCalls(body) {
|
|
7101
|
+
let parsed;
|
|
7102
|
+
try {
|
|
7103
|
+
parsed = JSON.parse(body.toString("utf8"));
|
|
7104
|
+
} catch {
|
|
7105
|
+
return void 0;
|
|
7106
|
+
}
|
|
7107
|
+
const batch = Array.isArray(parsed);
|
|
7108
|
+
const messages = batch ? parsed : [parsed];
|
|
7109
|
+
const calls = messages.flatMap((message2) => {
|
|
7110
|
+
const args = argsOf(message2);
|
|
7111
|
+
return args === void 0 ? [] : [{ message: message2, args }];
|
|
7112
|
+
});
|
|
7113
|
+
return calls.length === 0 ? void 0 : { parsed, batch, messages, calls };
|
|
7114
|
+
}
|
|
7115
|
+
async function complete({ parsed, batch, messages, calls }, body, root) {
|
|
7116
|
+
let size = body.length;
|
|
7117
|
+
for (const { message: message2, args } of calls) {
|
|
7118
|
+
delete args.source_content;
|
|
7119
|
+
if (typeof args.source_path !== "string") continue;
|
|
7120
|
+
const source = await readSource(root, args.source_path, "source_path", MAX_REQUEST_BYTES - size);
|
|
7121
|
+
if ("refusal" in source) return { kind: "answer", body: refusal(messages, batch, message2, source.refusal) };
|
|
7122
|
+
const content = source.content.toString("base64");
|
|
7123
|
+
args.source_content = content;
|
|
7124
|
+
size += content.length;
|
|
7125
|
+
}
|
|
7126
|
+
return { kind: "forward", body: Buffer.from(JSON.stringify(parsed)) };
|
|
7127
|
+
}
|
|
7128
|
+
function argsOf(message2) {
|
|
7129
|
+
if (!isObject(message2) || message2.method !== "tools/call" || !isObject(message2.params)) return void 0;
|
|
7130
|
+
const { name, arguments: args } = message2.params;
|
|
7131
|
+
return typeof name === "string" && TOOLS.has(name) && isObject(args) ? args : void 0;
|
|
7132
|
+
}
|
|
7133
|
+
function refusal(messages, batch, refused, reason2) {
|
|
7134
|
+
const answers = messages.flatMap((message2) => {
|
|
7135
|
+
if (!isObject(message2) || typeof message2.method !== "string" || !("id" in message2)) return [];
|
|
7136
|
+
const error = message2 === refused ? { code: INVALID_PARAMS, message: reason2 } : { code: INVALID_REQUEST, message: NOT_FORWARDED };
|
|
7137
|
+
return [{ jsonrpc: "2.0", id: message2.id, error }];
|
|
7138
|
+
});
|
|
7139
|
+
if (answers.length === 0) return void 0;
|
|
7140
|
+
return Buffer.from(JSON.stringify(batch ? answers : answers[0]));
|
|
7141
|
+
}
|
|
7142
|
+
|
|
7143
|
+
// src/proxy/api-publication.ts
|
|
7144
|
+
var ARTIFACT_COMMANDS = /* @__PURE__ */ new Set([
|
|
7145
|
+
"/talgent.storage.private.v1.ArtifactService/CreateArtifact",
|
|
7146
|
+
"/talgent.storage.private.v1.ArtifactService/ReplaceArtifact"
|
|
7147
|
+
]);
|
|
7148
|
+
async function completeArtifactCommand(request, root) {
|
|
7149
|
+
const encoding = request.headers["content-encoding"];
|
|
7150
|
+
if (encoding !== void 0 && encoding !== "identity") return { kind: "pass", body: { chunks: [], rest: request } };
|
|
7151
|
+
const head = await readHead(request, UNHELD_REQUEST_BYTES);
|
|
7152
|
+
if (head.rest !== void 0) return { kind: "pass", body: head };
|
|
7153
|
+
const body = Buffer.concat(head.chunks);
|
|
7154
|
+
let message2;
|
|
7155
|
+
try {
|
|
7156
|
+
message2 = JSON.parse(body.toString("utf8"));
|
|
7157
|
+
} catch {
|
|
7158
|
+
return { kind: "pass", body: { chunks: [body] } };
|
|
7159
|
+
}
|
|
7160
|
+
if (!isObject(message2) || typeof message2.path !== "string" || message2.content !== void 0) return { kind: "pass", body: { chunks: [body] } };
|
|
7161
|
+
const { path, ...rest } = message2;
|
|
7162
|
+
return holdingCompletion(async () => {
|
|
7163
|
+
const source = await readSource(root, path, "path", MAX_REQUEST_BYTES - body.length);
|
|
7164
|
+
if ("refusal" in source) return { kind: "refuse", message: source.refusal, missing: source.missing };
|
|
7165
|
+
return { kind: "forward", body: Buffer.from(JSON.stringify({ ...rest, content: source.content.toString("base64") })) };
|
|
7166
|
+
});
|
|
7167
|
+
}
|
|
7168
|
+
|
|
7097
7169
|
// src/proxy/server.ts
|
|
7098
7170
|
var KEYED_MODEL_PATHS = /* @__PURE__ */ new Set(["/v1/messages", "/v1/messages/count_tokens"]);
|
|
7099
|
-
var ROUTE = /^\/w\/([^/]+)\/(model|mcp|content)(\/.*)?$/;
|
|
7171
|
+
var ROUTE = /^\/w\/([^/]+)\/(model|mcp|content|api)(\/.*)?$/;
|
|
7172
|
+
var API_PATH = /^\/([^/]+)(\/.*)$/;
|
|
7100
7173
|
var CONTENT_HANDLE = /^\/([A-Za-z0-9_-]+)$/;
|
|
7101
7174
|
var LocalProxy = class {
|
|
7102
7175
|
portFile;
|
|
@@ -7183,6 +7256,7 @@ var LocalProxy = class {
|
|
|
7183
7256
|
if (match === null || !validId(match[1])) return notFound2(response);
|
|
7184
7257
|
const [, workId, route, rest = ""] = match;
|
|
7185
7258
|
if (route === "content") return this.content(request, response, workId, rest);
|
|
7259
|
+
if (route === "api") return this.api(request, response, workId, rest, search);
|
|
7186
7260
|
if (!await this.access.tokenValid(workId, presentedToken(request.headers))) {
|
|
7187
7261
|
return answerJson(response, 401, { code: "UNAUTHORIZED", message: "present the token of a run of this Work" });
|
|
7188
7262
|
}
|
|
@@ -7223,6 +7297,45 @@ var LocalProxy = class {
|
|
|
7223
7297
|
fail: (reason2) => this.log(`local proxy: Work ${workId} ${route} upstream failed: ${reason2}`)
|
|
7224
7298
|
});
|
|
7225
7299
|
}
|
|
7300
|
+
// A native API call of a Work's plain HTTP client, such as curl, which
|
|
7301
|
+
// presents the run's token as the first segment of the path. The proxy
|
|
7302
|
+
// adds the Work's identity, and for an Artifact command naming a file of
|
|
7303
|
+
// the Work, the file's bytes.
|
|
7304
|
+
async api(request, response, workId, rest, search) {
|
|
7305
|
+
const match = API_PATH.exec(rest);
|
|
7306
|
+
if (match === null) return notFound2(response);
|
|
7307
|
+
const [, token, path] = match;
|
|
7308
|
+
if (!await this.access.tokenValid(workId, token)) {
|
|
7309
|
+
return answerJson(response, 401, { code: "unauthenticated", message: "present the token of a run of this Work" });
|
|
7310
|
+
}
|
|
7311
|
+
const credentials = await this.access.credentials(workId);
|
|
7312
|
+
if (credentials === void 0) {
|
|
7313
|
+
return answerJson(response, 503, { code: "unavailable", message: "the Work's credentials were not delivered" });
|
|
7314
|
+
}
|
|
7315
|
+
if (credentials.api === void 0) return notFound2(response);
|
|
7316
|
+
const target = below(credentials.api.url, path, search);
|
|
7317
|
+
if (target === void 0) return notFound2(response);
|
|
7318
|
+
const headers = requestHeaders(request.headers);
|
|
7319
|
+
addIdentity(headers, credentials.gateway);
|
|
7320
|
+
let body;
|
|
7321
|
+
if (request.method === "POST" && ARTIFACT_COMMANDS.has(path)) {
|
|
7322
|
+
const completed = await completeArtifactCommand(request, this.access.workRoot(workId));
|
|
7323
|
+
if (completed.kind === "refuse") {
|
|
7324
|
+
if (completed.missing) return answerJson(response, 404, { code: "not_found", message: completed.message });
|
|
7325
|
+
return answerJson(response, 400, { code: "invalid_argument", message: completed.message });
|
|
7326
|
+
}
|
|
7327
|
+
if (completed.kind === "pass") body = completed.body;
|
|
7328
|
+
else {
|
|
7329
|
+
headers["content-length"] = String(completed.body.length);
|
|
7330
|
+
body = { chunks: [completed.body] };
|
|
7331
|
+
}
|
|
7332
|
+
}
|
|
7333
|
+
forward(request, response, target, headers, {
|
|
7334
|
+
body,
|
|
7335
|
+
timeouts: this.timeouts,
|
|
7336
|
+
fail: (reason2) => this.log(`local proxy: Work ${workId} api upstream failed: ${reason2}`)
|
|
7337
|
+
});
|
|
7338
|
+
}
|
|
7226
7339
|
// A Storage content link the MCP route rewrote into a local handle: only a
|
|
7227
7340
|
// handle issued to this Work reaches the platform's content endpoint, with
|
|
7228
7341
|
// the query it stands for and the Work's identity that endpoint also asks
|
|
@@ -7358,7 +7471,7 @@ function runOnce(command) {
|
|
|
7358
7471
|
|
|
7359
7472
|
// src/service/launchd.ts
|
|
7360
7473
|
import { mkdir as mkdir10, rm as rm8, writeFile as writeFile3 } from "node:fs/promises";
|
|
7361
|
-
import { join as
|
|
7474
|
+
import { join as join12 } from "node:path";
|
|
7362
7475
|
|
|
7363
7476
|
// src/service/manager.ts
|
|
7364
7477
|
import { execFile as execFile4 } from "node:child_process";
|
|
@@ -7401,13 +7514,13 @@ var LaunchAgentService = class {
|
|
|
7401
7514
|
constructor(run2, home, uid, logFile) {
|
|
7402
7515
|
this.run = run2;
|
|
7403
7516
|
this.logFile = logFile;
|
|
7404
|
-
this.plistFile =
|
|
7517
|
+
this.plistFile = join12(home, "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
|
|
7405
7518
|
this.domain = `gui/${uid}`;
|
|
7406
7519
|
this.target = `${this.domain}/${LAUNCHD_LABEL}`;
|
|
7407
7520
|
}
|
|
7408
7521
|
async install(spec) {
|
|
7409
|
-
await mkdir10(
|
|
7410
|
-
await mkdir10(
|
|
7522
|
+
await mkdir10(join12(this.plistFile, ".."), { recursive: true });
|
|
7523
|
+
await mkdir10(join12(spec.logFile, ".."), { recursive: true });
|
|
7411
7524
|
await writeFile3(this.plistFile, launchAgentPlist(spec), { mode: 420 });
|
|
7412
7525
|
await this.run("launchctl", ["bootout", this.target]);
|
|
7413
7526
|
await mustRun(this.run, "launchctl", ["bootstrap", this.domain, this.plistFile]);
|
|
@@ -7489,7 +7602,7 @@ function xml(value) {
|
|
|
7489
7602
|
|
|
7490
7603
|
// src/service/systemd.ts
|
|
7491
7604
|
import { mkdir as mkdir11, rm as rm9, writeFile as writeFile4 } from "node:fs/promises";
|
|
7492
|
-
import { join as
|
|
7605
|
+
import { join as join13 } from "node:path";
|
|
7493
7606
|
var UNIT = `${SERVICE_NAME}.service`;
|
|
7494
7607
|
var SystemdUserService = class {
|
|
7495
7608
|
startsAt = 'at login. To start it at boot and keep it running after you log out, run "loginctl enable-linger".';
|
|
@@ -7497,10 +7610,10 @@ var SystemdUserService = class {
|
|
|
7497
7610
|
unitFile;
|
|
7498
7611
|
constructor(run2, home) {
|
|
7499
7612
|
this.run = run2;
|
|
7500
|
-
this.unitFile =
|
|
7613
|
+
this.unitFile = join13(home, ".config", "systemd", "user", UNIT);
|
|
7501
7614
|
}
|
|
7502
7615
|
async install(spec) {
|
|
7503
|
-
await mkdir11(
|
|
7616
|
+
await mkdir11(join13(this.unitFile, ".."), { recursive: true });
|
|
7504
7617
|
await writeFile4(this.unitFile, systemdUnit(spec), { mode: 420 });
|
|
7505
7618
|
await this.systemctl("daemon-reload");
|
|
7506
7619
|
await this.systemctl("enable", UNIT);
|
|
@@ -7566,7 +7679,7 @@ function quote(value) {
|
|
|
7566
7679
|
|
|
7567
7680
|
// src/service/task-scheduler.ts
|
|
7568
7681
|
import { mkdir as mkdir12, rm as rm10, writeFile as writeFile5 } from "node:fs/promises";
|
|
7569
|
-
import { join as
|
|
7682
|
+
import { join as join14 } from "node:path";
|
|
7570
7683
|
var TASK_NAME = "Talgent Computer";
|
|
7571
7684
|
var WRAPPER_FILE = "talgent-computer-service.cmd";
|
|
7572
7685
|
var TASK_FILE = "talgent-computer-task.xml";
|
|
@@ -7586,9 +7699,9 @@ var LogonTaskService = class {
|
|
|
7586
7699
|
}
|
|
7587
7700
|
async install(spec) {
|
|
7588
7701
|
await mkdir12(this.serviceDir, { recursive: true });
|
|
7589
|
-
await mkdir12(
|
|
7590
|
-
const wrapper =
|
|
7591
|
-
const definition =
|
|
7702
|
+
await mkdir12(join14(spec.logFile, ".."), { recursive: true });
|
|
7703
|
+
const wrapper = join14(this.serviceDir, WRAPPER_FILE);
|
|
7704
|
+
const definition = join14(this.serviceDir, TASK_FILE);
|
|
7592
7705
|
await writeFile5(wrapper, wrapperScript(spec));
|
|
7593
7706
|
await writeFile5(definition, Buffer.concat([Buffer.from([255, 254]), Buffer.from(logonTask(spec, this.user, wrapper), "utf16le")]));
|
|
7594
7707
|
await this.end();
|
|
@@ -7604,7 +7717,7 @@ var LogonTaskService = class {
|
|
|
7604
7717
|
installed,
|
|
7605
7718
|
running: installed && state === "Running",
|
|
7606
7719
|
lastExit: installed && lastResult !== void 0 && /^\d+$/.test(lastResult) && state !== "Running" ? Number(lastResult) : void 0,
|
|
7607
|
-
supervisesUpgrades: installed && await definesSupervised(
|
|
7720
|
+
supervisesUpgrades: installed && await definesSupervised(join14(this.serviceDir, WRAPPER_FILE)),
|
|
7608
7721
|
definition: `Task Scheduler: ${TASK_NAME}`,
|
|
7609
7722
|
logs: this.logFile
|
|
7610
7723
|
};
|
|
@@ -7615,8 +7728,8 @@ var LogonTaskService = class {
|
|
|
7615
7728
|
await this.end();
|
|
7616
7729
|
await mustRun(this.run, "schtasks", ["/Delete", "/TN", TASK_NAME, "/F"]);
|
|
7617
7730
|
}
|
|
7618
|
-
await rm10(
|
|
7619
|
-
await rm10(
|
|
7731
|
+
await rm10(join14(this.serviceDir, WRAPPER_FILE), { force: true });
|
|
7732
|
+
await rm10(join14(this.serviceDir, TASK_FILE), { force: true });
|
|
7620
7733
|
return installed;
|
|
7621
7734
|
}
|
|
7622
7735
|
// Ending the task ends its console host only: the batch file and the run
|
|
@@ -8314,7 +8427,7 @@ function message(error) {
|
|
|
8314
8427
|
// package.json
|
|
8315
8428
|
var package_default = {
|
|
8316
8429
|
name: "@talgent/computer",
|
|
8317
|
-
version: "0.
|
|
8430
|
+
version: "0.2.0",
|
|
8318
8431
|
description: "Talgent Computer: registers a machine as a Managed Computer and keeps it connected.",
|
|
8319
8432
|
license: "Apache-2.0",
|
|
8320
8433
|
repository: {
|
|
@@ -8373,7 +8486,7 @@ async function runMachine(io, credential, url) {
|
|
|
8373
8486
|
restarting = true;
|
|
8374
8487
|
controller.abort();
|
|
8375
8488
|
});
|
|
8376
|
-
const copiesDir =
|
|
8489
|
+
const copiesDir = join15(dataDir, "claude-code");
|
|
8377
8490
|
const claudeCode = await prepareClaudeCode({ copiesDir }).catch((error) => {
|
|
8378
8491
|
if (error instanceof ClaudeCodeMissingError) return error;
|
|
8379
8492
|
throw error;
|
|
@@ -8416,10 +8529,10 @@ async function runMachine(io, credential, url) {
|
|
|
8416
8529
|
async function connect2(options) {
|
|
8417
8530
|
const { io, log, url, credential, claudeCode, processes, fence, upgrade } = options;
|
|
8418
8531
|
const { dataDir } = io.paths;
|
|
8419
|
-
const works = new Works({ worksDir:
|
|
8420
|
-
const credentials = new CredentialStore({ dir:
|
|
8532
|
+
const works = new Works({ worksDir: join15(dataDir, "works"), recordsDir: join15(dataDir, "state", "works") });
|
|
8533
|
+
const credentials = new CredentialStore({ dir: join15(dataDir, "state", "credentials") });
|
|
8421
8534
|
const proxy = new LocalProxy({
|
|
8422
|
-
portFile:
|
|
8535
|
+
portFile: join15(dataDir, "state", "proxy.json"),
|
|
8423
8536
|
access: {
|
|
8424
8537
|
tokenValid: (workId, token) => processes.proxyTokenValid(workId, token),
|
|
8425
8538
|
credentials: (workId) => credentials.get(workId),
|
|
@@ -8435,7 +8548,7 @@ async function connect2(options) {
|
|
|
8435
8548
|
processes,
|
|
8436
8549
|
claudeBinDir: claudeCode.binDir,
|
|
8437
8550
|
credentials,
|
|
8438
|
-
inputs: new WorkInputs({ recordsDir:
|
|
8551
|
+
inputs: new WorkInputs({ recordsDir: join15(dataDir, "state", "inputs"), credentials, log }),
|
|
8439
8552
|
proxyOrigin: () => proxy.origin(),
|
|
8440
8553
|
baseEnv: process.env,
|
|
8441
8554
|
home: homedir2(),
|
|
@@ -8502,22 +8615,22 @@ function selfUpgrade(dataDir, log, restart) {
|
|
|
8502
8615
|
version: COMPUTER_VERSION,
|
|
8503
8616
|
recordFile: upgradeRecordFile(dataDir),
|
|
8504
8617
|
installer: "unavailable" in installation ? installation : new NpmInstaller(installation),
|
|
8505
|
-
lock: new InstallLock({ file:
|
|
8618
|
+
lock: new InstallLock({ file: join15(dataDir, "state", "upgrade.lock"), staleAfterMs: INSTALL_LOCK_MS, waitMs: INSTALL_LOCK_MS }),
|
|
8506
8619
|
restart,
|
|
8507
8620
|
log
|
|
8508
8621
|
});
|
|
8509
8622
|
}
|
|
8510
8623
|
function runRecordFile(dataDir) {
|
|
8511
|
-
return
|
|
8624
|
+
return join15(dataDir, ...RUN_RECORD_PATH);
|
|
8512
8625
|
}
|
|
8513
8626
|
function upgradeRecordFile(dataDir) {
|
|
8514
|
-
return
|
|
8627
|
+
return join15(dataDir, "state", "upgrade.json");
|
|
8515
8628
|
}
|
|
8516
8629
|
function processTable(dataDir, uses) {
|
|
8517
|
-
return new ProcessTable({ stateRoot:
|
|
8630
|
+
return new ProcessTable({ stateRoot: join15(dataDir, "state", "processes"), shim: [process.execPath, thisProgram(), SHIM_ARG], uses });
|
|
8518
8631
|
}
|
|
8519
8632
|
function newFence(dataDir, processes, log) {
|
|
8520
|
-
return new Fence({ stateFile:
|
|
8633
|
+
return new Fence({ stateFile: join15(dataDir, "state", "fence.json"), processes, log });
|
|
8521
8634
|
}
|
|
8522
8635
|
async function restartWhenAsked(code, io) {
|
|
8523
8636
|
if (code !== EXIT_RESTART || supervised(process.env)) return code;
|