Skip to content
Closed
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
34 changes: 32 additions & 2 deletions apps/server/src/provider/Layers/GrokAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
type GrokSettings,
EventId,
type ProviderApprovalDecision,
type ProviderOptionSelection,
type ProviderRuntimeEvent,
type ProviderSession,
type ProviderUserInputAnswers,
Expand Down Expand Up @@ -32,6 +33,8 @@ import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawne
import * as EffectAcpErrors from "effect-acp/errors";
import type * as EffectAcpSchema from "effect-acp/schema";

import { getProviderOptionStringSelectionValue } from "@t3tools/shared/model";

import { resolveAttachmentPath } from "../../attachmentStore.ts";
import { ServerConfig } from "../../config.ts";
import * as McpProviderSession from "../../mcp/McpProviderSession.ts";
Expand All @@ -56,6 +59,8 @@ import { makeAcpNativeLoggerFactory } from "../acp/AcpNativeLogging.ts";
import {
applyGrokAcpModelSelection,
currentGrokModelIdFromSessionSetup,
currentGrokReasoningEffortFromSessionSetup,
GROK_REASONING_EFFORT_OPTION_ID,
makeGrokAcpRuntime,
resolveGrokAcpBaseModelId,
} from "../acp/GrokAcpSupport.ts";
Expand Down Expand Up @@ -117,9 +122,24 @@ interface GrokSessionContext {
* continues it, and only the last remaining prompt settles the turn. */
promptsInFlight: number;
currentModelId: string | undefined;
/** Reasoning effort last sent through `session/set_model`. */
currentReasoningEffort: string | undefined;
stopped: boolean;
}

function resolveRequestedGrokReasoningEffort(
modelSelection:
| { readonly options?: ReadonlyArray<ProviderOptionSelection> | null | undefined }
| undefined,
): string | undefined {
return (
getProviderOptionStringSelectionValue(
modelSelection?.options,
GROK_REASONING_EFFORT_OPTION_ID,
) ?? undefined
);
}

