@falai/agent 0.1.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 +516 -0
- package/dist/constants/index.d.ts +5 -0
- package/dist/constants/index.d.ts.map +1 -0
- package/dist/constants/index.js +5 -0
- package/dist/constants/index.js.map +1 -0
- package/dist/core/Agent.d.ts +98 -0
- package/dist/core/Agent.d.ts.map +1 -0
- package/dist/core/Agent.js +248 -0
- package/dist/core/Agent.js.map +1 -0
- package/dist/core/DomainRegistry.d.ts +26 -0
- package/dist/core/DomainRegistry.d.ts.map +1 -0
- package/dist/core/DomainRegistry.js +41 -0
- package/dist/core/DomainRegistry.js.map +1 -0
- package/dist/core/Events.d.ts +19 -0
- package/dist/core/Events.d.ts.map +1 -0
- package/dist/core/Events.js +79 -0
- package/dist/core/Events.js.map +1 -0
- package/dist/core/Observation.d.ts +24 -0
- package/dist/core/Observation.d.ts.map +1 -0
- package/dist/core/Observation.js +35 -0
- package/dist/core/Observation.js.map +1 -0
- package/dist/core/PromptBuilder.d.ts +121 -0
- package/dist/core/PromptBuilder.d.ts.map +1 -0
- package/dist/core/PromptBuilder.js +339 -0
- package/dist/core/PromptBuilder.js.map +1 -0
- package/dist/core/Route.d.ts +46 -0
- package/dist/core/Route.d.ts.map +1 -0
- package/dist/core/Route.js +113 -0
- package/dist/core/Route.js.map +1 -0
- package/dist/core/State.d.ts +50 -0
- package/dist/core/State.d.ts.map +1 -0
- package/dist/core/State.js +110 -0
- package/dist/core/State.js.map +1 -0
- package/dist/core/Tool.d.ts +31 -0
- package/dist/core/Tool.d.ts.map +1 -0
- package/dist/core/Tool.js +33 -0
- package/dist/core/Tool.js.map +1 -0
- package/dist/core/Transition.d.ts +32 -0
- package/dist/core/Transition.d.ts.map +1 -0
- package/dist/core/Transition.js +59 -0
- package/dist/core/Transition.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/providers/GeminiProvider.d.ts +40 -0
- package/dist/providers/GeminiProvider.d.ts.map +1 -0
- package/dist/providers/GeminiProvider.js +126 -0
- package/dist/providers/GeminiProvider.js.map +1 -0
- package/dist/providers/OpenAIProvider.d.ts +42 -0
- package/dist/providers/OpenAIProvider.d.ts.map +1 -0
- package/dist/providers/OpenAIProvider.js +164 -0
- package/dist/providers/OpenAIProvider.js.map +1 -0
- package/dist/providers/OpenRouterProvider.d.ts +46 -0
- package/dist/providers/OpenRouterProvider.d.ts.map +1 -0
- package/dist/providers/OpenRouterProvider.js +171 -0
- package/dist/providers/OpenRouterProvider.js.map +1 -0
- package/dist/types/agent.d.ts +105 -0
- package/dist/types/agent.d.ts.map +1 -0
- package/dist/types/agent.js +18 -0
- package/dist/types/agent.js.map +1 -0
- package/dist/types/ai.d.ts +78 -0
- package/dist/types/ai.d.ts.map +1 -0
- package/dist/types/ai.js +5 -0
- package/dist/types/ai.js.map +1 -0
- package/dist/types/history.d.ts +112 -0
- package/dist/types/history.d.ts.map +1 -0
- package/dist/types/history.js +34 -0
- package/dist/types/history.js.map +1 -0
- package/dist/types/index.d.ts +14 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +7 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/observation.d.ts +25 -0
- package/dist/types/observation.d.ts.map +1 -0
- package/dist/types/observation.js +5 -0
- package/dist/types/observation.js.map +1 -0
- package/dist/types/prompt.d.ts +46 -0
- package/dist/types/prompt.d.ts.map +1 -0
- package/dist/types/prompt.js +16 -0
- package/dist/types/prompt.js.map +1 -0
- package/dist/types/route.d.ts +59 -0
- package/dist/types/route.d.ts.map +1 -0
- package/dist/types/route.js +5 -0
- package/dist/types/route.js.map +1 -0
- package/dist/types/tool.d.ts +46 -0
- package/dist/types/tool.d.ts.map +1 -0
- package/dist/types/tool.js +5 -0
- package/dist/types/tool.js.map +1 -0
- package/dist/utils/retry.d.ts +13 -0
- package/dist/utils/retry.d.ts.map +1 -0
- package/dist/utils/retry.js +70 -0
- package/dist/utils/retry.js.map +1 -0
- package/docs/API_REFERENCE.md +517 -0
- package/docs/CONSTRUCTOR_OPTIONS.md +256 -0
- package/docs/CONTRIBUTING.md +481 -0
- package/docs/GETTING_STARTED.md +328 -0
- package/docs/PROVIDERS.md +472 -0
- package/docs/PUBLISHING.md +174 -0
- package/docs/README.md +68 -0
- package/docs/STRUCTURE.md +32 -0
- package/examples/declarative-agent.ts +217 -0
- package/examples/healthcare-agent.ts +283 -0
- package/examples/openai-agent.ts +167 -0
- package/examples/travel-agent.ts +342 -0
- package/package.json +73 -0
- package/src/constants/index.ts +5 -0
- package/src/core/Agent.ts +307 -0
- package/src/core/DomainRegistry.ts +50 -0
- package/src/core/Events.ts +101 -0
- package/src/core/Observation.ts +46 -0
- package/src/core/PromptBuilder.ts +511 -0
- package/src/core/Route.ts +136 -0
- package/src/core/State.ts +153 -0
- package/src/core/Tool.ts +54 -0
- package/src/core/Transition.ts +66 -0
- package/src/index.ts +83 -0
- package/src/providers/GeminiProvider.ts +220 -0
- package/src/providers/OpenAIProvider.ts +272 -0
- package/src/providers/OpenRouterProvider.ts +282 -0
- package/src/types/agent.ts +112 -0
- package/src/types/ai.ts +85 -0
- package/src/types/history.ts +125 -0
- package/src/types/index.ts +56 -0
- package/src/types/observation.ts +27 -0
- package/src/types/prompt.ts +49 -0
- package/src/types/route.ts +68 -0
- package/src/types/tool.ts +53 -0
- package/src/utils/retry.ts +96 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State in the route DSL
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { StateRef, TransitionSpec, TransitionResult } from "@/types/route";
|
|
6
|
+
import type { Guideline } from "@/types/agent";
|
|
7
|
+
|
|
8
|
+
import { END_ROUTE } from "@/constants";
|
|
9
|
+
import { Transition } from "@/core/Transition";
|
|
10
|
+
|
|
11
|
+
let stateIdCounter = 0;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Represents a state within a route
|
|
15
|
+
*/
|
|
16
|
+
export class State {
|
|
17
|
+
public readonly id: string;
|
|
18
|
+
private transitions: Transition[] = [];
|
|
19
|
+
private guidelines: Guideline[] = [];
|
|
20
|
+
|
|
21
|
+
constructor(
|
|
22
|
+
public readonly routeId: string,
|
|
23
|
+
public readonly description?: string
|
|
24
|
+
) {
|
|
25
|
+
this.id = `state_${++stateIdCounter}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Create a transition from this state to another
|
|
30
|
+
*
|
|
31
|
+
* @param spec - Transition specification (chatState, toolState, or direct state)
|
|
32
|
+
* @param condition - Optional condition for this transition
|
|
33
|
+
* @returns Object with target state that supports chaining
|
|
34
|
+
*/
|
|
35
|
+
transitionTo(spec: TransitionSpec, condition?: string): TransitionResult {
|
|
36
|
+
// Handle END_ROUTE
|
|
37
|
+
if (
|
|
38
|
+
spec.state &&
|
|
39
|
+
typeof spec.state === "symbol" &&
|
|
40
|
+
spec.state === END_ROUTE
|
|
41
|
+
) {
|
|
42
|
+
const endTransition = new Transition(
|
|
43
|
+
this.getRef(),
|
|
44
|
+
{ state: END_ROUTE },
|
|
45
|
+
condition
|
|
46
|
+
);
|
|
47
|
+
this.transitions.push(endTransition);
|
|
48
|
+
|
|
49
|
+
// Return a terminal state reference
|
|
50
|
+
return {
|
|
51
|
+
target: this.createTerminalRef(),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Handle direct state reference
|
|
56
|
+
if (spec.state && typeof spec.state !== "symbol") {
|
|
57
|
+
const transition = new Transition(this.getRef(), spec, condition);
|
|
58
|
+
this.transitions.push(transition);
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
target: this.createStateRefWithTransition(spec.state),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Create new target state for chatState or toolState
|
|
66
|
+
const targetState = new State(this.routeId, spec.chatState);
|
|
67
|
+
const transition = new Transition(this.getRef(), spec, condition);
|
|
68
|
+
transition.setTarget(targetState);
|
|
69
|
+
|
|
70
|
+
this.transitions.push(transition);
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
target: this.createStateRefWithTransition(
|
|
74
|
+
targetState.getRef(),
|
|
75
|
+
targetState
|
|
76
|
+
),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Add a guideline specific to this state
|
|
82
|
+
*/
|
|
83
|
+
addGuideline(guideline: Guideline): void {
|
|
84
|
+
this.guidelines.push(guideline);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Get guidelines for this state
|
|
89
|
+
*/
|
|
90
|
+
getGuidelines(): Guideline[] {
|
|
91
|
+
return [...this.guidelines];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Get all transitions from this state
|
|
96
|
+
*/
|
|
97
|
+
getTransitions(): Transition[] {
|
|
98
|
+
return [...this.transitions];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Get state reference
|
|
103
|
+
*/
|
|
104
|
+
getRef(): StateRef {
|
|
105
|
+
return {
|
|
106
|
+
id: this.id,
|
|
107
|
+
routeId: this.routeId,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Create a state reference with transitionTo capability for chaining
|
|
113
|
+
*/
|
|
114
|
+
private createStateRefWithTransition(
|
|
115
|
+
ref: StateRef,
|
|
116
|
+
state?: State
|
|
117
|
+
): StateRef & {
|
|
118
|
+
transitionTo: (
|
|
119
|
+
spec: TransitionSpec,
|
|
120
|
+
condition?: string
|
|
121
|
+
) => TransitionResult;
|
|
122
|
+
} {
|
|
123
|
+
const stateInstance = state || this;
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
...ref,
|
|
127
|
+
transitionTo: (spec: TransitionSpec, condition?: string) =>
|
|
128
|
+
stateInstance.transitionTo(spec, condition),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Create a terminal state reference (for END_ROUTE)
|
|
134
|
+
*/
|
|
135
|
+
private createTerminalRef(): StateRef & {
|
|
136
|
+
transitionTo: (
|
|
137
|
+
spec: TransitionSpec,
|
|
138
|
+
condition?: string
|
|
139
|
+
) => TransitionResult;
|
|
140
|
+
} {
|
|
141
|
+
const terminalRef: StateRef = {
|
|
142
|
+
id: "END",
|
|
143
|
+
routeId: this.routeId,
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
...terminalRef,
|
|
148
|
+
transitionTo: () => {
|
|
149
|
+
throw new Error("Cannot transition from END_ROUTE state");
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
}
|
package/src/core/Tool.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool definition and creation utilities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type {
|
|
6
|
+
ToolContext,
|
|
7
|
+
ToolHandler,
|
|
8
|
+
ToolRef,
|
|
9
|
+
ToolResult,
|
|
10
|
+
} from "@/types/tool";
|
|
11
|
+
|
|
12
|
+
let toolIdCounter = 0;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Define a new tool with type-safe context and arguments
|
|
16
|
+
*
|
|
17
|
+
* @param name - Name of the tool
|
|
18
|
+
* @param handler - Handler function that executes the tool
|
|
19
|
+
* @param options - Optional configuration
|
|
20
|
+
* @returns A reference to the defined tool
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* const getTool = defineTool<MyContext, [string, number], string>(
|
|
25
|
+
* 'get_data',
|
|
26
|
+
* async ({ context }, id, count) => {
|
|
27
|
+
* return { data: `Retrieved ${count} items for ${id}` };
|
|
28
|
+
* }
|
|
29
|
+
* );
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export function defineTool<TContext, TArgs extends unknown[], TResult>(
|
|
33
|
+
name: string,
|
|
34
|
+
handler: ToolHandler<TContext, TArgs, TResult>,
|
|
35
|
+
options?: {
|
|
36
|
+
description?: string;
|
|
37
|
+
parameters?: unknown;
|
|
38
|
+
}
|
|
39
|
+
): ToolRef<TContext, TArgs, TResult> {
|
|
40
|
+
const id = `tool_${++toolIdCounter}_${name}`;
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
id,
|
|
44
|
+
name,
|
|
45
|
+
handler,
|
|
46
|
+
description: options?.description,
|
|
47
|
+
parameters: options?.parameters,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Re-export types for convenience
|
|
53
|
+
*/
|
|
54
|
+
export type { ToolContext, ToolHandler, ToolRef, ToolResult };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transition between states in the route DSL
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { StateRef, TransitionSpec } from "@/types/route";
|
|
6
|
+
import type { State } from "@/core/State";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Represents a transition from one state to another
|
|
10
|
+
*/
|
|
11
|
+
export class Transition {
|
|
12
|
+
private target?: State;
|
|
13
|
+
|
|
14
|
+
constructor(
|
|
15
|
+
public readonly source: StateRef,
|
|
16
|
+
public readonly spec: TransitionSpec,
|
|
17
|
+
public readonly condition?: string
|
|
18
|
+
) {}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Set the target state for this transition
|
|
22
|
+
*/
|
|
23
|
+
setTarget(state: State): void {
|
|
24
|
+
this.target = state;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Get the target state
|
|
29
|
+
*/
|
|
30
|
+
getTarget(): State | undefined {
|
|
31
|
+
return this.target;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Check if this transition has a condition
|
|
36
|
+
*/
|
|
37
|
+
hasCondition(): boolean {
|
|
38
|
+
return !!this.condition;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Get transition description for logging/debugging
|
|
43
|
+
*/
|
|
44
|
+
describe(): string {
|
|
45
|
+
const parts: string[] = [];
|
|
46
|
+
|
|
47
|
+
if (this.spec.chatState) {
|
|
48
|
+
parts.push(`chat: "${this.spec.chatState}"`);
|
|
49
|
+
}
|
|
50
|
+
if (this.spec.toolState) {
|
|
51
|
+
parts.push(`tool: ${this.spec.toolState.name}`);
|
|
52
|
+
}
|
|
53
|
+
if (this.spec.state) {
|
|
54
|
+
if (typeof this.spec.state === "symbol") {
|
|
55
|
+
parts.push("state: END_ROUTE");
|
|
56
|
+
} else {
|
|
57
|
+
parts.push(`state: ${this.spec.state.id}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const transition = parts.join(", ");
|
|
62
|
+
const conditionPart = this.condition ? ` (when: ${this.condition})` : "";
|
|
63
|
+
|
|
64
|
+
return `${this.source.id} -> [${transition}]${conditionPart}`;
|
|
65
|
+
}
|
|
66
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @falai/agent - Standalone AI Agent framework
|
|
3
|
+
*
|
|
4
|
+
* A strongly-typed, modular agent framework with route DSL and AI provider strategy
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Core
|
|
8
|
+
export { Agent } from "./core/Agent";
|
|
9
|
+
export { Route } from "./core/Route";
|
|
10
|
+
export { State } from "./core/State";
|
|
11
|
+
export { Transition } from "./core/Transition";
|
|
12
|
+
export { Observation } from "./core/Observation";
|
|
13
|
+
export { defineTool } from "./core/Tool";
|
|
14
|
+
export { DomainRegistry } from "./core/DomainRegistry";
|
|
15
|
+
export { adaptEvent, createMessageEvent, createToolEvent } from "./core/Events";
|
|
16
|
+
export { PromptBuilder } from "./core/PromptBuilder";
|
|
17
|
+
export type { Customer, AgentInfo } from "./core/PromptBuilder";
|
|
18
|
+
export { BuiltInSection } from "./core/PromptBuilder";
|
|
19
|
+
|
|
20
|
+
// Providers
|
|
21
|
+
export { GeminiProvider } from "./providers/GeminiProvider";
|
|
22
|
+
export type { GeminiProviderOptions } from "./providers/GeminiProvider";
|
|
23
|
+
export { OpenAIProvider } from "./providers/OpenAIProvider";
|
|
24
|
+
export type { OpenAIProviderOptions } from "./providers/OpenAIProvider";
|
|
25
|
+
export { OpenRouterProvider } from "./providers/OpenRouterProvider";
|
|
26
|
+
export type { OpenRouterProviderOptions } from "./providers/OpenRouterProvider";
|
|
27
|
+
|
|
28
|
+
// Constants
|
|
29
|
+
export { END_ROUTE } from "./constants";
|
|
30
|
+
|
|
31
|
+
// Types
|
|
32
|
+
export type {
|
|
33
|
+
AgentOptions,
|
|
34
|
+
Term,
|
|
35
|
+
Guideline,
|
|
36
|
+
Capability,
|
|
37
|
+
GuidelineMatch,
|
|
38
|
+
} from "@/types/agent";
|
|
39
|
+
export { CompositionMode } from "@/types/agent";
|
|
40
|
+
|
|
41
|
+
export type {
|
|
42
|
+
Event,
|
|
43
|
+
EmittedEvent,
|
|
44
|
+
MessageEventData,
|
|
45
|
+
ToolEventData,
|
|
46
|
+
StatusEventData,
|
|
47
|
+
Participant,
|
|
48
|
+
} from "@/types/history";
|
|
49
|
+
export { EventKind, EventSource } from "@/types/history";
|
|
50
|
+
|
|
51
|
+
export type {
|
|
52
|
+
RouteRef,
|
|
53
|
+
StateRef,
|
|
54
|
+
RouteOptions,
|
|
55
|
+
TransitionSpec,
|
|
56
|
+
TransitionResult,
|
|
57
|
+
} from "@/types/route";
|
|
58
|
+
|
|
59
|
+
export type {
|
|
60
|
+
ToolContext,
|
|
61
|
+
ToolResult,
|
|
62
|
+
ToolHandler,
|
|
63
|
+
ToolRef,
|
|
64
|
+
} from "@/types/tool";
|
|
65
|
+
|
|
66
|
+
export type {
|
|
67
|
+
AiProvider,
|
|
68
|
+
GenerateMessageInput,
|
|
69
|
+
GenerateMessageOutput,
|
|
70
|
+
ReasoningConfig,
|
|
71
|
+
} from "@/types/ai";
|
|
72
|
+
|
|
73
|
+
export type {
|
|
74
|
+
PromptSection,
|
|
75
|
+
ContextVariable,
|
|
76
|
+
ContextVariableValue,
|
|
77
|
+
} from "@/types/prompt";
|
|
78
|
+
export { SectionStatus } from "@/types/prompt";
|
|
79
|
+
|
|
80
|
+
export type {
|
|
81
|
+
Observation as IObservation,
|
|
82
|
+
ObservationOptions,
|
|
83
|
+
} from "@/types/observation";
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google Gemini AI provider implementation with retry and backup models
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type {
|
|
6
|
+
GoogleGenAI as GoogleGenAIType,
|
|
7
|
+
GenerateContentConfig,
|
|
8
|
+
GenerateContentResponse,
|
|
9
|
+
} from "@google/genai";
|
|
10
|
+
import { GoogleGenAI } from "@google/genai";
|
|
11
|
+
|
|
12
|
+
import type {
|
|
13
|
+
AiProvider,
|
|
14
|
+
GenerateMessageInput,
|
|
15
|
+
GenerateMessageOutput,
|
|
16
|
+
} from "@/types/ai";
|
|
17
|
+
import { withTimeoutAndRetry } from "@/utils/retry";
|
|
18
|
+
|
|
19
|
+
const DEFAULT_RETRY_CONFIG = {
|
|
20
|
+
timeout: 60000,
|
|
21
|
+
retries: 3,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Configuration options for Gemini provider
|
|
26
|
+
* Uses types from @google/genai package
|
|
27
|
+
*/
|
|
28
|
+
export interface GeminiProviderOptions {
|
|
29
|
+
/** Gemini API key */
|
|
30
|
+
apiKey: string;
|
|
31
|
+
/** Model to use (required) - e.g., "models/gemini-2.5-pro" */
|
|
32
|
+
model: string;
|
|
33
|
+
/** Backup models to try if primary fails (default: []) */
|
|
34
|
+
backupModels?: string[];
|
|
35
|
+
/** Default generation config - uses GenerateContentConfig from @google/genai */
|
|
36
|
+
config?: Partial<GenerateContentConfig>;
|
|
37
|
+
/** Retry configuration */
|
|
38
|
+
retryConfig?: {
|
|
39
|
+
timeout?: number;
|
|
40
|
+
retries?: number;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Type guard for errors with status/code properties
|
|
46
|
+
*/
|
|
47
|
+
interface ErrorWithStatus {
|
|
48
|
+
status?: number;
|
|
49
|
+
code?: number;
|
|
50
|
+
message?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Determines if an error should trigger backup model usage
|
|
55
|
+
*/
|
|
56
|
+
const shouldUseBackupModel = (error: unknown): boolean => {
|
|
57
|
+
const err = error as ErrorWithStatus;
|
|
58
|
+
|
|
59
|
+
if (err?.status === 500 || err?.code === 500) {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const message = err?.message ?? String(error);
|
|
64
|
+
if (
|
|
65
|
+
message.includes("internal error") ||
|
|
66
|
+
message.includes("Internal error") ||
|
|
67
|
+
message.includes("INTERNAL")
|
|
68
|
+
) {
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (
|
|
73
|
+
err?.status === 429 ||
|
|
74
|
+
err?.code === 429 ||
|
|
75
|
+
err?.status === 503 ||
|
|
76
|
+
err?.code === 503
|
|
77
|
+
) {
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (message.includes("unavailable") || message.includes("not available")) {
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return false;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Gemini provider implementation with backup models and retry logic
|
|
90
|
+
*/
|
|
91
|
+
export class GeminiProvider implements AiProvider {
|
|
92
|
+
public readonly name = "gemini";
|
|
93
|
+
private genAI: GoogleGenAIType;
|
|
94
|
+
private primaryModel: string;
|
|
95
|
+
private backupModels: string[];
|
|
96
|
+
private config?: Partial<GenerateContentConfig>;
|
|
97
|
+
private retryConfig: { timeout: number; retries: number };
|
|
98
|
+
|
|
99
|
+
constructor(options: GeminiProviderOptions) {
|
|
100
|
+
const { apiKey, model, backupModels = [], config, retryConfig } = options;
|
|
101
|
+
|
|
102
|
+
if (!apiKey) {
|
|
103
|
+
throw new Error("Gemini API key is required");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (!model) {
|
|
107
|
+
throw new Error("Model is required. Example: 'models/gemini-2.5-pro'");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
this.genAI = new GoogleGenAI({ apiKey });
|
|
111
|
+
this.primaryModel = model;
|
|
112
|
+
this.backupModels = backupModels;
|
|
113
|
+
this.config = config;
|
|
114
|
+
this.retryConfig = {
|
|
115
|
+
timeout: retryConfig?.timeout || DEFAULT_RETRY_CONFIG.timeout,
|
|
116
|
+
retries: retryConfig?.retries || DEFAULT_RETRY_CONFIG.retries,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async generateMessage<TContext = unknown>(
|
|
121
|
+
input: GenerateMessageInput<TContext>
|
|
122
|
+
): Promise<GenerateMessageOutput> {
|
|
123
|
+
return this.generateWithBackup(input);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private async generateWithBackup<TContext = unknown>(
|
|
127
|
+
input: GenerateMessageInput<TContext>
|
|
128
|
+
): Promise<GenerateMessageOutput> {
|
|
129
|
+
// Try primary model first
|
|
130
|
+
try {
|
|
131
|
+
return await this.generateWithModel(this.primaryModel, input);
|
|
132
|
+
} catch (primaryError: unknown) {
|
|
133
|
+
const primaryErrMsg = String(primaryError);
|
|
134
|
+
console.warn(
|
|
135
|
+
`[GEMINI] Primary model ${this.primaryModel} failed: ${primaryErrMsg}`
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
if (!shouldUseBackupModel(primaryError)) {
|
|
139
|
+
throw primaryError;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
console.log(`[GEMINI] Trying backup models`);
|
|
143
|
+
|
|
144
|
+
let lastBackupError: unknown = primaryError;
|
|
145
|
+
|
|
146
|
+
for (let i = 0; i < this.backupModels.length; i++) {
|
|
147
|
+
const backupModel = this.backupModels[i];
|
|
148
|
+
console.log(
|
|
149
|
+
`[GEMINI] Trying backup model ${i + 1}/${
|
|
150
|
+
this.backupModels.length
|
|
151
|
+
}: ${backupModel}`
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
try {
|
|
155
|
+
const result = await this.generateWithModel(backupModel, input);
|
|
156
|
+
console.log(`[GEMINI] Backup model ${backupModel} succeeded`);
|
|
157
|
+
return result;
|
|
158
|
+
} catch (backupError: unknown) {
|
|
159
|
+
const backupErrMsg = String(backupError);
|
|
160
|
+
console.warn(
|
|
161
|
+
`[GEMINI] Backup model ${backupModel} failed: ${backupErrMsg}`
|
|
162
|
+
);
|
|
163
|
+
lastBackupError = backupError;
|
|
164
|
+
|
|
165
|
+
if (
|
|
166
|
+
!shouldUseBackupModel(backupError) &&
|
|
167
|
+
i < this.backupModels.length - 1
|
|
168
|
+
) {
|
|
169
|
+
console.log(
|
|
170
|
+
`[GEMINI] Backup model error doesn't qualify for further attempts`
|
|
171
|
+
);
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const lastBackupErrMsg = String(lastBackupError);
|
|
178
|
+
console.error(
|
|
179
|
+
`[GEMINI] All models failed. Primary: ${primaryErrMsg}, Last backup: ${lastBackupErrMsg}`
|
|
180
|
+
);
|
|
181
|
+
throw lastBackupError;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
private async generateWithModel<TContext = unknown>(
|
|
186
|
+
model: string,
|
|
187
|
+
input: GenerateMessageInput<TContext>
|
|
188
|
+
): Promise<GenerateMessageOutput> {
|
|
189
|
+
const operation = async (): Promise<GenerateMessageOutput> => {
|
|
190
|
+
const response: GenerateContentResponse =
|
|
191
|
+
await this.genAI.models.generateContent({
|
|
192
|
+
model,
|
|
193
|
+
contents: input.prompt,
|
|
194
|
+
config: this.config,
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
const message = response.text;
|
|
198
|
+
if (!message) {
|
|
199
|
+
throw new Error("No response from Gemini");
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
message,
|
|
204
|
+
metadata: {
|
|
205
|
+
model,
|
|
206
|
+
tokensUsed: response.usageMetadata?.totalTokenCount,
|
|
207
|
+
promptTokens: response.usageMetadata?.promptTokenCount,
|
|
208
|
+
completionTokens: response.usageMetadata?.candidatesTokenCount,
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
return withTimeoutAndRetry(
|
|
214
|
+
operation,
|
|
215
|
+
this.retryConfig.timeout,
|
|
216
|
+
this.retryConfig.retries,
|
|
217
|
+
`Gemini ${model}`
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
}
|