diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index ad5781d..1b49b3c 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -1,11 +1,19 @@ -name: Publish +name: Publish to JSR + on: push: - branches: - - main + tags: + - 'v*.*.*' + workflow_dispatch: + inputs: + version: + description: 'Version to publish (e.g., 1.0.0)' + required: true + type: string jobs: publish: + name: Publish Package runs-on: ubuntu-latest permissions: @@ -13,7 +21,46 @@ jobs: id-token: write steps: - - uses: actions/checkout@v4 + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Deno + uses: denoland/setup-deno@v1 + with: + deno-version: v2.x + + - name: Verify formatting + run: deno fmt --check + + - name: Run linter + run: deno task lint + + - name: Type check + run: deno task check + +# - name: Run tests +# run: deno task test + + - name: Extract version from tag or input + id: version + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + VERSION="${{ github.event.inputs.version }}" + else + VERSION=${GITHUB_REF#refs/tags/v} + fi + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "Publishing version: $VERSION" + + - name: Update version in deno.jsonc + run: | + VERSION="${{ steps.version.outputs.version }}" + sed -i "s/\"version\": \".*\"/\"version\": \"$VERSION\"/" deno.jsonc + echo "Updated deno.jsonc version to $VERSION" + cat deno.jsonc | grep version + + - name: Generate version.ts from deno.jsonc + run: deno task update-version - - name: Publish package - run: npx jsr publish + - name: Publish to JSR + run: deno publish --allow-dirty diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..0234c2d --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,51 @@ +name: Test + +on: + push: + branches: + - main + - develop + pull_request: + branches: + - main + - develop + +jobs: + test: + name: Run Tests + runs-on: ubuntu-latest + + permissions: + contents: read + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Deno + uses: denoland/setup-deno@v1 + with: + deno-version: v2.x + + - name: Verify formatting + run: deno fmt --check + + - name: Run linter + run: deno task lint + + - name: Type check + run: deno task check + +# - name: Run tests +# run: deno task test +# +# - name: Generate coverage +# run: deno task test:coverage +# +# - name: Upload coverage to Codecov +# uses: codecov/codecov-action@v4 +# with: +# files: ./tests/coverage/lcov.info +# fail_ci_if_error: false +# env: +# CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.gitignore b/.gitignore index f6d5fd3..a9574c4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,16 +1,10 @@ .bb +dev-planning/ # Logs logs *.log npm-debug.log* -yarn-debug.log* -yarn-error.log* -lerna-debug.log* -.pnpm-debug.log* - -# Diagnostic reports (https://nodejs.org/api/report.html) -report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json # Runtime data pids @@ -25,18 +19,6 @@ lib-cov coverage *.lcov -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - # Compiled binary addons (https://nodejs.org/api/addons.html) build/Release @@ -44,36 +26,9 @@ build/Release node_modules/ jspm_packages/ -# Snowpack dependency directory (https://snowpack.dev/) -web_modules/ - -# TypeScript cache -*.tsbuildinfo - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Optional stylelint cache -.stylelintcache - -# Microbundle cache -.rpt2_cache/ -.rts2_cache_cjs/ -.rts2_cache_es/ -.rts2_cache_umd/ - -# Optional REPL history -.node_repl_history - # Output of 'npm pack' *.tgz -# Yarn Integrity file -.yarn-integrity - # dotenv environment variable files .env .env.development.local @@ -81,52 +36,3 @@ web_modules/ .env.production.local .env.local -# parcel-bundler cache (https://parceljs.org/) -.cache -.parcel-cache - -# Next.js build output -.next -out - -# Nuxt.js build / generate output -.nuxt -dist - -# Gatsby files -.cache/ -# Comment in the public line in if your project uses Gatsby and not Next.js -# https://nextjs.org/blog/next-9-1#public-directory-support -# public - -# vuepress build output -.vuepress/dist - -# vuepress v2.x temp and cache directory -.temp -.cache - -# Docusaurus cache and generated files -.docusaurus - -# Serverless directories -.serverless/ - -# FuseBox cache -.fusebox/ - -# DynamoDB Local files -.dynamodb/ - -# TernJS port file -.tern-port - -# Stores VSCode versions used for testing VSCode extensions -.vscode-test - -# yarn v2 -.yarn/cache -.yarn/unplugged -.yarn/build-state.yml -.yarn/install-state.gz -.pnp.* diff --git a/GUIDELINES.md b/GUIDELINES.md new file mode 100644 index 0000000..aecd3ef --- /dev/null +++ b/GUIDELINES.md @@ -0,0 +1,822 @@ +--- +title: BB Tools Framework Guidelines +project: bb-tools +version: 1.0.0 +created: 2025-11-02 +updated: 2025-11-02 +purpose: Guidelines for LLM collaboration on bb-tools framework development +--- + +# BB Tools Framework Guidelines + +## Project Purpose and Scope + +### Overview +bb-tools (@beyondbetter/tools) is the core framework/library for building tools that work with the Beyond Better (BB) AI assistant. This project provides base classes, interfaces, types, and utilities needed to create properly structured BB tools. + +**IMPORTANT**: These guidelines are for working on the bb-tools framework itself, NOT for users/consumers of the framework. The audience is the LLM assisting with framework development, maintenance, and enhancement. + +### Project Goals +- Provide robust base classes (LLMTool) for tool creation +- Define standardized interfaces (IProjectEditor, IConversationInteraction) +- Supply formatting utilities for browser (JSX/Preact) and console (ANSI) +- Maintain comprehensive type definitions +- Demonstrate best practices through working examples +- Enable publishing to JSR for Deno ecosystem + +### Expected Outcomes +- Clean, maintainable TypeScript code following Deno standards +- Clear documentation for framework consumers +- Working example tools that demonstrate framework capabilities +- Type-safe interfaces and implementations +- Consistent code organization and structure + +### Project Scope +**In Scope:** +- Framework core classes and utilities +- Type definitions and interfaces +- Example tools demonstrating framework usage +- Documentation for tool creators +- JSR publishing configuration + +**Out of Scope:** +- Individual tools created by framework consumers +- Beyond Better (BB) main application code +- Tool runtime environment (handled by BB) +- Tool distribution/marketplace + +## Available Data Sources and Resources + +### Data Source Configuration +**Primary Data Source**: `bb-tools` (filesystem) +- **Type**: Filesystem +- **Capabilities**: Read, Write, Delete +- **Root**: `$WORKING_DIR/bb-tools` +- **Access**: Full read/write access to all files and directories + +### Resource Organization + +#### Core Framework Files (Root Directory) +``` +mod.ts # Main module exports +llm_tool.ts # LLMTool base class implementation +llm_tool_tags.tsx # Browser/console formatting utilities (JSX) +types.ts # Core type definitions +project_editor.ts # IProjectEditor interface and types +conversation.ts # IConversationInteraction interface +message.ts # Message formatting utilities +``` + +#### Configuration Files +``` +deno.json # Deno project configuration & JSR publish settings +import_map.json # Import mappings +deno.lock # Dependency lock file +.gitignore # Git ignore patterns +``` + +#### Documentation (`docs/`) +``` +CREATING_TOOLS.md # Guide for tool creators (consumers) +TESTING.md # Testing guidelines for tools +tools.md # Comprehensive framework reference +``` + +#### Example Tools (`examples/`) +``` +mod.ts # Examples module exports +search_project.tool/ + ├── tool.ts # Tool implementation + ├── formatter.browser.tsx # Browser formatting + ├── formatter.console.ts # Console formatting + ├── info.json # Tool metadata + ├── types.ts # Tool-specific types + ├── tool.test.ts # Tool tests + └── README.md # Tool documentation +open_in_browser.tool/ + └── (same structure) +``` + +#### Other Files +``` +README.md # Project README +GUIDELINES.md # Project Guidelines (this file) +LICENSE # MIT License +.github/workflows/ # GitHub Actions (JSR publishing) +``` + +### Resource Access Patterns + +1. **Framework Core Modifications** + - Read existing implementation first + - Consider backward compatibility + - Update type definitions if interfaces change + - Test with example tools after changes + +2. **Example Tool Development** + - Follow established tool structure + - Include all standard files (tool.ts, formatters, tests, README, info.json) + - Use framework utilities consistently + - Demonstrate specific framework features + +3. **Documentation Updates** + - Keep in sync with code changes + - Update examples when APIs change + - Maintain consistency across all docs + - Reference actual code examples + +4. **Configuration Changes** + - Verify JSR publish settings in deno.json + - Update version numbers appropriately + - Maintain import map consistency + +## Development Standards + +### Language and Runtime +- **Language**: TypeScript (Deno flavor) +- **Runtime**: Deno (required dependency) +- **Style**: Standard Deno TypeScript conventions +- **Module System**: ES modules with explicit extensions + +### Code Organization + +1. **File Naming** + - Use lowercase with underscores: `llm_tool.ts` + - Browser formatters: `formatter.browser.tsx` (JSX/Preact) + - Console formatters: `formatter.console.ts` + - Tests: `tool.test.ts` or `*.test.ts` + - Types: `types.ts` (when tool-specific types needed) + +2. **Module Exports** + - Export public API through `mod.ts` + - Use explicit exports, avoid wildcard re-exports when clarity is needed + - Maintain clean separation of concerns + +3. **Type Safety** + - Strong typing throughout + - Explicit return types for public methods + - Proper interface implementation + - No `any` types without justification + +### Testing Standards + +**Framework Core**: +- No tests currently exist for the framework itself +- Framework reliability demonstrated through example tools + +**Example Tools**: +- Each example tool MUST include comprehensive tests +- Test files: `tool.test.ts` +- Test coverage should include: + - Basic functionality + - Input validation + - Browser formatter output + - Console formatter output + - Error handling +- Use Deno's built-in test framework +- Mock interfaces (IProjectEditor, IConversationInteraction) as needed + +### Publishing +- Published to JSR (JavaScript Registry) +- Version management in `deno.json` +- GitHub Actions workflow handles publishing +- Follow semantic versioning + +## Tool Creation Guidelines + +### Standard Tool Structure +When creating example tools or documenting tool patterns: + +``` +tool_name.tool/ +├── tool.ts # Tool implementation (extends LLMTool) +├── formatter.browser.tsx # Browser UI formatting (JSX/Preact) +├── formatter.console.ts # Console output formatting (ANSI) +├── info.json # Tool metadata and examples +├── types.ts # Tool-specific types (if needed) +├── tool.test.ts # Comprehensive tests +└── README.md # Tool documentation +``` + +### Key Implementation Requirements + +1. **Tool Class** + - Extend `LLMTool` base class + - Implement `inputSchema` getter (JSON Schema) + - Implement `runTool` method + - Implement `formatLogEntryToolUse` and `formatLogEntryToolResult` + - Set tool features using `LLMToolFeatures` interface: + - `mutates`: boolean - Tool modifies resources + - `stateful`: boolean - Tool maintains state between calls + - `async`: boolean - Tool runs asynchronously + - `idempotent`: boolean - Multiple runs produce same result + - `resourceIntensive`: boolean - Tool needs significant resources + - `requiresNetwork`: boolean - Tool needs internet access + +2. **Browser Formatter** (`formatter.browser.tsx`) + - Use Preact JSX syntax + - Import: `/** @jsxImportSource preact */` + - Use `LLMTool.TOOL_TAGS_BROWSER` utilities + - Return `LLMToolLogEntryFormattedResult` with title, subtitle, content, preview + +3. **Console Formatter** (`formatter.console.ts`) + - Use ANSI color codes via framework utilities + - Use `LLMTool.TOOL_STYLES_CONSOLE` utilities + - Use `common-tags` stripIndents for clean formatting + - Return `LLMToolLogEntryFormattedResult` with title, subtitle, content, preview + +4. **Info File** (`info.json`) + - Include: name, description, version, author, license + - Provide usage examples with sample inputs + - Document expected behavior + +5. **Tests** (`tool.test.ts`) + - Test tool execution + - Validate input schema + - Test both formatters + - Cover error scenarios + +### Tool Types and Patterns + +When creating or working with tools, understand these common patterns: + +**1. File System Tools** +- Tools that interact with project files +- MUST validate paths with `isPathWithinProject` utility +- Should use `ProjectEditor` methods for file operations +- Need careful error handling for file operations +- Consider impact on project structure + +Example pattern: +```typescript +if (!isPathWithinProject(projectEditor.projectRoot, filePath)) { + throw new Error(`Access denied: ${filePath} is outside project`); +} +``` + +**2. Data Processing Tools** +- Tools that process or analyze data +- Handle data validation early +- Consider performance implications for large datasets +- Implement proper error handling +- Support both sync and async operations + +**3. Network Tools** +- Tools that interact with external resources +- Set `requiresNetwork: true` in features +- Handle network errors gracefully +- Implement timeouts +- Consider rate limiting +- Validate external resources + +### Tool Results Structure + +Tools return `LLMToolRunResult` with these components: +```typescript +interface LLMToolRunResult { + toolResults: LLMToolRunResultContent; // Main results + toolResponse: LLMToolRunToolResponse; // Response message + bbResponse: LLMToolRunBbResponse; // BB-specific data + finalizeCallback?: (messageId: string) => void; // Optional cleanup +} +``` + +### Tool Planning Template + +When planning a new tool (for documentation or discussion), use this template: +```typescript +{ + toolName: string; // Descriptive name + description: string; // Brief purpose + inputSchema: JSONSchema4; // Parameter definitions + expectedOutput: string; // What tool returns + requiredActions: string[]; // Main functionality + errorScenarios: string[]; // Error handling cases +} +``` + +### Framework Utilities + +When working with framework code, understand these key utilities: + +**1. TOOL_TAGS_BROWSER** (JSX formatting for browser UI) + +*Base Components:* +- `container`: Wraps content in styled container +- `label`: Displays styled label text +- `list`: Creates styled list of items +- `listItem`: Individual list item styling +- `text`: Basic text styling + +*Content Components:* +- `title`: Tool title with optional category +- `subtitle`: Secondary title or description +- `status`: Status indicators (completed, error, etc.) +- `error`: Error message styling +- `success`: Success message styling +- `filename`: File path styling +- `url`: URL styling +- `code`: Code snippet styling +- `date`: Date formatting +- `size`: File size formatting +- `boolean`: Boolean value formatting +- `regex`: Regular expression formatting + +**2. TOOL_STYLES_CONSOLE** (ANSI formatting for console output) + +*Base Components:* +- `label`: Styled text labels +- `listItem`: Indented list items with bullets +- `text`: Basic text styling + +*Content Components:* +- `title`: Tool title with optional category +- `subtitle`: Secondary title or description +- `status`: Status indicators with colors +- `error`: Error message styling (red) +- `success`: Success message styling (green) +- `filename`: File path styling +- `url`: URL styling +- `code`: Code snippet styling +- `date`: Date formatting +- `size`: File size formatting +- `boolean`: Boolean value formatting +- `regex`: Regular expression formatting + +**3. Core Interfaces** +- `IProjectEditor`: File operations, project context, path validation +- `IConversationInteraction`: Conversation management, file handling +- `LLMToolInputSchema`: JSON Schema for input validation +- `LLMToolRunResult`: Standardized tool output structure +- `LLMToolFeatures`: Tool capability declarations + +## Restrictions and Guardrails + +### Security and Privacy +- No specific security or privacy concerns for this project +- Framework is for tool creation, not for handling sensitive data +- Tools created with framework may have their own security requirements + +### Approval Requirements +- No approval required for changes +- Human user (cng) is sole maintainer +- Implement requested changes directly +- Suggest improvements proactively + +### File Modification Guidelines +- All files are modifiable (no read-only restrictions) +- Always review current content before modifications +- Consider backward compatibility for framework changes +- Update related files when making changes (e.g., types + implementation) + +### Breaking Changes +When suggesting or implementing changes that affect the public API: +1. Clearly identify the breaking change +2. Explain impact on existing tools +3. Suggest migration path if needed +4. Consider version bump requirements + +## Collaboration Workflow + +### Decision Making + +**Ask the human user when:** +- Requirements are unclear or ambiguous +- Multiple valid approaches exist with different tradeoffs +- Breaking changes would affect framework consumers +- Significant architectural decisions are needed +- Uncertainty about project direction or priorities + +**Proceed autonomously when:** +- Implementing specific, clear requests +- Fixing obvious bugs or typos +- Improving code clarity or documentation +- Following established patterns +- Making non-breaking enhancements + +### Suggestion Guidelines + +**Always suggest when you identify:** +- Better/easier solutions the human may have missed +- Performance improvements +- Code clarity enhancements +- Consistency improvements across the codebase +- Potential issues or edge cases +- Opportunities to reduce duplication + +**Suggestion format:** +1. Acknowledge the request +2. Present the suggested alternative +3. Explain the benefits +4. Ask for preference if uncertain + +### Change Implementation + +**Batching vs Incremental:** +- Use whichever approach makes sense for the task +- **Batch changes** when: Multiple related files need updates, refactoring across the codebase, consistent pattern application +- **Incremental changes** when: Iterating on design, testing changes step-by-step, complex modifications with checkpoints +- Discuss approach if uncertain + +**Change Process:** +1. Load and review current file content +2. Show planned changes in thinking +3. Implement complete, working changes +4. Update related files if needed (types, docs, examples) +5. Suggest testing approach + +### Code Review Mindset + +When reviewing or modifying framework code: +- Consider how tool creators will use this API +- Think about edge cases and error handling +- Maintain consistency with existing patterns +- Prioritize clarity over cleverness +- Document complex logic +- Consider performance for common operations + +## Common Tasks and Patterns + +### Adding a New Framework Feature + +1. **Identify scope**: Core class, interface, utility, or type +2. **Check existing patterns**: Follow established conventions +3. **Update implementation**: Add the feature +4. **Update types**: Ensure type safety +5. **Update documentation**: Reflect new capability +6. **Consider examples**: Should example tools demonstrate this? +7. **Test with examples**: Verify examples still work + +### Creating a New Example Tool + +1. **Create directory**: `examples/tool_name.tool/` +2. **Implement tool.ts**: Extend LLMTool, implement required methods +3. **Create formatters**: Both browser.tsx and console.ts +4. **Add info.json**: Metadata and examples +5. **Write tests**: Comprehensive coverage +6. **Document in README**: Usage and implementation details +7. **Export from examples/mod.ts**: Make available to framework users + +### Updating Documentation + +1. **Identify affected docs**: README, docs/*, example READMEs +2. **Update code examples**: Ensure accuracy +3. **Verify consistency**: Cross-reference related documentation +4. **Check completeness**: All new features documented? +5. **Update version**: If significant doc changes + +### Refactoring Framework Code + +1. **Plan the refactor**: Identify scope and goals +2. **Check example usage**: How do examples use current API? +3. **Make changes**: Implement refactoring +4. **Update examples**: Ensure examples still work +5. **Update types**: Reflect any interface changes +6. **Update docs**: Document new patterns +7. **Verify exports**: Check mod.ts exports + +## Framework-Specific Considerations + +### Understanding the Framework Architecture + +**Core Concepts:** +- **LLMTool**: Base class providing common functionality +- **IProjectEditor**: Interface for file/project operations (consumed by tools) +- **IConversationInteraction**: Interface for conversation context (consumed by tools) +- **Formatters**: Dual formatting for browser UI and console output +- **Tool Metadata**: info.json provides tool information to BB + +**Tool Lifecycle:** +1. Tool registered with BB +2. LLM decides to use tool based on capabilities +3. BB validates input against `inputSchema` +4. Tool's `runTool` method executes +5. Results formatted for display (browser or console) +6. Results returned to LLM + +### Maintaining Backward Compatibility + +When modifying the framework: +- Existing tools should continue to work without changes +- Add new optional parameters rather than breaking existing ones +- Deprecate features rather than removing them suddenly +- Provide clear migration paths for breaking changes +- Consider semantic versioning implications + +### JSR Publishing Considerations + +- Package published to `@beyondbetter/tools` +- Version in `deno.json` must be bumped for releases +- Ensure all exports in `mod.ts` are intentional +- No private implementation details should leak through exports +- GitHub Actions handles publishing on push to main + +## Common Pitfalls and Solutions + +### TypeScript/Deno Specifics + +**Pitfall**: Forgetting file extensions in imports +```typescript +// ❌ Wrong +import { LLMTool } from './llm_tool'; + +// ✅ Correct +import { LLMTool } from './llm_tool.ts'; +``` + +**Pitfall**: Mixing JSX without proper setup +```typescript +// ❌ Wrong - missing JSX directive +import { h } from 'preact'; + +// ✅ Correct +/** @jsxImportSource preact */ +import type { JSX } from 'preact'; +``` + +### Framework Usage + +**Pitfall**: Not implementing all required methods +```typescript +// ❌ Wrong - missing formatters +class MyTool extends LLMTool { + get inputSchema() { /* ... */ } + async runTool() { /* ... */ } + // Missing: formatLogEntryToolUse, formatLogEntryToolResult +} + +// ✅ Correct - all methods implemented +class MyTool extends LLMTool { + get inputSchema() { /* ... */ } + async runTool() { /* ... */ } + formatLogEntryToolUse() { /* ... */ } + formatLogEntryToolResult() { /* ... */ } +} +``` + +**Pitfall**: Inconsistent formatting utilities +```typescript +// ❌ Wrong - using raw JSX without utilities +return
{text}
; + +// ✅ Correct - using framework utilities +return LLMTool.TOOL_TAGS_BROWSER.content.text(text); +``` + +### Testing + +**Pitfall**: Not mocking interfaces properly +```typescript +// ❌ Wrong - using real implementations +const tool = new MyTool(); +await tool.runTool(realInteraction, toolUse, realEditor); + +// ✅ Correct - mocking interfaces +const mockInteraction = { + projectEditor: mockEditor, + conversationLogger: mockLogger, + // ... other required properties +}; +await tool.runTool(mockInteraction, toolUse, mockEditor); +``` + +**Pitfall**: Not using test utilities +```typescript +// ❌ Wrong - manual temp directory management +const tempDir = await Deno.makeTempDir(); +try { + // test code +} finally { + await Deno.remove(tempDir, { recursive: true }); +} + +// ✅ Correct - using withTestProject helper +import { withTestProject } from '@beyondbetter/tools/testing'; + +await withTestProject(async (projectEditor) => { + // test code - cleanup handled automatically +}); +``` + +### Resource Management + +**Pitfall**: Not cleaning up resources +```typescript +// ❌ Wrong - no cleanup on error +async runTool() { + const resource = await acquireResource(); + // operations that might throw + await releaseResource(resource); +} + +// ✅ Correct - cleanup with try/finally +async runTool() { + const resource = await acquireResource(); + try { + // operations + return result; + } finally { + await releaseResource(resource); + } +}``` + +## Quick Reference + +### Essential Commands +```bash +# Run tests +deno test + +# Run specific test file +deno test examples/search_project.tool/tool.test.ts + +# Run tests with coverage +deno test --coverage + +# Format code +deno fmt + +# Lint code +deno lint + +# Check types +deno check mod.ts +``` + +### File Templates + +**Minimal Tool Implementation:** +See `examples/search_project.tool/` for complete reference implementation. + +**Minimal Test with Test Utilities:** +```typescript +import { assertEquals } from '@std/assert'; +import { withTestProject } from '@beyondbetter/tools/testing'; +import { MyTool } from './tool.ts'; + +Deno.test({ + name: 'MyTool - basic functionality', + async fn() { + await withTestProject(async (projectEditor) => { + const tool = new MyTool(); + const mockInteraction = { + projectEditor, + // ... other required properties + }; + const result = await tool.runTool( + mockInteraction, + mockToolUse, + projectEditor + ); + assertEquals(result.toolResults, expectedResults); + }); + }, +}); +``` + +**Mock Objects Pattern:** +```typescript +// Mock conversation interaction +const mockInteraction: IConversationInteraction = { + getFileMetadata: () => mockMetadata, + readProjectFileContent: async () => mockContent, + // ... other required methods +}; + +// Mock tool use +const mockToolUse: LLMAnswerToolUse = { + id: 'test-id', + name: 'your-tool', + toolInput: { + // Tool-specific parameters + }, +}; +``` + +### Key Framework Exports +```typescript +// From mod.ts +export { default as LLMTool } from './llm_tool.ts'; +export type { + IConversationInteraction, + IProjectEditor, + LLMToolInputSchema, + LLMToolRunResult, + LLMAnswerToolUse, + // ... other types +} from './types.ts'; +``` + +## Related Resources + +### Internal Documentation +- `README.md` - Project overview and quick start +- `docs/CREATING_TOOLS.md` - Comprehensive guide for tool creators +- `docs/TESTING.md` - Testing guidelines and best practices +- `docs/tools.md` - Framework reference documentation +- `examples/search_project.tool/README.md` - Example tool walkthrough + +### External Resources +- [JSR Package](https://jsr.io/@beyondbetter/tools) - Published package +- [Beyond Better](https://github.com/beyond-better/bb) - Main BB project +- [Deno Documentation](https://deno.land/manual) - Deno runtime docs +- [Preact Documentation](https://preactjs.com/) - JSX framework for browser formatting + +### Related BB Projects +- `@beyondbetter/types` - Shared types across BB projects +- Beyond Better main application - Tool runtime environment + +## Testing Reference + +### Test Coverage Requirements + +Each example tool must test: +1. **Core functionality** - All public methods +2. **Input validation** - Schema compliance +3. **Edge cases** - Boundary conditions, invalid inputs +4. **Error handling** - Expected errors, resource limits +5. **Formatters** - Both browser and console output +6. **Styling** - Proper use of TOOL_TAGS_BROWSER and TOOL_STYLES_CONSOLE + +### Test Utilities + +**withTestProject**: Helper for temporary project setup +```typescript +export async function withTestProject( + fn: (projectEditor: IProjectEditor) => Promise, +) { + const tempDir = await Deno.makeTempDir(); + try { + const projectEditor = createTestProjectEditor(tempDir); + await fn(projectEditor); + } finally { + await Deno.remove(tempDir, { recursive: true }); + } +} +``` + +### Formatter Testing Pattern + +```typescript +// Browser formatter test +Deno.test({ + name: 'MyTool - Browser formatter', + fn() { + const tool = new MyTool(); + const result = tool.formatLogEntryToolUse(mockInput, 'browser'); + + // Verify structure + assertEquals( + result.title, + LLMTool.TOOL_TAGS_BROWSER.content.title('Tool Use', 'My Tool') + ); + + // Verify content components + const content = result.content as JSX.Element; + assertNotEquals( + content.props.children.find( + (child) => child.type === LLMTool.TOOL_TAGS_BROWSER.base.label + ), + undefined + ); + }, +}); + +// Console formatter test +Deno.test({ + name: 'MyTool - Console formatter', + fn() { + const tool = new MyTool(); + const result = tool.formatLogEntryToolUse(mockInput, 'console'); + + // Verify structure + assertEquals( + result.title, + LLMTool.TOOL_STYLES_CONSOLE.content.title('Tool Use', 'My Tool') + ); + + // Verify styled content + assertStringIncludes( + result.content, + LLMTool.TOOL_STYLES_CONSOLE.base.label('Parameters') + ); + }, +}); +``` + +## Version History + +### 1.0.1 (2025-11-02) +- Enhanced with details from docs/CREATING_TOOLS.md +- Added tool types and patterns (File System, Data Processing, Network) +- Expanded styling components reference +- Added tool planning template +- Included testing utilities (withTestProject) +- Added comprehensive formatter testing patterns +- Included resource management best practices + +### 1.0.0 (2025-11-02) +- Initial guidelines created +- Documented framework structure and development standards +- Established collaboration workflow +- Defined testing and publishing processes \ No newline at end of file diff --git a/conversation.ts b/conversation.ts deleted file mode 100644 index 361a7fb..0000000 --- a/conversation.ts +++ /dev/null @@ -1,239 +0,0 @@ -import type { FileMetadata, TokenUsage, ToolUsageStats } from './types.ts'; -import type { LLMMessageContentPart, LLMMessageContentParts } from './message.ts'; - -/** - * Conversation management module for the BB Tools Framework. - * Provides interfaces and types for managing conversation state, file interactions, - * and tool usage tracking within a conversation context. - * - * @module - */ - -/** - * Core interface representing the conversation interaction capabilities needed by tools. - * Provides methods for managing files, tracking tool usage, and handling conversation state. - * - * @example - * ```ts - * class ConversationManager implements IConversationInteraction { - * async readProjectFileContent(filePath: string, revisionId: string) { - * // Implementation - * return await fs.readFile(filePath, 'utf8'); - * } - * // ... other method implementations - * } - * ``` - */ -export interface IConversationInteraction { - /** - * Retrieves metadata for a specific file and revision. - * Used to get information about a file without reading its contents. - * - * @param filePath - Path to the file relative to project root - * @param revisionId - Unique identifier for the file revision - * @returns File metadata if found, undefined otherwise - * - * @example - * ```ts - * const metadata = conversation.getFileMetadata('src/config.ts', 'rev-123'); - * if (metadata) { - * console.log(`File size: ${metadata.size} bytes`); - * } - * ``` - */ - getFileMetadata( - filePath: string, - revisionId: string, - ): FileMetadata | undefined; - - /** - * Reads the content of a project file for a specific revision. - * Supports both text and binary files. - * - * @param filePath - Path to the file relative to project root - * @param revisionId - Unique identifier for the file revision - * @returns Promise resolving to file content as string or Uint8Array - * - * @example - * ```ts - * const content = await conversation.readProjectFileContent('src/config.ts', 'rev-123'); - * if (typeof content === 'string') { - * console.log('File content:', content); - * } - * ``` - */ - readProjectFileContent( - filePath: string, - revisionId: string, - ): Promise; - - /** - * Stores a new revision of a file. - * Creates or updates file content while maintaining revision history. - * - * @param filePath - Path to the file relative to project root - * @param revisionId - Unique identifier for the new revision - * @param content - New file content as string or Uint8Array - * - * @example - * ```ts - * await conversation.storeFileRevision( - * 'src/config.ts', - * 'rev-124', - * 'export const config = { debug: true };' - * ); - * ``` - */ - storeFileRevision( - filePath: string, - revisionId: string, - content: string | Uint8Array, - ): Promise; - - /** - * Retrieves a specific revision of a file. - * Used to access historical versions of files. - * - * @param filePath - Path to the file relative to project root - * @param revisionId - Unique identifier for the file revision - * @returns Promise resolving to file content or null if not found - * - * @example - * ```ts - * const oldContent = await conversation.getFileRevision('src/config.ts', 'rev-100'); - * if (oldContent) { - * console.log('Previous version:', oldContent); - * } - * ``` - */ - getFileRevision( - filePath: string, - revisionId: string, - ): Promise; - - /** - * Retrieves current tool usage statistics. - * Used to track and analyze tool usage patterns. - * - * @returns Current tool usage statistics - * - * @example - * ```ts - * const stats = conversation.getToolUsageStats(); - * console.log(`Most used tool: ${[...stats.toolCounts.entries()] - * .sort((a, b) => b[1] - a[1])[0][0]}`); - * ``` - */ - getToolUsageStats(): ToolUsageStats; - - /** - * Updates statistics for a specific tool. - * Records tool usage and success/failure status. - * - * @param toolName - Name of the tool being tracked - * @param success - Whether the tool operation succeeded - * - * @example - * ```ts - * conversation.updateToolStats('search_files', true); - * ``` - */ - updateToolStats(toolName: string, success: boolean): void; - - /** - * Current token usage statistics for the conversation. - * Tracks input, output, and cache-related token usage. - * - * @example - * ```ts - * const usage = conversation.tokenUsageConversation; - * console.log(`Total tokens used: ${usage.totalTokens}`); - * ``` - */ - tokenUsageConversation: TokenUsage; - - /** - * Adds a single file to the conversation context. - * Associates file with a specific message and optional tool use. - * - * @param filePath - Path to the file relative to project root - * @param metadata - File metadata excluding path - * @param messageId - ID of the message this file is associated with - * @param toolUseId - Optional ID of the tool use that added this file - * @returns Object containing file path and complete metadata - * - * @example - * ```ts - * const { fileMetadata } = conversation.addFileForMessage( - * 'src/config.ts', - * { type: 'text', size: 1024, lastModified: new Date() }, - * 'msg-123' - * ); - * ``` - */ - addFileForMessage( - filePath: string, - metadata: Omit, - messageId: string, - toolUseId?: string, - ): { filePath: string; fileMetadata: FileMetadata }; - - /** - * Adds multiple files to the conversation context. - * Efficiently handles batch file additions. - * - * @param filesToAdd - Array of files with their metadata - * @param messageId - ID of the message these files are associated with - * @param toolUseId - Optional ID of the tool use that added these files - * @returns Array of objects containing file paths and complete metadata - * - * @example - * ```ts - * const files = await conversation.addFilesForMessage([ - * { - * fileName: 'src/config.ts', - * metadata: { type: 'text', size: 1024, lastModified: new Date() } - * }, - * { - * fileName: 'src/types.ts', - * metadata: { type: 'text', size: 2048, lastModified: new Date() } - * } - * ], 'msg-123'); - * ``` - */ - addFilesForMessage( - filesToAdd: Array<{ - fileName: string; - metadata: Omit; - }>, - messageId: string, - toolUseId?: string, - ): Array<{ filePath: string; fileMetadata: FileMetadata }>; - - /** - * Creates message content blocks for a specific file revision. - * Used to include file content in conversation messages. - * - * @param filePath - Path to the file relative to project root - * @param revisionId - Unique identifier for the file revision - * @param turnIndex - Index of the conversation turn - * @returns Promise resolving to content blocks or null if file cannot be processed - * - * @example - * ```ts - * const blocks = await conversation.createFileContentBlocks( - * 'src/config.ts', - * 'rev-123', - * 5 - * ); - * if (blocks) { - * console.log(`Created ${blocks.length} content blocks`); - * } - * ``` - */ - createFileContentBlocks( - filePath: string, - revisionId: string, - turnIndex: number, - ): Promise; -} diff --git a/data_source.ts b/data_source.ts new file mode 100644 index 0000000..314ac64 --- /dev/null +++ b/data_source.ts @@ -0,0 +1,209 @@ +/** + * Data source types and interfaces for the BB Tools Framework. + * Provides minimal type definitions for data source connections as seen by tools. + * + * Note: These are simplified versions of the full BB implementation types. + * They expose only what tools need to work with data sources. + * + * @module + */ + +/** + * Data source provider types supported by BB. + * Identifies the underlying provider technology. + * + * @example + * ```ts + * const type: DataSourceProviderType = 'filesystem'; + * ``` + */ +export type DataSourceProviderType = + | 'filesystem' + | 'google' + | 'notion' + | 'supabase' + | 'mcp' + | 'unknown'; + +/** + * Data source access methods. + * Indicates how BB interacts with the data source. + * + * @example + * ```ts + * const method: DataSourceAccessMethod = 'bb'; + * ``` + */ +export type DataSourceAccessMethod = 'bb' | 'mcp'; + +/** + * Capabilities that a data source may support. + * Tools can check these to determine what operations are available. + * + * @example + * ```ts + * const capabilities: DataSourceCapability[] = ['read', 'write', 'list']; + * ``` + */ +export type DataSourceCapability = + | 'read' + | 'write' + | 'list' + | 'search' + | 'move' + | 'delete'; + +/** + * Data source configuration. + * Provider-specific settings (simplified for tool access). + * + * @example + * ```ts + * const config: DataSourceConfig = { + * dataSourceRoot: '/path/to/project' + * }; + * ``` + */ +export interface DataSourceConfig { + dataSourceRoot?: string; // For filesystem type + [key: string]: unknown; // Provider-specific settings +} + +/** + * Data source connection interface. + * Represents a configured data source instance as seen by tools. + * + * This is a minimal interface exposing only what tools need. + * The full implementation in BB has additional methods and properties. + * + * @example + * ```ts + * function checkDataSource(ds: DataSourceConnection) { + * console.log(`${ds.name} (${ds.providerType})`); + * console.log(`Can write: ${ds.capabilities.includes('write')}`); + * console.log(`Read-only: ${ds.readonly}`); + * } + * ``` + */ +export interface DataSourceConnection { + /** + * Unique identifier for this connection. + * + * @example + * ```ts + * const id = dsConnection.id; // 'ds-abc123' + * ``` + */ + readonly id: string; + + /** + * Human-readable name for this connection. + * + * @example + * ```ts + * const name = dsConnection.name; // 'project-files' + * ``` + */ + name: string; + + /** + * Provider type (filesystem, google, notion, etc.). + * + * @example + * ```ts + * if (dsConnection.providerType === 'filesystem') { + * // Handle filesystem-specific logic + * } + * ``` + */ + readonly providerType: DataSourceProviderType; + + /** + * Access method (bb or mcp). + * + * @example + * ```ts + * const method = dsConnection.accessMethod; // 'bb' + * ``` + */ + readonly accessMethod: DataSourceAccessMethod; + + /** + * Capabilities this data source supports. + * + * @example + * ```ts + * const canWrite = dsConnection.capabilities.includes('write'); + * const canSearch = dsConnection.capabilities.includes('search'); + * ``` + */ + readonly capabilities: DataSourceCapability[]; + + /** + * Provider-specific configuration. + * + * @example + * ```ts + * if (dsConnection.providerType === 'filesystem') { + * const root = dsConnection.config.dataSourceRoot; + * } + * ``` + */ + config: DataSourceConfig; + + /** + * Whether this connection is currently enabled. + * + * @example + * ```ts + * if (!dsConnection.enabled) { + * console.warn('Data source is disabled'); + * } + * ``` + */ + enabled: boolean; + + /** + * Whether this connection is read-only. + * + * @example + * ```ts + * if (dsConnection.readonly) { + * console.log('Cannot modify resources in this data source'); + * } + * ``` + */ + readonly: boolean; + + /** + * Whether this is the primary data source. + * + * @example + * ```ts + * if (dsConnection.isPrimary) { + * console.log('This is the default data source'); + * } + * ``` + */ + isPrimary: boolean; + + /** + * URI prefix for resources in this data source. + * + * @example + * ```ts + * const prefix = dsConnection.uriPrefix; // 'bb+filesystem+project+file:' + * ``` + */ + uriPrefix?: string; + + /** + * URI template for constructing resource URIs. + * + * @example + * ```ts + * const template = dsConnection.uriTemplate; // 'bb+filesystem+project+file:./{path}' + * ``` + */ + uriTemplate?: string; +} diff --git a/deno.json b/deno.jsonc similarity index 87% rename from deno.json rename to deno.jsonc index 57f2a91..d9bfb69 100644 --- a/deno.json +++ b/deno.jsonc @@ -14,6 +14,8 @@ "test": "deno test --allow-read --allow-write", "format": "deno fmt", "check": "deno check mod.ts", + "lint": "deno lint *.ts *.tsx", + "update-version": "deno run --allow-read --allow-write scripts/update-version.ts", "update-deps": "deno cache mod.ts" }, "fmt": { diff --git a/deno.lock b/deno.lock index 3adbac8..882c76e 100644 --- a/deno.lock +++ b/deno.lock @@ -1,15 +1,18 @@ { - "version": "4", + "version": "5", "specifiers": { "jsr:@cliffy/ansi@1.0.0-rc.7": "1.0.0-rc.7", "jsr:@cliffy/internal@1.0.0-rc.7": "1.0.0-rc.7", "jsr:@std/encoding@~1.0.5": "1.0.5", "jsr:@std/fmt@^1.0.3": "1.0.3", "jsr:@std/fmt@~1.0.2": "1.0.3", + "jsr:@std/jsonc@^1.0.1": "1.0.2", "npm:@types/json-schema@7.0.15": "7.0.15", + "npm:@types/node@*": "22.12.0", "npm:ajv@8.14.0": "8.14.0", "npm:ajv@8.17.1": "8.17.1", "npm:common-tags@1.8.2": "1.8.2", + "npm:node-fetch@3.3.2": "3.3.2", "npm:open@*": "10.1.0", "npm:open@10.1.0": "10.1.0", "npm:preact@*": "10.25.3", @@ -33,12 +36,21 @@ }, "@std/fmt@1.0.3": { "integrity": "97765c16aa32245ff4e2204ecf7d8562496a3cb8592340a80e7e554e0bb9149f" + }, + "@std/jsonc@1.0.2": { + "integrity": "909605dae3af22bd75b1cbda8d64a32cf1fd2cf6efa3f9e224aba6d22c0f44c7" } }, "npm": { "@types/json-schema@7.0.15": { "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" }, + "@types/node@22.12.0": { + "integrity": "sha512-Fll2FZ1riMjNmlmJOdAyY5pUbkftXslB5DgEzlIuNaiWhXd00FhWxVC/r4yV/4wBb9JfImTu+jiSvXTkJ7F/gA==", + "dependencies": [ + "undici-types" + ] + }, "ajv@8.14.0": { "integrity": "sha512-oYs1UUtO97ZO2lJ4bwnWeQW8/zvOIQLGKcvPTsWmvc2SYgBb+upuNS5NxoLaMU4h8Ju3Nbj6Cq8mD2LQoqVKFA==", "dependencies": [ @@ -66,6 +78,9 @@ "common-tags@1.8.2": { "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==" }, + "data-uri-to-buffer@4.0.1": { + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==" + }, "default-browser-id@5.0.0": { "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==" }, @@ -85,14 +100,29 @@ "fast-uri@3.0.3": { "integrity": "sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==" }, + "fetch-blob@3.2.0": { + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "dependencies": [ + "node-domexception", + "web-streams-polyfill" + ] + }, + "formdata-polyfill@4.0.10": { + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dependencies": [ + "fetch-blob" + ] + }, "is-docker@3.0.0": { - "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==" + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "bin": true }, "is-inside-container@1.0.0": { "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", "dependencies": [ "is-docker" - ] + ], + "bin": true }, "is-wsl@3.1.0": { "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", @@ -103,6 +133,18 @@ "json-schema-traverse@1.0.0": { "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, + "node-domexception@1.0.0": { + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": true + }, + "node-fetch@3.3.2": { + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dependencies": [ + "data-uri-to-buffer", + "fetch-blob", + "formdata-polyfill" + ] + }, "open@10.1.0": { "integrity": "sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==", "dependencies": [ @@ -127,11 +169,17 @@ "run-applescript@7.0.0": { "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==" }, + "undici-types@6.20.0": { + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==" + }, "uri-js@4.4.1": { "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dependencies": [ "punycode" ] + }, + "web-streams-polyfill@3.3.3": { + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==" } }, "remote": { @@ -142,7 +190,16 @@ }, "workspace": { "dependencies": [ - "npm:preact@*" + "jsr:@cliffy/ansi@1.0.0-rc.7", + "jsr:@std/assert@^1.0.8", + "jsr:@std/fmt@^1.0.3", + "jsr:@std/fs@^1.0.5", + "jsr:@std/jsonc@^1.0.1", + "jsr:@std/path@^1.0.8", + "npm:@types/json-schema@7.0.15", + "npm:ajv@8.17.1", + "npm:common-tags@1.8.2", + "npm:preact@10.25.3" ] } } diff --git a/import_map.json b/import_map.json index 7150f00..e4bcd5d 100644 --- a/import_map.json +++ b/import_map.json @@ -9,6 +9,7 @@ "common-tags": "npm:common-tags@1.8.2", "@std/fmt": "jsr:@std/fmt@^1.0.3", "@std/assert": "jsr:@std/assert@^1.0.8", + "@std/jsonc": "jsr:@std/jsonc@^1.0.1", "@std/path": "jsr:@std/path@^1.0.8", "@std/fs": "jsr:@std/fs@^1.0.5" } diff --git a/interaction.ts b/interaction.ts new file mode 100644 index 0000000..33eb05d --- /dev/null +++ b/interaction.ts @@ -0,0 +1,292 @@ +import type { FileMetadata, TokenUsage, ToolUsageStats } from './types.ts'; +import type { LLMMessageContentParts } from './message.ts'; +import type { IProjectEditor } from './project_editor.ts'; + +/** + * Interaction management module for the BB Tools Framework. + * Provides interfaces and types for managing interaction state, resource handling, + * and tool usage tracking within an interaction context. + * + * Note: This interface represents capabilities available to tools during execution. + * It is extracted from BB's LLMConversationInteraction implementation. + * + * @module + */ + +/** + * Core interface representing the interaction capabilities needed by tools. + * Provides methods for managing resources, tracking tool usage, and handling interaction state. + * + * This interface is what tools receive when they execute - it provides access to: + * - Resource management (reading/writing files and other resources) + * - Conversation/interaction context + * - Project editing capabilities + * - Tool usage statistics + * + * @example + * ```ts + * class MyTool extends LLMTool { + * async runTool( + * interaction: IConversationInteraction, + * toolUse: LLMAnswerToolUse, + * projectEditor: IProjectEditor + * ) { + * // Access interaction capabilities + * const stats = interaction.getToolUsageStats(); + * + * // Read resource content + * const content = await interaction.readResourceContent( + * 'bb+filesystem+project+file:./src/config.ts', + * 'rev-123', + * metadata + * ); + * + * return { toolResults, toolResponse, bbResponse }; + * } + * } + * ``` + */ +export interface IConversationInteraction { + /** + * Reference to the project editor. + * Provides access to project-level operations and configuration. + * + * @example + * ```ts + * const projectRoot = interaction.projectEditor.projectRoot; + * const dsConnections = interaction.projectEditor.dsConnections; + * ``` + */ + projectEditor: IProjectEditor; + + /** + * Retrieves metadata for a specific resource revision. + * Used to get information about a resource without reading its contents. + * + * @param resourceUri - URI of the resource (e.g., 'bb+filesystem+project+file:./src/config.ts') + * @param revisionId - Unique identifier for the resource revision + * @returns Resource metadata if found, undefined otherwise + * + * @example + * ```ts + * const metadata = interaction.getFileMetadata( + * 'bb+filesystem+project+file:./src/config.ts', + * 'rev-123' + * ); + * if (metadata) { + * console.log(`Resource size: ${metadata.size} bytes`); + * } + * ``` + */ + getFileMetadata( + resourceUri: string, + revisionId: string, + ): FileMetadata | undefined; + + /** + * Reads the content of a resource for a specific revision. + * Supports both text and binary resources. + * + * @param resourceUri - URI of the resource + * @param revisionId - Unique identifier for the resource revision + * @param resourceMetadata - Optional metadata for the resource + * @returns Promise resolving to resource content as string or Uint8Array + * + * @example + * ```ts + * const content = await interaction.readResourceContent( + * 'bb+filesystem+project+file:./src/config.ts', + * 'rev-123', + * metadata + * ); + * if (typeof content === 'string') { + * console.log('Resource content:', content); + * } + * ``` + */ + readResourceContent( + resourceUri: string, + revisionId: string, + resourceMetadata: FileMetadata | undefined, + ): Promise; + + /** + * Stores a new revision of a resource. + * Creates or updates resource content while maintaining revision history. + * + * @param resourceUri - URI of the resource + * @param revisionId - Unique identifier for the new revision + * @param content - New resource content as string or Uint8Array + * @param resourceMetadata - Metadata for the resource + * + * @example + * ```ts + * await interaction.storeResourceRevision( + * 'bb+filesystem+project+file:./src/config.ts', + * 'rev-124', + * 'export const config = { debug: true };', + * metadata + * ); + * ``` + */ + storeResourceRevision( + resourceUri: string, + revisionId: string, + content: string | Uint8Array, + resourceMetadata: FileMetadata | undefined, + ): Promise; + + /** + * Retrieves a specific revision of a resource. + * Used to access historical versions of resources. + * + * @param resourceUri - URI of the resource + * @param revisionId - Unique identifier for the resource revision + * @param resourceMetadata - Optional metadata for the resource + * @returns Promise resolving to resource content or null if not found + * + * @example + * ```ts + * const oldContent = await interaction.getResourceRevision( + * 'bb+filesystem+project+file:./src/config.ts', + * 'rev-100', + * metadata + * ); + * if (oldContent) { + * console.log('Previous version:', oldContent); + * } + * ``` + */ + getResourceRevision( + resourceUri: string, + revisionId: string, + resourceMetadata: FileMetadata | undefined, + ): Promise; + + /** + * Retrieves current tool usage statistics. + * Used to track and analyze tool usage patterns. + * + * @returns Current tool usage statistics + * + * @example + * ```ts + * const stats = interaction.getToolUsageStats(); + * console.log(`Most used tool: ${[...stats.toolCounts.entries()] + * .sort((a, b) => b[1] - a[1])[0][0]}`); + * ``` + */ + getToolUsageStats(): ToolUsageStats; + + /** + * Updates statistics for a specific tool. + * Records tool usage and success/failure status. + * + * @param toolName - Name of the tool being tracked + * @param success - Whether the tool operation succeeded + * + * @example + * ```ts + * interaction.updateToolStats('search_files', true); + * ``` + */ + updateToolStats(toolName: string, success: boolean): void; + + /** + * Current token usage statistics for the interaction. + * Tracks input, output, and cache-related token usage. + * + * @example + * ```ts + * const usage = interaction.tokenUsageConversation; + * console.log(`Total tokens used: ${usage.totalTokens}`); + * ``` + */ + tokenUsageConversation: TokenUsage; + + /** + * Adds a single resource to the interaction context. + * Associates resource with a specific message and optional tool use. + * + * @param resourceUri - URI of the resource + * @param metadata - Resource metadata excluding URI + * @param messageId - ID of the message this resource is associated with + * @param toolUseId - Optional ID of the tool use that added this resource + * @returns Object containing resource URI and complete metadata + * + * @example + * ```ts + * const { resourceMetadata } = interaction.addResourceForMessage( + * 'bb+filesystem+project+file:./src/config.ts', + * { type: 'text', size: 1024, lastModified: new Date() }, + * 'msg-123' + * ); + * ``` + */ + addResourceForMessage( + resourceUri: string, + metadata: Omit, + messageId: string, + toolUseId?: string, + ): { resourceUri: string; resourceMetadata: FileMetadata }; + + /** + * Adds multiple resources to the interaction context. + * Efficiently handles batch resource additions. + * + * @param resourcesToAdd - Array of resources with their metadata + * @param messageId - ID of the message these resources are associated with + * @param toolUseId - Optional ID of the tool use that added these resources + * @returns Array of objects containing resource URIs and complete metadata + * + * @example + * ```ts + * const resources = await interaction.addResourcesForMessage([ + * { + * resourceUri: 'bb+filesystem+project+file:./src/config.ts', + * metadata: { type: 'text', size: 1024, lastModified: new Date() } + * }, + * { + * resourceUri: 'bb+filesystem+project+file:./src/types.ts', + * metadata: { type: 'text', size: 2048, lastModified: new Date() } + * } + * ], 'msg-123'); + * ``` + */ + addResourcesForMessage( + resourcesToAdd: Array<{ + resourceName?: string; + resourceUri: string; + metadata: Omit; + }>, + messageId: string, + toolUseId?: string, + ): Array<{ resourceUri: string; resourceMetadata: FileMetadata }>; + + /** + * Creates message content blocks for a specific resource revision. + * Used to include resource content in conversation messages. + * + * @param resourceUri - URI of the resource + * @param revisionId - Unique identifier for the resource revision + * @param turnIndex - Index of the conversation turn + * @returns Promise resolving to content blocks or null if resource cannot be processed + * + * @example + * ```ts + * const blocks = await interaction.createResourceContentBlocks( + * 'bb+filesystem+project+file:./src/config.ts', + * 'rev-123', + * 5 + * ); + * if (blocks) { + * console.log(`Created ${blocks.length} content blocks`); + * } + * ``` + */ + createResourceContentBlocks( + resourceUri: string, + revisionId: string, + turnIndex: number, + ): Promise; +} diff --git a/llm_tool.ts b/llm_tool.ts index 5d9be23..1b883b4 100644 --- a/llm_tool.ts +++ b/llm_tool.ts @@ -11,7 +11,7 @@ import type { JSONSchema4 } from 'json-schema'; import { Ajv } from 'ajv'; import { TOOL_STYLES_BROWSER, TOOL_STYLES_CONSOLE, TOOL_TAGS_BROWSER } from './llm_tool_tags.tsx'; -import type { IConversationInteraction } from './conversation.ts'; +import type { IConversationInteraction } from './interaction.ts'; import type { IProjectEditor } from './project_editor.ts'; import type { LLMAnswerToolUse, LLMMessageContentPart, LLMMessageContentParts } from './message.ts'; @@ -194,6 +194,7 @@ abstract class LLMTool { public features: LLMToolFeatures = {}, ) {} + // deno-lint-ignore require-await public async init(): Promise { return this; } diff --git a/llm_tool_tags.tsx b/llm_tool_tags.tsx index e31285e..127f6d1 100644 --- a/llm_tool_tags.tsx +++ b/llm_tool_tags.tsx @@ -69,6 +69,19 @@ const formatNumber = ( return new Intl.NumberFormat('en-US', opts).format(num); }; +const formatByteSize = (bytes: number, decimals = 2): string => { + if (bytes === 0) return '0 Bytes'; + + const k = 1024; + const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + const size = bytes / Math.pow(k, i); + + return `${ + formatNumber(size, { minimumFractionDigits: decimals, maximumFractionDigits: decimals }) + } ${sizes[i]}`; +}; + /** * Formats a duration in milliseconds into a human-readable string. * Automatically selects appropriate units (days, hours, minutes, seconds). @@ -171,7 +184,7 @@ const formatBoolean = ( */ export const TOOL_STYLES_BROWSER = { base: { - container: 'rounded-lg prose dark:prose-invert max-w-none py-1 px-4', + container: 'rounded-lg prose prose-sm dark:prose-invert max-w-none py-1 px-4', box: 'rounded-lg max-w-none py-1 px-4 whitespace-pre-wrap', pre: 'p-2.5 rounded font-mono text-sm text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-900/50', @@ -179,7 +192,9 @@ export const TOOL_STYLES_BROWSER = { list: 'space-y-2', listItem: 'ml-4', label: 'font-semibold text-gray-700 dark:text-gray-300', + text: 'text-gray-700 dark:text-gray-300', }, + // Status-based styles status: { error: 'bg-red-50 dark:bg-red-900/30 border-red-200 dark:border-red-800 text-red-700 dark:text-red-400', @@ -190,19 +205,27 @@ export const TOOL_STYLES_BROWSER = { info: 'bg-blue-50 dark:bg-blue-900/30 border-blue-200 dark:border-blue-800 text-blue-700 dark:text-blue-400', }, + // Content type styles content: { error: 'text-red-600 dark:text-red-400 bg-red-50 dark:bg-red-800 px-2 py-1 rounded', + // Existing styles code: 'bg-gray-50 dark:bg-gray-800 border-gray-200 dark:border-gray-700', data: 'bg-blue-50 dark:bg-blue-900/30 border-blue-200 dark:border-blue-700', filename: 'font-mono text-cyan-600 dark:text-cyan-400', + + // Time-related timestamp: 'font-mono text-gray-600 dark:text-gray-400', duration: 'font-mono text-purple-600 dark:text-purple-400', timeRange: 'font-mono text-purple-600 dark:text-purple-400', timeAgo: 'font-mono text-purple-600 dark:text-purple-400', + + // Numbers/Metrics percentage: 'font-mono text-emerald-600 dark:text-emerald-400', number: 'font-mono text-blue-600 dark:text-blue-400', bytes: 'font-mono text-blue-600 dark:text-blue-400', speed: 'font-mono text-blue-600 dark:text-blue-400', + + // Status/States status: { running: 'bg-blue-100 dark:bg-blue-900/50 text-blue-800 dark:text-blue-300 px-2 py-0.5 rounded-full text-sm', @@ -226,6 +249,8 @@ export const TOOL_STYLES_BROWSER = { low: 'text-green-600 dark:text-green-400 font-semibold', }, version: 'font-mono text-gray-600 dark:text-gray-400', + + // UI/Display badge: { default: 'bg-gray-100 dark:bg-gray-800 text-gray-800 dark:text-gray-200 px-2 py-0.5 rounded-full text-sm', @@ -293,6 +318,7 @@ export const TOOL_STYLES_CONSOLE = { info: (text: string): string => colors.blue(text), }, content: { + // Time-related timestamp: (date: Date | string): string => colors.gray(new Date(date).toISOString()), duration: (ms: number): string => colors.magenta(formatDuration(ms)), timeRange: (start: Date | string, end: Date | string): string => @@ -301,13 +327,12 @@ export const TOOL_STYLES_CONSOLE = { ), timeAgo: (date: Date | string): string => colors.gray(formatTimeAgo(new Date(date))), date: (value: Date | string): string => colors.gray(new Date(value).toLocaleString()), + + // Numbers/Metrics percentage: (value: number, decimals = 1): string => colors.green( `${ - formatNumber(value, { - minimumFractionDigits: decimals, - maximumFractionDigits: decimals, - }) + formatNumber(value, { minimumFractionDigits: decimals, maximumFractionDigits: decimals }) }%`, ), number: ( @@ -315,31 +340,16 @@ export const TOOL_STYLES_CONSOLE = { opts: { minimumFractionDigits?: number; maximumFractionDigits?: number } = {}, ): string => colors.blue(formatNumber(value, opts)), bytes: (value: number, decimals = 1): string => { - const units = ['B', 'KB', 'MB', 'GB']; - let size = value; - let unit = 0; - while (size >= 1024 && unit < units.length - 1) { - size /= 1024; - unit++; - } - return colors.blue( - `${ - formatNumber(size, { - minimumFractionDigits: decimals, - maximumFractionDigits: decimals, - }) - } ${units[unit]}`, - ); + return colors.blue(formatByteSize(value, decimals)); }, speed: (value: number, unit: string, decimals = 1): string => colors.blue( `${ - formatNumber(value, { - minimumFractionDigits: decimals, - maximumFractionDigits: decimals, - }) + formatNumber(value, { minimumFractionDigits: decimals, maximumFractionDigits: decimals }) } ${unit}/s`, ), + + // Status/States status: { running: (text: string): string => colors.blue(text), completed: (text: string): string => colors.green(text), @@ -357,6 +367,8 @@ export const TOOL_STYLES_CONSOLE = { low: (text: string): string => colors.green(colors.bold(text)), }, version: (version: string): string => colors.gray(version), + + // UI/Display badge: { default: (text: string): string => colors.gray(text), primary: (text: string): string => colors.blue(text), @@ -395,34 +407,31 @@ export const TOOL_STYLES_CONSOLE = { unit++; } return colors.gray( - `${ - formatNumber(size, { - minimumFractionDigits: 1, - maximumFractionDigits: 1, - }) - } ${units[unit]}`, + `${formatNumber(size, { minimumFractionDigits: 1, maximumFractionDigits: 1 })} ${ + units[unit] + }`, ); }, patch: (text: string): string => colors.yellow(text), code: (text: string): string => text, data: (text: string): string => colors.blue(text), + separator: (char = '─', length = 60): string => { + return colors.dim(char.repeat(length)); + }, }, }; // Header tag functions -export const createToolTitle = ( - toolRole: string, - toolName: string, -): JSX.Element => { +export const createToolTitle = (toolRole: string, toolName: string): JSX.Element => { const title = toolRole === 'Tool Use' ? 'Tool Input' : toolRole === 'Tool Result' ? 'Tool Output' : 'Tool'; return ( -
+ {title} ({toolName}) -
+ ); }; @@ -469,6 +478,10 @@ export const createToolLabel = (text: string): JSX.Element => ( {text} ); +export const createToolText = (text: string): JSX.Element => ( + {text} +); + export const createToolFilename = (text: string): JSX.Element => ( {text} ); @@ -478,15 +491,11 @@ export const createToolUrl = (text: string): JSX.Element => ( ); export const createToolCounts = (count: number): JSX.Element => ( - - {count.toLocaleString()} - + {count.toLocaleString()} ); export const createToolTokenUsage = (count: number): JSX.Element => ( - - {count.toLocaleString()} - + {count.toLocaleString()} ); export const createToolToolName = (text: string): JSX.Element => ( @@ -525,11 +534,7 @@ export const createToolSize = (bytes: number): JSX.Element => { size /= 1024; unit++; } - return ( - - {size.toFixed(1)} {units[unit]} - - ); + return {size.toFixed(1)} {units[unit]}; }; // Create new tag functions for time-related elements @@ -558,15 +563,9 @@ export const createToolTimeRange = (start: Date, end: Date): JSX.Element => ( ); // Create new tag functions for numbers/metrics -export const createToolPercentage = ( - value: number, - decimals = 1, -): JSX.Element => ( +export const createToolPercentage = (value: number, decimals = 1): JSX.Element => ( - {formatNumber(value, { - minimumFractionDigits: decimals, - maximumFractionDigits: decimals, - })}% + {formatNumber(value, { minimumFractionDigits: decimals, maximumFractionDigits: decimals })}% ); @@ -578,29 +577,20 @@ export const createToolNumber = (value: number): JSX.Element => ( export const createToolBytes = (bytes: number, decimals = 1): JSX.Element => ( - {formatNumber(bytes, { - minimumFractionDigits: decimals, - maximumFractionDigits: decimals, - })} B + {formatByteSize(bytes, decimals)} ); -export const createToolSpeed = ( - value: number, - unit: string, - decimals = 1, -): JSX.Element => ( +export const createToolSpeed = (value: number, unit: string, decimals = 1): JSX.Element => ( - {formatNumber(value, { - minimumFractionDigits: decimals, - maximumFractionDigits: decimals, - })} {unit}/s + {formatNumber(value, { minimumFractionDigits: decimals, maximumFractionDigits: decimals })}{' '} + {unit}/s ); // Create new tag functions for status/states export const createToolStatus = ( - status: 'running' | 'completed' | 'failed' | 'pending', + status: 'running' | 'completed' | 'warning' | 'failed' | 'pending', text?: string, ): JSX.Element => ( @@ -608,10 +598,7 @@ export const createToolStatus = ( ); -export const createToolProgress = ( - current: number, - total: number, -): JSX.Element => ( +export const createToolProgress = (current: number, total: number): JSX.Element => ( {current}/{total} @@ -664,20 +651,14 @@ export const createToolLink = (text: string, href: string): JSX.Element => ( ); -export const createToolDiff = ( - text: string, - type: 'add' | 'remove', -): JSX.Element => ( +export const createToolDiff = (text: string, type: 'add' | 'remove'): JSX.Element => ( {type === 'add' ? '+' : '-'} {text} ); -export const createToolTruncated = ( - text: string, - maxLength: number, -): JSX.Element => ( +export const createToolTruncated = (text: string, maxLength: number): JSX.Element => ( {text.length > maxLength ? `${text.slice(0, maxLength)}...` : text} @@ -714,22 +695,32 @@ export const createToolTruncated = ( * ``` */ export const TOOL_TAGS_BROWSER = { + // base elements base: { container: createToolContent, - box: createToolBox, + box: createToolBox, // like container, but without prose pre: createToolPre, code: createToolCode, list: createToolList, label: createToolLabel, + text: createToolText, }, + // Content elements content: { + // Standard elements title: createToolTitle, subtitle: createToolSubtitle, + + // File system elements filename: createToolFilename, directory: createToolDirectory, + + // Web elements url: createToolUrl, image: createToolImage, link: createToolLink, + + // Metrics and counts counts: createToolCounts, tokenUsage: createToolTokenUsage, size: createToolSize, @@ -737,20 +728,28 @@ export const TOOL_TAGS_BROWSER = { number: createToolNumber, bytes: createToolBytes, speed: createToolSpeed, + + // Time-related timestamp: createToolTimestamp, duration: createToolDuration, timeRange: createToolTimeRange, timeAgo: createToolTimeAgo, date: createToolDate, + + // Status and states status: createToolStatus, progress: createToolProgress, priority: createToolPriority, version: createToolVersion, boolean: createToolBoolean, + + // UI elements badge: createToolBadge, icon: createToolIcon, diff: createToolDiff, truncated: createToolTruncated, + + // Code elements regex: createToolRegex, error: createToolError, toolName: createToolToolName, diff --git a/message.ts b/message.ts index 619c4af..3a49b75 100755 --- a/message.ts +++ b/message.ts @@ -49,10 +49,48 @@ export interface LLMMessageContentPartBase { * ``` */ export interface LLMMessageContentPartTextBlock extends LLMMessageContentPartBase { + messageId?: string; type: 'text'; text: string; } +/** + * Represents a thinking block in a message. + * Contains the model's reasoning process (e.g., Claude's extended thinking). + * + * @example + * ```ts + * const thinkingBlock: LLMMessageContentPartThinkingBlock = { + * type: 'thinking', + * thinking: 'Let me analyze this step by step...' + * }; + * ``` + */ +export interface LLMMessageContentPartThinkingBlock extends LLMMessageContentPartBase { + messageId?: string; + type: 'thinking'; + thinking: string; + signature?: string; +} + +/** + * Represents a redacted thinking block in a message. + * Contains encrypted or obfuscated reasoning data. + * + * @example + * ```ts + * const redactedBlock: LLMMessageContentPartRedactedThinkingBlock = { + * type: 'redacted_thinking', + * data: 'base64encodeddata...' + * }; + * ``` + */ +export interface LLMMessageContentPartRedactedThinkingBlock extends LLMMessageContentPartBase { + messageId?: string; + type: 'redacted_thinking'; + data: string; +} + /** * Represents an image in a message. * Images are stored as base64-encoded data with associated media type. @@ -70,6 +108,7 @@ export interface LLMMessageContentPartTextBlock extends LLMMessageContentPartBas * ``` */ export interface LLMMessageContentPartImageBlock extends LLMMessageContentPartBase { + messageId?: string; type: 'image'; source: { type: 'base64'; @@ -78,6 +117,24 @@ export interface LLMMessageContentPartImageBlock extends LLMMessageContentPartBa }; } +/** + * Represents an audio block in a message (OpenAI). + * References a previous audio response from the model. + * + * @example + * ```ts + * const audioBlock: LLMMessageContentPartAudioBlock = { + * type: 'audio', + * id: 'audio_abc123' + * }; + * ``` + */ +export interface LLMMessageContentPartAudioBlock extends LLMMessageContentPartBase { + messageId?: string; + type: 'audio'; + id: string; // Unique identifier for a previous audio response from the model +} + /** * Represents a tool invocation in a message. * Contains the tool name and input parameters used for the invocation. @@ -88,15 +145,16 @@ export interface LLMMessageContentPartImageBlock extends LLMMessageContentPartBa * type: 'tool_use', * id: 'tool-123', * name: 'search_files', - * toolInput: { pattern: '*.ts' } + * input: { pattern: '*.ts' } * }; * ``` */ export interface LLMMessageContentPartToolUseBlock extends LLMMessageContentPartBase { + messageId?: string; type: 'tool_use'; id: string; + input: object; name: string; - toolInput: Record; } /** @@ -107,17 +165,18 @@ export interface LLMMessageContentPartToolUseBlock extends LLMMessageContentPart * ```ts * const resultBlock: LLMMessageContentPartToolResultBlock = { * type: 'tool_result', - * id: 'tool-123', - * success: true, - * content: { type: 'text', text: 'Found 5 files' } + * tool_use_id: 'tool-123', + * content: [{ type: 'text', text: 'Found 5 files' }], + * is_error: false * }; * ``` */ export interface LLMMessageContentPartToolResultBlock extends LLMMessageContentPartBase { + messageId?: string; type: 'tool_result'; - id: string; - success: boolean; - content: LLMMessageContentPart | LLMMessageContentParts; + tool_use_id?: string; + content?: Array; + is_error?: boolean; } /** @@ -130,6 +189,7 @@ export interface LLMMessageContentPartToolResultBlock extends LLMMessageContentP * switch(part.type) { * case 'text': return part.text; * case 'image': return part.source.data; + * case 'thinking': return part.thinking; * // ... * } * } @@ -137,7 +197,10 @@ export interface LLMMessageContentPartToolResultBlock extends LLMMessageContentP */ export type LLMMessageContentPart = | LLMMessageContentPartTextBlock + | LLMMessageContentPartThinkingBlock + | LLMMessageContentPartRedactedThinkingBlock | LLMMessageContentPartImageBlock + | LLMMessageContentPartAudioBlock | LLMMessageContentPartToolUseBlock | LLMMessageContentPartToolResultBlock; @@ -155,29 +218,25 @@ export type LLMMessageContentPart = */ export type LLMMessageContentParts = LLMMessageContentPart[]; -export interface LLMToolResult { - toolResult: unknown; - bbResponse: { data: unknown }; -} - /** - * Contains complete information about a tool use, including results. - * Used to track both the tool invocation and its outcome. + * Contains complete information about a tool use in an LLM answer. + * Tracks the tool invocation, input, and validation status. * * @example * ```ts * const toolUse: LLMAnswerToolUse = { - * id: 'tool-123', - * name: 'search_files', - * toolInput: { - * toolResult: { files: ['config.ts'] }, - * bbResponse: { data: { success: true } } - * } + * toolThinking: 'I need to search for TypeScript files...', + * toolInput: { pattern: '*.ts' }, + * toolUseId: 'tool-123', + * toolName: 'search_files', + * toolValidation: { validated: true, results: 'Input is valid' } * }; * ``` */ export interface LLMAnswerToolUse { - id: string; - name: string; - toolInput: LLMToolResult; + toolThinking?: string; + toolInput: Record; + toolUseId: string; + toolName: string; + toolValidation: { validated: boolean; results: string }; } diff --git a/mod.ts b/mod.ts index 0f519eb..baad904 100644 --- a/mod.ts +++ b/mod.ts @@ -59,17 +59,32 @@ export { default } from './llm_tool.ts'; // Core interfaces export type { IProjectEditor } from './project_editor.ts'; -export type { IConversationInteraction } from './conversation.ts'; +export type { IConversationInteraction } from './interaction.ts'; + +// Legacy export for backward compatibility +export type { IConversationInteraction as IConversation } from './interaction.ts'; + +// Data source types +export type { + DataSourceAccessMethod, + DataSourceCapability, + DataSourceConfig, + DataSourceConnection, + DataSourceProviderType, +} from './data_source.ts'; // Message types export type { LLMAnswerToolUse, LLMMessageContentPart, + LLMMessageContentPartAudioBlock, LLMMessageContentPartBase, LLMMessageContentPartImageBlock, LLMMessageContentPartImageBlockSourceMediaType, + LLMMessageContentPartRedactedThinkingBlock, LLMMessageContentParts, LLMMessageContentPartTextBlock, + LLMMessageContentPartThinkingBlock, LLMMessageContentPartToolResultBlock, LLMMessageContentPartToolUseBlock, } from './message.ts'; diff --git a/project_editor.ts b/project_editor.ts index 1b3d346..eefada7 100644 --- a/project_editor.ts +++ b/project_editor.ts @@ -1,5 +1,6 @@ -import type { IConversationInteraction } from './conversation.ts'; +import type { IConversationInteraction } from './interaction.ts'; import type { FileMetadata } from './types.ts'; +import type { DataSourceConnection } from './data_source.ts'; /** * Project management module for the BB Tools Framework. @@ -173,4 +174,50 @@ export interface IProjectEditor { isPathWithinProject( filePath: string, ): Promise; + + /** + * Resolves data source identifiers (IDs or names) to DataSourceConnection objects. + * Supports special 'all' identifier to get all enabled connections. + * + * This is the canonical method for resolving data sources. + * Tools should use this instead of directly accessing project data. + * + * @param dataSourceIds - Array of data source IDs or names, or ['all'] for all enabled sources + * @returns Object containing: + * - primaryDsConnection: The primary data source (may be from the resolved list or undefined) + * - dsConnections: Array of resolved DataSourceConnection objects + * - notFound: Array of IDs/names that could not be resolved + * + * @example Get specific data sources + * ```ts + * const { dsConnections, notFound } = projectEditor.getDsConnectionsById([ + * 'filesystem-local', + * 'notion-work' + * ]); + * if (notFound.length > 0) { + * console.warn(`Could not find: ${notFound.join(', ')}`); + * } + * ``` + * + * @example Get all enabled data sources + * ```ts + * const { dsConnections } = projectEditor.getDsConnectionsById(['all']); + * console.log(`Found ${dsConnections.length} enabled data sources`); + * ``` + * + * @example Get primary data source (omit parameter) + * ```ts + * const { primaryDsConnection, dsConnections } = projectEditor.getDsConnectionsById(); + * if (primaryDsConnection) { + * console.log(`Primary: ${primaryDsConnection.name}`); + * } + * ``` + */ + getDsConnectionsById( + dataSourceIds?: Array, + ): { + primaryDsConnection: DataSourceConnection | undefined; + dsConnections: DataSourceConnection[]; + notFound: string[]; + }; } diff --git a/scripts/update-version.ts b/scripts/update-version.ts new file mode 100644 index 0000000..a739f64 --- /dev/null +++ b/scripts/update-version.ts @@ -0,0 +1,119 @@ +#!/usr/bin/env -S deno run --allow-read --allow-write +/** + * Update Version Script + * + * Reads version from root deno.jsonc and generates shared/version.ts + * This ensures version consistency across the entire codebase. + * + * Usage: + * deno run --allow-read --allow-write scripts/update-version.ts + * deno task update-version + */ + +import { parse } from '@std/jsonc'; + +const DENO_CONFIG_PATH = './deno.jsonc'; +const VERSION_FILE_PATH = './shared/version.ts'; + +interface DenoConfig { + name?: string; + version?: string; + [key: string]: unknown; +} + +async function main() { + try { + // Read and parse deno.jsonc + console.log('📖 Reading version from deno.jsonc...'); + const denoConfigText = await Deno.readTextFile(DENO_CONFIG_PATH); + const denoConfig = parse(denoConfigText) as DenoConfig; + + if (!denoConfig.version) { + console.error('❌ Error: No version found in deno.jsonc'); + Deno.exit(1); + } + + const version = denoConfig.version; + const packageName = denoConfig.name || '@beyondbetter/tools'; + + console.log(`✅ Found version: ${version}`); + + // Generate version.ts content + const versionFileContent = `/** + * @module + * Version information for the MCP Client Inspector. + * + * This file is auto-generated by scripts/update-version.ts. + * DO NOT EDIT MANUALLY - Changes will be overwritten + * + * Source: deno.jsonc (root) + * Last updated: ${new Date().toISOString()} + */ + +/** + * Current version of the MCP Client Inspector package. + * + * This version is synchronized with the version in deno.jsonc and follows + * semantic versioning (semver) conventions. + */ + +/** + * Current package version + * @constant + */ +export const VERSION = '${version}'; + +/** + * Package name + * @constant + */ +export const PACKAGE_NAME = '${packageName}'; + +/** + * Full version string with package name + * @constant + */ +export const FULL_VERSION = \`\${PACKAGE_NAME}@\${VERSION}\`; + +/** + * Parse version into major, minor, patch components + * @returns Object with major, minor, and patch version numbers + */ +export function parseVersion(): { + major: number; + minor: number; + patch: number; + prerelease?: string; +} { + const match = VERSION.match(/^(\\d+)\\.(\\d+)\\.(\\d+)(?:-(.+))?$/); + + if (!match) { + throw new Error(\`Invalid version format: \${VERSION}\`); + } + + return { + major: parseInt(match[1], 10), + minor: parseInt(match[2], 10), + patch: parseInt(match[3], 10), + prerelease: match[4], + }; +} +`; + + // Write version.ts + console.log(`📝 Writing to ${VERSION_FILE_PATH}...`); + await Deno.writeTextFile(VERSION_FILE_PATH, versionFileContent); + + console.log('✅ Version file updated successfully!'); + console.log(` Package: ${packageName}`); + console.log(` Version: ${version}`); + console.log(` File: ${VERSION_FILE_PATH}`); + } catch (error) { + console.error('❌ Error updating version:', error); + Deno.exit(1); + } +} + +if (import.meta.main) { + main(); +} diff --git a/shared/version.ts b/shared/version.ts new file mode 100644 index 0000000..5d1a16f --- /dev/null +++ b/shared/version.ts @@ -0,0 +1,59 @@ +/** + * @module + * Version information for the MCP Client Inspector. + * + * This file is auto-generated by scripts/update-version.ts. + * DO NOT EDIT MANUALLY - Changes will be overwritten + * + * Source: deno.jsonc (root) + * Last updated: 2025-11-03T05:58:58.232Z + */ + +/** + * Current version of the MCP Client Inspector package. + * + * This version is synchronized with the version in deno.jsonc and follows + * semantic versioning (semver) conventions. + */ + +/** + * Current package version + * @constant + */ +export const VERSION = '0.1.2'; + +/** + * Package name + * @constant + */ +export const PACKAGE_NAME = '@beyondbetter/tools'; + +/** + * Full version string with package name + * @constant + */ +export const FULL_VERSION = `${PACKAGE_NAME}@${VERSION}`; + +/** + * Parse version into major, minor, patch components + * @returns Object with major, minor, and patch version numbers + */ +export function parseVersion(): { + major: number; + minor: number; + patch: number; + prerelease?: string; +} { + const match = VERSION.match(/^(\d+)\.(\d+)\.(\d+)(?:-(.+))?$/); + + if (!match) { + throw new Error(`Invalid version format: ${VERSION}`); + } + + return { + major: parseInt(match[1], 10), + minor: parseInt(match[2], 10), + patch: parseInt(match[3], 10), + prerelease: match[4], + }; +} diff --git a/types.ts b/types.ts index 5a3d659..3404333 100644 --- a/types.ts +++ b/types.ts @@ -18,7 +18,10 @@ import type { LLMMessageContentPartImageBlockSourceMediaType } from './message.t * inputTokens: 150, * outputTokens: 80, * totalTokens: 230, - * cacheCreationInputTokens: 100 + * cacheCreationInputTokens: 100, + * cacheReadInputTokens: 50, + * thoughtTokens: 20, + * totalAllTokens: 400 * }; * ``` */ @@ -28,6 +31,8 @@ export interface TokenUsage { totalTokens: number; cacheCreationInputTokens?: number; cacheReadInputTokens?: number; + thoughtTokens?: number; + totalAllTokens?: number; } /**