authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-10-09 09:42:40-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-10-09 09:42:40-07:00
log31b04e2ee929660d80d5931ed53fd17bf1d33ce6
tree0a706e9eea32af1a0f39a15e29f23f62d3866dac
parent784b5625f9e228cca8dd67e6e24e402325672544
signature Signed by PGP key B5690EEEBB952194

refactor(provider): bring opencode, muse, pi, core and testing in line with Effect conventions (#17542)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

77 files changed, 396 insertions(+), 380 deletions(-)

apps/server/scripts/record-grok-acp-replay-fixture.ts+2-2
......@@ -24,7 +24,7 @@ import * as Schema from "effect/Schema";
2424import { ChildProcessSpawner } from "effect/process";
2525import type * as EffectAcpProtocol from "effect-acp/protocol";
2626
27import { layerTestProviderHost } from "@t3tools/provider-testing/host";
27import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost";
2828import {
2929 GROK_DEFAULT_INSTANCE_ID,
3030 GROK_PROVIDER,
......@@ -490,7 +490,7 @@ const recordScenario = Effect.fn("recordGrokScenario")(function* (fixtureName: s
490490 ).pipe(
491491 Layer.provide(
492492 Layer.mergeAll(
493 layerTestProviderHost().pipe(Layer.provide(NodeServices.layer)),
493 TestProviderHost.layer().pipe(Layer.provide(NodeServices.layer)),
494494 NodeServices.layer,
495495 IdAllocator.layer,
496496 ),
apps/server/src/assets/AssetAccess.ts+2-2
......@@ -53,7 +53,7 @@ import * as ServerSecretStore from "../auth/ServerSecretStore.ts";
5353import { parseAttachmentFileExtension, resolveAttachmentPathById } from "../attachmentStore.ts";
5454import * as ServerConfig from "../config.ts";
5555import * as Orchestrator from "../orchestration-v2/Orchestrator.ts";
56import { expandHomePathWith } from "@t3tools/provider-core/server/pathExpansion";
56import { expandHomePath } from "@t3tools/provider-core/server/pathExpansion";
5757import * as ProjectFaviconResolver from "../project/ProjectFaviconResolver.ts";
5858import * as WorkspacePaths from "../workspace/WorkspacePaths.ts";
5959import * as NativeAppIconResolver from "./NativeAppIconResolver.ts";
......@@ -469,7 +469,7 @@ export const issueAssetUrl = Effect.fn("AssetAccess.issueAssetUrl")(function* (i
469469
470470 switch (input.resource._tag) {
471471 case "media-file": {
472 let requestedPath = expandHomePathWith(input.resource.path, path);
472 let requestedPath = expandHomePath(input.resource.path);
473473 if (!path.isAbsolute(requestedPath)) {
474474 if (!input.workspaceRoot) {
475475 return yield* new AssetWorkspaceContextNotFoundError({ resource: input.resource });
apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.test.ts+2-2
......@@ -53,7 +53,7 @@ import * as EffectAcpErrors from "effect-acp/errors";
5353import type * as EffectAcpProtocol from "effect-acp/protocol";
5454import type * as EffectAcpSchema from "effect-acp/compat";
5555
56import { layerTestProviderHost } from "@t3tools/provider-testing/host";
56import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost";
5757import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
5858import * as AcpSessionRuntime from "@t3tools/provider-acp/server/AcpSessionRuntime";
5959import {
......@@ -94,7 +94,7 @@ import {
9494
9595const DEFAULT_GROK_SETTINGS = Schema.decodeSync(GrokSettings)({});
9696
97const layerHost = layerTestProviderHost().pipe(Layer.provide(NodeServices.layer));
97const layerHost = TestProviderHost.layer().pipe(Layer.provide(NodeServices.layer));
9898
9999const layerTest = Layer.mergeAll(
100100 NodeServices.layer,
apps/server/src/orchestration-v2/Adapters/AcpRegistryAdapterV2.test.ts+2-2
......@@ -15,7 +15,7 @@ import * as EffectAcpErrors from "effect-acp/errors";
1515
1616import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";
1717import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
18import { layerTestProviderHost } from "@t3tools/provider-testing/host";
18import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost";
1919import * as ServerSettings from "../../serverSettings.ts";
2020import type {
2121 AcpRegistryAvailableCommands,
......@@ -44,7 +44,7 @@ const decodeAcpRegistryAdapterSettings = Schema.decodeUnknownEffect(
4444 AcpRegistryAdapterV2Driver.configSchema,
4545);
4646
47const layerHost = layerTestProviderHost().pipe(Layer.provide(NodeServices.layer));
47const layerHost = TestProviderHost.layer().pipe(Layer.provide(NodeServices.layer));
4848
4949const layerRegistry = Layer.succeed(
5050 HttpClient.HttpClient,
apps/server/src/orchestration-v2/Adapters/AcpRegistryAdapterV2.testkit.ts+2-2
......@@ -8,7 +8,7 @@ import * as Path from "effect/Path";
88import * as Schema from "effect/Schema";
99import { ChildProcessSpawner } from "effect/process";
1010
11import { layerTestProviderHost } from "@t3tools/provider-testing/host";
11import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost";
1212import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
1313import * as ProviderAdapterRegistry from "../ProviderAdapterRegistry.ts";
1414import type { ProviderReplayGate } from "@t3tools/provider-testing/replayGate";
......@@ -35,7 +35,7 @@ function layerAcpRegistryProviderAdapterRegistryReplay(
3535 transcript: AcpReplayTranscript,
3636 options: { readonly replayGate?: ProviderReplayGate } = {},
3737) {
38 const layerHost = layerTestProviderHost().pipe(Layer.provide(NodeServices.layer));
38 const layerHost = TestProviderHost.layer().pipe(Layer.provide(NodeServices.layer));
3939
4040 return ProviderAdapterRegistry.layerFromAdaptersEffect(
4141 Effect.gen(function* () {
apps/server/src/orchestration-v2/Adapters/AntigravityAdapterV2.test.ts+2-2
......@@ -24,7 +24,7 @@ import type * as EffectAcpSchema from "effect-acp/compat";
2424
2525import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";
2626import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
27import { layerTestProviderHost } from "@t3tools/provider-testing/host";
27import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost";
2828import type * as AcpSessionRuntime from "@t3tools/provider-acp/server/AcpSessionRuntime";
2929import { makeAntigravityAcpRuntime } from "../../provider/acp/AntigravityAcpSupport.ts";
3030import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
......@@ -131,7 +131,7 @@ const layerSession = Layer.mergeAll(
131131 NodeServices.layer,
132132 IdAllocator.layer,
133133 McpProviderSessions.layer,
134 layerTestProviderHost().pipe(Layer.provide(NodeServices.layer)),
134 TestProviderHost.layer().pipe(Layer.provide(NodeServices.layer)),
135135);
136136
137137describe("AntigravityAdapterV2 client file system", () => {
apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts+2-2
......@@ -108,7 +108,7 @@ import {
108108 claudeRateLimitEventToUpdate,
109109 type ClaudeScopedLimitNames,
110110} from "../../provider/claudeUsageLimits.ts";
111import type { ServerProviderShape } from "@t3tools/provider-core/server/snapshot";
111import type { ManagedServerProvider } from "@t3tools/provider-core/server/snapshot";
112112import { mergeProviderInstanceEnvironment } from "@t3tools/provider-core/server/instanceEnvironment";
113113import { T3_CODE_ORCHESTRATION_INSTRUCTIONS } from "@t3tools/provider-core/server/orchestrationInstructions";
114114import { buildRuntimeInstructions } from "@t3tools/provider-core/server/runtimeInstructions";
......@@ -3018,7 +3018,7 @@ export interface ClaudeAdapterV2Options {
30183018 readonly idAllocator: IdAllocator.IdAllocatorV2["Service"];
30193019 readonly queryRunner: ClaudeAgentSdkQueryRunnerShape;
30203020 readonly scopedLimitNames?: Ref.Ref<ClaudeScopedLimitNames>;
3021 readonly onUsageLimits?: ServerProviderShape["applyUsageLimits"];
3021 readonly onUsageLimits?: ManagedServerProvider["applyUsageLimits"];
30223022 /** Sink for wake-turn continuation requests; defaults to dropping them. */
30233023 readonly continuationRequests?: {
30243024 readonly offer: (
apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts+2-2
......@@ -12,7 +12,7 @@ import {
1212 completeCodexTurnTokenUsage,
1313 type CodexTurnTokenUsageState,
1414} from "../../provider/CodexTurnTokenUsage.ts";
15import type { ServerProviderShape } from "@t3tools/provider-core/server/snapshot";
15import type { ManagedServerProvider } from "@t3tools/provider-core/server/snapshot";
1616import type { CodexEffectiveRuntime } from "../../provider/CodexManagedRuntime.ts";
1717import { buildCodexInitializeParams } from "../../provider/CodexProvider.ts";
1818import {
......@@ -1646,7 +1646,7 @@ export interface CodexAdapterV2Options {
16461646 readonly settings: CodexSettings;
16471647 readonly environment: NodeJS.ProcessEnv;
16481648 readonly clientFactory: CodexAppServerClientFactoryShape;
1649 readonly onUsageLimits?: ServerProviderShape["applyUsageLimits"];
1649 readonly onUsageLimits?: ManagedServerProvider["applyUsageLimits"];
16501650 /**
16511651 * Resolves launch settings when each session opens, replacing `settings` and
16521652 * `environment`. Managed ChatGPT sign-in uses it to launch the T3-installed
apps/server/src/orchestration-v2/Adapters/CursorAdapterV2.testkit.ts+2-2
......@@ -15,7 +15,7 @@ import * as FileSystem from "effect/FileSystem";
1515import * as Layer from "effect/Layer";
1616import * as Schema from "effect/Schema";
1717
18import { layerTestProviderHost } from "@t3tools/provider-testing/host";
18import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost";
1919import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
2020import { ProviderAdapterDriverCreateError } from "@t3tools/provider-core/server/adapterDriver";
2121import * as ProviderAdapterRegistry from "../ProviderAdapterRegistry.ts";
......@@ -537,7 +537,7 @@ export function layer(
537537 Layer.provide(
538538 Layer.mergeAll(
539539 layerCursorAgentSdkReplay(transcript, options),
540 layerTestProviderHost().pipe(Layer.provide(NodeServices.layer)),
540 TestProviderHost.layer().pipe(Layer.provide(NodeServices.layer)),
541541 layerHostEnvironment,
542542 NodeServices.layer,
543543 IdAllocator.layer,
apps/server/src/orchestration-v2/Adapters/GrokAdapterV2.test.ts+2-2
......@@ -20,7 +20,7 @@ import * as Schema from "effect/Schema";
2020import { ChildProcessSpawner } from "effect/process";
2121import type * as EffectAcpSchema from "effect-acp/compat";
2222
23import { layerTestProviderHost } from "@t3tools/provider-testing/host";
23import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost";
2424import * as ProjectStore from "../ProjectStore.ts";
2525import { buildInitialGrokProviderSnapshot } from "@t3tools/provider-grok/testing";
2626import type { ProviderInstance } from "@t3tools/provider-core/server/driver";
......@@ -309,7 +309,7 @@ describe("Grok permission prompts", () => {
309309});
310310
311311describe("Grok launch permission mode", () => {
312 const layerHost = layerTestProviderHost().pipe(Layer.provide(NodeServices.layer));
312 const layerHost = TestProviderHost.layer().pipe(Layer.provide(NodeServices.layer));
313313 const layerTest = Layer.mergeAll(
314314 NodeServices.layer,
315315 IdAllocator.layer,
apps/server/src/orchestration-v2/Adapters/GrokAdapterV2.testkit.ts+2-2
......@@ -9,7 +9,7 @@ import { ChildProcessSpawner } from "effect/process";
99import { HostProcessPlatform } from "@t3tools/shared/hostProcess";
1010import { resolveSelfInvocation } from "@t3tools/shared/nodeRuntime";
1111
12import { layerTestProviderHost } from "@t3tools/provider-testing/host";
12import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost";
1313import { GROK_ACP_CANCEL_META, GROK_ACP_INITIALIZE_META } from "@t3tools/provider-grok/testing";
1414import { makeXAiPromptCompletionRuntime } from "@t3tools/provider-grok/testing";
1515import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
......@@ -36,7 +36,7 @@ function layerGrokProviderAdapterRegistryReplay(
3636 transcript: AcpReplayTranscript,
3737 options: { readonly replayGate?: ProviderReplayGate } = {},
3838) {
39 const layerHost = layerTestProviderHost().pipe(Layer.provide(NodeServices.layer));
39 const layerHost = TestProviderHost.layer().pipe(Layer.provide(NodeServices.layer));
4040
4141 return ProviderAdapterRegistry.layerFromAdaptersEffect(
4242 Effect.gen(function* () {
apps/server/src/orchestration-v2/Adapters/MuseAdapterV2.testkit.ts+2-2
......@@ -27,7 +27,7 @@ import * as Effect from "effect/Effect";
2727import * as Layer from "effect/Layer";
2828import * as Schema from "effect/Schema";
2929
30import { layerTestProviderHost } from "@t3tools/provider-testing/host";
30import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost";
3131import {
3232 museInitializeParams,
3333 museServeArgs,
......@@ -460,7 +460,7 @@ export function layer(input: {
460460 ).pipe(
461461 Layer.provide(
462462 Layer.mergeAll(
463 layerTestProviderHost().pipe(Layer.provide(NodeServices.layer)),
463 TestProviderHost.layer().pipe(Layer.provide(NodeServices.layer)),
464464 NodeServices.layer,
465465 IdAllocator.layer,
466466 ),
apps/server/src/orchestration-v2/Adapters/OpenCode2AdapterV2.testkit.ts+2-2
......@@ -26,7 +26,7 @@ import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
2626import * as ProviderAdapterRegistry from "../ProviderAdapterRegistry.ts";
2727import type { ProviderReplayGate } from "@t3tools/provider-testing/replayGate";
2828import type { OrchestratorV2ProviderReplayHarness } from "../testkit/ProviderReplayHarness.ts";
29import { layerTestProviderHost } from "@t3tools/provider-testing/host";
29import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost";
3030import { OPENCODE_PROVIDER } from "@t3tools/provider-opencode/testing";
3131import {
3232 OpenCodeReplayController,
......@@ -267,7 +267,7 @@ const makeReplayAdapter = (
267267 );
268268 });
269269
270const layerReplayHost = layerTestProviderHost().pipe(Layer.provide(NodeServices.layer));
270const layerReplayHost = TestProviderHost.layer().pipe(Layer.provide(NodeServices.layer));
271271
272272function layerRegistry(
273273 transcript: OpenCode2ReplayTranscript,
apps/server/src/orchestration-v2/Adapters/OpenCodeAdapterV2.testkit.ts+3-3
......@@ -8,7 +8,7 @@ import * as Schema from "effect/Schema";
88
99import { OpenCodeAdapterV2Driver } from "@t3tools/provider-opencode/server";
1010import * as OpenCodeRuntime from "@t3tools/provider-opencode/server/OpenCodeRuntime";
11import { layerTestProviderHost } from "@t3tools/provider-testing/host";
11import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost";
1212import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers";
1313import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
1414import { ProviderAdapterDriverCreateError } from "@t3tools/provider-core/server/adapterDriver";
......@@ -448,7 +448,7 @@ function layerOpenCodeReplayRuntime(transcript: OpenCodeSdkReplayTranscript) {
448448 detail: "OpenCode replay does not load skills.",
449449 }),
450450 ),
451 } satisfies OpenCodeRuntime.OpenCodeRuntimeShape);
451 } satisfies OpenCodeRuntime.OpenCodeRuntime["Service"]);
452452 }),
453453 );
454454}
......@@ -466,7 +466,7 @@ function layerOpenCodeProviderAdapterRegistryReplay(transcript: OpenCodeSdkRepla
466466 Layer.provide(
467467 Layer.mergeAll(
468468 layerOpenCodeReplayRuntime(transcript),
469 layerTestProviderHost().pipe(Layer.provide(NodeServices.layer)),
469 TestProviderHost.layer().pipe(Layer.provide(NodeServices.layer)),
470470 NodeServices.layer,
471471 IdAllocator.layer,
472472 Layer.succeed(
apps/server/src/orchestration-v2/Adapters/PiAdapterV2.testkit.ts+2-2
......@@ -30,7 +30,7 @@ import * as Sink from "effect/Sink";
3030import * as Stream from "effect/Stream";
3131import { ChildProcess, ChildProcessSpawner } from "effect/process";
3232
33import { layerTestProviderHost } from "@t3tools/provider-testing/host";
33import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost";
3434import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
3535import { ProviderAdapterDriverCreateError } from "@t3tools/provider-core/server/adapterDriver";
3636import * as ProviderAdapterRegistry from "../ProviderAdapterRegistry.ts";
......@@ -425,7 +425,7 @@ export function layer<E, R>(input: {
425425 Layer.provide(input.spawner),
426426 Layer.provide(
427427 Layer.mergeAll(
428 layerTestProviderHost().pipe(Layer.provide(NodeServices.layer)),
428 TestProviderHost.layer().pipe(Layer.provide(NodeServices.layer)),
429429 NodeServices.layer,
430430 IdAllocator.layer,
431431 ),
apps/server/src/provider/CodexProvider.ts+1-1
......@@ -716,7 +716,7 @@ export const checkCodexProviderStatus = Effect.fn("checkCodexProviderStatus")(fu
716716// NOTE: the singleton `CodexProviderLive` Layer has been removed as part of
717717// the per-instance-driver refactor. `CodexDriver.create()` builds a managed
718718// snapshot per instance (each with its own `CodexSettings`) and hands the
719// resulting `ServerProviderShape` back as `ProviderInstance.snapshot`.
719// resulting `ManagedServerProvider` back as `ProviderInstance.snapshot`.
720720//
721721// The `makePendingCodexProvider` and `checkCodexProviderStatus` helpers are
722722// re-exported for use by `CodexDriver`.
apps/server/src/provider/Drivers/CodexDriver.ts+1-1
......@@ -3,7 +3,7 @@
33 *
44 * A driver is a plain value (not a Context.Service) whose `create()` returns
55 * one `ProviderInstance` bundling:
6 * - `snapshot` — the live `ServerProviderShape` for this instance;
6 * - `snapshot` — the live `ManagedServerProvider` for this instance;
77 * - `adapter` — the Codex session/turn/approval runtime;
88 * - `textGeneration` — commit/PR/branch/title generation via `codex exec`.
99 *
apps/server/src/provider/ProviderRegistry.ts+2-2
......@@ -9,8 +9,8 @@
99 *
1010 * Historically this Layer composed four per-kind Live Layers
1111 * (`CodexProviderLive`, `ClaudeProviderLive`, …) that each exposed a
12 * `ServerProviderShape`. Those Lives were deleted during the driver /
13 * instance refactor — every driver now carries its `snapshot: ServerProviderShape`
12 * `ManagedServerProvider`. Those Lives were deleted during the driver /
13 * instance refactor — every driver now carries its `snapshot: ManagedServerProvider`
1414 * bundled onto the `ProviderInstance` the registry produces.
1515 *
1616 * Each configured instance (including multi-instance setups like
apps/server/src/provider/builtInProviderCatalog.ts+3-3
......@@ -1,6 +1,6 @@
11import type { ProviderDriverKind, ProviderInstanceId, ServerProvider } from "@t3tools/contracts";
22import type * as Stream from "effect/Stream";
3import type { ServerProviderShape } from "@t3tools/provider-core/server/snapshot";
3import type { ManagedServerProvider } from "@t3tools/provider-core/server/snapshot";
44
55export type ProviderSnapshotSource = {
66 /**
......@@ -11,7 +11,7 @@ export type ProviderSnapshotSource = {
1111 readonly instanceId: ProviderInstanceId;
1212 /** Driver implementation kind. */
1313 readonly driverKind: ProviderDriverKind;
14 readonly getSnapshot: ServerProviderShape["getSnapshot"];
15 readonly refresh: ServerProviderShape["refresh"];
14 readonly getSnapshot: ManagedServerProvider["getSnapshot"];
15 readonly refresh: ManagedServerProvider["refresh"];
1616 readonly streamChanges: Stream.Stream<ServerProvider>;
1717};
apps/server/src/sourceControl/SourceControlRepositoryService.ts+2-2
......@@ -21,7 +21,7 @@ import {
2121} from "@t3tools/contracts";
2222
2323import * as ServerConfig from "../config.ts";
24import { expandHomePathWith } from "@t3tools/provider-core/server/pathExpansion";
24import { expandHomePath } from "@t3tools/provider-core/server/pathExpansion";
2525import {
2626 parseGitCloneProgressLine,
2727 type GitCloneProgressLine,
......@@ -213,7 +213,7 @@ export const make = Effect.gen(function* () {
213213 });
214214 }
215215
216 return path.resolve(expandHomePathWith(trimmed, path));
216 return path.resolve(expandHomePath(trimmed));
217217 },
218218 );
219219
apps/server/src/textGeneration/OpenCode2TextGeneration.test.ts+2-2
......@@ -4,13 +4,13 @@ import { ProviderInstanceId } from "@t3tools/contracts";
44import * as Effect from "effect/Effect";
55import * as Layer from "effect/Layer";
66
7import { layerTestProviderHost } from "@t3tools/provider-testing/host";
7import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost";
88import * as OpenCode2AdapterV2Testkit from "../orchestration-v2/Adapters/OpenCode2AdapterV2.testkit.ts";
99import * as OpenCode2Server from "@t3tools/provider-opencode/server/v2/OpenCode2Server";
1010import * as OpenCode2TextGeneration from "@t3tools/provider-opencode/server/v2/textGeneration";
1111import { OPENCODE2_TITLE_GENERATION } from "./OpenCode2TextGeneration.fixture.ts";
1212
13const layer = layerTestProviderHost().pipe(Layer.provideMerge(NodeServices.layer));
13const layer = TestProviderHost.layer().pipe(Layer.provideMerge(NodeServices.layer));
1414
1515it.layer(layer)("OpenCode2TextGeneration", (it) => {
1616 it.effect("generates a title in a temporary session on the free tier and removes it", () =>
apps/server/src/workspace/WorkspaceEntries.ts+3-3
......@@ -23,7 +23,7 @@ import { HostProcessPlatform } from "@t3tools/shared/hostProcess";
2323import { isExplicitRelativePath, isWindowsAbsolutePath } from "@t3tools/shared/path";
2424import { normalizeSearchQuery } from "@t3tools/shared/searchRanking";
2525
26import { expandHomePathWith } from "@t3tools/provider-core/server/pathExpansion";
26import { expandHomePath } from "@t3tools/provider-core/server/pathExpansion";
2727import * as VcsProcess from "../vcs/VcsProcess.ts";
2828import * as WorkspacePaths from "./WorkspacePaths.ts";
2929import * as WorkspaceSearchIndex from "./WorkspaceSearchIndex.ts";
......@@ -120,7 +120,7 @@ const resolveBrowseTarget = Effect.fn("WorkspaceEntries.resolveBrowseTarget")(fu
120120 }
121121
122122 if (!isExplicitRelativePath(input.partialPath)) {
123 return path.resolve(expandHomePathWith(input.partialPath, path));
123 return path.resolve(expandHomePath(input.partialPath));
124124 }
125125
126126 if (!input.cwd) {
......@@ -128,7 +128,7 @@ const resolveBrowseTarget = Effect.fn("WorkspaceEntries.resolveBrowseTarget")(fu
128128 partialPath: input.partialPath,
129129 });
130130 }
131 return path.resolve(expandHomePathWith(input.cwd, path), input.partialPath);
131 return path.resolve(expandHomePath(input.cwd), input.partialPath);
132132});
133133
134134/** @public Service construction is part of the canonical Effect module API. */
apps/server/src/workspace/WorkspacePaths.ts+2-2
......@@ -14,7 +14,7 @@ import * as Layer from "effect/Layer";
1414import * as Path from "effect/Path";
1515import * as Schema from "effect/Schema";
1616
17import { expandHomePathWith } from "@t3tools/provider-core/server/pathExpansion";
17import { expandHomePath } from "@t3tools/provider-core/server/pathExpansion";
1818
1919export class WorkspaceRootNotExistsError extends Schema.TaggedError<WorkspaceRootNotExistsError>()(
2020 "WorkspaceRootNotExistsError",
......@@ -152,7 +152,7 @@ export const make = Effect.gen(function* () {
152152 const normalizeWorkspaceRoot: WorkspacePaths["Service"]["normalizeWorkspaceRoot"] = Effect.fn(
153153 "WorkspacePaths.normalizeWorkspaceRoot",
154154 )(function* (workspaceRoot, options) {
155 const normalizedWorkspaceRoot = path.resolve(expandHomePathWith(workspaceRoot.trim(), path));
155 const normalizedWorkspaceRoot = path.resolve(expandHomePath(workspaceRoot.trim()));
156156 let workspaceStat = yield* statWorkspaceRoot(
157157 workspaceRoot,
158158 normalizedWorkspaceRoot,
apps/server/src/worktreesDirectory.ts+2-2
......@@ -1,6 +1,6 @@
11import type * as Path from "effect/Path";
22
3import { expandHomePathWith } from "@t3tools/provider-core/server/pathExpansion";
3import { expandHomePath } from "@t3tools/provider-core/server/pathExpansion";
44
55/**
66 * Directory new worktrees are created under: the `worktreesDirectory`
......@@ -15,7 +15,7 @@ export function resolveWorktreesDirectory(
1515 path: Path.Path,
1616): string | null {
1717 if (setting === "") return defaultDir;
18 const expanded = expandHomePathWith(setting, path);
18 const expanded = expandHomePath(setting);
1919 if (!path.isAbsolute(expanded)) return null;
2020 const resolved = path.resolve(expanded);
2121 return isFilesystemRoot(resolved, path) ? null : resolved;
packages/provider-acp-registry/src/server/AcpRegistrySupport.test.ts+5-5
......@@ -24,7 +24,7 @@ import * as TestClock from "effect/testing/TestClock";
2424import { ChildProcess, ChildProcessSpawner } from "effect/process";
2525
2626import * as AcpRegistrySupport from "./AcpRegistrySupport.ts";
27import { layerTestProviderHost, TestProviderHostSettings } from "@t3tools/provider-testing/host";
27import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost";
2828
2929const registryUrl = "https://registry.test/registry.json";
3030const archiveUrl = "https://registry.test/example-agent.bin";
......@@ -60,7 +60,7 @@ function layerResolver(
6060) {
6161 return Layer.mergeAll(
6262 NodeServices.layer,
63 layerTestProviderHost().pipe(Layer.provide(NodeServices.layer)),
63 TestProviderHost.layer().pipe(Layer.provide(NodeServices.layer)),
6464 Layer.succeed(HostProcessPlatform, "linux"),
6565 Layer.succeed(HostProcessArchitecture, "x64"),
6666 Layer.succeed(HostProcessEnvironment, environment),
......@@ -776,7 +776,7 @@ describe("AcpRegistrySupport", () => {
776776 Effect.provide(
777777 Layer.mergeAll(
778778 NodeServices.layer,
779 layerTestProviderHost().pipe(Layer.provide(NodeServices.layer)),
779 TestProviderHost.layer().pipe(Layer.provide(NodeServices.layer)),
780780 ),
781781 ),
782782 Effect.provideService(HostProcessPlatform, "linux"),
......@@ -1374,7 +1374,7 @@ describe("AcpRegistrySupport", () => {
13741374 toolsDir: `${cacheDir}/tools`,
13751375 registryUrl,
13761376 });
1377 const hostSettings = yield* TestProviderHostSettings;
1377 const hostSettings = yield* TestProviderHost.TestProviderHostSettings;
13781378 const instanceId = ProviderInstanceId.make("uninstall-reference");
13791379 yield* hostSettings.set({
13801380 ...DEFAULT_SERVER_SETTINGS,
......@@ -1492,7 +1492,7 @@ describe("AcpRegistrySupport", () => {
14921492 const agentRoot = `${cacheDir}/tools/${agent.id}`;
14931493 expect(yield* fileSystem.exists(agentRoot)).toBe(true);
14941494
1495 const hostSettings = yield* TestProviderHostSettings;
1495 const hostSettings = yield* TestProviderHost.TestProviderHostSettings;
14961496 const instanceId = ProviderInstanceId.make("registry-reference");
14971497 yield* hostSettings.set({
14981498 ...DEFAULT_SERVER_SETTINGS,
packages/provider-core/src/server/driver.ts+2-2
......@@ -40,7 +40,7 @@ import type * as Scope from "effect/Scope";
4040import type * as ProviderAdapter from "./ProviderAdapter.ts";
4141import type { ProviderAuthController } from "./auth.ts";
4242import type { ProviderDriverError } from "./errors.ts";
43import type { ServerProviderShape } from "./snapshot.ts";
43import type { ManagedServerProvider } from "./snapshot.ts";
4444import type { ProviderTextGeneration } from "./textGeneration.ts";
4545
4646/**
......@@ -85,7 +85,7 @@ export interface ProviderInstance {
8585 readonly displayName: string | undefined;
8686 readonly accentColor?: string | undefined;
8787 readonly enabled: boolean;
88 readonly snapshot: ServerProviderShape;
88 readonly snapshot: ManagedServerProvider;
8989 readonly snapshotForCwd?: (
9090 cwd: string,
9191 ) => Effect.Effect<ProviderWorkspaceSnapshot, ProviderDriverError>;
packages/provider-core/src/server/maintenanceResolver.test.ts+2-2
......@@ -13,8 +13,8 @@ import * as Effect from "effect/Effect";
1313import * as Fiber from "effect/Fiber";
1414import * as Sink from "effect/Sink";
1515import * as Stream from "effect/Stream";
16import { HttpClient } from "effect/http";
17import { ChildProcessSpawner } from "effect/process";
16import * as HttpClient from "effect/http/HttpClient";
17import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
1818import * as ProviderLatestVersions from "./ProviderLatestVersions.ts";
1919import {
2020 createProviderVersionAdvisory,
packages/provider-core/src/server/maintenanceResolver.ts+4-2
......@@ -25,8 +25,10 @@ import * as Option from "effect/Option";
2525import * as Path from "effect/Path";
2626import * as Schema from "effect/Schema";
2727import * as Stream from "effect/Stream";
28import { HttpClient, HttpClientRequest } from "effect/http";
29import { ChildProcess, ChildProcessSpawner } from "effect/process";
28import * as HttpClient from "effect/http/HttpClient";
29import * as HttpClientRequest from "effect/http/HttpClientRequest";
30import * as ChildProcess from "effect/process/ChildProcess";
31import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
3032
3133import { collectUint8StreamText } from "./collectStreamText.ts";
3234import * as ProviderLatestVersions from "./ProviderLatestVersions.ts";
packages/provider-core/src/server/managedProvider.test.ts+1-1
......@@ -14,7 +14,7 @@ import * as Layer from "effect/Layer";
1414import * as PubSub from "effect/PubSub";
1515import * as Ref from "effect/Ref";
1616import * as Stream from "effect/Stream";
17import { TestClock } from "effect/testing";
17import * as TestClock from "effect/testing/TestClock";
1818
1919import * as ProviderHost from "./ProviderHost.ts";
2020import { makeManagedServerProvider } from "./managedProvider.ts";
packages/provider-core/src/server/managedProvider.ts+5-5
......@@ -17,7 +17,7 @@ import * as Semaphore from "effect/Semaphore";
1717
1818import * as ProviderHost from "./ProviderHost.ts";
1919import { applyUsageLimitsUpdate, resolveUsageLimitsAfterProbe } from "./usageLimits.ts";
20import type { ServerProviderShape } from "./snapshot.ts";
20import type { ManagedServerProvider } from "./snapshot.ts";
2121
2222interface ProviderSnapshotState {
2323 readonly snapshot: ServerProvider;
......@@ -38,7 +38,7 @@ function withUsageLimits(
3838export const makeManagedServerProvider = Effect.fn("makeManagedServerProvider")(function* <
3939 Settings,
4040>(input: {
41 readonly resolveMaintenance: ServerProviderShape["resolveMaintenance"];
41 readonly resolveMaintenance: ManagedServerProvider["resolveMaintenance"];
4242 readonly getSettings: Effect.Effect<Settings, ServerSettingsError>;
4343 readonly streamSettings: Stream.Stream<Settings>;
4444 readonly haveSettingsChanged: (previous: Settings, next: Settings) => boolean;
......@@ -54,7 +54,7 @@ export const makeManagedServerProvider = Effect.fn("makeManagedServerProvider")(
5454 readonly refreshOnInterval?: boolean;
5555 readonly checkProviderOnSettingsChange?: (previous: Settings, next: Settings) => boolean;
5656}): Effect.fn.Return<
57 ServerProviderShape,
57 ManagedServerProvider,
5858 ServerSettingsError,
5959 Scope.Scope | ProviderHost.ProviderHost
6060> {
......@@ -181,7 +181,7 @@ export const makeManagedServerProvider = Effect.fn("makeManagedServerProvider")(
181181 * `usageLimits` on whatever snapshot is published and leave the enrichment
182182 * generation alone, so an in-flight enrichment still lands.
183183 */
184 const applyUsageLimits: ServerProviderShape["applyUsageLimits"] = (update) =>
184 const applyUsageLimits: ManagedServerProvider["applyUsageLimits"] = (update) =>
185185 Effect.gen(function* () {
186186 const snapshotToPublish = yield* Ref.modify(snapshotStateRef, (state) => {
187187 const usageLimits = applyUsageLimitsUpdate({
......@@ -289,5 +289,5 @@ export const makeManagedServerProvider = Effect.fn("makeManagedServerProvider")(
289289 get streamChanges() {
290290 return Stream.fromPubSub(changesPubSub);
291291 },
292 } satisfies ServerProviderShape;
292 } satisfies ManagedServerProvider;
293293});
packages/provider-core/src/server/pathExpansion.ts-18
......@@ -2,8 +2,6 @@
22import * as NodeOS from "node:os";
33import * as NodePath from "node:path";
44
5import type * as Path from "effect/Path";
6
75/**
86 * Expand a leading `~` (or `~/…`, `~\…`) in a user-supplied path to the
97 * current user's home directory. Spawned processes don't get shell
......@@ -24,19 +22,3 @@ export function expandHomePath(value: string): string {
2422 }
2523 return value;
2624}
27
28/**
29 * Same expansion as `expandHomePath`, but joins with a caller-supplied
30 * `Path.Path` service instead of `node:path`. Use this inside Effect code that
31 * already has `Path.Path` in context so the platform layer stays in control of
32 * separator handling.
33 */
34export function expandHomePathWith(value: string, path: Path.Path): string {
35 if (value === "~") {
36 return NodeOS.homedir();
37 }
38 if (value.startsWith("~/") || value.startsWith("~\\")) {
39 return path.join(NodeOS.homedir(), value.slice(2));
40 }
41 return value;
42}
packages/provider-core/src/server/snapshot.ts+1-1
......@@ -3,7 +3,7 @@ import type * as Effect from "effect/Effect";
33import type * as Stream from "effect/Stream";
44import type { ProviderMaintenanceCapabilities } from "./maintenance.ts";
55
6export interface ServerProviderShape {
6export interface ManagedServerProvider {
77 /**
88 * Ownership-derived update capabilities. Cached between reads; pass
99 * `{ fresh: true }` before executing an update so it never trusts a
packages/provider-core/src/server/snapshotProbe.test.ts+2-1
......@@ -7,7 +7,8 @@ import * as Layer from "effect/Layer";
77import * as PlatformError from "effect/PlatformError";
88import * as Sink from "effect/Sink";
99import * as Stream from "effect/Stream";
10import { ChildProcess, ChildProcessSpawner } from "effect/process";
10import * as ChildProcess from "effect/process/ChildProcess";
11import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
1112
1213import {
1314 isCommandMissingCause,
packages/provider-core/src/server/snapshotProbe.ts+2-1
......@@ -16,7 +16,8 @@ import * as Effect from "effect/Effect";
1616import * as PlatformError from "effect/PlatformError";
1717import * as Schema from "effect/Schema";
1818import * as Stream from "effect/Stream";
19import { ChildProcess, ChildProcessSpawner } from "effect/process";
19import * as ChildProcess from "effect/process/ChildProcess";
20import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
2021import { readCustomModelEntries } from "@t3tools/shared/model";
2122import { HostProcessPlatform } from "@t3tools/shared/hostProcess";
2223import { createProviderVersionAdvisory } from "./maintenanceResolver.ts";
packages/provider-cursor/src/server/adapter.test.ts+4-4
......@@ -22,7 +22,7 @@ import * as Path from "effect/Path";
2222import * as Schema from "effect/Schema";
2323import * as Stream from "effect/Stream";
2424
25import { layerTestProviderHost } from "@t3tools/provider-testing/host";
25import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost";
2626import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
2727import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
2828import * as ProviderAdapter from "@t3tools/provider-core/server/ProviderAdapter";
......@@ -125,7 +125,7 @@ describe("CursorAdapterV2", () => {
125125 }),
126126 }),
127127 }),
128 Effect.provide(layerTestProviderHost({ cwd: workspace })),
128 Effect.provide(TestProviderHost.layer({ cwd: workspace })),
129129 );
130130 const runtime = yield* adapter.openSession({
131131 threadId,
......@@ -258,7 +258,7 @@ describe("CursorAdapterV2", () => {
258258 }),
259259 }),
260260 }),
261 Effect.provide(layerTestProviderHost({ cwd: workspace })),
261 Effect.provide(TestProviderHost.layer({ cwd: workspace })),
262262 );
263263 const runtime = yield* adapter.openSession({
264264 threadId,
......@@ -597,7 +597,7 @@ describe("CursorAdapterV2", () => {
597597 }),
598598 }),
599599 }),
600 Effect.provide(layerTestProviderHost({ cwd: workspace })),
600 Effect.provide(TestProviderHost.layer({ cwd: workspace })),
601601 );
602602 const runtime = yield* adapter.openSession({
603603 threadId,
packages/provider-cursor/src/server/credentialStore.test.ts+2-2
......@@ -6,11 +6,11 @@ import * as FileSystem from "effect/FileSystem";
66import * as Path from "effect/Path";
77
88import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";
9import { layerTestProviderHost } from "@t3tools/provider-testing/host";
9import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost";
1010import * as Layer from "effect/Layer";
1111import { makeCursorCredentialStore } from "./credentialStore.ts";
1212
13const layerHost = Layer.provideMerge(layerTestProviderHost(), NodeServices.layer);
13const layerHost = Layer.provideMerge(TestProviderHost.layer(), NodeServices.layer);
1414
1515const legacyCredentials = {
1616 version: 1 as const,
packages/provider-cursor/src/server/driver.test.ts+2-2
......@@ -11,7 +11,7 @@ import { HttpClient } from "effect/http";
1111import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
1212
1313import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers";
14import { layerTestProviderHost } from "@t3tools/provider-testing/host";
14import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost";
1515import { CursorDriver } from "./driver.ts";
1616import * as CursorAgentSdk from "./CursorAgentSdk.ts";
1717import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
......@@ -19,7 +19,7 @@ import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderS
1919import * as ProviderAdapter from "@t3tools/provider-core/server/ProviderAdapter";
2020import { Cursor } from "./sdk.ts";
2121
22const layerTest = layerTestProviderHost({ runBackgroundWork: false }).pipe(
22const layerTest = TestProviderHost.layer({ runBackgroundWork: false }).pipe(
2323 Layer.provideMerge(NodeServices.layer),
2424 Layer.provideMerge(IdAllocator.layer),
2525 Layer.provideMerge(McpProviderSessions.layer),
packages/provider-grok/src/server/driver.test.ts+2-2
......@@ -11,13 +11,13 @@ import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
1111
1212import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions";
1313import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers";
14import { layerTestProviderHost } from "@t3tools/provider-testing/host";
14import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost";
1515import { GrokDriver } from "./driver.ts";
1616
1717import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
1818import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
1919
20const layerTest = layerTestProviderHost({ runBackgroundWork: false }).pipe(
20const layerTest = TestProviderHost.layer({ runBackgroundWork: false }).pipe(
2121 Layer.provideMerge(NodeServices.layer),
2222 Layer.provideMerge(IdAllocator.layer),
2323 Layer.provideMerge(McpProviderSessions.layer),
packages/provider-muse/src/server/adapter.test.ts+2-2
......@@ -30,7 +30,7 @@ import * as Queue from "effect/Queue";
3030import * as Schema from "effect/Schema";
3131import * as Stream from "effect/Stream";
3232
33import { layerTestProviderHost } from "@t3tools/provider-testing/host";
33import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost";
3434import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
3535import type { MuseItem } from "./protocol.ts";
3636import type { MuseSdkHost } from "./sdk.ts";
......@@ -43,7 +43,7 @@ const testLayer = Layer.mergeAll(
4343 NodeServices.layer,
4444 IdAllocator.layer,
4545 McpProviderSessions.layer,
46 layerTestProviderHost().pipe(Layer.provide(NodeServices.layer)),
46 TestProviderHost.layer().pipe(Layer.provide(NodeServices.layer)),
4747);
4848const MUSE_PROVIDER = ProviderDriverKind.make("muse");
4949const INSTANCE_ID = ProviderInstanceId.make("muse_work");
packages/provider-muse/src/server/driver.ts+2-2
......@@ -5,8 +5,8 @@ import * as Effect from "effect/Effect";
55import * as FileSystem from "effect/FileSystem";
66import * as Path from "effect/Path";
77import * as Schema from "effect/Schema";
8import { HttpClient } from "effect/http";
9import { ChildProcessSpawner } from "effect/process";
8import * as HttpClient from "effect/http/HttpClient";
9import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
1010
1111import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions";
1212import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";
packages/provider-muse/src/server/maintenance.test.ts+2-1
......@@ -3,7 +3,8 @@ import { expect, it } from "@effect/vitest";
33import { ProviderDriverKind, ProviderInstanceId, type ServerProvider } from "@t3tools/contracts";
44import * as Effect from "effect/Effect";
55import * as FileSystem from "effect/FileSystem";
6import { HttpClient, HttpClientResponse } from "effect/http";
6import * as HttpClient from "effect/http/HttpClient";
7import * as HttpClientResponse from "effect/http/HttpClientResponse";
78
89import {
910 compareMuseVersions,
packages/provider-muse/src/server/maintenance.ts+2-1
......@@ -3,7 +3,8 @@ import { compareSemverVersions } from "@t3tools/shared/semver";
33import * as Effect from "effect/Effect";
44import * as FileSystem from "effect/FileSystem";
55import * as Schema from "effect/Schema";
6import { HttpClient, HttpClientRequest } from "effect/http";
6import * as HttpClient from "effect/http/HttpClient";
7import * as HttpClientRequest from "effect/http/HttpClientRequest";
78
89import {
910 enrichProviderSnapshotWithVersionAdvisory,
packages/provider-muse/src/server/status.ts+1-1
......@@ -5,7 +5,7 @@ import * as DateTime from "effect/DateTime";
55import * as Effect from "effect/Effect";
66import * as Option from "effect/Option";
77import * as Result from "effect/Result";
8import { ChildProcess } from "effect/process";
8import * as ChildProcess from "effect/process/ChildProcess";
99
1010import { createMuseSdkHost, makeMuseEnvironment } from "./sdk.ts";
1111import { parseMuseVersion } from "./maintenance.ts";
packages/provider-opencode/src/server/OpenCodeRuntime.environment.test.ts+2-1
......@@ -12,7 +12,8 @@ import * as FileSystem from "effect/FileSystem";
1212import * as Layer from "effect/Layer";
1313import * as Path from "effect/Path";
1414import * as TestClock from "effect/testing/TestClock";
15import { FetchHttpClient, HttpClient } from "effect/http";
15import * as FetchHttpClient from "effect/http/FetchHttpClient";
16import * as HttpClient from "effect/http/HttpClient";
1617import { describe, expect, it } from "vite-plus/test";
1718
1819import * as OpenCodeRuntime from "./OpenCodeRuntime.ts";
packages/provider-opencode/src/server/OpenCodeRuntime.ts+80-76
......@@ -28,7 +28,8 @@ import * as Result from "effect/Result";
2828import * as Scope from "effect/Scope";
2929import * as Schema from "effect/Schema";
3030import * as Stream from "effect/Stream";
31import { ChildProcess, ChildProcessSpawner } from "effect/process";
31import * as ChildProcess from "effect/process/ChildProcess";
32import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
3233
3334import { signalProcessGroup } from "@t3tools/provider-core/server/processGroup";
3435import { isWindowsCommandNotFound } from "@t3tools/provider-core/server/snapshotProbe";
......@@ -227,68 +228,71 @@ const decodeOpenCodeSkillsCliOutputExit = Schema.decodeUnknownExit(
227228 Schema.fromJsonString(Schema.Array(OpenCodeSkillSchema)),
228229);
229230
230export interface OpenCodeRuntimeShape {
231 /**
232 * Spawns a local OpenCode server process. Its lifetime is bound to the caller's
233 * `Scope.Scope` — the child is killed automatically when that scope closes.
234 * Consumers that want a long-lived server must create and hold a scope explicitly
235 * (see {@link Scope.make}) and close it when done.
236 */
237 readonly startOpenCodeServerProcess: (input: {
238 readonly binaryPath: string;
239 readonly directory: string;
240 readonly serverPassword?: string;
241 readonly environment?: NodeJS.ProcessEnv;
242 readonly port?: number;
243 readonly hostname?: string;
244 readonly timeoutMs?: number;
245 /** Checks the listening server and returns its version. Defaults to the 1.x health check. */
246 readonly verify?: (url: string) => Effect.Effect<string, OpenCodeRuntimeError>;
247 }) => Effect.Effect<OpenCodeServerProcess, OpenCodeRuntimeError, Scope.Scope>;
248 /**
249 * Returns a handle to either an externally-managed OpenCode server (when
250 * `serverUrl` is provided — no lifetime is attached to the caller's scope) or a
251 * freshly spawned local server whose lifetime is bound to the caller's scope.
252 */
253 readonly connectToOpenCodeServer: (input: {
254 readonly binaryPath: string;
255 readonly directory: string;
256 readonly serverUrl?: string | null;
257 readonly serverPassword?: string;
258 readonly environment?: NodeJS.ProcessEnv;
259 readonly port?: number;
260 readonly hostname?: string;
261 readonly timeoutMs?: number;
262 }) => Effect.Effect<OpenCodeServerConnection, OpenCodeRuntimeError, Scope.Scope>;
263 readonly runOpenCodeCommand: (input: {
264 readonly binaryPath: string;
265 readonly args: ReadonlyArray<string>;
266 readonly environment?: NodeJS.ProcessEnv;
267 readonly cwd?: string;
268 readonly maxOutputBytes?: number;
269 }) => Effect.Effect<OpenCodeCommandResult, OpenCodeRuntimeError>;
270 readonly createOpenCodeSdkClient: (input: {
271 readonly baseUrl: string;
272 readonly directory: string;
273 readonly serverPassword?: string;
274 }) => OpencodeClient;
275 readonly loadOpenCodeInventory: (
276 client: OpencodeClient,
277 ) => Effect.Effect<OpenCodeInventory, OpenCodeRuntimeError>;
278 readonly loadOpenCodeSkills: (
279 client: OpencodeClient,
280 ) => Effect.Effect<ReadonlyArray<OpenCodeSkill>, OpenCodeRuntimeError>;
281 readonly loadInventoryFromCli: (input: {
282 readonly binaryPath: string;
283 readonly cwd: string;
284 readonly environment?: NodeJS.ProcessEnv;
285 }) => Effect.Effect<OpenCodeInventory, OpenCodeRuntimeError>;
286 readonly loadSkillsFromCli: (input: {
287 readonly binaryPath: string;
288 readonly cwd: string;
289 readonly environment?: NodeJS.ProcessEnv;
290 }) => Effect.Effect<ReadonlyArray<OpenCodeSkill>, OpenCodeRuntimeError>;
291}
231export class OpenCodeRuntime extends Context.Service<
232 OpenCodeRuntime,
233 {
234 /**
235 * Spawns a local OpenCode server process. Its lifetime is bound to the caller's
236 * `Scope.Scope` — the child is killed automatically when that scope closes.
237 * Consumers that want a long-lived server must create and hold a scope explicitly
238 * (see {@link Scope.make}) and close it when done.
239 */
240 readonly startOpenCodeServerProcess: (input: {
241 readonly binaryPath: string;
242 readonly directory: string;
243 readonly serverPassword?: string;
244 readonly environment?: NodeJS.ProcessEnv;
245 readonly port?: number;
246 readonly hostname?: string;
247 readonly timeoutMs?: number;
248 /** Checks the listening server and returns its version. Defaults to the 1.x health check. */
249 readonly verify?: (url: string) => Effect.Effect<string, OpenCodeRuntimeError>;
250 }) => Effect.Effect<OpenCodeServerProcess, OpenCodeRuntimeError, Scope.Scope>;
251 /**
252 * Returns a handle to either an externally-managed OpenCode server (when
253 * `serverUrl` is provided — no lifetime is attached to the caller's scope) or a
254 * freshly spawned local server whose lifetime is bound to the caller's scope.
255 */
256 readonly connectToOpenCodeServer: (input: {
257 readonly binaryPath: string;
258 readonly directory: string;
259 readonly serverUrl?: string | null;
260 readonly serverPassword?: string;
261 readonly environment?: NodeJS.ProcessEnv;
262 readonly port?: number;
263 readonly hostname?: string;
264 readonly timeoutMs?: number;
265 }) => Effect.Effect<OpenCodeServerConnection, OpenCodeRuntimeError, Scope.Scope>;
266 readonly runOpenCodeCommand: (input: {
267 readonly binaryPath: string;
268 readonly args: ReadonlyArray<string>;
269 readonly environment?: NodeJS.ProcessEnv;
270 readonly cwd?: string;
271 readonly maxOutputBytes?: number;
272 }) => Effect.Effect<OpenCodeCommandResult, OpenCodeRuntimeError>;
273 readonly createOpenCodeSdkClient: (input: {
274 readonly baseUrl: string;
275 readonly directory: string;
276 readonly serverPassword?: string;
277 }) => OpencodeClient;
278 readonly loadOpenCodeInventory: (
279 client: OpencodeClient,
280 ) => Effect.Effect<OpenCodeInventory, OpenCodeRuntimeError>;
281 readonly loadOpenCodeSkills: (
282 client: OpencodeClient,
283 ) => Effect.Effect<ReadonlyArray<OpenCodeSkill>, OpenCodeRuntimeError>;
284 readonly loadInventoryFromCli: (input: {
285 readonly binaryPath: string;
286 readonly cwd: string;
287 readonly environment?: NodeJS.ProcessEnv;
288 }) => Effect.Effect<OpenCodeInventory, OpenCodeRuntimeError>;
289 readonly loadSkillsFromCli: (input: {
290 readonly binaryPath: string;
291 readonly cwd: string;
292 readonly environment?: NodeJS.ProcessEnv;
293 }) => Effect.Effect<ReadonlyArray<OpenCodeSkill>, OpenCodeRuntimeError>;
294 }
295>()("@t3tools/provider-opencode/server/OpenCodeRuntime") {}
292296
293297function parseServerUrlFromOutput(output: string): string | null {
294298 for (const line of output.split("\n")) {
......@@ -596,7 +600,7 @@ const makeOpenCodeRuntime = Effect.gen(function* () {
596600 const resolveCommand = (command: string, args: ReadonlyArray<string>, env?: NodeJS.ProcessEnv) =>
597601 resolveSpawnCommand(command, args, env ? { env } : {});
598602
599 const runOpenCodeCommand: OpenCodeRuntimeShape["runOpenCodeCommand"] = (input) =>
603 const runOpenCodeCommand: OpenCodeRuntime["Service"]["runOpenCodeCommand"] = (input) =>
600604 Effect.gen(function* () {
601605 const spawnCommand = yield* resolveCommand(input.binaryPath, input.args, input.environment);
602606 const child = yield* spawner.spawn(
......@@ -651,7 +655,7 @@ const makeOpenCodeRuntime = Effect.gen(function* () {
651655 ),
652656 );
653657
654 const createOpenCodeSdkClient: OpenCodeRuntimeShape["createOpenCodeSdkClient"] = (input) =>
658 const createOpenCodeSdkClient: OpenCodeRuntime["Service"]["createOpenCodeSdkClient"] = (input) =>
655659 createOpencodeClient({
656660 baseUrl: input.baseUrl,
657661 directory: input.directory,
......@@ -665,7 +669,9 @@ const makeOpenCodeRuntime = Effect.gen(function* () {
665669 throwOnError: true,
666670 });
667671
668 const startOpenCodeServerProcess: OpenCodeRuntimeShape["startOpenCodeServerProcess"] = (input) =>
672 const startOpenCodeServerProcess: OpenCodeRuntime["Service"]["startOpenCodeServerProcess"] = (
673 input,
674 ) =>
669675 Effect.gen(function* () {
670676 // Bind this server's lifetime to the caller's scope. When the caller's
671677 // scope closes, the spawned child is killed and all associated fibers
......@@ -872,7 +878,9 @@ const makeOpenCodeRuntime = Effect.gen(function* () {
872878 } satisfies OpenCodeServerProcess;
873879 });
874880
875 const connectToOpenCodeServer: OpenCodeRuntimeShape["connectToOpenCodeServer"] = (input) => {
881 const connectToOpenCodeServer: OpenCodeRuntime["Service"]["connectToOpenCodeServer"] = (
882 input,
883 ) => {
876884 const serverUrl = input.serverUrl?.trim();
877885 if (serverUrl) {
878886 const serverPassword = resolveOpenCodeServerPassword({
......@@ -937,7 +945,7 @@ const makeOpenCodeRuntime = Effect.gen(function* () {
937945 Effect.orElseSucceed((): ReadonlyArray<Agent> => []),
938946 );
939947
940 const loadOpenCodeSkills: OpenCodeRuntimeShape["loadOpenCodeSkills"] = (client) =>
948 const loadOpenCodeSkills: OpenCodeRuntime["Service"]["loadOpenCodeSkills"] = (client) =>
941949 runOpenCodeSdk("app.skills", (signal) => client.app.skills(undefined, { signal })).pipe(
942950 Effect.map((result) =>
943951 (result.data ?? []).map((skill) => ({
......@@ -950,7 +958,7 @@ const makeOpenCodeRuntime = Effect.gen(function* () {
950958 const loadSkills = (client: OpencodeClient) =>
951959 loadOpenCodeSkills(client).pipe(Effect.orElseSucceed((): ReadonlyArray<OpenCodeSkill> => []));
952960
953 const loadOpenCodeInventory: OpenCodeRuntimeShape["loadOpenCodeInventory"] = (client) =>
961 const loadOpenCodeInventory: OpenCodeRuntime["Service"]["loadOpenCodeInventory"] = (client) =>
954962 Effect.all(
955963 [
956964 loadProviders(client),
......@@ -970,7 +978,7 @@ const makeOpenCodeRuntime = Effect.gen(function* () {
970978 })),
971979 );
972980
973 const loadInventoryFromCli: OpenCodeRuntimeShape["loadInventoryFromCli"] = (input) =>
981 const loadInventoryFromCli: OpenCodeRuntime["Service"]["loadInventoryFromCli"] = (input) =>
974982 Effect.gen(function* () {
975983 const env = input.environment !== undefined ? { environment: input.environment } : ({} as {});
976984 const commandContext = { cwd: input.cwd, ...env };
......@@ -1070,7 +1078,7 @@ const makeOpenCodeRuntime = Effect.gen(function* () {
10701078 };
10711079 });
10721080
1073 const loadSkillsFromCli: OpenCodeRuntimeShape["loadSkillsFromCli"] = (input) =>
1081 const loadSkillsFromCli: OpenCodeRuntime["Service"]["loadSkillsFromCli"] = (input) =>
10741082 runOpenCodeCommand({
10751083 binaryPath: input.binaryPath,
10761084 args: ["debug", "skill"],
......@@ -1099,13 +1107,9 @@ const makeOpenCodeRuntime = Effect.gen(function* () {
10991107 loadOpenCodeSkills,
11001108 loadInventoryFromCli,
11011109 loadSkillsFromCli,
1102 } satisfies OpenCodeRuntimeShape;
1110 } satisfies OpenCodeRuntime["Service"];
11031111});
11041112
1105export class OpenCodeRuntime extends Context.Service<OpenCodeRuntime, OpenCodeRuntimeShape>()(
1106 "@t3tools/provider-opencode/server/OpenCodeRuntime",
1107) {}
1108
11091113export const layer = Layer.effect(OpenCodeRuntime, makeOpenCodeRuntime).pipe(
11101114 Layer.provide(NetService.layer),
11111115);
packages/provider-opencode/src/server/OpenCodeServerLedger.test.ts+1-1
......@@ -12,7 +12,7 @@ import * as FileSystem from "effect/FileSystem";
1212import * as Layer from "effect/Layer";
1313import * as Path from "effect/Path";
1414import * as Scope from "effect/Scope";
15import { ChildProcessSpawner } from "effect/process";
15import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
1616
1717import * as OpenCodeRuntime from "./OpenCodeRuntime.ts";
1818import * as OpenCodeServerLedger from "./OpenCodeServerLedger.ts";
packages/provider-opencode/src/server/OpenCodeServerLedger.ts+2-1
......@@ -6,7 +6,8 @@ import * as Layer from "effect/Layer";
66import * as Option from "effect/Option";
77import * as Path from "effect/Path";
88import * as Schema from "effect/Schema";
9import { ChildProcess, ChildProcessSpawner } from "effect/process";
9import * as ChildProcess from "effect/process/ChildProcess";
10import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
1011
1112import { writeFileStringAtomically } from "@t3tools/shared/atomicWrite";
1213import { signalProcessGroup } from "@t3tools/provider-core/server/processGroup";
packages/provider-opencode/src/server/OpenCodeServerOwner.test.ts+4-4
......@@ -24,7 +24,7 @@ const makeRuntime = Effect.gen(function* () {
2424 const failNextStart = yield* Ref.make(false);
2525 const started = yield* Deferred.make<void>();
2626 const closed = yield* Deferred.make<void>();
27 const runtime: OpenCodeRuntime.OpenCodeRuntimeShape = {
27 const runtime: OpenCodeRuntime.OpenCodeRuntime["Service"] = {
2828 startOpenCodeServerProcess: () =>
2929 Effect.gen(function* () {
3030 if (yield* Ref.getAndSet(failNextStart, false)) {
......@@ -115,7 +115,7 @@ it.effect("invalidates an exited process so the next borrower starts a new one",
115115 const starts = yield* Ref.make(0);
116116 const processExits: Array<Deferred.Deferred<number>> = [];
117117 const processClosed = yield* Deferred.make<void>();
118 const runtime: OpenCodeRuntime.OpenCodeRuntimeShape = {
118 const runtime: OpenCodeRuntime.OpenCodeRuntime["Service"] = {
119119 startOpenCodeServerProcess: () =>
120120 Effect.gen(function* () {
121121 const index = yield* Ref.updateAndGet(starts, (count) => count + 1);
......@@ -165,7 +165,7 @@ it.effect("replaces a dead cached process before its exit watcher runs", () =>
165165 const starts = yield* Ref.make(0);
166166 const closes = yield* Ref.make(0);
167167 const processRunning: Array<Ref.Ref<boolean>> = [];
168 const runtime: OpenCodeRuntime.OpenCodeRuntimeShape = {
168 const runtime: OpenCodeRuntime.OpenCodeRuntime["Service"] = {
169169 startOpenCodeServerProcess: () =>
170170 Effect.gen(function* () {
171171 const index = yield* Ref.updateAndGet(starts, (count) => count + 1);
......@@ -214,7 +214,7 @@ it.effect("cleans up an interrupted startup and allows a retry", () =>
214214 const starts = yield* Ref.make(0);
215215 const firstStartEntered = yield* Deferred.make<void>();
216216 const firstStartClosed = yield* Deferred.make<void>();
217 const runtime: OpenCodeRuntime.OpenCodeRuntimeShape = {
217 const runtime: OpenCodeRuntime.OpenCodeRuntime["Service"] = {
218218 startOpenCodeServerProcess: () =>
219219 Effect.gen(function* () {
220220 const index = yield* Ref.updateAndGet(starts, (count) => count + 1);
packages/provider-opencode/src/server/adapter.test.ts+6-6
......@@ -32,7 +32,7 @@ import * as Stream from "effect/Stream";
3232import * as TestClock from "effect/testing/TestClock";
3333
3434import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";
35import { layerTestProviderHost } from "@t3tools/provider-testing/host";
35import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost";
3636import type * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers";
3737import * as OpenCodeRuntime from "./OpenCodeRuntime.ts";
3838import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
......@@ -165,7 +165,7 @@ const makeOpenCodeRuntimeHarness = Effect.fn("makeOpenCodeRuntimeHarness")(funct
165165 Effect.provideService(OpenCodeRuntime.OpenCodeRuntime, {
166166 connectToOpenCodeServer: () => Effect.succeed({ url: "http://test.invalid", external: true }),
167167 createOpenCodeSdkClient: () => client,
168 } as unknown as OpenCodeRuntime.OpenCodeRuntimeShape),
168 } as unknown as OpenCodeRuntime.OpenCodeRuntime["Service"]),
169169 Effect.provideService(ProviderHost.ProviderHost, {
170170 paths: { cwd: "/workspace" },
171171 resolveAttachmentPath: () => null,
......@@ -1538,7 +1538,7 @@ describe("OpenCodeAdapterV2", () => {
15381538 connectToOpenCodeServer: () =>
15391539 Effect.succeed({ url: "http://test.invalid", external: true }),
15401540 createOpenCodeSdkClient: () => client,
1541 } as unknown as OpenCodeRuntime.OpenCodeRuntimeShape),
1541 } as unknown as OpenCodeRuntime.OpenCodeRuntime["Service"]),
15421542 Effect.provideService(ProviderHost.ProviderHost, {
15431543 paths: { cwd: "/workspace" },
15441544 resolveAttachmentPath: () => null,
......@@ -1835,7 +1835,7 @@ describe("OpenCodeAdapterV2", () => {
18351835 connectToOpenCodeServer: () =>
18361836 Effect.succeed({ url: "http://test.invalid", external: true }),
18371837 createOpenCodeSdkClient: () => client,
1838 } as unknown as OpenCodeRuntime.OpenCodeRuntimeShape),
1838 } as unknown as OpenCodeRuntime.OpenCodeRuntime["Service"]),
18391839 Effect.provideService(ProviderHost.ProviderHost, {
18401840 paths: { cwd: "/workspace" },
18411841 resolveAttachmentPath: () => null,
......@@ -2507,7 +2507,7 @@ describe("OpenCodeAdapterV2", () => {
25072507 },
25082508 } as unknown as OpencodeClient;
25092509 const unused = (operation: string) => () => Effect.die(`${operation} is not used`);
2510 const runtime: OpenCodeRuntime.OpenCodeRuntimeShape = {
2510 const runtime: OpenCodeRuntime.OpenCodeRuntime["Service"] = {
25112511 startOpenCodeServerProcess: unused("startOpenCodeServerProcess"),
25122512 connectToOpenCodeServer: () =>
25132513 Effect.succeed({
......@@ -2585,7 +2585,7 @@ describe("OpenCodeAdapterV2", () => {
25852585 Layer.mergeAll(
25862586 IdAllocator.layer,
25872587 McpProviderSessions.layer,
2588 layerTestProviderHost().pipe(Layer.provide(NodeServices.layer)),
2588 TestProviderHost.layer().pipe(Layer.provide(NodeServices.layer)),
25892589 ),
25902590 ),
25912591 ),
packages/provider-opencode/src/server/adapter.ts+12-15
......@@ -48,6 +48,7 @@ import * as Layer from "effect/Layer";
4848import * as Option from "effect/Option";
4949import * as Queue from "effect/Queue";
5050import * as Random from "effect/Random";
51import * as Ref from "effect/Ref";
5152import * as Schema from "effect/Schema";
5253import * as Semaphore from "effect/Semaphore";
5354import * as Scope from "effect/Scope";
......@@ -88,20 +89,15 @@ export const OPENCODE_DEFAULT_INSTANCE_ID = defaultInstanceIdForDriver(OPENCODE_
8889export const OPENCODE_SDK_PROTOCOL = "opencode-sdk.sse" as const;
8990const DEFAULT_OPENCODE_SETTINGS = Schema.decodeSync(OpenCodeSettings)({});
9091
91let openCodeMessageIdEpochMillis = -1;
92let openCodeMessageIdCounter = 0;
93
94const makeOpenCodeMessageId = Effect.fnUntraced(function* () {
92const makeOpenCodeMessageId = Effect.fnUntraced(function* (
93 clock: Ref.Ref<{ readonly epochMillis: number; readonly counter: number }>,
94) {
9595 const epochMillis = DateTime.toEpochMillis(yield* DateTime.now);
96 if (epochMillis !== openCodeMessageIdEpochMillis) {
97 openCodeMessageIdEpochMillis = epochMillis;
98 openCodeMessageIdCounter = 0;
99 }
100 openCodeMessageIdCounter += 1;
101 const encodedTime = BigInt.asUintN(
102 48,
103 BigInt(epochMillis) * 0x1000n + BigInt(openCodeMessageIdCounter),
104 )
96 const counter = yield* Ref.modify(clock, (previous) => {
97 const next = previous.epochMillis === epochMillis ? previous.counter + 1 : 1;
98 return [next, { epochMillis, counter: next }] as const;
99 });
100 const encodedTime = BigInt.asUintN(48, BigInt(epochMillis) * 0x1000n + BigInt(counter))
105101 .toString(16)
106102 .padStart(12, "0");
107103 const alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
......@@ -948,6 +944,7 @@ export const makeOpenCodeAdapterV2 = Effect.fn("makeOpenCodeAdapterV2")(function
948944 const idAllocator = yield* IdAllocator.IdAllocatorV2;
949945 const host = yield* ProviderHost.ProviderHost;
950946 const mcpSessions = yield* McpProviderSessions.McpProviderSessions;
947 const messageIdClock = yield* Ref.make({ epochMillis: -1, counter: 0 });
951948
952949 return ProviderAdapter.ProviderAdapterV2.of({
953950 instanceId: options.instanceId,
......@@ -3170,7 +3167,7 @@ export const makeOpenCodeAdapterV2 = Effect.fn("makeOpenCodeAdapterV2")(function
31703167 startedAt,
31713168 completedAt: null,
31723169 };
3173 const admissionMessageId = yield* makeOpenCodeMessageId();
3170 const admissionMessageId = yield* makeOpenCodeMessageId(messageIdClock);
31743171 // No Effect may be yielded between this check and installing the
31753172 // turn. If the event stream ended while IDs were being prepared,
31763173 // registering afterward would leave a running turn that the EOF
......@@ -3369,7 +3366,7 @@ export const makeOpenCodeAdapterV2 = Effect.fn("makeOpenCodeAdapterV2")(function
33693366 ...files,
33703367 ];
33713368 turn.admissionGeneration = state.nextAdmissionGeneration++;
3372 turn.admissionMessageId = yield* makeOpenCodeMessageId();
3369 turn.admissionMessageId = yield* makeOpenCodeMessageId(messageIdClock);
33733370 turn.admissionPending = true;
33743371 turn.admissionAccepted = false;
33753372 turn.admissionMessageObserved = false;
packages/provider-opencode/src/server/driver.test.ts+7-7
......@@ -12,8 +12,8 @@ import * as Effect from "effect/Effect";
1212import * as Fiber from "effect/Fiber";
1313import * as Layer from "effect/Layer";
1414import * as TestClock from "effect/testing/TestClock";
15import { HttpClient } from "effect/http";
16import { ChildProcessSpawner } from "effect/process";
15import * as HttpClient from "effect/http/HttpClient";
16import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
1717
1818import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions";
1919import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
......@@ -27,7 +27,7 @@ import {
2727 replayOpenCodeServer,
2828} from "./probeResponses.fixture.ts";
2929import { OpenCodeDriver, openCodeUpdateFor } from "./driver.ts";
30import { layerTestProviderHost } from "@t3tools/provider-testing/host";
30import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost";
3131
3232const serverStarts: Array<string> = [];
3333const reachedServer = (operation: string) =>
......@@ -46,13 +46,13 @@ const openCode2Runtime = {
4646 runOpenCodeCommand: () => Effect.succeed({ stdout: "opencode v2.0.18\n", stderr: "", code: 0 }),
4747 startOpenCodeServerProcess: () => reachedServer("start"),
4848 connectToOpenCodeServer: () => reachedServer("connect"),
49} as unknown as OpenCodeRuntime.OpenCodeRuntimeShape;
49} as unknown as OpenCodeRuntime.OpenCodeRuntime["Service"];
5050
5151const layer = Layer.mergeAll(
5252 IdAllocator.layer,
5353 McpProviderSessions.layer,
5454 ProviderLatestVersions.layer,
55 layerTestProviderHost(),
55 TestProviderHost.layer(),
5656 Layer.succeed(
5757 ProviderEventLoggers.ProviderEventLoggers,
5858 ProviderEventLoggers.NoOpProviderEventLoggers,
......@@ -211,12 +211,12 @@ const changingRuntime = {
211211 ),
212212 startOpenCodeServerProcess: () => reachedServer("start"),
213213 connectToOpenCodeServer: () => reachedServer("connect"),
214} as unknown as OpenCodeRuntime.OpenCodeRuntimeShape;
214} as unknown as OpenCodeRuntime.OpenCodeRuntime["Service"];
215215const layerUpdate = Layer.mergeAll(
216216 IdAllocator.layer,
217217 McpProviderSessions.layer,
218218 ProviderLatestVersions.layer,
219 layerTestProviderHost(),
219 TestProviderHost.layer(),
220220 Layer.succeed(
221221 ProviderEventLoggers.ProviderEventLoggers,
222222 ProviderEventLoggers.NoOpProviderEventLoggers,
packages/provider-opencode/src/server/driver.ts+2-2
......@@ -22,8 +22,8 @@ import * as FileSystem from "effect/FileSystem";
2222import * as Path from "effect/Path";
2323import * as Schema from "effect/Schema";
2424import * as Stream from "effect/Stream";
25import { HttpClient } from "effect/http";
26import { ChildProcessSpawner } from "effect/process";
25import * as HttpClient from "effect/http/HttpClient";
26import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
2727
2828import * as OpenCode2TextGeneration from "./v2/textGeneration.ts";
2929import { makeOpenCodeTextGeneration } from "./textGeneration.ts";
packages/provider-opencode/src/server/probeResponses.fixture.ts+2-1
......@@ -3,7 +3,8 @@
33 * `HttpClient` so the version probe runs against real bytes.
44 */
55import * as Effect from "effect/Effect";
6import { HttpClient, HttpClientResponse } from "effect/http";
6import * as HttpClient from "effect/http/HttpClient";
7import * as HttpClientResponse from "effect/http/HttpClientResponse";
78
89interface RecordedResponse {
910 readonly status: number;
packages/provider-opencode/src/server/status.test.ts+5-4
......@@ -9,7 +9,8 @@ import * as FileSystem from "effect/FileSystem";
99import * as Layer from "effect/Layer";
1010import * as Schema from "effect/Schema";
1111import * as TestClock from "effect/testing/TestClock";
12import { HttpClient, HttpClientResponse } from "effect/http";
12import * as HttpClient from "effect/http/HttpClient";
13import * as HttpClientResponse from "effect/http/HttpClientResponse";
1314import { beforeEach } from "vite-plus/test";
1415
1516import { OpenCodeSettings } from "../settings.ts";
......@@ -161,7 +162,7 @@ it.effect("keeps Go entitlement absence distinct from failed or malformed usage
161162 * are deleted. The snapshot-producing logic they wrapped now lives in the
162163 * standalone `checkOpenCodeProviderStatus(settings, cwd)` Effect, which
163164 * drivers call directly when building their per-instance snapshot
164 * `ServerProviderShape`. Tests mirror that shape: build a settings payload,
165 * `ManagedServerProvider`. Tests mirror that shape: build a settings payload,
165166 * invoke the check, assert on the returned snapshot.
166167 */
167168
......@@ -202,7 +203,7 @@ const runtimeMock = {
202203 },
203204};
204205
205const OpenCodeRuntimeTestDouble: OpenCodeRuntime.OpenCodeRuntimeShape = {
206const OpenCodeRuntimeTestDouble: OpenCodeRuntime.OpenCodeRuntime["Service"] = {
206207 startOpenCodeServerProcess: ({ serverPassword, environment }) =>
207208 Effect.gen(function* () {
208209 yield* Effect.addFinalizer(() =>
......@@ -264,7 +265,7 @@ const OpenCodeRuntimeTestDouble: OpenCodeRuntime.OpenCodeRuntimeShape = {
264265 createOpenCodeSdkClient: (input) => {
265266 runtimeMock.state.sdkClientInputs.push(input);
266267 return {} as unknown as ReturnType<
267 OpenCodeRuntime.OpenCodeRuntimeShape["createOpenCodeSdkClient"]
268 OpenCodeRuntime.OpenCodeRuntime["Service"]["createOpenCodeSdkClient"]
268269 >;
269270 },
270271 loadOpenCodeInventory: () =>
packages/provider-opencode/src/server/textGeneration.test.ts+7-5
......@@ -10,7 +10,7 @@ import * as TestClock from "effect/testing/TestClock";
1010import * as NetService from "@t3tools/shared/Net";
1111import { beforeEach, expect } from "vite-plus/test";
1212
13import { layerTestProviderHost } from "@t3tools/provider-testing/host";
13import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost";
1414import * as OpenCodeRuntime from "./OpenCodeRuntime.ts";
1515import * as OpenCodeServerOwner from "./OpenCodeServerOwner.ts";
1616import * as OpenCodeTextGeneration from "./textGeneration.ts";
......@@ -47,7 +47,7 @@ const runtimeMock = {
4747 },
4848};
4949
50const OpenCodeRuntimeTestDouble: OpenCodeRuntime.OpenCodeRuntimeShape = {
50const OpenCodeRuntimeTestDouble: OpenCodeRuntime.OpenCodeRuntime["Service"] = {
5151 startOpenCodeServerProcess: ({ binaryPath, serverPassword, environment }) =>
5252 Effect.gen(function* () {
5353 const index = runtimeMock.state.startCalls.length + 1;
......@@ -128,7 +128,9 @@ const OpenCodeRuntimeTestDouble: OpenCodeRuntime.OpenCodeRuntimeShape = {
128128 );
129129 },
130130 },
131 }) as unknown as ReturnType<OpenCodeRuntime.OpenCodeRuntimeShape["createOpenCodeSdkClient"]>,
131 }) as unknown as ReturnType<
132 OpenCodeRuntime.OpenCodeRuntime["Service"]["createOpenCodeSdkClient"]
133 >,
132134 loadOpenCodeInventory: () =>
133135 Effect.fail(
134136 new OpenCodeRuntime.OpenCodeRuntimeError({
......@@ -167,7 +169,7 @@ const layerOpenCodeTextGenerationTest = Layer.succeed(
167169 OpenCodeRuntime.OpenCodeRuntime,
168170 OpenCodeRuntimeTestDouble,
169171).pipe(
170 Layer.provideMerge(layerTestProviderHost()),
172 Layer.provideMerge(TestProviderHost.layer()),
171173 Layer.provideMerge(NetService.layer),
172174 Layer.provideMerge(NodeServices.layer),
173175);
......@@ -176,7 +178,7 @@ const layerOpenCodeTextGenerationExistingServerTest = Layer.succeed(
176178 OpenCodeRuntime.OpenCodeRuntime,
177179 OpenCodeRuntimeTestDouble,
178180).pipe(
179 Layer.provideMerge(layerTestProviderHost()),
181 Layer.provideMerge(TestProviderHost.layer()),
180182 Layer.provideMerge(NetService.layer),
181183 Layer.provideMerge(NodeServices.layer),
182184);
packages/provider-opencode/src/server/usageLimits.ts+3-1
......@@ -9,7 +9,9 @@ import * as Option from "effect/Option";
99import * as Path from "effect/Path";
1010import * as Schema from "effect/Schema";
1111import * as Hex from "effect/encoding/Hex";
12import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/http";
12import * as HttpClient from "effect/http/HttpClient";
13import * as HttpClientRequest from "effect/http/HttpClientRequest";
14import * as HttpClientResponse from "effect/http/HttpClientResponse";
1315
1416import {
1517 clampPercent,
packages/provider-opencode/src/server/v2/OpenCode2Client.live.test.ts+3-2
......@@ -20,8 +20,9 @@ import * as Layer from "effect/Layer";
2020import * as Option from "effect/Option";
2121import * as Path from "effect/Path";
2222import * as Stream from "effect/Stream";
23import { FetchHttpClient } from "effect/http";
24import { ChildProcess, ChildProcessSpawner } from "effect/process";
23import * as FetchHttpClient from "effect/http/FetchHttpClient";
24import * as ChildProcess from "effect/process/ChildProcess";
25import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
2526import { describe } from "vite-plus/test";
2627
2728import * as OpenCode2Client from "./OpenCode2Client.ts";
packages/provider-opencode/src/server/v2/OpenCode2Client.test.ts+2-1
......@@ -2,7 +2,8 @@ import { assert, it } from "@effect/vitest";
22import * as Effect from "effect/Effect";
33import * as Layer from "effect/Layer";
44import * as Redacted from "effect/Redacted";
5import { HttpClient, HttpClientResponse } from "effect/http";
5import * as HttpClient from "effect/http/HttpClient";
6import * as HttpClientResponse from "effect/http/HttpClientResponse";
67import { describe } from "vite-plus/test";
78
89import * as OpenCode2Client from "./OpenCode2Client.ts";
packages/provider-opencode/src/server/v2/OpenCode2Client.ts+25-21
......@@ -139,26 +139,27 @@ const undecodable = (data: string) =>
139139 });
140140
141141/** Subscribes to `/api/event`, then streams every frame this build can route. */
142const readEvents = (httpClient: HttpClient.HttpClient) =>
143 httpClient.get("/api/event", { headers: { accept: "text/event-stream" } }).pipe(
144 Effect.flatMap(HttpClientResponse.filterStatusOk),
145 Effect.map((response) =>
146 response.stream.pipe(
147 Stream.timeoutOrElse({
148 duration: SILENT_STREAM_TIMEOUT,
149 orElse: () => Stream.fail(new OpenCode2SilentStreamError()),
150 }),
151 Stream.decodeText,
152 Stream.pipeThroughChannel(Sse.decode()),
153 Stream.filterMapEffect((frame) => {
154 const decoded = decodeEvent(frame.data);
155 return Result.isSuccess(decoded)
156 ? Effect.succeed(Result.succeed<OpenCode2StreamEvent>(decoded.success))
157 : undecodable(frame.data);
158 }),
159 ),
142const readEvents = HttpClient.get("/api/event", {
143 headers: { accept: "text/event-stream" },
144}).pipe(
145 Effect.flatMap(HttpClientResponse.filterStatusOk),
146 Effect.map((response) =>
147 response.stream.pipe(
148 Stream.timeoutOrElse({
149 duration: SILENT_STREAM_TIMEOUT,
150 orElse: () => Stream.fail(new OpenCode2SilentStreamError()),
151 }),
152 Stream.decodeText,
153 Stream.pipeThroughChannel(Sse.decode()),
154 Stream.filterMapEffect((frame) => {
155 const decoded = decodeEvent(frame.data);
156 return Result.isSuccess(decoded)
157 ? Effect.succeed(Result.succeed<OpenCode2StreamEvent>(decoded.success))
158 : undecodable(frame.data);
159 }),
160160 ),
161 );
161 ),
162);
162163
163164/**
164165 * OpenCode decodes Basic credentials as UTF-8. `HttpClientRequest.basicAuth`
......@@ -182,8 +183,11 @@ export const make = Effect.gen(function* () {
182183 Effect.provideService(HttpClient.HttpClient, authenticated),
183184 Effect.map((client) => ({
184185 client,
185 events: readEvents(
186 HttpClient.mapRequest(authenticated, HttpClientRequest.prependUrl(baseUrl)),
186 events: readEvents.pipe(
187 Effect.provideService(
188 HttpClient.HttpClient,
189 HttpClient.mapRequest(authenticated, HttpClientRequest.prependUrl(baseUrl)),
190 ),
187191 ),
188192 })),
189193 );
packages/provider-opencode/src/server/v2/OpenCode2Server.live.test.ts+3-2
......@@ -19,8 +19,9 @@ import * as Path from "effect/Path";
1919import * as Redacted from "effect/Redacted";
2020import * as Scope from "effect/Scope";
2121import * as Stream from "effect/Stream";
22import { FetchHttpClient } from "effect/http";
23import { ChildProcess, ChildProcessSpawner } from "effect/process";
22import * as FetchHttpClient from "effect/http/FetchHttpClient";
23import * as ChildProcess from "effect/process/ChildProcess";
24import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
2425import { describe } from "vite-plus/test";
2526
2627import * as OpenCodeRuntime from "../OpenCodeRuntime.ts";
packages/provider-opencode/src/server/v2/OpenCode2Server.test.ts+3-1
......@@ -12,7 +12,9 @@ import * as Layer from "effect/Layer";
1212import * as Path from "effect/Path";
1313import * as Redacted from "effect/Redacted";
1414import * as TestClock from "effect/testing/TestClock";
15import { FetchHttpClient, HttpClient, HttpClientResponse } from "effect/http";
15import * as FetchHttpClient from "effect/http/FetchHttpClient";
16import * as HttpClient from "effect/http/HttpClient";
17import * as HttpClientResponse from "effect/http/HttpClientResponse";
1618import { describe } from "vite-plus/test";
1719
1820import * as OpenCodeRuntime from "../OpenCodeRuntime.ts";
packages/provider-opencode/src/server/v2/textGeneration.ts+4-3
......@@ -40,9 +40,9 @@ type Outcome =
4040const runOnServer = (
4141 connection: OpenCode2Server.OpenCode2Connection,
4242 input: TextGenerationOperations.Request<Schema.Top>,
43 resolveAttachmentPath: ProviderHost.ProviderHost["Service"]["resolveAttachmentPath"],
4443) =>
4544 Effect.gen(function* () {
45 const { resolveAttachmentPath } = yield* ProviderHost.ProviderHost;
4646 const { client } = connection;
4747 const parsed = OpenCodeRuntime.parseOpenCodeModelSlug(input.modelSelection.model);
4848 if (parsed === null) {
......@@ -184,11 +184,12 @@ const runOnServer = (
184184/** Text generation for an instance whose server is OpenCode 2. */
185185export const make = Effect.fn("OpenCode2TextGeneration.make")(function* () {
186186 const server = yield* OpenCode2Server.OpenCode2Server;
187 const { resolveAttachmentPath } = yield* ProviderHost.ProviderHost;
187 const host = yield* ProviderHost.ProviderHost;
188188 const run: TextGenerationOperations.Runner = (input) =>
189189 server
190 .withConnection((connection) => runOnServer(connection, input, resolveAttachmentPath))
190 .withConnection((connection) => runOnServer(connection, input))
191191 .pipe(
192 Effect.provideService(ProviderHost.ProviderHost, host),
192193 Effect.mapError((cause) =>
193194 isTextGenerationError(cause)
194195 ? cause
packages/provider-opencode/src/server/versionProbe.test.ts+5-3
......@@ -1,6 +1,8 @@
11import { assert, describe, it } from "@effect/vitest";
22import * as Effect from "effect/Effect";
3import { HttpClient, HttpClientError, HttpClientRequest } from "effect/http";
3import * as HttpClient from "effect/http/HttpClient";
4import * as HttpClientError from "effect/http/HttpClientError";
5import * as HttpClientRequest from "effect/http/HttpClientRequest";
46
57import * as OpenCodeRuntime from "./OpenCodeRuntime.ts";
68import {
......@@ -16,7 +18,7 @@ import {
1618
1719const noBinary = {
1820 runOpenCodeCommand: () => Effect.die("A configured server must not run the local binary"),
19} as unknown as OpenCodeRuntime.OpenCodeRuntimeShape;
21} as unknown as OpenCodeRuntime.OpenCodeRuntime["Service"];
2022
2123const probeServer = (serverUrl: string, serverPassword: string, http: HttpClient.HttpClient) =>
2224 probeOpenCodeRuntime({ binaryPath: "opencode", serverUrl, serverPassword }).pipe(
......@@ -172,7 +174,7 @@ describe("OpenCode version probe", () => {
172174 new OpenCodeRuntime.OpenCodeRuntimeError({ operation: "spawn", detail: "ENOENT" }),
173175 );
174176 },
175 } as unknown as OpenCodeRuntime.OpenCodeRuntimeShape;
177 } as unknown as OpenCodeRuntime.OpenCodeRuntime["Service"];
176178 const probe = yield* makeOpenCodeRuntimeProbe(
177179 probeOpenCodeRuntime({ binaryPath: "opencode", serverUrl: "", serverPassword: "" }).pipe(
178180 Effect.provideService(OpenCodeRuntime.OpenCodeRuntime, runtime),
packages/provider-opencode/src/server/versionProbe.ts+2-1
......@@ -6,7 +6,8 @@ import * as Effect from "effect/Effect";
66import * as Exit from "effect/Exit";
77import * as Option from "effect/Option";
88import * as Schema from "effect/Schema";
9import { HttpClient, HttpClientRequest } from "effect/http";
9import * as HttpClient from "effect/http/HttpClient";
10import * as HttpClientRequest from "effect/http/HttpClientRequest";
1011
1112import * as OpenCodeRuntime from "./OpenCodeRuntime.ts";
1213import { parseGenericCliVersion } from "@t3tools/provider-core/server/snapshotProbe";
packages/provider-pi/src/server/adapter.test.ts+4-3
......@@ -29,9 +29,10 @@ import * as Schema from "effect/Schema";
2929import * as Sink from "effect/Sink";
3030import * as Stream from "effect/Stream";
3131import * as TestClock from "effect/testing/TestClock";
32import { ChildProcess, ChildProcessSpawner } from "effect/process";
32import * as ChildProcess from "effect/process/ChildProcess";
33import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
3334
34import { layerTestProviderHost } from "@t3tools/provider-testing/host";
35import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost";
3536import * as ProviderContinuationRequests from "@t3tools/provider-core/server/ProviderContinuationRequests";
3637import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
3738import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
......@@ -50,7 +51,7 @@ const layerTest = Layer.mergeAll(
5051 NodeServices.layer,
5152 IdAllocator.layer,
5253 McpProviderSessions.layer,
53 layerTestProviderHost().pipe(Layer.provide(NodeServices.layer)),
54 TestProviderHost.layer().pipe(Layer.provide(NodeServices.layer)),
5455);
5556
5657const decodeJsonLine = Schema.decodeSync(Schema.fromJsonString(Schema.Unknown));
packages/provider-pi/src/server/adapter.ts+1-1
......@@ -58,7 +58,7 @@ import * as Queue from "effect/Queue";
5858import * as Schema from "effect/Schema";
5959import * as Semaphore from "effect/Semaphore";
6060import * as Stream from "effect/Stream";
61import { ChildProcessSpawner } from "effect/process";
61import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
6262
6363import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";
6464import { mcpToolPresentation } from "@t3tools/provider-core/server/mcpToolPresentation";
packages/provider-pi/src/server/driver.test.ts+5-4
......@@ -10,18 +10,19 @@ import * as Schema from "effect/Schema";
1010import * as Sink from "effect/Sink";
1111import * as Stream from "effect/Stream";
1212import * as TestClock from "effect/testing/TestClock";
13import { HttpClient } from "effect/http";
14import { ChildProcess, ChildProcessSpawner } from "effect/process";
13import * as HttpClient from "effect/http/HttpClient";
14import * as ChildProcess from "effect/process/ChildProcess";
15import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
1516
1617import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions";
1718import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
1819import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
19import { layerTestProviderHost } from "@t3tools/provider-testing/host";
20import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost";
2021import type { PiSettings } from "../settings.ts";
2122import { PiDriver } from "./driver.ts";
2223
2324const layerTest = Layer.mergeAll(
24 layerTestProviderHost({
25 TestProviderHost.layer({
2526 cwd: "/machine",
2627 settings: { ...DEFAULT_SERVER_SETTINGS, enableProviderUpdateChecks: false },
2728 runBackgroundWork: false,
packages/provider-pi/src/server/driver.ts+2-2
......@@ -12,8 +12,8 @@ import * as Effect from "effect/Effect";
1212import * as FileSystem from "effect/FileSystem";
1313import * as Path from "effect/Path";
1414import * as Schema from "effect/Schema";
15import { HttpClient } from "effect/http";
16import { ChildProcessSpawner } from "effect/process";
15import * as HttpClient from "effect/http/HttpClient";
16import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
1717
1818import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions";
1919import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";
packages/provider-pi/src/server/rpc.ts+2-1
......@@ -26,7 +26,8 @@ import * as Queue from "effect/Queue";
2626import * as Scope from "effect/Scope";
2727import * as Schema from "effect/Schema";
2828import * as Stream from "effect/Stream";
29import { ChildProcess, ChildProcessSpawner } from "effect/process";
29import * as ChildProcess from "effect/process/ChildProcess";
30import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
3031import { HostProcessPlatform } from "@t3tools/shared/hostProcess";
3132import { resolveSpawnCommand } from "@t3tools/shared/shell";
3233
packages/provider-pi/src/server/status.test.ts+2-1
......@@ -3,7 +3,8 @@ import { assert, describe, it } from "@effect/vitest";
33import * as Effect from "effect/Effect";
44import * as Sink from "effect/Sink";
55import * as Stream from "effect/Stream";
6import { ChildProcess, ChildProcessSpawner } from "effect/process";
6import * as ChildProcess from "effect/process/ChildProcess";
7import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
78
89import { checkPiProviderStatus, MINIMUM_PI_VERSION } from "./status.ts";
910
packages/provider-pi/src/server/status.ts+3-2
......@@ -23,8 +23,9 @@ import * as Stream from "effect/Stream";
2323import * as Exit from "effect/Exit";
2424import * as Option from "effect/Option";
2525import * as Result from "effect/Result";
26import { HttpClient } from "effect/http";
27import { ChildProcess, ChildProcessSpawner } from "effect/process";
26import * as HttpClient from "effect/http/HttpClient";
27import * as ChildProcess from "effect/process/ChildProcess";
28import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
2829
2930import { buildPiRpcLaunch, resolvePiLaunchArgs } from "./mcpInjection.ts";
3031import {
packages/provider-pi/src/server/textGeneration.test.ts+1-1
......@@ -7,7 +7,7 @@ import * as Queue from "effect/Queue";
77import * as Schema from "effect/Schema";
88import * as Sink from "effect/Sink";
99import * as Stream from "effect/Stream";
10import { ChildProcessSpawner } from "effect/process";
10import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
1111
1212import { makePiTextGeneration } from "./textGeneration.ts";
1313
packages/provider-pi/src/server/textGeneration.ts+1-1
......@@ -8,7 +8,7 @@ import * as Effect from "effect/Effect";
88import * as Option from "effect/Option";
99import * as Queue from "effect/Queue";
1010import * as Schema from "effect/Schema";
11import { ChildProcessSpawner } from "effect/process";
11import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
1212
1313import { TextGenerationError } from "@t3tools/contracts";
1414import type { PiSettings } from "../settings.ts";
packages/provider-testing/package.json+10-10
......@@ -3,25 +3,25 @@
33 "private": true,
44 "type": "module",
55 "exports": {
6 "./replayTranscript": {
7 "types": "./src/replayTranscript.ts",
8 "import": "./src/replayTranscript.ts"
6 "./fakeCli": {
7 "types": "./src/fakeCli.ts",
8 "import": "./src/fakeCli.ts"
99 },
1010 "./replayGate": {
1111 "types": "./src/replayGate.ts",
1212 "import": "./src/replayGate.ts"
1313 },
14 "./replayTranscript": {
15 "types": "./src/replayTranscript.ts",
16 "import": "./src/replayTranscript.ts"
17 },
1418 "./replayWorkspace": {
1519 "types": "./src/replayWorkspace.ts",
1620 "import": "./src/replayWorkspace.ts"
1721 },
18 "./host": {
19 "types": "./src/host.ts",
20 "import": "./src/host.ts"
21 },
22 "./fakeCli": {
23 "types": "./src/fakeCli.ts",
24 "import": "./src/fakeCli.ts"
22 "./TestProviderHost": {
23 "types": "./src/TestProviderHost.ts",
24 "import": "./src/TestProviderHost.ts"
2525 }
2626 },
2727 "scripts": {
packages/provider-testing/src/TestProviderHost.ts created+91
......@@ -0,0 +1,91 @@
1/**
2 * A `ProviderHost.ProviderHost` for driver and adapter tests. Its directories live in a
3 * scoped temp directory, settings are fixed, and background work always runs
4 * unless the test says otherwise.
5 *
6 * @module provider-testing/TestProviderHost
7 */
8import { DEFAULT_SERVER_SETTINGS, type ServerSettings } from "@t3tools/contracts";
9import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";
10import * as Context from "effect/Context";
11import * as Effect from "effect/Effect";
12import * as FileSystem from "effect/FileSystem";
13import * as Layer from "effect/Layer";
14import * as Option from "effect/Option";
15import * as Path from "effect/Path";
16import * as Ref from "effect/Ref";
17import * as Stream from "effect/Stream";
18
19export interface TestProviderHostOptions {
20 /** Session fallback cwd. Defaults to the test process cwd. */
21 readonly cwd?: string;
22 /** Initial settings. A test changes them through `TestProviderHostSettings`. */
23 readonly settings?: ServerSettings;
24 /** Whether background work such as status probes may run. Defaults to `true`. */
25 readonly runBackgroundWork?: boolean;
26}
27
28/** Lets a test change the settings its `layer` reports. */
29export class TestProviderHostSettings extends Context.Service<
30 TestProviderHostSettings,
31 { readonly set: (settings: ServerSettings) => Effect.Effect<void> }
32>()("@t3tools/provider-testing/TestProviderHost/TestProviderHostSettings") {}
33
34export const layer = (
35 options: TestProviderHostOptions = {},
36): Layer.Layer<
37 ProviderHost.ProviderHost | TestProviderHostSettings,
38 never,
39 FileSystem.FileSystem | Path.Path
40> =>
41 Layer.effectContext(
42 Effect.gen(function* () {
43 const fileSystem = yield* FileSystem.FileSystem;
44 const path = yield* Path.Path;
45 const baseDir = yield* fileSystem
46 .makeTempDirectoryScoped({ prefix: "t3-provider-host-" })
47 .pipe(Effect.orDie);
48 const stateDir = path.join(baseDir, "userdata");
49 const providerStatusCacheDir = path.join(baseDir, "caches");
50 const attachmentsDir = path.join(stateDir, "attachments");
51 for (const directory of [stateDir, providerStatusCacheDir, attachmentsDir]) {
52 yield* fileSystem.makeDirectory(directory, { recursive: true }).pipe(Effect.orDie);
53 }
54 const settings = yield* Ref.make(options.settings ?? DEFAULT_SERVER_SETTINGS);
55 const credentials = new Map<string, Uint8Array>();
56 const host = ProviderHost.ProviderHost.of({
57 paths: {
58 cwd: options.cwd ?? process.cwd(),
59 baseDir,
60 stateDir,
61 providerStatusCacheDir,
62 attachmentsDir,
63 },
64 settings: {
65 get: Ref.get(settings),
66 withSnapshot: (use) => Ref.get(settings).pipe(Effect.flatMap(use)),
67 changes: Stream.empty,
68 subscribe: Effect.succeed(Stream.empty),
69 },
70 shouldRunBackgroundWork: () => Effect.succeed(options.runBackgroundWork ?? true),
71 // Tests store attachments flat under the attachments directory by id.
72 resolveAttachmentPath: (attachment) => path.join(attachmentsDir, attachment.id),
73 // Credentials live in memory for the layer's lifetime.
74 credentials: (namespace, bindingId) =>
75 Effect.sync(() => {
76 const key = `${namespace}:${bindingId}`;
77 return {
78 binding: { owner: "t3" as const, key },
79 get: Effect.sync(() => Option.fromUndefinedOr(credentials.get(key))),
80 set: (value: Uint8Array) => Effect.sync(() => void credentials.set(key, value)),
81 remove: Effect.sync(() => void credentials.delete(key)),
82 };
83 }),
84 });
85 return Context.make(ProviderHost.ProviderHost, host).pipe(
86 Context.add(TestProviderHostSettings, {
87 set: (next) => Ref.set(settings, next),
88 }),
89 );
90 }),
91 );
packages/provider-testing/src/host.ts deleted-91
......@@ -1,91 +0,0 @@
1/**
2 * A `ProviderHost.ProviderHost` for driver and adapter tests. Its directories live in a
3 * scoped temp directory, settings are fixed, and background work always runs
4 * unless the test says otherwise.
5 *
6 * @module provider-testing/host
7 */
8import { DEFAULT_SERVER_SETTINGS, type ServerSettings } from "@t3tools/contracts";
9import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";
10import * as Context from "effect/Context";
11import * as Effect from "effect/Effect";
12import * as FileSystem from "effect/FileSystem";
13import * as Layer from "effect/Layer";
14import * as Option from "effect/Option";
15import * as Path from "effect/Path";
16import * as Ref from "effect/Ref";
17import * as Stream from "effect/Stream";
18
19export interface TestProviderHostOptions {
20 /** Session fallback cwd. Defaults to the test process cwd. */
21 readonly cwd?: string;
22 /** Initial settings. A test changes them through `TestProviderHostSettings`. */
23 readonly settings?: ServerSettings;
24 /** Whether background work such as status probes may run. Defaults to `true`. */
25 readonly runBackgroundWork?: boolean;
26}
27
28/** Lets a test change the settings its `layerTestProviderHost` reports. */
29export class TestProviderHostSettings extends Context.Service<
30 TestProviderHostSettings,
31 { readonly set: (settings: ServerSettings) => Effect.Effect<void> }
32>()("@t3tools/provider-testing/host/TestProviderHostSettings") {}
33
34export const layerTestProviderHost = (
35 options: TestProviderHostOptions = {},
36): Layer.Layer<
37 ProviderHost.ProviderHost | TestProviderHostSettings,
38 never,
39 FileSystem.FileSystem | Path.Path
40> =>
41 Layer.effectContext(
42 Effect.gen(function* () {
43 const fileSystem = yield* FileSystem.FileSystem;
44 const path = yield* Path.Path;
45 const baseDir = yield* fileSystem
46 .makeTempDirectoryScoped({ prefix: "t3-provider-host-" })
47 .pipe(Effect.orDie);
48 const stateDir = path.join(baseDir, "userdata");
49 const providerStatusCacheDir = path.join(baseDir, "caches");
50 const attachmentsDir = path.join(stateDir, "attachments");
51 for (const directory of [stateDir, providerStatusCacheDir, attachmentsDir]) {
52 yield* fileSystem.makeDirectory(directory, { recursive: true }).pipe(Effect.orDie);
53 }
54 const settings = yield* Ref.make(options.settings ?? DEFAULT_SERVER_SETTINGS);
55 const credentials = new Map<string, Uint8Array>();
56 const host = ProviderHost.ProviderHost.of({
57 paths: {
58 cwd: options.cwd ?? process.cwd(),
59 baseDir,
60 stateDir,
61 providerStatusCacheDir,
62 attachmentsDir,
63 },
64 settings: {
65 get: Ref.get(settings),
66 withSnapshot: (use) => Ref.get(settings).pipe(Effect.flatMap(use)),
67 changes: Stream.empty,
68 subscribe: Effect.succeed(Stream.empty),
69 },
70 shouldRunBackgroundWork: () => Effect.succeed(options.runBackgroundWork ?? true),
71 // Tests store attachments flat under the attachments directory by id.
72 resolveAttachmentPath: (attachment) => path.join(attachmentsDir, attachment.id),
73 // Credentials live in memory for the layer's lifetime.
74 credentials: (namespace, bindingId) =>
75 Effect.sync(() => {
76 const key = `${namespace}:${bindingId}`;
77 return {
78 binding: { owner: "t3" as const, key },
79 get: Effect.sync(() => Option.fromUndefinedOr(credentials.get(key))),
80 set: (value: Uint8Array) => Effect.sync(() => void credentials.set(key, value)),
81 remove: Effect.sync(() => void credentials.delete(key)),
82 };
83 }),
84 });
85 return Context.make(ProviderHost.ProviderHost, host).pipe(
86 Context.add(TestProviderHostSettings, {
87 set: (next) => Ref.set(settings, next),
88 }),
89 );
90 }),
91 );
packages/provider-testing/src/replayWorkspace.ts+2-1
......@@ -4,7 +4,8 @@ import * as FileSystem from "effect/FileSystem";
44import * as Path from "effect/Path";
55import * as PlatformError from "effect/PlatformError";
66import * as Schema from "effect/Schema";
7import { ChildProcess, ChildProcessSpawner } from "effect/process";
7import * as ChildProcess from "effect/process/ChildProcess";
8import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
89
910class ReplayFixtureGitCommandError extends Schema.TaggedError<ReplayFixtureGitCommandError>()(
1011 "ReplayFixtureGitCommandError",