feat(coding-agent): add InlineExtension type for named inline extension factories (#6267)
* feat(coding-agent): add InlineExtension type for named inline extension factories * test(coding-agent): update utilities and add regression test for InlineExtension
This commit is contained in:
@@ -22,7 +22,7 @@ import { ModelRegistry } from "../../src/core/model-registry.ts";
|
||||
import { SessionManager } from "../../src/core/session-manager.ts";
|
||||
import type { Settings } from "../../src/core/settings-manager.ts";
|
||||
import { SettingsManager } from "../../src/core/settings-manager.ts";
|
||||
import type { ExtensionFactory, ResourceLoader } from "../../src/index.ts";
|
||||
import type { InlineExtension, ResourceLoader } from "../../src/index.ts";
|
||||
import {
|
||||
type CreateTestExtensionsResultInput,
|
||||
createTestExtensionsResult,
|
||||
@@ -69,7 +69,7 @@ export interface HarnessOptions {
|
||||
allowedToolNames?: string[];
|
||||
excludedToolNames?: string[];
|
||||
resourceLoader?: ResourceLoader;
|
||||
extensionFactories?: Array<ExtensionFactory | CreateTestExtensionsResultInput>;
|
||||
extensionFactories?: Array<InlineExtension | CreateTestExtensionsResultInput>;
|
||||
withConfiguredAuth?: boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
import { existsSync, mkdirSync, rmSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import { DefaultResourceLoader } from "../../../src/core/resource-loader.ts";
|
||||
import type { ExtensionAPI } from "../../../src/index.ts";
|
||||
|
||||
const noop: (pi: ExtensionAPI) => void = () => {};
|
||||
|
||||
describe("inline extension naming", () => {
|
||||
const roots: string[] = [];
|
||||
|
||||
function fixture(name: string) {
|
||||
const root = join(tmpdir(), `pi-inline-naming-${name}-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
||||
const cwd = join(root, "project");
|
||||
const agentDir = join(root, "agent");
|
||||
mkdirSync(cwd, { recursive: true });
|
||||
mkdirSync(agentDir, { recursive: true });
|
||||
roots.push(root);
|
||||
return { root, cwd, agentDir };
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
roots.length = 0;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
while (roots.length > 0) {
|
||||
const root = roots.pop();
|
||||
if (root && existsSync(root)) {
|
||||
rmSync(root, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("displays bare factories as <inline:N>", async () => {
|
||||
const { cwd, agentDir } = fixture("bare");
|
||||
const loader = new DefaultResourceLoader({
|
||||
cwd,
|
||||
agentDir,
|
||||
noSkills: true,
|
||||
noPromptTemplates: true,
|
||||
noThemes: true,
|
||||
extensionFactories: [noop, noop],
|
||||
});
|
||||
|
||||
await loader.reload();
|
||||
|
||||
const result = loader.getExtensions();
|
||||
|
||||
expect(result.extensions).toHaveLength(2);
|
||||
expect(result.extensions[0].path).toBe("<inline:1>");
|
||||
expect(result.extensions[1].path).toBe("<inline:2>");
|
||||
});
|
||||
|
||||
it("displays named wrappers as <inline:name>", async () => {
|
||||
const { cwd, agentDir } = fixture("named");
|
||||
const loader = new DefaultResourceLoader({
|
||||
cwd,
|
||||
agentDir,
|
||||
noSkills: true,
|
||||
noPromptTemplates: true,
|
||||
noThemes: true,
|
||||
extensionFactories: [
|
||||
{ name: "my-provider", factory: noop },
|
||||
{ name: "my-commands", factory: noop },
|
||||
],
|
||||
});
|
||||
|
||||
await loader.reload();
|
||||
|
||||
const result = loader.getExtensions();
|
||||
|
||||
expect(result.extensions).toHaveLength(2);
|
||||
expect(result.extensions[0].path).toBe("<inline:my-provider>");
|
||||
expect(result.extensions[1].path).toBe("<inline:my-commands>");
|
||||
});
|
||||
|
||||
it("supports mixed bare and named factories", async () => {
|
||||
const { cwd, agentDir } = fixture("mixed");
|
||||
const loader = new DefaultResourceLoader({
|
||||
cwd,
|
||||
agentDir,
|
||||
noSkills: true,
|
||||
noPromptTemplates: true,
|
||||
noThemes: true,
|
||||
extensionFactories: [noop, { name: "named-ext", factory: noop }, noop],
|
||||
});
|
||||
|
||||
await loader.reload();
|
||||
|
||||
const result = loader.getExtensions();
|
||||
|
||||
expect(result.extensions).toHaveLength(3);
|
||||
expect(result.extensions[0].path).toBe("<inline:1>");
|
||||
expect(result.extensions[1].path).toBe("<inline:named-ext>");
|
||||
expect(result.extensions[2].path).toBe("<inline:3>");
|
||||
});
|
||||
});
|
||||
@@ -32,7 +32,7 @@ import { ModelRegistry } from "../src/core/model-registry.ts";
|
||||
import { SessionManager } from "../src/core/session-manager.ts";
|
||||
import type { Settings } from "../src/core/settings-manager.ts";
|
||||
import { SettingsManager } from "../src/core/settings-manager.ts";
|
||||
import type { ExtensionFactory, ResourceLoader } from "../src/index.ts";
|
||||
import type { InlineExtension, ResourceLoader } from "../src/index.ts";
|
||||
import {
|
||||
type CreateTestExtensionsResultInput,
|
||||
createTestExtensionsResult,
|
||||
@@ -335,7 +335,7 @@ export interface HarnessOptions {
|
||||
/** Optional resource loader override. */
|
||||
resourceLoader?: ResourceLoader;
|
||||
/** Inline extensions to load into the session resource loader. */
|
||||
extensionFactories?: Array<ExtensionFactory | CreateTestExtensionsResultInput>;
|
||||
extensionFactories?: Array<InlineExtension | CreateTestExtensionsResultInput>;
|
||||
}
|
||||
|
||||
export interface Harness {
|
||||
|
||||
@@ -11,7 +11,12 @@ import { getOAuthApiKey } from "@earendil-works/pi-ai/oauth";
|
||||
import { AgentSession } from "../src/core/agent-session.ts";
|
||||
import { AuthStorage } from "../src/core/auth-storage.ts";
|
||||
import { createEventBus } from "../src/core/event-bus.ts";
|
||||
import type { Extension, ExtensionFactory, LoadExtensionsResult } from "../src/core/extensions/index.ts";
|
||||
import type {
|
||||
Extension,
|
||||
ExtensionFactory,
|
||||
InlineExtension,
|
||||
LoadExtensionsResult,
|
||||
} from "../src/core/extensions/index.ts";
|
||||
import { createExtensionRuntime, loadExtensionFromFactory } from "../src/core/extensions/loader.ts";
|
||||
import { ModelRegistry } from "../src/core/model-registry.ts";
|
||||
import type { ResourceLoader } from "../src/core/resource-loader.ts";
|
||||
@@ -181,8 +186,10 @@ export interface CreateTestExtensionsResultInput {
|
||||
path?: string;
|
||||
}
|
||||
|
||||
type TestExtensionInput = InlineExtension | CreateTestExtensionsResultInput;
|
||||
|
||||
export async function createTestExtensionsResult(
|
||||
inputs: Array<ExtensionFactory | CreateTestExtensionsResultInput>,
|
||||
inputs: TestExtensionInput[],
|
||||
cwd = process.cwd(),
|
||||
): Promise<LoadExtensionsResult> {
|
||||
const runtime = createExtensionRuntime();
|
||||
@@ -190,9 +197,12 @@ export async function createTestExtensionsResult(
|
||||
const extensions: Extension[] = [];
|
||||
|
||||
for (const [index, input] of inputs.entries()) {
|
||||
const factory = typeof input === "function" ? input : input.factory;
|
||||
const extensionPath =
|
||||
typeof input === "function" ? `<inline:${index + 1}>` : (input.path ?? `<inline:${index + 1}>`);
|
||||
const isObject = typeof input !== "function";
|
||||
const hasName = isObject && "name" in input;
|
||||
const hasPath = isObject && "path" in input && typeof input.path === "string" && input.path !== "";
|
||||
const factory = isObject ? input.factory : input;
|
||||
const extensionPath = hasName ? `<inline:${input.name}>` : hasPath ? input.path : `<inline:${index + 1}>`;
|
||||
|
||||
extensions.push(await loadExtensionFromFactory(factory, cwd, eventBus, runtime, extensionPath));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user