@falai/agent 0.3.10 → 0.3.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/README.md +100 -16
  2. package/dist/cjs/core/Agent.d.ts +25 -0
  3. package/dist/cjs/core/Agent.d.ts.map +1 -1
  4. package/dist/cjs/core/Agent.js +130 -0
  5. package/dist/cjs/core/Agent.js.map +1 -1
  6. package/dist/cjs/index.d.ts +2 -0
  7. package/dist/cjs/index.d.ts.map +1 -1
  8. package/dist/cjs/index.js +3 -1
  9. package/dist/cjs/index.js.map +1 -1
  10. package/dist/cjs/providers/AnthropicProvider.d.ts +43 -0
  11. package/dist/cjs/providers/AnthropicProvider.d.ts.map +1 -0
  12. package/dist/cjs/providers/AnthropicProvider.js +328 -0
  13. package/dist/cjs/providers/AnthropicProvider.js.map +1 -0
  14. package/dist/cjs/providers/GeminiProvider.d.ts +4 -1
  15. package/dist/cjs/providers/GeminiProvider.d.ts.map +1 -1
  16. package/dist/cjs/providers/GeminiProvider.js +96 -0
  17. package/dist/cjs/providers/GeminiProvider.js.map +1 -1
  18. package/dist/cjs/providers/OpenAIProvider.d.ts +4 -1
  19. package/dist/cjs/providers/OpenAIProvider.d.ts.map +1 -1
  20. package/dist/cjs/providers/OpenAIProvider.js +115 -0
  21. package/dist/cjs/providers/OpenAIProvider.js.map +1 -1
  22. package/dist/cjs/providers/OpenRouterProvider.d.ts +4 -1
  23. package/dist/cjs/providers/OpenRouterProvider.d.ts.map +1 -1
  24. package/dist/cjs/providers/OpenRouterProvider.js +115 -0
  25. package/dist/cjs/providers/OpenRouterProvider.js.map +1 -1
  26. package/dist/cjs/providers/index.d.ts +13 -0
  27. package/dist/cjs/providers/index.d.ts.map +1 -0
  28. package/dist/cjs/providers/index.js +16 -0
  29. package/dist/cjs/providers/index.js.map +1 -0
  30. package/dist/cjs/types/ai.d.ts +28 -0
  31. package/dist/cjs/types/ai.d.ts.map +1 -1
  32. package/dist/core/Agent.d.ts +25 -0
  33. package/dist/core/Agent.d.ts.map +1 -1
  34. package/dist/core/Agent.js +130 -0
  35. package/dist/core/Agent.js.map +1 -1
  36. package/dist/index.d.ts +2 -0
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +1 -0
  39. package/dist/index.js.map +1 -1
  40. package/dist/providers/AnthropicProvider.d.ts +43 -0
  41. package/dist/providers/AnthropicProvider.d.ts.map +1 -0
  42. package/dist/providers/AnthropicProvider.js +321 -0
  43. package/dist/providers/AnthropicProvider.js.map +1 -0
  44. package/dist/providers/GeminiProvider.d.ts +4 -1
  45. package/dist/providers/GeminiProvider.d.ts.map +1 -1
  46. package/dist/providers/GeminiProvider.js +96 -0
  47. package/dist/providers/GeminiProvider.js.map +1 -1
  48. package/dist/providers/OpenAIProvider.d.ts +4 -1
  49. package/dist/providers/OpenAIProvider.d.ts.map +1 -1
  50. package/dist/providers/OpenAIProvider.js +115 -0
  51. package/dist/providers/OpenAIProvider.js.map +1 -1
  52. package/dist/providers/OpenRouterProvider.d.ts +4 -1
  53. package/dist/providers/OpenRouterProvider.d.ts.map +1 -1
  54. package/dist/providers/OpenRouterProvider.js +115 -0
  55. package/dist/providers/OpenRouterProvider.js.map +1 -1
  56. package/dist/providers/index.d.ts +13 -0
  57. package/dist/providers/index.d.ts.map +1 -0
  58. package/dist/providers/index.js +9 -0
  59. package/dist/providers/index.js.map +1 -0
  60. package/dist/types/ai.d.ts +28 -0
  61. package/dist/types/ai.d.ts.map +1 -1
  62. package/docs/API_REFERENCE.md +260 -2
  63. package/docs/PROVIDERS.md +139 -2
  64. package/examples/business-onboarding.ts +5 -4
  65. package/examples/declarative-agent.ts +1 -1
  66. package/examples/domain-scoping.ts +5 -4
  67. package/examples/healthcare-agent.ts +4 -4
  68. package/examples/openai-agent.ts +6 -4
  69. package/examples/rules-prohibitions.ts +4 -4
  70. package/examples/streaming-agent.ts +371 -0
  71. package/examples/travel-agent.ts +7 -4
  72. package/package.json +2 -1
  73. package/src/core/Agent.ts +174 -0
  74. package/src/index.ts +2 -0
  75. package/src/providers/AnthropicProvider.ts +467 -0
  76. package/src/providers/GeminiProvider.ts +135 -0
  77. package/src/providers/OpenAIProvider.ts +157 -0
  78. package/src/providers/OpenRouterProvider.ts +157 -0
  79. package/src/providers/index.ts +16 -0
  80. package/src/types/ai.ts +32 -0
