mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-07 22:09:57 +00:00
fix(plugins): expose model auth API to context-engine plugins (#41090)
Merged via squash.
Prepared head SHA: ee96e96bb9
Co-authored-by: xinhuagu <562450+xinhuagu@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
This commit is contained in:
@@ -53,4 +53,21 @@ describe("plugin runtime command execution", () => {
|
||||
const runtime = createPluginRuntime();
|
||||
expect(runtime.system.requestHeartbeatNow).toBe(requestHeartbeatNow);
|
||||
});
|
||||
|
||||
it("exposes runtime.modelAuth with getApiKeyForModel and resolveApiKeyForProvider", () => {
|
||||
const runtime = createPluginRuntime();
|
||||
expect(runtime.modelAuth).toBeDefined();
|
||||
expect(typeof runtime.modelAuth.getApiKeyForModel).toBe("function");
|
||||
expect(typeof runtime.modelAuth.resolveApiKeyForProvider).toBe("function");
|
||||
});
|
||||
|
||||
it("modelAuth wrappers strip agentDir and store to prevent credential steering", async () => {
|
||||
// The wrappers should not forward agentDir or store from plugin callers.
|
||||
// We verify this by checking the wrapper functions exist and are not the
|
||||
// raw implementations (they are wrapped, not direct references).
|
||||
const { getApiKeyForModel: rawGetApiKey } = await import("../../agents/model-auth.js");
|
||||
const runtime = createPluginRuntime();
|
||||
// Wrappers should NOT be the same reference as the raw functions
|
||||
expect(runtime.modelAuth.getApiKeyForModel).not.toBe(rawGetApiKey);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { createRequire } from "node:module";
|
||||
import {
|
||||
getApiKeyForModel as getApiKeyForModelRaw,
|
||||
resolveApiKeyForProvider as resolveApiKeyForProviderRaw,
|
||||
} from "../../agents/model-auth.js";
|
||||
import { resolveStateDir } from "../../config/paths.js";
|
||||
import { transcribeAudioFile } from "../../media-understanding/transcribe-audio.js";
|
||||
import { textToSpeechTelephony } from "../../tts/tts.js";
|
||||
@@ -59,6 +63,24 @@ export function createPluginRuntime(_options: CreatePluginRuntimeOptions = {}):
|
||||
events: createRuntimeEvents(),
|
||||
logging: createRuntimeLogging(),
|
||||
state: { resolveStateDir },
|
||||
modelAuth: {
|
||||
// Wrap model-auth helpers so plugins cannot steer credential lookups:
|
||||
// - agentDir / store: stripped (prevents reading other agents' stores)
|
||||
// - profileId / preferredProfile: stripped (prevents cross-provider
|
||||
// credential access via profile steering)
|
||||
// Plugins only specify provider/model; the core auth pipeline picks
|
||||
// the appropriate credential automatically.
|
||||
getApiKeyForModel: (params) =>
|
||||
getApiKeyForModelRaw({
|
||||
model: params.model,
|
||||
cfg: params.cfg,
|
||||
}),
|
||||
resolveApiKeyForProvider: (params) =>
|
||||
resolveApiKeyForProviderRaw({
|
||||
provider: params.provider,
|
||||
cfg: params.cfg,
|
||||
}),
|
||||
},
|
||||
} satisfies PluginRuntime;
|
||||
|
||||
return runtime;
|
||||
|
||||
@@ -52,4 +52,16 @@ export type PluginRuntimeCore = {
|
||||
state: {
|
||||
resolveStateDir: typeof import("../../config/paths.js").resolveStateDir;
|
||||
};
|
||||
modelAuth: {
|
||||
/** Resolve auth for a model. Only provider/model and optional cfg are used. */
|
||||
getApiKeyForModel: (params: {
|
||||
model: import("@mariozechner/pi-ai").Model<import("@mariozechner/pi-ai").Api>;
|
||||
cfg?: import("../../config/config.js").OpenClawConfig;
|
||||
}) => Promise<import("../../agents/model-auth.js").ResolvedProviderAuth>;
|
||||
/** Resolve auth for a provider by name. Only provider and optional cfg are used. */
|
||||
resolveApiKeyForProvider: (params: {
|
||||
provider: string;
|
||||
cfg?: import("../../config/config.js").OpenClawConfig;
|
||||
}) => Promise<import("../../agents/model-auth.js").ResolvedProviderAuth>;
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user