wave-agent-sdk 0.0.1 → 0.0.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 (82) hide show
  1. package/dist/agent.d.ts +37 -3
  2. package/dist/agent.d.ts.map +1 -1
  3. package/dist/agent.js +82 -5
  4. package/dist/index.d.ts +0 -1
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +0 -1
  7. package/dist/managers/aiManager.d.ts +7 -1
  8. package/dist/managers/aiManager.d.ts.map +1 -1
  9. package/dist/managers/aiManager.js +11 -5
  10. package/dist/managers/messageManager.d.ts +8 -0
  11. package/dist/managers/messageManager.d.ts.map +1 -1
  12. package/dist/managers/messageManager.js +26 -2
  13. package/dist/managers/skillManager.d.ts +4 -5
  14. package/dist/managers/skillManager.d.ts.map +1 -1
  15. package/dist/managers/skillManager.js +6 -82
  16. package/dist/managers/subagentManager.d.ts +96 -0
  17. package/dist/managers/subagentManager.d.ts.map +1 -0
  18. package/dist/managers/subagentManager.js +261 -0
  19. package/dist/managers/toolManager.d.ts +33 -1
  20. package/dist/managers/toolManager.d.ts.map +1 -1
  21. package/dist/managers/toolManager.js +43 -5
  22. package/dist/services/aiService.d.ts +5 -0
  23. package/dist/services/aiService.d.ts.map +1 -1
  24. package/dist/services/aiService.js +58 -28
  25. package/dist/services/session.d.ts.map +1 -1
  26. package/dist/services/session.js +4 -0
  27. package/dist/tools/grepTool.d.ts.map +1 -1
  28. package/dist/tools/grepTool.js +8 -6
  29. package/dist/tools/readTool.d.ts.map +1 -1
  30. package/dist/tools/readTool.js +36 -6
  31. package/dist/tools/skillTool.d.ts +8 -0
  32. package/dist/tools/skillTool.d.ts.map +1 -0
  33. package/dist/tools/skillTool.js +72 -0
  34. package/dist/tools/taskTool.d.ts +8 -0
  35. package/dist/tools/taskTool.d.ts.map +1 -0
  36. package/dist/tools/taskTool.js +109 -0
  37. package/dist/tools/todoWriteTool.d.ts +6 -0
  38. package/dist/tools/todoWriteTool.d.ts.map +1 -0
  39. package/dist/tools/todoWriteTool.js +203 -0
  40. package/dist/types.d.ts +65 -1
  41. package/dist/types.d.ts.map +1 -1
  42. package/dist/types.js +16 -0
  43. package/dist/utils/configResolver.d.ts +38 -0
  44. package/dist/utils/configResolver.d.ts.map +1 -0
  45. package/dist/utils/configResolver.js +106 -0
  46. package/dist/utils/configValidator.d.ts +36 -0
  47. package/dist/utils/configValidator.d.ts.map +1 -0
  48. package/dist/utils/configValidator.js +78 -0
  49. package/dist/utils/constants.d.ts +10 -0
  50. package/dist/utils/constants.d.ts.map +1 -1
  51. package/dist/utils/constants.js +10 -0
  52. package/dist/utils/fileFormat.d.ts +17 -0
  53. package/dist/utils/fileFormat.d.ts.map +1 -0
  54. package/dist/utils/fileFormat.js +35 -0
  55. package/dist/utils/messageOperations.d.ts +18 -0
  56. package/dist/utils/messageOperations.d.ts.map +1 -1
  57. package/dist/utils/messageOperations.js +43 -0
  58. package/dist/utils/subagentParser.d.ts +19 -0
  59. package/dist/utils/subagentParser.d.ts.map +1 -0
  60. package/dist/utils/subagentParser.js +159 -0
  61. package/package.json +11 -15
  62. package/src/agent.ts +130 -9
  63. package/src/index.ts +0 -1
  64. package/src/managers/aiManager.ts +22 -10
  65. package/src/managers/messageManager.ts +55 -1
  66. package/src/managers/skillManager.ts +7 -96
  67. package/src/managers/subagentManager.ts +368 -0
  68. package/src/managers/toolManager.ts +50 -5
  69. package/src/services/aiService.ts +92 -36
  70. package/src/services/session.ts +5 -0
  71. package/src/tools/grepTool.ts +9 -6
  72. package/src/tools/readTool.ts +40 -6
  73. package/src/tools/skillTool.ts +82 -0
  74. package/src/tools/taskTool.ts +128 -0
  75. package/src/tools/todoWriteTool.ts +232 -0
  76. package/src/types.ts +85 -1
  77. package/src/utils/configResolver.ts +142 -0
  78. package/src/utils/configValidator.ts +133 -0
  79. package/src/utils/constants.ts +10 -0
  80. package/src/utils/fileFormat.ts +40 -0
  81. package/src/utils/messageOperations.ts +80 -0
  82. package/src/utils/subagentParser.ts +223 -0
