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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions apps/server/src/provider/Layers/CopilotAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1007,7 +1007,10 @@ export function makeCopilotAdapter(
NodePath.join(directory, ".claude", "skills"),
].filter((candidate) => NodeFS.existsSync(candidate));

const baseClientOptions = makeCopilotClientOptions(copilotSettings, options?.environment);
const baseClientOptions = yield* makeCopilotClientOptions(
copilotSettings,
options?.environment,
);
const client = new CopilotClient({
...baseClientOptions,
...(projectSkillDirectories.length > 0
Expand Down Expand Up @@ -1559,9 +1562,11 @@ export function makeCopilotAdapter(
!Ref.getUnsafe(context.stopped) &&
(input.cwd === undefined || context.session.cwd === input.cwd),
);
// `yield*` on the right of `??` still short-circuits, so a live
// client is reused without resolving the bundled binary path.
const client =
liveContext?.client ??
new CopilotClient(makeCopilotClientOptions(copilotSettings, options?.environment));
new CopilotClient(yield* makeCopilotClientOptions(copilotSettings, options?.environment));
const ephemeral = liveContext === undefined;

const discovered = yield* Effect.tryPromise(async () => {
Expand Down
23 changes: 13 additions & 10 deletions apps/server/src/provider/Layers/CopilotProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import * as Effect from "effect/Effect";
import * as Option from "effect/Option";
import * as Result from "effect/Result";
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
import { HostProcessArchitecture, HostProcessPlatform } from "@t3tools/shared/hostProcess";
import { createModelCapabilities } from "@t3tools/shared/model";
import { resolveSpawnCommand } from "@t3tools/shared/shell";
import {
Expand Down Expand Up @@ -148,8 +149,10 @@ const CAPABILITIES_PROBE_TIMEOUT_MS = 30_000;
* the CLI never starts ("too many arguments. Expected 0 arguments").
* Spawning the native binary sidesteps the host runtime entirely.
*/
export function resolveBundledCopilotBinaryPath(): string | undefined {
const variants = process.platform === "linux" ? ["linux", "linuxmusl"] : [process.platform];
export const resolveBundledCopilotBinaryPath = Effect.gen(function* () {
const platform = yield* HostProcessPlatform;
const architecture = yield* HostProcessArchitecture;
const variants = platform === "linux" ? ["linux", "linuxmusl"] : [platform];

// The platform packages are transitive deps of `@github/copilot-sdk`,
// so with pnpm's strict layout they are only resolvable from the SDK's
Expand All @@ -163,7 +166,7 @@ export function resolveBundledCopilotBinaryPath(): string | undefined {
}

for (const variant of variants) {
const packageName = `@github/copilot-${variant}-${process.arch}`;
const packageName = `@github/copilot-${variant}-${architecture}`;
try {
const binary = require.resolve(packageName);
if (NodeFS.existsSync(binary)) {
Expand All @@ -174,26 +177,26 @@ export function resolveBundledCopilotBinaryPath(): string | undefined {
}
}
return undefined;
}
});

/**
* Build `CopilotClientOptions` shared by the probe, the adapter, and text
* generation. A non-default `binaryPath` routes the SDK at the
* user-installed CLI via `RuntimeConnection.forStdio`; the default routes
* at the bundled native binary (see {@link resolveBundledCopilotBinaryPath}).
*/
export function makeCopilotClientOptions(
export const makeCopilotClientOptions = Effect.fn("makeCopilotClientOptions")(function* (
copilotSettings: CopilotSettings,
environment?: NodeJS.ProcessEnv,
): ConstructorParameters<typeof CopilotClient>[0] {
) {
const env = makeCopilotEnvironment(copilotSettings, environment) as Record<
string,
string | undefined
>;
const cliPath =
copilotSettings.binaryPath !== "copilot"
? copilotSettings.binaryPath
: resolveBundledCopilotBinaryPath();
: yield* resolveBundledCopilotBinaryPath;
return {
env,
...(copilotSettings.homePath.length > 0 ? { baseDirectory: copilotSettings.homePath } : {}),
Expand All @@ -202,8 +205,8 @@ export function makeCopilotClientOptions(
// failures, config parsing, retrieval decisions) on the CLI's stderr,
// which the SDK pipes through as "[CLI subprocess]" lines.
logLevel: (env.COPILOT_DEBUG ? "debug" : "none") as "debug" | "none",
};
}
} satisfies ConstructorParameters<typeof CopilotClient>[0];
});

/**
* SDK-backed capabilities probe: starts a short-lived `CopilotClient`,
Expand All @@ -216,7 +219,7 @@ export const probeCopilotCapabilities = (
environment?: NodeJS.ProcessEnv,
): Effect.Effect<CopilotCapabilitiesProbe | undefined> =>
Effect.gen(function* () {
const client = new CopilotClient(makeCopilotClientOptions(copilotSettings, environment));
const client = new CopilotClient(yield* makeCopilotClientOptions(copilotSettings, environment));
return yield* Effect.tryPromise(async () => {
try {
await client.start();
Expand Down
5 changes: 4 additions & 1 deletion apps/server/src/textGeneration/CopilotTextGeneration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,12 @@ export const makeCopilotTextGeneration = Effect.fn("makeCopilotTextGeneration")(
modelSelection: ModelSelection;
}): Effect.Effect<S["Type"], TextGenerationError, S["DecodingServices"]> =>
Effect.gen(function* () {
// Resolved out here: the options now read host platform/arch from the
// Effect context, which the `async` callback below cannot yield in.
const clientOptions = yield* makeCopilotClientOptions(copilotSettings, environment);
const output = yield* Effect.tryPromise(async () => {
const client = new CopilotClient({
...makeCopilotClientOptions(copilotSettings, environment),
...clientOptions,
workingDirectory: cwd,
});
try {
Expand Down
Loading