@martian-engineering/lossless-claw 0.2.7 → 0.2.8
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 +448 -26
- package/package.json +1 -1
package/index.ts
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* DAG-based conversation summarization with incremental compaction,
|
|
5
5
|
* full-text search, and sub-agent expansion.
|
|
6
6
|
*/
|
|
7
|
-
import { readFileSync } from "node:fs";
|
|
7
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
8
|
+
import { join } from "node:path";
|
|
8
9
|
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
9
10
|
import { resolveLcmConfig } from "./src/db/config.js";
|
|
10
11
|
import { LcmContextEngine } from "./src/engine.js";
|
|
@@ -45,8 +46,12 @@ type PluginEnvSnapshot = {
|
|
|
45
46
|
pluginSummaryProvider: string;
|
|
46
47
|
openclawProvider: string;
|
|
47
48
|
openclawDefaultModel: string;
|
|
49
|
+
agentDir: string;
|
|
50
|
+
home: string;
|
|
48
51
|
};
|
|
49
52
|
|
|
53
|
+
type ReadEnvFn = (key: string) => string | undefined;
|
|
54
|
+
|
|
50
55
|
type CompleteSimpleOptions = {
|
|
51
56
|
apiKey?: string;
|
|
52
57
|
maxTokens: number;
|
|
@@ -90,6 +95,10 @@ type RuntimeModelAuth = {
|
|
|
90
95
|
}) => Promise<RuntimeModelAuthResult | undefined>;
|
|
91
96
|
};
|
|
92
97
|
|
|
98
|
+
const MODEL_AUTH_PR_URL = "https://github.com/openclaw/openclaw/pull/41090";
|
|
99
|
+
const MODEL_AUTH_MERGE_COMMIT = "4790e40";
|
|
100
|
+
const MODEL_AUTH_REQUIRED_RELEASE = "the first OpenClaw release after 2026.3.8";
|
|
101
|
+
|
|
93
102
|
/** Capture plugin env values once during initialization. */
|
|
94
103
|
function snapshotPluginEnv(env: NodeJS.ProcessEnv = process.env): PluginEnvSnapshot {
|
|
95
104
|
return {
|
|
@@ -99,6 +108,8 @@ function snapshotPluginEnv(env: NodeJS.ProcessEnv = process.env): PluginEnvSnaps
|
|
|
99
108
|
pluginSummaryProvider: "",
|
|
100
109
|
openclawProvider: env.OPENCLAW_PROVIDER?.trim() ?? "",
|
|
101
110
|
openclawDefaultModel: "",
|
|
111
|
+
agentDir: env.OPENCLAW_AGENT_DIR?.trim() || env.PI_CODING_AGENT_DIR?.trim() || "",
|
|
112
|
+
home: env.HOME?.trim() ?? "",
|
|
102
113
|
};
|
|
103
114
|
}
|
|
104
115
|
|
|
@@ -117,6 +128,58 @@ function readDefaultModelFromConfig(config: unknown): string {
|
|
|
117
128
|
return typeof primary === "string" ? primary.trim() : "";
|
|
118
129
|
}
|
|
119
130
|
|
|
131
|
+
/** Resolve common provider API keys from environment. */
|
|
132
|
+
function resolveApiKey(provider: string, readEnv: ReadEnvFn): string | undefined {
|
|
133
|
+
const keyMap: Record<string, string[]> = {
|
|
134
|
+
openai: ["OPENAI_API_KEY"],
|
|
135
|
+
anthropic: ["ANTHROPIC_API_KEY"],
|
|
136
|
+
google: ["GOOGLE_API_KEY", "GEMINI_API_KEY"],
|
|
137
|
+
groq: ["GROQ_API_KEY"],
|
|
138
|
+
xai: ["XAI_API_KEY"],
|
|
139
|
+
mistral: ["MISTRAL_API_KEY"],
|
|
140
|
+
together: ["TOGETHER_API_KEY"],
|
|
141
|
+
openrouter: ["OPENROUTER_API_KEY"],
|
|
142
|
+
"github-copilot": ["GITHUB_COPILOT_API_KEY", "GITHUB_TOKEN"],
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const providerKey = provider.trim().toLowerCase();
|
|
146
|
+
const keys = keyMap[providerKey] ?? [];
|
|
147
|
+
const normalizedProviderEnv = `${providerKey.replace(/[^a-z0-9]/g, "_").toUpperCase()}_API_KEY`;
|
|
148
|
+
keys.push(normalizedProviderEnv);
|
|
149
|
+
|
|
150
|
+
for (const key of keys) {
|
|
151
|
+
const value = readEnv(key)?.trim();
|
|
152
|
+
if (value) {
|
|
153
|
+
return value;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
type AuthProfileCredential =
|
|
160
|
+
| { type: "api_key"; provider: string; key?: string; email?: string }
|
|
161
|
+
| { type: "token"; provider: string; token?: string; expires?: number; email?: string }
|
|
162
|
+
| ({
|
|
163
|
+
type: "oauth";
|
|
164
|
+
provider: string;
|
|
165
|
+
access?: string;
|
|
166
|
+
refresh?: string;
|
|
167
|
+
expires?: number;
|
|
168
|
+
email?: string;
|
|
169
|
+
} & Record<string, unknown>);
|
|
170
|
+
|
|
171
|
+
type AuthProfileStore = {
|
|
172
|
+
profiles: Record<string, AuthProfileCredential>;
|
|
173
|
+
order?: Record<string, string[]>;
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
type PiAiOAuthCredentials = {
|
|
177
|
+
refresh: string;
|
|
178
|
+
access: string;
|
|
179
|
+
expires: number;
|
|
180
|
+
[key: string]: unknown;
|
|
181
|
+
};
|
|
182
|
+
|
|
120
183
|
type PiAiModule = {
|
|
121
184
|
completeSimple?: (
|
|
122
185
|
model: {
|
|
@@ -148,6 +211,11 @@ type PiAiModule = {
|
|
|
148
211
|
) => Promise<Record<string, unknown> & { content?: Array<{ type: string; text?: string }> }>;
|
|
149
212
|
getModel?: (provider: string, modelId: string) => unknown;
|
|
150
213
|
getModels?: (provider: string) => unknown[];
|
|
214
|
+
getEnvApiKey?: (provider: string) => string | undefined;
|
|
215
|
+
getOAuthApiKey?: (
|
|
216
|
+
providerId: string,
|
|
217
|
+
credentials: Record<string, PiAiOAuthCredentials>,
|
|
218
|
+
) => Promise<{ apiKey: string; newCredentials: PiAiOAuthCredentials } | null>;
|
|
151
219
|
};
|
|
152
220
|
|
|
153
221
|
/** Narrow unknown values to plain objects. */
|
|
@@ -251,14 +319,11 @@ function resolveProviderApiFromRuntimeConfig(
|
|
|
251
319
|
return typeof api === "string" && api.trim() ? api.trim() : undefined;
|
|
252
320
|
}
|
|
253
321
|
|
|
254
|
-
/** Resolve runtime.modelAuth from plugin runtime
|
|
255
|
-
function getRuntimeModelAuth(api: OpenClawPluginApi): RuntimeModelAuth {
|
|
322
|
+
/** Resolve runtime.modelAuth from plugin runtime when available. */
|
|
323
|
+
function getRuntimeModelAuth(api: OpenClawPluginApi): RuntimeModelAuth | undefined {
|
|
256
324
|
const runtime = api.runtime as OpenClawPluginApi["runtime"] & {
|
|
257
325
|
modelAuth?: RuntimeModelAuth;
|
|
258
326
|
};
|
|
259
|
-
if (!runtime.modelAuth) {
|
|
260
|
-
throw new Error("OpenClaw runtime.modelAuth is required by lossless-claw.");
|
|
261
|
-
}
|
|
262
327
|
return runtime.modelAuth;
|
|
263
328
|
}
|
|
264
329
|
|
|
@@ -292,6 +357,294 @@ function resolveApiKeyFromAuthResult(auth: RuntimeModelAuthResult | undefined):
|
|
|
292
357
|
return apiKey ? apiKey : undefined;
|
|
293
358
|
}
|
|
294
359
|
|
|
360
|
+
function buildLegacyAuthFallbackWarning(): string {
|
|
361
|
+
return [
|
|
362
|
+
"[lcm] OpenClaw runtime.modelAuth is unavailable; using legacy auth-profiles fallback.",
|
|
363
|
+
`Stock lossless-claw 0.2.7 expects OpenClaw plugin runtime support from PR #41090 (${MODEL_AUTH_PR_URL}).`,
|
|
364
|
+
`OpenClaw 2026.3.8 and 2026.3.8-beta.1 do not include merge commit ${MODEL_AUTH_MERGE_COMMIT};`,
|
|
365
|
+
`${MODEL_AUTH_REQUIRED_RELEASE} is required for stock lossless-claw 0.2.7 without this fallback patch.`,
|
|
366
|
+
].join(" ");
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/** Parse auth-profiles JSON into a minimal store shape. */
|
|
370
|
+
function parseAuthProfileStore(raw: string): AuthProfileStore | undefined {
|
|
371
|
+
try {
|
|
372
|
+
const parsed = JSON.parse(raw) as unknown;
|
|
373
|
+
if (!isRecord(parsed) || !isRecord(parsed.profiles)) {
|
|
374
|
+
return undefined;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const profiles: Record<string, AuthProfileCredential> = {};
|
|
378
|
+
for (const [profileId, value] of Object.entries(parsed.profiles)) {
|
|
379
|
+
if (!isRecord(value)) {
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
const type = value.type;
|
|
383
|
+
const provider = typeof value.provider === "string" ? value.provider.trim() : "";
|
|
384
|
+
if (!provider || (type !== "api_key" && type !== "token" && type !== "oauth")) {
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
profiles[profileId] = value as AuthProfileCredential;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const rawOrder = isRecord(parsed.order) ? parsed.order : undefined;
|
|
391
|
+
const order: Record<string, string[]> | undefined = rawOrder
|
|
392
|
+
? Object.entries(rawOrder).reduce<Record<string, string[]>>((acc, [provider, value]) => {
|
|
393
|
+
if (!Array.isArray(value)) {
|
|
394
|
+
return acc;
|
|
395
|
+
}
|
|
396
|
+
const ids = value
|
|
397
|
+
.map((entry) => (typeof entry === "string" ? entry.trim() : ""))
|
|
398
|
+
.filter(Boolean);
|
|
399
|
+
if (ids.length > 0) {
|
|
400
|
+
acc[provider] = ids;
|
|
401
|
+
}
|
|
402
|
+
return acc;
|
|
403
|
+
}, {})
|
|
404
|
+
: undefined;
|
|
405
|
+
|
|
406
|
+
return {
|
|
407
|
+
profiles,
|
|
408
|
+
...(order && Object.keys(order).length > 0 ? { order } : {}),
|
|
409
|
+
};
|
|
410
|
+
} catch {
|
|
411
|
+
return undefined;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/** Merge auth stores, letting later stores override earlier profiles/order. */
|
|
416
|
+
function mergeAuthProfileStores(stores: AuthProfileStore[]): AuthProfileStore | undefined {
|
|
417
|
+
if (stores.length === 0) {
|
|
418
|
+
return undefined;
|
|
419
|
+
}
|
|
420
|
+
const merged: AuthProfileStore = { profiles: {} };
|
|
421
|
+
for (const store of stores) {
|
|
422
|
+
merged.profiles = { ...merged.profiles, ...store.profiles };
|
|
423
|
+
if (store.order) {
|
|
424
|
+
merged.order = { ...(merged.order ?? {}), ...store.order };
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
return merged;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/** Determine candidate auth store paths ordered by precedence. */
|
|
431
|
+
function resolveAuthStorePaths(params: { agentDir?: string; envSnapshot: PluginEnvSnapshot }): string[] {
|
|
432
|
+
const paths: string[] = [];
|
|
433
|
+
const directAgentDir = params.agentDir?.trim();
|
|
434
|
+
if (directAgentDir) {
|
|
435
|
+
paths.push(join(directAgentDir, "auth-profiles.json"));
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const envAgentDir = params.envSnapshot.agentDir;
|
|
439
|
+
if (envAgentDir) {
|
|
440
|
+
paths.push(join(envAgentDir, "auth-profiles.json"));
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const home = params.envSnapshot.home;
|
|
444
|
+
if (home) {
|
|
445
|
+
paths.push(join(home, ".openclaw", "agents", "main", "agent", "auth-profiles.json"));
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
return [...new Set(paths)];
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/** Build profile selection order for provider auth lookup. */
|
|
452
|
+
function resolveAuthProfileCandidates(params: {
|
|
453
|
+
provider: string;
|
|
454
|
+
store: AuthProfileStore;
|
|
455
|
+
authProfileId?: string;
|
|
456
|
+
runtimeConfig?: unknown;
|
|
457
|
+
}): string[] {
|
|
458
|
+
const candidates: string[] = [];
|
|
459
|
+
const normalizedProvider = normalizeProviderId(params.provider);
|
|
460
|
+
const push = (value: string | undefined) => {
|
|
461
|
+
const profileId = value?.trim();
|
|
462
|
+
if (!profileId) {
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
if (!candidates.includes(profileId)) {
|
|
466
|
+
candidates.push(profileId);
|
|
467
|
+
}
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
push(params.authProfileId);
|
|
471
|
+
|
|
472
|
+
const storeOrder = findProviderConfigValue(params.store.order, params.provider);
|
|
473
|
+
for (const profileId of storeOrder ?? []) {
|
|
474
|
+
push(profileId);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
if (isRecord(params.runtimeConfig)) {
|
|
478
|
+
const auth = params.runtimeConfig.auth;
|
|
479
|
+
if (isRecord(auth)) {
|
|
480
|
+
const order = findProviderConfigValue(
|
|
481
|
+
isRecord(auth.order) ? (auth.order as Record<string, unknown>) : undefined,
|
|
482
|
+
params.provider,
|
|
483
|
+
);
|
|
484
|
+
if (Array.isArray(order)) {
|
|
485
|
+
for (const profileId of order) {
|
|
486
|
+
if (typeof profileId === "string") {
|
|
487
|
+
push(profileId);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
for (const [profileId, credential] of Object.entries(params.store.profiles)) {
|
|
495
|
+
if (normalizeProviderId(credential.provider) === normalizedProvider) {
|
|
496
|
+
push(profileId);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
return candidates;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/** Resolve OAuth/api-key/token credentials from auth-profiles store. */
|
|
504
|
+
async function resolveApiKeyFromAuthProfiles(params: {
|
|
505
|
+
provider: string;
|
|
506
|
+
authProfileId?: string;
|
|
507
|
+
agentDir?: string;
|
|
508
|
+
runtimeConfig?: unknown;
|
|
509
|
+
piAiModule: PiAiModule;
|
|
510
|
+
envSnapshot: PluginEnvSnapshot;
|
|
511
|
+
}): Promise<string | undefined> {
|
|
512
|
+
const storesWithPaths = resolveAuthStorePaths({
|
|
513
|
+
agentDir: params.agentDir,
|
|
514
|
+
envSnapshot: params.envSnapshot,
|
|
515
|
+
})
|
|
516
|
+
.map((path) => {
|
|
517
|
+
try {
|
|
518
|
+
const parsed = parseAuthProfileStore(readFileSync(path, "utf8"));
|
|
519
|
+
return parsed ? { path, store: parsed } : undefined;
|
|
520
|
+
} catch {
|
|
521
|
+
return undefined;
|
|
522
|
+
}
|
|
523
|
+
})
|
|
524
|
+
.filter((entry): entry is { path: string; store: AuthProfileStore } => !!entry);
|
|
525
|
+
if (storesWithPaths.length === 0) {
|
|
526
|
+
return undefined;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
const mergedStore = mergeAuthProfileStores(storesWithPaths.map((entry) => entry.store));
|
|
530
|
+
if (!mergedStore) {
|
|
531
|
+
return undefined;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
const candidates = resolveAuthProfileCandidates({
|
|
535
|
+
provider: params.provider,
|
|
536
|
+
store: mergedStore,
|
|
537
|
+
authProfileId: params.authProfileId,
|
|
538
|
+
runtimeConfig: params.runtimeConfig,
|
|
539
|
+
});
|
|
540
|
+
if (candidates.length === 0) {
|
|
541
|
+
return undefined;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
const persistPath =
|
|
545
|
+
params.agentDir?.trim() ? join(params.agentDir.trim(), "auth-profiles.json") : storesWithPaths[0]?.path;
|
|
546
|
+
|
|
547
|
+
for (const profileId of candidates) {
|
|
548
|
+
const credential = mergedStore.profiles[profileId];
|
|
549
|
+
if (!credential) {
|
|
550
|
+
continue;
|
|
551
|
+
}
|
|
552
|
+
if (normalizeProviderId(credential.provider) !== normalizeProviderId(params.provider)) {
|
|
553
|
+
continue;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
if (credential.type === "api_key") {
|
|
557
|
+
const key = credential.key?.trim();
|
|
558
|
+
if (key) {
|
|
559
|
+
return key;
|
|
560
|
+
}
|
|
561
|
+
continue;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
if (credential.type === "token") {
|
|
565
|
+
const token = credential.token?.trim();
|
|
566
|
+
if (!token) {
|
|
567
|
+
continue;
|
|
568
|
+
}
|
|
569
|
+
const expires = credential.expires;
|
|
570
|
+
if (typeof expires === "number" && Number.isFinite(expires) && expires > 0 && Date.now() >= expires) {
|
|
571
|
+
continue;
|
|
572
|
+
}
|
|
573
|
+
return token;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
const access = credential.access?.trim();
|
|
577
|
+
const expires = credential.expires;
|
|
578
|
+
const isExpired =
|
|
579
|
+
typeof expires === "number" && Number.isFinite(expires) && expires > 0 && Date.now() >= expires;
|
|
580
|
+
|
|
581
|
+
if (!isExpired && access) {
|
|
582
|
+
if (
|
|
583
|
+
(credential.provider === "google-gemini-cli" || credential.provider === "google-antigravity") &&
|
|
584
|
+
typeof credential.projectId === "string" &&
|
|
585
|
+
credential.projectId.trim()
|
|
586
|
+
) {
|
|
587
|
+
return JSON.stringify({
|
|
588
|
+
token: access,
|
|
589
|
+
projectId: credential.projectId.trim(),
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
return access;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
if (typeof params.piAiModule.getOAuthApiKey !== "function") {
|
|
596
|
+
continue;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
try {
|
|
600
|
+
const oauthCredential = {
|
|
601
|
+
access: credential.access ?? "",
|
|
602
|
+
refresh: credential.refresh ?? "",
|
|
603
|
+
expires: typeof credential.expires === "number" ? credential.expires : 0,
|
|
604
|
+
...(typeof credential.projectId === "string" ? { projectId: credential.projectId } : {}),
|
|
605
|
+
...(typeof credential.accountId === "string" ? { accountId: credential.accountId } : {}),
|
|
606
|
+
};
|
|
607
|
+
const refreshed = await params.piAiModule.getOAuthApiKey(params.provider, {
|
|
608
|
+
[params.provider]: oauthCredential,
|
|
609
|
+
});
|
|
610
|
+
if (!refreshed?.apiKey) {
|
|
611
|
+
continue;
|
|
612
|
+
}
|
|
613
|
+
mergedStore.profiles[profileId] = {
|
|
614
|
+
...credential,
|
|
615
|
+
...refreshed.newCredentials,
|
|
616
|
+
type: "oauth",
|
|
617
|
+
};
|
|
618
|
+
if (persistPath) {
|
|
619
|
+
try {
|
|
620
|
+
writeFileSync(
|
|
621
|
+
persistPath,
|
|
622
|
+
JSON.stringify(
|
|
623
|
+
{
|
|
624
|
+
version: 1,
|
|
625
|
+
profiles: mergedStore.profiles,
|
|
626
|
+
...(mergedStore.order ? { order: mergedStore.order } : {}),
|
|
627
|
+
},
|
|
628
|
+
null,
|
|
629
|
+
2,
|
|
630
|
+
),
|
|
631
|
+
"utf8",
|
|
632
|
+
);
|
|
633
|
+
} catch {
|
|
634
|
+
// Ignore persistence errors: refreshed credentials remain usable in-memory for this run.
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
return refreshed.apiKey;
|
|
638
|
+
} catch {
|
|
639
|
+
if (access) {
|
|
640
|
+
return access;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
return undefined;
|
|
646
|
+
}
|
|
647
|
+
|
|
295
648
|
/** Build a minimal but useful sub-agent prompt. */
|
|
296
649
|
function buildSubagentSystemPrompt(params: {
|
|
297
650
|
depth: number;
|
|
@@ -353,6 +706,7 @@ function createLcmDependencies(api: OpenClawPluginApi): LcmDependencies {
|
|
|
353
706
|
const envSnapshot = snapshotPluginEnv();
|
|
354
707
|
envSnapshot.openclawDefaultModel = readDefaultModelFromConfig(api.config);
|
|
355
708
|
const modelAuth = getRuntimeModelAuth(api);
|
|
709
|
+
const readEnv: ReadEnvFn = (key) => process.env[key];
|
|
356
710
|
const pluginConfig =
|
|
357
711
|
api.pluginConfig && typeof api.pluginConfig === "object" && !Array.isArray(api.pluginConfig)
|
|
358
712
|
? api.pluginConfig
|
|
@@ -371,6 +725,10 @@ function createLcmDependencies(api: OpenClawPluginApi): LcmDependencies {
|
|
|
371
725
|
}
|
|
372
726
|
}
|
|
373
727
|
|
|
728
|
+
if (!modelAuth) {
|
|
729
|
+
api.logger.warn(buildLegacyAuthFallbackWarning());
|
|
730
|
+
}
|
|
731
|
+
|
|
374
732
|
return {
|
|
375
733
|
config,
|
|
376
734
|
complete: async ({
|
|
@@ -448,7 +806,7 @@ function createLcmDependencies(api: OpenClawPluginApi): LcmDependencies {
|
|
|
448
806
|
};
|
|
449
807
|
|
|
450
808
|
let resolvedApiKey = apiKey?.trim();
|
|
451
|
-
if (!resolvedApiKey) {
|
|
809
|
+
if (!resolvedApiKey && modelAuth) {
|
|
452
810
|
try {
|
|
453
811
|
resolvedApiKey = resolveApiKeyFromAuthResult(
|
|
454
812
|
await modelAuth.resolveApiKeyForProvider({
|
|
@@ -464,6 +822,22 @@ function createLcmDependencies(api: OpenClawPluginApi): LcmDependencies {
|
|
|
464
822
|
);
|
|
465
823
|
}
|
|
466
824
|
}
|
|
825
|
+
if (!resolvedApiKey && !modelAuth) {
|
|
826
|
+
resolvedApiKey = resolveApiKey(providerId, readEnv);
|
|
827
|
+
}
|
|
828
|
+
if (!resolvedApiKey && !modelAuth && typeof mod.getEnvApiKey === "function") {
|
|
829
|
+
resolvedApiKey = mod.getEnvApiKey(providerId)?.trim();
|
|
830
|
+
}
|
|
831
|
+
if (!resolvedApiKey && !modelAuth) {
|
|
832
|
+
resolvedApiKey = await resolveApiKeyFromAuthProfiles({
|
|
833
|
+
provider: providerId,
|
|
834
|
+
authProfileId,
|
|
835
|
+
agentDir,
|
|
836
|
+
runtimeConfig,
|
|
837
|
+
piAiModule: mod,
|
|
838
|
+
envSnapshot,
|
|
839
|
+
});
|
|
840
|
+
}
|
|
467
841
|
|
|
468
842
|
const completeOptions = buildCompleteSimpleOptions({
|
|
469
843
|
api: resolvedModel.api,
|
|
@@ -587,28 +961,76 @@ function createLcmDependencies(api: OpenClawPluginApi): LcmDependencies {
|
|
|
587
961
|
return { provider, model: raw };
|
|
588
962
|
},
|
|
589
963
|
getApiKey: async (provider, model, options) => {
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
964
|
+
if (modelAuth) {
|
|
965
|
+
try {
|
|
966
|
+
const modelAuthKey = resolveApiKeyFromAuthResult(
|
|
967
|
+
await modelAuth.getApiKeyForModel({
|
|
968
|
+
model: buildModelAuthLookupModel({ provider, model }),
|
|
969
|
+
cfg: api.config,
|
|
970
|
+
...(options?.profileId ? { profileId: options.profileId } : {}),
|
|
971
|
+
...(options?.preferredProfile ? { preferredProfile: options.preferredProfile } : {}),
|
|
972
|
+
}),
|
|
973
|
+
);
|
|
974
|
+
if (modelAuthKey) {
|
|
975
|
+
return modelAuthKey;
|
|
976
|
+
}
|
|
977
|
+
} catch {
|
|
978
|
+
// Fall through to auth-profile lookup for older OpenClaw runtimes.
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
const envKey = resolveApiKey(provider, readEnv);
|
|
983
|
+
if (envKey) {
|
|
984
|
+
return envKey;
|
|
601
985
|
}
|
|
986
|
+
|
|
987
|
+
const piAiModuleId = "@mariozechner/pi-ai";
|
|
988
|
+
const mod = (await import(piAiModuleId)) as PiAiModule;
|
|
989
|
+
return resolveApiKeyFromAuthProfiles({
|
|
990
|
+
provider,
|
|
991
|
+
authProfileId: options?.profileId,
|
|
992
|
+
agentDir: api.resolvePath("."),
|
|
993
|
+
runtimeConfig: api.config,
|
|
994
|
+
piAiModule: mod,
|
|
995
|
+
envSnapshot,
|
|
996
|
+
});
|
|
602
997
|
},
|
|
603
998
|
requireApiKey: async (provider, model, options) => {
|
|
604
|
-
const key = await
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
999
|
+
const key = await (async () => {
|
|
1000
|
+
if (modelAuth) {
|
|
1001
|
+
try {
|
|
1002
|
+
const modelAuthKey = resolveApiKeyFromAuthResult(
|
|
1003
|
+
await modelAuth.getApiKeyForModel({
|
|
1004
|
+
model: buildModelAuthLookupModel({ provider, model }),
|
|
1005
|
+
cfg: api.config,
|
|
1006
|
+
...(options?.profileId ? { profileId: options.profileId } : {}),
|
|
1007
|
+
...(options?.preferredProfile ? { preferredProfile: options.preferredProfile } : {}),
|
|
1008
|
+
}),
|
|
1009
|
+
);
|
|
1010
|
+
if (modelAuthKey) {
|
|
1011
|
+
return modelAuthKey;
|
|
1012
|
+
}
|
|
1013
|
+
} catch {
|
|
1014
|
+
// Fall through to auth-profile lookup for older OpenClaw runtimes.
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
const envKey = resolveApiKey(provider, readEnv);
|
|
1019
|
+
if (envKey) {
|
|
1020
|
+
return envKey;
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
const piAiModuleId = "@mariozechner/pi-ai";
|
|
1024
|
+
const mod = (await import(piAiModuleId)) as PiAiModule;
|
|
1025
|
+
return resolveApiKeyFromAuthProfiles({
|
|
1026
|
+
provider,
|
|
1027
|
+
authProfileId: options?.profileId,
|
|
1028
|
+
agentDir: api.resolvePath("."),
|
|
1029
|
+
runtimeConfig: api.config,
|
|
1030
|
+
piAiModule: mod,
|
|
1031
|
+
envSnapshot,
|
|
1032
|
+
});
|
|
1033
|
+
})();
|
|
612
1034
|
if (!key) {
|
|
613
1035
|
throw new Error(`Missing API key for provider '${provider}' (model '${model}').`);
|
|
614
1036
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@martian-engineering/lossless-claw",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.8",
|
|
4
4
|
"description": "Lossless Context Management plugin for OpenClaw — DAG-based conversation summarization with incremental compaction",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|