@ynhcj/xiaoyi-channel 0.0.1-beta
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/index.d.ts +16 -0
- package/dist/index.js +21 -0
- package/dist/src/bot.d.ts +17 -0
- package/dist/src/bot.js +260 -0
- package/dist/src/channel.d.ts +6 -0
- package/dist/src/channel.js +87 -0
- package/dist/src/client.d.ts +35 -0
- package/dist/src/client.js +147 -0
- package/dist/src/config-schema.d.ts +54 -0
- package/dist/src/config-schema.js +55 -0
- package/dist/src/config.d.ts +17 -0
- package/dist/src/config.js +45 -0
- package/dist/src/file-download.d.ts +17 -0
- package/dist/src/file-download.js +53 -0
- package/dist/src/file-upload.d.ts +23 -0
- package/dist/src/file-upload.js +129 -0
- package/dist/src/formatter.d.ts +77 -0
- package/dist/src/formatter.js +252 -0
- package/dist/src/heartbeat.d.ts +39 -0
- package/dist/src/heartbeat.js +102 -0
- package/dist/src/monitor.d.ts +17 -0
- package/dist/src/monitor.js +191 -0
- package/dist/src/onboarding.d.ts +6 -0
- package/dist/src/onboarding.js +173 -0
- package/dist/src/outbound.d.ts +6 -0
- package/dist/src/outbound.js +208 -0
- package/dist/src/parser.d.ts +49 -0
- package/dist/src/parser.js +99 -0
- package/dist/src/push.d.ts +23 -0
- package/dist/src/push.js +146 -0
- package/dist/src/reply-dispatcher.d.ts +15 -0
- package/dist/src/reply-dispatcher.js +160 -0
- package/dist/src/runtime.d.ts +11 -0
- package/dist/src/runtime.js +18 -0
- package/dist/src/tools/calendar-tool.d.ts +6 -0
- package/dist/src/tools/calendar-tool.js +167 -0
- package/dist/src/tools/location-tool.d.ts +5 -0
- package/dist/src/tools/location-tool.js +136 -0
- package/dist/src/tools/note-tool.d.ts +5 -0
- package/dist/src/tools/note-tool.js +130 -0
- package/dist/src/tools/search-note-tool.d.ts +5 -0
- package/dist/src/tools/search-note-tool.js +130 -0
- package/dist/src/tools/session-manager.d.ts +29 -0
- package/dist/src/tools/session-manager.js +74 -0
- package/dist/src/tools/tool-context.d.ts +16 -0
- package/dist/src/tools/tool-context.js +7 -0
- package/dist/src/types.d.ts +163 -0
- package/dist/src/types.js +2 -0
- package/dist/src/utils/config-manager.d.ts +26 -0
- package/dist/src/utils/config-manager.js +56 -0
- package/dist/src/utils/crypto.d.ts +8 -0
- package/dist/src/utils/crypto.js +14 -0
- package/dist/src/utils/logger.d.ts +6 -0
- package/dist/src/utils/logger.js +34 -0
- package/dist/src/utils/session.d.ts +34 -0
- package/dist/src/utils/session.js +50 -0
- package/dist/src/websocket.d.ts +123 -0
- package/dist/src/websocket.js +547 -0
- package/openclaw.plugin.json +10 -0
- package/package.json +71 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// JSON Schema definition for XY channel configuration
|
|
2
|
+
export const xyConfigSchema = {
|
|
3
|
+
type: "object",
|
|
4
|
+
properties: {
|
|
5
|
+
enabled: {
|
|
6
|
+
type: "boolean",
|
|
7
|
+
description: "Enable/disable the XY channel",
|
|
8
|
+
default: false,
|
|
9
|
+
},
|
|
10
|
+
wsUrl1: {
|
|
11
|
+
type: "string",
|
|
12
|
+
description: "Primary WebSocket URL",
|
|
13
|
+
default: "ws://localhost:8765/ws/link",
|
|
14
|
+
},
|
|
15
|
+
wsUrl2: {
|
|
16
|
+
type: "string",
|
|
17
|
+
description: "Secondary WebSocket URL",
|
|
18
|
+
default: "ws://localhost:8768/ws/link",
|
|
19
|
+
},
|
|
20
|
+
apiKey: {
|
|
21
|
+
type: "string",
|
|
22
|
+
description: "API key for authentication",
|
|
23
|
+
},
|
|
24
|
+
uid: {
|
|
25
|
+
type: "string",
|
|
26
|
+
description: "User ID for file upload",
|
|
27
|
+
},
|
|
28
|
+
agentId: {
|
|
29
|
+
type: "string",
|
|
30
|
+
description: "Agent ID for this bot instance",
|
|
31
|
+
},
|
|
32
|
+
apiId: {
|
|
33
|
+
type: "string",
|
|
34
|
+
description: "API ID for push messages",
|
|
35
|
+
},
|
|
36
|
+
pushId: {
|
|
37
|
+
type: "string",
|
|
38
|
+
description: "Push ID for push messages",
|
|
39
|
+
},
|
|
40
|
+
fileUploadUrl: {
|
|
41
|
+
type: "string",
|
|
42
|
+
description: "Base URL for file upload service",
|
|
43
|
+
default: "http://localhost:8767",
|
|
44
|
+
},
|
|
45
|
+
pushUrl: {
|
|
46
|
+
type: "string",
|
|
47
|
+
description: "URL for push message service",
|
|
48
|
+
},
|
|
49
|
+
defaultSessionId: {
|
|
50
|
+
type: "string",
|
|
51
|
+
description: "Default session ID for push notifications (used when no target is specified, e.g., in cron jobs)",
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
required: ["apiKey", "agentId", "uid", "apiId", "pushId"],
|
|
55
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ClawdbotConfig } from "openclaw/plugin-sdk";
|
|
2
|
+
import type { XYChannelConfig } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Resolve and validate Xiaoyi channel configuration from ClawdbotConfig.
|
|
5
|
+
* Simplified version - only supports single account (no multi-account management).
|
|
6
|
+
*/
|
|
7
|
+
export declare function resolveXYConfig(cfg: ClawdbotConfig): XYChannelConfig;
|
|
8
|
+
/**
|
|
9
|
+
* List available account IDs.
|
|
10
|
+
* Simplified - always returns single account "default".
|
|
11
|
+
*/
|
|
12
|
+
export declare function listXYAccountIds(): string[];
|
|
13
|
+
/**
|
|
14
|
+
* Get default account ID.
|
|
15
|
+
* Simplified - always returns "default".
|
|
16
|
+
*/
|
|
17
|
+
export declare function getDefaultXYAccountId(): string;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve and validate Xiaoyi channel configuration from ClawdbotConfig.
|
|
3
|
+
* Simplified version - only supports single account (no multi-account management).
|
|
4
|
+
*/
|
|
5
|
+
export function resolveXYConfig(cfg) {
|
|
6
|
+
const xyConfig = cfg.channels?.["xiaoyi-channel"];
|
|
7
|
+
if (!xyConfig) {
|
|
8
|
+
throw new Error("Xiaoyi channel configuration not found in openclaw.json");
|
|
9
|
+
}
|
|
10
|
+
// Validate required fields
|
|
11
|
+
const required = ["apiKey", "agentId", "uid", "apiId", "pushId"];
|
|
12
|
+
for (const field of required) {
|
|
13
|
+
if (!xyConfig[field]) {
|
|
14
|
+
throw new Error(`XY channel configuration missing required field: ${field}`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
// Return configuration with defaults
|
|
18
|
+
return {
|
|
19
|
+
enabled: xyConfig.enabled ?? false,
|
|
20
|
+
wsUrl1: xyConfig.wsUrl1 ?? "ws://localhost:8765/ws/link",
|
|
21
|
+
wsUrl2: xyConfig.wsUrl2 ?? "ws://localhost:8768/ws/link",
|
|
22
|
+
apiKey: xyConfig.apiKey,
|
|
23
|
+
uid: xyConfig.uid,
|
|
24
|
+
agentId: xyConfig.agentId,
|
|
25
|
+
apiId: xyConfig.apiId,
|
|
26
|
+
pushId: xyConfig.pushId,
|
|
27
|
+
fileUploadUrl: xyConfig.fileUploadUrl ?? "http://localhost:8767",
|
|
28
|
+
pushUrl: xyConfig.pushUrl,
|
|
29
|
+
defaultSessionId: xyConfig.defaultSessionId,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* List available account IDs.
|
|
34
|
+
* Simplified - always returns single account "default".
|
|
35
|
+
*/
|
|
36
|
+
export function listXYAccountIds() {
|
|
37
|
+
return ["default"];
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Get default account ID.
|
|
41
|
+
* Simplified - always returns "default".
|
|
42
|
+
*/
|
|
43
|
+
export function getDefaultXYAccountId() {
|
|
44
|
+
return "default";
|
|
45
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Download a file from URL to local path.
|
|
3
|
+
*/
|
|
4
|
+
export declare function downloadFile(url: string, destPath: string): Promise<void>;
|
|
5
|
+
/**
|
|
6
|
+
* Download files from A2A file parts.
|
|
7
|
+
* Returns array of local file paths.
|
|
8
|
+
*/
|
|
9
|
+
export declare function downloadFilesFromParts(fileParts: Array<{
|
|
10
|
+
name: string;
|
|
11
|
+
mimeType: string;
|
|
12
|
+
uri: string;
|
|
13
|
+
}>, tempDir?: string): Promise<Array<{
|
|
14
|
+
path: string;
|
|
15
|
+
name: string;
|
|
16
|
+
mimeType: string;
|
|
17
|
+
}>>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// File download utilities
|
|
2
|
+
import fetch from "node-fetch";
|
|
3
|
+
import fs from "fs/promises";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { logger } from "./utils/logger.js";
|
|
6
|
+
/**
|
|
7
|
+
* Download a file from URL to local path.
|
|
8
|
+
*/
|
|
9
|
+
export async function downloadFile(url, destPath) {
|
|
10
|
+
logger.debug(`Downloading file from ${url} to ${destPath}`);
|
|
11
|
+
try {
|
|
12
|
+
const response = await fetch(url);
|
|
13
|
+
if (!response.ok) {
|
|
14
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
15
|
+
}
|
|
16
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
17
|
+
const buffer = Buffer.from(arrayBuffer);
|
|
18
|
+
await fs.writeFile(destPath, buffer);
|
|
19
|
+
logger.debug(`File downloaded successfully: ${destPath}`);
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
logger.error(`Failed to download file from ${url}:`, error);
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Download files from A2A file parts.
|
|
28
|
+
* Returns array of local file paths.
|
|
29
|
+
*/
|
|
30
|
+
export async function downloadFilesFromParts(fileParts, tempDir = "/tmp/xy_channel") {
|
|
31
|
+
// Create temp directory if it doesn't exist
|
|
32
|
+
await fs.mkdir(tempDir, { recursive: true });
|
|
33
|
+
const downloadedFiles = [];
|
|
34
|
+
for (const filePart of fileParts) {
|
|
35
|
+
const { name, mimeType, uri } = filePart;
|
|
36
|
+
// Generate safe file name
|
|
37
|
+
const safeName = name.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
38
|
+
const destPath = path.join(tempDir, `${Date.now()}_${safeName}`);
|
|
39
|
+
try {
|
|
40
|
+
await downloadFile(uri, destPath);
|
|
41
|
+
downloadedFiles.push({
|
|
42
|
+
path: destPath,
|
|
43
|
+
name,
|
|
44
|
+
mimeType,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
logger.error(`Failed to download file ${name}:`, error);
|
|
49
|
+
// Continue with other files
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return downloadedFiles;
|
|
53
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service for uploading files to XY file storage.
|
|
3
|
+
* Implements three-phase upload: prepare → upload → complete.
|
|
4
|
+
*/
|
|
5
|
+
export declare class XYFileUploadService {
|
|
6
|
+
private baseUrl;
|
|
7
|
+
private apiKey;
|
|
8
|
+
private uid;
|
|
9
|
+
constructor(baseUrl: string, apiKey: string, uid: string);
|
|
10
|
+
/**
|
|
11
|
+
* Upload a file using the three-phase process.
|
|
12
|
+
* Returns the objectId (as fileId) for use in A2A messages.
|
|
13
|
+
*/
|
|
14
|
+
uploadFile(filePath: string, objectType?: string): Promise<string>;
|
|
15
|
+
/**
|
|
16
|
+
* Upload multiple files and return their file IDs.
|
|
17
|
+
*/
|
|
18
|
+
uploadFiles(filePaths: string[], objectType?: string): Promise<Array<{
|
|
19
|
+
filePath: string;
|
|
20
|
+
fileId: string;
|
|
21
|
+
fileName: string;
|
|
22
|
+
}>>;
|
|
23
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// Three-phase file upload service
|
|
2
|
+
// OSMS file upload implementation
|
|
3
|
+
import fetch from "node-fetch";
|
|
4
|
+
import fs from "fs/promises";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import { calculateSHA256 } from "./utils/crypto.js";
|
|
7
|
+
/**
|
|
8
|
+
* Service for uploading files to XY file storage.
|
|
9
|
+
* Implements three-phase upload: prepare → upload → complete.
|
|
10
|
+
*/
|
|
11
|
+
export class XYFileUploadService {
|
|
12
|
+
baseUrl;
|
|
13
|
+
apiKey;
|
|
14
|
+
uid;
|
|
15
|
+
constructor(baseUrl, apiKey, uid) {
|
|
16
|
+
this.baseUrl = baseUrl;
|
|
17
|
+
this.apiKey = apiKey;
|
|
18
|
+
this.uid = uid;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Upload a file using the three-phase process.
|
|
22
|
+
* Returns the objectId (as fileId) for use in A2A messages.
|
|
23
|
+
*/
|
|
24
|
+
async uploadFile(filePath, objectType = "TEMPORARY_MATERIAL_DOC") {
|
|
25
|
+
console.log(`[XY File Upload] Starting file upload: ${filePath}`);
|
|
26
|
+
try {
|
|
27
|
+
// Read file
|
|
28
|
+
const fileBuffer = await fs.readFile(filePath);
|
|
29
|
+
const fileName = path.basename(filePath);
|
|
30
|
+
const fileSha256 = calculateSHA256(fileBuffer);
|
|
31
|
+
const fileSize = fileBuffer.length;
|
|
32
|
+
// Phase 1: Prepare
|
|
33
|
+
console.log(`[XY File Upload] Phase 1: Prepare upload for ${fileName}`);
|
|
34
|
+
const prepareResp = await fetch(`${this.baseUrl}/osms/v1/file/manager/prepare`, {
|
|
35
|
+
method: "POST",
|
|
36
|
+
headers: {
|
|
37
|
+
"Content-Type": "application/json",
|
|
38
|
+
"x-uid": this.uid,
|
|
39
|
+
"x-api-key": this.apiKey,
|
|
40
|
+
"x-request-from": "openclaw",
|
|
41
|
+
},
|
|
42
|
+
body: JSON.stringify({
|
|
43
|
+
objectType,
|
|
44
|
+
fileName,
|
|
45
|
+
fileSha256,
|
|
46
|
+
fileSize,
|
|
47
|
+
fileOwnerInfo: {
|
|
48
|
+
uid: this.uid,
|
|
49
|
+
teamId: this.uid,
|
|
50
|
+
},
|
|
51
|
+
useEdge: false,
|
|
52
|
+
}),
|
|
53
|
+
});
|
|
54
|
+
if (!prepareResp.ok) {
|
|
55
|
+
throw new Error(`Prepare failed: HTTP ${prepareResp.status}`);
|
|
56
|
+
}
|
|
57
|
+
const prepareData = await prepareResp.json();
|
|
58
|
+
console.log(`[XY File Upload] Prepare response:`, JSON.stringify(prepareData, null, 2));
|
|
59
|
+
if (prepareData.code !== "0") {
|
|
60
|
+
throw new Error(`Prepare failed: ${prepareData.desc}`);
|
|
61
|
+
}
|
|
62
|
+
const { objectId, draftId, uploadInfos } = prepareData;
|
|
63
|
+
console.log(`[XY File Upload] Prepare complete: objectId=${objectId}, draftId=${draftId}`);
|
|
64
|
+
// Phase 2: Upload
|
|
65
|
+
console.log(`[XY File Upload] Phase 2: Upload file data`);
|
|
66
|
+
const uploadInfo = uploadInfos[0]; // Single-part upload
|
|
67
|
+
const uploadResp = await fetch(uploadInfo.url, {
|
|
68
|
+
method: uploadInfo.method,
|
|
69
|
+
headers: uploadInfo.headers,
|
|
70
|
+
body: fileBuffer,
|
|
71
|
+
});
|
|
72
|
+
console.log(`[XY File Upload] Upload response status: ${uploadResp.status}, url: ${uploadInfo.url}`);
|
|
73
|
+
console.log(`[XY File Upload] Upload response headers:`, JSON.stringify(Object.fromEntries(uploadResp.headers.entries()), null, 2));
|
|
74
|
+
if (!uploadResp.ok) {
|
|
75
|
+
const uploadErrorText = await uploadResp.text();
|
|
76
|
+
console.log(`[XY File Upload] Upload error response:`, uploadErrorText);
|
|
77
|
+
throw new Error(`Upload failed: HTTP ${uploadResp.status}`);
|
|
78
|
+
}
|
|
79
|
+
console.log(`[XY File Upload] Upload complete`);
|
|
80
|
+
// Phase 3: Complete
|
|
81
|
+
console.log(`[XY File Upload] Phase 3: Complete upload`);
|
|
82
|
+
const completeResp = await fetch(`${this.baseUrl}/osms/v1/file/manager/complete`, {
|
|
83
|
+
method: "POST",
|
|
84
|
+
headers: {
|
|
85
|
+
"Content-Type": "application/json",
|
|
86
|
+
"x-uid": this.uid,
|
|
87
|
+
"x-api-key": this.apiKey,
|
|
88
|
+
"x-request-from": "openclaw",
|
|
89
|
+
},
|
|
90
|
+
body: JSON.stringify({
|
|
91
|
+
objectId,
|
|
92
|
+
draftId,
|
|
93
|
+
}),
|
|
94
|
+
});
|
|
95
|
+
if (!completeResp.ok) {
|
|
96
|
+
throw new Error(`Complete failed: HTTP ${completeResp.status}`);
|
|
97
|
+
}
|
|
98
|
+
const completeData = await completeResp.json();
|
|
99
|
+
console.log(`[XY File Upload] Complete response:`, JSON.stringify(completeData, null, 2));
|
|
100
|
+
console.log(`[XY File Upload] File upload successful: ${fileName} → objectId=${objectId}`);
|
|
101
|
+
return objectId;
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
console.error(`[XY File Upload] File upload failed for ${filePath}:`, error);
|
|
105
|
+
return "";
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Upload multiple files and return their file IDs.
|
|
110
|
+
*/
|
|
111
|
+
async uploadFiles(filePaths, objectType = "TEMPORARY_MATERIAL_DOC") {
|
|
112
|
+
const results = [];
|
|
113
|
+
for (const filePath of filePaths) {
|
|
114
|
+
try {
|
|
115
|
+
const fileId = await this.uploadFile(filePath, objectType);
|
|
116
|
+
results.push({
|
|
117
|
+
filePath,
|
|
118
|
+
fileId,
|
|
119
|
+
fileName: path.basename(filePath),
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
console.error(`[XY File Upload] Failed to upload ${filePath}, skipping:`, error);
|
|
124
|
+
// Continue with other files
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return results;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { XYChannelConfig, A2ACommand } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Parameters for sending an A2A response.
|
|
4
|
+
*/
|
|
5
|
+
export interface SendA2AResponseParams {
|
|
6
|
+
config: XYChannelConfig;
|
|
7
|
+
sessionId: string;
|
|
8
|
+
taskId: string;
|
|
9
|
+
messageId: string;
|
|
10
|
+
text?: string;
|
|
11
|
+
append: boolean;
|
|
12
|
+
final: boolean;
|
|
13
|
+
files?: Array<{
|
|
14
|
+
fileName: string;
|
|
15
|
+
fileType: string;
|
|
16
|
+
fileId: string;
|
|
17
|
+
}>;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Send an A2A artifact update response.
|
|
21
|
+
*/
|
|
22
|
+
export declare function sendA2AResponse(params: SendA2AResponseParams): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Parameters for sending a status update.
|
|
25
|
+
*/
|
|
26
|
+
export interface SendStatusUpdateParams {
|
|
27
|
+
config: XYChannelConfig;
|
|
28
|
+
sessionId: string;
|
|
29
|
+
taskId: string;
|
|
30
|
+
messageId: string;
|
|
31
|
+
text: string;
|
|
32
|
+
state: "submitted" | "working" | "input-required" | "completed" | "canceled" | "failed" | "unknown";
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Send an A2A task status update.
|
|
36
|
+
* Follows A2A protocol standard format with nested status object.
|
|
37
|
+
*/
|
|
38
|
+
export declare function sendStatusUpdate(params: SendStatusUpdateParams): Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* Parameters for sending a command.
|
|
41
|
+
*/
|
|
42
|
+
export interface SendCommandParams {
|
|
43
|
+
config: XYChannelConfig;
|
|
44
|
+
sessionId: string;
|
|
45
|
+
taskId: string;
|
|
46
|
+
messageId: string;
|
|
47
|
+
command: A2ACommand;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Send a command as an artifact update (final=false).
|
|
51
|
+
*/
|
|
52
|
+
export declare function sendCommand(params: SendCommandParams): Promise<void>;
|
|
53
|
+
/**
|
|
54
|
+
* Parameters for sending a clearContext response.
|
|
55
|
+
*/
|
|
56
|
+
export interface SendClearContextResponseParams {
|
|
57
|
+
config: XYChannelConfig;
|
|
58
|
+
sessionId: string;
|
|
59
|
+
messageId: string;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Send a clearContext response.
|
|
63
|
+
*/
|
|
64
|
+
export declare function sendClearContextResponse(params: SendClearContextResponseParams): Promise<void>;
|
|
65
|
+
/**
|
|
66
|
+
* Parameters for sending a tasks/cancel response.
|
|
67
|
+
*/
|
|
68
|
+
export interface SendTasksCancelResponseParams {
|
|
69
|
+
config: XYChannelConfig;
|
|
70
|
+
sessionId: string;
|
|
71
|
+
taskId: string;
|
|
72
|
+
messageId: string;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Send a tasks/cancel response.
|
|
76
|
+
*/
|
|
77
|
+
export declare function sendTasksCancelResponse(params: SendTasksCancelResponseParams): Promise<void>;
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
// OpenClaw → A2A format conversion
|
|
2
|
+
import { v4 as uuidv4 } from "uuid";
|
|
3
|
+
import { getXYWebSocketManager } from "./client.js";
|
|
4
|
+
import { getXYRuntime } from "./runtime.js";
|
|
5
|
+
/**
|
|
6
|
+
* Send an A2A artifact update response.
|
|
7
|
+
*/
|
|
8
|
+
export async function sendA2AResponse(params) {
|
|
9
|
+
const { config, sessionId, taskId, messageId, text, append, final, files } = params;
|
|
10
|
+
const runtime = getXYRuntime();
|
|
11
|
+
const log = runtime?.log ?? console.log;
|
|
12
|
+
const error = runtime?.error ?? console.error;
|
|
13
|
+
// Build artifact update event
|
|
14
|
+
const artifact = {
|
|
15
|
+
taskId,
|
|
16
|
+
kind: "artifact-update",
|
|
17
|
+
append,
|
|
18
|
+
lastChunk: true,
|
|
19
|
+
final,
|
|
20
|
+
artifact: {
|
|
21
|
+
artifactId: uuidv4(),
|
|
22
|
+
parts: [],
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
// Add text part (even if empty string, to maintain parts structure)
|
|
26
|
+
if (text !== undefined) {
|
|
27
|
+
artifact.artifact.parts.push({
|
|
28
|
+
kind: "text",
|
|
29
|
+
text,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
// Add file parts if provided
|
|
33
|
+
if (files && files.length > 0) {
|
|
34
|
+
artifact.artifact.parts.push({
|
|
35
|
+
kind: "data",
|
|
36
|
+
data: { fileInfo: files },
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
// Build JSON-RPC response
|
|
40
|
+
const jsonRpcResponse = {
|
|
41
|
+
jsonrpc: "2.0",
|
|
42
|
+
id: messageId,
|
|
43
|
+
result: artifact,
|
|
44
|
+
};
|
|
45
|
+
// Send via WebSocket
|
|
46
|
+
const wsManager = getXYWebSocketManager(config);
|
|
47
|
+
const outboundMessage = {
|
|
48
|
+
msgType: "agent_response",
|
|
49
|
+
agentId: config.agentId,
|
|
50
|
+
sessionId,
|
|
51
|
+
taskId,
|
|
52
|
+
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
53
|
+
};
|
|
54
|
+
// 📋 Log complete response body
|
|
55
|
+
log(`[A2A_RESPONSE] 📤 Sending A2A artifact-update response:`);
|
|
56
|
+
log(`[A2A_RESPONSE] - sessionId: ${sessionId}`);
|
|
57
|
+
log(`[A2A_RESPONSE] - taskId: ${taskId}`);
|
|
58
|
+
log(`[A2A_RESPONSE] - messageId: ${messageId}`);
|
|
59
|
+
log(`[A2A_RESPONSE] - append: ${append}`);
|
|
60
|
+
log(`[A2A_RESPONSE] - final: ${final}`);
|
|
61
|
+
log(`[A2A_RESPONSE] - text length: ${text?.length ?? 0}`);
|
|
62
|
+
log(`[A2A_RESPONSE] - files count: ${files?.length ?? 0}`);
|
|
63
|
+
log(`[A2A_RESPONSE] 📦 Complete outbound message:`);
|
|
64
|
+
log(JSON.stringify(outboundMessage, null, 2));
|
|
65
|
+
log(`[A2A_RESPONSE] 📦 JSON-RPC response body:`);
|
|
66
|
+
log(JSON.stringify(jsonRpcResponse, null, 2));
|
|
67
|
+
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
68
|
+
log(`[A2A_RESPONSE] ✅ Message sent successfully`);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Send an A2A task status update.
|
|
72
|
+
* Follows A2A protocol standard format with nested status object.
|
|
73
|
+
*/
|
|
74
|
+
export async function sendStatusUpdate(params) {
|
|
75
|
+
const { config, sessionId, taskId, messageId, text, state } = params;
|
|
76
|
+
const runtime = getXYRuntime();
|
|
77
|
+
const log = runtime?.log ?? console.log;
|
|
78
|
+
const error = runtime?.error ?? console.error;
|
|
79
|
+
// Build status update event following A2A protocol standard
|
|
80
|
+
const statusUpdate = {
|
|
81
|
+
taskId,
|
|
82
|
+
kind: "status-update",
|
|
83
|
+
final: false, // Status updates should not end the stream
|
|
84
|
+
status: {
|
|
85
|
+
message: {
|
|
86
|
+
role: "agent",
|
|
87
|
+
parts: [
|
|
88
|
+
{
|
|
89
|
+
kind: "text",
|
|
90
|
+
text,
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
},
|
|
94
|
+
state,
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
// Build JSON-RPC response
|
|
98
|
+
const jsonRpcResponse = {
|
|
99
|
+
jsonrpc: "2.0",
|
|
100
|
+
id: messageId,
|
|
101
|
+
result: statusUpdate,
|
|
102
|
+
};
|
|
103
|
+
// Send via WebSocket
|
|
104
|
+
const wsManager = getXYWebSocketManager(config);
|
|
105
|
+
const outboundMessage = {
|
|
106
|
+
msgType: "agent_response",
|
|
107
|
+
agentId: config.agentId,
|
|
108
|
+
sessionId,
|
|
109
|
+
taskId,
|
|
110
|
+
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
111
|
+
};
|
|
112
|
+
// 📋 Log complete response body
|
|
113
|
+
log(`[A2A_STATUS] 📤 Sending A2A status-update:`);
|
|
114
|
+
log(`[A2A_STATUS] - sessionId: ${sessionId}`);
|
|
115
|
+
log(`[A2A_STATUS] - taskId: ${taskId}`);
|
|
116
|
+
log(`[A2A_STATUS] - messageId: ${messageId}`);
|
|
117
|
+
log(`[A2A_STATUS] - state: ${state}`);
|
|
118
|
+
log(`[A2A_STATUS] - text: "${text}"`);
|
|
119
|
+
log(`[A2A_STATUS] 📦 Complete outbound message:`);
|
|
120
|
+
log(JSON.stringify(outboundMessage, null, 2));
|
|
121
|
+
log(`[A2A_STATUS] 📦 JSON-RPC response body:`);
|
|
122
|
+
log(JSON.stringify(jsonRpcResponse, null, 2));
|
|
123
|
+
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
124
|
+
log(`[A2A_STATUS] ✅ Status update sent successfully`);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Send a command as an artifact update (final=false).
|
|
128
|
+
*/
|
|
129
|
+
export async function sendCommand(params) {
|
|
130
|
+
const { config, sessionId, taskId, messageId, command } = params;
|
|
131
|
+
const runtime = getXYRuntime();
|
|
132
|
+
const log = runtime?.log ?? console.log;
|
|
133
|
+
const error = runtime?.error ?? console.error;
|
|
134
|
+
// Build artifact update with command as data
|
|
135
|
+
// Wrap command in commands array as per protocol requirement
|
|
136
|
+
const artifact = {
|
|
137
|
+
taskId,
|
|
138
|
+
kind: "artifact-update",
|
|
139
|
+
append: false,
|
|
140
|
+
lastChunk: true,
|
|
141
|
+
final: false, // Commands are not final
|
|
142
|
+
artifact: {
|
|
143
|
+
artifactId: uuidv4(),
|
|
144
|
+
parts: [
|
|
145
|
+
{
|
|
146
|
+
kind: "data",
|
|
147
|
+
data: {
|
|
148
|
+
commands: [command],
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
],
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
// Build JSON-RPC response
|
|
155
|
+
const jsonRpcResponse = {
|
|
156
|
+
jsonrpc: "2.0",
|
|
157
|
+
id: messageId,
|
|
158
|
+
result: artifact,
|
|
159
|
+
};
|
|
160
|
+
// Send via WebSocket
|
|
161
|
+
const wsManager = getXYWebSocketManager(config);
|
|
162
|
+
const outboundMessage = {
|
|
163
|
+
msgType: "agent_response",
|
|
164
|
+
agentId: config.agentId,
|
|
165
|
+
sessionId,
|
|
166
|
+
taskId,
|
|
167
|
+
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
168
|
+
};
|
|
169
|
+
// 📋 Log complete response body
|
|
170
|
+
log(`[A2A_COMMAND] 📤 Sending A2A command:`);
|
|
171
|
+
log(`[A2A_COMMAND] - sessionId: ${sessionId}`);
|
|
172
|
+
log(`[A2A_COMMAND] - taskId: ${taskId}`);
|
|
173
|
+
log(`[A2A_COMMAND] - messageId: ${messageId}`);
|
|
174
|
+
log(`[A2A_COMMAND] - command: ${command.header.namespace}::${command.header.name}`);
|
|
175
|
+
log(`[A2A_COMMAND] 📦 Complete outbound message:`);
|
|
176
|
+
log(JSON.stringify(outboundMessage, null, 2));
|
|
177
|
+
log(`[A2A_COMMAND] 📦 JSON-RPC response body:`);
|
|
178
|
+
log(JSON.stringify(jsonRpcResponse, null, 2));
|
|
179
|
+
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
180
|
+
log(`[A2A_COMMAND] ✅ Command sent successfully`);
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Send a clearContext response.
|
|
184
|
+
*/
|
|
185
|
+
export async function sendClearContextResponse(params) {
|
|
186
|
+
const { config, sessionId, messageId } = params;
|
|
187
|
+
const runtime = getXYRuntime();
|
|
188
|
+
const log = runtime?.log ?? console.log;
|
|
189
|
+
const error = runtime?.error ?? console.error;
|
|
190
|
+
// Build JSON-RPC response for clearContext
|
|
191
|
+
const jsonRpcResponse = {
|
|
192
|
+
jsonrpc: "2.0",
|
|
193
|
+
id: messageId,
|
|
194
|
+
result: {
|
|
195
|
+
status: {
|
|
196
|
+
state: "cleared",
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
error: {
|
|
200
|
+
code: 0,
|
|
201
|
+
// Note: Using any to bypass type check as the response format differs from standard A2A types
|
|
202
|
+
message: "",
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
// Send via WebSocket
|
|
206
|
+
const wsManager = getXYWebSocketManager(config);
|
|
207
|
+
const outboundMessage = {
|
|
208
|
+
msgType: "agent_response",
|
|
209
|
+
agentId: config.agentId,
|
|
210
|
+
sessionId,
|
|
211
|
+
taskId: sessionId, // Use sessionId as taskId for clearContext
|
|
212
|
+
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
213
|
+
};
|
|
214
|
+
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
215
|
+
log(`Sent clearContext response: sessionId=${sessionId}`);
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Send a tasks/cancel response.
|
|
219
|
+
*/
|
|
220
|
+
export async function sendTasksCancelResponse(params) {
|
|
221
|
+
const { config, sessionId, taskId, messageId } = params;
|
|
222
|
+
const runtime = getXYRuntime();
|
|
223
|
+
const log = runtime?.log ?? console.log;
|
|
224
|
+
const error = runtime?.error ?? console.error;
|
|
225
|
+
// Build JSON-RPC response for tasks/cancel
|
|
226
|
+
// Note: Using any to bypass type check as the response format differs from standard A2A types
|
|
227
|
+
const jsonRpcResponse = {
|
|
228
|
+
jsonrpc: "2.0",
|
|
229
|
+
id: messageId,
|
|
230
|
+
result: {
|
|
231
|
+
id: taskId,
|
|
232
|
+
status: {
|
|
233
|
+
state: "canceled",
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
error: {
|
|
237
|
+
code: 0,
|
|
238
|
+
message: "",
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
// Send via WebSocket
|
|
242
|
+
const wsManager = getXYWebSocketManager(config);
|
|
243
|
+
const outboundMessage = {
|
|
244
|
+
msgType: "agent_response",
|
|
245
|
+
agentId: config.agentId,
|
|
246
|
+
sessionId,
|
|
247
|
+
taskId,
|
|
248
|
+
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
249
|
+
};
|
|
250
|
+
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
251
|
+
log(`Sent tasks/cancel response: sessionId=${sessionId}, taskId=${taskId}`);
|
|
252
|
+
}
|