@juspay/neurolink 11.5.2 → 11.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -276,9 +276,43 @@ export declare abstract class BaseProvider implements AIProvider {
276
276
  */
277
277
  protected getToolCallRepairFn(options?: StreamOptions | TextGenerationOptions): ToolCallRepairFunction<ToolSet> | undefined;
278
278
  /**
279
- * Provider-specific streaming implementation (only used when tools are disabled)
280
- */
281
- protected abstract executeStream(options: StreamOptions, analysisSchema?: ValidationSchema): Promise<StreamResult>;
279
+ * Opt-in streaming hook. A provider that produces an async iterable of text
280
+ * chunks plus promises for how the turn ended implements this and gets a
281
+ * working `executeStream` for free.
282
+ *
283
+ * Deliberately optional rather than abstract: every provider that already
284
+ * overrides `executeStream` directly — the older and still perfectly valid
285
+ * pattern — never needs it.
286
+ *
287
+ * `finishReason` and `usage` are promises because both are only knowable
288
+ * once the underlying stream has finished. Resolve them when it does; the
289
+ * default `executeStream` chains off them rather than waiting for a
290
+ * consumer, so they must settle even if nobody drains the stream.
291
+ */
292
+ protected doStream?(options: StreamOptions): Promise<{
293
+ stream: AsyncIterable<{
294
+ content: string;
295
+ }>;
296
+ finishReason: Promise<string>;
297
+ usage: Promise<{
298
+ inputTokens: number;
299
+ outputTokens: number;
300
+ }>;
301
+ warnings?: string[];
302
+ }>;
303
+ /**
304
+ * Provider-specific streaming implementation (only used when tools are
305
+ * disabled).
306
+ *
307
+ * This used to be `protected abstract`, which meant a provider had exactly
308
+ * two options: write the whole adapter by hand, or not stream at all. The
309
+ * failure mode that produced was SageMaker's — a complete, working
310
+ * `doStream` sitting one property access away from an `executeStream` that
311
+ * unconditionally threw "not yet fully implemented". Providers that
312
+ * implement `doStream` now inherit a correct implementation, and providers
313
+ * that override this method are unaffected.
314
+ */
315
+ protected executeStream(options: StreamOptions, _analysisSchema?: ValidationSchema): Promise<StreamResult>;
282
316
  /**
283
317
  * Get the provider name
284
318
  */
@@ -4,7 +4,9 @@ import { isImageGenerationModel } from "../core/constants.js";
4
4
  import { MiddlewareFactory } from "../middleware/factory.js";
5
5
  import { modelSupports } from "../models/modelRegistry.js";
6
6
  import { ATTR, tracers } from "../telemetry/index.js";
7
- import { isAbortError, NeuroLinkError } from "../utils/errorHandling.js";
7
+ import { ERROR_CODES, isAbortError, NeuroLinkError, } from "../utils/errorHandling.js";
8
+ import { createAnalytics as buildAnalytics } from "./analytics.js";
9
+ import { ErrorCategory, ErrorSeverity } from "../constants/enums.js";
8
10
  import { duckTypedStatusCode, extractRetryAfterMsFromError, } from "../utils/providerRetry.js";
9
11
  import { hasLifecycleErrorFired, markLifecycleErrorFired, } from "../utils/lifecycleCallbacks.js";
10
12
  import { resolveLifecycleTimeoutMs } from "../utils/lifecycleTimeout.js";
@@ -1372,6 +1374,73 @@ export class BaseProvider {
1372
1374
  return createToolCallRepair()(...args);
1373
1375
  });
1374
1376
  }
