@vibe-lark/larkpal 0.1.63 → 0.1.64
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/bin/larkpal.js +0 -0
- package/dist/main.mjs +224 -150
- package/package.json +16 -15
package/bin/larkpal.js
CHANGED
|
File without changes
|
package/dist/main.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import * as path$2 from "node:path";
|
|
|
6
6
|
import path, { basename, dirname, extname, join, resolve } from "node:path";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
import "dotenv/config";
|
|
9
|
-
import { access, lstat, mkdir, open, readFile, readdir, readlink, rename, rm, stat, symlink, unlink, writeFile } from "node:fs/promises";
|
|
9
|
+
import { access, copyFile, lstat, mkdir, open, readFile, readdir, readlink, rename, rm, stat, symlink, unlink, writeFile } from "node:fs/promises";
|
|
10
10
|
import { homedir, tmpdir } from "node:os";
|
|
11
11
|
import { execFileSync, spawn } from "node:child_process";
|
|
12
12
|
import { createHash, randomUUID } from "node:crypto";
|
|
@@ -18,10 +18,10 @@ import { createCipheriv, createDecipheriv, createHmac, randomBytes } from "crypt
|
|
|
18
18
|
import path$1 from "path";
|
|
19
19
|
import os from "os";
|
|
20
20
|
import { EventEmitter } from "node:events";
|
|
21
|
+
import { pipeline } from "node:stream/promises";
|
|
22
|
+
import { PassThrough, Readable, Transform } from "node:stream";
|
|
21
23
|
import express, { Router } from "express";
|
|
22
24
|
import Busboy from "busboy";
|
|
23
|
-
import { PassThrough, Readable, Transform } from "node:stream";
|
|
24
|
-
import { pipeline } from "node:stream/promises";
|
|
25
25
|
import { EventEmitter as EventEmitter$1 } from "events";
|
|
26
26
|
import cron from "node-cron";
|
|
27
27
|
import * as Lark from "@larksuiteoapi/node-sdk";
|
|
@@ -2619,6 +2619,165 @@ function getLarkpalMcpServerCommand() {
|
|
|
2619
2619
|
};
|
|
2620
2620
|
}
|
|
2621
2621
|
//#endregion
|
|
2622
|
+
//#region src/gateway/artifact-store.ts
|
|
2623
|
+
const DEFAULT_ARTIFACT_TTL_MS = 4320 * 60 * 1e3;
|
|
2624
|
+
const DEFAULT_MAX_UPLOAD_BYTES = 50 * 1024 * 1024;
|
|
2625
|
+
var ArtifactUploadError = class extends Error {
|
|
2626
|
+
constructor(message, statusCode = 400) {
|
|
2627
|
+
super(message);
|
|
2628
|
+
this.statusCode = statusCode;
|
|
2629
|
+
this.name = "ArtifactUploadError";
|
|
2630
|
+
}
|
|
2631
|
+
};
|
|
2632
|
+
var GatewayArtifactStore = class {
|
|
2633
|
+
rootDir;
|
|
2634
|
+
maxUploadBytes;
|
|
2635
|
+
ttlMs;
|
|
2636
|
+
constructor(params) {
|
|
2637
|
+
const workspaceRoot = params?.workspaceRoot || resolveWorkspaceRoot();
|
|
2638
|
+
this.rootDir = join(workspaceRoot, ".artifacts");
|
|
2639
|
+
this.maxUploadBytes = params?.maxUploadBytes ?? readPositiveIntEnv("LARKPAL_ARTIFACT_MAX_UPLOAD_BYTES", DEFAULT_MAX_UPLOAD_BYTES);
|
|
2640
|
+
this.ttlMs = params?.ttlMs ?? readPositiveIntEnv("LARKPAL_ARTIFACT_TTL_MS", DEFAULT_ARTIFACT_TTL_MS);
|
|
2641
|
+
}
|
|
2642
|
+
async create(input) {
|
|
2643
|
+
validateContextId(input.sessionId, "sessionId");
|
|
2644
|
+
validateContextId(input.owner.tenantKey, "tenantKey");
|
|
2645
|
+
const artifactId = `art_${v4().replace(/-/g, "")}`;
|
|
2646
|
+
const createdAtMs = Date.now();
|
|
2647
|
+
const createdAt = new Date(createdAtMs).toISOString();
|
|
2648
|
+
const expiresAt = new Date(createdAtMs + this.ttlMs).toISOString();
|
|
2649
|
+
const artifactDir = this.getArtifactDir(input.owner.tenantKey, input.sessionId, artifactId);
|
|
2650
|
+
const blobPath = join(artifactDir, "blob");
|
|
2651
|
+
const tmpBlobPath = join(artifactDir, `.blob.${process.pid}.${Date.now()}.tmp`);
|
|
2652
|
+
await mkdir(artifactDir, { recursive: true });
|
|
2653
|
+
const hash = createHash("sha256");
|
|
2654
|
+
let size = 0;
|
|
2655
|
+
const metering = new Transform({ transform: (chunk, _encoding, callback) => {
|
|
2656
|
+
size += chunk.length;
|
|
2657
|
+
if (size > this.maxUploadBytes) {
|
|
2658
|
+
callback(new ArtifactUploadError(`Artifact exceeds max upload size ${this.maxUploadBytes} bytes`, 413));
|
|
2659
|
+
return;
|
|
2660
|
+
}
|
|
2661
|
+
hash.update(chunk);
|
|
2662
|
+
callback(null, chunk);
|
|
2663
|
+
} });
|
|
2664
|
+
try {
|
|
2665
|
+
await pipeline(input.stream, metering, createWriteStream(tmpBlobPath));
|
|
2666
|
+
await rename(tmpBlobPath, blobPath);
|
|
2667
|
+
} catch (err) {
|
|
2668
|
+
await rm(artifactDir, {
|
|
2669
|
+
recursive: true,
|
|
2670
|
+
force: true
|
|
2671
|
+
}).catch(() => void 0);
|
|
2672
|
+
throw err;
|
|
2673
|
+
}
|
|
2674
|
+
const metadata = {
|
|
2675
|
+
artifactId,
|
|
2676
|
+
sessionId: input.sessionId,
|
|
2677
|
+
tenantKey: input.owner.tenantKey,
|
|
2678
|
+
userId: input.owner.userId,
|
|
2679
|
+
openId: input.owner.openId,
|
|
2680
|
+
kind: "workspace-file",
|
|
2681
|
+
uri: `artifact://${artifactId}`,
|
|
2682
|
+
fileName: sanitizeFileName(input.fileName),
|
|
2683
|
+
mimeType: normalizeMimeType(input.mimeType),
|
|
2684
|
+
size,
|
|
2685
|
+
contentHash: `sha256:${hash.digest("hex")}`,
|
|
2686
|
+
createdAt,
|
|
2687
|
+
expiresAt,
|
|
2688
|
+
metadata: input.metadata,
|
|
2689
|
+
blobPath
|
|
2690
|
+
};
|
|
2691
|
+
await writeJson(join(artifactDir, "meta.json"), metadata);
|
|
2692
|
+
return toPublicRef(metadata);
|
|
2693
|
+
}
|
|
2694
|
+
async list(params) {
|
|
2695
|
+
validateContextId(params.sessionId, "sessionId");
|
|
2696
|
+
validateContextId(params.owner.tenantKey, "tenantKey");
|
|
2697
|
+
const sessionDir = this.getSessionDir(params.owner.tenantKey, params.sessionId);
|
|
2698
|
+
let entries;
|
|
2699
|
+
try {
|
|
2700
|
+
entries = await readdir(sessionDir);
|
|
2701
|
+
} catch {
|
|
2702
|
+
return [];
|
|
2703
|
+
}
|
|
2704
|
+
const results = [];
|
|
2705
|
+
for (const entry of entries) {
|
|
2706
|
+
if (!entry.startsWith("art_")) continue;
|
|
2707
|
+
const metadata = await readArtifactMetadata(join(sessionDir, entry, "meta.json"));
|
|
2708
|
+
if (!metadata) continue;
|
|
2709
|
+
if (!isOwnedBy(metadata, params.owner, params.sessionId)) continue;
|
|
2710
|
+
if (Date.parse(metadata.expiresAt) <= Date.now()) continue;
|
|
2711
|
+
results.push(toPublicRef(metadata));
|
|
2712
|
+
}
|
|
2713
|
+
return results.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
2714
|
+
}
|
|
2715
|
+
async statBlob(ref, owner) {
|
|
2716
|
+
const metadata = await readArtifactMetadata(this.getMetadataPath(owner.tenantKey, ref.sessionId, ref.artifactId));
|
|
2717
|
+
if (!metadata || !isOwnedBy(metadata, owner, ref.sessionId)) return null;
|
|
2718
|
+
const blobStat = await stat(metadata.blobPath).catch(() => null);
|
|
2719
|
+
return blobStat ? {
|
|
2720
|
+
path: metadata.blobPath,
|
|
2721
|
+
size: blobStat.size
|
|
2722
|
+
} : null;
|
|
2723
|
+
}
|
|
2724
|
+
async resolveBlob(params) {
|
|
2725
|
+
validateContextId(params.sessionId, "sessionId");
|
|
2726
|
+
validateContextId(params.owner.tenantKey, "tenantKey");
|
|
2727
|
+
validateContextId(params.artifactId, "artifactId");
|
|
2728
|
+
const metadata = await readArtifactMetadata(this.getMetadataPath(params.owner.tenantKey, params.sessionId, params.artifactId));
|
|
2729
|
+
if (!metadata || !isOwnedBy(metadata, params.owner, params.sessionId)) return null;
|
|
2730
|
+
if (Date.parse(metadata.expiresAt) <= Date.now()) return null;
|
|
2731
|
+
if (!await stat(metadata.blobPath).catch(() => null)) return null;
|
|
2732
|
+
return {
|
|
2733
|
+
ref: toPublicRef(metadata),
|
|
2734
|
+
blobPath: metadata.blobPath
|
|
2735
|
+
};
|
|
2736
|
+
}
|
|
2737
|
+
getSessionDir(tenantKey, sessionId) {
|
|
2738
|
+
return join(this.rootDir, tenantKey, sessionId);
|
|
2739
|
+
}
|
|
2740
|
+
getArtifactDir(tenantKey, sessionId, artifactId) {
|
|
2741
|
+
return join(this.getSessionDir(tenantKey, sessionId), artifactId);
|
|
2742
|
+
}
|
|
2743
|
+
getMetadataPath(tenantKey, sessionId, artifactId) {
|
|
2744
|
+
return join(this.getArtifactDir(tenantKey, sessionId, artifactId), "meta.json");
|
|
2745
|
+
}
|
|
2746
|
+
};
|
|
2747
|
+
function readPositiveIntEnv(name, fallback) {
|
|
2748
|
+
const raw = process.env[name];
|
|
2749
|
+
if (!raw) return fallback;
|
|
2750
|
+
const parsed = Number.parseInt(raw, 10);
|
|
2751
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
2752
|
+
}
|
|
2753
|
+
function validateContextId(value, fieldName) {
|
|
2754
|
+
if (!/^[A-Za-z0-9_.:-]{1,200}$/.test(value)) throw new ArtifactUploadError(`Invalid ${fieldName}`, 400);
|
|
2755
|
+
}
|
|
2756
|
+
function sanitizeFileName(fileName) {
|
|
2757
|
+
return fileName.replace(/[\\/\0\r\n]/g, "_").trim() || "upload.bin";
|
|
2758
|
+
}
|
|
2759
|
+
function normalizeMimeType(mimeType) {
|
|
2760
|
+
return mimeType?.trim() || "application/octet-stream";
|
|
2761
|
+
}
|
|
2762
|
+
function toPublicRef(metadata) {
|
|
2763
|
+
const { tenantKey: _tenantKey, userId: _userId, openId: _openId, blobPath: _blobPath, ...ref } = metadata;
|
|
2764
|
+
return ref;
|
|
2765
|
+
}
|
|
2766
|
+
function isOwnedBy(metadata, owner, sessionId) {
|
|
2767
|
+
return metadata.tenantKey === owner.tenantKey && metadata.userId === owner.userId && metadata.openId === owner.openId && metadata.sessionId === sessionId;
|
|
2768
|
+
}
|
|
2769
|
+
async function readArtifactMetadata(filePath) {
|
|
2770
|
+
try {
|
|
2771
|
+
return JSON.parse(await readFile(filePath, "utf-8"));
|
|
2772
|
+
} catch {
|
|
2773
|
+
return null;
|
|
2774
|
+
}
|
|
2775
|
+
}
|
|
2776
|
+
async function writeJson(filePath, data) {
|
|
2777
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
2778
|
+
await writeFile(filePath, JSON.stringify(data, null, 2), "utf-8");
|
|
2779
|
+
}
|
|
2780
|
+
//#endregion
|
|
2622
2781
|
//#region src/runtime/larkpal-agent-factory.ts
|
|
2623
2782
|
/**
|
|
2624
2783
|
* LarkpalAgent RuntimeAdapter 工厂
|
|
@@ -2766,6 +2925,7 @@ async function createLarkpalAgentAdapter() {
|
|
|
2766
2925
|
});
|
|
2767
2926
|
const { LarkpalAgentAdapter, ToolRegistry } = await import("@vibe-lark/larkpal-agent");
|
|
2768
2927
|
const registry = new ToolRegistry();
|
|
2928
|
+
const visualMaxBatchSize = parseInt(process.env.LARKPAL_AGENT_VISUAL_MAX_BATCH_SIZE || "3", 10);
|
|
2769
2929
|
const adapter = new LarkpalAgentAdapter({
|
|
2770
2930
|
llm,
|
|
2771
2931
|
systemPrompt,
|
|
@@ -2774,8 +2934,9 @@ async function createLarkpalAgentAdapter() {
|
|
|
2774
2934
|
registry,
|
|
2775
2935
|
scenarioManifestDirs,
|
|
2776
2936
|
scenarioProviderConfig,
|
|
2937
|
+
artifactResolvers: [createGatewayArtifactResolver()],
|
|
2777
2938
|
visualRuntime: {
|
|
2778
|
-
maxBatchSize:
|
|
2939
|
+
maxBatchSize: visualMaxBatchSize,
|
|
2779
2940
|
allowedDirs: [process.env.LARK_CLI_WORKDIR || "/tmp/ai-knowledge-lark-cli", "/tmp"].filter(Boolean)
|
|
2780
2941
|
}
|
|
2781
2942
|
});
|
|
@@ -2799,6 +2960,51 @@ async function createLarkpalAgentAdapter() {
|
|
|
2799
2960
|
else log$31.warn("未配置 LARKPAL_AGENT_MCP_SERVER_URL,Agent 无远程工具可用");
|
|
2800
2961
|
return wrapLarkpalAgentAdapter(adapter);
|
|
2801
2962
|
}
|
|
2963
|
+
function createGatewayArtifactResolver(params) {
|
|
2964
|
+
const store = params?.store || new GatewayArtifactStore();
|
|
2965
|
+
return {
|
|
2966
|
+
name: "larkpal-gateway-artifact-store",
|
|
2967
|
+
canResolve(artifact) {
|
|
2968
|
+
return getArtifactId(artifact) !== void 0;
|
|
2969
|
+
},
|
|
2970
|
+
async resolve(artifact, context) {
|
|
2971
|
+
const artifactId = getArtifactId(artifact);
|
|
2972
|
+
const tenantKey = context.tenantKey || context.authHeaders?.["x-tenant-key"];
|
|
2973
|
+
const userId = context.userId || context.authHeaders?.["x-user-id"];
|
|
2974
|
+
const openId = context.authHeaders?.["x-open-id"] || userId;
|
|
2975
|
+
if (!artifactId || !tenantKey || !userId || !openId) return void 0;
|
|
2976
|
+
const resolved = await store.resolveBlob({
|
|
2977
|
+
sessionId: context.sessionId,
|
|
2978
|
+
artifactId,
|
|
2979
|
+
owner: {
|
|
2980
|
+
tenantKey,
|
|
2981
|
+
userId,
|
|
2982
|
+
openId
|
|
2983
|
+
}
|
|
2984
|
+
});
|
|
2985
|
+
if (!resolved) return void 0;
|
|
2986
|
+
const targetDir = join(context.cwd, "source-artifacts", artifactId);
|
|
2987
|
+
const fileName = sanitizeWorkspaceFileName(resolved.ref.fileName || artifact.fileName || artifactId);
|
|
2988
|
+
const targetPath = join(targetDir, fileName);
|
|
2989
|
+
await mkdir(targetDir, { recursive: true });
|
|
2990
|
+
await copyFile(resolved.blobPath, targetPath);
|
|
2991
|
+
return {
|
|
2992
|
+
artifactId,
|
|
2993
|
+
kind: "workspace-file",
|
|
2994
|
+
path: targetPath,
|
|
2995
|
+
fileName,
|
|
2996
|
+
mimeType: resolved.ref.mimeType ?? artifact.mimeType,
|
|
2997
|
+
size: resolved.ref.size ?? artifact.size,
|
|
2998
|
+
contentHash: resolved.ref.contentHash ?? artifact.contentHash,
|
|
2999
|
+
source: artifact,
|
|
3000
|
+
refs: {
|
|
3001
|
+
uri: resolved.ref.uri,
|
|
3002
|
+
gatewayArtifactId: resolved.ref.artifactId
|
|
3003
|
+
}
|
|
3004
|
+
};
|
|
3005
|
+
}
|
|
3006
|
+
};
|
|
3007
|
+
}
|
|
2802
3008
|
function wrapLarkpalAgentAdapter(adapter) {
|
|
2803
3009
|
return {
|
|
2804
3010
|
get name() {
|
|
@@ -2877,6 +3083,17 @@ function mergeSourceArtifacts(existing, imageArtifacts) {
|
|
|
2877
3083
|
if (Array.isArray(existing)) return [...existing, ...imageArtifacts];
|
|
2878
3084
|
return [existing, ...imageArtifacts];
|
|
2879
3085
|
}
|
|
3086
|
+
function getArtifactId(artifact) {
|
|
3087
|
+
const artifactId = typeof artifact.artifactId === "string" ? artifact.artifactId.trim() : "";
|
|
3088
|
+
if (artifactId.startsWith("art_")) return artifactId;
|
|
3089
|
+
const uri = typeof artifact.uri === "string" ? artifact.uri.trim() : "";
|
|
3090
|
+
if (!uri.startsWith("artifact://")) return void 0;
|
|
3091
|
+
const idFromUri = uri.slice(11).split(/[/?#]/, 1)[0]?.trim();
|
|
3092
|
+
return idFromUri?.startsWith("art_") ? idFromUri : void 0;
|
|
3093
|
+
}
|
|
3094
|
+
function sanitizeWorkspaceFileName(fileName) {
|
|
3095
|
+
return basename(fileName).replace(/[\\/\0\r\n]/g, "_").trim() || "artifact.bin";
|
|
3096
|
+
}
|
|
2880
3097
|
//#endregion
|
|
2881
3098
|
//#region src/routing/session-router.ts
|
|
2882
3099
|
/**
|
|
@@ -3216,6 +3433,7 @@ const MAX_SUMMARY_STRING_LENGTH = 200;
|
|
|
3216
3433
|
const PROTECTED_CONTEXT_HEADERS = new Set([
|
|
3217
3434
|
"x-tenant-key",
|
|
3218
3435
|
"x-user-id",
|
|
3436
|
+
"x-open-id",
|
|
3219
3437
|
"x-session-id",
|
|
3220
3438
|
"x-conversation-id",
|
|
3221
3439
|
"x-trace-id",
|
|
@@ -3258,6 +3476,7 @@ function buildAgentRuntimeConfig(input) {
|
|
|
3258
3476
|
const authHeaders = buildAuthHeaders({
|
|
3259
3477
|
tenantKey,
|
|
3260
3478
|
userId,
|
|
3479
|
+
openId: input.identity?.openId,
|
|
3261
3480
|
sessionId,
|
|
3262
3481
|
conversationId,
|
|
3263
3482
|
traceId,
|
|
@@ -3420,6 +3639,7 @@ function buildAuthHeaders(params) {
|
|
|
3420
3639
|
const headers = {
|
|
3421
3640
|
"x-tenant-key": params.tenantKey,
|
|
3422
3641
|
"x-user-id": params.userId,
|
|
3642
|
+
...params.openId ? { "x-open-id": params.openId } : {},
|
|
3423
3643
|
"x-session-id": params.sessionId,
|
|
3424
3644
|
"x-conversation-id": params.conversationId,
|
|
3425
3645
|
"x-trace-id": params.traceId,
|
|
@@ -4999,152 +5219,6 @@ async function sendCallback(callbackUrl, taskId, status, result, error) {
|
|
|
4999
5219
|
}
|
|
5000
5220
|
}
|
|
5001
5221
|
//#endregion
|
|
5002
|
-
//#region src/gateway/artifact-store.ts
|
|
5003
|
-
const DEFAULT_ARTIFACT_TTL_MS = 4320 * 60 * 1e3;
|
|
5004
|
-
const DEFAULT_MAX_UPLOAD_BYTES = 50 * 1024 * 1024;
|
|
5005
|
-
var ArtifactUploadError = class extends Error {
|
|
5006
|
-
constructor(message, statusCode = 400) {
|
|
5007
|
-
super(message);
|
|
5008
|
-
this.statusCode = statusCode;
|
|
5009
|
-
this.name = "ArtifactUploadError";
|
|
5010
|
-
}
|
|
5011
|
-
};
|
|
5012
|
-
var GatewayArtifactStore = class {
|
|
5013
|
-
rootDir;
|
|
5014
|
-
maxUploadBytes;
|
|
5015
|
-
ttlMs;
|
|
5016
|
-
constructor(params) {
|
|
5017
|
-
const workspaceRoot = params?.workspaceRoot || resolveWorkspaceRoot();
|
|
5018
|
-
this.rootDir = join(workspaceRoot, ".artifacts");
|
|
5019
|
-
this.maxUploadBytes = params?.maxUploadBytes ?? readPositiveIntEnv("LARKPAL_ARTIFACT_MAX_UPLOAD_BYTES", DEFAULT_MAX_UPLOAD_BYTES);
|
|
5020
|
-
this.ttlMs = params?.ttlMs ?? readPositiveIntEnv("LARKPAL_ARTIFACT_TTL_MS", DEFAULT_ARTIFACT_TTL_MS);
|
|
5021
|
-
}
|
|
5022
|
-
async create(input) {
|
|
5023
|
-
validateContextId(input.sessionId, "sessionId");
|
|
5024
|
-
validateContextId(input.owner.tenantKey, "tenantKey");
|
|
5025
|
-
const artifactId = `art_${v4().replace(/-/g, "")}`;
|
|
5026
|
-
const createdAtMs = Date.now();
|
|
5027
|
-
const createdAt = new Date(createdAtMs).toISOString();
|
|
5028
|
-
const expiresAt = new Date(createdAtMs + this.ttlMs).toISOString();
|
|
5029
|
-
const artifactDir = this.getArtifactDir(input.owner.tenantKey, input.sessionId, artifactId);
|
|
5030
|
-
const blobPath = join(artifactDir, "blob");
|
|
5031
|
-
const tmpBlobPath = join(artifactDir, `.blob.${process.pid}.${Date.now()}.tmp`);
|
|
5032
|
-
await mkdir(artifactDir, { recursive: true });
|
|
5033
|
-
const hash = createHash("sha256");
|
|
5034
|
-
let size = 0;
|
|
5035
|
-
const metering = new Transform({ transform: (chunk, _encoding, callback) => {
|
|
5036
|
-
size += chunk.length;
|
|
5037
|
-
if (size > this.maxUploadBytes) {
|
|
5038
|
-
callback(new ArtifactUploadError(`Artifact exceeds max upload size ${this.maxUploadBytes} bytes`, 413));
|
|
5039
|
-
return;
|
|
5040
|
-
}
|
|
5041
|
-
hash.update(chunk);
|
|
5042
|
-
callback(null, chunk);
|
|
5043
|
-
} });
|
|
5044
|
-
try {
|
|
5045
|
-
await pipeline(input.stream, metering, createWriteStream(tmpBlobPath));
|
|
5046
|
-
await rename(tmpBlobPath, blobPath);
|
|
5047
|
-
} catch (err) {
|
|
5048
|
-
await rm(artifactDir, {
|
|
5049
|
-
recursive: true,
|
|
5050
|
-
force: true
|
|
5051
|
-
}).catch(() => void 0);
|
|
5052
|
-
throw err;
|
|
5053
|
-
}
|
|
5054
|
-
const metadata = {
|
|
5055
|
-
artifactId,
|
|
5056
|
-
sessionId: input.sessionId,
|
|
5057
|
-
tenantKey: input.owner.tenantKey,
|
|
5058
|
-
userId: input.owner.userId,
|
|
5059
|
-
openId: input.owner.openId,
|
|
5060
|
-
kind: "workspace-file",
|
|
5061
|
-
uri: `artifact://${artifactId}`,
|
|
5062
|
-
fileName: sanitizeFileName(input.fileName),
|
|
5063
|
-
mimeType: normalizeMimeType(input.mimeType),
|
|
5064
|
-
size,
|
|
5065
|
-
contentHash: `sha256:${hash.digest("hex")}`,
|
|
5066
|
-
createdAt,
|
|
5067
|
-
expiresAt,
|
|
5068
|
-
metadata: input.metadata,
|
|
5069
|
-
blobPath
|
|
5070
|
-
};
|
|
5071
|
-
await writeJson(join(artifactDir, "meta.json"), metadata);
|
|
5072
|
-
return toPublicRef(metadata);
|
|
5073
|
-
}
|
|
5074
|
-
async list(params) {
|
|
5075
|
-
validateContextId(params.sessionId, "sessionId");
|
|
5076
|
-
validateContextId(params.owner.tenantKey, "tenantKey");
|
|
5077
|
-
const sessionDir = this.getSessionDir(params.owner.tenantKey, params.sessionId);
|
|
5078
|
-
let entries;
|
|
5079
|
-
try {
|
|
5080
|
-
entries = await readdir(sessionDir);
|
|
5081
|
-
} catch {
|
|
5082
|
-
return [];
|
|
5083
|
-
}
|
|
5084
|
-
const results = [];
|
|
5085
|
-
for (const entry of entries) {
|
|
5086
|
-
if (!entry.startsWith("art_")) continue;
|
|
5087
|
-
const metadata = await readArtifactMetadata(join(sessionDir, entry, "meta.json"));
|
|
5088
|
-
if (!metadata) continue;
|
|
5089
|
-
if (!isOwnedBy(metadata, params.owner, params.sessionId)) continue;
|
|
5090
|
-
if (Date.parse(metadata.expiresAt) <= Date.now()) continue;
|
|
5091
|
-
results.push(toPublicRef(metadata));
|
|
5092
|
-
}
|
|
5093
|
-
return results.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
5094
|
-
}
|
|
5095
|
-
async statBlob(ref, owner) {
|
|
5096
|
-
const metadata = await readArtifactMetadata(this.getMetadataPath(owner.tenantKey, ref.sessionId, ref.artifactId));
|
|
5097
|
-
if (!metadata || !isOwnedBy(metadata, owner, ref.sessionId)) return null;
|
|
5098
|
-
const blobStat = await stat(metadata.blobPath).catch(() => null);
|
|
5099
|
-
return blobStat ? {
|
|
5100
|
-
path: metadata.blobPath,
|
|
5101
|
-
size: blobStat.size
|
|
5102
|
-
} : null;
|
|
5103
|
-
}
|
|
5104
|
-
getSessionDir(tenantKey, sessionId) {
|
|
5105
|
-
return join(this.rootDir, tenantKey, sessionId);
|
|
5106
|
-
}
|
|
5107
|
-
getArtifactDir(tenantKey, sessionId, artifactId) {
|
|
5108
|
-
return join(this.getSessionDir(tenantKey, sessionId), artifactId);
|
|
5109
|
-
}
|
|
5110
|
-
getMetadataPath(tenantKey, sessionId, artifactId) {
|
|
5111
|
-
return join(this.getArtifactDir(tenantKey, sessionId, artifactId), "meta.json");
|
|
5112
|
-
}
|
|
5113
|
-
};
|
|
5114
|
-
function readPositiveIntEnv(name, fallback) {
|
|
5115
|
-
const raw = process.env[name];
|
|
5116
|
-
if (!raw) return fallback;
|
|
5117
|
-
const parsed = Number.parseInt(raw, 10);
|
|
5118
|
-
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
5119
|
-
}
|
|
5120
|
-
function validateContextId(value, fieldName) {
|
|
5121
|
-
if (!/^[A-Za-z0-9_.:-]{1,200}$/.test(value)) throw new ArtifactUploadError(`Invalid ${fieldName}`, 400);
|
|
5122
|
-
}
|
|
5123
|
-
function sanitizeFileName(fileName) {
|
|
5124
|
-
return fileName.replace(/[\\/\0\r\n]/g, "_").trim() || "upload.bin";
|
|
5125
|
-
}
|
|
5126
|
-
function normalizeMimeType(mimeType) {
|
|
5127
|
-
return mimeType?.trim() || "application/octet-stream";
|
|
5128
|
-
}
|
|
5129
|
-
function toPublicRef(metadata) {
|
|
5130
|
-
const { tenantKey: _tenantKey, userId: _userId, openId: _openId, blobPath: _blobPath, ...ref } = metadata;
|
|
5131
|
-
return ref;
|
|
5132
|
-
}
|
|
5133
|
-
function isOwnedBy(metadata, owner, sessionId) {
|
|
5134
|
-
return metadata.tenantKey === owner.tenantKey && metadata.userId === owner.userId && metadata.openId === owner.openId && metadata.sessionId === sessionId;
|
|
5135
|
-
}
|
|
5136
|
-
async function readArtifactMetadata(filePath) {
|
|
5137
|
-
try {
|
|
5138
|
-
return JSON.parse(await readFile(filePath, "utf-8"));
|
|
5139
|
-
} catch {
|
|
5140
|
-
return null;
|
|
5141
|
-
}
|
|
5142
|
-
}
|
|
5143
|
-
async function writeJson(filePath, data) {
|
|
5144
|
-
await mkdir(dirname(filePath), { recursive: true });
|
|
5145
|
-
await writeFile(filePath, JSON.stringify(data, null, 2), "utf-8");
|
|
5146
|
-
}
|
|
5147
|
-
//#endregion
|
|
5148
5222
|
//#region src/gateway/artifact-handler.ts
|
|
5149
5223
|
const log$28 = larkLogger("gateway/artifacts");
|
|
5150
5224
|
function createArtifactRouter(config) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibe-lark/larkpal",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.64",
|
|
4
4
|
"description": "LarkPal - Lark/Feishu bot service",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/main.mjs",
|
|
@@ -21,9 +21,22 @@
|
|
|
21
21
|
"registry": "https://registry.npmjs.org",
|
|
22
22
|
"access": "public"
|
|
23
23
|
},
|
|
24
|
+
"packageManager": "pnpm@10.32.1",
|
|
24
25
|
"engines": {
|
|
25
26
|
"node": ">=22"
|
|
26
27
|
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"start": "node --env-file=.env bin/larkpal.js",
|
|
30
|
+
"build": "tsdown",
|
|
31
|
+
"test": "vitest run",
|
|
32
|
+
"test:watch": "vitest",
|
|
33
|
+
"lint": "eslint src/",
|
|
34
|
+
"lint:fix": "eslint src/ --fix",
|
|
35
|
+
"sit:larkpal-agent": "node scripts/sit/larkpal-agent-0.1.9-sit.mjs",
|
|
36
|
+
"typecheck": "tsc --noEmit",
|
|
37
|
+
"format": "prettier --write src/**/*.ts",
|
|
38
|
+
"format:check": "prettier --check src/**/*.ts"
|
|
39
|
+
},
|
|
27
40
|
"dependencies": {
|
|
28
41
|
"@larksuiteoapi/node-sdk": "^1.60.0",
|
|
29
42
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
@@ -38,7 +51,7 @@
|
|
|
38
51
|
"zod": "^4.3.6"
|
|
39
52
|
},
|
|
40
53
|
"optionalDependencies": {
|
|
41
|
-
"@vibe-lark/larkpal-agent": "^0.2.
|
|
54
|
+
"@vibe-lark/larkpal-agent": "^0.2.19"
|
|
42
55
|
},
|
|
43
56
|
"devDependencies": {
|
|
44
57
|
"@eslint/js": "^10.0.1",
|
|
@@ -56,17 +69,5 @@
|
|
|
56
69
|
"typescript": "^5.9.3",
|
|
57
70
|
"typescript-eslint": "^8.32.1",
|
|
58
71
|
"vitest": "^4.1.1"
|
|
59
|
-
},
|
|
60
|
-
"scripts": {
|
|
61
|
-
"start": "node --env-file=.env bin/larkpal.js",
|
|
62
|
-
"build": "tsdown",
|
|
63
|
-
"test": "vitest run",
|
|
64
|
-
"test:watch": "vitest",
|
|
65
|
-
"lint": "eslint src/",
|
|
66
|
-
"lint:fix": "eslint src/ --fix",
|
|
67
|
-
"sit:larkpal-agent": "node scripts/sit/larkpal-agent-0.1.9-sit.mjs",
|
|
68
|
-
"typecheck": "tsc --noEmit",
|
|
69
|
-
"format": "prettier --write src/**/*.ts",
|
|
70
|
-
"format:check": "prettier --check src/**/*.ts"
|
|
71
72
|
}
|
|
72
|
-
}
|
|
73
|
+
}
|