From 5f4788cf56b040bb3f2c825dc9335a528a34b434 Mon Sep 17 00:00:00 2001 From: Ramnique Singh <30795890+ramnique@users.noreply.github.com> Date: Fri, 10 Jul 2026 11:51:11 +0530 Subject: [PATCH] refactor(x): dissolve the tools support grab-bag into its owners MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit domains/support.ts bundled five unrelated concerns. Each moves to its owner: the catalog schema → tools/types.ts (every domain's typing); lenient ~-expansion → tools/paths.ts (documented as distinct from filesystem's throwing variant); the bg-task input schemas and resolveCodeProject → domains/background-tasks.ts; the code-run coalescer → domains/code.ts (catalog re-export retargeted); parser plumbing → domains/parsing.ts, now module-private. Co-Authored-By: Claude Fable 5 --- .../core/src/runtime/tools/catalog.ts | 4 +- .../runtime/tools/domains/agent-analysis.ts | 4 +- .../core/src/runtime/tools/domains/app.ts | 4 +- .../runtime/tools/domains/background-tasks.ts | 68 ++++++++- .../core/src/runtime/tools/domains/browser.ts | 4 +- .../core/src/runtime/tools/domains/code.ts | 31 +++- .../src/runtime/tools/domains/composio.ts | 4 +- .../core/src/runtime/tools/domains/files.ts | 4 +- .../src/runtime/tools/domains/live-note.ts | 4 +- .../core/src/runtime/tools/domains/mcp.ts | 4 +- .../core/src/runtime/tools/domains/memory.ts | 4 +- .../core/src/runtime/tools/domains/models.ts | 4 +- .../runtime/tools/domains/notifications.ts | 4 +- .../core/src/runtime/tools/domains/parsing.ts | 35 ++++- .../core/src/runtime/tools/domains/shell.ts | 4 +- .../core/src/runtime/tools/domains/support.ts | 133 ------------------ .../core/src/runtime/tools/domains/web.ts | 4 +- .../packages/core/src/runtime/tools/paths.ts | 18 +++ .../packages/core/src/runtime/tools/types.ts | 15 ++ 19 files changed, 165 insertions(+), 187 deletions(-) delete mode 100644 apps/x/packages/core/src/runtime/tools/domains/support.ts create mode 100644 apps/x/packages/core/src/runtime/tools/paths.ts create mode 100644 apps/x/packages/core/src/runtime/tools/types.ts diff --git a/apps/x/packages/core/src/runtime/tools/catalog.ts b/apps/x/packages/core/src/runtime/tools/catalog.ts index df7682b8..3a89d86e 100644 --- a/apps/x/packages/core/src/runtime/tools/catalog.ts +++ b/apps/x/packages/core/src/runtime/tools/catalog.ts @@ -22,8 +22,8 @@ import { modelTools } from "./domains/models.js"; import { liveNoteTools } from "./domains/live-note.js"; import { backgroundTaskTools } from "./domains/background-tasks.js"; import { notificationTools } from "./domains/notifications.js"; -import { BuiltinToolsSchema } from "./domains/support.js"; -export { coalesceCodeRunEvents } from "./domains/support.js"; +import { BuiltinToolsSchema } from "./types.js"; +export { coalesceCodeRunEvents } from "./domains/code.js"; // The builtin-tool catalog, assembled from domain modules // (./builtin-tools/*). SPREAD ORDER IS LOAD-BEARING: catalog key order is diff --git a/apps/x/packages/core/src/runtime/tools/domains/agent-analysis.ts b/apps/x/packages/core/src/runtime/tools/domains/agent-analysis.ts index e6e6fcfb..dbbd5a7f 100644 --- a/apps/x/packages/core/src/runtime/tools/domains/agent-analysis.ts +++ b/apps/x/packages/core/src/runtime/tools/domains/agent-analysis.ts @@ -5,9 +5,7 @@ import { z } from "zod"; import container from "../../../di/container.js"; import { IAgentsRepo } from "../../assembly/repo.js"; -import { - BuiltinToolsSchema, -} from "./support.js"; +import { BuiltinToolsSchema } from "../types.js"; export const agentAnalysisTools: z.infer = { diff --git a/apps/x/packages/core/src/runtime/tools/domains/app.ts b/apps/x/packages/core/src/runtime/tools/domains/app.ts index a81ced8d..595cc99f 100644 --- a/apps/x/packages/core/src/runtime/tools/domains/app.ts +++ b/apps/x/packages/core/src/runtime/tools/domains/app.ts @@ -13,9 +13,7 @@ import { listApps } from "../../../apps/indexer.js"; import { listImportantThreads, searchThreads } from "../../../knowledge/sync_gmail.js"; import { listTasks as listBackgroundTasks } from "../../../background-tasks/fileops.js"; import type { ISessions } from "../../sessions/api.js"; -import { - BuiltinToolsSchema, -} from "./support.js"; +import { BuiltinToolsSchema } from "../types.js"; export const appNavigationTools: z.infer = { diff --git a/apps/x/packages/core/src/runtime/tools/domains/background-tasks.ts b/apps/x/packages/core/src/runtime/tools/domains/background-tasks.ts index 25d0b5f0..2fbe53a3 100644 --- a/apps/x/packages/core/src/runtime/tools/domains/background-tasks.ts +++ b/apps/x/packages/core/src/runtime/tools/domains/background-tasks.ts @@ -3,12 +3,68 @@ // catalog key order (provider-payload bytes; see the key-order test there). import { z } from "zod"; -import { - CreateBackgroundTaskInput, - PatchBackgroundTaskInput, - resolveCodeProject, - BuiltinToolsSchema, -} from "./support.js"; +import { BuiltinToolsSchema } from "../types.js"; + +import * as path from "path"; +import container from "../../../di/container.js"; +import { BackgroundTaskSchema, TriggersSchema } from "@x/shared/dist/background-task.js"; +import * as gitService from "../../../code-mode/git/service.js"; +import type { ICodeProjectsRepo } from "../../../code-mode/projects/repo.js"; +import { expandHome } from "../paths.js"; + +// Inputs for the bg-task builtin tools. Reuse the canonical schema field +// descriptions; only `triggers` gets a tighter contextual override (the +// shared TriggersSchema description is written from the live-note perspective). +export const CreateBackgroundTaskInput = BackgroundTaskSchema.pick({ + name: true, + instructions: true, + triggers: true, + model: true, + provider: true, +}).extend({ + triggers: TriggersSchema.optional().describe('All three sub-fields (cronExpr, windows, eventMatchCriteria) are independently optional — mix freely. No triggers at all = manual-only (user clicks Run).'), + projectDir: z.string().optional().describe( + "Set this ONLY when the user wants the task to WRITE CODE. An absolute path (or ~/…) to a LOCAL GIT REPOSITORY with at least one commit. It turns this into a *coding task*: each run scans the trigger source for actionable items and implements them autonomously in isolated git worktrees off this repo — never touching the user's checkout. Extract the directory from the user's request (e.g. 'use ~/Work/space/test as the work directory'). Omit for ordinary output/action tasks.", + ), +}); + +export const PatchBackgroundTaskInput = BackgroundTaskSchema.pick({ + name: true, + instructions: true, + active: true, + triggers: true, + model: true, + provider: true, +}).partial().extend({ + slug: z.string().describe('The slug of the task to update (the folder name under bg-tasks/).'), + triggers: TriggersSchema.optional().describe('Replace the triggers object. To remove all triggers (make manual-only) pass an empty object.'), + projectDir: z.string().optional().describe("Point an existing task at a code repo (or change which one) to make it a coding task. Absolute path or ~/… to a local git repository with at least one commit. Same rules as on create."), + clearModel: z.boolean().optional().describe("Reset the task's model/provider override so it falls back to the default. Use this to unstick a bad/rejected model value (do not also pass model)."), +}); + +export async function resolveCodeProject(dirPath: string): Promise< + { ok: true; projectId: string; path: string; warning?: string } | { ok: false; error: string } +> { + const abs = path.resolve(expandHome(dirPath)); + const projectsRepo = container.resolve('codeProjectsRepo'); + let project: Awaited>; + try { + project = await projectsRepo.add(abs); + } catch (err) { + return { ok: false, error: `Could not use '${dirPath}' as a code directory: ${err instanceof Error ? err.message : String(err)}` }; + } + // Worktree isolation needs a real git repo with at least one commit + // (codeSessionService.create throws otherwise). Surface it now as a soft + // warning rather than letting the next run fail silently. + let warning: string | undefined; + try { + const info = await gitService.repoInfo(project.path); + if (!info.isGitRepo) warning = `${project.path} is not a git repository yet — run \`git init\` and make a commit, or the coding sessions will fail.`; + else if (!info.hasCommits) warning = `${project.path} has no commits yet — make an initial commit, or the coding sessions will fail.`; + } catch { /* best effort — worktree creation will surface it later */ } + return { ok: true, projectId: project.id, path: project.path, ...(warning ? { warning } : {}) }; +} + export const backgroundTaskTools: z.infer = { diff --git a/apps/x/packages/core/src/runtime/tools/domains/browser.ts b/apps/x/packages/core/src/runtime/tools/domains/browser.ts index ea5e0614..a32ab38e 100644 --- a/apps/x/packages/core/src/runtime/tools/domains/browser.ts +++ b/apps/x/packages/core/src/runtime/tools/domains/browser.ts @@ -8,9 +8,7 @@ import { BrowserControlInputSchema, type BrowserControlInput } from "@x/shared/d import { ensureLoaded as ensureBrowserSkillsLoaded, readSkillContent as readBrowserSkillContent, refreshFromRemote as refreshBrowserSkills } from "../../../application/browser-skills/index.js"; import type { ToolContext } from "../exec-tool.js"; import type { IBrowserControlService } from "../../../application/browser-control/service.js"; -import { - BuiltinToolsSchema, -} from "./support.js"; +import { BuiltinToolsSchema } from "../types.js"; export const browserTools: z.infer = { diff --git a/apps/x/packages/core/src/runtime/tools/domains/code.ts b/apps/x/packages/core/src/runtime/tools/domains/code.ts index e6cafa48..d220b1ae 100644 --- a/apps/x/packages/core/src/runtime/tools/domains/code.ts +++ b/apps/x/packages/core/src/runtime/tools/domains/code.ts @@ -12,11 +12,32 @@ import { ICodeModeConfigRepo } from "../../../code-mode/repo.js"; import type { ApprovalPolicy, CodeRunEvent as CodeRunEventType } from "@x/shared/dist/code-mode.js"; import type { CodeRunFeed } from "../../../code-mode/feed.js"; import type { ToolContext } from "../exec-tool.js"; -import { - expandHome, - coalesceCodeRunEvents, - BuiltinToolsSchema, -} from "./support.js"; +import { expandHome } from "../paths.js"; +import { BuiltinToolsSchema } from "../types.js"; + + + +// Shrink a code-run timeline for durable storage: consecutive same-role message +// chunks merge into one event. Display-lossless — the timeline renderer +// concatenates consecutive messages anyway (CodingRunTimeline) — and typically +// collapses the ~90% of a run's events that are per-token text deltas. +// Everything else (tool calls/updates, plans, permissions) is kept verbatim in +// order: updates are id-keyed transitions and must not be merged. +export function coalesceCodeRunEvents(events: CodeRunEventType[]): CodeRunEventType[] { + const out: CodeRunEventType[] = []; + for (const event of events) { + const last = out[out.length - 1]; + if ( + event.type === 'message' && last?.type === 'message' && last.role === event.role + ) { + out[out.length - 1] = { ...last, text: last.text + event.text }; + } else { + out.push(event); + } + } + return out; +} + export const codeAgentRunTools: z.infer = { diff --git a/apps/x/packages/core/src/runtime/tools/domains/composio.ts b/apps/x/packages/core/src/runtime/tools/domains/composio.ts index b069e822..f9ff020e 100644 --- a/apps/x/packages/core/src/runtime/tools/domains/composio.ts +++ b/apps/x/packages/core/src/runtime/tools/domains/composio.ts @@ -6,9 +6,7 @@ import { z } from "zod"; import { composioAccountsRepo } from "../../../composio/repo.js"; import { executeAction as executeComposioAction, isConfigured as isComposioConfigured, searchTools as searchComposioTools } from "../../../composio/client.js"; import { CURATED_TOOLKITS, CURATED_TOOLKIT_SLUGS } from "@x/shared/dist/composio.js"; -import { - BuiltinToolsSchema, -} from "./support.js"; +import { BuiltinToolsSchema } from "../types.js"; export const composioTools: z.infer = { diff --git a/apps/x/packages/core/src/runtime/tools/domains/files.ts b/apps/x/packages/core/src/runtime/tools/domains/files.ts index f0791cc8..fe20516c 100644 --- a/apps/x/packages/core/src/runtime/tools/domains/files.ts +++ b/apps/x/packages/core/src/runtime/tools/domains/files.ts @@ -5,9 +5,7 @@ import { z } from "zod"; import * as files from "../../../filesystem/files.js"; import { WorkDir } from "../../../config/config.js"; -import { - BuiltinToolsSchema, -} from "./support.js"; +import { BuiltinToolsSchema } from "../types.js"; export const fileTools: z.infer = { diff --git a/apps/x/packages/core/src/runtime/tools/domains/live-note.ts b/apps/x/packages/core/src/runtime/tools/domains/live-note.ts index 5d70a434..b33e658f 100644 --- a/apps/x/packages/core/src/runtime/tools/domains/live-note.ts +++ b/apps/x/packages/core/src/runtime/tools/domains/live-note.ts @@ -3,9 +3,7 @@ // catalog key order (provider-payload bytes; see the key-order test there). import { z } from "zod"; -import { - BuiltinToolsSchema, -} from "./support.js"; +import { BuiltinToolsSchema } from "../types.js"; export const liveNoteTools: z.infer = { diff --git a/apps/x/packages/core/src/runtime/tools/domains/mcp.ts b/apps/x/packages/core/src/runtime/tools/domains/mcp.ts index a1f5c5ae..33d0e7a1 100644 --- a/apps/x/packages/core/src/runtime/tools/domains/mcp.ts +++ b/apps/x/packages/core/src/runtime/tools/domains/mcp.ts @@ -7,9 +7,7 @@ import { executeTool, listServers, listTools } from "../../../mcp/mcp.js"; import container from "../../../di/container.js"; import { IMcpConfigRepo } from "../../../mcp/repo.js"; import { McpServerDefinition } from "@x/shared/dist/mcp.js"; -import { - BuiltinToolsSchema, -} from "./support.js"; +import { BuiltinToolsSchema } from "../types.js"; export const mcpTools: z.infer = { diff --git a/apps/x/packages/core/src/runtime/tools/domains/memory.ts b/apps/x/packages/core/src/runtime/tools/domains/memory.ts index 999093a9..b55c3c1f 100644 --- a/apps/x/packages/core/src/runtime/tools/domains/memory.ts +++ b/apps/x/packages/core/src/runtime/tools/domains/memory.ts @@ -6,9 +6,7 @@ import { z } from "zod"; import * as path from "path"; import * as fs from "fs/promises"; import { WorkDir } from "../../../config/config.js"; -import { - BuiltinToolsSchema, -} from "./support.js"; +import { BuiltinToolsSchema } from "../types.js"; export const memoryTools: z.infer = { diff --git a/apps/x/packages/core/src/runtime/tools/domains/models.ts b/apps/x/packages/core/src/runtime/tools/domains/models.ts index 276d11b5..05896140 100644 --- a/apps/x/packages/core/src/runtime/tools/domains/models.ts +++ b/apps/x/packages/core/src/runtime/tools/domains/models.ts @@ -6,9 +6,7 @@ import { z } from "zod"; import { getDefaultModelAndProvider } from "../../../models/defaults.js"; import { listGatewayModels } from "../../../models/gateway.js"; import { isSignedIn } from "../../../account/account.js"; -import { - BuiltinToolsSchema, -} from "./support.js"; +import { BuiltinToolsSchema } from "../types.js"; export const modelTools: z.infer = { diff --git a/apps/x/packages/core/src/runtime/tools/domains/notifications.ts b/apps/x/packages/core/src/runtime/tools/domains/notifications.ts index 5d364ffc..5e488c11 100644 --- a/apps/x/packages/core/src/runtime/tools/domains/notifications.ts +++ b/apps/x/packages/core/src/runtime/tools/domains/notifications.ts @@ -8,9 +8,7 @@ import type { ToolContext } from "../exec-tool.js"; import { getCurrentUseCase } from "../../../analytics/use_case.js"; import type { INotificationService } from "../../../application/notification/service.js"; import { notifyIfEnabled } from "../../../application/notification/notifier.js"; -import { - BuiltinToolsSchema, -} from "./support.js"; +import { BuiltinToolsSchema } from "../types.js"; export const notificationTools: z.infer = { diff --git a/apps/x/packages/core/src/runtime/tools/domains/parsing.ts b/apps/x/packages/core/src/runtime/tools/domains/parsing.ts index 8d8cde4e..11007e44 100644 --- a/apps/x/packages/core/src/runtime/tools/domains/parsing.ts +++ b/apps/x/packages/core/src/runtime/tools/domains/parsing.ts @@ -10,11 +10,36 @@ import { createLanguageModel } from "../../../models/models.js"; import { getDefaultModelAndProvider, resolveProviderConfig } from "../../../models/defaults.js"; import { captureLlmUsage } from "../../../analytics/usage.js"; import { getCurrentUseCase, withUseCase } from "../../../analytics/use_case.js"; -import { - _importDynamic, - BuiltinToolsSchema, - LLMPARSE_MIME_TYPES, -} from "./support.js"; +import { BuiltinToolsSchema } from "../types.js"; + + + +// Parser libraries are loaded dynamically inside parseFile.execute() +// to avoid pulling pdfjs-dist's DOM polyfills into the main bundle. +// Import paths are computed so esbuild cannot statically resolve them. +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const _importDynamic = new Function('mod', 'return import(mod)') as (mod: string) => Promise; + +const LLMPARSE_MIME_TYPES: Record = { + '.pdf': 'application/pdf', + '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + '.doc': 'application/msword', + '.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + '.xls': 'application/vnd.ms-excel', + '.csv': 'text/csv', + '.txt': 'text/plain', + '.html': 'text/html', + '.png': 'image/png', + '.jpg': 'image/jpeg', + '.jpeg': 'image/jpeg', + '.gif': 'image/gif', + '.webp': 'image/webp', + '.svg': 'image/svg+xml', + '.bmp': 'image/bmp', + '.tiff': 'image/tiff', +}; + export const parsingTools: z.infer = { diff --git a/apps/x/packages/core/src/runtime/tools/domains/shell.ts b/apps/x/packages/core/src/runtime/tools/domains/shell.ts index 36371c13..e1a7a523 100644 --- a/apps/x/packages/core/src/runtime/tools/domains/shell.ts +++ b/apps/x/packages/core/src/runtime/tools/domains/shell.ts @@ -8,9 +8,7 @@ import { executeCommand, executeCommandAbortable } from "../../../application/li import { agentSlackShimEnv } from "../../../slack/agent-slack-exec.js"; import { WorkDir } from "../../../config/config.js"; import type { ToolContext } from "../exec-tool.js"; -import { - BuiltinToolsSchema, -} from "./support.js"; +import { BuiltinToolsSchema } from "../types.js"; export const shellTools: z.infer = { diff --git a/apps/x/packages/core/src/runtime/tools/domains/support.ts b/apps/x/packages/core/src/runtime/tools/domains/support.ts deleted file mode 100644 index 0391cd3e..00000000 --- a/apps/x/packages/core/src/runtime/tools/domains/support.ts +++ /dev/null @@ -1,133 +0,0 @@ -// Shared support for the builtin-tools domain modules: the catalog schema -// and the helpers/input-schemas the tool entries use. Extracted verbatim -// from the historical monolith. - -import { z, ZodType } from "zod"; -import * as path from "path"; -import * as os from "os"; -import container from "../../../di/container.js"; -import { BackgroundTaskSchema, TriggersSchema } from "@x/shared/dist/background-task.js"; -import type { CodeRunEvent as CodeRunEventType } from "@x/shared/dist/code-mode.js"; -import type { ICodeProjectsRepo } from "../../../code-mode/projects/repo.js"; -import * as gitService from "../../../code-mode/git/service.js"; - -// Inputs for the bg-task builtin tools. Reuse the canonical schema field -// descriptions; only `triggers` gets a tighter contextual override (the -// shared TriggersSchema description is written from the live-note perspective). -export const CreateBackgroundTaskInput = BackgroundTaskSchema.pick({ - name: true, - instructions: true, - triggers: true, - model: true, - provider: true, -}).extend({ - triggers: TriggersSchema.optional().describe('All three sub-fields (cronExpr, windows, eventMatchCriteria) are independently optional — mix freely. No triggers at all = manual-only (user clicks Run).'), - projectDir: z.string().optional().describe( - "Set this ONLY when the user wants the task to WRITE CODE. An absolute path (or ~/…) to a LOCAL GIT REPOSITORY with at least one commit. It turns this into a *coding task*: each run scans the trigger source for actionable items and implements them autonomously in isolated git worktrees off this repo — never touching the user's checkout. Extract the directory from the user's request (e.g. 'use ~/Work/space/test as the work directory'). Omit for ordinary output/action tasks.", - ), -}); - -export const PatchBackgroundTaskInput = BackgroundTaskSchema.pick({ - name: true, - instructions: true, - active: true, - triggers: true, - model: true, - provider: true, -}).partial().extend({ - slug: z.string().describe('The slug of the task to update (the folder name under bg-tasks/).'), - triggers: TriggersSchema.optional().describe('Replace the triggers object. To remove all triggers (make manual-only) pass an empty object.'), - projectDir: z.string().optional().describe("Point an existing task at a code repo (or change which one) to make it a coding task. Absolute path or ~/… to a local git repository with at least one commit. Same rules as on create."), - clearModel: z.boolean().optional().describe("Reset the task's model/provider override so it falls back to the default. Use this to unstick a bad/rejected model value (do not also pass model)."), -}); - -// Turn a user-supplied directory into a registered code project id. Reuses the -// same idempotent registry the Code-section picker writes to (add() validates the -// dir exists & is a directory, and dedupes by resolved path). Returns a soft -// `warning` — not an error — when the repo isn't yet worktree-ready, so the task -// still gets created and the copilot can tell the user what to fix. -export function expandHome(p: string): string { - const t = p.trim(); - if (t === '~') return os.homedir(); - if (t.startsWith('~/') || t.startsWith(`~${path.sep}`)) return path.join(os.homedir(), t.slice(2)); - return t; -} - -// Shrink a code-run timeline for durable storage: consecutive same-role message -// chunks merge into one event. Display-lossless — the timeline renderer -// concatenates consecutive messages anyway (CodingRunTimeline) — and typically -// collapses the ~90% of a run's events that are per-token text deltas. -// Everything else (tool calls/updates, plans, permissions) is kept verbatim in -// order: updates are id-keyed transitions and must not be merged. -export function coalesceCodeRunEvents(events: CodeRunEventType[]): CodeRunEventType[] { - const out: CodeRunEventType[] = []; - for (const event of events) { - const last = out[out.length - 1]; - if ( - event.type === 'message' && last?.type === 'message' && last.role === event.role - ) { - out[out.length - 1] = { ...last, text: last.text + event.text }; - } else { - out.push(event); - } - } - return out; -} - -export async function resolveCodeProject(dirPath: string): Promise< - { ok: true; projectId: string; path: string; warning?: string } | { ok: false; error: string } -> { - const abs = path.resolve(expandHome(dirPath)); - const projectsRepo = container.resolve('codeProjectsRepo'); - let project: Awaited>; - try { - project = await projectsRepo.add(abs); - } catch (err) { - return { ok: false, error: `Could not use '${dirPath}' as a code directory: ${err instanceof Error ? err.message : String(err)}` }; - } - // Worktree isolation needs a real git repo with at least one commit - // (codeSessionService.create throws otherwise). Surface it now as a soft - // warning rather than letting the next run fail silently. - let warning: string | undefined; - try { - const info = await gitService.repoInfo(project.path); - if (!info.isGitRepo) warning = `${project.path} is not a git repository yet — run \`git init\` and make a commit, or the coding sessions will fail.`; - else if (!info.hasCommits) warning = `${project.path} has no commits yet — make an initial commit, or the coding sessions will fail.`; - } catch { /* best effort — worktree creation will surface it later */ } - return { ok: true, projectId: project.id, path: project.path, ...(warning ? { warning } : {}) }; -} -// Parser libraries are loaded dynamically inside parseFile.execute() -// to avoid pulling pdfjs-dist's DOM polyfills into the main bundle. -// Import paths are computed so esbuild cannot statically resolve them. -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export const _importDynamic = new Function('mod', 'return import(mod)') as (mod: string) => Promise; - -export const BuiltinToolsSchema = z.record(z.string(), z.object({ - description: z.string(), - inputSchema: z.custom(), - execute: z.function({ - input: z.any(), // (input, ctx?) => Promise - output: z.promise(z.any()), - }), - isAvailable: z.custom<() => Promise>().optional(), -})); - -export const LLMPARSE_MIME_TYPES: Record = { - '.pdf': 'application/pdf', - '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', - '.doc': 'application/msword', - '.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation', - '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', - '.xls': 'application/vnd.ms-excel', - '.csv': 'text/csv', - '.txt': 'text/plain', - '.html': 'text/html', - '.png': 'image/png', - '.jpg': 'image/jpeg', - '.jpeg': 'image/jpeg', - '.gif': 'image/gif', - '.webp': 'image/webp', - '.svg': 'image/svg+xml', - '.bmp': 'image/bmp', - '.tiff': 'image/tiff', -}; diff --git a/apps/x/packages/core/src/runtime/tools/domains/web.ts b/apps/x/packages/core/src/runtime/tools/domains/web.ts index 9b9c5ad2..e3d40829 100644 --- a/apps/x/packages/core/src/runtime/tools/domains/web.ts +++ b/apps/x/packages/core/src/runtime/tools/domains/web.ts @@ -9,9 +9,7 @@ import { WorkDir } from "../../../config/config.js"; import { isSignedIn } from "../../../account/account.js"; import { getAccessToken } from "../../../auth/tokens.js"; import { API_URL } from "../../../config/env.js"; -import { - BuiltinToolsSchema, -} from "./support.js"; +import { BuiltinToolsSchema } from "../types.js"; export const webSearchTools: z.infer = { diff --git a/apps/x/packages/core/src/runtime/tools/paths.ts b/apps/x/packages/core/src/runtime/tools/paths.ts new file mode 100644 index 00000000..80fc0528 --- /dev/null +++ b/apps/x/packages/core/src/runtime/tools/paths.ts @@ -0,0 +1,18 @@ +// Lenient ~-expansion for user-supplied paths in tool inputs. Distinct from +// filesystem/files.ts' private expandHomePath, which throws on empty input — +// tool entries want pass-through semantics for their own validation. + +import * as path from "path"; +import * as os from "os"; + +// Turn a user-supplied directory into a registered code project id. Reuses the +// same idempotent registry the Code-section picker writes to (add() validates the +// dir exists & is a directory, and dedupes by resolved path). Returns a soft +// `warning` — not an error — when the repo isn't yet worktree-ready, so the task +// still gets created and the copilot can tell the user what to fix. +export function expandHome(p: string): string { + const t = p.trim(); + if (t === '~') return os.homedir(); + if (t.startsWith('~/') || t.startsWith(`~${path.sep}`)) return path.join(os.homedir(), t.slice(2)); + return t; +} diff --git a/apps/x/packages/core/src/runtime/tools/types.ts b/apps/x/packages/core/src/runtime/tools/types.ts new file mode 100644 index 00000000..082d4254 --- /dev/null +++ b/apps/x/packages/core/src/runtime/tools/types.ts @@ -0,0 +1,15 @@ +// The builtin-tool catalog schema: every entry is {description, inputSchema, +// execute, isAvailable?}. Shared typing for the domain modules and the +// merged catalog. + +import { z, ZodType } from "zod"; + +export const BuiltinToolsSchema = z.record(z.string(), z.object({ + description: z.string(), + inputSchema: z.custom(), + execute: z.function({ + input: z.any(), // (input, ctx?) => Promise + output: z.promise(z.any()), + }), + isAvailable: z.custom<() => Promise>().optional(), +}));