Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions apps/server/src/auth/RpcAuthorization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,15 @@ export const RPC_REQUIRED_SCOPES = {
[WS_METHODS.serverStartProviderLogin]: AuthOrchestrationOperateScope,
[WS_METHODS.serverSubmitProviderLoginCode]: AuthOrchestrationOperateScope,
[WS_METHODS.serverCancelProviderLogin]: AuthOrchestrationOperateScope,
[WS_METHODS.providerAuthStart]: AuthOrchestrationOperateScope,
[WS_METHODS.providerAuthComplete]: AuthOrchestrationOperateScope,
[WS_METHODS.providerAuthCancel]: AuthOrchestrationOperateScope,
[WS_METHODS.providerAuthLogout]: AuthOrchestrationOperateScope,
[WS_METHODS.providerAuthSubscribe]: AuthOrchestrationOperateScope,
[WS_METHODS.providerInstallStart]: AuthOrchestrationOperateScope,
[WS_METHODS.providerInstallCancel]: AuthOrchestrationOperateScope,
[WS_METHODS.providerInstallSubscribe]: AuthOrchestrationReadScope,
[WS_METHODS.providerInstallRemove]: AuthOrchestrationOperateScope,
[WS_METHODS.serverUpdateServer]: AuthOrchestrationOperateScope,
[WS_METHODS.serverUpdateServerWithProgress]: AuthOrchestrationOperateScope,
[WS_METHODS.serverCommitDesktopUpdate]: AuthOrchestrationOperateScope,
Expand Down
51 changes: 51 additions & 0 deletions apps/server/src/server.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import * as UsageLimitSources from "./usage/UsageLimitSources.ts";
import { ProviderInstanceRegistry } from "./provider/Services/ProviderInstanceRegistry.ts";
import { ProviderAuthService } from "./provider/Services/ProviderAuthService.ts";
import { AntigravityInstallation } from "./provider/AntigravityInstallation.ts";
import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer";
import * as NodeSocket from "@effect/platform-node/NodeSocket";
import * as NodeServices from "@effect/platform-node/NodeServices";
Expand Down Expand Up @@ -34,6 +36,8 @@ import {
ProjectId,
ProviderDriverKind,
ProviderInstanceId,
type ProviderAuthState,
type ProviderInstallState,
UsageLimitSourceId,
ServerProviderMutationBusyError,
ResolvedKeybindingRule,
Expand Down Expand Up @@ -440,13 +444,36 @@ const makeBrowserOtlpPayload = (spanName: string) =>
return JSON.parse(request.body) as OtlpTracer.TraceData;
});

const IDLE_PROVIDER_AUTH_STATE: ProviderAuthState = {
instanceId: ProviderInstanceId.make("antigravity"),
phase: "idle",
flowId: null,
authorizationUrl: null,
expiresAt: null,
message: null,
};

const IDLE_PROVIDER_INSTALL_STATE: ProviderInstallState = {
driver: ProviderDriverKind.make("antigravity"),
operationId: null,
phase: "idle",
downloadedBytes: 0,
totalBytes: null,
version: null,
installedVersion: null,
canRemove: false,
message: null,
};

