@illuma-ai/agents 1.4.0-alpha.2 → 1.4.0-alpha.3

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 (42) hide show
  1. package/dist/cjs/main.cjs +37 -27
  2. package/dist/cjs/main.cjs.map +1 -1
  3. package/dist/cjs/tools/artifacts/schema.cjs +63 -0
  4. package/dist/cjs/tools/artifacts/schema.cjs.map +1 -0
  5. package/dist/cjs/tools/artifacts/tool.cjs +213 -0
  6. package/dist/cjs/tools/artifacts/tool.cjs.map +1 -0
  7. package/dist/cjs/tools/fileSearch/formatter.cjs +2 -4
  8. package/dist/cjs/tools/fileSearch/formatter.cjs.map +1 -1
  9. package/dist/cjs/tools/fileSearch/ragClient.cjs +4 -6
  10. package/dist/cjs/tools/fileSearch/ragClient.cjs.map +1 -1
  11. package/dist/cjs/tools/fileSearch/schema.cjs.map +1 -1
  12. package/dist/cjs/tools/fileSearch/tool.cjs.map +1 -1
  13. package/dist/esm/main.mjs +2 -0
  14. package/dist/esm/main.mjs.map +1 -1
  15. package/dist/esm/tools/artifacts/schema.mjs +56 -0
  16. package/dist/esm/tools/artifacts/schema.mjs.map +1 -0
  17. package/dist/esm/tools/artifacts/tool.mjs +207 -0
  18. package/dist/esm/tools/artifacts/tool.mjs.map +1 -0
  19. package/dist/esm/tools/fileSearch/formatter.mjs +2 -4
  20. package/dist/esm/tools/fileSearch/formatter.mjs.map +1 -1
  21. package/dist/esm/tools/fileSearch/ragClient.mjs +4 -6
  22. package/dist/esm/tools/fileSearch/ragClient.mjs.map +1 -1
  23. package/dist/esm/tools/fileSearch/schema.mjs.map +1 -1
  24. package/dist/esm/tools/fileSearch/tool.mjs.map +1 -1
  25. package/dist/types/index.d.ts +1 -0
  26. package/dist/types/tools/artifacts/index.d.ts +3 -0
  27. package/dist/types/tools/artifacts/schema.d.ts +63 -0
  28. package/dist/types/tools/artifacts/tool.d.ts +16 -0
  29. package/dist/types/tools/artifacts/types.d.ts +127 -0
  30. package/package.json +1 -1
  31. package/src/index.ts +1 -0
  32. package/src/tools/artifacts/__tests__/tool.test.ts +243 -0
  33. package/src/tools/artifacts/index.ts +33 -0
  34. package/src/tools/artifacts/schema.ts +76 -0
  35. package/src/tools/artifacts/tool.ts +277 -0
  36. package/src/tools/artifacts/types.ts +149 -0
  37. package/src/tools/fileSearch/__tests__/tool.test.ts +20 -10
  38. package/src/tools/fileSearch/formatter.ts +5 -7
  39. package/src/tools/fileSearch/ragClient.ts +6 -10
  40. package/src/tools/fileSearch/schema.ts +2 -2
  41. package/src/tools/fileSearch/tool.ts +6 -6
  42. package/src/tools/fileSearch/types.ts +4 -2
