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
58 changes: 58 additions & 0 deletions apps/server/src/provider/acp/AcpRuntimeModel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
parsePermissionRequest,
parseSessionModeState,
parseSessionUpdateEvent,
sessionModelStateFromInitialize,
sessionUpdateCountsAsLoadReplayActivity,
sessionUpdateIsReplay,
syntheticLoadSessionResponseFromInitialize,
Expand Down Expand Up @@ -216,6 +217,63 @@ describe("AcpRuntimeModel", () => {
expect(response.modes?.availableModes).toHaveLength(2);
});

it("keeps initialize mode state whose modes have null descriptions", () => {
const response = syntheticLoadSessionResponseFromInitialize({
protocolVersion: 1,
_meta: {
modeState: {
currentModeId: "code",
availableModes: [
{ id: "ask", name: "Ask", description: null },
{ id: "code", name: "Code", description: "Edit files", _meta: null },
],
_meta: null,
},
},
} satisfies EffectAcpSchema.InitializeResponse);

expect(parseSessionModeState(response)).toEqual({
currentModeId: "code",
availableModes: [
{ id: "ask", name: "Ask" },
{ id: "code", name: "Code", description: "Edit files" },
],
});
});

it("reads initialize model state and ignores malformed model state", () => {
const modelState = {
currentModelId: "grok-4.7",
availableModels: [
{
modelId: "grok-4.7",
name: "Grok 4.7",
description: null,
_meta: { reasoningEfforts: [{ id: "high", default: true }] },
},
{ modelId: "grok-4.6", name: "Grok 4.6" },
],
};

expect(sessionModelStateFromInitialize({ protocolVersion: 1, _meta: { modelState } })).toEqual(
modelState,
);
expect(
sessionModelStateFromInitialize({
protocolVersion: 1,
_meta: {
modelState: { currentModelId: "grok-4.7", availableModels: [{ modelId: "grok-4.7" }] },
},
}),
).toBeUndefined();
expect(
sessionModelStateFromInitialize({
protocolVersion: 1,
_meta: { modelState: { ...modelState, _meta: "not an object" } },
}),
).toBeUndefined();
});

it("projects typed ACP tool call updates into runtime events", () => {
const created = parseSessionUpdateEvent({
sessionId: "session-1",
Expand Down
41 changes: 7 additions & 34 deletions apps/server/src/provider/acp/AcpRuntimeModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import * as Duration from "effect/Duration";
import * as Effect from "effect/Effect";
import * as Option from "effect/Option";
import * as Ref from "effect/Ref";
import type * as EffectAcpSchema from "effect-acp/compat";
import * as Schema from "effect/Schema";
import * as EffectAcpSchema from "effect-acp/compat";
import * as EffectAcpSchemaV1 from "effect-acp/schema-v1";
import {
deriveToolActivityPresentation,
mergeToolActivityData,
Expand All @@ -19,39 +21,10 @@ function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}

function isSessionModelState(value: unknown): value is EffectAcpSchema.SessionModelState {
if (!isRecord(value) || typeof value.currentModelId !== "string") {
return false;
}
if (!Array.isArray(value.availableModels)) {
return false;
}
return value.availableModels.every(
(model) =>
isRecord(model) &&
typeof model.modelId === "string" &&
typeof model.name === "string" &&
(model.description === undefined ||
model.description === null ||
typeof model.description === "string"),
);
}

function isSessionModeState(value: unknown): value is EffectAcpSchema.SessionModeState {
if (!isRecord(value) || typeof value.currentModeId !== "string") {
return false;
}
if (!Array.isArray(value.availableModes)) {
return false;
}
return value.availableModes.every(
(mode) =>
isRecord(mode) &&
typeof mode.id === "string" &&
typeof mode.name === "string" &&
(mode.description === undefined || typeof mode.description === "string"),
);
}
// Guards for the untyped `initialize._meta` states some agents (Grok) advertise.
// Modes were removed from ACP v2, so the v1 wire schema is the source of truth.
const isSessionModelState = Schema.is(EffectAcpSchema.SessionModelState);
const isSessionModeState = Schema.is(EffectAcpSchemaV1.SessionModeState);

export interface AcpSessionMode {
readonly id: string;
Expand Down
Loading