ui-workspace's two trigger surfaces each declare a single-kind directory-flow hole (conversation.hero.workspace.directoryFlow / sidebar.workspaces.directoryFlow, same owner contract) and keep only the trigger and the adoption: the Open-local- folder entry renders while the surface's hole is occupied, and the occupant reports one picked path per open through the hole's owner conversation (open/busy/onPicked/onCancel/onError). directory-picker-native becomes dual-face: its browser half fills both holes with a renderless occupant driving host.pickDirectory, so the cordis.yml row that mounts the backend also composes the client interaction — a mismatch is impossible and a second flow package fails at client load. With composition wiring both sides, the host.describe.directoryPicker advertisement and the client's kind branching lose their last consumer: the field, WorkspacesService.directoryPickerKind(), the DirectoryPickerKind wire type, and the picker's per-open describe read are deleted. The connection fixture now serves a deterministic pickDirectory path so the keyless snapshot drives the full pick-then-adopt flow. ui-workspace's hand-rolled declaration deferral is replaced by the deferRegistration helper it duplicated.
74 lines
3.1 KiB
TypeScript
74 lines
3.1 KiB
TypeScript
/**
|
|
* Browser wire client. The plugin selects fixture or HTTP transport, provides
|
|
* the shared API client, and lets the runtime object layer start the stream
|
|
* controller with its sinks.
|
|
*/
|
|
import type { Context } from 'cordis'
|
|
import type { IApiClient } from './api.ts'
|
|
import { ConnectionController, type ConnectionConfig, type ConnectionSinks, type ConnectionState } from './connection.ts'
|
|
import { FixtureApiClient } from './fixture.ts'
|
|
import { WebApiClient } from './web-api-client.ts'
|
|
|
|
// ---- Contract re-exports (browser-safe apiproxy channels + core types) ----
|
|
export type {
|
|
ApiProxy, SessionsApi, SessionSummary, HostApi, EventsApi, MuxFrame, HostFrame,
|
|
ApprovalResponsePayload, QuestionResponsePayload, HistoryEntry, ToolEventView,
|
|
DirectoryEntry, DirectoryListing,
|
|
ToolCallView, ToolResultView, WorkspaceApi, WorkspaceId, WorkspaceView,
|
|
CommandsApi, CommandDescriptor, SkillsApi, SkillEntry,
|
|
ModelCatalogFailure, ModelCatalogModel, ModelProviderGroup, ModelReasoning,
|
|
ModelReasoningEffort, ModelTarget, SessionModels,
|
|
RpcRequest, RpcResponse, RpcResult, RpcError, RpcErrorCode,
|
|
ClientRequest, ServerResponse, ServerRequest, ClientResponse, RpcMessage, RpcReceipt,
|
|
IApiClient, SessionId, SessionEvent, ContentBlock, StreamChunk,
|
|
} from './api.ts'
|
|
export { RpcId, AbstractApiClient, transportError } from './api.ts'
|
|
|
|
// Connection loop types are public through ConnectionHandle.start; the
|
|
// controller remains package-internal.
|
|
export type { ConnectionConfig, ConnectionSinks, ConnectionState }
|
|
|
|
|
|
/** Required services (none — this is the wire root). */
|
|
export const inject: string[] = []
|
|
|
|
/**
|
|
* The ctx.connection service surface: the api client plus a one-shot
|
|
* controller starter (the runtime plugin supplies sinks when its object layer
|
|
* is ready — connection stays consumer-agnostic).
|
|
*/
|
|
export interface ConnectionHandle {
|
|
/** Shared api client (fixture or real, decided at boot from the page URL). */
|
|
readonly api: IApiClient
|
|
/**
|
|
* Start the connect/pump/reconnect loop with the consumer's frame sinks.
|
|
* One consumer owns the streams (the runtime object layer); a second call
|
|
* throws.
|
|
* @param sinks - frame/state callbacks.
|
|
* @param config - reconnect/backoff tunables.
|
|
* @returns stop handle for the loop.
|
|
*/
|
|
start(sinks: ConnectionSinks, config?: ConnectionConfig): { stop(): void }
|
|
}
|
|
|
|
/**
|
|
* Client plugin body: pick the api by page mode and provide ctx.connection.
|
|
* @param ctx - client cordis context.
|
|
*/
|
|
export function apply(ctx: Context): void {
|
|
const fixture = typeof location !== 'undefined' && new URLSearchParams(location.search).has('fixture')
|
|
const api: IApiClient = fixture ? new FixtureApiClient() : new WebApiClient()
|
|
let started = false
|
|
const handle: ConnectionHandle = {
|
|
api,
|
|
start(sinks, config) {
|
|
if (started) throw new Error('connection: the stream loop is already owned by another consumer')
|
|
started = true
|
|
const controller = new ConnectionController(api, sinks, config ?? {})
|
|
controller.start()
|
|
return { stop: () => { controller.stop() } }
|
|
},
|
|
}
|
|
ctx.provide('connection', handle)
|
|
}
|