Skip to content

Commit

Permalink
Merge pull request #40 from BBai-Tips/staging
Browse files Browse the repository at this point in the history
generate tools manifest file to facilitate loading from compiled binary
  • Loading branch information
cngarrison authored Oct 7, 2024
2 parents 8eef84c + 40850ac commit 100e516
Show file tree
Hide file tree
Showing 25 changed files with 425 additions and 35 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ jobs:
- name: Build API for ${{ matrix.target }}
run: |
cd api
deno compile --allow-env --allow-net --allow-read --allow-run --allow-write --target ${{ matrix.target }} --output ../build/bbai-api${{ matrix.target == 'x86_64-pc-windows-msvc' && '.exe' || '' }} src/main.ts
deno run --allow-read --allow-run --allow-write scripts/compile.ts --target ${{ matrix.target }} --output ../build/bbai-api${{ matrix.target == 'x86_64-pc-windows-msvc' && '.exe' || '' }}
- name: Create install script (Unix)
if: matrix.target != 'x86_64-pc-windows-msvc'
Expand Down
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0



## [0.0.24-beta] - 2024-10-07

### Changed

- generate tools manifest file to facilitate loading from compiled binary
- dynamically include tools at build compile time
- update build scripts and release workflow to use compile script


## [0.0.23-beta] - 2024-10-06

