Skip to content
Open
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
5 changes: 5 additions & 0 deletions apps/web/src/components/KeybindingsToast.browser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,11 @@ function createBaseServerConfig(): ServerConfig {
serverPassword: "",
customModels: [],
},
pi: {
enabled: false,
binaryPath: "pi",
customModels: [],
},
},
},
};
Expand Down
3 changes: 2 additions & 1 deletion apps/web/src/components/chat/providerIconUtils.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { ProviderDriverKind } from "@t3tools/contracts";
import { ClaudeAI, CursorIcon, Icon, OpenAI, OpenCodeIcon } from "../Icons";
import { ClaudeAI, CursorIcon, Icon, OpenAI, OpenCodeIcon, PiAgentIcon } from "../Icons";
import { PROVIDER_OPTIONS } from "../../session-logic";

export const PROVIDER_ICON_BY_PROVIDER: Partial<Record<ProviderDriverKind, Icon>> = {
[ProviderDriverKind.make("codex")]: OpenAI,
[ProviderDriverKind.make("claudeAgent")]: ClaudeAI,
[ProviderDriverKind.make("opencode")]: OpenCodeIcon,
[ProviderDriverKind.make("cursor")]: CursorIcon,
[ProviderDriverKind.make("pi")]: PiAgentIcon,
};