@@ -0,0 +1,232 @@
1
+ import type { ToolPlugin, ToolResult } from "./types.js";
2
+
3
+ interface TodoItem {
4
+ content: string;
5
+ status: "pending" | "in_progress" | "completed";
6
+ id: string;
7
+ }
8
+
9
+ /**
10
+ * TodoWrite tool for creating and managing structured task lists
11
+ */
12
+ export const todoWriteTool: ToolPlugin = {
13
+ name: "TodoWrite",
14
+ config: {
15
+ type: "function",
16
+ function: {
17
+ name: "TodoWrite",
18
+ description: `Use this tool to create and manage a structured task list for your current coding session. This helps you track progress, organize complex tasks, and demonstrate thoroughness to the user.
19
+ It also helps the user understand the progress of the task and overall progress of their requests.
20
+
21
+ ## When to Use This Tool
22
+ Use this tool proactively in these scenarios:
23
+
24
+ 1. Complex multi-step tasks - When a task requires 3 or more distinct steps or actions
25
+ 2. Non-trivial and complex tasks - Tasks that require careful planning or multiple operations
26
+ 3. User explicitly requests todo list - When the user directly asks you to use the todo list
27
+ 4. User provides multiple tasks - When users provide a list of things to be done (numbered or comma-separated)
28
+ 5. After receiving new instructions - Immediately capture user requirements as todos
29
+ 6. When you start working on a task - Mark it as in_progress BEFORE beginning work. Ideally you should only have one todo as in_progress at a time
30
+ 7. After completing a task - Mark it as completed and add any new follow-up tasks discovered during implementation
31
+
32
+ ## When NOT to Use This Tool
33
+
34
+ Skip using this tool when:
35
+ 1. There is only a single, straightforward task
36
+ 2. The task is trivial and tracking it provides no organizational benefit
37
+ 3. The task can be completed in less than 3 trivial steps
38
+ 4. The task is purely conversational or informational
39
+
40
+ NOTE that you should not use this tool if there is only one trivial task to do. In this case you are better off just doing the task directly.
41
+
42
+ ## Task States and Management
43
+
44
+ 1. **Task States**: Use these states to track progress:
45
+ - pending: Task not yet started
46
+ - in_progress: Currently working on (limit to ONE task at a time)
47
+ - completed: Task finished successfully
48
+
49
+ 2. **Task Management**:
50
+ - Update task status in real-time as you work
51
+ - Mark tasks complete IMMEDIATELY after finishing (don't batch completions)
52
+ - Only have ONE task in_progress at any time
53
+ - Complete current tasks before starting new ones
54
+ - Remove tasks that are no longer relevant from the list entirely
55
+
56
+ 3. **Task Completion Requirements**:
57
+ - ONLY mark a task as completed when you have FULLY accomplished it
58
+ - If you encounter errors, blockers, or cannot finish, keep the task as in_progress
59
+ - When blocked, create a new task describing what needs to be resolved
60
+ - Never mark a task as completed if:
61
+ - Tests are failing
62
+ - Implementation is partial
63
+ - You encountered unresolved errors
64
+ - You couldn't find necessary files or dependencies
65
+
66
+ 4. **Task Breakdown**:
67
+ - Create specific, actionable items
68
+ - Break complex tasks into smaller, manageable steps
69
+ - Use clear, descriptive task names
70
+
71
+ When in doubt, use this tool. Being proactive with task management demonstrates attentiveness and ensures you complete all requirements successfully.`,
72
+ parameters: {
73
+ type: "object",
74
+ properties: {
75
+ todos: {
76
+ type: "array",
77
+ items: {
78
+ type: "object",
79
+ properties: {
80
+ content: {
81
+ type: "string",
82
+ minLength: 1,
83
+ },
84
+ status: {
85
+ type: "string",
86
+ enum: ["pending", "in_progress", "completed"],
87
+ },
88
+ id: {
89
+ type: "string",
90
+ },
91
+ },
92
+ required: ["content", "status", "id"],
93
+ additionalProperties: false,
94
+ },
95
+ description: "The updated todo list",
96
+ },
97
+ },
98
+ required: ["todos"],
99
+ additionalProperties: false,
100
+ },
101
+ },
102
+ },
103
+
104
+ execute: async (args: Record<string, unknown>): Promise<ToolResult> => {
105
+ try {
106
+ // Validate arguments
107
+ const { todos } = args as { todos?: TodoItem[] };
108
+
109
+ if (!todos || !Array.isArray(todos)) {
110
+ return {
111
+ success: false,
112
+ content: "",
113
+ error: "todos parameter must be an array",
114
+ shortResult: "Invalid todos format",
115
+ };
116
+ }
117
+
118
+ // Validate each task item
119
+ for (const [index, todo] of todos.entries()) {
120
+ if (!todo || typeof todo !== "object") {
121
+ return {
122
+ success: false,
123
+ content: "",
124
+ error: `Todo item at index ${index} must be an object`,
125
+ shortResult: "Invalid todo item",
126
+ };
127
+ }
128
+
129
+ if (
130
+ !todo.content ||
131
+ typeof todo.content !== "string" ||
132
+ todo.content.trim().length === 0
133
+ ) {
134
+ return {
135
+ success: false,
136
+ content: "",
137
+ error: `Todo item at index ${index} must have non-empty content`,
138
+ shortResult: "Invalid todo content",
139
+ };
140
+ }
141
+
142
+ if (!["pending", "in_progress", "completed"].includes(todo.status)) {
143
+ return {
144
+ success: false,
145
+ content: "",
146
+ error: `Todo item at index ${index} has invalid status: ${todo.status}`,
147
+ shortResult: "Invalid todo status",
148
+ };
149
+ }
150
+
151
+ if (
152
+ !todo.id ||
153
+ typeof todo.id !== "string" ||
154
+ todo.id.trim().length === 0
155
+ ) {
156
+ return {
157
+ success: false,
158
+ content: "",
159
+ error: `Todo item at index ${index} must have a non-empty id`,
160
+ shortResult: "Invalid todo id",
161
+ };
162
+ }
163
+ }
164
+
165
+ // Check for duplicate IDs
166
+ const ids = todos.map((todo) => todo.id);
167
+ const duplicateIds = ids.filter((id, index) => ids.indexOf(id) !== index);
168
+ if (duplicateIds.length > 0) {
169
+ return {
170
+ success: false,
171
+ content: "",
172
+ error: `Duplicate todo IDs found: ${duplicateIds.join(", ")}`,
173
+ shortResult: "Duplicate todo IDs",
174
+ };
175
+ }
176
+
177
+ // Check that only one task is in_progress
178
+ const inProgressTodos = todos.filter(
179
+ (todo) => todo.status === "in_progress",
180
+ );
181
+ if (inProgressTodos.length > 1) {
182
+ return {
183
+ success: false,
184
+ content: "",
185
+ error: `Only one todo can be in_progress at a time. Found ${inProgressTodos.length} in_progress todos`,
186
+ shortResult: "Multiple in_progress todos",
187
+ };
188
+ }
189
+
190
+ const completedCount = todos.filter(
191
+ (t) => t.status === "completed",
192
+ ).length;
193
+ const totalCount = todos.length;
194
+
195
+ let shortResult = `${completedCount}/${totalCount} done`;
196
+
197
+ if (totalCount > 0) {
198
+ const symbols = {
199
+ pending: "[ ]",
200
+ in_progress: "[>]",
201
+ completed: "[x]",
202
+ };
203
+ const inProgress = todos.filter((t) => t.status === "in_progress");
204
+ const pending = todos.filter((t) => t.status === "pending");
205
+
206
+ if (inProgress.length > 0) {
207
+ shortResult += `\n${symbols.in_progress} ${inProgress[0].content}`;
208
+ }
209
+
210
+ if (pending.length > 0) {
211
+ shortResult += `\n${symbols.pending} ${pending[0].content}`;
212
+ if (pending.length > 1) {
213
+ shortResult += ` +${pending.length - 1}`;
214
+ }
215
+ }
216
+ }
217
+
218
+ return {
219
+ success: true,
220
+ content: `Todo list updated: ${completedCount}/${totalCount} completed`,
221
+ shortResult: shortResult,
222
+ };
223
+ } catch (error) {
224
+ return {
225
+ success: false,
226
+ content: "",
227
+ error: error instanceof Error ? error.message : String(error),
228
+ shortResult: "Todo list update failed",
229
+ };
230
+ }
231
+ },
232
+ };
package/src/types.ts CHANGED
@@ -25,7 +25,8 @@ export type MessageBlock =
25
25
  | CommandOutputBlock