@@ -9,6 +9,7 @@ import type {
9
9
  AiProvider,
10
10
  GenerateMessageInput,
11
11
  GenerateMessageOutput,
12
+ GenerateMessageStreamChunk,
12
13
  AgentStructuredResponse,
13
14
  } from "../types/ai";
14
15
  import { withTimeoutAndRetry } from "../utils/retry";
@@ -164,6 +165,12 @@ export class OpenAIProvider implements AiProvider {
164
165
  return this.generateWithBackup(input);
165
166
  }
166
167
 
168
+ async *generateMessageStream<TContext = unknown>(
169
+ input: GenerateMessageInput<TContext>
170
+ ): AsyncGenerator<GenerateMessageStreamChunk> {
171
+ yield* this.generateStreamWithBackup(input);
172
+ }
173
+
167
174
  private async generateWithBackup<TContext = unknown>(
168
175
  input: GenerateMessageInput<TContext>
169
176
  ): Promise<GenerateMessageOutput> {
@@ -353,4 +360,154 @@ export class OpenAIProvider implements AiProvider {
353
360
  `OpenAI ${model}`
354
361
  );
355
362
  }
363
+
364
+ private async *generateStreamWithBackup<TContext = unknown>(
365
+ input: GenerateMessageInput<TContext>
366
+ ): AsyncGenerator<GenerateMessageStreamChunk> {
367
+ // Try primary model first
368
+ try {
369
+ yield* this.generateStreamWithModel(this.primaryModel, input);
370
+ } catch (primaryError: unknown) {
371
+ const primaryErrMsg = getErrorMessage(primaryError);
372
+ console.warn(
373
+ `[OPENAI] Primary model ${this.primaryModel} failed: ${primaryErrMsg}`
374
+ );
375
+
376
+ if (!shouldUseBackupModel(primaryError)) {
377
+ throw primaryError;
378
+ }
379
+
380
+ console.log(`[OPENAI] Trying backup models for streaming`);
381
+
382
+ let lastBackupError: unknown = primaryError;
383
+
384
+ for (let i = 0; i < this.backupModels.length; i++) {
385
+ const backupModel = this.backupModels[i];
386
+ console.log(
387
+ `[OPENAI] Trying backup model ${i + 1}/${
388
+ this.backupModels.length
389
+ }: ${backupModel}`
390
+ );
391
+
392
+ try {
393
+ yield* this.generateStreamWithModel(backupModel, input);
394
+ console.log(`[OPENAI] Backup model ${backupModel} succeeded`);
395
+ return;
396
+ } catch (backupError: unknown) {
397
+ const backupErrMsg = getErrorMessage(backupError);
398
+ console.warn(
399
+ `[OPENAI] Backup model ${backupModel} failed: ${backupErrMsg}`
400
+ );
401
+ lastBackupError = backupError;
402
+
403
+ if (
404
+ !shouldUseBackupModel(backupError) &&
405
+ i < this.backupModels.length - 1
406
+ ) {
407
+ console.log(
408
+ `[OPENAI] Backup model error doesn't qualify for further attempts`
409
+ );
410
+ break;
411
+ }
412
+ }
413
+ }
414
+
415
+ const lastBackupErrMsg = getErrorMessage(lastBackupError);
416
+ console.error(
417
+ `[OPENAI] All models failed. Primary: ${primaryErrMsg}, Last backup: ${lastBackupErrMsg}`
418
+ );
419
+ throw lastBackupError;
420
+ }
421
+ }
422
+
423
+ private async *generateStreamWithModel<TContext = unknown>(
424
+ model: string,
425
+ input: GenerateMessageInput<TContext>
426
+ ): AsyncGenerator<GenerateMessageStreamChunk> {
427
+ const params = {
428
+ ...this.config,
429
+ model,
430
+ messages: [
431
+ {
432
+ role: "user" as const,
433
+ content: input.prompt,
434
+ },
435
+ ],
436
+ stream: true as const,
437
+ };
438
+
439
+ // Override with input parameters if provided
440
+ if (input.parameters?.maxOutputTokens !== undefined) {
441
+ params.max_tokens = input.parameters.maxOutputTokens;
442
+ }
443
+
444
+ // Use JSON mode if requested
445
+ // Note: OpenAI streaming doesn't support the responses.parse API,
446
+ // so we use response_format with JSON mode instead
447
+ if (input.parameters?.jsonMode) {
448
+ params.response_format = { type: "json_object" };
449
+ }
450
+
451
+ const stream = await this.client.chat.completions.create(params);
452
+
453
+ let accumulated = "";
454
+ let currentModel = model;
455
+ let finishReason: string | undefined;
456
+ let promptTokens: number | undefined;
457
+ let completionTokens: number | undefined;
458
+ let totalTokens: number | undefined;
459
+
460
+ for await (const chunk of stream) {
461
+ currentModel = chunk.model;
462
+ const delta = chunk.choices[0]?.delta?.content || "";
463
+
464
+ if (delta) {
465
+ accumulated += delta;
466
+ yield {
467
+ delta,
468
+ accumulated,
469
+ done: false,
470
+ };
471
+ }
472
+
473
+ if (chunk.choices[0]?.finish_reason) {
474
+ finishReason = chunk.choices[0].finish_reason;
475
+ }
476
+
477
+ // OpenAI includes usage in the final chunk for some models
478
+ if (chunk.usage) {
479
+ promptTokens = chunk.usage.prompt_tokens;
480
+ completionTokens = chunk.usage.completion_tokens;
481
+ totalTokens = chunk.usage.total_tokens;
482
+ }
483
+ }
484
+
485
+ // Parse JSON response if JSON mode was enabled
486
+ let structured: AgentStructuredResponse | undefined;
487
+ if (input.parameters?.jsonMode && accumulated) {
488
+ try {
489
+ structured = JSON.parse(accumulated) as AgentStructuredResponse;
490
+ } catch (error) {
491
+ console.warn(
492
+ "[OPENAI] Failed to parse JSON response in stream:",
493
+ error
494
+ );
495
+ }
496
+ }
497
+
498
+ // Yield final chunk
499
+ yield {
500
+ delta: "",
501
+ accumulated,
502
+ done: true,
503
+ metadata: {
504
+ model: currentModel,
505
+ finishReason,
506
+ tokensUsed: totalTokens,
507
+ promptTokens,
508
+ completionTokens,
509
+ },
510
+ structured,
511
+ };
512
+ }
356
513
  }
