tool registry, claude tool awareness, and model selector fix
- Add agent-agnostic tool registry (tool-registry.ts) that discovers tools from disk - Embed tool-loader extension as platform infrastructure (ensure-tool-loader.ts) - Inject tool context into Claude prompts on first message - Add marketplace tool sync (sync-marketplace.ts) - Fix model selector defaulting to claude-code when no model explicitly selected - Exclude tool-loader-source.ts from tsconfig (Pi-specific deps) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { DATA_PATH } from './data-path';
|
||||
import { parseSeedVersion } from './sync-version';
|
||||
|
||||
const MARKETPLACE_URL = process.env.MARKETPLACE_URL ?? 'https://marketplace.officer.dev';
|
||||
const GLOBAL_TOOLS_DIR = join(DATA_PATH, 'tools');
|
||||
|
||||
type ToolInput = {
|
||||
type: string;
|
||||
description: string;
|
||||
values?: string;
|
||||
};
|
||||
|
||||
type MarketplaceTool = {
|
||||
dirName: string;
|
||||
name: string;
|
||||
label: string;
|
||||
description: string;
|
||||
body: string;
|
||||
version: number;
|
||||
language: string;
|
||||
inputs: Record<string, ToolInput>;
|
||||
implementation: string;
|
||||
};
|
||||
|
||||
type MarketplaceResponse = {
|
||||
categories: Array<{ name: string; tools: MarketplaceTool[] }>;
|
||||
uncategorized: MarketplaceTool[];
|
||||
};
|
||||
|
||||
function buildToolMd(tool: MarketplaceTool): string {
|
||||
const lines = ['---'];
|
||||
lines.push(`name: ${tool.name}`);
|
||||
lines.push(`label: ${tool.label}`);
|
||||
lines.push(`description: ${tool.description}`);
|
||||
lines.push(`version: ${tool.version}`);
|
||||
lines.push(`language: ${tool.language}`);
|
||||
|
||||
if (tool.inputs && Object.keys(tool.inputs).length > 0) {
|
||||
lines.push('inputs:');
|
||||
for (const [key, input] of Object.entries(tool.inputs)) {
|
||||
lines.push(` ${key}:`);
|
||||
lines.push(` type: ${input.type}`);
|
||||
if (input.values) {
|
||||
lines.push(` values: ${input.values}`);
|
||||
}
|
||||
lines.push(` description: "${input.description}"`);
|
||||
}
|
||||
}
|
||||
|
||||
lines.push('---');
|
||||
lines.push('');
|
||||
lines.push(tool.body);
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
export async function syncMarketplaceTools(): Promise<void> {
|
||||
try {
|
||||
const res = await fetch(`${MARKETPLACE_URL}/api/tools/native`);
|
||||
if (!res.ok) {
|
||||
console.error(`[marketplace] Failed to fetch tools: ${res.status} ${res.statusText}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const data = (await res.json()) as MarketplaceResponse;
|
||||
|
||||
mkdirSync(GLOBAL_TOOLS_DIR, { recursive: true });
|
||||
|
||||
const allTools: MarketplaceTool[] = [];
|
||||
for (const category of data.categories) {
|
||||
allTools.push(...category.tools);
|
||||
}
|
||||
if (data.uncategorized) {
|
||||
allTools.push(...data.uncategorized);
|
||||
}
|
||||
|
||||
for (const tool of allTools) {
|
||||
const targetDir = join(GLOBAL_TOOLS_DIR, tool.dirName);
|
||||
const targetToolMd = join(targetDir, 'TOOL.md');
|
||||
|
||||
if (existsSync(targetToolMd)) {
|
||||
const existingVersion = parseSeedVersion(readFileSync(targetToolMd, 'utf-8'));
|
||||
if (tool.version <= existingVersion) continue;
|
||||
}
|
||||
|
||||
mkdirSync(targetDir, { recursive: true });
|
||||
writeFileSync(targetToolMd, buildToolMd(tool), 'utf-8');
|
||||
writeFileSync(join(targetDir, 'index.ts'), tool.implementation, 'utf-8');
|
||||
|
||||
console.log(`[marketplace] Synced tool: ${tool.dirName} (v${tool.version})`);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[marketplace] Failed to sync tools:', err instanceof Error ? err.message : err);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user