function isAvailableProviderOption(option: (typeof PROVIDER_OPTIONS)[number]): option is {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { useSettings, useUpdateSettings } from "../../hooks/useSettings";
import { cn } from "../../lib/utils";
import { normalizeProviderAccentColor } from "../../providerInstances";
import { Button } from "../ui/button";
import { ACPRegistryIcon, Gemini, GithubCopilotIcon, PiAgentIcon, type Icon } from "../Icons";
import { ACPRegistryIcon, Gemini, GithubCopilotIcon, type Icon } from "../Icons";
import {
Dialog,
DialogDescription,
Expand Down Expand Up @@ -86,11 +86,6 @@ const COMING_SOON_DRIVER_OPTIONS: readonly ComingSoonDriverOption[] = [
label: "ACP Registry",
icon: ACPRegistryIcon,
},
{
value: ProviderDriverKind.make("piAgent"),
label: "Pi Agent",
icon: PiAgentIcon,
},
];

/**
Expand Down
7 changes: 7 additions & 0 deletions apps/web/src/components/settings/ProviderSettingsForm.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ describe("ProviderSettingsForm helpers", () => {
});
});

it("exposes Pi as an active configurable driver", () => {
const pi = DRIVER_OPTION_BY_VALUE[ProviderDriverKind.make("pi")];
expect(pi).toBeDefined();
expect(pi?.label).toBe("Pi");
expect(deriveProviderSettingsFields(pi!).map((field) => field.key)).toEqual(["binaryPath"]);
});

it("preserves unknown config keys while omitting empty configurable fields", () => {
const opencode = DRIVER_OPTION_BY_VALUE[ProviderDriverKind.make("opencode")];
expect(opencode).toBeDefined();
Expand Down
10 changes: 9 additions & 1 deletion apps/web/src/components/settings/providerDriverMeta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ import {
CodexSettings,
CursorSettings,
OpenCodeSettings,
PiSettings,
ProviderDriverKind,
} from "@t3tools/contracts";
import type * as Schema from "effect/Schema";
import { ClaudeAI, CursorIcon, type Icon, OpenAI, OpenCodeIcon } from "../Icons";
import { ClaudeAI, CursorIcon, type Icon, OpenAI, OpenCodeIcon, PiAgentIcon } from "../Icons";

type ProviderSettingsSchema = {
readonly fields: Readonly<Record<string, Schema.Top>>;
Expand Down Expand Up @@ -59,6 +60,13 @@ export const PROVIDER_CLIENT_DEFINITIONS: readonly ProviderClientDefinition[] =
icon: OpenCodeIcon,
settingsSchema: OpenCodeSettings,
},
{
value: ProviderDriverKind.make("pi"),
label: "Pi",
icon: PiAgentIcon,
badgeLabel: "Early Access",
settingsSchema: PiSettings,
},
];

export const PROVIDER_CLIENT_DEFINITION_BY_VALUE: Partial<
Expand Down
2 changes: 2 additions & 0 deletions packages/contracts/src/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ const CODEX_DRIVER_KIND = ProviderDriverKind.make("codex");
const CLAUDE_DRIVER_KIND = ProviderDriverKind.make("claudeAgent");
const CURSOR_DRIVER_KIND = ProviderDriverKind.make("cursor");
const OPENCODE_DRIVER_KIND = ProviderDriverKind.make("opencode");
const PI_DRIVER_KIND = ProviderDriverKind.make("pi");

export const DEFAULT_MODEL = "gpt-5.4";
export const DEFAULT_GIT_TEXT_GENERATION_MODEL = "gpt-5.4-mini";
Expand Down Expand Up @@ -200,4 +201,5 @@ export const PROVIDER_DISPLAY_NAMES: Partial<Record<ProviderDriverKind, string>>
[CLAUDE_DRIVER_KIND]: "Claude",
[CURSOR_DRIVER_KIND]: "Cursor",
[OPENCODE_DRIVER_KIND]: "OpenCode",
[PI_DRIVER_KIND]: "Pi",
};
21 changes: 21 additions & 0 deletions packages/contracts/src/providerRuntime.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,27 @@ describe("ProviderRuntimeEvent", () => {
expect(parsed.payload.answers.sandbox_mode).toBe("workspace-write");
});

it("accepts Pi RPC raw event source subcategories", () => {
const parsed = decodeRuntimeEvent({
type: "runtime.warning",
eventId: "event-pi-raw-1",
provider: "pi",
providerInstanceId: "pi",
createdAt: "2026-02-28T00:00:02.000Z",
threadId: "thread-2",
raw: {
source: "pi.rpc.response",
method: "get_state",
payload: { type: "response", command: "get_state", success: true },
},
payload: {
message: "Pi RPC state refreshed.",
},
});

expect(parsed.raw?.source).toBe("pi.rpc.response");
});

it("rejects legacy message.delta type", () => {
expect(() =>
decodeRuntimeEvent({
Expand Down
1 change: 1 addition & 0 deletions packages/contracts/src/providerRuntime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const RuntimeEventRawSource = Schema.Union([
Schema.Literal("opencode.sdk.event"),
Schema.Literal("acp.jsonrpc"),
Schema.TemplateLiteral(["acp.", Schema.String, ".extension"]),
Schema.TemplateLiteral(["pi.rpc.", Schema.String]),
]);
export type RuntimeEventRawSource = typeof RuntimeEventRawSource.Type;

Expand Down
47 changes: 47 additions & 0 deletions packages/contracts/src/settings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,44 @@ describe("ServerSettings.providerInstances (slice-2 invariant)", () => {
});
});

describe("ServerSettings.providers.pi", () => {
it("defaults Pi legacy settings to disabled without requiring an on-disk key", () => {
const decoded = decodeServerSettings({});

expect(decoded.providers.pi).toEqual({
enabled: false,
binaryPath: "pi",
customModels: [],
});
});

it("decodes empty and enabled Pi provider settings", () => {
expect(
decodeServerSettings({
providers: {
pi: {},
},
}).providers.pi,
).toEqual({
enabled: false,
binaryPath: "pi",
customModels: [],
});

expect(
decodeServerSettings({
providers: {
pi: { enabled: true },
},
}).providers.pi,
).toEqual({
enabled: true,
binaryPath: "pi",
customModels: [],
});
});
});

describe("ServerSettingsPatch.providerInstances", () => {
it("treats providerInstances as an optional whole-map replacement", () => {
const patch = decodeServerSettingsPatch({});
Expand Down Expand Up @@ -107,6 +145,9 @@ describe("ServerSettingsPatch string normalization", () => {
binaryPath: " /opt/homebrew/bin/codex ",
homePath: " ~/.codex ",
},
pi: {
binaryPath: " /usr/local/bin/pi ",
},
},
providerInstances: {
codex_personal: {
Expand All @@ -122,6 +163,7 @@ describe("ServerSettingsPatch string normalization", () => {
expect(patch.observability?.otlpTracesUrl).toBe("http://localhost:4318/v1/traces");
expect(patch.providers?.codex?.binaryPath).toBe("/opt/homebrew/bin/codex");
expect(patch.providers?.codex?.homePath).toBe("~/.codex");
expect(patch.providers?.pi?.binaryPath).toBe("/usr/local/bin/pi");
expect(patch.providerInstances?.[ProviderInstanceId.make("codex_personal")]?.driver).toBe(
"codex",
);
Expand All @@ -144,10 +186,15 @@ describe("ServerSettingsPatch string normalization", () => {
...defaultSettings.providers.codex,
binaryPath: " /opt/homebrew/bin/codex ",
},
pi: {
...defaultSettings.providers.pi,
binaryPath: " /usr/local/bin/pi ",
},
},
});

expect(encoded.addProjectBaseDirectory).toBe("~/Development");
expect(encoded.providers?.codex?.binaryPath).toBe("/opt/homebrew/bin/codex");
expect(encoded.providers?.pi?.binaryPath).toBe("/usr/local/bin/pi");
});
});
35 changes: 35 additions & 0 deletions packages/contracts/src/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,33 @@ export const OpenCodeSettings = makeProviderSettingsSchema(
);
export type OpenCodeSettings = typeof OpenCodeSettings.Type;

export const PiSettings = makeProviderSettingsSchema(
{
enabled: Schema.Boolean.pipe(
Schema.withDecodingDefault(Effect.succeed(false)),
Schema.annotateKey({ providerSettingsForm: { hidden: true } }),
),
binaryPath: makeBinaryPathSetting("pi").pipe(
Schema.annotateKey({
title: "Binary path",
description: "Path to the Pi binary.",
providerSettingsForm: {
placeholder: "pi",
clearWhenEmpty: "omit",
},
}),
),
customModels: Schema.Array(Schema.String).pipe(
Schema.withDecodingDefault(Effect.succeed([])),
Schema.annotateKey({ providerSettingsForm: { hidden: true } }),
),
},
{
order: ["binaryPath"],
},
);
export type PiSettings = typeof PiSettings.Type;

export const ObservabilitySettings = Schema.Struct({
otlpTracesUrl: TrimmedString.pipe(Schema.withDecodingDefault(Effect.succeed(""))),
otlpMetricsUrl: TrimmedString.pipe(Schema.withDecodingDefault(Effect.succeed(""))),
Expand Down Expand Up @@ -370,6 +397,7 @@ export const ServerSettings = Schema.Struct({
claudeAgent: ClaudeSettings.pipe(Schema.withDecodingDefault(Effect.succeed({}))),
cursor: CursorSettings.pipe(Schema.withDecodingDefault(Effect.succeed({}))),
opencode: OpenCodeSettings.pipe(Schema.withDecodingDefault(Effect.succeed({}))),
pi: PiSettings.pipe(Schema.withDecodingDefault(Effect.succeed({}))),
}).pipe(Schema.withDecodingDefault(Effect.succeed({}))),
// New driver-agnostic instance map. Keyed by `ProviderInstanceId`; values
// are `ProviderInstanceConfig` envelopes. The driver-specific config blob
Expand Down Expand Up @@ -445,6 +473,12 @@ const OpenCodeSettingsPatch = Schema.Struct({
customModels: Schema.optionalKey(Schema.Array(Schema.String)),
});

const PiSettingsPatch = Schema.Struct({
enabled: Schema.optionalKey(Schema.Boolean),
binaryPath: Schema.optionalKey(TrimmedString),
customModels: Schema.optionalKey(Schema.Array(Schema.String)),
});

export const ServerSettingsPatch = Schema.Struct({
// Server settings
enableAssistantStreaming: Schema.optionalKey(Schema.Boolean),
Expand All @@ -464,6 +498,7 @@ export const ServerSettingsPatch = Schema.Struct({
claudeAgent: Schema.optionalKey(ClaudeSettingsPatch),
cursor: Schema.optionalKey(CursorSettingsPatch),
opencode: Schema.optionalKey(OpenCodeSettingsPatch),
pi: Schema.optionalKey(PiSettingsPatch),
}),
),
// Whole-map replacement for the new instance config. Patching individual
Expand Down
Loading