pi-openai-toolkit 0.9.0 → 0.10.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/src/config.ts CHANGED
@@ -11,10 +11,21 @@ import {
11
11
  DEFAULT_NATIVE_FALLBACK_CONFIG,
12
12
  DEFAULT_WEB_SEARCH_CONFIG,
13
13
  RESPONSES_COMPACT_CAPABLE_APIS,
14
+ BREAKER_LIMIT_MAX,
15
+ BREAKER_LIMIT_MIN,
16
+ BREAKER_WINDOW_MAX,
17
+ BREAKER_WINDOW_MIN,
18
+ CLASSIFIER_MAX_LAG_MAX,
19
+ CLASSIFIER_MAX_LAG_MIN,
20
+ DEFAULT_CLASSIFIER_TIMEOUT_MS,
21
+ EVIDENCE_ROUNDS_MAX,
22
+ EVIDENCE_ROUNDS_MIN,
14
23
  REVIEWER_TIMEOUT_MAX_MS,
15
24
  REVIEWER_TIMEOUT_MIN_MS,
16
25
  THINKING_LEVELS,
17
26
  TOOLKIT_ID,
27
+ type AutoModeCircuitBreakerConfig,
28
+ type AutoModeClassifierConfig,
18
29
  type AutoModeConfig,
19
30
  type AutoModeGate,
20
31
  type CompactionConfig,
@@ -47,7 +58,21 @@ const NATIVE_FALLBACK_FIELDS = new Set(["enabled", "model", "thinkingLevel"]);
47
58
  const AUTO_COMPACTION_FIELDS = new Set(["enabled", "continuation", "unsupportedFallback", "reserveTokens"]);
48
59
  const WEB_SEARCH_FIELDS = new Set(["enabled", "models"]);
49
60
  const IMAGE_GENERATION_FIELDS = new Set(["enabled"]);
50
- const AUTO_MODE_FIELDS = new Set(["enabled", "models", "reviewerModel", "gate", "extraTools", "timeoutMs"]);
61
+ const AUTO_MODE_FIELDS = new Set([
62
+ "enabled",
63
+ "models",
64
+ "reviewerModel",
65
+ "gate",
66
+ "extraTools",
67
+ "timeoutMs",
68
+ "transcript",
69
+ "evidenceTools",
70
+ "maxEvidenceRounds",
71
+ "classifier",
72
+ "circuitBreaker",
73
+ ]);
74
+ const AUTO_MODE_CLASSIFIER_FIELDS = new Set(["enabled", "model", "timeoutMs", "maxLag"]);
75
+ const AUTO_MODE_BREAKER_FIELDS = new Set(["consecutiveDenials", "recentDenials", "windowSize"]);
51
76
 
52
77
  function isRecord(value: unknown): value is Record<string, unknown> {
53
78
  return !!value && typeof value === "object" && !Array.isArray(value);
@@ -156,6 +181,21 @@ function toNonNegativeInteger(value: unknown, fieldPath: string, warnings: strin
156
181
  return undefined;
157
182
  }
158
183
 
184
+ function toBoundedInteger(
185
+ value: unknown,
186
+ fieldPath: string,
187
+ warnings: string[],
188
+ min: number,
189
+ max: number,
190
+ ): number | undefined {
191
+ if (value === undefined) return undefined;
192
+ if (typeof value === "number" && Number.isInteger(value) && Number.isFinite(value) && value >= min && value <= max) {
193
+ return value;
194
+ }
195
+ warnings.push(`Ignoring ${fieldPath}: expected an integer between ${min} and ${max}.`);
196
+ return undefined;
197
+ }
198
+
159
199
  function toSupportedApis(
160
200
  value: unknown,
161
201
  fieldPath: string,
@@ -233,6 +273,8 @@ function cloneDefaults(): ToolkitConfig {
233
273
  ...DEFAULT_AUTO_MODE_CONFIG,
234
274
  models: [...DEFAULT_AUTO_MODE_CONFIG.models],
235
275
  extraTools: [...DEFAULT_AUTO_MODE_CONFIG.extraTools],
276
+ classifier: { ...DEFAULT_AUTO_MODE_CONFIG.classifier },
277
+ circuitBreaker: { ...DEFAULT_AUTO_MODE_CONFIG.circuitBreaker },
236
278
  },
237
279
  };
238
280
  }
@@ -404,6 +446,116 @@ function applyAutoModeConfig(
404
446
  if (timeoutMs !== undefined) {
405
447
  resolved.timeoutMs = timeoutMs;
406
448
  }
449
+
450
+ resolved.transcript = toBoolean(raw.transcript, "autoMode.transcript", warnings) ?? resolved.transcript;
451
+ resolved.evidenceTools =
452
+ toBoolean(raw.evidenceTools, "autoMode.evidenceTools", warnings) ?? resolved.evidenceTools;
453
+
454
+ const maxEvidenceRounds = toBoundedInteger(
455
+ raw.maxEvidenceRounds,
456
+ "autoMode.maxEvidenceRounds",
457
+ warnings,
458
+ EVIDENCE_ROUNDS_MIN,
459
+ EVIDENCE_ROUNDS_MAX,
460
+ );
461
+ if (maxEvidenceRounds !== undefined) {
462
+ resolved.maxEvidenceRounds = maxEvidenceRounds;
463
+ }
464
+
465
+ if (raw.classifier !== undefined) {
466
+ if (isRecord(raw.classifier)) {
467
+ applyAutoModeClassifierConfig(raw.classifier, resolved.classifier, warnings);
468
+ } else {
469
+ warnings.push("Ignoring autoMode.classifier: expected a JSON object.");
470
+ }
471
+ }
472
+
473
+ if (raw.circuitBreaker !== undefined) {
474
+ if (isRecord(raw.circuitBreaker)) {
475
+ applyAutoModeBreakerConfig(raw.circuitBreaker, resolved.circuitBreaker, warnings);
476
+ } else {
477
+ warnings.push("Ignoring autoMode.circuitBreaker: expected a JSON object.");
478
+ }
479
+ }
480
+ }
481
+
482
+ function applyAutoModeClassifierConfig(
483
+ raw: Record<string, unknown>,
484
+ resolved: AutoModeClassifierConfig,
485
+ warnings: string[],
486
+ ): void {
487
+ warnUnknownFields(raw, AUTO_MODE_CLASSIFIER_FIELDS, "autoMode.classifier", warnings);
488
+ resolved.enabled =
489
+ toBoolean(raw.enabled, "autoMode.classifier.enabled", warnings) ?? resolved.enabled;
490
+
491
+ const model = toModelSpec(raw.model, "autoMode.classifier.model", warnings);
492
+ if (model !== undefined) {
493
+ resolved.model = model === null ? undefined : model;
494
+ }
495
+
496
+ const timeoutMs =
497
+ toBoundedInteger(
498
+ raw.timeoutMs,
499
+ "autoMode.classifier.timeoutMs",
500
+ warnings,
501
+ REVIEWER_TIMEOUT_MIN_MS,
502
+ REVIEWER_TIMEOUT_MAX_MS,
503
+ ) ?? DEFAULT_CLASSIFIER_TIMEOUT_MS;
504
+ if (raw.timeoutMs !== undefined) {
505
+ resolved.timeoutMs = timeoutMs;
506
+ }
507
+
508
+ const maxLag = toBoundedInteger(
509
+ raw.maxLag,
510
+ "autoMode.classifier.maxLag",
511
+ warnings,
512
+ CLASSIFIER_MAX_LAG_MIN,
513
+ CLASSIFIER_MAX_LAG_MAX,
514
+ );
515
+ if (maxLag !== undefined) {
516
+ resolved.maxLag = maxLag;
517
+ }
518
+ }
519
+
520
+ function applyAutoModeBreakerConfig(
521
+ raw: Record<string, unknown>,
522
+ resolved: AutoModeCircuitBreakerConfig,
523
+ warnings: string[],
524
+ ): void {
525
+ warnUnknownFields(raw, AUTO_MODE_BREAKER_FIELDS, "autoMode.circuitBreaker", warnings);
526
+
527
+ const consecutiveDenials = toBoundedInteger(
528
+ raw.consecutiveDenials,
529
+ "autoMode.circuitBreaker.consecutiveDenials",
530
+ warnings,
531
+ BREAKER_LIMIT_MIN,
532
+ BREAKER_LIMIT_MAX,
533
+ );
534
+ if (consecutiveDenials !== undefined) {
535
+ resolved.consecutiveDenials = consecutiveDenials;
536
+ }
537
+
538
+ const recentDenials = toBoundedInteger(
539
+ raw.recentDenials,
540
+ "autoMode.circuitBreaker.recentDenials",
541
+ warnings,
542
+ BREAKER_LIMIT_MIN,
543
+ BREAKER_LIMIT_MAX,
544
+ );
545
+ if (recentDenials !== undefined) {
546
+ resolved.recentDenials = recentDenials;
547
+ }
548
+
549
+ const windowSize = toBoundedInteger(
550
+ raw.windowSize,
551
+ "autoMode.circuitBreaker.windowSize",
552
+ warnings,
553
+ BREAKER_WINDOW_MIN,
554
+ BREAKER_WINDOW_MAX,
555
+ );
556
+ if (windowSize !== undefined) {
557
+ resolved.windowSize = windowSize;
558
+ }
407
559
  }
408
560
 
409
561
  /**
package/src/types.ts CHANGED
@@ -111,11 +111,52 @@ export type AutoModeConfig = {
111
111
  extraTools: string[];
112
112
  /** Reviewer deadline in ms; expiry degrades to human confirmation or fail-closed. */
113
113
  timeoutMs: number;
114
+ /** Give the reviewer a bounded transcript so it can judge user authorization. */
115
+ transcript: boolean;
116
+ /** Let the reviewer spend read-only tool calls establishing facts before deciding. */
117
+ evidenceTools: boolean;
118
+ maxEvidenceRounds: number;
119
+ /** Non-blocking trajectory pre-scorer that can satisfy ordinary gated calls. */
120
+ classifier: AutoModeClassifierConfig;
121
+ /** End a turn that keeps getting denied instead of negotiating forever. */
122
+ circuitBreaker: AutoModeCircuitBreakerConfig;
123
+ };
124
+
125
+ export type AutoModeClassifierConfig = {
126
+ enabled: boolean;
127
+ /** "provider/model-id"; falls back to `autoMode.reviewerModel` when unset. */
128
+ model?: string;
129
+ timeoutMs: number;
130
+ /** How many tool calls may elapse between scoring and use before a score is stale. */
131
+ maxLag: number;
132
+ };
133
+
134
+ export type AutoModeCircuitBreakerConfig = {
135
+ /** Consecutive reviewer denials in one turn that end the turn. 0 disables. */
136
+ consecutiveDenials: number;
137
+ /** Denials inside `windowSize` reviewed calls that end the turn. 0 disables. */
138
+ recentDenials: number;
139
+ windowSize: number;
114
140
  };
