mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-02 21:45:17 +00:00
* refactor: extract agent core package Introduce packages/agent-core as the OpenClaw-owned home for reusable agent loop, harness, session, prompt, and runtime dependency contracts. * refactor: extract shared llm runtime Move provider model registries, stream wrappers, OAuth helpers, and LLM utilities into src/llm with plugin-sdk barrels instead of depending on the old embedded runtime layout. * refactor: remove pi runtime internals Rename remaining Pi-shaped agent surfaces to OpenClaw agent runtime names, delete obsolete Pi docs and package graph checks, and add the third-party notice for incorporated code. * refactor: tighten agent session runtime Make agent-core/runtime dependencies explicit, consolidate compaction and session transcript helpers, and move model/session helpers behind OpenClaw-owned contracts. * refactor: remove static model and pi auth paths Drop static model catalogs and Pi auth bridges, move model/provider facts to manifest-owned runtime contracts, and harden internal embedded-agent utilities. * refactor: remove legacy provider compat paths * docs: remove agent parity notes * fix: skip provider wildcard metadata parsing * refactor: share session extension sdk loading * refactor: inline acpx proxy error formatter * refactor: fold edit recovery into edit tool * fix: accept extension batch separator * test: align startup provider plugin expectations * fix: restore provider-scoped release discovery * test: align static asset packaging expectations * fix: run static provider catalogs during scoped discovery * fix: add provider entry catalogs for scoped live discovery * fix: load lightweight provider catalog entries * fix: refresh provider-scoped plugin metadata * fix: keep provider catalog entries on release live path * fix: keep static manifest models in release live checks * fix: harden release model discovery * fix: reduce OpenAI live cache probe reasoning * fix: disable OpenAI cache probe reasoning * ci: extend OpenAI gateway live timeout * fix: extend live gateway model budget * fix: stabilize release validation regressions * fix: honor provider aliases in model rows * fix: stabilize release validation lanes * fix: stabilize release memory qa * ci: stabilize release validation lanes * ci: prefer ipv4 for live docker node calls * fix: restore shared tool-call stream wrapper * ci: remove legacy pi test shard alias * fix: clean up embedded agent test drift * fix: stabilize runtime alias status * fix: clean up embedded agent ci drift * fix: restore release ci invariants * fix: clean up post-rebase runtime drift * fix: restore release ci checks * fix: restore release ci after rebase * fix: remove stale pi runtime path * test: align compaction runtime expectations * test: update plugin prerelease expectations * fix: handle claude live tool approvals * fix: stabilize release validation gates * fix: finish agent runtime import * test: finish post-rebase agent runtime mocks * fix: keep codex compaction native * fix: stabilize codex app-server hook tests * test: isolate codex diagnostic active run * test: remove codex diagnostic completion race # Conflicts: # extensions/codex/src/app-server/run-attempt.test.ts * ci: fix full release manifest performance run id * refactor: narrow llm plugin sdk boundary * chore: drop generated google boundary stamps * fix: repair rebase fallout * fix: clean up rebased runtime references * fix: decode codex jwt payloads as base64url * fix: preserve shipped pi runtime alias * fix: add scoped sdk virtual modules * fix: decode llm codex oauth jwt as base64url * fix: avoid stale vertex adc negative cache * fix: harden tool arg decoding and codeql path * fix: keep vertex adc negative checks live * refactor: consolidate codex jwt and edit helpers * fix: await codex oauth node runtime imports * fix: preserve sdk tool and notice contracts * fix: preserve shipped compat config boundaries * fix: align codex oauth callback host * fix: terminate agent-core loop streams on failure * fix: keep codex oauth callback alive during fallback * ci: include session tools in critical codeql scans * fix: keep Cloudflare Anthropic provider auth header * docs: redirect legacy pi runtime pages * fix: honor bundled web provider compat discovery * fix: protect session output spill files * fix: keep legacy agent dir env blocked * fix: contain auto-discovered skill symlinks * fix: harden agent core sdk proxy surfaces * fix: restore approval reaction sdk compat * fix: keep live docker runs bounded * fix: keep codex oauth redirect host aligned * fix: resolve post-rebase agent runtime drift * fix: redact anthropic oauth parse failures * fix: preserve responses strict tool shaping * fix: repair agent runtime rebase cleanup * docs: redirect retired parity pages * fix: bound auto-discovered resources to roots * fix: repair post-rebase agent test drift * fix: preserve bundled provider allowlist migration * fix: preserve manifest-owned provider aliases * fix: declare photon image dependency * fix: keep provider headers out of proxy body * fix: preserve shipped env aliases * fix: refresh control ui i18n generated state * fix: quote read fallback paths * fix: preview edits through configured backend * test: satisfy core test typecheck * fix: preserve ZAI usage auth fallback * test: repair codex diagnostic test * fix: repair agent runtime rebase drift * test: finish embedded runner import rename * fix: repair agent runtime rebase integrations * test: align compaction oauth fallback expectations * fix: allow sdk-auth session models * fix: update doctor tool schema import * fix: preserve bedrock plugin region * fix: stream harmony-like prose immediately * ci: include session runtime in codeql shards * fix: repair latest rebase integrations * fix: honor explicit codex websocket transport * fix: keep openai-compatible credentials provider-scoped * fix: refresh sdk api baseline after rebase * fix: route cli runtime aliases through openclaw harness * test: rename stale harness mock expectation * test: rename embedded agent overflow calls * test: clean embedded auth test wording * test: use openclaw stream types in deepinfra cache test * fix: refresh sdk api baseline on latest main * fix: honor bundled discovery compat allowlists * fix: refresh sdk api baseline after latest rebase * fix: remove stale rebase imports * test: rename stale model catalog mock * test: mock renamed doctor runtime modules * fix: map canonical kimi env auth * fix: use internal model registry in bench script * fix: migrate deepinfra provider catalog entry * fix: enforce builtin tool suppression * fix: route compaction auth and proxy payloads safely * refactor: prune unused llm registry leftovers * test: update codex hooks session import * test: fix model picker ci coverage * test: align model picker auth mock types
277 lines
9.2 KiB
TypeScript
277 lines
9.2 KiB
TypeScript
import { vi } from "vitest";
|
|
import { normalizeStringEntries } from "../shared/string-normalization.js";
|
|
|
|
vi.mock("../logging/subsystem.js", () => {
|
|
const createMockLogger = () => ({
|
|
subsystem: "test",
|
|
isEnabled: vi.fn(() => true),
|
|
trace: vi.fn(),
|
|
info: vi.fn(),
|
|
warn: vi.fn(),
|
|
error: vi.fn(),
|
|
debug: vi.fn(),
|
|
fatal: vi.fn(),
|
|
raw: vi.fn(),
|
|
child: vi.fn(() => createMockLogger()),
|
|
});
|
|
return {
|
|
createSubsystemLogger: vi.fn(() => createMockLogger()),
|
|
};
|
|
});
|
|
|
|
vi.mock("../cli/deps.js", () => ({
|
|
createDefaultDeps: vi.fn(() => ({})),
|
|
}));
|
|
|
|
const acpManagerMock = vi.hoisted(() => ({
|
|
current: {
|
|
resolveSession: vi.fn(() => null),
|
|
} as unknown,
|
|
}));
|
|
|
|
vi.mock("../acp/control-plane/manager.js", () => ({
|
|
testing: {
|
|
resetAcpSessionManagerForTests: vi.fn(() => {
|
|
acpManagerMock.current = {
|
|
resolveSession: vi.fn(() => null),
|
|
};
|
|
}),
|
|
setAcpSessionManagerForTests: vi.fn((manager: unknown) => {
|
|
acpManagerMock.current = manager;
|
|
}),
|
|
},
|
|
getAcpSessionManager: vi.fn(() => acpManagerMock.current),
|
|
}));
|
|
|
|
vi.mock("../agents/embedded-agent.js", () => ({
|
|
abortEmbeddedAgentRun: vi.fn().mockReturnValue(false),
|
|
runEmbeddedAgent: vi.fn(),
|
|
resolveEmbeddedSessionLane: (key: string) => `session:${key.trim() || "main"}`,
|
|
}));
|
|
|
|
vi.mock("../agents/model-catalog.js", () => ({
|
|
loadManifestModelCatalog: vi.fn(() => []),
|
|
loadModelCatalog: vi.fn(),
|
|
}));
|
|
|
|
vi.mock("../agents/model-selection.js", () => {
|
|
type ConfigWithModels = {
|
|
agents?: {
|
|
defaults?: {
|
|
model?: string | { primary?: string; fallbacks?: string[] };
|
|
models?: Record<string, { params?: { thinking?: string } } | undefined>;
|
|
thinkingDefault?: string;
|
|
};
|
|
};
|
|
};
|
|
type ModelRef = { provider: string; model: string };
|
|
type CatalogEntry = { id?: string; model?: string; name?: string; reasoning?: boolean };
|
|
|
|
const parseModelRefImpl = (raw: string, defaultProvider = "openai"): ModelRef | null => {
|
|
const value = raw.trim();
|
|
if (!value) {
|
|
return null;
|
|
}
|
|
const slash = value.indexOf("/");
|
|
if (slash >= 0) {
|
|
return {
|
|
provider: value.slice(0, slash).trim(),
|
|
model: value.slice(slash + 1).trim(),
|
|
};
|
|
}
|
|
return { provider: defaultProvider, model: value };
|
|
};
|
|
const parseModelRef = vi.fn(parseModelRefImpl);
|
|
const normalizeModelRef = (provider: string, model: string): ModelRef => ({
|
|
provider: provider.trim().toLowerCase(),
|
|
model: model.trim(),
|
|
});
|
|
const modelKey = (provider: string, model: string) =>
|
|
`${provider.trim().toLowerCase()}/${model.trim().toLowerCase()}`;
|
|
const isModelKeyAllowedBySet = (allowedKeys: ReadonlySet<string>, key: string) => {
|
|
if (allowedKeys.has(key)) {
|
|
return true;
|
|
}
|
|
const slash = key.indexOf("/");
|
|
return slash > 0 && allowedKeys.has(`${key.slice(0, slash)}/*`);
|
|
};
|
|
const resolvePrimary = (cfg?: ConfigWithModels): string | undefined => {
|
|
const primary = cfg?.agents?.defaults?.model;
|
|
if (typeof primary === "string") {
|
|
return primary;
|
|
}
|
|
return primary?.primary;
|
|
};
|
|
const resolveDefaultRef = (cfg?: ConfigWithModels): ModelRef => {
|
|
const parsed = parseModelRefImpl(resolvePrimary(cfg) ?? "openai/gpt-5.5", "openai");
|
|
return parsed ?? { provider: "openai", model: "gpt-5.5" };
|
|
};
|
|
const resolveModelConfig = (cfg: ConfigWithModels | undefined, ref: ModelRef) => {
|
|
const models = cfg?.agents?.defaults?.models ?? {};
|
|
return models[`${ref.provider}/${ref.model}`] ?? models[modelKey(ref.provider, ref.model)];
|
|
};
|
|
|
|
return {
|
|
buildAllowedModelSet: vi.fn(({ cfg }: { cfg?: ConfigWithModels; catalog?: CatalogEntry[] }) => {
|
|
const refs = new Set<string>();
|
|
const modelConfig = cfg?.agents?.defaults?.models ?? {};
|
|
for (const raw of Object.keys(modelConfig)) {
|
|
const parsed = parseModelRefImpl(raw, "openai");
|
|
if (parsed) {
|
|
refs.add(modelKey(parsed.provider, parsed.model));
|
|
}
|
|
}
|
|
const primary = resolveDefaultRef(cfg);
|
|
refs.add(modelKey(primary.provider, primary.model));
|
|
const fallbackRefs =
|
|
typeof cfg?.agents?.defaults?.model === "object"
|
|
? (cfg.agents.defaults.model.fallbacks ?? [])
|
|
: [];
|
|
for (const fallback of fallbackRefs) {
|
|
const parsed = parseModelRefImpl(fallback, primary.provider);
|
|
if (parsed) {
|
|
refs.add(modelKey(parsed.provider, parsed.model));
|
|
}
|
|
}
|
|
return {
|
|
allowedKeys: refs,
|
|
allowedCatalog: [],
|
|
allowAny: Object.keys(modelConfig).length === 0,
|
|
};
|
|
}),
|
|
createModelVisibilityPolicy: vi.fn(
|
|
({ cfg, catalog = [] }: { cfg?: ConfigWithModels; catalog?: CatalogEntry[] }) => {
|
|
const refs = new Set<string>();
|
|
const modelConfig = cfg?.agents?.defaults?.models ?? {};
|
|
for (const raw of Object.keys(modelConfig)) {
|
|
const parsed = parseModelRefImpl(raw, "openai");
|
|
if (parsed) {
|
|
refs.add(modelKey(parsed.provider, parsed.model));
|
|
}
|
|
}
|
|
const primary = resolveDefaultRef(cfg);
|
|
refs.add(modelKey(primary.provider, primary.model));
|
|
const allowAny = Object.keys(modelConfig).length === 0;
|
|
const allowsKey = (key: string) => allowAny || isModelKeyAllowedBySet(refs, key);
|
|
return {
|
|
allowAny,
|
|
allowedKeys: refs,
|
|
allowedCatalog: catalog,
|
|
exactModelRefs: Object.keys(modelConfig).filter((key) => !key.endsWith("/*")),
|
|
providerWildcards: new Set(
|
|
Object.keys(modelConfig)
|
|
.filter((key) => key.endsWith("/*"))
|
|
.map((key) => key.slice(0, -2).trim().toLowerCase()),
|
|
),
|
|
hasConfiguredEntries: Object.keys(modelConfig).length > 0,
|
|
hasProviderWildcards: Object.keys(modelConfig).some((key) => key.endsWith("/*")),
|
|
allowsKey,
|
|
allows: ({ provider, model }: ModelRef) => allowsKey(modelKey(provider, model)),
|
|
resolveSelection: ({ provider, model }: ModelRef) => {
|
|
const key = modelKey(provider, model);
|
|
if (allowsKey(key)) {
|
|
return { provider, model };
|
|
}
|
|
const fallback = catalog[0];
|
|
return fallback?.id ? { provider: "openai", model: fallback.id } : null;
|
|
},
|
|
visibleCatalog: ({ catalog: visibleCatalog }: { catalog: CatalogEntry[] }) =>
|
|
visibleCatalog,
|
|
};
|
|
},
|
|
),
|
|
buildConfiguredModelCatalog: vi.fn(() => []),
|
|
isModelKeyAllowedBySet,
|
|
isCliProvider: vi.fn(() => false),
|
|
modelKey,
|
|
normalizeModelRef,
|
|
parseModelRef,
|
|
resolveConfiguredModelRef: vi.fn(
|
|
({ cfg }: { cfg?: ConfigWithModels; defaultProvider?: string; defaultModel?: string }) =>
|
|
resolveDefaultRef(cfg),
|
|
),
|
|
resolveDefaultModelForAgent: vi.fn(({ cfg }: { cfg?: ConfigWithModels }) =>
|
|
resolveDefaultRef(cfg),
|
|
),
|
|
resolveThinkingDefault: vi.fn(
|
|
({
|
|
cfg,
|
|
provider,
|
|
model,
|
|
catalog,
|
|
}: {
|
|
cfg?: ConfigWithModels;
|
|
provider: string;
|
|
model: string;
|
|
catalog?: CatalogEntry[];
|
|
}) => {
|
|
const ref = normalizeModelRef(provider, model);
|
|
const modelThinking = resolveModelConfig(cfg, ref)?.params?.thinking;
|
|
if (modelThinking) {
|
|
return modelThinking;
|
|
}
|
|
const defaultThinking = cfg?.agents?.defaults?.thinkingDefault;
|
|
if (defaultThinking) {
|
|
return defaultThinking;
|
|
}
|
|
const entry = catalog?.find((item) => item.id === model || item.model === model);
|
|
if (entry?.reasoning && entry.name?.includes("4.6")) {
|
|
return "adaptive";
|
|
}
|
|
return entry?.reasoning ? "low" : "off";
|
|
},
|
|
),
|
|
};
|
|
});
|
|
|
|
vi.mock("../agents/subagent-announce.js", () => ({
|
|
runSubagentAnnounceFlow: vi.fn(),
|
|
}));
|
|
|
|
vi.mock("../gateway/call.js", () => ({
|
|
callGateway: vi.fn(),
|
|
}));
|
|
|
|
vi.mock("../agents/workspace.js", () => ({
|
|
DEFAULT_AGENT_WORKSPACE_DIR: "/tmp/openclaw-workspace",
|
|
DEFAULT_AGENTS_FILENAME: "AGENTS.md",
|
|
DEFAULT_IDENTITY_FILENAME: "IDENTITY.md",
|
|
resolveDefaultAgentWorkspaceDir: () => "/tmp/openclaw-workspace",
|
|
ensureAgentWorkspace: vi.fn(async ({ dir }: { dir: string }) => ({ dir })),
|
|
}));
|
|
|
|
vi.mock("../agents/skills.js", () => ({
|
|
buildWorkspaceSkillSnapshot: vi.fn(() => undefined),
|
|
loadWorkspaceSkillEntries: vi.fn(() => []),
|
|
}));
|
|
|
|
vi.mock("../agents/skills/refresh.js", () => ({
|
|
getSkillsSnapshotVersion: vi.fn(() => 0),
|
|
}));
|
|
|
|
vi.mock("../agents/skills/refresh-state.js", () => ({
|
|
getSkillsSnapshotVersion: vi.fn(() => 0),
|
|
shouldRefreshSnapshotForVersion: vi.fn(() => false),
|
|
}));
|
|
|
|
vi.mock("../agents/skills/filter.js", () => ({
|
|
normalizeSkillFilter: vi.fn((skillFilter?: ReadonlyArray<unknown>) =>
|
|
skillFilter ? normalizeStringEntries(skillFilter) : undefined,
|
|
),
|
|
normalizeSkillFilterForComparison: vi.fn((skillFilter?: ReadonlyArray<unknown>) =>
|
|
skillFilter
|
|
?.map((entry) => String(entry).trim())
|
|
.filter(Boolean)
|
|
.toSorted(),
|
|
),
|
|
matchesSkillFilter: vi.fn(() => true),
|
|
}));
|
|
|
|
vi.mock("../agents/exec-defaults.js", () => ({
|
|
canExecRequestNode: vi.fn(() => false),
|
|
}));
|
|
|
|
vi.mock("../infra/skills-remote.js", () => ({
|
|
getRemoteSkillEligibility: vi.fn(() => undefined),
|
|
}));
|