langflower 0.0.6 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,6 +6,8 @@ export type HandlerContext = {
6
6
  /** Absolute (or project-relative) roots trusted outside the project. */
7
7
  readonly allowedRoots: readonly string[];
8
8
  readonly bashEnabled: boolean;
9
+ /** Per-invoke abort (tool timeout / run cancel). */
10
+ readonly signal?: AbortSignal;
9
11
  };
10
12
  export type BuiltinTool<Id extends string = string> = {
11
13
  readonly id: Id;
@@ -1,2 +1,16 @@
1
+ /** Always skip these directory names (ts-scan-style), even with --no-ignore. */
2
+ export declare const WALK_EXCLUDE_DIR_NAMES: readonly ["node_modules", "dist", "build", ".git"];
1
3
  export declare const globToRegExp: (pattern: string) => RegExp;
2
- export declare const walkFiles: (root: string, dir: string, respectGitignore: boolean) => Promise<readonly string[]>;
4
+ export type WalkFilesOptions = {
5
+ readonly respectGitignore?: boolean;
6
+ readonly signal?: AbortSignal;
7
+ /** Stop collecting after this many files (default unlimited). */
8
+ readonly maxFiles?: number;
9
+ /** Yield to the event loop every N directory entries (default 32). */
10
+ readonly yieldEvery?: number;
11
+ };
12
+ /**
13
+ * Async recursive file walk. Third arg may be `respectGitignore` boolean
14
+ * (legacy) or {@link WalkFilesOptions}.
15
+ */
16
+ export declare const walkFiles: (root: string, dir: string, options?: boolean | WalkFilesOptions) => Promise<readonly string[]>;
@@ -1,6 +1,13 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import { loadGitIgnoreMatcher } from '../gitignore.js';
4
+ /** Always skip these directory names (ts-scan-style), even with --no-ignore. */
5
+ export const WALK_EXCLUDE_DIR_NAMES = [
6
+ 'node_modules',
7
+ 'dist',
8
+ 'build',
9
+ '.git',
10
+ ];
4
11
  export const globToRegExp = (pattern) => {
5
12
  const escaped = pattern
6
13
  .replace(/\\/g, '/')
@@ -11,12 +18,42 @@ export const globToRegExp = (pattern) => {
11
18
  .replace(/::DOUBLESTAR::/g, '.*');
12
19
  return new RegExp(`^${escaped}$`);
13
20
  };
14
- export const walkFiles = async (root, dir, respectGitignore) => {
21
+ const normalizeOptions = (options) => typeof options === 'boolean' ? { respectGitignore: options } : options;
22
+ const yieldEventLoop = () => new Promise((resolve) => {
23
+ setImmediate(resolve);
24
+ });
25
+ const throwIfAborted = (signal) => {
26
+ if (signal?.aborted) {
27
+ throw new Error('aborted');
28
+ }
29
+ };
30
+ /**
31
+ * Async recursive file walk. Third arg may be `respectGitignore` boolean
32
+ * (legacy) or {@link WalkFilesOptions}.
33
+ */
34
+ export const walkFiles = async (root, dir, options = true) => {
35
+ const opts = normalizeOptions(options);
36
+ const respectGitignore = opts.respectGitignore !== false;
37
+ const yieldEvery = opts.yieldEvery ?? 32;
15
38
  const matcher = respectGitignore
16
39
  ? await loadGitIgnoreMatcher(root)
17
40
  : { ignores: () => false };
18
41
  const out = [];
42
+ const visitedDirs = new Set();
43
+ let steps = 0;
19
44
  const visit = async (absoluteDir) => {
45
+ throwIfAborted(opts.signal);
46
+ let realDir;
47
+ try {
48
+ realDir = await fs.realpath(absoluteDir);
49
+ }
50
+ catch {
51
+ return;
52
+ }
53
+ if (visitedDirs.has(realDir)) {
54
+ return;
55
+ }
56
+ visitedDirs.add(realDir);
20
57
  let entries;
21
58
  try {
22
59
  entries = await fs.readdir(absoluteDir, { withFileTypes: true });
@@ -25,6 +62,16 @@ export const walkFiles = async (root, dir, respectGitignore) => {
25
62
  return;
26
63
  }
27
64
  for (const entry of entries) {
65
+ throwIfAborted(opts.signal);
66
+ steps += 1;
67
+ if (steps % yieldEvery === 0) {
68
+ await yieldEventLoop();
69
+ throwIfAborted(opts.signal);
70
+ }
71
+ if (entry.isDirectory() &&
72
+ WALK_EXCLUDE_DIR_NAMES.includes(entry.name)) {
73
+ continue;
74
+ }
28
75
  const absolute = path.join(absoluteDir, entry.name);
29
76
  const relative = path
30
77
  .relative(root, absolute)
@@ -33,11 +80,18 @@ export const walkFiles = async (root, dir, respectGitignore) => {
33
80
  if (matcher.ignores(relative, entry.isDirectory())) {
34
81
  continue;
35
82
  }
83
+ if (entry.isSymbolicLink()) {
84
+ continue;
85
+ }
36
86
  if (entry.isDirectory()) {
37
87
  await visit(absolute);
38
88
  }
39
89
  else if (entry.isFile()) {
40
90
  out.push(relative);
91
+ if (opts.maxFiles !== undefined &&
92
+ out.length >= opts.maxFiles) {
93
+ return;
94
+ }
41
95
  }
42
96
  }
43
97
  };
@@ -48,7 +48,10 @@ export const createProjectHarness = (options) => {
48
48
  return deniedToolResult(toolId, detail);
49
49
  }
50
50
  try {
51
- const text = await invokeBuiltin(toolId, ctx, call.args);
51
+ const invokeCtx = call.signal === undefined
52
+ ? ctx
53
+ : { ...ctx, signal: call.signal };
54
+ const text = await invokeBuiltin(toolId, invokeCtx, call.args);
52
55
  return { ok: true, text };
53
56
  }
54
57
  catch (error) {
@@ -15,6 +15,8 @@ export type ToolHandlerContext = {
15
15
  readonly webFetch?: (request: WebFetchRequest) => Promise<WebFetchResult>;
16
16
  readonly denyPaths?: readonly string[];
17
17
  readonly allowedHosts?: readonly string[];
18
+ /** Per-invoke abort (tool timeout / run cancel) for builtins. */
19
+ readonly signal?: AbortSignal;
18
20
  };
19
21
  export type ToolHandler = (args: Readonly<Record<string, unknown>>, ctx: ToolHandlerContext) => Promise<string>;
20
22
  export type DomainToolConfig = {
@@ -12,6 +12,8 @@ export type ToolInvokeResult = {
12
12
  export type ToolInvokeCall = {
13
13
  readonly toolId: string;
14
14
  readonly args: Readonly<Record<string, unknown>>;
15
+ /** Per-invoke abort (tool timeout / run cancel). */
16
+ readonly signal?: AbortSignal;
15
17
  };
16
18
  export type BuiltinToolRegistration = {
17
19
  readonly toolId: string;