@@ -11,6 +11,7 @@ import type {
11
11
  AgentStructuredResponse,
12
12
  GenerateMessageInput,
13
13
  GenerateMessageOutput,
14
+ GenerateMessageStreamChunk,
14
15
  } from "../types/ai";
15
16
  import { withTimeoutAndRetry } from "../utils/retry";
16
17
 
@@ -174,6 +175,12 @@ export class OpenRouterProvider implements AiProvider {
174
175
  return this.generateWithBackup(input);
175
176
  }
176
177
 
178
+ async *generateMessageStream<TContext = unknown>(
179
+ input: GenerateMessageInput<TContext>
180
+ ): AsyncGenerator<GenerateMessageStreamChunk> {
181
+ yield* this.generateStreamWithBackup(input);
182
+ }
183
+
177
184
  private async generateWithBackup<TContext = unknown>(
178
185
  input: GenerateMessageInput<TContext>
179
186
  ): Promise<GenerateMessageOutput> {
@@ -363,4 +370,154 @@ export class OpenRouterProvider implements AiProvider {
363
370
  `OpenRouter ${model}`
364
371
  );
365
372
  }
373
+
374
+ private async *generateStreamWithBackup<TContext = unknown>(
375
+ input: GenerateMessageInput<TContext>
376
+ ): AsyncGenerator<GenerateMessageStreamChunk> {
377
+ // Try primary model first
378
+ try {
379
+ yield* this.generateStreamWithModel(this.primaryModel, input);
380
+ } catch (primaryError: unknown) {
381
+ const primaryErrMsg = getErrorMessage(primaryError);
382
+ console.warn(
383
+ `[OPENROUTER] Primary model ${this.primaryModel} failed: ${primaryErrMsg}`
384
+ );
385
+
386
+ if (!shouldUseBackupModel(primaryError)) {
387
+ throw primaryError;
388
+ }
389
+
390
+ console.log(`[OPENROUTER] Trying backup models for streaming`);
391
+
392
+ let lastBackupError: unknown = primaryError;
393
+
394
+ for (let i = 0; i < this.backupModels.length; i++) {
395
+ const backupModel = this.backupModels[i];
396
+ console.log(
397
+ `[OPENROUTER] Trying backup model ${i + 1}/${
398
+ this.backupModels.length
399
+ }: ${backupModel}`
400
+ );
401
+
402
+ try {
403
+ yield* this.generateStreamWithModel(backupModel, input);
404
+ console.log(`[OPENROUTER] Backup model ${backupModel} succeeded`);
405
+ return;
406
+ } catch (backupError: unknown) {
407
+ const backupErrMsg = getErrorMessage(backupError);
408
+ console.warn(
409
+ `[OPENROUTER] Backup model ${backupModel} failed: ${backupErrMsg}`
410
+ );
411
+ lastBackupError = backupError;
412
+
413
+ if (
414
+ !shouldUseBackupModel(backupError) &&
415
+ i < this.backupModels.length - 1
416
+ ) {
417
+ console.log(
418
+ `[OPENROUTER] Backup model error doesn't qualify for further attempts`
419
+ );
420
+ break;
421
+ }
422
+ }
423
+ }
424
+
425
+ const lastBackupErrMsg = getErrorMessage(lastBackupError);
426
+ console.error(
427
+ `[OPENROUTER] All models failed. Primary: ${primaryErrMsg}, Last backup: ${lastBackupErrMsg}`
428
+ );
429
+ throw lastBackupError;
430
+ }
431
+ }
432
+
433
+ private async *generateStreamWithModel<TContext = unknown>(
434
+ model: string,
435
+ input: GenerateMessageInput<TContext>
436
+ ): AsyncGenerator<GenerateMessageStreamChunk> {
437
+ const params = {
438
+ ...this.config,
439
+ model,
440
+ messages: [
441
+ {
442
+ role: "user" as const,
443
+ content: input.prompt,
444
+ },
445
+ ],
446
+ stream: true as const,
447
+ };
448
+
449
+ // Override with input parameters if provided
450
+ if (input.parameters?.maxOutputTokens !== undefined) {
451
+ params.max_tokens = input.parameters.maxOutputTokens;
452
+ }
453
+
454
+ // Use JSON mode if requested
455
+ // Note: OpenRouter streaming doesn't support the responses.parse API,
456
+ // so we use response_format with JSON mode instead
457
+ if (input.parameters?.jsonMode) {
458
+ params.response_format = { type: "json_object" };
459
+ }
460
+
461
+ const stream = await this.client.chat.completions.create(params);
462
+
463
+ let accumulated = "";
464
+ let currentModel = model;
465
+ let finishReason: string | undefined;
466
+ let promptTokens: number | undefined;
467
+ let completionTokens: number | undefined;
468
+ let totalTokens: number | undefined;
469
+
470
+ for await (const chunk of stream) {
471
+ currentModel = chunk.model;
472
+ const delta = chunk.choices[0]?.delta?.content || "";
473
+
474
+ if (delta) {
475
+ accumulated += delta;
476
+ yield {
477
+ delta,
478
+ accumulated,
479
+ done: false,
480
+ };
481
+ }
482
+
483
+ if (chunk.choices[0]?.finish_reason) {
484
+ finishReason = chunk.choices[0].finish_reason;
485
+ }
486
+
487
+ // OpenRouter may include usage in the final chunk
488
+ if (chunk.usage) {
489
+ promptTokens = chunk.usage.prompt_tokens;
490
+ completionTokens = chunk.usage.completion_tokens;
491
+ totalTokens = chunk.usage.total_tokens;
492
+ }
493
+ }
494
+
495
+ // Parse JSON response if JSON mode was enabled
496
+ let structured: AgentStructuredResponse | undefined;
497
+ if (input.parameters?.jsonMode && accumulated) {
498
+ try {
499
+ structured = JSON.parse(accumulated) as AgentStructuredResponse;
500
+ } catch (error) {
501
+ console.warn(
502
+ "[OPENROUTER] Failed to parse JSON response in stream:",
503
+ error
504
+ );
505
+ }
506
+ }
507
+
508
+ // Yield final chunk
509
+ yield {
510
+ delta: "",
511
+ accumulated,
512
+ done: true,
513
+ metadata: {
514
+ model: currentModel,
515
+ finishReason,
516
+ tokensUsed: totalTokens,
517
+ promptTokens,
518
+ completionTokens,
519
+ },
520
+ structured,
521
+ };
522
+ }
366
523
  }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * AI Provider exports
