@falai/agent 2.6.0 → 2.7.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 +1 -1
- package/dist/adapters/MemoryAdapter.js +29 -33
- package/dist/adapters/MemoryAdapter.js.map +1 -1
- package/dist/adapters/PostgreSQLAdapter.d.ts.map +1 -1
- package/dist/adapters/PostgreSQLAdapter.js +16 -11
- package/dist/adapters/PostgreSQLAdapter.js.map +1 -1
- package/dist/adapters/RedisAdapter.d.ts +1 -0
- package/dist/adapters/RedisAdapter.d.ts.map +1 -1
- package/dist/adapters/RedisAdapter.js +75 -25
- package/dist/adapters/RedisAdapter.js.map +1 -1
- package/dist/adapters/SQLiteAdapter.d.ts.map +1 -1
- package/dist/adapters/SQLiteAdapter.js +7 -29
- package/dist/adapters/SQLiteAdapter.js.map +1 -1
- package/dist/adapters/sessionRow.d.ts +22 -0
- package/dist/adapters/sessionRow.d.ts.map +1 -0
- package/dist/adapters/sessionRow.js +48 -0
- package/dist/adapters/sessionRow.js.map +1 -0
- package/dist/cjs/adapters/MemoryAdapter.js +29 -33
- package/dist/cjs/adapters/MemoryAdapter.js.map +1 -1
- package/dist/cjs/adapters/PostgreSQLAdapter.d.ts.map +1 -1
- package/dist/cjs/adapters/PostgreSQLAdapter.js +16 -11
- package/dist/cjs/adapters/PostgreSQLAdapter.js.map +1 -1
- package/dist/cjs/adapters/RedisAdapter.d.ts +1 -0
- package/dist/cjs/adapters/RedisAdapter.d.ts.map +1 -1
- package/dist/cjs/adapters/RedisAdapter.js +75 -25
- package/dist/cjs/adapters/RedisAdapter.js.map +1 -1
- package/dist/cjs/adapters/SQLiteAdapter.d.ts.map +1 -1
- package/dist/cjs/adapters/SQLiteAdapter.js +7 -29
- package/dist/cjs/adapters/SQLiteAdapter.js.map +1 -1
- package/dist/cjs/adapters/sessionRow.d.ts +22 -0
- package/dist/cjs/adapters/sessionRow.d.ts.map +1 -0
- package/dist/cjs/adapters/sessionRow.js +52 -0
- package/dist/cjs/adapters/sessionRow.js.map +1 -0
- package/dist/cjs/core/Agent.d.ts +8 -0
- package/dist/cjs/core/Agent.d.ts.map +1 -1
- package/dist/cjs/core/Agent.js +43 -3
- package/dist/cjs/core/Agent.js.map +1 -1
- package/dist/cjs/core/AutoChainExecutor.d.ts +8 -18
- package/dist/cjs/core/AutoChainExecutor.d.ts.map +1 -1
- package/dist/cjs/core/AutoChainExecutor.js +23 -26
- package/dist/cjs/core/AutoChainExecutor.js.map +1 -1
- package/dist/cjs/core/CompactionEngine.d.ts +14 -1
- package/dist/cjs/core/CompactionEngine.d.ts.map +1 -1
- package/dist/cjs/core/CompactionEngine.js +30 -6
- package/dist/cjs/core/CompactionEngine.js.map +1 -1
- package/dist/cjs/core/FlowRouter.d.ts.map +1 -1
- package/dist/cjs/core/FlowRouter.js +32 -10
- package/dist/cjs/core/FlowRouter.js.map +1 -1
- package/dist/cjs/core/ResponseGenerationError.d.ts.map +1 -1
- package/dist/cjs/core/ResponseGenerationError.js +3 -5
- package/dist/cjs/core/ResponseGenerationError.js.map +1 -1
- package/dist/cjs/core/ResponseModal.d.ts +29 -0
- package/dist/cjs/core/ResponseModal.d.ts.map +1 -1
- package/dist/cjs/core/ResponseModal.js +141 -18
- package/dist/cjs/core/ResponseModal.js.map +1 -1
- package/dist/cjs/core/ResponsePipeline.d.ts +36 -6
- package/dist/cjs/core/ResponsePipeline.d.ts.map +1 -1
- package/dist/cjs/core/ResponsePipeline.js +208 -73
- package/dist/cjs/core/ResponsePipeline.js.map +1 -1
- package/dist/cjs/core/SessionFinalizer.d.ts.map +1 -1
- package/dist/cjs/core/SessionFinalizer.js +30 -3
- package/dist/cjs/core/SessionFinalizer.js.map +1 -1
- package/dist/cjs/core/SessionManager.d.ts +10 -1
- package/dist/cjs/core/SessionManager.d.ts.map +1 -1
- package/dist/cjs/core/SessionManager.js +43 -16
- package/dist/cjs/core/SessionManager.js.map +1 -1
- package/dist/cjs/core/SignalProcessor.d.ts.map +1 -1
- package/dist/cjs/core/SignalProcessor.js +5 -77
- package/dist/cjs/core/SignalProcessor.js.map +1 -1
- package/dist/cjs/core/Step.d.ts.map +1 -1
- package/dist/cjs/core/Step.js +50 -2
- package/dist/cjs/core/Step.js.map +1 -1
- package/dist/cjs/core/StepLifecycle.d.ts +16 -6
- package/dist/cjs/core/StepLifecycle.d.ts.map +1 -1
- package/dist/cjs/core/StepLifecycle.js +97 -14
- package/dist/cjs/core/StepLifecycle.js.map +1 -1
- package/dist/cjs/core/StreamingToolExecutor.d.ts.map +1 -1
- package/dist/cjs/core/StreamingToolExecutor.js +28 -4
- package/dist/cjs/core/StreamingToolExecutor.js.map +1 -1
- package/dist/cjs/core/ToolLoopExecutor.d.ts +5 -1
- package/dist/cjs/core/ToolLoopExecutor.d.ts.map +1 -1
- package/dist/cjs/core/ToolLoopExecutor.js +156 -64
- package/dist/cjs/core/ToolLoopExecutor.js.map +1 -1
- package/dist/cjs/core/ToolManager.d.ts +1 -1
- package/dist/cjs/core/ToolManager.d.ts.map +1 -1
- package/dist/cjs/core/ToolManager.js +40 -17
- package/dist/cjs/core/ToolManager.js.map +1 -1
- package/dist/cjs/core/flow-namespace.d.ts +15 -0
- package/dist/cjs/core/flow-namespace.d.ts.map +1 -1
- package/dist/cjs/core/flow-namespace.js +22 -0
- package/dist/cjs/core/flow-namespace.js.map +1 -1
- package/dist/cjs/index.d.ts +4 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +5 -2
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/providers/AnthropicProvider.d.ts +10 -3
- package/dist/cjs/providers/AnthropicProvider.d.ts.map +1 -1
- package/dist/cjs/providers/AnthropicProvider.js +54 -73
- package/dist/cjs/providers/AnthropicProvider.js.map +1 -1
- package/dist/cjs/providers/GeminiProvider.d.ts +9 -3
- package/dist/cjs/providers/GeminiProvider.d.ts.map +1 -1
- package/dist/cjs/providers/GeminiProvider.js +24 -68
- package/dist/cjs/providers/GeminiProvider.js.map +1 -1
- package/dist/cjs/providers/OpenAICompatibleProvider.d.ts +9 -0
- package/dist/cjs/providers/OpenAICompatibleProvider.d.ts.map +1 -1
- package/dist/cjs/providers/OpenAICompatibleProvider.js +101 -74
- package/dist/cjs/providers/OpenAICompatibleProvider.js.map +1 -1
- package/dist/cjs/providers/OpenAIProvider.d.ts +1 -1
- package/dist/cjs/providers/OpenAIProvider.d.ts.map +1 -1
- package/dist/cjs/providers/OpenAIProvider.js +1 -1
- package/dist/cjs/providers/errorClassification.d.ts +7 -0
- package/dist/cjs/providers/errorClassification.d.ts.map +1 -1
- package/dist/cjs/providers/errorClassification.js +27 -0
- package/dist/cjs/providers/errorClassification.js.map +1 -1
- package/dist/cjs/types/agent.d.ts +35 -0
- package/dist/cjs/types/agent.d.ts.map +1 -1
- package/dist/cjs/types/flow.d.ts +49 -32
- package/dist/cjs/types/flow.d.ts.map +1 -1
- package/dist/cjs/types/index.d.ts +1 -1
- 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 +2 -0
- package/dist/cjs/types/tool.d.ts.map +1 -1
- package/dist/cjs/types/tool.js.map +1 -1
- package/dist/cjs/utils/index.d.ts +4 -4
- package/dist/cjs/utils/index.d.ts.map +1 -1
- package/dist/cjs/utils/index.js +10 -1
- package/dist/cjs/utils/index.js.map +1 -1
- package/dist/cjs/utils/retry.d.ts +99 -1
- package/dist/cjs/utils/retry.d.ts.map +1 -1
- package/dist/cjs/utils/retry.js +140 -3
- package/dist/cjs/utils/retry.js.map +1 -1
- package/dist/cjs/utils/serialize.d.ts +17 -0
- package/dist/cjs/utils/serialize.d.ts.map +1 -1
- package/dist/cjs/utils/serialize.js +33 -0
- package/dist/cjs/utils/serialize.js.map +1 -1
- package/dist/cjs/utils/session.d.ts +21 -1
- package/dist/cjs/utils/session.d.ts.map +1 -1
- package/dist/cjs/utils/session.js +36 -4
- package/dist/cjs/utils/session.js.map +1 -1
- package/dist/core/Agent.d.ts +8 -0
- package/dist/core/Agent.d.ts.map +1 -1
- package/dist/core/Agent.js +43 -3
- package/dist/core/Agent.js.map +1 -1
- package/dist/core/AutoChainExecutor.d.ts +8 -18
- package/dist/core/AutoChainExecutor.d.ts.map +1 -1
- package/dist/core/AutoChainExecutor.js +23 -26
- package/dist/core/AutoChainExecutor.js.map +1 -1
- package/dist/core/CompactionEngine.d.ts +14 -1
- package/dist/core/CompactionEngine.d.ts.map +1 -1
- package/dist/core/CompactionEngine.js +30 -6
- package/dist/core/CompactionEngine.js.map +1 -1
- package/dist/core/FlowRouter.d.ts.map +1 -1
- package/dist/core/FlowRouter.js +32 -10
- package/dist/core/FlowRouter.js.map +1 -1
- package/dist/core/ResponseGenerationError.d.ts.map +1 -1
- package/dist/core/ResponseGenerationError.js +3 -5
- package/dist/core/ResponseGenerationError.js.map +1 -1
- package/dist/core/ResponseModal.d.ts +29 -0
- package/dist/core/ResponseModal.d.ts.map +1 -1
- package/dist/core/ResponseModal.js +142 -19
- package/dist/core/ResponseModal.js.map +1 -1
- package/dist/core/ResponsePipeline.d.ts +36 -6
- package/dist/core/ResponsePipeline.d.ts.map +1 -1
- package/dist/core/ResponsePipeline.js +208 -73
- package/dist/core/ResponsePipeline.js.map +1 -1
- package/dist/core/SessionFinalizer.d.ts.map +1 -1
- package/dist/core/SessionFinalizer.js +31 -4
- package/dist/core/SessionFinalizer.js.map +1 -1
- package/dist/core/SessionManager.d.ts +10 -1
- package/dist/core/SessionManager.d.ts.map +1 -1
- package/dist/core/SessionManager.js +44 -17
- package/dist/core/SessionManager.js.map +1 -1
- package/dist/core/SignalProcessor.d.ts.map +1 -1
- package/dist/core/SignalProcessor.js +5 -77
- package/dist/core/SignalProcessor.js.map +1 -1
- package/dist/core/Step.d.ts.map +1 -1
- package/dist/core/Step.js +50 -2
- package/dist/core/Step.js.map +1 -1
- package/dist/core/StepLifecycle.d.ts +16 -6
- package/dist/core/StepLifecycle.d.ts.map +1 -1
- package/dist/core/StepLifecycle.js +97 -14
- package/dist/core/StepLifecycle.js.map +1 -1
- package/dist/core/StreamingToolExecutor.d.ts.map +1 -1
- package/dist/core/StreamingToolExecutor.js +28 -4
- package/dist/core/StreamingToolExecutor.js.map +1 -1
- package/dist/core/ToolLoopExecutor.d.ts +5 -1
- package/dist/core/ToolLoopExecutor.d.ts.map +1 -1
- package/dist/core/ToolLoopExecutor.js +156 -64
- package/dist/core/ToolLoopExecutor.js.map +1 -1
- package/dist/core/ToolManager.d.ts +1 -1
- package/dist/core/ToolManager.d.ts.map +1 -1
- package/dist/core/ToolManager.js +41 -18
- package/dist/core/ToolManager.js.map +1 -1
- package/dist/core/flow-namespace.d.ts +15 -0
- package/dist/core/flow-namespace.d.ts.map +1 -1
- package/dist/core/flow-namespace.js +22 -0
- package/dist/core/flow-namespace.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/providers/AnthropicProvider.d.ts +10 -3
- package/dist/providers/AnthropicProvider.d.ts.map +1 -1
- package/dist/providers/AnthropicProvider.js +56 -75
- package/dist/providers/AnthropicProvider.js.map +1 -1
- package/dist/providers/GeminiProvider.d.ts +9 -3
- package/dist/providers/GeminiProvider.d.ts.map +1 -1
- package/dist/providers/GeminiProvider.js +26 -70
- package/dist/providers/GeminiProvider.js.map +1 -1
- package/dist/providers/OpenAICompatibleProvider.d.ts +9 -0
- package/dist/providers/OpenAICompatibleProvider.d.ts.map +1 -1
- package/dist/providers/OpenAICompatibleProvider.js +103 -76
- package/dist/providers/OpenAICompatibleProvider.js.map +1 -1
- package/dist/providers/OpenAIProvider.d.ts +1 -1
- package/dist/providers/OpenAIProvider.d.ts.map +1 -1
- package/dist/providers/OpenAIProvider.js +1 -1
- package/dist/providers/errorClassification.d.ts +7 -0
- package/dist/providers/errorClassification.d.ts.map +1 -1
- package/dist/providers/errorClassification.js +26 -0
- package/dist/providers/errorClassification.js.map +1 -1
- package/dist/types/agent.d.ts +35 -0
- package/dist/types/agent.d.ts.map +1 -1
- package/dist/types/flow.d.ts +49 -32
- package/dist/types/flow.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types/tool.d.ts +2 -0
- package/dist/types/tool.d.ts.map +1 -1
- package/dist/types/tool.js.map +1 -1
- package/dist/utils/index.d.ts +4 -4
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +3 -3
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/retry.d.ts +99 -1
- package/dist/utils/retry.d.ts.map +1 -1
- package/dist/utils/retry.js +137 -3
- package/dist/utils/retry.js.map +1 -1
- package/dist/utils/serialize.d.ts +17 -0
- package/dist/utils/serialize.d.ts.map +1 -1
- package/dist/utils/serialize.js +31 -0
- package/dist/utils/serialize.js.map +1 -1
- package/dist/utils/session.d.ts +21 -1
- package/dist/utils/session.d.ts.map +1 -1
- package/dist/utils/session.js +33 -4
- package/dist/utils/session.js.map +1 -1
- package/docs/concepts/architecture.md +3 -3
- package/docs/concepts/directives.md +1 -1
- package/docs/guides/error-handling.md +46 -45
- package/docs/guides/flow-control.md +8 -1
- package/docs/guides/instructions.md +15 -6
- package/docs/guides/persistence.md +12 -5
- package/docs/guides/streaming.md +10 -0
- package/docs/migration/README.md +4 -0
- package/docs/migration/v2-3-to-v2-4.md +4 -0
- package/docs/migration/v2-6-to-v2-7.md +246 -0
- package/docs/reference/adapters.md +15 -1
- package/docs/reference/create-agent.md +30 -0
- package/docs/reference/directive.md +1 -1
- package/docs/reference/errors.md +29 -31
- package/docs/reference/providers.md +23 -19
- package/docs/reference/step.md +28 -21
- package/docs/reference/tool.md +14 -5
- package/docs/start/02-first-agent.md +8 -4
- package/docs/start/03-collect-data.md +19 -10
- package/examples/01-quickstart.ts +1 -1
- package/examples/02-data-extraction.ts +1 -1
- package/examples/03-tools.ts +1 -1
- package/examples/04-instructions.ts +1 -1
- package/examples/05-branching.ts +1 -1
- package/examples/06-flow-control.ts +3 -3
- package/examples/07-streaming.ts +1 -1
- package/examples/08-persistence.ts +1 -1
- package/examples/09-signals.ts +1 -1
- package/package.json +2 -2
- package/src/adapters/MemoryAdapter.ts +29 -33
- package/src/adapters/PostgreSQLAdapter.ts +23 -18
- package/src/adapters/RedisAdapter.ts +81 -34
- package/src/adapters/SQLiteAdapter.ts +10 -31
- package/src/adapters/sessionRow.ts +57 -0
- package/src/core/Agent.ts +51 -3
- package/src/core/AutoChainExecutor.ts +37 -50
- package/src/core/CompactionEngine.ts +40 -6
- package/src/core/FlowRouter.ts +38 -12
- package/src/core/ResponseGenerationError.ts +3 -6
- package/src/core/ResponseModal.ts +185 -19
- package/src/core/ResponsePipeline.ts +259 -85
- package/src/core/SessionFinalizer.ts +35 -5
- package/src/core/SessionManager.ts +55 -21
- package/src/core/SignalProcessor.ts +5 -85
- package/src/core/Step.ts +79 -2
- package/src/core/StepLifecycle.ts +125 -22
- package/src/core/StreamingToolExecutor.ts +31 -6
- package/src/core/ToolLoopExecutor.ts +201 -75
- package/src/core/ToolManager.ts +45 -18
- package/src/core/flow-namespace.ts +29 -0
- package/src/index.ts +4 -1
- package/src/providers/AnthropicProvider.ts +83 -131
- package/src/providers/GeminiProvider.ts +42 -116
- package/src/providers/OpenAICompatibleProvider.ts +132 -133
- package/src/providers/OpenAIProvider.ts +2 -2
- package/src/providers/errorClassification.ts +34 -0
- package/src/types/agent.ts +34 -0
- package/src/types/flow.ts +37 -39
- package/src/types/index.ts +1 -0
- package/src/types/tool.ts +2 -0
- package/src/utils/index.ts +22 -3
- package/src/utils/retry.ts +251 -2
- package/src/utils/serialize.ts +38 -0
- package/src/utils/session.ts +41 -4
- package/dist/cjs/core/DirectiveBus.d.ts +0 -88
- package/dist/cjs/core/DirectiveBus.d.ts.map +0 -1
- package/dist/cjs/core/DirectiveBus.js +0 -196
- package/dist/cjs/core/DirectiveBus.js.map +0 -1
- package/dist/core/DirectiveBus.d.ts +0 -88
- package/dist/core/DirectiveBus.d.ts.map +0 -1
- package/dist/core/DirectiveBus.js +0 -192
- package/dist/core/DirectiveBus.js.map +0 -1
- package/src/core/DirectiveBus.ts +0 -248
|
@@ -21,14 +21,14 @@ import type {
|
|
|
21
21
|
} from "../types";
|
|
22
22
|
import type { ProviderCapabilities } from "../types/ai";
|
|
23
23
|
import type { HistoryItem } from "../types/history";
|
|
24
|
-
import { withTimeoutAndRetry, withStreamRetry, resolveRetryConfig, combineAbortSignals } from "../utils/retry";
|
|
24
|
+
import { withTimeoutAndRetry, withStreamRetry, withBackupFallback, streamWithBackupFallback, backupFallbackLogging, resolveRetryConfig, combineAbortSignals } from "../utils/retry";
|
|
25
25
|
import { assertUsableCompletion } from "../utils/completion";
|
|
26
26
|
import { tryParseJSONResponse } from "../utils/json";
|
|
27
27
|
import { logger } from "../utils/logger";
|
|
28
28
|
import {
|
|
29
29
|
classifyProviderError,
|
|
30
|
-
getErrorMessage,
|
|
31
30
|
isBackupEligible,
|
|
31
|
+
isRetriableProviderError,
|
|
32
32
|
toProviderError,
|
|
33
33
|
type ErrorClassificationOptions,
|
|
34
34
|
} from "./errorClassification";
|
|
@@ -38,8 +38,8 @@ import {
|
|
|
38
38
|
* Uses types from @google/genai package
|
|
39
39
|
*/
|
|
40
40
|
export interface GeminiProviderOptions {
|
|
41
|
-
/** Gemini API key */
|
|
42
|
-
apiKey
|
|
41
|
+
/** Gemini API key. Optional when `client` is injected (tests). */
|
|
42
|
+
apiKey?: string;
|
|
43
43
|
/** Model to use (required) - e.g., "gemini-3.1-pro-preview" */
|
|
44
44
|
model: string;
|
|
45
45
|
/** Backup models to try if primary fails (default: []) */
|
|
@@ -51,6 +51,12 @@ export interface GeminiProviderOptions {
|
|
|
51
51
|
timeout?: number;
|
|
52
52
|
retries?: number;
|
|
53
53
|
};
|
|
54
|
+
/**
|
|
55
|
+
* Pre-configured SDK client. Overrides the internally-constructed one.
|
|
56
|
+
* Intended for tests injecting scripted transports; production callers
|
|
57
|
+
* should pass `apiKey` instead.
|
|
58
|
+
*/
|
|
59
|
+
client?: GoogleGenAIType;
|
|
54
60
|
}
|
|
55
61
|
|
|
56
62
|
/**
|
|
@@ -88,9 +94,9 @@ export class GeminiProvider implements AiProvider {
|
|
|
88
94
|
private retryConfig: { timeout: number; retries: number };
|
|
89
95
|
|
|
90
96
|
constructor(options: GeminiProviderOptions) {
|
|
91
|
-
const { apiKey, model, backupModels = [], config, retryConfig } = options;
|
|
97
|
+
const { apiKey, model, backupModels = [], config, retryConfig, client } = options;
|
|
92
98
|
|
|
93
|
-
if (!apiKey) {
|
|
99
|
+
if (!client && !apiKey) {
|
|
94
100
|
throw new Error("Gemini API key is required");
|
|
95
101
|
}
|
|
96
102
|
|
|
@@ -98,7 +104,7 @@ export class GeminiProvider implements AiProvider {
|
|
|
98
104
|
throw new Error("Model is required. Example: 'gemini-3.1-pro-preview'");
|
|
99
105
|
}
|
|
100
106
|
|
|
101
|
-
this.genAI = new GoogleGenAI({ apiKey });
|
|
107
|
+
this.genAI = client ?? new GoogleGenAI({ apiKey });
|
|
102
108
|
this.primaryModel = model;
|
|
103
109
|
this.backupModels = backupModels;
|
|
104
110
|
this.config = config;
|
|
@@ -352,58 +358,19 @@ export class GeminiProvider implements AiProvider {
|
|
|
352
358
|
>(
|
|
353
359
|
input: GenerateMessageInput<TContext>
|
|
354
360
|
): Promise<GenerateMessageOutput<TStructured>> {
|
|
355
|
-
|
|
356
|
-
try {
|
|
357
|
-
return await this.generateWithModel(this.primaryModel, input);
|
|
358
|
-
} catch (primaryError: unknown) {
|
|
359
|
-
const primaryErrMsg = getErrorMessage(primaryError);
|
|
360
|
-
logger.warn(
|
|
361
|
-
`[GEMINI] Primary model ${this.primaryModel} failed: ${primaryErrMsg}`
|
|
362
|
-
);
|
|
363
|
-
|
|
364
|
-
if (!shouldUseBackupModel(primaryError)) {
|
|
365
|
-
throw toProviderError(primaryError, this.name, CLASSIFICATION_OPTIONS);
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
logger.debug(`[GEMINI] Trying backup models`);
|
|
369
|
-
|
|
370
|
-
let lastBackupError: unknown = primaryError;
|
|
371
|
-
|
|
372
|
-
for (let i = 0; i < this.backupModels.length; i++) {
|
|
373
|
-
const backupModel = this.backupModels[i];
|
|
374
|
-
logger.debug(
|
|
375
|
-
`[GEMINI] Trying backup model ${i + 1}/${this.backupModels.length
|
|
376
|
-
}: ${backupModel}`
|
|
377
|
-
);
|
|
361
|
+
const observer = backupFallbackLogging("[GEMINI]", shouldUseBackupModel);
|
|
378
362
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
if (
|
|
391
|
-
!shouldUseBackupModel(backupError) &&
|
|
392
|
-
i < this.backupModels.length - 1
|
|
393
|
-
) {
|
|
394
|
-
logger.debug(
|
|
395
|
-
`[GEMINI] Backup model error doesn't qualify for further attempts`
|
|
396
|
-
);
|
|
397
|
-
break;
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
const lastBackupErrMsg = getErrorMessage(lastBackupError);
|
|
403
|
-
logger.error(
|
|
404
|
-
`[GEMINI] All models failed. Primary: ${primaryErrMsg}, Last backup: ${lastBackupErrMsg}`
|
|
405
|
-
);
|
|
406
|
-
throw toProviderError(lastBackupError, this.name, CLASSIFICATION_OPTIONS);
|
|
363
|
+
try {
|
|
364
|
+
return await withBackupFallback({
|
|
365
|
+
models: [this.primaryModel, ...this.backupModels],
|
|
366
|
+
attempt: (model) =>
|
|
367
|
+
this.generateWithModel<TContext, TStructured>(model, input),
|
|
368
|
+
shouldTryBackup: shouldUseBackupModel,
|
|
369
|
+
...observer.callbacks,
|
|
370
|
+
});
|
|
371
|
+
} catch (error: unknown) {
|
|
372
|
+
observer.logExhausted(error);
|
|
373
|
+
throw toProviderError(error, this.name, CLASSIFICATION_OPTIONS);
|
|
407
374
|
}
|
|
408
375
|
}
|
|
409
376
|
|
|
@@ -555,7 +522,8 @@ export class GeminiProvider implements AiProvider {
|
|
|
555
522
|
operation,
|
|
556
523
|
this.retryConfig.timeout,
|
|
557
524
|
this.retryConfig.retries,
|
|
558
|
-
`Gemini ${model}
|
|
525
|
+
`Gemini ${model}`,
|
|
526
|
+
(error) => isRetriableProviderError(error, CLASSIFICATION_OPTIONS)
|
|
559
527
|
) as Promise<GenerateMessageOutput<TStructured>>;
|
|
560
528
|
}
|
|
561
529
|
|
|
@@ -565,64 +533,22 @@ export class GeminiProvider implements AiProvider {
|
|
|
565
533
|
>(
|
|
566
534
|
input: GenerateMessageInput<TContext>
|
|
567
535
|
): AsyncGenerator<GenerateMessageStreamChunk<TStructured>> {
|
|
568
|
-
|
|
569
|
-
try {
|
|
570
|
-
yield* withStreamRetry(
|
|
571
|
-
(signal) => this.generateStreamWithModel(this.primaryModel, input, signal),
|
|
572
|
-
{ maxRetries: this.retryConfig.retries, firstChunkTimeoutMs: this.retryConfig.timeout, operationName: `Gemini ${this.primaryModel} stream` }
|
|
573
|
-
);
|
|
574
|
-
} catch (primaryError: unknown) {
|
|
575
|
-
const primaryErrMsg = getErrorMessage(primaryError);
|
|
576
|
-
logger.warn(
|
|
577
|
-
`[GEMINI] Primary model ${this.primaryModel} failed: ${primaryErrMsg}`
|
|
578
|
-
);
|
|
579
|
-
|
|
580
|
-
if (!shouldUseBackupModel(primaryError)) {
|
|
581
|
-
throw toProviderError(primaryError, this.name, CLASSIFICATION_OPTIONS);
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
logger.debug(`[GEMINI] Trying backup models for streaming`);
|
|
585
|
-
|
|
586
|
-
let lastBackupError: unknown = primaryError;
|
|
587
|
-
|
|
588
|
-
for (let i = 0; i < this.backupModels.length; i++) {
|
|
589
|
-
const backupModel = this.backupModels[i];
|
|
590
|
-
logger.debug(
|
|
591
|
-
`[GEMINI] Trying backup model ${i + 1}/${this.backupModels.length
|
|
592
|
-
}: ${backupModel}`
|
|
593
|
-
);
|
|
536
|
+
const observer = backupFallbackLogging("[GEMINI]", shouldUseBackupModel, { streaming: true });
|
|
594
537
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
if (
|
|
610
|
-
!shouldUseBackupModel(backupError) &&
|
|
611
|
-
i < this.backupModels.length - 1
|
|
612
|
-
) {
|
|
613
|
-
logger.debug(
|
|
614
|
-
`[GEMINI] Backup model error doesn't qualify for further attempts`
|
|
615
|
-
);
|
|
616
|
-
break;
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
const lastBackupErrMsg = getErrorMessage(lastBackupError);
|
|
622
|
-
logger.error(
|
|
623
|
-
`[GEMINI] All models failed. Primary: ${primaryErrMsg}, Last backup: ${lastBackupErrMsg}`
|
|
624
|
-
);
|
|
625
|
-
throw toProviderError(lastBackupError, this.name, CLASSIFICATION_OPTIONS);
|
|
538
|
+
try {
|
|
539
|
+
yield* streamWithBackupFallback({
|
|
540
|
+
models: [this.primaryModel, ...this.backupModels],
|
|
541
|
+
attempt: (model) =>
|
|
542
|
+
withStreamRetry(
|
|
543
|
+
(signal) => this.generateStreamWithModel<TContext, TStructured>(model, input, signal),
|
|
544
|
+
{ maxRetries: this.retryConfig.retries, firstChunkTimeoutMs: this.retryConfig.timeout, operationName: `Gemini ${model} stream`, isRetriable: (error) => isRetriableProviderError(error, CLASSIFICATION_OPTIONS) }
|
|
545
|
+
),
|
|
546
|
+
shouldTryBackup: shouldUseBackupModel,
|
|
547
|
+
...observer.callbacks,
|
|
548
|
+
});
|
|
549
|
+
} catch (error: unknown) {
|
|
550
|
+
observer.logExhausted(error);
|
|
551
|
+
throw toProviderError(error, this.name, CLASSIFICATION_OPTIONS);
|
|
626
552
|
}
|
|
627
553
|
}
|
|
628
554
|
|
|
@@ -27,11 +27,13 @@ import type {
|
|
|
27
27
|
} from "../types";
|
|
28
28
|
import type { ProviderCapabilities } from "../types/ai";
|
|
29
29
|
import type { HistoryItem } from "../types/history";
|
|
30
|
-
import {
|
|
30
|
+
import type { ResponseInput } from "openai/resources/responses/responses";
|
|
31
|
+
import { withTimeoutAndRetry, withStreamRetry, withBackupFallback, streamWithBackupFallback, backupFallbackLogging, resolveRetryConfig, logger, assertUsableCompletion, combineAbortSignals } from "../utils";
|
|
31
32
|
import {
|
|
32
33
|
classifyProviderError,
|
|
33
34
|
getErrorMessage,
|
|
34
35
|
isBackupEligible,
|
|
36
|
+
isRetriableProviderError,
|
|
35
37
|
toProviderError,
|
|
36
38
|
type ErrorClassificationOptions,
|
|
37
39
|
} from "./errorClassification";
|
|
@@ -142,6 +144,56 @@ export abstract class OpenAICompatibleProvider implements AiProvider {
|
|
|
142
144
|
// Provider-specific hooks
|
|
143
145
|
// ---------------------------------------------------------------------
|
|
144
146
|
|
|
147
|
+
/**
|
|
148
|
+
* Map interaction history onto Responses-API input items so structured
|
|
149
|
+
* generations carry the same context as chat completions: plain messages
|
|
150
|
+
* pass through as easy-input messages, and tool exchanges map to
|
|
151
|
+
* function_call / function_call_output pairs keyed by the original ids.
|
|
152
|
+
* The prompt is appended as the final user turn, mirroring how the
|
|
153
|
+
* chat-completions path builds its messages array.
|
|
154
|
+
*/
|
|
155
|
+
private buildResponsesInput(history: HistoryItem[], prompt: string): ResponseInput {
|
|
156
|
+
const items: ResponseInput = [];
|
|
157
|
+
|
|
158
|
+
for (const item of history) {
|
|
159
|
+
switch (item.role) {
|
|
160
|
+
case "system":
|
|
161
|
+
items.push({ role: "system", content: item.content });
|
|
162
|
+
break;
|
|
163
|
+
case "user":
|
|
164
|
+
items.push({ role: "user", content: item.content });
|
|
165
|
+
break;
|
|
166
|
+
case "assistant": {
|
|
167
|
+
if (item.content) {
|
|
168
|
+
items.push({ role: "assistant", content: item.content });
|
|
169
|
+
}
|
|
170
|
+
for (const toolCall of item.tool_calls ?? []) {
|
|
171
|
+
items.push({
|
|
172
|
+
type: "function_call",
|
|
173
|
+
call_id: toolCall.id,
|
|
174
|
+
name: toolCall.name,
|
|
175
|
+
arguments: JSON.stringify(toolCall.arguments),
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
case "tool":
|
|
181
|
+
items.push({
|
|
182
|
+
type: "function_call_output",
|
|
183
|
+
call_id: item.tool_call_id,
|
|
184
|
+
output:
|
|
185
|
+
typeof item.content === "string"
|
|
186
|
+
? item.content
|
|
187
|
+
: JSON.stringify(item.content),
|
|
188
|
+
});
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
items.push({ role: "user", content: prompt });
|
|
194
|
+
return items;
|
|
195
|
+
}
|
|
196
|
+
|
|
145
197
|
/**
|
|
146
198
|
* Generate a structured (JSON schema) response. Default uses the
|
|
147
199
|
* responses.parse API (OpenAI, OpenRouter). Subclasses whose API lacks
|
|
@@ -162,8 +214,10 @@ export abstract class OpenAICompatibleProvider implements AiProvider {
|
|
|
162
214
|
const response = await this.client.responses.parse(
|
|
163
215
|
{
|
|
164
216
|
model,
|
|
165
|
-
|
|
166
|
-
|
|
217
|
+
// Full conversation context: mapped history plus the prompt as the
|
|
218
|
+
// final user turn. This used to send only `instructions` with an empty
|
|
219
|
+
// `input`, leaving every structured turn without conversation memory.
|
|
220
|
+
input: this.buildResponsesInput(input.history, input.prompt),
|
|
167
221
|
reasoning: {
|
|
168
222
|
effort: input.parameters?.reasoning?.effort || "low",
|
|
169
223
|
},
|
|
@@ -368,65 +422,22 @@ export abstract class OpenAICompatibleProvider implements AiProvider {
|
|
|
368
422
|
>(
|
|
369
423
|
input: GenerateMessageInput<TContext>
|
|
370
424
|
): Promise<GenerateMessageOutput<TStructured>> {
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
input
|
|
376
|
-
);
|
|
377
|
-
} catch (primaryError: unknown) {
|
|
378
|
-
const primaryErrMsg = getErrorMessage(primaryError);
|
|
379
|
-
logger.warn(
|
|
380
|
-
`[${this.logLabel}] Primary model ${this.primaryModel} failed: ${primaryErrMsg}`
|
|
381
|
-
);
|
|
382
|
-
|
|
383
|
-
if (!this.shouldUseBackupModel(primaryError)) {
|
|
384
|
-
throw this.wrapTerminalError(primaryError);
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
logger.debug(`[${this.logLabel}] Trying backup models`);
|
|
388
|
-
|
|
389
|
-
let lastBackupError: unknown = primaryError;
|
|
390
|
-
|
|
391
|
-
for (let i = 0; i < this.backupModels.length; i++) {
|
|
392
|
-
const backupModel = this.backupModels[i];
|
|
393
|
-
logger.debug(
|
|
394
|
-
`[${this.logLabel}] Trying backup model ${i + 1}/${
|
|
395
|
-
this.backupModels.length
|
|
396
|
-
}: ${backupModel}`
|
|
397
|
-
);
|
|
398
|
-
|
|
399
|
-
try {
|
|
400
|
-
const result = await this.generateWithModel<TContext, TStructured>(
|
|
401
|
-
backupModel,
|
|
402
|
-
input
|
|
403
|
-
);
|
|
404
|
-
logger.debug(`[${this.logLabel}] Backup model ${backupModel} succeeded`);
|
|
405
|
-
return result;
|
|
406
|
-
} catch (backupError: unknown) {
|
|
407
|
-
const backupErrMsg = getErrorMessage(backupError);
|
|
408
|
-
logger.warn(
|
|
409
|
-
`[${this.logLabel}] Backup model ${backupModel} failed: ${backupErrMsg}`
|
|
410
|
-
);
|
|
411
|
-
lastBackupError = backupError;
|
|
412
|
-
|
|
413
|
-
if (
|
|
414
|
-
!this.shouldUseBackupModel(backupError) &&
|
|
415
|
-
i < this.backupModels.length - 1
|
|
416
|
-
) {
|
|
417
|
-
logger.debug(
|
|
418
|
-
`[${this.logLabel}] Backup model error doesn't qualify for further attempts`
|
|
419
|
-
);
|
|
420
|
-
break;
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
}
|
|
425
|
+
const observer = backupFallbackLogging(
|
|
426
|
+
`[${this.logLabel}]`,
|
|
427
|
+
(error) => this.shouldUseBackupModel(error)
|
|
428
|
+
);
|
|
424
429
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
+
try {
|
|
431
|
+
return await withBackupFallback({
|
|
432
|
+
models: [this.primaryModel, ...this.backupModels],
|
|
433
|
+
attempt: (model) =>
|
|
434
|
+
this.generateWithModel<TContext, TStructured>(model, input),
|
|
435
|
+
shouldTryBackup: (error) => this.shouldUseBackupModel(error),
|
|
436
|
+
...observer.callbacks,
|
|
437
|
+
});
|
|
438
|
+
} catch (error: unknown) {
|
|
439
|
+
observer.logExhausted(error);
|
|
440
|
+
throw this.wrapTerminalError(error);
|
|
430
441
|
}
|
|
431
442
|
}
|
|
432
443
|
|
|
@@ -436,65 +447,26 @@ export abstract class OpenAICompatibleProvider implements AiProvider {
|
|
|
436
447
|
>(
|
|
437
448
|
input: GenerateMessageInput<TContext>
|
|
438
449
|
): AsyncGenerator<GenerateMessageStreamChunk<TStructured>> {
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
);
|
|
445
|
-
} catch (primaryError: unknown) {
|
|
446
|
-
const primaryErrMsg = getErrorMessage(primaryError);
|
|
447
|
-
logger.warn(
|
|
448
|
-
`[${this.logLabel}] Primary model ${this.primaryModel} failed: ${primaryErrMsg}`
|
|
449
|
-
);
|
|
450
|
-
|
|
451
|
-
if (!this.shouldUseBackupModel(primaryError)) {
|
|
452
|
-
throw this.wrapTerminalError(primaryError);
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
logger.debug(`[${this.logLabel}] Trying backup models for streaming`);
|
|
456
|
-
|
|
457
|
-
let lastBackupError: unknown = primaryError;
|
|
458
|
-
|
|
459
|
-
for (let i = 0; i < this.backupModels.length; i++) {
|
|
460
|
-
const backupModel = this.backupModels[i];
|
|
461
|
-
logger.debug(
|
|
462
|
-
`[${this.logLabel}] Trying backup model ${i + 1}/${
|
|
463
|
-
this.backupModels.length
|
|
464
|
-
}: ${backupModel}`
|
|
465
|
-
);
|
|
466
|
-
|
|
467
|
-
try {
|
|
468
|
-
yield* withStreamRetry(
|
|
469
|
-
(signal) => this.generateStreamWithModel<TContext, TStructured>(backupModel, input, signal),
|
|
470
|
-
{ maxRetries: this.retryConfig.retries, firstChunkTimeoutMs: this.retryConfig.timeout, operationName: `${this.logLabel} ${backupModel} stream` }
|
|
471
|
-
);
|
|
472
|
-
logger.debug(`[${this.logLabel}] Backup model ${backupModel} succeeded`);
|
|
473
|
-
return;
|
|
474
|
-
} catch (backupError: unknown) {
|
|
475
|
-
const backupErrMsg = getErrorMessage(backupError);
|
|
476
|
-
logger.warn(
|
|
477
|
-
`[${this.logLabel}] Backup model ${backupModel} failed: ${backupErrMsg}`
|
|
478
|
-
);
|
|
479
|
-
lastBackupError = backupError;
|
|
480
|
-
|
|
481
|
-
if (
|
|
482
|
-
!this.shouldUseBackupModel(backupError) &&
|
|
483
|
-
i < this.backupModels.length - 1
|
|
484
|
-
) {
|
|
485
|
-
logger.debug(
|
|
486
|
-
`[${this.logLabel}] Backup model error doesn't qualify for further attempts`
|
|
487
|
-
);
|
|
488
|
-
break;
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
}
|
|
450
|
+
const observer = backupFallbackLogging(
|
|
451
|
+
`[${this.logLabel}]`,
|
|
452
|
+
(error) => this.shouldUseBackupModel(error),
|
|
453
|
+
{ streaming: true }
|
|
454
|
+
);
|
|
492
455
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
456
|
+
try {
|
|
457
|
+
yield* streamWithBackupFallback({
|
|
458
|
+
models: [this.primaryModel, ...this.backupModels],
|
|
459
|
+
attempt: (model) =>
|
|
460
|
+
withStreamRetry(
|
|
461
|
+
(signal) => this.generateStreamWithModel<TContext, TStructured>(model, input, signal),
|
|
462
|
+
{ maxRetries: this.retryConfig.retries, firstChunkTimeoutMs: this.retryConfig.timeout, operationName: `${this.logLabel} ${model} stream`, isRetriable: (error) => isRetriableProviderError(error, this.classificationOptions) }
|
|
463
|
+
),
|
|
464
|
+
shouldTryBackup: (error) => this.shouldUseBackupModel(error),
|
|
465
|
+
...observer.callbacks,
|
|
466
|
+
});
|
|
467
|
+
} catch (error: unknown) {
|
|
468
|
+
observer.logExhausted(error);
|
|
469
|
+
throw this.wrapTerminalError(error);
|
|
498
470
|
}
|
|
499
471
|
}
|
|
500
472
|
|
|
@@ -529,7 +501,8 @@ export abstract class OpenAICompatibleProvider implements AiProvider {
|
|
|
529
501
|
operation,
|
|
530
502
|
this.retryConfig.timeout,
|
|
531
503
|
this.retryConfig.retries,
|
|
532
|
-
`${this.displayName} ${model}
|
|
504
|
+
`${this.displayName} ${model}`,
|
|
505
|
+
(error) => isRetriableProviderError(error, this.classificationOptions)
|
|
533
506
|
) as Promise<GenerateMessageOutput<TStructured>>;
|
|
534
507
|
}
|
|
535
508
|
|
|
@@ -689,10 +662,15 @@ export abstract class OpenAICompatibleProvider implements AiProvider {
|
|
|
689
662
|
let promptTokens: number | undefined;
|
|
690
663
|
let completionTokens: number | undefined;
|
|
691
664
|
let totalTokens: number | undefined;
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
665
|
+
// Tool calls arrive across MANY deltas: the first carries the id/name,
|
|
666
|
+
// later ones carry argument-string fragments that must be concatenated.
|
|
667
|
+
// Accumulate per call (keyed by `index`) and parse once at stream end —
|
|
668
|
+
// parsing per fragment produced N broken entries for one real call.
|
|
669
|
+
const pendingToolCalls = new Map<
|
|
670
|
+
number,
|
|
671
|
+
{ toolName: string; argumentsBuffer: string }
|
|
672
|
+
>();
|
|
673
|
+
let lastSlot = 0;
|
|
696
674
|
|
|
697
675
|
for await (const chunk of stream) {
|
|
698
676
|
currentModel = chunk.model;
|
|
@@ -707,18 +685,25 @@ export abstract class OpenAICompatibleProvider implements AiProvider {
|
|
|
707
685
|
const choice = chunk.choices?.[0];
|
|
708
686
|
if (!choice) continue;
|
|
709
687
|
|
|
710
|
-
//
|
|
688
|
+
// Accumulate streamed tool-call fragments by index
|
|
711
689
|
if (choice.delta?.tool_calls) {
|
|
712
690
|
for (const toolCall of choice.delta.tool_calls) {
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
691
|
+
// Spec-compliant chunks carry `index`; servers that omit it send
|
|
692
|
+
// one call's fragments sequentially, so keep appending to lastSlot.
|
|
693
|
+
const slot =
|
|
694
|
+
typeof toolCall.index === "number" ? toolCall.index : lastSlot;
|
|
695
|
+
lastSlot = slot;
|
|
696
|
+
const entry = pendingToolCalls.get(slot) ?? {
|
|
697
|
+
toolName: "",
|
|
698
|
+
argumentsBuffer: "",
|
|
699
|
+
};
|
|
700
|
+
if (toolCall.function?.name) {
|
|
701
|
+
entry.toolName = toolCall.function.name;
|
|
702
|
+
}
|
|
703
|
+
if (toolCall.function?.arguments) {
|
|
704
|
+
entry.argumentsBuffer += toolCall.function.arguments;
|
|
721
705
|
}
|
|
706
|
+
pendingToolCalls.set(slot, entry);
|
|
722
707
|
}
|
|
723
708
|
}
|
|
724
709
|
|
|
@@ -753,6 +738,20 @@ export abstract class OpenAICompatibleProvider implements AiProvider {
|
|
|
753
738
|
}
|
|
754
739
|
}
|
|
755
740
|
|
|
741
|
+
const toolCalls: Array<{
|
|
742
|
+
toolName: string;
|
|
743
|
+
arguments: Record<string, unknown>;
|
|
744
|
+
}> = [];
|
|
745
|
+
for (const [, entry] of [...pendingToolCalls.entries()].sort(
|
|
746
|
+
([a], [b]) => a - b
|
|
747
|
+
)) {
|
|
748
|
+
if (!entry.toolName) continue;
|
|
749
|
+
toolCalls.push({
|
|
750
|
+
toolName: entry.toolName,
|
|
751
|
+
arguments: this.parseToolCallArguments(entry.argumentsBuffer, true),
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
|
|
756
755
|
// Include tool calls in structured response (even without JSON schema)
|
|
757
756
|
if (toolCalls.length > 0) {
|
|
758
757
|
structured = {
|
|
@@ -19,7 +19,7 @@ export interface OpenAIProviderOptions {
|
|
|
19
19
|
apiKey: string;
|
|
20
20
|
/** Organization ID (optional) */
|
|
21
21
|
organization?: string;
|
|
22
|
-
/** Model to use (required) - e.g., "gpt-5.
|
|
22
|
+
/** Model to use (required) - e.g., "gpt-5.6", "gpt-5.4-mini" */
|
|
23
23
|
model: string;
|
|
24
24
|
/** Backup models to try if primary fails (default: []) */
|
|
25
25
|
backupModels?: string[];
|
|
@@ -64,7 +64,7 @@ export class OpenAIProvider extends OpenAICompatibleProvider {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
if (!model) {
|
|
67
|
-
throw new Error("Model is required. Example: 'gpt-5.
|
|
67
|
+
throw new Error("Model is required. Example: 'gpt-5.6' or 'gpt-5.5'");
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
super({
|
|
@@ -150,6 +150,40 @@ export function isBackupEligible(code: ProviderErrorCode): boolean {
|
|
|
150
150
|
return code === "rate_limited" || code === "overloaded";
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
+
/** Errors whose name marks them as caller- or SDK-initiated aborts. */
|
|
154
|
+
function isAbortLikeError(error: unknown): boolean {
|
|
155
|
+
if (!(error instanceof Error)) return false;
|
|
156
|
+
// OpenAI SDK: APIUserAbortError. Generic fetch/DOM: AbortError / TimeoutError
|
|
157
|
+
// handled below via classification (a timeout IS retriable; a caller abort is not).
|
|
158
|
+
return (
|
|
159
|
+
error.name === "AbortError" ||
|
|
160
|
+
error.name === "APIUserAbortError" ||
|
|
161
|
+
/abort/i.test(error.message)
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Whether an error is worth retrying with backoff: transient availability,
|
|
167
|
+
* rate-limiting, timeouts and network faults only. Deterministic failures —
|
|
168
|
+
* auth (401/403), invalid request (400/404/422) and caller aborts — must fail
|
|
169
|
+
* fast instead of burning the retry budget on the same outcome.
|
|
170
|
+
*/
|
|
171
|
+
export function isRetriableProviderError(
|
|
172
|
+
error: unknown,
|
|
173
|
+
options?: ErrorClassificationOptions
|
|
174
|
+
): boolean {
|
|
175
|
+
if (isAbortLikeError(error)) {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
const code = classifyProviderError(error, options);
|
|
179
|
+
return (
|
|
180
|
+
code === "rate_limited" ||
|
|
181
|
+
code === "overloaded" ||
|
|
182
|
+
code === "timeout" ||
|
|
183
|
+
code === "network"
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
153
187
|
/**
|
|
154
188
|
* Wrap a terminal failure (after retries/backup exhaustion) in a
|
|
155
189
|
* normalized ProviderError, preserving the original error as `cause`.
|
package/src/types/agent.ts
CHANGED
|
@@ -126,6 +126,13 @@ export interface AgentOptions<TContext = unknown, TData = unknown> {
|
|
|
126
126
|
* Rendered into the system prompt as "who you are and how you communicate."
|
|
127
127
|
*/
|
|
128
128
|
persona?: Template<TContext>;
|
|
129
|
+
/**
|
|
130
|
+
* Hard bound on session.history length (default: 400). The oldest entries
|
|
131
|
+
* are trimmed — never splitting an assistant/tool pair — when the bound is
|
|
132
|
+
* exceeded and no compaction strategy shrank the history first. Set 0 to
|
|
133
|
+
* disable bounding entirely.
|
|
134
|
+
*/
|
|
135
|
+
maxHistoryMessages?: number;
|
|
129
136
|
/** Enable debug logging */
|
|
130
137
|
debug?: boolean;
|
|
131
138
|
/** Default context data available to the agent */
|
|
@@ -187,6 +194,15 @@ export interface AgentOptions<TContext = unknown, TData = unknown> {
|
|
|
187
194
|
* @default 10
|
|
188
195
|
*/
|
|
189
196
|
maxDirectiveChain?: number;
|
|
197
|
+
/**
|
|
198
|
+
* Maximum number of tool loop iterations allowed within a single response
|
|
199
|
+
* generation before the pipeline stops executing further tool calls.
|
|
200
|
+
* Guards against runaway recursive tool calling. An explicit `0` is honored
|
|
201
|
+
* (no tool loops). Applies to both `respond()` and streaming paths.
|
|
202
|
+
*
|
|
203
|
+
* @default 5
|
|
204
|
+
*/
|
|
205
|
+
maxToolLoops?: number;
|
|
190
206
|
/**
|
|
191
207
|
* Optional compaction configuration for managing conversation history size.
|
|
192
208
|
* When provided, the agent will validate the options and make them available
|
|
@@ -321,6 +337,13 @@ export interface AppliedInstruction {
|
|
|
321
337
|
scopeRef?: string;
|
|
322
338
|
}
|
|
323
339
|
|
|
340
|
+
/** A flow that left its active position during a turn. */
|
|
341
|
+
export interface EndedFlow {
|
|
342
|
+
flowId: string;
|
|
343
|
+
title?: string;
|
|
344
|
+
reason: StoppedReason;
|
|
345
|
+
}
|
|
346
|
+
|
|
324
347
|
export interface AgentResponse<TData = Record<string, unknown>> {
|
|
325
348
|
message: string;
|
|
326
349
|
session?: SessionState<TData>;
|
|
@@ -341,6 +364,17 @@ export interface AgentResponse<TData = Record<string, unknown>> {
|
|
|
341
364
|
* Mirrors the observability framing of `executedSteps` and `appliedInstructions`.
|
|
342
365
|
*/
|
|
343
366
|
triggeredSignals?: SignalFiring<unknown, TData>[];
|
|
367
|
+
/**
|
|
368
|
+
* Provider-reported usage for this turn's primary generation (routing and
|
|
369
|
+
* extraction sub-calls are not included).
|
|
370
|
+
*/
|
|
371
|
+
metadata?: { tokensUsed?: number };
|
|
372
|
+
/**
|
|
373
|
+
* Flows that LEFT their active position this turn — completions, redirects,
|
|
374
|
+
* resets. planTurn knows these internally; surfaced so consumers stop
|
|
375
|
+
* re-deriving them from executedSteps + session cursor inspection.
|
|
376
|
+
*/
|
|
377
|
+
endedFlows?: EndedFlow[];
|
|
344
378
|
}
|
|
345
379
|
|
|
346
380
|
export interface AgentResponseStreamChunk<TData = Record<string, unknown>> {
|