Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 1 addition & 11 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
} from './components/index.js';
import { editorTheme, theme } from './theme.js';
import { matchCommands, type SlashCommand } from './commands/index.js';
import { truncateAtWord } from './utils/format.js';
import { initSpinner } from './utils/spinner.js';

function truncateForHistory(text: string): string {
Expand All @@ -38,17 +39,6 @@ function truncateForHistory(text: string): string {
return `${preview} [+${lines.length - 1} lines]`;
}

function truncateAtWord(str: string, maxLength: number): string {
if (str.length <= maxLength) {
return str;
}
const lastSpace = str.lastIndexOf(' ', maxLength);
if (lastSpace > maxLength * 0.5) {
return `${str.slice(0, lastSpace)}...`;
}
return `${str.slice(0, maxLength)}...`;
}

function summarizeToolResult(tool: string, args: Record<string, unknown>, result: string): string {
if (tool === 'skill') {
const skillName = args.skill as string;
Expand Down
12 changes: 1 addition & 11 deletions src/components/tool-event.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Container, Spacer, Text, type TUI } from '@mariozechner/pi-tui';
import type { ApprovalDecision } from '../agent/types.js';
import { theme } from '../theme.js';
import { truncateAtWord } from '../utils/format.js';
import { subscribeSpinner, SPINNER_INTERVAL_MS } from '../utils/spinner.js';

const CIRCLE = '⏺';
Expand All @@ -13,17 +14,6 @@ function formatToolName(name: string): string {
.join(' ');
}

function truncateAtWord(str: string, maxLength: number): string {
if (str.length <= maxLength) {
return str;
}
const lastSpace = str.lastIndexOf(' ', maxLength);
if (lastSpace > maxLength * 0.5) {
return `${str.slice(0, lastSpace)}...`;
}
return `${str.slice(0, maxLength)}...`;
}

function formatArgs(tool: string, args: Record<string, unknown>): string {
if ('query' in args) {
const query = String(args.query);
Expand Down
12 changes: 3 additions & 9 deletions src/evals/components/eval-app.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Container, Spacer, Text, type TUI } from '@mariozechner/pi-tui';
import { theme } from '../../theme.js';
import { truncateEnd } from '../../utils/format.js';
import { EvalCurrentQuestion } from './eval-current-question.js';
import { EvalProgress } from './eval-progress.js';
import { EvalRecentResults, type EvalResult } from './eval-recent-results.js';
Expand Down Expand Up @@ -192,25 +193,18 @@ export class EvalApp extends Container {
const iconColor = result.score === 1 ? theme.success : theme.error;
this.addChild(
new Text(
`${iconColor(icon)} ${theme.muted(`[${result.score}]`)} ${this.truncate(result.question, 65)}`,
`${iconColor(icon)} ${theme.muted(`[${result.score}]`)} ${truncateEnd(result.question, 65)}`,
0,
0,
),
);
if (result.comment && result.score !== 1) {
this.addChild(new Text(` ${theme.muted(this.truncate(result.comment, 80))}`, 0, 0));
this.addChild(new Text(` ${theme.muted(truncateEnd(result.comment, 80))}`, 0, 0));
}
}

this.addChild(new Spacer(1));
this.addChild(new Text('─'.repeat(70), 0, 0));
this.addChild(new Text(theme.muted('View full results: https://smith.langchain.com'), 0, 0));
}

private truncate(value: string, maxLength: number): string {
if (value.length <= maxLength) {
return value;
}
return `${value.slice(0, maxLength)}...`;
}
}
12 changes: 1 addition & 11 deletions src/evals/components/eval-current-question.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,6 @@
import { Container, Loader, type TUI } from '@mariozechner/pi-tui';
import { theme } from '../../theme.js';

function truncateAtWord(str: string, maxLength: number): string {
if (str.length <= maxLength) {
return str;
}
const lastSpace = str.lastIndexOf(' ', maxLength);
if (lastSpace > maxLength * 0.5) {
return `${str.slice(0, lastSpace)}...`;
}
return `${str.slice(0, maxLength)}...`;
}
import { truncateAtWord } from '../../utils/format.js';

export class EvalCurrentQuestion extends Container {
private readonly tui: TUI;
Expand Down
12 changes: 1 addition & 11 deletions src/evals/components/eval-recent-results.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,13 @@
import { Container, Text } from '@mariozechner/pi-tui';
import { theme } from '../../theme.js';
import { truncateAtWord } from '../../utils/format.js';

export interface EvalResult {
question: string;
score: number;
comment: string;
}

function truncateAtWord(str: string, maxLength: number): string {
if (str.length <= maxLength) {
return str;
}
const lastSpace = str.lastIndexOf(' ', maxLength);
if (lastSpace > maxLength * 0.5) {
return `${str.slice(0, lastSpace)}...`;
}
return `${str.slice(0, maxLength)}...`;
}

export class EvalRecentResults extends Container {
setResults(results: EvalResult[], maxDisplay = 5) {
this.clear();
Expand Down
6 changes: 1 addition & 5 deletions src/tools/finance/read-filings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { z } from 'zod';
import { callLlm } from '../../model/llm.js';
import { formatToolResult } from '../types.js';
import { getCurrentDate } from '../../agent/prompts.js';
import { escapeTemplateVars } from '../../utils/format.js';
import { getFilings, get10KFilingItems, get10QFilingItems, get8KFilingItems, getFilingItemTypes, type FilingItemTypes } from './filings.js';
import { withTimeout, SUB_TOOL_TIMEOUT_MS } from './utils.js';

Expand Down Expand Up @@ -40,11 +41,6 @@ Intelligent meta-tool for reading SEC filing content. Takes a natural language q
- Intelligently retrieves specific sections when query targets particular content, full filing otherwise
`.trim();

// Escape curly braces for LangChain template interpolation
function escapeTemplateVars(str: string): string {
return str.replace(/\{/g, '{{').replace(/\}/g, '}}');
}

const FilingTypeSchema = z.enum(['10-K', '10-Q', '8-K']);

const FilingPlanSchema = z.object({
Expand Down
6 changes: 1 addition & 5 deletions src/tools/finance/screen-stocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { z } from 'zod';
import { callLlm } from '../../model/llm.js';
import { formatToolResult } from '../types.js';
import { getCurrentDate } from '../../agent/prompts.js';
import { escapeTemplateVars } from '../../utils/format.js';
import { api } from './api.js';

/**
Expand Down Expand Up @@ -62,11 +63,6 @@ const ScreenerFilterSchema = z.object({

type ScreenerFilters = z.infer<typeof ScreenerFilterSchema>;

// Escape curly braces for LangChain template interpolation
function escapeTemplateVars(str: string): string {
return str.replace(/\{/g, '{{').replace(/\}/g, '}}');
}

function buildScreenerPrompt(metrics: Record<string, unknown>): string {
const escapedMetrics = escapeTemplateVars(JSON.stringify(metrics, null, 2));

Expand Down
24 changes: 24 additions & 0 deletions src/utils/format.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { describe, expect, test } from 'bun:test';
import { escapeTemplateVars, truncateAtWord, truncateEnd } from './format.js';

describe('format utils', () => {
test('truncateAtWord preserves short strings', () => {
expect(truncateAtWord('short text', 20)).toBe('short text');
});

test('truncateAtWord cuts at a nearby word boundary', () => {
expect(truncateAtWord('alpha beta gamma', 12)).toBe('alpha beta...');
});

test('truncateAtWord hard-cuts when no useful word boundary exists', () => {
expect(truncateAtWord('alphabetagamma', 8)).toBe('alphabet...');
});

test('truncateEnd cuts exactly at max length', () => {
expect(truncateEnd('alpha beta gamma', 10)).toBe('alpha beta...');
});

test('escapeTemplateVars doubles curly braces for prompt templates', () => {
expect(escapeTemplateVars('{"field": "{value}"}')).toBe('{{"field": "{{value}}"}}');
});
});
22 changes: 22 additions & 0 deletions src/utils/format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,25 @@ const compactFormatter = new Intl.NumberFormat('en', {
export function formatTokensCompact(n: number): string {
return compactFormatter.format(n).toLowerCase();
}

export function truncateAtWord(str: string, maxLength: number): string {
if (str.length <= maxLength) {
return str;
}
const lastSpace = str.lastIndexOf(' ', maxLength);
if (lastSpace > maxLength * 0.5) {
return `${str.slice(0, lastSpace)}...`;
}
return `${str.slice(0, maxLength)}...`;
}

export function truncateEnd(str: string, maxLength: number): string {
if (str.length <= maxLength) {
return str;
}
return `${str.slice(0, maxLength)}...`;
}

export function escapeTemplateVars(str: string): string {
return str.replace(/\{/g, '{{').replace(/\}/g, '}}');
}
3 changes: 2 additions & 1 deletion src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@ export { cursorHandlers } from './input-key-handlers.js';
export type { CursorContext } from './input-key-handlers.js';
export { getToolDescription } from './tool-description.js';
export { transformMarkdownTables, formatResponse } from './markdown-table.js';
export { truncateAtWord, truncateEnd, escapeTemplateVars } from './format.js';
export { estimateTokens } from './tokens.js';
export {
parseApiErrorInfo,
classifyError,
isContextOverflowError,
isNonRetryableError,
formatUserFacingError,
} from './errors.js';
} from './errors.js';
Loading