mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-15 22:03:45 +00:00
Merge branch 'main' into feat/custom-thinking-budgets
This commit is contained in:
commit
d311978dfd
41 changed files with 1664 additions and 538 deletions
|
|
@ -1262,8 +1262,24 @@ export class AgentSession {
|
|||
|
||||
const contextWindow = this.model?.contextWindow ?? 0;
|
||||
|
||||
// Skip overflow check if the message came from a different model.
|
||||
// This handles the case where user switched from a smaller-context model (e.g. opus)
|
||||
// to a larger-context model (e.g. codex) - the overflow error from the old model
|
||||
// shouldn't trigger compaction for the new model.
|
||||
const sameModel =
|
||||
this.model && assistantMessage.provider === this.model.provider && assistantMessage.model === this.model.id;
|
||||
|
||||
// Skip overflow check if the error is from before a compaction in the current path.
|
||||
// This handles the case where an error was kept after compaction (in the "kept" region).
|
||||
// The error shouldn't trigger another compaction since we already compacted.
|
||||
// Example: opus fails → switch to codex → compact → switch back to opus → opus error
|
||||
// is still in context but shouldn't trigger compaction again.
|
||||
const compactionEntry = this.sessionManager.getBranch().find((e) => e.type === "compaction");
|
||||
const errorIsFromBeforeCompaction =
|
||||
compactionEntry && assistantMessage.timestamp < new Date(compactionEntry.timestamp).getTime();
|
||||
|
||||
// Case 1: Overflow - LLM returned context overflow error
|
||||
if (isContextOverflow(assistantMessage, contextWindow)) {
|
||||
if (sameModel && !errorIsFromBeforeCompaction && isContextOverflow(assistantMessage, contextWindow)) {
|
||||
// Remove the error message from agent state (it IS saved to session for history,
|
||||
// but we don't want it in context for the retry)
|
||||
const messages = this.agent.state.messages;
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ export type {
|
|||
// Re-exports
|
||||
AgentToolResult,
|
||||
AgentToolUpdateCallback,
|
||||
// App keybindings (for custom editors)
|
||||
AppAction,
|
||||
AppendEntryHandler,
|
||||
BashToolResultEvent,
|
||||
BeforeAgentStartEvent,
|
||||
|
|
@ -42,6 +44,7 @@ export type {
|
|||
GetAllToolsHandler,
|
||||
GetThinkingLevelHandler,
|
||||
GrepToolResultEvent,
|
||||
KeybindingsManager,
|
||||
LoadExtensionsResult,
|
||||
// Loaded Extension
|
||||
LoadedExtension,
|
||||
|
|
|
|||
|
|
@ -99,6 +99,7 @@ function createNoOpUIContext(): ExtensionUIContext {
|
|||
setEditorText: () => {},
|
||||
getEditorText: () => "",
|
||||
editor: async () => undefined,
|
||||
setEditorComponent: () => {},
|
||||
get theme() {
|
||||
return theme;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ const noOpUIContext: ExtensionUIContext = {
|
|||
setEditorText: () => {},
|
||||
getEditorText: () => "",
|
||||
editor: async () => undefined,
|
||||
setEditorComponent: () => {},
|
||||
get theme() {
|
||||
return theme;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -15,12 +15,13 @@ import type {
|
|||
ThinkingLevel,
|
||||
} from "@mariozechner/pi-agent-core";
|
||||
import type { ImageContent, Model, TextContent, ToolResultMessage } from "@mariozechner/pi-ai";
|
||||
import type { Component, KeyId, TUI } from "@mariozechner/pi-tui";
|
||||
import type { Component, EditorComponent, EditorTheme, KeyId, TUI } from "@mariozechner/pi-tui";
|
||||
import type { Static, TSchema } from "@sinclair/typebox";
|
||||
import type { Theme } from "../../modes/interactive/theme/theme.js";
|
||||
import type { CompactionPreparation, CompactionResult } from "../compaction/index.js";
|
||||
import type { EventBus } from "../event-bus.js";
|
||||
import type { ExecOptions, ExecResult } from "../exec.js";
|
||||
import type { AppAction, KeybindingsManager } from "../keybindings.js";
|
||||
import type { CustomMessage } from "../messages.js";
|
||||
import type { ModelRegistry } from "../model-registry.js";
|
||||
import type {
|
||||
|
|
@ -41,6 +42,7 @@ import type {
|
|||
|
||||
export type { ExecOptions, ExecResult } from "../exec.js";
|
||||
export type { AgentToolResult, AgentToolUpdateCallback };
|
||||
export type { AppAction, KeybindingsManager } from "../keybindings.js";
|
||||
|
||||
// ============================================================================
|
||||
// UI Context
|
||||
|
|
@ -92,6 +94,7 @@ export interface ExtensionUIContext {
|
|||
factory: (
|
||||
tui: TUI,
|
||||
theme: Theme,
|
||||
keybindings: KeybindingsManager,
|
||||
done: (result: T) => void,
|
||||
) => (Component & { dispose?(): void }) | Promise<Component & { dispose?(): void }>,
|
||||
): Promise<T>;
|
||||
|
|
@ -105,6 +108,43 @@ export interface ExtensionUIContext {
|
|||
/** Show a multi-line editor for text editing. */
|
||||
editor(title: string, prefill?: string): Promise<string | undefined>;
|
||||
|
||||
/**
|
||||
* Set a custom editor component via factory function.
|
||||
* Pass undefined to restore the default editor.
|
||||
*
|
||||
* The factory receives:
|
||||
* - `theme`: EditorTheme for styling borders and autocomplete
|
||||
* - `keybindings`: KeybindingsManager for app-level keybindings
|
||||
*
|
||||
* For full app keybinding support (escape, ctrl+d, model switching, etc.),
|
||||
* extend `CustomEditor` from `@mariozechner/pi-coding-agent` and call
|
||||
* `super.handleInput(data)` for keys you don't handle.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { CustomEditor } from "@mariozechner/pi-coding-agent";
|
||||
*
|
||||
* class VimEditor extends CustomEditor {
|
||||
* private mode: "normal" | "insert" = "insert";
|
||||
*
|
||||
* handleInput(data: string): void {
|
||||
* if (this.mode === "normal") {
|
||||
* // Handle vim normal mode keys...
|
||||
* if (data === "i") { this.mode = "insert"; return; }
|
||||
* }
|
||||
* super.handleInput(data); // App keybindings + text editing
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* ctx.ui.setEditorComponent((tui, theme, keybindings) =>
|
||||
* new VimEditor(tui, theme, keybindings)
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
setEditorComponent(
|
||||
factory: ((tui: TUI, theme: EditorTheme, keybindings: KeybindingsManager) => EditorComponent) | undefined,
|
||||
): void;
|
||||
|
||||
/** Get the current theme for styling. */
|
||||
readonly theme: Theme;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,8 @@ export const defaultModelPerProvider: Record<KnownProvider, string> = {
|
|||
|
||||
export interface ScopedModel {
|
||||
model: Model<Api>;
|
||||
thinkingLevel: ThinkingLevel;
|
||||
/** Thinking level if explicitly specified in pattern (e.g., "model:high"), undefined otherwise */
|
||||
thinkingLevel?: ThinkingLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -98,7 +99,8 @@ function tryMatchModel(modelPattern: string, availableModels: Model<Api>[]): Mod
|
|||
|
||||
export interface ParsedModelResult {
|
||||
model: Model<Api> | undefined;
|
||||
thinkingLevel: ThinkingLevel;
|
||||
/** Thinking level if explicitly specified in pattern, undefined otherwise */
|
||||
thinkingLevel?: ThinkingLevel;
|
||||
warning: string | undefined;
|
||||
}
|
||||
|
||||
|
|
@ -119,14 +121,14 @@ export function parseModelPattern(pattern: string, availableModels: Model<Api>[]
|
|||
// Try exact match first
|
||||
const exactMatch = tryMatchModel(pattern, availableModels);
|
||||
if (exactMatch) {
|
||||
return { model: exactMatch, thinkingLevel: "off", warning: undefined };
|
||||
return { model: exactMatch, thinkingLevel: undefined, warning: undefined };
|
||||
}
|
||||
|
||||
// No match - try splitting on last colon if present
|
||||
const lastColonIndex = pattern.lastIndexOf(":");
|
||||
if (lastColonIndex === -1) {
|
||||
// No colons, pattern simply doesn't match any model
|
||||
return { model: undefined, thinkingLevel: "off", warning: undefined };
|
||||
return { model: undefined, thinkingLevel: undefined, warning: undefined };
|
||||
}
|
||||
|
||||
const prefix = pattern.substring(0, lastColonIndex);
|
||||
|
|
@ -137,22 +139,21 @@ export function parseModelPattern(pattern: string, availableModels: Model<Api>[]
|
|||
const result = parseModelPattern(prefix, availableModels);
|
||||
if (result.model) {
|
||||
// Only use this thinking level if no warning from inner recursion
|
||||
// (if there was an invalid suffix deeper, we already have "off")
|
||||
return {
|
||||
model: result.model,
|
||||
thinkingLevel: result.warning ? "off" : suffix,
|
||||
thinkingLevel: result.warning ? undefined : suffix,
|
||||
warning: result.warning,
|
||||
};
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
// Invalid suffix - recurse on prefix with "off" and warn
|
||||
// Invalid suffix - recurse on prefix and warn
|
||||
const result = parseModelPattern(prefix, availableModels);
|
||||
if (result.model) {
|
||||
return {
|
||||
model: result.model,
|
||||
thinkingLevel: "off",
|
||||
warning: `Invalid thinking level "${suffix}" in pattern "${pattern}". Using "off" instead.`,
|
||||
thinkingLevel: undefined,
|
||||
warning: `Invalid thinking level "${suffix}" in pattern "${pattern}". Using default instead.`,
|
||||
};
|
||||
}
|
||||
return result;
|
||||
|
|
@ -180,7 +181,7 @@ export async function resolveModelScope(patterns: string[], modelRegistry: Model
|
|||
// Extract optional thinking level suffix (e.g., "provider/*:high")
|
||||
const colonIdx = pattern.lastIndexOf(":");
|
||||
let globPattern = pattern;
|
||||
let thinkingLevel: ThinkingLevel = "off";
|
||||
let thinkingLevel: ThinkingLevel | undefined;
|
||||
|
||||
if (colonIdx !== -1) {
|
||||
const suffix = pattern.substring(colonIdx + 1);
|
||||
|
|
@ -282,7 +283,7 @@ export async function findInitialModel(options: {
|
|||
if (scopedModels.length > 0 && !isContinuing) {
|
||||
return {
|
||||
model: scopedModels[0].model,
|
||||
thinkingLevel: scopedModels[0].thinkingLevel,
|
||||
thinkingLevel: scopedModels[0].thinkingLevel ?? defaultThinkingLevel ?? "off",
|
||||
fallbackMessage: undefined,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -531,6 +531,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|||
setEditorText: () => {},
|
||||
getEditorText: () => "",
|
||||
editor: async () => undefined,
|
||||
setEditorComponent: () => {},
|
||||
get theme() {
|
||||
return {} as any;
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue