openclaw-abacusai-auth 1.2.1 → 1.2.3
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 +143 -12
- 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;
|
|
@@ -388,7 +421,10 @@ function cleanSchema(schema: unknown): unknown {
|
|
|
388
421
|
}
|
|
389
422
|
|
|
390
423
|
/**
|
|
391
|
-
* Strip `strict` field from tools
|
|
424
|
+
* Strip `strict` field from tools, clean schemas, and promote name/parameters
|
|
425
|
+
* to the top level of each tool object for RouteLLM compatibility.
|
|
426
|
+
* RouteLLM expects `name` and `parameters` accessible at the tool level,
|
|
427
|
+
* not only nested under `function`.
|
|
392
428
|
*/
|
|
393
429
|
function normalizeToolsForRouteLLM(tools: unknown[]): unknown[] {
|
|
394
430
|
return tools.map((t) => {
|
|
@@ -408,12 +444,107 @@ function normalizeToolsForRouteLLM(tools: unknown[]): unknown[] {
|
|
|
408
444
|
}
|
|
409
445
|
|
|
410
446
|
copy.function = fn;
|
|
447
|
+
|
|
448
|
+
// Promote name and parameters to top level for RouteLLM
|
|
449
|
+
if (fn.name && !copy.name) {
|
|
450
|
+
copy.name = fn.name;
|
|
451
|
+
}
|
|
452
|
+
if (fn.parameters !== undefined && copy.parameters === undefined) {
|
|
453
|
+
copy.parameters = fn.parameters;
|
|
454
|
+
}
|
|
455
|
+
if (fn.description && !copy.description) {
|
|
456
|
+
copy.description = fn.description;
|
|
457
|
+
}
|
|
411
458
|
}
|
|
412
459
|
|
|
413
460
|
return copy;
|
|
414
461
|
});
|
|
415
462
|
}
|
|
416
463
|
|
|
464
|
+
/**
|
|
465
|
+
* Normalize tool_calls in request messages for RouteLLM.
|
|
466
|
+
* RouteLLM expects `name` and `parameters` at the top level of each tool_call,
|
|
467
|
+
* but OpenClaw sends them nested under `function` (OpenAI standard format).
|
|
468
|
+
*/
|
|
469
|
+
function normalizeMessagesForRouteLLM(messages: unknown[]): unknown[] {
|
|
470
|
+
return messages.map((msg) => {
|
|
471
|
+
if (!msg || typeof msg !== "object") return msg;
|
|
472
|
+
const m = msg as Record<string, unknown>;
|
|
473
|
+
if (m.role !== "assistant" || !Array.isArray(m.tool_calls)) return msg;
|
|
474
|
+
|
|
475
|
+
const normalized = { ...m };
|
|
476
|
+
normalized.tool_calls = (m.tool_calls as unknown[]).map((tc) => {
|
|
477
|
+
if (!tc || typeof tc !== "object") return tc;
|
|
478
|
+
const call = { ...(tc as Record<string, unknown>) };
|
|
479
|
+
|
|
480
|
+
// Extract name from function.name if not already at top level
|
|
481
|
+
if (!call.name && call.function && typeof call.function === "object") {
|
|
482
|
+
const fn = call.function as Record<string, unknown>;
|
|
483
|
+
call.name = fn.name;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// Extract parameters from function.arguments if not already at top level
|
|
487
|
+
if (call.parameters === undefined && call.function && typeof call.function === "object") {
|
|
488
|
+
const fn = call.function as Record<string, unknown>;
|
|
489
|
+
const args = fn.arguments;
|
|
490
|
+
if (typeof args === "string") {
|
|
491
|
+
try { call.parameters = JSON.parse(args); } catch { call.parameters = args; }
|
|
492
|
+
} else if (args !== undefined) {
|
|
493
|
+
call.parameters = args;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
return call;
|
|
498
|
+
});
|
|
499
|
+
return normalized;
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Normalize a single tool_call from RouteLLM response to OpenAI standard format.
|
|
505
|
+
* RouteLLM may return tool_calls with flat `name`/`parameters` instead of nested `function`.
|
|
506
|
+
*/
|
|
507
|
+
function normalizeResponseToolCall(tc: Record<string, unknown>): Record<string, unknown> {
|
|
508
|
+
// Already in standard format
|
|
509
|
+
if (tc.function && typeof tc.function === "object") {
|
|
510
|
+
return tc;
|
|
511
|
+
}
|
|
512
|
+
// Flat format: { name, parameters/arguments } → { function: { name, arguments } }
|
|
513
|
+
if (tc.name) {
|
|
514
|
+
const args = tc.arguments ?? tc.parameters ?? "{}";
|
|
515
|
+
const argsStr = typeof args === "string" ? args : JSON.stringify(args);
|
|
516
|
+
return {
|
|
517
|
+
id: tc.id ?? tc.call_id ?? `call_${crypto.randomUUID()}`,
|
|
518
|
+
type: "function",
|
|
519
|
+
function: { name: tc.name, arguments: argsStr },
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
return tc;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* Normalize tool_calls in a parsed response JSON (non-streaming).
|
|
527
|
+
*/
|
|
528
|
+
function normalizeResponseToolCalls(json: Record<string, unknown>): Record<string, unknown> {
|
|
529
|
+
if (!Array.isArray(json.choices)) return json;
|
|
530
|
+
json.choices = (json.choices as unknown[]).map((c) => {
|
|
531
|
+
if (!c || typeof c !== "object") return c;
|
|
532
|
+
const choice = { ...(c as Record<string, unknown>) };
|
|
533
|
+
const message = choice.message;
|
|
534
|
+
if (message && typeof message === "object") {
|
|
535
|
+
const msg = { ...(message as Record<string, unknown>) };
|
|
536
|
+
if (Array.isArray(msg.tool_calls)) {
|
|
537
|
+
msg.tool_calls = (msg.tool_calls as unknown[]).map((tc) =>
|
|
538
|
+
tc && typeof tc === "object" ? normalizeResponseToolCall(tc as Record<string, unknown>) : tc
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
choice.message = msg;
|
|
542
|
+
}
|
|
543
|
+
return choice;
|
|
544
|
+
});
|
|
545
|
+
return json;
|
|
546
|
+
}
|
|
547
|
+
|
|
417
548
|
async function handleProxyRequest(req: IncomingMessage, res: ServerResponse) {
|
|
418
549
|
const path = req.url ?? "/";
|
|
419
550
|
const target = `${ROUTELLM_BASE}${path}`;
|
|
@@ -431,6 +562,10 @@ async function handleProxyRequest(req: IncomingMessage, res: ServerResponse) {
|
|
|
431
562
|
if (Array.isArray(parsed.tools)) {
|
|
432
563
|
parsed.tools = normalizeToolsForRouteLLM(parsed.tools);
|
|
433
564
|
}
|
|
565
|
+
// Normalize tool_calls in messages: add top-level name/parameters for RouteLLM
|
|
566
|
+
if (Array.isArray(parsed.messages)) {
|
|
567
|
+
parsed.messages = normalizeMessagesForRouteLLM(parsed.messages);
|
|
568
|
+
}
|
|
434
569
|
body = JSON.stringify(parsed);
|
|
435
570
|
}
|
|
436
571
|
|
|
@@ -476,7 +611,7 @@ async function handleProxyRequest(req: IncomingMessage, res: ServerResponse) {
|
|
|
476
611
|
let buffer = "";
|
|
477
612
|
|
|
478
613
|
const pump = async () => {
|
|
479
|
-
for (
|
|
614
|
+
for (; ;) {
|
|
480
615
|
const { done, value } = await reader.read();
|
|
481
616
|
if (done) {
|
|
482
617
|
// Process any remaining buffer
|
|
@@ -506,17 +641,19 @@ async function handleProxyRequest(req: IncomingMessage, res: ServerResponse) {
|
|
|
506
641
|
await release();
|
|
507
642
|
});
|
|
508
643
|
} else {
|
|
509
|
-
// Non-streaming response - add id and object fields
|
|
644
|
+
// Non-streaming response - add id and object fields, normalize tool_calls
|
|
510
645
|
const data = await upstream.text();
|
|
511
646
|
await release();
|
|
512
647
|
try {
|
|
513
|
-
|
|
648
|
+
let json = JSON.parse(data) as Record<string, unknown>;
|
|
514
649
|
if (!("id" in json)) {
|
|
515
650
|
json.id = chunkId;
|
|
516
651
|
}
|
|
517
652
|
if (!("object" in json)) {
|
|
518
653
|
json.object = "chat.completion";
|
|
519
654
|
}
|
|
655
|
+
// Normalize tool_calls to standard OpenAI format
|
|
656
|
+
json = normalizeResponseToolCalls(json);
|
|
520
657
|
res.writeHead(upstream.status, { "Content-Type": "application/json" });
|
|
521
658
|
res.end(JSON.stringify(json));
|
|
522
659
|
} catch {
|
|
@@ -604,7 +741,7 @@ interface PluginAuthContext {
|
|
|
604
741
|
}
|
|
605
742
|
|
|
606
743
|
const abacusaiPlugin = {
|
|
607
|
-
id: "
|
|
744
|
+
id: "abacusai-auth",
|
|
608
745
|
name: "AbacusAI Auth",
|
|
609
746
|
description: "AbacusAI RouteLLM provider plugin with direct connection and schema normalization",
|
|
610
747
|
configSchema: emptyPluginConfigSchema(),
|
|
@@ -750,12 +887,6 @@ const abacusaiPlugin = {
|
|
|
750
887
|
api: "openai-completions",
|
|
751
888
|
auth: "token",
|
|
752
889
|
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
890
|
},
|
|
760
891
|
},
|
|
761
892
|
},
|
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.3",
|
|
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
|
+
}
|