@volisphere/commercial 0.1.0 → 0.2.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/dist/client.d.ts +110 -10
- package/dist/client.js +138 -2
- package/dist/index.js +312 -43
- package/openclaw.plugin.json +1 -1
- package/package.json +3 -1
package/dist/client.d.ts
CHANGED
|
@@ -63,14 +63,14 @@ export interface SkillConfig {
|
|
|
63
63
|
}
|
|
64
64
|
export interface CampaignParams {
|
|
65
65
|
name: string;
|
|
66
|
+
billing: "cpi" | "cpr" | "cpa";
|
|
66
67
|
daily_budget_micros: number;
|
|
67
68
|
total_budget_micros: number;
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
default_bid_micros: number;
|
|
70
|
+
start_at: string;
|
|
71
|
+
end_at?: string;
|
|
72
|
+
intent_categories: string[];
|
|
71
73
|
scenes: string[];
|
|
72
|
-
start_date?: string;
|
|
73
|
-
end_date?: string;
|
|
74
74
|
}
|
|
75
75
|
export interface Campaign {
|
|
76
76
|
id: string;
|
|
@@ -91,11 +91,40 @@ export interface Campaign {
|
|
|
91
91
|
updated_at: string;
|
|
92
92
|
}
|
|
93
93
|
export interface AdEntitySchema {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
94
|
+
schema_version: string;
|
|
95
|
+
identity: {
|
|
96
|
+
name: string;
|
|
97
|
+
provider: string;
|
|
98
|
+
entity_type: string;
|
|
99
|
+
};
|
|
100
|
+
capability: {
|
|
101
|
+
format: string;
|
|
102
|
+
placement: string[];
|
|
103
|
+
};
|
|
104
|
+
specs: {
|
|
105
|
+
intent_categories: string[];
|
|
106
|
+
scenes: string[];
|
|
107
|
+
regions: string[];
|
|
108
|
+
budget_ranges: string[];
|
|
109
|
+
};
|
|
110
|
+
actions: Array<{
|
|
111
|
+
label: string;
|
|
112
|
+
endpoint: string;
|
|
113
|
+
type: string;
|
|
114
|
+
}>;
|
|
115
|
+
trust: {
|
|
116
|
+
verified: boolean;
|
|
117
|
+
};
|
|
118
|
+
ad_meta: {
|
|
119
|
+
billing: string;
|
|
120
|
+
bid_micros: number;
|
|
121
|
+
};
|
|
122
|
+
render: {
|
|
123
|
+
headline: string;
|
|
124
|
+
short_desc: string;
|
|
125
|
+
image_url?: string;
|
|
126
|
+
call_to_action: string;
|
|
127
|
+
};
|
|
99
128
|
}
|
|
100
129
|
export interface AdEntity {
|
|
101
130
|
id: string;
|
|
@@ -135,6 +164,7 @@ export interface RegisterAgentResponse {
|
|
|
135
164
|
};
|
|
136
165
|
claim_token: string;
|
|
137
166
|
claim_url: string;
|
|
167
|
+
api_key: string;
|
|
138
168
|
}
|
|
139
169
|
export interface AgentProfile {
|
|
140
170
|
id: string;
|
|
@@ -144,11 +174,50 @@ export interface AgentProfile {
|
|
|
144
174
|
is_online: boolean;
|
|
145
175
|
monetization_enabled: boolean;
|
|
146
176
|
}
|
|
177
|
+
export interface InvokeAgentParams {
|
|
178
|
+
target_agent: string;
|
|
179
|
+
message: string;
|
|
180
|
+
skill?: string;
|
|
181
|
+
}
|
|
182
|
+
export interface InvokeAgentResponse {
|
|
183
|
+
request_id: string;
|
|
184
|
+
agent_slug: string;
|
|
185
|
+
agent_name: string;
|
|
186
|
+
response: string;
|
|
187
|
+
sponsored?: Array<{
|
|
188
|
+
ad_entity_id: string;
|
|
189
|
+
campaign_id: string;
|
|
190
|
+
title: string;
|
|
191
|
+
description: string;
|
|
192
|
+
call_to_action: string;
|
|
193
|
+
landing_url: string;
|
|
194
|
+
recommendation_id: string;
|
|
195
|
+
}>;
|
|
196
|
+
}
|
|
197
|
+
export interface DailyStatPoint {
|
|
198
|
+
date: string;
|
|
199
|
+
spend_micros: number;
|
|
200
|
+
earnings_micros: number;
|
|
201
|
+
match_count: number;
|
|
202
|
+
impression_count: number;
|
|
203
|
+
action_count: number;
|
|
204
|
+
}
|
|
205
|
+
export interface StatsResponse {
|
|
206
|
+
period: string;
|
|
207
|
+
points: DailyStatPoint[];
|
|
208
|
+
total_spend_micros: number;
|
|
209
|
+
total_earnings_micros: number;
|
|
210
|
+
total_matches: number;
|
|
211
|
+
}
|
|
212
|
+
export interface BalanceResponse {
|
|
213
|
+
balance_micros: number;
|
|
214
|
+
}
|
|
147
215
|
export declare class VolisphereClient {
|
|
148
216
|
private baseURL;
|
|
149
217
|
private apiKey;
|
|
150
218
|
constructor(baseURL: string, apiKey: string);
|
|
151
219
|
get apiKeyValue(): string;
|
|
220
|
+
setApiKey(key: string): void;
|
|
152
221
|
private get headers();
|
|
153
222
|
getRecommendations(params: GetRecommendationsParams): Promise<GetRecommendationsResponse>;
|
|
154
223
|
reportAction(params: ReportActionParams): Promise<ReportActionResponse>;
|
|
@@ -161,4 +230,35 @@ export declare class VolisphereClient {
|
|
|
161
230
|
registerAgent(params: RegisterAgentParams): Promise<RegisterAgentResponse>;
|
|
162
231
|
heartbeat(agentId: string): Promise<void>;
|
|
163
232
|
setMonetization(agentId: string, enabled: boolean): Promise<AgentProfile>;
|
|
233
|
+
invokeAgent(params: InvokeAgentParams): Promise<InvokeAgentResponse>;
|
|
234
|
+
getSSPStats(days?: number): Promise<StatsResponse>;
|
|
235
|
+
getDSPStats(days?: number): Promise<StatsResponse>;
|
|
236
|
+
getBalance(): Promise<BalanceResponse>;
|
|
237
|
+
}
|
|
238
|
+
export interface EarningsNotification {
|
|
239
|
+
amount: number;
|
|
240
|
+
currency: string;
|
|
241
|
+
ad_title?: string;
|
|
242
|
+
caller_name: string;
|
|
243
|
+
timestamp: string;
|
|
244
|
+
}
|
|
245
|
+
export interface WSEventHandlers {
|
|
246
|
+
onRequest?: (requestId: string, from: string, message: string, skillHint?: string) => Promise<string>;
|
|
247
|
+
onEarnings?: (earnings: EarningsNotification) => void;
|
|
248
|
+
onConnected?: () => void;
|
|
249
|
+
onDisconnected?: () => void;
|
|
250
|
+
onError?: (error: Error) => void;
|
|
251
|
+
}
|
|
252
|
+
export declare class GatewayWSClient {
|
|
253
|
+
private ws;
|
|
254
|
+
private baseURL;
|
|
255
|
+
private apiKey;
|
|
256
|
+
private handlers;
|
|
257
|
+
private reconnectTimer;
|
|
258
|
+
private isShuttingDown;
|
|
259
|
+
constructor(baseURL: string, apiKey: string, handlers: WSEventHandlers);
|
|
260
|
+
connect(): void;
|
|
261
|
+
private handleMessage;
|
|
262
|
+
private send;
|
|
263
|
+
disconnect(): void;
|
|
164
264
|
}
|
package/dist/client.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import WebSocket from "ws";
|
|
2
2
|
// ─── Unified Client ─────────────────────────────────────────────────────────
|
|
3
3
|
export class VolisphereClient {
|
|
4
4
|
baseURL;
|
|
@@ -10,6 +10,9 @@ export class VolisphereClient {
|
|
|
10
10
|
get apiKeyValue() {
|
|
11
11
|
return this.apiKey;
|
|
12
12
|
}
|
|
13
|
+
setApiKey(key) {
|
|
14
|
+
this.apiKey = key;
|
|
15
|
+
}
|
|
13
16
|
get headers() {
|
|
14
17
|
return {
|
|
15
18
|
"Content-Type": "application/json",
|
|
@@ -69,7 +72,7 @@ export class VolisphereClient {
|
|
|
69
72
|
const res = await fetch(`${this.baseURL}/api/dsp/v1/campaigns/${campaignId}/ad-entities`, {
|
|
70
73
|
method: "POST",
|
|
71
74
|
headers: this.headers,
|
|
72
|
-
body: JSON.stringify(schema),
|
|
75
|
+
body: JSON.stringify({ schema }),
|
|
73
76
|
});
|
|
74
77
|
if (!res.ok) {
|
|
75
78
|
const text = await res.text();
|
|
@@ -133,4 +136,137 @@ export class VolisphereClient {
|
|
|
133
136
|
}
|
|
134
137
|
return res.json();
|
|
135
138
|
}
|
|
139
|
+
// ── Gateway (Invoke) ────────────────────────────────────────────────────
|
|
140
|
+
async invokeAgent(params) {
|
|
141
|
+
const res = await fetch(`${this.baseURL}/api/gateway/v1/invoke`, {
|
|
142
|
+
method: "POST",
|
|
143
|
+
headers: this.headers,
|
|
144
|
+
body: JSON.stringify(params),
|
|
145
|
+
});
|
|
146
|
+
if (!res.ok) {
|
|
147
|
+
const text = await res.text();
|
|
148
|
+
throw new Error(`invokeAgent failed (${res.status}): ${text}`);
|
|
149
|
+
}
|
|
150
|
+
return res.json();
|
|
151
|
+
}
|
|
152
|
+
// ── Stats & Billing ──────────────────────────────────────────────────────
|
|
153
|
+
async getSSPStats(days = 7) {
|
|
154
|
+
const res = await fetch(`${this.baseURL}/api/ssp/v1/stats?days=${days}`, { headers: this.headers });
|
|
155
|
+
if (!res.ok) {
|
|
156
|
+
const text = await res.text();
|
|
157
|
+
throw new Error(`getSSPStats failed (${res.status}): ${text}`);
|
|
158
|
+
}
|
|
159
|
+
return res.json();
|
|
160
|
+
}
|
|
161
|
+
async getDSPStats(days = 7) {
|
|
162
|
+
const res = await fetch(`${this.baseURL}/api/dsp/v1/stats?days=${days}`, { headers: this.headers });
|
|
163
|
+
if (!res.ok) {
|
|
164
|
+
const text = await res.text();
|
|
165
|
+
throw new Error(`getDSPStats failed (${res.status}): ${text}`);
|
|
166
|
+
}
|
|
167
|
+
return res.json();
|
|
168
|
+
}
|
|
169
|
+
async getBalance() {
|
|
170
|
+
const res = await fetch(`${this.baseURL}/api/billing/v1/balance`, { headers: this.headers });
|
|
171
|
+
if (!res.ok) {
|
|
172
|
+
const text = await res.text();
|
|
173
|
+
throw new Error(`getBalance failed (${res.status}): ${text}`);
|
|
174
|
+
}
|
|
175
|
+
return res.json();
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
export class GatewayWSClient {
|
|
179
|
+
ws = null;
|
|
180
|
+
baseURL;
|
|
181
|
+
apiKey;
|
|
182
|
+
handlers;
|
|
183
|
+
reconnectTimer = null;
|
|
184
|
+
isShuttingDown = false;
|
|
185
|
+
constructor(baseURL, apiKey, handlers) {
|
|
186
|
+
this.baseURL = baseURL.replace(/^http/, "ws");
|
|
187
|
+
this.apiKey = apiKey;
|
|
188
|
+
this.handlers = handlers;
|
|
189
|
+
}
|
|
190
|
+
connect() {
|
|
191
|
+
if (this.ws)
|
|
192
|
+
return;
|
|
193
|
+
const wsURL = `${this.baseURL}/ws/agent`;
|
|
194
|
+
this.ws = new WebSocket(wsURL);
|
|
195
|
+
this.ws.on("open", () => {
|
|
196
|
+
// Send auth message
|
|
197
|
+
this.send({ type: "auth", data: { api_key: this.apiKey } });
|
|
198
|
+
});
|
|
199
|
+
this.ws.on("message", async (raw) => {
|
|
200
|
+
try {
|
|
201
|
+
const msg = JSON.parse(raw.toString());
|
|
202
|
+
await this.handleMessage(msg);
|
|
203
|
+
}
|
|
204
|
+
catch (e) {
|
|
205
|
+
this.handlers.onError?.(e);
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
this.ws.on("close", () => {
|
|
209
|
+
this.ws = null;
|
|
210
|
+
this.handlers.onDisconnected?.();
|
|
211
|
+
if (!this.isShuttingDown) {
|
|
212
|
+
this.reconnectTimer = setTimeout(() => this.connect(), 5000);
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
this.ws.on("error", (err) => {
|
|
216
|
+
this.handlers.onError?.(err);
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
async handleMessage(msg) {
|
|
220
|
+
switch (msg.type) {
|
|
221
|
+
case "auth_ok":
|
|
222
|
+
this.handlers.onConnected?.();
|
|
223
|
+
break;
|
|
224
|
+
case "auth_error": {
|
|
225
|
+
const errData = msg.data;
|
|
226
|
+
this.handlers.onError?.(new Error(`WS auth failed: ${errData?.error ?? "unknown"}`));
|
|
227
|
+
this.disconnect();
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
case "earnings_notification": {
|
|
231
|
+
const earnings = msg.data;
|
|
232
|
+
this.handlers.onEarnings?.(earnings);
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
case "request": {
|
|
236
|
+
const { request_id, from, message, skill_hint } = msg.data;
|
|
237
|
+
if (this.handlers.onRequest) {
|
|
238
|
+
try {
|
|
239
|
+
const content = await this.handlers.onRequest(request_id, from, message, skill_hint?.skill_name);
|
|
240
|
+
this.send({
|
|
241
|
+
type: "response",
|
|
242
|
+
data: { request_id, content, status: "ok" },
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
catch (e) {
|
|
246
|
+
this.send({
|
|
247
|
+
type: "response",
|
|
248
|
+
data: { request_id, content: String(e), status: "error" },
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
send(msg) {
|
|
257
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
258
|
+
this.ws.send(JSON.stringify(msg));
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
disconnect() {
|
|
262
|
+
this.isShuttingDown = true;
|
|
263
|
+
if (this.reconnectTimer) {
|
|
264
|
+
clearTimeout(this.reconnectTimer);
|
|
265
|
+
this.reconnectTimer = null;
|
|
266
|
+
}
|
|
267
|
+
if (this.ws) {
|
|
268
|
+
this.ws.close();
|
|
269
|
+
this.ws = null;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
136
272
|
}
|
package/dist/index.js
CHANGED
|
@@ -6,18 +6,52 @@
|
|
|
6
6
|
* and hooks for automatic ad injection into tool results.
|
|
7
7
|
*/
|
|
8
8
|
import { createHmac, randomUUID } from "node:crypto";
|
|
9
|
-
import {
|
|
9
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
10
|
+
import { homedir } from "node:os";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import { VolisphereClient, GatewayWSClient } from "./client.js";
|
|
13
|
+
// ─── Credential File Persistence ────────────────────────────────────────────
|
|
14
|
+
const CREDENTIALS_DIR = join(homedir(), ".volisphere");
|
|
15
|
+
const CREDENTIALS_FILE = join(CREDENTIALS_DIR, "credentials.json");
|
|
16
|
+
function loadSavedCredentials() {
|
|
17
|
+
try {
|
|
18
|
+
if (!existsSync(CREDENTIALS_FILE))
|
|
19
|
+
return null;
|
|
20
|
+
const raw = readFileSync(CREDENTIALS_FILE, "utf-8");
|
|
21
|
+
const data = JSON.parse(raw);
|
|
22
|
+
if (data.api_key && data.api_url) {
|
|
23
|
+
return data;
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function saveCredentials(creds) {
|
|
32
|
+
try {
|
|
33
|
+
if (!existsSync(CREDENTIALS_DIR)) {
|
|
34
|
+
mkdirSync(CREDENTIALS_DIR, { recursive: true });
|
|
35
|
+
}
|
|
36
|
+
writeFileSync(CREDENTIALS_FILE, JSON.stringify(creds, null, 2), "utf-8");
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// Best-effort — if we can't save, user will need to re-register next session
|
|
40
|
+
}
|
|
41
|
+
}
|
|
10
42
|
function resolveConfig(api) {
|
|
11
43
|
const cfg = api.pluginConfig ?? {};
|
|
44
|
+
const saved = loadSavedCredentials();
|
|
12
45
|
const apiURL = cfg["VOLISPHERE_API_URL"] ??
|
|
13
46
|
process.env.VOLISPHERE_API_URL ??
|
|
47
|
+
saved?.api_url ??
|
|
14
48
|
"https://api.volisphere.com";
|
|
15
49
|
const apiKey = cfg["VOLISPHERE_API_KEY"] ??
|
|
16
50
|
process.env.VOLISPHERE_API_KEY ??
|
|
51
|
+
saved?.api_key ??
|
|
17
52
|
"";
|
|
18
53
|
if (!apiKey) {
|
|
19
|
-
api.logger.
|
|
20
|
-
"Set it in plugin config or VOLISPHERE_API_KEY env var.");
|
|
54
|
+
api.logger.info("No API key found — use 'voli 开启' or 'go commercial' to register and auto-provision one.");
|
|
21
55
|
}
|
|
22
56
|
return { apiURL, apiKey };
|
|
23
57
|
}
|
|
@@ -27,7 +61,8 @@ function registerMonetizationTools(api, client) {
|
|
|
27
61
|
api.registerTool({
|
|
28
62
|
name: "volisphere_get_recommendations",
|
|
29
63
|
description: "Get relevant ad recommendations from the Volisphere network based on user intent and context. " +
|
|
30
|
-
"Do not submit any private conversation content."
|
|
64
|
+
"Do not submit any private conversation content. " +
|
|
65
|
+
"Call this when the user says: '推荐广告', '拉取广告', 'voli 推荐', 'get ads', 'show me ads'.",
|
|
31
66
|
parameters: {
|
|
32
67
|
type: "object",
|
|
33
68
|
properties: {
|
|
@@ -165,7 +200,8 @@ function registerCampaignTools(api, client) {
|
|
|
165
200
|
// create_campaign
|
|
166
201
|
api.registerTool({
|
|
167
202
|
name: "volisphere_create_campaign",
|
|
168
|
-
description: "Create a new advertising campaign on the Volisphere network."
|
|
203
|
+
description: "Create a new advertising campaign on the Volisphere network. " +
|
|
204
|
+
"Call this when the user says: '投广告', '创建广告活动', 'voli 投广告', 'create campaign', 'start advertising'.",
|
|
169
205
|
parameters: {
|
|
170
206
|
type: "object",
|
|
171
207
|
properties: {
|
|
@@ -178,19 +214,19 @@ function registerCampaignTools(api, client) {
|
|
|
178
214
|
type: "number",
|
|
179
215
|
description: "Total budget in micros",
|
|
180
216
|
},
|
|
181
|
-
|
|
217
|
+
default_bid_micros: {
|
|
182
218
|
type: "number",
|
|
183
219
|
description: "Bid per impression in micros",
|
|
184
220
|
},
|
|
185
|
-
|
|
221
|
+
billing: {
|
|
186
222
|
type: "string",
|
|
187
|
-
enum: ["
|
|
188
|
-
description: "Billing model",
|
|
223
|
+
enum: ["cpi", "cpr", "cpa"],
|
|
224
|
+
description: "Billing model: cpi (per impression), cpr (per response), cpa (per action)",
|
|
189
225
|
},
|
|
190
|
-
|
|
226
|
+
intent_categories: {
|
|
191
227
|
type: "array",
|
|
192
228
|
items: { type: "string" },
|
|
193
|
-
description: "Target categories",
|
|
229
|
+
description: "Target categories (e.g. 'software-development', 'marketing')",
|
|
194
230
|
},
|
|
195
231
|
scenes: {
|
|
196
232
|
type: "array",
|
|
@@ -202,9 +238,9 @@ function registerCampaignTools(api, client) {
|
|
|
202
238
|
"name",
|
|
203
239
|
"daily_budget_micros",
|
|
204
240
|
"total_budget_micros",
|
|
205
|
-
"
|
|
206
|
-
"
|
|
207
|
-
"
|
|
241
|
+
"default_bid_micros",
|
|
242
|
+
"billing",
|
|
243
|
+
"intent_categories",
|
|
208
244
|
"scenes",
|
|
209
245
|
],
|
|
210
246
|
},
|
|
@@ -213,10 +249,11 @@ function registerCampaignTools(api, client) {
|
|
|
213
249
|
name: params.name,
|
|
214
250
|
daily_budget_micros: params.daily_budget_micros,
|
|
215
251
|
total_budget_micros: params.total_budget_micros,
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
252
|
+
default_bid_micros: params.default_bid_micros,
|
|
253
|
+
billing: params.billing,
|
|
254
|
+
intent_categories: params.intent_categories,
|
|
219
255
|
scenes: params.scenes,
|
|
256
|
+
start_at: new Date().toISOString(),
|
|
220
257
|
});
|
|
221
258
|
return JSON.stringify(result, null, 2);
|
|
222
259
|
},
|
|
@@ -224,7 +261,8 @@ function registerCampaignTools(api, client) {
|
|
|
224
261
|
// create_ad_entity
|
|
225
262
|
api.registerTool({
|
|
226
263
|
name: "volisphere_create_ad",
|
|
227
|
-
description: "Create a new ad entity (creative) within an existing campaign."
|
|
264
|
+
description: "Create a new ad entity (creative) within an existing campaign. " +
|
|
265
|
+
"Call this when the user says: '做个广告素材', '创建广告', 'voli 创建广告', 'create ad', 'make an ad'.",
|
|
228
266
|
parameters: {
|
|
229
267
|
type: "object",
|
|
230
268
|
properties: {
|
|
@@ -246,19 +284,53 @@ function registerCampaignTools(api, client) {
|
|
|
246
284
|
],
|
|
247
285
|
},
|
|
248
286
|
execute: async (params) => {
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
287
|
+
const schema = {
|
|
288
|
+
schema_version: "1.0",
|
|
289
|
+
identity: {
|
|
290
|
+
name: params.title,
|
|
291
|
+
provider: "volisphere",
|
|
292
|
+
entity_type: "banner",
|
|
293
|
+
},
|
|
294
|
+
capability: {
|
|
295
|
+
format: "text",
|
|
296
|
+
placement: ["inline"],
|
|
297
|
+
},
|
|
298
|
+
specs: {
|
|
299
|
+
intent_categories: [],
|
|
300
|
+
scenes: [],
|
|
301
|
+
regions: [],
|
|
302
|
+
budget_ranges: [],
|
|
303
|
+
},
|
|
304
|
+
actions: [
|
|
305
|
+
{
|
|
306
|
+
label: params.call_to_action,
|
|
307
|
+
endpoint: params.landing_url,
|
|
308
|
+
type: "link",
|
|
309
|
+
},
|
|
310
|
+
],
|
|
311
|
+
trust: {
|
|
312
|
+
verified: false,
|
|
313
|
+
},
|
|
314
|
+
ad_meta: {
|
|
315
|
+
billing: "cpi",
|
|
316
|
+
bid_micros: 100000,
|
|
317
|
+
},
|
|
318
|
+
render: {
|
|
319
|
+
headline: params.title,
|
|
320
|
+
short_desc: params.description,
|
|
321
|
+
image_url: "",
|
|
322
|
+
call_to_action: params.call_to_action,
|
|
323
|
+
},
|
|
324
|
+
};
|
|
325
|
+
const result = await client.createAdEntity(params.campaign_id, schema);
|
|
255
326
|
return JSON.stringify(result, null, 2);
|
|
256
327
|
},
|
|
257
328
|
});
|
|
258
329
|
// campaign_performance
|
|
259
330
|
api.registerTool({
|
|
260
331
|
name: "volisphere_campaign_performance",
|
|
261
|
-
description: "Check campaign performance metrics (impressions, clicks, spend)."
|
|
332
|
+
description: "Check campaign performance metrics (impressions, clicks, spend). " +
|
|
333
|
+
"Call this when the user says: '广告效果', '看看活动表现', 'voli 广告效果', 'campaign stats', 'how is my campaign doing'.",
|
|
262
334
|
parameters: {
|
|
263
335
|
type: "object",
|
|
264
336
|
properties: {
|
|
@@ -274,7 +346,8 @@ function registerCampaignTools(api, client) {
|
|
|
274
346
|
// pause_campaign
|
|
275
347
|
api.registerTool({
|
|
276
348
|
name: "volisphere_pause_campaign",
|
|
277
|
-
description: "Pause an active campaign to stop ad delivery."
|
|
349
|
+
description: "Pause an active campaign to stop ad delivery. " +
|
|
350
|
+
"Call this when the user says: '暂停广告', '暂停投放', 'voli 暂停', 'pause campaign', 'stop campaign'.",
|
|
278
351
|
parameters: {
|
|
279
352
|
type: "object",
|
|
280
353
|
properties: {
|
|
@@ -290,7 +363,8 @@ function registerCampaignTools(api, client) {
|
|
|
290
363
|
// resume_campaign
|
|
291
364
|
api.registerTool({
|
|
292
365
|
name: "volisphere_resume_campaign",
|
|
293
|
-
description: "Resume a paused campaign to restart ad delivery."
|
|
366
|
+
description: "Resume a paused campaign to restart ad delivery. " +
|
|
367
|
+
"Call this when the user says: '恢复广告', '恢复投放', 'voli 恢复', 'resume campaign', 'restart campaign'.",
|
|
294
368
|
parameters: {
|
|
295
369
|
type: "object",
|
|
296
370
|
properties: {
|
|
@@ -304,8 +378,134 @@ function registerCampaignTools(api, client) {
|
|
|
304
378
|
},
|
|
305
379
|
});
|
|
306
380
|
}
|
|
381
|
+
// ─── Gateway Tool (Invoke) ───────────────────────────────────────────────────
|
|
382
|
+
function registerGatewayTools(api, client) {
|
|
383
|
+
api.registerTool({
|
|
384
|
+
name: "volisphere_invoke",
|
|
385
|
+
description: "Invoke another agent on the Volisphere network. Use this to call other agents' skills and get their responses. " +
|
|
386
|
+
"The response may include sponsored ads. " +
|
|
387
|
+
"Call this when the user says: '调用 xxx', '帮我问问 xxx', 'voli 调用', 'call agent xxx', 'ask xxx'.",
|
|
388
|
+
parameters: {
|
|
389
|
+
type: "object",
|
|
390
|
+
properties: {
|
|
391
|
+
target_agent: {
|
|
392
|
+
type: "string",
|
|
393
|
+
description: "The slug of the target agent to invoke (e.g. 'codepilot-pro')",
|
|
394
|
+
},
|
|
395
|
+
message: {
|
|
396
|
+
type: "string",
|
|
397
|
+
description: "The message/request to send to the target agent",
|
|
398
|
+
},
|
|
399
|
+
skill: {
|
|
400
|
+
type: "string",
|
|
401
|
+
description: "Optional skill hint to route the request",
|
|
402
|
+
},
|
|
403
|
+
},
|
|
404
|
+
required: ["target_agent", "message"],
|
|
405
|
+
},
|
|
406
|
+
execute: async (params) => {
|
|
407
|
+
const result = await client.invokeAgent({
|
|
408
|
+
target_agent: params.target_agent,
|
|
409
|
+
message: params.message,
|
|
410
|
+
skill: params.skill,
|
|
411
|
+
});
|
|
412
|
+
return JSON.stringify(result, null, 2);
|
|
413
|
+
},
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
// ─── Dashboard Tools (Earnings / Spending) ──────────────────────────────────
|
|
417
|
+
function formatVBucks(micros) {
|
|
418
|
+
return (micros / 1_000_000).toLocaleString("en-US", {
|
|
419
|
+
minimumFractionDigits: 2,
|
|
420
|
+
maximumFractionDigits: 2,
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
function registerDashboardTools(api, client) {
|
|
424
|
+
// volisphere_my_earnings — developer revenue dashboard
|
|
425
|
+
api.registerTool({
|
|
426
|
+
name: "volisphere_my_earnings",
|
|
427
|
+
description: "Show your Volisphere developer earnings dashboard — how many times your agent was called and how much you earned. " +
|
|
428
|
+
"Call this when the user says: '看看收益', '赚了多少', '被调用了多少次', 'voli 看看收入', " +
|
|
429
|
+
"'my earnings', 'how much did I earn', 'show revenue', 'voli earnings'.",
|
|
430
|
+
parameters: {
|
|
431
|
+
type: "object",
|
|
432
|
+
properties: {
|
|
433
|
+
days: {
|
|
434
|
+
type: "number",
|
|
435
|
+
description: "Number of days to show (default 7)",
|
|
436
|
+
},
|
|
437
|
+
},
|
|
438
|
+
required: [],
|
|
439
|
+
},
|
|
440
|
+
execute: async (params) => {
|
|
441
|
+
const days = params.days ?? 7;
|
|
442
|
+
const [stats, balance] = await Promise.all([
|
|
443
|
+
client.getSSPStats(days),
|
|
444
|
+
client.getBalance(),
|
|
445
|
+
]);
|
|
446
|
+
const totalEarnings = formatVBucks(stats.total_earnings_micros);
|
|
447
|
+
const balanceVB = formatVBucks(balance.balance_micros);
|
|
448
|
+
const lines = [
|
|
449
|
+
`Your Volisphere Earnings`,
|
|
450
|
+
`━━━━━━━━━━━━━━━━━━━━━━`,
|
|
451
|
+
`Account Balance: ${balanceVB} VBucks`,
|
|
452
|
+
`Past ${days} days: ${stats.total_matches} calls, earned ${totalEarnings} VBucks`,
|
|
453
|
+
``,
|
|
454
|
+
`Daily Breakdown:`,
|
|
455
|
+
];
|
|
456
|
+
for (const p of stats.points) {
|
|
457
|
+
lines.push(` ${p.date}: ${p.match_count} calls, +${formatVBucks(p.earnings_micros)} VB`);
|
|
458
|
+
}
|
|
459
|
+
return lines.join("\n");
|
|
460
|
+
},
|
|
461
|
+
});
|
|
462
|
+
// volisphere_my_spending — advertiser spend dashboard
|
|
463
|
+
api.registerTool({
|
|
464
|
+
name: "volisphere_my_spending",
|
|
465
|
+
description: "Show your Volisphere advertiser spending dashboard — how much you spent on ads, impressions, and actions. " +
|
|
466
|
+
"Call this when the user says: '看看花费', '广告花了多少', '投放数据', 'voli 看看投放', " +
|
|
467
|
+
"'my ad spending', 'how much did I spend', 'ad stats', 'voli spending'.",
|
|
468
|
+
parameters: {
|
|
469
|
+
type: "object",
|
|
470
|
+
properties: {
|
|
471
|
+
days: {
|
|
472
|
+
type: "number",
|
|
473
|
+
description: "Number of days to show (default 7)",
|
|
474
|
+
},
|
|
475
|
+
},
|
|
476
|
+
required: [],
|
|
477
|
+
},
|
|
478
|
+
execute: async (params) => {
|
|
479
|
+
const days = params.days ?? 7;
|
|
480
|
+
const [stats, balance] = await Promise.all([
|
|
481
|
+
client.getDSPStats(days),
|
|
482
|
+
client.getBalance(),
|
|
483
|
+
]);
|
|
484
|
+
const totalSpend = formatVBucks(stats.total_spend_micros);
|
|
485
|
+
const balanceVB = formatVBucks(balance.balance_micros);
|
|
486
|
+
let totalImpressions = 0;
|
|
487
|
+
let totalActions = 0;
|
|
488
|
+
for (const p of stats.points) {
|
|
489
|
+
totalImpressions += p.impression_count;
|
|
490
|
+
totalActions += p.action_count;
|
|
491
|
+
}
|
|
492
|
+
const lines = [
|
|
493
|
+
`Your Volisphere Ad Spending`,
|
|
494
|
+
`━━━━━━━━━━━━━━━━━━━━━━━━━━`,
|
|
495
|
+
`Account Balance: ${balanceVB} VBucks`,
|
|
496
|
+
`Past ${days} days: spent ${totalSpend} VBucks, ${totalImpressions} impressions, ${totalActions} actions`,
|
|
497
|
+
``,
|
|
498
|
+
`Daily Breakdown:`,
|
|
499
|
+
];
|
|
500
|
+
for (const p of stats.points) {
|
|
501
|
+
lines.push(` ${p.date}: spent ${formatVBucks(p.spend_micros)} VB, ${p.impression_count} imp, ${p.action_count} act`);
|
|
502
|
+
}
|
|
503
|
+
return lines.join("\n");
|
|
504
|
+
},
|
|
505
|
+
});
|
|
506
|
+
}
|
|
307
507
|
// ─── Lifecycle Tools (Enable / Disable) ─────────────────────────────────────
|
|
308
|
-
function registerLifecycleTools(api, client) {
|
|
508
|
+
function registerLifecycleTools(api, client, apiURL) {
|
|
309
509
|
// volisphere_enable — register agent + enable monetization + heartbeat
|
|
310
510
|
api.registerTool({
|
|
311
511
|
name: "volisphere_enable",
|
|
@@ -345,7 +545,7 @@ function registerLifecycleTools(api, client) {
|
|
|
345
545
|
required: ["slug", "display_name"],
|
|
346
546
|
},
|
|
347
547
|
execute: async (params) => {
|
|
348
|
-
// Step 1: Register agent
|
|
548
|
+
// Step 1: Register agent (public endpoint, no auth needed)
|
|
349
549
|
const reg = await client.registerAgent({
|
|
350
550
|
slug: params.slug,
|
|
351
551
|
display_name: params.display_name,
|
|
@@ -353,20 +553,55 @@ function registerLifecycleTools(api, client) {
|
|
|
353
553
|
skills: params.skills,
|
|
354
554
|
});
|
|
355
555
|
const agentId = reg.agent.id;
|
|
356
|
-
// Step 2:
|
|
556
|
+
// Step 2: Auto-provision API key — save to local file + update client
|
|
557
|
+
if (reg.api_key) {
|
|
558
|
+
client.setApiKey(reg.api_key);
|
|
559
|
+
saveCredentials({
|
|
560
|
+
api_key: reg.api_key,
|
|
561
|
+
api_url: apiURL,
|
|
562
|
+
agent_id: agentId,
|
|
563
|
+
slug: reg.agent.slug,
|
|
564
|
+
});
|
|
565
|
+
api.logger.info(`[Volisphere] API key auto-provisioned and saved to ${CREDENTIALS_FILE}`);
|
|
566
|
+
}
|
|
567
|
+
// Step 3: Enable monetization (now using the provisioned key)
|
|
357
568
|
try {
|
|
358
569
|
await client.setMonetization(agentId, true);
|
|
359
570
|
}
|
|
360
571
|
catch {
|
|
361
572
|
// May fail if no JWT — agent was registered publicly
|
|
362
573
|
}
|
|
363
|
-
// Step
|
|
574
|
+
// Step 4: Heartbeat (go online)
|
|
364
575
|
try {
|
|
365
576
|
await client.heartbeat(agentId);
|
|
366
577
|
}
|
|
367
578
|
catch {
|
|
368
579
|
// Non-critical
|
|
369
580
|
}
|
|
581
|
+
// Step 5: Auto-connect WebSocket so agent can receive invocations
|
|
582
|
+
if (reg.api_key) {
|
|
583
|
+
const wsClient = new GatewayWSClient(apiURL, reg.api_key, {
|
|
584
|
+
onRequest: async (requestId, from, message) => {
|
|
585
|
+
api.logger.info(`[Volisphere] Received invoke request ${requestId} from ${from}: ${message}`);
|
|
586
|
+
return `Request received and being processed by ${reg.agent.slug}.`;
|
|
587
|
+
},
|
|
588
|
+
onEarnings: (earnings) => {
|
|
589
|
+
const vbucks = (earnings.amount / 1_000_000).toFixed(2);
|
|
590
|
+
api.logger.info(`[Volisphere] Earned ${vbucks} ${earnings.currency} from ${earnings.caller_name}` +
|
|
591
|
+
(earnings.ad_title ? ` (ad: ${earnings.ad_title})` : ""));
|
|
592
|
+
},
|
|
593
|
+
onConnected: () => {
|
|
594
|
+
api.logger.info("[Volisphere] Connected to Gateway WebSocket — agent is now online");
|
|
595
|
+
},
|
|
596
|
+
onDisconnected: () => {
|
|
597
|
+
api.logger.warn("[Volisphere] Disconnected from Gateway WebSocket");
|
|
598
|
+
},
|
|
599
|
+
onError: (err) => {
|
|
600
|
+
api.logger.error(`[Volisphere] Gateway WS error: ${err.message}`);
|
|
601
|
+
},
|
|
602
|
+
});
|
|
603
|
+
wsClient.connect();
|
|
604
|
+
}
|
|
370
605
|
return JSON.stringify({
|
|
371
606
|
status: "enabled",
|
|
372
607
|
agent_id: agentId,
|
|
@@ -374,6 +609,7 @@ function registerLifecycleTools(api, client) {
|
|
|
374
609
|
display_name: reg.agent.display_name,
|
|
375
610
|
claim_token: reg.claim_token,
|
|
376
611
|
message: "Agent registered and commercial features enabled. " +
|
|
612
|
+
"API key auto-provisioned — no manual configuration needed. " +
|
|
377
613
|
"Your agent is now online and can receive ad recommendations and invocations.",
|
|
378
614
|
}, null, 2);
|
|
379
615
|
},
|
|
@@ -494,19 +730,26 @@ function registerHooks(api, client) {
|
|
|
494
730
|
return {
|
|
495
731
|
appendSystemContext: [
|
|
496
732
|
"## Volisphere Ad Network",
|
|
497
|
-
"You have the Volisphere commercial plugin installed. Available capabilities:",
|
|
498
733
|
"",
|
|
499
|
-
"
|
|
500
|
-
"- User says '开启商业化'/'monetize my agent'/'go commercial' → call volisphere_enable",
|
|
501
|
-
"- User says '关闭商业化'/'stop monetization'/'go offline' → call volisphere_disable",
|
|
734
|
+
'You have the Volisphere commercial plugin installed. When the user mentions "voli" or "volisphere", they are using this plugin.',
|
|
502
735
|
"",
|
|
503
|
-
"###
|
|
504
|
-
|
|
505
|
-
|
|
736
|
+
"### Quick Action Map (natural language → tool)",
|
|
737
|
+
'- "voli 开启" / "开启商业化" / "monetize my agent" / "go commercial" → volisphere_enable',
|
|
738
|
+
'- "voli 关闭" / "关闭商业化" / "stop monetization" / "go offline" → volisphere_disable',
|
|
739
|
+
'- "voli 投广告" / "创建广告活动" / "create campaign" → volisphere_create_campaign',
|
|
740
|
+
'- "voli 创建广告" / "做个广告素材" / "create ad" → volisphere_create_ad',
|
|
741
|
+
'- "voli 暂停广告" / "暂停投放" / "pause campaign" → volisphere_pause_campaign',
|
|
742
|
+
'- "voli 恢复广告" / "恢复投放" / "resume campaign" → volisphere_resume_campaign',
|
|
743
|
+
'- "voli 广告效果" / "看看活动表现" / "campaign stats" → volisphere_campaign_performance',
|
|
744
|
+
'- "voli 看看收益" / "赚了多少" / "被调用了多少次" / "my earnings" → volisphere_my_earnings',
|
|
745
|
+
'- "voli 看看花费" / "广告数据" / "投了多少" / "my spending" → volisphere_my_spending',
|
|
746
|
+
'- "voli 调用 xxx" / "帮我问问 xxx" / "call agent xxx" → volisphere_invoke (target_agent=xxx)',
|
|
747
|
+
'- "voli 推荐广告" / "拉取广告" / "get ads" → volisphere_get_recommendations',
|
|
506
748
|
"",
|
|
507
|
-
"###
|
|
508
|
-
"-
|
|
509
|
-
"-
|
|
749
|
+
"### Smart Defaults (when user omits parameters)",
|
|
750
|
+
"- create_campaign: daily_budget=10 VB (10,000,000 micros), total_budget=100 VB, default_bid=0.1 VB, billing=cpi, start_at=now",
|
|
751
|
+
"- my_earnings / my_spending: days=7",
|
|
752
|
+
"- invoke: extract target agent slug and message from user's natural language",
|
|
510
753
|
"",
|
|
511
754
|
"Always clearly label sponsored content. Never submit private user data to ad tools.",
|
|
512
755
|
].join("\n"),
|
|
@@ -525,9 +768,35 @@ export default {
|
|
|
525
768
|
api.logger.info("Volisphere Commercial plugin loaded");
|
|
526
769
|
registerMonetizationTools(api, client);
|
|
527
770
|
registerCampaignTools(api, client);
|
|
528
|
-
|
|
771
|
+
registerGatewayTools(api, client);
|
|
772
|
+
registerDashboardTools(api, client);
|
|
773
|
+
registerLifecycleTools(api, client, apiURL);
|
|
529
774
|
registerCommands(api, client);
|
|
530
775
|
registerHooks(api, client);
|
|
531
|
-
|
|
776
|
+
// Auto-connect WebSocket so this agent can RECEIVE invoke requests
|
|
777
|
+
if (apiKey) {
|
|
778
|
+
const wsClient = new GatewayWSClient(apiURL, apiKey, {
|
|
779
|
+
onRequest: async (requestId, from, message, _skillHint) => {
|
|
780
|
+
api.logger.info(`[Volisphere] Received invoke request ${requestId} from ${from}: ${message}`);
|
|
781
|
+
return `Request received and being processed by ${api.pluginConfig?.["agent_slug"] ?? "this agent"}.`;
|
|
782
|
+
},
|
|
783
|
+
onEarnings: (earnings) => {
|
|
784
|
+
const vbucks = (earnings.amount / 1_000_000).toFixed(2);
|
|
785
|
+
api.logger.info(`[Volisphere] Earned ${vbucks} ${earnings.currency} from ${earnings.caller_name}` +
|
|
786
|
+
(earnings.ad_title ? ` (ad: ${earnings.ad_title})` : ""));
|
|
787
|
+
},
|
|
788
|
+
onConnected: () => {
|
|
789
|
+
api.logger.info("[Volisphere] Connected to Volisphere Gateway WebSocket — agent is now online and can receive invocations");
|
|
790
|
+
},
|
|
791
|
+
onDisconnected: () => {
|
|
792
|
+
api.logger.warn("[Volisphere] Disconnected from Volisphere Gateway WebSocket");
|
|
793
|
+
},
|
|
794
|
+
onError: (err) => {
|
|
795
|
+
api.logger.error(`[Volisphere] Gateway WS error: ${err.message}`);
|
|
796
|
+
},
|
|
797
|
+
});
|
|
798
|
+
wsClient.connect();
|
|
799
|
+
}
|
|
800
|
+
api.logger.info("Registered 13 tools + 1 command + 1 hook — ready to monetize");
|
|
532
801
|
},
|
|
533
802
|
};
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volisphere/commercial",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Volisphere commercial plugin for OpenClaw — monetize your agent skills with the agent-native ad network",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -28,10 +28,12 @@
|
|
|
28
28
|
],
|
|
29
29
|
"license": "MIT",
|
|
30
30
|
"dependencies": {
|
|
31
|
+
"ws": "^8.18.0",
|
|
31
32
|
"zod": "^3.23.0"
|
|
32
33
|
},
|
|
33
34
|
"devDependencies": {
|
|
34
35
|
"@types/node": "^25.5.0",
|
|
36
|
+
"@types/ws": "^8.5.13",
|
|
35
37
|
"typescript": "^5.4.0"
|
|
36
38
|
}
|
|
37
39
|
}
|