@juspay/neurolink 12.11.3 → 12.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/CHANGELOG.md +2 -3
  2. package/dist/browser/neurolink.min.js +533 -581
  3. package/dist/constants/enums.d.ts +13 -0
  4. package/dist/constants/enums.js +14 -0
  5. package/dist/core/baseProvider.d.ts +71 -3
  6. package/dist/core/baseProvider.js +152 -44
  7. package/dist/core/modules/GenerationHandler.d.ts +22 -24
  8. package/dist/core/modules/GenerationHandler.js +28 -463
  9. package/dist/core/nativeGenerateLoop.d.ts +35 -0
  10. package/dist/core/nativeGenerateLoop.js +299 -0
  11. package/dist/files/fileTools.d.ts +5 -5
  12. package/dist/index.d.ts +1 -0
  13. package/dist/index.js +4 -0
  14. package/dist/middleware/builtin/guardrails.d.ts +0 -5
  15. package/dist/middleware/builtin/guardrails.js +33 -5
  16. package/dist/middleware/factory.js +1 -1
  17. package/dist/middleware/wrapLanguageModel.d.ts +18 -0
  18. package/dist/middleware/wrapLanguageModel.js +53 -0
  19. package/dist/processors/media/AudioProcessor.js +46 -11
  20. package/dist/providers/amazonSagemaker.d.ts +17 -1
  21. package/dist/providers/amazonSagemaker.js +110 -0
  22. package/dist/providers/anthropic/client.d.ts +11 -0
  23. package/dist/providers/anthropic/client.js +148 -1
  24. package/dist/providers/catalog/index.generated.d.ts +1 -1
  25. package/dist/providers/catalog/index.generated.js +3 -0
  26. package/dist/providers/catalog/loader.js +1 -0
  27. package/dist/providers/catalog/mancer.json +192 -0
  28. package/dist/providers/configuredOpenAICompat.d.ts +11 -0
  29. package/dist/providers/configuredOpenAICompat.js +16 -0
  30. package/dist/providers/googleVertex/client.d.ts +0 -9
  31. package/dist/providers/googleVertex/client.js +0 -33
  32. package/dist/providers/openaiChatCompletionsBase.d.ts +21 -4
  33. package/dist/providers/openaiChatCompletionsBase.js +220 -0
  34. package/dist/providers/providerTypeUtils.d.ts +1 -2
  35. package/dist/providers/providerTypeUtils.js +5 -1
  36. package/dist/types/aiCompat.d.ts +485 -0
  37. package/dist/types/aiCompat.js +17 -0
  38. package/dist/types/conversation.d.ts +1 -1
  39. package/dist/types/generate.d.ts +52 -0
  40. package/dist/types/middleware.d.ts +3 -6
  41. package/dist/types/providerCatalog.generated.d.ts +2 -2
  42. package/dist/types/providers.d.ts +14 -1
  43. package/dist/types/tools.d.ts +2 -2
  44. package/dist/utils/generationErrors.d.ts +78 -6
  45. package/dist/utils/generationErrors.js +114 -6
  46. package/dist/utils/nativeSingleShot.d.ts +3 -0
  47. package/dist/utils/nativeSingleShot.js +83 -0
  48. package/dist/utils/tool.d.ts +30 -5
  49. package/dist/utils/tool.js +43 -5
  50. package/package.json +3 -6
  51. package/dist/utils/generation.d.ts +0 -8
  52. package/dist/utils/generation.js +0 -8
@@ -19,10 +19,7 @@
19
19
  */
20
20
  import type { AIProviderName } from "../constants/enums.js";
21
21
  import { BaseProvider } from "../core/baseProvider.js";