const buildAppUnderTest = (options?: {
onPairingChangesSubscribed?: Effect.Effect<void>;
config?: Partial<ServerConfig.ServerConfig["Service"]>;
layers?: {
keybindings?: Partial<Keybindings.Keybindings["Service"]>;
usageLimitSources?: Partial<UsageLimitSources.UsageLimitSources["Service"]>;
providerInstances?: Partial<ProviderInstanceRegistry["Service"]>;
providerAuth?: Partial<ProviderAuthService["Service"]>;
antigravityInstallation?: Partial<AntigravityInstallation["Service"]>;
environmentTheme?: Partial<EnvironmentTheme.EnvironmentThemeService["Service"]>;
providerRegistry?: Partial<ProviderRegistry.ProviderRegistry["Service"]>;
providerService?: Partial<ProviderService.ProviderService["Service"]>;
Expand Down Expand Up @@ -990,6 +1017,30 @@ const buildAppUnderTest = (options?: {
...options?.layers?.serverLifecycleEvents,
}),
),
Layer.provide(
Layer.mock(ProviderAuthService)({
start: () => Effect.succeed(IDLE_PROVIDER_AUTH_STATE),
complete: () => Effect.succeed(IDLE_PROVIDER_AUTH_STATE),
cancel: () => Effect.succeed(IDLE_PROVIDER_AUTH_STATE),
logout: () => Effect.succeed(IDLE_PROVIDER_AUTH_STATE),
subscribe: () => Stream.make(IDLE_PROVIDER_AUTH_STATE),
tryHandlePromptCommand: () => Effect.succeed(false),
...options?.layers?.providerAuth,
}),
),
Layer.provide(
Layer.mock(AntigravityInstallation)({
managedDirectory: "/tmp/antigravity",
resolve: () => Effect.die("resolve is not stubbed"),
acquire: () => Effect.die("acquire is not stubbed"),
start: Effect.succeed(IDLE_PROVIDER_INSTALL_STATE),
cancel: () => Effect.succeed(IDLE_PROVIDER_INSTALL_STATE),
state: Effect.succeed(IDLE_PROVIDER_INSTALL_STATE),
changes: Stream.make(IDLE_PROVIDER_INSTALL_STATE),
remove: () => Effect.void,
...options?.layers?.antigravityInstallation,
}),
),
Layer.provide(
Layer.mock(ServerRuntimeStartup.ServerRuntimeStartup)({
awaitCommandReady: Effect.void,
Expand Down
16 changes: 12 additions & 4 deletions apps/server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import * as PrimeAgentRecoveryLedger from "./provider/prime/PrimeAgentRecoveryLe
import { ProviderAdapterRegistryLive } from "./provider/Layers/ProviderAdapterRegistry.ts";
import * as ModelManifest from "./provider/ModelManifest.ts";
import { AntigravityInstallation } from "./provider/AntigravityInstallation.ts";
import { ProviderAuthServiceLive } from "./provider/Layers/ProviderAuthService.ts";
import * as ProviderEventLoggers from "./provider/Layers/ProviderEventLoggers.ts";
import { ProviderServiceLive } from "./provider/Layers/ProviderService.ts";
import { ProviderSessionReaperLive } from "./provider/Layers/ProviderSessionReaper.ts";
Expand Down Expand Up @@ -298,10 +299,17 @@ const ProviderSessionDirectoryLayerLive = ProviderSessionDirectoryLive.pipe(
// `create()`; `ProviderEventLoggers.layer` owns the shared native/canonical
// NDJSON writers and is provided at the outer runtime layer so both
// `ProviderService` and the per-instance drivers read the same logger pair.
const ProviderLayerLive = ProviderServiceLive.pipe(
Layer.provide(ProviderAdapterRegistryLive),
Layer.provideMerge(ProviderSessionDirectoryLayerLive),
Layer.provideMerge(RollbackSagaRepositoryLive),
// ProviderAuthService backs the provider setup/auth RPCs and reads the provider
// service and session directory, so it is layered on top of them rather than
// provided to them.
const ProviderLayerLive = ProviderAuthServiceLive.pipe(
Layer.provideMerge(
ProviderServiceLive.pipe(
Layer.provide(ProviderAdapterRegistryLive),
Layer.provideMerge(ProviderSessionDirectoryLayerLive),
Layer.provideMerge(RollbackSagaRepositoryLive),
),
),
);

const PersistenceLayerLive = PrimeAgentRecoveryLedger.layer.pipe(
Expand Down
106 changes: 92 additions & 14 deletions apps/server/src/ws.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import {
ProjectWriteFileError,
ProviderUploadFeedbackError,
ProviderDriverKind,
ProviderSetupError,
RelayClientInstallFailedError,
type RelayClientInstallProgressEvent,
ServerSelfUpdateError,
Expand Down Expand Up @@ -126,6 +127,8 @@ import {
ProviderLoginCoordinatorLive,
} from "./provider/providerLoginCoordinator.ts";
import { ProviderLoginSessionsLive } from "./provider/ProviderLoginSessions.ts";
import { ProviderAuthService } from "./provider/Services/ProviderAuthService.ts";
import { makeProviderInstallation } from "./provider/providerInstallation.ts";
import * as ServerSelfUpdate from "./cloud/selfUpdate.ts";
import * as ServerLifecycleEvents from "./serverLifecycleEvents.ts";
import * as ServerRuntimeStartup from "./serverRuntimeStartup.ts";
Expand Down Expand Up @@ -526,6 +529,8 @@ const makeWsRpcLayer = (
const providerMaintenanceRunner = yield* ProviderMaintenanceRunner.ProviderMaintenanceRunner;
const primeManagedMaintenance = yield* PrimeManagedMaintenance.PrimeManagedMaintenance;
const providerLogin = yield* ProviderLoginCoordinator;
const providerAuth = yield* ProviderAuthService;
const providerInstallation = yield* makeProviderInstallation();
const serverUpdate = yield* ServerSelfUpdate.ServerSelfUpdate;
const config = yield* ServerConfig.ServerConfig;
const lifecycleEvents = yield* ServerLifecycleEvents.ServerLifecycleEvents;
Expand Down Expand Up @@ -1793,20 +1798,47 @@ const makeWsRpcLayer = (
[WS_METHODS.serverRefreshProviders]: (input) =>
observeRpcEffect(
WS_METHODS.serverRefreshProviders,
(input.cwd !== undefined && input.instanceId !== undefined
? providerRegistry.refreshWorkspaceSnapshot({
instanceId: input.instanceId,
cwd: input.cwd,
})
: input.instanceId !== undefined
? providerRegistry.refreshInstance(input.instanceId)
: providerRegistry.refresh()
).pipe(
Effect.tap(() =>
input.instanceId === undefined ? usageLimitSources.refresh : Effect.void,
),
Effect.map((providers) => ({ providers })),
),
Effect.gen(function* () {
let providers = yield* input.cwd !== undefined && input.instanceId !== undefined
? providerRegistry.refreshWorkspaceSnapshot({
instanceId: input.instanceId,
cwd: input.cwd,
})
: input.instanceId !== undefined
? providerRegistry.refreshInstance(input.instanceId)
: providerRegistry.refresh();
if (input.instanceId === undefined) {
yield* usageLimitSources.refresh;
}
if (input.refreshModels) {
const instances = yield* providerInstances.listInstances;
for (const instance of instances) {
if (
!instance.refreshModels ||
(input.instanceId !== undefined && input.instanceId !== instance.instanceId) ||
!providers.some(
(provider) =>
provider.instanceId === instance.instanceId &&
provider.enabled &&
provider.installed,
)
)
continue;
yield* instance.refreshModels().pipe(
Effect.mapError(
(error) =>
new ProviderSetupError({
instanceId: instance.instanceId,
operation: "refresh-models",
detail: error.detail,
}),
),
);
providers = yield* providerRegistry.refreshInstance(instance.instanceId);
}
}
return { providers };
}),
{ "rpc.aggregate": "server" },
),
[WS_METHODS.providerAskSessionSideQuestion]: (input) =>
Expand Down Expand Up @@ -2129,6 +2161,52 @@ const makeWsRpcLayer = (
observeRpcEffect(WS_METHODS.serverCancelProviderLogin, providerLogin.cancel(input), {
"rpc.aggregate": "server",
}),
[WS_METHODS.providerAuthStart]: (input) =>
observeRpcEffect(
WS_METHODS.providerAuthStart,
providerAuth.start(input, currentSessionId),
{ "rpc.aggregate": "provider" },
),
[WS_METHODS.providerAuthComplete]: (input) =>
observeRpcEffect(
WS_METHODS.providerAuthComplete,
providerAuth.complete(input, currentSessionId),
{ "rpc.aggregate": "provider" },
),
[WS_METHODS.providerAuthCancel]: (input) =>
observeRpcEffect(
WS_METHODS.providerAuthCancel,
providerAuth.cancel(input, currentSessionId),
{ "rpc.aggregate": "provider" },
),
[WS_METHODS.providerAuthLogout]: (input) =>
observeRpcEffect(WS_METHODS.providerAuthLogout, providerAuth.logout(input), {
"rpc.aggregate": "provider",
}),
[WS_METHODS.providerAuthSubscribe]: (input) =>
observeRpcStream(
WS_METHODS.providerAuthSubscribe,
providerAuth.subscribe(input, currentSessionId),
{ "rpc.aggregate": "provider" },
),
[WS_METHODS.providerInstallStart]: (input) =>
observeRpcEffect(WS_METHODS.providerInstallStart, providerInstallation.start(input), {
"rpc.aggregate": "provider",
}),
[WS_METHODS.providerInstallCancel]: (input) =>
observeRpcEffect(WS_METHODS.providerInstallCancel, providerInstallation.cancel(input), {
"rpc.aggregate": "provider",
}),
[WS_METHODS.providerInstallSubscribe]: (input) =>
observeRpcStream(
WS_METHODS.providerInstallSubscribe,
providerInstallation.subscribe(input),
{ "rpc.aggregate": "provider" },
),
[WS_METHODS.providerInstallRemove]: (input) =>
observeRpcEffect(WS_METHODS.providerInstallRemove, providerInstallation.remove(input), {
"rpc.aggregate": "provider",
}),
[WS_METHODS.serverUpdateServer]: (input) =>
observeRpcEffect(WS_METHODS.serverUpdateServer, serverUpdate.update(input), {
"rpc.aggregate": "server",
Expand Down
2 changes: 2 additions & 0 deletions packages/client-runtime/src/rpc/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ export type EnvironmentRpcTag = keyof WsRpcProtocolClient & string;
type RpcMethod<TTag extends EnvironmentRpcTag> = WsRpcProtocolClient[TTag];

export type EnvironmentSubscriptionRpcTag =
| typeof WS_METHODS.providerAuthSubscribe
| typeof WS_METHODS.providerInstallSubscribe
| typeof ORCHESTRATION_WS_METHODS.subscribeShell
| typeof ORCHESTRATION_WS_METHODS.subscribeThread
| typeof WS_METHODS.subscribeAuthAccess
Expand Down
53 changes: 52 additions & 1 deletion packages/client-runtime/src/state/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -941,6 +941,52 @@ export function createServerEnvironmentAtoms<R, E>(
updateStateAtom,
settingsValueAtom,
providersValueAtom,
providerAuthState: createEnvironmentRpcSubscriptionAtomFamily(runtime, {
label: "environment-data:provider:auth-state",
tag: WS_METHODS.providerAuthSubscribe,
idleTtlMs: 0,
}),
startProviderAuth: createEnvironmentRpcCommand(runtime, {
label: "environment-data:provider:auth-start",
tag: WS_METHODS.providerAuthStart,
concurrency: {
mode: "singleFlight",
key: ({ environmentId, input }) => JSON.stringify([environmentId, input.instanceId]),
},
}),
completeProviderAuth: createEnvironmentRpcCommand(runtime, {
label: "environment-data:provider:auth-complete",
tag: WS_METHODS.providerAuthComplete,
}),
cancelProviderAuth: createEnvironmentRpcCommand(runtime, {
label: "environment-data:provider:auth-cancel",
tag: WS_METHODS.providerAuthCancel,
}),
logoutProviderAuth: createEnvironmentRpcCommand(runtime, {
label: "environment-data:provider:auth-logout",
tag: WS_METHODS.providerAuthLogout,
}),
providerInstallState: createEnvironmentRpcSubscriptionAtomFamily(runtime, {
label: "environment-data:provider:install-state",
tag: WS_METHODS.providerInstallSubscribe,
idleTtlMs: 0,
}),
startProviderInstall: createEnvironmentRpcCommand(runtime, {
label: "environment-data:provider:install-start",
tag: WS_METHODS.providerInstallStart,
concurrency: {
mode: "singleFlight",
key: ({ environmentId }) => environmentId,
},
}),
cancelProviderInstall: createEnvironmentRpcCommand(runtime, {
label: "environment-data:provider:install-cancel",
tag: WS_METHODS.providerInstallCancel,
}),
removeProviderInstallation: createEnvironmentRpcCommand(runtime, {
label: "environment-data:provider:install-remove",
tag: WS_METHODS.providerInstallRemove,
}),
traceDiagnostics: createEnvironmentRpcQueryAtomFamily(runtime, {
label: "environment-data:server:trace-diagnostics",
tag: WS_METHODS.serverGetTraceDiagnostics,
Expand Down Expand Up @@ -999,7 +1045,12 @@ export function createServerEnvironmentAtoms<R, E>(
concurrency: {
mode: "singleFlight",
key: ({ environmentId, input }) =>
JSON.stringify([environmentId, input.instanceId ?? null, input.cwd ?? null]),
JSON.stringify([
environmentId,
input.instanceId ?? null,
input.cwd ?? null,
input.refreshModels ?? false,
]),
},
}),
updateProvider: createEnvironmentRpcCommand(runtime, {
Expand Down
Loading
Loading