@genui-a3/providers 0.0.3 → 0.0.5
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 +4 -0
- package/dist/anthropic/index.cjs +115 -12
- package/dist/anthropic/index.cjs.map +1 -1
- package/dist/anthropic/index.d.cts +3 -1
- package/dist/anthropic/index.d.ts +3 -1
- package/dist/anthropic/index.js +115 -12
- package/dist/anthropic/index.js.map +1 -1
- package/dist/bedrock/index.cjs +115 -12
- package/dist/bedrock/index.cjs.map +1 -1
- package/dist/bedrock/index.d.cts +3 -1
- package/dist/bedrock/index.d.ts +3 -1
- package/dist/bedrock/index.js +115 -12
- package/dist/bedrock/index.js.map +1 -1
- package/dist/openai/index.cjs +115 -12
- package/dist/openai/index.cjs.map +1 -1
- package/dist/openai/index.d.cts +3 -1
- package/dist/openai/index.d.ts +3 -1
- package/dist/openai/index.js +115 -12
- package/dist/openai/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -119,6 +119,9 @@ const provider = createBedrockProvider({
|
|
|
119
119
|
|
|
120
120
|
If the primary model fails, the provider automatically retries with the next model in the list. If all models fail, the last error is thrown.
|
|
121
121
|
|
|
122
|
+
All providers include built-in resilience: automatic retries with exponential backoff, per-request and total timeouts, and model fallback.
|
|
123
|
+
See the [Resilience documentation](../docs/RESILIENCE.md) for configuration options and defaults.
|
|
124
|
+
|
|
122
125
|
## Per-Agent Provider Override
|
|
123
126
|
|
|
124
127
|
Each agent can override the session-level provider:
|
|
@@ -152,6 +155,7 @@ Both providers implement the `Provider` interface from `@genui-a3/core`:
|
|
|
152
155
|
| `name` | Human-readable name (`'bedrock'` or `'openai'`) |
|
|
153
156
|
|
|
154
157
|
To create a custom provider, implement this interface and pass it to `ChatSession` or an individual agent.
|
|
158
|
+
See [Creating a Custom Provider](./CUSTOM_PROVIDERS.md) for a step-by-step guide to building your own.
|
|
155
159
|
|
|
156
160
|
## Exports
|
|
157
161
|
|
package/dist/anthropic/index.cjs
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
var anthropic = require('@ai-sdk/anthropic');
|
|
4
4
|
var ai = require('ai');
|
|
5
|
+
var core = require('@genui-a3/core');
|
|
5
6
|
var client = require('@ag-ui/client');
|
|
6
7
|
|
|
7
8
|
// anthropic/index.ts
|
|
@@ -69,20 +70,119 @@ function extractDelta(partial, prevLength) {
|
|
|
69
70
|
return chatbotMessage.slice(prevLength);
|
|
70
71
|
}
|
|
71
72
|
|
|
73
|
+
// utils/backoff.ts
|
|
74
|
+
function calculateBackoff(attempt, config) {
|
|
75
|
+
let delay;
|
|
76
|
+
switch (config.strategy) {
|
|
77
|
+
case "exponential":
|
|
78
|
+
delay = config.baseDelayMs * Math.pow(2, attempt);
|
|
79
|
+
break;
|
|
80
|
+
case "linear":
|
|
81
|
+
delay = config.baseDelayMs * (attempt + 1);
|
|
82
|
+
break;
|
|
83
|
+
case "fixed":
|
|
84
|
+
delay = config.baseDelayMs;
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
delay = Math.min(delay, config.maxDelayMs);
|
|
88
|
+
if (config.jitter) {
|
|
89
|
+
delay = Math.random() * delay;
|
|
90
|
+
}
|
|
91
|
+
return delay;
|
|
92
|
+
}
|
|
93
|
+
function sleep(ms, signal) {
|
|
94
|
+
return new Promise((resolve, reject) => {
|
|
95
|
+
if (signal?.aborted) {
|
|
96
|
+
reject(signal.reason);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const timer = setTimeout(resolve, ms);
|
|
100
|
+
signal?.addEventListener(
|
|
101
|
+
"abort",
|
|
102
|
+
() => {
|
|
103
|
+
clearTimeout(timer);
|
|
104
|
+
reject(signal.reason);
|
|
105
|
+
},
|
|
106
|
+
{ once: true }
|
|
107
|
+
);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
72
111
|
// utils/executeWithFallback.ts
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
112
|
+
function buildSignal(requestTimeoutMs, totalAbort) {
|
|
113
|
+
const signals = [];
|
|
114
|
+
if (requestTimeoutMs !== void 0) {
|
|
115
|
+
signals.push(AbortSignal.timeout(requestTimeoutMs));
|
|
116
|
+
}
|
|
117
|
+
if (totalAbort) {
|
|
118
|
+
signals.push(totalAbort);
|
|
119
|
+
}
|
|
120
|
+
if (signals.length === 0) return void 0;
|
|
121
|
+
if (signals.length === 1) return signals[0];
|
|
122
|
+
return AbortSignal.any(signals);
|
|
123
|
+
}
|
|
124
|
+
async function attemptAction(action, model, signal, attempt, errors) {
|
|
125
|
+
try {
|
|
126
|
+
const value = await action(model, signal);
|
|
127
|
+
return { ok: true, value };
|
|
128
|
+
} catch (error) {
|
|
129
|
+
const errorObj = error;
|
|
130
|
+
errors.push({ model, attempt, error: errorObj });
|
|
131
|
+
return { ok: false, error: errorObj };
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
function checkTotalTimeout(totalAbort, timeoutMs, errors) {
|
|
135
|
+
if (totalAbort?.signal.aborted) {
|
|
136
|
+
throw new core.A3TimeoutError(`Total timeout of ${timeoutMs}ms exceeded`, errors);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
async function handleAttemptError(errorObj, attempt, maxRetries, retryAll, resolved, totalAbort) {
|
|
140
|
+
const isLastAttempt = attempt === 1 + maxRetries;
|
|
141
|
+
const isRetryable = retryAll || resolved.isRetryableError(errorObj);
|
|
142
|
+
if (isRetryable && !isLastAttempt) {
|
|
143
|
+
const delay = calculateBackoff(attempt - 1, resolved.backoff);
|
|
144
|
+
await sleep(delay, totalAbort?.signal).catch(() => {
|
|
145
|
+
});
|
|
146
|
+
return "retry";
|
|
147
|
+
}
|
|
148
|
+
return "next-model";
|
|
149
|
+
}
|
|
150
|
+
async function executeWithFallback(models, action, config) {
|
|
151
|
+
const resolved = config ?? core.DEFAULT_RESILIENCE_CONFIG;
|
|
152
|
+
const errors = [];
|
|
153
|
+
const maxRetries = resolved.retry === false ? 0 : resolved.retry.maxAttempts;
|
|
154
|
+
const retryAll = resolved.retry !== false && resolved.retry.retryOn === "all";
|
|
155
|
+
let totalAbort;
|
|
156
|
+
let totalTimer;
|
|
157
|
+
if (resolved.timeout.totalTimeoutMs !== void 0) {
|
|
158
|
+
totalAbort = new AbortController();
|
|
159
|
+
totalTimer = setTimeout(
|
|
160
|
+
() => totalAbort.abort(new Error("Total timeout exceeded")),
|
|
161
|
+
resolved.timeout.totalTimeoutMs
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
try {
|
|
165
|
+
for (let modelIndex = 0; modelIndex < models.length; modelIndex++) {
|
|
166
|
+
const model = models[modelIndex];
|
|
167
|
+
for (let attempt = 1; attempt <= 1 + maxRetries; attempt++) {
|
|
168
|
+
checkTotalTimeout(totalAbort, resolved.timeout.totalTimeoutMs, errors);
|
|
169
|
+
const signal = buildSignal(resolved.timeout.requestTimeoutMs, totalAbort?.signal);
|
|
170
|
+
const result = await attemptAction(action, model, signal, attempt, errors);
|
|
171
|
+
if (result.ok) return result.value;
|
|
172
|
+
checkTotalTimeout(totalAbort, resolved.timeout.totalTimeoutMs, errors);
|
|
173
|
+
const decision = await handleAttemptError(result.error, attempt, maxRetries, retryAll, resolved, totalAbort);
|
|
174
|
+
if (decision === "next-model") break;
|
|
82
175
|
}
|
|
83
176
|
}
|
|
177
|
+
throw new core.A3ResilienceError(
|
|
178
|
+
`All models failed after ${errors.length} total attempt(s): ${models.join(", ")}`,
|
|
179
|
+
errors
|
|
180
|
+
);
|
|
181
|
+
} finally {
|
|
182
|
+
if (totalTimer !== void 0) {
|
|
183
|
+
clearTimeout(totalTimer);
|
|
184
|
+
}
|
|
84
185
|
}
|
|
85
|
-
throw new Error("All models failed");
|
|
86
186
|
}
|
|
87
187
|
|
|
88
188
|
// anthropic/index.ts
|
|
@@ -136,20 +236,23 @@ function createAnthropicProvider(config) {
|
|
|
136
236
|
baseURL: config.baseURL
|
|
137
237
|
});
|
|
138
238
|
const models = config.models;
|
|
239
|
+
const resilience = core.resolveResilienceConfig(config.resilience);
|
|
139
240
|
return {
|
|
140
241
|
name: "anthropic",
|
|
141
242
|
async sendRequest(request) {
|
|
142
243
|
const messages = toAIMessages(request.messages);
|
|
143
244
|
return executeWithFallback(
|
|
144
245
|
models,
|
|
145
|
-
(model) => sendWithModel(anthropicProvider, model, request.systemPrompt, messages, request.responseSchema)
|
|
246
|
+
(model) => sendWithModel(anthropicProvider, model, request.systemPrompt, messages, request.responseSchema),
|
|
247
|
+
resilience
|
|
146
248
|
);
|
|
147
249
|
},
|
|
148
250
|
async *sendRequestStream(request) {
|
|
149
251
|
const messages = toAIMessages(request.messages);
|
|
150
252
|
const { result, reader, first } = await executeWithFallback(
|
|
151
253
|
models,
|
|
152
|
-
(model) => sendStreamWithModel(anthropicProvider, model, request.systemPrompt, messages, request.responseSchema)
|
|
254
|
+
(model) => sendStreamWithModel(anthropicProvider, model, request.systemPrompt, messages, request.responseSchema),
|
|
255
|
+
resilience
|
|
153
256
|
);
|
|
154
257
|
yield* processAnthropicStream(result, reader, first, "anthropic", request.responseSchema);
|
|
155
258
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../anthropic/streamProcessor.ts","../../utils/executeWithFallback.ts","../../anthropic/index.ts"],"names":["EventType","generateText","Output","streamText","createAnthropic"],"mappings":"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","file":"index.cjs","sourcesContent":["import { EventType } from '@ag-ui/client'\nimport { ZodType } from 'zod'\nimport type { AgentId, StreamEvent, BaseState } from '@genui-a3/core'\nimport type { StreamTextResult, ToolSet } from 'ai'\n\n/**\n * Processes an Anthropic streaming response (via Vercel AI SDK) into AG-UI events.\n *\n * Uses `partialOutputStream` from `streamText` + `Output.object()` to receive\n * progressively-built partial objects. Tracks `chatbotMessage` growth to yield\n * TEXT_MESSAGE_CONTENT deltas. After the stream completes, validates the final\n * object and yields TOOL_CALL_RESULT.\n *\n * @param streamResult - The streamText result containing partialOutputStream and output promise\n * @param reader - Pre-started async iterator for the partial object stream\n * @param first - The first iteration result (already consumed to trigger the API call)\n * @param agentId - Agent identifier for event tagging\n * @param schema - Zod schema for final response validation\n * @returns Async generator of AG-UI stream events\n */\nexport async function* processAnthropicStream<TState extends BaseState = BaseState>(\n streamResult: StreamTextResult<ToolSet, never>,\n reader: AsyncIterator<unknown>,\n first: IteratorResult<unknown>,\n agentId: AgentId,\n schema: ZodType,\n): AsyncGenerator<StreamEvent<TState>> {\n let prevMessageLength = 0\n\n try {\n // Process the first partial (already consumed to trigger the API call)\n if (!first.done) {\n const partial = first.value as Record<string, unknown>\n const delta = extractDelta(partial, prevMessageLength)\n if (delta) {\n prevMessageLength += delta.length\n yield {\n type: EventType.TEXT_MESSAGE_CONTENT,\n messageId: '',\n delta,\n agentId,\n } as StreamEvent<TState>\n }\n }\n\n // Process remaining partials\n let next = await reader.next()\n while (!next.done) {\n const partial = next.value as Record<string, unknown>\n const delta = extractDelta(partial, prevMessageLength)\n if (delta) {\n prevMessageLength += delta.length\n yield {\n type: EventType.TEXT_MESSAGE_CONTENT,\n messageId: '',\n delta,\n agentId,\n } as StreamEvent<TState>\n }\n // eslint-disable-next-line no-await-in-loop\n next = await reader.next()\n }\n\n // Stream complete — await and validate the final object\n const finalObject = await streamResult.output\n\n if (finalObject === null) {\n yield {\n type: EventType.RUN_ERROR,\n message: 'Anthropic stream completed with null output',\n agentId,\n } as StreamEvent<TState>\n return\n }\n\n const validated = schema.parse(finalObject)\n yield {\n type: EventType.TOOL_CALL_RESULT,\n toolCallId: '',\n messageId: '',\n content: JSON.stringify(validated),\n agentId,\n } as StreamEvent<TState>\n } catch (err) {\n yield {\n type: EventType.RUN_ERROR,\n message: `Anthropic stream error: ${(err as Error).message}`,\n agentId,\n } as StreamEvent<TState>\n }\n}\n\n/**\n * Extracts the new portion of chatbotMessage from a partial object.\n */\nfunction extractDelta(partial: Record<string, unknown>, prevLength: number): string | null {\n const chatbotMessage = partial.chatbotMessage\n if (typeof chatbotMessage !== 'string' || chatbotMessage.length <= prevLength) {\n return null\n }\n return chatbotMessage.slice(prevLength)\n}\n","/**\n * Executes an action with model fallback support.\n * Tries each model in order; if one fails, falls back to the next.\n * Throws the last error if all models fail.\n *\n * @param models - Model identifiers in priority order\n * @param action - Async action to attempt with each model\n * @returns The result from the first successful model\n * @throws The error from the last model if all fail\n *\n * @example\n * ```typescript\n * const result = await executeWithFallback(\n * ['model-primary', 'model-fallback'],\n * (model) => provider.call(model, params),\n * )\n * ```\n */\nexport async function executeWithFallback<T>(models: string[], action: (model: string) => Promise<T>): Promise<T> {\n const errors: Array<{ model: string; error: Error }> = []\n\n for (let i = 0; i < models.length; i++) {\n const model = models[i]\n\n try {\n // eslint-disable-next-line no-await-in-loop\n return await action(model)\n } catch (error) {\n const errorObj = error as Error\n errors.push({ model, error: errorObj })\n\n if (i === models.length - 1) {\n throw errorObj\n }\n }\n }\n\n throw new Error('All models failed')\n}\n","import { createAnthropic } from '@ai-sdk/anthropic'\nimport { generateText, streamText, Output, ModelMessage } from 'ai'\nimport type {\n Provider,\n ProviderRequest,\n ProviderResponse,\n ProviderMessage,\n BaseState,\n StreamEvent,\n} from '@genui-a3/core'\nimport { processAnthropicStream } from './streamProcessor'\nimport { executeWithFallback } from '../utils/executeWithFallback'\n\n/**\n * Configuration for creating an Anthropic provider.\n */\nexport interface AnthropicProviderConfig {\n /** Anthropic API key. Defaults to ANTHROPIC_API_KEY env var. */\n apiKey?: string\n /**\n * Model identifiers in order of preference (first = primary, rest = fallbacks).\n * e.g. ['claude-sonnet-4-5-20250929', 'claude-haiku-4-5-20251001']\n */\n models: string[]\n /** Optional custom base URL for the Anthropic API */\n baseURL?: string\n}\n\nfunction toAIMessages(messages: ProviderMessage[]): ModelMessage[] {\n return messages.map((msg) => ({\n role: msg.role,\n content: msg.content,\n }))\n}\n\nexport function prepareMessages(messages: ModelMessage[]): ModelMessage[] {\n if (messages.length === 0) return messages\n const lastMessage = messages[messages.length - 1]\n if (lastMessage.role === 'assistant') {\n return [...messages, { role: 'user', content: 'Continue' }]\n }\n return messages\n}\n\nasync function sendWithModel(\n anthropicProvider: ReturnType<typeof createAnthropic>,\n model: string,\n system: string,\n messages: ModelMessage[],\n schema: ProviderRequest['responseSchema'],\n): Promise<ProviderResponse> {\n const preparedMessages = prepareMessages(messages)\n const result = await generateText({\n model: anthropicProvider(model),\n system,\n messages: preparedMessages,\n output: Output.object({ schema }),\n })\n\n return {\n content: JSON.stringify(result.output),\n usage: {\n inputTokens: result.usage.inputTokens ?? 0,\n outputTokens: result.usage.outputTokens ?? 0,\n totalTokens: (result.usage.inputTokens ?? 0) + (result.usage.outputTokens ?? 0),\n },\n }\n}\n\nasync function sendStreamWithModel(\n anthropicProvider: ReturnType<typeof createAnthropic>,\n model: string,\n system: string,\n messages: ModelMessage[],\n schema: ProviderRequest['responseSchema'],\n) {\n const preparedMessages = prepareMessages(messages)\n const result = streamText({\n model: anthropicProvider(model),\n system,\n messages: preparedMessages,\n output: Output.object({ schema }),\n })\n\n // Force the API call to start so executeWithFallback can catch connection errors\n const partialStream = result.partialOutputStream\n const reader = partialStream[Symbol.asyncIterator]()\n const first = await reader.next()\n\n return { result, reader, first }\n}\n\n/**\n * Creates an Anthropic provider instance.\n *\n * Uses the Vercel AI SDK (`ai` + `@ai-sdk/anthropic`) for structured output via\n * `generateText` + `Output.object()` (blocking) and `streamText` + `Output.object()`\n * (streaming). The AI SDK handles Zod-to-JSON-schema conversion, partial JSON\n * parsing, and validation internally.\n *\n * @param config - Anthropic provider configuration\n * @returns A Provider implementation using Anthropic\n *\n * @example\n * ```typescript\n * const provider = createAnthropicProvider({\n * models: ['claude-sonnet-4-5-20250929', 'claude-haiku-4-5-20251001'],\n * })\n * ```\n */\nexport function createAnthropicProvider(config: AnthropicProviderConfig): Provider {\n const anthropicProvider = createAnthropic({\n apiKey: config.apiKey,\n baseURL: config.baseURL,\n })\n const models = config.models\n\n return {\n name: 'anthropic',\n\n async sendRequest(request: ProviderRequest): Promise<ProviderResponse> {\n const messages = toAIMessages(request.messages)\n\n return executeWithFallback(models, (model) =>\n sendWithModel(anthropicProvider, model, request.systemPrompt, messages, request.responseSchema),\n )\n },\n\n async *sendRequestStream<TState extends BaseState = BaseState>(\n request: ProviderRequest,\n ): AsyncGenerator<StreamEvent<TState>> {\n const messages = toAIMessages(request.messages)\n\n const { result, reader, first } = await executeWithFallback(models, (model) =>\n sendStreamWithModel(anthropicProvider, model, request.systemPrompt, messages, request.responseSchema),\n )\n\n yield* processAnthropicStream<TState>(result, reader, first, 'anthropic', request.responseSchema)\n },\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../anthropic/streamProcessor.ts","../../utils/backoff.ts","../../utils/executeWithFallback.ts","../../anthropic/index.ts"],"names":["EventType","A3TimeoutError","DEFAULT_RESILIENCE_CONFIG","A3ResilienceError","generateText","Output","streamText","createAnthropic","resolveResilienceConfig"],"mappings":"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","file":"index.cjs","sourcesContent":["import { EventType } from '@ag-ui/client'\nimport { ZodType } from 'zod'\nimport type { AgentId, StreamEvent, BaseState } from '@genui-a3/core'\nimport type { StreamTextResult, ToolSet } from 'ai'\n\n/**\n * Processes an Anthropic streaming response (via Vercel AI SDK) into AG-UI events.\n *\n * Uses `partialOutputStream` from `streamText` + `Output.object()` to receive\n * progressively-built partial objects. Tracks `chatbotMessage` growth to yield\n * TEXT_MESSAGE_CONTENT deltas. After the stream completes, validates the final\n * object and yields TOOL_CALL_RESULT.\n *\n * @param streamResult - The streamText result containing partialOutputStream and output promise\n * @param reader - Pre-started async iterator for the partial object stream\n * @param first - The first iteration result (already consumed to trigger the API call)\n * @param agentId - Agent identifier for event tagging\n * @param schema - Zod schema for final response validation\n * @returns Async generator of AG-UI stream events\n */\nexport async function* processAnthropicStream<TState extends BaseState = BaseState>(\n streamResult: StreamTextResult<ToolSet, never>,\n reader: AsyncIterator<unknown>,\n first: IteratorResult<unknown>,\n agentId: AgentId,\n schema: ZodType,\n): AsyncGenerator<StreamEvent<TState>> {\n let prevMessageLength = 0\n\n try {\n // Process the first partial (already consumed to trigger the API call)\n if (!first.done) {\n const partial = first.value as Record<string, unknown>\n const delta = extractDelta(partial, prevMessageLength)\n if (delta) {\n prevMessageLength += delta.length\n yield {\n type: EventType.TEXT_MESSAGE_CONTENT,\n messageId: '',\n delta,\n agentId,\n } as StreamEvent<TState>\n }\n }\n\n // Process remaining partials\n let next = await reader.next()\n while (!next.done) {\n const partial = next.value as Record<string, unknown>\n const delta = extractDelta(partial, prevMessageLength)\n if (delta) {\n prevMessageLength += delta.length\n yield {\n type: EventType.TEXT_MESSAGE_CONTENT,\n messageId: '',\n delta,\n agentId,\n } as StreamEvent<TState>\n }\n // eslint-disable-next-line no-await-in-loop\n next = await reader.next()\n }\n\n // Stream complete — await and validate the final object\n const finalObject = await streamResult.output\n\n if (finalObject === null) {\n yield {\n type: EventType.RUN_ERROR,\n message: 'Anthropic stream completed with null output',\n agentId,\n } as StreamEvent<TState>\n return\n }\n\n const validated = schema.parse(finalObject)\n yield {\n type: EventType.TOOL_CALL_RESULT,\n toolCallId: '',\n messageId: '',\n content: JSON.stringify(validated),\n agentId,\n } as StreamEvent<TState>\n } catch (err) {\n yield {\n type: EventType.RUN_ERROR,\n message: `Anthropic stream error: ${(err as Error).message}`,\n agentId,\n } as StreamEvent<TState>\n }\n}\n\n/**\n * Extracts the new portion of chatbotMessage from a partial object.\n */\nfunction extractDelta(partial: Record<string, unknown>, prevLength: number): string | null {\n const chatbotMessage = partial.chatbotMessage\n if (typeof chatbotMessage !== 'string' || chatbotMessage.length <= prevLength) {\n return null\n }\n return chatbotMessage.slice(prevLength)\n}\n","import type { BackoffConfig } from '@genui-a3/core'\n\n/**\n * Calculates the backoff delay for a given retry attempt.\n *\n * @param attempt - Zero-based attempt index (0 = first retry)\n * @param config - Backoff configuration with all fields required\n * @returns Delay in milliseconds\n */\nexport function calculateBackoff(attempt: number, config: Required<BackoffConfig>): number {\n let delay: number\n\n switch (config.strategy) {\n case 'exponential':\n delay = config.baseDelayMs * Math.pow(2, attempt)\n break\n case 'linear':\n delay = config.baseDelayMs * (attempt + 1)\n break\n case 'fixed':\n delay = config.baseDelayMs\n break\n }\n\n delay = Math.min(delay, config.maxDelayMs)\n\n if (config.jitter) {\n delay = Math.random() * delay\n }\n\n return delay\n}\n\n/**\n * Sleeps for the specified duration. Can be aborted via an AbortSignal.\n *\n * @param ms - Duration in milliseconds\n * @param signal - Optional AbortSignal to cancel the sleep early\n */\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n if (signal?.aborted) {\n reject(signal.reason as Error)\n return\n }\n\n const timer = setTimeout(resolve, ms)\n\n signal?.addEventListener(\n 'abort',\n () => {\n clearTimeout(timer)\n reject(signal.reason as Error)\n },\n { once: true },\n )\n })\n}\n","import {\n A3ResilienceError,\n A3TimeoutError,\n DEFAULT_RESILIENCE_CONFIG,\n type ResilienceErrorEntry,\n type ResolvedResilienceConfig,\n} from '@genui-a3/core'\nimport { calculateBackoff, sleep } from './backoff'\n\n/**\n * Builds an AbortSignal that fires when either the per-request timeout or total timeout expires.\n */\nfunction buildSignal(requestTimeoutMs?: number, totalAbort?: AbortSignal): AbortSignal | undefined {\n const signals: AbortSignal[] = []\n\n if (requestTimeoutMs !== undefined) {\n signals.push(AbortSignal.timeout(requestTimeoutMs))\n }\n\n if (totalAbort) {\n signals.push(totalAbort)\n }\n\n if (signals.length === 0) return undefined\n if (signals.length === 1) return signals[0]\n return AbortSignal.any(signals)\n}\n\ntype AttemptResult<T> = { ok: true; value: T } | { ok: false; error: Error }\n\nasync function attemptAction<T>(\n action: (model: string, signal?: AbortSignal) => Promise<T>,\n model: string,\n signal: AbortSignal | undefined,\n attempt: number,\n errors: ResilienceErrorEntry[],\n): Promise<AttemptResult<T>> {\n try {\n const value = await action(model, signal)\n return { ok: true, value }\n } catch (error) {\n const errorObj = error as Error\n errors.push({ model, attempt, error: errorObj })\n return { ok: false, error: errorObj }\n }\n}\n\nfunction checkTotalTimeout(totalAbort: AbortController | undefined, timeoutMs: number | undefined, errors: ResilienceErrorEntry[]): void {\n if (totalAbort?.signal.aborted) {\n throw new A3TimeoutError(`Total timeout of ${timeoutMs}ms exceeded`, errors)\n }\n}\n\nasync function handleAttemptError(\n errorObj: Error,\n attempt: number,\n maxRetries: number,\n retryAll: boolean,\n resolved: ResolvedResilienceConfig,\n totalAbort: AbortController | undefined,\n): Promise<'retry' | 'next-model'> {\n const isLastAttempt = attempt === 1 + maxRetries\n const isRetryable = retryAll || resolved.isRetryableError(errorObj)\n\n if (isRetryable && !isLastAttempt) {\n const delay = calculateBackoff(attempt - 1, resolved.backoff)\n await sleep(delay, totalAbort?.signal).catch(() => {\n // Sleep was aborted by total timeout — will be caught at top of loop\n })\n return 'retry'\n }\n\n return 'next-model'\n}\n\n/**\n * Executes an action with model fallback, retry, backoff, and timeout support.\n *\n * For each model (in priority order):\n * 1. Attempts the action up to `1 + maxAttempts` times\n * 2. On transient errors, waits with backoff before retrying\n * 3. On non-retryable errors (or after exhausting retries), falls back to the next model\n *\n * Throws `A3ResilienceError` with full error history when all models are exhausted.\n * Throws `A3TimeoutError` when the total timeout is exceeded.\n *\n * @param models - Model identifiers in priority order\n * @param action - Async action to attempt with each model. Receives an optional AbortSignal.\n * @param config - Resolved resilience configuration (defaults applied if omitted)\n * @returns The result from the first successful attempt\n * @throws {A3ResilienceError} When all models and retries are exhausted\n * @throws {A3TimeoutError} When the total timeout is exceeded\n *\n * @example\n * ```typescript\n * const result = await executeWithFallback(\n * ['model-primary', 'model-fallback'],\n * (model, signal) => provider.call(model, params, { abortSignal: signal }),\n * resolvedConfig,\n * )\n * ```\n */\nexport async function executeWithFallback<T>(\n models: string[],\n action: (model: string, signal?: AbortSignal) => Promise<T>,\n config?: ResolvedResilienceConfig,\n): Promise<T> {\n const resolved = config ?? DEFAULT_RESILIENCE_CONFIG\n const errors: ResilienceErrorEntry[] = []\n const maxRetries = resolved.retry === false ? 0 : resolved.retry.maxAttempts\n const retryAll = resolved.retry !== false && resolved.retry.retryOn === 'all'\n\n // Total timeout controller\n let totalAbort: AbortController | undefined\n let totalTimer: ReturnType<typeof setTimeout> | undefined\n\n if (resolved.timeout.totalTimeoutMs !== undefined) {\n totalAbort = new AbortController()\n totalTimer = setTimeout(\n () => totalAbort!.abort(new Error('Total timeout exceeded')),\n resolved.timeout.totalTimeoutMs,\n )\n }\n\n try {\n for (let modelIndex = 0; modelIndex < models.length; modelIndex++) {\n const model = models[modelIndex]\n\n for (let attempt = 1; attempt <= 1 + maxRetries; attempt++) {\n checkTotalTimeout(totalAbort, resolved.timeout.totalTimeoutMs, errors)\n\n const signal = buildSignal(resolved.timeout.requestTimeoutMs, totalAbort?.signal)\n // eslint-disable-next-line no-await-in-loop\n const result = await attemptAction(action, model, signal, attempt, errors)\n if (result.ok) return result.value\n\n checkTotalTimeout(totalAbort, resolved.timeout.totalTimeoutMs, errors)\n\n // eslint-disable-next-line no-await-in-loop\n const decision = await handleAttemptError(result.error, attempt, maxRetries, retryAll, resolved, totalAbort)\n if (decision === 'next-model') break\n }\n }\n\n // All models exhausted\n throw new A3ResilienceError(\n `All models failed after ${errors.length} total attempt(s): ${models.join(', ')}`,\n errors,\n )\n } finally {\n if (totalTimer !== undefined) {\n clearTimeout(totalTimer)\n }\n }\n}\n","import { createAnthropic } from '@ai-sdk/anthropic'\nimport { generateText, streamText, Output, ModelMessage } from 'ai'\nimport {\n resolveResilienceConfig,\n type Provider,\n type ProviderRequest,\n type ProviderResponse,\n type ProviderMessage,\n type BaseState,\n type StreamEvent,\n type ResilienceConfig,\n type ResolvedResilienceConfig,\n} from '@genui-a3/core'\nimport { processAnthropicStream } from './streamProcessor'\nimport { executeWithFallback } from '../utils/executeWithFallback'\n\n/**\n * Configuration for creating an Anthropic provider.\n */\nexport interface AnthropicProviderConfig {\n /** Anthropic API key. Defaults to ANTHROPIC_API_KEY env var. */\n apiKey?: string\n /**\n * Model identifiers in order of preference (first = primary, rest = fallbacks).\n * e.g. ['claude-sonnet-4-5-20250929', 'claude-haiku-4-5-20251001']\n */\n models: string[]\n /** Optional custom base URL for the Anthropic API */\n baseURL?: string\n /** Resilience configuration (retry, backoff, timeout). Uses industry-standard defaults if omitted. */\n resilience?: ResilienceConfig\n}\n\nfunction toAIMessages(messages: ProviderMessage[]): ModelMessage[] {\n return messages.map((msg) => ({\n role: msg.role,\n content: msg.content,\n }))\n}\n\nexport function prepareMessages(messages: ModelMessage[]): ModelMessage[] {\n if (messages.length === 0) return messages\n const lastMessage = messages[messages.length - 1]\n if (lastMessage.role === 'assistant') {\n return [...messages, { role: 'user', content: 'Continue' }]\n }\n return messages\n}\n\nasync function sendWithModel(\n anthropicProvider: ReturnType<typeof createAnthropic>,\n model: string,\n system: string,\n messages: ModelMessage[],\n schema: ProviderRequest['responseSchema'],\n): Promise<ProviderResponse> {\n const preparedMessages = prepareMessages(messages)\n const result = await generateText({\n model: anthropicProvider(model),\n system,\n messages: preparedMessages,\n output: Output.object({ schema }),\n })\n\n return {\n content: JSON.stringify(result.output),\n usage: {\n inputTokens: result.usage.inputTokens ?? 0,\n outputTokens: result.usage.outputTokens ?? 0,\n totalTokens: (result.usage.inputTokens ?? 0) + (result.usage.outputTokens ?? 0),\n },\n }\n}\n\nasync function sendStreamWithModel(\n anthropicProvider: ReturnType<typeof createAnthropic>,\n model: string,\n system: string,\n messages: ModelMessage[],\n schema: ProviderRequest['responseSchema'],\n) {\n const preparedMessages = prepareMessages(messages)\n const result = streamText({\n model: anthropicProvider(model),\n system,\n messages: preparedMessages,\n output: Output.object({ schema }),\n })\n\n // Force the API call to start so executeWithFallback can catch connection errors\n const partialStream = result.partialOutputStream\n const reader = partialStream[Symbol.asyncIterator]()\n const first = await reader.next()\n\n return { result, reader, first }\n}\n\n/**\n * Creates an Anthropic provider instance.\n *\n * Uses the Vercel AI SDK (`ai` + `@ai-sdk/anthropic`) for structured output via\n * `generateText` + `Output.object()` (blocking) and `streamText` + `Output.object()`\n * (streaming). The AI SDK handles Zod-to-JSON-schema conversion, partial JSON\n * parsing, and validation internally.\n *\n * @param config - Anthropic provider configuration\n * @returns A Provider implementation using Anthropic\n *\n * @example\n * ```typescript\n * const provider = createAnthropicProvider({\n * models: ['claude-sonnet-4-5-20250929', 'claude-haiku-4-5-20251001'],\n * })\n * ```\n */\nexport function createAnthropicProvider(config: AnthropicProviderConfig): Provider {\n const anthropicProvider = createAnthropic({\n apiKey: config.apiKey,\n baseURL: config.baseURL,\n })\n const models = config.models\n const resilience: ResolvedResilienceConfig = resolveResilienceConfig(config.resilience)\n\n return {\n name: 'anthropic',\n\n async sendRequest(request: ProviderRequest): Promise<ProviderResponse> {\n const messages = toAIMessages(request.messages)\n\n return executeWithFallback(\n models,\n (model) => sendWithModel(anthropicProvider, model, request.systemPrompt, messages, request.responseSchema),\n resilience,\n )\n },\n\n async *sendRequestStream<TState extends BaseState = BaseState>(\n request: ProviderRequest,\n ): AsyncGenerator<StreamEvent<TState>> {\n const messages = toAIMessages(request.messages)\n\n const { result, reader, first } = await executeWithFallback(\n models,\n (model) => sendStreamWithModel(anthropicProvider, model, request.systemPrompt, messages, request.responseSchema),\n resilience,\n )\n\n yield* processAnthropicStream<TState>(result, reader, first, 'anthropic', request.responseSchema)\n },\n }\n}\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ModelMessage } from 'ai';
|
|
2
|
-
import { Provider } from '@genui-a3/core';
|
|
2
|
+
import { ResilienceConfig, Provider } from '@genui-a3/core';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Configuration for creating an Anthropic provider.
|
|
@@ -14,6 +14,8 @@ interface AnthropicProviderConfig {
|
|
|
14
14
|
models: string[];
|
|
15
15
|
/** Optional custom base URL for the Anthropic API */
|
|
16
16
|
baseURL?: string;
|
|
17
|
+
/** Resilience configuration (retry, backoff, timeout). Uses industry-standard defaults if omitted. */
|
|
18
|
+
resilience?: ResilienceConfig;
|
|
17
19
|
}
|
|
18
20
|
declare function prepareMessages(messages: ModelMessage[]): ModelMessage[];
|
|
19
21
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ModelMessage } from 'ai';
|
|
2
|
-
import { Provider } from '@genui-a3/core';
|
|
2
|
+
import { ResilienceConfig, Provider } from '@genui-a3/core';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Configuration for creating an Anthropic provider.
|
|
@@ -14,6 +14,8 @@ interface AnthropicProviderConfig {
|
|
|
14
14
|
models: string[];
|
|
15
15
|
/** Optional custom base URL for the Anthropic API */
|
|
16
16
|
baseURL?: string;
|
|
17
|
+
/** Resilience configuration (retry, backoff, timeout). Uses industry-standard defaults if omitted. */
|
|
18
|
+
resilience?: ResilienceConfig;
|
|
17
19
|
}
|
|
18
20
|
declare function prepareMessages(messages: ModelMessage[]): ModelMessage[];
|
|
19
21
|
/**
|
package/dist/anthropic/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createAnthropic } from '@ai-sdk/anthropic';
|
|
2
2
|
import { streamText, Output, generateText } from 'ai';
|
|
3
|
+
import { resolveResilienceConfig, DEFAULT_RESILIENCE_CONFIG, A3ResilienceError, A3TimeoutError } from '@genui-a3/core';
|
|
3
4
|
import { EventType } from '@ag-ui/client';
|
|
4
5
|
|
|
5
6
|
// anthropic/index.ts
|
|
@@ -67,20 +68,119 @@ function extractDelta(partial, prevLength) {
|
|
|
67
68
|
return chatbotMessage.slice(prevLength);
|
|
68
69
|
}
|
|
69
70
|
|
|
71
|
+
// utils/backoff.ts
|
|
72
|
+
function calculateBackoff(attempt, config) {
|
|
73
|
+
let delay;
|
|
74
|
+
switch (config.strategy) {
|
|
75
|
+
case "exponential":
|
|
76
|
+
delay = config.baseDelayMs * Math.pow(2, attempt);
|
|
77
|
+
break;
|
|
78
|
+
case "linear":
|
|
79
|
+
delay = config.baseDelayMs * (attempt + 1);
|
|
80
|
+
break;
|
|
81
|
+
case "fixed":
|
|
82
|
+
delay = config.baseDelayMs;
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
delay = Math.min(delay, config.maxDelayMs);
|
|
86
|
+
if (config.jitter) {
|
|
87
|
+
delay = Math.random() * delay;
|
|
88
|
+
}
|
|
89
|
+
return delay;
|
|
90
|
+
}
|
|
91
|
+
function sleep(ms, signal) {
|
|
92
|
+
return new Promise((resolve, reject) => {
|
|
93
|
+
if (signal?.aborted) {
|
|
94
|
+
reject(signal.reason);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const timer = setTimeout(resolve, ms);
|
|
98
|
+
signal?.addEventListener(
|
|
99
|
+
"abort",
|
|
100
|
+
() => {
|
|
101
|
+
clearTimeout(timer);
|
|
102
|
+
reject(signal.reason);
|
|
103
|
+
},
|
|
104
|
+
{ once: true }
|
|
105
|
+
);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
70
109
|
// utils/executeWithFallback.ts
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
110
|
+
function buildSignal(requestTimeoutMs, totalAbort) {
|
|
111
|
+
const signals = [];
|
|
112
|
+
if (requestTimeoutMs !== void 0) {
|
|
113
|
+
signals.push(AbortSignal.timeout(requestTimeoutMs));
|
|
114
|
+
}
|
|
115
|
+
if (totalAbort) {
|
|
116
|
+
signals.push(totalAbort);
|
|
117
|
+
}
|
|
118
|
+
if (signals.length === 0) return void 0;
|
|
119
|
+
if (signals.length === 1) return signals[0];
|
|
120
|
+
return AbortSignal.any(signals);
|
|
121
|
+
}
|
|
122
|
+
async function attemptAction(action, model, signal, attempt, errors) {
|
|
123
|
+
try {
|
|
124
|
+
const value = await action(model, signal);
|
|
125
|
+
return { ok: true, value };
|
|
126
|
+
} catch (error) {
|
|
127
|
+
const errorObj = error;
|
|
128
|
+
errors.push({ model, attempt, error: errorObj });
|
|
129
|
+
return { ok: false, error: errorObj };
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function checkTotalTimeout(totalAbort, timeoutMs, errors) {
|
|
133
|
+
if (totalAbort?.signal.aborted) {
|
|
134
|
+
throw new A3TimeoutError(`Total timeout of ${timeoutMs}ms exceeded`, errors);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
async function handleAttemptError(errorObj, attempt, maxRetries, retryAll, resolved, totalAbort) {
|
|
138
|
+
const isLastAttempt = attempt === 1 + maxRetries;
|
|
139
|
+
const isRetryable = retryAll || resolved.isRetryableError(errorObj);
|
|
140
|
+
if (isRetryable && !isLastAttempt) {
|
|
141
|
+
const delay = calculateBackoff(attempt - 1, resolved.backoff);
|
|
142
|
+
await sleep(delay, totalAbort?.signal).catch(() => {
|
|
143
|
+
});
|
|
144
|
+
return "retry";
|
|
145
|
+
}
|
|
146
|
+
return "next-model";
|
|
147
|
+
}
|
|
148
|
+
async function executeWithFallback(models, action, config) {
|
|
149
|
+
const resolved = config ?? DEFAULT_RESILIENCE_CONFIG;
|
|
150
|
+
const errors = [];
|
|
151
|
+
const maxRetries = resolved.retry === false ? 0 : resolved.retry.maxAttempts;
|
|
152
|
+
const retryAll = resolved.retry !== false && resolved.retry.retryOn === "all";
|
|
153
|
+
let totalAbort;
|
|
154
|
+
let totalTimer;
|
|
155
|
+
if (resolved.timeout.totalTimeoutMs !== void 0) {
|
|
156
|
+
totalAbort = new AbortController();
|
|
157
|
+
totalTimer = setTimeout(
|
|
158
|
+
() => totalAbort.abort(new Error("Total timeout exceeded")),
|
|
159
|
+
resolved.timeout.totalTimeoutMs
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
try {
|
|
163
|
+
for (let modelIndex = 0; modelIndex < models.length; modelIndex++) {
|
|
164
|
+
const model = models[modelIndex];
|
|
165
|
+
for (let attempt = 1; attempt <= 1 + maxRetries; attempt++) {
|
|
166
|
+
checkTotalTimeout(totalAbort, resolved.timeout.totalTimeoutMs, errors);
|
|
167
|
+
const signal = buildSignal(resolved.timeout.requestTimeoutMs, totalAbort?.signal);
|
|
168
|
+
const result = await attemptAction(action, model, signal, attempt, errors);
|
|
169
|
+
if (result.ok) return result.value;
|
|
170
|
+
checkTotalTimeout(totalAbort, resolved.timeout.totalTimeoutMs, errors);
|
|
171
|
+
const decision = await handleAttemptError(result.error, attempt, maxRetries, retryAll, resolved, totalAbort);
|
|
172
|
+
if (decision === "next-model") break;
|
|
80
173
|
}
|
|
81
174
|
}
|
|
175
|
+
throw new A3ResilienceError(
|
|
176
|
+
`All models failed after ${errors.length} total attempt(s): ${models.join(", ")}`,
|
|
177
|
+
errors
|
|
178
|
+
);
|
|
179
|
+
} finally {
|
|
180
|
+
if (totalTimer !== void 0) {
|
|
181
|
+
clearTimeout(totalTimer);
|
|
182
|
+
}
|
|
82
183
|
}
|
|
83
|
-
throw new Error("All models failed");
|
|
84
184
|
}
|
|
85
185
|
|
|
86
186
|
// anthropic/index.ts
|
|
@@ -134,20 +234,23 @@ function createAnthropicProvider(config) {
|
|
|
134
234
|
baseURL: config.baseURL
|
|
135
235
|
});
|
|
136
236
|
const models = config.models;
|
|
237
|
+
const resilience = resolveResilienceConfig(config.resilience);
|
|
137
238
|
return {
|
|
138
239
|
name: "anthropic",
|
|
139
240
|
async sendRequest(request) {
|
|
140
241
|
const messages = toAIMessages(request.messages);
|
|
141
242
|
return executeWithFallback(
|
|
142
243
|
models,
|
|
143
|
-
(model) => sendWithModel(anthropicProvider, model, request.systemPrompt, messages, request.responseSchema)
|
|
244
|
+
(model) => sendWithModel(anthropicProvider, model, request.systemPrompt, messages, request.responseSchema),
|
|
245
|
+
resilience
|
|
144
246
|
);
|
|
145
247
|
},
|
|
146
248
|
async *sendRequestStream(request) {
|
|
147
249
|
const messages = toAIMessages(request.messages);
|
|
148
250
|
const { result, reader, first } = await executeWithFallback(
|
|
149
251
|
models,
|
|
150
|
-
(model) => sendStreamWithModel(anthropicProvider, model, request.systemPrompt, messages, request.responseSchema)
|
|
252
|
+
(model) => sendStreamWithModel(anthropicProvider, model, request.systemPrompt, messages, request.responseSchema),
|
|
253
|
+
resilience
|
|
151
254
|
);
|
|
152
255
|
yield* processAnthropicStream(result, reader, first, "anthropic", request.responseSchema);
|
|
153
256
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../anthropic/streamProcessor.ts","../../utils/executeWithFallback.ts","../../anthropic/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import { EventType } from '@ag-ui/client'\nimport { ZodType } from 'zod'\nimport type { AgentId, StreamEvent, BaseState } from '@genui-a3/core'\nimport type { StreamTextResult, ToolSet } from 'ai'\n\n/**\n * Processes an Anthropic streaming response (via Vercel AI SDK) into AG-UI events.\n *\n * Uses `partialOutputStream` from `streamText` + `Output.object()` to receive\n * progressively-built partial objects. Tracks `chatbotMessage` growth to yield\n * TEXT_MESSAGE_CONTENT deltas. After the stream completes, validates the final\n * object and yields TOOL_CALL_RESULT.\n *\n * @param streamResult - The streamText result containing partialOutputStream and output promise\n * @param reader - Pre-started async iterator for the partial object stream\n * @param first - The first iteration result (already consumed to trigger the API call)\n * @param agentId - Agent identifier for event tagging\n * @param schema - Zod schema for final response validation\n * @returns Async generator of AG-UI stream events\n */\nexport async function* processAnthropicStream<TState extends BaseState = BaseState>(\n streamResult: StreamTextResult<ToolSet, never>,\n reader: AsyncIterator<unknown>,\n first: IteratorResult<unknown>,\n agentId: AgentId,\n schema: ZodType,\n): AsyncGenerator<StreamEvent<TState>> {\n let prevMessageLength = 0\n\n try {\n // Process the first partial (already consumed to trigger the API call)\n if (!first.done) {\n const partial = first.value as Record<string, unknown>\n const delta = extractDelta(partial, prevMessageLength)\n if (delta) {\n prevMessageLength += delta.length\n yield {\n type: EventType.TEXT_MESSAGE_CONTENT,\n messageId: '',\n delta,\n agentId,\n } as StreamEvent<TState>\n }\n }\n\n // Process remaining partials\n let next = await reader.next()\n while (!next.done) {\n const partial = next.value as Record<string, unknown>\n const delta = extractDelta(partial, prevMessageLength)\n if (delta) {\n prevMessageLength += delta.length\n yield {\n type: EventType.TEXT_MESSAGE_CONTENT,\n messageId: '',\n delta,\n agentId,\n } as StreamEvent<TState>\n }\n // eslint-disable-next-line no-await-in-loop\n next = await reader.next()\n }\n\n // Stream complete — await and validate the final object\n const finalObject = await streamResult.output\n\n if (finalObject === null) {\n yield {\n type: EventType.RUN_ERROR,\n message: 'Anthropic stream completed with null output',\n agentId,\n } as StreamEvent<TState>\n return\n }\n\n const validated = schema.parse(finalObject)\n yield {\n type: EventType.TOOL_CALL_RESULT,\n toolCallId: '',\n messageId: '',\n content: JSON.stringify(validated),\n agentId,\n } as StreamEvent<TState>\n } catch (err) {\n yield {\n type: EventType.RUN_ERROR,\n message: `Anthropic stream error: ${(err as Error).message}`,\n agentId,\n } as StreamEvent<TState>\n }\n}\n\n/**\n * Extracts the new portion of chatbotMessage from a partial object.\n */\nfunction extractDelta(partial: Record<string, unknown>, prevLength: number): string | null {\n const chatbotMessage = partial.chatbotMessage\n if (typeof chatbotMessage !== 'string' || chatbotMessage.length <= prevLength) {\n return null\n }\n return chatbotMessage.slice(prevLength)\n}\n","/**\n * Executes an action with model fallback support.\n * Tries each model in order; if one fails, falls back to the next.\n * Throws the last error if all models fail.\n *\n * @param models - Model identifiers in priority order\n * @param action - Async action to attempt with each model\n * @returns The result from the first successful model\n * @throws The error from the last model if all fail\n *\n * @example\n * ```typescript\n * const result = await executeWithFallback(\n * ['model-primary', 'model-fallback'],\n * (model) => provider.call(model, params),\n * )\n * ```\n */\nexport async function executeWithFallback<T>(models: string[], action: (model: string) => Promise<T>): Promise<T> {\n const errors: Array<{ model: string; error: Error }> = []\n\n for (let i = 0; i < models.length; i++) {\n const model = models[i]\n\n try {\n // eslint-disable-next-line no-await-in-loop\n return await action(model)\n } catch (error) {\n const errorObj = error as Error\n errors.push({ model, error: errorObj })\n\n if (i === models.length - 1) {\n throw errorObj\n }\n }\n }\n\n throw new Error('All models failed')\n}\n","import { createAnthropic } from '@ai-sdk/anthropic'\nimport { generateText, streamText, Output, ModelMessage } from 'ai'\nimport type {\n Provider,\n ProviderRequest,\n ProviderResponse,\n ProviderMessage,\n BaseState,\n StreamEvent,\n} from '@genui-a3/core'\nimport { processAnthropicStream } from './streamProcessor'\nimport { executeWithFallback } from '../utils/executeWithFallback'\n\n/**\n * Configuration for creating an Anthropic provider.\n */\nexport interface AnthropicProviderConfig {\n /** Anthropic API key. Defaults to ANTHROPIC_API_KEY env var. */\n apiKey?: string\n /**\n * Model identifiers in order of preference (first = primary, rest = fallbacks).\n * e.g. ['claude-sonnet-4-5-20250929', 'claude-haiku-4-5-20251001']\n */\n models: string[]\n /** Optional custom base URL for the Anthropic API */\n baseURL?: string\n}\n\nfunction toAIMessages(messages: ProviderMessage[]): ModelMessage[] {\n return messages.map((msg) => ({\n role: msg.role,\n content: msg.content,\n }))\n}\n\nexport function prepareMessages(messages: ModelMessage[]): ModelMessage[] {\n if (messages.length === 0) return messages\n const lastMessage = messages[messages.length - 1]\n if (lastMessage.role === 'assistant') {\n return [...messages, { role: 'user', content: 'Continue' }]\n }\n return messages\n}\n\nasync function sendWithModel(\n anthropicProvider: ReturnType<typeof createAnthropic>,\n model: string,\n system: string,\n messages: ModelMessage[],\n schema: ProviderRequest['responseSchema'],\n): Promise<ProviderResponse> {\n const preparedMessages = prepareMessages(messages)\n const result = await generateText({\n model: anthropicProvider(model),\n system,\n messages: preparedMessages,\n output: Output.object({ schema }),\n })\n\n return {\n content: JSON.stringify(result.output),\n usage: {\n inputTokens: result.usage.inputTokens ?? 0,\n outputTokens: result.usage.outputTokens ?? 0,\n totalTokens: (result.usage.inputTokens ?? 0) + (result.usage.outputTokens ?? 0),\n },\n }\n}\n\nasync function sendStreamWithModel(\n anthropicProvider: ReturnType<typeof createAnthropic>,\n model: string,\n system: string,\n messages: ModelMessage[],\n schema: ProviderRequest['responseSchema'],\n) {\n const preparedMessages = prepareMessages(messages)\n const result = streamText({\n model: anthropicProvider(model),\n system,\n messages: preparedMessages,\n output: Output.object({ schema }),\n })\n\n // Force the API call to start so executeWithFallback can catch connection errors\n const partialStream = result.partialOutputStream\n const reader = partialStream[Symbol.asyncIterator]()\n const first = await reader.next()\n\n return { result, reader, first }\n}\n\n/**\n * Creates an Anthropic provider instance.\n *\n * Uses the Vercel AI SDK (`ai` + `@ai-sdk/anthropic`) for structured output via\n * `generateText` + `Output.object()` (blocking) and `streamText` + `Output.object()`\n * (streaming). The AI SDK handles Zod-to-JSON-schema conversion, partial JSON\n * parsing, and validation internally.\n *\n * @param config - Anthropic provider configuration\n * @returns A Provider implementation using Anthropic\n *\n * @example\n * ```typescript\n * const provider = createAnthropicProvider({\n * models: ['claude-sonnet-4-5-20250929', 'claude-haiku-4-5-20251001'],\n * })\n * ```\n */\nexport function createAnthropicProvider(config: AnthropicProviderConfig): Provider {\n const anthropicProvider = createAnthropic({\n apiKey: config.apiKey,\n baseURL: config.baseURL,\n })\n const models = config.models\n\n return {\n name: 'anthropic',\n\n async sendRequest(request: ProviderRequest): Promise<ProviderResponse> {\n const messages = toAIMessages(request.messages)\n\n return executeWithFallback(models, (model) =>\n sendWithModel(anthropicProvider, model, request.systemPrompt, messages, request.responseSchema),\n )\n },\n\n async *sendRequestStream<TState extends BaseState = BaseState>(\n request: ProviderRequest,\n ): AsyncGenerator<StreamEvent<TState>> {\n const messages = toAIMessages(request.messages)\n\n const { result, reader, first } = await executeWithFallback(models, (model) =>\n sendStreamWithModel(anthropicProvider, model, request.systemPrompt, messages, request.responseSchema),\n )\n\n yield* processAnthropicStream<TState>(result, reader, first, 'anthropic', request.responseSchema)\n },\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../anthropic/streamProcessor.ts","../../utils/backoff.ts","../../utils/executeWithFallback.ts","../../anthropic/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import { EventType } from '@ag-ui/client'\nimport { ZodType } from 'zod'\nimport type { AgentId, StreamEvent, BaseState } from '@genui-a3/core'\nimport type { StreamTextResult, ToolSet } from 'ai'\n\n/**\n * Processes an Anthropic streaming response (via Vercel AI SDK) into AG-UI events.\n *\n * Uses `partialOutputStream` from `streamText` + `Output.object()` to receive\n * progressively-built partial objects. Tracks `chatbotMessage` growth to yield\n * TEXT_MESSAGE_CONTENT deltas. After the stream completes, validates the final\n * object and yields TOOL_CALL_RESULT.\n *\n * @param streamResult - The streamText result containing partialOutputStream and output promise\n * @param reader - Pre-started async iterator for the partial object stream\n * @param first - The first iteration result (already consumed to trigger the API call)\n * @param agentId - Agent identifier for event tagging\n * @param schema - Zod schema for final response validation\n * @returns Async generator of AG-UI stream events\n */\nexport async function* processAnthropicStream<TState extends BaseState = BaseState>(\n streamResult: StreamTextResult<ToolSet, never>,\n reader: AsyncIterator<unknown>,\n first: IteratorResult<unknown>,\n agentId: AgentId,\n schema: ZodType,\n): AsyncGenerator<StreamEvent<TState>> {\n let prevMessageLength = 0\n\n try {\n // Process the first partial (already consumed to trigger the API call)\n if (!first.done) {\n const partial = first.value as Record<string, unknown>\n const delta = extractDelta(partial, prevMessageLength)\n if (delta) {\n prevMessageLength += delta.length\n yield {\n type: EventType.TEXT_MESSAGE_CONTENT,\n messageId: '',\n delta,\n agentId,\n } as StreamEvent<TState>\n }\n }\n\n // Process remaining partials\n let next = await reader.next()\n while (!next.done) {\n const partial = next.value as Record<string, unknown>\n const delta = extractDelta(partial, prevMessageLength)\n if (delta) {\n prevMessageLength += delta.length\n yield {\n type: EventType.TEXT_MESSAGE_CONTENT,\n messageId: '',\n delta,\n agentId,\n } as StreamEvent<TState>\n }\n // eslint-disable-next-line no-await-in-loop\n next = await reader.next()\n }\n\n // Stream complete — await and validate the final object\n const finalObject = await streamResult.output\n\n if (finalObject === null) {\n yield {\n type: EventType.RUN_ERROR,\n message: 'Anthropic stream completed with null output',\n agentId,\n } as StreamEvent<TState>\n return\n }\n\n const validated = schema.parse(finalObject)\n yield {\n type: EventType.TOOL_CALL_RESULT,\n toolCallId: '',\n messageId: '',\n content: JSON.stringify(validated),\n agentId,\n } as StreamEvent<TState>\n } catch (err) {\n yield {\n type: EventType.RUN_ERROR,\n message: `Anthropic stream error: ${(err as Error).message}`,\n agentId,\n } as StreamEvent<TState>\n }\n}\n\n/**\n * Extracts the new portion of chatbotMessage from a partial object.\n */\nfunction extractDelta(partial: Record<string, unknown>, prevLength: number): string | null {\n const chatbotMessage = partial.chatbotMessage\n if (typeof chatbotMessage !== 'string' || chatbotMessage.length <= prevLength) {\n return null\n }\n return chatbotMessage.slice(prevLength)\n}\n","import type { BackoffConfig } from '@genui-a3/core'\n\n/**\n * Calculates the backoff delay for a given retry attempt.\n *\n * @param attempt - Zero-based attempt index (0 = first retry)\n * @param config - Backoff configuration with all fields required\n * @returns Delay in milliseconds\n */\nexport function calculateBackoff(attempt: number, config: Required<BackoffConfig>): number {\n let delay: number\n\n switch (config.strategy) {\n case 'exponential':\n delay = config.baseDelayMs * Math.pow(2, attempt)\n break\n case 'linear':\n delay = config.baseDelayMs * (attempt + 1)\n break\n case 'fixed':\n delay = config.baseDelayMs\n break\n }\n\n delay = Math.min(delay, config.maxDelayMs)\n\n if (config.jitter) {\n delay = Math.random() * delay\n }\n\n return delay\n}\n\n/**\n * Sleeps for the specified duration. Can be aborted via an AbortSignal.\n *\n * @param ms - Duration in milliseconds\n * @param signal - Optional AbortSignal to cancel the sleep early\n */\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n if (signal?.aborted) {\n reject(signal.reason as Error)\n return\n }\n\n const timer = setTimeout(resolve, ms)\n\n signal?.addEventListener(\n 'abort',\n () => {\n clearTimeout(timer)\n reject(signal.reason as Error)\n },\n { once: true },\n )\n })\n}\n","import {\n A3ResilienceError,\n A3TimeoutError,\n DEFAULT_RESILIENCE_CONFIG,\n type ResilienceErrorEntry,\n type ResolvedResilienceConfig,\n} from '@genui-a3/core'\nimport { calculateBackoff, sleep } from './backoff'\n\n/**\n * Builds an AbortSignal that fires when either the per-request timeout or total timeout expires.\n */\nfunction buildSignal(requestTimeoutMs?: number, totalAbort?: AbortSignal): AbortSignal | undefined {\n const signals: AbortSignal[] = []\n\n if (requestTimeoutMs !== undefined) {\n signals.push(AbortSignal.timeout(requestTimeoutMs))\n }\n\n if (totalAbort) {\n signals.push(totalAbort)\n }\n\n if (signals.length === 0) return undefined\n if (signals.length === 1) return signals[0]\n return AbortSignal.any(signals)\n}\n\ntype AttemptResult<T> = { ok: true; value: T } | { ok: false; error: Error }\n\nasync function attemptAction<T>(\n action: (model: string, signal?: AbortSignal) => Promise<T>,\n model: string,\n signal: AbortSignal | undefined,\n attempt: number,\n errors: ResilienceErrorEntry[],\n): Promise<AttemptResult<T>> {\n try {\n const value = await action(model, signal)\n return { ok: true, value }\n } catch (error) {\n const errorObj = error as Error\n errors.push({ model, attempt, error: errorObj })\n return { ok: false, error: errorObj }\n }\n}\n\nfunction checkTotalTimeout(totalAbort: AbortController | undefined, timeoutMs: number | undefined, errors: ResilienceErrorEntry[]): void {\n if (totalAbort?.signal.aborted) {\n throw new A3TimeoutError(`Total timeout of ${timeoutMs}ms exceeded`, errors)\n }\n}\n\nasync function handleAttemptError(\n errorObj: Error,\n attempt: number,\n maxRetries: number,\n retryAll: boolean,\n resolved: ResolvedResilienceConfig,\n totalAbort: AbortController | undefined,\n): Promise<'retry' | 'next-model'> {\n const isLastAttempt = attempt === 1 + maxRetries\n const isRetryable = retryAll || resolved.isRetryableError(errorObj)\n\n if (isRetryable && !isLastAttempt) {\n const delay = calculateBackoff(attempt - 1, resolved.backoff)\n await sleep(delay, totalAbort?.signal).catch(() => {\n // Sleep was aborted by total timeout — will be caught at top of loop\n })\n return 'retry'\n }\n\n return 'next-model'\n}\n\n/**\n * Executes an action with model fallback, retry, backoff, and timeout support.\n *\n * For each model (in priority order):\n * 1. Attempts the action up to `1 + maxAttempts` times\n * 2. On transient errors, waits with backoff before retrying\n * 3. On non-retryable errors (or after exhausting retries), falls back to the next model\n *\n * Throws `A3ResilienceError` with full error history when all models are exhausted.\n * Throws `A3TimeoutError` when the total timeout is exceeded.\n *\n * @param models - Model identifiers in priority order\n * @param action - Async action to attempt with each model. Receives an optional AbortSignal.\n * @param config - Resolved resilience configuration (defaults applied if omitted)\n * @returns The result from the first successful attempt\n * @throws {A3ResilienceError} When all models and retries are exhausted\n * @throws {A3TimeoutError} When the total timeout is exceeded\n *\n * @example\n * ```typescript\n * const result = await executeWithFallback(\n * ['model-primary', 'model-fallback'],\n * (model, signal) => provider.call(model, params, { abortSignal: signal }),\n * resolvedConfig,\n * )\n * ```\n */\nexport async function executeWithFallback<T>(\n models: string[],\n action: (model: string, signal?: AbortSignal) => Promise<T>,\n config?: ResolvedResilienceConfig,\n): Promise<T> {\n const resolved = config ?? DEFAULT_RESILIENCE_CONFIG\n const errors: ResilienceErrorEntry[] = []\n const maxRetries = resolved.retry === false ? 0 : resolved.retry.maxAttempts\n const retryAll = resolved.retry !== false && resolved.retry.retryOn === 'all'\n\n // Total timeout controller\n let totalAbort: AbortController | undefined\n let totalTimer: ReturnType<typeof setTimeout> | undefined\n\n if (resolved.timeout.totalTimeoutMs !== undefined) {\n totalAbort = new AbortController()\n totalTimer = setTimeout(\n () => totalAbort!.abort(new Error('Total timeout exceeded')),\n resolved.timeout.totalTimeoutMs,\n )\n }\n\n try {\n for (let modelIndex = 0; modelIndex < models.length; modelIndex++) {\n const model = models[modelIndex]\n\n for (let attempt = 1; attempt <= 1 + maxRetries; attempt++) {\n checkTotalTimeout(totalAbort, resolved.timeout.totalTimeoutMs, errors)\n\n const signal = buildSignal(resolved.timeout.requestTimeoutMs, totalAbort?.signal)\n // eslint-disable-next-line no-await-in-loop\n const result = await attemptAction(action, model, signal, attempt, errors)\n if (result.ok) return result.value\n\n checkTotalTimeout(totalAbort, resolved.timeout.totalTimeoutMs, errors)\n\n // eslint-disable-next-line no-await-in-loop\n const decision = await handleAttemptError(result.error, attempt, maxRetries, retryAll, resolved, totalAbort)\n if (decision === 'next-model') break\n }\n }\n\n // All models exhausted\n throw new A3ResilienceError(\n `All models failed after ${errors.length} total attempt(s): ${models.join(', ')}`,\n errors,\n )\n } finally {\n if (totalTimer !== undefined) {\n clearTimeout(totalTimer)\n }\n }\n}\n","import { createAnthropic } from '@ai-sdk/anthropic'\nimport { generateText, streamText, Output, ModelMessage } from 'ai'\nimport {\n resolveResilienceConfig,\n type Provider,\n type ProviderRequest,\n type ProviderResponse,\n type ProviderMessage,\n type BaseState,\n type StreamEvent,\n type ResilienceConfig,\n type ResolvedResilienceConfig,\n} from '@genui-a3/core'\nimport { processAnthropicStream } from './streamProcessor'\nimport { executeWithFallback } from '../utils/executeWithFallback'\n\n/**\n * Configuration for creating an Anthropic provider.\n */\nexport interface AnthropicProviderConfig {\n /** Anthropic API key. Defaults to ANTHROPIC_API_KEY env var. */\n apiKey?: string\n /**\n * Model identifiers in order of preference (first = primary, rest = fallbacks).\n * e.g. ['claude-sonnet-4-5-20250929', 'claude-haiku-4-5-20251001']\n */\n models: string[]\n /** Optional custom base URL for the Anthropic API */\n baseURL?: string\n /** Resilience configuration (retry, backoff, timeout). Uses industry-standard defaults if omitted. */\n resilience?: ResilienceConfig\n}\n\nfunction toAIMessages(messages: ProviderMessage[]): ModelMessage[] {\n return messages.map((msg) => ({\n role: msg.role,\n content: msg.content,\n }))\n}\n\nexport function prepareMessages(messages: ModelMessage[]): ModelMessage[] {\n if (messages.length === 0) return messages\n const lastMessage = messages[messages.length - 1]\n if (lastMessage.role === 'assistant') {\n return [...messages, { role: 'user', content: 'Continue' }]\n }\n return messages\n}\n\nasync function sendWithModel(\n anthropicProvider: ReturnType<typeof createAnthropic>,\n model: string,\n system: string,\n messages: ModelMessage[],\n schema: ProviderRequest['responseSchema'],\n): Promise<ProviderResponse> {\n const preparedMessages = prepareMessages(messages)\n const result = await generateText({\n model: anthropicProvider(model),\n system,\n messages: preparedMessages,\n output: Output.object({ schema }),\n })\n\n return {\n content: JSON.stringify(result.output),\n usage: {\n inputTokens: result.usage.inputTokens ?? 0,\n outputTokens: result.usage.outputTokens ?? 0,\n totalTokens: (result.usage.inputTokens ?? 0) + (result.usage.outputTokens ?? 0),\n },\n }\n}\n\nasync function sendStreamWithModel(\n anthropicProvider: ReturnType<typeof createAnthropic>,\n model: string,\n system: string,\n messages: ModelMessage[],\n schema: ProviderRequest['responseSchema'],\n) {\n const preparedMessages = prepareMessages(messages)\n const result = streamText({\n model: anthropicProvider(model),\n system,\n messages: preparedMessages,\n output: Output.object({ schema }),\n })\n\n // Force the API call to start so executeWithFallback can catch connection errors\n const partialStream = result.partialOutputStream\n const reader = partialStream[Symbol.asyncIterator]()\n const first = await reader.next()\n\n return { result, reader, first }\n}\n\n/**\n * Creates an Anthropic provider instance.\n *\n * Uses the Vercel AI SDK (`ai` + `@ai-sdk/anthropic`) for structured output via\n * `generateText` + `Output.object()` (blocking) and `streamText` + `Output.object()`\n * (streaming). The AI SDK handles Zod-to-JSON-schema conversion, partial JSON\n * parsing, and validation internally.\n *\n * @param config - Anthropic provider configuration\n * @returns A Provider implementation using Anthropic\n *\n * @example\n * ```typescript\n * const provider = createAnthropicProvider({\n * models: ['claude-sonnet-4-5-20250929', 'claude-haiku-4-5-20251001'],\n * })\n * ```\n */\nexport function createAnthropicProvider(config: AnthropicProviderConfig): Provider {\n const anthropicProvider = createAnthropic({\n apiKey: config.apiKey,\n baseURL: config.baseURL,\n })\n const models = config.models\n const resilience: ResolvedResilienceConfig = resolveResilienceConfig(config.resilience)\n\n return {\n name: 'anthropic',\n\n async sendRequest(request: ProviderRequest): Promise<ProviderResponse> {\n const messages = toAIMessages(request.messages)\n\n return executeWithFallback(\n models,\n (model) => sendWithModel(anthropicProvider, model, request.systemPrompt, messages, request.responseSchema),\n resilience,\n )\n },\n\n async *sendRequestStream<TState extends BaseState = BaseState>(\n request: ProviderRequest,\n ): AsyncGenerator<StreamEvent<TState>> {\n const messages = toAIMessages(request.messages)\n\n const { result, reader, first } = await executeWithFallback(\n models,\n (model) => sendStreamWithModel(anthropicProvider, model, request.systemPrompt, messages, request.responseSchema),\n resilience,\n )\n\n yield* processAnthropicStream<TState>(result, reader, first, 'anthropic', request.responseSchema)\n },\n }\n}\n"]}
|