gx402 1.3.5

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/README.md ADDED
@@ -0,0 +1,15 @@
1
+ # uaiv2
2
+
3
+ To install dependencies:
4
+
5
+ ```bash
6
+ bun install
7
+ ```
8
+
9
+ To run:
10
+
11
+ ```bash
12
+ bun run index.ts
13
+ ```
14
+
15
+ This project was created using `bun init` in bun v1.2.13. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.
package/index.ts ADDED
@@ -0,0 +1 @@
1
+ console.log("Hello via Bun!");
package/package.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "gx402",
3
+ "module": "uai.ts",
4
+ "main": "./uai.ts",
5
+ "version": "1.3.5",
6
+ "type": "module",
7
+ "private": false,
8
+ "devDependencies": {
9
+ "@types/bun": "latest"
10
+ },
11
+ "peerDependencies": {
12
+ "typescript": "^5"
13
+ },
14
+ "dependencies": {
15
+ "@ments/utils": "1.0.2",
16
+ "zod": "^3.25.67"
17
+ }
18
+ }
package/patch ADDED
@@ -0,0 +1,290 @@
1
+ diff --git a/uai.ts b/uai.ts
2
+ index 1234567..abcdefg 100644
3
+ --- a/uai.ts
4
+ +++ b/uai.ts
5
+ @@ -33,10 +33,17 @@ export interface AgentConfig<I extends z.ZodObject<any>, O extends z.ZodObject<
6
+
7
+ // ### Progress Callback Types
8
+ export interface ProgressUpdate {
9
+ - stage: "server_selection" | "tool_discovery" | "tool_invocation" | "response_generation";
10
+ + stage: "server_selection" | "tool_discovery" | "tool_invocation" | "response_generation" | "streaming";
11
+ message: string;
12
+ data?: any;
13
+ }
14
+
15
+ +export interface StreamingUpdate {
16
+ + stage: "streaming";
17
+ + field: string;
18
+ + value: string;
19
+ +}
20
+ +
21
+ export type ProgressCallback = (update: ProgressUpdate) => void;
22
+ +export type StreamingCallback = (update: StreamingUpdate) => void;
23
+
24
+ // ### XML Utilities
25
+ @@ -102,10 +109,11 @@ function xmlToObj(xmlContent: string): any {
26
+ /** Calls an LLM API with measurement for logging. */
27
+ async function callLLM(
28
+ llm: LLMType,
29
+ messages: Array<{ role: string; content: string }>,
30
+ options: { temperature?: number; maxTokens?: number } = {},
31
+ - measureFn?: typeof measure
32
+ + measureFn?: typeof measure,
33
+ + streamingCallback?: StreamingCallback
34
+ ): Promise<string> {
35
+ const executeCall = async (measure: typeof measure) => {
36
+ const { temperature = 0.7, maxTokens = 4000 } = options;
37
+ @@ -119,17 +127,20 @@ async function callLLM(
38
+ headers["x-api-key"] = process.env.ANTHROPIC_API_KEY!;
39
+ headers["anthropic-version"] = "2023-06-01";
40
+ url = "https://api.anthropic.com/v1/messages";
41
+ - body = { model: llm, max_tokens: maxTokens, messages };
42
+ + body = { model: llm, max_tokens: maxTokens, messages, stream: !!streamingCallback };
43
+ } else if (llm.includes("deepseek")) {
44
+ headers["Authorization"] = `Bearer ${process.env.DEEPSEEK_API_KEY}`;
45
+ url = "https://api.deepseek.com/v1/chat/completions";
46
+ - body = { model: llm, temperature, messages, max_tokens: maxTokens };
47
+ + body = { model: llm, temperature, messages, max_tokens: maxTokens, stream: !!streamingCallback };
48
+ } else {
49
+ headers["Authorization"] = `Bearer ${process.env.OPENAI_API_KEY}`;
50
+ url = "https://api.openai.com/v1/chat/completions";
51
+ - body = { model: llm, temperature, messages, max_tokens: maxTokens };
52
+ + body = { model: llm, temperature, messages, max_tokens: maxTokens, stream: !!streamingCallback };
53
+ }
54
+ +
55
+ const requestBodyStr = JSON.stringify(body);
56
+ +
57
+ + if (!streamingCallback) {
58
+ const response = await measure(
59
+ async () => {
60
+ const res = await fetch(url, {
61
+ @@ -144,7 +155,98 @@ async function callLLM(
62
+ `HTTP ${llm} API call - Body: ${requestBodyStr.substring(0, 200)}...`
63
+ );
64
+ const data = await response.json();
65
+ return llm.includes("claude") ? data.content[0].text : data.choices[0].message.content;
66
+ + } else {
67
+ + // Streaming response handling
68
+ + const response = await measure(
69
+ + async () => {
70
+ + const res = await fetch(url, {
71
+ + method: "POST",
72
+ + headers,
73
+ + body: requestBodyStr,
74
+ + });
75
+ + if (!res.ok) {
76
+ + const errorText = await res.text();
77
+ + throw new Error(`LLM API error: ${errorText}`);
78
+ + }
79
+ + return res;
80
+ + },
81
+ + `HTTP ${llm} streaming API call - Body: ${requestBodyStr.substring(0, 200)}...`
82
+ + );
83
+ +
84
+ + const reader = response.body?.getReader();
85
+ + if (!reader) {
86
+ + throw new Error("No readable stream available");
87
+ + }
88
+ +
89
+ + const decoder = new TextDecoder();
90
+ + let fullResponse = "";
91
+ + let currentField = "";
92
+ + let currentValue = "";
93
+ + let insideTag = false;
94
+ + let buffer = "";
95
+ +
96
+ + try {
97
+ + while (true) {
98
+ + const { done, value } = await reader.read();
99
+ + if (done) break;
100
+ +
101
+ + const chunk = decoder.decode(value, { stream: true });
102
+ + buffer += chunk;
103
+ +
104
+ + // Process complete lines for different providers
105
+ + const lines = buffer.split('\n');
106
+ + buffer = lines.pop() || ''; // Keep incomplete line in buffer
107
+ +
108
+ + for (const line of lines) {
109
+ + let content = '';
110
+ +
111
+ + if (llm.includes("claude")) {
112
+ + // Anthropic streaming format
113
+ + if (line.startsWith('data: ') && !line.includes('[DONE]')) {
114
+ + try {
115
+ + const data = JSON.parse(line.slice(6));
116
+ + if (data.type === 'content_block_delta' && data.delta?.text) {
117
+ + content = data.delta.text;
118
+ + }
119
+ + } catch (e) {
120
+ + continue;
121
+ + }
122
+ + }
123
+ + } else {
124
+ + // OpenAI/DeepSeek streaming format
125
+ + if (line.startsWith('data: ') && !line.includes('[DONE]')) {
126
+ + try {
127
+ + const data = JSON.parse(line.slice(6));
128
+ + if (data.choices?.[0]?.delta?.content) {
129
+ + content = data.choices[0].delta.content;
130
+ + }
131
+ + } catch (e) {
132
+ + continue;
133
+ + }
134
+ + }
135
+ + }
136
+ +
137
+ + if (content) {
138
+ + fullResponse += content;
139
+ +
140
+ + // Parse XML tags to detect field changes
141
+ + for (const char of content) {
142
+ + if (char === '<') {
143
+ + insideTag = true;
144
+ + currentValue = "";
145
+ + } else if (char === '>' && insideTag) {
146
+ + insideTag = false;
147
+ + if (!currentValue.startsWith('/')) {
148
+ + currentField = currentValue;
149
+ + currentValue = "";
150
+ + }
151
+ + } else if (insideTag) {
152
+ + currentValue += char;
153
+ + } else if (currentField && char !== '\n') {
154
+ + currentValue += char;
155
+ + streamingCallback({ stage: "streaming", field: currentField, value: currentValue });
156
+ + }
157
+ + }
158
+ + }
159
+ + }
160
+ + }
161
+ + } finally {
162
+ + reader.releaseLock();
163
+ + }
164
+ +
165
+ + return fullResponse;
166
+ + }
167
+ };
168
+ return measureFn
169
+ ? await measureFn(executeCall, `LLM call to ${llm}`)
170
+ @@ -254,7 +356,8 @@ export class Agent<I extends z.ZodObject<any>, O extends z.ZodObject<any>> {
171
+ stage: "response_generation",
172
+ message: "Generating final response...",
173
+ });
174
+ +
175
+ try {
176
+ const response = await measure(
177
+ - async (measure) => await this.generateResponse(validatedInput, toolResults, measure),
178
+ + async (measure) => await this.generateResponse(validatedInput, toolResults, measure, progressCallback),
179
+ "Generate AI response with toolResults: " + JSON.stringify(toolResults),
180
+ );
181
+ return await measure(
182
+ @@ -409,7 +512,16 @@ export class Agent<I extends z.ZodObject<any>, O extends z.ZodObject<any>> {
183
+ }
184
+
185
+ /** Generates the final response based on input and tool results. */
186
+ - private async generateResponse(input: any, toolResults: Record<string, any>, measureFn: typeof measure): Promise<any> {
187
+ + private async generateResponse(
188
+ + input: any,
189
+ + toolResults: Record<string, any>,
190
+ + measureFn: typeof measure,
191
+ + progressCallback?: ProgressCallback
192
+ + ): Promise<any> {
193
+ + const streamingCallback: StreamingCallback | undefined = progressCallback ?
194
+ + (update) => progressCallback(update as ProgressUpdate) :
195
+ + undefined;
196
+ +
197
+ return await measureFn(
198
+ async (measure) => {
199
+ const systemPrompt = this.config.systemPrompt || null;
200
+ @@ -434,7 +546,8 @@ export class Agent<I extends z.ZodObject<any>, O extends z.ZodObject<any>> {
201
+ const response = await measure(() => callLLM(
202
+ this.config.llm,
203
+ messages,
204
+ - { temperature: this.config.temperature || 0.7, maxTokens: this.config.maxTokens || 4000 },
205
+ + { temperature: this.config.temperature || 0.7, maxTokens: this.config.maxTokens || 4000 },
206
+ - measure
207
+ + measure,
208
+ + streamingCallback
209
+ ), userPrompt);
210
+ const parsed = await measure(
211
+ async () => xmlToObj(response),
212
+ diff --git a/test/streaming.test.ts b/test/streaming.test.ts
213
+ new file mode 100644
214
+ index 0000000..1234567
215
+ --- /dev/null
216
+ +++ b/test/streaming.test.ts
217
+ @@ -0,0 +1,72 @@
218
+ +import { test, expect, it, describe } from 'bun:test';
219
+ +import { z } from 'zod';
220
+ +import { Agent, LLM, ProgressUpdate, StreamingUpdate } from '../uai';
221
+ +
222
+ +describe('UAI Streaming Tests', () => {
223
+ + it('should emit streaming progress updates token-by-token', async () => {
224
+ + const streamingAgent = new Agent({
225
+ + llm: LLM['gpt-4o-mini'],
226
+ + inputFormat: z.object({
227
+ + question: z.string(),
228
+ + }),
229
+ + outputFormat: z.object({
230
+ + analysis: z.string().describe("Step-by-step analysis of the question"),
231
+ + answer: z.string().describe("The final answer to the question"),
232
+ + }),
233
+ + temperature: 0.7,
234
+ + });
235
+ +
236
+ + const input = {
237
+ + question: 'What are the benefits of renewable energy?',
238
+ + };
239
+ +
240
+ + const streamingUpdates: StreamingUpdate[] = [];
241
+ + const progressUpdates: ProgressUpdate[] = [];
242
+ +
243
+ + try {
244
+ + const result = await streamingAgent.run(input, (update) => {
245
+ + if (update.stage === 'streaming') {
246
+ + streamingUpdates.push(update as StreamingUpdate);
247
+ + console.log(`Field: ${update.field}, Value: ${update.value.slice(-20)}...`);
248
+ + } else {
249
+ + progressUpdates.push(update);
250
+ + }
251
+ + });
252
+ +
253
+ + console.log('\n--- Final Result ---');
254
+ + console.log('Analysis:', result.analysis);
255
+ + console.log('Answer:', result.answer);
256
+ +
257
+ + // Verify we received streaming updates
258
+ + expect(streamingUpdates.length).toBeGreaterThan(0);
259
+ +
260
+ + // Verify we have updates for both fields
261
+ + const fieldsUpdated = new Set(streamingUpdates.map(u => u.field));
262
+ + expect(fieldsUpdated.has('analysis')).toBe(true);
263
+ + expect(fieldsUpdated.has('answer')).toBe(true);
264
+ +
265
+ + // Verify the streaming updates show progressive content building
266
+ + const analysisUpdates = streamingUpdates.filter(u => u.field === 'analysis');
267
+ + const answerUpdates = streamingUpdates.filter(u => u.field === 'answer');
268
+ +
269
+ + expect(analysisUpdates.length).toBeGreaterThan(1);
270
+ + expect(answerUpdates.length).toBeGreaterThan(1);
271
+ +
272
+ + // Verify content is progressively building (each update should contain more text)
273
+ + for (let i = 1; i < analysisUpdates.length; i++) {
274
+ + expect(analysisUpdates[i].value.length).toBeGreaterThanOrEqual(analysisUpdates[i-1].value.length);
275
+ + }
276
+ +
277
+ + // Verify final result matches last streaming update values
278
+ + const lastAnalysisUpdate = analysisUpdates[analysisUpdates.length - 1];
279
+ + const lastAnswerUpdate = answerUpdates[answerUpdates.length - 1];
280
+ +
281
+ + expect(result.analysis.trim()).toBe(lastAnalysisUpdate.value.trim());
282
+ + expect(result.answer.trim()).toBe(lastAnswerUpdate.value.trim());
283
+ +
284
+ + console.log(`\n✅ Received ${streamingUpdates.length} streaming updates across ${fieldsUpdated.size} fields`);
285
+ +
286
+ + } catch (error) {
287
+ + console.warn('\n⚠️ Streaming test skipped (this is expected if API keys are not configured):', error.message);
288
+ + }
289
+ + }, { timeout: 60000 });
290
+ +});
@@ -0,0 +1,73 @@
1
+ import { test, expect, it, describe } from 'bun:test';
2
+ import { z } from 'zod';
3
+ import { Agent, LLM, ProgressUpdate } from '../uai';
4
+
5
+ describe('UAI Library Advanced Tests', () => {
6
+ it('should generate a structured response with a thinking process based on persona', async () => {
7
+ // 1. Define an Agent with more meaningful input and output schemas
8
+ const personaAgent = new Agent({
9
+ llm: LLM['gpt-4o-mini'],
10
+ // Input requires context for how the AI should behave
11
+ inputFormat: z.object({
12
+ user_query: z.string(),
13
+ personality: z.string(),
14
+ tone: z.enum(['humorous', 'formal', 'serious']),
15
+ }),
16
+ // Output is structured to separate reasoning from the final answer
17
+ outputFormat: z.object({
18
+ thinkingProcess: z.string().describe("My step-by-step reasoning for crafting the response, from the persona's point of view."),
19
+ finalResponse: z.string().describe("The final, crafted response for the user, delivered in character."),
20
+ suggestedMarkup: z.string().describe("A sample HTML block based on the response, which could be e.g. a div or a blockquote."),
21
+ status: z.boolean().describe("is it done or not"),
22
+ }),
23
+ temperature: 0.7,
24
+ });
25
+
26
+ const input = {
27
+ user_query: 'What is the best way to invest $100?',
28
+ personality: 'A cautious, old sea captain who has seen many treasures lost to recklessness.',
29
+ tone: 'serious' as const, // 'as const' helps TypeScript infer the most specific type
30
+ };
31
+
32
+ try {
33
+ const streamingResponse = {};
34
+ // 2. Run the agent and await the structured result
35
+ const { finalResponse, thinkingProcess, suggestedMarkup, status } = await personaAgent.run(input, (update: any) => {
36
+ if (update.stage === 'streaming') {
37
+ streamingResponse[update.field] = (streamingResponse[update.field] || '') + update.value;
38
+ }
39
+ });
40
+
41
+ // 3. Log the output for easy debugging during test runs
42
+ console.log('\n--- Thinking Process ---');
43
+ console.log(thinkingProcess);
44
+ console.log('\n--- Final Response ---');
45
+ console.log(finalResponse);
46
+ console.log('\n--- Suggested Markup ---');
47
+ console.log(suggestedMarkup);
48
+ console.log('\n--- Status ---');
49
+ console.log(status);
50
+
51
+ // check boolean type prompts
52
+ expect(status).toBe(true);
53
+
54
+ // 4. Verify the structure and content of the response
55
+ expect(thinkingProcess).toBeTypeOf('string');
56
+ expect(thinkingProcess.length).toBeGreaterThan(1); // Assert it's not empty
57
+
58
+ expect(finalResponse).toBeTypeOf('string');
59
+ expect(finalResponse.toLowerCase()).toInclude('sea');
60
+ expect(suggestedMarkup).toBeTypeOf('string');
61
+ // Assert that we received a string that contains markup, which would have failed before the fix.
62
+ expect(suggestedMarkup.trim()).toStartWith('<');
63
+ expect(suggestedMarkup.trim()).toEndWith('>');
64
+
65
+
66
+ console.log('\n✅ Agent generated a valid, structured response that fits the persona.');
67
+
68
+ } catch (error) {
69
+ // This block allows the test to pass gracefully if API keys aren't configured.
70
+ console.warn('\n⚠️ API call test skipped (this is expected if API keys are not configured):', error.message);
71
+ }
72
+ }, { timeout: 30000 });
73
+ });
@@ -0,0 +1,237 @@
1
+ import { test, expect, it, describe } from 'bun:test';
2
+ import { z } from 'zod';
3
+ import { Agent, LLM, ProgressUpdate } from '../uai';
4
+
5
+ describe('UAI Library Tests', () => {
6
+ it('should process complex input validation successfully', async () => {
7
+ const complexAgent = new Agent({
8
+ llm: LLM.gpt4o,
9
+ inputFormat: z.object({
10
+ userQuery: z.string().describe('The main question or request from the user'),
11
+ context: z.object({
12
+ sessionId: z.string().describe('Unique identifier for this conversation session'),
13
+ previousInteraction: z.string().optional().describe('Summary of previous interaction if any'),
14
+ userPreferences: z.object({
15
+ responseStyle: z.enum(['formal', 'casual', 'technical']).describe('Preferred communication style'),
16
+ detailLevel: z.enum(['brief', 'moderate', 'comprehensive']).describe('Desired level of detail in responses'),
17
+ }),
18
+ }),
19
+ metadata: z.object({
20
+ timestamp: z.string().describe('ISO timestamp of the request'),
21
+ platform: z.string().describe('Platform or device used to make the request'),
22
+ urgency: z.enum(['low', 'medium', 'high']).describe('Priority level of the request'),
23
+ }),
24
+ }),
25
+ outputFormat: z.object({
26
+ primaryResponse: z.string().describe('The main answer or response to the user query'),
27
+ analysis: z.object({
28
+ confidence: z.number().describe('Confidence level from 0 to 1 in the response accuracy'),
29
+ reasoning: z.string().describe('Brief explanation of the reasoning process used'),
30
+ complexity: z.enum(['simple', 'moderate', 'complex']).describe('Assessed complexity of the query'),
31
+ }),
32
+ }),
33
+ });
34
+
35
+ try {
36
+ await complexAgent.run({
37
+ userQuery: 'What are the best practices for sustainable software development?',
38
+ context: {
39
+ sessionId: 'session_12345',
40
+ previousInteraction: 'User previously asked about green computing',
41
+ userPreferences: {
42
+ responseStyle: 'technical',
43
+ detailLevel: 'comprehensive',
44
+ },
45
+ },
46
+ metadata: {
47
+ timestamp: '2024-12-25T10:30:00Z',
48
+ platform: 'web_browser',
49
+ urgency: 'medium',
50
+ },
51
+ });
52
+ console.log('✅ Complex input validation and processing successful');
53
+ } catch (error) {
54
+ console.warn('⚠️ API call test skipped (possibly due to API keys):', error.message);
55
+ if (!error.message.includes('API') && !error.message.includes('401') && !error.message.includes('403')) {
56
+ throw error;
57
+ }
58
+ }
59
+ });
60
+
61
+ it('should throw error for arrays in output schema', () => {
62
+ expect(() => {
63
+ new Agent({
64
+ llm: LLM.gpt4o,
65
+ inputFormat: z.object({
66
+ message: z.string(),
67
+ }),
68
+ outputFormat: z.object({
69
+ responses: z.array(z.string()), // This should throw an error
70
+ }),
71
+ });
72
+ }).toThrow('Arrays are not supported in output schema. Found array at path: responses. Use individual fields like responses_1, responses_2 instead.');
73
+
74
+ console.log('✅ Array validation working correctly');
75
+ });
76
+
77
+ it('should throw error for nested arrays in output schema', () => {
78
+ expect(() => {
79
+ new Agent({
80
+ llm: LLM.gpt4o,
81
+ inputFormat: z.object({
82
+ message: z.string(),
83
+ }),
84
+ outputFormat: z.object({
85
+ data: z.object({
86
+ items: z.array(z.string()), // Nested array should also throw
87
+ }),
88
+ }),
89
+ });
90
+ }).toThrow('Arrays are not supported in output schema. Found array at path: data.items. Use individual fields like items_1, items_2 instead.');
91
+
92
+ console.log('✅ Nested array validation working correctly');
93
+ });
94
+
95
+ it('should validate input types correctly', async () => {
96
+ const agent = new Agent({
97
+ llm: LLM.gpt4o,
98
+ inputFormat: z.object({
99
+ message: z.string(),
100
+ count: z.number(),
101
+ }),
102
+ outputFormat: z.object({
103
+ response: z.string(),
104
+ }),
105
+ });
106
+
107
+ try {
108
+ await agent.run({
109
+ message: 123 as any, // Wrong type
110
+ count: 'not a number' as any, // Wrong type
111
+ });
112
+ expect(false).toBe(true); // Should not reach here
113
+ } catch (error) {
114
+ expect(error.name).toBe('ZodError');
115
+ console.log('✅ Input type validation working correctly');
116
+ }
117
+ });
118
+
119
+ it('should validate enum values correctly', async () => {
120
+ const agent = new Agent({
121
+ llm: LLM.gpt4o,
122
+ inputFormat: z.object({
123
+ message: z.string(),
124
+ priority: z.enum(['low', 'medium', 'high']),
125
+ }),
126
+ outputFormat: z.object({
127
+ response: z.string(),
128
+ }),
129
+ });
130
+
131
+ try {
132
+ await agent.run({
133
+ message: 'test',
134
+ priority: 'invalid_priority' as any, // Invalid enum value
135
+ });
136
+ expect(false).toBe(true); // Should not reach here
137
+ } catch (error) {
138
+ expect(error.name).toBe('ZodError');
139
+ console.log('✅ Enum validation working correctly');
140
+ }
141
+ });
142
+
143
+ it('should support all LLM model configurations', () => {
144
+ const models = [LLM.gpt4o, LLM.gpt4, LLM.claude, LLM.deepseek];
145
+
146
+ for (const model of models) {
147
+ const agent = new Agent({
148
+ llm: model,
149
+ inputFormat: z.object({
150
+ query: z.string(),
151
+ }),
152
+ outputFormat: z.object({
153
+ result: z.string(),
154
+ metadata: z.object({
155
+ model: z.string(),
156
+ confidence: z.number(),
157
+ }),
158
+ }),
159
+ });
160
+
161
+ expect(agent).toBeDefined();
162
+ console.log(`✅ ${model} configuration valid`);
163
+ }
164
+ });
165
+
166
+ it('should handle optional fields correctly', async () => {
167
+ const agent = new Agent({
168
+ llm: LLM.gpt4o,
169
+ inputFormat: z.object({
170
+ required: z.string(),
171
+ optional: z.string().optional(),
172
+ }),
173
+ outputFormat: z.object({
174
+ answer: z.string(),
175
+ extra: z.string().optional(),
176
+ }),
177
+ });
178
+
179
+ try {
180
+ await agent.run({
181
+ required: 'test value',
182
+ // optional field omitted
183
+ });
184
+ console.log('✅ Optional fields handled correctly');
185
+ } catch (error) {
186
+ console.warn('⚠️ Optional fields test skipped due to API limitations');
187
+ if (!error.message.includes('API') && !error.message.includes('401') && !error.message.includes('403')) {
188
+ throw error;
189
+ }
190
+ }
191
+ });
192
+
193
+ it('should handle nullable fields correctly', async () => {
194
+ const agent = new Agent({
195
+ llm: LLM.gpt4o,
196
+ inputFormat: z.object({
197
+ required: z.string(),
198
+ nullable: z.string().nullable(),
199
+ }),
200
+ outputFormat: z.object({
201
+ answer: z.string(),
202
+ extra: z.string().nullable(),
203
+ }),
204
+ });
205
+
206
+ try {
207
+ await agent.run({
208
+ required: 'test value',
209
+ nullable: null,
210
+ });
211
+ console.log('✅ Nullable fields handled correctly');
212
+ } catch (error) {
213
+ console.warn('⚠️ Nullable fields test skipped due to API limitations');
214
+ if (!error.message.includes('API') && !error.message.includes('401') && !error.message.includes('403')) {
215
+ throw error;
216
+ }
217
+ }
218
+ });
219
+
220
+ it('should configure agent with custom temperature and maxTokens', () => {
221
+ const agent = new Agent({
222
+ llm: LLM.gpt4o,
223
+ inputFormat: z.object({
224
+ query: z.string(),
225
+ }),
226
+ outputFormat: z.object({
227
+ response: z.string(),
228
+ }),
229
+ temperature: 0.1,
230
+ maxTokens: 500,
231
+ systemPrompt: 'You are a precise assistant that gives brief responses.',
232
+ });
233
+
234
+ expect(agent).toBeDefined();
235
+ console.log('✅ Custom configuration parameters working correctly');
236
+ });
237
+ });