@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.
- package/CHANGELOG.md +3 -3
- package/dist/browser/neurolink.min.js +533 -581
- package/dist/constants/enums.d.ts +13 -0
- package/dist/constants/enums.js +14 -0
- package/dist/core/baseProvider.d.ts +71 -3
- package/dist/core/baseProvider.js +152 -44
- package/dist/core/modules/GenerationHandler.d.ts +22 -24
- package/dist/core/modules/GenerationHandler.js +28 -463
- package/dist/core/nativeGenerateLoop.d.ts +35 -0
- package/dist/core/nativeGenerateLoop.js +261 -0
- package/dist/files/fileTools.d.ts +5 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.js +4 -0
- package/dist/mcp/toolRegistry.js +7 -0
- package/dist/middleware/builtin/guardrails.d.ts +0 -5
- package/dist/middleware/builtin/guardrails.js +33 -5
- package/dist/middleware/factory.js +1 -1
- package/dist/middleware/wrapLanguageModel.d.ts +18 -0
- package/dist/middleware/wrapLanguageModel.js +53 -0
- package/dist/neurolink.d.ts +7 -0
- package/dist/neurolink.js +61 -11
- package/dist/processors/media/AudioProcessor.js +46 -11
- package/dist/providers/amazonSagemaker.d.ts +17 -1
- package/dist/providers/amazonSagemaker.js +110 -0
- package/dist/providers/anthropic/client.d.ts +11 -0
- package/dist/providers/anthropic/client.js +148 -1
- package/dist/providers/catalog/index.generated.d.ts +1 -1
- package/dist/providers/catalog/index.generated.js +3 -0
- package/dist/providers/catalog/loader.js +1 -0
- package/dist/providers/catalog/mancer.json +192 -0
- package/dist/providers/configuredOpenAICompat.d.ts +11 -0
- package/dist/providers/configuredOpenAICompat.js +16 -0
- package/dist/providers/googleVertex/client.d.ts +0 -9
- package/dist/providers/googleVertex/client.js +0 -33
- package/dist/providers/openaiChatCompletionsBase.d.ts +21 -1
- package/dist/providers/openaiChatCompletionsBase.js +178 -0
- package/dist/providers/providerTypeUtils.d.ts +1 -2
- package/dist/providers/providerTypeUtils.js +5 -1
- package/dist/types/aiCompat.d.ts +485 -0
- package/dist/types/aiCompat.js +17 -0
- package/dist/types/conversation.d.ts +1 -1
- package/dist/types/generate.d.ts +52 -0
- package/dist/types/middleware.d.ts +3 -6
- package/dist/types/providerCatalog.generated.d.ts +2 -2
- package/dist/types/providers.d.ts +14 -1
- package/dist/types/tools.d.ts +25 -2
- package/dist/utils/errorHandling.d.ts +20 -3
- package/dist/utils/errorHandling.js +22 -5
- package/dist/utils/generationErrors.d.ts +78 -6
- package/dist/utils/generationErrors.js +114 -6
- package/dist/utils/mcpDefaults.d.ts +1 -1
- package/dist/utils/mcpDefaults.js +4 -1
- package/dist/utils/nativeSingleShot.d.ts +3 -0
- package/dist/utils/nativeSingleShot.js +83 -0
- package/dist/utils/tool.d.ts +30 -5
- package/dist/utils/tool.js +43 -5
- package/package.json +3 -6
- package/dist/utils/generation.d.ts +0 -8
- 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
|
-
//
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
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
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
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:
|
|
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(() =>
|
|
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
|
+
}
|