genai-lite 0.14.0 → 0.15.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/README.md +22 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +9 -1
- package/dist/llm/LLMService.d.ts +35 -3
- package/dist/llm/LLMService.js +673 -184
- package/dist/llm/clients/AnthropicClientAdapter.d.ts +11 -3
- package/dist/llm/clients/AnthropicClientAdapter.js +283 -56
- package/dist/llm/clients/GeminiClientAdapter.d.ts +9 -3
- package/dist/llm/clients/GeminiClientAdapter.js +215 -41
- package/dist/llm/clients/LlamaCppClientAdapter.d.ts +30 -7
- package/dist/llm/clients/LlamaCppClientAdapter.js +398 -90
- package/dist/llm/clients/LlamaCppServerClient.d.ts +31 -5
- package/dist/llm/clients/LlamaCppServerClient.js +50 -4
- package/dist/llm/clients/MistralClientAdapter.d.ts +10 -3
- package/dist/llm/clients/MistralClientAdapter.js +250 -47
- package/dist/llm/clients/MockClientAdapter.d.ts +6 -3
- package/dist/llm/clients/MockClientAdapter.js +135 -11
- package/dist/llm/clients/OpenAIClientAdapter.d.ts +9 -3
- package/dist/llm/clients/OpenAIClientAdapter.js +189 -38
- package/dist/llm/clients/OpenRouterClientAdapter.d.ts +9 -3
- package/dist/llm/clients/OpenRouterClientAdapter.js +190 -38
- package/dist/llm/clients/llamaCppState.d.ts +17 -0
- package/dist/llm/clients/llamaCppState.js +83 -0
- package/dist/llm/clients/preparedAdapterUtils.d.ts +31 -0
- package/dist/llm/clients/preparedAdapterUtils.js +195 -0
- package/dist/llm/clients/types.d.ts +79 -2
- package/dist/llm/clients/types.js +8 -0
- package/dist/llm/config.js +7 -1
- package/dist/llm/services/ModelResolver.d.ts +4 -2
- package/dist/llm/services/ModelResolver.js +29 -12
- package/dist/llm/services/RequestValidator.js +29 -0
- package/dist/llm/services/SettingsManager.js +22 -0
- package/dist/llm/tokenization/bounds.d.ts +11 -0
- package/dist/llm/tokenization/bounds.js +132 -0
- package/dist/llm/tokenization/index.d.ts +4 -0
- package/dist/llm/tokenization/index.js +13 -0
- package/dist/llm/tokenization/profiles.d.ts +8 -0
- package/dist/llm/tokenization/profiles.js +174 -0
- package/dist/llm/tokenization/types.d.ts +46 -0
- package/dist/llm/tokenization/types.js +4 -0
- package/dist/llm/types.d.ts +229 -3
- package/dist/prompting/content.js +11 -0
- package/dist/prompting/index.d.ts +6 -4
- package/dist/prompting/index.js +9 -1
- package/dist/shared/adapters/usageUtils.d.ts +21 -0
- package/dist/shared/adapters/usageUtils.js +103 -0
- package/package.json +5 -4
package/dist/llm/LLMService.js
CHANGED
|
@@ -6,6 +6,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
6
6
|
};
|
|
7
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
8
|
exports.LLMService = void 0;
|
|
9
|
+
const node_crypto_1 = require("node:crypto");
|
|
9
10
|
const defaultLogger_1 = require("../logging/defaultLogger");
|
|
10
11
|
const config_1 = require("./config");
|
|
11
12
|
const template_1 = require("../prompting/template");
|
|
@@ -20,6 +21,8 @@ const SettingsManager_1 = require("./services/SettingsManager");
|
|
|
20
21
|
const ModelResolver_1 = require("./services/ModelResolver");
|
|
21
22
|
const withRetry_1 = require("../shared/services/withRetry");
|
|
22
23
|
const types_1 = require("./clients/types");
|
|
24
|
+
const preparedAdapterUtils_1 = require("./clients/preparedAdapterUtils");
|
|
25
|
+
const tokenization_1 = require("./tokenization");
|
|
23
26
|
/**
|
|
24
27
|
* Main process service for LLM operations
|
|
25
28
|
*
|
|
@@ -33,6 +36,7 @@ const types_1 = require("./clients/types");
|
|
|
33
36
|
*/
|
|
34
37
|
class LLMService {
|
|
35
38
|
constructor(getApiKey, options = {}) {
|
|
39
|
+
this.preparedCalls = new WeakMap();
|
|
36
40
|
this.getApiKey = getApiKey;
|
|
37
41
|
this.retryOptions = options.retry;
|
|
38
42
|
this.defaultTimeoutMs = options.timeoutMs;
|
|
@@ -93,12 +97,14 @@ class LLMService {
|
|
|
93
97
|
const resolvedModelId = resolved.modelId;
|
|
94
98
|
const modelInfo = resolved.modelInfo;
|
|
95
99
|
const source = this.getCapabilitySource(resolvedProviderId, resolvedModelId);
|
|
100
|
+
const capabilities = this.buildModelCapabilities(modelInfo, source);
|
|
96
101
|
return {
|
|
97
102
|
object: "model.capabilities",
|
|
98
103
|
provider: resolvedProviderId,
|
|
99
104
|
model: resolvedModelId,
|
|
100
105
|
modelInfo: { ...modelInfo },
|
|
101
|
-
structuredOutput:
|
|
106
|
+
structuredOutput: capabilities.structuredOutput,
|
|
107
|
+
capabilities,
|
|
102
108
|
};
|
|
103
109
|
}
|
|
104
110
|
/**
|
|
@@ -171,81 +177,119 @@ class LLMService {
|
|
|
171
177
|
*/
|
|
172
178
|
async sendMessage(request, callOptions) {
|
|
173
179
|
this.logger.info(`LLMService.sendMessage called with presetId: ${request.presetId}, provider: ${request.providerId}, model: ${request.modelId}`);
|
|
180
|
+
const canonical = await this.prepareMessage(request, { mode: "complete" });
|
|
181
|
+
if (this.isFailureResponse(canonical)) {
|
|
182
|
+
return canonical;
|
|
183
|
+
}
|
|
184
|
+
return this.sendPrepared(canonical, callOptions);
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Resolves and freezes the final semantic provider request without retrieving
|
|
188
|
+
* credentials or creating transport state.
|
|
189
|
+
*/
|
|
190
|
+
async prepareMessage(request, options) {
|
|
191
|
+
if (!options ||
|
|
192
|
+
(options.mode !== "complete" && options.mode !== "stream")) {
|
|
193
|
+
return this.createPreparedFailure(request.providerId ?? "unknown", request.modelId ?? "unknown", types_1.ADAPTER_ERROR_CODES.INVALID_PREPARED_CALL, "Prepared-call mode must be either 'complete' or 'stream'.", "validation_error");
|
|
194
|
+
}
|
|
195
|
+
const result = await this.prepareCanonicalRequest(request, options.mode);
|
|
196
|
+
if ("error" in result) {
|
|
197
|
+
return result.error;
|
|
198
|
+
}
|
|
199
|
+
const handle = Object.create(null);
|
|
200
|
+
Object.defineProperties(handle, {
|
|
201
|
+
mode: {
|
|
202
|
+
value: options.mode,
|
|
203
|
+
enumerable: true,
|
|
204
|
+
configurable: false,
|
|
205
|
+
writable: false,
|
|
206
|
+
},
|
|
207
|
+
toJSON: {
|
|
208
|
+
value: () => {
|
|
209
|
+
throw new TypeError("Prepared LLM calls cannot be serialized.");
|
|
210
|
+
},
|
|
211
|
+
enumerable: false,
|
|
212
|
+
configurable: false,
|
|
213
|
+
writable: false,
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
Object.freeze(handle);
|
|
217
|
+
this.preparedCalls.set(handle, result.prepared);
|
|
218
|
+
return handle;
|
|
219
|
+
}
|
|
220
|
+
/** Returns the immutable inspection view for a service-owned prepared call. */
|
|
221
|
+
async inspectPrepared(handle) {
|
|
222
|
+
const prepared = this.preparedCalls.get(handle);
|
|
223
|
+
if (!prepared) {
|
|
224
|
+
return this.createPreparedHandleError(handle, types_1.ADAPTER_ERROR_CODES.INVALID_PREPARED_CALL, "The prepared call is invalid, forged, or belongs to another LLMService instance.");
|
|
225
|
+
}
|
|
226
|
+
return (0, preparedAdapterUtils_1.deepFreeze)({
|
|
227
|
+
provider: prepared.providerId,
|
|
228
|
+
model: prepared.modelId,
|
|
229
|
+
mode: prepared.adapterPrepared.mode,
|
|
230
|
+
request: structuredClone(prepared.adapterPrepared.requestView),
|
|
231
|
+
promptAccounting: structuredClone(prepared.adapterPrepared.promptAccounting),
|
|
232
|
+
...(prepared.adapterPrepared.outputTokenLimit && {
|
|
233
|
+
outputTokenLimit: structuredClone(prepared.adapterPrepared.outputTokenLimit),
|
|
234
|
+
}),
|
|
235
|
+
bindings: structuredClone(prepared.adapterPrepared.bindings),
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
/** Dispatches a reusable complete-mode prepared call. */
|
|
239
|
+
async sendPrepared(handle, callOptions) {
|
|
240
|
+
const resolved = this.resolvePreparedHandle(handle, "complete");
|
|
241
|
+
if ("error" in resolved) {
|
|
242
|
+
return resolved.error;
|
|
243
|
+
}
|
|
244
|
+
const prepared = resolved.prepared;
|
|
245
|
+
const adapter = prepared.clientAdapter;
|
|
246
|
+
if (!adapter.sendPrepared) {
|
|
247
|
+
return this.createPreparedFailure(prepared.providerId, prepared.modelId, types_1.ADAPTER_ERROR_CODES.PREPARED_CALL_UNSUPPORTED, `Prepared calls are not supported for provider '${prepared.providerId}'.`, "unsupported_feature");
|
|
248
|
+
}
|
|
249
|
+
const apiKey = await this.resolveDispatchApiKey(prepared);
|
|
250
|
+
if (this.isFailureResponse(apiKey)) {
|
|
251
|
+
return apiKey;
|
|
252
|
+
}
|
|
253
|
+
const adapterOptions = this.createAdapterOptions(callOptions);
|
|
254
|
+
const retryOnTimeout = this.retryOptions?.retryOnTimeout ?? true;
|
|
255
|
+
const retryableCodes = new Set([
|
|
256
|
+
types_1.ADAPTER_ERROR_CODES.RATE_LIMIT_EXCEEDED,
|
|
257
|
+
types_1.ADAPTER_ERROR_CODES.NETWORK_ERROR,
|
|
258
|
+
...(retryOnTimeout ? [types_1.ADAPTER_ERROR_CODES.REQUEST_TIMEOUT] : []),
|
|
259
|
+
]);
|
|
174
260
|
try {
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
const prepared = preparedResult.prepared;
|
|
180
|
-
try {
|
|
181
|
-
this.logger.info(`Making LLM request with ${prepared.clientAdapter.constructor.name} for provider: ${prepared.providerId}`);
|
|
182
|
-
// Unified retry layer: adapters never throw, so retry decisions are made on
|
|
183
|
-
// RETURNED failure responses. SDK-internal retries are disabled (maxRetries: 0
|
|
184
|
-
// at client construction) — this loop is the single owner of retry behavior.
|
|
185
|
-
const retryOnTimeout = this.retryOptions?.retryOnTimeout ?? true;
|
|
186
|
-
const retryableCodes = new Set([
|
|
187
|
-
types_1.ADAPTER_ERROR_CODES.RATE_LIMIT_EXCEEDED,
|
|
188
|
-
types_1.ADAPTER_ERROR_CODES.NETWORK_ERROR,
|
|
189
|
-
...(retryOnTimeout ? [types_1.ADAPTER_ERROR_CODES.REQUEST_TIMEOUT] : []),
|
|
190
|
-
]);
|
|
191
|
-
const result = await (0, withRetry_1.withRetry)(() => prepared.clientAdapter.sendMessage(prepared.internalRequest, prepared.apiKey, prepared.adapterOptions), (res) => {
|
|
192
|
-
if (res.object !== 'error') {
|
|
193
|
-
return { retry: false };
|
|
194
|
-
}
|
|
195
|
-
const code = String(res.error.code);
|
|
196
|
-
const status = res.error.status;
|
|
197
|
-
const retryable = retryableCodes.has(code) ||
|
|
198
|
-
(code === types_1.ADAPTER_ERROR_CODES.PROVIDER_ERROR &&
|
|
199
|
-
typeof status === 'number' &&
|
|
200
|
-
(status === 408 || status === 409 || status >= 500));
|
|
201
|
-
return { retry: retryable, retryAfterMs: res.error.retryAfterMs };
|
|
202
|
-
}, {
|
|
203
|
-
...this.retryOptions,
|
|
204
|
-
...(callOptions?.maxRetries !== undefined && {
|
|
205
|
-
maxRetries: callOptions.maxRetries,
|
|
206
|
-
}),
|
|
207
|
-
signal: callOptions?.signal,
|
|
208
|
-
logger: this.logger,
|
|
209
|
-
label: `${prepared.providerId}/${prepared.modelId}`,
|
|
210
|
-
});
|
|
211
|
-
const processedResult = this.postProcessResponse(result, prepared);
|
|
212
|
-
if (processedResult.object === "chat.completion") {
|
|
213
|
-
this.logger.info(`LLM request completed successfully for model: ${prepared.modelId}`);
|
|
261
|
+
const result = await (0, withRetry_1.withRetry)(async () => {
|
|
262
|
+
const revalidation = await this.revalidatePrepared(prepared, adapterOptions);
|
|
263
|
+
if (revalidation) {
|
|
264
|
+
return revalidation;
|
|
214
265
|
}
|
|
215
|
-
return
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
|
|
266
|
+
return adapter.sendPrepared(prepared.adapterPrepared, apiKey, adapterOptions);
|
|
267
|
+
}, (response) => {
|
|
268
|
+
if (response.object !== "error") {
|
|
269
|
+
return { retry: false };
|
|
270
|
+
}
|
|
271
|
+
const code = String(response.error.code);
|
|
272
|
+
const status = response.error.status;
|
|
219
273
|
return {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
: "An unknown error occurred during message sending.",
|
|
226
|
-
code: "PROVIDER_ERROR",
|
|
227
|
-
type: "server_error",
|
|
228
|
-
providerError: error,
|
|
229
|
-
},
|
|
230
|
-
object: "error",
|
|
274
|
+
retry: retryableCodes.has(code) ||
|
|
275
|
+
(code === types_1.ADAPTER_ERROR_CODES.PROVIDER_ERROR &&
|
|
276
|
+
typeof status === "number" &&
|
|
277
|
+
(status === 408 || status === 409 || status >= 500)),
|
|
278
|
+
retryAfterMs: response.error.retryAfterMs,
|
|
231
279
|
};
|
|
232
|
-
}
|
|
280
|
+
}, {
|
|
281
|
+
...this.retryOptions,
|
|
282
|
+
...(callOptions?.maxRetries !== undefined && {
|
|
283
|
+
maxRetries: callOptions.maxRetries,
|
|
284
|
+
}),
|
|
285
|
+
signal: callOptions?.signal,
|
|
286
|
+
logger: this.logger,
|
|
287
|
+
label: `${prepared.providerId}/${prepared.modelId}`,
|
|
288
|
+
});
|
|
289
|
+
return this.postProcessResponse(result, prepared);
|
|
233
290
|
}
|
|
234
291
|
catch (error) {
|
|
235
|
-
this.
|
|
236
|
-
return {
|
|
237
|
-
provider: request.providerId || request.presetId || 'unknown',
|
|
238
|
-
model: request.modelId || request.presetId || 'unknown',
|
|
239
|
-
error: {
|
|
240
|
-
message: error instanceof Error
|
|
241
|
-
? error.message
|
|
242
|
-
: "An unknown error occurred.",
|
|
243
|
-
code: "UNEXPECTED_ERROR",
|
|
244
|
-
type: "server_error",
|
|
245
|
-
providerError: error,
|
|
246
|
-
},
|
|
247
|
-
object: "error",
|
|
248
|
-
};
|
|
292
|
+
return this.createUnexpectedDispatchFailure(prepared, error);
|
|
249
293
|
}
|
|
250
294
|
}
|
|
251
295
|
/**
|
|
@@ -256,83 +300,146 @@ class LLMService {
|
|
|
256
300
|
* are yielded as a single `error` event.
|
|
257
301
|
*/
|
|
258
302
|
async *streamMessage(request, callOptions) {
|
|
303
|
+
const attemptId = (0, node_crypto_1.randomUUID)();
|
|
259
304
|
this.logger.info(`LLMService.streamMessage called with presetId: ${request.presetId}, provider: ${request.providerId}, model: ${request.modelId}`);
|
|
305
|
+
const canonical = await this.prepareMessage(request, { mode: "stream" });
|
|
306
|
+
if (this.isFailureResponse(canonical)) {
|
|
307
|
+
yield { attemptId, type: "error", error: canonical };
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
yield* this.streamPreparedWithAttempt(canonical, callOptions, attemptId);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
/** Dispatches a reusable stream-mode prepared call with no automatic retries. */
|
|
314
|
+
async *streamPrepared(handle, callOptions) {
|
|
315
|
+
yield* this.streamPreparedWithAttempt(handle, callOptions, (0, node_crypto_1.randomUUID)());
|
|
316
|
+
}
|
|
317
|
+
async *streamPreparedWithAttempt(handle, callOptions, attemptId) {
|
|
318
|
+
const resolved = this.resolvePreparedHandle(handle, "stream");
|
|
319
|
+
if ("error" in resolved) {
|
|
320
|
+
yield { attemptId, type: "error", error: resolved.error };
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
const prepared = resolved.prepared;
|
|
324
|
+
const adapter = prepared.clientAdapter;
|
|
325
|
+
if (!adapter.streamPrepared) {
|
|
326
|
+
yield {
|
|
327
|
+
attemptId,
|
|
328
|
+
type: "error",
|
|
329
|
+
error: this.createPreparedFailure(prepared.providerId, prepared.modelId, types_1.ADAPTER_ERROR_CODES.PREPARED_CALL_UNSUPPORTED, `Prepared streaming is not supported for provider '${prepared.providerId}'.`, "unsupported_feature"),
|
|
330
|
+
};
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
const apiKey = await this.resolveDispatchApiKey(prepared);
|
|
334
|
+
if (this.isFailureResponse(apiKey)) {
|
|
335
|
+
yield { attemptId, type: "error", error: apiKey };
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
const cancellation = new AbortController();
|
|
339
|
+
const signal = callOptions?.signal
|
|
340
|
+
? AbortSignal.any([callOptions.signal, cancellation.signal])
|
|
341
|
+
: cancellation.signal;
|
|
342
|
+
const adapterOptions = this.createAdapterOptions({
|
|
343
|
+
...callOptions,
|
|
344
|
+
signal,
|
|
345
|
+
});
|
|
346
|
+
const partialState = {
|
|
347
|
+
provider: prepared.providerId,
|
|
348
|
+
model: prepared.modelId,
|
|
349
|
+
started: false,
|
|
350
|
+
choices: new Map(),
|
|
351
|
+
};
|
|
352
|
+
let iterator;
|
|
353
|
+
let terminal = false;
|
|
260
354
|
try {
|
|
261
|
-
const
|
|
262
|
-
if (
|
|
263
|
-
yield { type: "error", error:
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
266
|
-
const prepared = preparedResult.prepared;
|
|
267
|
-
if (!prepared.clientAdapter.streamMessage) {
|
|
268
|
-
yield {
|
|
269
|
-
type: "error",
|
|
270
|
-
error: {
|
|
271
|
-
provider: prepared.providerId,
|
|
272
|
-
model: prepared.modelId,
|
|
273
|
-
error: {
|
|
274
|
-
message: `Streaming is not supported for provider '${prepared.providerId}'.`,
|
|
275
|
-
code: "PROVIDER_ERROR",
|
|
276
|
-
type: "unsupported_feature",
|
|
277
|
-
},
|
|
278
|
-
object: "error",
|
|
279
|
-
},
|
|
280
|
-
};
|
|
355
|
+
const revalidation = await this.revalidatePrepared(prepared, adapterOptions);
|
|
356
|
+
if (revalidation) {
|
|
357
|
+
yield { attemptId, type: "error", error: revalidation };
|
|
281
358
|
return;
|
|
282
359
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
360
|
+
iterator = adapter
|
|
361
|
+
.streamPrepared(prepared.adapterPrepared, apiKey, adapterOptions)[Symbol.asyncIterator]();
|
|
362
|
+
while (!terminal) {
|
|
363
|
+
const step = await this.nextStreamEvent(iterator, signal);
|
|
364
|
+
if (step.done) {
|
|
365
|
+
break;
|
|
366
|
+
}
|
|
367
|
+
const event = step.value;
|
|
368
|
+
this.observeStreamPartial(partialState, event);
|
|
369
|
+
if (event.type === "adapter_evidence") {
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
if (event.type === "complete") {
|
|
373
|
+
const processed = this.postProcessResponse(event.response, prepared);
|
|
374
|
+
if (processed.object === "error") {
|
|
375
|
+
const failure = this.finalizeStreamFailure(processed, partialState, attemptId, prepared);
|
|
376
|
+
terminal = true;
|
|
377
|
+
yield {
|
|
378
|
+
attemptId,
|
|
379
|
+
type: "error",
|
|
380
|
+
error: failure,
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
else {
|
|
384
|
+
terminal = true;
|
|
385
|
+
yield { attemptId, type: "complete", response: processed };
|
|
294
386
|
}
|
|
295
|
-
|
|
387
|
+
break;
|
|
296
388
|
}
|
|
389
|
+
if (event.type === "error") {
|
|
390
|
+
const failure = this.finalizeStreamFailure(event.error, partialState, attemptId, prepared);
|
|
391
|
+
terminal = true;
|
|
392
|
+
yield {
|
|
393
|
+
attemptId,
|
|
394
|
+
type: "error",
|
|
395
|
+
error: failure,
|
|
396
|
+
};
|
|
397
|
+
break;
|
|
398
|
+
}
|
|
399
|
+
const { observedEvidence: _observedEvidence, ...publicEvent } = event;
|
|
400
|
+
yield { ...publicEvent, attemptId };
|
|
297
401
|
}
|
|
298
|
-
|
|
299
|
-
|
|
402
|
+
if (!terminal) {
|
|
403
|
+
terminal = true;
|
|
404
|
+
const aborted = signal.aborted;
|
|
300
405
|
yield {
|
|
406
|
+
attemptId,
|
|
301
407
|
type: "error",
|
|
302
|
-
error:
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
? error.message
|
|
308
|
-
: "An unknown error occurred during message streaming.",
|
|
309
|
-
code: "PROVIDER_ERROR",
|
|
310
|
-
type: "server_error",
|
|
311
|
-
providerError: error,
|
|
312
|
-
},
|
|
313
|
-
object: "error",
|
|
314
|
-
},
|
|
408
|
+
error: this.finalizeStreamFailure(this.createPreparedFailure(prepared.providerId, prepared.modelId, aborted
|
|
409
|
+
? types_1.ADAPTER_ERROR_CODES.REQUEST_ABORTED
|
|
410
|
+
: types_1.ADAPTER_ERROR_CODES.PROVIDER_ERROR, aborted
|
|
411
|
+
? "The streaming request was aborted."
|
|
412
|
+
: "The provider stream ended without a terminal event.", aborted ? "abort_error" : "server_error"), partialState, attemptId, prepared),
|
|
315
413
|
};
|
|
316
414
|
}
|
|
317
415
|
}
|
|
318
416
|
catch (error) {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
error:
|
|
326
|
-
|
|
417
|
+
if (!terminal) {
|
|
418
|
+
terminal = true;
|
|
419
|
+
const aborted = signal.aborted;
|
|
420
|
+
yield {
|
|
421
|
+
attemptId,
|
|
422
|
+
type: "error",
|
|
423
|
+
error: this.finalizeStreamFailure(this.createPreparedFailure(prepared.providerId, prepared.modelId, aborted
|
|
424
|
+
? types_1.ADAPTER_ERROR_CODES.REQUEST_ABORTED
|
|
425
|
+
: types_1.ADAPTER_ERROR_CODES.PROVIDER_ERROR, aborted
|
|
426
|
+
? "The streaming request was aborted."
|
|
427
|
+
: error instanceof Error
|
|
327
428
|
? error.message
|
|
328
|
-
: "An unknown error occurred.",
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
429
|
+
: "An unknown streaming error occurred.", aborted ? "abort_error" : "server_error", error), partialState, attemptId, prepared),
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
finally {
|
|
434
|
+
cancellation.abort();
|
|
435
|
+
if (iterator?.return) {
|
|
436
|
+
try {
|
|
437
|
+
void Promise.resolve(iterator.return()).catch(() => undefined);
|
|
438
|
+
}
|
|
439
|
+
catch {
|
|
440
|
+
// Iterator cleanup is best-effort and must never replace the terminal.
|
|
441
|
+
}
|
|
442
|
+
}
|
|
336
443
|
}
|
|
337
444
|
}
|
|
338
445
|
async resolveAndValidateCapabilities(request, options) {
|
|
@@ -386,12 +493,45 @@ class LLMService {
|
|
|
386
493
|
resolvedRequest,
|
|
387
494
|
finalSettings,
|
|
388
495
|
capabilities,
|
|
496
|
+
...(resolved.adapterPreparationState !== undefined && {
|
|
497
|
+
adapterPreparationState: resolved.adapterPreparationState,
|
|
498
|
+
}),
|
|
389
499
|
},
|
|
390
500
|
};
|
|
391
501
|
}
|
|
392
502
|
buildModelCapabilities(modelInfo, source) {
|
|
503
|
+
const structuredOutput = this.getStructuredOutputSupport(modelInfo, source);
|
|
504
|
+
const tokenProfile = (0, tokenization_1.resolveTokenProfile)(modelInfo.providerId, modelInfo.id);
|
|
505
|
+
const reportsUsage = modelInfo.providerId === "openai" ||
|
|
506
|
+
modelInfo.providerId === "anthropic" ||
|
|
507
|
+
modelInfo.providerId === "gemini" ||
|
|
508
|
+
modelInfo.providerId === "mistral" ||
|
|
509
|
+
modelInfo.providerId === "openrouter" ||
|
|
510
|
+
modelInfo.providerId === "llamacpp";
|
|
393
511
|
return {
|
|
394
|
-
structuredOutput
|
|
512
|
+
structuredOutput,
|
|
513
|
+
...(modelInfo.contextWindow !== undefined && {
|
|
514
|
+
contextWindow: {
|
|
515
|
+
tokens: modelInfo.contextWindow,
|
|
516
|
+
source,
|
|
517
|
+
},
|
|
518
|
+
}),
|
|
519
|
+
contentTokenCounting: tokenProfile.status === "available" ? "exact" : "unavailable",
|
|
520
|
+
preparedMessageTokenCounting: modelInfo.providerId === "llamacpp" ? "runtime" : "unavailable",
|
|
521
|
+
...(tokenProfile.status === "available" && {
|
|
522
|
+
tokenProfileId: tokenProfile.profile.id,
|
|
523
|
+
tokenProfileMappingRevision: tokenProfile.mappingRevision,
|
|
524
|
+
}),
|
|
525
|
+
...(reportsUsage && {
|
|
526
|
+
reportsPromptUsage: true,
|
|
527
|
+
reportsCompletionUsage: true,
|
|
528
|
+
}),
|
|
529
|
+
distinguishesLimitCause: modelInfo.providerId === "anthropic" ||
|
|
530
|
+
modelInfo.providerId === "gemini",
|
|
531
|
+
structuredOutputDelivery: {
|
|
532
|
+
native: structuredOutput,
|
|
533
|
+
prompt: "instruction_only",
|
|
534
|
+
},
|
|
395
535
|
};
|
|
396
536
|
}
|
|
397
537
|
getStructuredOutputSupport(modelInfo, source) {
|
|
@@ -415,7 +555,329 @@ class LLMService {
|
|
|
415
555
|
getCapabilitySource(providerId, modelId) {
|
|
416
556
|
return (0, config_1.getModelById)(modelId, providerId) ? "registry" : "fallback";
|
|
417
557
|
}
|
|
418
|
-
|
|
558
|
+
isFailureResponse(value) {
|
|
559
|
+
return (typeof value === "object" &&
|
|
560
|
+
value !== null &&
|
|
561
|
+
value.object === "error");
|
|
562
|
+
}
|
|
563
|
+
createPreparedFailure(provider, model, code, message, type, providerError) {
|
|
564
|
+
return {
|
|
565
|
+
provider,
|
|
566
|
+
model,
|
|
567
|
+
error: {
|
|
568
|
+
message,
|
|
569
|
+
code,
|
|
570
|
+
type,
|
|
571
|
+
...(providerError !== undefined && { providerError }),
|
|
572
|
+
},
|
|
573
|
+
object: "error",
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
createPreparedHandleError(handle, code, message) {
|
|
577
|
+
const mode = typeof handle === "object" && handle !== null && "mode" in handle
|
|
578
|
+
? String(handle.mode ?? "unknown")
|
|
579
|
+
: "unknown";
|
|
580
|
+
return this.createPreparedFailure("unknown", "unknown", code, message, mode === "unknown" ? "invalid_request_error" : "validation_error");
|
|
581
|
+
}
|
|
582
|
+
resolvePreparedHandle(handle, expectedMode) {
|
|
583
|
+
if ((typeof handle !== "object" && typeof handle !== "function") ||
|
|
584
|
+
handle === null) {
|
|
585
|
+
return {
|
|
586
|
+
error: this.createPreparedHandleError(handle, types_1.ADAPTER_ERROR_CODES.INVALID_PREPARED_CALL, "The prepared call is invalid or forged."),
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
const prepared = this.preparedCalls.get(handle);
|
|
590
|
+
if (!prepared) {
|
|
591
|
+
return {
|
|
592
|
+
error: this.createPreparedHandleError(handle, types_1.ADAPTER_ERROR_CODES.INVALID_PREPARED_CALL, "The prepared call is invalid, forged, or belongs to another LLMService instance."),
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
if (prepared.adapterPrepared.mode !== expectedMode) {
|
|
596
|
+
return {
|
|
597
|
+
error: this.createPreparedFailure(prepared.providerId, prepared.modelId, types_1.ADAPTER_ERROR_CODES.PREPARED_CALL_MODE_MISMATCH, `A '${prepared.adapterPrepared.mode}' prepared call cannot be dispatched as '${expectedMode}'.`, "validation_error"),
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
return { prepared };
|
|
601
|
+
}
|
|
602
|
+
async resolveDispatchApiKey(prepared) {
|
|
603
|
+
try {
|
|
604
|
+
const apiKey = await this.getApiKey(prepared.providerId);
|
|
605
|
+
if (!apiKey) {
|
|
606
|
+
return this.createPreparedFailure(prepared.providerId, prepared.modelId, "API_KEY_ERROR", `API key for provider '${prepared.providerId}' could not be retrieved. Ensure your ApiKeyProvider is configured correctly.`, "authentication_error");
|
|
607
|
+
}
|
|
608
|
+
if (prepared.clientAdapter.validateApiKey &&
|
|
609
|
+
!prepared.clientAdapter.validateApiKey(apiKey)) {
|
|
610
|
+
return this.createPreparedFailure(prepared.providerId, prepared.modelId, types_1.ADAPTER_ERROR_CODES.INVALID_API_KEY, `Invalid API key format for provider '${prepared.providerId}'. Please check your API key.`, "authentication_error");
|
|
611
|
+
}
|
|
612
|
+
return apiKey;
|
|
613
|
+
}
|
|
614
|
+
catch (error) {
|
|
615
|
+
return this.createPreparedFailure(prepared.providerId, prepared.modelId, types_1.ADAPTER_ERROR_CODES.PROVIDER_ERROR, error instanceof Error
|
|
616
|
+
? error.message
|
|
617
|
+
: "An unknown error occurred while retrieving credentials.", "server_error", error);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
createAdapterOptions(callOptions) {
|
|
621
|
+
const timeoutMs = callOptions?.timeoutMs ?? this.defaultTimeoutMs;
|
|
622
|
+
return {
|
|
623
|
+
...(callOptions?.signal && { signal: callOptions.signal }),
|
|
624
|
+
...(timeoutMs !== undefined && { timeoutMs }),
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
async revalidatePrepared(prepared, options) {
|
|
628
|
+
if (!prepared.clientAdapter.revalidatePreparedRequest) {
|
|
629
|
+
return undefined;
|
|
630
|
+
}
|
|
631
|
+
const result = await prepared.clientAdapter.revalidatePreparedRequest(prepared.adapterPrepared, options);
|
|
632
|
+
return result.valid ? undefined : result.error;
|
|
633
|
+
}
|
|
634
|
+
async nextStreamEvent(iterator, signal) {
|
|
635
|
+
if (signal.aborted) {
|
|
636
|
+
throw new Error("Streaming request aborted");
|
|
637
|
+
}
|
|
638
|
+
return new Promise((resolve, reject) => {
|
|
639
|
+
const onAbort = () => {
|
|
640
|
+
reject(new Error("Streaming request aborted"));
|
|
641
|
+
};
|
|
642
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
643
|
+
Promise.resolve(iterator.next()).then((result) => {
|
|
644
|
+
signal.removeEventListener("abort", onAbort);
|
|
645
|
+
resolve(result);
|
|
646
|
+
}, (error) => {
|
|
647
|
+
signal.removeEventListener("abort", onAbort);
|
|
648
|
+
reject(error);
|
|
649
|
+
});
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
observeStreamPartial(state, event) {
|
|
653
|
+
const observed = event.observedEvidence;
|
|
654
|
+
if (observed?.choice) {
|
|
655
|
+
const choice = this.getStreamPartialChoice(state, observed.choice.index);
|
|
656
|
+
if (observed.choice.rawContentDelta !== undefined) {
|
|
657
|
+
choice.rawContent =
|
|
658
|
+
(choice.rawContent ?? "") + observed.choice.rawContentDelta;
|
|
659
|
+
}
|
|
660
|
+
if (observed.choice.rawContentParts) {
|
|
661
|
+
choice.rawContentParts = [
|
|
662
|
+
...(choice.rawContentParts ?? []),
|
|
663
|
+
...observed.choice.rawContentParts,
|
|
664
|
+
];
|
|
665
|
+
}
|
|
666
|
+
if (observed.choice.rawAnswerAccounting) {
|
|
667
|
+
choice.rawAnswerAccounting =
|
|
668
|
+
observed.choice.rawAnswerAccounting;
|
|
669
|
+
}
|
|
670
|
+
if (observed.choice.finishReason !== undefined) {
|
|
671
|
+
choice.finishReason = observed.choice.finishReason;
|
|
672
|
+
}
|
|
673
|
+
if (observed.choice.termination) {
|
|
674
|
+
choice.termination = observed.choice.termination;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
if (observed?.usageEvidence) {
|
|
678
|
+
state.usageEvidence = {
|
|
679
|
+
...(state.usageEvidence ?? {}),
|
|
680
|
+
...observed.usageEvidence,
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
if (observed?.usage) {
|
|
684
|
+
state.usage = {
|
|
685
|
+
...(state.usage ?? {}),
|
|
686
|
+
...Object.fromEntries(Object.entries(observed.usage).filter(([, value]) => value !== undefined)),
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
if (event.type === "adapter_evidence") {
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
if (event.type === "start") {
|
|
693
|
+
state.started = true;
|
|
694
|
+
state.provider = event.provider;
|
|
695
|
+
state.model = event.model;
|
|
696
|
+
state.id = event.id;
|
|
697
|
+
state.created = event.created;
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
if (event.type === "content_delta" || event.type === "reasoning_delta") {
|
|
701
|
+
const choice = this.getStreamPartialChoice(state, event.index);
|
|
702
|
+
if (event.type === "content_delta") {
|
|
703
|
+
choice.content += event.delta;
|
|
704
|
+
}
|
|
705
|
+
else {
|
|
706
|
+
choice.reasoning += event.delta;
|
|
707
|
+
}
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
if (event.type === "usage") {
|
|
711
|
+
state.usage = {
|
|
712
|
+
...(state.usage ?? {}),
|
|
713
|
+
...Object.fromEntries(Object.entries(event.usage).filter(([, value]) => value !== undefined)),
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
getStreamPartialChoice(state, index) {
|
|
718
|
+
let choice = state.choices.get(index);
|
|
719
|
+
if (!choice) {
|
|
720
|
+
choice = { content: "", reasoning: "" };
|
|
721
|
+
state.choices.set(index, choice);
|
|
722
|
+
}
|
|
723
|
+
return choice;
|
|
724
|
+
}
|
|
725
|
+
finalizeStreamFailure(failure, state, attemptId, prepared) {
|
|
726
|
+
const processed = this.postProcessResponse(this.attachStreamPartial(failure, state, attemptId), prepared);
|
|
727
|
+
return processed;
|
|
728
|
+
}
|
|
729
|
+
attachStreamPartial(failure, state, attemptId) {
|
|
730
|
+
const hasEvidence = state.started ||
|
|
731
|
+
state.choices.size > 0 ||
|
|
732
|
+
state.usage !== undefined ||
|
|
733
|
+
state.usageEvidence !== undefined;
|
|
734
|
+
if (!hasEvidence) {
|
|
735
|
+
return failure;
|
|
736
|
+
}
|
|
737
|
+
const choices = Array.from(state.choices.entries())
|
|
738
|
+
.sort(([left], [right]) => left - right)
|
|
739
|
+
.map(([index, choice]) => ({
|
|
740
|
+
index,
|
|
741
|
+
message: {
|
|
742
|
+
role: "assistant",
|
|
743
|
+
content: choice.content,
|
|
744
|
+
},
|
|
745
|
+
...(choice.reasoning.length > 0 && {
|
|
746
|
+
reasoning: choice.reasoning,
|
|
747
|
+
}),
|
|
748
|
+
...(choice.rawContent !== undefined && {
|
|
749
|
+
rawContent: choice.rawContent,
|
|
750
|
+
}),
|
|
751
|
+
...(choice.rawContentParts && {
|
|
752
|
+
rawContentParts: choice.rawContentParts,
|
|
753
|
+
}),
|
|
754
|
+
...(choice.rawAnswerAccounting && {
|
|
755
|
+
rawAnswerAccounting: choice.rawAnswerAccounting,
|
|
756
|
+
}),
|
|
757
|
+
finish_reason: choice.finishReason ?? null,
|
|
758
|
+
termination: choice.termination ?? {
|
|
759
|
+
rawReason: null,
|
|
760
|
+
kind: "unknown",
|
|
761
|
+
},
|
|
762
|
+
}));
|
|
763
|
+
const observed = {
|
|
764
|
+
id: state.id ?? attemptId,
|
|
765
|
+
provider: state.provider,
|
|
766
|
+
model: state.model,
|
|
767
|
+
created: state.created ?? Math.floor(Date.now() / 1000),
|
|
768
|
+
choices,
|
|
769
|
+
...(state.usage && { usage: state.usage }),
|
|
770
|
+
...(state.usageEvidence && {
|
|
771
|
+
usageEvidence: state.usageEvidence,
|
|
772
|
+
}),
|
|
773
|
+
};
|
|
774
|
+
if (!failure.partialResponse) {
|
|
775
|
+
return { ...failure, partialResponse: observed };
|
|
776
|
+
}
|
|
777
|
+
const provided = failure.partialResponse;
|
|
778
|
+
const choicesByIndex = new Map(observed.choices.map((choice, position) => [
|
|
779
|
+
choice.index ?? position,
|
|
780
|
+
choice,
|
|
781
|
+
]));
|
|
782
|
+
for (const [position, choice] of provided.choices.entries()) {
|
|
783
|
+
const index = choice.index ?? position;
|
|
784
|
+
const prior = choicesByIndex.get(index);
|
|
785
|
+
choicesByIndex.set(index, {
|
|
786
|
+
...(prior ?? {}),
|
|
787
|
+
...choice,
|
|
788
|
+
message: {
|
|
789
|
+
...(prior?.message ?? { role: "assistant", content: "" }),
|
|
790
|
+
...choice.message,
|
|
791
|
+
},
|
|
792
|
+
...(choice.rawContentParts === undefined &&
|
|
793
|
+
prior?.rawContentParts && {
|
|
794
|
+
rawContentParts: prior.rawContentParts,
|
|
795
|
+
}),
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
const usage = {
|
|
799
|
+
...(observed.usage ?? {}),
|
|
800
|
+
...Object.fromEntries(Object.entries(provided.usage ?? {}).filter(([, value]) => value !== undefined)),
|
|
801
|
+
};
|
|
802
|
+
const usageEvidence = {
|
|
803
|
+
...(observed.usageEvidence ?? {}),
|
|
804
|
+
...Object.fromEntries(Object.entries(provided.usageEvidence ?? {}).filter(([, value]) => value !== undefined)),
|
|
805
|
+
};
|
|
806
|
+
return {
|
|
807
|
+
...failure,
|
|
808
|
+
partialResponse: {
|
|
809
|
+
...observed,
|
|
810
|
+
...provided,
|
|
811
|
+
choices: Array.from(choicesByIndex.entries())
|
|
812
|
+
.sort(([left], [right]) => left - right)
|
|
813
|
+
.map(([, choice]) => choice),
|
|
814
|
+
...(Object.keys(usage).length > 0 && { usage }),
|
|
815
|
+
...(Object.keys(usageEvidence).length > 0 && {
|
|
816
|
+
usageEvidence,
|
|
817
|
+
}),
|
|
818
|
+
},
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
isValidPreparedAccounting(accounting) {
|
|
822
|
+
if (accounting.status === "unavailable") {
|
|
823
|
+
return true;
|
|
824
|
+
}
|
|
825
|
+
return accounting.count !== undefined || accounting.upperBound !== undefined;
|
|
826
|
+
}
|
|
827
|
+
createEffectiveOutputTokenLimit(request, providerId, modelInfo, filteredMaxTokens) {
|
|
828
|
+
if (filteredMaxTokens === undefined ||
|
|
829
|
+
!Number.isSafeInteger(filteredMaxTokens) ||
|
|
830
|
+
filteredMaxTokens <= 0) {
|
|
831
|
+
return undefined;
|
|
832
|
+
}
|
|
833
|
+
const presetId = request.presetId;
|
|
834
|
+
const preset = presetId
|
|
835
|
+
? this.presetManager
|
|
836
|
+
.getPresets()
|
|
837
|
+
.find((candidate) => candidate.id === presetId)
|
|
838
|
+
: undefined;
|
|
839
|
+
const source = request.settings?.maxTokens !== undefined
|
|
840
|
+
? "request"
|
|
841
|
+
: preset?.settings.maxTokens !== undefined
|
|
842
|
+
? "preset"
|
|
843
|
+
: modelInfo.defaultSettings?.maxTokens !== undefined ||
|
|
844
|
+
modelInfo.maxTokens !== undefined
|
|
845
|
+
? "model_default"
|
|
846
|
+
: "library_default";
|
|
847
|
+
const hardLimit = modelInfo.hardOutputTokenLimit;
|
|
848
|
+
const tokens = hardLimit
|
|
849
|
+
? Math.min(filteredMaxTokens, hardLimit.tokens)
|
|
850
|
+
: filteredMaxTokens;
|
|
851
|
+
const counts = hardLimit?.counts ??
|
|
852
|
+
(providerId === "mistral"
|
|
853
|
+
? "visible_output"
|
|
854
|
+
: providerId === "openrouter"
|
|
855
|
+
? "provider_defined"
|
|
856
|
+
: providerId === "openai" ||
|
|
857
|
+
providerId === "anthropic" ||
|
|
858
|
+
providerId === "gemini" ||
|
|
859
|
+
providerId === "llamacpp"
|
|
860
|
+
? "visible_and_reasoning"
|
|
861
|
+
: "unknown");
|
|
862
|
+
return {
|
|
863
|
+
tokens,
|
|
864
|
+
source,
|
|
865
|
+
...(tokens !== filteredMaxTokens && {
|
|
866
|
+
requestedTokens: filteredMaxTokens,
|
|
867
|
+
clamp: {
|
|
868
|
+
tokens,
|
|
869
|
+
source: hardLimit.source,
|
|
870
|
+
},
|
|
871
|
+
}),
|
|
872
|
+
counts,
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
createUnexpectedDispatchFailure(prepared, error) {
|
|
876
|
+
return this.createPreparedFailure(prepared.providerId, prepared.modelId, types_1.ADAPTER_ERROR_CODES.PROVIDER_ERROR, error instanceof Error
|
|
877
|
+
? error.message
|
|
878
|
+
: "An unknown error occurred during prepared dispatch.", "server_error", error);
|
|
879
|
+
}
|
|
880
|
+
async prepareCanonicalRequest(request, mode) {
|
|
419
881
|
const validation = await this.resolveAndValidateCapabilities(request, {
|
|
420
882
|
validateStructure: true,
|
|
421
883
|
detectLocalCapabilities: true,
|
|
@@ -423,7 +885,7 @@ class LLMService {
|
|
|
423
885
|
if ("error" in validation) {
|
|
424
886
|
return { error: validation.error };
|
|
425
887
|
}
|
|
426
|
-
const { providerId, modelId, modelInfo, resolvedRequest, finalSettings, } = validation.context;
|
|
888
|
+
const { providerId, modelId, modelInfo, resolvedRequest, finalSettings, adapterPreparationState, } = validation.context;
|
|
427
889
|
// Get provider info for parameter filtering
|
|
428
890
|
const providerInfo = (0, config_1.getProviderById)(providerId);
|
|
429
891
|
if (!providerInfo) {
|
|
@@ -442,6 +904,11 @@ class LLMService {
|
|
|
442
904
|
}
|
|
443
905
|
// Filter out unsupported parameters
|
|
444
906
|
const filteredSettings = this.settingsManager.filterUnsupportedParameters(finalSettings, modelInfo, providerInfo);
|
|
907
|
+
const outputTokenLimit = this.createEffectiveOutputTokenLimit(request, providerId, modelInfo, filteredSettings.maxTokens);
|
|
908
|
+
if (outputTokenLimit?.clamp &&
|
|
909
|
+
filteredSettings.maxTokens !== outputTokenLimit.tokens) {
|
|
910
|
+
filteredSettings.maxTokens = outputTokenLimit.tokens;
|
|
911
|
+
}
|
|
445
912
|
const internalRequest = {
|
|
446
913
|
...resolvedRequest,
|
|
447
914
|
settings: filteredSettings,
|
|
@@ -454,43 +921,34 @@ class LLMService {
|
|
|
454
921
|
});
|
|
455
922
|
// Get client adapter
|
|
456
923
|
const clientAdapter = this.adapterRegistry.getAdapter(providerId);
|
|
924
|
+
if (!clientAdapter.prepareRequest) {
|
|
925
|
+
return {
|
|
926
|
+
error: this.createPreparedFailure(providerId, modelId, types_1.ADAPTER_ERROR_CODES.PREPARED_CALL_UNSUPPORTED, `Prepared calls are not supported for provider '${providerId}'.`, "unsupported_feature"),
|
|
927
|
+
};
|
|
928
|
+
}
|
|
457
929
|
try {
|
|
458
|
-
const
|
|
459
|
-
|
|
930
|
+
const adapterResult = await clientAdapter.prepareRequest(internalRequest, {
|
|
931
|
+
mode,
|
|
932
|
+
modelInfo,
|
|
933
|
+
outputTokenLimit,
|
|
934
|
+
...(adapterPreparationState !== undefined && {
|
|
935
|
+
providerState: adapterPreparationState,
|
|
936
|
+
}),
|
|
937
|
+
});
|
|
938
|
+
if ("error" in adapterResult) {
|
|
939
|
+
return { error: adapterResult.error };
|
|
940
|
+
}
|
|
941
|
+
if (!this.isValidPreparedAccounting(adapterResult.prepared.promptAccounting)) {
|
|
460
942
|
return {
|
|
461
|
-
error:
|
|
462
|
-
provider: providerId,
|
|
463
|
-
model: modelId,
|
|
464
|
-
error: {
|
|
465
|
-
message: `API key for provider '${providerId}' could not be retrieved. Ensure your ApiKeyProvider is configured correctly.`,
|
|
466
|
-
code: "API_KEY_ERROR",
|
|
467
|
-
type: "authentication_error",
|
|
468
|
-
},
|
|
469
|
-
object: "error",
|
|
470
|
-
},
|
|
943
|
+
error: this.createPreparedFailure(providerId, modelId, types_1.ADAPTER_ERROR_CODES.INVALID_PREPARED_CALL, "The adapter returned invalid prepared prompt accounting.", "server_error"),
|
|
471
944
|
};
|
|
472
945
|
}
|
|
473
|
-
|
|
474
|
-
if (clientAdapter.validateApiKey && !clientAdapter.validateApiKey(apiKey)) {
|
|
946
|
+
if (adapterResult.prepared.mode !== mode) {
|
|
475
947
|
return {
|
|
476
|
-
error: {
|
|
477
|
-
provider: providerId,
|
|
478
|
-
model: modelId,
|
|
479
|
-
error: {
|
|
480
|
-
message: `Invalid API key format for provider '${providerId}'. Please check your API key.`,
|
|
481
|
-
code: "INVALID_API_KEY",
|
|
482
|
-
type: "authentication_error",
|
|
483
|
-
},
|
|
484
|
-
object: "error",
|
|
485
|
-
},
|
|
948
|
+
error: this.createPreparedFailure(providerId, modelId, types_1.ADAPTER_ERROR_CODES.INVALID_PREPARED_CALL, `The adapter prepared mode '${String(adapterResult.prepared.mode)}' instead of '${mode}'.`, "server_error"),
|
|
486
949
|
};
|
|
487
950
|
}
|
|
488
|
-
const
|
|
489
|
-
...(callOptions?.signal && { signal: callOptions.signal }),
|
|
490
|
-
...((callOptions?.timeoutMs ?? this.defaultTimeoutMs) !== undefined && {
|
|
491
|
-
timeoutMs: callOptions?.timeoutMs ?? this.defaultTimeoutMs,
|
|
492
|
-
}),
|
|
493
|
-
};
|
|
951
|
+
const adapterPrepared = (0, preparedAdapterUtils_1.deepFreeze)(adapterResult.prepared);
|
|
494
952
|
return {
|
|
495
953
|
prepared: {
|
|
496
954
|
providerId,
|
|
@@ -499,33 +957,59 @@ class LLMService {
|
|
|
499
957
|
resolvedRequest,
|
|
500
958
|
internalRequest,
|
|
501
959
|
clientAdapter,
|
|
502
|
-
|
|
503
|
-
adapterOptions,
|
|
960
|
+
adapterPrepared,
|
|
504
961
|
},
|
|
505
962
|
};
|
|
506
963
|
}
|
|
507
964
|
catch (error) {
|
|
508
|
-
this.logger.error("Error preparing LLM request:", error);
|
|
509
965
|
return {
|
|
510
|
-
error:
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
error: {
|
|
514
|
-
message: error instanceof Error
|
|
515
|
-
? error.message
|
|
516
|
-
: "An unknown error occurred during request preparation.",
|
|
517
|
-
code: "PROVIDER_ERROR",
|
|
518
|
-
type: "server_error",
|
|
519
|
-
providerError: error,
|
|
520
|
-
},
|
|
521
|
-
object: "error",
|
|
522
|
-
},
|
|
966
|
+
error: this.createPreparedFailure(providerId, modelId, types_1.ADAPTER_ERROR_CODES.PROVIDER_ERROR, error instanceof Error
|
|
967
|
+
? error.message
|
|
968
|
+
: "An unknown error occurred during request preparation.", "server_error", error),
|
|
523
969
|
};
|
|
524
970
|
}
|
|
525
971
|
}
|
|
526
972
|
postProcessResponse(result, prepared) {
|
|
527
973
|
if (result.object === "error") {
|
|
528
|
-
|
|
974
|
+
if (!result.partialResponse) {
|
|
975
|
+
return result;
|
|
976
|
+
}
|
|
977
|
+
const processed = this.postProcessResponse({
|
|
978
|
+
...result.partialResponse,
|
|
979
|
+
object: "chat.completion",
|
|
980
|
+
}, prepared);
|
|
981
|
+
if (processed.object === "error") {
|
|
982
|
+
return {
|
|
983
|
+
...result,
|
|
984
|
+
...(processed.partialResponse && {
|
|
985
|
+
partialResponse: processed.partialResponse,
|
|
986
|
+
}),
|
|
987
|
+
};
|
|
988
|
+
}
|
|
989
|
+
const { object: _object, ...partialResponse } = processed;
|
|
990
|
+
return { ...result, partialResponse };
|
|
991
|
+
}
|
|
992
|
+
const tokenProfile = (0, tokenization_1.resolveTokenProfile)(prepared.providerId, prepared.modelId);
|
|
993
|
+
if (tokenProfile.status === "available") {
|
|
994
|
+
for (const choice of result.choices) {
|
|
995
|
+
const rawContent = choice.rawContent ?? choice.message.content;
|
|
996
|
+
choice.rawContent ?? (choice.rawContent = rawContent);
|
|
997
|
+
if (!choice.rawAnswerAccounting) {
|
|
998
|
+
const counted = (0, tokenization_1.countTextTokens)(rawContent, tokenProfile.profile);
|
|
999
|
+
if (counted.status === "available") {
|
|
1000
|
+
choice.rawAnswerAccounting = {
|
|
1001
|
+
tokens: counted.count.tokens,
|
|
1002
|
+
method: counted.count.method,
|
|
1003
|
+
source: "library",
|
|
1004
|
+
tokenizerId: counted.count.tokenizerId,
|
|
1005
|
+
tokenProfileRevision: counted.count.tokenProfileRevision,
|
|
1006
|
+
reasoning: choice.reasoning && choice.reasoning.length > 0
|
|
1007
|
+
? "excluded"
|
|
1008
|
+
: "unknown",
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
529
1013
|
}
|
|
530
1014
|
// Post-process for thinking tag fallback
|
|
531
1015
|
// This feature extracts reasoning from XML tags when native reasoning is not active.
|
|
@@ -542,6 +1026,7 @@ class LLMService {
|
|
|
542
1026
|
// Process the response - extract thinking tags if present
|
|
543
1027
|
const choice = result.choices[0];
|
|
544
1028
|
if (choice?.message?.content) {
|
|
1029
|
+
choice.rawContent ?? (choice.rawContent = choice.message.content);
|
|
545
1030
|
const { extracted, remaining } = (0, parser_1.extractInitialTaggedContent)(choice.message.content, tagName);
|
|
546
1031
|
if (extracted !== null) {
|
|
547
1032
|
// Success: thinking tag found
|
|
@@ -557,6 +1042,9 @@ class LLMService {
|
|
|
557
1042
|
choice.reasoning = extracted;
|
|
558
1043
|
}
|
|
559
1044
|
choice.message.content = remaining;
|
|
1045
|
+
if (choice.rawAnswerAccounting) {
|
|
1046
|
+
choice.rawAnswerAccounting.reasoning = "included_extracted";
|
|
1047
|
+
}
|
|
560
1048
|
}
|
|
561
1049
|
else {
|
|
562
1050
|
// Tag was not found
|
|
@@ -585,7 +1073,8 @@ class LLMService {
|
|
|
585
1073
|
model: result.model,
|
|
586
1074
|
created: result.created,
|
|
587
1075
|
choices: result.choices,
|
|
588
|
-
usage: result.usage
|
|
1076
|
+
usage: result.usage,
|
|
1077
|
+
usageEvidence: result.usageEvidence,
|
|
589
1078
|
}
|
|
590
1079
|
};
|
|
591
1080
|
}
|