palz-connector 1.1.0 → 1.1.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/openclaw.plugin.json +1 -1
- package/package.json +2 -1
- package/src/media.ts +94 -91
- package/src/oss.ts +91 -0
- package/src/outbound.ts +5 -5
- package/src/reply-dispatcher.ts +5 -5
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "palz-connector",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "index.ts",
|
|
6
6
|
"description": "Palz IM 接入 OpenClaw — 模块化架构,基于 OpenClaw Runtime 消息管道",
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
}
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
+
"ali-oss": "^6.21.0",
|
|
19
20
|
"ws": "^8.18.0"
|
|
20
21
|
}
|
|
21
22
|
}
|
package/src/media.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Palz Connector 媒体处理
|
|
3
3
|
*
|
|
4
|
-
* 将 IM
|
|
4
|
+
* 将 IM 消息中的图片上传到 OSS,返回公网 URL,
|
|
5
5
|
* 供 OpenClaw Runtime 作为媒体附件处理。
|
|
6
6
|
*/
|
|
7
7
|
|
|
@@ -9,80 +9,45 @@ import fs from "fs";
|
|
|
9
9
|
import path from "path";
|
|
10
10
|
import os from "os";
|
|
11
11
|
import type { OpenAIContent, ContentPart, ImageUrlContentPart, PalzMediaInfo } from "./types.js";
|
|
12
|
+
import { uploadFileToOss, uploadBufferToOss } from "./oss.js";
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"image/jpeg": ".jpg",
|
|
16
|
-
"image/png": ".png",
|
|
17
|
-
"image/gif": ".gif",
|
|
18
|
-
"image/webp": ".webp",
|
|
19
|
-
"image/bmp": ".bmp",
|
|
20
|
-
};
|
|
21
|
-
return map[mime] || ".png";
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* 从 base64 data URL 提取图片并保存到临时文件。
|
|
26
|
-
*/
|
|
27
|
-
async function saveDataUrlToTempFile(
|
|
28
|
-
dataUrl: string,
|
|
29
|
-
log?: (...args: any[]) => void,
|
|
30
|
-
): Promise<PalzMediaInfo | null> {
|
|
31
|
-
const match = dataUrl.match(/^data:(image\/[^;]+);base64,(.+)$/);
|
|
32
|
-
if (!match) {
|
|
33
|
-
log?.(`palz-media: [saveDataUrl] 不匹配 data URL 格式, url前60字符="${dataUrl.slice(0, 60)}"`);
|
|
34
|
-
return null;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const mimeType = match[1];
|
|
38
|
-
const base64Data = match[2];
|
|
39
|
-
const ext = mimeToExt(mimeType);
|
|
40
|
-
const tmpDir = path.join(os.tmpdir(), "palz-media");
|
|
41
|
-
fs.mkdirSync(tmpDir, { recursive: true });
|
|
42
|
-
const filePath = path.join(tmpDir, `palz_${Date.now()}_${Math.random().toString(36).slice(2, 8)}${ext}`);
|
|
43
|
-
|
|
44
|
-
try {
|
|
45
|
-
const buf = Buffer.from(base64Data, "base64");
|
|
46
|
-
fs.writeFileSync(filePath, buf);
|
|
47
|
-
log?.(`palz-media: [saveDataUrl] 输入: mime=${mimeType} base64Len=${base64Data.length} → 输出: path=${filePath} fileSize=${buf.length}bytes`);
|
|
48
|
-
return { path: filePath, contentType: mimeType, placeholder: "<media:image>" };
|
|
49
|
-
} catch (err: any) {
|
|
50
|
-
log?.(`palz-media: [saveDataUrl] 失败: mime=${mimeType} error=${err.message}`);
|
|
51
|
-
return null;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
14
|
+
/** OpenClaw 允许访问的媒体目录 */
|
|
15
|
+
const MEDIA_DIR = path.join(os.homedir(), ".openclaw", "media");
|
|
54
16
|
|
|
55
17
|
/**
|
|
56
|
-
*
|
|
18
|
+
* 将 Buffer 保存到 OpenClaw 媒体目录,返回 PalzMediaInfo。
|
|
57
19
|
*/
|
|
58
|
-
|
|
59
|
-
|
|
20
|
+
function saveBufferToMediaDir(
|
|
21
|
+
buffer: Buffer,
|
|
22
|
+
contentType: string,
|
|
23
|
+
ext: string,
|
|
60
24
|
log?: (...args: any[]) => void,
|
|
61
|
-
):
|
|
62
|
-
log?.(`palz-media: [download] 输入: url=${url.slice(0, 200)}`);
|
|
25
|
+
): PalzMediaInfo | null {
|
|
63
26
|
try {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
log?.(`palz-media: [download] 失败: status=${resp.status} url=${url.slice(0, 200)}`);
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
const contentType = resp.headers.get("content-type")?.split(";")[0]?.trim() || "image/png";
|
|
70
|
-
const buffer = Buffer.from(await resp.arrayBuffer());
|
|
71
|
-
const ext = mimeToExt(contentType);
|
|
72
|
-
const tmpDir = path.join(os.tmpdir(), "palz-media");
|
|
73
|
-
fs.mkdirSync(tmpDir, { recursive: true });
|
|
74
|
-
const filePath = path.join(tmpDir, `palz_${Date.now()}_${Math.random().toString(36).slice(2, 8)}${ext}`);
|
|
27
|
+
fs.mkdirSync(MEDIA_DIR, { recursive: true });
|
|
28
|
+
const filePath = path.join(MEDIA_DIR, `palz_${Date.now()}_${Math.random().toString(36).slice(2, 8)}${ext}`);
|
|
75
29
|
fs.writeFileSync(filePath, buffer);
|
|
76
|
-
log?.(`palz-media: [
|
|
30
|
+
log?.(`palz-media: [saveToMediaDir] 成功: path=${filePath} size=${buffer.length}bytes mime=${contentType}`);
|
|
77
31
|
return { path: filePath, contentType, placeholder: "<media:image>" };
|
|
78
32
|
} catch (err: any) {
|
|
79
|
-
log?.(`palz-media: [
|
|
33
|
+
log?.(`palz-media: [saveToMediaDir] 失败: error=${err.message}`);
|
|
80
34
|
return null;
|
|
81
35
|
}
|
|
82
36
|
}
|
|
83
37
|
|
|
38
|
+
function mimeToExt(mime: string): string {
|
|
39
|
+
const map: Record<string, string> = {
|
|
40
|
+
"image/jpeg": ".jpg",
|
|
41
|
+
"image/png": ".png",
|
|
42
|
+
"image/gif": ".gif",
|
|
43
|
+
"image/webp": ".webp",
|
|
44
|
+
"image/bmp": ".bmp",
|
|
45
|
+
};
|
|
46
|
+
return map[mime] || ".png";
|
|
47
|
+
}
|
|
48
|
+
|
|
84
49
|
/**
|
|
85
|
-
* 从 OpenAI Content
|
|
50
|
+
* 从 OpenAI Content 中提取所有图片,上传到 OSS 并返回公网 URL。
|
|
86
51
|
*/
|
|
87
52
|
export async function resolvePalzMediaList(
|
|
88
53
|
content: OpenAIContent,
|
|
@@ -106,9 +71,30 @@ export async function resolvePalzMediaList(
|
|
|
106
71
|
let info: PalzMediaInfo | null = null;
|
|
107
72
|
|
|
108
73
|
if (url.startsWith("data:")) {
|
|
109
|
-
|
|
74
|
+
// data URL → 解码 → 保存到 OpenClaw 媒体目录
|
|
75
|
+
const match = url.match(/^data:(image\/[^;]+);base64,(.+)$/);
|
|
76
|
+
if (match) {
|
|
77
|
+
const mimeType = match[1];
|
|
78
|
+
const base64Data = match[2];
|
|
79
|
+
const ext = mimeToExt(mimeType);
|
|
80
|
+
const buffer = Buffer.from(base64Data, "base64");
|
|
81
|
+
info = saveBufferToMediaDir(buffer, mimeType, ext, log);
|
|
82
|
+
}
|
|
110
83
|
} else if (url.startsWith("http://") || url.startsWith("https://")) {
|
|
111
|
-
|
|
84
|
+
// HTTP URL → 下载 → 保存到 OpenClaw 媒体目录
|
|
85
|
+
try {
|
|
86
|
+
const resp = await fetch(url);
|
|
87
|
+
if (resp.ok) {
|
|
88
|
+
const contentType = resp.headers.get("content-type")?.split(";")[0]?.trim() || "image/png";
|
|
89
|
+
const buffer = Buffer.from(await resp.arrayBuffer());
|
|
90
|
+
const ext = mimeToExt(contentType);
|
|
91
|
+
info = saveBufferToMediaDir(buffer, contentType, ext, log);
|
|
92
|
+
} else {
|
|
93
|
+
log?.(`palz-media: [resolve] HTTP下载失败: status=${resp.status}`);
|
|
94
|
+
}
|
|
95
|
+
} catch (err: any) {
|
|
96
|
+
log?.(`palz-media: [resolve] HTTP下载异常: ${err.message}`);
|
|
97
|
+
}
|
|
112
98
|
}
|
|
113
99
|
|
|
114
100
|
if (info) {
|
|
@@ -124,59 +110,76 @@ export async function resolvePalzMediaList(
|
|
|
124
110
|
}
|
|
125
111
|
|
|
126
112
|
/**
|
|
127
|
-
*
|
|
113
|
+
* 将本地文件路径、data URL 或 HTTP URL 转为 OSS 公网链接(用于出站消息)。
|
|
114
|
+
* 替代 loadMediaAsDataUrl,避免 base64 传输。
|
|
128
115
|
*/
|
|
129
|
-
export async function
|
|
116
|
+
export async function loadMediaAsOssUrl(
|
|
130
117
|
mediaUrl: string,
|
|
131
118
|
log?: (...args: any[]) => void,
|
|
132
119
|
): Promise<string | null> {
|
|
133
|
-
log?.(`palz-media: [
|
|
120
|
+
log?.(`palz-media: [loadAsOssUrl] 输入: url=${mediaUrl.slice(0, 200)}`);
|
|
134
121
|
|
|
135
|
-
|
|
136
|
-
|
|
122
|
+
// 已经是 OSS 链接,直接返回
|
|
123
|
+
if (mediaUrl.startsWith("https://oss.csaiagent.com/") || mediaUrl.startsWith("https://cstv-data.oss-cn-beijing.aliyuncs.com/")) {
|
|
124
|
+
log?.(`palz-media: [loadAsOssUrl] 已是OSS链接, 直接返回`);
|
|
137
125
|
return mediaUrl;
|
|
138
126
|
}
|
|
139
127
|
|
|
140
|
-
|
|
141
|
-
if (
|
|
128
|
+
// data URL → 解码 → 上传到 OSS
|
|
129
|
+
if (mediaUrl.startsWith("data:")) {
|
|
130
|
+
const match = mediaUrl.match(/^data:(image\/[^;]+);base64,(.+)$/);
|
|
131
|
+
if (!match) {
|
|
132
|
+
log?.(`palz-media: [loadAsOssUrl] data URL 格式不匹配`);
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
const mimeType = match[1];
|
|
136
|
+
const base64Data = match[2];
|
|
137
|
+
const ext = mimeToExt(mimeType);
|
|
138
|
+
try {
|
|
139
|
+
const buffer = Buffer.from(base64Data, "base64");
|
|
140
|
+
const ossUrl = await uploadBufferToOss(buffer, ext, log);
|
|
141
|
+
log?.(`palz-media: [loadAsOssUrl] data URL → OSS: mime=${mimeType} bufSize=${buffer.length} ossUrl=${ossUrl}`);
|
|
142
|
+
return ossUrl;
|
|
143
|
+
} catch (err: any) {
|
|
144
|
+
log?.(`palz-media: [loadAsOssUrl] data URL上传OSS失败: ${err.message}`);
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// 本地文件路径(绝对或相对)→ 上传到 OSS
|
|
150
|
+
const rawPath = mediaUrl.replace(/^MEDIA:/, "");
|
|
151
|
+
const filePath = path.isAbsolute(rawPath) ? rawPath : path.resolve(rawPath);
|
|
152
|
+
if (fs.existsSync(filePath)) {
|
|
142
153
|
try {
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
".jpg": "image/jpeg",
|
|
147
|
-
".jpeg": "image/jpeg",
|
|
148
|
-
".png": "image/png",
|
|
149
|
-
".gif": "image/gif",
|
|
150
|
-
".webp": "image/webp",
|
|
151
|
-
};
|
|
152
|
-
const mimeType = mimeMap[ext] || "image/png";
|
|
153
|
-
const dataUrl = `data:${mimeType};base64,${buffer.toString("base64")}`;
|
|
154
|
-
log?.(`palz-media: [loadAsDataUrl] 输出: 本地文件=${filePath} size=${buffer.length}bytes mime=${mimeType} dataUrlLen=${dataUrl.length}`);
|
|
155
|
-
return dataUrl;
|
|
154
|
+
const ossUrl = await uploadFileToOss(filePath, log);
|
|
155
|
+
log?.(`palz-media: [loadAsOssUrl] 本地文件 → OSS: path=${filePath} ossUrl=${ossUrl}`);
|
|
156
|
+
return ossUrl;
|
|
156
157
|
} catch (err: any) {
|
|
157
|
-
log?.(`palz-media: [
|
|
158
|
+
log?.(`palz-media: [loadAsOssUrl] 本地文件上传OSS失败: ${filePath} error=${err.message}`);
|
|
158
159
|
return null;
|
|
159
160
|
}
|
|
160
161
|
}
|
|
161
162
|
|
|
163
|
+
// HTTP URL → 下载 → 上传到 OSS
|
|
162
164
|
if (mediaUrl.startsWith("http://") || mediaUrl.startsWith("https://")) {
|
|
163
165
|
try {
|
|
164
166
|
const resp = await fetch(mediaUrl);
|
|
165
167
|
if (!resp.ok) {
|
|
166
|
-
log?.(`palz-media: [
|
|
168
|
+
log?.(`palz-media: [loadAsOssUrl] HTTP下载失败: status=${resp.status}`);
|
|
167
169
|
return null;
|
|
168
170
|
}
|
|
169
|
-
const
|
|
171
|
+
const contentType = resp.headers.get("content-type")?.split(";")[0]?.trim() || "image/png";
|
|
170
172
|
const buffer = Buffer.from(await resp.arrayBuffer());
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
173
|
+
const ext = mimeToExt(contentType);
|
|
174
|
+
const ossUrl = await uploadBufferToOss(buffer, ext, log);
|
|
175
|
+
log?.(`palz-media: [loadAsOssUrl] HTTP → OSS: size=${buffer.length} mime=${contentType} ossUrl=${ossUrl}`);
|
|
176
|
+
return ossUrl;
|
|
174
177
|
} catch (err: any) {
|
|
175
|
-
log?.(`palz-media: [
|
|
178
|
+
log?.(`palz-media: [loadAsOssUrl] HTTP下载上传OSS异常: ${err.message}`);
|
|
176
179
|
return null;
|
|
177
180
|
}
|
|
178
181
|
}
|
|
179
182
|
|
|
180
|
-
log?.(`palz-media: [
|
|
183
|
+
log?.(`palz-media: [loadAsOssUrl] 无法识别的URL格式`);
|
|
181
184
|
return null;
|
|
182
185
|
}
|
package/src/oss.ts
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Palz Connector OSS 工具
|
|
3
|
+
*
|
|
4
|
+
* 阿里云 OSS 上传/下载,AK/SK 通过环境变量读取:
|
|
5
|
+
* OSS_ACCESS_KEY_ID
|
|
6
|
+
* OSS_ACCESS_KEY_SECRET
|
|
7
|
+
*
|
|
8
|
+
* 上传后返回公网可访问的 URL。
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import OSS from "ali-oss";
|
|
12
|
+
import path from "path";
|
|
13
|
+
import fs from "fs";
|
|
14
|
+
|
|
15
|
+
const OSS_REGION = "oss-cn-beijing";
|
|
16
|
+
const OSS_BUCKET = "cstv-data";
|
|
17
|
+
const OSS_CUSTOM_DOMAIN = "oss.csaiagent.com";
|
|
18
|
+
const OSS_UPLOAD_PREFIX = "palz-media";
|
|
19
|
+
|
|
20
|
+
let _client: OSS | null = null;
|
|
21
|
+
|
|
22
|
+
function getOssClient(): OSS {
|
|
23
|
+
if (_client) return _client;
|
|
24
|
+
|
|
25
|
+
const accessKeyId = process.env.OSS_ACCESS_KEY_ID;
|
|
26
|
+
const accessKeySecret = process.env.OSS_ACCESS_KEY_SECRET;
|
|
27
|
+
|
|
28
|
+
if (!accessKeyId || !accessKeySecret) {
|
|
29
|
+
throw new Error(
|
|
30
|
+
"OSS credentials not configured. Set OSS_ACCESS_KEY_ID and OSS_ACCESS_KEY_SECRET environment variables.",
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
_client = new OSS({
|
|
35
|
+
region: OSS_REGION,
|
|
36
|
+
accessKeyId,
|
|
37
|
+
accessKeySecret,
|
|
38
|
+
bucket: OSS_BUCKET,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
return _client;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 上传本地文件到 OSS,返回公网 URL。
|
|
46
|
+
*/
|
|
47
|
+
export async function uploadFileToOss(
|
|
48
|
+
localPath: string,
|
|
49
|
+
log?: (...args: any[]) => void,
|
|
50
|
+
): Promise<string | null> {
|
|
51
|
+
log?.(`palz-oss: [upload] 输入: localPath=${localPath}`);
|
|
52
|
+
try {
|
|
53
|
+
const client = getOssClient();
|
|
54
|
+
const ext = path.extname(localPath) || ".png";
|
|
55
|
+
const objectName = `${OSS_UPLOAD_PREFIX}/${Date.now()}_${Math.random().toString(36).slice(2, 8)}${ext}`;
|
|
56
|
+
|
|
57
|
+
const fileBuffer = fs.readFileSync(localPath);
|
|
58
|
+
await client.put(objectName, fileBuffer);
|
|
59
|
+
|
|
60
|
+
const fileUrl = `https://${OSS_CUSTOM_DOMAIN}/${objectName}`;
|
|
61
|
+
log?.(`palz-oss: [upload] 成功: objectName=${objectName} url=${fileUrl}`);
|
|
62
|
+
return fileUrl;
|
|
63
|
+
} catch (err: any) {
|
|
64
|
+
log?.(`palz-oss: [upload] 失败: localPath=${localPath} error=${err.message}`);
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* 上传 Buffer 到 OSS,返回公网 URL。
|
|
71
|
+
*/
|
|
72
|
+
export async function uploadBufferToOss(
|
|
73
|
+
buffer: Buffer,
|
|
74
|
+
ext: string = ".png",
|
|
75
|
+
log?: (...args: any[]) => void,
|
|
76
|
+
): Promise<string | null> {
|
|
77
|
+
log?.(`palz-oss: [uploadBuffer] 输入: bufferSize=${buffer.length} ext=${ext}`);
|
|
78
|
+
try {
|
|
79
|
+
const client = getOssClient();
|
|
80
|
+
const objectName = `${OSS_UPLOAD_PREFIX}/${Date.now()}_${Math.random().toString(36).slice(2, 8)}${ext}`;
|
|
81
|
+
|
|
82
|
+
await client.put(objectName, buffer);
|
|
83
|
+
|
|
84
|
+
const fileUrl = `https://${OSS_CUSTOM_DOMAIN}/${objectName}`;
|
|
85
|
+
log?.(`palz-oss: [uploadBuffer] 成功: objectName=${objectName} url=${fileUrl}`);
|
|
86
|
+
return fileUrl;
|
|
87
|
+
} catch (err: any) {
|
|
88
|
+
log?.(`palz-oss: [uploadBuffer] 失败: error=${err.message}`);
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}
|
package/src/outbound.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { resolvePalzAccount } from "./config.js";
|
|
9
9
|
import { sendToPalzIM } from "./send.js";
|
|
10
|
-
import {
|
|
10
|
+
import { loadMediaAsOssUrl } from "./media.js";
|
|
11
11
|
import { parsePalzTarget } from "./targets.js";
|
|
12
12
|
import type { ContentPart, TextContentPart, OpenAIContent } from "./types.js";
|
|
13
13
|
|
|
@@ -51,10 +51,10 @@ export const palzOutbound = {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
if (mediaUrl) {
|
|
54
|
-
const
|
|
55
|
-
if (
|
|
56
|
-
contentParts.push({ type: "image_url", image_url: { url:
|
|
57
|
-
log(`palz-outbound: [sendMedia] 媒体转换成功:
|
|
54
|
+
const ossUrl = await loadMediaAsOssUrl(mediaUrl, log);
|
|
55
|
+
if (ossUrl) {
|
|
56
|
+
contentParts.push({ type: "image_url", image_url: { url: ossUrl } });
|
|
57
|
+
log(`palz-outbound: [sendMedia] 媒体转换成功: ossUrl=${ossUrl}`);
|
|
58
58
|
} else {
|
|
59
59
|
contentParts.push({ type: "text", text: `\n📎 ${mediaUrl}` });
|
|
60
60
|
log(`palz-outbound: [sendMedia] 媒体转换失败, 使用文本链接替代`);
|
package/src/reply-dispatcher.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { getPalzRuntime } from "./runtime.js";
|
|
12
|
-
import {
|
|
12
|
+
import { loadMediaAsOssUrl } from "./media.js";
|
|
13
13
|
import { sendToPalzIM } from "./send.js";
|
|
14
14
|
import type { PalzConfig, StreamChunkOpts, ContentPart, OpenAIContent } from "./types.js";
|
|
15
15
|
|
|
@@ -119,10 +119,10 @@ export function createPalzReplyDispatcher(params: CreatePalzReplyDispatcherParam
|
|
|
119
119
|
|
|
120
120
|
for (let i = 0; i < mediaUrls.length; i++) {
|
|
121
121
|
log(`${tag}: [DELIVER 媒体] ${i + 1}/${mediaUrls.length} url=${mediaUrls[i].slice(0, 200)}`);
|
|
122
|
-
const
|
|
123
|
-
if (
|
|
124
|
-
contentParts.push({ type: "image_url", image_url: { url:
|
|
125
|
-
log(`${tag}: [DELIVER 媒体转换成功] ${i + 1}/${mediaUrls.length}
|
|
122
|
+
const ossUrl = await loadMediaAsOssUrl(mediaUrls[i], log);
|
|
123
|
+
if (ossUrl) {
|
|
124
|
+
contentParts.push({ type: "image_url", image_url: { url: ossUrl } });
|
|
125
|
+
log(`${tag}: [DELIVER 媒体转换成功] ${i + 1}/${mediaUrls.length} ossUrl=${ossUrl}`);
|
|
126
126
|
} else {
|
|
127
127
|
contentParts.push({ type: "text", text: `\n📎 ${mediaUrls[i]}` });
|
|
128
128
|
log(`${tag}: [DELIVER 媒体转换失败] ${i + 1}/${mediaUrls.length} 使用文本链接替代`);
|