@juspay/neurolink 9.69.1 → 9.69.2
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/CHANGELOG.md +2 -0
- package/dist/browser/neurolink.min.js +353 -338
- package/dist/lib/providers/anthropic.d.ts +5 -3
- package/dist/lib/providers/anthropic.js +842 -162
- package/dist/lib/utils/noOutputSentinel.d.ts +1 -1
- package/dist/lib/utils/noOutputSentinel.js +1 -1
- package/dist/providers/anthropic.d.ts +5 -3
- package/dist/providers/anthropic.js +842 -162
- package/dist/utils/noOutputSentinel.d.ts +1 -1
- package/dist/utils/noOutputSentinel.js +1 -1
- package/package.json +2 -1
- package/dist/lib/providers/anthropicBaseProvider.d.ts +0 -23
- package/dist/lib/providers/anthropicBaseProvider.js +0 -213
- package/dist/providers/anthropicBaseProvider.d.ts +0 -23
- package/dist/providers/anthropicBaseProvider.js +0 -212
|
@@ -44,7 +44,7 @@ underlyingError?: unknown): Promise<{
|
|
|
44
44
|
* observation with the enriched status message. Without this, only
|
|
45
45
|
* `StreamHandler`-based providers produced the rich telemetry; the
|
|
46
46
|
* provider-specific paths (openAI, openaiCompatible, litellm,
|
|
47
|
-
* huggingFace, openRouter,
|
|
47
|
+
* huggingFace, openRouter, anthropic) yielded the sentinel
|
|
48
48
|
* to direct stream consumers but Pipeline B saw nothing.
|
|
49
49
|
*
|
|
50
50
|
* Stamps three attributes:
|
|
@@ -135,7 +135,7 @@ underlyingError) {
|
|
|
135
135
|
* observation with the enriched status message. Without this, only
|
|
136
136
|
* `StreamHandler`-based providers produced the rich telemetry; the
|
|
137
137
|
* provider-specific paths (openAI, openaiCompatible, litellm,
|
|
138
|
-
* huggingFace, openRouter,
|
|
138
|
+
* huggingFace, openRouter, anthropic) yielded the sentinel
|
|
139
139
|
* to direct stream consumers but Pipeline B saw nothing.
|
|
140
140
|
*
|
|
141
141
|
* Stamps three attributes:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juspay/neurolink",
|
|
3
|
-
"version": "9.69.
|
|
3
|
+
"version": "9.69.2",
|
|
4
4
|
"packageManager": "pnpm@10.15.1",
|
|
5
5
|
"description": "Universal AI Development Platform with working MCP integration, multi-provider support, voice (TTS/STT/realtime), and professional CLI. 58+ external MCP servers discoverable, multimodal file processing, RAG pipelines. Build, test, and deploy AI applications with 21+ providers: OpenAI, Anthropic, Google AI Studio, Google Vertex, AWS Bedrock, Azure OpenAI, Mistral, LiteLLM, SageMaker, Hugging Face, Ollama, OpenAI-compatible, OpenRouter, DeepSeek, NVIDIA NIM, LM Studio, llama.cpp, plus voice (OpenAI TTS, ElevenLabs, Deepgram, Azure Speech).",
|
|
6
6
|
"author": {
|
|
@@ -301,6 +301,7 @@
|
|
|
301
301
|
"@ai-sdk/mistral": "^3.0.21",
|
|
302
302
|
"@ai-sdk/openai": "^3.0.37",
|
|
303
303
|
"@ai-sdk/provider": "^3.0.8",
|
|
304
|
+
"@anthropic-ai/sdk": "^0.102.0",
|
|
304
305
|
"@anthropic-ai/vertex-sdk": "^0.16.0",
|
|
305
306
|
"@aws-sdk/client-bedrock": "^3.1000.0",
|
|
306
307
|
"@aws-sdk/client-bedrock-runtime": "^3.1000.0",
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import type { ZodType } from "zod";
|
|
2
|
-
import { type AIProviderName } from "../constants/enums.js";
|
|
3
|
-
import { BaseProvider } from "../core/baseProvider.js";
|
|
4
|
-
import type { StreamOptions, StreamResult } from "../types/index.js";
|
|
5
|
-
import type { LanguageModel, Schema } from "../types/index.js";
|
|
6
|
-
/**
|
|
7
|
-
* Anthropic provider implementation using BaseProvider pattern
|
|
8
|
-
* Migrated from direct API calls to Vercel AI SDK (@ai-sdk/anthropic)
|
|
9
|
-
* Follows exact Google AI interface patterns for compatibility
|
|
10
|
-
*/
|
|
11
|
-
export declare class AnthropicProviderV2 extends BaseProvider {
|
|
12
|
-
constructor(modelName?: string);
|
|
13
|
-
protected getProviderName(): AIProviderName;
|
|
14
|
-
protected getDefaultModel(): string;
|
|
15
|
-
/**
|
|
16
|
-
* Returns the Vercel AI SDK model instance for Anthropic
|
|
17
|
-
*/
|
|
18
|
-
protected getAISDKModel(): LanguageModel;
|
|
19
|
-
protected formatProviderError(error: unknown): Error;
|
|
20
|
-
private getApiKey;
|
|
21
|
-
protected executeStream(options: StreamOptions, _analysisSchema?: ZodType | Schema<unknown>): Promise<StreamResult>;
|
|
22
|
-
}
|
|
23
|
-
export default AnthropicProviderV2;
|
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
2
|
-
import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
|
|
3
|
-
import { AnthropicModels } from "../constants/enums.js";
|
|
4
|
-
import { BaseProvider } from "../core/baseProvider.js";
|
|
5
|
-
import { AuthenticationError, NetworkError, ProviderError, RateLimitError, } from "../types/index.js";
|
|
6
|
-
import { logger } from "../utils/logger.js";
|
|
7
|
-
import { buildNoOutputSentinel, detectPostStreamNoOutput, stampNoOutputSpan, } from "../utils/noOutputSentinel.js";
|
|
8
|
-
import { calculateCost } from "../utils/pricing.js";
|
|
9
|
-
import { createAnthropicBaseConfig, validateApiKey, } from "../utils/providerConfig.js";
|
|
10
|
-
import { composeAbortSignals, createTimeoutController, TimeoutError, } from "../utils/timeout.js";
|
|
11
|
-
import { resolveToolChoice } from "../utils/toolChoice.js";
|
|
12
|
-
import { getModelId } from "./providerTypeUtils.js";
|
|
13
|
-
import { NoOutputGeneratedError } from "../utils/generationErrors.js";
|
|
14
|
-
import { streamText } from "../utils/generation.js";
|
|
15
|
-
const streamTracer = trace.getTracer("neurolink.provider.anthropic");
|
|
16
|
-
/**
|
|
17
|
-
* Anthropic provider implementation using BaseProvider pattern
|
|
18
|
-
* Migrated from direct API calls to Vercel AI SDK (@ai-sdk/anthropic)
|
|
19
|
-
* Follows exact Google AI interface patterns for compatibility
|
|
20
|
-
*/
|
|
21
|
-
export class AnthropicProviderV2 extends BaseProvider {
|
|
22
|
-
constructor(modelName) {
|
|
23
|
-
super(modelName, "anthropic");
|
|
24
|
-
logger.debug("AnthropicProviderV2 initialized", {
|
|
25
|
-
model: this.modelName,
|
|
26
|
-
provider: this.providerName,
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
// ===================
|
|
30
|
-
// ABSTRACT METHOD IMPLEMENTATIONS
|
|
31
|
-
// ===================
|
|
32
|
-
getProviderName() {
|
|
33
|
-
return "anthropic";
|
|
34
|
-
}
|
|
35
|
-
getDefaultModel() {
|
|
36
|
-
return process.env.ANTHROPIC_MODEL || AnthropicModels.CLAUDE_3_5_SONNET;
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Returns the Vercel AI SDK model instance for Anthropic
|
|
40
|
-
*/
|
|
41
|
-
getAISDKModel() {
|
|
42
|
-
const apiKey = this.getApiKey();
|
|
43
|
-
const anthropic = createAnthropic({ apiKey });
|
|
44
|
-
return anthropic(this.modelName);
|
|
45
|
-
}
|
|
46
|
-
formatProviderError(error) {
|
|
47
|
-
if (error instanceof TimeoutError) {
|
|
48
|
-
return new NetworkError(`Request timed out: ${error.message}`, this.providerName);
|
|
49
|
-
}
|
|
50
|
-
const errorWithStatus = error;
|
|
51
|
-
if (errorWithStatus?.status === 401) {
|
|
52
|
-
return new AuthenticationError("Invalid Anthropic API key. Please check your ANTHROPIC_API_KEY environment variable.", this.providerName);
|
|
53
|
-
}
|
|
54
|
-
if (errorWithStatus?.status === 429) {
|
|
55
|
-
return new RateLimitError("Anthropic rate limit exceeded. Please try again later.", this.providerName);
|
|
56
|
-
}
|
|
57
|
-
if (errorWithStatus?.status === 400) {
|
|
58
|
-
return new ProviderError(`Bad request: ${errorWithStatus?.message || "Invalid request parameters"}`, this.providerName);
|
|
59
|
-
}
|
|
60
|
-
return new ProviderError(`Anthropic error: ${errorWithStatus?.message || String(error) || "Unknown error"}`, this.providerName);
|
|
61
|
-
}
|
|
62
|
-
// Configuration helper - now using consolidated utility
|
|
63
|
-
getApiKey() {
|
|
64
|
-
return validateApiKey(createAnthropicBaseConfig());
|
|
65
|
-
}
|
|
66
|
-
// executeGenerate removed - BaseProvider handles all generation with tools
|
|
67
|
-
async executeStream(options, _analysisSchema) {
|
|
68
|
-
// Note: StreamOptions validation handled differently than TextGenerationOptions
|
|
69
|
-
const model = await this.getAISDKModelWithMiddleware(options);
|
|
70
|
-
const timeout = this.getTimeout(options);
|
|
71
|
-
const timeoutController = createTimeoutController(timeout, this.providerName, "stream");
|
|
72
|
-
try {
|
|
73
|
-
// Get tools - options.tools is pre-merged by BaseProvider.stream()
|
|
74
|
-
const shouldUseTools = !options.disableTools && this.supportsTools();
|
|
75
|
-
const tools = shouldUseTools
|
|
76
|
-
? options.tools || (await this.getAllTools())
|
|
77
|
-
: {};
|
|
78
|
-
// Wrap streamText in an OTel span to capture provider-level latency and token usage
|
|
79
|
-
const streamSpan = streamTracer.startSpan("neurolink.provider.streamText", {
|
|
80
|
-
kind: SpanKind.CLIENT,
|
|
81
|
-
attributes: {
|
|
82
|
-
"gen_ai.system": "anthropic",
|
|
83
|
-
"gen_ai.request.model": getModelId(model, this.modelName || "unknown"),
|
|
84
|
-
},
|
|
85
|
-
});
|
|
86
|
-
// Reviewer follow-up: capture upstream provider errors via onError
|
|
87
|
-
// so the post-stream NoOutput detect can propagate the real cause
|
|
88
|
-
// into the sentinel's providerError / modelResponseRaw.
|
|
89
|
-
let capturedProviderError;
|
|
90
|
-
let result;
|
|
91
|
-
try {
|
|
92
|
-
result = streamText({
|
|
93
|
-
model,
|
|
94
|
-
prompt: options.input.text ?? "",
|
|
95
|
-
system: options.systemPrompt,
|
|
96
|
-
temperature: options.temperature,
|
|
97
|
-
maxOutputTokens: options.maxTokens, // No default limit - unlimited unless specified
|
|
98
|
-
maxRetries: 0, // NL11: Disable AI SDK's invisible internal retries; we handle retries with OTel instrumentation
|
|
99
|
-
tools,
|
|
100
|
-
toolChoice: resolveToolChoice(options, tools, shouldUseTools),
|
|
101
|
-
abortSignal: composeAbortSignals(options.abortSignal, timeoutController?.controller.signal),
|
|
102
|
-
experimental_telemetry: this.telemetryHandler.getTelemetryConfig(options),
|
|
103
|
-
experimental_repairToolCall: this.getToolCallRepairFn(options),
|
|
104
|
-
onError: (event) => {
|
|
105
|
-
capturedProviderError = event.error;
|
|
106
|
-
logger.error("AnthropicBaseProvider: Stream error", {
|
|
107
|
-
error: event.error instanceof Error
|
|
108
|
-
? event.error.message
|
|
109
|
-
: String(event.error),
|
|
110
|
-
});
|
|
111
|
-
},
|
|
112
|
-
onStepFinish: ({ toolCalls, toolResults }) => {
|
|
113
|
-
this.handleToolExecutionStorage(toolCalls, toolResults, options, new Date()).catch((error) => {
|
|
114
|
-
logger.warn("[AnthropicBaseProvider] Failed to store tool executions", {
|
|
115
|
-
provider: this.providerName,
|
|
116
|
-
error: error instanceof Error ? error.message : String(error),
|
|
117
|
-
});
|
|
118
|
-
});
|
|
119
|
-
},
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
catch (err) {
|
|
123
|
-
streamSpan.recordException(err instanceof Error ? err : new Error(String(err)));
|
|
124
|
-
streamSpan.setStatus({
|
|
125
|
-
code: SpanStatusCode.ERROR,
|
|
126
|
-
message: err instanceof Error ? err.message : String(err),
|
|
127
|
-
});
|
|
128
|
-
streamSpan.end();
|
|
129
|
-
throw err;
|
|
130
|
-
}
|
|
131
|
-
// Collect token usage and finish reason asynchronously when the stream completes,
|
|
132
|
-
// then end the span. This avoids blocking the stream consumer.
|
|
133
|
-
Promise.resolve(result.usage)
|
|
134
|
-
.then((usage) => {
|
|
135
|
-
streamSpan.setAttribute("gen_ai.usage.input_tokens", usage.inputTokens || 0);
|
|
136
|
-
streamSpan.setAttribute("gen_ai.usage.output_tokens", usage.outputTokens || 0);
|
|
137
|
-
const cost = calculateCost(this.providerName, this.modelName, {
|
|
138
|
-
input: usage.inputTokens || 0,
|
|
139
|
-
output: usage.outputTokens || 0,
|
|
140
|
-
total: (usage.inputTokens || 0) + (usage.outputTokens || 0),
|
|
141
|
-
});
|
|
142
|
-
if (cost && cost > 0) {
|
|
143
|
-
streamSpan.setAttribute("neurolink.cost", cost);
|
|
144
|
-
}
|
|
145
|
-
})
|
|
146
|
-
.catch(() => {
|
|
147
|
-
// Usage may not be available if the stream is aborted
|
|
148
|
-
});
|
|
149
|
-
Promise.resolve(result.finishReason)
|
|
150
|
-
.then((reason) => {
|
|
151
|
-
streamSpan.setAttribute("gen_ai.response.finish_reason", reason || "unknown");
|
|
152
|
-
})
|
|
153
|
-
.catch(() => {
|
|
154
|
-
// Finish reason may not be available if the stream is aborted
|
|
155
|
-
});
|
|
156
|
-
Promise.resolve(result.text)
|
|
157
|
-
.then(() => {
|
|
158
|
-
streamSpan.end();
|
|
159
|
-
})
|
|
160
|
-
.catch((err) => {
|
|
161
|
-
streamSpan.setStatus({
|
|
162
|
-
code: SpanStatusCode.ERROR,
|
|
163
|
-
message: err instanceof Error ? err.message : String(err),
|
|
164
|
-
});
|
|
165
|
-
streamSpan.end();
|
|
166
|
-
});
|
|
167
|
-
timeoutController?.cleanup();
|
|
168
|
-
// Transform string stream to content object stream (match Google AI pattern)
|
|
169
|
-
const transformedStream = async function* () {
|
|
170
|
-
let chunkCount = 0;
|
|
171
|
-
try {
|
|
172
|
-
for await (const chunk of result.textStream) {
|
|
173
|
-
chunkCount++;
|
|
174
|
-
yield { content: chunk };
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
catch (streamError) {
|
|
178
|
-
if (NoOutputGeneratedError.isInstance(streamError)) {
|
|
179
|
-
logger.warn("AnthropicBaseProvider: Stream produced no output (NoOutputGeneratedError) — caught from textStream");
|
|
180
|
-
const sentinel = await buildNoOutputSentinel(streamError, result, capturedProviderError);
|
|
181
|
-
stampNoOutputSpan(sentinel);
|
|
182
|
-
yield sentinel;
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
throw streamError;
|
|
186
|
-
}
|
|
187
|
-
// Curator P3-6 (round-2 fix): production trigger sets the error
|
|
188
|
-
// on result.finishReason rejection, not on textStream iteration.
|
|
189
|
-
// Surface that path here so the sentinel actually fires.
|
|
190
|
-
if (chunkCount === 0) {
|
|
191
|
-
const detected = await detectPostStreamNoOutput(result, capturedProviderError);
|
|
192
|
-
if (detected) {
|
|
193
|
-
logger.warn("AnthropicBaseProvider: Stream produced no output (NoOutputGeneratedError) — caught from finishReason rejection");
|
|
194
|
-
stampNoOutputSpan(detected.sentinel);
|
|
195
|
-
yield detected.sentinel;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
};
|
|
199
|
-
return {
|
|
200
|
-
stream: transformedStream(),
|
|
201
|
-
provider: this.providerName,
|
|
202
|
-
model: this.modelName,
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
|
-
catch (error) {
|
|
206
|
-
timeoutController?.cleanup();
|
|
207
|
-
throw this.handleProviderError(error);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
// Export for testing
|
|
212
|
-
export default AnthropicProviderV2;
|
|
213
|
-
//# sourceMappingURL=anthropicBaseProvider.js.map
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import type { ZodType } from "zod";
|
|
2
|
-
import { type AIProviderName } from "../constants/enums.js";
|
|
3
|
-
import { BaseProvider } from "../core/baseProvider.js";
|
|
4
|
-
import type { StreamOptions, StreamResult } from "../types/index.js";
|
|
5
|
-
import type { LanguageModel, Schema } from "../types/index.js";
|
|
6
|
-
/**
|
|
7
|
-
* Anthropic provider implementation using BaseProvider pattern
|
|
8
|
-
* Migrated from direct API calls to Vercel AI SDK (@ai-sdk/anthropic)
|
|
9
|
-
* Follows exact Google AI interface patterns for compatibility
|
|
10
|
-
*/
|
|
11
|
-
export declare class AnthropicProviderV2 extends BaseProvider {
|
|
12
|
-
constructor(modelName?: string);
|
|
13
|
-
protected getProviderName(): AIProviderName;
|
|
14
|
-
protected getDefaultModel(): string;
|
|
15
|
-
/**
|
|
16
|
-
* Returns the Vercel AI SDK model instance for Anthropic
|
|
17
|
-
*/
|
|
18
|
-
protected getAISDKModel(): LanguageModel;
|
|
19
|
-
protected formatProviderError(error: unknown): Error;
|
|
20
|
-
private getApiKey;
|
|
21
|
-
protected executeStream(options: StreamOptions, _analysisSchema?: ZodType | Schema<unknown>): Promise<StreamResult>;
|
|
22
|
-
}
|
|
23
|
-
export default AnthropicProviderV2;
|
|
@@ -1,212 +0,0 @@
|
|
|
1
|
-
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
2
|
-
import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
|
|
3
|
-
import { AnthropicModels } from "../constants/enums.js";
|
|
4
|
-
import { BaseProvider } from "../core/baseProvider.js";
|
|
5
|
-
import { AuthenticationError, NetworkError, ProviderError, RateLimitError, } from "../types/index.js";
|
|
6
|
-
import { logger } from "../utils/logger.js";
|
|
7
|
-
import { buildNoOutputSentinel, detectPostStreamNoOutput, stampNoOutputSpan, } from "../utils/noOutputSentinel.js";
|
|
8
|
-
import { calculateCost } from "../utils/pricing.js";
|
|
9
|
-
import { createAnthropicBaseConfig, validateApiKey, } from "../utils/providerConfig.js";
|
|
10
|
-
import { composeAbortSignals, createTimeoutController, TimeoutError, } from "../utils/timeout.js";
|
|
11
|
-
import { resolveToolChoice } from "../utils/toolChoice.js";
|
|
12
|
-
import { getModelId } from "./providerTypeUtils.js";
|
|
13
|
-
import { NoOutputGeneratedError } from "../utils/generationErrors.js";
|
|
14
|
-
import { streamText } from "../utils/generation.js";
|
|
15
|
-
const streamTracer = trace.getTracer("neurolink.provider.anthropic");
|
|
16
|
-
/**
|
|
17
|
-
* Anthropic provider implementation using BaseProvider pattern
|
|
18
|
-
* Migrated from direct API calls to Vercel AI SDK (@ai-sdk/anthropic)
|
|
19
|
-
* Follows exact Google AI interface patterns for compatibility
|
|
20
|
-
*/
|
|
21
|
-
export class AnthropicProviderV2 extends BaseProvider {
|
|
22
|
-
constructor(modelName) {
|
|
23
|
-
super(modelName, "anthropic");
|
|
24
|
-
logger.debug("AnthropicProviderV2 initialized", {
|
|
25
|
-
model: this.modelName,
|
|
26
|
-
provider: this.providerName,
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
// ===================
|
|
30
|
-
// ABSTRACT METHOD IMPLEMENTATIONS
|
|
31
|
-
// ===================
|
|
32
|
-
getProviderName() {
|
|
33
|
-
return "anthropic";
|
|
34
|
-
}
|
|
35
|
-
getDefaultModel() {
|
|
36
|
-
return process.env.ANTHROPIC_MODEL || AnthropicModels.CLAUDE_3_5_SONNET;
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Returns the Vercel AI SDK model instance for Anthropic
|
|
40
|
-
*/
|
|
41
|
-
getAISDKModel() {
|
|
42
|
-
const apiKey = this.getApiKey();
|
|
43
|
-
const anthropic = createAnthropic({ apiKey });
|
|
44
|
-
return anthropic(this.modelName);
|
|
45
|
-
}
|
|
46
|
-
formatProviderError(error) {
|
|
47
|
-
if (error instanceof TimeoutError) {
|
|
48
|
-
return new NetworkError(`Request timed out: ${error.message}`, this.providerName);
|
|
49
|
-
}
|
|
50
|
-
const errorWithStatus = error;
|
|
51
|
-
if (errorWithStatus?.status === 401) {
|
|
52
|
-
return new AuthenticationError("Invalid Anthropic API key. Please check your ANTHROPIC_API_KEY environment variable.", this.providerName);
|
|
53
|
-
}
|
|
54
|
-
if (errorWithStatus?.status === 429) {
|
|
55
|
-
return new RateLimitError("Anthropic rate limit exceeded. Please try again later.", this.providerName);
|
|
56
|
-
}
|
|
57
|
-
if (errorWithStatus?.status === 400) {
|
|
58
|
-
return new ProviderError(`Bad request: ${errorWithStatus?.message || "Invalid request parameters"}`, this.providerName);
|
|
59
|
-
}
|
|
60
|
-
return new ProviderError(`Anthropic error: ${errorWithStatus?.message || String(error) || "Unknown error"}`, this.providerName);
|
|
61
|
-
}
|
|
62
|
-
// Configuration helper - now using consolidated utility
|
|
63
|
-
getApiKey() {
|
|
64
|
-
return validateApiKey(createAnthropicBaseConfig());
|
|
65
|
-
}
|
|
66
|
-
// executeGenerate removed - BaseProvider handles all generation with tools
|
|
67
|
-
async executeStream(options, _analysisSchema) {
|
|
68
|
-
// Note: StreamOptions validation handled differently than TextGenerationOptions
|
|
69
|
-
const model = await this.getAISDKModelWithMiddleware(options);
|
|
70
|
-
const timeout = this.getTimeout(options);
|
|
71
|
-
const timeoutController = createTimeoutController(timeout, this.providerName, "stream");
|
|
72
|
-
try {
|
|
73
|
-
// Get tools - options.tools is pre-merged by BaseProvider.stream()
|
|
74
|
-
const shouldUseTools = !options.disableTools && this.supportsTools();
|
|
75
|
-
const tools = shouldUseTools
|
|
76
|
-
? options.tools || (await this.getAllTools())
|
|
77
|
-
: {};
|
|
78
|
-
// Wrap streamText in an OTel span to capture provider-level latency and token usage
|
|
79
|
-
const streamSpan = streamTracer.startSpan("neurolink.provider.streamText", {
|
|
80
|
-
kind: SpanKind.CLIENT,
|
|
81
|
-
attributes: {
|
|
82
|
-
"gen_ai.system": "anthropic",
|
|
83
|
-
"gen_ai.request.model": getModelId(model, this.modelName || "unknown"),
|
|
84
|
-
},
|
|
85
|
-
});
|
|
86
|
-
// Reviewer follow-up: capture upstream provider errors via onError
|
|
87
|
-
// so the post-stream NoOutput detect can propagate the real cause
|
|
88
|
-
// into the sentinel's providerError / modelResponseRaw.
|
|
89
|
-
let capturedProviderError;
|
|
90
|
-
let result;
|
|
91
|
-
try {
|
|
92
|
-
result = streamText({
|
|
93
|
-
model,
|
|
94
|
-
prompt: options.input.text ?? "",
|
|
95
|
-
system: options.systemPrompt,
|
|
96
|
-
temperature: options.temperature,
|
|
97
|
-
maxOutputTokens: options.maxTokens, // No default limit - unlimited unless specified
|
|
98
|
-
maxRetries: 0, // NL11: Disable AI SDK's invisible internal retries; we handle retries with OTel instrumentation
|
|
99
|
-
tools,
|
|
100
|
-
toolChoice: resolveToolChoice(options, tools, shouldUseTools),
|
|
101
|
-
abortSignal: composeAbortSignals(options.abortSignal, timeoutController?.controller.signal),
|
|
102
|
-
experimental_telemetry: this.telemetryHandler.getTelemetryConfig(options),
|
|
103
|
-
experimental_repairToolCall: this.getToolCallRepairFn(options),
|
|
104
|
-
onError: (event) => {
|
|
105
|
-
capturedProviderError = event.error;
|
|
106
|
-
logger.error("AnthropicBaseProvider: Stream error", {
|
|
107
|
-
error: event.error instanceof Error
|
|
108
|
-
? event.error.message
|
|
109
|
-
: String(event.error),
|
|
110
|
-
});
|
|
111
|
-
},
|
|
112
|
-
onStepFinish: ({ toolCalls, toolResults }) => {
|
|
113
|
-
this.handleToolExecutionStorage(toolCalls, toolResults, options, new Date()).catch((error) => {
|
|
114
|
-
logger.warn("[AnthropicBaseProvider] Failed to store tool executions", {
|
|
115
|
-
provider: this.providerName,
|
|
116
|
-
error: error instanceof Error ? error.message : String(error),
|
|
117
|
-
});
|
|
118
|
-
});
|
|
119
|
-
},
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
catch (err) {
|
|
123
|
-
streamSpan.recordException(err instanceof Error ? err : new Error(String(err)));
|
|
124
|
-
streamSpan.setStatus({
|
|
125
|
-
code: SpanStatusCode.ERROR,
|
|
126
|
-
message: err instanceof Error ? err.message : String(err),
|
|
127
|
-
});
|
|
128
|
-
streamSpan.end();
|
|
129
|
-
throw err;
|
|
130
|
-
}
|
|
131
|
-
// Collect token usage and finish reason asynchronously when the stream completes,
|
|
132
|
-
// then end the span. This avoids blocking the stream consumer.
|
|
133
|
-
Promise.resolve(result.usage)
|
|
134
|
-
.then((usage) => {
|
|
135
|
-
streamSpan.setAttribute("gen_ai.usage.input_tokens", usage.inputTokens || 0);
|
|
136
|
-
streamSpan.setAttribute("gen_ai.usage.output_tokens", usage.outputTokens || 0);
|
|
137
|
-
const cost = calculateCost(this.providerName, this.modelName, {
|
|
138
|
-
input: usage.inputTokens || 0,
|
|
139
|
-
output: usage.outputTokens || 0,
|
|
140
|
-
total: (usage.inputTokens || 0) + (usage.outputTokens || 0),
|
|
141
|
-
});
|
|
142
|
-
if (cost && cost > 0) {
|
|
143
|
-
streamSpan.setAttribute("neurolink.cost", cost);
|
|
144
|
-
}
|
|
145
|
-
})
|
|
146
|
-
.catch(() => {
|
|
147
|
-
// Usage may not be available if the stream is aborted
|
|
148
|
-
});
|
|
149
|
-
Promise.resolve(result.finishReason)
|
|
150
|
-
.then((reason) => {
|
|
151
|
-
streamSpan.setAttribute("gen_ai.response.finish_reason", reason || "unknown");
|
|
152
|
-
})
|
|
153
|
-
.catch(() => {
|
|
154
|
-
// Finish reason may not be available if the stream is aborted
|
|
155
|
-
});
|
|
156
|
-
Promise.resolve(result.text)
|
|
157
|
-
.then(() => {
|
|
158
|
-
streamSpan.end();
|
|
159
|
-
})
|
|
160
|
-
.catch((err) => {
|
|
161
|
-
streamSpan.setStatus({
|
|
162
|
-
code: SpanStatusCode.ERROR,
|
|
163
|
-
message: err instanceof Error ? err.message : String(err),
|
|
164
|
-
});
|
|
165
|
-
streamSpan.end();
|
|
166
|
-
});
|
|
167
|
-
timeoutController?.cleanup();
|
|
168
|
-
// Transform string stream to content object stream (match Google AI pattern)
|
|
169
|
-
const transformedStream = async function* () {
|
|
170
|
-
let chunkCount = 0;
|
|
171
|
-
try {
|
|
172
|
-
for await (const chunk of result.textStream) {
|
|
173
|
-
chunkCount++;
|
|
174
|
-
yield { content: chunk };
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
catch (streamError) {
|
|
178
|
-
if (NoOutputGeneratedError.isInstance(streamError)) {
|
|
179
|
-
logger.warn("AnthropicBaseProvider: Stream produced no output (NoOutputGeneratedError) — caught from textStream");
|
|
180
|
-
const sentinel = await buildNoOutputSentinel(streamError, result, capturedProviderError);
|
|
181
|
-
stampNoOutputSpan(sentinel);
|
|
182
|
-
yield sentinel;
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
throw streamError;
|
|
186
|
-
}
|
|
187
|
-
// Curator P3-6 (round-2 fix): production trigger sets the error
|
|
188
|
-
// on result.finishReason rejection, not on textStream iteration.
|
|
189
|
-
// Surface that path here so the sentinel actually fires.
|
|
190
|
-
if (chunkCount === 0) {
|
|
191
|
-
const detected = await detectPostStreamNoOutput(result, capturedProviderError);
|
|
192
|
-
if (detected) {
|
|
193
|
-
logger.warn("AnthropicBaseProvider: Stream produced no output (NoOutputGeneratedError) — caught from finishReason rejection");
|
|
194
|
-
stampNoOutputSpan(detected.sentinel);
|
|
195
|
-
yield detected.sentinel;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
};
|
|
199
|
-
return {
|
|
200
|
-
stream: transformedStream(),
|
|
201
|
-
provider: this.providerName,
|
|
202
|
-
model: this.modelName,
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
|
-
catch (error) {
|
|
206
|
-
timeoutController?.cleanup();
|
|
207
|
-
throw this.handleProviderError(error);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
// Export for testing
|
|
212
|
-
export default AnthropicProviderV2;
|