1377
+ /**
1378
+ * Provider-specific streaming implementation (only used when tools are
1379
+ * disabled).
1380
+ *
1381
+ * This used to be `protected abstract`, which meant a provider had exactly
1382
+ * two options: write the whole adapter by hand, or not stream at all. The
1383
+ * failure mode that produced was SageMaker's — a complete, working
1384
+ * `doStream` sitting one property access away from an `executeStream` that
1385
+ * unconditionally threw "not yet fully implemented". Providers that
1386
+ * implement `doStream` now inherit a correct implementation, and providers
1387
+ * that override this method are unaffected.
1388
+ */
1389
+ async executeStream(options, _analysisSchema) {
1390
+ if (!this.doStream) {
1391
+ throw new NeuroLinkError({
1392
+ code: ERROR_CODES.INVALID_CONFIGURATION,
1393
+ message: `${this.providerName} cannot stream: it neither implements doStream() nor overrides executeStream()`,
1394
+ category: ErrorCategory.CONFIGURATION,
1395
+ severity: ErrorSeverity.CRITICAL,
1396
+ retriable: false,
1397
+ context: { provider: this.providerName, model: this.modelName },
1398
+ });
1399
+ }
1400
+ const startTime = Date.now();
1401
+ const { stream, finishReason, usage, warnings } = await this.doStream(options);
1402
+ if (warnings?.length) {
1403
+ logger.warn(`[${this.providerName}] doStream reported warnings`, {
1404
+ provider: this.providerName,
1405
+ count: warnings.length,
1406
+ });
1407
+ }
1408
+ // `metadata` is handed back by reference and filled in when the turn
1409
+ // ends — the documented contract for background-loop streams, since a
1410
+ // result-object spread would snapshot a top-level getter before the
1411
+ // stream has produced anything.
1412
+ const metadata = {
1413
+ startTime,
1414
+ streamId: `${this.providerName}-${startTime}`,
1415
+ };
1416
+ // Chained off the provider's own promises rather than off the consumer
1417
+ // draining the stream. Binding analytics to a stream iterator's finally
1418
+ // block means a caller that awaits analytics without iterating waits
1419
+ // forever, because a generator body does not run until it is iterated.
1420
+ const analytics = (async () => {
1421
+ const [resolvedFinishReason, resolvedUsage] = await Promise.all([
1422
+ finishReason,
1423
+ usage,
1424
+ ]);
1425
+ metadata.finishReason = resolvedFinishReason;
1426
+ metadata.rawFinishReason = resolvedFinishReason;
1427
+ return buildAnalytics(this.providerName, this.modelName || this.getDefaultModel(), {
1428
+ usage: {
1429
+ input: resolvedUsage.inputTokens,
1430
+ output: resolvedUsage.outputTokens,
1431
+ total: resolvedUsage.inputTokens + resolvedUsage.outputTokens,
1432
+ },
1433
+ stopReason: resolvedFinishReason,
1434
+ }, Date.now() - startTime, { streamingMode: true });
1435
+ })();
1436
+ return {
1437
+ stream,
1438
+ model: this.modelName || this.getDefaultModel(),
1439
+ provider: this.getProviderName(),
1440
+ analytics,
1441
+ metadata,
1442
+ };
1443
+ }
1375
1444
  /**
1376
1445
  * Get AI SDK model with middleware applied
1377
1446
  * This method wraps the base model with any configured middleware
@@ -276,9 +276,43 @@ export declare abstract class BaseProvider implements AIProvider {
276
276
  */
277
277
  protected getToolCallRepairFn(options?: StreamOptions | TextGenerationOptions): ToolCallRepairFunction<ToolSet> | undefined;
278
278
  /**
279
- * Provider-specific streaming implementation (only used when tools are disabled)
280
- */
281
- protected abstract executeStream(options: StreamOptions, analysisSchema?: ValidationSchema): Promise<StreamResult>;
279
+ * Opt-in streaming hook. A provider that produces an async iterable of text
280
+ * chunks plus promises for how the turn ended implements this and gets a
281
+ * working `executeStream` for free.
282
+ *
283
+ * Deliberately optional rather than abstract: every provider that already
284
+ * overrides `executeStream` directly — the older and still perfectly valid
285
+ * pattern — never needs it.
286
+ *
287
+ * `finishReason` and `usage` are promises because both are only knowable
288
+ * once the underlying stream has finished. Resolve them when it does; the
289
+ * default `executeStream` chains off them rather than waiting for a
290
+ * consumer, so they must settle even if nobody drains the stream.
291
+ */
292
+ protected doStream?(options: StreamOptions): Promise<{
293
+ stream: AsyncIterable<{
294
+ content: string;
295
+ }>;
296
+ finishReason: Promise<string>;
297
+ usage: Promise<{
298
+ inputTokens: number;
299
+ outputTokens: number;
300
+ }>;
301
+ warnings?: string[];
302
+ }>;
303
+ /**
304
+ * Provider-specific streaming implementation (only used when tools are
305
+ * disabled).
306
+ *
307
+ * This used to be `protected abstract`, which meant a provider had exactly
308
+ * two options: write the whole adapter by hand, or not stream at all. The
309
+ * failure mode that produced was SageMaker's — a complete, working
310
+ * `doStream` sitting one property access away from an `executeStream` that
311
+ * unconditionally threw "not yet fully implemented". Providers that
312
+ * implement `doStream` now inherit a correct implementation, and providers
313
+ * that override this method are unaffected.
314
+ */
315
+ protected executeStream(options: StreamOptions, _analysisSchema?: ValidationSchema): Promise<StreamResult>;
282
316
  /**
283
317
  * Get the provider name
284
318
  */
@@ -4,7 +4,9 @@ import { isImageGenerationModel } from "../core/constants.js";
4
4
  import { MiddlewareFactory } from "../middleware/factory.js";
5
5
  import { modelSupports } from "../models/modelRegistry.js";
6
6
  import { ATTR, tracers } from "../telemetry/index.js";
