mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-01 06:02:56 +00:00
feat(providers): add opt-in Hoplite and searchable provider grid (#6953)
Preserve the bundled Hoplite ACP adapter and provisioning transformer, activate it only after Add, and provide a searchable provider grid. Restore the contributor's original icon without the copyright mark. Co-authored-by: Odin Glynn-Martin <odin.glynn@icloud.com>
This commit is contained in:
co-authored by
Odin Glynn-Martin
parent
e08389065c
commit
16bb179e67
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="1.394 1.679 8.494 7.828" fill="currentColor"><path d="M8.237 7.813C8.276 7.789 8.327 7.796 8.358 7.83L9.741 9.346C9.797 9.407 9.754 9.506 9.671 9.507H8.977C8.924 9.507 8.881 9.463 8.881 9.41V9.125C8.881 9.089 8.861 9.057 8.83 9.04L7.589 8.379C7.524 8.344 7.521 8.251 7.584 8.212L8.237 7.813ZM3.944 1.679C4.502 1.679 5.052 1.82 5.54 2.09L5.962 2.323C6.024 2.358 6.029 2.446 5.97 2.486L5.091 3.09C5.065 3.108 5.05 3.137 5.05 3.169V3.495C5.05 3.529 5.032 3.561 5.003 3.578L4.68 3.769C4.594 3.82 4.498 3.722 4.549 3.637L4.592 3.566C4.63 3.502 4.584 3.421 4.51 3.421H4.028C3.925 3.421 3.894 3.562 3.989 3.604L4.377 3.775C4.448 3.806 4.455 3.904 4.389 3.944L3.77 4.323C3.755 4.332 3.737 4.337 3.72 4.337H3.361C3.309 4.337 3.268 4.379 3.268 4.431C3.268 4.484 3.309 4.526 3.361 4.526H3.779C3.797 4.526 3.814 4.521 3.829 4.513L7.599 2.272C8.135 1.954 8.815 2.008 9.294 2.407C9.307 2.418 9.314 2.434 9.314 2.451V2.729C9.315 2.812 9.413 2.856 9.475 2.8L9.728 2.57C9.789 2.514 9.888 2.558 9.888 2.641V5.812C9.888 5.865 9.845 5.908 9.792 5.908H9.359C9.331 5.908 9.303 5.895 9.285 5.873L8.374 4.773C8.337 4.728 8.268 4.728 8.229 4.771L7.468 5.625C7.429 5.669 7.438 5.737 7.487 5.769L9.038 6.776C9.098 6.815 9.096 6.903 9.035 6.939L6.618 8.363C6.556 8.4 6.478 8.358 6.474 8.286L6.439 7.633C6.437 7.578 6.48 7.532 6.535 7.532H6.925C6.948 7.532 6.971 7.523 6.988 7.508L7.211 7.31C7.276 7.252 7.236 7.144 7.148 7.142L5.991 7.118H1.49C1.437 7.118 1.394 7.075 1.394 7.022V3.308C1.394 3.255 1.437 3.212 1.49 3.212H2.544C2.597 3.212 2.64 3.169 2.64 3.116V2.969C2.64 2.916 2.597 2.873 2.544 2.873H1.461C1.366 2.873 1.329 2.75 1.408 2.697L2.134 2.221C2.672 1.867 3.301 1.679 3.944 1.679Z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -0,0 +1,7 @@
|
||||
import type { PluginServerContext } from "@getpaseo/plugin/server";
|
||||
import { createHopliteProvider } from "./server/provider.js";
|
||||
|
||||
export default function contribute(server: PluginServerContext) {
|
||||
server.registerProvider(createHopliteProvider());
|
||||
return () => {};
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{ "id": "hoplite-provider", "requirements": { "paseo": ">=0.11.0" } }
|
||||
@@ -0,0 +1,17 @@
|
||||
import { runAcpProvider } from "@getpaseo/plugin/server/acp";
|
||||
import type { ProviderRegistration } from "@getpaseo/plugin/server/provider";
|
||||
import { runPhaseTransformer } from "./run-phase.js";
|
||||
|
||||
export function createHopliteProvider(): ProviderRegistration {
|
||||
return {
|
||||
requiresConfiguration: true,
|
||||
...runAcpProvider({
|
||||
id: "hoplite",
|
||||
label: "Hoplite",
|
||||
description: "Hoplite's coding agent through the local `hoplite acp` bridge",
|
||||
icon: "icon.svg",
|
||||
command: ["hoplite", "acp"],
|
||||
transformers: [runPhaseTransformer],
|
||||
}),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { runPhaseTransformer } from "./run-phase.js";
|
||||
|
||||
const context = { sessionId: "session-1" };
|
||||
|
||||
// Frames captured from `hoplite acp` 3.0.0.
|
||||
function runPhase(phase: string | null, label: string | null) {
|
||||
return {
|
||||
method: "_hoplite/run_phase",
|
||||
params: {
|
||||
sessionId: "thr_c75d39f7d7624256af918eeee2f4941b",
|
||||
runId: "run_eb213b97bd3147afb2927fbc27f87180",
|
||||
phase,
|
||||
label,
|
||||
startedAt: phase === null ? null : "2026-10-04T17:51:46.311Z",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
describe("runPhaseTransformer", () => {
|
||||
it("shows a provisioning phase as one running workspace item per run", () => {
|
||||
const update = runPhaseTransformer.notification?.(
|
||||
runPhase("fetching_repository", "Cloning repository"),
|
||||
context,
|
||||
);
|
||||
|
||||
expect(update).toEqual({
|
||||
type: "timeline",
|
||||
item: {
|
||||
type: "tool_call",
|
||||
id: "hoplite-run-phase:run_eb213b97bd3147afb2927fbc27f87180",
|
||||
callId: "hoplite-run-phase:run_eb213b97bd3147afb2927fbc27f87180",
|
||||
name: "Hoplite workspace",
|
||||
detail: { type: "plain_text", label: "Cloning repository", icon: "wrench" },
|
||||
status: "running",
|
||||
error: null,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("completes the same item when the run leaves provisioning", () => {
|
||||
const update = runPhaseTransformer.notification?.(runPhase(null, null), context);
|
||||
|
||||
expect(update).toMatchObject({
|
||||
type: "timeline",
|
||||
item: {
|
||||
id: "hoplite-run-phase:run_eb213b97bd3147afb2927fbc27f87180",
|
||||
status: "completed",
|
||||
detail: { label: "Workspace ready" },
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("falls back to the phase name when Hoplite sends no label", () => {
|
||||
const update = runPhaseTransformer.notification?.(
|
||||
runPhase("configuring_workspace", null),
|
||||
context,
|
||||
);
|
||||
|
||||
expect(update).toMatchObject({ item: { detail: { label: "configuring_workspace" } } });
|
||||
});
|
||||
|
||||
it("ignores other vendor notifications and malformed phases", () => {
|
||||
expect(
|
||||
runPhaseTransformer.notification?.({ method: "_hoplite/other", params: {} }, context),
|
||||
).toBeNull();
|
||||
expect(
|
||||
runPhaseTransformer.notification?.(
|
||||
{ method: "_hoplite/run_phase", params: { phase: "x" } },
|
||||
context,
|
||||
),
|
||||
).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
import type { AcpTransformer } from "@getpaseo/plugin/server/acp";
|
||||
import { z } from "zod";
|
||||
|
||||
// Hoplite provisions a cloud workspace before the first agent output of a run and reports each
|
||||
// step as `_hoplite/run_phase`. A null phase ends provisioning for that run.
|
||||
const runPhaseSchema = z.object({
|
||||
runId: z.string().min(1),
|
||||
phase: z.string().nullable(),
|
||||
label: z.string().nullable(),
|
||||
});
|
||||
|
||||
const RUN_PHASE_METHOD = "_hoplite/run_phase";
|
||||
|
||||
export const runPhaseTransformer: AcpTransformer = {
|
||||
notification(notification) {
|
||||
if (notification.method !== RUN_PHASE_METHOD) return null;
|
||||
const params = runPhaseSchema.safeParse(notification.params);
|
||||
if (!params.success) return null;
|
||||
const { runId, phase, label } = params.data;
|
||||
const id = `hoplite-run-phase:${runId}`;
|
||||
const running = phase !== null;
|
||||
return {
|
||||
type: "timeline",
|
||||
item: {
|
||||
type: "tool_call",
|
||||
id,
|
||||
callId: id,
|
||||
name: "Hoplite workspace",
|
||||
detail: {
|
||||
type: "plain_text",
|
||||
label: running ? (label ?? phase) : "Workspace ready",
|
||||
icon: "wrench",
|
||||
},
|
||||
status: running ? "running" : "completed",
|
||||
error: null,
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user