feat(FN-3059): align provider metadata and documentation

Merged feat(FN-3059) which aligns provider metadata and documentation across the codebase, updating README and getting-started docs plus refinements to the CustomProviderForm and ProviderIcon dashboard components.

Fusion-Task-Id: FN-3059
This commit is contained in:
Fusion
2026-05-01 17:39:47 -07:00
committed by gsxdsm
parent da19aa2634
commit 4d13ba01ea
15 changed files with 138 additions and 155 deletions

View File

@@ -55,5 +55,4 @@ These tools are **not** part of the pi extension's user-invokable `extension.ts`
| Tool | Purpose | Parameters |
|---|---|---|
| `fn_identity` | Return loaded soul/instructions/memory summary for this heartbeat tick (must be called first) | none |
| `fn_heartbeat_done` | Signal end of heartbeat run with optional summary | `summary?` (string) |

View File

@@ -2,7 +2,7 @@ import { useMemo, useState } from "react";
import type { CustomProviderConfig, CustomProviderModelInput } from "../api";
import "./CustomProviderForm.css";
// Keep in sync with BUILT_IN_PROVIDER_IDS in register-custom-provider-routes.ts
// Reserved built-in IDs (including hidden/deprecated aliases) to prevent custom-provider collisions.
export const BUILT_IN_PROVIDER_IDS = new Set<string>([
"anthropic", "claude-cli", "pi-claude-cli", "openai", "openai-codex", "google", "gemini", "google-antigravity",
"antigravity", "google-vertex", "vertex", "google-cloud-code", "cloud-code", "google-gemini-cli", "google-generative-ai",

View File

@@ -26,6 +26,7 @@ import { DroidCliProviderCard } from "./DroidCliProviderCard";
import { LoginInstructions } from "./LoginInstructions";
import { CustomProviderForm } from "./CustomProviderForm";
import { appendTokenQuery } from "../auth";
import { filterVisibleOnboardingAndSettingsProviders } from "./providerVisibility";
const mapLegacyCustomProviderToConfig = (
provider: CustomProvider | CustomProviderConfig,
@@ -736,12 +737,13 @@ export function ModelOnboardingModal({
const loadAuthStatus = useCallback(async () => {
try {
const { providers, ghCli } = await fetchAuthStatus();
setAuthProviders(providers);
const visibleProviders = filterVisibleOnboardingAndSettingsProviders(providers);
setAuthProviders(visibleProviders);
setGhCliStatus(ghCli);
setLoginInstructions((prev) => {
const next: Record<string, string> = {};
for (const [providerId, instructions] of Object.entries(prev)) {
const provider = providers.find((candidate) => candidate.id === providerId);
const provider = visibleProviders.find((candidate) => candidate.id === providerId);
if (provider && !provider.authenticated && provider.loginInProgress) {
next[providerId] = instructions;
}
@@ -755,7 +757,7 @@ export function ModelOnboardingModal({
if (outcome !== "pending") {
continue;
}
const provider = providers.find((candidate) => candidate.id === providerId);
const provider = visibleProviders.find((candidate) => candidate.id === providerId);
if (!provider?.loginInProgress) {
delete next[providerId];
changed = true;
@@ -766,7 +768,7 @@ export function ModelOnboardingModal({
// Remove from skippedProviders when a provider becomes authenticated
setSkippedProviders((prev) => {
const updated = { ...prev };
for (const p of providers) {
for (const p of visibleProviders) {
if (p.authenticated && updated[p.id]) {
delete updated[p.id];
}
@@ -1129,9 +1131,10 @@ export function ModelOnboardingModal({
try {
const { providers, ghCli } = await fetchAuthStatus();
setAuthProviders(providers);
const visibleProviders = filterVisibleOnboardingAndSettingsProviders(providers);
setAuthProviders(visibleProviders);
setGhCliStatus(ghCli);
const provider = providers.find((p) => p.id === providerId);
const provider = visibleProviders.find((p) => p.id === providerId);
if (provider?.authenticated) {
if (pollIntervalRef.current) {
clearInterval(pollIntervalRef.current);

View File

@@ -659,12 +659,14 @@ const providerConfig: Record<
google: { component: GeminiIcon, color: "var(--provider-gemini)" },
gemini: { component: GeminiIcon, color: "var(--provider-gemini)" }, // Gemini alias family
// Deprecated upstream in pi-coding-agent 0.71+, retained for legacy usage/auth history rendering.
"google-antigravity": { component: GeminiIcon, color: "var(--provider-gemini)", label: "Google Gemini" },
antigravity: { component: GeminiIcon, color: "var(--provider-gemini)", label: "Google Gemini" },
"google-vertex": { component: GeminiIcon, color: "var(--provider-gemini)", label: "Google Vertex AI" },
vertex: { component: GeminiIcon, color: "var(--provider-gemini)", label: "Google Vertex AI" },
"google-cloud-code": { component: GeminiIcon, color: "var(--provider-gemini)", label: "Google Cloud Code" },
"cloud-code": { component: GeminiIcon, color: "var(--provider-gemini)", label: "Google Cloud Code" },
// Deprecated upstream in pi-coding-agent 0.71+, retained for legacy usage/auth history rendering.
"google-gemini-cli": { component: GeminiIcon, color: "var(--provider-gemini)", label: "Google Gemini CLI" },
"google-generative-ai": { component: GeminiIcon, color: "var(--provider-gemini)", label: "Google Generative AI" },

View File

@@ -41,6 +41,7 @@ import { appendTokenQuery } from "../auth";
import { useConfirm } from "../hooks/useConfirm";
import { useNodes } from "../hooks/useNodes";
import { NodeHealthDot } from "./NodeHealthDot";
import { filterVisibleOnboardingAndSettingsProviders } from "./providerVisibility";
// ---------------------------------------------------------------------------
// GitHub star count — fetched once per session, cached in localStorage (1 h).
@@ -654,11 +655,12 @@ export function SettingsModal({
const loadAuthStatus = useCallback(async () => {
try {
const { providers } = await fetchAuthStatus();
setAuthProviders(providers);
const visibleProviders = filterVisibleOnboardingAndSettingsProviders(providers);
setAuthProviders(visibleProviders);
setLoginInstructions((prev) => {
const next: Record<string, string> = {};
for (const [providerId, instructions] of Object.entries(prev)) {
const provider = providers.find((candidate) => candidate.id === providerId);
const provider = visibleProviders.find((candidate) => candidate.id === providerId);
if (provider && !provider.authenticated) {
next[providerId] = instructions;
}
@@ -910,8 +912,9 @@ export function SettingsModal({
pollIntervalRef.current = setInterval(async () => {
try {
const { providers } = await fetchAuthStatus();
setAuthProviders(providers);
const provider = providers.find((p) => p.id === providerId);
const visibleProviders = filterVisibleOnboardingAndSettingsProviders(providers);
setAuthProviders(visibleProviders);
const provider = visibleProviders.find((p) => p.id === providerId);
if (provider?.authenticated) {
if (pollIntervalRef.current) {
clearInterval(pollIntervalRef.current);

View File

@@ -249,6 +249,26 @@ describe("ModelOnboardingModal", () => {
});
});
it("hides deprecated google CLI and antigravity providers while keeping supported Google/Gemini entries", async () => {
mockFetchAuthStatus.mockResolvedValueOnce({
providers: [
{ id: "google", name: "Google", authenticated: false, type: "api_key" },
{ id: "gemini", name: "Gemini", authenticated: false, type: "api_key" },
{ id: "google-antigravity", name: "Google Antigravity", authenticated: false, type: "oauth" },
{ id: "antigravity", name: "Antigravity", authenticated: false, type: "oauth" },
{ id: "google-gemini-cli", name: "Google Gemini CLI", authenticated: false, type: "cli" },
],
});
render(<ModelOnboardingModal onComplete={vi.fn()} addToast={vi.fn()} projectId="proj_123" />);
expect(await screen.findByTestId("onboarding-provider-card-google")).toBeInTheDocument();
expect(screen.getByTestId("onboarding-provider-card-gemini")).toBeInTheDocument();
expect(screen.queryByTestId("onboarding-provider-card-google-antigravity")).not.toBeInTheDocument();
expect(screen.queryByTestId("onboarding-provider-card-antigravity")).not.toBeInTheDocument();
expect(screen.queryByText("Google Gemini CLI")).not.toBeInTheDocument();
});
it("shows Back and Next buttons on middle steps", async () => {
mockFetchAuthStatus.mockResolvedValueOnce({
providers: [

View File

@@ -827,6 +827,27 @@ describe("SettingsModal", () => {
expect(screen.getByTestId("auth-status-openai")).toHaveTextContent("✗ Not connected");
});
it("hides deprecated Google CLI and antigravity auth providers", async () => {
mockFetchAuthStatus.mockResolvedValueOnce({
providers: [
{ id: "google", name: "Google", authenticated: false, type: "api_key" },
{ id: "gemini", name: "Gemini", authenticated: false, type: "api_key" },
{ id: "google-antigravity", name: "Google Antigravity", authenticated: false, type: "oauth" },
{ id: "antigravity", name: "Antigravity", authenticated: false, type: "oauth" },
{ id: "google-gemini-cli", name: "Google Gemini CLI", authenticated: false, type: "cli" },
],
});
renderModal();
await waitForSettingsModalReady();
expect(screen.getByTestId("auth-provider-icon-google")).toBeInTheDocument();
expect(screen.getByTestId("auth-provider-icon-gemini")).toBeInTheDocument();
expect(screen.queryByTestId("auth-provider-icon-google-antigravity")).not.toBeInTheDocument();
expect(screen.queryByTestId("auth-provider-icon-antigravity")).not.toBeInTheDocument();
expect(screen.queryByText("Google Gemini CLI")).not.toBeInTheDocument();
});
it("scrolls settings content to top after OAuth login succeeds", async () => {
const openSpy = vi.spyOn(window, "open").mockImplementation(() => null);
mockLoginProvider.mockResolvedValue({ url: "https://example.com/auth", instructions: "" });

View File

@@ -0,0 +1,17 @@
const HIDDEN_ONBOARDING_AND_SETTINGS_PROVIDER_IDS = new Set([
"google-antigravity",
"antigravity",
"google-gemini-cli",
]);
export function isProviderVisibleInOnboardingAndSettings(providerId: string): boolean {
return !HIDDEN_ONBOARDING_AND_SETTINGS_PROVIDER_IDS.has(providerId);
}
export function filterVisibleOnboardingAndSettingsProviders<T extends { id: string }>(
providers: T[],
): T[] {
return providers.filter((provider) => isProviderVisibleInOnboardingAndSettings(provider.id));
}
export { HIDDEN_ONBOARDING_AND_SETTINGS_PROVIDER_IDS };

View File

@@ -2886,8 +2886,8 @@ describe("HeartbeatMonitor", () => {
expect(callArgs.systemPrompt).toContain("fn_task_document_write");
expect(callArgs.tools).toBe("readonly");
// Tools: fn_task_create, fn_task_log, fn_task_document_write, fn_task_document_read, fn_list_agents, fn_delegate_task,
// fn_memory_search, fn_memory_get, fn_memory_append, fn_identity, fn_heartbeat_done
expect(callArgs.customTools).toHaveLength(11);
// fn_memory_search, fn_memory_get, fn_memory_append, fn_heartbeat_done
expect(callArgs.customTools).toHaveLength(10);
expect(callArgs.customTools![0]!.name).toBe("fn_task_create");
expect(callArgs.customTools![1]!.name).toBe("fn_task_log");
expect(callArgs.customTools![2]!.name).toBe("fn_task_document_write");
@@ -2897,10 +2897,8 @@ describe("HeartbeatMonitor", () => {
expect(callArgs.customTools![6]!.name).toBe("fn_memory_search");
expect(callArgs.customTools![7]!.name).toBe("fn_memory_get");
expect(callArgs.customTools![8]!.name).toBe("fn_memory_append");
// fn_identity appears before fn_heartbeat_done
expect(callArgs.customTools![9]!.name).toBe("fn_identity");
// fn_heartbeat_done is last (terminal tool)
expect(callArgs.customTools![10]!.name).toBe("fn_heartbeat_done");
expect(callArgs.customTools![9]!.name).toBe("fn_heartbeat_done");
});
it("includes memory instructions even when agent has no custom instructions", async () => {
@@ -6053,7 +6051,7 @@ describe("HeartbeatMonitor observability — prompt persistence + run-scoped log
expect(savedRun.heartbeatProcedureSource).toBe("default");
// The execution prompt should contain the procedure text before the no-task action menu
expect(savedRun.executionPrompt).toContain("fn_identity");
expect(savedRun.executionPrompt).toContain("Identity Snapshot");
expect(savedRun.executionPrompt).toContain("Heartbeat Procedure");
// The wake delta header should appear before the action menu items
const procedureIdx = savedRun.executionPrompt!.indexOf("Heartbeat Procedure");
@@ -6114,34 +6112,26 @@ describe("HeartbeatMonitor observability — prompt persistence + run-scoped log
expect(taskDescIdx).toBeGreaterThanOrEqual(0);
expect(procedureIdx).toBeLessThan(taskDescIdx);
// fn_identity instruction should appear in the execution prompt
expect(savedRun.executionPrompt).toContain("fn_identity");
// Identity Snapshot should appear in the execution prompt
expect(savedRun.executionPrompt).toContain("## Identity Snapshot");
expect(result.status).toBe("completed");
});
it("fn_identity tool returns correct agent identity information", async () => {
it("does not register a fn_identity tool (removed in favor of inline snapshot)", async () => {
const store = createStoreWithAgent({ soul: "I am a senior executor.", memory: "Always log blockers." });
let capturedIdentityTool: any;
let capturedTools: any[] | undefined;
const mockSession = createMockAgentSession();
mockedCreateFnAgent.mockImplementation(async (opts: any) => {
capturedIdentityTool = opts.customTools?.find((t: any) => t.name === "fn_identity");
capturedTools = opts.customTools;
return { session: mockSession as any };
});
const monitor = new HeartbeatMonitor({ store, taskStore: mockTaskStore, rootDir: "/tmp" });
await monitor.executeHeartbeat({ agentId: "agent-001", source: "on_demand" });
expect(capturedIdentityTool).toBeDefined();
expect(capturedIdentityTool.name).toBe("fn_identity");
// Call the tool and verify output structure
const toolResult = await capturedIdentityTool.execute("call-1", {});
expect(toolResult.content[0].text).toContain("agentId: agent-001");
expect(toolResult.content[0].text).toContain("name: Test Agent");
expect(toolResult.details.soulPresent).toBe(true);
expect(toolResult.details.memoryPresent).toBe(true);
expect(toolResult.details.soulPreview).toContain("I am a senior executor.");
expect(capturedTools).toBeDefined();
expect(capturedTools!.find((t) => t.name === "fn_identity")).toBeUndefined();
});
it("inlines the Identity Snapshot block into the execution prompt for runtime-agnostic delivery", async () => {
@@ -6167,12 +6157,15 @@ describe("HeartbeatMonitor observability — prompt persistence + run-scoped log
// Snapshot header + identity fields appear in the execution prompt body itself,
// so non-pi runtimes (openclaw/hermes/paperclip) that may not propagate
// customTools still see the agent's identity every tick.
// customTools still see the agent's identity every tick. Snapshot carries
// presence flags + content hashes only — full content lives in the system
// prompt's Custom Instructions section.
expect(exec).toContain("## Identity Snapshot");
expect(exec).toContain("- agentId: agent-001");
expect(exec).toContain("- soul: loaded");
expect(exec).toContain("- memory: loaded");
expect(exec).toContain("I keep momentum across stalled tasks.");
expect(exec).toMatch(/- soul: loaded \(\d+ chars, sha256:[0-9a-f]{8}\)/);
expect(exec).toMatch(/- memory: loaded \(\d+ chars, sha256:[0-9a-f]{8}\)/);
// Snapshot must NOT contain full preview content (that lives in the system prompt)
expect(exec).not.toContain("I keep momentum across stalled tasks.");
// Snapshot must precede the Wake Delta and the Heartbeat Procedure
const snapIdx = exec.indexOf("## Identity Snapshot");

View File

@@ -21,7 +21,8 @@ import type { AgentStore, AgentHeartbeatRun, HeartbeatInvocationSource, AgentHea
import { buildExecutionMemoryInstructions, isEphemeralAgent, hasAgentIdentity } from "@fusion/core";
import type { ToolDefinition } from "@mariozechner/pi-coding-agent";
import { Type, type Static } from "@mariozechner/pi-ai";
import { createTaskCreateTool, createTaskLogToolWithContext, createTaskDocumentWriteTool, createTaskDocumentReadTool, createListAgentsTool, createDelegateTaskTool, createSendMessageTool, createReadMessagesTool, createMemoryTools, createIdentityTool, taskCreateParams } from "./agent-tools.js";
import { createHash } from "node:crypto";
import { createTaskCreateTool, createTaskLogToolWithContext, createTaskDocumentWriteTool, createTaskDocumentReadTool, createListAgentsTool, createDelegateTaskTool, createSendMessageTool, createReadMessagesTool, createMemoryTools, taskCreateParams } from "./agent-tools.js";
import { AgentLogger } from "./agent-logger.js";
import { resolveAgentInstructionsWithRatings, buildSystemPromptWithInstructions, resolveAgentHeartbeatProcedure } from "./agent-instructions.js";
import { heartbeatLog, formatError } from "./logger.js";
@@ -295,9 +296,8 @@ export const HEARTBEAT_PROCEDURE = `## Heartbeat Procedure (run every tick, in o
1. **Identity & context** — review the **Identity Snapshot** at the top of
this prompt. Confirm your role, soul, instructions, and memory match what
you expect, and surface any anomalies in your first text output before
doing anything else. (If fn_identity is available in your runtime you may
also call it for full structured detail; the snapshot above is the
authoritative source.)
doing anything else. The full content is in the Custom Instructions
section of your system prompt.
2. **Inbox** — when fn_read_messages is available, call it. Process any pending
messages first; reply with reply_to_message_id when answering.
3. **Wake delta** — read the Wake Delta block above. The wake reason is the
@@ -328,9 +328,8 @@ export const HEARTBEAT_NO_TASK_PROCEDURE = `## Heartbeat Procedure (run every ti
1. **Identity & context** — review the **Identity Snapshot** at the top of
this prompt. Confirm your role, soul, instructions, and memory match what
you expect, and surface any anomalies in your first text output before
doing anything else. (If fn_identity is available in your runtime you may
also call it for full structured detail; the snapshot above is the
authoritative source.)
doing anything else. The full content is in the Custom Instructions
section of your system prompt.
2. **Inbox** — when fn_read_messages is available, call it. Process any pending
messages first; reply with reply_to_message_id when answering.
3. **Wake delta** — read the Wake Delta block above. The wake reason is the
@@ -372,49 +371,43 @@ function truncatePrompt(text: string, maxChars: number): string {
* wrap external CLIs and may not propagate JS `customTools` callbacks to the
* underlying agent. Embedding the snapshot in the prompt body guarantees the
* agent always sees its identity regardless of runtime tool support.
* `fn_identity` remains available as a richer optional read for runtimes
* that DO support custom tools.
*
* The full soul/instructions/memory content is already loaded in the system
* prompt's Custom Instructions section. The snapshot intentionally carries
* only presence flags + 8-char content hashes — enough to detect drift or
* misload, without paying a multi-KB preview tax on every tick.
*/
function shortContentHash(value: string): string {
return createHash("sha256").update(value).digest("hex").slice(0, 8);
}
function buildIdentitySnapshot(args: {
agent: Agent;
resolvedInstructions: string;
}): string {
const { agent, resolvedInstructions } = args;
const SOUL_PREVIEW = 500;
const INSTR_PREVIEW = 1000;
const MEM_PREVIEW = 1000;
const soulPresent = typeof agent.soul === "string" && agent.soul.trim().length > 0;
const instrPresent = resolvedInstructions.trim().length > 0;
const memPresent = typeof agent.memory === "string" && agent.memory.trim().length > 0;
const soulTrimmed = typeof agent.soul === "string" ? agent.soul.trim() : "";
const instrTrimmed = resolvedInstructions.trim();
const memTrimmed = typeof agent.memory === "string" ? agent.memory.trim() : "";
const lines: string[] = [
const formatField = (trimmed: string): string => {
if (!trimmed) return "absent";
return `loaded (${trimmed.length} chars, sha256:${shortContentHash(trimmed)})`;
};
return [
"## Identity Snapshot",
"",
"Verify these match what you expect. Surface any anomalies in your first text output before acting.",
"Full content is in the Custom Instructions section of your system prompt. Surface anomalies in your first text output before acting.",
"",
`- agentId: ${agent.id}`,
`- name: ${agent.name}`,
`- role: ${agent.role}`,
`- soul: ${soulPresent ? "loaded" : "absent"}`,
`- instructions: ${instrPresent ? "loaded" : "absent"}`,
`- memory: ${memPresent ? "loaded" : "absent"}`,
];
if (soulPresent) {
const preview = (agent.soul as string).trim().slice(0, SOUL_PREVIEW);
lines.push("", `### Soul (first ${SOUL_PREVIEW} chars)`, preview);
}
if (instrPresent) {
const preview = resolvedInstructions.trim().slice(0, INSTR_PREVIEW);
lines.push("", `### Instructions (first ${INSTR_PREVIEW} chars)`, preview);
}
if (memPresent) {
const preview = (agent.memory as string).trim().slice(0, MEM_PREVIEW);
lines.push("", `### Memory (first ${MEM_PREVIEW} chars)`, preview);
}
return lines.join("\n");
`- soul: ${formatField(soulTrimmed)}`,
`- instructions: ${formatField(instrTrimmed)}`,
`- memory: ${formatField(memTrimmed)}`,
].join("\n");
}
async function getHeartbeatMemorySettings(taskStore: TaskStore): Promise<Settings | undefined> {
@@ -1415,9 +1408,6 @@ export class HeartbeatMonitor {
[resolvedInstructionsForIdentity, memoryInstructions].filter((part) => part.trim()).join("\n\n"),
);
// Register fn_identity tool before fn_heartbeat_done (which must stay last)
heartbeatTools.push(createIdentityTool({ agent, resolvedInstructions: resolvedInstructionsForIdentity }));
// fn_heartbeat_done must be the last tool in the array (stable terminal signal)
heartbeatTools.push(heartbeatDoneTool);

View File

@@ -11,7 +11,7 @@ import { appendFile, mkdir, readFile, readdir, stat, writeFile } from "node:fs/p
import { existsSync } from "node:fs";
import { createHash } from "node:crypto";
import { join } from "node:path";
import type { AgentStore, AgentState, AgentCapability, TaskDocument, TaskDocumentCreateInput, TaskStore, RunMutationContext, MessageStore, Message, SourceType, Settings, ResearchRun, ResearchRunStatus, Agent, TaskCreateInput } from "@fusion/core";
import type { AgentStore, AgentState, AgentCapability, TaskDocument, TaskDocumentCreateInput, TaskStore, RunMutationContext, MessageStore, Message, SourceType, Settings, ResearchRun, ResearchRunStatus, TaskCreateInput } from "@fusion/core";
import { dailyMemoryPath, ensureOpenClawMemoryFiles, getMemoryBackendCapabilities, getProjectMemory, isEphemeralAgent, memoryLongTermPath, resolveMemoryBackend, resolveResearchSettings, resolveTitleSummarizerSettingsModel, scheduleQmdProjectMemoryRefresh, searchProjectMemory, shouldSkipBackgroundQmdRefresh, summarizeTitle } from "@fusion/core";
import { ResearchOrchestrator } from "./research-orchestrator.js";
import { ResearchProviderRegistry } from "./research/provider-registry.js";
@@ -1359,78 +1359,3 @@ export function createReadMessagesTool(messageStore: MessageStore, agentId: stri
};
}
/** Arguments for {@link createIdentityTool}. */
export interface CreateIdentityToolArgs {
/** The agent record for this heartbeat run. */
agent: Agent;
/** The resolved instructions string (from resolveAgentInstructionsWithRatings). */
resolvedInstructions: string;
}
/**
* Create the `fn_identity` tool for heartbeat sessions.
*
* When called, it returns a structured summary of which soul, instructions, and
* memory are currently loaded for this tick. The agent is expected to call this
* as its FIRST tool action so operators (via dashboard run logs) can verify
* correct identity was applied.
*/
export function createIdentityTool({ agent, resolvedInstructions }: CreateIdentityToolArgs): ToolDefinition {
const identityParams = Type.Object({});
return {
name: "fn_identity",
label: "Identity Check",
description: "Return a structured summary of which soul, instructions, and memory are loaded for this heartbeat tick. Call this FIRST before any other tool.",
parameters: identityParams,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
execute: async (_id: string, _params: Static<typeof identityParams>, _signal?: any, _onUpdate?: any, _ctx?: any) => {
const PREVIEW_CHARS = 500;
const INSTRUCTIONS_PREVIEW_CHARS = 1000;
const MEMORY_PREVIEW_CHARS = 1000;
const soulPresent = typeof agent.soul === "string" && agent.soul.trim().length > 0;
const instructionsPresent = resolvedInstructions.trim().length > 0;
const memoryPresent = typeof agent.memory === "string" && agent.memory.trim().length > 0;
const soulPreview = soulPresent ? (agent.soul as string).slice(0, PREVIEW_CHARS) : "";
const instructionsPreview = instructionsPresent ? resolvedInstructions.slice(0, INSTRUCTIONS_PREVIEW_CHARS) : "";
const memoryPreview = memoryPresent ? (agent.memory as string).slice(0, MEMORY_PREVIEW_CHARS) : "";
const result = {
agentId: agent.id,
name: agent.name,
role: agent.role,
soulPresent,
instructionsPresent,
memoryPresent,
soulPreview,
instructionsPreview,
memoryPreview,
};
const lines = [
`agentId: ${result.agentId}`,
`name: ${result.name}`,
`role: ${result.role}`,
`soul: ${result.soulPresent ? "loaded" : "absent"}`,
`instructions: ${result.instructionsPresent ? "loaded" : "absent"}`,
`memory: ${result.memoryPresent ? "loaded" : "absent"}`,
];
if (result.soulPresent && result.soulPreview) {
lines.push(`\nSoul preview (first ${PREVIEW_CHARS} chars):\n${result.soulPreview}`);
}
if (result.instructionsPresent && result.instructionsPreview) {
lines.push(`\nInstructions preview (first ${INSTRUCTIONS_PREVIEW_CHARS} chars):\n${result.instructionsPreview}`);
}
if (result.memoryPresent && result.memoryPreview) {
lines.push(`\nMemory preview (first ${MEMORY_PREVIEW_CHARS} chars):\n${result.memoryPreview}`);
}
return {
content: [{ type: "text" as const, text: lines.join("\n") }],
details: result,
};
},
};
}