22
- import type { LanguageModel, OpenAICompatBuildBodyArgs, OpenAICompatChatMessage, OpenAICompatChatRequest, OpenAICompatChatTool, OpenAICompatResponseFormat, OpenAICompatStreamLifecycleListeners, Schema, StreamOptions, StreamResult, ZodUnknownSchema } from "../types/index.js";
23
- /**
24
- * Abstract HTTP+SSE provider for OpenAI chat-completions-shaped endpoints.
25
- */
22
+ import type { LanguageModel, OpenAICompatBuildBodyArgs, OpenAICompatChatMessage, OpenAICompatChatRequest, OpenAICompatChatTool, OpenAICompatResponseFormat, OpenAICompatStreamLifecycleListeners, Schema, EnhancedGenerateResult, TextGenerationOptions, ValidationSchema, StreamOptions, StreamResult, ZodUnknownSchema } from "../types/index.js";
26
23
  export declare abstract class OpenAIChatCompletionsProvider extends BaseProvider {
27
24
  protected config: {
28
25
  baseURL: string;
@@ -227,6 +224,26 @@ export declare abstract class OpenAIChatCompletionsProvider extends BaseProvider
227
224
  * streamText, no AI SDK orchestrator. Tool calls, multi-step loops,
228
225
  * telemetry, abort handling all inline.
229
226
  */
227
+ /**
228
+ * Native non-streaming generate.
229
+ *
230
+ * Drives the SAME `doGenerate` the ai loop drove — `buildDelegatingModel`'s,
231
+ * reached through `getAISDKModel()` — and supplies only the multi-step tool
232
+ * iteration around it. That matters: `doGenerate` is where the JSON-versus-SSE
233
+ * wire choice lives (`useStreamingWireForGenerate()`, false by default), along
234
+ * with the 400 retry, the context-overflow refit and the invalid-model
235
+ * fallback. An earlier attempt ran generate through the STREAMING loop
236
+ * instead and silently began sending `stream: true` on a path that had always
237
+ * sent plain JSON; ten providers returned empty content against a
238
+ * non-streaming body and a stream-rejecting backend failed outright. Loop
239
+ * around doGenerate, never around streamOneStep.
240
+ *
241
+ * Tool turns are appended in the message-builder shape, which
242
+ * `messageBuilderToOpenAI` already round-trips: an assistant message carrying
243
+ * `tool-call` parts, then one `tool` message of `tool-result` parts.
244
+ */
245
+ generate(optionsOrPrompt: TextGenerationOptions | string, analysisSchema?: ValidationSchema): Promise<EnhancedGenerateResult | null>;
246
+ private executeNativeGenerate;
230
247
  protected executeStream(options: StreamOptions, _analysisSchema?: ZodUnknownSchema | Schema<unknown>): Promise<StreamResult>;
231
248
  private runStreamLoop;
232
249
  private streamOneStep;
@@ -32,9 +32,15 @@ import { NoOutputGeneratedError } from "../utils/generationErrors.js";
32
32
  import { buildNoOutputSentinel, stampNoOutputSpan, } from "../utils/noOutputSentinel.js";
33
33
  import { composeAbortSignalsScoped, createTimeoutController, mergeAbortSignals, } from "../utils/timeout.js";
34
34
  import { emitToolEndFromStepFinish } from "../utils/toolEndEmitter.js";
35
+ import { resolveRequestKind } from "../core/resolveRequestKind.js";
36
+ import { appendJsonSchemaInstruction, hasNativeDoGenerate, runNativeGenerateLoop, } from "../core/nativeGenerateLoop.js";
37
+ import { resolveToolExecutionRecords } from "../core/toolExecutionRecorder.js";
38
+ import { convertZodToJsonSchema } from "../utils/schemaConversion.js";
39
+ import { coerceJsonToSchema, schemaAccepts } from "../utils/json/coerce.js";
35
40
  import { resolveToolChoice } from "../utils/toolChoice.js";
36
41
  import { transformToolExecutions } from "../utils/transformationUtils.js";
37
42
  import { withProviderRetry } from "../utils/providerRetry.js";
43
+ import { isSchemaComplexityError, isToolsSchemaConflictError, } from "../core/modules/structuredOutputPolicy.js";
38
44
  import { resolveDeferredTool } from "../tools/toolDiscovery.js";
39
45
  import { buildAPIError, buildBody, buildToolsForOpenAI, buildWireToolNameMaps, createDeferredAnalytics, ensureJsonWordInBody, estimateWireTokens, mapNeuroLinkToolChoice, mergeUsage, messageBuilderToOpenAI, parseSSEStream, stringifyToolOutput, stripTrailingSlash, v3ResponseFormatToOpenAI, v3ToolChoiceToOpenAI, v3ToolsToOpenAI, } from "./openaiChatCompletionsClient.js";
40
46
  import { createStreamChannel } from "../core/streamChannel.js";
@@ -47,6 +53,19 @@ const WINDOW_FIT_MARGIN_TOKENS = 512;
47
53
  /**
48
54
  * Abstract HTTP+SSE provider for OpenAI chat-completions-shaped endpoints.
49
55
  */
56
+ /**
57
+ * Did the model's text yield an object the caller's schema accepts?
58
+ *
59
+ * This is the trigger for the prompt-side structured-output fallback. It asks
60
+ * the question the ai-package's structured-output parser used to ask by
61
+ * throwing: did the native `response_format` attempt actually produce the
62
+ * object. A schema we cannot validate with accepts everything, so an unknown
63
+ * schema never forces a pointless second request.
64
+ */
65
+ const yieldsSchemaValidObject = (text, schema) => {
66
+ const coerced = coerceJsonToSchema(text, schema);
67
+ return coerced !== null && schemaAccepts(schema, coerced.structuredData);
68
+ };
50
69
  export class OpenAIChatCompletionsProvider extends BaseProvider {
51
70
  config;
52
71
  resolvedModel;
@@ -707,6 +726,207 @@ export class OpenAIChatCompletionsProvider extends BaseProvider {
707
726
  * streamText, no AI SDK orchestrator. Tool calls, multi-step loops,
708
727
  * telemetry, abort handling all inline.
709
728
  */
729
+ /**
730
+ * Native non-streaming generate.
731
+ *
732
+ * Drives the SAME `doGenerate` the ai loop drove — `buildDelegatingModel`'s,
733
+ * reached through `getAISDKModel()` — and supplies only the multi-step tool
734
+ * iteration around it. That matters: `doGenerate` is where the JSON-versus-SSE
735
+ * wire choice lives (`useStreamingWireForGenerate()`, false by default), along
736
+ * with the 400 retry, the context-overflow refit and the invalid-model
737
+ * fallback. An earlier attempt ran generate through the STREAMING loop
738
+ * instead and silently began sending `stream: true` on a path that had always
739
+ * sent plain JSON; ten providers returned empty content against a
740
+ * non-streaming body and a stream-rejecting backend failed outright. Loop
741
+ * around doGenerate, never around streamOneStep.
742
+ *
743
+ * Tool turns are appended in the message-builder shape, which
744
+ * `messageBuilderToOpenAI` already round-trips: an assistant message carrying
745
+ * `tool-call` parts, then one `tool` message of `tool-result` parts.
746
+ */
747
+ async generate(optionsOrPrompt, analysisSchema) {
748
+ await this.ensureModelLimits();
749
+ const options = this.normalizeTextOptions(optionsOrPrompt);
750
+ if (resolveRequestKind(options, this.modelName) !== "text") {
751
+ return super.generate(options, analysisSchema);
752
+ }
753
+ this.validateOptions(options);
754
+ const mergedTools = await this.getToolsForStream(options);
755
+ // Reuse BaseProvider's invalid-model fallback. Overriding generate() skips
756
+ // it otherwise, and a retired default stops degrading to the next live
757
+ // model in the catalog entry.
758
+ const callerOwnsFallback = "disableInternalFallback" in options &&
759
+ options.disableInternalFallback === true;
760
+ // The native loop bypasses BaseProvider.executeGeneration, so the turn
761
+ // budget has to be composed here or it stops existing for this provider.
762
+ return this.runGenerateWithModelFallback(() => this.withTurnTimeout({ ...options, tools: mergedTools }, this.getDescriptorGenerateMs(), (timedOptions) => this.executeNativeGenerate(timedOptions)), callerOwnsFallback);
763
+ }
764
+ async executeNativeGenerate(options) {
765
+ const startTime = Date.now();
766
+ const modelId = await this.resolveModelName();
767
+ // Middleware must wrap the model here. The native loop bypasses
768
+ // BaseProvider.executeGeneration, and with it the only place middleware was
769
+ // ever applied — a probe showed a caller's wrapGenerate running zero times
770
+ // on every native provider while their onFinish still fired, because
771
+ // onFinish had been special-cased and nothing else had.
772
+ const model = await this.getAISDKModelWithMiddleware(options);
773
+ // Runtime guard rather than an assertion: `LanguageModel` is a union that
774
+ // includes a bare string id, and a double assertion through unknown is
775
+ // banned by Critical Rule 14.
776
+ if (!hasNativeDoGenerate(model)) {
777
+ throw this.handleProviderError(new Error(`${this.providerName}: model handle exposes no doGenerate()`));
778
+ }
779
+ const doGenerate = model.doGenerate.bind(model);
780
+ const shouldUseTools = !options.disableTools && this.supportsTools();
781
+ const toolsRecord = shouldUseTools
782
+ ? options.tools || (await this.getAllTools())
783
+ : {};
784
+ // v3 tool shape — the same one doGenerate already converts internally.
785
+ const v3Tools = shouldUseTools
786
+ ? Object.entries(toolsRecord).map(([name, t]) => {
787
+ const tool = t;
788
+ return {
789
+ type: "function",
790
+ name,
791
+ description: tool.description ?? "",
792
+ inputSchema: (tool.inputSchema
793
+ ? convertZodToJsonSchema(tool.inputSchema)
794
+ : { type: "object", properties: {} }),
795
+ };
796
+ })
797
+ : undefined;
798
+ const hasTools = !!v3Tools && v3Tools.length > 0;
799
+ // Structured output rides response_format, which is what Output.object did
800
+ // on the ai path. Suppressed where the provider says the combination with
801
+ // tools is rejected.
802
+ const responseFormat = options.schema && !(hasTools && this.suppressResponseFormatWithTools())
803
+ ? {
804
+ type: "json",
805
+ schema: convertZodToJsonSchema(options.schema),
806
+ }
807
+ : undefined;
808
+ const conversation = (await this.buildMessagesForStream(options));
809
+ const toolExecutionSummaries = [];
810
+ const runLoop = (conv, format) => runNativeGenerateLoop({
811
+ doGenerate,
812
+ conversation: conv,
813
+ ...(v3Tools ? { tools: v3Tools } : {}),
814
+ toolsRecord,
815
+ ...(hasTools && options.toolChoice
816
+ ? { toolChoice: resolveToolChoice(options, toolsRecord, true) }
817
+ : {}),
818
+ // The per-call `timeout` keeps its per-MODEL-CALL meaning once
819
+ // `turnTimeoutMs` owns the whole-turn deadline, and it reaches the
820
+ // model layer only through this channel. Without it each step fell
821
+ // back to the provider default, so a caller asking for a short
822
+ // per-call timeout got the default on every request.
823
+ ...(typeof options.timeout === "number"
824
+ ? {
825
+ providerOptions: {
826
+ neurolink: { timeoutMs: options.timeout },
827
+ },
828
+ }
829
+ : {}),
830
+ ...(format ? { responseFormat: format } : {}),
831
+ maxSteps: options.maxSteps || DEFAULT_MAX_STEPS,
832
+ ...(options.maxTokens ? { maxOutputTokens: options.maxTokens } : {}),
833
+ ...(options.temperature !== undefined
834
+ ? { temperature: options.temperature }
835
+ : {}),
836
+ ...(options.abortSignal ? { abortSignal: options.abortSignal } : {}),
837
+ ...(options.toolTimeoutMs !== undefined
838
+ ? { toolTimeoutMs: options.toolTimeoutMs }
839
+ : {}),
840
+ // withProviderRetry + handleProviderError are what the ai loop
841
+ // supplied around each call: without them a 429 surfaces as a raw
842
+ // upstream string instead of a RateLimitError, and a throttle is
843
+ // never retried.
844
+ runStep: (call) => withProviderRetry(call, trace.getActiveSpan() ?? undefined, `${this.providerName} generate`).catch((err) => {
845
+ throw this.handleProviderError(err);
846
+ }),
847
+ }, toolExecutionSummaries);
848
+ // Structured output rides `response_format` first. When a vendor rejects
849
+ // that outright — a tools/JSON-mode conflict, or a schema its constrained
850
+ // decoder will not accept — the recovery is to ask for the same object in
851
+ // words instead: drop `response_format` and spell the JSON Schema into the
852
+ // system prompt, letting coerceJsonToSchema recover the object from text.
853
+ //
854
+ // Ported from GenerationHandler's `promptJsonInstruction` fallback, which
855
+ // runs this on the ai-package path. That path is unreachable for every
856
+ // provider driven by this loop — GMI Cloud's MiniMax endpoint, the one it
857
+ // was written for, is a Tier-2 catalog provider on this very base class —
858
+ // so without this the recovery would simply not happen for it.
859
+ let loop;
860
+ try {
861
+ loop = await runLoop(conversation, responseFormat);
862
+ }
863
+ catch (error) {
864
+ const recoverable = responseFormat !== undefined &&
865
+ (isToolsSchemaConflictError(error) || isSchemaComplexityError(error));
866
+ if (!recoverable) {
867
+ throw error;
868
+ }
869
+ logger.warn(`[${this.providerName}] provider rejected response_format — retrying with the schema in the system prompt`, { provider: this.providerName, model: modelId });
870
+ loop = await runLoop(appendJsonSchemaInstruction(conversation, responseFormat.schema), undefined);
871
+ }
872
+ // The vendor can also IGNORE `response_format` and answer in prose without
873
+ // erroring at all — GMI Cloud's MiniMax endpoint does exactly that, and it
874
+ // is the case the fallback was written for. On the ai-package path the
875
+ // structured-output parser threw on the unparseable answer, so the catch
876
+ // above was reached; the native loop has no such parser, so the silent
877
+ // case sailed through and handed the caller prose. Same recovery, keyed on
878
+ // the result rather than on an exception.
879
+ if (responseFormat !== undefined &&
880
+ options.schema !== undefined &&
881
+ !yieldsSchemaValidObject(loop.text, options.schema)) {
882
+ logger.warn(`[${this.providerName}] response_format did not yield a schema-valid object — retrying with the schema in the system prompt`, { provider: this.providerName, model: modelId });
883
+ loop = await runLoop(appendJsonSchemaInstruction(conversation, responseFormat.schema), undefined);
884
+ }
885
+ const { text, finishReason, toolsUsed } = loop;
886
+ const inputTokens = loop.inputTokens;
887
+ const outputTokens = loop.outputTokens;
888
+ // stopReason / stepsUsed parity with the other native loops (Vertex
889
+ // Gemini / Claude / Bedrock) and with the ai-package path this replaced.
890
+ // Without them a consumer cannot tell a completed turn from one the step
891
+ // cap truncated: the turn that ends on a `tool-calls` finish with the
892
+ // budget spent is exactly the case the caller configured `maxSteps` to
893
+ // bound, and reporting it as a plain completion hides that.
894
+ const stepsUsed = loop.steps;
895
+ const stopReason = stepsUsed >= (options.maxSteps || DEFAULT_MAX_STEPS) &&
896
+ finishReason === "tool-calls"
897
+ ? "step-cap"
898
+ : finishReason === "error"
899
+ ? "provider-error"
900
+ : "completed";
901
+ const enhanced = {
902
+ content: text,
903
+ provider: this.providerName,
904
+ model: modelId,
905
+ finishReason,
906
+ stopReason,
907
+ stepsUsed,
908
+ usage: {
909
+ input: inputTokens,
910
+ output: outputTokens,
911
+ total: inputTokens + outputTokens,
912
+ // doGenerate reads prompt_tokens_details.cached_tokens, and the loop
913
+ // carries the counters out; discarding them here billed cached input
914
+ // at the full rate in calculateCost and made cache effectiveness
915
+ // invisible. Anthropic's native path already forwards both.
916
+ ...(loop.cacheReadTokens
917
+ ? { cacheReadTokens: loop.cacheReadTokens }
918
+ : {}),
919
+ ...(loop.cacheWriteTokens
920
+ ? { cacheCreationTokens: loop.cacheWriteTokens }
921
+ : {}),
922
+ },
923
+ responseTime: Date.now() - startTime,
924
+ toolsUsed,
925
+ toolExecutions: resolveToolExecutionRecords(options, transformToolExecutions(toolExecutionSummaries)),
926
+ enhancedWithTools: toolsUsed.length > 0,
927
+ };
928
+ return this.finalizeNativeGenerate(enhanced, options, startTime);
929
+ }
710
930
  async executeStream(options, _analysisSchema) {
711
931
  this.validateStreamOptions(options);
712
932
  const startTime = Date.now();
@@ -1,6 +1,5 @@
1
1
  import type { StreamTextResult } from "../types/index.js";
2
2
  import type { LanguageModel } from "../types/index.js";
3
- import type { streamText } from "../utils/generation.js";
4
3
  /**
5
4
  * Extract the model identifier from a LanguageModel value.
6
5
  *
@@ -20,4 +19,4 @@ export declare function getModelId(model: LanguageModel, fallback?: string): str
20
19
  * `toolResults`, `toolCalls`) exists on the SDK result with compatible types.
21
20
  * This function performs the structural down-cast without `as any`.
22
21
  */
23
- export declare function toAnalyticsStreamResult(result: ReturnType<typeof streamText>): StreamTextResult;
22
+ export declare function toAnalyticsStreamResult(result: StreamTextResult | Record<string, unknown>): StreamTextResult;
@@ -32,7 +32,11 @@ export function getModelId(model, fallback = "unknown") {
32
32
  * `toolResults`, `toolCalls`) exists on the SDK result with compatible types.
33
33
  * This function performs the structural down-cast without `as any`.
34
34
  */
35
- export function toAnalyticsStreamResult(result) {
35
+ export function toAnalyticsStreamResult(
36
+ // Was `ReturnType<typeof streamText>`. Nothing calls streamText any more —
37
+ // every streaming path is native — so the parameter is the structural
38
+ // superset this narrows FROM, stated directly.
39
+ result) {
36
40
  // The AI SDK v6 result is a structural superset of our StreamTextResult.
37
41
  // Both use PromiseLike for async fields and compatible usage shapes
38
42
  // (extractTokenUsage handles both v4 and v6 field names).