function settlePendingApprovalsAsCancelled(
pendingApprovals: ReadonlyMap<ApprovalRequestId, PendingApproval>,
): Effect.Effect<void> {
Expand Down Expand Up @@ -738,13 +758,18 @@ export function makeGrokAdapter(grokSettings: GrokSettings, options?: GrokAdapte
const requestedStartModelId = grokModelSelection?.model
? resolveGrokAcpBaseModelId(grokModelSelection.model)
: undefined;
const boundModelId = yield* applyGrokAcpModelSelection({
const bound = yield* applyGrokAcpModelSelection({
runtime: acp,
currentModelId: currentGrokModelIdFromSessionSetup(started.sessionSetupResult),
requestedModelId: requestedStartModelId,
currentReasoningEffort: currentGrokReasoningEffortFromSessionSetup(
started.sessionSetupResult,
),
requestedReasoningEffort: resolveRequestedGrokReasoningEffort(grokModelSelection),
mapError: (cause) =>
mapAcpToAdapterError(PROVIDER, input.threadId, "session/set_model", cause),
});
const boundModelId = bound.modelId;

const now = yield* nowIso;
const session: ProviderSession = {
Expand Down Expand Up @@ -778,6 +803,7 @@ export function makeGrokAdapter(grokSettings: GrokSettings, options?: GrokAdapte
interruptedTurnIds: new Set(),
promptsInFlight: 0,
currentModelId: boundModelId,
currentReasoningEffort: bound.reasoningEffort,
stopped: false,
};

Expand Down Expand Up @@ -942,13 +968,16 @@ export function makeGrokAdapter(grokSettings: GrokSettings, options?: GrokAdapte
const requestedTurnModelId = turnModelSelection?.model
? resolveGrokAcpBaseModelId(turnModelSelection.model)
: undefined;
const currentModelId = yield* applyGrokAcpModelSelection({
const turnSelection = yield* applyGrokAcpModelSelection({
runtime: ctx.acp,
currentModelId: ctx.currentModelId,
requestedModelId: requestedTurnModelId,
currentReasoningEffort: ctx.currentReasoningEffort,
requestedReasoningEffort: resolveRequestedGrokReasoningEffort(turnModelSelection),
mapError: (cause) =>
mapAcpToAdapterError(PROVIDER, input.threadId, "session/set_model", cause),
});
const currentModelId = turnSelection.modelId;

const text = input.input?.trim();
const imagePromptParts = yield* Effect.forEach(
Expand Down Expand Up @@ -998,6 +1027,7 @@ export function makeGrokAdapter(grokSettings: GrokSettings, options?: GrokAdapte
}

ctx.currentModelId = currentModelId;
ctx.currentReasoningEffort = turnSelection.reasoningEffort;
const displayModel = currentModelId
? resolveGrokAcpBaseModelId(currentModelId)
: undefined;
Expand Down
35 changes: 34 additions & 1 deletion apps/server/src/provider/Layers/GrokProvider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,43 @@ import * as Path from "effect/Path";
import * as Schema from "effect/Schema";
import { GrokSettings } from "@t3tools/contracts";

import { buildInitialGrokProviderSnapshot, checkGrokProviderStatus } from "./GrokProvider.ts";
import {
buildGrokModelCapabilities,
buildInitialGrokProviderSnapshot,
checkGrokProviderStatus,
} from "./GrokProvider.ts";

const decodeGrokSettings = Schema.decodeSync(GrokSettings);

describe("buildGrokModelCapabilities", () => {
it("exposes the reasoning picker advertised by the model metadata", () => {
const capabilities = buildGrokModelCapabilities({
supportsReasoningEffort: true,
reasoningEffort: "high",
reasoningEfforts: [
{ id: "high", value: "high", label: "High Effort", default: true },
{ id: "low", value: "low", label: "Low Effort", default: false },
],
});
expect(capabilities.optionDescriptors).toEqual([
{
id: "reasoningEffort",
label: "Reasoning",
type: "select",
options: [
{ id: "high", label: "High Effort", isDefault: true },
{ id: "low", label: "Low Effort" },
],
currentValue: "high",
},
]);
});

it("exposes no options for models without reasoning metadata", () => {
expect(buildGrokModelCapabilities(null).optionDescriptors).toEqual([]);
});
});

describe("buildInitialGrokProviderSnapshot", () => {
it.effect("returns a disabled snapshot when settings.enabled is false", () =>
Effect.gen(function* () {
Expand Down
30 changes: 28 additions & 2 deletions apps/server/src/provider/Layers/GrokProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { createModelCapabilities } from "@t3tools/shared/model";
import { resolveSpawnCommand } from "@t3tools/shared/shell";

import {
buildSelectOptionDescriptor,
buildServerProvider,
isCommandMissingCause,
parseGenericCliVersion,
Expand All @@ -29,7 +30,12 @@ import {
enrichProviderSnapshotWithVersionAdvisory,
type ProviderMaintenanceCapabilities,
} from "../providerMaintenance.ts";
import { makeGrokAcpRuntime, resolveGrokAcpBaseModelId } from "../acp/GrokAcpSupport.ts";
import {
GROK_REASONING_EFFORT_OPTION_ID,
grokReasoningEffortLevelsFromModelMeta,
makeGrokAcpRuntime,
resolveGrokAcpBaseModelId,
} from "../acp/GrokAcpSupport.ts";

const GROK_PRESENTATION = {
displayName: "Grok",
Expand Down Expand Up @@ -99,6 +105,26 @@ function grokModelsFromSettings(
return providerModelsFromSettings(builtInModels, customModels ?? [], EMPTY_CAPABILITIES);
}

export function buildGrokModelCapabilities(meta: unknown | null | undefined): ModelCapabilities {
const reasoningEffortLevels = grokReasoningEffortLevelsFromModelMeta(meta);
if (reasoningEffortLevels.length === 0) {
return EMPTY_CAPABILITIES;
}
return createModelCapabilities({
optionDescriptors: [
buildSelectOptionDescriptor({
id: GROK_REASONING_EFFORT_OPTION_ID,
label: "Reasoning",
options: reasoningEffortLevels.map((level) => ({
value: level.value,
label: level.label,
...(level.isDefault ? { isDefault: true } : {}),
})),
}),
],
});
}

function buildGrokDiscoveredModelsFromSessionModelState(
modelState: EffectAcpSchema.SessionModelState | null | undefined,
): ReadonlyArray<ServerProviderModel> {
Expand All @@ -117,7 +143,7 @@ function buildGrokDiscoveredModelsFromSessionModelState(
slug,
name: model.name.trim() || slug,
isCustom: false,
capabilities: EMPTY_CAPABILITIES,
capabilities: buildGrokModelCapabilities(model._meta),
};
})
.filter((model): model is ServerProviderModel => model !== undefined);
Expand Down
8 changes: 7 additions & 1 deletion apps/server/src/provider/acp/AcpSessionRuntime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,10 @@ export class AcpSessionRuntime extends Context.Service<
*/
readonly setSessionModel: (
modelId: string,
options?: {
/** Agent-specific request metadata, merged into the request `_meta`. */
readonly meta?: Readonly<Record<string, unknown>>;
},
) => Effect.Effect<EffectAcpSchema.SetSessionModelResponse, EffectAcpErrors.AcpError>;
/**
* Sends a generic ACP extension request and records it through the request logger.
Expand Down Expand Up @@ -789,12 +793,14 @@ export const make = (
Effect.flatMap((started) => setConfigOption(started.modelConfigId ?? "model", model)),
Effect.asVoid,
),
setSessionModel: (modelId) =>
setSessionModel: (modelId, setModelOptions) =>
getStartedState.pipe(
Effect.flatMap((started) => {
const meta = setModelOptions?.meta;
const requestPayload = {
sessionId: started.sessionId,
modelId,
...(meta && Object.keys(meta).length > 0 ? { _meta: meta } : {}),
} satisfies EffectAcpSchema.SetSessionModelRequest;
return runLoggedRequest(
"session/set_model",
Expand Down
Loading
Loading