@falai/agent 1.1.2 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -0
- package/dist/cjs/core/Agent.d.ts +17 -1
- package/dist/cjs/core/Agent.d.ts.map +1 -1
- package/dist/cjs/core/Agent.js +47 -0
- package/dist/cjs/core/Agent.js.map +1 -1
- package/dist/cjs/core/BatchPromptBuilder.d.ts +3 -0
- package/dist/cjs/core/BatchPromptBuilder.d.ts.map +1 -1
- package/dist/cjs/core/BatchPromptBuilder.js +14 -11
- package/dist/cjs/core/BatchPromptBuilder.js.map +1 -1
- package/dist/cjs/core/CompactionEngine.d.ts +65 -0
- package/dist/cjs/core/CompactionEngine.d.ts.map +1 -0
- package/dist/cjs/core/CompactionEngine.js +251 -0
- package/dist/cjs/core/CompactionEngine.js.map +1 -0
- package/dist/cjs/core/PromptComposer.d.ts +8 -1
- package/dist/cjs/core/PromptComposer.d.ts.map +1 -1
- package/dist/cjs/core/PromptComposer.js +238 -118
- package/dist/cjs/core/PromptComposer.js.map +1 -1
- package/dist/cjs/core/PromptSectionCache.d.ts +57 -0
- package/dist/cjs/core/PromptSectionCache.d.ts.map +1 -0
- package/dist/cjs/core/PromptSectionCache.js +108 -0
- package/dist/cjs/core/PromptSectionCache.js.map +1 -0
- package/dist/cjs/core/ResponseEngine.d.ts +3 -0
- package/dist/cjs/core/ResponseEngine.d.ts.map +1 -1
- package/dist/cjs/core/ResponseEngine.js +10 -6
- package/dist/cjs/core/ResponseEngine.js.map +1 -1
- package/dist/cjs/core/ResponseModal.d.ts.map +1 -1
- package/dist/cjs/core/ResponseModal.js +79 -20
- package/dist/cjs/core/ResponseModal.js.map +1 -1
- package/dist/cjs/core/RoutingEngine.d.ts +10 -0
- package/dist/cjs/core/RoutingEngine.d.ts.map +1 -1
- package/dist/cjs/core/RoutingEngine.js +3 -2
- package/dist/cjs/core/RoutingEngine.js.map +1 -1
- package/dist/cjs/core/SessionManager.d.ts.map +1 -1
- package/dist/cjs/core/SessionManager.js +20 -0
- package/dist/cjs/core/SessionManager.js.map +1 -1
- package/dist/cjs/core/StreamingToolExecutor.d.ts +142 -0
- package/dist/cjs/core/StreamingToolExecutor.d.ts.map +1 -0
- package/dist/cjs/core/StreamingToolExecutor.js +455 -0
- package/dist/cjs/core/StreamingToolExecutor.js.map +1 -0
- package/dist/cjs/core/ToolManager.d.ts +18 -1
- package/dist/cjs/core/ToolManager.d.ts.map +1 -1
- package/dist/cjs/core/ToolManager.js +91 -0
- package/dist/cjs/core/ToolManager.js.map +1 -1
- package/dist/cjs/index.d.ts +5 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +8 -2
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/providers/AnthropicProvider.d.ts.map +1 -1
- package/dist/cjs/providers/AnthropicProvider.js +8 -7
- package/dist/cjs/providers/AnthropicProvider.js.map +1 -1
- package/dist/cjs/providers/GeminiProvider.d.ts +25 -0
- package/dist/cjs/providers/GeminiProvider.d.ts.map +1 -1
- package/dist/cjs/providers/GeminiProvider.js +79 -51
- package/dist/cjs/providers/GeminiProvider.js.map +1 -1
- package/dist/cjs/providers/OpenAIProvider.d.ts.map +1 -1
- package/dist/cjs/providers/OpenAIProvider.js +14 -6
- package/dist/cjs/providers/OpenAIProvider.js.map +1 -1
- package/dist/cjs/providers/OpenRouterProvider.d.ts.map +1 -1
- package/dist/cjs/providers/OpenRouterProvider.js +7 -6
- package/dist/cjs/providers/OpenRouterProvider.js.map +1 -1
- package/dist/cjs/types/agent.d.ts +44 -0
- package/dist/cjs/types/agent.d.ts.map +1 -1
- package/dist/cjs/types/agent.js.map +1 -1
- package/dist/cjs/types/compaction.d.ts +50 -0
- package/dist/cjs/types/compaction.d.ts.map +1 -0
- package/dist/cjs/types/compaction.js +6 -0
- package/dist/cjs/types/compaction.js.map +1 -0
- package/dist/cjs/types/index.d.ts +4 -2
- package/dist/cjs/types/index.d.ts.map +1 -1
- package/dist/cjs/types/index.js.map +1 -1
- package/dist/cjs/types/tool.d.ts +84 -0
- package/dist/cjs/types/tool.d.ts.map +1 -1
- package/dist/core/Agent.d.ts +17 -1
- package/dist/core/Agent.d.ts.map +1 -1
- package/dist/core/Agent.js +47 -0
- package/dist/core/Agent.js.map +1 -1
- package/dist/core/BatchPromptBuilder.d.ts +3 -0
- package/dist/core/BatchPromptBuilder.d.ts.map +1 -1
- package/dist/core/BatchPromptBuilder.js +14 -11
- package/dist/core/BatchPromptBuilder.js.map +1 -1
- package/dist/core/CompactionEngine.d.ts +65 -0
- package/dist/core/CompactionEngine.d.ts.map +1 -0
- package/dist/core/CompactionEngine.js +244 -0
- package/dist/core/CompactionEngine.js.map +1 -0
- package/dist/core/PromptComposer.d.ts +8 -1
- package/dist/core/PromptComposer.d.ts.map +1 -1
- package/dist/core/PromptComposer.js +238 -118
- package/dist/core/PromptComposer.js.map +1 -1
- package/dist/core/PromptSectionCache.d.ts +57 -0
- package/dist/core/PromptSectionCache.d.ts.map +1 -0
- package/dist/core/PromptSectionCache.js +104 -0
- package/dist/core/PromptSectionCache.js.map +1 -0
- package/dist/core/ResponseEngine.d.ts +3 -0
- package/dist/core/ResponseEngine.d.ts.map +1 -1
- package/dist/core/ResponseEngine.js +10 -6
- package/dist/core/ResponseEngine.js.map +1 -1
- package/dist/core/ResponseModal.d.ts.map +1 -1
- package/dist/core/ResponseModal.js +79 -20
- package/dist/core/ResponseModal.js.map +1 -1
- package/dist/core/RoutingEngine.d.ts +10 -0
- package/dist/core/RoutingEngine.d.ts.map +1 -1
- package/dist/core/RoutingEngine.js +3 -2
- package/dist/core/RoutingEngine.js.map +1 -1
- package/dist/core/SessionManager.d.ts.map +1 -1
- package/dist/core/SessionManager.js +17 -0
- package/dist/core/SessionManager.js.map +1 -1
- package/dist/core/StreamingToolExecutor.d.ts +142 -0
- package/dist/core/StreamingToolExecutor.d.ts.map +1 -0
- package/dist/core/StreamingToolExecutor.js +448 -0
- package/dist/core/StreamingToolExecutor.js.map +1 -0
- package/dist/core/ToolManager.d.ts +18 -1
- package/dist/core/ToolManager.d.ts.map +1 -1
- package/dist/core/ToolManager.js +91 -0
- package/dist/core/ToolManager.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/providers/AnthropicProvider.d.ts.map +1 -1
- package/dist/providers/AnthropicProvider.js +8 -7
- package/dist/providers/AnthropicProvider.js.map +1 -1
- package/dist/providers/GeminiProvider.d.ts +25 -0
- package/dist/providers/GeminiProvider.d.ts.map +1 -1
- package/dist/providers/GeminiProvider.js +79 -51
- package/dist/providers/GeminiProvider.js.map +1 -1
- package/dist/providers/OpenAIProvider.d.ts.map +1 -1
- package/dist/providers/OpenAIProvider.js +14 -6
- package/dist/providers/OpenAIProvider.js.map +1 -1
- package/dist/providers/OpenRouterProvider.d.ts.map +1 -1
- package/dist/providers/OpenRouterProvider.js +7 -6
- package/dist/providers/OpenRouterProvider.js.map +1 -1
- package/dist/types/agent.d.ts +44 -0
- package/dist/types/agent.d.ts.map +1 -1
- package/dist/types/agent.js.map +1 -1
- package/dist/types/compaction.d.ts +50 -0
- package/dist/types/compaction.d.ts.map +1 -0
- package/dist/types/compaction.js +5 -0
- package/dist/types/compaction.js.map +1 -0
- package/dist/types/index.d.ts +4 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types/tool.d.ts +84 -0
- package/dist/types/tool.d.ts.map +1 -1
- package/docs/api/overview.md +140 -0
- package/docs/core/tools/enhanced-tool.md +186 -0
- package/docs/core/tools/streaming-execution.md +161 -0
- package/docs/guides/context-compaction.md +96 -0
- package/docs/guides/prompt-optimization.md +164 -0
- package/examples/advanced-patterns/context-compaction.ts +223 -0
- package/examples/advanced-patterns/streaming-responses.ts +85 -7
- package/examples/tools/enhanced-tool-metadata.ts +268 -0
- package/examples/tools/streaming-tool-execution.ts +283 -0
- package/package.json +1 -1
- package/src/core/Agent.ts +58 -2
- package/src/core/BatchPromptBuilder.ts +14 -11
- package/src/core/CompactionEngine.ts +318 -0
- package/src/core/PromptComposer.ts +261 -141
- package/src/core/PromptSectionCache.ts +136 -0
- package/src/core/ResponseEngine.ts +9 -6
- package/src/core/ResponseModal.ts +81 -20
- package/src/core/RoutingEngine.ts +13 -2
- package/src/core/SessionManager.ts +19 -0
- package/src/core/StreamingToolExecutor.ts +572 -0
- package/src/core/ToolManager.ts +151 -41
- package/src/index.ts +14 -0
- package/src/providers/AnthropicProvider.ts +11 -12
- package/src/providers/GeminiProvider.ts +83 -52
- package/src/providers/OpenAIProvider.ts +21 -13
- package/src/providers/OpenRouterProvider.ts +13 -13
- package/src/types/agent.ts +45 -0
- package/src/types/compaction.ts +52 -0
- package/src/types/index.ts +35 -14
- package/src/types/tool.ts +108 -0
|
@@ -152,6 +152,66 @@ export class GeminiProvider implements AiProvider {
|
|
|
152
152
|
};
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
+
/**
|
|
156
|
+
* Convert tool parameter schemas (JSON Schema) to Gemini's Schema format.
|
|
157
|
+
* Gemini's FunctionDeclaration.parameters expects its own Schema type,
|
|
158
|
+
* not raw JSON Schema. This method handles the conversion, including
|
|
159
|
+
* edge cases like empty objects that Gemini rejects.
|
|
160
|
+
*
|
|
161
|
+
* @private
|
|
162
|
+
*/
|
|
163
|
+
private convertToolParameters(parameters: unknown): Schema | undefined {
|
|
164
|
+
if (!parameters || typeof parameters !== "object") {
|
|
165
|
+
return undefined;
|
|
166
|
+
}
|
|
167
|
+
try {
|
|
168
|
+
return this.adaptSchemaForGemini(parameters as StructuredSchema);
|
|
169
|
+
} catch (error) {
|
|
170
|
+
logger.warn(`[GeminiProvider] Failed to convert tool parameters, passing as-is:`, error);
|
|
171
|
+
return parameters as Schema;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Safely extract text from a Gemini response or chunk.
|
|
177
|
+
* The `.text` getter can throw when the response contains only function calls
|
|
178
|
+
* (observed in some SDK versions). This method falls back to manually
|
|
179
|
+
* extracting text parts from candidates.
|
|
180
|
+
*
|
|
181
|
+
* @private
|
|
182
|
+
*/
|
|
183
|
+
private safeExtractText(responseOrChunk: { text?: string; candidates?: Array<{ content?: { parts?: Array<{ text?: string; functionCall?: unknown }> } }> }): string {
|
|
184
|
+
try {
|
|
185
|
+
return responseOrChunk.text || "";
|
|
186
|
+
} catch {
|
|
187
|
+
// .text getter threw — extract text parts manually
|
|
188
|
+
const parts = responseOrChunk.candidates?.[0]?.content?.parts;
|
|
189
|
+
if (parts) {
|
|
190
|
+
return parts
|
|
191
|
+
.filter((p) => p.text != null)
|
|
192
|
+
.map((p) => p.text)
|
|
193
|
+
.join("");
|
|
194
|
+
}
|
|
195
|
+
return "";
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Build Gemini function declarations from framework tool definitions.
|
|
201
|
+
* Converts JSON Schema parameters to Gemini's Schema format.
|
|
202
|
+
*
|
|
203
|
+
* @private
|
|
204
|
+
*/
|
|
205
|
+
private buildFunctionDeclarations(
|
|
206
|
+
tools: Array<{ id: string; name?: string; description?: string; parameters?: unknown }>
|
|
207
|
+
): FunctionDeclaration[] {
|
|
208
|
+
return tools.map((tool) => ({
|
|
209
|
+
name: tool.name || tool.id,
|
|
210
|
+
description: tool.description || "",
|
|
211
|
+
parameters: this.convertToolParameters(tool.parameters),
|
|
212
|
+
}));
|
|
213
|
+
}
|
|
214
|
+
|
|
155
215
|
/**
|
|
156
216
|
* Adapt common schema format to Gemini's specific requirements.
|
|
157
217
|
* Gemini has strict validation:
|
|
@@ -356,15 +416,8 @@ export class GeminiProvider implements AiProvider {
|
|
|
356
416
|
if (hasTools) {
|
|
357
417
|
const toolNames = input.tools?.map((tool) => tool.name || tool.id) || [];
|
|
358
418
|
logger.debug(`[GeminiProvider] Configuring ${toolNames.length} tools for model ${model}:`, toolNames);
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
functionDeclarations: input.tools?.map((tool) => ({
|
|
362
|
-
name: tool.name || tool.id,
|
|
363
|
-
description: tool.description || "",
|
|
364
|
-
parameters: tool.parameters as FunctionDeclaration["parameters"], // JSON schema
|
|
365
|
-
})),
|
|
366
|
-
},
|
|
367
|
-
];
|
|
419
|
+
const functionDeclarations = this.buildFunctionDeclarations(input.tools!);
|
|
420
|
+
configOverride.tools = [{ functionDeclarations }];
|
|
368
421
|
|
|
369
422
|
} else if (hasJsonSchema) {
|
|
370
423
|
// Only set JSON schema if no tools are present
|
|
@@ -380,7 +433,10 @@ export class GeminiProvider implements AiProvider {
|
|
|
380
433
|
response = await this.genAI.models.generateContent({
|
|
381
434
|
model,
|
|
382
435
|
contents: input.prompt,
|
|
383
|
-
config:
|
|
436
|
+
config: {
|
|
437
|
+
...configOverride,
|
|
438
|
+
...(input.signal ? { abortSignal: input.signal } : {}),
|
|
439
|
+
},
|
|
384
440
|
});
|
|
385
441
|
} catch (error: unknown) {
|
|
386
442
|
logger.error(`[GeminiProvider] API call failed:`, error);
|
|
@@ -399,7 +455,7 @@ export class GeminiProvider implements AiProvider {
|
|
|
399
455
|
if (part.functionCall) {
|
|
400
456
|
toolCalls.push({
|
|
401
457
|
toolName: part.functionCall.name || "",
|
|
402
|
-
arguments: part.functionCall.args as Record<string, unknown
|
|
458
|
+
arguments: (part.functionCall.args as Record<string, unknown>) || {},
|
|
403
459
|
});
|
|
404
460
|
}
|
|
405
461
|
}
|
|
@@ -408,30 +464,13 @@ export class GeminiProvider implements AiProvider {
|
|
|
408
464
|
// Debug logging for response structure
|
|
409
465
|
if (!response.text && toolCalls.length === 0) {
|
|
410
466
|
logger.debug(`[GeminiProvider] Debug - Response structure:`, {
|
|
411
|
-
hasText: !!response.text,
|
|
412
467
|
candidatesCount: response.candidates?.length || 0,
|
|
413
|
-
firstCandidateContent: response.candidates?.[0]?.content,
|
|
414
468
|
firstCandidateParts: response.candidates?.[0]?.content?.parts?.length || 0,
|
|
415
469
|
});
|
|
416
470
|
}
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
message = response.text || "";
|
|
421
|
-
} catch (textError) {
|
|
422
|
-
// Sometimes response.text throws when there are function calls
|
|
423
|
-
logger.debug(`[GeminiProvider] Could not get response.text (likely due to function calls):`, textError);
|
|
424
|
-
|
|
425
|
-
// Try to extract text parts manually
|
|
426
|
-
if (response.candidates && response.candidates[0]?.content?.parts) {
|
|
427
|
-
const textParts = response.candidates[0].content.parts
|
|
428
|
-
.filter(part => part.text)
|
|
429
|
-
.map(part => part.text)
|
|
430
|
-
.join('');
|
|
431
|
-
message = textParts;
|
|
432
|
-
logger.debug(`[GeminiProvider] Extracted text from parts:`, message);
|
|
433
|
-
}
|
|
434
|
-
}
|
|
471
|
+
|
|
472
|
+
// Safely extract text — .text getter can throw when response has only function calls
|
|
473
|
+
const message = this.safeExtractText(response);
|
|
435
474
|
|
|
436
475
|
// Only throw error if we have no text AND no function calls
|
|
437
476
|
if (!message && toolCalls.length === 0) {
|
|
@@ -443,15 +482,8 @@ export class GeminiProvider implements AiProvider {
|
|
|
443
482
|
// Log when we have function calls but no text (this is normal)
|
|
444
483
|
if (toolCalls.length > 0 && !message) {
|
|
445
484
|
logger.debug(`[GeminiProvider] Function calls detected without text message:`, toolCalls.map(tc => tc.toolName));
|
|
446
|
-
} else if (toolCalls.length > 0 && message) {
|
|
447
|
-
logger.debug(`[GeminiProvider] Response has both text and function calls:`, {
|
|
448
|
-
messageLength: message.length,
|
|
449
|
-
toolCalls: toolCalls.map(tc => tc.toolName),
|
|
450
|
-
});
|
|
451
485
|
}
|
|
452
486
|
|
|
453
|
-
|
|
454
|
-
|
|
455
487
|
// Parse JSON response if schema was provided
|
|
456
488
|
let structured: AgentStructuredResponse | undefined;
|
|
457
489
|
if (input.parameters?.jsonSchema) {
|
|
@@ -574,15 +606,8 @@ export class GeminiProvider implements AiProvider {
|
|
|
574
606
|
if (hasTools) {
|
|
575
607
|
const toolNames = input.tools?.map((tool) => tool.name || tool.id) || [];
|
|
576
608
|
logger.debug(`[GeminiProvider] Configuring ${toolNames.length} tools for streaming:`, toolNames);
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
functionDeclarations: input.tools?.map((tool) => ({
|
|
580
|
-
name: tool.name || tool.id,
|
|
581
|
-
description: tool.description || "",
|
|
582
|
-
parameters: tool.parameters as FunctionDeclaration["parameters"],
|
|
583
|
-
})),
|
|
584
|
-
},
|
|
585
|
-
];
|
|
609
|
+
const functionDeclarations = this.buildFunctionDeclarations(input.tools!);
|
|
610
|
+
configOverride.tools = [{ functionDeclarations }];
|
|
586
611
|
|
|
587
612
|
} else if (hasJsonSchema) {
|
|
588
613
|
// Only set JSON schema if no tools are present
|
|
@@ -598,7 +623,10 @@ export class GeminiProvider implements AiProvider {
|
|
|
598
623
|
stream = await this.genAI.models.generateContentStream({
|
|
599
624
|
model,
|
|
600
625
|
contents: input.prompt,
|
|
601
|
-
config:
|
|
626
|
+
config: {
|
|
627
|
+
...configOverride,
|
|
628
|
+
...(input.signal ? { abortSignal: input.signal } : {}),
|
|
629
|
+
},
|
|
602
630
|
});
|
|
603
631
|
} catch (error: unknown) {
|
|
604
632
|
logger.error(`[GeminiProvider] Streaming API call failed:`, error);
|
|
@@ -615,7 +643,10 @@ export class GeminiProvider implements AiProvider {
|
|
|
615
643
|
}> = [];
|
|
616
644
|
|
|
617
645
|
for await (const chunk of stream) {
|
|
618
|
-
|
|
646
|
+
if (input.signal?.aborted) break;
|
|
647
|
+
|
|
648
|
+
// Safely extract text — chunk.text can throw when chunk has only function calls
|
|
649
|
+
const delta = this.safeExtractText(chunk);
|
|
619
650
|
|
|
620
651
|
// Extract tool calls from chunk
|
|
621
652
|
if (chunk.candidates && chunk.candidates[0]?.content?.parts) {
|
|
@@ -623,7 +654,7 @@ export class GeminiProvider implements AiProvider {
|
|
|
623
654
|
if (part.functionCall) {
|
|
624
655
|
toolCalls.push({
|
|
625
656
|
toolName: part.functionCall.name || "",
|
|
626
|
-
arguments: part.functionCall.args as Record<string, unknown
|
|
657
|
+
arguments: (part.functionCall.args as Record<string, unknown>) || {},
|
|
627
658
|
});
|
|
628
659
|
}
|
|
629
660
|
}
|
|
@@ -657,12 +688,12 @@ export class GeminiProvider implements AiProvider {
|
|
|
657
688
|
}
|
|
658
689
|
}
|
|
659
690
|
|
|
660
|
-
//
|
|
691
|
+
// Include tool calls in structured response (even without JSON schema)
|
|
661
692
|
if (toolCalls.length > 0) {
|
|
662
693
|
structured = {
|
|
663
694
|
message: structured?.message || accumulated,
|
|
664
|
-
toolCalls,
|
|
665
695
|
...structured,
|
|
696
|
+
toolCalls,
|
|
666
697
|
} as AgentStructuredResponse;
|
|
667
698
|
}
|
|
668
699
|
|
|
@@ -222,8 +222,7 @@ export class OpenAIProvider implements AiProvider {
|
|
|
222
222
|
for (let i = 0; i < this.backupModels.length; i++) {
|
|
223
223
|
const backupModel = this.backupModels[i];
|
|
224
224
|
logger.debug(
|
|
225
|
-
`[OPENAI] Trying backup model ${i + 1}/${
|
|
226
|
-
this.backupModels.length
|
|
225
|
+
`[OPENAI] Trying backup model ${i + 1}/${this.backupModels.length
|
|
227
226
|
}: ${backupModel}`
|
|
228
227
|
);
|
|
229
228
|
|
|
@@ -336,10 +335,7 @@ export class OpenAIProvider implements AiProvider {
|
|
|
336
335
|
// Fall back to regular chat completions API if no schema provided
|
|
337
336
|
const response = await this.client.chat.completions.create(params);
|
|
338
337
|
|
|
339
|
-
const message = response.choices[0]?.message?.content;
|
|
340
|
-
if (!message) {
|
|
341
|
-
throw new Error("No response from OpenAI");
|
|
342
|
-
}
|
|
338
|
+
const message = response.choices[0]?.message?.content || "";
|
|
343
339
|
|
|
344
340
|
let toolCalls: Array<{
|
|
345
341
|
toolName: string;
|
|
@@ -368,7 +364,11 @@ export class OpenAIProvider implements AiProvider {
|
|
|
368
364
|
};
|
|
369
365
|
});
|
|
370
366
|
}
|
|
371
|
-
|
|
367
|
+
|
|
368
|
+
// Only throw error if we have no text AND no function calls
|
|
369
|
+
if (!message && toolCalls.length === 0) {
|
|
370
|
+
throw new Error("No response from OpenAI");
|
|
371
|
+
}
|
|
372
372
|
|
|
373
373
|
return {
|
|
374
374
|
message,
|
|
@@ -423,8 +423,7 @@ export class OpenAIProvider implements AiProvider {
|
|
|
423
423
|
for (let i = 0; i < this.backupModels.length; i++) {
|
|
424
424
|
const backupModel = this.backupModels[i];
|
|
425
425
|
logger.debug(
|
|
426
|
-
`[OPENAI] Trying backup model ${i + 1}/${
|
|
427
|
-
this.backupModels.length
|
|
426
|
+
`[OPENAI] Trying backup model ${i + 1}/${this.backupModels.length
|
|
428
427
|
}: ${backupModel}`
|
|
429
428
|
);
|
|
430
429
|
|
|
@@ -530,9 +529,9 @@ export class OpenAIProvider implements AiProvider {
|
|
|
530
529
|
try {
|
|
531
530
|
toolCallArguments = toolCall.function.arguments
|
|
532
531
|
? (JSON.parse(toolCall.function.arguments) as Record<
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
532
|
+
string,
|
|
533
|
+
unknown
|
|
534
|
+
>)
|
|
536
535
|
: {};
|
|
537
536
|
} catch (error) {
|
|
538
537
|
logger.warn(
|
|
@@ -584,6 +583,15 @@ export class OpenAIProvider implements AiProvider {
|
|
|
584
583
|
}
|
|
585
584
|
}
|
|
586
585
|
|
|
586
|
+
// Include tool calls in structured response (even without JSON schema)
|
|
587
|
+
if (toolCalls.length > 0) {
|
|
588
|
+
structured = {
|
|
589
|
+
...(structured || {}),
|
|
590
|
+
message: (structured as AgentStructuredResponse | undefined)?.message || accumulated,
|
|
591
|
+
toolCalls,
|
|
592
|
+
} as TStructured;
|
|
593
|
+
}
|
|
594
|
+
|
|
587
595
|
// Yield final chunk
|
|
588
596
|
yield {
|
|
589
597
|
delta: "",
|
|
@@ -596,7 +604,7 @@ export class OpenAIProvider implements AiProvider {
|
|
|
596
604
|
promptTokens,
|
|
597
605
|
completionTokens,
|
|
598
606
|
},
|
|
599
|
-
structured
|
|
607
|
+
structured,
|
|
600
608
|
};
|
|
601
609
|
}
|
|
602
610
|
}
|
|
@@ -229,8 +229,7 @@ export class OpenRouterProvider implements AiProvider {
|
|
|
229
229
|
for (let i = 0; i < this.backupModels.length; i++) {
|
|
230
230
|
const backupModel = this.backupModels[i];
|
|
231
231
|
logger.debug(
|
|
232
|
-
`[OPENROUTER] Trying backup model ${i + 1}/${
|
|
233
|
-
this.backupModels.length
|
|
232
|
+
`[OPENROUTER] Trying backup model ${i + 1}/${this.backupModels.length
|
|
234
233
|
}: ${backupModel}`
|
|
235
234
|
);
|
|
236
235
|
|
|
@@ -345,10 +344,7 @@ export class OpenRouterProvider implements AiProvider {
|
|
|
345
344
|
// Fall back to regular chat completions API if no schema provided
|
|
346
345
|
const response = await this.client.chat.completions.create(params);
|
|
347
346
|
|
|
348
|
-
const message = response.choices[0]?.message?.content;
|
|
349
|
-
if (!message) {
|
|
350
|
-
throw new Error("No response from OpenRouter");
|
|
351
|
-
}
|
|
347
|
+
const message = response.choices[0]?.message?.content || "";
|
|
352
348
|
|
|
353
349
|
let toolCalls: Array<{
|
|
354
350
|
toolName: string;
|
|
@@ -378,6 +374,11 @@ export class OpenRouterProvider implements AiProvider {
|
|
|
378
374
|
});
|
|
379
375
|
}
|
|
380
376
|
|
|
377
|
+
// Only throw error if we have no text AND no function calls
|
|
378
|
+
if (!message && toolCalls.length === 0) {
|
|
379
|
+
throw new Error("No response from OpenRouter");
|
|
380
|
+
}
|
|
381
|
+
|
|
381
382
|
return {
|
|
382
383
|
message,
|
|
383
384
|
metadata: {
|
|
@@ -428,8 +429,7 @@ export class OpenRouterProvider implements AiProvider {
|
|
|
428
429
|
for (let i = 0; i < this.backupModels.length; i++) {
|
|
429
430
|
const backupModel = this.backupModels[i];
|
|
430
431
|
logger.debug(
|
|
431
|
-
`[OPENROUTER] Trying backup model ${i + 1}/${
|
|
432
|
-
this.backupModels.length
|
|
432
|
+
`[OPENROUTER] Trying backup model ${i + 1}/${this.backupModels.length
|
|
433
433
|
}: ${backupModel}`
|
|
434
434
|
);
|
|
435
435
|
|
|
@@ -532,9 +532,9 @@ export class OpenRouterProvider implements AiProvider {
|
|
|
532
532
|
try {
|
|
533
533
|
toolCallArguments = toolCall.function.arguments
|
|
534
534
|
? (JSON.parse(toolCall.function.arguments) as Record<
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
535
|
+
string,
|
|
536
|
+
unknown
|
|
537
|
+
>)
|
|
538
538
|
: {};
|
|
539
539
|
} catch (error) {
|
|
540
540
|
logger.warn(
|
|
@@ -589,9 +589,9 @@ export class OpenRouterProvider implements AiProvider {
|
|
|
589
589
|
// If tools were used, include them in structured response
|
|
590
590
|
if (toolCalls.length > 0) {
|
|
591
591
|
structured = {
|
|
592
|
-
|
|
592
|
+
...(structured || {}),
|
|
593
|
+
message: (structured as AgentStructuredResponse | undefined)?.message || accumulated,
|
|
593
594
|
toolCalls,
|
|
594
|
-
...structured,
|
|
595
595
|
} as TStructured;
|
|
596
596
|
}
|
|
597
597
|
|
package/src/types/agent.ts
CHANGED
|
@@ -9,6 +9,39 @@ import type { PersistenceConfig } from "./persistence";
|
|
|
9
9
|
import type { SessionState } from "./session";
|
|
10
10
|
import type { StructuredSchema } from "./schema";
|
|
11
11
|
import { Template, ConditionTemplate } from "./template";
|
|
12
|
+
import type { PromptCacheConfig } from "../core/PromptSectionCache";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Agent-level compaction configuration.
|
|
16
|
+
* Unlike CompactionOptions, this does not require a `provider` since the agent already has one.
|
|
17
|
+
*/
|
|
18
|
+
export interface AgentCompactionConfig {
|
|
19
|
+
/** Maximum token budget for the conversation */
|
|
20
|
+
maxTokens: number;
|
|
21
|
+
/**
|
|
22
|
+
* Threshold ratio (0–1) at which to trigger compaction.
|
|
23
|
+
* Must be between 0.5 and 0.95.
|
|
24
|
+
* @default 0.8
|
|
25
|
+
*/
|
|
26
|
+
compactionThreshold?: number;
|
|
27
|
+
/**
|
|
28
|
+
* Number of recent messages to always preserve unchanged.
|
|
29
|
+
* Must be >= 2.
|
|
30
|
+
* @default 4
|
|
31
|
+
*/
|
|
32
|
+
preserveRecentCount?: number;
|
|
33
|
+
/**
|
|
34
|
+
* Maximum characters per tool result before truncation.
|
|
35
|
+
* Must be > 0.
|
|
36
|
+
* @default 5000
|
|
37
|
+
*/
|
|
38
|
+
maxToolResultChars?: number;
|
|
39
|
+
/**
|
|
40
|
+
* Whether compaction is enabled.
|
|
41
|
+
* @default true when config is provided
|
|
42
|
+
*/
|
|
43
|
+
enabled?: boolean;
|
|
44
|
+
}
|
|
12
45
|
|
|
13
46
|
/**
|
|
14
47
|
* Composition mode determines how the agent processes and structures responses
|
|
@@ -132,6 +165,18 @@ export interface AgentOptions<TContext = unknown, TData = unknown> {
|
|
|
132
165
|
* @default 1
|
|
133
166
|
*/
|
|
134
167
|
maxStepsPerBatch?: number;
|
|
168
|
+
/**
|
|
169
|
+
* Optional compaction configuration for managing conversation history size.
|
|
170
|
+
* When provided, the agent will validate the options and make them available
|
|
171
|
+
* for use by the SessionManager/CompactionEngine.
|
|
172
|
+
*/
|
|
173
|
+
compaction?: AgentCompactionConfig;
|
|
174
|
+
/**
|
|
175
|
+
* Optional prompt cache configuration for controlling section memoization behavior.
|
|
176
|
+
* When provided, controls whether prompt sections are cached across turns.
|
|
177
|
+
* @default { enabled: true }
|
|
178
|
+
*/
|
|
179
|
+
promptCache?: PromptCacheConfig;
|
|
135
180
|
}
|
|
136
181
|
|
|
137
182
|
/**
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context compaction types for managing conversation history size
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { AiProvider } from "./ai";
|
|
6
|
+
import type { HistoryItem } from "./history";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Configuration for the compaction engine.
|
|
10
|
+
*
|
|
11
|
+
* Validation constraints:
|
|
12
|
+
* - `compactionThreshold` must be between 0.5 and 0.95
|
|
13
|
+
* - `preserveRecentCount` must be >= 2
|
|
14
|
+
* - `maxToolResultChars` must be > 0
|
|
15
|
+
*/
|
|
16
|
+
export interface CompactionOptions {
|
|
17
|
+
/** Maximum token budget for the conversation */
|
|
18
|
+
maxTokens: number;
|
|
19
|
+
/**
|
|
20
|
+
* Threshold ratio (0–1) at which to trigger compaction.
|
|
21
|
+
* Must be between 0.5 and 0.95.
|
|
22
|
+
*/
|
|
23
|
+
compactionThreshold: number;
|
|
24
|
+
/**
|
|
25
|
+
* Number of recent messages to always preserve unchanged.
|
|
26
|
+
* Must be >= 2.
|
|
27
|
+
*/
|
|
28
|
+
preserveRecentCount: number;
|
|
29
|
+
/**
|
|
30
|
+
* Maximum characters per tool result before truncation.
|
|
31
|
+
* Must be > 0.
|
|
32
|
+
*/
|
|
33
|
+
maxToolResultChars: number;
|
|
34
|
+
/** Provider to use for LLM summarization during auto-compact */
|
|
35
|
+
provider: AiProvider;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Result of a compaction operation
|
|
40
|
+
*/
|
|
41
|
+
export interface CompactionResult {
|
|
42
|
+
/** The compacted history */
|
|
43
|
+
history: HistoryItem[];
|
|
44
|
+
/** Strategy that was applied */
|
|
45
|
+
strategy: 'none' | 'tool_result_budget' | 'micro_compact' | 'auto_compact';
|
|
46
|
+
/** Estimated tokens after compaction */
|
|
47
|
+
estimatedTokens: number;
|
|
48
|
+
/** Number of messages removed/compacted */
|
|
49
|
+
messagesCompacted: number;
|
|
50
|
+
/** Summary text (if auto-compact was used) */
|
|
51
|
+
summary?: string;
|
|
52
|
+
}
|
package/src/types/index.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// Agent types
|
|
6
6
|
export type {
|
|
7
7
|
AgentOptions,
|
|
8
|
+
AgentCompactionConfig,
|
|
8
9
|
Term,
|
|
9
10
|
Guideline,
|
|
10
11
|
GuidelineMatch,
|
|
@@ -58,12 +59,19 @@ export * from "./route";
|
|
|
58
59
|
export type { SessionState, PendingTransition } from "./session";
|
|
59
60
|
|
|
60
61
|
// Tool types
|
|
61
|
-
export type {
|
|
62
|
-
Tool,
|
|
63
|
-
ToolContext,
|
|
64
|
-
ToolResult,
|
|
62
|
+
export type {
|
|
63
|
+
Tool,
|
|
64
|
+
ToolContext,
|
|
65
|
+
ToolResult,
|
|
65
66
|
ToolHandler,
|
|
66
67
|
ToolExecutionResult,
|
|
68
|
+
EnhancedTool,
|
|
69
|
+
ToolValidationResult,
|
|
70
|
+
ToolPermissionResult,
|
|
71
|
+
ToolCallRequest,
|
|
72
|
+
ToolExecutionUpdate,
|
|
73
|
+
TrackedTool,
|
|
74
|
+
ToolStatus,
|
|
67
75
|
DataEnrichmentConfig,
|
|
68
76
|
ValidationConfig,
|
|
69
77
|
ApiCallConfig,
|
|
@@ -71,6 +79,19 @@ export type {
|
|
|
71
79
|
} from "./tool";
|
|
72
80
|
export { ToolScope } from "./tool";
|
|
73
81
|
|
|
82
|
+
// Compaction types
|
|
83
|
+
export type {
|
|
84
|
+
CompactionOptions,
|
|
85
|
+
CompactionResult,
|
|
86
|
+
} from "./compaction";
|
|
87
|
+
|
|
88
|
+
// Prompt cache types (re-exported from core)
|
|
89
|
+
export type {
|
|
90
|
+
PromptSectionType,
|
|
91
|
+
PromptCacheConfig,
|
|
92
|
+
SectionCompute,
|
|
93
|
+
} from "../core/PromptSectionCache";
|
|
94
|
+
|
|
74
95
|
// AI provider types
|
|
75
96
|
export type {
|
|
76
97
|
AiProvider,
|
|
@@ -105,15 +126,15 @@ export type {
|
|
|
105
126
|
export * from "./persistence";
|
|
106
127
|
|
|
107
128
|
// Template types
|
|
108
|
-
export type {
|
|
109
|
-
Template,
|
|
110
|
-
TemplateContext,
|
|
111
|
-
ConditionTemplate,
|
|
112
|
-
ConditionEvaluationResult
|
|
129
|
+
export type {
|
|
130
|
+
Template,
|
|
131
|
+
TemplateContext,
|
|
132
|
+
ConditionTemplate,
|
|
133
|
+
ConditionEvaluationResult
|
|
113
134
|
} from "./template";
|
|
114
|
-
export {
|
|
115
|
-
ConditionEvaluator,
|
|
116
|
-
createConditionEvaluator,
|
|
117
|
-
extractAIContextStrings,
|
|
118
|
-
hasProgrammaticConditions
|
|
135
|
+
export {
|
|
136
|
+
ConditionEvaluator,
|
|
137
|
+
createConditionEvaluator,
|
|
138
|
+
extractAIContextStrings,
|
|
139
|
+
hasProgrammaticConditions
|
|
119
140
|
} from "../utils/condition";
|
package/src/types/tool.ts
CHANGED
|
@@ -116,6 +116,114 @@ export enum ToolScope {
|
|
|
116
116
|
|
|
117
117
|
|
|
118
118
|
|
|
119
|
+
// --- EnhancedTool and supporting types ---
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Result of input validation on a tool call
|
|
123
|
+
*/
|
|
124
|
+
export interface ToolValidationResult {
|
|
125
|
+
valid: boolean;
|
|
126
|
+
error?: string;
|
|
127
|
+
/** Suggested corrected input */
|
|
128
|
+
correctedInput?: Record<string, unknown>;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Result of a permission check on a tool call
|
|
133
|
+
*/
|
|
134
|
+
export interface ToolPermissionResult {
|
|
135
|
+
allowed: boolean;
|
|
136
|
+
reason?: string;
|
|
137
|
+
/** If not allowed, can the user override? */
|
|
138
|
+
canOverride?: boolean;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* A single tool invocation request from the LLM
|
|
143
|
+
*/
|
|
144
|
+
export interface ToolCallRequest {
|
|
145
|
+
/** Unique ID for this tool call instance */
|
|
146
|
+
id: string;
|
|
147
|
+
/** Tool name/ID to execute */
|
|
148
|
+
toolName: string;
|
|
149
|
+
/** Arguments passed to the tool */
|
|
150
|
+
arguments: Record<string, unknown>;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Result or progress update from a tool execution
|
|
155
|
+
*/
|
|
156
|
+
export interface ToolExecutionUpdate<TData = unknown> {
|
|
157
|
+
/** The tool call this update relates to */
|
|
158
|
+
toolCallId: string;
|
|
159
|
+
/** Result message (undefined for progress updates) */
|
|
160
|
+
result?: ToolExecutionResult;
|
|
161
|
+
/** Progress message for long-running tools */
|
|
162
|
+
progress?: string;
|
|
163
|
+
/** Updated context after tool execution */
|
|
164
|
+
contextUpdate?: Record<string, unknown>;
|
|
165
|
+
/** Updated data after tool execution */
|
|
166
|
+
dataUpdate?: Partial<TData>;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Internal status of a tracked tool in the executor queue
|
|
171
|
+
*/
|
|
172
|
+
export type ToolStatus = 'queued' | 'executing' | 'completed' | 'yielded';
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Internal type tracking the state of a queued or executing tool
|
|
176
|
+
*/
|
|
177
|
+
export interface TrackedTool<TContext = unknown, TData = unknown> {
|
|
178
|
+
id: string;
|
|
179
|
+
toolCall: ToolCallRequest;
|
|
180
|
+
tool: EnhancedTool<TContext, TData>;
|
|
181
|
+
status: ToolStatus;
|
|
182
|
+
isConcurrencySafe: boolean;
|
|
183
|
+
promise?: Promise<void>;
|
|
184
|
+
results: ToolExecutionResult[];
|
|
185
|
+
pendingProgress: string[];
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Extended tool interface with rich metadata for concurrency control,
|
|
190
|
+
* permission gating, input validation, and result size management.
|
|
191
|
+
*
|
|
192
|
+
* All additional methods/properties are optional — plain `Tool` objects
|
|
193
|
+
* remain fully compatible.
|
|
194
|
+
*/
|
|
195
|
+
export interface EnhancedTool<
|
|
196
|
+
TContext = any,
|
|
197
|
+
TData = any,
|
|
198
|
+
TResult = any
|
|
199
|
+
> extends Tool<TContext, TData, TResult> {
|
|
200
|
+
/** Whether this tool is safe to run concurrently with other concurrent-safe tools */
|
|
201
|
+
isConcurrencySafe?(input?: Record<string, unknown>): boolean;
|
|
202
|
+
/** Whether this tool only reads data without side effects */
|
|
203
|
+
isReadOnly?(input?: Record<string, unknown>): boolean;
|
|
204
|
+
/** Whether this tool performs destructive/irreversible operations */
|
|
205
|
+
isDestructive?(input?: Record<string, unknown>): boolean;
|
|
206
|
+
|
|
207
|
+
/** How the tool responds to abort signals: 'cancel' = immediate abort, 'block' = allow completion */
|
|
208
|
+
interruptBehavior?(): 'cancel' | 'block';
|
|
209
|
+
/** Maximum characters for the tool result before truncation */
|
|
210
|
+
maxResultSizeChars?: number;
|
|
211
|
+
|
|
212
|
+
/** Validate input before execution */
|
|
213
|
+
validateInput?(
|
|
214
|
+
input: Record<string, unknown>,
|
|
215
|
+
context: ToolContext<TContext, TData>
|
|
216
|
+
): Promise<ToolValidationResult> | ToolValidationResult;
|
|
217
|
+
|
|
218
|
+
/** Check permissions before execution */
|
|
219
|
+
checkPermissions?(
|
|
220
|
+
input: Record<string, unknown>,
|
|
221
|
+
context: ToolContext<TContext, TData>
|
|
222
|
+
): Promise<ToolPermissionResult> | ToolPermissionResult;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// --- Existing tool configuration types ---
|
|
226
|
+
|
|
119
227
|
/**
|
|
120
228
|
* Configuration for data enrichment tools
|
|
121
229
|
*/
|