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";...@@ -24,7 +24,7 @@ import * as Schema from "effect/Schema";
24import { ChildProcessSpawner } from "effect/process";24import { ChildProcessSpawner } from "effect/process";
25import type * as EffectAcpProtocol from "effect-acp/protocol";25import type * as EffectAcpProtocol from "effect-acp/protocol";
2626
27import { layerTestProviderHost } from "@t3tools/provider-testing/host";27import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost";
28import {28import {
29 GROK_DEFAULT_INSTANCE_ID,29 GROK_DEFAULT_INSTANCE_ID,
30 GROK_PROVIDER,30 GROK_PROVIDER,
...@@ -490,7 +490,7 @@ const recordScenario = Effect.fn("recordGrokScenario")(function* (fixtureName: s...@@ -490,7 +490,7 @@ const recordScenario = Effect.fn("recordGrokScenario")(function* (fixtureName: s
490 ).pipe(490 ).pipe(
491 Layer.provide(491 Layer.provide(
492 Layer.mergeAll(492 Layer.mergeAll(
493 layerTestProviderHost().pipe(Layer.provide(NodeServices.layer)),493 TestProviderHost.layer().pipe(Layer.provide(NodeServices.layer)),
494 NodeServices.layer,494 NodeServices.layer,
495 IdAllocator.layer,495 IdAllocator.layer,
496 ),496 ),
apps/server/src/assets/AssetAccess.ts+2-2
...@@ -53,7 +53,7 @@ import * as ServerSecretStore from "../auth/ServerSecretStore.ts";...@@ -53,7 +53,7 @@ import * as ServerSecretStore from "../auth/ServerSecretStore.ts";
53import { parseAttachmentFileExtension, resolveAttachmentPathById } from "../attachmentStore.ts";53import { parseAttachmentFileExtension, resolveAttachmentPathById } from "../attachmentStore.ts";
54import * as ServerConfig from "../config.ts";54import * as ServerConfig from "../config.ts";
55import * as Orchestrator from "../orchestration-v2/Orchestrator.ts";55import * as Orchestrator from "../orchestration-v2/Orchestrator.ts";
56import { expandHomePathWith } from "@t3tools/provider-core/server/pathExpansion";56import { expandHomePath } from "@t3tools/provider-core/server/pathExpansion";
57import * as ProjectFaviconResolver from "../project/ProjectFaviconResolver.ts";57import * as ProjectFaviconResolver from "../project/ProjectFaviconResolver.ts";
58import * as WorkspacePaths from "../workspace/WorkspacePaths.ts";58import * as WorkspacePaths from "../workspace/WorkspacePaths.ts";
59import * as NativeAppIconResolver from "./NativeAppIconResolver.ts";59import * as NativeAppIconResolver from "./NativeAppIconResolver.ts";
...@@ -469,7 +469,7 @@ export const issueAssetUrl = Effect.fn("AssetAccess.issueAssetUrl")(function* (i...@@ -469,7 +469,7 @@ export const issueAssetUrl = Effect.fn("AssetAccess.issueAssetUrl")(function* (i
469469
470 switch (input.resource._tag) {470 switch (input.resource._tag) {
471 case "media-file": {471 case "media-file": {
472 let requestedPath = expandHomePathWith(input.resource.path, path);472 let requestedPath = expandHomePath(input.resource.path);
473 if (!path.isAbsolute(requestedPath)) {473 if (!path.isAbsolute(requestedPath)) {
474 if (!input.workspaceRoot) {474 if (!input.workspaceRoot) {
475 return yield* new AssetWorkspaceContextNotFoundError({ resource: input.resource });475 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";...@@ -53,7 +53,7 @@ import * as EffectAcpErrors from "effect-acp/errors";
53import type * as EffectAcpProtocol from "effect-acp/protocol";53import type * as EffectAcpProtocol from "effect-acp/protocol";
54import type * as EffectAcpSchema from "effect-acp/compat";54import type * as EffectAcpSchema from "effect-acp/compat";
5555
56import { layerTestProviderHost } from "@t3tools/provider-testing/host";56import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost";
57import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";57import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
58import * as AcpSessionRuntime from "@t3tools/provider-acp/server/AcpSessionRuntime";58import * as AcpSessionRuntime from "@t3tools/provider-acp/server/AcpSessionRuntime";
59import {59import {
...@@ -94,7 +94,7 @@ import {...@@ -94,7 +94,7 @@ import {
9494
95const DEFAULT_GROK_SETTINGS = Schema.decodeSync(GrokSettings)({});95const 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
99const layerTest = Layer.mergeAll(99const layerTest = Layer.mergeAll(
100 NodeServices.layer,100 NodeServices.layer,
apps/server/src/orchestration-v2/Adapters/AcpRegistryAdapterV2.test.ts+2-2
...@@ -15,7 +15,7 @@ import * as EffectAcpErrors from "effect-acp/errors";...@@ -15,7 +15,7 @@ import * as EffectAcpErrors from "effect-acp/errors";
1515
16import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";16import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";
17import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";17import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
18import { layerTestProviderHost } from "@t3tools/provider-testing/host";18import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost";
19import * as ServerSettings from "../../serverSettings.ts";19import * as ServerSettings from "../../serverSettings.ts";
20import type {20import type {
21 AcpRegistryAvailableCommands,21 AcpRegistryAvailableCommands,
...@@ -44,7 +44,7 @@ const decodeAcpRegistryAdapterSettings = Schema.decodeUnknownEffect(...@@ -44,7 +44,7 @@ const decodeAcpRegistryAdapterSettings = Schema.decodeUnknownEffect(
44 AcpRegistryAdapterV2Driver.configSchema,44 AcpRegistryAdapterV2Driver.configSchema,
45);45);
4646
47const layerHost = layerTestProviderHost().pipe(Layer.provide(NodeServices.layer));47const layerHost = TestProviderHost.layer().pipe(Layer.provide(NodeServices.layer));
4848
49const layerRegistry = Layer.succeed(49const layerRegistry = Layer.succeed(
50 HttpClient.HttpClient,50 HttpClient.HttpClient,
apps/server/src/orchestration-v2/Adapters/AcpRegistryAdapterV2.testkit.ts+2-2
...@@ -8,7 +8,7 @@ import * as Path from "effect/Path";...@@ -8,7 +8,7 @@ import * as Path from "effect/Path";
8import * as Schema from "effect/Schema";8import * as Schema from "effect/Schema";
9import { ChildProcessSpawner } from "effect/process";9import { ChildProcessSpawner } from "effect/process";
1010
11import { layerTestProviderHost } from "@t3tools/provider-testing/host";11import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost";
12import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";12import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
13import * as ProviderAdapterRegistry from "../ProviderAdapterRegistry.ts";13import * as ProviderAdapterRegistry from "../ProviderAdapterRegistry.ts";
14import type { ProviderReplayGate } from "@t3tools/provider-testing/replayGate";14import type { ProviderReplayGate } from "@t3tools/provider-testing/replayGate";
...@@ -35,7 +35,7 @@ function layerAcpRegistryProviderAdapterRegistryReplay(...@@ -35,7 +35,7 @@ function layerAcpRegistryProviderAdapterRegistryReplay(
35 transcript: AcpReplayTranscript,35 transcript: AcpReplayTranscript,
36 options: { readonly replayGate?: ProviderReplayGate } = {},36 options: { readonly replayGate?: ProviderReplayGate } = {},
37) {37) {
38 const layerHost = layerTestProviderHost().pipe(Layer.provide(NodeServices.layer));38 const layerHost = TestProviderHost.layer().pipe(Layer.provide(NodeServices.layer));
3939
40 return ProviderAdapterRegistry.layerFromAdaptersEffect(40 return ProviderAdapterRegistry.layerFromAdaptersEffect(
41 Effect.gen(function* () {41 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";...@@ -24,7 +24,7 @@ import type * as EffectAcpSchema from "effect-acp/compat";
2424
25import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";25import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";
26import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";26import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
27import { layerTestProviderHost } from "@t3tools/provider-testing/host";27import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost";
28import type * as AcpSessionRuntime from "@t3tools/provider-acp/server/AcpSessionRuntime";28import type * as AcpSessionRuntime from "@t3tools/provider-acp/server/AcpSessionRuntime";
29import { makeAntigravityAcpRuntime } from "../../provider/acp/AntigravityAcpSupport.ts";29import { makeAntigravityAcpRuntime } from "../../provider/acp/AntigravityAcpSupport.ts";
30import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";30import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
...@@ -131,7 +131,7 @@ const layerSession = Layer.mergeAll(...@@ -131,7 +131,7 @@ const layerSession = Layer.mergeAll(
131 NodeServices.layer,131 NodeServices.layer,
132 IdAllocator.layer,132 IdAllocator.layer,
133 McpProviderSessions.layer,133 McpProviderSessions.layer,
134 layerTestProviderHost().pipe(Layer.provide(NodeServices.layer)),134 TestProviderHost.layer().pipe(Layer.provide(NodeServices.layer)),
135);135);
136136
137describe("AntigravityAdapterV2 client file system", () => {137describe("AntigravityAdapterV2 client file system", () => {
apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts+2-2
...@@ -108,7 +108,7 @@ import {...@@ -108,7 +108,7 @@ import {
108 claudeRateLimitEventToUpdate,108 claudeRateLimitEventToUpdate,
109 type ClaudeScopedLimitNames,109 type ClaudeScopedLimitNames,
110} from "../../provider/claudeUsageLimits.ts";110} from "../../provider/claudeUsageLimits.ts";
111import type { ServerProviderShape } from "@t3tools/provider-core/server/snapshot";111import type { ManagedServerProvider } from "@t3tools/provider-core/server/snapshot";
112import { mergeProviderInstanceEnvironment } from "@t3tools/provider-core/server/instanceEnvironment";112import { mergeProviderInstanceEnvironment } from "@t3tools/provider-core/server/instanceEnvironment";
113import { T3_CODE_ORCHESTRATION_INSTRUCTIONS } from "@t3tools/provider-core/server/orchestrationInstructions";113import { T3_CODE_ORCHESTRATION_INSTRUCTIONS } from "@t3tools/provider-core/server/orchestrationInstructions";
114import { buildRuntimeInstructions } from "@t3tools/provider-core/server/runtimeInstructions";114import { buildRuntimeInstructions } from "@t3tools/provider-core/server/runtimeInstructions";
...@@ -3018,7 +3018,7 @@ export interface ClaudeAdapterV2Options {...@@ -3018,7 +3018,7 @@ export interface ClaudeAdapterV2Options {
3018 readonly idAllocator: IdAllocator.IdAllocatorV2["Service"];3018 readonly idAllocator: IdAllocator.IdAllocatorV2["Service"];
3019 readonly queryRunner: ClaudeAgentSdkQueryRunnerShape;3019 readonly queryRunner: ClaudeAgentSdkQueryRunnerShape;
3020 readonly scopedLimitNames?: Ref.Ref<ClaudeScopedLimitNames>;3020 readonly scopedLimitNames?: Ref.Ref<ClaudeScopedLimitNames>;
3021 readonly onUsageLimits?: ServerProviderShape["applyUsageLimits"];3021 readonly onUsageLimits?: ManagedServerProvider["applyUsageLimits"];
3022 /** Sink for wake-turn continuation requests; defaults to dropping them. */3022 /** Sink for wake-turn continuation requests; defaults to dropping them. */
3023 readonly continuationRequests?: {3023 readonly continuationRequests?: {
3024 readonly offer: (3024 readonly offer: (
apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts+2-2
...@@ -12,7 +12,7 @@ import {...@@ -12,7 +12,7 @@ import {
12 completeCodexTurnTokenUsage,12 completeCodexTurnTokenUsage,
13 type CodexTurnTokenUsageState,13 type CodexTurnTokenUsageState,
14} from "../../provider/CodexTurnTokenUsage.ts";14} from "../../provider/CodexTurnTokenUsage.ts";
15import type { ServerProviderShape } from "@t3tools/provider-core/server/snapshot";15import type { ManagedServerProvider } from "@t3tools/provider-core/server/snapshot";
16import type { CodexEffectiveRuntime } from "../../provider/CodexManagedRuntime.ts";16import type { CodexEffectiveRuntime } from "../../provider/CodexManagedRuntime.ts";
17import { buildCodexInitializeParams } from "../../provider/CodexProvider.ts";17import { buildCodexInitializeParams } from "../../provider/CodexProvider.ts";
18import {18import {
...@@ -1646,7 +1646,7 @@ export interface CodexAdapterV2Options {...@@ -1646,7 +1646,7 @@ export interface CodexAdapterV2Options {
1646 readonly settings: CodexSettings;1646 readonly settings: CodexSettings;
1647 readonly environment: NodeJS.ProcessEnv;1647 readonly environment: NodeJS.ProcessEnv;
1648 readonly clientFactory: CodexAppServerClientFactoryShape;1648 readonly clientFactory: CodexAppServerClientFactoryShape;
1649 readonly onUsageLimits?: ServerProviderShape["applyUsageLimits"];1649 readonly onUsageLimits?: ManagedServerProvider["applyUsageLimits"];
1650 /**1650 /**
1651 * Resolves launch settings when each session opens, replacing `settings` and1651 * Resolves launch settings when each session opens, replacing `settings` and
1652 * `environment`. Managed ChatGPT sign-in uses it to launch the T3-installed1652 * `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";...@@ -15,7 +15,7 @@ import * as FileSystem from "effect/FileSystem";
15import * as Layer from "effect/Layer";15import * as Layer from "effect/Layer";
16import * as Schema from "effect/Schema";16import * as Schema from "effect/Schema";
1717
18import { layerTestProviderHost } from "@t3tools/provider-testing/host";18import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost";
19import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";19import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
20import { ProviderAdapterDriverCreateError } from "@t3tools/provider-core/server/adapterDriver";20import { ProviderAdapterDriverCreateError } from "@t3tools/provider-core/server/adapterDriver";
21import * as ProviderAdapterRegistry from "../ProviderAdapterRegistry.ts";21import * as ProviderAdapterRegistry from "../ProviderAdapterRegistry.ts";
...@@ -537,7 +537,7 @@ export function layer(...@@ -537,7 +537,7 @@ export function layer(
537 Layer.provide(537 Layer.provide(
538 Layer.mergeAll(538 Layer.mergeAll(
539 layerCursorAgentSdkReplay(transcript, options),539 layerCursorAgentSdkReplay(transcript, options),
540 layerTestProviderHost().pipe(Layer.provide(NodeServices.layer)),540 TestProviderHost.layer().pipe(Layer.provide(NodeServices.layer)),
541 layerHostEnvironment,541 layerHostEnvironment,
542 NodeServices.layer,542 NodeServices.layer,
543 IdAllocator.layer,543 IdAllocator.layer,
apps/server/src/orchestration-v2/Adapters/GrokAdapterV2.test.ts+2-2
...@@ -20,7 +20,7 @@ import * as Schema from "effect/Schema";...@@ -20,7 +20,7 @@ import * as Schema from "effect/Schema";
20import { ChildProcessSpawner } from "effect/process";20import { ChildProcessSpawner } from "effect/process";
21import type * as EffectAcpSchema from "effect-acp/compat";21import type * as EffectAcpSchema from "effect-acp/compat";
2222
23import { layerTestProviderHost } from "@t3tools/provider-testing/host";23import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost";
24import * as ProjectStore from "../ProjectStore.ts";24import * as ProjectStore from "../ProjectStore.ts";
25import { buildInitialGrokProviderSnapshot } from "@t3tools/provider-grok/testing";25import { buildInitialGrokProviderSnapshot } from "@t3tools/provider-grok/testing";
26import type { ProviderInstance } from "@t3tools/provider-core/server/driver";26import type { ProviderInstance } from "@t3tools/provider-core/server/driver";
...@@ -309,7 +309,7 @@ describe("Grok permission prompts", () => {...@@ -309,7 +309,7 @@ describe("Grok permission prompts", () => {
309});309});
310310
311describe("Grok launch permission mode", () => {311describe("Grok launch permission mode", () => {
312 const layerHost = layerTestProviderHost().pipe(Layer.provide(NodeServices.layer));312 const layerHost = TestProviderHost.layer().pipe(Layer.provide(NodeServices.layer));
313 const layerTest = Layer.mergeAll(313 const layerTest = Layer.mergeAll(
314 NodeServices.layer,314 NodeServices.layer,
315 IdAllocator.layer,315 IdAllocator.layer,
apps/server/src/orchestration-v2/Adapters/GrokAdapterV2.testkit.ts+2-2
...@@ -9,7 +9,7 @@ import { ChildProcessSpawner } from "effect/process";...@@ -9,7 +9,7 @@ import { ChildProcessSpawner } from "effect/process";
9import { HostProcessPlatform } from "@t3tools/shared/hostProcess";9import { HostProcessPlatform } from "@t3tools/shared/hostProcess";
10import { resolveSelfInvocation } from "@t3tools/shared/nodeRuntime";10import { resolveSelfInvocation } from "@t3tools/shared/nodeRuntime";
1111
12import { layerTestProviderHost } from "@t3tools/provider-testing/host";12import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost";
13import { GROK_ACP_CANCEL_META, GROK_ACP_INITIALIZE_META } from "@t3tools/provider-grok/testing";13import { GROK_ACP_CANCEL_META, GROK_ACP_INITIALIZE_META } from "@t3tools/provider-grok/testing";
14import { makeXAiPromptCompletionRuntime } from "@t3tools/provider-grok/testing";14import { makeXAiPromptCompletionRuntime } from "@t3tools/provider-grok/testing";
15import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";15import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
...@@ -36,7 +36,7 @@ function layerGrokProviderAdapterRegistryReplay(...@@ -36,7 +36,7 @@ function layerGrokProviderAdapterRegistryReplay(
36 transcript: AcpReplayTranscript,36 transcript: AcpReplayTranscript,
37 options: { readonly replayGate?: ProviderReplayGate } = {},37 options: { readonly replayGate?: ProviderReplayGate } = {},
38) {38) {
39 const layerHost = layerTestProviderHost().pipe(Layer.provide(NodeServices.layer));39 const layerHost = TestProviderHost.layer().pipe(Layer.provide(NodeServices.layer));
4040
41 return ProviderAdapterRegistry.layerFromAdaptersEffect(41 return ProviderAdapterRegistry.layerFromAdaptersEffect(
42 Effect.gen(function* () {42 Effect.gen(function* () {
apps/server/src/orchestration-v2/Adapters/MuseAdapterV2.testkit.ts+2-2
...@@ -27,7 +27,7 @@ import * as Effect from "effect/Effect";...@@ -27,7 +27,7 @@ import * as Effect from "effect/Effect";
27import * as Layer from "effect/Layer";27import * as Layer from "effect/Layer";
28import * as Schema from "effect/Schema";28import * as Schema from "effect/Schema";
2929
30import { layerTestProviderHost } from "@t3tools/provider-testing/host";30import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost";
31import {31import {
32 museInitializeParams,32 museInitializeParams,
33 museServeArgs,33 museServeArgs,
...@@ -460,7 +460,7 @@ export function layer(input: {...@@ -460,7 +460,7 @@ export function layer(input: {
460 ).pipe(460 ).pipe(
461 Layer.provide(461 Layer.provide(
462 Layer.mergeAll(462 Layer.mergeAll(
463 layerTestProviderHost().pipe(Layer.provide(NodeServices.layer)),463 TestProviderHost.layer().pipe(Layer.provide(NodeServices.layer)),
464 NodeServices.layer,464 NodeServices.layer,
465 IdAllocator.layer,465 IdAllocator.layer,
466 ),466 ),
apps/server/src/orchestration-v2/Adapters/OpenCode2AdapterV2.testkit.ts+2-2
...@@ -26,7 +26,7 @@ import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";...@@ -26,7 +26,7 @@ import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
26import * as ProviderAdapterRegistry from "../ProviderAdapterRegistry.ts";26import * as ProviderAdapterRegistry from "../ProviderAdapterRegistry.ts";
27import type { ProviderReplayGate } from "@t3tools/provider-testing/replayGate";27import type { ProviderReplayGate } from "@t3tools/provider-testing/replayGate";
28import type { OrchestratorV2ProviderReplayHarness } from "../testkit/ProviderReplayHarness.ts";28import type { OrchestratorV2ProviderReplayHarness } from "../testkit/ProviderReplayHarness.ts";
29import { layerTestProviderHost } from "@t3tools/provider-testing/host";29import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost";
30import { OPENCODE_PROVIDER } from "@t3tools/provider-opencode/testing";30import { OPENCODE_PROVIDER } from "@t3tools/provider-opencode/testing";
31import {31import {
32 OpenCodeReplayController,32 OpenCodeReplayController,
...@@ -267,7 +267,7 @@ const makeReplayAdapter = (...@@ -267,7 +267,7 @@ const makeReplayAdapter = (
267 );267 );
268 });268 });
269269
270const layerReplayHost = layerTestProviderHost().pipe(Layer.provide(NodeServices.layer));270const layerReplayHost = TestProviderHost.layer().pipe(Layer.provide(NodeServices.layer));
271271
272function layerRegistry(272function layerRegistry(
273 transcript: OpenCode2ReplayTranscript,273 transcript: OpenCode2ReplayTranscript,
apps/server/src/orchestration-v2/Adapters/OpenCodeAdapterV2.testkit.ts+3-3
...@@ -8,7 +8,7 @@ import * as Schema from "effect/Schema";...@@ -8,7 +8,7 @@ import * as Schema from "effect/Schema";
88
9import { OpenCodeAdapterV2Driver } from "@t3tools/provider-opencode/server";9import { OpenCodeAdapterV2Driver } from "@t3tools/provider-opencode/server";
10import * as OpenCodeRuntime from "@t3tools/provider-opencode/server/OpenCodeRuntime";10import * as OpenCodeRuntime from "@t3tools/provider-opencode/server/OpenCodeRuntime";
11import { layerTestProviderHost } from "@t3tools/provider-testing/host";11import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost";
12import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers";12import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers";
13import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";13import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
14import { ProviderAdapterDriverCreateError } from "@t3tools/provider-core/server/adapterDriver";14import { ProviderAdapterDriverCreateError } from "@t3tools/provider-core/server/adapterDriver";
...@@ -448,7 +448,7 @@ function layerOpenCodeReplayRuntime(transcript: OpenCodeSdkReplayTranscript) {...@@ -448,7 +448,7 @@ function layerOpenCodeReplayRuntime(transcript: OpenCodeSdkReplayTranscript) {
448 detail: "OpenCode replay does not load skills.",448 detail: "OpenCode replay does not load skills.",
449 }),449 }),
450 ),450 ),
451 } satisfies OpenCodeRuntime.OpenCodeRuntimeShape);451 } satisfies OpenCodeRuntime.OpenCodeRuntime["Service"]);
452 }),452 }),
453 );453 );
454}454}
...@@ -466,7 +466,7 @@ function layerOpenCodeProviderAdapterRegistryReplay(transcript: OpenCodeSdkRepla...@@ -466,7 +466,7 @@ function layerOpenCodeProviderAdapterRegistryReplay(transcript: OpenCodeSdkRepla
466 Layer.provide(466 Layer.provide(
467 Layer.mergeAll(467 Layer.mergeAll(
468 layerOpenCodeReplayRuntime(transcript),468 layerOpenCodeReplayRuntime(transcript),
469 layerTestProviderHost().pipe(Layer.provide(NodeServices.layer)),469 TestProviderHost.layer().pipe(Layer.provide(NodeServices.layer)),
470 NodeServices.layer,470 NodeServices.layer,
471 IdAllocator.layer,471 IdAllocator.layer,
472 Layer.succeed(472 Layer.succeed(
apps/server/src/orchestration-v2/Adapters/PiAdapterV2.testkit.ts+2-2
...@@ -30,7 +30,7 @@ import * as Sink from "effect/Sink";...@@ -30,7 +30,7 @@ import * as Sink from "effect/Sink";
30import * as Stream from "effect/Stream";30import * as Stream from "effect/Stream";
31import { ChildProcess, ChildProcessSpawner } from "effect/process";31import { ChildProcess, ChildProcessSpawner } from "effect/process";
3232
33import { layerTestProviderHost } from "@t3tools/provider-testing/host";33import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost";
34import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";34import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
35import { ProviderAdapterDriverCreateError } from "@t3tools/provider-core/server/adapterDriver";35import { ProviderAdapterDriverCreateError } from "@t3tools/provider-core/server/adapterDriver";
36import * as ProviderAdapterRegistry from "../ProviderAdapterRegistry.ts";36import * as ProviderAdapterRegistry from "../ProviderAdapterRegistry.ts";
...@@ -425,7 +425,7 @@ export function layer<E, R>(input: {...@@ -425,7 +425,7 @@ export function layer<E, R>(input: {
425 Layer.provide(input.spawner),425 Layer.provide(input.spawner),
426 Layer.provide(426 Layer.provide(
427 Layer.mergeAll(427 Layer.mergeAll(
428 layerTestProviderHost().pipe(Layer.provide(NodeServices.layer)),428 TestProviderHost.layer().pipe(Layer.provide(NodeServices.layer)),
429 NodeServices.layer,429 NodeServices.layer,
430 IdAllocator.layer,430 IdAllocator.layer,
431 ),431 ),
apps/server/src/provider/CodexProvider.ts+1-1
...@@ -716,7 +716,7 @@ export const checkCodexProviderStatus = Effect.fn("checkCodexProviderStatus")(fu...@@ -716,7 +716,7 @@ export const checkCodexProviderStatus = Effect.fn("checkCodexProviderStatus")(fu
716// NOTE: the singleton `CodexProviderLive` Layer has been removed as part of716// NOTE: the singleton `CodexProviderLive` Layer has been removed as part of
717// the per-instance-driver refactor. `CodexDriver.create()` builds a managed717// the per-instance-driver refactor. `CodexDriver.create()` builds a managed
718// snapshot per instance (each with its own `CodexSettings`) and hands the718// 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`.
720//720//
721// The `makePendingCodexProvider` and `checkCodexProviderStatus` helpers are721// The `makePendingCodexProvider` and `checkCodexProviderStatus` helpers are
722// re-exported for use by `CodexDriver`.722// re-exported for use by `CodexDriver`.
apps/server/src/provider/Drivers/CodexDriver.ts+1-1
...@@ -3,7 +3,7 @@...@@ -3,7 +3,7 @@
3 *3 *
4 * A driver is a plain value (not a Context.Service) whose `create()` returns4 * A driver is a plain value (not a Context.Service) whose `create()` returns
5 * one `ProviderInstance` bundling:5 * one `ProviderInstance` bundling:
6 * - `snapshot` — the live `ServerProviderShape` for this instance;6 * - `snapshot` — the live `ManagedServerProvider` for this instance;
7 * - `adapter` — the Codex session/turn/approval runtime;7 * - `adapter` — the Codex session/turn/approval runtime;
8 * - `textGeneration` — commit/PR/branch/title generation via `codex exec`.8 * - `textGeneration` — commit/PR/branch/title generation via `codex exec`.
9 *9 *
apps/server/src/provider/ProviderRegistry.ts+2-2
...@@ -9,8 +9,8 @@...@@ -9,8 +9,8 @@
9 *9 *
10 * Historically this Layer composed four per-kind Live Layers10 * Historically this Layer composed four per-kind Live Layers
11 * (`CodexProviderLive`, `ClaudeProviderLive`, …) that each exposed a11 * (`CodexProviderLive`, `ClaudeProviderLive`, …) that each exposed a
12 * `ServerProviderShape`. Those Lives were deleted during the driver /12 * `ManagedServerProvider`. Those Lives were deleted during the driver /
13 * instance refactor — every driver now carries its `snapshot: ServerProviderShape`13 * instance refactor — every driver now carries its `snapshot: ManagedServerProvider`
14 * bundled onto the `ProviderInstance` the registry produces.14 * bundled onto the `ProviderInstance` the registry produces.
15 *15 *
16 * Each configured instance (including multi-instance setups like16 * Each configured instance (including multi-instance setups like
apps/server/src/provider/builtInProviderCatalog.ts+3-3
...@@ -1,6 +1,6 @@...@@ -1,6 +1,6 @@
1import type { ProviderDriverKind, ProviderInstanceId, ServerProvider } from "@t3tools/contracts";1import type { ProviderDriverKind, ProviderInstanceId, ServerProvider } from "@t3tools/contracts";
2import type * as Stream from "effect/Stream";2import 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
5export type ProviderSnapshotSource = {5export type ProviderSnapshotSource = {
6 /**6 /**
...@@ -11,7 +11,7 @@ export type ProviderSnapshotSource = {...@@ -11,7 +11,7 @@ export type ProviderSnapshotSource = {
11 readonly instanceId: ProviderInstanceId;11 readonly instanceId: ProviderInstanceId;
12 /** Driver implementation kind. */12 /** Driver implementation kind. */
13 readonly driverKind: ProviderDriverKind;13 readonly driverKind: ProviderDriverKind;
14 readonly getSnapshot: ServerProviderShape["getSnapshot"];14 readonly getSnapshot: ManagedServerProvider["getSnapshot"];
15 readonly refresh: ServerProviderShape["refresh"];15 readonly refresh: ManagedServerProvider["refresh"];
16 readonly streamChanges: Stream.Stream<ServerProvider>;16 readonly streamChanges: Stream.Stream<ServerProvider>;
17};17};
apps/server/src/sourceControl/SourceControlRepositoryService.ts+2-2
...@@ -21,7 +21,7 @@ import {...@@ -21,7 +21,7 @@ import {
21} from "@t3tools/contracts";21} from "@t3tools/contracts";
2222
23import * as ServerConfig from "../config.ts";23import * as ServerConfig from "../config.ts";
24import { expandHomePathWith } from "@t3tools/provider-core/server/pathExpansion";24import { expandHomePath } from "@t3tools/provider-core/server/pathExpansion";
25import {25import {
26 parseGitCloneProgressLine,26 parseGitCloneProgressLine,
27 type GitCloneProgressLine,27 type GitCloneProgressLine,
...@@ -213,7 +213,7 @@ export const make = Effect.gen(function* () {...@@ -213,7 +213,7 @@ export const make = Effect.gen(function* () {
213 });213 });
214 }214 }
215215
216 return path.resolve(expandHomePathWith(trimmed, path));216 return path.resolve(expandHomePath(trimmed));
217 },217 },
218 );218 );
219219
apps/server/src/textGeneration/OpenCode2TextGeneration.test.ts+2-2
...@@ -4,13 +4,13 @@ import { ProviderInstanceId } from "@t3tools/contracts";...@@ -4,13 +4,13 @@ import { ProviderInstanceId } from "@t3tools/contracts";
4import * as Effect from "effect/Effect";4import * as Effect from "effect/Effect";
5import * as Layer from "effect/Layer";5import * as Layer from "effect/Layer";
66
7import { layerTestProviderHost } from "@t3tools/provider-testing/host";7import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost";
8import * as OpenCode2AdapterV2Testkit from "../orchestration-v2/Adapters/OpenCode2AdapterV2.testkit.ts";8import * as OpenCode2AdapterV2Testkit from "../orchestration-v2/Adapters/OpenCode2AdapterV2.testkit.ts";
9import * as OpenCode2Server from "@t3tools/provider-opencode/server/v2/OpenCode2Server";9import * as OpenCode2Server from "@t3tools/provider-opencode/server/v2/OpenCode2Server";
10import * as OpenCode2TextGeneration from "@t3tools/provider-opencode/server/v2/textGeneration";10import * as OpenCode2TextGeneration from "@t3tools/provider-opencode/server/v2/textGeneration";
11import { OPENCODE2_TITLE_GENERATION } from "./OpenCode2TextGeneration.fixture.ts";11import { 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
15it.layer(layer)("OpenCode2TextGeneration", (it) => {15it.layer(layer)("OpenCode2TextGeneration", (it) => {
16 it.effect("generates a title in a temporary session on the free tier and removes it", () =>16 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";...@@ -23,7 +23,7 @@ import { HostProcessPlatform } from "@t3tools/shared/hostProcess";
23import { isExplicitRelativePath, isWindowsAbsolutePath } from "@t3tools/shared/path";23import { isExplicitRelativePath, isWindowsAbsolutePath } from "@t3tools/shared/path";
24import { normalizeSearchQuery } from "@t3tools/shared/searchRanking";24import { normalizeSearchQuery } from "@t3tools/shared/searchRanking";
2525
26import { expandHomePathWith } from "@t3tools/provider-core/server/pathExpansion";26import { expandHomePath } from "@t3tools/provider-core/server/pathExpansion";
27import * as VcsProcess from "../vcs/VcsProcess.ts";27import * as VcsProcess from "../vcs/VcsProcess.ts";
28import * as WorkspacePaths from "./WorkspacePaths.ts";28import * as WorkspacePaths from "./WorkspacePaths.ts";
29import * as WorkspaceSearchIndex from "./WorkspaceSearchIndex.ts";29import * as WorkspaceSearchIndex from "./WorkspaceSearchIndex.ts";
...@@ -120,7 +120,7 @@ const resolveBrowseTarget = Effect.fn("WorkspaceEntries.resolveBrowseTarget")(fu...@@ -120,7 +120,7 @@ const resolveBrowseTarget = Effect.fn("WorkspaceEntries.resolveBrowseTarget")(fu
120 }120 }
121121
122 if (!isExplicitRelativePath(input.partialPath)) {122 if (!isExplicitRelativePath(input.partialPath)) {
123 return path.resolve(expandHomePathWith(input.partialPath, path));123 return path.resolve(expandHomePath(input.partialPath));
124 }124 }
125125
126 if (!input.cwd) {126 if (!input.cwd) {
...@@ -128,7 +128,7 @@ const resolveBrowseTarget = Effect.fn("WorkspaceEntries.resolveBrowseTarget")(fu...@@ -128,7 +128,7 @@ const resolveBrowseTarget = Effect.fn("WorkspaceEntries.resolveBrowseTarget")(fu
128 partialPath: input.partialPath,128 partialPath: input.partialPath,
129 });129 });
130 }130 }
131 return path.resolve(expandHomePathWith(input.cwd, path), input.partialPath);131 return path.resolve(expandHomePath(input.cwd), input.partialPath);
132});132});
133133
134/** @public Service construction is part of the canonical Effect module API. */134/** @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";...@@ -14,7 +14,7 @@ import * as Layer from "effect/Layer";
14import * as Path from "effect/Path";14import * as Path from "effect/Path";
15import * as Schema from "effect/Schema";15import * as Schema from "effect/Schema";
1616
17import { expandHomePathWith } from "@t3tools/provider-core/server/pathExpansion";17import { expandHomePath } from "@t3tools/provider-core/server/pathExpansion";
1818
19export class WorkspaceRootNotExistsError extends Schema.TaggedError<WorkspaceRootNotExistsError>()(19export class WorkspaceRootNotExistsError extends Schema.TaggedError<WorkspaceRootNotExistsError>()(
20 "WorkspaceRootNotExistsError",20 "WorkspaceRootNotExistsError",
...@@ -152,7 +152,7 @@ export const make = Effect.gen(function* () {...@@ -152,7 +152,7 @@ export const make = Effect.gen(function* () {
152 const normalizeWorkspaceRoot: WorkspacePaths["Service"]["normalizeWorkspaceRoot"] = Effect.fn(152 const normalizeWorkspaceRoot: WorkspacePaths["Service"]["normalizeWorkspaceRoot"] = Effect.fn(
153 "WorkspacePaths.normalizeWorkspaceRoot",153 "WorkspacePaths.normalizeWorkspaceRoot",
154 )(function* (workspaceRoot, options) {154 )(function* (workspaceRoot, options) {
155 const normalizedWorkspaceRoot = path.resolve(expandHomePathWith(workspaceRoot.trim(), path));155 const normalizedWorkspaceRoot = path.resolve(expandHomePath(workspaceRoot.trim()));
156 let workspaceStat = yield* statWorkspaceRoot(156 let workspaceStat = yield* statWorkspaceRoot(
157 workspaceRoot,157 workspaceRoot,
158 normalizedWorkspaceRoot,158 normalizedWorkspaceRoot,
apps/server/src/worktreesDirectory.ts+2-2
...@@ -1,6 +1,6 @@...@@ -1,6 +1,6 @@
1import type * as Path from "effect/Path";1import type * as Path from "effect/Path";
22
3import { expandHomePathWith } from "@t3tools/provider-core/server/pathExpansion";3import { expandHomePath } from "@t3tools/provider-core/server/pathExpansion";
44
5/**5/**
6 * Directory new worktrees are created under: the `worktreesDirectory`6 * Directory new worktrees are created under: the `worktreesDirectory`
...@@ -15,7 +15,7 @@ export function resolveWorktreesDirectory(...@@ -15,7 +15,7 @@ export function resolveWorktreesDirectory(
15 path: Path.Path,15 path: Path.Path,
16): string | null {16): string | null {
17 if (setting === "") return defaultDir;17 if (setting === "") return defaultDir;
18 const expanded = expandHomePathWith(setting, path);18 const expanded = expandHomePath(setting);
19 if (!path.isAbsolute(expanded)) return null;19 if (!path.isAbsolute(expanded)) return null;
20 const resolved = path.resolve(expanded);20 const resolved = path.resolve(expanded);
21 return isFilesystemRoot(resolved, path) ? null : resolved;21 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";...@@ -24,7 +24,7 @@ import * as TestClock from "effect/testing/TestClock";
24import { ChildProcess, ChildProcessSpawner } from "effect/process";24import { ChildProcess, ChildProcessSpawner } from "effect/process";
2525
26import * as AcpRegistrySupport from "./AcpRegistrySupport.ts";26import * as AcpRegistrySupport from "./AcpRegistrySupport.ts";
27import { layerTestProviderHost, TestProviderHostSettings } from "@t3tools/provider-testing/host";27import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost";
2828
29const registryUrl = "https://registry.test/registry.json";29const registryUrl = "https://registry.test/registry.json";
30const archiveUrl = "https://registry.test/example-agent.bin";30const archiveUrl = "https://registry.test/example-agent.bin";
...@@ -60,7 +60,7 @@ function layerResolver(...@@ -60,7 +60,7 @@ function layerResolver(
60) {60) {
61 return Layer.mergeAll(61 return Layer.mergeAll(
62 NodeServices.layer,62 NodeServices.layer,
63 layerTestProviderHost().pipe(Layer.provide(NodeServices.layer)),63 TestProviderHost.layer().pipe(Layer.provide(NodeServices.layer)),
64 Layer.succeed(HostProcessPlatform, "linux"),64 Layer.succeed(HostProcessPlatform, "linux"),
65 Layer.succeed(HostProcessArchitecture, "x64"),65 Layer.succeed(HostProcessArchitecture, "x64"),
66 Layer.succeed(HostProcessEnvironment, environment),66 Layer.succeed(HostProcessEnvironment, environment),
...@@ -776,7 +776,7 @@ describe("AcpRegistrySupport", () => {...@@ -776,7 +776,7 @@ describe("AcpRegistrySupport", () => {
776 Effect.provide(776 Effect.provide(
777 Layer.mergeAll(777 Layer.mergeAll(
778 NodeServices.layer,778 NodeServices.layer,
779 layerTestProviderHost().pipe(Layer.provide(NodeServices.layer)),779 TestProviderHost.layer().pipe(Layer.provide(NodeServices.layer)),
780 ),780 ),
781 ),781 ),
782 Effect.provideService(HostProcessPlatform, "linux"),782 Effect.provideService(HostProcessPlatform, "linux"),
...@@ -1374,7 +1374,7 @@ describe("AcpRegistrySupport", () => {...@@ -1374,7 +1374,7 @@ describe("AcpRegistrySupport", () => {
1374 toolsDir: `${cacheDir}/tools`,1374 toolsDir: `${cacheDir}/tools`,
1375 registryUrl,1375 registryUrl,
1376 });1376 });
1377 const hostSettings = yield* TestProviderHostSettings;1377 const hostSettings = yield* TestProviderHost.TestProviderHostSettings;
1378 const instanceId = ProviderInstanceId.make("uninstall-reference");1378 const instanceId = ProviderInstanceId.make("uninstall-reference");
1379 yield* hostSettings.set({1379 yield* hostSettings.set({
1380 ...DEFAULT_SERVER_SETTINGS,1380 ...DEFAULT_SERVER_SETTINGS,
...@@ -1492,7 +1492,7 @@ describe("AcpRegistrySupport", () => {...@@ -1492,7 +1492,7 @@ describe("AcpRegistrySupport", () => {
1492 const agentRoot = `${cacheDir}/tools/${agent.id}`;1492 const agentRoot = `${cacheDir}/tools/${agent.id}`;
1493 expect(yield* fileSystem.exists(agentRoot)).toBe(true);1493 expect(yield* fileSystem.exists(agentRoot)).toBe(true);
14941494
1495 const hostSettings = yield* TestProviderHostSettings;1495 const hostSettings = yield* TestProviderHost.TestProviderHostSettings;
1496 const instanceId = ProviderInstanceId.make("registry-reference");1496 const instanceId = ProviderInstanceId.make("registry-reference");
1497 yield* hostSettings.set({1497 yield* hostSettings.set({
1498 ...DEFAULT_SERVER_SETTINGS,1498 ...DEFAULT_SERVER_SETTINGS,
packages/provider-core/src/server/driver.ts+2-2
...@@ -40,7 +40,7 @@ import type * as Scope from "effect/Scope";...@@ -40,7 +40,7 @@ import type * as Scope from "effect/Scope";
40import type * as ProviderAdapter from "./ProviderAdapter.ts";40import type * as ProviderAdapter from "./ProviderAdapter.ts";
41import type { ProviderAuthController } from "./auth.ts";41import type { ProviderAuthController } from "./auth.ts";
42import type { ProviderDriverError } from "./errors.ts";42import type { ProviderDriverError } from "./errors.ts";
43import type { ServerProviderShape } from "./snapshot.ts";43import type { ManagedServerProvider } from "./snapshot.ts";
44import type { ProviderTextGeneration } from "./textGeneration.ts";44import type { ProviderTextGeneration } from "./textGeneration.ts";
4545
46/**46/**
...@@ -85,7 +85,7 @@ export interface ProviderInstance {...@@ -85,7 +85,7 @@ export interface ProviderInstance {
85 readonly displayName: string | undefined;85 readonly displayName: string | undefined;
86 readonly accentColor?: string | undefined;86 readonly accentColor?: string | undefined;
87 readonly enabled: boolean;87 readonly enabled: boolean;
88 readonly snapshot: ServerProviderShape;88 readonly snapshot: ManagedServerProvider;
89 readonly snapshotForCwd?: (89 readonly snapshotForCwd?: (
90 cwd: string,90 cwd: string,
91 ) => Effect.Effect<ProviderWorkspaceSnapshot, ProviderDriverError>;91 ) => Effect.Effect<ProviderWorkspaceSnapshot, ProviderDriverError>;
packages/provider-core/src/server/maintenanceResolver.test.ts+2-2
...@@ -13,8 +13,8 @@ import * as Effect from "effect/Effect";...@@ -13,8 +13,8 @@ import * as Effect from "effect/Effect";
13import * as Fiber from "effect/Fiber";13import * as Fiber from "effect/Fiber";
14import * as Sink from "effect/Sink";14import * as Sink from "effect/Sink";
15import * as Stream from "effect/Stream";15import * as Stream from "effect/Stream";
16import { HttpClient } from "effect/http";16import * as HttpClient from "effect/http/HttpClient";
17import { ChildProcessSpawner } from "effect/process";17import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
18import * as ProviderLatestVersions from "./ProviderLatestVersions.ts";18import * as ProviderLatestVersions from "./ProviderLatestVersions.ts";
19import {19import {
20 createProviderVersionAdvisory,20 createProviderVersionAdvisory,
packages/provider-core/src/server/maintenanceResolver.ts+4-2
...@@ -25,8 +25,10 @@ import * as Option from "effect/Option";...@@ -25,8 +25,10 @@ import * as Option from "effect/Option";
25import * as Path from "effect/Path";25import * as Path from "effect/Path";
26import * as Schema from "effect/Schema";26import * as Schema from "effect/Schema";
27import * as Stream from "effect/Stream";27import * as Stream from "effect/Stream";
28import { HttpClient, HttpClientRequest } from "effect/http";28import * as HttpClient from "effect/http/HttpClient";
29import { ChildProcess, ChildProcessSpawner } from "effect/process";29import * as HttpClientRequest from "effect/http/HttpClientRequest";
30import * as ChildProcess from "effect/process/ChildProcess";
31import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
3032
31import { collectUint8StreamText } from "./collectStreamText.ts";33import { collectUint8StreamText } from "./collectStreamText.ts";
32import * as ProviderLatestVersions from "./ProviderLatestVersions.ts";34import * 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";...@@ -14,7 +14,7 @@ import * as Layer from "effect/Layer";
14import * as PubSub from "effect/PubSub";14import * as PubSub from "effect/PubSub";
15import * as Ref from "effect/Ref";15import * as Ref from "effect/Ref";
16import * as Stream from "effect/Stream";16import * as Stream from "effect/Stream";
17import { TestClock } from "effect/testing";17import * as TestClock from "effect/testing/TestClock";
1818
19import * as ProviderHost from "./ProviderHost.ts";19import * as ProviderHost from "./ProviderHost.ts";
20import { makeManagedServerProvider } from "./managedProvider.ts";20import { makeManagedServerProvider } from "./managedProvider.ts";
packages/provider-core/src/server/managedProvider.ts+5-5
...@@ -17,7 +17,7 @@ import * as Semaphore from "effect/Semaphore";...@@ -17,7 +17,7 @@ import * as Semaphore from "effect/Semaphore";
1717
18import * as ProviderHost from "./ProviderHost.ts";18import * as ProviderHost from "./ProviderHost.ts";
19import { applyUsageLimitsUpdate, resolveUsageLimitsAfterProbe } from "./usageLimits.ts";19import { applyUsageLimitsUpdate, resolveUsageLimitsAfterProbe } from "./usageLimits.ts";
20import type { ServerProviderShape } from "./snapshot.ts";20import type { ManagedServerProvider } from "./snapshot.ts";
2121
22interface ProviderSnapshotState {22interface ProviderSnapshotState {
23 readonly snapshot: ServerProvider;23 readonly snapshot: ServerProvider;
...@@ -38,7 +38,7 @@ function withUsageLimits(...@@ -38,7 +38,7 @@ function withUsageLimits(
38export const makeManagedServerProvider = Effect.fn("makeManagedServerProvider")(function* <38export const makeManagedServerProvider = Effect.fn("makeManagedServerProvider")(function* <
39 Settings,39 Settings,
40>(input: {40>(input: {
41 readonly resolveMaintenance: ServerProviderShape["resolveMaintenance"];41 readonly resolveMaintenance: ManagedServerProvider["resolveMaintenance"];
42 readonly getSettings: Effect.Effect<Settings, ServerSettingsError>;42 readonly getSettings: Effect.Effect<Settings, ServerSettingsError>;
43 readonly streamSettings: Stream.Stream<Settings>;43 readonly streamSettings: Stream.Stream<Settings>;
44 readonly haveSettingsChanged: (previous: Settings, next: Settings) => boolean;44 readonly haveSettingsChanged: (previous: Settings, next: Settings) => boolean;
...@@ -54,7 +54,7 @@ export const makeManagedServerProvider = Effect.fn("makeManagedServerProvider")(...@@ -54,7 +54,7 @@ export const makeManagedServerProvider = Effect.fn("makeManagedServerProvider")(
54 readonly refreshOnInterval?: boolean;54 readonly refreshOnInterval?: boolean;
55 readonly checkProviderOnSettingsChange?: (previous: Settings, next: Settings) => boolean;55 readonly checkProviderOnSettingsChange?: (previous: Settings, next: Settings) => boolean;
56}): Effect.fn.Return<56}): Effect.fn.Return<
57 ServerProviderShape,57 ManagedServerProvider,
58 ServerSettingsError,58 ServerSettingsError,
59 Scope.Scope | ProviderHost.ProviderHost59 Scope.Scope | ProviderHost.ProviderHost
60> {60> {
...@@ -181,7 +181,7 @@ export const makeManagedServerProvider = Effect.fn("makeManagedServerProvider")(...@@ -181,7 +181,7 @@ export const makeManagedServerProvider = Effect.fn("makeManagedServerProvider")(
181 * `usageLimits` on whatever snapshot is published and leave the enrichment181 * `usageLimits` on whatever snapshot is published and leave the enrichment
182 * generation alone, so an in-flight enrichment still lands.182 * generation alone, so an in-flight enrichment still lands.
183 */183 */
184 const applyUsageLimits: ServerProviderShape["applyUsageLimits"] = (update) =>184 const applyUsageLimits: ManagedServerProvider["applyUsageLimits"] = (update) =>
185 Effect.gen(function* () {185 Effect.gen(function* () {
186 const snapshotToPublish = yield* Ref.modify(snapshotStateRef, (state) => {186 const snapshotToPublish = yield* Ref.modify(snapshotStateRef, (state) => {
187 const usageLimits = applyUsageLimitsUpdate({187 const usageLimits = applyUsageLimitsUpdate({
...@@ -289,5 +289,5 @@ export const makeManagedServerProvider = Effect.fn("makeManagedServerProvider")(...@@ -289,5 +289,5 @@ export const makeManagedServerProvider = Effect.fn("makeManagedServerProvider")(
289 get streamChanges() {289 get streamChanges() {
290 return Stream.fromPubSub(changesPubSub);290 return Stream.fromPubSub(changesPubSub);
291 },291 },
292 } satisfies ServerProviderShape;292 } satisfies ManagedServerProvider;
293});293});
packages/provider-core/src/server/pathExpansion.ts-18
...@@ -2,8 +2,6 @@...@@ -2,8 +2,6 @@
2import * as NodeOS from "node:os";2import * as NodeOS from "node:os";
3import * as NodePath from "node:path";3import * as NodePath from "node:path";
44
5import type * as Path from "effect/Path";
6
7/**5/**
8 * Expand a leading `~` (or `~/…`, `~\…`) in a user-supplied path to the6 * Expand a leading `~` (or `~/…`, `~\…`) in a user-supplied path to the
9 * current user's home directory. Spawned processes don't get shell7 * current user's home directory. Spawned processes don't get shell
...@@ -24,19 +22,3 @@ export function expandHomePath(value: string): string {...@@ -24,19 +22,3 @@ export function expandHomePath(value: string): string {
24 }22 }
25 return value;23 return value;
26}24}
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";...@@ -3,7 +3,7 @@ import type * as Effect from "effect/Effect";
3import type * as Stream from "effect/Stream";3import type * as Stream from "effect/Stream";
4import type { ProviderMaintenanceCapabilities } from "./maintenance.ts";4import type { ProviderMaintenanceCapabilities } from "./maintenance.ts";
55
6export interface ServerProviderShape {6export interface ManagedServerProvider {
7 /**7 /**
8 * Ownership-derived update capabilities. Cached between reads; pass8 * Ownership-derived update capabilities. Cached between reads; pass
9 * `{ fresh: true }` before executing an update so it never trusts a9 * `{ 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";...@@ -7,7 +7,8 @@ import * as Layer from "effect/Layer";
7import * as PlatformError from "effect/PlatformError";7import * as PlatformError from "effect/PlatformError";
8import * as Sink from "effect/Sink";8import * as Sink from "effect/Sink";
9import * as Stream from "effect/Stream";9import * 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
12import {13import {
13 isCommandMissingCause,14 isCommandMissingCause,
packages/provider-core/src/server/snapshotProbe.ts+2-1
...@@ -16,7 +16,8 @@ import * as Effect from "effect/Effect";...@@ -16,7 +16,8 @@ import * as Effect from "effect/Effect";
16import * as PlatformError from "effect/PlatformError";16import * as PlatformError from "effect/PlatformError";
17import * as Schema from "effect/Schema";17import * as Schema from "effect/Schema";
18import * as Stream from "effect/Stream";18import * 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";
20import { readCustomModelEntries } from "@t3tools/shared/model";21import { readCustomModelEntries } from "@t3tools/shared/model";
21import { HostProcessPlatform } from "@t3tools/shared/hostProcess";22import { HostProcessPlatform } from "@t3tools/shared/hostProcess";
22import { createProviderVersionAdvisory } from "./maintenanceResolver.ts";23import { createProviderVersionAdvisory } from "./maintenanceResolver.ts";
packages/provider-cursor/src/server/adapter.test.ts+4-4
...@@ -22,7 +22,7 @@ import * as Path from "effect/Path";...@@ -22,7 +22,7 @@ import * as Path from "effect/Path";
22import * as Schema from "effect/Schema";22import * as Schema from "effect/Schema";
23import * as Stream from "effect/Stream";23import * as Stream from "effect/Stream";
2424
25import { layerTestProviderHost } from "@t3tools/provider-testing/host";25import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost";
26import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";26import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
27import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";27import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
28import * as ProviderAdapter from "@t3tools/provider-core/server/ProviderAdapter";28import * as ProviderAdapter from "@t3tools/provider-core/server/ProviderAdapter";
...@@ -125,7 +125,7 @@ describe("CursorAdapterV2", () => {...@@ -125,7 +125,7 @@ describe("CursorAdapterV2", () => {
125 }),125 }),
126 }),126 }),
127 }),127 }),
128 Effect.provide(layerTestProviderHost({ cwd: workspace })),128 Effect.provide(TestProviderHost.layer({ cwd: workspace })),
129 );129 );
130 const runtime = yield* adapter.openSession({130 const runtime = yield* adapter.openSession({
131 threadId,131 threadId,
...@@ -258,7 +258,7 @@ describe("CursorAdapterV2", () => {...@@ -258,7 +258,7 @@ describe("CursorAdapterV2", () => {
258 }),258 }),
259 }),259 }),
260 }),260 }),
261 Effect.provide(layerTestProviderHost({ cwd: workspace })),261 Effect.provide(TestProviderHost.layer({ cwd: workspace })),
262 );262 );
263 const runtime = yield* adapter.openSession({263 const runtime = yield* adapter.openSession({
264 threadId,264 threadId,
...@@ -597,7 +597,7 @@ describe("CursorAdapterV2", () => {...@@ -597,7 +597,7 @@ describe("CursorAdapterV2", () => {
597 }),597 }),
598 }),598 }),
599 }),599 }),
600 Effect.provide(layerTestProviderHost({ cwd: workspace })),600 Effect.provide(TestProviderHost.layer({ cwd: workspace })),
601 );601 );
602 const runtime = yield* adapter.openSession({602 const runtime = yield* adapter.openSession({
603 threadId,603 threadId,
packages/provider-cursor/src/server/credentialStore.test.ts+2-2
...@@ -6,11 +6,11 @@ import * as FileSystem from "effect/FileSystem";...@@ -6,11 +6,11 @@ import * as FileSystem from "effect/FileSystem";
6import * as Path from "effect/Path";6import * as Path from "effect/Path";
77
8import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";8import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";
9import { layerTestProviderHost } from "@t3tools/provider-testing/host";9import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost";
10import * as Layer from "effect/Layer";10import * as Layer from "effect/Layer";
11import { makeCursorCredentialStore } from "./credentialStore.ts";11import { makeCursorCredentialStore } from "./credentialStore.ts";
1212
13const layerHost = Layer.provideMerge(layerTestProviderHost(), NodeServices.layer);13const layerHost = Layer.provideMerge(TestProviderHost.layer(), NodeServices.layer);
1414
15const legacyCredentials = {15const legacyCredentials = {
16 version: 1 as const,16 version: 1 as const,
packages/provider-cursor/src/server/driver.test.ts+2-2
...@@ -11,7 +11,7 @@ import { HttpClient } from "effect/http";...@@ -11,7 +11,7 @@ import { HttpClient } from "effect/http";
11import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";11import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
1212
13import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers";13import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers";
14import { layerTestProviderHost } from "@t3tools/provider-testing/host";14import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost";
15import { CursorDriver } from "./driver.ts";15import { CursorDriver } from "./driver.ts";
16import * as CursorAgentSdk from "./CursorAgentSdk.ts";16import * as CursorAgentSdk from "./CursorAgentSdk.ts";
17import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";17import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
...@@ -19,7 +19,7 @@ import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderS...@@ -19,7 +19,7 @@ import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderS
19import * as ProviderAdapter from "@t3tools/provider-core/server/ProviderAdapter";19import * as ProviderAdapter from "@t3tools/provider-core/server/ProviderAdapter";
20import { Cursor } from "./sdk.ts";20import { Cursor } from "./sdk.ts";
2121
22const layerTest = layerTestProviderHost({ runBackgroundWork: false }).pipe(22const layerTest = TestProviderHost.layer({ runBackgroundWork: false }).pipe(
23 Layer.provideMerge(NodeServices.layer),23 Layer.provideMerge(NodeServices.layer),
24 Layer.provideMerge(IdAllocator.layer),24 Layer.provideMerge(IdAllocator.layer),
25 Layer.provideMerge(McpProviderSessions.layer),25 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";...@@ -11,13 +11,13 @@ import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
1111
12import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions";12import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions";
13import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers";13import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers";
14import { layerTestProviderHost } from "@t3tools/provider-testing/host";14import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost";
15import { GrokDriver } from "./driver.ts";15import { GrokDriver } from "./driver.ts";
1616
17import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";17import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
18import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";18import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
1919
20const layerTest = layerTestProviderHost({ runBackgroundWork: false }).pipe(20const layerTest = TestProviderHost.layer({ runBackgroundWork: false }).pipe(
21 Layer.provideMerge(NodeServices.layer),21 Layer.provideMerge(NodeServices.layer),
22 Layer.provideMerge(IdAllocator.layer),22 Layer.provideMerge(IdAllocator.layer),
23 Layer.provideMerge(McpProviderSessions.layer),23 Layer.provideMerge(McpProviderSessions.layer),
packages/provider-muse/src/server/adapter.test.ts+2-2
...@@ -30,7 +30,7 @@ import * as Queue from "effect/Queue";...@@ -30,7 +30,7 @@ import * as Queue from "effect/Queue";
30import * as Schema from "effect/Schema";30import * as Schema from "effect/Schema";
31import * as Stream from "effect/Stream";31import * as Stream from "effect/Stream";
3232
33import { layerTestProviderHost } from "@t3tools/provider-testing/host";33import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost";
34import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";34import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
35import type { MuseItem } from "./protocol.ts";35import type { MuseItem } from "./protocol.ts";
36import type { MuseSdkHost } from "./sdk.ts";36import type { MuseSdkHost } from "./sdk.ts";
...@@ -43,7 +43,7 @@ const testLayer = Layer.mergeAll(...@@ -43,7 +43,7 @@ const testLayer = Layer.mergeAll(
43 NodeServices.layer,43 NodeServices.layer,
44 IdAllocator.layer,44 IdAllocator.layer,
45 McpProviderSessions.layer,45 McpProviderSessions.layer,
46 layerTestProviderHost().pipe(Layer.provide(NodeServices.layer)),46 TestProviderHost.layer().pipe(Layer.provide(NodeServices.layer)),
47);47);
48const MUSE_PROVIDER = ProviderDriverKind.make("muse");48const MUSE_PROVIDER = ProviderDriverKind.make("muse");
49const INSTANCE_ID = ProviderInstanceId.make("muse_work");49const 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";...@@ -5,8 +5,8 @@ import * as Effect from "effect/Effect";
5import * as FileSystem from "effect/FileSystem";5import * as FileSystem from "effect/FileSystem";
6import * as Path from "effect/Path";6import * as Path from "effect/Path";
7import * as Schema from "effect/Schema";7import * as Schema from "effect/Schema";
8import { HttpClient } from "effect/http";8import * as HttpClient from "effect/http/HttpClient";
9import { ChildProcessSpawner } from "effect/process";9import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
1010
11import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions";11import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions";
12import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";12import * 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";...@@ -3,7 +3,8 @@ import { expect, it } from "@effect/vitest";
3import { ProviderDriverKind, ProviderInstanceId, type ServerProvider } from "@t3tools/contracts";3import { ProviderDriverKind, ProviderInstanceId, type ServerProvider } from "@t3tools/contracts";
4import * as Effect from "effect/Effect";4import * as Effect from "effect/Effect";
5import * as FileSystem from "effect/FileSystem";5import * 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
8import {9import {
9 compareMuseVersions,10 compareMuseVersions,
packages/provider-muse/src/server/maintenance.ts+2-1
...@@ -3,7 +3,8 @@ import { compareSemverVersions } from "@t3tools/shared/semver";...@@ -3,7 +3,8 @@ import { compareSemverVersions } from "@t3tools/shared/semver";
3import * as Effect from "effect/Effect";3import * as Effect from "effect/Effect";
4import * as FileSystem from "effect/FileSystem";4import * as FileSystem from "effect/FileSystem";
5import * as Schema from "effect/Schema";5import * 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
8import {9import {
9 enrichProviderSnapshotWithVersionAdvisory,10 enrichProviderSnapshotWithVersionAdvisory,
packages/provider-muse/src/server/status.ts+1-1
...@@ -5,7 +5,7 @@ import * as DateTime from "effect/DateTime";...@@ -5,7 +5,7 @@ import * as DateTime from "effect/DateTime";
5import * as Effect from "effect/Effect";5import * as Effect from "effect/Effect";
6import * as Option from "effect/Option";6import * as Option from "effect/Option";
7import * as Result from "effect/Result";7import * as Result from "effect/Result";
8import { ChildProcess } from "effect/process";8import * as ChildProcess from "effect/process/ChildProcess";
99
10import { createMuseSdkHost, makeMuseEnvironment } from "./sdk.ts";10import { createMuseSdkHost, makeMuseEnvironment } from "./sdk.ts";
11import { parseMuseVersion } from "./maintenance.ts";11import { 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";...@@ -12,7 +12,8 @@ import * as FileSystem from "effect/FileSystem";
12import * as Layer from "effect/Layer";12import * as Layer from "effect/Layer";
13import * as Path from "effect/Path";13import * as Path from "effect/Path";
14import * as TestClock from "effect/testing/TestClock";14import * 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";
16import { describe, expect, it } from "vite-plus/test";17import { describe, expect, it } from "vite-plus/test";
1718
18import * as OpenCodeRuntime from "./OpenCodeRuntime.ts";19import * as OpenCodeRuntime from "./OpenCodeRuntime.ts";
packages/provider-opencode/src/server/OpenCodeRuntime.ts+80-76
...@@ -28,7 +28,8 @@ import * as Result from "effect/Result";...@@ -28,7 +28,8 @@ import * as Result from "effect/Result";
28import * as Scope from "effect/Scope";28import * as Scope from "effect/Scope";
29import * as Schema from "effect/Schema";29import * as Schema from "effect/Schema";
30import * as Stream from "effect/Stream";30import * 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
33import { signalProcessGroup } from "@t3tools/provider-core/server/processGroup";34import { signalProcessGroup } from "@t3tools/provider-core/server/processGroup";
34import { isWindowsCommandNotFound } from "@t3tools/provider-core/server/snapshotProbe";35import { isWindowsCommandNotFound } from "@t3tools/provider-core/server/snapshotProbe";
...@@ -227,68 +228,71 @@ const decodeOpenCodeSkillsCliOutputExit = Schema.decodeUnknownExit(...@@ -227,68 +228,71 @@ const decodeOpenCodeSkillsCliOutputExit = Schema.decodeUnknownExit(
227 Schema.fromJsonString(Schema.Array(OpenCodeSkillSchema)),228 Schema.fromJsonString(Schema.Array(OpenCodeSkillSchema)),
228);229);
229230
230export interface OpenCodeRuntimeShape {231export class OpenCodeRuntime extends Context.Service<
231 /**232 OpenCodeRuntime,
232 * Spawns a local OpenCode server process. Its lifetime is bound to the caller's233 {
233 * `Scope.Scope` — the child is killed automatically when that scope closes.234 /**
234 * Consumers that want a long-lived server must create and hold a scope explicitly235 * Spawns a local OpenCode server process. Its lifetime is bound to the caller's
235 * (see {@link Scope.make}) and close it when done.236 * `Scope.Scope` — the child is killed automatically when that scope closes.
236 */237 * Consumers that want a long-lived server must create and hold a scope explicitly
237 readonly startOpenCodeServerProcess: (input: {238 * (see {@link Scope.make}) and close it when done.
238 readonly binaryPath: string;239 */
239 readonly directory: string;240 readonly startOpenCodeServerProcess: (input: {
240 readonly serverPassword?: string;241 readonly binaryPath: string;
241 readonly environment?: NodeJS.ProcessEnv;242 readonly directory: string;
242 readonly port?: number;243 readonly serverPassword?: string;
243 readonly hostname?: string;244 readonly environment?: NodeJS.ProcessEnv;
244 readonly timeoutMs?: number;245 readonly port?: number;
245 /** Checks the listening server and returns its version. Defaults to the 1.x health check. */246 readonly hostname?: string;
246 readonly verify?: (url: string) => Effect.Effect<string, OpenCodeRuntimeError>;247 readonly timeoutMs?: number;
247 }) => Effect.Effect<OpenCodeServerProcess, OpenCodeRuntimeError, Scope.Scope>;248 /** Checks the listening server and returns its version. Defaults to the 1.x health check. */
248 /**249 readonly verify?: (url: string) => Effect.Effect<string, OpenCodeRuntimeError>;
249 * Returns a handle to either an externally-managed OpenCode server (when250 }) => Effect.Effect<OpenCodeServerProcess, OpenCodeRuntimeError, Scope.Scope>;
250 * `serverUrl` is provided — no lifetime is attached to the caller's scope) or a251 /**
251 * freshly spawned local server whose lifetime is bound to the caller's scope.252 * Returns a handle to either an externally-managed OpenCode server (when
252 */253 * `serverUrl` is provided — no lifetime is attached to the caller's scope) or a
253 readonly connectToOpenCodeServer: (input: {254 * freshly spawned local server whose lifetime is bound to the caller's scope.
254 readonly binaryPath: string;255 */
255 readonly directory: string;256 readonly connectToOpenCodeServer: (input: {
256 readonly serverUrl?: string | null;257 readonly binaryPath: string;
257 readonly serverPassword?: string;258 readonly directory: string;
258 readonly environment?: NodeJS.ProcessEnv;259 readonly serverUrl?: string | null;
259 readonly port?: number;260 readonly serverPassword?: string;
260 readonly hostname?: string;261 readonly environment?: NodeJS.ProcessEnv;
261 readonly timeoutMs?: number;262 readonly port?: number;
262 }) => Effect.Effect<OpenCodeServerConnection, OpenCodeRuntimeError, Scope.Scope>;263 readonly hostname?: string;
263 readonly runOpenCodeCommand: (input: {264 readonly timeoutMs?: number;
264 readonly binaryPath: string;265 }) => Effect.Effect<OpenCodeServerConnection, OpenCodeRuntimeError, Scope.Scope>;
265 readonly args: ReadonlyArray<string>;266 readonly runOpenCodeCommand: (input: {
266 readonly environment?: NodeJS.ProcessEnv;267 readonly binaryPath: string;
267 readonly cwd?: string;268 readonly args: ReadonlyArray<string>;
268 readonly maxOutputBytes?: number;269 readonly environment?: NodeJS.ProcessEnv;
269 }) => Effect.Effect<OpenCodeCommandResult, OpenCodeRuntimeError>;270 readonly cwd?: string;
270 readonly createOpenCodeSdkClient: (input: {271 readonly maxOutputBytes?: number;
271 readonly baseUrl: string;272 }) => Effect.Effect<OpenCodeCommandResult, OpenCodeRuntimeError>;
272 readonly directory: string;273 readonly createOpenCodeSdkClient: (input: {
273 readonly serverPassword?: string;274 readonly baseUrl: string;
274 }) => OpencodeClient;275 readonly directory: string;
275 readonly loadOpenCodeInventory: (276 readonly serverPassword?: string;
276 client: OpencodeClient,277 }) => OpencodeClient;
277 ) => Effect.Effect<OpenCodeInventory, OpenCodeRuntimeError>;278 readonly loadOpenCodeInventory: (
278 readonly loadOpenCodeSkills: (279 client: OpencodeClient,
279 client: OpencodeClient,280 ) => Effect.Effect<OpenCodeInventory, OpenCodeRuntimeError>;
280 ) => Effect.Effect<ReadonlyArray<OpenCodeSkill>, OpenCodeRuntimeError>;281 readonly loadOpenCodeSkills: (
281 readonly loadInventoryFromCli: (input: {282 client: OpencodeClient,
282 readonly binaryPath: string;283 ) => Effect.Effect<ReadonlyArray<OpenCodeSkill>, OpenCodeRuntimeError>;
283 readonly cwd: string;284 readonly loadInventoryFromCli: (input: {
284 readonly environment?: NodeJS.ProcessEnv;285 readonly binaryPath: string;
285 }) => Effect.Effect<OpenCodeInventory, OpenCodeRuntimeError>;286 readonly cwd: string;
286 readonly loadSkillsFromCli: (input: {287 readonly environment?: NodeJS.ProcessEnv;
287 readonly binaryPath: string;288 }) => Effect.Effect<OpenCodeInventory, OpenCodeRuntimeError>;
288 readonly cwd: string;289 readonly loadSkillsFromCli: (input: {
289 readonly environment?: NodeJS.ProcessEnv;290 readonly binaryPath: string;
290 }) => Effect.Effect<ReadonlyArray<OpenCodeSkill>, OpenCodeRuntimeError>;291 readonly cwd: string;
291}292 readonly environment?: NodeJS.ProcessEnv;
293 }) => Effect.Effect<ReadonlyArray<OpenCodeSkill>, OpenCodeRuntimeError>;
294 }
295>()("@t3tools/provider-opencode/server/OpenCodeRuntime") {}
292296
293function parseServerUrlFromOutput(output: string): string | null {297function parseServerUrlFromOutput(output: string): string | null {
294 for (const line of output.split("\n")) {298 for (const line of output.split("\n")) {
...@@ -596,7 +600,7 @@ const makeOpenCodeRuntime = Effect.gen(function* () {...@@ -596,7 +600,7 @@ const makeOpenCodeRuntime = Effect.gen(function* () {
596 const resolveCommand = (command: string, args: ReadonlyArray<string>, env?: NodeJS.ProcessEnv) =>600 const resolveCommand = (command: string, args: ReadonlyArray<string>, env?: NodeJS.ProcessEnv) =>
597 resolveSpawnCommand(command, args, env ? { env } : {});601 resolveSpawnCommand(command, args, env ? { env } : {});
598602
599 const runOpenCodeCommand: OpenCodeRuntimeShape["runOpenCodeCommand"] = (input) =>603 const runOpenCodeCommand: OpenCodeRuntime["Service"]["runOpenCodeCommand"] = (input) =>
600 Effect.gen(function* () {604 Effect.gen(function* () {
601 const spawnCommand = yield* resolveCommand(input.binaryPath, input.args, input.environment);605 const spawnCommand = yield* resolveCommand(input.binaryPath, input.args, input.environment);
602 const child = yield* spawner.spawn(606 const child = yield* spawner.spawn(
...@@ -651,7 +655,7 @@ const makeOpenCodeRuntime = Effect.gen(function* () {...@@ -651,7 +655,7 @@ const makeOpenCodeRuntime = Effect.gen(function* () {
651 ),655 ),
652 );656 );
653657
654 const createOpenCodeSdkClient: OpenCodeRuntimeShape["createOpenCodeSdkClient"] = (input) =>658 const createOpenCodeSdkClient: OpenCodeRuntime["Service"]["createOpenCodeSdkClient"] = (input) =>
655 createOpencodeClient({659 createOpencodeClient({
656 baseUrl: input.baseUrl,660 baseUrl: input.baseUrl,
657 directory: input.directory,661 directory: input.directory,
...@@ -665,7 +669,9 @@ const makeOpenCodeRuntime = Effect.gen(function* () {...@@ -665,7 +669,9 @@ const makeOpenCodeRuntime = Effect.gen(function* () {
665 throwOnError: true,669 throwOnError: true,
666 });670 });
667671
668 const startOpenCodeServerProcess: OpenCodeRuntimeShape["startOpenCodeServerProcess"] = (input) =>672 const startOpenCodeServerProcess: OpenCodeRuntime["Service"]["startOpenCodeServerProcess"] = (
673 input,
674 ) =>
669 Effect.gen(function* () {675 Effect.gen(function* () {
670 // Bind this server's lifetime to the caller's scope. When the caller's676 // Bind this server's lifetime to the caller's scope. When the caller's
671 // scope closes, the spawned child is killed and all associated fibers677 // scope closes, the spawned child is killed and all associated fibers
...@@ -872,7 +878,9 @@ const makeOpenCodeRuntime = Effect.gen(function* () {...@@ -872,7 +878,9 @@ const makeOpenCodeRuntime = Effect.gen(function* () {
872 } satisfies OpenCodeServerProcess;878 } satisfies OpenCodeServerProcess;
873 });879 });
874880
875 const connectToOpenCodeServer: OpenCodeRuntimeShape["connectToOpenCodeServer"] = (input) => {881 const connectToOpenCodeServer: OpenCodeRuntime["Service"]["connectToOpenCodeServer"] = (
882 input,
883 ) => {
876 const serverUrl = input.serverUrl?.trim();884 const serverUrl = input.serverUrl?.trim();
877 if (serverUrl) {885 if (serverUrl) {
878 const serverPassword = resolveOpenCodeServerPassword({886 const serverPassword = resolveOpenCodeServerPassword({
...@@ -937,7 +945,7 @@ const makeOpenCodeRuntime = Effect.gen(function* () {...@@ -937,7 +945,7 @@ const makeOpenCodeRuntime = Effect.gen(function* () {
937 Effect.orElseSucceed((): ReadonlyArray<Agent> => []),945 Effect.orElseSucceed((): ReadonlyArray<Agent> => []),
938 );946 );
939947
940 const loadOpenCodeSkills: OpenCodeRuntimeShape["loadOpenCodeSkills"] = (client) =>948 const loadOpenCodeSkills: OpenCodeRuntime["Service"]["loadOpenCodeSkills"] = (client) =>
941 runOpenCodeSdk("app.skills", (signal) => client.app.skills(undefined, { signal })).pipe(949 runOpenCodeSdk("app.skills", (signal) => client.app.skills(undefined, { signal })).pipe(
942 Effect.map((result) =>950 Effect.map((result) =>
943 (result.data ?? []).map((skill) => ({951 (result.data ?? []).map((skill) => ({
...@@ -950,7 +958,7 @@ const makeOpenCodeRuntime = Effect.gen(function* () {...@@ -950,7 +958,7 @@ const makeOpenCodeRuntime = Effect.gen(function* () {
950 const loadSkills = (client: OpencodeClient) =>958 const loadSkills = (client: OpencodeClient) =>
951 loadOpenCodeSkills(client).pipe(Effect.orElseSucceed((): ReadonlyArray<OpenCodeSkill> => []));959 loadOpenCodeSkills(client).pipe(Effect.orElseSucceed((): ReadonlyArray<OpenCodeSkill> => []));
952960
953 const loadOpenCodeInventory: OpenCodeRuntimeShape["loadOpenCodeInventory"] = (client) =>961 const loadOpenCodeInventory: OpenCodeRuntime["Service"]["loadOpenCodeInventory"] = (client) =>
954 Effect.all(962 Effect.all(
955 [963 [
956 loadProviders(client),964 loadProviders(client),
...@@ -970,7 +978,7 @@ const makeOpenCodeRuntime = Effect.gen(function* () {...@@ -970,7 +978,7 @@ const makeOpenCodeRuntime = Effect.gen(function* () {
970 })),978 })),
971 );979 );
972980
973 const loadInventoryFromCli: OpenCodeRuntimeShape["loadInventoryFromCli"] = (input) =>981 const loadInventoryFromCli: OpenCodeRuntime["Service"]["loadInventoryFromCli"] = (input) =>
974 Effect.gen(function* () {982 Effect.gen(function* () {
975 const env = input.environment !== undefined ? { environment: input.environment } : ({} as {});983 const env = input.environment !== undefined ? { environment: input.environment } : ({} as {});
976 const commandContext = { cwd: input.cwd, ...env };984 const commandContext = { cwd: input.cwd, ...env };
...@@ -1070,7 +1078,7 @@ const makeOpenCodeRuntime = Effect.gen(function* () {...@@ -1070,7 +1078,7 @@ const makeOpenCodeRuntime = Effect.gen(function* () {
1070 };1078 };
1071 });1079 });
10721080
1073 const loadSkillsFromCli: OpenCodeRuntimeShape["loadSkillsFromCli"] = (input) =>1081 const loadSkillsFromCli: OpenCodeRuntime["Service"]["loadSkillsFromCli"] = (input) =>
1074 runOpenCodeCommand({1082 runOpenCodeCommand({
1075 binaryPath: input.binaryPath,1083 binaryPath: input.binaryPath,
1076 args: ["debug", "skill"],1084 args: ["debug", "skill"],
...@@ -1099,13 +1107,9 @@ const makeOpenCodeRuntime = Effect.gen(function* () {...@@ -1099,13 +1107,9 @@ const makeOpenCodeRuntime = Effect.gen(function* () {
1099 loadOpenCodeSkills,1107 loadOpenCodeSkills,
1100 loadInventoryFromCli,1108 loadInventoryFromCli,
1101 loadSkillsFromCli,1109 loadSkillsFromCli,
1102 } satisfies OpenCodeRuntimeShape;1110 } satisfies OpenCodeRuntime["Service"];
1103});1111});
11041112
1105export class OpenCodeRuntime extends Context.Service<OpenCodeRuntime, OpenCodeRuntimeShape>()(
1106 "@t3tools/provider-opencode/server/OpenCodeRuntime",
1107) {}
1108
1109export const layer = Layer.effect(OpenCodeRuntime, makeOpenCodeRuntime).pipe(1113export const layer = Layer.effect(OpenCodeRuntime, makeOpenCodeRuntime).pipe(
1110 Layer.provide(NetService.layer),1114 Layer.provide(NetService.layer),
1111);1115);
packages/provider-opencode/src/server/OpenCodeServerLedger.test.ts+1-1
...@@ -12,7 +12,7 @@ import * as FileSystem from "effect/FileSystem";...@@ -12,7 +12,7 @@ import * as FileSystem from "effect/FileSystem";
12import * as Layer from "effect/Layer";12import * as Layer from "effect/Layer";
13import * as Path from "effect/Path";13import * as Path from "effect/Path";
14import * as Scope from "effect/Scope";14import * as Scope from "effect/Scope";
15import { ChildProcessSpawner } from "effect/process";15import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
1616
17import * as OpenCodeRuntime from "./OpenCodeRuntime.ts";17import * as OpenCodeRuntime from "./OpenCodeRuntime.ts";
18import * as OpenCodeServerLedger from "./OpenCodeServerLedger.ts";18import * as OpenCodeServerLedger from "./OpenCodeServerLedger.ts";
packages/provider-opencode/src/server/OpenCodeServerLedger.ts+2-1
...@@ -6,7 +6,8 @@ import * as Layer from "effect/Layer";...@@ -6,7 +6,8 @@ import * as Layer from "effect/Layer";
6import * as Option from "effect/Option";6import * as Option from "effect/Option";
7import * as Path from "effect/Path";7import * as Path from "effect/Path";
8import * as Schema from "effect/Schema";8import * 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
11import { writeFileStringAtomically } from "@t3tools/shared/atomicWrite";12import { writeFileStringAtomically } from "@t3tools/shared/atomicWrite";
12import { signalProcessGroup } from "@t3tools/provider-core/server/processGroup";13import { 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* () {...@@ -24,7 +24,7 @@ const makeRuntime = Effect.gen(function* () {
24 const failNextStart = yield* Ref.make(false);24 const failNextStart = yield* Ref.make(false);
25 const started = yield* Deferred.make<void>();25 const started = yield* Deferred.make<void>();
26 const closed = yield* Deferred.make<void>();26 const closed = yield* Deferred.make<void>();
27 const runtime: OpenCodeRuntime.OpenCodeRuntimeShape = {27 const runtime: OpenCodeRuntime.OpenCodeRuntime["Service"] = {
28 startOpenCodeServerProcess: () =>28 startOpenCodeServerProcess: () =>
29 Effect.gen(function* () {29 Effect.gen(function* () {
30 if (yield* Ref.getAndSet(failNextStart, false)) {30 if (yield* Ref.getAndSet(failNextStart, false)) {
...@@ -115,7 +115,7 @@ it.effect("invalidates an exited process so the next borrower starts a new one",...@@ -115,7 +115,7 @@ it.effect("invalidates an exited process so the next borrower starts a new one",
115 const starts = yield* Ref.make(0);115 const starts = yield* Ref.make(0);
116 const processExits: Array<Deferred.Deferred<number>> = [];116 const processExits: Array<Deferred.Deferred<number>> = [];
117 const processClosed = yield* Deferred.make<void>();117 const processClosed = yield* Deferred.make<void>();
118 const runtime: OpenCodeRuntime.OpenCodeRuntimeShape = {118 const runtime: OpenCodeRuntime.OpenCodeRuntime["Service"] = {
119 startOpenCodeServerProcess: () =>119 startOpenCodeServerProcess: () =>
120 Effect.gen(function* () {120 Effect.gen(function* () {
121 const index = yield* Ref.updateAndGet(starts, (count) => count + 1);121 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", () =>...@@ -165,7 +165,7 @@ it.effect("replaces a dead cached process before its exit watcher runs", () =>
165 const starts = yield* Ref.make(0);165 const starts = yield* Ref.make(0);
166 const closes = yield* Ref.make(0);166 const closes = yield* Ref.make(0);
167 const processRunning: Array<Ref.Ref<boolean>> = [];167 const processRunning: Array<Ref.Ref<boolean>> = [];
168 const runtime: OpenCodeRuntime.OpenCodeRuntimeShape = {168 const runtime: OpenCodeRuntime.OpenCodeRuntime["Service"] = {
169 startOpenCodeServerProcess: () =>169 startOpenCodeServerProcess: () =>
170 Effect.gen(function* () {170 Effect.gen(function* () {
171 const index = yield* Ref.updateAndGet(starts, (count) => count + 1);171 const index = yield* Ref.updateAndGet(starts, (count) => count + 1);
...@@ -214,7 +214,7 @@ it.effect("cleans up an interrupted startup and allows a retry", () =>...@@ -214,7 +214,7 @@ it.effect("cleans up an interrupted startup and allows a retry", () =>
214 const starts = yield* Ref.make(0);214 const starts = yield* Ref.make(0);
215 const firstStartEntered = yield* Deferred.make<void>();215 const firstStartEntered = yield* Deferred.make<void>();
216 const firstStartClosed = yield* Deferred.make<void>();216 const firstStartClosed = yield* Deferred.make<void>();
217 const runtime: OpenCodeRuntime.OpenCodeRuntimeShape = {217 const runtime: OpenCodeRuntime.OpenCodeRuntime["Service"] = {
218 startOpenCodeServerProcess: () =>218 startOpenCodeServerProcess: () =>
219 Effect.gen(function* () {219 Effect.gen(function* () {
220 const index = yield* Ref.updateAndGet(starts, (count) => count + 1);220 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";...@@ -32,7 +32,7 @@ import * as Stream from "effect/Stream";
32import * as TestClock from "effect/testing/TestClock";32import * as TestClock from "effect/testing/TestClock";
3333
34import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";34import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";
35import { layerTestProviderHost } from "@t3tools/provider-testing/host";35import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost";
36import type * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers";36import type * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers";
37import * as OpenCodeRuntime from "./OpenCodeRuntime.ts";37import * as OpenCodeRuntime from "./OpenCodeRuntime.ts";
38import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";38import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
...@@ -165,7 +165,7 @@ const makeOpenCodeRuntimeHarness = Effect.fn("makeOpenCodeRuntimeHarness")(funct...@@ -165,7 +165,7 @@ const makeOpenCodeRuntimeHarness = Effect.fn("makeOpenCodeRuntimeHarness")(funct
165 Effect.provideService(OpenCodeRuntime.OpenCodeRuntime, {165 Effect.provideService(OpenCodeRuntime.OpenCodeRuntime, {
166 connectToOpenCodeServer: () => Effect.succeed({ url: "http://test.invalid", external: true }),166 connectToOpenCodeServer: () => Effect.succeed({ url: "http://test.invalid", external: true }),
167 createOpenCodeSdkClient: () => client,167 createOpenCodeSdkClient: () => client,
168 } as unknown as OpenCodeRuntime.OpenCodeRuntimeShape),168 } as unknown as OpenCodeRuntime.OpenCodeRuntime["Service"]),
169 Effect.provideService(ProviderHost.ProviderHost, {169 Effect.provideService(ProviderHost.ProviderHost, {
170 paths: { cwd: "/workspace" },170 paths: { cwd: "/workspace" },
171 resolveAttachmentPath: () => null,171 resolveAttachmentPath: () => null,
...@@ -1538,7 +1538,7 @@ describe("OpenCodeAdapterV2", () => {...@@ -1538,7 +1538,7 @@ describe("OpenCodeAdapterV2", () => {
1538 connectToOpenCodeServer: () =>1538 connectToOpenCodeServer: () =>
1539 Effect.succeed({ url: "http://test.invalid", external: true }),1539 Effect.succeed({ url: "http://test.invalid", external: true }),
1540 createOpenCodeSdkClient: () => client,1540 createOpenCodeSdkClient: () => client,
1541 } as unknown as OpenCodeRuntime.OpenCodeRuntimeShape),1541 } as unknown as OpenCodeRuntime.OpenCodeRuntime["Service"]),
1542 Effect.provideService(ProviderHost.ProviderHost, {1542 Effect.provideService(ProviderHost.ProviderHost, {
1543 paths: { cwd: "/workspace" },1543 paths: { cwd: "/workspace" },
1544 resolveAttachmentPath: () => null,1544 resolveAttachmentPath: () => null,
...@@ -1835,7 +1835,7 @@ describe("OpenCodeAdapterV2", () => {...@@ -1835,7 +1835,7 @@ describe("OpenCodeAdapterV2", () => {
1835 connectToOpenCodeServer: () =>1835 connectToOpenCodeServer: () =>
1836 Effect.succeed({ url: "http://test.invalid", external: true }),1836 Effect.succeed({ url: "http://test.invalid", external: true }),
1837 createOpenCodeSdkClient: () => client,1837 createOpenCodeSdkClient: () => client,
1838 } as unknown as OpenCodeRuntime.OpenCodeRuntimeShape),1838 } as unknown as OpenCodeRuntime.OpenCodeRuntime["Service"]),
1839 Effect.provideService(ProviderHost.ProviderHost, {1839 Effect.provideService(ProviderHost.ProviderHost, {
1840 paths: { cwd: "/workspace" },1840 paths: { cwd: "/workspace" },
1841 resolveAttachmentPath: () => null,1841 resolveAttachmentPath: () => null,
...@@ -2507,7 +2507,7 @@ describe("OpenCodeAdapterV2", () => {...@@ -2507,7 +2507,7 @@ describe("OpenCodeAdapterV2", () => {
2507 },2507 },
2508 } as unknown as OpencodeClient;2508 } as unknown as OpencodeClient;
2509 const unused = (operation: string) => () => Effect.die(`${operation} is not used`);2509 const unused = (operation: string) => () => Effect.die(`${operation} is not used`);
2510 const runtime: OpenCodeRuntime.OpenCodeRuntimeShape = {2510 const runtime: OpenCodeRuntime.OpenCodeRuntime["Service"] = {
2511 startOpenCodeServerProcess: unused("startOpenCodeServerProcess"),2511 startOpenCodeServerProcess: unused("startOpenCodeServerProcess"),
2512 connectToOpenCodeServer: () =>2512 connectToOpenCodeServer: () =>
2513 Effect.succeed({2513 Effect.succeed({
...@@ -2585,7 +2585,7 @@ describe("OpenCodeAdapterV2", () => {...@@ -2585,7 +2585,7 @@ describe("OpenCodeAdapterV2", () => {
2585 Layer.mergeAll(2585 Layer.mergeAll(
2586 IdAllocator.layer,2586 IdAllocator.layer,
2587 McpProviderSessions.layer,2587 McpProviderSessions.layer,
2588 layerTestProviderHost().pipe(Layer.provide(NodeServices.layer)),2588 TestProviderHost.layer().pipe(Layer.provide(NodeServices.layer)),
2589 ),2589 ),
2590 ),2590 ),
2591 ),2591 ),
packages/provider-opencode/src/server/adapter.ts+12-15
...@@ -48,6 +48,7 @@ import * as Layer from "effect/Layer";...@@ -48,6 +48,7 @@ import * as Layer from "effect/Layer";
48import * as Option from "effect/Option";48import * as Option from "effect/Option";
49import * as Queue from "effect/Queue";49import * as Queue from "effect/Queue";
50import * as Random from "effect/Random";50import * as Random from "effect/Random";
51import * as Ref from "effect/Ref";
51import * as Schema from "effect/Schema";52import * as Schema from "effect/Schema";
52import * as Semaphore from "effect/Semaphore";53import * as Semaphore from "effect/Semaphore";
53import * as Scope from "effect/Scope";54import * as Scope from "effect/Scope";
...@@ -88,20 +89,15 @@ export const OPENCODE_DEFAULT_INSTANCE_ID = defaultInstanceIdForDriver(OPENCODE_...@@ -88,20 +89,15 @@ export const OPENCODE_DEFAULT_INSTANCE_ID = defaultInstanceIdForDriver(OPENCODE_
88export const OPENCODE_SDK_PROTOCOL = "opencode-sdk.sse" as const;89export const OPENCODE_SDK_PROTOCOL = "opencode-sdk.sse" as const;
89const DEFAULT_OPENCODE_SETTINGS = Schema.decodeSync(OpenCodeSettings)({});90const DEFAULT_OPENCODE_SETTINGS = Schema.decodeSync(OpenCodeSettings)({});
9091
91let openCodeMessageIdEpochMillis = -1;92const makeOpenCodeMessageId = Effect.fnUntraced(function* (
92let openCodeMessageIdCounter = 0;93 clock: Ref.Ref<{ readonly epochMillis: number; readonly counter: number }>,
9394) {
94const makeOpenCodeMessageId = Effect.fnUntraced(function* () {
95 const epochMillis = DateTime.toEpochMillis(yield* DateTime.now);95 const epochMillis = DateTime.toEpochMillis(yield* DateTime.now);
96 if (epochMillis !== openCodeMessageIdEpochMillis) {96 const counter = yield* Ref.modify(clock, (previous) => {
97 openCodeMessageIdEpochMillis = epochMillis;97 const next = previous.epochMillis === epochMillis ? previous.counter + 1 : 1;
98 openCodeMessageIdCounter = 0;98 return [next, { epochMillis, counter: next }] as const;
99 }99 });
100 openCodeMessageIdCounter += 1;100 const encodedTime = BigInt.asUintN(48, BigInt(epochMillis) * 0x1000n + BigInt(counter))
101 const encodedTime = BigInt.asUintN(
102 48,
103 BigInt(epochMillis) * 0x1000n + BigInt(openCodeMessageIdCounter),
104 )
105 .toString(16)101 .toString(16)
106 .padStart(12, "0");102 .padStart(12, "0");
107 const alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";103 const alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
...@@ -948,6 +944,7 @@ export const makeOpenCodeAdapterV2 = Effect.fn("makeOpenCodeAdapterV2")(function...@@ -948,6 +944,7 @@ export const makeOpenCodeAdapterV2 = Effect.fn("makeOpenCodeAdapterV2")(function
948 const idAllocator = yield* IdAllocator.IdAllocatorV2;944 const idAllocator = yield* IdAllocator.IdAllocatorV2;
949 const host = yield* ProviderHost.ProviderHost;945 const host = yield* ProviderHost.ProviderHost;
950 const mcpSessions = yield* McpProviderSessions.McpProviderSessions;946 const mcpSessions = yield* McpProviderSessions.McpProviderSessions;
947 const messageIdClock = yield* Ref.make({ epochMillis: -1, counter: 0 });
951948
952 return ProviderAdapter.ProviderAdapterV2.of({949 return ProviderAdapter.ProviderAdapterV2.of({
953 instanceId: options.instanceId,950 instanceId: options.instanceId,
...@@ -3170,7 +3167,7 @@ export const makeOpenCodeAdapterV2 = Effect.fn("makeOpenCodeAdapterV2")(function...@@ -3170,7 +3167,7 @@ export const makeOpenCodeAdapterV2 = Effect.fn("makeOpenCodeAdapterV2")(function
3170 startedAt,3167 startedAt,
3171 completedAt: null,3168 completedAt: null,
3172 };3169 };
3173 const admissionMessageId = yield* makeOpenCodeMessageId();3170 const admissionMessageId = yield* makeOpenCodeMessageId(messageIdClock);
3174 // No Effect may be yielded between this check and installing the3171 // No Effect may be yielded between this check and installing the
3175 // turn. If the event stream ended while IDs were being prepared,3172 // turn. If the event stream ended while IDs were being prepared,
3176 // registering afterward would leave a running turn that the EOF3173 // registering afterward would leave a running turn that the EOF
...@@ -3369,7 +3366,7 @@ export const makeOpenCodeAdapterV2 = Effect.fn("makeOpenCodeAdapterV2")(function...@@ -3369,7 +3366,7 @@ export const makeOpenCodeAdapterV2 = Effect.fn("makeOpenCodeAdapterV2")(function
3369 ...files,3366 ...files,
3370 ];3367 ];
3371 turn.admissionGeneration = state.nextAdmissionGeneration++;3368 turn.admissionGeneration = state.nextAdmissionGeneration++;
3372 turn.admissionMessageId = yield* makeOpenCodeMessageId();3369 turn.admissionMessageId = yield* makeOpenCodeMessageId(messageIdClock);
3373 turn.admissionPending = true;3370 turn.admissionPending = true;
3374 turn.admissionAccepted = false;3371 turn.admissionAccepted = false;
3375 turn.admissionMessageObserved = false;3372 turn.admissionMessageObserved = false;
packages/provider-opencode/src/server/driver.test.ts+7-7
...@@ -12,8 +12,8 @@ import * as Effect from "effect/Effect";...@@ -12,8 +12,8 @@ import * as Effect from "effect/Effect";
12import * as Fiber from "effect/Fiber";12import * as Fiber from "effect/Fiber";
13import * as Layer from "effect/Layer";13import * as Layer from "effect/Layer";
14import * as TestClock from "effect/testing/TestClock";14import * as TestClock from "effect/testing/TestClock";
15import { HttpClient } from "effect/http";15import * as HttpClient from "effect/http/HttpClient";
16import { ChildProcessSpawner } from "effect/process";16import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
1717
18import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions";18import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions";
19import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";19import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
...@@ -27,7 +27,7 @@ import {...@@ -27,7 +27,7 @@ import {
27 replayOpenCodeServer,27 replayOpenCodeServer,
28} from "./probeResponses.fixture.ts";28} from "./probeResponses.fixture.ts";
29import { OpenCodeDriver, openCodeUpdateFor } from "./driver.ts";29import { OpenCodeDriver, openCodeUpdateFor } from "./driver.ts";
30import { layerTestProviderHost } from "@t3tools/provider-testing/host";30import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost";
3131
32const serverStarts: Array<string> = [];32const serverStarts: Array<string> = [];
33const reachedServer = (operation: string) =>33const reachedServer = (operation: string) =>
...@@ -46,13 +46,13 @@ const openCode2Runtime = {...@@ -46,13 +46,13 @@ const openCode2Runtime = {
46 runOpenCodeCommand: () => Effect.succeed({ stdout: "opencode v2.0.18\n", stderr: "", code: 0 }),46 runOpenCodeCommand: () => Effect.succeed({ stdout: "opencode v2.0.18\n", stderr: "", code: 0 }),
47 startOpenCodeServerProcess: () => reachedServer("start"),47 startOpenCodeServerProcess: () => reachedServer("start"),
48 connectToOpenCodeServer: () => reachedServer("connect"),48 connectToOpenCodeServer: () => reachedServer("connect"),
49} as unknown as OpenCodeRuntime.OpenCodeRuntimeShape;49} as unknown as OpenCodeRuntime.OpenCodeRuntime["Service"];
5050
51const layer = Layer.mergeAll(51const layer = Layer.mergeAll(
52 IdAllocator.layer,52 IdAllocator.layer,
53 McpProviderSessions.layer,53 McpProviderSessions.layer,
54 ProviderLatestVersions.layer,54 ProviderLatestVersions.layer,
55 layerTestProviderHost(),55 TestProviderHost.layer(),
56 Layer.succeed(56 Layer.succeed(
57 ProviderEventLoggers.ProviderEventLoggers,57 ProviderEventLoggers.ProviderEventLoggers,
58 ProviderEventLoggers.NoOpProviderEventLoggers,58 ProviderEventLoggers.NoOpProviderEventLoggers,
...@@ -211,12 +211,12 @@ const changingRuntime = {...@@ -211,12 +211,12 @@ const changingRuntime = {
211 ),211 ),
212 startOpenCodeServerProcess: () => reachedServer("start"),212 startOpenCodeServerProcess: () => reachedServer("start"),
213 connectToOpenCodeServer: () => reachedServer("connect"),213 connectToOpenCodeServer: () => reachedServer("connect"),
214} as unknown as OpenCodeRuntime.OpenCodeRuntimeShape;214} as unknown as OpenCodeRuntime.OpenCodeRuntime["Service"];
215const layerUpdate = Layer.mergeAll(215const layerUpdate = Layer.mergeAll(
216 IdAllocator.layer,216 IdAllocator.layer,
217 McpProviderSessions.layer,217 McpProviderSessions.layer,
218 ProviderLatestVersions.layer,218 ProviderLatestVersions.layer,
219 layerTestProviderHost(),219 TestProviderHost.layer(),
220 Layer.succeed(220 Layer.succeed(
221 ProviderEventLoggers.ProviderEventLoggers,221 ProviderEventLoggers.ProviderEventLoggers,
222 ProviderEventLoggers.NoOpProviderEventLoggers,222 ProviderEventLoggers.NoOpProviderEventLoggers,
packages/provider-opencode/src/server/driver.ts+2-2
...@@ -22,8 +22,8 @@ import * as FileSystem from "effect/FileSystem";...@@ -22,8 +22,8 @@ import * as FileSystem from "effect/FileSystem";
22import * as Path from "effect/Path";22import * as Path from "effect/Path";
23import * as Schema from "effect/Schema";23import * as Schema from "effect/Schema";
24import * as Stream from "effect/Stream";24import * as Stream from "effect/Stream";
25import { HttpClient } from "effect/http";25import * as HttpClient from "effect/http/HttpClient";
26import { ChildProcessSpawner } from "effect/process";26import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
2727
28import * as OpenCode2TextGeneration from "./v2/textGeneration.ts";28import * as OpenCode2TextGeneration from "./v2/textGeneration.ts";
29import { makeOpenCodeTextGeneration } from "./textGeneration.ts";29import { makeOpenCodeTextGeneration } from "./textGeneration.ts";
packages/provider-opencode/src/server/probeResponses.fixture.ts+2-1
...@@ -3,7 +3,8 @@...@@ -3,7 +3,8 @@
3 * `HttpClient` so the version probe runs against real bytes.3 * `HttpClient` so the version probe runs against real bytes.
4 */4 */
5import * as Effect from "effect/Effect";5import * 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
8interface RecordedResponse {9interface RecordedResponse {
9 readonly status: number;10 readonly status: number;
packages/provider-opencode/src/server/status.test.ts+5-4
...@@ -9,7 +9,8 @@ import * as FileSystem from "effect/FileSystem";...@@ -9,7 +9,8 @@ import * as FileSystem from "effect/FileSystem";
9import * as Layer from "effect/Layer";9import * as Layer from "effect/Layer";
10import * as Schema from "effect/Schema";10import * as Schema from "effect/Schema";
11import * as TestClock from "effect/testing/TestClock";11import * 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";
13import { beforeEach } from "vite-plus/test";14import { beforeEach } from "vite-plus/test";
1415
15import { OpenCodeSettings } from "../settings.ts";16import { OpenCodeSettings } from "../settings.ts";
...@@ -161,7 +162,7 @@ it.effect("keeps Go entitlement absence distinct from failed or malformed usage...@@ -161,7 +162,7 @@ it.effect("keeps Go entitlement absence distinct from failed or malformed usage
161 * are deleted. The snapshot-producing logic they wrapped now lives in the162 * are deleted. The snapshot-producing logic they wrapped now lives in the
162 * standalone `checkOpenCodeProviderStatus(settings, cwd)` Effect, which163 * standalone `checkOpenCodeProviderStatus(settings, cwd)` Effect, which
163 * drivers call directly when building their per-instance snapshot164 * 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,
165 * invoke the check, assert on the returned snapshot.166 * invoke the check, assert on the returned snapshot.
166 */167 */
167168
...@@ -202,7 +203,7 @@ const runtimeMock = {...@@ -202,7 +203,7 @@ const runtimeMock = {
202 },203 },
203};204};
204205
205const OpenCodeRuntimeTestDouble: OpenCodeRuntime.OpenCodeRuntimeShape = {206const OpenCodeRuntimeTestDouble: OpenCodeRuntime.OpenCodeRuntime["Service"] = {
206 startOpenCodeServerProcess: ({ serverPassword, environment }) =>207 startOpenCodeServerProcess: ({ serverPassword, environment }) =>
207 Effect.gen(function* () {208 Effect.gen(function* () {
208 yield* Effect.addFinalizer(() =>209 yield* Effect.addFinalizer(() =>
...@@ -264,7 +265,7 @@ const OpenCodeRuntimeTestDouble: OpenCodeRuntime.OpenCodeRuntimeShape = {...@@ -264,7 +265,7 @@ const OpenCodeRuntimeTestDouble: OpenCodeRuntime.OpenCodeRuntimeShape = {
264 createOpenCodeSdkClient: (input) => {265 createOpenCodeSdkClient: (input) => {
265 runtimeMock.state.sdkClientInputs.push(input);266 runtimeMock.state.sdkClientInputs.push(input);
266 return {} as unknown as ReturnType<267 return {} as unknown as ReturnType<
267 OpenCodeRuntime.OpenCodeRuntimeShape["createOpenCodeSdkClient"]268 OpenCodeRuntime.OpenCodeRuntime["Service"]["createOpenCodeSdkClient"]
268 >;269 >;
269 },270 },
270 loadOpenCodeInventory: () =>271 loadOpenCodeInventory: () =>
packages/provider-opencode/src/server/textGeneration.test.ts+7-5
...@@ -10,7 +10,7 @@ import * as TestClock from "effect/testing/TestClock";...@@ -10,7 +10,7 @@ import * as TestClock from "effect/testing/TestClock";
10import * as NetService from "@t3tools/shared/Net";10import * as NetService from "@t3tools/shared/Net";
11import { beforeEach, expect } from "vite-plus/test";11import { beforeEach, expect } from "vite-plus/test";
1212
13import { layerTestProviderHost } from "@t3tools/provider-testing/host";13import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost";
14import * as OpenCodeRuntime from "./OpenCodeRuntime.ts";14import * as OpenCodeRuntime from "./OpenCodeRuntime.ts";
15import * as OpenCodeServerOwner from "./OpenCodeServerOwner.ts";15import * as OpenCodeServerOwner from "./OpenCodeServerOwner.ts";
16import * as OpenCodeTextGeneration from "./textGeneration.ts";16import * as OpenCodeTextGeneration from "./textGeneration.ts";
...@@ -47,7 +47,7 @@ const runtimeMock = {...@@ -47,7 +47,7 @@ const runtimeMock = {
47 },47 },
48};48};
4949
50const OpenCodeRuntimeTestDouble: OpenCodeRuntime.OpenCodeRuntimeShape = {50const OpenCodeRuntimeTestDouble: OpenCodeRuntime.OpenCodeRuntime["Service"] = {
51 startOpenCodeServerProcess: ({ binaryPath, serverPassword, environment }) =>51 startOpenCodeServerProcess: ({ binaryPath, serverPassword, environment }) =>
52 Effect.gen(function* () {52 Effect.gen(function* () {
53 const index = runtimeMock.state.startCalls.length + 1;53 const index = runtimeMock.state.startCalls.length + 1;
...@@ -128,7 +128,9 @@ const OpenCodeRuntimeTestDouble: OpenCodeRuntime.OpenCodeRuntimeShape = {...@@ -128,7 +128,9 @@ const OpenCodeRuntimeTestDouble: OpenCodeRuntime.OpenCodeRuntimeShape = {
128 );128 );
129 },129 },
130 },130 },
131 }) as unknown as ReturnType<OpenCodeRuntime.OpenCodeRuntimeShape["createOpenCodeSdkClient"]>,131 }) as unknown as ReturnType<
132 OpenCodeRuntime.OpenCodeRuntime["Service"]["createOpenCodeSdkClient"]
133 >,
132 loadOpenCodeInventory: () =>134 loadOpenCodeInventory: () =>
133 Effect.fail(135 Effect.fail(
134 new OpenCodeRuntime.OpenCodeRuntimeError({136 new OpenCodeRuntime.OpenCodeRuntimeError({
...@@ -167,7 +169,7 @@ const layerOpenCodeTextGenerationTest = Layer.succeed(...@@ -167,7 +169,7 @@ const layerOpenCodeTextGenerationTest = Layer.succeed(
167 OpenCodeRuntime.OpenCodeRuntime,169 OpenCodeRuntime.OpenCodeRuntime,
168 OpenCodeRuntimeTestDouble,170 OpenCodeRuntimeTestDouble,
169).pipe(171).pipe(
170 Layer.provideMerge(layerTestProviderHost()),172 Layer.provideMerge(TestProviderHost.layer()),
171 Layer.provideMerge(NetService.layer),173 Layer.provideMerge(NetService.layer),
172 Layer.provideMerge(NodeServices.layer),174 Layer.provideMerge(NodeServices.layer),
173);175);
...@@ -176,7 +178,7 @@ const layerOpenCodeTextGenerationExistingServerTest = Layer.succeed(...@@ -176,7 +178,7 @@ const layerOpenCodeTextGenerationExistingServerTest = Layer.succeed(
176 OpenCodeRuntime.OpenCodeRuntime,178 OpenCodeRuntime.OpenCodeRuntime,
177 OpenCodeRuntimeTestDouble,179 OpenCodeRuntimeTestDouble,
178).pipe(180).pipe(
179 Layer.provideMerge(layerTestProviderHost()),181 Layer.provideMerge(TestProviderHost.layer()),
180 Layer.provideMerge(NetService.layer),182 Layer.provideMerge(NetService.layer),
181 Layer.provideMerge(NodeServices.layer),183 Layer.provideMerge(NodeServices.layer),
182);184);
packages/provider-opencode/src/server/usageLimits.ts+3-1
...@@ -9,7 +9,9 @@ import * as Option from "effect/Option";...@@ -9,7 +9,9 @@ import * as Option from "effect/Option";
9import * as Path from "effect/Path";9import * as Path from "effect/Path";
10import * as Schema from "effect/Schema";10import * as Schema from "effect/Schema";
11import * as Hex from "effect/encoding/Hex";11import * 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
14import {16import {
15 clampPercent,17 clampPercent,
packages/provider-opencode/src/server/v2/OpenCode2Client.live.test.ts+3-2
...@@ -20,8 +20,9 @@ import * as Layer from "effect/Layer";...@@ -20,8 +20,9 @@ import * as Layer from "effect/Layer";
20import * as Option from "effect/Option";20import * as Option from "effect/Option";
21import * as Path from "effect/Path";21import * as Path from "effect/Path";
22import * as Stream from "effect/Stream";22import * as Stream from "effect/Stream";
23import { FetchHttpClient } from "effect/http";23import * as FetchHttpClient from "effect/http/FetchHttpClient";
24import { ChildProcess, ChildProcessSpawner } from "effect/process";24import * as ChildProcess from "effect/process/ChildProcess";
25import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
25import { describe } from "vite-plus/test";26import { describe } from "vite-plus/test";
2627
27import * as OpenCode2Client from "./OpenCode2Client.ts";28import * 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";...@@ -2,7 +2,8 @@ import { assert, it } from "@effect/vitest";
2import * as Effect from "effect/Effect";2import * as Effect from "effect/Effect";
3import * as Layer from "effect/Layer";3import * as Layer from "effect/Layer";
4import * as Redacted from "effect/Redacted";4import * 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";
6import { describe } from "vite-plus/test";7import { describe } from "vite-plus/test";
78
8import * as OpenCode2Client from "./OpenCode2Client.ts";9import * as OpenCode2Client from "./OpenCode2Client.ts";
packages/provider-opencode/src/server/v2/OpenCode2Client.ts+25-21
...@@ -139,26 +139,27 @@ const undecodable = (data: string) =>...@@ -139,26 +139,27 @@ const undecodable = (data: string) =>
139 });139 });
140140
141/** Subscribes to `/api/event`, then streams every frame this build can route. */141/** Subscribes to `/api/event`, then streams every frame this build can route. */
142const readEvents = (httpClient: HttpClient.HttpClient) =>142const readEvents = HttpClient.get("/api/event", {
143 httpClient.get("/api/event", { headers: { accept: "text/event-stream" } }).pipe(143 headers: { accept: "text/event-stream" },
144 Effect.flatMap(HttpClientResponse.filterStatusOk),144}).pipe(
145 Effect.map((response) =>145 Effect.flatMap(HttpClientResponse.filterStatusOk),
146 response.stream.pipe(146 Effect.map((response) =>
147 Stream.timeoutOrElse({147 response.stream.pipe(
148 duration: SILENT_STREAM_TIMEOUT,148 Stream.timeoutOrElse({
149 orElse: () => Stream.fail(new OpenCode2SilentStreamError()),149 duration: SILENT_STREAM_TIMEOUT,
150 }),150 orElse: () => Stream.fail(new OpenCode2SilentStreamError()),
151 Stream.decodeText,151 }),
152 Stream.pipeThroughChannel(Sse.decode()),152 Stream.decodeText,
153 Stream.filterMapEffect((frame) => {153 Stream.pipeThroughChannel(Sse.decode()),
154 const decoded = decodeEvent(frame.data);154 Stream.filterMapEffect((frame) => {
155 return Result.isSuccess(decoded)155 const decoded = decodeEvent(frame.data);
156 ? Effect.succeed(Result.succeed<OpenCode2StreamEvent>(decoded.success))156 return Result.isSuccess(decoded)
157 : undecodable(frame.data);157 ? Effect.succeed(Result.succeed<OpenCode2StreamEvent>(decoded.success))
158 }),158 : undecodable(frame.data);
159 ),159 }),
160 ),160 ),
161 );161 ),
162);
162163
163/**164/**
164 * OpenCode decodes Basic credentials as UTF-8. `HttpClientRequest.basicAuth`165 * OpenCode decodes Basic credentials as UTF-8. `HttpClientRequest.basicAuth`
...@@ -182,8 +183,11 @@ export const make = Effect.gen(function* () {...@@ -182,8 +183,11 @@ export const make = Effect.gen(function* () {
182 Effect.provideService(HttpClient.HttpClient, authenticated),183 Effect.provideService(HttpClient.HttpClient, authenticated),
183 Effect.map((client) => ({184 Effect.map((client) => ({
184 client,185 client,
185 events: readEvents(186 events: readEvents.pipe(
186 HttpClient.mapRequest(authenticated, HttpClientRequest.prependUrl(baseUrl)),187 Effect.provideService(
188 HttpClient.HttpClient,
189 HttpClient.mapRequest(authenticated, HttpClientRequest.prependUrl(baseUrl)),
190 ),
187 ),191 ),
188 })),192 })),
189 );193 );
packages/provider-opencode/src/server/v2/OpenCode2Server.live.test.ts+3-2
...@@ -19,8 +19,9 @@ import * as Path from "effect/Path";...@@ -19,8 +19,9 @@ import * as Path from "effect/Path";
19import * as Redacted from "effect/Redacted";19import * as Redacted from "effect/Redacted";
20import * as Scope from "effect/Scope";20import * as Scope from "effect/Scope";
21import * as Stream from "effect/Stream";21import * as Stream from "effect/Stream";
22import { FetchHttpClient } from "effect/http";22import * as FetchHttpClient from "effect/http/FetchHttpClient";
23import { ChildProcess, ChildProcessSpawner } from "effect/process";23import * as ChildProcess from "effect/process/ChildProcess";
24import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
24import { describe } from "vite-plus/test";25import { describe } from "vite-plus/test";
2526
26import * as OpenCodeRuntime from "../OpenCodeRuntime.ts";27import * 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";...@@ -12,7 +12,9 @@ import * as Layer from "effect/Layer";
12import * as Path from "effect/Path";12import * as Path from "effect/Path";
13import * as Redacted from "effect/Redacted";13import * as Redacted from "effect/Redacted";
14import * as TestClock from "effect/testing/TestClock";14import * 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";
16import { describe } from "vite-plus/test";18import { describe } from "vite-plus/test";
1719
18import * as OpenCodeRuntime from "../OpenCodeRuntime.ts";20import * as OpenCodeRuntime from "../OpenCodeRuntime.ts";
packages/provider-opencode/src/server/v2/textGeneration.ts+4-3
...@@ -40,9 +40,9 @@ type Outcome =...@@ -40,9 +40,9 @@ type Outcome =
40const runOnServer = (40const runOnServer = (
41 connection: OpenCode2Server.OpenCode2Connection,41 connection: OpenCode2Server.OpenCode2Connection,
42 input: TextGenerationOperations.Request<Schema.Top>,42 input: TextGenerationOperations.Request<Schema.Top>,
43 resolveAttachmentPath: ProviderHost.ProviderHost["Service"]["resolveAttachmentPath"],
44) =>43) =>
45 Effect.gen(function* () {44 Effect.gen(function* () {
45 const { resolveAttachmentPath } = yield* ProviderHost.ProviderHost;
46 const { client } = connection;46 const { client } = connection;
47 const parsed = OpenCodeRuntime.parseOpenCodeModelSlug(input.modelSelection.model);47 const parsed = OpenCodeRuntime.parseOpenCodeModelSlug(input.modelSelection.model);
48 if (parsed === null) {48 if (parsed === null) {
...@@ -184,11 +184,12 @@ const runOnServer = (...@@ -184,11 +184,12 @@ const runOnServer = (
184/** Text generation for an instance whose server is OpenCode 2. */184/** Text generation for an instance whose server is OpenCode 2. */
185export const make = Effect.fn("OpenCode2TextGeneration.make")(function* () {185export const make = Effect.fn("OpenCode2TextGeneration.make")(function* () {
186 const server = yield* OpenCode2Server.OpenCode2Server;186 const server = yield* OpenCode2Server.OpenCode2Server;
187 const { resolveAttachmentPath } = yield* ProviderHost.ProviderHost;187 const host = yield* ProviderHost.ProviderHost;
188 const run: TextGenerationOperations.Runner = (input) =>188 const run: TextGenerationOperations.Runner = (input) =>
189 server189 server
190 .withConnection((connection) => runOnServer(connection, input, resolveAttachmentPath))190 .withConnection((connection) => runOnServer(connection, input))
191 .pipe(191 .pipe(
192 Effect.provideService(ProviderHost.ProviderHost, host),
192 Effect.mapError((cause) =>193 Effect.mapError((cause) =>
193 isTextGenerationError(cause)194 isTextGenerationError(cause)
194 ? cause195 ? cause
packages/provider-opencode/src/server/versionProbe.test.ts+5-3
...@@ -1,6 +1,8 @@...@@ -1,6 +1,8 @@
1import { assert, describe, it } from "@effect/vitest";1import { assert, describe, it } from "@effect/vitest";
2import * as Effect from "effect/Effect";2import * 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
5import * as OpenCodeRuntime from "./OpenCodeRuntime.ts";7import * as OpenCodeRuntime from "./OpenCodeRuntime.ts";
6import {8import {
...@@ -16,7 +18,7 @@ import {...@@ -16,7 +18,7 @@ import {
1618
17const noBinary = {19const noBinary = {
18 runOpenCodeCommand: () => Effect.die("A configured server must not run the local binary"),20 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
21const probeServer = (serverUrl: string, serverPassword: string, http: HttpClient.HttpClient) =>23const probeServer = (serverUrl: string, serverPassword: string, http: HttpClient.HttpClient) =>
22 probeOpenCodeRuntime({ binaryPath: "opencode", serverUrl, serverPassword }).pipe(24 probeOpenCodeRuntime({ binaryPath: "opencode", serverUrl, serverPassword }).pipe(
...@@ -172,7 +174,7 @@ describe("OpenCode version probe", () => {...@@ -172,7 +174,7 @@ describe("OpenCode version probe", () => {
172 new OpenCodeRuntime.OpenCodeRuntimeError({ operation: "spawn", detail: "ENOENT" }),174 new OpenCodeRuntime.OpenCodeRuntimeError({ operation: "spawn", detail: "ENOENT" }),
173 );175 );
174 },176 },
175 } as unknown as OpenCodeRuntime.OpenCodeRuntimeShape;177 } as unknown as OpenCodeRuntime.OpenCodeRuntime["Service"];
176 const probe = yield* makeOpenCodeRuntimeProbe(178 const probe = yield* makeOpenCodeRuntimeProbe(
177 probeOpenCodeRuntime({ binaryPath: "opencode", serverUrl: "", serverPassword: "" }).pipe(179 probeOpenCodeRuntime({ binaryPath: "opencode", serverUrl: "", serverPassword: "" }).pipe(
178 Effect.provideService(OpenCodeRuntime.OpenCodeRuntime, runtime),180 Effect.provideService(OpenCodeRuntime.OpenCodeRuntime, runtime),
packages/provider-opencode/src/server/versionProbe.ts+2-1
...@@ -6,7 +6,8 @@ import * as Effect from "effect/Effect";...@@ -6,7 +6,8 @@ import * as Effect from "effect/Effect";
6import * as Exit from "effect/Exit";6import * as Exit from "effect/Exit";
7import * as Option from "effect/Option";7import * as Option from "effect/Option";
8import * as Schema from "effect/Schema";8import * 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
11import * as OpenCodeRuntime from "./OpenCodeRuntime.ts";12import * as OpenCodeRuntime from "./OpenCodeRuntime.ts";
12import { parseGenericCliVersion } from "@t3tools/provider-core/server/snapshotProbe";13import { 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";...@@ -29,9 +29,10 @@ import * as Schema from "effect/Schema";
29import * as Sink from "effect/Sink";29import * as Sink from "effect/Sink";
30import * as Stream from "effect/Stream";30import * as Stream from "effect/Stream";
31import * as TestClock from "effect/testing/TestClock";31import * 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";
35import * as ProviderContinuationRequests from "@t3tools/provider-core/server/ProviderContinuationRequests";36import * as ProviderContinuationRequests from "@t3tools/provider-core/server/ProviderContinuationRequests";
36import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";37import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
37import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";38import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
...@@ -50,7 +51,7 @@ const layerTest = Layer.mergeAll(...@@ -50,7 +51,7 @@ const layerTest = Layer.mergeAll(
50 NodeServices.layer,51 NodeServices.layer,
51 IdAllocator.layer,52 IdAllocator.layer,
52 McpProviderSessions.layer,53 McpProviderSessions.layer,
53 layerTestProviderHost().pipe(Layer.provide(NodeServices.layer)),54 TestProviderHost.layer().pipe(Layer.provide(NodeServices.layer)),
54);55);
5556
56const decodeJsonLine = Schema.decodeSync(Schema.fromJsonString(Schema.Unknown));57const 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";...@@ -58,7 +58,7 @@ import * as Queue from "effect/Queue";
58import * as Schema from "effect/Schema";58import * as Schema from "effect/Schema";
59import * as Semaphore from "effect/Semaphore";59import * as Semaphore from "effect/Semaphore";
60import * as Stream from "effect/Stream";60import * as Stream from "effect/Stream";
61import { ChildProcessSpawner } from "effect/process";61import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
6262
63import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";63import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";
64import { mcpToolPresentation } from "@t3tools/provider-core/server/mcpToolPresentation";64import { 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";...@@ -10,18 +10,19 @@ import * as Schema from "effect/Schema";
10import * as Sink from "effect/Sink";10import * as Sink from "effect/Sink";
11import * as Stream from "effect/Stream";11import * as Stream from "effect/Stream";
12import * as TestClock from "effect/testing/TestClock";12import * as TestClock from "effect/testing/TestClock";
13import { HttpClient } from "effect/http";13import * as HttpClient from "effect/http/HttpClient";
14import { ChildProcess, ChildProcessSpawner } from "effect/process";14import * as ChildProcess from "effect/process/ChildProcess";
15import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
1516
16import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions";17import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions";
17import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";18import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator";
18import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";19import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
19import { layerTestProviderHost } from "@t3tools/provider-testing/host";20import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost";
20import type { PiSettings } from "../settings.ts";21import type { PiSettings } from "../settings.ts";
21import { PiDriver } from "./driver.ts";22import { PiDriver } from "./driver.ts";
2223
23const layerTest = Layer.mergeAll(24const layerTest = Layer.mergeAll(
24 layerTestProviderHost({25 TestProviderHost.layer({
25 cwd: "/machine",26 cwd: "/machine",
26 settings: { ...DEFAULT_SERVER_SETTINGS, enableProviderUpdateChecks: false },27 settings: { ...DEFAULT_SERVER_SETTINGS, enableProviderUpdateChecks: false },
27 runBackgroundWork: false,28 runBackgroundWork: false,
packages/provider-pi/src/server/driver.ts+2-2
...@@ -12,8 +12,8 @@ import * as Effect from "effect/Effect";...@@ -12,8 +12,8 @@ import * as Effect from "effect/Effect";
12import * as FileSystem from "effect/FileSystem";12import * as FileSystem from "effect/FileSystem";
13import * as Path from "effect/Path";13import * as Path from "effect/Path";
14import * as Schema from "effect/Schema";14import * as Schema from "effect/Schema";
15import { HttpClient } from "effect/http";15import * as HttpClient from "effect/http/HttpClient";
16import { ChildProcessSpawner } from "effect/process";16import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
1717
18import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions";18import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions";
19import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";19import * 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";...@@ -26,7 +26,8 @@ import * as Queue from "effect/Queue";
26import * as Scope from "effect/Scope";26import * as Scope from "effect/Scope";
27import * as Schema from "effect/Schema";27import * as Schema from "effect/Schema";
28import * as Stream from "effect/Stream";28import * 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";
30import { HostProcessPlatform } from "@t3tools/shared/hostProcess";31import { HostProcessPlatform } from "@t3tools/shared/hostProcess";
31import { resolveSpawnCommand } from "@t3tools/shared/shell";32import { 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";...@@ -3,7 +3,8 @@ import { assert, describe, it } from "@effect/vitest";
3import * as Effect from "effect/Effect";3import * as Effect from "effect/Effect";
4import * as Sink from "effect/Sink";4import * as Sink from "effect/Sink";
5import * as Stream from "effect/Stream";5import * 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
8import { checkPiProviderStatus, MINIMUM_PI_VERSION } from "./status.ts";9import { 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";...@@ -23,8 +23,9 @@ import * as Stream from "effect/Stream";
23import * as Exit from "effect/Exit";23import * as Exit from "effect/Exit";
24import * as Option from "effect/Option";24import * as Option from "effect/Option";
25import * as Result from "effect/Result";25import * as Result from "effect/Result";
26import { HttpClient } from "effect/http";26import * as HttpClient from "effect/http/HttpClient";
27import { ChildProcess, ChildProcessSpawner } from "effect/process";27import * as ChildProcess from "effect/process/ChildProcess";
28import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
2829
29import { buildPiRpcLaunch, resolvePiLaunchArgs } from "./mcpInjection.ts";30import { buildPiRpcLaunch, resolvePiLaunchArgs } from "./mcpInjection.ts";
30import {31import {
packages/provider-pi/src/server/textGeneration.test.ts+1-1
...@@ -7,7 +7,7 @@ import * as Queue from "effect/Queue";...@@ -7,7 +7,7 @@ import * as Queue from "effect/Queue";
7import * as Schema from "effect/Schema";7import * as Schema from "effect/Schema";
8import * as Sink from "effect/Sink";8import * as Sink from "effect/Sink";
9import * as Stream from "effect/Stream";9import * as Stream from "effect/Stream";
10import { ChildProcessSpawner } from "effect/process";10import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
1111
12import { makePiTextGeneration } from "./textGeneration.ts";12import { makePiTextGeneration } from "./textGeneration.ts";
1313
packages/provider-pi/src/server/textGeneration.ts+1-1
...@@ -8,7 +8,7 @@ import * as Effect from "effect/Effect";...@@ -8,7 +8,7 @@ import * as Effect from "effect/Effect";
8import * as Option from "effect/Option";8import * as Option from "effect/Option";
9import * as Queue from "effect/Queue";9import * as Queue from "effect/Queue";
10import * as Schema from "effect/Schema";10import * as Schema from "effect/Schema";
11import { ChildProcessSpawner } from "effect/process";11import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
1212
13import { TextGenerationError } from "@t3tools/contracts";13import { TextGenerationError } from "@t3tools/contracts";
14import type { PiSettings } from "../settings.ts";14import type { PiSettings } from "../settings.ts";
packages/provider-testing/package.json+10-10
...@@ -3,25 +3,25 @@...@@ -3,25 +3,25 @@
3 "private": true,3 "private": true,
4 "type": "module",4 "type": "module",
5 "exports": {5 "exports": {
6 "./replayTranscript": {6 "./fakeCli": {
7 "types": "./src/replayTranscript.ts",7 "types": "./src/fakeCli.ts",
8 "import": "./src/replayTranscript.ts"8 "import": "./src/fakeCli.ts"
9 },9 },
10 "./replayGate": {10 "./replayGate": {
11 "types": "./src/replayGate.ts",11 "types": "./src/replayGate.ts",
12 "import": "./src/replayGate.ts"12 "import": "./src/replayGate.ts"
13 },13 },
14 "./replayTranscript": {
15 "types": "./src/replayTranscript.ts",
16 "import": "./src/replayTranscript.ts"
17 },
14 "./replayWorkspace": {18 "./replayWorkspace": {
15 "types": "./src/replayWorkspace.ts",19 "types": "./src/replayWorkspace.ts",
16 "import": "./src/replayWorkspace.ts"20 "import": "./src/replayWorkspace.ts"
17 },21 },
18 "./host": {22 "./TestProviderHost": {
19 "types": "./src/host.ts",23 "types": "./src/TestProviderHost.ts",
20 "import": "./src/host.ts"24 "import": "./src/TestProviderHost.ts"
21 },
22 "./fakeCli": {
23 "types": "./src/fakeCli.ts",
24 "import": "./src/fakeCli.ts"
25 }25 }
26 },26 },
27 "scripts": {27 "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";...@@ -4,7 +4,8 @@ import * as FileSystem from "effect/FileSystem";
4import * as Path from "effect/Path";4import * as Path from "effect/Path";
5import * as PlatformError from "effect/PlatformError";5import * as PlatformError from "effect/PlatformError";
6import * as Schema from "effect/Schema";6import * 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
9class ReplayFixtureGitCommandError extends Schema.TaggedError<ReplayFixtureGitCommandError>()(10class ReplayFixtureGitCommandError extends Schema.TaggedError<ReplayFixtureGitCommandError>()(
10 "ReplayFixtureGitCommandError",11 "ReplayFixtureGitCommandError",