@nextclaw/nextclaw-engine-claude-agent-sdk 0.2.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/LICENSE +21 -0
- package/README.md +34 -0
- package/claude-agent-sdk-loader.cjs +17 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +432 -0
- package/openclaw.plugin.json +11 -0
- package/package.json +43 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 NextClaw contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# @nextclaw/nextclaw-engine-claude-agent-sdk
|
|
2
|
+
|
|
3
|
+
Independent NextClaw engine plugin that registers `claude-agent-sdk` using Anthropic official `@anthropic-ai/claude-agent-sdk`.
|
|
4
|
+
|
|
5
|
+
## Build
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm -C packages/extensions/nextclaw-engine-plugin-claude-agent-sdk build
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage (local path)
|
|
12
|
+
|
|
13
|
+
Add plugin load path to config:
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{
|
|
17
|
+
"plugins": {
|
|
18
|
+
"load": {
|
|
19
|
+
"paths": [
|
|
20
|
+
"/absolute/path/to/packages/extensions/nextclaw-engine-plugin-claude-agent-sdk"
|
|
21
|
+
]
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"agents": {
|
|
25
|
+
"defaults": {
|
|
26
|
+
"engine": "claude-agent-sdk",
|
|
27
|
+
"model": "claude-sonnet-4-6",
|
|
28
|
+
"engineConfig": {
|
|
29
|
+
"apiKey": "sk-ant-xxx"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
```
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
let cachedModule = null;
|
|
2
|
+
|
|
3
|
+
async function loadClaudeAgentSdkModule() {
|
|
4
|
+
if (cachedModule) {
|
|
5
|
+
return cachedModule;
|
|
6
|
+
}
|
|
7
|
+
const mod = await import("@anthropic-ai/claude-agent-sdk");
|
|
8
|
+
if (!mod || typeof mod.query !== "function") {
|
|
9
|
+
throw new Error("[claude-agent-sdk] failed to load query() from @anthropic-ai/claude-agent-sdk");
|
|
10
|
+
}
|
|
11
|
+
cachedModule = mod;
|
|
12
|
+
return cachedModule;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
module.exports = {
|
|
16
|
+
loadClaudeAgentSdkModule
|
|
17
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { AgentEngineFactoryContext, AgentEngine } from '@nextclaw/core';
|
|
2
|
+
|
|
3
|
+
type PluginApi = {
|
|
4
|
+
registerEngine: (factory: (context: AgentEngineFactoryContext) => AgentEngine, opts?: {
|
|
5
|
+
kind?: string;
|
|
6
|
+
}) => void;
|
|
7
|
+
};
|
|
8
|
+
type PluginDefinition = {
|
|
9
|
+
id: string;
|
|
10
|
+
name: string;
|
|
11
|
+
description: string;
|
|
12
|
+
configSchema: Record<string, unknown>;
|
|
13
|
+
register: (api: PluginApi) => void;
|
|
14
|
+
};
|
|
15
|
+
declare const plugin: PluginDefinition;
|
|
16
|
+
|
|
17
|
+
export { plugin as default };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import {
|
|
3
|
+
getApiBase,
|
|
4
|
+
getProvider,
|
|
5
|
+
SkillsLoader
|
|
6
|
+
} from "@nextclaw/core";
|
|
7
|
+
function readString(input, key) {
|
|
8
|
+
const value = input[key];
|
|
9
|
+
if (typeof value !== "string") {
|
|
10
|
+
return void 0;
|
|
11
|
+
}
|
|
12
|
+
const trimmed = value.trim();
|
|
13
|
+
return trimmed || void 0;
|
|
14
|
+
}
|
|
15
|
+
function readBoolean(input, key) {
|
|
16
|
+
const value = input[key];
|
|
17
|
+
if (typeof value === "boolean") {
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
return void 0;
|
|
21
|
+
}
|
|
22
|
+
function readNumber(input, key) {
|
|
23
|
+
const value = input[key];
|
|
24
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
return void 0;
|
|
28
|
+
}
|
|
29
|
+
function readRecord(input, key) {
|
|
30
|
+
const value = input[key];
|
|
31
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
32
|
+
return void 0;
|
|
33
|
+
}
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
function readStringRecord(input, key) {
|
|
37
|
+
const value = readRecord(input, key);
|
|
38
|
+
if (!value) {
|
|
39
|
+
return void 0;
|
|
40
|
+
}
|
|
41
|
+
const out = {};
|
|
42
|
+
for (const [entryKey, entryValue] of Object.entries(value)) {
|
|
43
|
+
if (typeof entryValue !== "string") {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
const normalized = entryValue.trim();
|
|
47
|
+
if (!normalized) {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
out[entryKey] = normalized;
|
|
51
|
+
}
|
|
52
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
53
|
+
}
|
|
54
|
+
function readStringOrNullRecord(input, key) {
|
|
55
|
+
const value = readRecord(input, key);
|
|
56
|
+
if (!value) {
|
|
57
|
+
return void 0;
|
|
58
|
+
}
|
|
59
|
+
const out = {};
|
|
60
|
+
for (const [entryKey, entryValue] of Object.entries(value)) {
|
|
61
|
+
if (typeof entryValue === "string") {
|
|
62
|
+
out[entryKey] = entryValue.trim();
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (entryValue === null) {
|
|
66
|
+
out[entryKey] = null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
70
|
+
}
|
|
71
|
+
function readStringArray(input, key) {
|
|
72
|
+
const value = input[key];
|
|
73
|
+
if (!Array.isArray(value)) {
|
|
74
|
+
return void 0;
|
|
75
|
+
}
|
|
76
|
+
const normalized = value.map((entry) => typeof entry === "string" ? entry.trim() : "").filter(Boolean);
|
|
77
|
+
return normalized.length > 0 ? normalized : void 0;
|
|
78
|
+
}
|
|
79
|
+
function readRequestedSkills(metadata) {
|
|
80
|
+
if (!metadata) {
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
const raw = metadata.requested_skills ?? metadata.requestedSkills;
|
|
84
|
+
const values = [];
|
|
85
|
+
if (Array.isArray(raw)) {
|
|
86
|
+
for (const entry of raw) {
|
|
87
|
+
if (typeof entry !== "string") {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const trimmed = entry.trim();
|
|
91
|
+
if (trimmed) {
|
|
92
|
+
values.push(trimmed);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
} else if (typeof raw === "string") {
|
|
96
|
+
values.push(
|
|
97
|
+
...raw.split(/[,\s]+/g).map((entry) => entry.trim()).filter(Boolean)
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
return Array.from(new Set(values)).slice(0, 8);
|
|
101
|
+
}
|
|
102
|
+
function readPermissionMode(input, key) {
|
|
103
|
+
const value = readString(input, key);
|
|
104
|
+
if (value === "default" || value === "acceptEdits" || value === "bypassPermissions" || value === "plan" || value === "dontAsk") {
|
|
105
|
+
return value;
|
|
106
|
+
}
|
|
107
|
+
return void 0;
|
|
108
|
+
}
|
|
109
|
+
function readSettingSources(input, key) {
|
|
110
|
+
const list = readStringArray(input, key);
|
|
111
|
+
if (!list) {
|
|
112
|
+
return void 0;
|
|
113
|
+
}
|
|
114
|
+
const out = [];
|
|
115
|
+
for (const entry of list) {
|
|
116
|
+
if (entry === "user" || entry === "project" || entry === "local") {
|
|
117
|
+
out.push(entry);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return out.length > 0 ? out : void 0;
|
|
121
|
+
}
|
|
122
|
+
function readExecutable(input, key) {
|
|
123
|
+
const value = readString(input, key);
|
|
124
|
+
if (value === "node" || value === "bun" || value === "deno") {
|
|
125
|
+
return value;
|
|
126
|
+
}
|
|
127
|
+
return void 0;
|
|
128
|
+
}
|
|
129
|
+
function normalizeClaudeModel(value) {
|
|
130
|
+
const trimmed = value.trim();
|
|
131
|
+
if (!trimmed.includes("/")) {
|
|
132
|
+
return trimmed;
|
|
133
|
+
}
|
|
134
|
+
const parts = trimmed.split("/").filter(Boolean);
|
|
135
|
+
return parts[parts.length - 1] ?? trimmed;
|
|
136
|
+
}
|
|
137
|
+
function resolveEngineConfig(config, model, engineConfig) {
|
|
138
|
+
const provider = getProvider(config, model);
|
|
139
|
+
const apiKey = readString(engineConfig, "apiKey") ?? provider?.apiKey ?? void 0;
|
|
140
|
+
const apiBase = readString(engineConfig, "apiBase") ?? getApiBase(config, model) ?? void 0;
|
|
141
|
+
return { apiKey, apiBase };
|
|
142
|
+
}
|
|
143
|
+
const require2 = createRequire(import.meta.url);
|
|
144
|
+
const claudeAgentLoader = require2("../claude-agent-sdk-loader.cjs");
|
|
145
|
+
class PluginClaudeAgentSdkEngine {
|
|
146
|
+
constructor(options) {
|
|
147
|
+
this.options = options;
|
|
148
|
+
this.defaultModel = options.model;
|
|
149
|
+
this.skillsLoader = new SkillsLoader(options.workspace);
|
|
150
|
+
}
|
|
151
|
+
kind = "claude-agent-sdk";
|
|
152
|
+
sdkModulePromise = null;
|
|
153
|
+
sessionIdsByKey = /* @__PURE__ */ new Map();
|
|
154
|
+
defaultModel;
|
|
155
|
+
skillsLoader;
|
|
156
|
+
async handleInbound(params) {
|
|
157
|
+
const reply = await this.processDirect({
|
|
158
|
+
content: params.message.content,
|
|
159
|
+
sessionKey: params.sessionKey,
|
|
160
|
+
channel: params.message.channel,
|
|
161
|
+
chatId: params.message.chatId,
|
|
162
|
+
metadata: params.message.metadata
|
|
163
|
+
});
|
|
164
|
+
if (!reply.trim()) {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
const outbound = {
|
|
168
|
+
channel: params.message.channel,
|
|
169
|
+
chatId: params.message.chatId,
|
|
170
|
+
content: reply,
|
|
171
|
+
media: [],
|
|
172
|
+
metadata: {}
|
|
173
|
+
};
|
|
174
|
+
if (params.publishResponse ?? true) {
|
|
175
|
+
await this.options.bus.publishOutbound(outbound);
|
|
176
|
+
}
|
|
177
|
+
return outbound;
|
|
178
|
+
}
|
|
179
|
+
async processDirect(params) {
|
|
180
|
+
const sessionKey = typeof params.sessionKey === "string" && params.sessionKey.trim() ? params.sessionKey : "cli:direct";
|
|
181
|
+
const channel = typeof params.channel === "string" && params.channel.trim() ? params.channel : "cli";
|
|
182
|
+
const chatId = typeof params.chatId === "string" && params.chatId.trim() ? params.chatId : "direct";
|
|
183
|
+
const session = this.options.sessionManager.getOrCreate(sessionKey);
|
|
184
|
+
const modelInput = readString(params.metadata ?? {}, "model") ?? this.defaultModel;
|
|
185
|
+
const model = normalizeClaudeModel(modelInput);
|
|
186
|
+
const requestedSkills = readRequestedSkills(params.metadata ?? {});
|
|
187
|
+
const userExtra = { channel, chatId };
|
|
188
|
+
if (requestedSkills.length > 0) {
|
|
189
|
+
userExtra.requested_skills = requestedSkills;
|
|
190
|
+
}
|
|
191
|
+
const userEvent = this.options.sessionManager.addMessage(session, "user", params.content, userExtra);
|
|
192
|
+
params.onSessionEvent?.(userEvent);
|
|
193
|
+
const sdk = await this.getSdkModule();
|
|
194
|
+
const abortController = new AbortController();
|
|
195
|
+
const timeout = this.createRequestTimeout(abortController);
|
|
196
|
+
const queryOptions = this.buildQueryOptions(sessionKey, model, abortController);
|
|
197
|
+
const requestedSkillsContent = this.skillsLoader.loadSkillsForContext(requestedSkills);
|
|
198
|
+
const prompt = requestedSkillsContent.trim().length > 0 ? [
|
|
199
|
+
"## Requested Skills",
|
|
200
|
+
"Use the following selected skills for this turn:",
|
|
201
|
+
requestedSkillsContent,
|
|
202
|
+
"## User Message",
|
|
203
|
+
params.content
|
|
204
|
+
].join("\n\n") : params.content;
|
|
205
|
+
const query = sdk.query({
|
|
206
|
+
prompt,
|
|
207
|
+
options: queryOptions
|
|
208
|
+
});
|
|
209
|
+
const assistantMessages = [];
|
|
210
|
+
let resultReply = "";
|
|
211
|
+
try {
|
|
212
|
+
for await (const message of query) {
|
|
213
|
+
this.trackSessionId(sessionKey, message);
|
|
214
|
+
const streamEvent = this.options.sessionManager.appendEvent(session, {
|
|
215
|
+
type: this.toSessionEventType(message),
|
|
216
|
+
data: { message },
|
|
217
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
218
|
+
});
|
|
219
|
+
params.onSessionEvent?.(streamEvent);
|
|
220
|
+
const delta = this.extractAssistantDelta(message);
|
|
221
|
+
if (delta) {
|
|
222
|
+
params.onAssistantDelta?.(delta);
|
|
223
|
+
}
|
|
224
|
+
const assistantText = this.extractAssistantText(message);
|
|
225
|
+
if (assistantText) {
|
|
226
|
+
assistantMessages.push(assistantText);
|
|
227
|
+
}
|
|
228
|
+
const result = this.extractResultMessage(message);
|
|
229
|
+
if (!result) {
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
if (!result.ok) {
|
|
233
|
+
throw new Error(result.error);
|
|
234
|
+
}
|
|
235
|
+
resultReply = result.text;
|
|
236
|
+
}
|
|
237
|
+
} finally {
|
|
238
|
+
if (timeout !== null) {
|
|
239
|
+
clearTimeout(timeout);
|
|
240
|
+
}
|
|
241
|
+
query.close();
|
|
242
|
+
}
|
|
243
|
+
const assistantReply = assistantMessages.join("\n").trim();
|
|
244
|
+
const reply = assistantReply || resultReply.trim();
|
|
245
|
+
const assistantEvent = this.options.sessionManager.addMessage(session, "assistant", reply, {
|
|
246
|
+
channel,
|
|
247
|
+
chatId
|
|
248
|
+
});
|
|
249
|
+
params.onSessionEvent?.(assistantEvent);
|
|
250
|
+
this.options.sessionManager.save(session);
|
|
251
|
+
return reply;
|
|
252
|
+
}
|
|
253
|
+
applyRuntimeConfig(_config) {
|
|
254
|
+
}
|
|
255
|
+
async getSdkModule() {
|
|
256
|
+
if (!this.sdkModulePromise) {
|
|
257
|
+
this.sdkModulePromise = claudeAgentLoader.loadClaudeAgentSdkModule();
|
|
258
|
+
}
|
|
259
|
+
return this.sdkModulePromise;
|
|
260
|
+
}
|
|
261
|
+
buildQueryOptions(sessionKey, model, abortController) {
|
|
262
|
+
const env = {
|
|
263
|
+
...process.env,
|
|
264
|
+
...this.options.env ?? {}
|
|
265
|
+
};
|
|
266
|
+
if (this.options.apiKey) {
|
|
267
|
+
env.ANTHROPIC_API_KEY = this.options.apiKey;
|
|
268
|
+
}
|
|
269
|
+
if (this.options.apiBase) {
|
|
270
|
+
env.ANTHROPIC_BASE_URL = this.options.apiBase;
|
|
271
|
+
env.ANTHROPIC_API_URL = this.options.apiBase;
|
|
272
|
+
}
|
|
273
|
+
const options = {
|
|
274
|
+
...this.options.baseQueryOptions,
|
|
275
|
+
abortController,
|
|
276
|
+
cwd: this.options.workspace,
|
|
277
|
+
model,
|
|
278
|
+
env
|
|
279
|
+
};
|
|
280
|
+
const resumeSessionId = this.sessionIdsByKey.get(sessionKey);
|
|
281
|
+
if (resumeSessionId) {
|
|
282
|
+
options.resume = resumeSessionId;
|
|
283
|
+
}
|
|
284
|
+
if (options.permissionMode === "bypassPermissions" && options.allowDangerouslySkipPermissions !== true) {
|
|
285
|
+
options.allowDangerouslySkipPermissions = true;
|
|
286
|
+
}
|
|
287
|
+
return options;
|
|
288
|
+
}
|
|
289
|
+
createRequestTimeout(abortController) {
|
|
290
|
+
if (this.options.requestTimeoutMs <= 0) {
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
const timeout = setTimeout(() => {
|
|
294
|
+
abortController.abort();
|
|
295
|
+
}, this.options.requestTimeoutMs);
|
|
296
|
+
timeout.unref?.();
|
|
297
|
+
return timeout;
|
|
298
|
+
}
|
|
299
|
+
trackSessionId(sessionKey, message) {
|
|
300
|
+
const maybeSessionId = message && typeof message === "object" && "session_id" in message ? message.session_id : void 0;
|
|
301
|
+
if (typeof maybeSessionId === "string" && maybeSessionId.trim()) {
|
|
302
|
+
this.sessionIdsByKey.set(sessionKey, maybeSessionId.trim());
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
toSessionEventType(message) {
|
|
306
|
+
const baseType = message && typeof message === "object" && "type" in message && typeof message.type === "string" ? message.type : "unknown";
|
|
307
|
+
const maybeSubtype = message && typeof message === "object" && "subtype" in message ? message.subtype : void 0;
|
|
308
|
+
if (typeof maybeSubtype === "string" && maybeSubtype.trim()) {
|
|
309
|
+
return `engine.claude.${baseType}.${maybeSubtype.trim()}`;
|
|
310
|
+
}
|
|
311
|
+
return `engine.claude.${baseType}`;
|
|
312
|
+
}
|
|
313
|
+
extractAssistantText(message) {
|
|
314
|
+
if (message.type !== "assistant") {
|
|
315
|
+
return "";
|
|
316
|
+
}
|
|
317
|
+
const payload = message.message;
|
|
318
|
+
const content = payload?.content;
|
|
319
|
+
if (typeof content === "string") {
|
|
320
|
+
return content.trim();
|
|
321
|
+
}
|
|
322
|
+
if (!Array.isArray(content)) {
|
|
323
|
+
return "";
|
|
324
|
+
}
|
|
325
|
+
const text = content.map((block) => {
|
|
326
|
+
if (!block || typeof block !== "object") {
|
|
327
|
+
return "";
|
|
328
|
+
}
|
|
329
|
+
const candidate = block;
|
|
330
|
+
if (candidate.type !== "text" || typeof candidate.text !== "string") {
|
|
331
|
+
return "";
|
|
332
|
+
}
|
|
333
|
+
return candidate.text;
|
|
334
|
+
}).join("").trim();
|
|
335
|
+
return text;
|
|
336
|
+
}
|
|
337
|
+
extractAssistantDelta(message) {
|
|
338
|
+
if (message.type !== "stream_event") {
|
|
339
|
+
return "";
|
|
340
|
+
}
|
|
341
|
+
const event = message.event;
|
|
342
|
+
if (!event || typeof event !== "object") {
|
|
343
|
+
return "";
|
|
344
|
+
}
|
|
345
|
+
const eventObj = event;
|
|
346
|
+
if (eventObj.type === "content_block_delta") {
|
|
347
|
+
const delta = eventObj.delta;
|
|
348
|
+
if (delta?.type === "text_delta" && typeof delta.text === "string") {
|
|
349
|
+
return delta.text;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
if (typeof eventObj.text === "string") {
|
|
353
|
+
return eventObj.text;
|
|
354
|
+
}
|
|
355
|
+
return "";
|
|
356
|
+
}
|
|
357
|
+
extractResultMessage(message) {
|
|
358
|
+
if (message.type !== "result") {
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
if (message.subtype === "success") {
|
|
362
|
+
return { ok: true, text: typeof message.result === "string" ? message.result : "" };
|
|
363
|
+
}
|
|
364
|
+
const errors = Array.isArray(message.errors) ? message.errors.map((entry) => String(entry)).filter(Boolean) : [];
|
|
365
|
+
return {
|
|
366
|
+
ok: false,
|
|
367
|
+
error: errors.join("; ") || `claude-agent-sdk execution failed: ${message.subtype}`
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
const plugin = {
|
|
372
|
+
id: "nextclaw-engine-claude-agent-sdk",
|
|
373
|
+
name: "NextClaw Claude Agent SDK Engine",
|
|
374
|
+
description: "Registers engine kind `claude-agent-sdk` backed by Anthropic Claude Agent SDK.",
|
|
375
|
+
configSchema: {
|
|
376
|
+
type: "object",
|
|
377
|
+
additionalProperties: true,
|
|
378
|
+
properties: {}
|
|
379
|
+
},
|
|
380
|
+
register(api) {
|
|
381
|
+
api.registerEngine(
|
|
382
|
+
(context) => {
|
|
383
|
+
const engineConfig = context.engineConfig ?? {};
|
|
384
|
+
const modelInput = readString(engineConfig, "model") ?? context.model;
|
|
385
|
+
const model = normalizeClaudeModel(modelInput);
|
|
386
|
+
const resolved = resolveEngineConfig(context.config, modelInput, engineConfig);
|
|
387
|
+
const permissionMode = readPermissionMode(engineConfig, "permissionMode") ?? "bypassPermissions";
|
|
388
|
+
const allowDangerouslySkipPermissions = readBoolean(engineConfig, "allowDangerouslySkipPermissions");
|
|
389
|
+
const includePartialMessages = readBoolean(engineConfig, "includePartialMessages") ?? true;
|
|
390
|
+
const maxTurns = readNumber(engineConfig, "maxTurns") ?? context.maxIterations;
|
|
391
|
+
const maxThinkingTokens = readNumber(engineConfig, "maxThinkingTokens");
|
|
392
|
+
const requestTimeoutMs = Math.max(0, Math.trunc(readNumber(engineConfig, "requestTimeoutMs") ?? 3e4));
|
|
393
|
+
const baseQueryOptions = {
|
|
394
|
+
permissionMode,
|
|
395
|
+
includePartialMessages,
|
|
396
|
+
maxTurns,
|
|
397
|
+
additionalDirectories: readStringArray(engineConfig, "additionalDirectories"),
|
|
398
|
+
allowedTools: readStringArray(engineConfig, "allowedTools"),
|
|
399
|
+
disallowedTools: readStringArray(engineConfig, "disallowedTools"),
|
|
400
|
+
settingSources: readSettingSources(engineConfig, "settingSources"),
|
|
401
|
+
pathToClaudeCodeExecutable: readString(engineConfig, "pathToClaudeCodeExecutable") ?? readString(engineConfig, "claudeCodePath"),
|
|
402
|
+
executable: readExecutable(engineConfig, "executable"),
|
|
403
|
+
executableArgs: readStringArray(engineConfig, "executableArgs"),
|
|
404
|
+
extraArgs: readStringOrNullRecord(engineConfig, "extraArgs"),
|
|
405
|
+
sandbox: readRecord(engineConfig, "sandbox"),
|
|
406
|
+
persistSession: readBoolean(engineConfig, "persistSession"),
|
|
407
|
+
continue: readBoolean(engineConfig, "continue"),
|
|
408
|
+
...typeof maxThinkingTokens === "number" ? { maxThinkingTokens } : {}
|
|
409
|
+
};
|
|
410
|
+
if (typeof allowDangerouslySkipPermissions === "boolean") {
|
|
411
|
+
baseQueryOptions.allowDangerouslySkipPermissions = allowDangerouslySkipPermissions;
|
|
412
|
+
}
|
|
413
|
+
return new PluginClaudeAgentSdkEngine({
|
|
414
|
+
bus: context.bus,
|
|
415
|
+
sessionManager: context.sessionManager,
|
|
416
|
+
model,
|
|
417
|
+
workspace: readString(engineConfig, "workingDirectory") ?? context.workspace,
|
|
418
|
+
apiKey: resolved.apiKey,
|
|
419
|
+
apiBase: resolved.apiBase,
|
|
420
|
+
env: readStringRecord(engineConfig, "env"),
|
|
421
|
+
baseQueryOptions,
|
|
422
|
+
requestTimeoutMs
|
|
423
|
+
});
|
|
424
|
+
},
|
|
425
|
+
{ kind: "claude-agent-sdk" }
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
};
|
|
429
|
+
var index_default = plugin;
|
|
430
|
+
export {
|
|
431
|
+
index_default as default
|
|
432
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "nextclaw-engine-claude-agent-sdk",
|
|
3
|
+
"name": "NextClaw Claude Agent SDK Engine",
|
|
4
|
+
"description": "Registers engine kind `claude-agent-sdk` backed by Anthropic Claude Agent SDK.",
|
|
5
|
+
"version": "0.1.0",
|
|
6
|
+
"configSchema": {
|
|
7
|
+
"type": "object",
|
|
8
|
+
"additionalProperties": true,
|
|
9
|
+
"properties": {}
|
|
10
|
+
}
|
|
11
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nextclaw/nextclaw-engine-claude-agent-sdk",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "NextClaw Claude Agent SDK engine plugin backed by Anthropic official SDK.",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"claude-agent-sdk-loader.cjs",
|
|
16
|
+
"openclaw.plugin.json",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
19
|
+
"openclaw": {
|
|
20
|
+
"extensions": [
|
|
21
|
+
"dist/index.js"
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@anthropic-ai/claude-agent-sdk": "^0.2.63",
|
|
26
|
+
"@nextclaw/core": "^0.7.0",
|
|
27
|
+
"zod": "^4.0.0"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@types/node": "^20.17.6",
|
|
31
|
+
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
|
32
|
+
"@typescript-eslint/parser": "^7.18.0",
|
|
33
|
+
"eslint": "^8.57.1",
|
|
34
|
+
"eslint-config-prettier": "^9.1.0",
|
|
35
|
+
"tsup": "^8.3.5",
|
|
36
|
+
"typescript": "^5.6.3"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsup --config tsup.config.ts",
|
|
40
|
+
"lint": "eslint .",
|
|
41
|
+
"tsc": "tsc -p tsconfig.json"
|
|
42
|
+
}
|
|
43
|
+
}
|