26
26
  | CompressBlock
27
27
  | MemoryBlock
28
- | CustomCommandBlock;
28
+ | CustomCommandBlock
29
+ | SubagentBlock;
29
30
 
30
31
  export interface TextBlock {
31
32
  type: "text";
@@ -98,6 +99,14 @@ export interface CustomCommandBlock {
98
99
  originalInput?: string; // Original user input, used for UI display (e.g., "/fix-issue 123 high")
99
100
  }
100
101
 
102
+ export interface SubagentBlock {
103
+ type: "subagent";
104
+ subagentId: string;
105
+ subagentName: string;
106
+ status: "active" | "completed" | "error" | "aborted";
107
+ messages: Message[];
108
+ }
109
+
101
110
  export interface AIRequest {
102
111
  content: string;
103
112
  files: unknown[];
@@ -258,3 +267,78 @@ export const SKILL_DEFAULTS = {
258
267
  SCAN_TIMEOUT: 5000,
259
268
  LOAD_TIMEOUT: 2000,
260
269
  } as const;
270
+
271
+ // Configuration types for Agent Constructor Configuration feature
272
+ export interface GatewayConfig {
273
+ apiKey: string;
274
+ baseURL: string;
275
+ }
276
+
277
+ export interface ModelConfig {
278
+ agentModel: string;
279
+ fastModel: string;
280
+ }
281
+
282
+ export interface ConfigurationResolver {
283
+ /**
284
+ * Resolves gateway configuration from constructor args and environment
285
+ * @param apiKey - API key from constructor (optional)
286
+ * @param baseURL - Base URL from constructor (optional)
287
+ * @returns Resolved gateway configuration
288
+ * @throws Error if required configuration is missing after fallbacks
289
+ */
290
+ resolveGatewayConfig(apiKey?: string, baseURL?: string): GatewayConfig;
291
+
292
+ /**
293
+ * Resolves model configuration with fallbacks
294
+ * @param agentModel - Agent model from constructor (optional)
295
+ * @param fastModel - Fast model from constructor (optional)
296
+ * @returns Resolved model configuration with defaults
297
+ */
298
+ resolveModelConfig(agentModel?: string, fastModel?: string): ModelConfig;
299
+
300
+ /**
301
+ * Resolves token limit with fallbacks
302
+ * @param constructorLimit - Token limit from constructor (optional)
303
+ * @returns Resolved token limit
304
+ */
305
+ resolveTokenLimit(constructorLimit?: number): number;
306
+ }
307
+
308
+ export interface ConfigurationValidator {
309
+ /**
310
+ * Validates gateway configuration
311
+ * @param config - Configuration to validate
312
+ * @throws Error with descriptive message if invalid
313
+ */
314
+ validateGatewayConfig(config: GatewayConfig): void;
315
+
316
+ /**
317
+ * Validates token limit value
318
+ * @param tokenLimit - Token limit to validate
319
+ * @throws Error if invalid
320
+ */
321
+ validateTokenLimit(tokenLimit: number): void;
322
+ }
323
+
324
+ export class ConfigurationError extends Error {
325
+ constructor(
326
+ message: string,
327
+ public readonly field: string,
328
+ public readonly provided?: unknown,
329
+ ) {
330
+ super(message);
331
+ this.name = "ConfigurationError";
332
+ }
333
+ }
334
+
335
+ // Standard error messages
336
+ export const CONFIG_ERRORS = {
337
+ MISSING_API_KEY:
338
+ "Gateway configuration requires apiKey. Provide via constructor or AIGW_TOKEN environment variable.",
339
+ MISSING_BASE_URL:
340
+ "Gateway configuration requires baseURL. Provide via constructor or AIGW_URL environment variable.",
341
+ INVALID_TOKEN_LIMIT: "Token limit must be a positive integer.",
342
+ EMPTY_API_KEY: "API key cannot be empty string.",
343
+ EMPTY_BASE_URL: "Base URL cannot be empty string.",
344
+ } as const;
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Configuration resolver utilities for Agent Constructor Configuration
3
+ * Resolves configuration from constructor arguments with environment fallbacks
4
+ */
5
+
6
+ import {
7
+ GatewayConfig,
8
+ ModelConfig,
9
+ ConfigurationError,
10
+ CONFIG_ERRORS,
11
+ } from "../types.js";
12
+
13
+ export class ConfigResolver {
14
+ /**
15
+ * Resolves gateway configuration from constructor args and environment
16
+ * @param apiKey - API key from constructor (optional)
17
+ * @param baseURL - Base URL from constructor (optional)
18
+ * @returns Resolved gateway configuration
19
+ * @throws ConfigurationError if required configuration is missing after fallbacks
20
+ */
21
+ static resolveGatewayConfig(
22
+ apiKey?: string,
23
+ baseURL?: string,
24
+ ): GatewayConfig {
25
+ // Resolve API key: constructor > environment variable
26
+ // Note: Explicitly provided empty strings should be treated as invalid, not fall back to env
27
+ let resolvedApiKey: string;
28
+ if (apiKey !== undefined) {
29
+ resolvedApiKey = apiKey;
30
+ } else {
31
+ resolvedApiKey = process.env.AIGW_TOKEN || "";
32
+ }
33
+
34
+ if (!resolvedApiKey && apiKey === undefined) {
35
+ throw new ConfigurationError(CONFIG_ERRORS.MISSING_API_KEY, "apiKey", {
36
+ constructor: apiKey,
37
+ environment: process.env.AIGW_TOKEN,
38
+ });
39
+ }
40
+
41
+ if (resolvedApiKey.trim() === "") {
42
+ throw new ConfigurationError(
43
+ CONFIG_ERRORS.EMPTY_API_KEY,
44
+ "apiKey",
45
+ resolvedApiKey,
46
+ );
47
+ }
48
+
49
+ // Resolve base URL: constructor > environment variable
50
+ // Note: Explicitly provided empty strings should be treated as invalid, not fall back to env
51
+ let resolvedBaseURL: string;
52
+ if (baseURL !== undefined) {
53
+ resolvedBaseURL = baseURL;
54
+ } else {
55
+ resolvedBaseURL = process.env.AIGW_URL || "";
56
+ }
57
+
58
+ if (!resolvedBaseURL && baseURL === undefined) {
59
+ throw new ConfigurationError(CONFIG_ERRORS.MISSING_BASE_URL, "baseURL", {
60
+ constructor: baseURL,
61
+ environment: process.env.AIGW_URL,
62
+ });
63
+ }
64
+
65
+ if (resolvedBaseURL.trim() === "") {
66
+ throw new ConfigurationError(
67
+ CONFIG_ERRORS.EMPTY_BASE_URL,
68
+ "baseURL",
69
+ resolvedBaseURL,
70
+ );
71
+ }
72
+
73
+ return {
74
+ apiKey: resolvedApiKey,
75
+ baseURL: resolvedBaseURL,
76
+ };
77
+ }
78
+
79
+ /**
80
+ * Resolves model configuration with fallbacks
81
+ * @param agentModel - Agent model from constructor (optional)
82
+ * @param fastModel - Fast model from constructor (optional)
83
+ * @returns Resolved model configuration with defaults
84
+ */
85
+ static resolveModelConfig(
86
+ agentModel?: string,
87
+ fastModel?: string,
88
+ ): ModelConfig {
89
+ // Default values as per data-model.md
90
+ const DEFAULT_AGENT_MODEL = "claude-sonnet-4-20250514";
91
+ const DEFAULT_FAST_MODEL = "gemini-2.5-flash";
92
+
93
+ // Resolve agent model: constructor > environment > default
94
+ const resolvedAgentModel =
95
+ agentModel || process.env.AIGW_MODEL || DEFAULT_AGENT_MODEL;
96
+
97
+ // Resolve fast model: constructor > environment > default
98
+ const resolvedFastModel =
99
+ fastModel || process.env.AIGW_FAST_MODEL || DEFAULT_FAST_MODEL;
100
+
101
+ return {
102
+ agentModel: resolvedAgentModel,
103
+ fastModel: resolvedFastModel,
104
+ };
105
+ }
106
+
107
+ /**
108
+ * Resolves token limit with fallbacks
109
+ * @param constructorLimit - Token limit from constructor (optional)
110
+ * @returns Resolved token limit
111
+ */
112
+ static resolveTokenLimit(constructorLimit?: number): number {
113
+ const DEFAULT_TOKEN_LIMIT = 64000;
114
+
115
+ // If constructor value provided, use it
116
+ if (constructorLimit !== undefined) {
117
+ return constructorLimit;
118
+ }
119
+
120
+ // Try environment variable
121
+ const envTokenLimit = process.env.TOKEN_LIMIT;
122
+ if (envTokenLimit) {
123
+ const parsed = parseInt(envTokenLimit, 10);
124
+ if (!isNaN(parsed)) {
125
+ return parsed;
126
+ }
127
+ }
128
+
129
+ // Use default
130
+ return DEFAULT_TOKEN_LIMIT;
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Static configuration resolver instance
136
+ * Implements ConfigurationResolver interface from types.ts
137
+ */
138
+ export const configResolver = {
139
+ resolveGatewayConfig: ConfigResolver.resolveGatewayConfig,
140
+ resolveModelConfig: ConfigResolver.resolveModelConfig,
141
+ resolveTokenLimit: ConfigResolver.resolveTokenLimit,
142
+ };
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Configuration validator utilities for Agent Constructor Configuration
3
+ * Validates configuration values for correctness and security
4
+ */
5
+
6
+ import { GatewayConfig, ConfigurationError, CONFIG_ERRORS } from "../types.js";
7
+
8
+ export class ConfigValidator {
9
+ /**
10
+ * Validates gateway configuration
11
+ * @param config - Configuration to validate
12
+ * @throws ConfigurationError with descriptive message if invalid
13
+ */
14
+ static validateGatewayConfig(config: GatewayConfig): void {
15
+ // Validate API key
16
+ if (!config.apiKey || typeof config.apiKey !== "string") {
17
+ throw new ConfigurationError(
18
+ CONFIG_ERRORS.EMPTY_API_KEY,
19
+ "apiKey",
20
+ config.apiKey,
21
+ );
22
+ }
23
+
24
+ if (config.apiKey.trim() === "") {
25
+ throw new ConfigurationError(
26
+ CONFIG_ERRORS.EMPTY_API_KEY,
27
+ "apiKey",
28
+ config.apiKey,
29
+ );
30
+ }
31
+
32
+ // Validate base URL
33
+ if (!config.baseURL || typeof config.baseURL !== "string") {
34
+ throw new ConfigurationError(
35
+ CONFIG_ERRORS.EMPTY_BASE_URL,
36
+ "baseURL",
37
+ config.baseURL,
38
+ );
39
+ }
40
+
41
+ if (config.baseURL.trim() === "") {
42
+ throw new ConfigurationError(
43
+ CONFIG_ERRORS.EMPTY_BASE_URL,
44
+ "baseURL",
45
+ config.baseURL,
46
+ );
47
+ }
48
+
49
+ // Basic URL format validation
50
+ try {
51
+ new URL(config.baseURL);
52
+ } catch {
53
+ throw new ConfigurationError(
54
+ `Base URL must be a valid URL format. Received: ${config.baseURL}`,
55
+ "baseURL",
56
+ config.baseURL,
57
+ );
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Validates token limit value
63
+ * @param tokenLimit - Token limit to validate
64
+ * @throws ConfigurationError if invalid
65
+ */
66
+ static validateTokenLimit(tokenLimit: number): void {
67
+ if (typeof tokenLimit !== "number") {
68
+ throw new ConfigurationError(
69
+ CONFIG_ERRORS.INVALID_TOKEN_LIMIT,
70
+ "tokenLimit",
71
+ tokenLimit,
72
+ );
73
+ }
74
+
75
+ if (!Number.isInteger(tokenLimit)) {
76
+ throw new ConfigurationError(
77
+ CONFIG_ERRORS.INVALID_TOKEN_LIMIT,
78
+ "tokenLimit",
79
+ tokenLimit,
80
+ );
81
+ }
82
+
83
+ if (tokenLimit <= 0) {
84
+ throw new ConfigurationError(
85
+ CONFIG_ERRORS.INVALID_TOKEN_LIMIT,
86
+ "tokenLimit",
87
+ tokenLimit,
88
+ );
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Validates model configuration (basic validation)
94
+ * @param agentModel - Agent model string
95
+ * @param fastModel - Fast model string
96
+ * @throws ConfigurationError if invalid
97
+ */
98
+ static validateModelConfig(agentModel: string, fastModel: string): void {
99
+ if (
100
+ !agentModel ||
101
+ typeof agentModel !== "string" ||
102
+ agentModel.trim() === ""
103
+ ) {
104
+ throw new ConfigurationError(
105
+ "Agent model must be a non-empty string.",
106
+ "agentModel",
107
+ agentModel,
108
+ );
109
+ }
110
+
111
+ if (
112
+ !fastModel ||
113
+ typeof fastModel !== "string" ||
114
+ fastModel.trim() === ""
115
+ ) {
116
+ throw new ConfigurationError(
117
+ "Fast model must be a non-empty string.",
118
+ "fastModel",
119
+ fastModel,
120
+ );
121
+ }
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Static configuration validator instance
127
+ * Implements ConfigurationValidator interface from types.ts
128
+ */
129
+ export const configValidator = {
130
+ validateGatewayConfig: ConfigValidator.validateGatewayConfig,
131
+ validateTokenLimit: ConfigValidator.validateTokenLimit,
132
+ validateModelConfig: ConfigValidator.validateModelConfig,
133
+ };
@@ -31,7 +31,17 @@ export const USER_MEMORY_FILE = path.join(DATA_DIRECTORY, "user-memory.md");
31
31
  */
32
32
  export const DEFAULT_TOKEN_LIMIT = 64000; // Default token limit
33
33
 
34
+ /**
35
+ * @deprecated These constants are now legacy. Use ModelConfig through Agent constructor instead.
36
+ * They are maintained for backward compatibility with existing code that might still reference them,
37
+ * but the actual AI services now use configuration injection.
38
+ */
34
39
  export const FAST_MODEL_ID = process.env.AIGW_FAST_MODEL || "gemini-2.5-flash";
35
40
 
41
+ /**
42
+ * @deprecated These constants are now legacy. Use ModelConfig through Agent constructor instead.
43
+ * They are maintained for backward compatibility with existing code that might still reference them,
44
+ * but the actual AI services now use configuration injection.
45
+ */
36
46
  export const AGENT_MODEL_ID =
37
47
  process.env.AIGW_MODEL || "claude-sonnet-4-20250514";
@@ -0,0 +1,40 @@
1
+ import { extname } from "path";
2
+
3
+ /**
4
+ * List of binary document file extensions that should not be read as text
5
+ */
6
+ export const BINARY_DOCUMENT_EXTENSIONS = [
7
+ ".pdf",
8
+ ".doc",
9
+ ".docx",
10
+ ".xls",
11
+ ".xlsx",
12
+ ".ppt",
13
+ ".pptx",
14
+ ".odt",
15
+ ".ods",
16
+ ".odp",
17
+ ".rtf",
18
+ ] as const;
19
+
20
+ /**
21
+ * Check if a file is a binary document format based on its extension
22
+ * @param filePath - The path to the file
23
+ * @returns true if the file is a binary document format, false otherwise
24
+ */
25
+ export function isBinaryDocument(filePath: string): boolean {
26
+ const fileExtension = extname(filePath).toLowerCase();
27
+ return (BINARY_DOCUMENT_EXTENSIONS as readonly string[]).includes(
28
+ fileExtension,
29
+ );
30
+ }
31
+
32
+ /**
33
+ * Get a human-readable error message for unsupported binary document formats
34
+ * @param filePath - The path to the file
35
+ * @returns Error message string
36
+ */
37
+ export function getBinaryDocumentError(filePath: string): string {
38
+ const fileExtension = extname(filePath).toLowerCase();
39
+ return `Reading binary document files with extension '${fileExtension}' is not supported. Supported formats include text files, code files, images, and Jupyter notebooks.`;
40
+ }