@f5-sales-demo/pi-ai 21.6.4 → 21.7.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/package.json +2 -2
- package/src/model-thinking.ts +94 -49
- package/src/models.json +19671 -1927
- package/src/providers/openai-codex/request-transformer.ts +4 -9
- package/src/providers/openai-codex-responses.ts +2 -1
- package/src/stream.ts +13 -3
- package/src/types.ts +20 -9
- package/src/utils/discovery/codex.ts +50 -22
- package/src/utils/oauth/callback-server.ts +38 -2
- package/src/utils/oauth/index.ts +1 -0
- package/src/utils/oauth/openai-codex.ts +29 -14
- package/src/utils/oauth/types.ts +7 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@f5-sales-demo/pi-ai",
|
|
4
|
-
"version": "21.
|
|
4
|
+
"version": "21.7.0",
|
|
5
5
|
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
|
6
6
|
"homepage": "https://github.com/f5-sales-demo/xcsh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"@anthropic-ai/sdk": "^0.115",
|
|
47
47
|
"@aws-sdk/client-bedrock-runtime": "^3",
|
|
48
48
|
"@bufbuild/protobuf": "^2.11",
|
|
49
|
-
"@f5-sales-demo/pi-utils": "21.
|
|
49
|
+
"@f5-sales-demo/pi-utils": "21.7.0",
|
|
50
50
|
"@google/genai": "^2.15",
|
|
51
51
|
"@sinclair/typebox": "^0.34",
|
|
52
52
|
"@smithy/node-http-handler": "^4.4",
|
package/src/model-thinking.ts
CHANGED
|
@@ -17,6 +17,18 @@ export const enum Effort {
|
|
|
17
17
|
Max = "max",
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
/** Public reasoning preset enum. `none` is distinct from inheriting the provider default. */
|
|
21
|
+
export const ReasoningEffort = {
|
|
22
|
+
None: "none",
|
|
23
|
+
Minimal: Effort.Minimal,
|
|
24
|
+
Low: Effort.Low,
|
|
25
|
+
Medium: Effort.Medium,
|
|
26
|
+
High: Effort.High,
|
|
27
|
+
XHigh: Effort.XHigh,
|
|
28
|
+
Max: Effort.Max,
|
|
29
|
+
} as const;
|
|
30
|
+
export type ReasoningEffort = "none" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
|
|
31
|
+
|
|
20
32
|
/**
|
|
21
33
|
* Anthropic's `output_config.effort` enum, verbatim. Authority is the API's own
|
|
22
34
|
* validation error: `Input should be 'low', 'medium', 'high', 'xhigh' or 'max'`.
|
|
@@ -48,6 +60,36 @@ export const THINKING_EFFORTS: readonly Effort[] = [
|
|
|
48
60
|
Effort.Max,
|
|
49
61
|
];
|
|
50
62
|
|
|
63
|
+
export const REASONING_EFFORTS: readonly ReasoningEffort[] = [ReasoningEffort.None, ...THINKING_EFFORTS];
|
|
64
|
+
|
|
65
|
+
const EFFORT_DESCRIPTIONS: Readonly<Record<ReasoningEffort, string>> = {
|
|
66
|
+
[ReasoningEffort.None]: "No reasoning",
|
|
67
|
+
[ReasoningEffort.Minimal]: "Very brief reasoning",
|
|
68
|
+
[ReasoningEffort.Low]: "Light reasoning",
|
|
69
|
+
[ReasoningEffort.Medium]: "Balanced reasoning",
|
|
70
|
+
[ReasoningEffort.High]: "Deep reasoning",
|
|
71
|
+
[ReasoningEffort.XHigh]: "Very deep reasoning",
|
|
72
|
+
[ReasoningEffort.Max]: "Maximum reasoning",
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/** Build explicit metadata for custom models and programmatic registries. */
|
|
76
|
+
export function createThinkingConfig(
|
|
77
|
+
supportedLevels: readonly ReasoningEffort[],
|
|
78
|
+
mode: ThinkingConfig["mode"] = "effort",
|
|
79
|
+
defaultLevel: ReasoningEffort = supportedLevels.includes(ReasoningEffort.Medium)
|
|
80
|
+
? ReasoningEffort.Medium
|
|
81
|
+
: supportedLevels[0]!,
|
|
82
|
+
): ThinkingConfig {
|
|
83
|
+
if (supportedLevels.length === 0 || !supportedLevels.includes(defaultLevel)) {
|
|
84
|
+
throw new Error("Thinking metadata requires supported levels and an exact supported default");
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
mode,
|
|
88
|
+
defaultLevel,
|
|
89
|
+
supportedLevels: supportedLevels.map(effort => ({ effort, description: EFFORT_DESCRIPTIONS[effort] })),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
51
93
|
const DEFAULT_REASONING_EFFORTS: readonly Effort[] = [Effort.Minimal, Effort.Low, Effort.Medium, Effort.High];
|
|
52
94
|
const DEFAULT_REASONING_EFFORTS_WITH_XHIGH: readonly Effort[] = [
|
|
53
95
|
Effort.Minimal,
|
|
@@ -226,18 +268,31 @@ export function linkSparkPromotionTargets(models: ApiModel<Api>[]): void {
|
|
|
226
268
|
* @throws Error when a reasoning-capable model is missing thinking metadata
|
|
227
269
|
*/
|
|
228
270
|
export function getSupportedEfforts<TApi extends Api>(model: ApiModel<TApi>): readonly Effort[] {
|
|
271
|
+
return getSupportedReasoningEfforts(model).filter((effort): effort is Effort => effort !== ReasoningEffort.None);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/** Returns the exact service-advertised list, including an explicit `none` preset. */
|
|
275
|
+
export function getSupportedReasoningEfforts<TApi extends Api>(model: ApiModel<TApi>): readonly ReasoningEffort[] {
|
|
229
276
|
if (!model.reasoning) {
|
|
230
277
|
return [];
|
|
231
278
|
}
|
|
232
279
|
if (!model.thinking) {
|
|
233
280
|
throw new Error(`Model ${model.provider}/${model.id} is missing thinking metadata`);
|
|
234
281
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
282
|
+
return model.thinking.supportedLevels.map(level => level.effort);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export function requireSupportedReasoningEffort<TApi extends Api>(
|
|
286
|
+
model: ApiModel<TApi>,
|
|
287
|
+
effort: ReasoningEffort,
|
|
288
|
+
): ReasoningEffort {
|
|
289
|
+
const levels = getSupportedReasoningEfforts(model);
|
|
290
|
+
if (!levels.includes(effort)) {
|
|
291
|
+
throw new Error(
|
|
292
|
+
`Reasoning effort ${effort} is not supported by ${model.provider}/${model.id}. Supported efforts: ${levels.join(", ")}`,
|
|
293
|
+
);
|
|
239
294
|
}
|
|
240
|
-
return
|
|
295
|
+
return effort;
|
|
241
296
|
}
|
|
242
297
|
|
|
243
298
|
/**
|
|
@@ -261,20 +316,9 @@ export function clampThinkingLevelForModel<TApi extends Api>(
|
|
|
261
316
|
return requested;
|
|
262
317
|
}
|
|
263
318
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
let clamped: Effort | undefined;
|
|
270
|
-
for (const effort of levels) {
|
|
271
|
-
if (THINKING_EFFORTS.indexOf(effort) > requestedIndex) {
|
|
272
|
-
break;
|
|
273
|
-
}
|
|
274
|
-
clamped = effort;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
return clamped ?? levels[0];
|
|
319
|
+
throw new Error(
|
|
320
|
+
`Thinking effort ${requested} is not supported by ${model.provider}/${model.id}. Supported efforts: ${levels.join(", ")}`,
|
|
321
|
+
);
|
|
278
322
|
}
|
|
279
323
|
|
|
280
324
|
export function requireSupportedEffort<TApi extends Api>(model: ApiModel<TApi>, effort: Effort): Effort {
|
|
@@ -392,15 +436,13 @@ function applyOpenAICatalogPolicy(model: ApiModel<Api>, parsedModel: OpenAIModel
|
|
|
392
436
|
function inferModelThinking<TApi extends Api>(model: ApiModel<TApi>): ThinkingConfig {
|
|
393
437
|
const parsedModel = parseKnownModel(model.id);
|
|
394
438
|
const efforts = inferSupportedEfforts(parsedModel, model);
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
if (!minLevel || !maxLevel) {
|
|
398
|
-
throw new Error(`Model ${model.provider}/${model.id} resolved to an empty thinking range`);
|
|
439
|
+
if (efforts.length === 0) {
|
|
440
|
+
throw new Error(`Model ${model.provider}/${model.id} resolved to no thinking presets`);
|
|
399
441
|
}
|
|
400
442
|
const thinking: ThinkingConfig = {
|
|
401
443
|
mode: inferThinkingControlMode(model, parsedModel),
|
|
402
|
-
|
|
403
|
-
|
|
444
|
+
supportedLevels: efforts.map(effort => ({ effort, description: EFFORT_DESCRIPTIONS[effort] })),
|
|
445
|
+
defaultLevel: efforts.includes(ReasoningEffort.Medium) ? ReasoningEffort.Medium : efforts[0]!,
|
|
404
446
|
};
|
|
405
447
|
if (
|
|
406
448
|
model.provider === "google-vertex" &&
|
|
@@ -408,18 +450,22 @@ function inferModelThinking<TApi extends Api>(model: ApiModel<TApi>): ThinkingCo
|
|
|
408
450
|
!model.id.toLowerCase().includes("lite")
|
|
409
451
|
) {
|
|
410
452
|
thinking.defaultLevel = Effort.High;
|
|
411
|
-
if (parsedModel.version.major === 3) {
|
|
412
|
-
thinking.canDisable = false;
|
|
413
|
-
}
|
|
414
453
|
}
|
|
415
454
|
return thinking;
|
|
416
455
|
}
|
|
417
456
|
|
|
418
457
|
function normalizeThinkingConfig(thinking: ThinkingConfig | undefined): ThinkingConfig | undefined {
|
|
419
|
-
if (!thinking ||
|
|
458
|
+
if (!thinking || !Array.isArray(thinking.supportedLevels) || thinking.supportedLevels.length === 0) {
|
|
420
459
|
return undefined;
|
|
421
460
|
}
|
|
422
|
-
|
|
461
|
+
const supportedLevels = thinking.supportedLevels.filter(
|
|
462
|
+
(level, index, levels) =>
|
|
463
|
+
REASONING_EFFORTS.includes(level.effort) && levels.findIndex(value => value.effort === level.effort) === index,
|
|
464
|
+
);
|
|
465
|
+
if (supportedLevels.length === 0 || !supportedLevels.some(level => level.effort === thinking.defaultLevel)) {
|
|
466
|
+
return undefined;
|
|
467
|
+
}
|
|
468
|
+
return { ...thinking, supportedLevels };
|
|
423
469
|
}
|
|
424
470
|
|
|
425
471
|
function thinkingsEqual(left: ThinkingConfig | undefined, right: ThinkingConfig | undefined): boolean {
|
|
@@ -427,30 +473,23 @@ function thinkingsEqual(left: ThinkingConfig | undefined, right: ThinkingConfig
|
|
|
427
473
|
if (!left || !right) return false;
|
|
428
474
|
return (
|
|
429
475
|
left.mode === right.mode &&
|
|
430
|
-
left.minLevel === right.minLevel &&
|
|
431
|
-
left.maxLevel === right.maxLevel &&
|
|
432
476
|
left.defaultLevel === right.defaultLevel &&
|
|
433
|
-
left.
|
|
477
|
+
left.supportedLevels.length === right.supportedLevels.length &&
|
|
478
|
+
left.supportedLevels.every(
|
|
479
|
+
(level, index) =>
|
|
480
|
+
level.effort === right.supportedLevels[index]?.effort &&
|
|
481
|
+
level.description === right.supportedLevels[index]?.description,
|
|
482
|
+
)
|
|
434
483
|
);
|
|
435
484
|
}
|
|
436
485
|
|
|
437
|
-
function
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
return [];
|
|
442
|
-
}
|
|
443
|
-
return THINKING_EFFORTS.slice(minIndex, maxIndex + 1);
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
function intersectEfforts(left: readonly Effort[], right: readonly Effort[]): readonly Effort[] {
|
|
447
|
-
return left.filter(effort => right.includes(effort));
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
function inferSupportedEfforts<TApi extends Api>(parsedModel: ParsedModel, model: ApiModel<TApi>): readonly Effort[] {
|
|
486
|
+
function inferSupportedEfforts<TApi extends Api>(
|
|
487
|
+
parsedModel: ParsedModel,
|
|
488
|
+
model: ApiModel<TApi>,
|
|
489
|
+
): readonly ReasoningEffort[] {
|
|
451
490
|
switch (parsedModel.family) {
|
|
452
491
|
case "openai":
|
|
453
|
-
return inferOpenAISupportedEfforts(parsedModel);
|
|
492
|
+
return inferOpenAISupportedEfforts(parsedModel, model);
|
|
454
493
|
case "gemini":
|
|
455
494
|
return inferGeminiSupportedEfforts(parsedModel);
|
|
456
495
|
case "anthropic":
|
|
@@ -460,7 +499,13 @@ function inferSupportedEfforts<TApi extends Api>(parsedModel: ParsedModel, model
|
|
|
460
499
|
}
|
|
461
500
|
}
|
|
462
501
|
|
|
463
|
-
function inferOpenAISupportedEfforts
|
|
502
|
+
function inferOpenAISupportedEfforts<TApi extends Api>(
|
|
503
|
+
model: OpenAIModel,
|
|
504
|
+
catalogModel: ApiModel<TApi>,
|
|
505
|
+
): readonly ReasoningEffort[] {
|
|
506
|
+
if (semverGte(model.version, "5.6") && catalogModel.api === "openai-codex-responses") {
|
|
507
|
+
return [ReasoningEffort.None, Effort.Low, Effort.Medium, Effort.High, Effort.XHigh, Effort.Max];
|
|
508
|
+
}
|
|
464
509
|
if (model.variant === "codex-mini" && semverEqual(model.version, "5.1")) {
|
|
465
510
|
return GPT_5_1_CODEX_MINI_EFFORTS;
|
|
466
511
|
}
|