@juspay/neurolink 12.11.2 → 12.12.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.
Files changed (59) hide show
  1. package/CHANGELOG.md +3 -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 +261 -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/mcp/toolRegistry.js +7 -0
  15. package/dist/middleware/builtin/guardrails.d.ts +0 -5
  16. package/dist/middleware/builtin/guardrails.js +33 -5
  17. package/dist/middleware/factory.js +1 -1
  18. package/dist/middleware/wrapLanguageModel.d.ts +18 -0
  19. package/dist/middleware/wrapLanguageModel.js +53 -0
  20. package/dist/neurolink.d.ts +7 -0
  21. package/dist/neurolink.js +61 -11
  22. package/dist/processors/media/AudioProcessor.js +46 -11
  23. package/dist/providers/amazonSagemaker.d.ts +17 -1
  24. package/dist/providers/amazonSagemaker.js +110 -0
  25. package/dist/providers/anthropic/client.d.ts +11 -0
  26. package/dist/providers/anthropic/client.js +148 -1
  27. package/dist/providers/catalog/index.generated.d.ts +1 -1
  28. package/dist/providers/catalog/index.generated.js +3 -0
  29. package/dist/providers/catalog/loader.js +1 -0
  30. package/dist/providers/catalog/mancer.json +192 -0
  31. package/dist/providers/configuredOpenAICompat.d.ts +11 -0
  32. package/dist/providers/configuredOpenAICompat.js +16 -0
  33. package/dist/providers/googleVertex/client.d.ts +0 -9
  34. package/dist/providers/googleVertex/client.js +0 -33
  35. package/dist/providers/openaiChatCompletionsBase.d.ts +21 -1
  36. package/dist/providers/openaiChatCompletionsBase.js +178 -0
  37. package/dist/providers/providerTypeUtils.d.ts +1 -2
  38. package/dist/providers/providerTypeUtils.js +5 -1
  39. package/dist/types/aiCompat.d.ts +485 -0
  40. package/dist/types/aiCompat.js +17 -0
  41. package/dist/types/conversation.d.ts +1 -1
  42. package/dist/types/generate.d.ts +52 -0
  43. package/dist/types/middleware.d.ts +3 -6
  44. package/dist/types/providerCatalog.generated.d.ts +2 -2
  45. package/dist/types/providers.d.ts +14 -1
  46. package/dist/types/tools.d.ts +25 -2
  47. package/dist/utils/errorHandling.d.ts +20 -3
  48. package/dist/utils/errorHandling.js +22 -5
  49. package/dist/utils/generationErrors.d.ts +78 -6
  50. package/dist/utils/generationErrors.js +114 -6
  51. package/dist/utils/mcpDefaults.d.ts +1 -1
  52. package/dist/utils/mcpDefaults.js +4 -1
  53. package/dist/utils/nativeSingleShot.d.ts +3 -0
  54. package/dist/utils/nativeSingleShot.js +83 -0
  55. package/dist/utils/tool.d.ts +30 -5
  56. package/dist/utils/tool.js +43 -5
  57. package/package.json +3 -6
  58. package/dist/utils/generation.d.ts +0 -8
  59. package/dist/utils/generation.js +0 -8
@@ -335,22 +335,57 @@ export class AudioProcessor extends BaseFileProcessor {
335
335
  return skipped(`format is not one Whisper accepts (extension "${ext ?? "none"}", mimetype "${mimetype ?? "none"}"); supported: ${AUDIO_CONFIG.WHISPER_SUPPORTED_FORMATS.join(", ")}`);
336
336
  }
