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:
Victor Araújo
2026-07-06 15:59:37 -03:00
committed by GitHub
parent c8ada4e76e
commit b3dff19a04
13 changed files with 164 additions and 19 deletions
+2 -2
View File
@@ -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>");
});
});
+2 -2
View File
@@ -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 {
+15 -5
View File
@@ -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));
}