3
+ * Centralized export point for all AI provider implementations
4
+ */
5
+
6
+ export { AnthropicProvider } from "./AnthropicProvider";
7
+ export type { AnthropicProviderOptions } from "./AnthropicProvider";
8
+
9
+ export { GeminiProvider } from "./GeminiProvider";
10
+ export type { GeminiProviderOptions } from "./GeminiProvider";
11
+
12
+ export { OpenAIProvider } from "./OpenAIProvider";
13
+ export type { OpenAIProviderOptions } from "./OpenAIProvider";
14
+
15
+ export { OpenRouterProvider } from "./OpenRouterProvider";
16
+ export type { OpenRouterProviderOptions } from "./OpenRouterProvider";
package/src/types/ai.ts CHANGED
@@ -94,6 +94,31 @@ export interface GenerateMessageOutput {
94
94
  structured?: AgentStructuredResponse;
95
95
  }
96
96
 
97
+ /**
98
+ * Stream chunk from AI message generation
99
+ */
100
+ export interface GenerateMessageStreamChunk {
101
+ /** The delta/chunk of the message */
102
+ delta: string;
103
+ /** Accumulated message so far */
104
+ accumulated: string;
105
+ /** Whether this is the final chunk */
106
+ done: boolean;
107
+ /** Optional metadata about generation */
108
+ metadata?: {
109
+ /** Model used */
110
+ model?: string;
111
+ /** Tokens consumed */
112
+ tokensUsed?: number;
113
+ /** Finish reason */
114
+ finishReason?: string;
115
+ /** Additional provider-specific data */
116
+ [key: string]: unknown;
117
+ };
118
+ /** Structured response data (only available when done=true and JSON mode is enabled) */
119
+ structured?: AgentStructuredResponse;
120
+ }
121
+
97
122
  /**
98
123
  * AI provider interface (strategy pattern)
99
124
  */
@@ -107,4 +132,11 @@ export interface AiProvider {
107
132
  generateMessage<TContext = unknown>(
108
133
  input: GenerateMessageInput<TContext>
109
134
  ): Promise<GenerateMessageOutput>;
135
+
136
+ /**
137
+ * Generate a message as a stream based on prompt and context
138
+ */
139
+ generateMessageStream<TContext = unknown>(
140
+ input: GenerateMessageInput<TContext>
141
+ ): AsyncGenerator<GenerateMessageStreamChunk>;
110
142
  }