@theokit/sdk-tools 0.15.0 → 0.16.0

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.16.0
4
+
5
+ ### Minor Changes
6
+
7
+ - ef00db3: Add `createCurrentTimeTool` — a built-in `current_time` tool. Codex-faithful at the core (Codex's
8
+ `clock.curr_time` returns UTC as `YYYY-MM-DD HH:MM:SS UTC`); this keeps that as the default and adds an
9
+ optional IANA `timezone` (additive superset — omitted ⇒ UTC) plus an unambiguous `iso` instant. Returns
10
+ `{ ok, current_time, iso, timezone }` or `{ ok: false, error: 'invalid_timezone' }`. The clock is
11
+ injectable (`{ clock }`) so the tool is deterministic under test.
12
+
13
+ ## 0.15.1
14
+
15
+ ### Patch Changes
16
+
17
+ - 4c5bd35: M15 review fixes (injected fs path only; local path unaffected): (1) the backend directory walk in
18
+ `glob_files`/`search_text` decides entry type via `stat` (which follows symlinks), so an in-boundary
19
+ symlink cycle could recurse until PATH_MAX — now depth-capped so it terminates; (2) `edit_file`'s
20
+ backend read mapped every failure to `not_found` — now only a genuinely missing file (`FileNotFoundError`)
21
+ maps to `not_found`; any other read error (e.g. a directory, a permission error) propagates (fail-loud),
22
+ matching the local path's ENOENT-only classification.
23
+
3
24
  ## 0.15.0
4
25
 
5
26
  ### Minor Changes
package/dist/index.cjs CHANGED
@@ -267,6 +267,46 @@ function createSessionArtifactStore(options) {
267
267
  }
268
268
  return { write, read, has, list, path };
269
269
  }
270
+ function formatInTimezone(now, tz) {
271
+ const parts = new Intl.DateTimeFormat("en-US", {
272
+ timeZone: tz,
273
+ year: "numeric",
274
+ month: "2-digit",
275
+ day: "2-digit",
276
+ hour: "2-digit",
277
+ minute: "2-digit",
278
+ second: "2-digit",
279
+ hour12: false
280
+ }).formatToParts(now);
281
+ const p = {};
282
+ for (const { type, value } of parts) p[type] = value;
283
+ const hour = p.hour === "24" ? "00" : p.hour;
284
+ return `${p.year}-${p.month}-${p.day} ${hour}:${p.minute}:${p.second} ${tz}`;
285
+ }
286
+ function createCurrentTimeTool(opts = {}) {
287
+ const clock = opts.clock ?? (() => /* @__PURE__ */ new Date());
288
+ return sdk.Tool.create({
289
+ name: "current_time",
290
+ description: "Get the current date and time. Returns { current_time, iso, timezone } as a JSON string, where current_time is 'YYYY-MM-DD HH:MM:SS <timezone>' and iso is the ISO-8601 instant. Pass an optional IANA timezone (e.g. 'America/Sao_Paulo', 'Europe/Lisbon'); defaults to UTC. Never state the date or time from memory \u2014 always call this. Returns { ok: false, error: 'invalid_timezone' } for an unknown timezone.",
291
+ inputSchema: zod.z.object({
292
+ timezone: zod.z.string().optional().describe("IANA timezone, e.g. 'America/Sao_Paulo' or 'Europe/Lisbon'. Defaults to UTC.")
293
+ }),
294
+ handler: ({ timezone }) => {
295
+ const tz = timezone ?? "UTC";
296
+ const now = clock();
297
+ try {
298
+ return JSON.stringify({
299
+ ok: true,
300
+ current_time: formatInTimezone(now, tz),
301
+ iso: now.toISOString(),
302
+ timezone: tz
303
+ });
304
+ } catch {
305
+ return JSON.stringify({ ok: false, error: "invalid_timezone", timezone: tz });
306
+ }
307
+ }
308
+ });
309
+ }
270
310
 
271
311
  // src/internal/context-match.ts
272
312
  var ContextMatchError = class extends Error {
@@ -381,8 +421,11 @@ async function editViaBackend(filesystem$1, ctx, path, old_string, new_string) {
381
421
  let content;
382
422
  try {
383
423
  content = await backend.readFile(path);
384
- } catch {
385
- return JSON.stringify({ ok: false, error: "not_found", path });
424
+ } catch (err) {
425
+ if (err instanceof filesystem.FileNotFoundError) {
426
+ return JSON.stringify({ ok: false, error: "not_found", path });
427
+ }
428
+ throw err;
386
429
  }
387
430
  const outcome = computeEdit(content, old_string, new_string);
388
431
  if (!outcome.ok) return JSON.stringify({ ok: false, error: "no_match", path });
@@ -656,6 +699,7 @@ function runGitProcess(cwd, args, timeoutMs, maxStdoutBytes) {
656
699
  });
657
700
  }
658
701
  var DEFAULT_EXCLUDES = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", ".theo"]);
