@agi-cli/sdk 0.1.54 → 0.1.56

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.
Files changed (81) hide show
  1. package/package.json +53 -27
  2. package/src/agent/types.ts +1 -1
  3. package/src/auth/src/index.ts +70 -0
  4. package/src/auth/src/oauth.ts +172 -0
  5. package/src/config/src/index.ts +120 -0
  6. package/src/config/src/manager.ts +102 -0
  7. package/src/config/src/paths.ts +98 -0
  8. package/src/core/src/errors.ts +102 -0
  9. package/src/core/src/index.ts +75 -0
  10. package/src/core/src/providers/resolver.ts +84 -0
  11. package/src/core/src/streaming/artifacts.ts +41 -0
  12. package/src/core/src/tools/builtin/bash.ts +90 -0
  13. package/src/core/src/tools/builtin/bash.txt +7 -0
  14. package/src/core/src/tools/builtin/edit.ts +152 -0
  15. package/src/core/src/tools/builtin/edit.txt +7 -0
  16. package/src/core/src/tools/builtin/file-cache.ts +39 -0
  17. package/src/core/src/tools/builtin/finish.ts +11 -0
  18. package/src/core/src/tools/builtin/finish.txt +5 -0
  19. package/src/core/src/tools/builtin/fs/cd.ts +19 -0
  20. package/src/core/src/tools/builtin/fs/cd.txt +5 -0
  21. package/src/core/src/tools/builtin/fs/index.ts +20 -0
  22. package/src/core/src/tools/builtin/fs/ls.ts +60 -0
  23. package/src/core/src/tools/builtin/fs/ls.txt +8 -0
  24. package/src/core/src/tools/builtin/fs/pwd.ts +17 -0
  25. package/src/core/src/tools/builtin/fs/pwd.txt +5 -0
  26. package/src/core/src/tools/builtin/fs/read.ts +80 -0
  27. package/src/core/src/tools/builtin/fs/read.txt +8 -0
  28. package/src/core/src/tools/builtin/fs/tree.ts +71 -0
  29. package/src/core/src/tools/builtin/fs/tree.txt +8 -0
  30. package/src/core/src/tools/builtin/fs/util.ts +95 -0
  31. package/src/core/src/tools/builtin/fs/write.ts +61 -0
  32. package/src/core/src/tools/builtin/fs/write.txt +8 -0
  33. package/src/core/src/tools/builtin/git.commit.txt +6 -0
  34. package/src/core/src/tools/builtin/git.diff.txt +5 -0
  35. package/src/core/src/tools/builtin/git.status.txt +5 -0
  36. package/src/core/src/tools/builtin/git.ts +128 -0
  37. package/src/core/src/tools/builtin/grep.ts +140 -0
  38. package/src/core/src/tools/builtin/grep.txt +9 -0
  39. package/src/core/src/tools/builtin/ignore.ts +45 -0
  40. package/src/core/src/tools/builtin/patch.ts +269 -0
  41. package/src/core/src/tools/builtin/patch.txt +7 -0
  42. package/src/core/src/tools/builtin/plan.ts +58 -0
  43. package/src/core/src/tools/builtin/plan.txt +6 -0
  44. package/src/core/src/tools/builtin/progress.ts +55 -0
  45. package/src/core/src/tools/builtin/progress.txt +7 -0
  46. package/src/core/src/tools/builtin/ripgrep.ts +102 -0
  47. package/src/core/src/tools/builtin/ripgrep.txt +7 -0
  48. package/src/core/src/tools/builtin/websearch.ts +219 -0
  49. package/src/core/src/tools/builtin/websearch.txt +12 -0
  50. package/src/core/src/tools/loader.ts +398 -0
  51. package/src/core/src/types/index.ts +11 -0
  52. package/src/core/src/types/types.ts +4 -0
  53. package/src/index.ts +57 -58
  54. package/src/prompts/src/agents/build.txt +5 -0
  55. package/src/prompts/src/agents/general.txt +6 -0
  56. package/src/prompts/src/agents/plan.txt +13 -0
  57. package/src/prompts/src/base.txt +14 -0
  58. package/src/prompts/src/debug.ts +104 -0
  59. package/src/prompts/src/index.ts +1 -0
  60. package/src/prompts/src/modes/oneshot.txt +9 -0
  61. package/src/prompts/src/providers/anthropic.txt +151 -0
  62. package/src/prompts/src/providers/anthropicSpoof.txt +1 -0
  63. package/src/prompts/src/providers/default.txt +310 -0
  64. package/src/prompts/src/providers/google.txt +155 -0
  65. package/src/prompts/src/providers/openai.txt +310 -0
  66. package/src/prompts/src/providers.ts +116 -0
  67. package/src/providers/src/authorization.ts +17 -0
  68. package/src/providers/src/catalog.ts +4201 -0
  69. package/src/providers/src/env.ts +26 -0
  70. package/src/providers/src/index.ts +12 -0
  71. package/src/providers/src/pricing.ts +135 -0
  72. package/src/providers/src/utils.ts +24 -0
  73. package/src/providers/src/validate.ts +39 -0
  74. package/src/types/src/auth.ts +26 -0
  75. package/src/types/src/config.ts +40 -0
  76. package/src/types/src/index.ts +14 -0
  77. package/src/types/src/provider.ts +28 -0
  78. package/src/global.d.ts +0 -4
  79. package/src/tools/builtin/fs.ts +0 -1
  80. package/src/tools/builtin/git.ts +0 -1
  81. package/src/web-ui.ts +0 -8
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Base error class for all AGI errors
3
+ */
4
+ export class AGIError extends Error {
5
+ constructor(
6
+ message: string,
7
+ public readonly code: string,
8
+ public readonly status: number = 500,
9
+ public readonly details?: Record<string, unknown>,
10
+ ) {
11
+ super(message);
12
+ this.name = this.constructor.name;
13
+ Error.captureStackTrace(this, this.constructor);
14
+ }
15
+
16
+ toJSON() {
17
+ return {
18
+ name: this.name,
19
+ message: this.message,
20
+ code: this.code,
21
+ status: this.status,
22
+ details: this.details,
23
+ };
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Authentication and authorization errors
29
+ */
30
+ export class AuthError extends AGIError {
31
+ constructor(message: string, details?: Record<string, unknown>) {
32
+ super(message, 'AUTH_ERROR', 401, details);
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Configuration errors
38
+ */
39
+ export class ConfigError extends AGIError {
40
+ constructor(message: string, details?: Record<string, unknown>) {
41
+ super(message, 'CONFIG_ERROR', 500, details);
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Tool execution errors
47
+ */
48
+ export class ToolError extends AGIError {
49
+ constructor(message: string, details?: Record<string, unknown>) {
50
+ super(message, 'TOOL_ERROR', 500, details);
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Provider errors (API, model not found, etc.)
56
+ */
57
+ export class ProviderError extends AGIError {
58
+ constructor(message: string, details?: Record<string, unknown>) {
59
+ super(message, 'PROVIDER_ERROR', 500, details);
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Database errors
65
+ */
66
+ export class DatabaseError extends AGIError {
67
+ constructor(message: string, details?: Record<string, unknown>) {
68
+ super(message, 'DATABASE_ERROR', 500, details);
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Validation errors (bad input, schema mismatch, etc.)
74
+ */
75
+ export class ValidationError extends AGIError {
76
+ constructor(message: string, details?: Record<string, unknown>) {
77
+ super(message, 'VALIDATION_ERROR', 400, details);
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Not found errors (session, agent, tool, etc.)
83
+ */
84
+ export class NotFoundError extends AGIError {
85
+ constructor(message: string, details?: Record<string, unknown>) {
86
+ super(message, 'NOT_FOUND', 404, details);
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Service errors from ask-service, session-manager, etc.
92
+ */
93
+ export class ServiceError extends AGIError {
94
+ constructor(
95
+ message: string,
96
+ code = 'SERVICE_ERROR',
97
+ status = 500,
98
+ details?: Record<string, unknown>,
99
+ ) {
100
+ super(message, code, status, details);
101
+ }
102
+ }
@@ -0,0 +1,75 @@
1
+ // =======================
2
+ // Core AI Functions (from AI SDK)
3
+ // =======================
4
+ export {
5
+ generateText,
6
+ streamText,
7
+ generateObject,
8
+ streamObject,
9
+ tool,
10
+ } from 'ai';
11
+ export type { CoreMessage, Tool } from 'ai';
12
+
13
+ // =======================
14
+ // Provider & Model Resolution
15
+ // =======================
16
+ export { resolveModel } from './providers/resolver';
17
+ export type { ProviderName, ModelConfig } from './providers/resolver';
18
+
19
+ // Re-export provider catalog and utilities for easy access
20
+ export {
21
+ catalog,
22
+ providerIds,
23
+ isProviderId,
24
+ isProviderAuthorized,
25
+ validateProviderModel,
26
+ } from '../../providers/src/index.ts';
27
+ export type { ProviderId, ModelInfo } from '../../types/src/index.ts';
28
+
29
+ // =======================
30
+ // Tools
31
+ // =======================
32
+ export { discoverProjectTools } from './tools/loader';
33
+ export type { DiscoveredTool } from './tools/loader';
34
+
35
+ // Re-export builtin tools for direct access
36
+ export { buildFsTools } from './tools/builtin/fs/index';
37
+ export { buildGitTools } from './tools/builtin/git';
38
+
39
+ // =======================
40
+ // Streaming & Artifacts
41
+ // =======================
42
+ export {
43
+ createFileDiffArtifact,
44
+ createToolResultPayload,
45
+ } from './streaming/artifacts';
46
+ export type {
47
+ Artifact,
48
+ FileDiffArtifact,
49
+ FileArtifact,
50
+ } from './streaming/artifacts';
51
+
52
+ // =======================
53
+ // Types
54
+ // =======================
55
+ export type { ExecutionContext, ToolResult } from './types/index';
56
+
57
+ // =======================
58
+ // Schema Validation
59
+ // =======================
60
+ export { z } from 'zod';
61
+
62
+ // =======================
63
+ // Error Handling
64
+ // =======================
65
+ export {
66
+ AGIError,
67
+ AuthError,
68
+ ConfigError,
69
+ ToolError,
70
+ ProviderError,
71
+ DatabaseError,
72
+ ValidationError,
73
+ NotFoundError,
74
+ ServiceError,
75
+ } from './errors';
@@ -0,0 +1,84 @@
1
+ import { openai, createOpenAI } from '@ai-sdk/openai';
2
+ import { anthropic, createAnthropic } from '@ai-sdk/anthropic';
3
+ import { google } from '@ai-sdk/google';
4
+ import { createOpenRouter } from '@openrouter/ai-sdk-provider';
5
+ import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
6
+
7
+ export type ProviderName =
8
+ | 'openai'
9
+ | 'anthropic'
10
+ | 'google'
11
+ | 'openrouter'
12
+ | 'opencode';
13
+
14
+ export type ModelConfig = {
15
+ apiKey?: string;
16
+ customFetch?: typeof fetch;
17
+ baseURL?: string;
18
+ };
19
+
20
+ export async function resolveModel(
21
+ provider: ProviderName,
22
+ model: string,
23
+ config: ModelConfig = {},
24
+ ) {
25
+ if (provider === 'openai') {
26
+ if (config.apiKey) {
27
+ const instance = createOpenAI({ apiKey: config.apiKey });
28
+ return instance(model);
29
+ }
30
+ return openai(model);
31
+ }
32
+
33
+ if (provider === 'anthropic') {
34
+ if (config.customFetch) {
35
+ return createAnthropic({
36
+ apiKey: config.apiKey || '',
37
+ fetch: config.customFetch as typeof fetch,
38
+ });
39
+ }
40
+ if (config.apiKey) {
41
+ const instance = createAnthropic({ apiKey: config.apiKey });
42
+ return instance(model);
43
+ }
44
+ return anthropic(model);
45
+ }
46
+
47
+ if (provider === 'google') {
48
+ if (config.apiKey) {
49
+ throw new Error('Google provider config not yet supported in SDK');
50
+ }
51
+ return google(model);
52
+ }
53
+
54
+ if (provider === 'openrouter') {
55
+ const apiKey = config.apiKey || process.env.OPENROUTER_API_KEY || '';
56
+ const openrouter = createOpenRouter({ apiKey });
57
+ return openrouter.chat(model);
58
+ }
59
+
60
+ if (provider === 'opencode') {
61
+ const baseURL = config.baseURL || 'https://opencode.ai/zen/v1';
62
+ const apiKey = config.apiKey || process.env.OPENCODE_API_KEY || '';
63
+
64
+ const ocOpenAI = createOpenAI({ apiKey, baseURL });
65
+ const ocAnthropic = createAnthropic({ apiKey, baseURL });
66
+ const ocCompat = createOpenAICompatible({
67
+ name: 'opencode',
68
+ baseURL,
69
+ headers: apiKey ? { Authorization: `Bearer ${apiKey}` } : undefined,
70
+ });
71
+
72
+ const id = model.toLowerCase();
73
+ if (id.includes('claude')) return ocAnthropic(model);
74
+ if (
75
+ id.includes('qwen3-coder') ||
76
+ id.includes('grok-code') ||
77
+ id.includes('kimi-k2')
78
+ )
79
+ return ocCompat(model);
80
+ return ocOpenAI(model);
81
+ }
82
+
83
+ throw new Error(`Unsupported provider: ${provider}`);
84
+ }
@@ -0,0 +1,41 @@
1
+ // Shared types and helpers for tool artifacts persisted in message parts.
2
+
3
+ export type FileDiffArtifact = {
4
+ kind: 'file_diff';
5
+ patchFormat: 'unified';
6
+ patch: string; // full unified patch text
7
+ summary?: { files?: number; additions?: number; deletions?: number };
8
+ };
9
+
10
+ export type FileArtifact = {
11
+ kind: 'file';
12
+ path: string; // repository-relative path
13
+ mime?: string;
14
+ size?: number;
15
+ sha256?: string;
16
+ };
17
+
18
+ export type Artifact =
19
+ | FileDiffArtifact
20
+ | FileArtifact
21
+ | { kind: string; [k: string]: unknown };
22
+
23
+ export function createFileDiffArtifact(
24
+ patch: string,
25
+ summary?: { files?: number; additions?: number; deletions?: number },
26
+ ): FileDiffArtifact {
27
+ return { kind: 'file_diff', patchFormat: 'unified', patch, summary };
28
+ }
29
+
30
+ export function createToolResultPayload(
31
+ name: string,
32
+ result?: unknown,
33
+ artifact?: Artifact,
34
+ ) {
35
+ const payload: { name: string; result?: unknown; artifact?: Artifact } = {
36
+ name,
37
+ };
38
+ if (result !== undefined) payload.result = result;
39
+ if (artifact) payload.artifact = artifact;
40
+ return payload;
41
+ }
@@ -0,0 +1,90 @@
1
+ import { tool, type Tool } from 'ai';
2
+ import { z } from 'zod';
3
+ import { exec } from 'node:child_process';
4
+ import DESCRIPTION from './bash.txt' with { type: 'text' };
5
+
6
+ function normalizePath(p: string) {
7
+ const parts = p.replace(/\\/g, '/').split('/');
8
+ const stack: string[] = [];
9
+ for (const part of parts) {
10
+ if (!part || part === '.') continue;
11
+ if (part === '..') stack.pop();
12
+ else stack.push(part);
13
+ }
14
+ return `/${stack.join('/')}`;
15
+ }
16
+
17
+ function resolveSafePath(projectRoot: string, p: string) {
18
+ const root = normalizePath(projectRoot);
19
+ const abs = normalizePath(`${root}/${p || '.'}`);
20
+ if (!(abs === root || abs.startsWith(`${root}/`))) {
21
+ throw new Error(`cwd escapes project root: ${p}`);
22
+ }
23
+ return abs;
24
+ }
25
+
26
+ export function buildBashTool(projectRoot: string): {
27
+ name: string;
28
+ tool: Tool;
29
+ } {
30
+ const bash = tool({
31
+ description: DESCRIPTION,
32
+ inputSchema: z
33
+ .object({
34
+ cmd: z.string().describe('Shell command to run (bash -lc <cmd>)'),
35
+ cwd: z
36
+ .string()
37
+ .default('.')
38
+ .describe('Working directory relative to project root'),
39
+ allowNonZeroExit: z
40
+ .boolean()
41
+ .optional()
42
+ .default(false)
43
+ .describe('If true, do not throw on non-zero exit'),
44
+ })
45
+ .strict(),
46
+ async execute({
47
+ cmd,
48
+ cwd,
49
+ allowNonZeroExit,
50
+ }: {
51
+ cmd: string;
52
+ cwd?: string;
53
+ allowNonZeroExit?: boolean;
54
+ }) {
55
+ const absCwd = resolveSafePath(projectRoot, cwd || '.');
56
+
57
+ return new Promise((resolve, reject) => {
58
+ const proc = exec(`bash -lc '${cmd.replace(/'/g, "'\\''")}'`, {
59
+ cwd: absCwd,
60
+ maxBuffer: 10 * 1024 * 1024,
61
+ });
62
+
63
+ let stdout = '';
64
+ let stderr = '';
65
+
66
+ proc.stdout?.on('data', (chunk) => {
67
+ stdout += chunk.toString();
68
+ });
69
+
70
+ proc.stderr?.on('data', (chunk) => {
71
+ stderr += chunk.toString();
72
+ });
73
+
74
+ proc.on('close', (exitCode) => {
75
+ if (exitCode !== 0 && !allowNonZeroExit) {
76
+ const msg = (stderr || stdout || `Command failed: ${cmd}`).trim();
77
+ reject(new Error(msg));
78
+ } else {
79
+ resolve({ exitCode: exitCode ?? 0, stdout, stderr });
80
+ }
81
+ });
82
+
83
+ proc.on('error', (err) => {
84
+ reject(err);
85
+ });
86
+ });
87
+ },
88
+ });
89
+ return { name: 'bash', tool: bash };
90
+ }
@@ -0,0 +1,7 @@
1
+ - Execute a shell command using `bash -lc`
2
+ - Returns `stdout`, `stderr`, and `exitCode`
3
+ - `cwd` is relative to the project root and sandboxed within it
4
+
5
+ Usage tips:
6
+ - Chain multiple commands with `&&` to fail-fast
7
+ - For long outputs, consider redirecting to a file and reading it
@@ -0,0 +1,152 @@
1
+ import { tool, type Tool } from 'ai';
2
+ import { z } from 'zod';
3
+ import { readFile, writeFile, access } from 'node:fs/promises';
4
+ import { constants } from 'node:fs';
5
+ import DESCRIPTION from './edit.txt' with { type: 'text' };
6
+
7
+ const replaceOp = z.object({
8
+ type: z.literal('replace'),
9
+ find: z.string().describe('String or regex (when regex=true)'),
10
+ replace: z.string().default(''),
11
+ regex: z.boolean().optional().default(false),
12
+ flags: z.string().optional().default('g'),
13
+ count: z
14
+ .number()
15
+ .int()
16
+ .min(1)
17
+ .optional()
18
+ .describe('Limit number of replacements'),
19
+ });
20
+
21
+ const insertOp = z.object({
22
+ type: z.literal('insert'),
23
+ position: z.enum(['before', 'after', 'start', 'end']).default('after'),
24
+ pattern: z.string().optional().describe('Anchor pattern for before/after'),
25
+ content: z.string(),
26
+ once: z.boolean().optional().default(true),
27
+ });
28
+
29
+ const deleteRangeOp = z.object({
30
+ type: z.literal('delete_range'),
31
+ start: z.string().describe('Start marker (first occurrence)'),
32
+ end: z.string().describe('End marker (first occurrence after start)'),
33
+ includeBoundaries: z.boolean().optional().default(false),
34
+ });
35
+
36
+ const opSchema = z.discriminatedUnion('type', [
37
+ replaceOp,
38
+ insertOp,
39
+ deleteRangeOp,
40
+ ]);
41
+
42
+ export const editTool: Tool = tool({
43
+ description: DESCRIPTION,
44
+ inputSchema: z.object({
45
+ path: z.string().min(1),
46
+ ops: z.array(opSchema).min(1),
47
+ create: z.boolean().optional().default(false),
48
+ }),
49
+ async execute({
50
+ path,
51
+ ops,
52
+ create,
53
+ }: {
54
+ path: string;
55
+ ops: z.infer<typeof opSchema>[];
56
+ create?: boolean;
57
+ }) {
58
+ let exists = false;
59
+ try {
60
+ await access(path, constants.F_OK);
61
+ exists = true;
62
+ } catch {}
63
+
64
+ if (!exists) {
65
+ if (!create) throw new Error(`File not found: ${path}`);
66
+ await writeFile(path, '');
67
+ }
68
+ let text = await readFile(path, 'utf-8');
69
+ let applied = 0;
70
+
71
+ for (const op of ops) {
72
+ if (op.type === 'replace') {
73
+ const originalText = text;
74
+ if (op.regex) {
75
+ const re = new RegExp(op.find, op.flags || 'g');
76
+ if (op.count && op.count > 0) {
77
+ let n = 0;
78
+ text = text.replace(re, (m) => {
79
+ if (n < (op.count as number)) {
80
+ n += 1;
81
+ return op.replace;
82
+ }
83
+ return m;
84
+ });
85
+ } else text = text.replace(re, op.replace);
86
+ } else {
87
+ // Check if the text to find exists
88
+ if (!text.includes(op.find)) {
89
+ console.warn(
90
+ `Warning: Text not found for replace operation: "${op.find.substring(0, 50)}${op.find.length > 50 ? '...' : ''}"`,
91
+ );
92
+ }
93
+ if (op.count && op.count > 0) {
94
+ let remaining = op.count as number;
95
+ let idx = text.indexOf(op.find);
96
+ while (idx !== -1 && remaining > 0) {
97
+ text =
98
+ text.slice(0, idx) +
99
+ op.replace +
100
+ text.slice(idx + op.find.length);
101
+ remaining -= 1;
102
+ idx = text.indexOf(op.find, idx + op.replace.length);
103
+ }
104
+ } else {
105
+ text = text.split(op.find).join(op.replace);
106
+ }
107
+ }
108
+ // Only count as applied if text actually changed
109
+ if (text !== originalText) {
110
+ applied += 1;
111
+ }
112
+ } else if (op.type === 'insert') {
113
+ if (op.position === 'start') {
114
+ text = `${op.content}${text}`;
115
+ applied += 1;
116
+ continue;
117
+ }
118
+ if (op.position === 'end') {
119
+ text = `${text}${op.content}`;
120
+ applied += 1;
121
+ continue;
122
+ }
123
+ if (!op.pattern)
124
+ throw new Error('insert requires pattern for before/after');
125
+ const idx = text.indexOf(op.pattern);
126
+ if (idx === -1) continue;
127
+ if (op.position === 'before')
128
+ text = text.slice(0, idx) + op.content + text.slice(idx);
129
+ else
130
+ text =
131
+ text.slice(0, idx + op.pattern.length) +
132
+ op.content +
133
+ text.slice(idx + op.pattern.length);
134
+ applied += 1;
135
+ if (op.once) continue;
136
+ } else if (op.type === 'delete_range') {
137
+ const startIdx = text.indexOf(op.start);
138
+ if (startIdx === -1) continue;
139
+ const after = startIdx + op.start.length;
140
+ const endIdx = text.indexOf(op.end, after);
141
+ if (endIdx === -1) continue;
142
+ const from = op.includeBoundaries ? startIdx : after;
143
+ const to = op.includeBoundaries ? endIdx + op.end.length : endIdx;
144
+ text = text.slice(0, from) + text.slice(to);
145
+ applied += 1;
146
+ }
147
+ }
148
+
149
+ await writeFile(path, text);
150
+ return { path, opsApplied: applied, bytes: text.length };
151
+ },
152
+ });
@@ -0,0 +1,7 @@
1
+ - Edit a file using structured operations
2
+ - Supported ops: `replace`, `insert`, `delete_range`
3
+ - Paths are relative to the project root; can create files if flagged
4
+
5
+ Usage tips:
6
+ - Prefer minimal, targeted changes for clarity
7
+ - For sweeping refactors, consider Write or Patch to replace full content
@@ -0,0 +1,39 @@
1
+ /**
2
+ * File content cache to track modifications during a session/step
3
+ * This helps ensure tools always work with the latest file content
4
+ */
5
+
6
+ const fileContentCache = new Map<string, Map<string, string>>();
7
+
8
+ export function getFileCache(sessionId: string): Map<string, string> {
9
+ if (!fileContentCache.has(sessionId)) {
10
+ fileContentCache.set(sessionId, new Map());
11
+ }
12
+ return fileContentCache.get(sessionId) as Map<string, string>;
13
+ }
14
+
15
+ export function updateFileCache(
16
+ sessionId: string,
17
+ filePath: string,
18
+ content: string,
19
+ ): void {
20
+ const cache = getFileCache(sessionId);
21
+ cache.set(filePath, content);
22
+ }
23
+
24
+ export function getCachedContent(
25
+ sessionId: string,
26
+ filePath: string,
27
+ ): string | undefined {
28
+ const cache = getFileCache(sessionId);
29
+ return cache.get(filePath);
30
+ }
31
+
32
+ export function clearFileCache(sessionId: string): void {
33
+ fileContentCache.delete(sessionId);
34
+ }
35
+
36
+ export function clearFileCacheEntry(sessionId: string, filePath: string): void {
37
+ const cache = getFileCache(sessionId);
38
+ cache.delete(filePath);
39
+ }
@@ -0,0 +1,11 @@
1
+ import { z } from 'zod';
2
+ import { tool } from 'ai';
3
+ import DESCRIPTION from './finish.txt' with { type: 'text' };
4
+
5
+ export const finishTool = tool({
6
+ description: DESCRIPTION,
7
+ inputSchema: z.object({}),
8
+ async execute() {
9
+ return { done: true } as const;
10
+ },
11
+ });
@@ -0,0 +1,5 @@
1
+ - Signal that the task is complete
2
+ - Agent should stream the summary directly as assistant message not using the finish tool
3
+
4
+ Usage tips:
5
+ - Ensure all necessary outputs are already saved/emitted before finishing
@@ -0,0 +1,19 @@
1
+ import { tool, type Tool } from 'ai';
2
+ import { z } from 'zod';
3
+ import DESCRIPTION from './cd.txt' with { type: 'text' };
4
+
5
+ // description imported above
6
+
7
+ export function buildCdTool(): { name: string; tool: Tool } {
8
+ const cd = tool({
9
+ description: DESCRIPTION,
10
+ inputSchema: z.object({
11
+ path: z.string().describe('Relative directory path'),
12
+ }),
13
+ async execute({ path }: { path: string }) {
14
+ // Actual cwd update is handled in the adapter; this is a placeholder schema
15
+ return { cwd: path };
16
+ },
17
+ });
18
+ return { name: 'cd', tool: cd };
19
+ }
@@ -0,0 +1,5 @@
1
+ - Change the current working directory (relative to the project root)
2
+ - The runner enforces boundaries; this tool only requests a new cwd
3
+
4
+ Usage tip:
5
+ - Use PWD to verify the current directory
@@ -0,0 +1,20 @@
1
+ import type { Tool } from 'ai';
2
+ import { buildReadTool } from './read.ts';
3
+ import { buildWriteTool } from './write.ts';
4
+ import { buildLsTool } from './ls.ts';
5
+ import { buildTreeTool } from './tree.ts';
6
+ import { buildPwdTool } from './pwd.ts';
7
+ import { buildCdTool } from './cd.ts';
8
+
9
+ export function buildFsTools(
10
+ projectRoot: string,
11
+ ): Array<{ name: string; tool: Tool }> {
12
+ const out: Array<{ name: string; tool: Tool }> = [];
13
+ out.push(buildReadTool(projectRoot));
14
+ out.push(buildWriteTool(projectRoot));
15
+ out.push(buildLsTool(projectRoot));
16
+ out.push(buildTreeTool(projectRoot));
17
+ out.push(buildPwdTool());
18
+ out.push(buildCdTool());
19
+ return out;
20
+ }