@modelzen/feishu-codex-bridge 0.1.6 → 0.1.7
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.js +180 -11
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -3528,6 +3528,156 @@ async function handleDmConsole(channel, cfg, msg) {
|
|
|
3528
3528
|
});
|
|
3529
3529
|
}
|
|
3530
3530
|
|
|
3531
|
+
// src/bot/media.ts
|
|
3532
|
+
import { mkdir as mkdir8, readdir as readdir2, rm as rm3, stat as stat2 } from "fs/promises";
|
|
3533
|
+
import { join as join10 } from "path";
|
|
3534
|
+
var MAX_IMAGES = 9;
|
|
3535
|
+
var MEDIA_TTL_MS = 60 * 6e4;
|
|
3536
|
+
var EXT_BY_CONTENT_TYPE = {
|
|
3537
|
+
"image/png": "png",
|
|
3538
|
+
"image/jpeg": "jpg",
|
|
3539
|
+
"image/jpg": "jpg",
|
|
3540
|
+
"image/gif": "gif",
|
|
3541
|
+
"image/webp": "webp",
|
|
3542
|
+
"image/bmp": "bmp",
|
|
3543
|
+
"image/heic": "heic",
|
|
3544
|
+
"image/heif": "heif",
|
|
3545
|
+
"image/tiff": "tiff"
|
|
3546
|
+
};
|
|
3547
|
+
function messageHasImages(msg) {
|
|
3548
|
+
if ((msg.resources ?? []).some((r) => r.type === "image")) return true;
|
|
3549
|
+
return msg.rawContentType === "merge_forward";
|
|
3550
|
+
}
|
|
3551
|
+
async function collectInboundImages(channel, msg) {
|
|
3552
|
+
let refs;
|
|
3553
|
+
try {
|
|
3554
|
+
refs = await gatherRefs(channel, msg);
|
|
3555
|
+
} catch (err) {
|
|
3556
|
+
log.warn("intake", "image-gather-failed", { err: String(err) });
|
|
3557
|
+
return [];
|
|
3558
|
+
}
|
|
3559
|
+
if (refs.length === 0) return [];
|
|
3560
|
+
await pruneOldMedia();
|
|
3561
|
+
try {
|
|
3562
|
+
await mkdir8(paths.mediaDir, { recursive: true });
|
|
3563
|
+
} catch {
|
|
3564
|
+
}
|
|
3565
|
+
const out = [];
|
|
3566
|
+
let index = 0;
|
|
3567
|
+
for (const ref of refs.slice(0, MAX_IMAGES)) {
|
|
3568
|
+
const path = await downloadOne(channel, ref, index++);
|
|
3569
|
+
if (path) out.push(path);
|
|
3570
|
+
}
|
|
3571
|
+
log.info("intake", "images", { found: refs.length, downloaded: out.length });
|
|
3572
|
+
return out;
|
|
3573
|
+
}
|
|
3574
|
+
async function gatherRefs(channel, msg) {
|
|
3575
|
+
const refs = [];
|
|
3576
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3577
|
+
const add = (messageId, fileKey) => {
|
|
3578
|
+
if (!fileKey || seen.has(fileKey)) return;
|
|
3579
|
+
seen.add(fileKey);
|
|
3580
|
+
refs.push({ messageId, fileKey });
|
|
3581
|
+
};
|
|
3582
|
+
for (const r of msg.resources ?? []) {
|
|
3583
|
+
if (r.type === "image") add(msg.messageId, r.fileKey);
|
|
3584
|
+
}
|
|
3585
|
+
if (msg.rawContentType === "merge_forward") {
|
|
3586
|
+
const items = await fetchSubMessages(channel, msg.messageId);
|
|
3587
|
+
for (const sub of items) {
|
|
3588
|
+
if (!sub.message_id || sub.message_id === msg.messageId) continue;
|
|
3589
|
+
for (const key of imageKeysFromContent(sub.msg_type, sub.body?.content)) {
|
|
3590
|
+
add(sub.message_id, key);
|
|
3591
|
+
}
|
|
3592
|
+
}
|
|
3593
|
+
}
|
|
3594
|
+
return refs;
|
|
3595
|
+
}
|
|
3596
|
+
async function fetchSubMessages(channel, messageId) {
|
|
3597
|
+
try {
|
|
3598
|
+
const res = await channel.rawClient.im.v1.message.get({ path: { message_id: messageId } });
|
|
3599
|
+
return res.data?.items ?? [];
|
|
3600
|
+
} catch (err) {
|
|
3601
|
+
log.warn("intake", "submessages-failed", { messageId, err: String(err) });
|
|
3602
|
+
return [];
|
|
3603
|
+
}
|
|
3604
|
+
}
|
|
3605
|
+
function imageKeysFromContent(msgType, content) {
|
|
3606
|
+
if (!content) return [];
|
|
3607
|
+
let parsed;
|
|
3608
|
+
try {
|
|
3609
|
+
parsed = JSON.parse(content);
|
|
3610
|
+
} catch {
|
|
3611
|
+
return [];
|
|
3612
|
+
}
|
|
3613
|
+
if (msgType === "image") {
|
|
3614
|
+
const key = parsed?.image_key;
|
|
3615
|
+
return key ? [key] : [];
|
|
3616
|
+
}
|
|
3617
|
+
const keys = [];
|
|
3618
|
+
walkForImageKeys(parsed, keys);
|
|
3619
|
+
return keys;
|
|
3620
|
+
}
|
|
3621
|
+
function walkForImageKeys(node, out) {
|
|
3622
|
+
if (!node || typeof node !== "object") return;
|
|
3623
|
+
if (Array.isArray(node)) {
|
|
3624
|
+
for (const child of node) walkForImageKeys(child, out);
|
|
3625
|
+
return;
|
|
3626
|
+
}
|
|
3627
|
+
const obj = node;
|
|
3628
|
+
if (obj.tag === "img" && typeof obj.image_key === "string") out.push(obj.image_key);
|
|
3629
|
+
for (const k of Object.keys(obj)) walkForImageKeys(obj[k], out);
|
|
3630
|
+
}
|
|
3631
|
+
async function downloadOne(channel, ref, index) {
|
|
3632
|
+
try {
|
|
3633
|
+
const res = await channel.rawClient.im.v1.messageResource.get({
|
|
3634
|
+
path: { message_id: ref.messageId, file_key: ref.fileKey },
|
|
3635
|
+
params: { type: "image" }
|
|
3636
|
+
});
|
|
3637
|
+
const ext = extFromHeaders(res.headers);
|
|
3638
|
+
const file = join10(paths.mediaDir, `${safeName(ref.fileKey)}-${index}.${ext}`);
|
|
3639
|
+
await res.writeFile(file);
|
|
3640
|
+
return file;
|
|
3641
|
+
} catch (err) {
|
|
3642
|
+
log.warn("intake", "image-download-failed", { fileKey: ref.fileKey.slice(0, 24), err: String(err) });
|
|
3643
|
+
return void 0;
|
|
3644
|
+
}
|
|
3645
|
+
}
|
|
3646
|
+
function extFromHeaders(headers) {
|
|
3647
|
+
const ct = readHeader(headers, "content-type");
|
|
3648
|
+
if (ct) {
|
|
3649
|
+
const base = ct.split(";")[0]?.trim().toLowerCase();
|
|
3650
|
+
if (base && EXT_BY_CONTENT_TYPE[base]) return EXT_BY_CONTENT_TYPE[base];
|
|
3651
|
+
}
|
|
3652
|
+
return "png";
|
|
3653
|
+
}
|
|
3654
|
+
function readHeader(headers, name) {
|
|
3655
|
+
if (!headers || typeof headers !== "object") return void 0;
|
|
3656
|
+
const h = headers;
|
|
3657
|
+
const raw = typeof h.get === "function" ? h.get(name) : h[name] ?? h[name.toLowerCase()];
|
|
3658
|
+
return typeof raw === "string" ? raw : Array.isArray(raw) ? String(raw[0]) : void 0;
|
|
3659
|
+
}
|
|
3660
|
+
function safeName(fileKey) {
|
|
3661
|
+
return fileKey.replace(/[^a-zA-Z0-9_-]/g, "").slice(-40) || "img";
|
|
3662
|
+
}
|
|
3663
|
+
async function pruneOldMedia() {
|
|
3664
|
+
let entries;
|
|
3665
|
+
try {
|
|
3666
|
+
entries = await readdir2(paths.mediaDir);
|
|
3667
|
+
} catch {
|
|
3668
|
+
return;
|
|
3669
|
+
}
|
|
3670
|
+
const cutoff = Date.now() - MEDIA_TTL_MS;
|
|
3671
|
+
for (const name of entries) {
|
|
3672
|
+
const file = join10(paths.mediaDir, name);
|
|
3673
|
+
try {
|
|
3674
|
+
const st = await stat2(file);
|
|
3675
|
+
if (st.mtimeMs < cutoff) await rm3(file, { force: true });
|
|
3676
|
+
} catch {
|
|
3677
|
+
}
|
|
3678
|
+
}
|
|
3679
|
+
}
|
|
3680
|
+
|
|
3531
3681
|
// src/bot/comments.ts
|
|
3532
3682
|
var SUPPORTED_FILE_TYPES = /* @__PURE__ */ new Set(["doc", "docx", "sheet", "file"]);
|
|
3533
3683
|
var REPLY_MAX_CHARS = 2e3;
|
|
@@ -3922,19 +4072,34 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
3922
4072
|
async function handleTurn(msg, text, sessionKey, flat, project) {
|
|
3923
4073
|
const existing = active.get(sessionKey);
|
|
3924
4074
|
if (existing) {
|
|
3925
|
-
|
|
3926
|
-
|
|
4075
|
+
const images = messageHasImages(msg) ? await collectInboundImages(channel, msg) : void 0;
|
|
4076
|
+
const cur = active.get(sessionKey);
|
|
4077
|
+
if (!cur) {
|
|
4078
|
+
startReservedRun(msg, text, sessionKey, flat, project, images);
|
|
4079
|
+
return;
|
|
4080
|
+
}
|
|
4081
|
+
if (getPendingPolicy(cfg) === "steer" && cur.run && cur.thread) {
|
|
4082
|
+
const tid = cur.run.turnId();
|
|
3927
4083
|
if (tid) {
|
|
3928
4084
|
try {
|
|
3929
|
-
await
|
|
3930
|
-
log.info("intake", "steer", { tid });
|
|
4085
|
+
await cur.thread.steer({ text, images }, tid);
|
|
4086
|
+
log.info("intake", "steer", { tid, images: images?.length ?? 0 });
|
|
3931
4087
|
return;
|
|
3932
4088
|
} catch (err) {
|
|
3933
4089
|
log.warn("intake", "steer-failed", { err: String(err) });
|
|
3934
4090
|
}
|
|
3935
4091
|
}
|
|
3936
4092
|
}
|
|
3937
|
-
|
|
4093
|
+
cur.queue.push({ text, images });
|
|
4094
|
+
log.info("intake", "queued", { depth: cur.queue.length });
|
|
4095
|
+
return;
|
|
4096
|
+
}
|
|
4097
|
+
startReservedRun(msg, text, sessionKey, flat, project);
|
|
4098
|
+
}
|
|
4099
|
+
function startReservedRun(msg, text, sessionKey, flat, project, preloadedImages) {
|
|
4100
|
+
const existing = active.get(sessionKey);
|
|
4101
|
+
if (existing) {
|
|
4102
|
+
existing.queue.push({ text, images: preloadedImages });
|
|
3938
4103
|
log.info("intake", "queued", { depth: existing.queue.length });
|
|
3939
4104
|
return;
|
|
3940
4105
|
}
|
|
@@ -3943,6 +4108,7 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
3943
4108
|
void withTrace({ chatId: msg.chatId, msgId: msg.messageId }, async () => {
|
|
3944
4109
|
const reaction = runReaction(msg.messageId, !sema.hasFree());
|
|
3945
4110
|
try {
|
|
4111
|
+
const images = preloadedImages ?? (messageHasImages(msg) ? await collectInboundImages(channel, msg) : void 0);
|
|
3946
4112
|
let thread = await resolveThread(sessionKey, msg.chatId);
|
|
3947
4113
|
if (!thread) {
|
|
3948
4114
|
const cwd = project?.cwd ?? fallbackCwd;
|
|
@@ -3967,6 +4133,7 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
3967
4133
|
flat,
|
|
3968
4134
|
thread,
|
|
3969
4135
|
firstText: text,
|
|
4136
|
+
images,
|
|
3970
4137
|
knownThreadId: sessionKey,
|
|
3971
4138
|
requesterOpenId: msg.senderId
|
|
3972
4139
|
},
|
|
@@ -4019,7 +4186,8 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
4019
4186
|
return;
|
|
4020
4187
|
}
|
|
4021
4188
|
const firstText = text || "\u4F60\u597D\uFF0C\u6211\u4EEC\u5F00\u59CB\u5427\u3002";
|
|
4022
|
-
|
|
4189
|
+
const images = messageHasImages(msg) ? await collectInboundImages(channel, msg) : void 0;
|
|
4190
|
+
log.info("card", "start", { project: project?.name ?? "(unregistered)", model, effort, images: images?.length ?? 0 });
|
|
4023
4191
|
await launchRun(
|
|
4024
4192
|
{
|
|
4025
4193
|
chatId: msg.chatId,
|
|
@@ -4027,6 +4195,7 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
4027
4195
|
replyInThread: true,
|
|
4028
4196
|
thread,
|
|
4029
4197
|
firstText,
|
|
4198
|
+
images,
|
|
4030
4199
|
model,
|
|
4031
4200
|
effort,
|
|
4032
4201
|
cwd,
|
|
@@ -4432,14 +4601,14 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
4432
4601
|
};
|
|
4433
4602
|
let curCardKey;
|
|
4434
4603
|
try {
|
|
4435
|
-
let
|
|
4604
|
+
let turnInput = { text: opts.firstText, images: opts.images };
|
|
4436
4605
|
let replyTo = opts.replyTo;
|
|
4437
4606
|
let replyInThread = opts.flat ? false : opts.replyInThread ?? Boolean(opts.knownThreadId);
|
|
4438
4607
|
for (; ; ) {
|
|
4439
4608
|
const rec = topicThreadId ? await getSession(topicThreadId) : void 0;
|
|
4440
4609
|
const turnModel = rec?.model ?? opts.model;
|
|
4441
4610
|
const turnEffort = rec?.effort ?? opts.effort;
|
|
4442
|
-
const run = opts.thread.runStreamed(
|
|
4611
|
+
const run = opts.thread.runStreamed(turnInput, { model: turnModel, effort: turnEffort });
|
|
4443
4612
|
state.run = run;
|
|
4444
4613
|
const render = new RunRender();
|
|
4445
4614
|
render.showTools = getShowToolCalls(cfg);
|
|
@@ -4526,7 +4695,7 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
4526
4695
|
log.info("card", "final", { terminal: render.terminal() });
|
|
4527
4696
|
if (killed) break;
|
|
4528
4697
|
if (state.queue.length === 0) break;
|
|
4529
|
-
|
|
4698
|
+
turnInput = state.queue.shift();
|
|
4530
4699
|
}
|
|
4531
4700
|
} catch (err) {
|
|
4532
4701
|
log.fail("intake", err);
|
|
@@ -4890,7 +5059,7 @@ async function runUpdate(opts = {}) {
|
|
|
4890
5059
|
}
|
|
4891
5060
|
|
|
4892
5061
|
// src/cli/commands/bot.ts
|
|
4893
|
-
import { rm as
|
|
5062
|
+
import { rm as rm4 } from "fs/promises";
|
|
4894
5063
|
async function runBotInit(name) {
|
|
4895
5064
|
if (!ensureCodex()) {
|
|
4896
5065
|
process.exitCode = 1;
|
|
@@ -4944,7 +5113,7 @@ async function runBotRm(name) {
|
|
|
4944
5113
|
}
|
|
4945
5114
|
const after = await removeBot(bot2.appId);
|
|
4946
5115
|
await removeSecret(secretKeyForApp(bot2.appId));
|
|
4947
|
-
await
|
|
5116
|
+
await rm4(botDir(bot2.appId), { recursive: true, force: true });
|
|
4948
5117
|
console.log(`\u2713 \u5DF2\u79FB\u9664\u673A\u5668\u4EBA\u300C${bot2.name}\u300D(${bot2.appId})\uFF1A\u6CE8\u518C\u8868 + \u5BC6\u94A5 + \u72B6\u6001\u76EE\u5F55(projects/sessions)\u3002`);
|
|
4949
5118
|
if (after.bots.length === 0) {
|
|
4950
5119
|
console.log(" \u5DF2\u65E0\u4EFB\u4F55\u673A\u5668\u4EBA\uFF0C`bot init` \u91CD\u65B0\u521B\u5EFA\u3002");
|