702
+ var MAX_BACKEND_WALK_DEPTH = 64;
659
703
  function createGlobTool(opts) {
660
704
  const { projectRoot, filesystem: filesystem$1 } = opts;
661
705
  return sdk.Tool.create({
@@ -673,7 +717,7 @@ function createGlobTool(opts) {
673
717
  const backend = await filesystem.resolveFilesystem(filesystem$1, ctx ?? {});
674
718
  const searchRel = cwd ?? "";
675
719
  const found = [];
676
- await walkDirBackend(backend, searchRel, searchRel, regex, found);
720
+ await walkDirBackend(backend, searchRel, searchRel, regex, found, 0);
677
721
  return JSON.stringify({ ok: true, files: found.sort(), count: found.length });
678
722
  }
679
723
  const searchRoot = cwd ? safePathJoin(projectRoot, cwd) : projectRoot;
@@ -714,7 +758,8 @@ async function walkDir(base, dir, pattern, results) {
714
758
  }
715
759
  }
716
760
  }
717
- async function walkDirBackend(backend, base, dir, pattern, results) {
761
+ async function walkDirBackend(backend, base, dir, pattern, results, depth) {
762
+ if (depth > MAX_BACKEND_WALK_DEPTH) return;
718
763
  let names;
719
764
  try {
720
765
  names = await backend.list(dir);
@@ -722,10 +767,10 @@ async function walkDirBackend(backend, base, dir, pattern, results) {
722
767
  return;
723
768
  }
724
769
  for (const name of names) {
725
- await walkBackendEntry(backend, base, dir, name, pattern, results);
770
+ await walkBackendEntry(backend, base, dir, name, pattern, results, depth);
726
771
  }
727
772
  }
728
- async function walkBackendEntry(backend, base, dir, name, pattern, results) {
773
+ async function walkBackendEntry(backend, base, dir, name, pattern, results, depth) {
729
774
  if (DEFAULT_EXCLUDES.has(name)) return;
730
775
  const fullRel = dir === "" ? name : `${dir}/${name}`;
731
776
  const relPath = base === "" ? fullRel : path.relative(base, fullRel);
@@ -736,7 +781,7 @@ async function walkBackendEntry(backend, base, dir, name, pattern, results) {
736
781
  return;
737
782
  }
738
783
  if (st.isDirectory) {
739
- await walkDirBackend(backend, base, fullRel, pattern, results);
784
+ await walkDirBackend(backend, base, fullRel, pattern, results, depth + 1);
740
785
  } else if (st.isFile && pattern.test(relPath)) {
741
786
  results.push(fullRel);
742
787
  }
@@ -1808,6 +1853,7 @@ var DEFAULT_MAX_MATCHES = 100;
1808
1853
  var DEFAULT_MAX_FILE_SIZE = 1024 * 1024;
1809
1854
  var BINARY_PROBE_BYTES2 = 8 * 1024;
1810
1855
  var PREVIEW_MAX = 200;
1856
+ var MAX_BACKEND_WALK_DEPTH2 = 64;
1811
1857
  function createSearchTextTool(opts) {
1812
1858
  const {
1813
1859
  projectRoot,
@@ -1836,7 +1882,7 @@ function createSearchTextTool(opts) {
1836
1882
  const scopeRel = resolveScopeRel(path, projectRoot);
1837
1883
  if ("error" in scopeRel) return scopeRel.error;
1838
1884
  const backend = await filesystem.resolveFilesystem(filesystem$1, ctx ?? {});
1839
- await walkBackend(backend, scopeRel.rel, state);
1885
+ await walkBackend(backend, scopeRel.rel, state, 0);
1840
1886
  return JSON.stringify({
1841
1887
  ok: true,
1842
1888
  matches: state.matches,
@@ -1946,8 +1992,9 @@ function resolveScopeRel(path, projectRoot) {
1946
1992
  throw err;
1947
1993
  }
1948
1994
  }
1949
- async function walkBackend(backend, dirRel, state) {
1995
+ async function walkBackend(backend, dirRel, state, depth) {
1950
1996
  if (state.truncated) return;
1997
+ if (depth > MAX_BACKEND_WALK_DEPTH2) return;
1951
1998
  let names;
1952
1999
  try {
1953
2000
  names = await backend.list(dirRel);
@@ -1956,10 +2003,10 @@ async function walkBackend(backend, dirRel, state) {
1956
2003
  }
1957
2004
  for (const name of names) {
1958
2005
  if (state.truncated) return;
1959
- await handleBackendEntry(backend, dirRel, name, state);
2006
+ await handleBackendEntry(backend, dirRel, name, state, depth);
1960
2007
  }
1961
2008
  }
1962
- async function handleBackendEntry(backend, dirRel, name, state) {
2009
+ async function handleBackendEntry(backend, dirRel, name, state, depth) {
1963
2010
  const entryRel = dirRel === "" ? name : `${dirRel}/${name}`;
1964
2011
  if (isForbiddenPath(entryRel)) return;
1965
2012
  let st;
@@ -1969,7 +2016,7 @@ async function handleBackendEntry(backend, dirRel, name, state) {
1969
2016
  return;
1970
2017
  }
1971
2018
  if (st.isDirectory) {
1972
- await walkBackend(backend, entryRel, state);
2019
+ await walkBackend(backend, entryRel, state, depth + 1);
1973
2020
  } else if (st.isFile) {
1974
2021
  await scanFileBackend(backend, entryRel, st.size, state);
1975
2022
  }
@@ -2565,6 +2612,7 @@ exports.catastrophicShellReason = catastrophicShellReason;
2565
2612
  exports.commandDenialReason = commandDenialReason;
2566
2613
  exports.createApplyPatchTool = createApplyPatchTool;
2567
2614
  exports.createBraveWebSearchAdapter = createBraveWebSearchAdapter;
2615
+ exports.createCurrentTimeTool = createCurrentTimeTool;
2568
2616
  exports.createEditFileTool = createEditFileTool;
2569
2617
  exports.createGenericHttpSearchAdapter = createGenericHttpSearchAdapter;
2570
2618
  exports.createGitDiffTool = createGitDiffTool;