### Changed
Expand Down
6 changes: 4 additions & 2 deletions api/deno.jsonc
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
{
"name": "bbai-api",
"version": "0.0.23-beta",
"version": "0.0.24-beta",
"exports": "./src/main.ts",
"tasks": {
"start": "deno run --allow-read --allow-write --allow-run --allow-net --allow-env src/main.ts",
"dev": "deno run --watch --allow-read --allow-write --allow-run --allow-net --allow-env src/main.ts",
"debug": "LOG_LEVEL=debug deno run --allow-read --allow-write --allow-run --allow-net --allow-env src/main.ts",
"test": "deno test --allow-read --allow-write --allow-run --allow-net --allow-env tests/ tests/t/llms/tools/index.ts",
"build": "deno compile --allow-env --allow-net --allow-read --allow-run --allow-write --output ../build/bbai-api src/main.ts",
"generate-tools-manifest": "deno run --allow-read --allow-write scripts/generate_core_tools_manifest.ts && deno fmt ./src/llms/tools_manifest.ts",
"build": "deno run --allow-read --allow-run --allow-write scripts/compile.ts",
"build-local": "deno task generate-tools-manifest && deno compile --allow-env --allow-net --allow-read --allow-run --allow-write --output ../build/bbai-api src/main.ts",
"format": "deno fmt",
"check-format": "deno fmt --check",
"check-types": "deno check src/main.ts",
Expand Down
88 changes: 88 additions & 0 deletions api/scripts/compile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#!/usr/bin/env -S deno run --allow-read --allow-run --allow-write

import { parseArgs } from '@std/cli';
import { walk } from '@std/fs';
import { join } from '@std/path';

const TOOLS_DIR = './src/llms/tools';
const MAIN_FILE = 'src/main.ts';
const OUTPUT_FILE = '../build/bbai-api';

const args = parseArgs(Deno.args, {
string: ['target', 'output'],
alias: { t: 'target', o: 'output' },
});

const target = args.target ? `--target ${args.target}` : '';
const output = args.output || OUTPUT_FILE;

async function getIncludeFiles() {
const includeFiles = [];
for await (
const entry of walk(TOOLS_DIR, {
exts: ['.ts', '.tsx', '.json'],
followSymlinks: false,
})
) {
if (entry.isFile) {
includeFiles.push(entry.path);
}
}
return includeFiles;
}

// Run the generate-tools-manifest task
const manifestProcess = new Deno.Command('deno', {
args: ['task', 'generate-tools-manifest'],
stdout: 'piped',
stderr: 'piped',
});
const { code: manifestCode, stdout: manifestStdout, stderr: manifestStderr } = await manifestProcess.output();

const manifestOutput = new TextDecoder().decode(manifestStdout);
const manifestErrorOutput = new TextDecoder().decode(manifestStderr);

if (manifestCode !== 0) {
if (manifestErrorOutput !== '') console.error(manifestErrorOutput);
console.error('Failed to generate tools manifest');
Deno.exit(manifestCode);
}

console.log(manifestOutput);
if (manifestErrorOutput !== '') console.error(manifestErrorOutput);

const includeFiles = await getIncludeFiles();
const includeArgs = includeFiles.map((file) => `--include ${file}`).join(' ');
console.log(`Including files for core tools:\n${JSON.stringify(includeFiles, null, 2)}`);

// Compile the API
const compileProcess = new Deno.Command('deno', {
args: [
'compile',
'-A',
'--unstable',
target,
'--output',
output,
...includeArgs.split(' '),
MAIN_FILE,
].filter(Boolean),
stdout: 'piped',
stderr: 'piped',
});

const { code: compileCode, stdout: compileStdout, stderr: compileStderr } = await compileProcess.output();

const compileOutput = new TextDecoder().decode(compileStdout);
const compileErrorOutput = new TextDecoder().decode(compileStderr);

if (compileCode !== 0) {
if (compileErrorOutput !== '') console.error(compileErrorOutput);
console.error('Compilation failed');
Deno.exit(compileCode);
}

console.log(compileOutput);
if (compileErrorOutput !== '') console.error(compileErrorOutput);

console.log('Compilation successful');
43 changes: 43 additions & 0 deletions api/scripts/generate_core_tools_manifest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//import { walk } from "@std/fs";
import { join } from '@std/path';
import { stripIndents } from 'common-tags';

// paths relative to api/deno.jsonc
const TOOLS_DIR = './src/llms/tools';
const OUTPUT_FILE = './src/llms/tools_manifest.ts';

async function generateCoreTools() {
const tools = [];

//for await (const entry of walk(TOOLS_DIR, { maxDepth: 1 })) {
for await (const entry of Deno.readDir(TOOLS_DIR)) {
if (entry.isDirectory && entry.name.endsWith('.tool')) {
const infoPath = join(TOOLS_DIR, entry.name, 'info.json');
try {
const info = JSON.parse(await Deno.readTextFile(infoPath));
tools.push({
toolNamePath: entry.name,
metadata: info,
});
} catch (error) {
console.error(`Error reading ${infoPath}:`, error);
}
}
}

const fileContent = `// This file is auto-generated. Do not edit manually.
import type { ToolMetadata } from './llmToolManager.ts';
interface CoreTool {
toolNamePath: string;
metadata: ToolMetadata;
}
export const CORE_TOOLS: Array<CoreTool> = ${JSON.stringify(tools, null, '\t')};
`;

await Deno.writeTextFile(OUTPUT_FILE, fileContent);
console.log(`Generated ${OUTPUT_FILE} with ${tools.length} tools.`);
}

await generateCoreTools();
49 changes: 31 additions & 18 deletions api/src/llms/llmToolManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,25 @@ import { createError, ErrorType } from 'api/utils/error.ts';
import type { LLMValidationErrorOptions } from '../errors/error.ts';
import { logger } from 'shared/logger.ts';
import type { FullConfigSchema } from 'shared/configManager.ts';

import { compare as compareVersions, parse as parseVersion } from '@std/semver';
import { dirname, fromFileUrl, join } from '@std/path';
import { dirname, fromFileUrl, isAbsolute, join, SEPARATOR } from '@std/path';
import { exists } from '@std/fs';

const BUILT_IN_TOOL_DIRECTORY = join(dirname(fromFileUrl(import.meta.url)), 'tools');
import { CORE_TOOLS } from './tools_manifest.ts';

interface ToolMetadata {
export interface ToolMetadata {
name: string;
version: string;
author: string;
license: string;
description: string;
path: string;
toolSets: string | string[];
enabled: boolean;
path?: string; // is set by code, not part of manifest
toolSets?: string | string[]; //defaults to 'core'
category?: string | string[];
enabled?: boolean; //defaults to true
error?: string;
config?: unknown;
}

export type LLMToolManagerToolSetType = 'core' | 'coding' | 'research' | 'creative';
Expand All @@ -42,17 +47,20 @@ class LLMToolManager {
}

async init() {
const toolDirectories = [
BUILT_IN_TOOL_DIRECTORY,
...this.fullConfig.userToolDirectories,
];

await this.loadToolMetadata(toolDirectories);
await this.loadToolMetadata(this.fullConfig.userToolDirectories);

return this;
}

private async loadToolMetadata(directories: string[]) {
for (const coreTool of CORE_TOOLS) {
const toolNamePath = join('tools', coreTool.toolNamePath);
coreTool.metadata.path = toolNamePath;
//logger.debug(`LLMToolManager: Metadata for CORE tool ${coreTool.toolNamePath}`, coreTool.metadata);
logger.debug(`LLMToolManager: Setting metadata for CORE tool ${coreTool.toolNamePath}`);
this.toolMetadata.set(coreTool.metadata.name, coreTool.metadata);
}

//logger.debug(`LLMToolManager: Processing tool directories:`, directories);
for (const directory of directories) {
logger.debug(`LLMToolManager: Checking ${directory} for tools`);
Expand Down Expand Up @@ -130,7 +138,7 @@ class LLMToolManager {

private shouldReplaceExistingTool(existing: ToolMetadata, newMetadata: ToolMetadata): boolean {
// Prefer user-supplied tools
if (this.fullConfig.userToolDirectories.some((dir) => newMetadata.path.startsWith(dir))) {
if (this.fullConfig.userToolDirectories.some((dir) => newMetadata.path!.startsWith(dir))) {
if (compareVersions(parseVersion(existing.version), parseVersion(newMetadata.version)) > 0) {
logger.warn(
`LLMToolManager: User-supplied tool ${newMetadata.name} (${newMetadata.version}) is older than built-in tool (${existing.version})`,
Expand All @@ -142,6 +150,7 @@ class LLMToolManager {
}

async getTool(name: string): Promise<LLMTool | undefined> {
logger.info(`LLMToolManager: Getting Tool ${name}`);
if (this.loadedTools.has(name)) {
logger.debug(`LLMToolManager: Returning cached ${name} tool`);
return this.loadedTools.get(name);
Expand All @@ -161,10 +170,14 @@ class LLMToolManager {
// Proceed with loading the tool

try {
logger.debug(`LLMToolManager: Tool ${name} is loading`);
const module = await import(`${metadata.path}/tool.ts`);
logger.info(`LLMToolManager: Is tool ${name} absolute ${metadata.path}`);
const toolPath = isAbsolute(metadata.path!)
? join(metadata.path!, 'tool.ts')
: `.${SEPARATOR}${metadata.path}${SEPARATOR}tool.ts`;
logger.info(`LLMToolManager: Tool ${name} is loading from ${toolPath}`);
const module = await import(toolPath);
const tool = new module.default();
logger.debug(`LLMToolManager: Tool ${tool.name} is loaded`);
//logger.debug(`LLMToolManager: Tool ${tool.name} is loaded`);
this.loadedTools.set(name, tool);
return tool;
} catch (error) {
Expand All @@ -179,10 +192,10 @@ class LLMToolManager {
return metadata ? `${metadata.path}/tool.ts` : undefined;
}

async getAllTools(): Promise<LLMTool[]> {
async getAllTools(enabledOnly = true): Promise<LLMTool[]> {
const tools: LLMTool[] = [];
for (const metadata of this.toolMetadata.values()) {
if (this.isToolEnabled(metadata)) {
if (enabledOnly && this.isToolEnabled(metadata)) {
const tool = await this.getTool(metadata.name);
if (tool) {
tools.push(tool);
Expand Down
2 changes: 1 addition & 1 deletion api/src/llms/tools/fetchWebPage.tool/formatter.browser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import type { JSX } from 'preact';
import type { LLMToolInputSchema, LLMToolRunResultContent } from 'api/llms/llmTool.ts';
//import type { LLMMessageContentPart, LLMMessageContentParts } from 'api/llms/llmMessage.ts';
import { getContentFromToolResult } from '../../../utils/llms.utils.ts';
import { getContentFromToolResult } from 'api/utils/llms.ts';

export const formatToolUse = (toolInput: LLMToolInputSchema): JSX.Element => {
const { url } = toolInput as { url: string };
Expand Down
2 changes: 1 addition & 1 deletion api/src/llms/tools/fetchWebPage.tool/formatter.console.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { LLMToolInputSchema, LLMToolRunResultContent } from 'api/llms/llmTo
//import type { LLMMessageContentPart, LLMMessageContentParts } from 'api/llms/llmMessage.ts';
import { colors } from 'cliffy/ansi/colors.ts';
import { stripIndents } from 'common-tags';
import { getContentFromToolResult } from '../../../utils/llms.utils.ts';
import { getContentFromToolResult } from 'api/utils/llms.ts';

export const formatToolUse = (toolInput: LLMToolInputSchema): string => {
const { url } = toolInput as { url: string };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import type { JSX } from 'preact';
import type { LLMToolInputSchema, LLMToolRunResultContent } from 'api/llms/llmTool.ts';
//import type { LLMMessageContentPart, LLMMessageContentParts } from 'api/llms/llmMessage.ts';
import { getContentFromToolResult } from '../../../utils/llms.utils.ts';
import { getContentFromToolResult } from 'api/utils/llms.ts';

export const formatToolUse = (toolInput: LLMToolInputSchema): JSX.Element => {
const { url } = toolInput as { url: string };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { LLMToolInputSchema, LLMToolRunResultContent } from 'api/llms/llmTo
//import type { LLMMessageContentPart, LLMMessageContentParts } from 'api/llms/llmMessage.ts';
import { colors } from 'cliffy/ansi/colors.ts';
import { stripIndents } from 'common-tags';
import { getContentFromToolResult } from '../../../utils/llms.utils.ts';
import { getContentFromToolResult } from 'api/utils/llms.ts';

export const formatToolUse = (toolInput: LLMToolInputSchema): string => {
const { url } = toolInput as { url: string };
Expand Down
2 changes: 1 addition & 1 deletion api/src/llms/tools/searchAndReplace.tool/tool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { isPathWithinProject } from 'api/utils/fileHandling.ts';
import { logger } from 'shared/logger.ts';
import { dirname, join } from '@std/path';
import { ensureDir } from '@std/fs';
import { getContentFromToolResult } from '../../../utils/llms.utils.ts';
import { getContentFromToolResult } from 'api/utils/llms.ts';

export default class LLMToolSearchAndReplace extends LLMTool {
private static readonly MIN_SEARCH_LENGTH = 1;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/** @jsxImportSource preact */
import type { JSX } from 'preact';
import type { LLMToolInputSchema, LLMToolRunResultContent } from 'api/llms/llmTool.ts';

export const formatToolUse = (toolInput: LLMToolInputSchema): JSX.Element => {
return (
<div className='tool-use'>
</div>
);
};

export const formatToolResult = (toolResult: LLMToolRunResultContent): JSX.Element => {
return (
<div className='tool-result'>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { LLMToolInputSchema, LLMToolRunResultContent } from 'api/llms/llmTool.ts';

export const formatToolUse = (toolInput: LLMToolInputSchema): string => {
return '';
};

export const formatToolResult = (toolResult: LLMToolRunResultContent): string => {
return '';
};
22 changes: 20 additions & 2 deletions api/src/llms/tools/searchAndReplaceMultilineCode.tool/tool.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
//import type { JSX } from 'preact';
import type { JSX } from 'preact';

import LLMTool from 'api/llms/llmTool.ts';
import type { LLMToolInputSchema, LLMToolRunResult } from 'api/llms/llmTool.ts';
import type { LLMToolInputSchema, LLMToolRunResult, LLMToolRunResultContent } from 'api/llms/llmTool.ts';
import type LLMConversationInteraction from 'api/llms/conversationInteraction.ts';
import type { LLMAnswerToolUse } from 'api/llms/llmMessage.ts';
import type ProjectEditor from 'api/editor/projectEditor.ts';
import { createError, ErrorType } from 'api/utils/error.ts';
import type { FileHandlingErrorOptions } from 'api/errors/error.ts';
import { isPathWithinProject } from 'api/utils/fileHandling.ts';
import { logger } from 'shared/logger.ts';
import {
formatToolResult as formatToolResultBrowser,
formatToolUse as formatToolUseBrowser,
} from './formatter.browser.tsx';
import {
formatToolResult as formatToolResultConsole,
formatToolUse as formatToolUseConsole,
} from './formatter.console.ts';

import { dirname, join } from '@std/path';
import { ensureDir } from '@std/fs';

Expand Down Expand Up @@ -232,6 +242,14 @@ export default class LLMToolSearchAndReplaceCode extends LLMTool {
};
}

formatToolUse(toolInput: LLMToolInputSchema, format: 'console' | 'browser'): string | JSX.Element {
return format === 'console' ? formatToolUseConsole(toolInput) : formatToolUseBrowser(toolInput);
}

formatToolResult(toolResult: LLMToolRunResultContent, format: 'console' | 'browser'): string | JSX.Element {
return format === 'console' ? formatToolResultConsole(toolResult) : formatToolResultBrowser(toolResult);
}

/**
* Detects the programming language of a file based on its extension and content.
*
Expand Down
Loading

0 comments on commit 100e516

Please sign in to comment.