@alfe.ai/openclaw 0.3.1 → 0.4.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/plugin2.cjs +54 -51
- package/dist/plugin2.js +54 -51
- package/package.json +2 -2
package/dist/plugin2.cjs
CHANGED
|
@@ -3090,13 +3090,6 @@ async function registerWithDaemon(client, log, pluginName = "@alfe.ai/openclaw")
|
|
|
3090
3090
|
const pkg = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href)("../package.json");
|
|
3091
3091
|
const DEFAULT_SOCKET_PATH = (0, node_path.join)((0, node_os.homedir)(), ".alfe", "gateway.sock");
|
|
3092
3092
|
let ipcClient = null;
|
|
3093
|
-
/**
|
|
3094
|
-
* The local AI proxy (packages/ai-proxy-local) runs alongside the daemon on a
|
|
3095
|
-
* fixed loopback port in every runtime mode (managed VM / docker / self-hosted),
|
|
3096
|
-
* injects the agent's Alfe key, and forwards arbitrary paths to the cloud AI
|
|
3097
|
-
* proxy. openclaw-chat hardcodes the same host for /__alfe/set-identity.
|
|
3098
|
-
*/
|
|
3099
|
-
const LOCAL_AI_PROXY_URL = process.env.ALFE_AI_PROXY_URL ?? "http://127.0.0.1:18193";
|
|
3100
3093
|
/** Max avatar upload size — mirrors the server-side finalize check. */
|
|
3101
3094
|
const MAX_AVATAR_BYTES = 5 * 1024 * 1024;
|
|
3102
3095
|
/** Image types the avatar presign/finalize flow accepts, keyed by file extension. */
|
|
@@ -3113,6 +3106,23 @@ function avatarMimeFromPath(path) {
|
|
|
3113
3106
|
if (!mimeType) throw new Error(`unsupported image type "${ext || path}" — set_avatar accepts .png, .jpg/.jpeg, or .webp`);
|
|
3114
3107
|
return mimeType;
|
|
3115
3108
|
}
|
|
3109
|
+
/**
|
|
3110
|
+
* Infer an avatar mime type for a remote image — prefer the response's
|
|
3111
|
+
* Content-Type, else the URL path extension. Lets `set_avatar` accept a URL
|
|
3112
|
+
* (e.g. the one `generate_image` returns), which is how an agent naturally
|
|
3113
|
+
* wants to set the picture it just generated.
|
|
3114
|
+
*/
|
|
3115
|
+
function avatarMimeFromUrl(url, contentType) {
|
|
3116
|
+
const ct = (contentType ?? "").split(";")[0].trim().toLowerCase();
|
|
3117
|
+
if (ct === "image/png" || ct === "image/jpeg" || ct === "image/webp") return ct;
|
|
3118
|
+
let pathname = url;
|
|
3119
|
+
try {
|
|
3120
|
+
pathname = new URL(url).pathname;
|
|
3121
|
+
} catch {}
|
|
3122
|
+
const byExt = AVATAR_MIME_BY_EXT[(0, node_path.extname)(pathname).toLowerCase()];
|
|
3123
|
+
if (byExt) return byExt;
|
|
3124
|
+
throw new Error(`could not determine the image type for "${url}" — set_avatar accepts png, jpeg, or webp`);
|
|
3125
|
+
}
|
|
3116
3126
|
function ok(result) {
|
|
3117
3127
|
return { content: [{
|
|
3118
3128
|
type: "text",
|
|
@@ -3120,10 +3130,17 @@ function ok(result) {
|
|
|
3120
3130
|
}] };
|
|
3121
3131
|
}
|
|
3122
3132
|
function errResult(message) {
|
|
3123
|
-
return {
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3133
|
+
return {
|
|
3134
|
+
content: [{
|
|
3135
|
+
type: "text",
|
|
3136
|
+
text: JSON.stringify({ error: message })
|
|
3137
|
+
}],
|
|
3138
|
+
isError: true,
|
|
3139
|
+
details: {
|
|
3140
|
+
status: "error",
|
|
3141
|
+
error: message
|
|
3142
|
+
}
|
|
3143
|
+
};
|
|
3127
3144
|
}
|
|
3128
3145
|
function defineTool(def) {
|
|
3129
3146
|
return {
|
|
@@ -3257,12 +3274,25 @@ function buildVoiceTools(client) {
|
|
|
3257
3274
|
}),
|
|
3258
3275
|
defineTool({
|
|
3259
3276
|
name: "set_avatar",
|
|
3260
|
-
description: "Set THIS agent's
|
|
3261
|
-
parameters: Type.Object({
|
|
3277
|
+
description: "Set THIS agent's OWN profile picture from an image — pass a `url` (e.g. the URL returned by `generate_image`) OR a local file `path` you created/downloaded (png/jpeg/webp, <=5MB). Updates the real profile picture shown in the dashboard, chat, and apps. IMPORTANT: this is how you set YOUR OWN avatar — do NOT use `update_identity` (that edits a CONTACT/person you know, not your own profile) and do NOT use `config set ui.assistant.avatar` (no effect).",
|
|
3278
|
+
parameters: Type.Object({
|
|
3279
|
+
url: Type.Optional(Type.String({ description: "URL of an image to use as the avatar — e.g. the `url` returned by `generate_image`. png/jpeg/webp, <=5MB." })),
|
|
3280
|
+
path: Type.Optional(Type.String({ description: "Absolute path to a LOCAL image file (.png, .jpg/.jpeg, or .webp, <=5MB). Use `url` instead if you have a URL." }))
|
|
3281
|
+
}),
|
|
3262
3282
|
handler: async (params) => {
|
|
3283
|
+
const url = params.url;
|
|
3263
3284
|
const path = params.path;
|
|
3264
|
-
|
|
3265
|
-
|
|
3285
|
+
let bytes;
|
|
3286
|
+
let mimeType;
|
|
3287
|
+
if (url) {
|
|
3288
|
+
const dl = await fetch(url);
|
|
3289
|
+
if (!dl.ok) throw new Error(`failed to download the image from the url (${String(dl.status)})`);
|
|
3290
|
+
bytes = Buffer.from(await dl.arrayBuffer());
|
|
3291
|
+
mimeType = avatarMimeFromUrl(url, dl.headers.get("content-type"));
|
|
3292
|
+
} else if (path) {
|
|
3293
|
+
mimeType = avatarMimeFromPath(path);
|
|
3294
|
+
bytes = await (0, node_fs_promises.readFile)(path);
|
|
3295
|
+
} else throw new Error("provide either `url` (an image URL, e.g. from generate_image) or `path` (a local image file)");
|
|
3266
3296
|
if (bytes.length > MAX_AVATAR_BYTES) throw new Error(`image is ${String(bytes.length)} bytes — the avatar limit is ${String(MAX_AVATAR_BYTES)} bytes (5MB)`);
|
|
3267
3297
|
const { uploadUrl, s3Key } = await client.presignAvatar({
|
|
3268
3298
|
mimeType,
|
|
@@ -3271,7 +3301,7 @@ function buildVoiceTools(client) {
|
|
|
3271
3301
|
const putRes = await fetch(uploadUrl, {
|
|
3272
3302
|
method: "PUT",
|
|
3273
3303
|
headers: { "Content-Type": mimeType },
|
|
3274
|
-
body: bytes
|
|
3304
|
+
body: new Uint8Array(bytes)
|
|
3275
3305
|
});
|
|
3276
3306
|
if (!putRes.ok) throw new Error(`failed to upload the avatar image (${String(putRes.status)})`);
|
|
3277
3307
|
return {
|
|
@@ -3283,49 +3313,22 @@ function buildVoiceTools(client) {
|
|
|
3283
3313
|
}),
|
|
3284
3314
|
defineTool({
|
|
3285
3315
|
name: "generate_image",
|
|
3286
|
-
description: "Generate an image from a text prompt. Returns a URL to the generated PNG. To show the image to the user, embed it in your reply as markdown: . Optional `model` picks the image model (default gpt-image-1).",
|
|
3316
|
+
description: "Generate an image from a text prompt. Returns a URL to the generated PNG. To show the image to the user, embed it in your reply as markdown: . To set the generated image as YOUR OWN profile picture, pass its url to `set_avatar` (NOT update_identity, which edits other people you know). Or skip this and use `generate_avatar` to generate + set your avatar in one step. Optional `model` picks the image model (default gpt-image-1). Generation is synchronous and may take up to ~30s; if it times out, retry or use a standard size.",
|
|
3287
3317
|
parameters: Type.Object({
|
|
3288
3318
|
prompt: Type.String({ description: "Text description of the image to generate." }),
|
|
3289
3319
|
model: Type.Optional(Type.String({ description: "Image model id (default \"gpt-image-1\"). Others may be available, e.g. dall-e-3." })),
|
|
3290
|
-
size: Type.Optional(Type.String({ description: "Image size
|
|
3320
|
+
size: Type.Optional(Type.String({ description: "Image size. For the default gpt-image-1 use one of \"1024x1024\" (square), \"1536x1024\" (landscape), \"1024x1536\" (portrait), or \"auto\" — NOT \"512x512\". Other models accept their own sizes. Omit to use the provider default." })),
|
|
3291
3321
|
quality: Type.Optional(Type.String({ description: "Image quality hint (provider-dependent)." }))
|
|
3292
3322
|
}),
|
|
3293
3323
|
handler: async (params) => {
|
|
3294
|
-
const
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
method: "POST",
|
|
3300
|
-
headers: { "Content-Type": "application/json" },
|
|
3301
|
-
body: JSON.stringify({
|
|
3302
|
-
model,
|
|
3303
|
-
prompt,
|
|
3304
|
-
...size ? { size } : {},
|
|
3305
|
-
...quality ? { quality } : {}
|
|
3306
|
-
})
|
|
3307
|
-
});
|
|
3308
|
-
const genJson = await genRes.json().catch(() => ({}));
|
|
3309
|
-
if (!genRes.ok) throw new Error(`image generation failed (${String(genRes.status)}): ${genJson.message ?? genJson.error ?? "unknown error"}`);
|
|
3310
|
-
const b64 = genJson.data?.[0]?.b64_json;
|
|
3311
|
-
if (!b64) throw new Error("image generation returned no image data");
|
|
3312
|
-
const buffer = Buffer.from(b64, "base64");
|
|
3313
|
-
const filename = `generated-image-${String(Date.now())}.png`;
|
|
3314
|
-
const { attachments } = await client.presignAttachments([{
|
|
3315
|
-
filename,
|
|
3316
|
-
mimeType: "image/png",
|
|
3317
|
-
size: buffer.length
|
|
3318
|
-
}]);
|
|
3319
|
-
if (attachments.length === 0) throw new Error("failed to presign upload for the generated image");
|
|
3320
|
-
const att = attachments[0];
|
|
3321
|
-
const putRes = await fetch(att.uploadUrl, {
|
|
3322
|
-
method: "PUT",
|
|
3323
|
-
headers: { "Content-Type": "image/png" },
|
|
3324
|
-
body: buffer
|
|
3324
|
+
const { imageUrl, model } = await client.generateImage({
|
|
3325
|
+
prompt: params.prompt,
|
|
3326
|
+
model: params.model,
|
|
3327
|
+
size: params.size,
|
|
3328
|
+
quality: params.quality
|
|
3325
3329
|
});
|
|
3326
|
-
if (!putRes.ok) throw new Error(`failed to upload the generated image (${String(putRes.status)})`);
|
|
3327
3330
|
return {
|
|
3328
|
-
imageUrl
|
|
3331
|
+
imageUrl,
|
|
3329
3332
|
model,
|
|
3330
3333
|
instruction: "Show this image to the user by including it in your reply as markdown: ."
|
|
3331
3334
|
};
|
package/dist/plugin2.js
CHANGED
|
@@ -3091,13 +3091,6 @@ async function registerWithDaemon(client, log, pluginName = "@alfe.ai/openclaw")
|
|
|
3091
3091
|
const pkg = createRequire(import.meta.url)("../package.json");
|
|
3092
3092
|
const DEFAULT_SOCKET_PATH = join(homedir(), ".alfe", "gateway.sock");
|
|
3093
3093
|
let ipcClient = null;
|
|
3094
|
-
/**
|
|
3095
|
-
* The local AI proxy (packages/ai-proxy-local) runs alongside the daemon on a
|
|
3096
|
-
* fixed loopback port in every runtime mode (managed VM / docker / self-hosted),
|
|
3097
|
-
* injects the agent's Alfe key, and forwards arbitrary paths to the cloud AI
|
|
3098
|
-
* proxy. openclaw-chat hardcodes the same host for /__alfe/set-identity.
|
|
3099
|
-
*/
|
|
3100
|
-
const LOCAL_AI_PROXY_URL = process.env.ALFE_AI_PROXY_URL ?? "http://127.0.0.1:18193";
|
|
3101
3094
|
/** Max avatar upload size — mirrors the server-side finalize check. */
|
|
3102
3095
|
const MAX_AVATAR_BYTES = 5 * 1024 * 1024;
|
|
3103
3096
|
/** Image types the avatar presign/finalize flow accepts, keyed by file extension. */
|
|
@@ -3114,6 +3107,23 @@ function avatarMimeFromPath(path) {
|
|
|
3114
3107
|
if (!mimeType) throw new Error(`unsupported image type "${ext || path}" — set_avatar accepts .png, .jpg/.jpeg, or .webp`);
|
|
3115
3108
|
return mimeType;
|
|
3116
3109
|
}
|
|
3110
|
+
/**
|
|
3111
|
+
* Infer an avatar mime type for a remote image — prefer the response's
|
|
3112
|
+
* Content-Type, else the URL path extension. Lets `set_avatar` accept a URL
|
|
3113
|
+
* (e.g. the one `generate_image` returns), which is how an agent naturally
|
|
3114
|
+
* wants to set the picture it just generated.
|
|
3115
|
+
*/
|
|
3116
|
+
function avatarMimeFromUrl(url, contentType) {
|
|
3117
|
+
const ct = (contentType ?? "").split(";")[0].trim().toLowerCase();
|
|
3118
|
+
if (ct === "image/png" || ct === "image/jpeg" || ct === "image/webp") return ct;
|
|
3119
|
+
let pathname = url;
|
|
3120
|
+
try {
|
|
3121
|
+
pathname = new URL(url).pathname;
|
|
3122
|
+
} catch {}
|
|
3123
|
+
const byExt = AVATAR_MIME_BY_EXT[extname(pathname).toLowerCase()];
|
|
3124
|
+
if (byExt) return byExt;
|
|
3125
|
+
throw new Error(`could not determine the image type for "${url}" — set_avatar accepts png, jpeg, or webp`);
|
|
3126
|
+
}
|
|
3117
3127
|
function ok(result) {
|
|
3118
3128
|
return { content: [{
|
|
3119
3129
|
type: "text",
|
|
@@ -3121,10 +3131,17 @@ function ok(result) {
|
|
|
3121
3131
|
}] };
|
|
3122
3132
|
}
|
|
3123
3133
|
function errResult(message) {
|
|
3124
|
-
return {
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3134
|
+
return {
|
|
3135
|
+
content: [{
|
|
3136
|
+
type: "text",
|
|
3137
|
+
text: JSON.stringify({ error: message })
|
|
3138
|
+
}],
|
|
3139
|
+
isError: true,
|
|
3140
|
+
details: {
|
|
3141
|
+
status: "error",
|
|
3142
|
+
error: message
|
|
3143
|
+
}
|
|
3144
|
+
};
|
|
3128
3145
|
}
|
|
3129
3146
|
function defineTool(def) {
|
|
3130
3147
|
return {
|
|
@@ -3258,12 +3275,25 @@ function buildVoiceTools(client) {
|
|
|
3258
3275
|
}),
|
|
3259
3276
|
defineTool({
|
|
3260
3277
|
name: "set_avatar",
|
|
3261
|
-
description: "Set THIS agent's
|
|
3262
|
-
parameters: Type.Object({
|
|
3278
|
+
description: "Set THIS agent's OWN profile picture from an image — pass a `url` (e.g. the URL returned by `generate_image`) OR a local file `path` you created/downloaded (png/jpeg/webp, <=5MB). Updates the real profile picture shown in the dashboard, chat, and apps. IMPORTANT: this is how you set YOUR OWN avatar — do NOT use `update_identity` (that edits a CONTACT/person you know, not your own profile) and do NOT use `config set ui.assistant.avatar` (no effect).",
|
|
3279
|
+
parameters: Type.Object({
|
|
3280
|
+
url: Type.Optional(Type.String({ description: "URL of an image to use as the avatar — e.g. the `url` returned by `generate_image`. png/jpeg/webp, <=5MB." })),
|
|
3281
|
+
path: Type.Optional(Type.String({ description: "Absolute path to a LOCAL image file (.png, .jpg/.jpeg, or .webp, <=5MB). Use `url` instead if you have a URL." }))
|
|
3282
|
+
}),
|
|
3263
3283
|
handler: async (params) => {
|
|
3284
|
+
const url = params.url;
|
|
3264
3285
|
const path = params.path;
|
|
3265
|
-
|
|
3266
|
-
|
|
3286
|
+
let bytes;
|
|
3287
|
+
let mimeType;
|
|
3288
|
+
if (url) {
|
|
3289
|
+
const dl = await fetch(url);
|
|
3290
|
+
if (!dl.ok) throw new Error(`failed to download the image from the url (${String(dl.status)})`);
|
|
3291
|
+
bytes = Buffer.from(await dl.arrayBuffer());
|
|
3292
|
+
mimeType = avatarMimeFromUrl(url, dl.headers.get("content-type"));
|
|
3293
|
+
} else if (path) {
|
|
3294
|
+
mimeType = avatarMimeFromPath(path);
|
|
3295
|
+
bytes = await readFile(path);
|
|
3296
|
+
} else throw new Error("provide either `url` (an image URL, e.g. from generate_image) or `path` (a local image file)");
|
|
3267
3297
|
if (bytes.length > MAX_AVATAR_BYTES) throw new Error(`image is ${String(bytes.length)} bytes — the avatar limit is ${String(MAX_AVATAR_BYTES)} bytes (5MB)`);
|
|
3268
3298
|
const { uploadUrl, s3Key } = await client.presignAvatar({
|
|
3269
3299
|
mimeType,
|
|
@@ -3272,7 +3302,7 @@ function buildVoiceTools(client) {
|
|
|
3272
3302
|
const putRes = await fetch(uploadUrl, {
|
|
3273
3303
|
method: "PUT",
|
|
3274
3304
|
headers: { "Content-Type": mimeType },
|
|
3275
|
-
body: bytes
|
|
3305
|
+
body: new Uint8Array(bytes)
|
|
3276
3306
|
});
|
|
3277
3307
|
if (!putRes.ok) throw new Error(`failed to upload the avatar image (${String(putRes.status)})`);
|
|
3278
3308
|
return {
|
|
@@ -3284,49 +3314,22 @@ function buildVoiceTools(client) {
|
|
|
3284
3314
|
}),
|
|
3285
3315
|
defineTool({
|
|
3286
3316
|
name: "generate_image",
|
|
3287
|
-
description: "Generate an image from a text prompt. Returns a URL to the generated PNG. To show the image to the user, embed it in your reply as markdown: . Optional `model` picks the image model (default gpt-image-1).",
|
|
3317
|
+
description: "Generate an image from a text prompt. Returns a URL to the generated PNG. To show the image to the user, embed it in your reply as markdown: . To set the generated image as YOUR OWN profile picture, pass its url to `set_avatar` (NOT update_identity, which edits other people you know). Or skip this and use `generate_avatar` to generate + set your avatar in one step. Optional `model` picks the image model (default gpt-image-1). Generation is synchronous and may take up to ~30s; if it times out, retry or use a standard size.",
|
|
3288
3318
|
parameters: Type.Object({
|
|
3289
3319
|
prompt: Type.String({ description: "Text description of the image to generate." }),
|
|
3290
3320
|
model: Type.Optional(Type.String({ description: "Image model id (default \"gpt-image-1\"). Others may be available, e.g. dall-e-3." })),
|
|
3291
|
-
size: Type.Optional(Type.String({ description: "Image size
|
|
3321
|
+
size: Type.Optional(Type.String({ description: "Image size. For the default gpt-image-1 use one of \"1024x1024\" (square), \"1536x1024\" (landscape), \"1024x1536\" (portrait), or \"auto\" — NOT \"512x512\". Other models accept their own sizes. Omit to use the provider default." })),
|
|
3292
3322
|
quality: Type.Optional(Type.String({ description: "Image quality hint (provider-dependent)." }))
|
|
3293
3323
|
}),
|
|
3294
3324
|
handler: async (params) => {
|
|
3295
|
-
const
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
method: "POST",
|
|
3301
|
-
headers: { "Content-Type": "application/json" },
|
|
3302
|
-
body: JSON.stringify({
|
|
3303
|
-
model,
|
|
3304
|
-
prompt,
|
|
3305
|
-
...size ? { size } : {},
|
|
3306
|
-
...quality ? { quality } : {}
|
|
3307
|
-
})
|
|
3308
|
-
});
|
|
3309
|
-
const genJson = await genRes.json().catch(() => ({}));
|
|
3310
|
-
if (!genRes.ok) throw new Error(`image generation failed (${String(genRes.status)}): ${genJson.message ?? genJson.error ?? "unknown error"}`);
|
|
3311
|
-
const b64 = genJson.data?.[0]?.b64_json;
|
|
3312
|
-
if (!b64) throw new Error("image generation returned no image data");
|
|
3313
|
-
const buffer = Buffer.from(b64, "base64");
|
|
3314
|
-
const filename = `generated-image-${String(Date.now())}.png`;
|
|
3315
|
-
const { attachments } = await client.presignAttachments([{
|
|
3316
|
-
filename,
|
|
3317
|
-
mimeType: "image/png",
|
|
3318
|
-
size: buffer.length
|
|
3319
|
-
}]);
|
|
3320
|
-
if (attachments.length === 0) throw new Error("failed to presign upload for the generated image");
|
|
3321
|
-
const att = attachments[0];
|
|
3322
|
-
const putRes = await fetch(att.uploadUrl, {
|
|
3323
|
-
method: "PUT",
|
|
3324
|
-
headers: { "Content-Type": "image/png" },
|
|
3325
|
-
body: buffer
|
|
3325
|
+
const { imageUrl, model } = await client.generateImage({
|
|
3326
|
+
prompt: params.prompt,
|
|
3327
|
+
model: params.model,
|
|
3328
|
+
size: params.size,
|
|
3329
|
+
quality: params.quality
|
|
3326
3330
|
});
|
|
3327
|
-
if (!putRes.ok) throw new Error(`failed to upload the generated image (${String(putRes.status)})`);
|
|
3328
3331
|
return {
|
|
3329
|
-
imageUrl
|
|
3332
|
+
imageUrl,
|
|
3330
3333
|
model,
|
|
3331
3334
|
instruction: "Show this image to the user by including it in your reply as markdown: ."
|
|
3332
3335
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alfe.ai/openclaw",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "OpenClaw plugin for Alfe — connects to local gateway daemon via IPC for integration management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"@auriclabs/logger": "^0.1.1",
|
|
27
27
|
"@sinclair/typebox": "^0.34.48",
|
|
28
28
|
"zod": "^4.1.5",
|
|
29
|
-
"@alfe.ai/agent-api-client": "^0.
|
|
29
|
+
"@alfe.ai/agent-api-client": "^0.5.0",
|
|
30
30
|
"@alfe.ai/config": "^0.3.0"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|