@mobcode/openclaw-plugin 0.1.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/README.md +131 -0
- package/index.js +3 -0
- package/openclaw.plugin.json +63 -0
- package/package.json +32 -0
- package/src/config-utils.js +148 -0
- package/src/feature-matrix.js +49 -0
- package/src/gateway-methods.js +180 -0
- package/src/http-routes.js +159 -0
- package/src/openclaw-gateway-runtime.js +85 -0
- package/src/openclaw-introspection.js +10 -0
- package/src/openclaw-session-reader.js +75 -0
- package/src/plugin-definition.js +63 -0
- package/src/plugin-tools.js +300 -0
- package/src/provider-catalog.js +245 -0
- package/src/provider-model-fetch.js +265 -0
- package/src/runtime-events.js +96 -0
- package/src/state-store.js +1430 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
const STATIC_PROVIDER_CATALOG = [
|
|
2
|
+
{
|
|
3
|
+
id: "openai",
|
|
4
|
+
title: "OpenAI",
|
|
5
|
+
defaultBaseUrl: "https://api.openai.com/v1",
|
|
6
|
+
defaultApi: "openai-completions",
|
|
7
|
+
defaultAuth: "api-key",
|
|
8
|
+
keyLabel: "API key",
|
|
9
|
+
modelFetchStrategy: "openai-compatible",
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
id: "anthropic",
|
|
13
|
+
title: "Anthropic",
|
|
14
|
+
defaultBaseUrl: "https://api.anthropic.com/v1",
|
|
15
|
+
defaultApi: "anthropic-messages",
|
|
16
|
+
defaultAuth: "api-key",
|
|
17
|
+
keyLabel: "API key",
|
|
18
|
+
modelFetchStrategy: "anthropic",
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
id: "google",
|
|
22
|
+
title: "Google (Gemini)",
|
|
23
|
+
defaultBaseUrl: "https://generativelanguage.googleapis.com/v1beta/openai",
|
|
24
|
+
defaultApi: "openai-completions",
|
|
25
|
+
defaultAuth: "api-key",
|
|
26
|
+
keyLabel: "API key",
|
|
27
|
+
modelFetchStrategy: "google",
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: "deepseek",
|
|
31
|
+
title: "DeepSeek",
|
|
32
|
+
defaultBaseUrl: "https://api.deepseek.com/v1",
|
|
33
|
+
defaultApi: "openai-completions",
|
|
34
|
+
defaultAuth: "api-key",
|
|
35
|
+
keyLabel: "API key",
|
|
36
|
+
modelFetchStrategy: "openai-compatible",
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: "openrouter",
|
|
40
|
+
title: "OpenRouter",
|
|
41
|
+
defaultBaseUrl: "https://openrouter.ai/api/v1",
|
|
42
|
+
defaultApi: "openai-completions",
|
|
43
|
+
defaultAuth: "api-key",
|
|
44
|
+
keyLabel: "API key",
|
|
45
|
+
modelFetchStrategy: "openrouter",
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: "xai",
|
|
49
|
+
title: "xAI (Grok)",
|
|
50
|
+
defaultBaseUrl: "https://api.x.ai/v1",
|
|
51
|
+
defaultApi: "openai-completions",
|
|
52
|
+
defaultAuth: "api-key",
|
|
53
|
+
keyLabel: "API key",
|
|
54
|
+
modelFetchStrategy: "openai-compatible",
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: "mistral",
|
|
58
|
+
title: "Mistral",
|
|
59
|
+
defaultBaseUrl: "https://api.mistral.ai/v1",
|
|
60
|
+
defaultApi: "openai-completions",
|
|
61
|
+
defaultAuth: "api-key",
|
|
62
|
+
keyLabel: "API key",
|
|
63
|
+
modelFetchStrategy: "openai-compatible",
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: "moonshot",
|
|
67
|
+
title: "Moonshot (Kimi)",
|
|
68
|
+
defaultBaseUrl: "https://api.moonshot.ai/v1",
|
|
69
|
+
defaultApi: "openai-completions",
|
|
70
|
+
defaultAuth: "api-key",
|
|
71
|
+
keyLabel: "API key",
|
|
72
|
+
modelFetchStrategy: "openai-compatible",
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: "kimi-coding",
|
|
76
|
+
title: "Kimi Coding",
|
|
77
|
+
defaultBaseUrl: "https://api.kimi.com/coding/",
|
|
78
|
+
defaultApi: "anthropic-messages",
|
|
79
|
+
defaultAuth: "api-key",
|
|
80
|
+
keyLabel: "API key",
|
|
81
|
+
modelFetchStrategy: "anthropic",
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
id: "qwen-portal",
|
|
85
|
+
title: "Qwen Portal",
|
|
86
|
+
defaultBaseUrl: "https://portal.qwen.ai/v1",
|
|
87
|
+
defaultApi: "openai-completions",
|
|
88
|
+
defaultAuth: "api-key",
|
|
89
|
+
keyLabel: "API key",
|
|
90
|
+
modelFetchStrategy: "openai-compatible",
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: "minimax",
|
|
94
|
+
title: "MiniMax",
|
|
95
|
+
defaultBaseUrl: "https://api.minimax.io/anthropic",
|
|
96
|
+
defaultApi: "anthropic-messages",
|
|
97
|
+
defaultAuth: "api-key",
|
|
98
|
+
keyLabel: "API key",
|
|
99
|
+
modelFetchStrategy: "anthropic",
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
id: "xiaomi",
|
|
103
|
+
title: "Xiaomi MiMo",
|
|
104
|
+
defaultBaseUrl: "https://api.xiaomimimo.com/anthropic",
|
|
105
|
+
defaultApi: "anthropic-messages",
|
|
106
|
+
defaultAuth: "api-key",
|
|
107
|
+
keyLabel: "API key",
|
|
108
|
+
modelFetchStrategy: "anthropic",
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
id: "venice",
|
|
112
|
+
title: "Venice",
|
|
113
|
+
defaultBaseUrl: "https://api.venice.ai/api/v1",
|
|
114
|
+
defaultApi: "openai-completions",
|
|
115
|
+
defaultAuth: "api-key",
|
|
116
|
+
keyLabel: "API key",
|
|
117
|
+
modelFetchStrategy: "openai-compatible",
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
id: "together",
|
|
121
|
+
title: "Together AI",
|
|
122
|
+
defaultBaseUrl: "https://api.together.xyz/v1",
|
|
123
|
+
defaultApi: "openai-completions",
|
|
124
|
+
defaultAuth: "api-key",
|
|
125
|
+
keyLabel: "API key",
|
|
126
|
+
modelFetchStrategy: "openai-compatible",
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
id: "huggingface",
|
|
130
|
+
title: "Hugging Face",
|
|
131
|
+
defaultBaseUrl: "https://router.huggingface.co/v1",
|
|
132
|
+
defaultApi: "openai-completions",
|
|
133
|
+
defaultAuth: "api-key",
|
|
134
|
+
keyLabel: "HF token",
|
|
135
|
+
modelFetchStrategy: "openai-compatible",
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
id: "qianfan",
|
|
139
|
+
title: "Baidu Qianfan",
|
|
140
|
+
defaultBaseUrl: "https://qianfan.baidubce.com/v2",
|
|
141
|
+
defaultApi: "openai-completions",
|
|
142
|
+
defaultAuth: "api-key",
|
|
143
|
+
keyLabel: "API key",
|
|
144
|
+
modelFetchStrategy: "openai-compatible",
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
id: "byteplus",
|
|
148
|
+
title: "BytePlus ModelArk",
|
|
149
|
+
defaultBaseUrl: "https://ark.ap-southeast.bytepluses.com/api/v3",
|
|
150
|
+
defaultApi: "openai-completions",
|
|
151
|
+
defaultAuth: "api-key",
|
|
152
|
+
keyLabel: "API key",
|
|
153
|
+
modelFetchStrategy: "openai-compatible",
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
id: "byteplus-coding",
|
|
157
|
+
title: "BytePlus Coding",
|
|
158
|
+
defaultBaseUrl: "https://ark.ap-southeast.bytepluses.com/api/coding/v3",
|
|
159
|
+
defaultApi: "openai-completions",
|
|
160
|
+
defaultAuth: "api-key",
|
|
161
|
+
keyLabel: "API key",
|
|
162
|
+
modelFetchStrategy: "openai-compatible",
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
id: "doubao",
|
|
166
|
+
title: "Doubao",
|
|
167
|
+
defaultBaseUrl: "https://ark.cn-beijing.volces.com/api/v3",
|
|
168
|
+
defaultApi: "openai-completions",
|
|
169
|
+
defaultAuth: "api-key",
|
|
170
|
+
keyLabel: "API key",
|
|
171
|
+
modelFetchStrategy: "openai-compatible",
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
id: "doubao-coding",
|
|
175
|
+
title: "Doubao Coding",
|
|
176
|
+
defaultBaseUrl: "https://ark.cn-beijing.volces.com/api/coding/v3",
|
|
177
|
+
defaultApi: "openai-completions",
|
|
178
|
+
defaultAuth: "api-key",
|
|
179
|
+
keyLabel: "API key",
|
|
180
|
+
modelFetchStrategy: "openai-compatible",
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
id: "nvidia",
|
|
184
|
+
title: "NVIDIA NIM",
|
|
185
|
+
defaultBaseUrl: "https://integrate.api.nvidia.com/v1",
|
|
186
|
+
defaultApi: "openai-completions",
|
|
187
|
+
defaultAuth: "api-key",
|
|
188
|
+
keyLabel: "API key",
|
|
189
|
+
modelFetchStrategy: "openai-compatible",
|
|
190
|
+
},
|
|
191
|
+
];
|
|
192
|
+
|
|
193
|
+
function cloneJson(value) {
|
|
194
|
+
return value === undefined ? undefined : JSON.parse(JSON.stringify(value));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export function listStaticProviders() {
|
|
198
|
+
return STATIC_PROVIDER_CATALOG.map((entry) => cloneJson(entry));
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function getStaticProvider(providerId) {
|
|
202
|
+
const normalized = String(providerId ?? "").trim().toLowerCase();
|
|
203
|
+
return (
|
|
204
|
+
STATIC_PROVIDER_CATALOG.find((entry) => entry.id.toLowerCase() === normalized) ?? null
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function buildStaticProvidersPayload() {
|
|
209
|
+
return {
|
|
210
|
+
generatedAt: new Date().toISOString(),
|
|
211
|
+
source: "mobcode-static-provider-catalog",
|
|
212
|
+
providers: STATIC_PROVIDER_CATALOG.map((entry) => ({
|
|
213
|
+
providerId: entry.id,
|
|
214
|
+
label: entry.title,
|
|
215
|
+
aliases: [],
|
|
216
|
+
envVars: [],
|
|
217
|
+
docsPath: null,
|
|
218
|
+
groupLabel: entry.title,
|
|
219
|
+
groupHint: null,
|
|
220
|
+
setupChoices: [
|
|
221
|
+
{
|
|
222
|
+
value: entry.id,
|
|
223
|
+
label: entry.title,
|
|
224
|
+
hint: null,
|
|
225
|
+
authKind: entry.defaultAuth,
|
|
226
|
+
methodId: entry.defaultAuth,
|
|
227
|
+
preset: {
|
|
228
|
+
baseUrl: entry.defaultBaseUrl,
|
|
229
|
+
api: entry.defaultApi,
|
|
230
|
+
auth: entry.defaultAuth,
|
|
231
|
+
},
|
|
232
|
+
defaultModel: null,
|
|
233
|
+
keyLabel: entry.keyLabel,
|
|
234
|
+
},
|
|
235
|
+
],
|
|
236
|
+
modelPicker: [
|
|
237
|
+
{
|
|
238
|
+
choice: entry.id,
|
|
239
|
+
label: entry.title,
|
|
240
|
+
hint: null,
|
|
241
|
+
},
|
|
242
|
+
],
|
|
243
|
+
})),
|
|
244
|
+
};
|
|
245
|
+
}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import { getStaticProvider } from "./provider-catalog.js";
|
|
2
|
+
|
|
3
|
+
function normalizeBase(baseUrl) {
|
|
4
|
+
const trimmed = String(baseUrl ?? "").trim();
|
|
5
|
+
if (!trimmed) {
|
|
6
|
+
return "";
|
|
7
|
+
}
|
|
8
|
+
return trimmed.endsWith("/") ? trimmed.slice(0, -1) : trimmed;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function buildModelsUrl({ baseUrl, suffix, defaultPath, queryParameters }) {
|
|
12
|
+
const normalized = normalizeBase(baseUrl);
|
|
13
|
+
const uri = new URL(normalized);
|
|
14
|
+
const path = uri.pathname.trim() ? uri.pathname : defaultPath;
|
|
15
|
+
uri.pathname = path.endsWith("/") ? `${path.slice(0, -1)}${suffix}` : `${path}${suffix}`;
|
|
16
|
+
if (queryParameters) {
|
|
17
|
+
for (const [key, value] of Object.entries(queryParameters)) {
|
|
18
|
+
if (value !== undefined && value !== null && String(value).trim()) {
|
|
19
|
+
uri.searchParams.set(key, String(value));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return uri;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function fetchJson(url, init) {
|
|
27
|
+
const response = await fetch(url, init);
|
|
28
|
+
const body = await response.text();
|
|
29
|
+
if (!response.ok) {
|
|
30
|
+
const message = extractErrorMessage(body) ?? `HTTP ${response.status}`;
|
|
31
|
+
throw new Error(message);
|
|
32
|
+
}
|
|
33
|
+
if (!body.trim()) {
|
|
34
|
+
return {};
|
|
35
|
+
}
|
|
36
|
+
return JSON.parse(body);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function extractErrorMessage(body) {
|
|
40
|
+
const trimmed = String(body ?? "").trim();
|
|
41
|
+
if (!trimmed) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
const decoded = JSON.parse(trimmed);
|
|
46
|
+
if (decoded && typeof decoded === "object") {
|
|
47
|
+
if (decoded.error && typeof decoded.error === "object") {
|
|
48
|
+
const message = String(decoded.error.message ?? "").trim();
|
|
49
|
+
if (message) {
|
|
50
|
+
return message;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const message = String(decoded.message ?? "").trim();
|
|
54
|
+
if (message) {
|
|
55
|
+
return message;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
} catch {}
|
|
59
|
+
return trimmed.length > 240 ? `${trimmed.slice(0, 237)}...` : trimmed;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function normalizeModelRows(providerId, ids) {
|
|
63
|
+
return [...new Set(ids.map((value) => String(value ?? "").trim()).filter(Boolean))]
|
|
64
|
+
.sort((a, b) => a.localeCompare(b))
|
|
65
|
+
.map((id) => ({
|
|
66
|
+
provider: providerId,
|
|
67
|
+
id,
|
|
68
|
+
name: id,
|
|
69
|
+
}));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function fetchOpenAiCompatibleModels({ providerId, baseUrl, apiKey }) {
|
|
73
|
+
const url = buildModelsUrl({
|
|
74
|
+
baseUrl,
|
|
75
|
+
suffix: "/models",
|
|
76
|
+
defaultPath: "/v1",
|
|
77
|
+
});
|
|
78
|
+
const decoded = await fetchJson(url, {
|
|
79
|
+
headers: {
|
|
80
|
+
...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
|
|
81
|
+
"Content-Type": "application/json",
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
const data = Array.isArray(decoded?.data) ? decoded.data : [];
|
|
85
|
+
return normalizeModelRows(
|
|
86
|
+
providerId,
|
|
87
|
+
data.map((entry) => entry?.id ?? entry?.name),
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function fetchAnthropicModels({ providerId, baseUrl, apiKey }) {
|
|
92
|
+
if (!apiKey) {
|
|
93
|
+
throw new Error("API key is required for this provider");
|
|
94
|
+
}
|
|
95
|
+
const url = buildModelsUrl({
|
|
96
|
+
baseUrl,
|
|
97
|
+
suffix: "/models",
|
|
98
|
+
defaultPath: "/v1",
|
|
99
|
+
});
|
|
100
|
+
const decoded = await fetchJson(url, {
|
|
101
|
+
headers: {
|
|
102
|
+
"x-api-key": apiKey,
|
|
103
|
+
"anthropic-version": "2023-06-01",
|
|
104
|
+
"Content-Type": "application/json",
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
const data = Array.isArray(decoded?.data) ? decoded.data : [];
|
|
108
|
+
return normalizeModelRows(
|
|
109
|
+
providerId,
|
|
110
|
+
data.map((entry) => entry?.id ?? entry?.name),
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function fetchGoogleModels({ providerId, baseUrl, apiKey }) {
|
|
115
|
+
if (!apiKey) {
|
|
116
|
+
throw new Error("API key is required for this provider");
|
|
117
|
+
}
|
|
118
|
+
const normalizedBase = normalizeBase(baseUrl);
|
|
119
|
+
const baseWithoutCompat = normalizedBase.endsWith("/v1beta/openai")
|
|
120
|
+
? normalizedBase.slice(0, -"/v1beta/openai".length)
|
|
121
|
+
: normalizedBase;
|
|
122
|
+
const url = buildModelsUrl({
|
|
123
|
+
baseUrl: baseWithoutCompat,
|
|
124
|
+
suffix: "/models",
|
|
125
|
+
defaultPath: "/v1beta",
|
|
126
|
+
queryParameters: { key: apiKey },
|
|
127
|
+
});
|
|
128
|
+
const decoded = await fetchJson(url, {
|
|
129
|
+
headers: {
|
|
130
|
+
"Content-Type": "application/json",
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
const models = Array.isArray(decoded?.models) ? decoded.models : [];
|
|
134
|
+
return normalizeModelRows(
|
|
135
|
+
providerId,
|
|
136
|
+
models.map((entry) => {
|
|
137
|
+
const name = String(entry?.name ?? "").trim();
|
|
138
|
+
return name.startsWith("models/") ? name.slice(7) : name;
|
|
139
|
+
}),
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function fetchProviderModelsByStrategy({ providerId, strategy, baseUrl, apiKey }) {
|
|
144
|
+
switch (strategy) {
|
|
145
|
+
case "anthropic":
|
|
146
|
+
return await fetchAnthropicModels({ providerId, baseUrl, apiKey });
|
|
147
|
+
case "google":
|
|
148
|
+
return await fetchGoogleModels({ providerId, baseUrl, apiKey });
|
|
149
|
+
case "openrouter":
|
|
150
|
+
case "openai-compatible":
|
|
151
|
+
default:
|
|
152
|
+
return await fetchOpenAiCompatibleModels({ providerId, baseUrl, apiKey });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function resolveConfiguredProvider(config, providerId) {
|
|
157
|
+
const provider =
|
|
158
|
+
config?.models?.providers?.[providerId] &&
|
|
159
|
+
typeof config.models.providers[providerId] === "object" &&
|
|
160
|
+
!Array.isArray(config.models.providers[providerId])
|
|
161
|
+
? config.models.providers[providerId]
|
|
162
|
+
: {};
|
|
163
|
+
return provider;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export async function buildProviderModelsPayloadFromInternet(api, providerId) {
|
|
167
|
+
const normalizedProviderId = String(providerId ?? "").trim().toLowerCase();
|
|
168
|
+
if (!normalizedProviderId) {
|
|
169
|
+
return {
|
|
170
|
+
generatedAt: new Date().toISOString(),
|
|
171
|
+
source: "mobcode-provider-api",
|
|
172
|
+
providerId: "",
|
|
173
|
+
models: [],
|
|
174
|
+
warning: "providerId required",
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const staticProvider = getStaticProvider(normalizedProviderId);
|
|
179
|
+
if (!staticProvider) {
|
|
180
|
+
return {
|
|
181
|
+
generatedAt: new Date().toISOString(),
|
|
182
|
+
source: "mobcode-provider-api",
|
|
183
|
+
providerId: normalizedProviderId,
|
|
184
|
+
models: [],
|
|
185
|
+
warning: `Provider '${normalizedProviderId}' is not in the MobCode provider catalog.`,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const config = api.runtime.config.loadConfig();
|
|
190
|
+
const configuredProvider = resolveConfiguredProvider(config, normalizedProviderId);
|
|
191
|
+
const baseUrl =
|
|
192
|
+
String(configuredProvider.baseUrl ?? "").trim() || staticProvider.defaultBaseUrl;
|
|
193
|
+
const apiVersion = String(configuredProvider.api ?? "").trim() || staticProvider.defaultApi;
|
|
194
|
+
const authMode = String(configuredProvider.auth ?? "").trim() || staticProvider.defaultAuth;
|
|
195
|
+
|
|
196
|
+
let resolvedAuth = null;
|
|
197
|
+
try {
|
|
198
|
+
resolvedAuth = await api.runtime.modelAuth.resolveApiKeyForProvider({
|
|
199
|
+
provider: normalizedProviderId,
|
|
200
|
+
cfg: config,
|
|
201
|
+
});
|
|
202
|
+
} catch (error) {
|
|
203
|
+
return {
|
|
204
|
+
generatedAt: new Date().toISOString(),
|
|
205
|
+
source: "mobcode-provider-api",
|
|
206
|
+
providerId: normalizedProviderId,
|
|
207
|
+
models: [],
|
|
208
|
+
warning: `Could not resolve provider auth: ${error instanceof Error ? error.message : String(error)}`,
|
|
209
|
+
provider: {
|
|
210
|
+
baseUrl,
|
|
211
|
+
api: apiVersion,
|
|
212
|
+
auth: authMode,
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const apiKey = String(resolvedAuth?.apiKey ?? "").trim();
|
|
218
|
+
if (!apiKey) {
|
|
219
|
+
return {
|
|
220
|
+
generatedAt: new Date().toISOString(),
|
|
221
|
+
source: "mobcode-provider-api",
|
|
222
|
+
providerId: normalizedProviderId,
|
|
223
|
+
models: [],
|
|
224
|
+
warning: `Provider '${normalizedProviderId}' is not authenticated yet.`,
|
|
225
|
+
provider: {
|
|
226
|
+
baseUrl,
|
|
227
|
+
api: apiVersion,
|
|
228
|
+
auth: authMode,
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
try {
|
|
234
|
+
const models = await fetchProviderModelsByStrategy({
|
|
235
|
+
providerId: normalizedProviderId,
|
|
236
|
+
strategy: staticProvider.modelFetchStrategy,
|
|
237
|
+
baseUrl,
|
|
238
|
+
apiKey,
|
|
239
|
+
});
|
|
240
|
+
return {
|
|
241
|
+
generatedAt: new Date().toISOString(),
|
|
242
|
+
source: "mobcode-provider-api",
|
|
243
|
+
providerId: normalizedProviderId,
|
|
244
|
+
provider: {
|
|
245
|
+
baseUrl,
|
|
246
|
+
api: apiVersion,
|
|
247
|
+
auth: authMode,
|
|
248
|
+
},
|
|
249
|
+
models,
|
|
250
|
+
};
|
|
251
|
+
} catch (error) {
|
|
252
|
+
return {
|
|
253
|
+
generatedAt: new Date().toISOString(),
|
|
254
|
+
source: "mobcode-provider-api",
|
|
255
|
+
providerId: normalizedProviderId,
|
|
256
|
+
provider: {
|
|
257
|
+
baseUrl,
|
|
258
|
+
api: apiVersion,
|
|
259
|
+
auth: authMode,
|
|
260
|
+
},
|
|
261
|
+
models: [],
|
|
262
|
+
warning: error instanceof Error ? error.message : String(error),
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { tryCreateOperatorApprovalsGatewayClient } from "./openclaw-gateway-runtime.js";
|
|
2
|
+
|
|
3
|
+
function createPushPayloadFromTranscript(update) {
|
|
4
|
+
return {
|
|
5
|
+
type: "session.transcript.update",
|
|
6
|
+
sessionKey: update.sessionKey ?? null,
|
|
7
|
+
messageId: update.messageId ?? null,
|
|
8
|
+
sessionFile: update.sessionFile,
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function createPushPayloadFromAgentEvent(event) {
|
|
13
|
+
return {
|
|
14
|
+
type: "agent.event",
|
|
15
|
+
runId: event.runId,
|
|
16
|
+
stream: event.stream,
|
|
17
|
+
sessionKey: event.sessionKey ?? null,
|
|
18
|
+
data: event.data ?? {},
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function createPushPayloadFromApprovalEvent(event, payload) {
|
|
23
|
+
return {
|
|
24
|
+
type: event,
|
|
25
|
+
sessionKey: payload?.request?.sessionKey ?? null,
|
|
26
|
+
approvalId: payload?.id ?? null,
|
|
27
|
+
payload,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function registerMobcodeRuntimeObservers({ api, store, pluginConfig }) {
|
|
32
|
+
const retainMessages = pluginConfig?.storage?.retainMessages !== false;
|
|
33
|
+
const pushEnabled = pluginConfig?.push?.enabled === true;
|
|
34
|
+
let approvalsClient = null;
|
|
35
|
+
|
|
36
|
+
api.registerService({
|
|
37
|
+
id: "mobcode-runtime-observers",
|
|
38
|
+
async start(ctx) {
|
|
39
|
+
await store.init();
|
|
40
|
+
|
|
41
|
+
api.runtime.events.onSessionTranscriptUpdate(async (update) => {
|
|
42
|
+
if (retainMessages && update?.message !== undefined) {
|
|
43
|
+
await store.appendMessage(update);
|
|
44
|
+
}
|
|
45
|
+
if (pushEnabled) {
|
|
46
|
+
await store.enqueuePush(createPushPayloadFromTranscript(update));
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
api.runtime.events.onAgentEvent(async (event) => {
|
|
51
|
+
if (!pushEnabled) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const stream = typeof event?.stream === "string" ? event.stream : "";
|
|
55
|
+
if (stream !== "lifecycle" && stream !== "assistant" && stream !== "tool") {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
await store.enqueuePush(createPushPayloadFromAgentEvent(event));
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
approvalsClient = await tryCreateOperatorApprovalsGatewayClient({
|
|
62
|
+
config: ctx.config,
|
|
63
|
+
clientDisplayName: "MobCode Exec Approvals",
|
|
64
|
+
onEvent: async (eventFrame) => {
|
|
65
|
+
if (eventFrame?.event === "exec.approval.requested") {
|
|
66
|
+
await store.recordApprovalRequested(eventFrame.payload);
|
|
67
|
+
if (pushEnabled) {
|
|
68
|
+
await store.enqueuePush(
|
|
69
|
+
createPushPayloadFromApprovalEvent(eventFrame.event, eventFrame.payload),
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (eventFrame?.event === "exec.approval.resolved") {
|
|
75
|
+
await store.recordApprovalResolved(eventFrame.payload);
|
|
76
|
+
if (pushEnabled) {
|
|
77
|
+
await store.enqueuePush(
|
|
78
|
+
createPushPayloadFromApprovalEvent(eventFrame.event, eventFrame.payload),
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
onConnectError: (err) => {
|
|
84
|
+
api.logger.warn(`mobcode approvals listener connect error: ${err.message}`);
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
approvalsClient?.start();
|
|
88
|
+
|
|
89
|
+
api.logger.info("mobcode runtime observers started");
|
|
90
|
+
},
|
|
91
|
+
async stop() {
|
|
92
|
+
approvalsClient?.stop();
|
|
93
|
+
approvalsClient = null;
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
}
|