337
337
  try {
338
- // Dynamic imports to avoid loading these modules when transcription is not needed
339
- const [{ createOpenAI }, { experimental_transcribe }] = await Promise.all([import("@ai-sdk/openai"), import("../../utils/generation.js")]);
340
- const openai = createOpenAI({ apiKey });
341
- const model = openai.transcription("whisper-1");
338
+ // Native multipart POST to OpenAI's transcription endpoint. This used to
339
+ // go through @ai-sdk/openai's createOpenAI().transcription() plus the ai
340
+ // package's experimental_transcribe; both were dropped, and this is the
341
+ // only wire behaviour of theirs the processor ever depended on. The same
342
+ // request is already made natively by voice/providers/OpenAISTT.ts.
343
+ // Only `text` is read off the response, as before.
344
+ const baseUrl = (process.env.OPENAI_BASE_URL ?? "https://api.openai.com/v1").replace(/\/+$/, "");
345
+ const form = new FormData();
346
+ form.append("file", new Blob([new Uint8Array(buffer)], {
347
+ type: mimetype || "audio/mpeg",
348
+ }), filename);
349
+ form.append("model", "whisper-1");
350
+ form.append("response_format", "verbose_json");
342
351
  // Wrap in withTimeout — large audio files can take a while, but a
343
352
  // stalled request shouldn't block the processor forever. A TimeoutError
344
353
  // lands in the same handler as other failures below, which reports it as
345
354
  // the reason rather than discarding it.
346
- const result = await withTimeout(experimental_transcribe({
347
- model,
348
- audio: buffer,
349
- }), AUDIO_CONFIG.TRANSCRIPTION_TIMEOUT_MS, "openai-whisper", "generate");
350
- if (result.text && result.text.trim().length > 0) {
351
- logger.debug(`[AudioProcessor] Transcribed ${filename} via openai-whisper (${result.text.trim().length} chars)`);
355
+ // `withTimeout` only races the promise against a timer — it cannot
356
+ // cancel the operation. This code owns the raw fetch now, so without an
357
+ // abort the socket and its in-flight upload (up to 25MB) stay alive
358
+ // after the timeout has already resolved the caller.
359
+ const transcriptionAbort = new AbortController();
360
+ const transcriptionTimer = setTimeout(() => transcriptionAbort.abort(), AUDIO_CONFIG.TRANSCRIPTION_TIMEOUT_MS);
361
+ let response;
362
+ try {
363
+ response = await withTimeout(fetch(`${baseUrl}/audio/transcriptions`, {
364
+ method: "POST",
365
+ headers: { Authorization: `Bearer ${apiKey}` },
366
+ body: form,
367
+ signal: transcriptionAbort.signal,
368
+ }), AUDIO_CONFIG.TRANSCRIPTION_TIMEOUT_MS, "openai-whisper", "generate");
369
+ }
370
+ finally {
371
+ clearTimeout(transcriptionTimer);
372
+ }
373
+ if (!response.ok) {
374
+ const detail = await response.text().catch(() => "");
375
+ // Mirrors the old behaviour: a non-2xx used to surface as a thrown
376
+ // APICallError caught by the handler below and reported as the reason.
377
+ return skipped(`transcription request failed — HTTP ${response.status}${detail ? `: ${detail.slice(0, 200)}` : ""}`);
378
+ }
379
+ const payload = await response.json();
380
+ const rawText = typeof payload === "object" &&
381
+ payload !== null &&
382
+ typeof payload.text === "string"
383
+ ? payload.text
384
+ : "";
385
+ if (rawText.trim().length > 0) {
386
+ logger.debug(`[AudioProcessor] Transcribed ${filename} via openai-whisper (${rawText.trim().length} chars)`);
352
387
  return {
353
- transcript: result.text.trim(),
388
+ transcript: rawText.trim(),
354
389
  hasTranscript: true,
355
390
  transcriptionProvider: "openai-whisper",
356
391
  transcriptionSkippedReason: undefined,
@@ -1,7 +1,7 @@
1
1
  import type { AIProviderName } from "../constants/enums.js";
2
2
  import { BaseProvider } from "../core/baseProvider.js";
3
3
  import type { NeuroLink } from "../neurolink.js";
4
- import type { StreamOptions } from "../types/index.js";
4
+ import type { EnhancedGenerateResult, TextGenerationOptions, ValidationSchema, StreamOptions } from "../types/index.js";
5
5
  import type { LanguageModel } from "../types/index.js";
6
6
  /**
7
7
  * Amazon SageMaker Provider extending BaseProvider
@@ -20,6 +20,22 @@ export declare class AmazonSageMakerProvider extends BaseProvider {
20
20
  protected getProviderName(): AIProviderName;
21
21
  protected getDefaultModel(): string;
22
22
  protected getAISDKModel(): LanguageModel;
23
+ /**
24
+ * Native non-streaming generate.
25
+ *
26
+ * SageMaker's doGenerate makes one invokeEndpoint call and already returns
27
+ * toolCalls; no streaming is involved, so the wire hazard that reverted the
28
+ * first migration does not apply here. This supplies only the multi-step
29
+ * iteration the ai package used to.
30
+ *
31
+ * NOT EXERCISED LIVE. This machine has no SageMaker endpoint or credentials.
32
+ * The single-step shape is identical by construction — with no tool calls the
33
+ * loop breaks after exactly one doGenerate carrying the same options the ai
34
+ * loop passed. The multi-step branch is the new code and wants a real
35
+ * endpoint before it is trusted.
36
+ */
37
+ generate(optionsOrPrompt: TextGenerationOptions | string, analysisSchema?: ValidationSchema): Promise<EnhancedGenerateResult | null>;
38
+ private executeNativeGenerate;
23
39
  /**
24
40
  * Streaming was previously an `executeStream` override that unconditionally
25
41
  * threw "not yet fully implemented" — while `SageMakerLanguageModel.doStream`
@@ -1,6 +1,13 @@
1
1
  import { BaseProvider } from "../core/baseProvider.js";
2
2
  import { createStreamChannel } from "../core/streamChannel.js";
3
3
  import { logger } from "../utils/logger.js";
4
+ import { resolveRequestKind } from "../core/resolveRequestKind.js";
5
+ import { resolveToolExecutionRecords } from "../core/toolExecutionRecorder.js";
6
+ import { transformToolExecutions } from "../utils/transformationUtils.js";
7
+ import { convertZodToJsonSchema } from "../utils/schemaConversion.js";
8
+ import { withProviderRetry } from "../utils/providerRetry.js";
9
+ import { DEFAULT_MAX_STEPS } from "../core/constants.js";
10
+ import { hasNativeDoGenerate, runNativeGenerateLoop, } from "../core/nativeGenerateLoop.js";
4
11
  import { withSpan } from "../telemetry/withSpan.js";
5
12
  import { tracers } from "../telemetry/tracers.js";
6
13
  // SageMaker-specific imports
@@ -93,6 +100,109 @@ export class AmazonSageMakerProvider extends BaseProvider {
93
100
  const smModel = this.sagemakerModel;
94
101
  return smModel;
95
102
  }
103
+ /**
104
+ * Native non-streaming generate.
105
+ *
106
+ * SageMaker's doGenerate makes one invokeEndpoint call and already returns
107
+ * toolCalls; no streaming is involved, so the wire hazard that reverted the
108
+ * first migration does not apply here. This supplies only the multi-step
109
+ * iteration the ai package used to.
110
+ *
111
+ * NOT EXERCISED LIVE. This machine has no SageMaker endpoint or credentials.
112
+ * The single-step shape is identical by construction — with no tool calls the
113
+ * loop breaks after exactly one doGenerate carrying the same options the ai
114
+ * loop passed. The multi-step branch is the new code and wants a real
115
+ * endpoint before it is trusted.
116
+ */
117
+ async generate(optionsOrPrompt, analysisSchema) {
118
+ await this.ensureModelLimits();
119
+ const options = this.normalizeTextOptions(optionsOrPrompt);
120
+ if (resolveRequestKind(options, this.modelName) !== "text") {
121
+ return super.generate(options, analysisSchema);
122
+ }
123
+ this.validateOptions(options);
124
+ const mergedTools = await this.getToolsForStream(options);
125
+ const callerOwnsFallback = "disableInternalFallback" in options &&
126
+ options.disableInternalFallback === true;
127
+ // The native loop bypasses BaseProvider.executeGeneration, so the turn
128
+ // budget has to be composed here or it stops existing for this provider.
129
+ return this.runGenerateWithModelFallback(() => this.withTurnTimeout({ ...options, tools: mergedTools }, this.getDescriptorGenerateMs(), (timedOptions) => this.executeNativeGenerate(timedOptions)), callerOwnsFallback);
130
+ }
131
+ async executeNativeGenerate(options) {
132
+ const startTime = Date.now();
133
+ // Middleware must wrap the model here. The native loop bypasses
134
+ // BaseProvider.executeGeneration, and with it the only place middleware was
135
+ // ever applied — a probe showed a caller's wrapGenerate running zero times
136
+ // on every native provider while their onFinish still fired, because
137
+ // onFinish had been special-cased and nothing else had.
138
+ const model = await this.getAISDKModelWithMiddleware(options);
139
+ if (!hasNativeDoGenerate(model)) {
140
+ throw this.handleProviderError(new Error("sagemaker: model handle exposes no doGenerate()"));
141
+ }
142
+ const doGenerate = model.doGenerate.bind(model);
143
+ const shouldUseTools = !options.disableTools && this.supportsTools();
144
+ const toolsRecord = shouldUseTools
145
+ ? options.tools || {}
146
+ : {};
147
+ const v3Tools = Object.entries(toolsRecord).map(([name, t]) => {
148
+ const tool = t;
149
+ return {
150
+ type: "function",
151
+ name,
152
+ description: tool.description ?? "",
153
+ inputSchema: (tool.inputSchema
154
+ ? convertZodToJsonSchema(tool.inputSchema)
155
+ : { type: "object", properties: {} }),
156
+ };
157
+ });
158
+ // Structured output was dropped entirely on this path: the schema never
159
+ // reached the request, and nothing downstream re-imposed it, so a caller
160
+ // asking for an object got whatever JSON coerceJsonToSchema could scrape
161
+ // out of prose.
162
+ const responseFormat = options.schema
163
+ ? {
164
+ type: "json",
165
+ schema: convertZodToJsonSchema(options.schema),
166
+ }
167
+ : undefined;
168
+ const conversation = (await this.buildMessagesForStream(options));
169
+ const toolExecutionSummaries = [];
170
+ const loop = await runNativeGenerateLoop({
171
+ doGenerate,
172
+ conversation,
173
+ ...(responseFormat ? { responseFormat } : {}),
174
+ ...(v3Tools.length > 0 ? { tools: v3Tools } : {}),
175
+ toolsRecord,
176
+ maxSteps: options.maxSteps || DEFAULT_MAX_STEPS,
177
+ ...(options.maxTokens ? { maxOutputTokens: options.maxTokens } : {}),
178
+ ...(options.temperature !== undefined
179
+ ? { temperature: options.temperature }
180
+ : {}),
181
+ ...(options.abortSignal ? { abortSignal: options.abortSignal } : {}),
182
+ ...(options.toolTimeoutMs !== undefined
183
+ ? { toolTimeoutMs: options.toolTimeoutMs }
184
+ : {}),
185
+ runStep: (call) => withProviderRetry(call, undefined, "sagemaker generate").catch((err) => {
186
+ throw this.handleProviderError(err);
187
+ }),
188
+ }, toolExecutionSummaries);
189
+ const enhanced = {
190
+ content: loop.text,
191
+ provider: this.providerName,
192
+ model: this.modelName,
193
+ finishReason: loop.finishReason,
194
+ usage: {
195
+ input: loop.inputTokens,
196
+ output: loop.outputTokens,
197
+ total: loop.inputTokens + loop.outputTokens,
198
+ },
199
+ responseTime: Date.now() - startTime,
200
+ toolsUsed: loop.toolsUsed,
201
+ toolExecutions: resolveToolExecutionRecords(options, transformToolExecutions(toolExecutionSummaries)),
202
+ enhancedWithTools: loop.toolsUsed.length > 0,
203
+ };
204
+ return this.finalizeNativeGenerate(enhanced, options, startTime);
205
+ }
96
206
  /**
97
207
  * Streaming was previously an `executeStream` override that unconditionally
98
208
  * threw "not yet fully implemented" — while `SageMakerLanguageModel.doStream`
@@ -133,6 +133,17 @@ export declare class AnthropicProvider extends BaseProvider {
133
133
  * BaseProvider so that expired tokens are renewed automatically.
134
134
  */
135
135
  generate(optionsOrPrompt: TextGenerationOptions | string, analysisSchema?: ValidationSchema): Promise<EnhancedGenerateResult | null>;
136
+ /**
137
+ * Text turns run natively; every other request kind still goes to
138
+ * BaseProvider.generate().
139
+ *
140
+ * The loop runs over this provider's own delegating-model `doGenerate`,
141
+ * which issues a NON-streaming `messages.create`. That matters: the streaming
142
+ * loop adapter hardcodes `stream: true`, and an earlier attempt that routed
143
+ * generate through it silently changed the wire.
144
+ */
145
+ private dispatchGenerate;
146
+ private executeNativeGenerate;
136
147
  /**
137
148
  * Fold a captured snapshot into the provider's usage bookkeeping and log it.
138
149
  *
@@ -25,6 +25,11 @@ import { calculateCost } from "../../utils/pricing.js";
25
25
  import { stringifyAnthropicToolOutput } from "./toolOutput.js";
26
26
  import { createAnthropicLoopAdapter } from "./loopAdapter.js";
27
27
  import { runAgenticLoop } from "../../core/loopEngine.js";
28
+ import { hasNativeDoGenerate, runNativeGenerateLoop, } from "../../core/nativeGenerateLoop.js";
29
+ import { withProviderRetry } from "../../utils/providerRetry.js";
30
+ import { resolveRequestKind } from "../../core/resolveRequestKind.js";
31
+ import { resolveToolExecutionRecords } from "../../core/toolExecutionRecorder.js";
32
+ import { transformToolExecutions } from "../../utils/transformationUtils.js";
28
33
  import { createAnthropicConfig, getProviderModel, validateApiKey, } from "../../utils/providerConfig.js";
29
34
  import { composeAbortSignals, createTimeoutController, mergeAbortSignals, TimeoutError, } from "../../utils/timeout.js";
30
35
  import { resolveToolChoice } from "../../utils/toolChoice.js";
@@ -1327,7 +1332,7 @@ export class AnthropicProvider extends BaseProvider {
1327
1332
  // rather than on the instance is what makes it concurrency-safe: several
1328
1333
  // generate() calls can be in flight on one provider instance, and an
1329
1334
  // instance field would attribute one call's limits to another.
1330
- const { result, snapshot } = await withLimitCapture(() => super.generate(optionsOrPrompt, analysisSchema));
1335
+ const { result, snapshot } = await withLimitCapture(() => this.dispatchGenerate(optionsOrPrompt, analysisSchema));
1331
1336
  if (result && snapshot) {
1332
1337
  this.recordLimitSnapshot(snapshot);
1333
1338
  result.limits = snapshot;
@@ -1337,6 +1342,148 @@ export class AnthropicProvider extends BaseProvider {
1337
1342
  }
1338
1343
  return result;
1339
1344
  }
1345
+ /**
1346
+ * Text turns run natively; every other request kind still goes to
1347
+ * BaseProvider.generate().
1348
+ *
1349
+ * The loop runs over this provider's own delegating-model `doGenerate`,
1350
+ * which issues a NON-streaming `messages.create`. That matters: the streaming
1351
+ * loop adapter hardcodes `stream: true`, and an earlier attempt that routed
1352
+ * generate through it silently changed the wire.
1353
+ */
1354
+ async dispatchGenerate(optionsOrPrompt, analysisSchema) {
1355
+ await this.ensureModelLimits();
1356
+ const options = this.normalizeTextOptions(optionsOrPrompt);
1357
+ if (resolveRequestKind(options, this.modelName) !== "text") {
1358
+ return super.generate(options, analysisSchema);
1359
+ }
1360
+ this.validateOptions(options);
1361
+ const mergedTools = await this.getToolsForStream(options);
1362
+ const callerOwnsFallback = "disableInternalFallback" in options &&
1363
+ options.disableInternalFallback === true;
1364
+ // The native loop bypasses BaseProvider.executeGeneration, so the turn
1365
+ // budget has to be composed here or it stops existing for this provider.
1366
+ return this.runGenerateWithModelFallback(() => this.withTurnTimeout({ ...options, tools: mergedTools }, this.getDescriptorGenerateMs(), (timedOptions) => this.executeNativeGenerate(timedOptions)), callerOwnsFallback);
1367
+ }
1368
+ async executeNativeGenerate(options) {
1369
+ const startTime = Date.now();
1370
+ const modelId = this.modelName || getDefaultAnthropicModel();
1371
+ // Middleware must wrap the model here. The native loop bypasses
1372
+ // BaseProvider.executeGeneration, and with it the only place middleware was
1373
+ // ever applied — a probe showed a caller's wrapGenerate running zero times
1374
+ // on every native provider while their onFinish still fired, because
1375
+ // onFinish had been special-cased and nothing else had.
1376
+ const model = await this.getAISDKModelWithMiddleware(options);
1377
+ if (!hasNativeDoGenerate(model)) {
1378
+ throw this.handleProviderError(new Error("anthropic: model handle exposes no doGenerate()"));
1379
+ }
1380
+ const doGenerate = model.doGenerate.bind(model);
1381
+ const shouldUseTools = !options.disableTools && this.supportsTools();
1382
+ const toolsRecord = shouldUseTools
1383
+ ? options.tools || {}
1384
+ : {};
1385
+ const v3Tools = Object.entries(toolsRecord).map(([name, t]) => {
1386
+ const tool = t;
1387
+ return {
1388
+ type: "function",
1389
+ name,
1390
+ description: tool.description ?? "",
1391
+ inputSchema: (tool.inputSchema
1392
+ ? convertZodToJsonSchema(tool.inputSchema)
1393
+ : { type: "object", properties: {} }),
1394
+ };
1395
+ });
1396
+ const hasTools = v3Tools.length > 0;
1397
+ // Two structured-output routes, and doGenerate implements both. With no
1398
+ // tools it replaces the tool list with one forced json tool; with tools it
1399
+ // APPENDS final_result so the real tools stay callable. Picking the wrong
1400
+ // one is what dropped structuredData to null on the first attempt.
1401
+ const schemaJson = options.schema
1402
+ ? convertZodToJsonSchema(options.schema)
1403
+ : undefined;
1404
+ const responseFormat = schemaJson && !hasTools
1405
+ ? { type: "json", schema: schemaJson }
1406
+ : undefined;
1407
+ const anthropicNamespace = {};
1408
+ if (schemaJson && hasTools) {
1409
+ anthropicNamespace.finalResultSchema = schemaJson;
1410
+ }
1411
+ if (options.thinkingConfig?.enabled &&
1412
+ options.thinkingConfig.budgetTokens) {
1413
+ anthropicNamespace.thinking = {
1414
+ type: "enabled",
1415
+ budget_tokens: options.thinkingConfig.budgetTokens,
1416
+ };
1417
+ }
1418
+ // The per-call `timeout` keeps its per-MODEL-CALL meaning once
1419
+ // `turnTimeoutMs` owns the whole-turn deadline, and it reaches the model
1420
+ // layer only through providerOptions.neurolink. Without it each step fell
1421
+ // back to the provider default.
1422
+ const mergedProviderOptions = {};
1423
+ if (Object.keys(anthropicNamespace).length > 0) {
1424
+ mergedProviderOptions.anthropic = anthropicNamespace;
1425
+ }
1426
+ if (typeof options.timeout === "number") {
1427
+ mergedProviderOptions.neurolink = { timeoutMs: options.timeout };
1428
+ }
1429
+ const providerOptions = Object.keys(mergedProviderOptions).length > 0
1430
+ ? mergedProviderOptions
1431
+ : undefined;
1432
+ const conversation = (await this.buildMessagesForStream(options));
1433
+ const toolExecutionSummaries = [];
1434
+ const loop = await runNativeGenerateLoop({
1435
+ doGenerate,
1436
+ conversation,
1437
+ ...(hasTools ? { tools: v3Tools } : {}),
1438
+ toolsRecord,
1439
+ // A caller's toolChoice was dropped here while the streaming path and
1440
+ // the OpenAI-compatible native path both forwarded it, so
1441
+ // `toolChoice: "required"` and named-tool choices silently degraded to
1442
+ // Anthropic's default `auto` on generate().
1443
+ ...(hasTools && options.toolChoice
1444
+ ? { toolChoice: resolveToolChoice(options, toolsRecord, true) }
1445
+ : {}),
1446
+ ...(responseFormat ? { responseFormat } : {}),
1447
+ ...(providerOptions ? { providerOptions } : {}),
1448
+ maxSteps: options.maxSteps || DEFAULT_MAX_STEPS,
1449
+ ...(options.maxTokens ? { maxOutputTokens: options.maxTokens } : {}),
1450
+ ...(options.temperature !== undefined
1451
+ ? { temperature: options.temperature }
1452
+ : {}),
1453
+ ...(options.abortSignal ? { abortSignal: options.abortSignal } : {}),
1454
+ ...(options.toolTimeoutMs !== undefined
1455
+ ? { toolTimeoutMs: options.toolTimeoutMs }
1456
+ : {}),
1457
+ runStep: (call) => withProviderRetry(call, trace.getActiveSpan() ?? undefined, "anthropic generate").catch((err) => {
1458
+ throw this.handleProviderError(err);
1459
+ }),
1460
+ }, toolExecutionSummaries);
1461
+ const enhanced = {
1462
+ content: loop.text,
1463
+ provider: this.providerName,
1464
+ model: modelId,
1465
+ finishReason: loop.finishReason,
1466
+ ...(loop.rawFinishReason
1467
+ ? { rawFinishReason: loop.rawFinishReason }
1468
+ : {}),
1469
+ usage: {
1470
+ input: loop.inputTokens,
1471
+ output: loop.outputTokens,
1472
+ total: loop.inputTokens + loop.outputTokens,
1473
+ ...(loop.cacheReadTokens
1474
+ ? { cacheReadTokens: loop.cacheReadTokens }
1475
+ : {}),
1476
+ ...(loop.cacheWriteTokens
1477
+ ? { cacheCreationTokens: loop.cacheWriteTokens }
1478
+ : {}),
1479
+ },
1480
+ responseTime: Date.now() - startTime,
1481
+ toolsUsed: loop.toolsUsed,
1482
+ toolExecutions: resolveToolExecutionRecords(options, transformToolExecutions(toolExecutionSummaries)),
1483
+ enhancedWithTools: loop.toolsUsed.length > 0,
1484
+ };
1485
+ return this.finalizeNativeGenerate(enhanced, options, startTime);
1486
+ }
1340
1487
  /**
1341
1488
  * Fold a captured snapshot into the provider's usage bookkeeping and log it.
1342
1489
  *
@@ -1,3 +1,3 @@
1
1
  import type { ProviderCatalogJson } from "../../types/index.js";
2
2
  export declare const CATALOG_JSON_ENTRIES: ProviderCatalogJson[];
3
- export declare const CATALOG_PROVIDER_IDS: readonly ["baseten", "cerebras", "cloudflare", "fireworks", "gmicloud", "groq", "inception-labs", "io-intelligence", "mistral", "perplexity", "sambanova", "together-ai", "upstage", "xai"];
3
+ export declare const CATALOG_PROVIDER_IDS: readonly ["baseten", "cerebras", "cloudflare", "fireworks", "gmicloud", "groq", "inception-labs", "io-intelligence", "mancer", "mistral", "perplexity", "sambanova", "together-ai", "upstage", "xai"];
@@ -8,6 +8,7 @@ import gmicloudJson from "./gmicloud.json" with { type: "json" };
8
8
  import groqJson from "./groq.json" with { type: "json" };
9
9
  import inceptionLabsJson from "./inception-labs.json" with { type: "json" };
10
10
  import ioIntelligenceJson from "./io-intelligence.json" with { type: "json" };
11
+ import mancerJson from "./mancer.json" with { type: "json" };
11
12
  import mistralJson from "./mistral.json" with { type: "json" };
12
13
  import perplexityJson from "./perplexity.json" with { type: "json" };
13
14
  import sambanovaJson from "./sambanova.json" with { type: "json" };
@@ -23,6 +24,7 @@ export const CATALOG_JSON_ENTRIES = [
23
24
  groqJson,
24
25
  inceptionLabsJson,
25
26
  ioIntelligenceJson,
27
+ mancerJson,
26
28
  mistralJson,
27
29
  perplexityJson,
28
30
  sambanovaJson,
@@ -39,6 +41,7 @@ export const CATALOG_PROVIDER_IDS = [
39
41
  "groq",
40
42
  "inception-labs",
41
43
  "io-intelligence",
44
+ "mancer",
42
45
  "mistral",
43
46
  "perplexity",
44
47
  "sambanova",
@@ -89,6 +89,7 @@ export function buildCatalogEntries() {
89
89
  entry.models.fallbacks[0],
90
90
  fallbackModels: [...entry.models.fallbacks],
91
91
  errorRules: buildErrorRules(entry),
92
+ supportsTools: entry.capabilities.tools,
92
93
  };
93
94
  const { baseURLTemplate } = entry.wire;
94
95
  if (baseURLTemplate) {
@@ -0,0 +1,192 @@
1
+ {
2
+ "$schema": "./provider-catalog.schema.json",
3
+ "id": "mancer",
4
+ "displayName": "Mancer",
5
+ "aliases": ["mancer-tech"],
6
+ "tier": 2,
7
+ "wire": {
8
+ "baseURL": "https://neuro.mancer.tech/oai/v1"
9
+ },
10
+ "models": {
11
+ "default": "deepseek-v4-flash",
12
+ "fallbacks": ["deepseek-v4-flash", "gpt-oss-120b"],
13
+ "defaultContextWindow": 1048576,
14
+ "defaultMaxOutputTokens": 1048576,
15
+ "catalog": {
16
+ "mythomax": {
17
+ "contextWindow": 8192,
18
+ "maxOutputTokens": 8192,
19
+ "pricingPerMTok": {
20
+ "input": 0.14,
21
+ "output": 0.24
22
+ },
23
+ "vision": false,
24
+ "status": "production",
25
+ "description": "MythoMax (LLaMA 2, Simplified Alpaca format) — pricing and limits from the authenticated /oai/v1/models roster, 2026-09-03"
26
+ },
27
+ "deepseek-v4-flash": {
28
+ "contextWindow": 1048576,
29
+ "maxOutputTokens": 1048576,
30
+ "pricingPerMTok": {
31
+ "input": 0.07,
32
+ "output": 0.2
33
+ },
34
+ "vision": false,
35
+ "status": "production",
36
+ "description": "DeepSeek V4 Flash — Mancer's flagship general model; paid credits required — pricing and limits from the authenticated /oai/v1/models roster, 2026-09-03"
37
+ },
38
+ "deepseek-v4-flash-0731": {
39
+ "contextWindow": 1048576,
40
+ "maxOutputTokens": 1048576,
41
+ "pricingPerMTok": {
42
+ "input": 0.07,
43
+ "output": 0.2
44
+ },
45
+ "vision": false,
46
+ "status": "production",
47
+ "description": "DeepSeek V4 Flash, 2026-07-31 snapshot; paid credits required — pricing and limits from the authenticated /oai/v1/models roster, 2026-09-03"
48
+ },
49
+ "mytholite": {
50
+ "contextWindow": 2560,
51
+ "maxOutputTokens": 150,
52
+ "pricingPerMTok": {
53
+ "input": 0,
54
+ "output": 0
55
+ },
56
+ "vision": false,
57
+ "status": "production",
58
+ "description": "MythoLite — Mancer's free demo model (2,560-token context, 150-token completions, Simplified Alpaca format); the only model usable with a zero balance — pricing and limits from the authenticated /oai/v1/models roster, 2026-09-03"
59
+ },
60
+ "remm-slerp": {
61
+ "contextWindow": 6144,
62
+ "maxOutputTokens": 6144,
63
+ "pricingPerMTok": {
64
+ "input": 0.14,
65
+ "output": 0.26
66
+ },
67
+ "vision": false,
68
+ "status": "production",
69
+ "description": "ReMM-SLERP (Simplified Alpaca format) — pricing and limits from the authenticated /oai/v1/models roster, 2026-09-03"
70
+ },
71
+ "magnum-72b-v4": {
72
+ "contextWindow": 32768,
73
+ "maxOutputTokens": 4096,
74
+ "pricingPerMTok": {
75
+ "input": 1,
76
+ "output": 2
77
+ },
78
+ "vision": false,
79
+ "status": "production",
80
+ "description": "Magnum 72B v4 (ChatML format) — pricing and limits from the authenticated /oai/v1/models roster, 2026-09-03"
81
+ },
82
+ "glm-4.7": {
83
+ "contextWindow": 131072,
84
+ "maxOutputTokens": 131072,
85
+ "pricingPerMTok": {
86
+ "input": 0.28,
87
+ "output": 1
88
+ },
89
+ "vision": false,
90
+ "status": "production",
91
+ "description": "GLM-4.7 — pricing and limits from the authenticated /oai/v1/models roster, 2026-09-03"
92
+ },
93
+ "gpt-oss-120b": {
94
+ "contextWindow": 131072,
95
+ "maxOutputTokens": 131072,
96
+ "pricingPerMTok": {
97
+ "input": 0.022,
98
+ "output": 0.2
99
+ },
100
+ "vision": false,
101
+ "status": "production",
102
+ "description": "GPT-OSS 120B — pricing and limits from the authenticated /oai/v1/models roster, 2026-09-03"
103
+ },
104
+ "weaver-alpha": {
105
+ "contextWindow": 8000,
106
+ "maxOutputTokens": 6000,
107
+ "pricingPerMTok": {
108
+ "input": 0.16,
109
+ "output": 0.3
110
+ },
111
+ "vision": false,
112
+ "status": "production",
113
+ "description": "Weaver Alpha (Simplified Alpaca format) — pricing and limits from the authenticated /oai/v1/models roster, 2026-09-03"
114
+ },
115
+ "dans-pe-1.3-24b": {
116
+ "contextWindow": 32768,
117
+ "maxOutputTokens": 8192,
118
+ "pricingPerMTok": {
119
+ "input": 0.2,
120
+ "output": 0.8
121
+ },
122
+ "vision": false,
123
+ "status": "production",
124
+ "description": "Dan's PersonalityEngine 1.3 24B — pricing and limits from the authenticated /oai/v1/models roster, 2026-09-03"
125
+ }
126
+ },
127
+ "topModels": ["deepseek-v4-flash", "gpt-oss-120b", "glm-4.7", "mytholite"],
128
+ "testModel": "mytholite"
129
+ },
130
+ "capabilities": {
131
+ "text": true,
132
+ "streaming": true,
133
+ "tools": false,
134
+ "toolsWithStreaming": false,
135
+ "structuredOutput": true,
136
+ "structuredOutputWithTools": false,
137
+ "embeddings": false,
138
+ "thinking": false
139
+ },
140
+ "errorRules": [
141
+ {
142
+ "status": 401,
143
+ "pattern": "Bad API key",
144
+ "class": "authentication",
145
+ "message": "Invalid Mancer API key. Check {apiKeyEnvVar}. Get one at https://mancer.tech/dashboard"
146
+ },
147
+ {
148
+ "pattern": "Unknown Model",
149
+ "class": "invalid-model",
150
+ "message": "Mancer model '{model}' is unknown. Pick a current model from the /oai/v1/models roster or https://mancer.tech/pricing."
151
+ },
152
+ {
153
+ "status": 402,
154
+ "pattern": "requires paid credits",
155
+ "class": "provider",
156
+ "message": "Mancer model '{model}' requires paid credits; with a zero balance only the free model 'mytholite' works. Add credits at https://mancer.tech/dashboard or see https://mancer.tech/pricing."
157
+ }
158
+ ],
159
+ "setup": {
160
+ "url": "https://mancer.tech/dashboard",
161
+ "apiKeyFormat": "^mcr_[A-Za-z0-9]+$",
162
+ "billingPolicy": "free-tier",
163
+ "instructions": [
164
+ "1. Visit: https://mancer.tech/dashboard and sign in",
165
+ "2. Create an API key (prefix mcr_)",
166
+ "3. Without credits only the free model 'mytholite' answers; every other model returns 402 until you add credits at https://mancer.tech/pricing",
167
+ "4. Set {apiKeyEnvVar} in your .env file"
168
+ ],
169
+ "description": "OpenAI-compatible endpoint at https://neuro.mancer.tech/oai/v1. One free demo model; paid credits unlock the rest of the roster."
170
+ },
171
+ "evidence": {
172
+ "rosterVerified": {
173
+ "date": "2026-09-03",
174
+ "method": "authenticated GET /oai/v1/models; full response retained as evidence/mancer-roster-authenticated.json in the campaign scratchpad and every catalog price/limit machine-checked against it (Mancer re-prices — gpt-oss-120b input moved 0.024 → 0.022 within the day)",
175
+ "status": 200
176
+ },
177
+ "authProbe": {
178
+ "date": "2026-09-03",
179
+ "status": 401
180
+ },
181
+ "billingProbe": {
182
+ "date": "2026-09-03",
183
+ "status": 402,
184
+ "method": "POST /oai/v1/chat/completions on deepseek-v4-flash with a zero balance: {\"error\":{\"type\":\"CANT_AFFORD\",\"message\":\"This model requires paid credits to use!\"}}"
185
+ },
186
+ "liveMatrix": {
187
+ "date": "2026-09-03",
188
+ "result": "18-probe harness on the free model mytholite: roster, chat, max_completion_tokens, system role, content parts, sampling params, SSE stream (usage chunk + [DONE]), json_schema (valid JSON matching schema) and json_object all 200; tools and tools+schema 400 BAD_PARAMETERS ('\"auto\" tool choice requires --enable-auto-tool-choice and --tool-call-parser') so tools are declared false; vision 200 but text-only (image ignored); bad key 401 'Bad API key'; unknown model 400 'Unknown Model'. Paid models could not be capability-probed without credits — tools may work there and can be re-declared once probed. Nightly matrix pins testModel mytholite."
189
+ },
190
+ "addedInPR": "pending"
191
+ }
192
+ }