115
141
 
116
142
  export const REVIEWER_TIMEOUT_MIN_MS = 1_000;
117
143
  export const REVIEWER_TIMEOUT_MAX_MS = 120_000;
118
144
 
145
+ /** Read-only investigation rounds the reviewer may spend before it must answer. */
146
+ export const EVIDENCE_ROUNDS_MIN = 0;
147
+ export const EVIDENCE_ROUNDS_MAX = 8;
148
+
149
+ export const CLASSIFIER_MAX_LAG_MIN = 0;
150
+ export const CLASSIFIER_MAX_LAG_MAX = 20;
151
+
152
+ export const DEFAULT_CLASSIFIER_TIMEOUT_MS = 15_000;
153
+
154
+ /** Denial counts are small integers; 0 disables the matching trigger. */
155
+ export const BREAKER_LIMIT_MIN = 0;
156
+ export const BREAKER_LIMIT_MAX = 100;
157
+ export const BREAKER_WINDOW_MIN = 1;
158
+ export const BREAKER_WINDOW_MAX = 200;
159
+
119
160
  export type ToolkitConfig = {
120
161
  compaction: CompactionConfig;
121
162
  webSearch: WebSearchConfig;
@@ -490,6 +531,20 @@ export const DEFAULT_AUTO_MODE_CONFIG: AutoModeConfig = {
490
531
  gate: "side-effect",
491
532
  extraTools: [],
492
533
  timeoutMs: 30_000,
534
+ transcript: true,
535
+ evidenceTools: true,
536
+ maxEvidenceRounds: 3,
537
+ classifier: {
538
+ enabled: false,
539
+ model: undefined,
540
+ timeoutMs: DEFAULT_CLASSIFIER_TIMEOUT_MS,
541
+ maxLag: 2,
542
+ },
543
+ circuitBreaker: {
544
+ consecutiveDenials: 3,
545
+ recentDenials: 10,
546
+ windowSize: 50,
547
+ },
493
548
  };
494
549
 
495
550
  export const DEFAULT_TOOLKIT_CONFIG: ToolkitConfig = {