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;
}
/**