7
- import { isAbortError, NeuroLinkError } from "../utils/errorHandling.js";
7
+ import { ERROR_CODES, isAbortError, NeuroLinkError, } from "../utils/errorHandling.js";
8
+ import { createAnalytics as buildAnalytics } from "./analytics.js";
9
+ import { ErrorCategory, ErrorSeverity } from "../constants/enums.js";
8
10
  import { duckTypedStatusCode, extractRetryAfterMsFromError, } from "../utils/providerRetry.js";
9
11
  import { hasLifecycleErrorFired, markLifecycleErrorFired, } from "../utils/lifecycleCallbacks.js";
10
12
  import { resolveLifecycleTimeoutMs } from "../utils/lifecycleTimeout.js";
@@ -1372,6 +1374,73 @@ export class BaseProvider {
1372
1374
  return createToolCallRepair()(...args);
1373
1375
  });
1374
1376
  }
1377
+ /**
1378
+ * Provider-specific streaming implementation (only used when tools are
1379
+ * disabled).
1380
+ *
1381
+ * This used to be `protected abstract`, which meant a provider had exactly
1382
+ * two options: write the whole adapter by hand, or not stream at all. The
1383
+ * failure mode that produced was SageMaker's — a complete, working
1384
+ * `doStream` sitting one property access away from an `executeStream` that
1385
+ * unconditionally threw "not yet fully implemented". Providers that
1386
+ * implement `doStream` now inherit a correct implementation, and providers
1387
+ * that override this method are unaffected.
1388
+ */
1389
+ async executeStream(options, _analysisSchema) {
1390
+ if (!this.doStream) {
1391
+ throw new NeuroLinkError({
1392
+ code: ERROR_CODES.INVALID_CONFIGURATION,
1393
+ message: `${this.providerName} cannot stream: it neither implements doStream() nor overrides executeStream()`,
1394
+ category: ErrorCategory.CONFIGURATION,
1395
+ severity: ErrorSeverity.CRITICAL,
1396
+ retriable: false,
1397
+ context: { provider: this.providerName, model: this.modelName },
1398
+ });
1399
+ }
1400
+ const startTime = Date.now();
1401
+ const { stream, finishReason, usage, warnings } = await this.doStream(options);
1402
+ if (warnings?.length) {
1403
+ logger.warn(`[${this.providerName}] doStream reported warnings`, {
1404
+ provider: this.providerName,
1405
+ count: warnings.length,
1406
+ });
1407
+ }
1408
+ // `metadata` is handed back by reference and filled in when the turn
1409
+ // ends — the documented contract for background-loop streams, since a
1410
+ // result-object spread would snapshot a top-level getter before the
1411
+ // stream has produced anything.
1412
+ const metadata = {
1413
+ startTime,
1414
+ streamId: `${this.providerName}-${startTime}`,
1415
+ };
1416
+ // Chained off the provider's own promises rather than off the consumer
1417
+ // draining the stream. Binding analytics to a stream iterator's finally
1418
+ // block means a caller that awaits analytics without iterating waits
1419
+ // forever, because a generator body does not run until it is iterated.
1420
+ const analytics = (async () => {
1421
+ const [resolvedFinishReason, resolvedUsage] = await Promise.all([
1422
+ finishReason,
1423
+ usage,
1424
+ ]);
1425
+ metadata.finishReason = resolvedFinishReason;
1426
+ metadata.rawFinishReason = resolvedFinishReason;
1427
+ return buildAnalytics(this.providerName, this.modelName || this.getDefaultModel(), {
1428
+ usage: {
1429
+ input: resolvedUsage.inputTokens,
1430
+ output: resolvedUsage.outputTokens,
1431
+ total: resolvedUsage.inputTokens + resolvedUsage.outputTokens,
1432
+ },
1433
+ stopReason: resolvedFinishReason,
1434
+ }, Date.now() - startTime, { streamingMode: true });
1435
+ })();
1436
+ return {
1437
+ stream,
1438
+ model: this.modelName || this.getDefaultModel(),
1439
+ provider: this.getProviderName(),
1440
+ analytics,
1441
+ metadata,
1442
+ };
1443
+ }
1375
1444
  /**
1376
1445
  * Get AI SDK model with middleware applied
1377
1446
  * This method wraps the base model with any configured middleware
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/neurolink",
3
- "version": "11.5.2",
3
+ "version": "11.6.0",
4
4
  "packageManager": "pnpm@10.15.1",
5
5
  "description": "TypeScript AI SDK with 24+ LLM providers behind one consistent API. MCP-native (connect any MCP server), voice TTS/STT/realtime, RAG, agents, memory, context compaction. OpenAI · Anthropic · Gemini · Bedrock · Azure · Ollama · DeepSeek · NVIDIA NIM and more.",
6
6
  "author": {