@hywkp/test-openclaw-sider 1.0.2
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/README.md +65 -0
- package/README.zh_CN.md +106 -0
- package/index.ts +80 -0
- package/openclaw.plugin.json +10 -0
- package/package.json +51 -0
- package/setup-entry.ts +6 -0
- package/src/account.ts +350 -0
- package/src/auth.ts +292 -0
- package/src/channel.ts +3864 -0
- package/src/config.ts +29 -0
- package/src/inbound-media.ts +196 -0
- package/src/media-upload.ts +983 -0
- package/src/remote-browser-support.ts +64 -0
- package/src/setup-core.ts +431 -0
- package/src/user-agent.ts +17 -0
package/src/config.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export const SIDER_PLUGIN_ID = "test-openclaw-sider";
|
|
2
|
+
export const SIDER_CHANNEL_ID = SIDER_PLUGIN_ID;
|
|
3
|
+
|
|
4
|
+
export const SIDER_PLUGIN_NAME = "Chrome channel for Openclaw by Sider";
|
|
5
|
+
export const SIDER_PLUGIN_DESCRIPTION = "Official Chrome channel plugin for connecting OpenClaw in Sider Chrome extension";
|
|
6
|
+
|
|
7
|
+
export const SIDER_CHANNEL_LABEL = "Chrome channel for Openclaw by Sider";
|
|
8
|
+
export const SIDER_CHANNEL_SELECTION_LABEL = "Chrome channel for Openclaw by Sider";
|
|
9
|
+
export const SIDER_CHANNEL_DOCS_PATH = "/channels/sider";
|
|
10
|
+
export const SIDER_CHANNEL_DOCS_LABEL = "sider";
|
|
11
|
+
export const SIDER_CHANNEL_BLURB = "Command your OpenClaw from Chrome Sidebar directly."
|
|
12
|
+
export const SIDER_CHANNEL_ALIASES = ["sider"] as const;
|
|
13
|
+
|
|
14
|
+
export const SIDER_DEFAULT_BASE_URL = "https://selfclaw.apps.wisebox.ai";
|
|
15
|
+
export const SIDER_SETUP_TOKEN_ENV = "SIDER_SETUP_TOKEN";
|
|
16
|
+
export const SIDER_BASE_URL_ENV = "SIDER_BASE_URL";
|
|
17
|
+
export const SIDER_REMOTE_BROWSER_ENABLE_ENV = "SIDER_ENABLE_REMOTE_BROWSER_MCP";
|
|
18
|
+
|
|
19
|
+
export function readSiderBaseUrlEnv(): string | undefined {
|
|
20
|
+
return process.env.SIDER_BASE_URL;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function readDefaultSiderSetupTokenEnv(): string | undefined {
|
|
24
|
+
return process.env.SIDER_SETUP_TOKEN;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function readSiderRemoteBrowserEnableEnv(): string | undefined {
|
|
28
|
+
return process.env.SIDER_ENABLE_REMOTE_BROWSER_MCP;
|
|
29
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { type OpenClawConfig, type PluginRuntime } from "openclaw/plugin-sdk";
|
|
2
|
+
import { buildAgentMediaPayload } from "openclaw/plugin-sdk/media-runtime";
|
|
3
|
+
import { formatAuthorizationHeader } from "./auth.js";
|
|
4
|
+
import { SIDER_CHANNEL_ID } from "./config.js";
|
|
5
|
+
import { SIDER_USER_AGENT } from "./user-agent.js";
|
|
6
|
+
|
|
7
|
+
export type SiderInboundMediaItem = {
|
|
8
|
+
url?: string;
|
|
9
|
+
mimeType?: string;
|
|
10
|
+
fileName?: string;
|
|
11
|
+
objectKey?: string;
|
|
12
|
+
fileId?: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
type ResolveInboundMediaParams = {
|
|
16
|
+
runtime: PluginRuntime;
|
|
17
|
+
cfg: OpenClawConfig;
|
|
18
|
+
accountId: string;
|
|
19
|
+
mediaItems: SiderInboundMediaItem[];
|
|
20
|
+
maxBytes?: number;
|
|
21
|
+
gatewayUrl?: string;
|
|
22
|
+
token?: string;
|
|
23
|
+
requestTimeoutMs?: number;
|
|
24
|
+
logger?: {
|
|
25
|
+
debug?: (message: string, data?: Record<string, unknown>) => void;
|
|
26
|
+
warn?: (message: string, data?: Record<string, unknown>) => void;
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type ResolveInboundMediaResult = {
|
|
31
|
+
mediaPayload: ReturnType<typeof buildAgentMediaPayload>;
|
|
32
|
+
unresolvedMedia: SiderInboundMediaItem[];
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const DEFAULT_INBOUND_MEDIA_MAX_BYTES = 30 * 1024 * 1024;
|
|
36
|
+
const DEFAULT_DOWNLOAD_URL_TIMEOUT_MS = 8_000;
|
|
37
|
+
|
|
38
|
+
function resolveInboundMediaMaxBytes(params: {
|
|
39
|
+
cfg: OpenClawConfig;
|
|
40
|
+
maxBytes?: number;
|
|
41
|
+
}): number {
|
|
42
|
+
if (typeof params.maxBytes === "number" && Number.isFinite(params.maxBytes) && params.maxBytes > 0) {
|
|
43
|
+
return Math.floor(params.maxBytes);
|
|
44
|
+
}
|
|
45
|
+
const channelCfg = (params.cfg.channels?.[SIDER_CHANNEL_ID] ?? {}) as Record<string, unknown>;
|
|
46
|
+
const configuredMb = channelCfg.mediaMaxMb;
|
|
47
|
+
if (
|
|
48
|
+
typeof configuredMb === "number" &&
|
|
49
|
+
Number.isFinite(configuredMb) &&
|
|
50
|
+
configuredMb > 0
|
|
51
|
+
) {
|
|
52
|
+
return Math.floor(configuredMb * 1024 * 1024);
|
|
53
|
+
}
|
|
54
|
+
return DEFAULT_INBOUND_MEDIA_MAX_BYTES;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function resolveGatewayApiUrl(gatewayUrl: string, apiPath: string): string {
|
|
58
|
+
const url = new URL(gatewayUrl);
|
|
59
|
+
const basePath = url.pathname.replace(/\/+$/, "");
|
|
60
|
+
const normalizedPath = apiPath.startsWith("/") ? apiPath : `/${apiPath}`;
|
|
61
|
+
url.pathname = `${basePath}${normalizedPath}`;
|
|
62
|
+
url.search = "";
|
|
63
|
+
url.hash = "";
|
|
64
|
+
return url.toString();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function resolveSiderDownloadUrl(params: {
|
|
68
|
+
gatewayUrl: string;
|
|
69
|
+
objectKey: string;
|
|
70
|
+
token?: string;
|
|
71
|
+
timeoutMs?: number;
|
|
72
|
+
}): Promise<string> {
|
|
73
|
+
const url = new URL(resolveGatewayApiUrl(params.gatewayUrl, "/v1/files/download-url"));
|
|
74
|
+
url.searchParams.set("object_key", params.objectKey);
|
|
75
|
+
const headers: Record<string, string> = {
|
|
76
|
+
"User-Agent": SIDER_USER_AGENT,
|
|
77
|
+
};
|
|
78
|
+
if (params.token?.trim()) {
|
|
79
|
+
headers.Authorization = formatAuthorizationHeader(params.token);
|
|
80
|
+
}
|
|
81
|
+
const response = await fetch(url.toString(), {
|
|
82
|
+
method: "GET",
|
|
83
|
+
headers,
|
|
84
|
+
signal: AbortSignal.timeout(
|
|
85
|
+
Math.max(1_000, Math.floor(params.timeoutMs ?? DEFAULT_DOWNLOAD_URL_TIMEOUT_MS)),
|
|
86
|
+
),
|
|
87
|
+
});
|
|
88
|
+
const text = await response.text();
|
|
89
|
+
if (!response.ok) {
|
|
90
|
+
const detail = text.trim();
|
|
91
|
+
const suffix = detail ? `: ${detail.slice(0, 300)}` : "";
|
|
92
|
+
throw new Error(`HTTP ${response.status} ${response.statusText}${suffix}`);
|
|
93
|
+
}
|
|
94
|
+
let parsed: unknown;
|
|
95
|
+
try {
|
|
96
|
+
parsed = text.trim() ? (JSON.parse(text) as unknown) : {};
|
|
97
|
+
} catch (error) {
|
|
98
|
+
throw new Error(`invalid JSON from /v1/files/download-url: ${String(error)}`);
|
|
99
|
+
}
|
|
100
|
+
const record =
|
|
101
|
+
parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
102
|
+
? (parsed as Record<string, unknown>)
|
|
103
|
+
: null;
|
|
104
|
+
const downloadUrl = typeof record?.url === "string" ? record.url.trim() : "";
|
|
105
|
+
if (!downloadUrl) {
|
|
106
|
+
throw new Error("/v1/files/download-url response missing url");
|
|
107
|
+
}
|
|
108
|
+
return downloadUrl;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export async function resolveInboundSiderMedia(
|
|
112
|
+
params: ResolveInboundMediaParams,
|
|
113
|
+
): Promise<ResolveInboundMediaResult> {
|
|
114
|
+
const maxBytes = resolveInboundMediaMaxBytes({
|
|
115
|
+
cfg: params.cfg,
|
|
116
|
+
maxBytes: params.maxBytes,
|
|
117
|
+
});
|
|
118
|
+
const downloaded: Array<{ path: string; contentType?: string | null }> = [];
|
|
119
|
+
const unresolvedMedia: SiderInboundMediaItem[] = [];
|
|
120
|
+
|
|
121
|
+
for (const media of params.mediaItems) {
|
|
122
|
+
let sourceUrl = media.url?.trim();
|
|
123
|
+
if (!sourceUrl && media.objectKey?.trim() && params.gatewayUrl?.trim()) {
|
|
124
|
+
try {
|
|
125
|
+
sourceUrl = await resolveSiderDownloadUrl({
|
|
126
|
+
gatewayUrl: params.gatewayUrl,
|
|
127
|
+
objectKey: media.objectKey,
|
|
128
|
+
token: params.token,
|
|
129
|
+
timeoutMs: params.requestTimeoutMs,
|
|
130
|
+
});
|
|
131
|
+
params.logger?.debug?.("sider inbound media resolved download url from object_key", {
|
|
132
|
+
accountId: params.accountId,
|
|
133
|
+
objectKey: media.objectKey,
|
|
134
|
+
fileId: media.fileId,
|
|
135
|
+
});
|
|
136
|
+
} catch (error) {
|
|
137
|
+
params.logger?.warn?.("sider inbound media failed to resolve download url from object_key", {
|
|
138
|
+
accountId: params.accountId,
|
|
139
|
+
objectKey: media.objectKey,
|
|
140
|
+
fileId: media.fileId,
|
|
141
|
+
error: String(error),
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (!sourceUrl) {
|
|
146
|
+
unresolvedMedia.push(media);
|
|
147
|
+
params.logger?.warn?.("sider inbound media missing source url", {
|
|
148
|
+
accountId: params.accountId,
|
|
149
|
+
fileId: media.fileId,
|
|
150
|
+
objectKey: media.objectKey,
|
|
151
|
+
hasGatewayUrl: Boolean(params.gatewayUrl?.trim()),
|
|
152
|
+
});
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
try {
|
|
156
|
+
const fetched = await params.runtime.channel.media.fetchRemoteMedia({
|
|
157
|
+
url: sourceUrl,
|
|
158
|
+
maxBytes,
|
|
159
|
+
});
|
|
160
|
+
const saveResult = await params.runtime.channel.media.saveMediaBuffer(
|
|
161
|
+
fetched.buffer,
|
|
162
|
+
fetched.contentType ?? media.mimeType,
|
|
163
|
+
"inbound",
|
|
164
|
+
maxBytes,
|
|
165
|
+
media.fileName ?? fetched.fileName,
|
|
166
|
+
);
|
|
167
|
+
downloaded.push({
|
|
168
|
+
path: saveResult.path,
|
|
169
|
+
contentType: saveResult.contentType,
|
|
170
|
+
});
|
|
171
|
+
params.logger?.debug?.("sider inbound media saved", {
|
|
172
|
+
accountId: params.accountId,
|
|
173
|
+
sourceUrl,
|
|
174
|
+
savedPath: saveResult.path,
|
|
175
|
+
contentType: saveResult.contentType,
|
|
176
|
+
objectKey: media.objectKey,
|
|
177
|
+
fileId: media.fileId,
|
|
178
|
+
});
|
|
179
|
+
} catch (error) {
|
|
180
|
+
unresolvedMedia.push(media);
|
|
181
|
+
params.logger?.warn?.("sider inbound media download failed", {
|
|
182
|
+
accountId: params.accountId,
|
|
183
|
+
sourceUrl,
|
|
184
|
+
mimeType: media.mimeType,
|
|
185
|
+
objectKey: media.objectKey,
|
|
186
|
+
fileId: media.fileId,
|
|
187
|
+
error: String(error),
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
mediaPayload: buildAgentMediaPayload(downloaded),
|
|
194
|
+
unresolvedMedia,
|
|
195
|
+
};
|
|
196
|
+
}
|