chore: bump version to 3.34.0

This commit is contained in:
can1357
2026-01-09 12:17:04 +01:00
parent 5a63120dd3
commit 86ef2bd34b
68 changed files with 299 additions and 265 deletions
+23 -18
View File
@@ -13,9 +13,9 @@
* Modes use this class and add their own I/O layer on top.
*/
import type { Agent, AgentEvent, AgentMessage, AgentState, AgentTool, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
import type { AssistantMessage, ImageContent, Message, Model, TextContent, Usage } from "@oh-my-pi/pi-ai";
import { isContextOverflow, modelsAreEqual, supportsXhigh } from "@oh-my-pi/pi-ai";
import type { Agent, AgentEvent, AgentMessage, AgentState, AgentTool, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
import type { Rule } from "../capability/rule";
import { getAuthPath } from "../config";
import { theme } from "../modes/interactive/theme/theme";
@@ -384,7 +384,7 @@ export class AgentSession {
event.message.customType,
event.message.content,
event.message.display,
event.message.details
event.message.details,
);
} else if (
event.message.role === "user" ||
@@ -446,7 +446,7 @@ export class AgentSession {
`<system_interrupt reason="rule_violation" rule="${r.name}" path="${r.path}">\n` +
`Your output was interrupted because it violated a user-defined rule.\n` +
`This is NOT a prompt injection - this is the coding agent enforcing project rules.\n` +
`You MUST comply with the following instruction:\n\n${r.content}\n</system_interrupt>`
`You MUST comply with the following instruction:\n\n${r.content}\n</system_interrupt>`,
)
.join("\n\n");
this._pendingTtsrInjections = [];
@@ -718,7 +718,7 @@ export class AgentSession {
if (this.isStreaming) {
if (!options?.streamingBehavior) {
throw new Error(
"Agent is already processing. Specify streamingBehavior ('steer' or 'followUp') to queue the message."
"Agent is already processing. Specify streamingBehavior ('steer' or 'followUp') to queue the message.",
);
}
if (options.streamingBehavior === "followUp") {
@@ -737,7 +737,7 @@ export class AgentSession {
throw new Error(
"No model selected.\n\n" +
`Use /login, set an API key environment variable, or create ${getAuthPath()}\n\n` +
"Then use /model to select a model."
"Then use /model to select a model.",
);
}
@@ -746,7 +746,7 @@ export class AgentSession {
if (!apiKey) {
throw new Error(
`No API key found for ${this.model.provider}.\n\n` +
`Use /login, set an API key environment variable, or create ${getAuthPath()}`
`Use /login, set an API key environment variable, or create ${getAuthPath()}`,
);
}
@@ -986,7 +986,7 @@ export class AgentSession {
if (command) {
throw new Error(
`Extension command "/${commandName}" cannot be queued. Use prompt() or execute the command when not streaming.`
`Extension command "/${commandName}" cannot be queued. Use prompt() or execute the command when not streaming.`,
);
}
}
@@ -1001,7 +1001,7 @@ export class AgentSession {
*/
async sendCustomMessage<T = unknown>(
message: Pick<CustomMessage<T>, "customType" | "content" | "display" | "details">,
options?: { triggerTurn?: boolean; deliverAs?: "steer" | "followUp" | "nextTurn" }
options?: { triggerTurn?: boolean; deliverAs?: "steer" | "followUp" | "nextTurn" },
): Promise<void> {
const appMessage: CustomMessage<T> = {
role: "custom",
@@ -1031,7 +1031,12 @@ export class AgentSession {
}
this.agent.appendMessage(appMessage);
this.sessionManager.appendCustomMessageEntry(message.customType, message.content, message.display, message.details);
this.sessionManager.appendCustomMessageEntry(
message.customType,
message.content,
message.display,
message.details,
);
}
/**
@@ -1176,7 +1181,7 @@ export class AgentSession {
*/
async cycleRoleModels(
roleOrder: string[],
options?: { temporary?: boolean }
options?: { temporary?: boolean },
): Promise<RoleModelCycleResult | undefined> {
const availableModels = this._modelRegistry.getAvailable();
if (availableModels.length === 0) return undefined;
@@ -1189,7 +1194,7 @@ export class AgentSession {
for (const role of roleOrder) {
const roleModelStr =
role === "default"
? this.settingsManager.getModelRole("default") ?? `${currentModel.provider}/${currentModel.id}`
? (this.settingsManager.getModelRole("default") ?? `${currentModel.provider}/${currentModel.id}`)
: this.settingsManager.getModelRole(role);
if (!roleModelStr) continue;
@@ -1454,7 +1459,7 @@ export class AgentSession {
this.model,
apiKey,
customInstructions,
this._compactionAbortController.signal
this._compactionAbortController.signal,
);
summary = result.summary;
firstKeptEntryId = result.firstKeptEntryId;
@@ -1627,7 +1632,7 @@ export class AgentSession {
this.model,
apiKey,
undefined,
this._autoCompactionAbortController.signal
this._autoCompactionAbortController.signal,
);
summary = compactResult.summary;
firstKeptEntryId = compactResult.firstKeptEntryId;
@@ -1684,7 +1689,7 @@ export class AgentSession {
throw new Error(
`Context overflow: ${
error instanceof Error ? error.message : "compaction failed"
}. Your input may be too large for the context window.`
}. Your input may be too large for the context window.`,
);
}
} finally {
@@ -1722,7 +1727,7 @@ export class AgentSession {
const err = message.errorMessage;
// Match: overloaded_error, rate limit, 429, 500, 502, 503, 504, service unavailable, connection error
return /overloaded|rate.?limit|too many requests|429|500|502|503|504|service.?unavailable|server error|internal error|connection.?error/i.test(
err
err,
);
}
@@ -1876,7 +1881,7 @@ export class AgentSession {
async executeBash(
command: string,
onChunk?: (chunk: string) => void,
options?: { excludeFromContext?: boolean }
options?: { excludeFromContext?: boolean },
): Promise<BashResult> {
this._bashAbortController = new AbortController();
@@ -2107,7 +2112,7 @@ export class AgentSession {
*/
async navigateTree(
targetId: string,
options: { summarize?: boolean; customInstructions?: string } = {}
options: { summarize?: boolean; customInstructions?: string } = {},
): Promise<{ editorText?: string; cancelled: boolean; aborted?: boolean; summaryEntry?: BranchSummaryEntry }> {
const oldLeafId = this.sessionManager.getLeafId();
@@ -2130,7 +2135,7 @@ export class AgentSession {
const { entries: entriesToSummarize, commonAncestorId } = collectEntriesForBranchSummary(
this.sessionManager,
oldLeafId,
targetId
targetId,
);
// Prepare event data
@@ -44,7 +44,10 @@ export class AuthStorage {
* @param authPath - Primary path for reading/writing auth.json
* @param fallbackPaths - Additional paths to check when reading (legacy support)
*/
constructor(private authPath: string, private fallbackPaths: string[] = []) {}
constructor(
private authPath: string,
private fallbackPaths: string[] = [],
) {}
/**
* Set a runtime API key override (not persisted to disk).
@@ -173,7 +176,7 @@ export class AuthStorage {
onAuth: (info: { url: string; instructions?: string }) => void;
onPrompt: (prompt: { message: string; placeholder?: string }) => Promise<string>;
onProgress?: (message: string) => void;
}
},
): Promise<void> {
let credentials: OAuthCredentials;
@@ -181,7 +184,7 @@ export class AuthStorage {
case "anthropic":
credentials = await loginAnthropic(
(url) => callbacks.onAuth({ url }),
() => callbacks.onPrompt({ message: "Paste the authorization code:" })
() => callbacks.onPrompt({ message: "Paste the authorization code:" }),
);
break;
case "github-copilot":
@@ -5,9 +5,9 @@
* a summary of the branch being left so context isn't lost.
*/
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
import type { Model } from "@oh-my-pi/pi-ai";
import { completeSimple } from "@oh-my-pi/pi-ai";
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
import branchSummaryPrompt from "../../prompts/branch-summary.md" with { type: "text" };
import branchSummaryPreamble from "../../prompts/branch-summary-preamble.md" with { type: "text" };
import {
@@ -5,9 +5,9 @@
* and after compaction the session is reloaded.
*/
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
import type { AssistantMessage, Model, Usage } from "@oh-my-pi/pi-ai";
import { complete, completeSimple } from "@oh-my-pi/pi-ai";
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
import compactionSummaryPrompt from "../../prompts/compaction-summary.md" with { type: "text" };
import compactionTurnPrefixPrompt from "../../prompts/compaction-turn-prefix.md" with { type: "text" };
import compactionUpdateSummaryPrompt from "../../prompts/compaction-update-summary.md" with { type: "text" };
@@ -2,8 +2,8 @@
* Shared utilities for compaction and branch summarization.
*/
import type { Message } from "@oh-my-pi/pi-ai";
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
import type { Message } from "@oh-my-pi/pi-ai";
import summarizationSystemPrompt from "../../prompts/summarization-system.md" with { type: "text" };
// ============================================================================
@@ -5,8 +5,8 @@
* They can provide custom rendering for tool calls and results in the TUI.
*/
import type { Model } from "@oh-my-pi/pi-ai";
import type { AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
import type { Model } from "@oh-my-pi/pi-ai";
import type { Component } from "@oh-my-pi/pi-tui";
import type { Static, TSchema } from "@sinclair/typebox";
import type { Theme } from "../../modes/interactive/theme/theme";
@@ -140,7 +140,7 @@ export interface CustomTool<TParams extends TSchema = TSchema, TDetails = any> {
params: Static<TParams>,
onUpdate: AgentToolUpdateCallback<TDetails> | undefined,
ctx: CustomToolContext,
signal?: AbortSignal
signal?: AbortSignal,
): Promise<AgentToolResult<TDetails>>;
/** Called on session lifecycle events - use to reconstruct state or cleanup resources */
@@ -154,7 +154,7 @@ export interface CustomTool<TParams extends TSchema = TSchema, TDetails = any> {
/** Factory function that creates a custom tool or array of tools */
export type CustomToolFactory = (
pi: CustomToolAPI
pi: CustomToolAPI,
) => CustomTool<any, any> | CustomTool<any, any>[] | Promise<CustomTool<any, any> | CustomTool<any, any>[]>;
/** Loaded custom tool with metadata and wrapped AgentTool */
@@ -2,8 +2,8 @@
* Extension runner - executes extensions and manages their lifecycle.
*/
import type { ImageContent, Model } from "@oh-my-pi/pi-ai";
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
import type { ImageContent, Model } from "@oh-my-pi/pi-ai";
import type { KeyId } from "@oh-my-pi/pi-tui";
import { theme } from "../../modes/interactive/theme/theme";
import type { ModelRegistry } from "../model-registry";
@@ -53,7 +53,7 @@ export type BranchHandler = (entryId: string) => Promise<{ cancelled: boolean }>
export type NavigateTreeHandler = (
targetId: string,
options?: { summarize?: boolean }
options?: { summarize?: boolean },
) => Promise<{ cancelled: boolean }>;
const noOpUIContext: ExtensionUIContext = {
@@ -94,7 +94,7 @@ export class ExtensionRunner {
extensions: LoadedExtension[],
cwd: string,
sessionManager: SessionManager,
modelRegistry: ModelRegistry
modelRegistry: ModelRegistry,
) {
this.extensions = extensions;
this.uiContext = noOpUIContext;
@@ -215,7 +215,7 @@ export class ExtensionRunner {
if (ExtensionRunner.RESERVED_SHORTCUTS.has(normalizedKey)) {
console.warn(
`Extension shortcut '${key}' from ${shortcut.extensionPath} conflicts with built-in shortcut. Skipping.`
`Extension shortcut '${key}' from ${shortcut.extensionPath} conflicts with built-in shortcut. Skipping.`,
);
continue;
}
@@ -223,7 +223,7 @@ export class ExtensionRunner {
const existing = allShortcuts.get(normalizedKey);
if (existing) {
console.warn(
`Extension shortcut conflict: '${key}' registered by both ${existing.extensionPath} and ${shortcut.extensionPath}. Using ${shortcut.extensionPath}.`
`Extension shortcut conflict: '${key}' registered by both ${existing.extensionPath} and ${shortcut.extensionPath}. Using ${shortcut.extensionPath}.`,
);
}
allShortcuts.set(normalizedKey, shortcut);
@@ -309,7 +309,7 @@ export class ExtensionRunner {
}
private isSessionBeforeEvent(
type: string
type: string,
): type is "session_before_switch" | "session_before_branch" | "session_before_compact" | "session_before_tree" {
return (
type === "session_before_switch" ||
@@ -320,7 +320,7 @@ export class ExtensionRunner {
}
async emit(
event: ExtensionEvent
event: ExtensionEvent,
): Promise<SessionBeforeCompactResult | SessionBeforeTreeResult | ToolResultEventResult | undefined> {
const ctx = this.createContext();
let result: SessionBeforeCompactResult | SessionBeforeTreeResult | ToolResultEventResult | undefined;
@@ -428,7 +428,7 @@ export class ExtensionRunner {
async emitBeforeAgentStart(
prompt: string,
images?: ImageContent[]
images?: ImageContent[],
): Promise<BeforeAgentStartCombinedResult | undefined> {
const ctx = this.createContext();
const messages: NonNullable<BeforeAgentStartEventResult["message"]>[] = [];
@@ -8,8 +8,8 @@
* - Interact with the user via UI primitives
*/
import type { ImageContent, Model, TextContent, ToolResultMessage } from "@oh-my-pi/pi-ai";
import type { AgentMessage, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
import type { ImageContent, Model, TextContent, ToolResultMessage } from "@oh-my-pi/pi-ai";
import type { Component, KeyId, TUI } from "@oh-my-pi/pi-tui";
import type { Static, TSchema } from "@sinclair/typebox";
import type { Theme } from "../../modes/interactive/theme/theme";
@@ -67,8 +67,8 @@ export interface ExtensionUIContext {
factory: (
tui: TUI,
theme: Theme,
done: (result: T) => void
) => (Component & { dispose?(): void }) | Promise<Component & { dispose?(): void }>
done: (result: T) => void,
) => (Component & { dispose?(): void }) | Promise<Component & { dispose?(): void }>,
): Promise<T>;
/** Set the text in the core input editor. */
@@ -178,7 +178,7 @@ export interface ToolDefinition<TParams extends TSchema = TSchema, TDetails = un
params: Static<TParams>,
onUpdate: AgentToolUpdateCallback<TDetails> | undefined,
ctx: ExtensionContext,
signal?: AbortSignal
signal?: AbortSignal,
): Promise<AgentToolResult<TDetails>>;
/** Called on session lifecycle events - use to reconstruct state or cleanup resources */
@@ -490,7 +490,7 @@ export interface MessageRenderOptions {
export type MessageRenderer<T = unknown> = (
message: CustomMessage<T>,
options: MessageRenderOptions,
theme: Theme
theme: Theme,
) => Component | undefined;
// ============================================================================
@@ -535,17 +535,17 @@ export interface ExtensionAPI {
on(event: "session_start", handler: ExtensionHandler<SessionStartEvent>): void;
on(
event: "session_before_switch",
handler: ExtensionHandler<SessionBeforeSwitchEvent, SessionBeforeSwitchResult>
handler: ExtensionHandler<SessionBeforeSwitchEvent, SessionBeforeSwitchResult>,
): void;
on(event: "session_switch", handler: ExtensionHandler<SessionSwitchEvent>): void;
on(
event: "session_before_branch",
handler: ExtensionHandler<SessionBeforeBranchEvent, SessionBeforeBranchResult>
handler: ExtensionHandler<SessionBeforeBranchEvent, SessionBeforeBranchResult>,
): void;
on(event: "session_branch", handler: ExtensionHandler<SessionBranchEvent>): void;
on(
event: "session_before_compact",
handler: ExtensionHandler<SessionBeforeCompactEvent, SessionBeforeCompactResult>
handler: ExtensionHandler<SessionBeforeCompactEvent, SessionBeforeCompactResult>,
): void;
on(event: "session_compact", handler: ExtensionHandler<SessionCompactEvent>): void;
on(event: "session_shutdown", handler: ExtensionHandler<SessionShutdownEvent>): void;
@@ -580,7 +580,7 @@ export interface ExtensionAPI {
options: {
description?: string;
handler: (ctx: ExtensionContext) => Promise<void> | void;
}
},
): void;
/** Register a CLI flag. */
@@ -590,7 +590,7 @@ export interface ExtensionAPI {
description?: string;
type: "boolean" | "string";
default?: boolean | string;
}
},
): void;
/** Get the value of a registered CLI flag. */
@@ -610,7 +610,7 @@ export interface ExtensionAPI {
/** Send a custom message to the session. */
sendMessage<T = unknown>(
message: Pick<CustomMessage<T>, "customType" | "content" | "display" | "details">,
options?: { triggerTurn?: boolean; deliverAs?: "steer" | "followUp" | "nextTurn" }
options?: { triggerTurn?: boolean; deliverAs?: "steer" | "followUp" | "nextTurn" },
): void;
/** Append a custom entry to the session for state persistence (not sent to LLM). */
@@ -663,7 +663,7 @@ type HandlerFn = (...args: unknown[]) => Promise<unknown>;
export type SendMessageHandler = <T = unknown>(
message: Pick<CustomMessage<T>, "customType" | "content" | "display" | "details">,
options?: { triggerTurn?: boolean; deliverAs?: "steer" | "followUp" | "nextTurn" }
options?: { triggerTurn?: boolean; deliverAs?: "steer" | "followUp" | "nextTurn" },
) => void;
export type AppendEntryHandler = <T = unknown>(customType: string, data?: T) => void;
@@ -2,8 +2,8 @@
* Tool wrappers for extensions.
*/
import type { ImageContent, TextContent } from "@oh-my-pi/pi-ai";
import type { AgentTool, AgentToolContext, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
import type { ImageContent, TextContent } from "@oh-my-pi/pi-ai";
import type { Theme } from "../../modes/interactive/theme/theme";
import type { ExtensionRunner } from "./runner";
import type { ExtensionContext, RegisteredTool, ToolCallEventResult, ToolResultEventResult } from "./types";
@@ -26,7 +26,7 @@ export function wrapRegisteredTool(registeredTool: RegisteredTool, getContext: (
definition.renderResult?.(
result,
{ expanded: options.expanded, isPartial: options.isPartial, spinnerFrame: options.spinnerFrame },
theme as Theme
theme as Theme,
)
: undefined,
};
@@ -37,7 +37,7 @@ export function wrapRegisteredTool(registeredTool: RegisteredTool, getContext: (
*/
export function wrapRegisteredTools(
registeredTools: RegisteredTool[],
getContext: () => ExtensionContext
getContext: () => ExtensionContext,
): AgentTool[] {
return registeredTools.map((rt) => wrapRegisteredTool(rt, getContext));
}
@@ -55,7 +55,7 @@ export function wrapToolWithExtensions<T>(tool: AgentTool<any, T>, runner: Exten
params: Record<string, unknown>,
signal?: AbortSignal,
onUpdate?: AgentToolUpdateCallback<T>,
context?: AgentToolContext
context?: AgentToolContext,
) => {
// Emit tool_call event - extensions can block execution
if (runner.hasHandlers("tool_call")) {
@@ -113,7 +113,8 @@ export function wrapToolWithExtensions<T>(tool: AgentTool<any, T>, runner: Exten
if (resultResult.isError === true && !executionError) {
// Extension marks a successful result as error
const textBlocks = (modifiedContent ?? []).filter((c): c is TextContent => c.type === "text");
const errorText = textBlocks.map((t) => t.text).join("\n") || "Tool result marked as error by extension";
const errorText =
textBlocks.map((t) => t.text).join("\n") || "Tool result marked as error by extension";
throw new Error(errorText);
}
if (resultResult.isError === false && executionError) {
@@ -2,8 +2,8 @@
* Hook runner - executes hooks and manages their lifecycle.
*/
import type { Model } from "@oh-my-pi/pi-ai";
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
import type { Model } from "@oh-my-pi/pi-ai";
import { theme } from "../../modes/interactive/theme/theme";
import type { ModelRegistry } from "../model-registry";
import type { SessionManager } from "../session-manager";
@@ -272,7 +272,7 @@ export class HookRunner {
* Check if event type is a session "before_*" event that can be cancelled.
*/
private isSessionBeforeEvent(
type: string
type: string,
): type is "session_before_switch" | "session_before_branch" | "session_before_compact" | "session_before_tree" {
return (
type === "session_before_switch" ||
@@ -287,7 +287,7 @@ export class HookRunner {
* Returns the result from session before_* / tool_result events (if any handler returns one).
*/
async emit(
event: HookEvent
event: HookEvent,
): Promise<SessionBeforeCompactResult | SessionBeforeTreeResult | ToolResultEventResult | undefined> {
const ctx = this.createContext();
let result: SessionBeforeCompactResult | SessionBeforeTreeResult | ToolResultEventResult | undefined;
@@ -400,7 +400,7 @@ export class HookRunner {
*/
async emitBeforeAgentStart(
prompt: string,
images?: import("@oh-my-pi/pi-ai").ImageContent[]
images?: import("@oh-my-pi/pi-ai").ImageContent[],
): Promise<BeforeAgentStartEventResult | undefined> {
const ctx = this.createContext();
let result: BeforeAgentStartEventResult | undefined;
@@ -5,8 +5,8 @@
* and interact with the user via UI primitives.
*/
import type { ImageContent, Message, Model, TextContent, ToolResultMessage } from "@oh-my-pi/pi-ai";
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
import type { ImageContent, Message, Model, TextContent, ToolResultMessage } from "@oh-my-pi/pi-ai";
import type { Component, TUI } from "@oh-my-pi/pi-tui";
import type { Theme } from "../../modes/interactive/theme/theme";
import type { CompactionPreparation, CompactionResult } from "../compaction/index";
@@ -97,8 +97,8 @@ export interface HookUIContext {
factory: (
tui: TUI,
theme: Theme,
done: (result: T) => void
) => (Component & { dispose?(): void }) | Promise<Component & { dispose?(): void }>
done: (result: T) => void,
) => (Component & { dispose?(): void }) | Promise<Component & { dispose?(): void }>,
): Promise<T>;
/**
@@ -632,7 +632,7 @@ export interface HookMessageRenderOptions {
export type HookMessageRenderer<T = unknown> = (
message: HookMessage<T>,
options: HookMessageRenderOptions,
theme: Theme
theme: Theme,
) => Component | undefined;
/**
@@ -658,7 +658,7 @@ export interface HookAPI {
on(event: "session_branch", handler: HookHandler<SessionBranchEvent>): void;
on(
event: "session_before_compact",
handler: HookHandler<SessionBeforeCompactEvent, SessionBeforeCompactResult>
handler: HookHandler<SessionBeforeCompactEvent, SessionBeforeCompactResult>,
): void;
on(event: "session_compact", handler: HookHandler<SessionCompactEvent>): void;
on(event: "session_shutdown", handler: HookHandler<SessionShutdownEvent>): void;
@@ -692,7 +692,7 @@ export interface HookAPI {
*/
sendMessage<T = unknown>(
message: Pick<HookMessage<T>, "customType" | "content" | "display" | "details">,
triggerTurn?: boolean
triggerTurn?: boolean,
): void;
/**
+3 -3
View File
@@ -5,8 +5,8 @@
* and provides a transformer to convert them to LLM-compatible messages.
*/
import type { ImageContent, Message, TextContent } from "@oh-my-pi/pi-ai";
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
import type { ImageContent, Message, TextContent } from "@oh-my-pi/pi-ai";
export const COMPACTION_SUMMARY_PREFIX = `The conversation history before this point was compacted into the following summary:
@@ -136,7 +136,7 @@ export function createBranchSummaryMessage(summary: string, fromId: string, time
export function createCompactionSummaryMessage(
summary: string,
tokensBefore: number,
timestamp: string
timestamp: string,
): CompactionSummaryMessage {
return {
role: "compactionSummary",
@@ -152,7 +152,7 @@ export function createCustomMessage(
content: string | (TextContent | ImageContent)[],
display: boolean,
details: unknown | undefined,
timestamp: string
timestamp: string,
): CustomMessage {
return {
role: "custom",
@@ -39,7 +39,7 @@ const ModelDefinitionSchema = Type.Object({
Type.Literal("anthropic-messages"),
Type.Literal("google-generative-ai"),
Type.Literal("google-vertex"),
])
]),
),
reasoning: Type.Boolean(),
input: Type.Array(Type.Union([Type.Literal("text"), Type.Literal("image")])),
@@ -66,7 +66,7 @@ const ProviderConfigSchema = Type.Object({
Type.Literal("anthropic-messages"),
Type.Literal("google-generative-ai"),
Type.Literal("google-vertex"),
])
]),
),
headers: Type.Optional(Type.Record(Type.String(), Type.String())),
authHeader: Type.Optional(Type.Boolean()),
@@ -126,7 +126,7 @@ export class ModelRegistry {
constructor(
readonly authStorage: AuthStorage,
private modelsJsonPath: string | undefined = undefined,
private fallbackPaths: string[] = []
private fallbackPaths: string[] = [],
) {
// Set up fallback resolver for custom provider API keys
this.authStorage.setFallbackResolver((provider) => {
@@ -189,7 +189,9 @@ export class ModelRegistry {
// Update github-copilot base URL based on OAuth credentials
const copilotCred = this.authStorage.get("github-copilot");
if (copilotCred?.type === "oauth") {
const domain = copilotCred.enterpriseUrl ? normalizeDomain(copilotCred.enterpriseUrl) ?? undefined : undefined;
const domain = copilotCred.enterpriseUrl
? (normalizeDomain(copilotCred.enterpriseUrl) ?? undefined)
: undefined;
const baseUrl = getGitHubCopilotBaseUrl(copilotCred.access, domain);
this.models = combined.map((m) => (m.provider === "github-copilot" ? { ...m, baseUrl } : m));
} else {
@@ -265,7 +267,7 @@ export class ModelRegistry {
return emptyCustomModelsResult(`Failed to parse models.json: ${error.message}\n\nFile: ${modelsJsonPath}`);
}
return emptyCustomModelsResult(
`Failed to load models.json: ${error instanceof Error ? error.message : error}\n\nFile: ${modelsJsonPath}`
`Failed to load models.json: ${error instanceof Error ? error.message : error}\n\nFile: ${modelsJsonPath}`,
);
}
}
@@ -279,7 +281,7 @@ export class ModelRegistry {
// Override-only config: just needs baseUrl (to override built-in)
if (!providerConfig.baseUrl) {
throw new Error(
`Provider ${providerName}: must specify either "baseUrl" (for override) or "models" (for replacement).`
`Provider ${providerName}: must specify either "baseUrl" (for override) or "models" (for replacement).`,
);
}
} else {
@@ -297,7 +299,7 @@ export class ModelRegistry {
if (!hasProviderApi && !hasModelApi) {
throw new Error(
`Provider ${providerName}, model ${modelDef.id}: no "api" specified. Set at provider or model level.`
`Provider ${providerName}, model ${modelDef.id}: no "api" specified. Set at provider or model level.`,
);
}
@@ -329,7 +331,9 @@ export class ModelRegistry {
// Merge headers: provider headers are base, model headers override
let headers =
providerConfig.headers || modelDef.headers ? { ...providerConfig.headers, ...modelDef.headers } : undefined;
providerConfig.headers || modelDef.headers
? { ...providerConfig.headers, ...modelDef.headers }
: undefined;
// If authHeader is true, add Authorization header with resolved API key
if (providerConfig.authHeader && providerConfig.apiKey) {
@@ -2,8 +2,8 @@
* Model resolution, scoping, and initial selection
*/
import { type Api, type KnownProvider, type Model, modelsAreEqual } from "@oh-my-pi/pi-ai";
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
import { type Api, type KnownProvider, type Model, modelsAreEqual } from "@oh-my-pi/pi-ai";
import chalk from "chalk";
import { minimatch } from "minimatch";
import { isValidThinkingLevel } from "../cli/args";
@@ -25,6 +25,7 @@ export const defaultModelPerProvider: Record<KnownProvider, string> = {
cerebras: "zai-glm-4.6",
zai: "glm-4.6",
mistral: "devstral-medium-latest",
opencode: "claude-sonnet-4-5",
};
export interface ScopedModel {
@@ -79,7 +80,7 @@ function tryMatchModel(modelPattern: string, availableModels: Model<Api>[]): Mod
const provider = modelPattern.substring(0, slashIndex);
const modelId = modelPattern.substring(slashIndex + 1);
const providerMatch = availableModels.find(
(m) => m.provider.toLowerCase() === provider.toLowerCase() && m.id.toLowerCase() === modelId.toLowerCase()
(m) => m.provider.toLowerCase() === provider.toLowerCase() && m.id.toLowerCase() === modelId.toLowerCase(),
);
if (providerMatch) {
return providerMatch;
@@ -97,7 +98,7 @@ function tryMatchModel(modelPattern: string, availableModels: Model<Api>[]): Mod
const matches = availableModels.filter(
(m) =>
m.id.toLowerCase().includes(modelPattern.toLowerCase()) ||
m.name?.toLowerCase().includes(modelPattern.toLowerCase())
m.name?.toLowerCase().includes(modelPattern.toLowerCase()),
);
if (matches.length === 0) {
@@ -351,7 +352,7 @@ export async function restoreModelFromSession(
savedModelId: string,
currentModel: Model<Api> | undefined,
shouldPrintMessages: boolean,
modelRegistry: ModelRegistry
modelRegistry: ModelRegistry,
): Promise<{ model: Model<Api> | undefined; fallbackMessage: string | undefined }> {
const restoredModel = modelRegistry.find(savedProvider, savedModelId);
@@ -427,7 +428,7 @@ export async function restoreModelFromSession(
*/
export async function findSmolModel(
modelRegistry: ModelRegistry,
savedModel?: string
savedModel?: string,
): Promise<Model<Api> | undefined> {
const availableModels = modelRegistry.getAvailable();
if (availableModels.length === 0) return undefined;
@@ -470,7 +471,7 @@ export async function findSmolModel(
*/
export async function findSlowModel(
modelRegistry: ModelRegistry,
savedModel?: string
savedModel?: string,
): Promise<Model<Api> | undefined> {
const availableModels = modelRegistry.getAvailable();
if (availableModels.length === 0) return undefined;
+13 -13
View File
@@ -27,8 +27,8 @@
*/
import { join } from "node:path";
import type { Model } from "@oh-my-pi/pi-ai";
import { Agent, type AgentTool, type ThinkingLevel } from "@oh-my-pi/pi-agent-core";
import type { Model } from "@oh-my-pi/pi-ai";
import type { Component } from "@oh-my-pi/pi-tui";
import chalk from "chalk";
// Import discovery to register all providers on startup
@@ -248,7 +248,7 @@ export async function discoverAuthStorage(agentDir: string = getDefaultAgentDir(
*/
export async function discoverModels(
authStorage: AuthStorage,
agentDir: string = getDefaultAgentDir()
agentDir: string = getDefaultAgentDir(),
): Promise<ModelRegistry> {
const primaryPath = join(agentDir, "models.json");
// Get all models.json paths (user-level only), excluding the primary
@@ -288,7 +288,7 @@ export function discoverSkills(cwd?: string, _agentDir?: string, settings?: Skil
*/
export function discoverContextFiles(
cwd?: string,
_agentDir?: string
_agentDir?: string,
): Array<{ path: string; content: string; depth?: number }> {
return loadContextFilesInternal({
cwd: cwd ?? process.cwd(),
@@ -436,11 +436,11 @@ function customToolToDefinition(tool: CustomTool): ToolDefinition {
const component = tool.renderResult?.(
result,
{ expanded: options.expanded, isPartial: options.isPartial, spinnerFrame: options.spinnerFrame },
theme
theme,
);
// Return empty component if undefined to match Component type requirement
return component ?? ({ render: () => [] } as unknown as Component);
}
}
: undefined,
[TOOL_DEFINITION_MARKER]: true,
};
@@ -465,19 +465,19 @@ function createCustomToolsExtension(tools: CustomTool[]): ExtensionFactory {
};
api.on("session_start", async (_event, ctx) =>
runOnSession({ reason: "start", previousSessionFile: undefined }, ctx)
runOnSession({ reason: "start", previousSessionFile: undefined }, ctx),
);
api.on("session_switch", async (event, ctx) =>
runOnSession({ reason: "switch", previousSessionFile: event.previousSessionFile }, ctx)
runOnSession({ reason: "switch", previousSessionFile: event.previousSessionFile }, ctx),
);
api.on("session_branch", async (event, ctx) =>
runOnSession({ reason: "branch", previousSessionFile: event.previousSessionFile }, ctx)
runOnSession({ reason: "branch", previousSessionFile: event.previousSessionFile }, ctx),
);
api.on("session_tree", async (_event, ctx) =>
runOnSession({ reason: "tree", previousSessionFile: undefined }, ctx)
runOnSession({ reason: "tree", previousSessionFile: undefined }, ctx),
);
api.on("session_shutdown", async (_event, ctx) =>
runOnSession({ reason: "shutdown", previousSessionFile: undefined }, ctx)
runOnSession({ reason: "shutdown", previousSessionFile: undefined }, ctx),
);
};
}
@@ -659,7 +659,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
if (options.hasUI && serverNames.length > 0) {
process.stderr.write(
chalk.gray(`Connecting to MCP servers: ${serverNames.join(", ")}...
`)
`),
);
}
},
@@ -728,7 +728,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
configuredPaths,
cwd,
eventBus,
settingsManager.getDisabledExtensions()
settingsManager.getDisabledExtensions(),
);
time("discoverAndLoadExtensions");
for (const { path, error } of extensionsResult.errors) {
@@ -905,7 +905,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
transformContext: extensionRunner
? async (messages) => {
return extensionRunner.emitContext(messages);
}
}
: undefined,
steeringMode: settingsManager.getSteeringMode(),
followUpMode: settingsManager.getFollowUpMode(),
@@ -1,6 +1,6 @@
import { basename, join, resolve } from "node:path";
import type { ImageContent, Message, TextContent, Usage } from "@oh-my-pi/pi-ai";
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
import type { ImageContent, Message, TextContent, Usage } from "@oh-my-pi/pi-ai";
import { nanoid } from "nanoid";
import { getAgentDir as getDefaultAgentDir } from "../config";
import { resizeImage } from "../utils/image-resize";
@@ -304,7 +304,7 @@ function toError(value: unknown): Error {
export function buildSessionContext(
entries: SessionEntry[],
leafId?: string | null,
byId?: Map<string, SessionEntry>
byId?: Map<string, SessionEntry>,
): SessionContext {
// Build uuid index if not available
if (!byId) {
@@ -382,7 +382,7 @@ export function buildSessionContext(
messages.push(entry.message);
} else if (entry.type === "custom_message") {
messages.push(
createCustomMessage(entry.customType, entry.content, entry.display, entry.details, entry.timestamp)
createCustomMessage(entry.customType, entry.content, entry.display, entry.details, entry.timestamp),
);
} else if (entry.type === "branch_summary" && entry.summary) {
messages.push(createBranchSummaryMessage(entry.summary, entry.fromId, entry.timestamp));
@@ -540,7 +540,7 @@ function getSortedSessions(sessionDir: string, storage: SessionStorage): RecentS
/** Exported for testing */
export function findMostRecentSession(
sessionDir: string,
storage: SessionStorage = new FileSessionStorage()
storage: SessionStorage = new FileSessionStorage(),
): string | null {
const sessions = getSortedSessions(sessionDir, storage);
return sessions[0]?.path || null;
@@ -645,7 +645,7 @@ async function truncateForPersistence<T>(obj: T, key?: string): Promise<T> {
const newItem = await truncateForPersistence(item, key);
if (newItem !== item) changed = true;
return newItem;
})
}),
);
return changed ? (result as T) : obj;
}
@@ -800,7 +800,7 @@ class NdjsonFileWriter {
export function getRecentSessions(
sessionDir: string,
limit = 3,
storage: SessionStorage = new FileSessionStorage()
storage: SessionStorage = new FileSessionStorage(),
): RecentSessionInfo[] {
return getSortedSessions(sessionDir, storage).slice(0, limit);
}
@@ -1021,7 +1021,7 @@ export class SessionManager {
async () => {
await this._closePersistWriterInternal();
},
{ ignoreError: true }
{ ignoreError: true },
);
}
@@ -1229,7 +1229,7 @@ export class SessionManager {
firstKeptEntryId: string,
tokensBefore: number,
details?: T,
fromExtension?: boolean
fromExtension?: boolean,
): string {
const entry: CompactionEntry<T> = {
type: "compaction",
@@ -1272,7 +1272,7 @@ export class SessionManager {
customType: string,
content: string | (TextContent | ImageContent)[],
display: boolean,
details?: T
details?: T,
): string {
const entry: CustomMessageEntry<T> = {
type: "custom_message",
@@ -1632,7 +1632,7 @@ export class SessionManager {
static async open(
path: string,
sessionDir?: string,
storage: SessionStorage = new FileSessionStorage()
storage: SessionStorage = new FileSessionStorage(),
): Promise<SessionManager> {
// Extract cwd from session header if possible, otherwise use process.cwd()
const entries = loadEntriesFromFile(path, storage);
@@ -1653,7 +1653,7 @@ export class SessionManager {
static async continueRecent(
cwd: string,
sessionDir?: string,
storage: SessionStorage = new FileSessionStorage()
storage: SessionStorage = new FileSessionStorage(),
): Promise<SessionManager> {
const dir = sessionDir ?? getDefaultSessionDir(cwd, storage);
const mostRecent = findMostRecentSession(dir, storage);
@@ -6,8 +6,8 @@
import * as fs from "node:fs";
import * as path from "node:path";
import type { TextContent } from "@oh-my-pi/pi-ai";
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
import type { TextContent } from "@oh-my-pi/pi-ai";
import type { Component } from "@oh-my-pi/pi-tui";
import { Text } from "@oh-my-pi/pi-tui";
import { Type } from "@sinclair/typebox";
+1 -1
View File
@@ -1,7 +1,7 @@
import { homedir } from "node:os";
import path from "node:path";
import type { ImageContent, TextContent } from "@oh-my-pi/pi-ai";
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
import type { ImageContent, TextContent } from "@oh-my-pi/pi-ai";
import type { Component } from "@oh-my-pi/pi-tui";
import { Text } from "@oh-my-pi/pi-tui";
import { Type } from "@sinclair/typebox";
@@ -13,8 +13,8 @@
* - Session artifacts for debugging
*/
import type { Usage } from "@oh-my-pi/pi-ai";
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
import type { Usage } from "@oh-my-pi/pi-ai";
import type { Theme } from "../../../modes/interactive/theme/theme";
import taskDescriptionTemplate from "../../../prompts/tools/task.md" with { type: "text" };
import { formatDuration } from "../render-utils";
@@ -59,12 +59,12 @@ export const taskSchema = Type.Object({
model: Type.Optional(
Type.String({
description: "Model override for all tasks (fuzzy matching, e.g. 'sonnet', 'opus')",
})
}),
),
output: Type.Optional(
Type.Any({
description: "JTD schema for structured subagent output (used by the complete tool)",
})
}),
),
tasks: Type.Array(taskItemSchema, {
description: "Tasks to run in parallel",
@@ -13,8 +13,8 @@
* 5. Parent can send { type: "abort" } to request cancellation
*/
import type { Api, Model } from "@oh-my-pi/pi-ai";
import type { AgentEvent, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
import type { Api, Model } from "@oh-my-pi/pi-ai";
import type { AgentSessionEvent } from "../../agent-session";
import { parseModelPattern, parseModelString } from "../../model-resolver";
import { createAgentSession, discoverAuthStorage, discoverModels } from "../../sdk";
@@ -88,7 +88,7 @@ let pendingAbort = false;
*/
function resolveModelOverride(
override: string | undefined,
modelRegistry: { getAvailable: () => Model<Api>[]; find: (provider: string, id: string) => Model<Api> | undefined }
modelRegistry: { getAvailable: () => Model<Api>[]; find: (provider: string, id: string) => Model<Api> | undefined },
): { model?: Model<Api>; thinkingLevel?: ThinkingLevel } {
if (!override) return {};
@@ -190,7 +190,7 @@ async function runTask(runState: RunState, payload: SubagentWorkerStartPayload):
() => {
void session.abort();
},
{ once: true, signal: sessionAbortController.signal }
{ once: true, signal: sessionAbortController.signal },
);
// Initialize extensions (equivalent to CLI's extension initialization)