mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 17:30:31 +00:00
refactor(chat): split current and connected app-server gateways
This commit is contained in:
parent
f7e5319c3e
commit
a099e1fffc
6 changed files with 94 additions and 33 deletions
|
|
@ -5,12 +5,22 @@ import type { CodexInput } from "../../../domain/chat/input";
|
|||
import type { ComposerInputSnapshot } from "../application/composer/input-snapshot";
|
||||
import { createThreadReferenceResolver, type ThreadReferenceResolver } from "./thread-reference-resolver";
|
||||
import { type ChatMetadataTransports, createChatMetadataTransports } from "./transports/metadata-transports";
|
||||
import { type ChatSessionTransports, createChatSessionTransports } from "./transports/session-transports";
|
||||
import {
|
||||
type ChatConnectedSessionTransports,
|
||||
type ChatCurrentSessionTransports,
|
||||
createChatConnectedSessionTransports,
|
||||
createChatCurrentSessionTransports,
|
||||
} from "./transports/session-transports";
|
||||
|
||||
export interface ChatAppServerGatewayHost {
|
||||
export interface ChatCurrentAppServerGatewayHost {
|
||||
codexPath(): string;
|
||||
vaultPath: string;
|
||||
currentClient(): AppServerClient | null;
|
||||
}
|
||||
|
||||
export interface ChatConnectedAppServerGatewayHost {
|
||||
vaultPath: string;
|
||||
currentClient(): AppServerClient | null;
|
||||
connectedClient(): Promise<AppServerClient | null>;
|
||||
}
|
||||
|
||||
|
|
@ -20,17 +30,19 @@ interface ChatThreadReferenceResolverOptions {
|
|||
setStatus(status: string): void;
|
||||
}
|
||||
|
||||
export interface ChatAppServerGateway extends ChatSessionTransports, ChatMetadataTransports {
|
||||
export interface ChatCurrentAppServerGateway extends ChatCurrentSessionTransports, ChatMetadataTransports {
|
||||
clientAccess: AppServerClientAccess;
|
||||
connectionAvailable(): boolean;
|
||||
readFileBase64(path: string, options?: { timeoutMs?: number }): Promise<string | null>;
|
||||
threadReferences(options: ChatThreadReferenceResolverOptions): ThreadReferenceResolver;
|
||||
}
|
||||
|
||||
export function createChatAppServerGateway(host: ChatAppServerGatewayHost): ChatAppServerGateway {
|
||||
export interface ChatAppServerGateway extends ChatCurrentAppServerGateway, ChatConnectedSessionTransports {}
|
||||
|
||||
export function createChatCurrentAppServerGateway(host: ChatCurrentAppServerGatewayHost): ChatCurrentAppServerGateway {
|
||||
return {
|
||||
clientAccess: createCurrentClientAccess(host),
|
||||
...createChatSessionTransports(host),
|
||||
...createChatCurrentSessionTransports(host),
|
||||
...createChatMetadataTransports(host),
|
||||
connectionAvailable: () => host.currentClient() !== null,
|
||||
readFileBase64: (path, options) => readCurrentClientFileBase64(host, path, options),
|
||||
|
|
@ -48,7 +60,17 @@ export function createChatAppServerGateway(host: ChatAppServerGatewayHost): Chat
|
|||
};
|
||||
}
|
||||
|
||||
function createCurrentClientAccess(host: ChatAppServerGatewayHost): AppServerClientAccess {
|
||||
export function createChatAppServerGateway(
|
||||
currentGateway: ChatCurrentAppServerGateway,
|
||||
host: ChatConnectedAppServerGatewayHost,
|
||||
): ChatAppServerGateway {
|
||||
return {
|
||||
...currentGateway,
|
||||
...createChatConnectedSessionTransports(host),
|
||||
};
|
||||
}
|
||||
|
||||
function createCurrentClientAccess(host: ChatCurrentAppServerGatewayHost): AppServerClientAccess {
|
||||
return {
|
||||
withClient: async (operation, options: AppServerClientAccessOptions = {}) => {
|
||||
if (options.serverRequests?.kind === "reject") {
|
||||
|
|
@ -67,7 +89,7 @@ function createCurrentClientAccess(host: ChatAppServerGatewayHost): AppServerCli
|
|||
}
|
||||
|
||||
async function readCurrentClientFileBase64(
|
||||
host: ChatAppServerGatewayHost,
|
||||
host: ChatCurrentAppServerGatewayHost,
|
||||
path: string,
|
||||
options: { timeoutMs?: number } = {},
|
||||
): Promise<string | null> {
|
||||
|
|
|
|||
|
|
@ -42,42 +42,52 @@ interface ConnectedChatAppServerClientHost extends CurrentChatAppServerClientHos
|
|||
connectedClient(): Promise<AppServerClient | null>;
|
||||
}
|
||||
|
||||
interface ChatAppServerTransportHost extends ConnectedChatAppServerClientHost {
|
||||
interface ChatCurrentAppServerTransportHost extends CurrentChatAppServerClientHost {
|
||||
vaultPath: string;
|
||||
}
|
||||
|
||||
interface ChatAppServerTransportHost extends ConnectedChatAppServerClientHost, ChatCurrentAppServerTransportHost {}
|
||||
|
||||
interface AppServerThreadTurnsPage {
|
||||
readonly data: ThreadTurnsPage["turns"];
|
||||
readonly nextCursor: string | null;
|
||||
}
|
||||
|
||||
export interface ChatSessionTransports {
|
||||
readonly turn: ChatTurnTransport;
|
||||
export interface ChatCurrentSessionTransports {
|
||||
readonly runtimeSettings: RuntimeSettingsTransport;
|
||||
readonly threadStart: ThreadStartTransport;
|
||||
readonly threadHistory: ThreadHistoryTransport;
|
||||
readonly threadGoalRead: ThreadGoalReadTransport;
|
||||
}
|
||||
|
||||
export interface ChatConnectedSessionTransports {
|
||||
readonly turn: ChatTurnTransport;
|
||||
readonly threadResume: ThreadResumeTransport;
|
||||
readonly threadMutation: ThreadMutationTransport;
|
||||
readonly threadEphemeral: EphemeralThreadTransport;
|
||||
readonly threadGoalRead: ThreadGoalReadTransport;
|
||||
readonly threadGoal: ThreadGoalTransport;
|
||||
}
|
||||
|
||||
export function createChatSessionTransports(host: ChatAppServerTransportHost): ChatSessionTransports {
|
||||
export function createChatCurrentSessionTransports(host: ChatCurrentAppServerTransportHost): ChatCurrentSessionTransports {
|
||||
return {
|
||||
turn: createChatTurnTransport(host),
|
||||
runtimeSettings: createChatRuntimeSettingsTransport(host),
|
||||
threadStart: createChatThreadStartTransport(host),
|
||||
threadHistory: createChatThreadHistoryTransport(host),
|
||||
threadGoalRead: createChatThreadGoalReadTransport(host),
|
||||
};
|
||||
}
|
||||
|
||||
export function createChatConnectedSessionTransports(host: ChatAppServerTransportHost): ChatConnectedSessionTransports {
|
||||
return {
|
||||
turn: createChatTurnTransport(host),
|
||||
threadResume: createChatThreadResumeTransport(host),
|
||||
threadMutation: createChatThreadMutationTransport(host),
|
||||
threadEphemeral: createChatEphemeralThreadTransport(host),
|
||||
threadGoalRead: createChatThreadGoalReadTransport(host),
|
||||
threadGoal: createChatThreadGoalTransport(host),
|
||||
};
|
||||
}
|
||||
|
||||
function createChatThreadStartTransport(host: ChatAppServerTransportHost): ThreadStartTransport {
|
||||
function createChatThreadStartTransport(host: ChatCurrentAppServerTransportHost): ThreadStartTransport {
|
||||
return {
|
||||
startThread: (request) =>
|
||||
withCurrentChatAppServerClient(host, async (client) => {
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ import { type ConnectionManager, StaleConnectionError } from "../../../../app-se
|
|||
import { isStaleAppServerSharedQueryContextError } from "../../../../app-server/query/shared-queries";
|
||||
import type { SharedServerMetadata } from "../../../../domain/server/metadata";
|
||||
import { type ChatInboundHandler, createChatInboundHandler } from "../../app-server/inbound/handler";
|
||||
import type { ChatAppServerGateway } from "../../app-server/session-gateway";
|
||||
import { type ChatConnectionActions, createChatConnectionActions } from "../../application/connection/connection-actions";
|
||||
import type { ServerDiagnosticsTransport } from "../../application/connection/metadata-transport";
|
||||
import { createServerDiagnosticsActions } from "../../application/connection/server-diagnostics-actions";
|
||||
import { createServerMetadataActions } from "../../application/connection/server-metadata-actions";
|
||||
import type { LocalIdSource } from "../../application/local-id-source";
|
||||
|
|
@ -26,7 +26,7 @@ interface ChatPanelConnectionStatus {
|
|||
|
||||
interface ChatPanelConnectionBundleInput {
|
||||
connection: ConnectionManager;
|
||||
appServer: ChatAppServerGateway;
|
||||
diagnosticsTransport: ServerDiagnosticsTransport;
|
||||
localItemIds: LocalIdSource;
|
||||
status: ChatPanelConnectionStatus;
|
||||
autoTitleCoordinator: AutoTitleCoordinator;
|
||||
|
|
@ -120,7 +120,7 @@ export function createConnectionBundle(
|
|||
input: ChatPanelConnectionBundleInput,
|
||||
): ChatPanelConnectionBundle {
|
||||
const { environment, stateStore } = host;
|
||||
const { connection, appServer, localItemIds, status, autoTitleCoordinator } = input;
|
||||
const { connection, diagnosticsTransport, localItemIds, status, autoTitleCoordinator } = input;
|
||||
const serverRequestResponders = createServerRequestResponderRegistry();
|
||||
const serverMetadata = createServerMetadataActions({
|
||||
stateStore,
|
||||
|
|
@ -132,7 +132,7 @@ export function createConnectionBundle(
|
|||
});
|
||||
const serverDiagnostics = createServerDiagnosticsActions({
|
||||
stateStore,
|
||||
diagnosticsTransport: appServer.serverDiagnostics,
|
||||
diagnosticsTransport,
|
||||
appServerMetadataSnapshot: () => environment.plugin.appServerQueries.appServerMetadataSnapshot(),
|
||||
});
|
||||
const refreshSharedThreads = async (): Promise<void> => {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { recoverRolloutTokenUsage } from "../../../../app-server/services/rollou
|
|||
import { createThreadOperationsTransport, createThreadTitleTransport } from "../../../threads/app-server/workflow-transports";
|
||||
import { createThreadOperations, type ThreadOperations } from "../../../threads/workflows/thread-operations";
|
||||
import { createThreadTitleService, type ThreadTitleService } from "../../../threads/workflows/thread-title-service";
|
||||
import type { ChatAppServerGateway } from "../../app-server/session-gateway";
|
||||
import type { ChatAppServerGateway, ChatCurrentAppServerGateway } from "../../app-server/session-gateway";
|
||||
import type { LocalIdSource } from "../../application/local-id-source";
|
||||
import type { ChatStateStore } from "../../application/state/store";
|
||||
import { threadStreamItems } from "../../application/state/thread-stream";
|
||||
|
|
@ -56,7 +56,7 @@ interface ChatPanelThreadHost {
|
|||
}
|
||||
|
||||
interface ChatPanelThreadFoundationInput {
|
||||
appServer: ChatAppServerGateway;
|
||||
appServer: ChatCurrentAppServerGateway;
|
||||
localItemIds: LocalIdSource;
|
||||
status: ChatPanelThreadStatus;
|
||||
refreshLiveState: () => void;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { codexPanelAppServerInitializeParams } from "../../../app-server/connection/client-profile";
|
||||
import { ConnectionManager } from "../../../app-server/connection/connection-manager";
|
||||
import { isStaleAppServerSharedQueryContextError } from "../../../app-server/query/shared-queries";
|
||||
import { createChatAppServerGateway } from "../app-server/session-gateway";
|
||||
import { createChatAppServerGateway, createChatCurrentAppServerGateway } from "../app-server/session-gateway";
|
||||
import { reconnectPanel } from "../application/connection/reconnect-actions";
|
||||
import { createLocalIdSource, type LocalIdSource } from "../application/local-id-source";
|
||||
import { runtimeSnapshotForChatState } from "../application/runtime/snapshot";
|
||||
|
|
@ -114,15 +114,10 @@ function composeChatPanelSessionRuntime(host: ChatPanelSessionRuntimeHost): Chat
|
|||
const localItemIds = createLocalIdSource();
|
||||
const connection = createConnectionManager(environment);
|
||||
const currentClient = () => connection.currentClient();
|
||||
let ensureConnected = (): Promise<void> => Promise.reject(new Error("Codex app-server connection actions are not initialized."));
|
||||
const appServer = createChatAppServerGateway({
|
||||
const currentAppServer = createChatCurrentAppServerGateway({
|
||||
codexPath: () => environment.plugin.settingsRef.settings.codexPath(),
|
||||
vaultPath: environment.plugin.settingsRef.vaultPath,
|
||||
currentClient,
|
||||
connectedClient: async () => {
|
||||
await ensureConnected();
|
||||
return currentClient();
|
||||
},
|
||||
});
|
||||
const status = createSessionStatus(stateStore, localItemIds);
|
||||
const refreshTabHeader = () => {
|
||||
|
|
@ -141,7 +136,7 @@ function composeChatPanelSessionRuntime(host: ChatPanelSessionRuntimeHost): Chat
|
|||
};
|
||||
|
||||
const threadFoundation = createThreadFoundation(host, {
|
||||
appServer,
|
||||
appServer: currentAppServer,
|
||||
localItemIds,
|
||||
status,
|
||||
refreshLiveState,
|
||||
|
|
@ -160,7 +155,7 @@ function composeChatPanelSessionRuntime(host: ChatPanelSessionRuntimeHost): Chat
|
|||
},
|
||||
{
|
||||
connection,
|
||||
appServer,
|
||||
diagnosticsTransport: currentAppServer.serverDiagnostics,
|
||||
localItemIds,
|
||||
autoTitleCoordinator: threadFoundation.autoTitleCoordinator,
|
||||
status,
|
||||
|
|
@ -170,7 +165,15 @@ function composeChatPanelSessionRuntime(host: ChatPanelSessionRuntimeHost): Chat
|
|||
connection: { actions: connectionActions },
|
||||
inboundHandler,
|
||||
} = connectionBundle;
|
||||
ensureConnected = () => connectionActions.ensureConnected();
|
||||
const ensureConnected = () => connectionActions.ensureConnected();
|
||||
const appServer = createChatAppServerGateway(currentAppServer, {
|
||||
vaultPath: environment.plugin.settingsRef.vaultPath,
|
||||
currentClient,
|
||||
connectedClient: async () => {
|
||||
await connectionActions.ensureConnected();
|
||||
return currentClient();
|
||||
},
|
||||
});
|
||||
const refreshActiveThreads = () => connectionActions.refreshActiveThreads();
|
||||
const runtime = createRuntimeBundle(host, {
|
||||
connection,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import type { ThreadRecord } from "../../../../src/app-server/protocol/thread";
|
|||
import type { TurnItem, TurnRecord } from "../../../../src/app-server/protocol/turn";
|
||||
import type { CodexInput } from "../../../../src/domain/chat/input";
|
||||
import { createServerDiagnostics, diagnosticProbeOk } from "../../../../src/domain/server/diagnostics";
|
||||
import { createChatAppServerGateway } from "../../../../src/features/chat/app-server/session-gateway";
|
||||
import { createChatAppServerGateway, createChatCurrentAppServerGateway } from "../../../../src/features/chat/app-server/session-gateway";
|
||||
import { createThreadReferenceResolver } from "../../../../src/features/chat/app-server/thread-reference-resolver";
|
||||
import { preparedUserInputWithWikiLinkMentionsSkillsAndContext } from "../../../../src/features/chat/application/composer/wikilink-context";
|
||||
import { deferred } from "../../../support/async";
|
||||
|
|
@ -14,6 +14,28 @@ import { deferred } from "../../../support/async";
|
|||
const textInput = (text: string): CodexInput => [{ type: "text", text }];
|
||||
|
||||
describe("chat app-server transports", () => {
|
||||
it("adds connection-requiring capabilities only after the connected client host exists", async () => {
|
||||
const client = { request: vi.fn() } as unknown as AppServerClient;
|
||||
const currentGateway = createChatCurrentAppServerGateway({
|
||||
codexPath: () => "codex",
|
||||
vaultPath: "/vault",
|
||||
currentClient: () => client,
|
||||
});
|
||||
const connectedClient = vi.fn().mockResolvedValue(client);
|
||||
|
||||
expect(currentGateway.connectionAvailable()).toBe(true);
|
||||
expect(currentGateway).not.toHaveProperty("turn");
|
||||
|
||||
const gateway = createChatAppServerGateway(currentGateway, {
|
||||
vaultPath: "/vault",
|
||||
currentClient: () => client,
|
||||
connectedClient,
|
||||
});
|
||||
|
||||
await expect(gateway.turn.ensureConnected()).resolves.toBe(true);
|
||||
expect(connectedClient).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("starts threads with the session vault path and projects activation snapshots", async () => {
|
||||
const request = vi.fn().mockResolvedValue(threadStartResponse("thread"));
|
||||
const client = { request } as unknown as AppServerClient;
|
||||
|
|
@ -521,10 +543,14 @@ function createTestGateway(options: {
|
|||
connectedClient?: () => Promise<AppServerClient | null>;
|
||||
}) {
|
||||
const codexPath = options.codexPath;
|
||||
return createChatAppServerGateway({
|
||||
const currentGateway = createChatCurrentAppServerGateway({
|
||||
codexPath: typeof codexPath === "function" ? codexPath : () => codexPath ?? "codex",
|
||||
vaultPath: options.vaultPath ?? "/vault",
|
||||
currentClient: options.currentClient,
|
||||
});
|
||||
return createChatAppServerGateway(currentGateway, {
|
||||
vaultPath: options.vaultPath ?? "/vault",
|
||||
currentClient: options.currentClient,
|
||||
connectedClient: options.connectedClient ?? (async () => options.currentClient()),
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue