@falai/agent 0.3.0 → 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.
- package/README.md +257 -24
- package/dist/cjs/core/Agent.d.ts +37 -0
- package/dist/cjs/core/Agent.d.ts.map +1 -1
- package/dist/cjs/core/Agent.js +167 -1
- package/dist/cjs/core/Agent.js.map +1 -1
- package/dist/cjs/core/DomainRegistry.d.ts +10 -0
- package/dist/cjs/core/DomainRegistry.d.ts.map +1 -1
- package/dist/cjs/core/DomainRegistry.js +25 -0
- package/dist/cjs/core/DomainRegistry.js.map +1 -1
- package/dist/cjs/core/PromptBuilder.d.ts +9 -1
- package/dist/cjs/core/PromptBuilder.d.ts.map +1 -1
- package/dist/cjs/core/PromptBuilder.js +49 -2
- package/dist/cjs/core/PromptBuilder.js.map +1 -1
- package/dist/cjs/core/Route.d.ts +16 -0
- package/dist/cjs/core/Route.d.ts.map +1 -1
- package/dist/cjs/core/Route.js +22 -0
- package/dist/cjs/core/Route.js.map +1 -1
- package/dist/cjs/index.d.ts +2 -0
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +3 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/providers/AnthropicProvider.d.ts +43 -0
- package/dist/cjs/providers/AnthropicProvider.d.ts.map +1 -0
- package/dist/cjs/providers/AnthropicProvider.js +328 -0
- package/dist/cjs/providers/AnthropicProvider.js.map +1 -0
- package/dist/cjs/providers/GeminiProvider.d.ts +4 -1
- package/dist/cjs/providers/GeminiProvider.d.ts.map +1 -1
- package/dist/cjs/providers/GeminiProvider.js +96 -0
- package/dist/cjs/providers/GeminiProvider.js.map +1 -1
- package/dist/cjs/providers/OpenAIProvider.d.ts +4 -1
- package/dist/cjs/providers/OpenAIProvider.d.ts.map +1 -1
- package/dist/cjs/providers/OpenAIProvider.js +115 -0
- package/dist/cjs/providers/OpenAIProvider.js.map +1 -1
- package/dist/cjs/providers/OpenRouterProvider.d.ts +4 -1
- package/dist/cjs/providers/OpenRouterProvider.d.ts.map +1 -1
- package/dist/cjs/providers/OpenRouterProvider.js +115 -0
- package/dist/cjs/providers/OpenRouterProvider.js.map +1 -1
- package/dist/cjs/providers/index.d.ts +13 -0
- package/dist/cjs/providers/index.d.ts.map +1 -0
- package/dist/cjs/providers/index.js +16 -0
- package/dist/cjs/providers/index.js.map +1 -0
- package/dist/cjs/types/ai.d.ts +28 -0
- package/dist/cjs/types/ai.d.ts.map +1 -1
- package/dist/cjs/types/route.d.ts +6 -0
- package/dist/cjs/types/route.d.ts.map +1 -1
- package/dist/core/Agent.d.ts +37 -0
- package/dist/core/Agent.d.ts.map +1 -1
- package/dist/core/Agent.js +167 -1
- package/dist/core/Agent.js.map +1 -1
- package/dist/core/DomainRegistry.d.ts +10 -0
- package/dist/core/DomainRegistry.d.ts.map +1 -1
- package/dist/core/DomainRegistry.js +25 -0
- package/dist/core/DomainRegistry.js.map +1 -1
- package/dist/core/PromptBuilder.d.ts +9 -1
- package/dist/core/PromptBuilder.d.ts.map +1 -1
- package/dist/core/PromptBuilder.js +49 -2
- package/dist/core/PromptBuilder.js.map +1 -1
- package/dist/core/Route.d.ts +16 -0
- package/dist/core/Route.d.ts.map +1 -1
- package/dist/core/Route.js +22 -0
- package/dist/core/Route.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/providers/AnthropicProvider.d.ts +43 -0
- package/dist/providers/AnthropicProvider.d.ts.map +1 -0
- package/dist/providers/AnthropicProvider.js +321 -0
- package/dist/providers/AnthropicProvider.js.map +1 -0
- package/dist/providers/GeminiProvider.d.ts +4 -1
- package/dist/providers/GeminiProvider.d.ts.map +1 -1
- package/dist/providers/GeminiProvider.js +96 -0
- package/dist/providers/GeminiProvider.js.map +1 -1
- package/dist/providers/OpenAIProvider.d.ts +4 -1
- package/dist/providers/OpenAIProvider.d.ts.map +1 -1
- package/dist/providers/OpenAIProvider.js +115 -0
- package/dist/providers/OpenAIProvider.js.map +1 -1
- package/dist/providers/OpenRouterProvider.d.ts +4 -1
- package/dist/providers/OpenRouterProvider.d.ts.map +1 -1
- package/dist/providers/OpenRouterProvider.js +115 -0
- package/dist/providers/OpenRouterProvider.js.map +1 -1
- package/dist/providers/index.d.ts +13 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +9 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/types/ai.d.ts +28 -0
- package/dist/types/ai.d.ts.map +1 -1
- package/dist/types/route.d.ts +6 -0
- package/dist/types/route.d.ts.map +1 -1
- package/docs/API_REFERENCE.md +357 -2
- package/docs/CONSTRUCTOR_OPTIONS.md +178 -37
- package/docs/GETTING_STARTED.md +10 -2
- package/docs/PROVIDERS.md +139 -2
- package/examples/business-onboarding.ts +708 -0
- package/examples/declarative-agent.ts +1 -1
- package/examples/domain-scoping.ts +267 -0
- package/examples/healthcare-agent.ts +4 -4
- package/examples/openai-agent.ts +6 -4
- package/examples/rules-prohibitions.ts +258 -0
- package/examples/streaming-agent.ts +371 -0
- package/examples/travel-agent.ts +7 -4
- package/package.json +2 -1
- package/src/core/Agent.ts +220 -1
- package/src/core/DomainRegistry.ts +30 -0
- package/src/core/PromptBuilder.ts +70 -3
- package/src/core/Route.ts +28 -0
- package/src/index.ts +2 -0
- package/src/providers/AnthropicProvider.ts +467 -0
- package/src/providers/GeminiProvider.ts +135 -0
- package/src/providers/OpenAIProvider.ts +157 -0
- package/src/providers/OpenRouterProvider.ts +157 -0
- package/src/providers/index.ts +16 -0
- package/src/types/ai.ts +32 -0
- package/src/types/route.ts +6 -0
|
@@ -13,6 +13,7 @@ import type {
|
|
|
13
13
|
AiProvider,
|
|
14
14
|
GenerateMessageInput,
|
|
15
15
|
GenerateMessageOutput,
|
|
16
|
+
GenerateMessageStreamChunk,
|
|
16
17
|
AgentStructuredResponse,
|
|
17
18
|
} from "../types/ai";
|
|
18
19
|
import { withTimeoutAndRetry } from "../utils/retry";
|
|
@@ -124,6 +125,12 @@ export class GeminiProvider implements AiProvider {
|
|
|
124
125
|
return this.generateWithBackup(input);
|
|
125
126
|
}
|
|
126
127
|
|
|
128
|
+
async *generateMessageStream<TContext = unknown>(
|
|
129
|
+
input: GenerateMessageInput<TContext>
|
|
130
|
+
): AsyncGenerator<GenerateMessageStreamChunk> {
|
|
131
|
+
yield* this.generateStreamWithBackup(input);
|
|
132
|
+
}
|
|
133
|
+
|
|
127
134
|
private async generateWithBackup<TContext = unknown>(
|
|
128
135
|
input: GenerateMessageInput<TContext>
|
|
129
136
|
): Promise<GenerateMessageOutput> {
|
|
@@ -236,4 +243,132 @@ export class GeminiProvider implements AiProvider {
|
|
|
236
243
|
`Gemini ${model}`
|
|
237
244
|
);
|
|
238
245
|
}
|
|
246
|
+
|
|
247
|
+
private async *generateStreamWithBackup<TContext = unknown>(
|
|
248
|
+
input: GenerateMessageInput<TContext>
|
|
249
|
+
): AsyncGenerator<GenerateMessageStreamChunk> {
|
|
250
|
+
// Try primary model first
|
|
251
|
+
try {
|
|
252
|
+
yield* this.generateStreamWithModel(this.primaryModel, input);
|
|
253
|
+
} catch (primaryError: unknown) {
|
|
254
|
+
const primaryErrMsg = String(primaryError);
|
|
255
|
+
console.warn(
|
|
256
|
+
`[GEMINI] Primary model ${this.primaryModel} failed: ${primaryErrMsg}`
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
if (!shouldUseBackupModel(primaryError)) {
|
|
260
|
+
throw primaryError;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
console.log(`[GEMINI] Trying backup models for streaming`);
|
|
264
|
+
|
|
265
|
+
let lastBackupError: unknown = primaryError;
|
|
266
|
+
|
|
267
|
+
for (let i = 0; i < this.backupModels.length; i++) {
|
|
268
|
+
const backupModel = this.backupModels[i];
|
|
269
|
+
console.log(
|
|
270
|
+
`[GEMINI] Trying backup model ${i + 1}/${
|
|
271
|
+
this.backupModels.length
|
|
272
|
+
}: ${backupModel}`
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
try {
|
|
276
|
+
yield* this.generateStreamWithModel(backupModel, input);
|
|
277
|
+
console.log(`[GEMINI] Backup model ${backupModel} succeeded`);
|
|
278
|
+
return;
|
|
279
|
+
} catch (backupError: unknown) {
|
|
280
|
+
const backupErrMsg = String(backupError);
|
|
281
|
+
console.warn(
|
|
282
|
+
`[GEMINI] Backup model ${backupModel} failed: ${backupErrMsg}`
|
|
283
|
+
);
|
|
284
|
+
lastBackupError = backupError;
|
|
285
|
+
|
|
286
|
+
if (
|
|
287
|
+
!shouldUseBackupModel(backupError) &&
|
|
288
|
+
i < this.backupModels.length - 1
|
|
289
|
+
) {
|
|
290
|
+
console.log(
|
|
291
|
+
`[GEMINI] Backup model error doesn't qualify for further attempts`
|
|
292
|
+
);
|
|
293
|
+
break;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const lastBackupErrMsg = String(lastBackupError);
|
|
299
|
+
console.error(
|
|
300
|
+
`[GEMINI] All models failed. Primary: ${primaryErrMsg}, Last backup: ${lastBackupErrMsg}`
|
|
301
|
+
);
|
|
302
|
+
throw lastBackupError;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
private async *generateStreamWithModel<TContext = unknown>(
|
|
307
|
+
model: string,
|
|
308
|
+
input: GenerateMessageInput<TContext>
|
|
309
|
+
): AsyncGenerator<GenerateMessageStreamChunk> {
|
|
310
|
+
// Enable JSON mode if requested
|
|
311
|
+
const configOverride: Partial<GenerateContentConfig> = { ...this.config };
|
|
312
|
+
if (input.parameters?.jsonMode) {
|
|
313
|
+
configOverride.responseMimeType = "application/json";
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const stream = await this.genAI.models.generateContentStream({
|
|
317
|
+
model,
|
|
318
|
+
contents: input.prompt,
|
|
319
|
+
config: configOverride,
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
let accumulated = "";
|
|
323
|
+
let promptTokenCount = 0;
|
|
324
|
+
let candidatesTokenCount = 0;
|
|
325
|
+
let totalTokenCount = 0;
|
|
326
|
+
|
|
327
|
+
for await (const chunk of stream) {
|
|
328
|
+
const delta = chunk.text || "";
|
|
329
|
+
|
|
330
|
+
if (delta) {
|
|
331
|
+
accumulated += delta;
|
|
332
|
+
yield {
|
|
333
|
+
delta,
|
|
334
|
+
accumulated,
|
|
335
|
+
done: false,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Update token counts if available
|
|
340
|
+
if (chunk.usageMetadata) {
|
|
341
|
+
promptTokenCount = chunk.usageMetadata.promptTokenCount || 0;
|
|
342
|
+
candidatesTokenCount = chunk.usageMetadata.candidatesTokenCount || 0;
|
|
343
|
+
totalTokenCount = chunk.usageMetadata.totalTokenCount || 0;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Parse JSON response if JSON mode was enabled
|
|
348
|
+
let structured: AgentStructuredResponse | undefined;
|
|
349
|
+
if (input.parameters?.jsonMode && accumulated) {
|
|
350
|
+
try {
|
|
351
|
+
structured = JSON.parse(accumulated) as AgentStructuredResponse;
|
|
352
|
+
} catch (error) {
|
|
353
|
+
console.warn(
|
|
354
|
+
"[GEMINI] Failed to parse JSON response in stream:",
|
|
355
|
+
error
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Yield final chunk
|
|
361
|
+
yield {
|
|
362
|
+
delta: "",
|
|
363
|
+
accumulated,
|
|
364
|
+
done: true,
|
|
365
|
+
metadata: {
|
|
366
|
+
model,
|
|
367
|
+
tokensUsed: totalTokenCount,
|
|
368
|
+
promptTokens: promptTokenCount,
|
|
369
|
+
completionTokens: candidatesTokenCount,
|
|
370
|
+
},
|
|
371
|
+
structured,
|
|
372
|
+
};
|
|
373
|
+
}
|
|
239
374
|
}
|
|
@@ -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
|
}
|
package/src/types/route.ts
CHANGED
|
@@ -41,6 +41,12 @@ export interface RouteOptions {
|
|
|
41
41
|
conditions?: string[];
|
|
42
42
|
/** Initial guidelines for this route */
|
|
43
43
|
guidelines?: Guideline[];
|
|
44
|
+
/** Domain names that are allowed in this route (undefined = all domains) */
|
|
45
|
+
domains?: string[];
|
|
46
|
+
/** Absolute rules the agent must follow in this route */
|
|
47
|
+
rules?: string[];
|
|
48
|
+
/** Absolute prohibitions the agent must never do in this route */
|
|
49
|
+
prohibitions?: string[];
|
|
44
50
|
}
|
|
45
51
|
|
|
46
52
|
/**
|