@@ -0,0 +1,63 @@
1
+ import { z } from 'zod';
2
+ export declare const ARTIFACT_WRITE_ACTIONS: readonly ["write", "edit", "verify", "delete"];
3
+ export declare const CONTENT_READ_ACTIONS: readonly ["read", "search", "list", "info"];
4
+ export declare const artifactToolSchema: z.ZodObject<{
5
+ action: z.ZodEnum<["write", "edit", "verify", "delete"]>;
6
+ content_id: z.ZodOptional<z.ZodString>;
7
+ content: z.ZodOptional<z.ZodString>;
8
+ name: z.ZodOptional<z.ZodString>;
9
+ old_str: z.ZodOptional<z.ZodString>;
10
+ new_str: z.ZodOptional<z.ZodString>;
11
+ replace_all: z.ZodOptional<z.ZodBoolean>;
12
+ }, "strip", z.ZodTypeAny, {
13
+ action: "write" | "delete" | "edit" | "verify";
14
+ content?: string | undefined;
15
+ name?: string | undefined;
16
+ old_str?: string | undefined;
17
+ new_str?: string | undefined;
18
+ content_id?: string | undefined;
19
+ replace_all?: boolean | undefined;
20
+ }, {
21
+ action: "write" | "delete" | "edit" | "verify";
22
+ content?: string | undefined;
23
+ name?: string | undefined;
24
+ old_str?: string | undefined;
25
+ new_str?: string | undefined;
26
+ content_id?: string | undefined;
27
+ replace_all?: boolean | undefined;
28
+ }>;
29
+ export declare const contentReaderSchema: z.ZodObject<{
30
+ action: z.ZodEnum<["read", "search", "list", "info"]>;
31
+ content_id: z.ZodOptional<z.ZodString>;
32
+ start_line: z.ZodOptional<z.ZodNumber>;
33
+ end_line: z.ZodOptional<z.ZodNumber>;
34
+ pattern: z.ZodOptional<z.ZodString>;
35
+ flags: z.ZodOptional<z.ZodString>;
36
+ context: z.ZodOptional<z.ZodNumber>;
37
+ offset: z.ZodOptional<z.ZodNumber>;
38
+ limit: z.ZodOptional<z.ZodNumber>;
39
+ }, "strip", z.ZodTypeAny, {
40
+ action: "read" | "search" | "info" | "list";
41
+ pattern?: string | undefined;
42
+ context?: number | undefined;
43
+ flags?: string | undefined;
44
+ content_id?: string | undefined;
45
+ start_line?: number | undefined;
46
+ end_line?: number | undefined;
47
+ offset?: number | undefined;
48
+ limit?: number | undefined;
49
+ }, {
50
+ action: "read" | "search" | "info" | "list";
51
+ pattern?: string | undefined;
52
+ context?: number | undefined;
53
+ flags?: string | undefined;
54
+ content_id?: string | undefined;
55
+ start_line?: number | undefined;
56
+ end_line?: number | undefined;
57
+ offset?: number | undefined;
58
+ limit?: number | undefined;
59
+ }>;
60
+ export declare const ARTIFACT_TOOL_NAME = "artifact_tool";
61
+ export declare const CONTENT_READER_NAME = "content_reader";
62
+ export type ArtifactToolInput = z.infer<typeof artifactToolSchema>;
63
+ export type ContentReaderInput = z.infer<typeof contentReaderSchema>;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * artifact_tool + content_reader library factories.
3
+ *
4
+ * The library owns the LangChain wiring (schema, description, response
5
+ * shape) and the action dispatch; the runtime supplies a handler bundle
6
+ * matching the `ArtifactWriteHandlers` / `ContentReadHandlers` interface.
7
+ *
8
+ * This keeps 800+ LOC of host-specific handler logic (S3 adapters, file
9
+ * model CRUD, syntax checkers, line utils) out of the library while
10
+ * still centralizing the tool surface every runtime shares.
11
+ */
12
+ import { DynamicStructuredTool } from '@langchain/core/tools';
13
+ import type { ArtifactToolConfig, ContentReaderToolConfig } from './types';
14
+ export declare function createArtifactTool(config: ArtifactToolConfig): DynamicStructuredTool;
15
+ export declare function createContentReaderTool(config: ContentReaderToolConfig): DynamicStructuredTool;
16
+ export { ARTIFACT_TOOL_NAME, CONTENT_READER_NAME, ARTIFACT_WRITE_ACTIONS, CONTENT_READ_ACTIONS, } from './schema';
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Artifact-tool types. The library owns the LangChain wiring, schema, and
3
+ * action dispatch. Runtime supplies an `ArtifactHandlers` bundle — one
4
+ * function per action — so ranger reuses its existing handlers, CLI
5
+ * supplies disk-backed ones, and A2A server supplies buffer-backed ones.
6
+ *
7
+ * Each handler receives:
8
+ * - `args`: the parsed input (typed per action)
9
+ * - `scope`: runtime-resolved scope identity (conversationId + userId
10
+ * for ranger, agent-run-id for CLI, a2a-task-id for A2A)
11
+ *
12
+ * Each handler returns a `[llmText, toolArtifact]` tuple matching
13
+ * LangChain's `content_and_artifact` response format.
14
+ */
15
+ export interface ArtifactToolScope {
16
+ /**
17
+ * Primary scope — whatever the runtime uses to silo artifacts per
18
+ * session/run/conversation. Ranger uses `conversationId`; CLI uses
19
+ * `agent-run-id` or `agent-id`; A2A uses `a2a-task-id`.
20
+ */
21
+ conversationId: string;
22
+ /** Optional — present when the runtime has an authenticated user. */
23
+ userId?: string;
24
+ /** Free-form extension — runtimes can add their own fields. */
25
+ [key: string]: unknown;
26
+ }
27
+ export type ArtifactToolResult = [llmText: string, artifact?: unknown];
28
+ export interface WriteArgs {
29
+ action: 'write';
30
+ content_id?: string;
31
+ content: string;
32
+ name?: string;
33
+ }
34
+ export interface EditArgs {
35
+ action: 'edit';
36
+ content_id: string;
37
+ old_str: string;
38
+ new_str: string;
39
+ replace_all?: boolean;
40
+ }
41
+ export interface VerifyArgs {
42
+ action: 'verify';
43
+ content_id: string;
44
+ }
45
+ export interface DeleteArgs {
46
+ action: 'delete';
47
+ content_id: string;
48
+ }
49
+ export interface ReadArgs {
50
+ action: 'read';
51
+ content_id: string;
52
+ start_line?: number;
53
+ end_line?: number;
54
+ offset?: number;
55
+ limit?: number;
56
+ }
57
+ export interface SearchArgs {
58
+ action: 'search';
59
+ content_id: string;
60
+ pattern: string;
61
+ flags?: string;
62
+ context?: number;
63
+ offset?: number;
64
+ limit?: number;
65
+ }
66
+ export interface ListArgs {
67
+ action: 'list';
68
+ }
69
+ export interface InfoArgs {
70
+ action: 'info';
71
+ content_id: string;
72
+ }
73
+ export type ArtifactWriteAction = WriteArgs | EditArgs | VerifyArgs | DeleteArgs;
74
+ export type ArtifactReadAction = ReadArgs | SearchArgs | ListArgs | InfoArgs;
75
+ /** Writer-surface handlers — invoked by `artifact_tool`. */
76
+ export interface ArtifactWriteHandlers {
77
+ write(args: WriteArgs, scope: ArtifactToolScope): Promise<ArtifactToolResult>;
78
+ edit(args: EditArgs, scope: ArtifactToolScope): Promise<ArtifactToolResult>;
79
+ verify(args: VerifyArgs, scope: ArtifactToolScope): Promise<ArtifactToolResult>;
80
+ delete(args: DeleteArgs, scope: ArtifactToolScope): Promise<ArtifactToolResult>;
81
+ }
82
+ /** Reader-surface handlers — invoked by `content_reader`. */
83
+ export interface ContentReadHandlers {
84
+ read(args: ReadArgs, scope: ArtifactToolScope): Promise<ArtifactToolResult>;
85
+ search(args: SearchArgs, scope: ArtifactToolScope): Promise<ArtifactToolResult>;
86
+ list(args: ListArgs, scope: ArtifactToolScope): Promise<ArtifactToolResult>;
87
+ info(args: InfoArgs, scope: ArtifactToolScope): Promise<ArtifactToolResult>;
88
+ }
89
+ /**
90
+ * Optional content_id self-healing: runtimes that want to let the LLM
91
+ * pass nicknames (e.g., "Dashboard") instead of canonical IDs implement
92
+ * this. Library falls through when unset.
93
+ */
94
+ export interface ContentIdResolver {
95
+ resolve(id: string, scope: ArtifactToolScope): Promise<{
96
+ resolvedId: string;
97
+ resolvedName?: string;
98
+ } | null>;
99
+ }
100
+ export interface ArtifactToolLogger {
101
+ debug: (msg: string, ...args: unknown[]) => void;
102
+ info: (msg: string, ...args: unknown[]) => void;
103
+ warn: (msg: string, ...args: unknown[]) => void;
104
+ error: (msg: string, ...args: unknown[]) => void;
105
+ }
106
+ export interface ArtifactToolBaseConfig {
107
+ /**
108
+ * Resolves the runtime scope from the LangChain `config` object on each
109
+ * invocation. Ranger pulls `conversationId` + `userId` from request
110
+ * metadata; CLI pulls `agent-run-id` from its runner context.
111
+ */
112
+ getScope: (config: unknown) => ArtifactToolScope | null;
113
+ resolver?: ContentIdResolver;
114
+ logger?: ArtifactToolLogger;
115
+ /**
116
+ * Description override. Host can inject brand-specific guidance
117
+ * (e.g., ranger's HTML branding mandate, TSX style rules). Defaults
118
+ * to a generic description appropriate for any runtime.
119
+ */
120
+ descriptionOverride?: string;
121
+ }
122
+ export interface ArtifactToolConfig extends ArtifactToolBaseConfig {
123
+ handlers: ArtifactWriteHandlers;
124
+ }
125
+ export interface ContentReaderToolConfig extends ArtifactToolBaseConfig {
126
+ handlers: ContentReadHandlers;
127
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@illuma-ai/agents",
3
- "version": "1.4.0-alpha.2",
3
+ "version": "1.4.0-alpha.3",
4
4
  "main": "./dist/cjs/main.cjs",
5
5
  "module": "./dist/esm/main.mjs",
6
6
  "types": "./dist/types/index.d.ts",
package/src/index.ts CHANGED
@@ -27,6 +27,7 @@ export * from './tools/handlers';
27
27
  export * from './tools/search';
28
28
  export * from './tools/memory';
29
29
  export * from './tools/fileSearch';
30
+ export * from './tools/artifacts';
30
31
  export * from './tools/proxyTool';
31
32
 
32
33
  /* Capability Providers */
@@ -0,0 +1,243 @@
1
+ /**
2
+ * Unit tests for the artifact_tool + content_reader library factories.
3
+ *
4
+ * The factories are thin dispatchers — their job is:
5
+ * 1. Resolve scope from runnableConfig (error if missing)
6
+ * 2. Optionally self-heal content_id via the resolver
7
+ * 3. Validate per-action required args
8
+ * 4. Route to the runtime's handler, propagate errors
9
+ *
10
+ * We verify each of those paths here with mock handlers.
11
+ */
12
+
13
+ import {
14
+ createArtifactTool,
15
+ createContentReaderTool,
16
+ } from '../tool';
17
+ import type {
18
+ ArtifactWriteHandlers,
19
+ ContentReadHandlers,
20
+ ArtifactToolScope,
21
+ ArtifactToolResult,
22
+ ContentIdResolver,
23
+ } from '../types';
24
+
25
+ function makeScope(): ArtifactToolScope {
26
+ return { conversationId: 'conv-1', userId: 'user-1' };
27
+ }
28
+
29
+ function makeWriteHandlers(): ArtifactWriteHandlers & {
30
+ _calls: Array<{ action: string; args: unknown; scope: ArtifactToolScope }>;
31
+ } {
32
+ const calls: Array<{
33
+ action: string;
34
+ args: unknown;
35
+ scope: ArtifactToolScope;
36
+ }> = [];
37
+ return {
38
+ _calls: calls,
39
+ write: jest.fn(async (args, scope): Promise<ArtifactToolResult> => {
40
+ calls.push({ action: 'write', args, scope });
41
+ return ['wrote it', { id: 'new-id' }];
42
+ }),
43
+ edit: jest.fn(async (args, scope): Promise<ArtifactToolResult> => {
44
+ calls.push({ action: 'edit', args, scope });
45
+ return ['edited', {}];
46
+ }),
47
+ verify: jest.fn(async (args, scope): Promise<ArtifactToolResult> => {
48
+ calls.push({ action: 'verify', args, scope });
49
+ return ['verified', {}];
50
+ }),
51
+ delete: jest.fn(async (args, scope): Promise<ArtifactToolResult> => {
52
+ calls.push({ action: 'delete', args, scope });
53
+ return ['deleted', {}];
54
+ }),
55
+ };
56
+ }
57
+
58
+ function makeReadHandlers(): ContentReadHandlers & {
59
+ _calls: Array<{ action: string; args: unknown; scope: ArtifactToolScope }>;
60
+ } {
61
+ const calls: Array<{
62
+ action: string;
63
+ args: unknown;
64
+ scope: ArtifactToolScope;
65
+ }> = [];
66
+ return {
67
+ _calls: calls,
68
+ read: jest.fn(async (args, scope): Promise<ArtifactToolResult> => {
69
+ calls.push({ action: 'read', args, scope });
70
+ return ['read-output', {}];
71
+ }),
72
+ search: jest.fn(async (args, scope): Promise<ArtifactToolResult> => {
73
+ calls.push({ action: 'search', args, scope });
74
+ return ['search-output', {}];
75
+ }),
76
+ list: jest.fn(async (args, scope): Promise<ArtifactToolResult> => {
77
+ calls.push({ action: 'list', args, scope });
78
+ return ['list-output', {}];
79
+ }),
80
+ info: jest.fn(async (args, scope): Promise<ArtifactToolResult> => {
81
+ calls.push({ action: 'info', args, scope });
82
+ return ['info-output', {}];
83
+ }),
84
+ };
85
+ }
86
+
87
+ describe('createArtifactTool', () => {
88
+ it('returns a scope error when getScope returns null', async () => {
89
+ const handlers = makeWriteHandlers();
90
+ const t = createArtifactTool({
91
+ handlers,
92
+ getScope: () => null,
93
+ });
94
+ const result = await t.invoke({ action: 'write', content: 'x' });
95
+ const text = Array.isArray(result) ? String(result[0]) : String(result);
96
+ expect(text).toMatch(/no conversation context/i);
97
+ });
98
+
99
+ it('dispatches write to handlers.write with resolved args + scope', async () => {
100
+ const handlers = makeWriteHandlers();
101
+ const t = createArtifactTool({
102
+ handlers,
103
+ getScope: makeScope,
104
+ });
105
+ await t.invoke({ action: 'write', content: 'hello', name: 'doc.md' });
106
+ expect(handlers.write).toHaveBeenCalledTimes(1);
107
+ expect(handlers._calls[0].args).toEqual(
108
+ expect.objectContaining({ action: 'write', content: 'hello', name: 'doc.md' }),
109
+ );
110
+ expect(handlers._calls[0].scope).toEqual(
111
+ expect.objectContaining({ conversationId: 'conv-1' }),
112
+ );
113
+ });
114
+
115
+ it('rejects write without content', async () => {
116
+ const handlers = makeWriteHandlers();
117
+ const t = createArtifactTool({
118
+ handlers,
119
+ getScope: makeScope,
120
+ });
121
+ const result = await t.invoke({ action: 'write' });
122
+ const text = Array.isArray(result) ? String(result[0]) : String(result);
123
+ expect(text).toMatch(/write requires content/i);
124
+ expect(handlers.write).not.toHaveBeenCalled();
125
+ });
126
+
127
+ it('rejects edit/verify/delete without content_id', async () => {
128
+ const handlers = makeWriteHandlers();
129
+ const t = createArtifactTool({
130
+ handlers,
131
+ getScope: makeScope,
132
+ });
133
+ const e = await t.invoke({ action: 'edit', old_str: 'a', new_str: 'b' });
134
+ expect(String(Array.isArray(e) ? e[0] : e)).toMatch(/edit requires content_id/i);
135
+ const v = await t.invoke({ action: 'verify' });
136
+ expect(String(Array.isArray(v) ? v[0] : v)).toMatch(/verify requires content_id/i);
137
+ const d = await t.invoke({ action: 'delete' });
138
+ expect(String(Array.isArray(d) ? d[0] : d)).toMatch(/delete requires content_id/i);
139
+ });
140
+
141
+ it('applies the resolver before dispatching to handler', async () => {
142
+ const handlers = makeWriteHandlers();
143
+ const resolver: ContentIdResolver = {
144
+ resolve: jest.fn(async (id) => ({ resolvedId: `canonical:${id}`, resolvedName: 'X' })),
145
+ };
146
+ const t = createArtifactTool({
147
+ handlers,
148
+ getScope: makeScope,
149
+ resolver,
150
+ });
151
+ await t.invoke({
152
+ action: 'edit',
153
+ content_id: 'nickname',
154
+ old_str: 'a',
155
+ new_str: 'b',
156
+ });
157
+ expect(handlers._calls[0].args).toEqual(
158
+ expect.objectContaining({ content_id: 'canonical:nickname' }),
159
+ );
160
+ });
161
+
162
+ it('catches handler exceptions and returns them as tool errors', async () => {
163
+ const handlers = makeWriteHandlers();
164
+ handlers.write = jest.fn(async () => {
165
+ throw new Error('s3 unavailable');
166
+ });
167
+ const t = createArtifactTool({
168
+ handlers,
169
+ getScope: makeScope,
170
+ });
171
+ const result = await t.invoke({ action: 'write', content: 'x' });
172
+ const text = Array.isArray(result) ? String(result[0]) : String(result);
173
+ expect(text).toMatch(/s3 unavailable/);
174
+ });
175
+
176
+ it('honors descriptionOverride for host-specific guidance', async () => {
177
+ const handlers = makeWriteHandlers();
178
+ const t = createArtifactTool({
179
+ handlers,
180
+ getScope: makeScope,
181
+ descriptionOverride: 'HOST-BRAND-GUIDE',
182
+ });
183
+ expect(t.description).toBe('HOST-BRAND-GUIDE');
184
+ });
185
+ });
186
+
187
+ describe('createContentReaderTool', () => {
188
+ it('dispatches list with no content_id required', async () => {
189
+ const handlers = makeReadHandlers();
190
+ const t = createContentReaderTool({
191
+ handlers,
192
+ getScope: makeScope,
193
+ });
194
+ await t.invoke({ action: 'list' });
195
+ expect(handlers.list).toHaveBeenCalledTimes(1);
196
+ });
197
+
198
+ it('rejects read/info/search without content_id', async () => {
199
+ const handlers = makeReadHandlers();
200
+ const t = createContentReaderTool({
201
+ handlers,
202
+ getScope: makeScope,
203
+ });
204
+ const r = await t.invoke({ action: 'read' });
205
+ expect(String(Array.isArray(r) ? r[0] : r)).toMatch(/read requires content_id/i);
206
+ const i = await t.invoke({ action: 'info' });
207
+ expect(String(Array.isArray(i) ? i[0] : i)).toMatch(/info requires content_id/i);
208
+ const s = await t.invoke({ action: 'search', pattern: 'p' });
209
+ expect(String(Array.isArray(s) ? s[0] : s)).toMatch(/search requires content_id/i);
210
+ });
211
+
212
+ it('rejects search without pattern even when content_id is given', async () => {
213
+ const handlers = makeReadHandlers();
214
+ const t = createContentReaderTool({
215
+ handlers,
216
+ getScope: makeScope,
217
+ });
218
+ const result = await t.invoke({ action: 'search', content_id: 'x' });
219
+ const text = Array.isArray(result) ? String(result[0]) : String(result);
220
+ expect(text).toMatch(/search requires pattern/i);
221
+ });
222
+
223
+ it('passes read pagination args through to the handler', async () => {
224
+ const handlers = makeReadHandlers();
225
+ const t = createContentReaderTool({
226
+ handlers,
227
+ getScope: makeScope,
228
+ });
229
+ await t.invoke({
230
+ action: 'read',
231
+ content_id: 'c-1',
232
+ start_line: 10,
233
+ end_line: 50,
234
+ });
235
+ expect(handlers._calls[0].args).toEqual(
236
+ expect.objectContaining({
237
+ content_id: 'c-1',
238
+ start_line: 10,
239
+ end_line: 50,
240
+ }),
241
+ );
242
+ });
243
+ });
@@ -0,0 +1,33 @@
1
+ export {
2
+ createArtifactTool,
3
+ createContentReaderTool,
4
+ ARTIFACT_TOOL_NAME,
5
+ CONTENT_READER_NAME,
6
+ ARTIFACT_WRITE_ACTIONS,
7
+ CONTENT_READ_ACTIONS,
8
+ } from './tool';
9
+ export {
10
+ artifactToolSchema,
11
+ contentReaderSchema,
12
+ type ArtifactToolInput,
13
+ type ContentReaderInput,
14
+ } from './schema';
15
+ export type {
16
+ ArtifactToolScope,
17
+ ArtifactToolResult,
18
+ ArtifactToolLogger,
19
+ ArtifactToolBaseConfig,
20
+ ArtifactToolConfig,
21
+ ContentReaderToolConfig,
22
+ ArtifactWriteHandlers,
23
+ ContentReadHandlers,
24
+ ContentIdResolver,
25
+ WriteArgs,
26
+ EditArgs,
27
+ VerifyArgs,
28
+ DeleteArgs,
29
+ ReadArgs,
30
+ SearchArgs,
31
+ ListArgs,
32
+ InfoArgs,
33
+ } from './types';
@@ -0,0 +1,76 @@
1
+ import { z } from 'zod';
2
+
3
+ export const ARTIFACT_WRITE_ACTIONS = ['write', 'edit', 'verify', 'delete'] as const;
4
+ export const CONTENT_READ_ACTIONS = ['read', 'search', 'list', 'info'] as const;
5
+
6
+ export const artifactToolSchema = z.object({
7
+ action: z
8
+ .enum(ARTIFACT_WRITE_ACTIONS)
9
+ .describe(
10
+ 'Authoring action: write (create/overwrite), edit (str_replace), verify (syntax check), delete (remove).',
11
+ ),
12
+ content_id: z
13
+ .string()
14
+ .optional()
15
+ .describe(
16
+ 'ID of the artifact entry. Required for edit/verify/delete; optional for write (supply to overwrite an existing entry).',
17
+ ),
18
+
19
+ // write
20
+ content: z
21
+ .string()
22
+ .optional()
23
+ .describe('Full file content (required for write action).'),
24
+ name: z
25
+ .string()
26
+ .optional()
27
+ .describe(
28
+ 'Filename for the new entry (required when creating). MUST include the correct file extension — the extension drives the preview template.',
29
+ ),
30
+
31
+ // edit (str_replace)
32
+ old_str: z
33
+ .string()
34
+ .optional()
35
+ .describe('Exact string to find and replace (required for edit).'),
36
+ new_str: z.string().optional().describe('Replacement string (required for edit).'),
37
+ replace_all: z
38
+ .boolean()
39
+ .optional()
40
+ .describe(
41
+ 'edit: when true, replaces every occurrence of old_str. When false (default) and old_str matches more than one location, the edit is refused.',
42
+ ),
43
+ });
44
+
45
+ export const contentReaderSchema = z.object({
46
+ action: z
47
+ .enum(CONTENT_READ_ACTIONS)
48
+ .describe(
49
+ 'Read-only action: read (lines), search (regex), list (all entries), info (metadata).',
50
+ ),
51
+ content_id: z
52
+ .string()
53
+ .optional()
54
+ .describe(
55
+ 'ID of the content entry. Required for read/search/info. Omit for list.',
56
+ ),
57
+
58
+ // read pagination
59
+ start_line: z.number().optional().describe('1-based start line for reading.'),
60
+ end_line: z.number().optional().describe('1-based end line (inclusive).'),
61
+
62
+ // search
63
+ pattern: z.string().optional().describe('Regex pattern (required for search).'),
64
+ flags: z.string().optional().describe('Regex flags (e.g., "i" for case-insensitive).'),
65
+ context: z.number().optional().describe('Lines of context around each match.'),
66
+
67
+ // shared pagination
68
+ offset: z.number().optional().describe('Offset for read or search pagination.'),
69
+ limit: z.number().optional().describe('Max lines (read) or matches (search).'),
70
+ });
71
+
72
+ export const ARTIFACT_TOOL_NAME = 'artifact_tool';
73
+ export const CONTENT_READER_NAME = 'content_reader';
74
+
75
+ export type ArtifactToolInput = z.infer<typeof artifactToolSchema>;
76
+ export type ContentReaderInput = z.infer<typeof contentReaderSchema>;