openclaw-abacusai-auth 1.2.1 → 1.2.2
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/index.ts +128 -11
- package/openclaw.plugin.json +5 -3
- package/package.json +2 -2
package/index.ts
CHANGED
|
@@ -290,7 +290,8 @@ function generateChunkId(): string {
|
|
|
290
290
|
}
|
|
291
291
|
|
|
292
292
|
/**
|
|
293
|
-
* Normalize SSE chunk to add missing id and object fields
|
|
293
|
+
* Normalize SSE chunk to add missing id and object fields,
|
|
294
|
+
* and normalize any tool_calls in delta to standard OpenAI format.
|
|
294
295
|
*/
|
|
295
296
|
function normalizeSseChunk(line: string, chunkId: string): string {
|
|
296
297
|
if (!line.startsWith("data: ") || line === "data: [DONE]") {
|
|
@@ -304,6 +305,38 @@ function normalizeSseChunk(line: string, chunkId: string): string {
|
|
|
304
305
|
if (!("object" in json)) {
|
|
305
306
|
json.object = "chat.completion.chunk";
|
|
306
307
|
}
|
|
308
|
+
// Normalize tool_calls in streaming delta
|
|
309
|
+
if (Array.isArray(json.choices)) {
|
|
310
|
+
json.choices = (json.choices as unknown[]).map((c) => {
|
|
311
|
+
if (!c || typeof c !== "object") return c;
|
|
312
|
+
const choice = c as Record<string, unknown>;
|
|
313
|
+
const delta = choice.delta;
|
|
314
|
+
if (delta && typeof delta === "object") {
|
|
315
|
+
const d = delta as Record<string, unknown>;
|
|
316
|
+
if (Array.isArray(d.tool_calls)) {
|
|
317
|
+
d.tool_calls = (d.tool_calls as unknown[]).map((tc) => {
|
|
318
|
+
if (!tc || typeof tc !== "object") return tc;
|
|
319
|
+
const call = tc as Record<string, unknown>;
|
|
320
|
+
// If flat format (name at top level, no function), convert
|
|
321
|
+
if (call.name && !call.function) {
|
|
322
|
+
const args = call.arguments ?? call.parameters ?? "";
|
|
323
|
+
return {
|
|
324
|
+
index: call.index,
|
|
325
|
+
id: call.id ?? call.call_id ?? `call_${crypto.randomUUID()}`,
|
|
326
|
+
type: "function",
|
|
327
|
+
function: {
|
|
328
|
+
name: call.name,
|
|
329
|
+
arguments: typeof args === "string" ? args : JSON.stringify(args),
|
|
330
|
+
},
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
return call;
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return choice;
|
|
338
|
+
});
|
|
339
|
+
}
|
|
307
340
|
return `data: ${JSON.stringify(json)}`;
|
|
308
341
|
} catch {
|
|
309
342
|
return line;
|
|
@@ -414,6 +447,90 @@ function normalizeToolsForRouteLLM(tools: unknown[]): unknown[] {
|
|
|
414
447
|
});
|
|
415
448
|
}
|
|
416
449
|
|
|
450
|
+
/**
|
|
451
|
+
* Normalize tool_calls in request messages for RouteLLM.
|
|
452
|
+
* RouteLLM expects `name` and `parameters` at the top level of each tool_call,
|
|
453
|
+
* but OpenClaw sends them nested under `function` (OpenAI standard format).
|
|
454
|
+
*/
|
|
455
|
+
function normalizeMessagesForRouteLLM(messages: unknown[]): unknown[] {
|
|
456
|
+
return messages.map((msg) => {
|
|
457
|
+
if (!msg || typeof msg !== "object") return msg;
|
|
458
|
+
const m = msg as Record<string, unknown>;
|
|
459
|
+
if (m.role !== "assistant" || !Array.isArray(m.tool_calls)) return msg;
|
|
460
|
+
|
|
461
|
+
const normalized = { ...m };
|
|
462
|
+
normalized.tool_calls = (m.tool_calls as unknown[]).map((tc) => {
|
|
463
|
+
if (!tc || typeof tc !== "object") return tc;
|
|
464
|
+
const call = { ...(tc as Record<string, unknown>) };
|
|
465
|
+
|
|
466
|
+
// Extract name from function.name if not already at top level
|
|
467
|
+
if (!call.name && call.function && typeof call.function === "object") {
|
|
468
|
+
const fn = call.function as Record<string, unknown>;
|
|
469
|
+
call.name = fn.name;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Extract parameters from function.arguments if not already at top level
|
|
473
|
+
if (call.parameters === undefined && call.function && typeof call.function === "object") {
|
|
474
|
+
const fn = call.function as Record<string, unknown>;
|
|
475
|
+
const args = fn.arguments;
|
|
476
|
+
if (typeof args === "string") {
|
|
477
|
+
try { call.parameters = JSON.parse(args); } catch { call.parameters = args; }
|
|
478
|
+
} else if (args !== undefined) {
|
|
479
|
+
call.parameters = args;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
return call;
|
|
484
|
+
});
|
|
485
|
+
return normalized;
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Normalize a single tool_call from RouteLLM response to OpenAI standard format.
|
|
491
|
+
* RouteLLM may return tool_calls with flat `name`/`parameters` instead of nested `function`.
|
|
492
|
+
*/
|
|
493
|
+
function normalizeResponseToolCall(tc: Record<string, unknown>): Record<string, unknown> {
|
|
494
|
+
// Already in standard format
|
|
495
|
+
if (tc.function && typeof tc.function === "object") {
|
|
496
|
+
return tc;
|
|
497
|
+
}
|
|
498
|
+
// Flat format: { name, parameters/arguments } → { function: { name, arguments } }
|
|
499
|
+
if (tc.name) {
|
|
500
|
+
const args = tc.arguments ?? tc.parameters ?? "{}";
|
|
501
|
+
const argsStr = typeof args === "string" ? args : JSON.stringify(args);
|
|
502
|
+
return {
|
|
503
|
+
id: tc.id ?? tc.call_id ?? `call_${crypto.randomUUID()}`,
|
|
504
|
+
type: "function",
|
|
505
|
+
function: { name: tc.name, arguments: argsStr },
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
return tc;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Normalize tool_calls in a parsed response JSON (non-streaming).
|
|
513
|
+
*/
|
|
514
|
+
function normalizeResponseToolCalls(json: Record<string, unknown>): Record<string, unknown> {
|
|
515
|
+
if (!Array.isArray(json.choices)) return json;
|
|
516
|
+
json.choices = (json.choices as unknown[]).map((c) => {
|
|
517
|
+
if (!c || typeof c !== "object") return c;
|
|
518
|
+
const choice = { ...(c as Record<string, unknown>) };
|
|
519
|
+
const message = choice.message;
|
|
520
|
+
if (message && typeof message === "object") {
|
|
521
|
+
const msg = { ...(message as Record<string, unknown>) };
|
|
522
|
+
if (Array.isArray(msg.tool_calls)) {
|
|
523
|
+
msg.tool_calls = (msg.tool_calls as unknown[]).map((tc) =>
|
|
524
|
+
tc && typeof tc === "object" ? normalizeResponseToolCall(tc as Record<string, unknown>) : tc
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
choice.message = msg;
|
|
528
|
+
}
|
|
529
|
+
return choice;
|
|
530
|
+
});
|
|
531
|
+
return json;
|
|
532
|
+
}
|
|
533
|
+
|
|
417
534
|
async function handleProxyRequest(req: IncomingMessage, res: ServerResponse) {
|
|
418
535
|
const path = req.url ?? "/";
|
|
419
536
|
const target = `${ROUTELLM_BASE}${path}`;
|
|
@@ -431,6 +548,10 @@ async function handleProxyRequest(req: IncomingMessage, res: ServerResponse) {
|
|
|
431
548
|
if (Array.isArray(parsed.tools)) {
|
|
432
549
|
parsed.tools = normalizeToolsForRouteLLM(parsed.tools);
|
|
433
550
|
}
|
|
551
|
+
// Normalize tool_calls in messages: add top-level name/parameters for RouteLLM
|
|
552
|
+
if (Array.isArray(parsed.messages)) {
|
|
553
|
+
parsed.messages = normalizeMessagesForRouteLLM(parsed.messages);
|
|
554
|
+
}
|
|
434
555
|
body = JSON.stringify(parsed);
|
|
435
556
|
}
|
|
436
557
|
|
|
@@ -476,7 +597,7 @@ async function handleProxyRequest(req: IncomingMessage, res: ServerResponse) {
|
|
|
476
597
|
let buffer = "";
|
|
477
598
|
|
|
478
599
|
const pump = async () => {
|
|
479
|
-
for (
|
|
600
|
+
for (; ;) {
|
|
480
601
|
const { done, value } = await reader.read();
|
|
481
602
|
if (done) {
|
|
482
603
|
// Process any remaining buffer
|
|
@@ -506,17 +627,19 @@ async function handleProxyRequest(req: IncomingMessage, res: ServerResponse) {
|
|
|
506
627
|
await release();
|
|
507
628
|
});
|
|
508
629
|
} else {
|
|
509
|
-
// Non-streaming response - add id and object fields
|
|
630
|
+
// Non-streaming response - add id and object fields, normalize tool_calls
|
|
510
631
|
const data = await upstream.text();
|
|
511
632
|
await release();
|
|
512
633
|
try {
|
|
513
|
-
|
|
634
|
+
let json = JSON.parse(data) as Record<string, unknown>;
|
|
514
635
|
if (!("id" in json)) {
|
|
515
636
|
json.id = chunkId;
|
|
516
637
|
}
|
|
517
638
|
if (!("object" in json)) {
|
|
518
639
|
json.object = "chat.completion";
|
|
519
640
|
}
|
|
641
|
+
// Normalize tool_calls to standard OpenAI format
|
|
642
|
+
json = normalizeResponseToolCalls(json);
|
|
520
643
|
res.writeHead(upstream.status, { "Content-Type": "application/json" });
|
|
521
644
|
res.end(JSON.stringify(json));
|
|
522
645
|
} catch {
|
|
@@ -604,7 +727,7 @@ interface PluginAuthContext {
|
|
|
604
727
|
}
|
|
605
728
|
|
|
606
729
|
const abacusaiPlugin = {
|
|
607
|
-
id: "
|
|
730
|
+
id: "abacusai-auth",
|
|
608
731
|
name: "AbacusAI Auth",
|
|
609
732
|
description: "AbacusAI RouteLLM provider plugin with direct connection and schema normalization",
|
|
610
733
|
configSchema: emptyPluginConfigSchema(),
|
|
@@ -750,12 +873,6 @@ const abacusaiPlugin = {
|
|
|
750
873
|
api: "openai-completions",
|
|
751
874
|
auth: "token",
|
|
752
875
|
models: modelIds.map((id) => buildModelDefinition(id)),
|
|
753
|
-
// Note: compat options are kept for future OpenClaw core support
|
|
754
|
-
// Currently, schema cleaning is done by the local proxy
|
|
755
|
-
compat: {
|
|
756
|
-
requiresAdditionalPropertiesFalse: true,
|
|
757
|
-
supportsStrictMode: false,
|
|
758
|
-
},
|
|
759
876
|
},
|
|
760
877
|
},
|
|
761
878
|
},
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openclaw-abacusai-auth",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"description": "OpenClaw AbacusAI provider plugin - Third-party plugin for AbacusAI RouteLLM integration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
@@ -26,4 +26,4 @@
|
|
|
26
26
|
"./index.ts"
|
|
27
27
|
]
|
|
28
28
|
}
|
|
29
|
-
}
|
|
29
|
+
}
|