genai-lite 0.12.0 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  import type { ApiKeyProvider, PresetMode } from '../types';
2
2
  import type { Logger, LogLevel } from '../logging/types';
3
- import type { LLMChatRequest, LLMChatRequestWithPreset, LLMResponse, LLMFailureResponse, ProviderInfo, ModelInfo, ApiProviderId, LLMSettings, ModelContext, LLMMessage, LLMStreamEvent } from "./types";
3
+ import type { LLMChatRequest, LLMChatRequestWithPreset, LLMResponse, LLMFailureResponse, ProviderInfo, ModelInfo, ApiProviderId, LLMSettings, ModelContext, LLMMessage, LLMStreamEvent, LLMRequestCapabilityPreflight, LLMRequestCapabilityValidationResult, ModelCapabilitiesResult, StructuredOutputSupport } from "./types";
4
4
  import type { ModelPreset } from "../types/presets";
5
5
  import { type RetryPolicy } from "../shared/services/withRetry";
6
6
  export type { PresetMode };
@@ -101,6 +101,31 @@ export declare class LLMService {
101
101
  * @returns Promise resolving to array of model information
102
102
  */
103
103
  getModels(providerId: ApiProviderId): Promise<ModelInfo[]>;
104
+ /**
105
+ * Gets static capability metadata for a provider/model pair without retrieving
106
+ * API keys, calling provider adapters, or performing network I/O.
107
+ *
108
+ * Unknown or unregistered models use the same fallback model resolution policy
109
+ * as sendMessage(), but their optional capabilities are reported as unknown.
110
+ */
111
+ getModelCapabilities(providerId: ApiProviderId, modelId: string): Promise<ModelCapabilitiesResult | LLMFailureResponse>;
112
+ /**
113
+ * Convenience wrapper for checking structured-output support.
114
+ *
115
+ * Returns `unknown` when genai-lite has no explicit capability metadata. Callers
116
+ * should decide whether unknown is acceptable for their use case.
117
+ */
118
+ supportsStructuredOutput(providerId: ApiProviderId, modelId: string): Promise<StructuredOutputSupport | LLMFailureResponse>;
119
+ /**
120
+ * Preflights provider/model capability requirements for a request without
121
+ * retrieving API keys, calling provider adapters, or performing network I/O.
122
+ *
123
+ * This checks the effective settings after preset and model defaults are
124
+ * applied. Unknown/unspecified capabilities are reported as unknown and remain
125
+ * valid; explicitly unsupported capabilities return the same validation error
126
+ * shape as sendMessage() where possible.
127
+ */
128
+ validateRequestCapabilities(request: LLMRequestCapabilityPreflight): Promise<LLMRequestCapabilityValidationResult>;
104
129
  /**
105
130
  * Sends a chat message to an LLM provider
106
131
  *
@@ -116,6 +141,10 @@ export declare class LLMService {
116
141
  * are yielded as a single `error` event.
117
142
  */
118
143
  streamMessage(request: LLMChatRequest | LLMChatRequestWithPreset, callOptions?: StreamMessageOptions): AsyncGenerator<LLMStreamEvent>;
144
+ private resolveAndValidateCapabilities;
145
+ private buildModelCapabilities;
146
+ private getStructuredOutputSupport;
147
+ private getCapabilitySource;
119
148
  private prepareRequest;
120
149
  private postProcessResponse;
121
150
  /**
@@ -76,6 +76,93 @@ class LLMService {
76
76
  this.logger.debug(`Found ${models.length} models for provider: ${providerId}`);
77
77
  return [...models]; // Return a copy to prevent external modification
78
78
  }
79
+ /**
80
+ * Gets static capability metadata for a provider/model pair without retrieving
81
+ * API keys, calling provider adapters, or performing network I/O.
82
+ *
83
+ * Unknown or unregistered models use the same fallback model resolution policy
84
+ * as sendMessage(), but their optional capabilities are reported as unknown.
85
+ */
86
+ async getModelCapabilities(providerId, modelId) {
87
+ this.logger.debug(`LLMService.getModelCapabilities called for provider: ${providerId}, model: ${modelId}`);
88
+ const resolved = await this.modelResolver.resolve({ providerId, modelId }, { detectLocalCapabilities: false });
89
+ if (resolved.error) {
90
+ return resolved.error;
91
+ }
92
+ const resolvedProviderId = resolved.providerId;
93
+ const resolvedModelId = resolved.modelId;
94
+ const modelInfo = resolved.modelInfo;
95
+ const source = this.getCapabilitySource(resolvedProviderId, resolvedModelId);
96
+ return {
97
+ object: "model.capabilities",
98
+ provider: resolvedProviderId,
99
+ model: resolvedModelId,
100
+ modelInfo: { ...modelInfo },
101
+ structuredOutput: this.getStructuredOutputSupport(modelInfo, source),
102
+ };
103
+ }
104
+ /**
105
+ * Convenience wrapper for checking structured-output support.
106
+ *
107
+ * Returns `unknown` when genai-lite has no explicit capability metadata. Callers
108
+ * should decide whether unknown is acceptable for their use case.
109
+ */
110
+ async supportsStructuredOutput(providerId, modelId) {
111
+ const capabilities = await this.getModelCapabilities(providerId, modelId);
112
+ if (capabilities.object === "error") {
113
+ return capabilities;
114
+ }
115
+ return capabilities.structuredOutput;
116
+ }
117
+ /**
118
+ * Preflights provider/model capability requirements for a request without
119
+ * retrieving API keys, calling provider adapters, or performing network I/O.
120
+ *
121
+ * This checks the effective settings after preset and model defaults are
122
+ * applied. Unknown/unspecified capabilities are reported as unknown and remain
123
+ * valid; explicitly unsupported capabilities return the same validation error
124
+ * shape as sendMessage() where possible.
125
+ */
126
+ async validateRequestCapabilities(request) {
127
+ this.logger.debug(`LLMService.validateRequestCapabilities called with presetId: ${request.presetId}, provider: ${request.providerId}, model: ${request.modelId}`);
128
+ try {
129
+ const validation = await this.resolveAndValidateCapabilities(request, {
130
+ validateStructure: false,
131
+ detectLocalCapabilities: false,
132
+ });
133
+ if ("error" in validation) {
134
+ return {
135
+ ...validation.error,
136
+ valid: false,
137
+ ...(validation.capabilities && { capabilities: validation.capabilities }),
138
+ };
139
+ }
140
+ return {
141
+ object: "capability.validation",
142
+ valid: true,
143
+ provider: validation.context.providerId,
144
+ model: validation.context.modelId,
145
+ capabilities: validation.context.capabilities,
146
+ };
147
+ }
148
+ catch (error) {
149
+ this.logger.error("Error in LLMService.validateRequestCapabilities:", error);
150
+ return {
151
+ provider: request.providerId || request.presetId || 'unknown',
152
+ model: request.modelId || request.presetId || 'unknown',
153
+ error: {
154
+ message: error instanceof Error
155
+ ? error.message
156
+ : "Unexpected error during capability preflight",
157
+ code: "UNKNOWN_ERROR",
158
+ type: "server_error",
159
+ providerError: error,
160
+ },
161
+ object: "error",
162
+ valid: false,
163
+ };
164
+ }
165
+ }
79
166
  /**
80
167
  * Sends a chat message to an LLM provider
81
168
  *
@@ -248,44 +335,95 @@ class LLMService {
248
335
  };
249
336
  }
250
337
  }
251
- async prepareRequest(request, callOptions) {
252
- // Resolve model information from preset or direct IDs
253
- const resolved = await this.modelResolver.resolve(request);
338
+ async resolveAndValidateCapabilities(request, options) {
339
+ const resolved = await this.modelResolver.resolve(request, {
340
+ detectLocalCapabilities: options.detectLocalCapabilities,
341
+ });
254
342
  if (resolved.error) {
255
343
  return { error: resolved.error };
256
344
  }
257
- const { providerId, modelId, modelInfo, settings: resolvedSettings } = resolved;
258
- // Create a proper LLMChatRequest with resolved values
345
+ const providerId = resolved.providerId;
346
+ const modelId = resolved.modelId;
347
+ const modelInfo = resolved.modelInfo;
348
+ const source = this.getCapabilitySource(providerId, modelId);
349
+ const capabilities = this.buildModelCapabilities(modelInfo, source);
259
350
  const resolvedRequest = {
260
351
  ...request,
261
- providerId: providerId,
262
- modelId: modelId,
352
+ providerId,
353
+ modelId,
354
+ messages: "messages" in request && Array.isArray(request.messages)
355
+ ? request.messages
356
+ : [],
263
357
  };
264
- // Validate basic request structure
265
- const structureValidationResult = this.requestValidator.validateRequestStructure(resolvedRequest);
266
- if (structureValidationResult) {
267
- return { error: structureValidationResult };
268
- }
269
- // Validate settings if provided
270
- const combinedSettings = { ...resolvedSettings, ...request.settings };
271
- if (combinedSettings) {
272
- const settingsValidation = this.requestValidator.validateSettings(combinedSettings, providerId, modelId);
273
- if (settingsValidation) {
274
- return { error: settingsValidation };
358
+ if (options.validateStructure) {
359
+ const structureValidationResult = this.requestValidator.validateRequestStructure(resolvedRequest);
360
+ if (structureValidationResult) {
361
+ return { error: structureValidationResult, capabilities };
275
362
  }
276
363
  }
277
- // Apply model-specific defaults and merge with user settings
364
+ const combinedSettings = {
365
+ ...(resolved.settings || {}),
366
+ ...(request.settings || {}),
367
+ };
368
+ const settingsValidation = this.requestValidator.validateSettings(combinedSettings, providerId, modelId);
369
+ if (settingsValidation) {
370
+ return { error: settingsValidation, capabilities };
371
+ }
278
372
  const finalSettings = this.settingsManager.mergeSettingsForModel(modelId, providerId, combinedSettings, modelInfo);
279
- // Validate reasoning settings for model capabilities
280
373
  const reasoningValidation = this.requestValidator.validateReasoningSettings(modelInfo, finalSettings.reasoning, resolvedRequest);
281
374
  if (reasoningValidation) {
282
- return { error: reasoningValidation };
375
+ return { error: reasoningValidation, capabilities };
283
376
  }
284
- // Validate structured output settings for model capabilities
285
377
  const structuredOutputValidation = this.requestValidator.validateStructuredOutputSettings(modelInfo, finalSettings.structuredOutput, resolvedRequest);
286
378
  if (structuredOutputValidation) {
287
- return { error: structuredOutputValidation };
379
+ return { error: structuredOutputValidation, capabilities };
380
+ }
381
+ return {
382
+ context: {
383
+ providerId,
384
+ modelId,
385
+ modelInfo,
386
+ resolvedRequest,
387
+ finalSettings,
388
+ capabilities,
389
+ },
390
+ };
391
+ }
392
+ buildModelCapabilities(modelInfo, source) {
393
+ return {
394
+ structuredOutput: this.getStructuredOutputSupport(modelInfo, source),
395
+ };
396
+ }
397
+ getStructuredOutputSupport(modelInfo, source) {
398
+ if (modelInfo.structuredOutput === undefined) {
399
+ return {
400
+ status: "unknown",
401
+ source,
402
+ };
403
+ }
404
+ return {
405
+ status: modelInfo.structuredOutput.supported ? "supported" : "unsupported",
406
+ ...(modelInfo.structuredOutput.strictMode !== undefined && {
407
+ strictMode: modelInfo.structuredOutput.strictMode,
408
+ }),
409
+ ...(modelInfo.structuredOutput.notes && {
410
+ notes: modelInfo.structuredOutput.notes,
411
+ }),
412
+ source,
413
+ };
414
+ }
415
+ getCapabilitySource(providerId, modelId) {
416
+ return (0, config_1.getModelById)(modelId, providerId) ? "registry" : "fallback";
417
+ }
418
+ async prepareRequest(request, callOptions) {
419
+ const validation = await this.resolveAndValidateCapabilities(request, {
420
+ validateStructure: true,
421
+ detectLocalCapabilities: true,
422
+ });
423
+ if ("error" in validation) {
424
+ return { error: validation.error };
288
425
  }
426
+ const { providerId, modelId, modelInfo, resolvedRequest, finalSettings, } = validation.context;
289
427
  // Get provider info for parameter filtering
290
428
  const providerInfo = (0, config_1.getProviderById)(providerId);
291
429
  if (!providerInfo) {
@@ -355,9 +493,9 @@ class LLMService {
355
493
  };
356
494
  return {
357
495
  prepared: {
358
- providerId: providerId,
359
- modelId: modelId,
360
- modelInfo: modelInfo,
496
+ providerId,
497
+ modelId,
498
+ modelInfo,
361
499
  resolvedRequest,
362
500
  internalRequest,
363
501
  clientAdapter,
@@ -54,26 +54,15 @@ class AnthropicClientAdapter {
54
54
  hasStopSequences: !!messageParams.stop_sequences,
55
55
  useStructuredOutput,
56
56
  });
57
- // Make the API call - use beta endpoint for structured output
58
- let completion;
59
- if (useStructuredOutput) {
60
- // For structured output, we need to use the beta messages endpoint with proper headers
61
- completion = await anthropic.messages.create(messageParams, {
62
- headers: {
63
- 'anthropic-beta': 'structured-outputs-2025-11-13',
64
- },
65
- ...requestTransportOptions,
66
- });
67
- }
68
- else {
69
- completion =
70
- Object.keys(requestTransportOptions).length > 0
71
- ? await anthropic.messages.create(messageParams, requestTransportOptions)
72
- : await anthropic.messages.create(messageParams);
73
- }
57
+ // Structured and unstructured requests take the same endpoint - structured
58
+ // output is GA, so there is no beta header or beta endpoint involved.
59
+ const completion = Object.keys(requestTransportOptions).length > 0
60
+ ? await anthropic.messages.create(messageParams, requestTransportOptions)
61
+ : await anthropic.messages.create(messageParams);
74
62
  this.logger.info(`Anthropic API call successful, response ID: ${completion.id}`);
75
- // Convert to standardized response format
76
- // Cast to any to handle beta response type differences
63
+ // Convert to standardized response format. `create` is typed against the
64
+ // streaming/non-streaming param union; we never set `stream`, so this is
65
+ // always a Message.
77
66
  return this.createSuccessResponse(completion, request);
78
67
  }
79
68
  catch (error) {
@@ -94,17 +83,12 @@ class AnthropicClientAdapter {
94
83
  let started = false;
95
84
  try {
96
85
  const { anthropic, messageParams, requestTransportOptions, useStructuredOutput, } = this.prepareMessageRequest(request, apiKey, options);
97
- this.logger.info(`Making Anthropic streaming API call for model: ${request.modelId}`);
98
- const streamOptions = useStructuredOutput
99
- ? {
100
- headers: {
101
- "anthropic-beta": "structured-outputs-2025-11-13",
102
- },
103
- ...requestTransportOptions,
104
- }
105
- : requestTransportOptions;
106
- const stream = Object.keys(streamOptions).length > 0
107
- ? anthropic.messages.stream(messageParams, streamOptions)
86
+ this.logger.info(`Making Anthropic streaming API call for model: ${request.modelId}`, {
87
+ useStructuredOutput,
88
+ });
89
+ // No beta header for structured output - output_config.format is GA.
90
+ const stream = Object.keys(requestTransportOptions).length > 0
91
+ ? anthropic.messages.stream(messageParams, requestTransportOptions)
108
92
  : anthropic.messages.stream(messageParams);
109
93
  for await (const event of stream) {
110
94
  sawEvent = true;
@@ -254,20 +238,22 @@ class AnthropicClientAdapter {
254
238
  stop_sequences: request.settings.stopSequences,
255
239
  }),
256
240
  };
257
- // Handle structured output configuration for Anthropic
258
- // Note: Structured output requires the beta API endpoint
241
+ // Handle structured output configuration for Anthropic.
242
+ // Structured outputs are generally available: the stable request field is
243
+ // output_config.format, with no beta header. The format object carries only
244
+ // `type` and `schema` - the generic StructuredOutputSettings `name` and
245
+ // `strict` fields exist for other providers and are not serialized here.
259
246
  if (useStructuredOutput) {
260
247
  const so = request.settings.structuredOutput;
261
248
  // Anthropic requires additionalProperties: false on all object schemas
262
249
  const processedSchema = so.strict !== false
263
250
  ? this.addAdditionalPropertiesFalse(so.schema)
264
- : so.schema;
265
- // Anthropic's format: output_format.schema is the schema directly
266
- messageParams.output_format = {
267
- type: 'json_schema',
268
- name: so.name,
269
- schema: processedSchema,
270
- strict: so.strict !== false,
251
+ : { ...so.schema };
252
+ messageParams.output_config = {
253
+ format: {
254
+ type: "json_schema",
255
+ schema: processedSchema,
256
+ },
271
257
  };
272
258
  }
273
259
  // Handle reasoning/thinking configuration for Claude models
@@ -873,6 +873,10 @@ exports.SUPPORTED_MODELS = [
873
873
  outputType: 'summary',
874
874
  requiresStreamingAbove: 21333,
875
875
  },
876
+ structuredOutput: {
877
+ supported: true,
878
+ strictMode: true,
879
+ },
876
880
  },
877
881
  {
878
882
  id: "claude-sonnet-4-5-20250929",
@@ -897,6 +901,10 @@ exports.SUPPORTED_MODELS = [
897
901
  outputType: 'summary',
898
902
  requiresStreamingAbove: 21333,
899
903
  },
904
+ structuredOutput: {
905
+ supported: true,
906
+ strictMode: true,
907
+ },
900
908
  },
901
909
  {
902
910
  id: "claude-haiku-4-5-20251001",
@@ -921,8 +929,14 @@ exports.SUPPORTED_MODELS = [
921
929
  outputType: 'summary',
922
930
  requiresStreamingAbove: 21333,
923
931
  },
932
+ structuredOutput: {
933
+ supported: true,
934
+ strictMode: true,
935
+ },
924
936
  },
925
937
  // Anthropic Models - Claude 4 Series
938
+ // Note: Anthropic's structured outputs are generally available for Claude 4.5
939
+ // and later models only, so the entries below declare it unsupported.
926
940
  {
927
941
  id: "claude-sonnet-4-20250514",
928
942
  name: "Claude Sonnet 4",
@@ -946,6 +960,10 @@ exports.SUPPORTED_MODELS = [
946
960
  outputType: 'summary',
947
961
  requiresStreamingAbove: 21333,
948
962
  },
963
+ structuredOutput: {
964
+ supported: false,
965
+ notes: "Anthropic structured outputs require Claude 4.5 or later",
966
+ },
949
967
  },
950
968
  {
951
969
  id: "claude-opus-4-20250514",
@@ -970,6 +988,10 @@ exports.SUPPORTED_MODELS = [
970
988
  outputType: 'summary',
971
989
  requiresStreamingAbove: 21333,
972
990
  },
991
+ structuredOutput: {
992
+ supported: false,
993
+ notes: "Anthropic structured outputs require Claude 4.5 or later",
994
+ },
973
995
  },
974
996
  {
975
997
  id: "claude-3-7-sonnet-20250219",
@@ -994,6 +1016,10 @@ exports.SUPPORTED_MODELS = [
994
1016
  outputType: 'full',
995
1017
  requiresStreamingAbove: 21333,
996
1018
  },
1019
+ structuredOutput: {
1020
+ supported: false,
1021
+ notes: "Anthropic structured outputs require Claude 4.5 or later",
1022
+ },
997
1023
  },
998
1024
  {
999
1025
  id: "claude-3-5-sonnet-20241022",
@@ -1008,6 +1034,10 @@ exports.SUPPORTED_MODELS = [
1008
1034
  supportsPromptCache: true,
1009
1035
  cacheWritesPrice: 3.75,
1010
1036
  cacheReadsPrice: 0.3,
1037
+ structuredOutput: {
1038
+ supported: false,
1039
+ notes: "Anthropic structured outputs require Claude 4.5 or later",
1040
+ },
1011
1041
  },
1012
1042
  {
1013
1043
  id: "claude-3-5-haiku-20241022",
@@ -1022,6 +1052,10 @@ exports.SUPPORTED_MODELS = [
1022
1052
  supportsPromptCache: true,
1023
1053
  cacheWritesPrice: 1.0,
1024
1054
  cacheReadsPrice: 0.08,
1055
+ structuredOutput: {
1056
+ supported: false,
1057
+ notes: "Anthropic structured outputs require Claude 4.5 or later",
1058
+ },
1025
1059
  },
1026
1060
  // Google Gemini Models - Gemini 3 Series (Preview)
1027
1061
  {
@@ -23,6 +23,17 @@ export interface ModelResolution {
23
23
  settings?: LLMSettings;
24
24
  error?: LLMFailureResponse;
25
25
  }
26
+ /**
27
+ * Options controlling model resolution side effects.
28
+ */
29
+ export interface ModelResolutionOptions {
30
+ /**
31
+ * Whether resolution may query local provider adapters for dynamic model
32
+ * capabilities. Defaults to true for sendMessage(), but public capability
33
+ * preflight uses false so it remains no-network/no-adapter.
34
+ */
35
+ detectLocalCapabilities?: boolean;
36
+ }
26
37
  /**
27
38
  * Resolves model information from presets or direct provider/model IDs
28
39
  */
@@ -37,7 +48,7 @@ export declare class ModelResolver {
37
48
  * @param options Options containing either presetId or providerId/modelId
38
49
  * @returns Resolved model info and settings or error response
39
50
  */
40
- resolve(options: ModelSelectionOptions): Promise<ModelResolution>;
51
+ resolve(options: ModelSelectionOptions, resolutionOptions?: ModelResolutionOptions): Promise<ModelResolution>;
41
52
  /**
42
53
  * Asks the llama.cpp adapter which GGUF model the server has loaded and returns
43
54
  * its detected capabilities (cached inside the adapter). Returns undefined when
@@ -18,7 +18,8 @@ class ModelResolver {
18
18
  * @param options Options containing either presetId or providerId/modelId
19
19
  * @returns Resolved model info and settings or error response
20
20
  */
21
- async resolve(options) {
21
+ async resolve(options, resolutionOptions = {}) {
22
+ const detectLocalCapabilities = resolutionOptions.detectLocalCapabilities !== false;
22
23
  // If presetId is provided, use it
23
24
  if (options.presetId) {
24
25
  const preset = this.presetManager.resolvePreset(options.presetId);
@@ -53,7 +54,7 @@ class ModelResolver {
53
54
  }
54
55
  // Overlay detected GGUF capabilities for llama.cpp presets (the server decides
55
56
  // which model is actually loaded, regardless of the preset's modelId)
56
- if (preset.providerId === 'llamacpp') {
57
+ if (preset.providerId === 'llamacpp' && detectLocalCapabilities) {
57
58
  const detected = await this.detectLlamaCppCapabilities();
58
59
  if (detected) {
59
60
  modelInfo = { ...modelInfo, ...detected };
@@ -108,7 +109,7 @@ class ModelResolver {
108
109
  // decides which model is actually loaded, so detected capabilities and vendor
109
110
  // default settings must overlay whatever the registry says.
110
111
  let detectedCapabilities;
111
- if (options.providerId === 'llamacpp') {
112
+ if (options.providerId === 'llamacpp' && detectLocalCapabilities) {
112
113
  detectedCapabilities = await this.detectLlamaCppCapabilities();
113
114
  }
114
115
  if (modelInfo) {
@@ -119,6 +119,38 @@ export interface ModelStructuredOutputCapabilities {
119
119
  /** Additional notes about the model's structured output support */
120
120
  notes?: string;
121
121
  }
122
+ /**
123
+ * Capability support status for provider/model features.
124
+ *
125
+ * `unknown` means genai-lite does not have explicit metadata for the feature.
126
+ * Callers should decide whether to allow, reject, or fall back for unknown
127
+ * capabilities.
128
+ */
129
+ export type CapabilityStatus = "supported" | "unsupported" | "unknown";
130
+ /**
131
+ * Source of a capability decision.
132
+ */
133
+ export type CapabilitySource = "registry" | "detected" | "fallback";
134
+ /**
135
+ * Structured-output support status for a provider/model pair.
136
+ */
137
+ export interface StructuredOutputSupport {
138
+ /** Whether structured output is known supported, known unsupported, or unknown */
139
+ status: CapabilityStatus;
140
+ /** Whether strict provider-side schema enforcement is supported, when known */
141
+ strictMode?: boolean;
142
+ /** Additional notes from model metadata */
143
+ notes?: string;
144
+ /** Where the capability decision came from */
145
+ source: CapabilitySource;
146
+ }
147
+ /**
148
+ * Capability summary for a provider/model pair.
149
+ */
150
+ export interface ModelCapabilities {
151
+ /** Structured-output support status */
152
+ structuredOutput: StructuredOutputSupport;
153
+ }
122
154
  /**
123
155
  * Message roles supported by LLM APIs
124
156
  */
@@ -427,6 +459,35 @@ export interface LLMChatRequestWithPreset extends Omit<LLMChatRequest, 'provider
427
459
  /** Preset ID (alternative to providerId/modelId) */
428
460
  presetId?: string;
429
461
  }
462
+ /**
463
+ * Request used by LLMService.validateRequestCapabilities().
464
+ *
465
+ * This intentionally does not require messages: callers often need to validate
466
+ * provider/model/settings compatibility during configuration, before the final
467
+ * prompt messages exist.
468
+ */
469
+ export interface LLMRequestCapabilityPreflight {
470
+ /** Provider ID (required if not using presetId) */
471
+ providerId?: ApiProviderId;
472
+ /** Model ID (required if not using presetId) */
473
+ modelId?: string;
474
+ /** Preset ID (alternative to providerId/modelId) */
475
+ presetId?: string;
476
+ /** Settings whose provider/model capability requirements should be checked */
477
+ settings?: LLMSettings;
478
+ }
479
+ /**
480
+ * Result of LLMService.getModelCapabilities().
481
+ */
482
+ export interface ModelCapabilitiesResult {
483
+ object: "model.capabilities";
484
+ provider: ApiProviderId;
485
+ model: string;
486
+ /** Resolved model metadata used to derive the capability summary */
487
+ modelInfo: ModelInfo;
488
+ /** Structured-output support status for this provider/model pair */
489
+ structuredOutput: StructuredOutputSupport;
490
+ }
430
491
  /**
431
492
  * Individual choice in an LLM response
432
493
  */
@@ -496,6 +557,30 @@ export interface LLMFailureResponse {
496
557
  /** The partial response that was generated before the error occurred (if available) */
497
558
  partialResponse?: Omit<LLMResponse, 'object'>;
498
559
  }
560
+ /**
561
+ * Successful result of LLMService.validateRequestCapabilities().
562
+ */
563
+ export interface LLMRequestCapabilityValidationSuccess {
564
+ object: "capability.validation";
565
+ valid: true;
566
+ provider: ApiProviderId;
567
+ model: string;
568
+ capabilities: ModelCapabilities;
569
+ }
570
+ /**
571
+ * Failure result of LLMService.validateRequestCapabilities().
572
+ *
573
+ * The base error envelope intentionally matches LLMFailureResponse so callers
574
+ * can use the same diagnostic handling as sendMessage().
575
+ */
576
+ export type LLMRequestCapabilityValidationFailure = LLMFailureResponse & {
577
+ valid: false;
578
+ capabilities?: ModelCapabilities;
579
+ };
580
+ /**
581
+ * Result of LLMService.validateRequestCapabilities().
582
+ */
583
+ export type LLMRequestCapabilityValidationResult = LLMRequestCapabilityValidationSuccess | LLMRequestCapabilityValidationFailure;
499
584
  /**
500
585
  * Streaming event emitted by LLMService.streamMessage().
501
586
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genai-lite",
3
- "version": "0.12.0",
3
+ "version": "0.13.1",
4
4
  "description": "A lightweight, portable toolkit for interacting with various Generative AI APIs.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",