diff --git a/src/agents/model-compat.test.ts b/src/agents/model-compat.test.ts index 1e11b12437f..0aed752e7a6 100644 --- a/src/agents/model-compat.test.ts +++ b/src/agents/model-compat.test.ts @@ -1,9 +1,9 @@ import type { Api, Model } from "@mariozechner/pi-ai"; +import type { ModelRegistry } from "@mariozechner/pi-coding-agent"; import { describe, expect, it } from "vitest"; import { isModernModelRef } from "./live-model-filter.js"; import { normalizeModelCompat } from "./model-compat.js"; import { resolveForwardCompatModel } from "./model-forward-compat.js"; -import type { ModelRegistry } from "./pi-model-discovery.js"; const baseModel = (): Model => ({ diff --git a/src/agents/model-forward-compat.ts b/src/agents/model-forward-compat.ts index a160302f7eb..dceba15fd39 100644 --- a/src/agents/model-forward-compat.ts +++ b/src/agents/model-forward-compat.ts @@ -1,8 +1,8 @@ import type { Api, Model } from "@mariozechner/pi-ai"; +import type { ModelRegistry } from "@mariozechner/pi-coding-agent"; import { DEFAULT_CONTEXT_TOKENS } from "./defaults.js"; import { normalizeModelCompat } from "./model-compat.js"; import { normalizeProviderId } from "./model-selection.js"; -import type { ModelRegistry } from "./pi-model-discovery.js"; const OPENAI_CODEX_GPT_53_MODEL_ID = "gpt-5.3-codex"; const OPENAI_CODEX_TEMPLATE_MODEL_IDS = ["gpt-5.2-codex"] as const; diff --git a/src/agents/pi-embedded-runner/model.ts b/src/agents/pi-embedded-runner/model.ts index f9e95023d5e..16aea8b4c82 100644 --- a/src/agents/pi-embedded-runner/model.ts +++ b/src/agents/pi-embedded-runner/model.ts @@ -1,4 +1,5 @@ import type { Api, Model } from "@mariozechner/pi-ai"; +import type { AuthStorage, ModelRegistry } from "@mariozechner/pi-coding-agent"; import type { OpenClawConfig } from "../../config/config.js"; import type { ModelDefinitionConfig } from "../../config/types.js"; import { resolveOpenClawAgentDir } from "../agent-paths.js"; @@ -7,12 +8,7 @@ import { buildModelAliasLines } from "../model-alias-lines.js"; import { normalizeModelCompat } from "../model-compat.js"; import { resolveForwardCompatModel } from "../model-forward-compat.js"; import { normalizeProviderId } from "../model-selection.js"; -import { - discoverAuthStorage, - discoverModels, - type AuthStorage, - type ModelRegistry, -} from "../pi-model-discovery.js"; +import { discoverAuthStorage, discoverModels } from "../pi-model-discovery.js"; type InlineModelEntry = ModelDefinitionConfig & { provider: string; diff --git a/src/agents/pi-embedded-runner/run/types.ts b/src/agents/pi-embedded-runner/run/types.ts index e908dadeb87..469ff8bb33a 100644 --- a/src/agents/pi-embedded-runner/run/types.ts +++ b/src/agents/pi-embedded-runner/run/types.ts @@ -1,10 +1,10 @@ import type { AgentMessage } from "@mariozechner/pi-agent-core"; import type { Api, AssistantMessage, Model } from "@mariozechner/pi-ai"; +import type { AuthStorage, ModelRegistry } from "@mariozechner/pi-coding-agent"; import type { ThinkLevel } from "../../../auto-reply/thinking.js"; import type { SessionSystemPromptReport } from "../../../config/sessions/types.js"; import type { PluginHookBeforeAgentStartResult } from "../../../plugins/types.js"; import type { MessagingToolSend } from "../../pi-embedded-messaging.js"; -import type { AuthStorage, ModelRegistry } from "../../pi-model-discovery.js"; import type { NormalizedUsage } from "../../usage.js"; import type { RunEmbeddedPiAgentParams } from "./params.js"; diff --git a/src/agents/pi-model-discovery.compat.test.ts b/src/agents/pi-model-discovery.compat.test.ts new file mode 100644 index 00000000000..dcba11e7cd0 --- /dev/null +++ b/src/agents/pi-model-discovery.compat.test.ts @@ -0,0 +1,26 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; + +describe("pi-model-discovery module compatibility", () => { + afterEach(() => { + vi.resetModules(); + vi.doUnmock("@mariozechner/pi-coding-agent"); + }); + + it("loads when InMemoryAuthStorageBackend is not exported", async () => { + vi.resetModules(); + vi.doMock("@mariozechner/pi-coding-agent", () => { + class MockAuthStorage {} + class MockModelRegistry {} + + return { + AuthStorage: MockAuthStorage, + ModelRegistry: MockModelRegistry, + }; + }); + + await expect(import("./pi-model-discovery.js")).resolves.toMatchObject({ + discoverAuthStorage: expect.any(Function), + discoverModels: expect.any(Function), + }); + }); +}); diff --git a/src/agents/pi-model-discovery.ts b/src/agents/pi-model-discovery.ts index a2d3dccf6aa..c283a653310 100644 --- a/src/agents/pi-model-discovery.ts +++ b/src/agents/pi-model-discovery.ts @@ -1,14 +1,46 @@ import fs from "node:fs"; import path from "node:path"; -import { - AuthStorage, - InMemoryAuthStorageBackend, - ModelRegistry, +import * as PiCodingAgent from "@mariozechner/pi-coding-agent"; +import type { + AuthStorage as PiAuthStorage, + ModelRegistry as PiModelRegistry, } from "@mariozechner/pi-coding-agent"; import { ensureAuthProfileStore } from "./auth-profiles.js"; import { resolvePiCredentialMapFromStore, type PiCredentialMap } from "./pi-auth-credentials.js"; -export { AuthStorage, ModelRegistry } from "@mariozechner/pi-coding-agent"; +const PiAuthStorageClass = PiCodingAgent.AuthStorage; +const PiModelRegistryClass = PiCodingAgent.ModelRegistry; + +export { PiAuthStorageClass as AuthStorage, PiModelRegistryClass as ModelRegistry }; + +type InMemoryAuthStorageBackendLike = { + withLock( + update: (current: string) => { + result: T; + next?: string; + }, + ): T; +}; + +function createInMemoryAuthStorageBackend( + initialData: PiCredentialMap, +): InMemoryAuthStorageBackendLike { + let snapshot = JSON.stringify(initialData, null, 2); + return { + withLock( + update: (current: string) => { + result: T; + next?: string; + }, + ): T { + const { result, next } = update(snapshot); + if (typeof next === "string") { + snapshot = next; + } + return result; + }, + }; +} function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null && !Array.isArray(value); @@ -60,19 +92,25 @@ function scrubLegacyStaticAuthJsonEntries(pathname: string): void { function createAuthStorage(AuthStorageLike: unknown, path: string, creds: PiCredentialMap) { const withInMemory = AuthStorageLike as { inMemory?: (data?: unknown) => unknown }; if (typeof withInMemory.inMemory === "function") { - return withInMemory.inMemory(creds) as AuthStorage; + return withInMemory.inMemory(creds) as PiAuthStorage; } const withFromStorage = AuthStorageLike as { fromStorage?: (storage: unknown) => unknown; }; if (typeof withFromStorage.fromStorage === "function") { - const backend = new InMemoryAuthStorageBackend(); + const backendCtor = ( + PiCodingAgent as { InMemoryAuthStorageBackend?: new () => InMemoryAuthStorageBackendLike } + ).InMemoryAuthStorageBackend; + const backend = + typeof backendCtor === "function" + ? new backendCtor() + : createInMemoryAuthStorageBackend(creds); backend.withLock(() => ({ result: undefined, next: JSON.stringify(creds, null, 2), })); - return withFromStorage.fromStorage(backend) as AuthStorage; + return withFromStorage.fromStorage(backend) as PiAuthStorage; } const withFactory = AuthStorageLike as { create?: (path: string) => unknown }; @@ -80,7 +118,7 @@ function createAuthStorage(AuthStorageLike: unknown, path: string, creds: PiCred typeof withFactory.create === "function" ? withFactory.create(path) : new (AuthStorageLike as { new (path: string): unknown })(path) - ) as AuthStorage & { + ) as PiAuthStorage & { setRuntimeApiKey?: (provider: string, apiKey: string) => void; }; if (typeof withRuntimeOverride.setRuntimeApiKey === "function") { @@ -101,13 +139,13 @@ function resolvePiCredentials(agentDir: string): PiCredentialMap { } // Compatibility helpers for pi-coding-agent 0.50+ (discover* helpers removed). -export function discoverAuthStorage(agentDir: string): AuthStorage { +export function discoverAuthStorage(agentDir: string): PiAuthStorage { const credentials = resolvePiCredentials(agentDir); const authPath = path.join(agentDir, "auth.json"); scrubLegacyStaticAuthJsonEntries(authPath); - return createAuthStorage(AuthStorage, authPath, credentials); + return createAuthStorage(PiAuthStorageClass, authPath, credentials); } -export function discoverModels(authStorage: AuthStorage, agentDir: string): ModelRegistry { - return new ModelRegistry(authStorage, path.join(agentDir, "models.json")); +export function discoverModels(authStorage: PiAuthStorage, agentDir: string): PiModelRegistry { + return new PiModelRegistryClass(authStorage, path.join(agentDir, "models.json")); } diff --git a/src/commands/models/list.list-command.ts b/src/commands/models/list.list-command.ts index dc195985706..11ebae8f16d 100644 --- a/src/commands/models/list.list-command.ts +++ b/src/commands/models/list.list-command.ts @@ -1,7 +1,7 @@ import type { Api, Model } from "@mariozechner/pi-ai"; +import type { ModelRegistry } from "@mariozechner/pi-coding-agent"; import { resolveForwardCompatModel } from "../../agents/model-forward-compat.js"; import { parseModelRef } from "../../agents/model-selection.js"; -import type { ModelRegistry } from "../../agents/pi-model-discovery.js"; import type { RuntimeEnv } from "../../runtime.js"; import { resolveConfiguredEntries } from "./list.configured.js"; import { formatErrorWithStack } from "./list.errors.js"; diff --git a/src/commands/models/list.registry.ts b/src/commands/models/list.registry.ts index b86c236e61f..012b4eafb07 100644 --- a/src/commands/models/list.registry.ts +++ b/src/commands/models/list.registry.ts @@ -1,4 +1,5 @@ import type { Api, Model } from "@mariozechner/pi-ai"; +import type { ModelRegistry } from "@mariozechner/pi-coding-agent"; import { resolveOpenClawAgentDir } from "../../agents/agent-paths.js"; import type { AuthProfileStore } from "../../agents/auth-profiles.js"; import { listProfilesForProvider } from "../../agents/auth-profiles.js"; @@ -8,7 +9,6 @@ import { resolveEnvApiKey, } from "../../agents/model-auth.js"; import { ensureOpenClawModelsJson } from "../../agents/models-config.js"; -import type { ModelRegistry } from "../../agents/pi-model-discovery.js"; import { discoverAuthStorage, discoverModels } from "../../agents/pi-model-discovery.js"; import type { OpenClawConfig } from "../../config/config.js"; import {