@lensagent/core 0.1.0 → 0.1.2

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.
Files changed (99) hide show
  1. package/README.md +63 -4
  2. package/config/lens.example.jsonc +36 -13
  3. package/dist/adapters/executor-router.d.ts +20 -0
  4. package/dist/adapters/executor-router.js +86 -0
  5. package/dist/adapters/executor.d.ts +9 -3
  6. package/dist/adapters/executor.js +196 -61
  7. package/dist/adapters/lsp.d.ts +2 -1
  8. package/dist/adapters/lsp.js +98 -41
  9. package/dist/adapters/mcp.js +52 -17
  10. package/dist/adapters/revert.d.ts +5 -4
  11. package/dist/adapters/revert.js +6 -64
  12. package/dist/adapters/web.d.ts +9 -0
  13. package/dist/adapters/web.js +244 -0
  14. package/dist/config/files.d.ts +14 -0
  15. package/dist/config/files.js +57 -0
  16. package/dist/config/runtime.d.ts +3 -4
  17. package/dist/config/runtime.js +116 -97
  18. package/dist/config/schema.d.ts +56 -10
  19. package/dist/config/schema.js +48 -8
  20. package/dist/contracts/capability.d.ts +27 -1
  21. package/dist/contracts/domain.d.ts +1 -0
  22. package/dist/contracts/model.d.ts +33 -6
  23. package/dist/contracts/review.d.ts +17 -0
  24. package/dist/contracts/review.js +1 -0
  25. package/dist/core/access-policy.d.ts +40 -0
  26. package/dist/core/access-policy.js +68 -0
  27. package/dist/core/access.d.ts +30 -2
  28. package/dist/core/access.js +159 -26
  29. package/dist/core/context-boundaries.d.ts +25 -0
  30. package/dist/core/context-boundaries.js +106 -0
  31. package/dist/core/context.d.ts +3 -0
  32. package/dist/core/context.js +11 -0
  33. package/dist/core/host.js +261 -173
  34. package/dist/entry/pair.d.ts +1 -0
  35. package/dist/entry/pair.js +14 -0
  36. package/dist/entry/run.js +1 -1
  37. package/dist/execution/lsp-process.d.ts +3 -2
  38. package/dist/execution/lsp-process.js +46 -28
  39. package/dist/http/server.d.ts +5 -0
  40. package/dist/http/server.js +262 -80
  41. package/dist/runtime/agent.d.ts +9 -4
  42. package/dist/runtime/agent.js +361 -286
  43. package/dist/runtime/artifacts.d.ts +13 -1
  44. package/dist/runtime/artifacts.js +137 -10
  45. package/dist/runtime/cache.d.ts +59 -16
  46. package/dist/runtime/cache.js +124 -43
  47. package/dist/runtime/calls.d.ts +1 -1
  48. package/dist/runtime/calls.js +164 -48
  49. package/dist/runtime/capabilities.d.ts +2 -2
  50. package/dist/runtime/capabilities.js +4 -5
  51. package/dist/runtime/cleanup.d.ts +5 -2
  52. package/dist/runtime/cleanup.js +48 -17
  53. package/dist/runtime/compaction.d.ts +9 -0
  54. package/dist/runtime/compaction.js +52 -0
  55. package/dist/runtime/context.d.ts +8 -4
  56. package/dist/runtime/context.js +115 -128
  57. package/dist/runtime/events.d.ts +7 -0
  58. package/dist/runtime/events.js +83 -0
  59. package/dist/runtime/images.d.ts +14 -0
  60. package/dist/runtime/images.js +82 -0
  61. package/dist/runtime/kernel-capabilities.d.ts +7 -1
  62. package/dist/runtime/kernel-capabilities.js +146 -68
  63. package/dist/runtime/model-audit.d.ts +9 -0
  64. package/dist/runtime/model-audit.js +39 -0
  65. package/dist/runtime/model.d.ts +109 -4
  66. package/dist/runtime/model.js +368 -143
  67. package/dist/runtime/observability.d.ts +1 -0
  68. package/dist/runtime/observability.js +60 -15
  69. package/dist/runtime/policy.d.ts +1 -1
  70. package/dist/runtime/policy.js +2 -2
  71. package/dist/runtime/profile.d.ts +2 -1
  72. package/dist/runtime/profile.js +12 -15
  73. package/dist/runtime/projects.d.ts +3 -1
  74. package/dist/runtime/projects.js +38 -17
  75. package/dist/runtime/ptc-worker.d.ts +1 -0
  76. package/dist/runtime/ptc-worker.js +97 -0
  77. package/dist/runtime/ptc.d.ts +16 -0
  78. package/dist/runtime/ptc.js +128 -0
  79. package/dist/runtime/results.d.ts +5 -4
  80. package/dist/runtime/results.js +29 -51
  81. package/dist/runtime/review.d.ts +4 -1
  82. package/dist/runtime/review.js +101 -22
  83. package/dist/runtime/scheduler.js +1 -0
  84. package/dist/runtime/skills.js +34 -5
  85. package/dist/runtime/task-audit.d.ts +21 -0
  86. package/dist/runtime/task-audit.js +122 -0
  87. package/dist/runtime/time.d.ts +9 -0
  88. package/dist/runtime/time.js +18 -0
  89. package/dist/runtime/tokenizer.d.ts +2 -0
  90. package/dist/runtime/tokenizer.js +26 -1
  91. package/dist/store/database.js +26 -1
  92. package/dist/store/event-log.d.ts +1 -0
  93. package/dist/store/event-log.js +22 -5
  94. package/dist/store/schema.d.ts +1 -1
  95. package/dist/store/schema.js +50 -1
  96. package/dist/store/store.d.ts +65 -19
  97. package/dist/store/store.js +986 -188
  98. package/package.json +18 -8
  99. package/proto/executor.proto +69 -55
@@ -1,18 +1,21 @@
1
1
  import { createHash, randomUUID } from 'node:crypto';
2
2
  import { watch } from 'node:fs';
3
- import { readFile, rename, writeFile } from 'node:fs/promises';
3
+ import { readFile, rename, rm, writeFile } from 'node:fs/promises';
4
4
  import { basename, dirname, isAbsolute, normalize, resolve } from 'node:path';
5
5
  import { Ajv } from 'ajv';
6
6
  import { canonicalize } from 'json-canonicalize';
7
7
  import { parse } from 'jsonc-parser';
8
8
  import { fail } from '../core/domain.js';
9
9
  import { ConfigSchema } from './schema.js';
10
+ import { modelReasoningEfforts } from '../runtime/profile.js';
10
11
  export class ConfigRuntime {
11
12
  path;
12
13
  #validate;
13
14
  #snapshot;
14
15
  #watcher;
15
16
  #reloadTail = Promise.resolve();
17
+ #source = null;
18
+ #onChange;
16
19
  constructor(root, path = 'config/lens.jsonc') {
17
20
  this.path = resolve(root, path);
18
21
  this.#validate = new Ajv({ allErrors: true, strict: false }).compile(ConfigSchema);
@@ -28,11 +31,13 @@ export class ConfigRuntime {
28
31
  source = await readFile(this.path, 'utf8');
29
32
  }
30
33
  catch (error) {
34
+ this.#source = null;
31
35
  fail('CONFIG_INVALID', 503, 'The configuration file could not be read.', { reason: message(error) });
32
36
  }
37
+ this.#source = source;
33
38
  const previous = this.#snapshot;
34
39
  const fileDigest = hash(source);
35
- if (previous?.fileDigest === fileDigest)
40
+ if (previous?.fileDigest === fileDigest && previous.error === null)
36
41
  return previous;
37
42
  const candidate = this.parse(source);
38
43
  const config = previous === undefined ? candidate : effective(previous.config, candidate);
@@ -47,6 +52,15 @@ export class ConfigRuntime {
47
52
  error: null
48
53
  };
49
54
  this.#snapshot = next;
55
+ if (previous !== undefined) {
56
+ try {
57
+ this.#onChange?.(next);
58
+ }
59
+ catch {
60
+ this.#snapshot = { ...next, error: 'CONFIG_APPLY_FAILED', appliedHot: [] };
61
+ fail('CONFIG_APPLY_FAILED', 503, 'Configuration was saved but could not be applied.');
62
+ }
63
+ }
50
64
  return next;
51
65
  }
52
66
  parse(source) {
@@ -58,89 +72,107 @@ export class ConfigRuntime {
58
72
  const semantic = semanticIssues(document);
59
73
  if (semantic.length > 0)
60
74
  fail('CONFIG_INVALID', 400, 'The configuration is invalid.', { issues: semantic });
75
+ // Migrate the two former checklist names without advertising executable aliases.
76
+ for (const role of Object.values(document.roles)) {
77
+ role.capabilities = [...new Set(role.capabilities.filter((id) => id !== 'compact').map((id) => id === 'slash_command_list' || id === 'slash_command_run' ? 'slash_command' : id === 'update_plan' || id === 'update_todo' ? 'todo' : id))];
78
+ }
79
+ for (const preset of Object.values(document.policyPresets)) {
80
+ const policies = preset.capabilities;
81
+ const previous = policies.update_todo ?? policies.update_plan;
82
+ if (policies.todo === undefined && previous !== undefined)
83
+ policies.todo = previous;
84
+ const slashPolicy = policies.slash_command_run ?? policies.slash_command_list;
85
+ if (policies.slash_command === undefined && slashPolicy !== undefined)
86
+ policies.slash_command = slashPolicy;
87
+ delete policies.compact;
88
+ delete policies.slash_command_list;
89
+ delete policies.slash_command_run;
90
+ delete policies.update_plan;
91
+ delete policies.update_todo;
92
+ }
61
93
  return document;
62
94
  }
63
95
  validate(source) {
64
96
  try {
65
97
  const config = this.parse(source);
66
- return { valid: true, normalized_digest: hash(canonicalize(config)), errors: [] };
98
+ return { valid: true, normalized_digest: hash(canonicalize(config)), errors: [], warnings: unsupportedSettings(config) };
67
99
  }
68
100
  catch (error) {
69
- return { valid: false, normalized_digest: null, errors: validationDetails(error) };
101
+ return { valid: false, normalized_digest: null, errors: validationDetails(error), warnings: [] };
70
102
  }
71
103
  }
72
- replace(source, expectedFileDigest) {
73
- if (expectedFileDigest !== this.current.fileDigest)
74
- fail('CONFIG_CONFLICT', 409, 'The configuration file changed.');
75
- // Resolved against the file, because the document the client edited was rendered from the file.
76
- // Using the effective config would fail to restore a secret belonging to a restart-only setting
77
- // that has been saved but not yet applied -- the placeholder would be written as the value.
78
- const persisted = restoreSecrets(source, this.current.fileConfig);
79
- this.parse(persisted);
104
+ replace(source, expectedFileDigest, authorize = () => { }) {
80
105
  const operation = this.#reloadTail.then(async () => {
106
+ await authorize();
107
+ const currentSource = await readFile(this.path, 'utf8');
108
+ if (expectedFileDigest !== hash(currentSource))
109
+ fail('CONFIG_CONFLICT', 409, 'The configuration file changed.');
110
+ this.parse(source);
81
111
  const temporary = `${this.path}.${process.pid}.${randomUUID()}.tmp`;
82
- await writeFile(temporary, persisted, 'utf8');
83
- await rename(temporary, this.path);
84
- return this.load();
112
+ try {
113
+ await writeFile(temporary, source, { encoding: 'utf8', mode: 0o600 });
114
+ await authorize();
115
+ if (expectedFileDigest !== hash(await readFile(this.path, 'utf8')))
116
+ fail('CONFIG_CONFLICT', 409, 'The configuration file changed.');
117
+ await rename(temporary, this.path);
118
+ return await this.load();
119
+ }
120
+ finally {
121
+ await rm(temporary, { force: true });
122
+ }
85
123
  });
86
124
  this.#reloadTail = operation.then(() => undefined, () => undefined);
87
125
  return operation;
88
126
  }
89
127
  start(onChange) {
128
+ this.#onChange = onChange;
90
129
  this.#watcher = watch(dirname(this.path), (_event, filename) => {
91
130
  if (filename !== null && filename !== basename(this.path))
92
131
  return;
93
132
  this.#reloadTail = this.#reloadTail.then(async () => {
94
133
  try {
95
- const previous = this.current;
96
- const next = await this.load();
97
- if (next !== previous)
98
- onChange(next);
134
+ await this.load();
99
135
  }
100
136
  catch (error) {
101
137
  const current = this.current;
102
- const source = await readFile(this.path, 'utf8').catch(() => '');
138
+ const source = await readFile(this.path, 'utf8').catch(() => null);
139
+ this.#source = source;
103
140
  const next = {
104
141
  ...current,
105
- fileDigest: hash(source),
142
+ fileDigest: hash(source ?? ''),
106
143
  metadataRevision: current.metadataRevision + 1,
107
144
  error: message(error)
108
145
  };
109
146
  this.#snapshot = next;
110
- onChange(next);
147
+ try {
148
+ onChange(next);
149
+ }
150
+ catch { /* Snapshot retains the visible application error. */ }
111
151
  }
112
- });
152
+ }).catch(() => undefined);
113
153
  });
114
154
  }
115
155
  close() {
116
156
  this.#watcher?.close();
117
157
  this.#watcher = undefined;
118
158
  }
119
- publicConfig() {
120
- return redact(this.current.config);
121
- }
122
- /** The file as a client should see it: every saved setting, with secrets redacted. */
123
- publicFileConfig() {
124
- return redact(this.current.fileConfig);
125
- }
126
159
  document() {
127
160
  const snapshot = this.current;
128
- // Rendered from the file, not the effective config, so a restart-only setting that has been
129
- // saved is visible and survives the next read/modify/write.
130
- const content = snapshot.error === null ? `${JSON.stringify(this.publicFileConfig(), null, 2)}\n` : null;
161
+ const content = this.#source;
131
162
  return {
132
163
  content,
133
164
  content_omitted: content === null,
134
165
  // Documented as a decimal string, like the revisions, so a client never has to guess
135
166
  // whether a large size arrived as a number.
136
- file_size_bytes: String(content === null ? 0 : Buffer.byteLength(content)),
167
+ file_size_bytes: String(Buffer.byteLength(content ?? '')),
137
168
  file_digest: snapshot.fileDigest,
138
169
  active_config_digest: snapshot.digest,
139
- file_valid: snapshot.error === null,
170
+ file_valid: snapshot.error === null && content !== null,
140
171
  // `SchemaIssue` carries a path and a stable code beside the message.
141
- validation_errors: snapshot.error === null
172
+ validation_errors: snapshot.error === null && content !== null
142
173
  ? []
143
- : [{ path: '/', code: 'CONFIG_INVALID', message: snapshot.error }],
174
+ : [{ path: '/', code: 'CONFIG_INVALID', message: snapshot.error ?? 'The configuration file could not be read.' }],
175
+ validation_warnings: unsupportedSettings(snapshot.fileConfig),
144
176
  applied_hot: snapshot.appliedHot,
145
177
  pending_restart: snapshot.pendingRestart
146
178
  };
@@ -157,55 +189,6 @@ export function resolveSecret(value) {
157
189
  }
158
190
  return secret;
159
191
  }
160
- function redact(value, key = '', headerValue = false) {
161
- if (typeof value === 'string' && (key === 'apiKey' || headerValue))
162
- return '[redacted]';
163
- if (Array.isArray(value))
164
- return value.map((entry) => redact(entry, '', headerValue));
165
- if (typeof value !== 'object' || value === null)
166
- return value;
167
- return Object.fromEntries(Object.entries(value).map(([entryKey, entry]) => [
168
- entryKey,
169
- redact(entry, entryKey, headerValue || key === 'headers')
170
- ]));
171
- }
172
- function restoreSecrets(source, current) {
173
- const candidate = parse(source);
174
- if (typeof candidate !== 'object' || candidate === null || Array.isArray(candidate))
175
- return source;
176
- let changed = false;
177
- const document = candidate;
178
- const providers = record(document.providers);
179
- for (const [providerId, provider] of Object.entries(providers ?? {})) {
180
- const options = record(record(provider)?.options);
181
- if (options?.apiKey !== '[redacted]')
182
- continue;
183
- const existing = current.providers[providerId]?.options.apiKey;
184
- if (existing !== undefined) {
185
- options.apiKey = existing;
186
- changed = true;
187
- }
188
- }
189
- const servers = record(document.mcpServers);
190
- for (const [serverId, server] of Object.entries(servers ?? {})) {
191
- const headers = record(record(server)?.headers);
192
- if (headers === null)
193
- continue;
194
- for (const [name, value] of Object.entries(headers)) {
195
- if (value !== '[redacted]')
196
- continue;
197
- const existing = current.mcpServers?.[serverId]?.headers?.[name];
198
- if (existing !== undefined) {
199
- headers[name] = existing;
200
- changed = true;
201
- }
202
- }
203
- }
204
- return changed ? `${JSON.stringify(document, null, 2)}\n` : source;
205
- }
206
- function record(value) {
207
- return typeof value === 'object' && value !== null && !Array.isArray(value) ? value : null;
208
- }
209
192
  function hash(value) {
210
193
  return createHash('sha256').update(value).digest('hex');
211
194
  }
@@ -230,20 +213,34 @@ function semanticIssues(config) {
230
213
  const result = [];
231
214
  const issue = (path, messageValue) => { result.push({ kind: 'semantic', path, message: messageValue }); };
232
215
  const profiles = new Set(Object.keys(config.modelHarnessProfiles ?? {}));
233
- for (const [profileId, profile] of Object.entries(config.modelHarnessProfiles ?? {})) {
234
- if (!profile.effort.allowed.includes(profile.effort.default))
235
- issue(`/modelHarnessProfiles/${profileId}/effort/default`, 'The default effort is not allowed by the profile.');
236
- }
237
216
  for (const [providerId, provider] of Object.entries(config.providers)) {
238
217
  if (!/^[a-z0-9][a-z0-9._-]{0,127}$/u.test(providerId))
239
218
  issue(`/providers/${providerId}`, 'Provider ID is invalid.');
240
219
  if (provider.adapter === '@ai-sdk/openai-compatible' && provider.options.baseURL === undefined)
241
220
  issue(`/providers/${providerId}/options/baseURL`, 'OpenAI-compatible providers require baseURL.');
242
221
  for (const [modelId, model] of Object.entries(provider.models)) {
222
+ if (modelReasoningEfforts(config, `${providerId}/${modelId}`).length === 0)
223
+ issue(`/providers/${providerId}/models/${modelId}/reasoningEfforts`, 'Declare reasoningEfforts on the Model or its Provider.');
243
224
  if (modelId.length === 0 || modelId.includes('\n'))
244
225
  issue(`/providers/${providerId}/models/${modelId}`, 'Model ID is invalid.');
245
226
  if (model.harnessProfile !== undefined && !profiles.has(model.harnessProfile))
246
227
  issue(`/providers/${providerId}/models/${modelId}/harnessProfile`, 'Model Harness Profile does not exist.');
228
+ if (provider.adapter === '@ai-sdk/openai-compatible' && model.harnessProfile !== undefined && config.modelHarnessProfiles?.[model.harnessProfile]?.semantics.reasoningStateRoundTrip === 'required') {
229
+ issue(`/providers/${providerId}/models/${modelId}/harnessProfile`, 'This adapter cannot guarantee required reasoning state round trips.');
230
+ }
231
+ const cache = model.promptCache ?? provider.promptCache;
232
+ if (cache !== undefined) {
233
+ const path = `/providers/${providerId}/models/${modelId}/promptCache`;
234
+ if ((cache.mode === 'anthropic' && provider.adapter !== '@ai-sdk/anthropic') || (cache.mode === 'openai-explicit' && provider.adapter !== '@ai-sdk/openai'))
235
+ issue(path, 'Cache mode must match the Provider adapter.');
236
+ if (cache.ttl !== undefined && !((cache.mode === 'anthropic' && cache.ttl !== '30m') || (cache.mode === 'openai-explicit' && cache.ttl === '30m')))
237
+ issue(path, 'Cache TTL must match an explicit cache mode.');
238
+ if (cache.retention !== undefined && (provider.adapter !== '@ai-sdk/openai' || cache.mode !== 'implicit'))
239
+ issue(path, 'Cache retention applies only to OpenAI implicit caching.');
240
+ }
241
+ const threshold = model.compactionThresholdTokens ?? provider.compactionThresholdTokens;
242
+ if (threshold !== undefined && threshold > model.contextWindowTokens)
243
+ issue(`/providers/${providerId}/models/${modelId}/compactionThresholdTokens`, 'The effective Compaction threshold cannot exceed the model context window.');
247
244
  }
248
245
  }
249
246
  for (const [roleName, role] of Object.entries(config.roles)) {
@@ -253,12 +250,9 @@ function semanticIssues(config) {
253
250
  issue(`/roles/${roleName}/model`, 'Role model is not configured.');
254
251
  if (role.modelReasoningEffort === null)
255
252
  issue(`/roles/${roleName}/modelReasoningEffort`, 'Role reasoning effort is required.');
256
- else {
257
- const allowed = model?.harnessProfile === undefined
258
- ? ['none', 'minimal', 'low', 'medium', 'high']
259
- : config.modelHarnessProfiles?.[model.harnessProfile]?.effort.allowed ?? [];
260
- if (!allowed.includes(role.modelReasoningEffort))
261
- issue(`/roles/${roleName}/modelReasoningEffort`, 'Role reasoning effort is not allowed by the Model Harness Profile.');
253
+ else if (model !== undefined && role.model !== null) {
254
+ if (!modelReasoningEfforts(config, role.model).includes(role.modelReasoningEffort))
255
+ issue(`/roles/${roleName}/modelReasoningEffort`, 'Select an effort from the Model or Provider reasoningEfforts.');
262
256
  }
263
257
  if (agentRole && (role.policyPreset === null || config.policyPresets[role.policyPreset] === undefined))
264
258
  issue(`/roles/${roleName}/policyPreset`, 'Agent roles require an existing Policy preset.');
@@ -273,6 +267,8 @@ function semanticIssues(config) {
273
267
  for (const reference of role.providerRouting.fallback) {
274
268
  if (configuredModel(config, reference) === undefined)
275
269
  issue(`/roles/${roleName}/providerRouting/fallback`, `Fallback model ${reference} is not configured.`);
270
+ else if (role.modelReasoningEffort !== null && !modelReasoningEfforts(config, reference).includes(role.modelReasoningEffort))
271
+ issue(`/roles/${roleName}/providerRouting/fallback`, `Fallback model ${reference} does not list the selected effort ${role.modelReasoningEffort}.`);
276
272
  }
277
273
  }
278
274
  for (const [serverId, server] of Object.entries(config.mcpServers ?? {})) {
@@ -338,12 +334,16 @@ function effective(current, candidate) {
338
334
  delete hot.observability;
339
335
  delete hot.mcpServers;
340
336
  delete hot.lspServers;
337
+ delete hot.localExecutor;
338
+ delete hot.executors;
341
339
  return {
342
340
  ...hot,
343
341
  listen: current.listen,
344
342
  tls: current.tls,
345
343
  database: current.database,
346
344
  executor: current.executor,
345
+ ...(current.localExecutor === undefined ? {} : { localExecutor: current.localExecutor }),
346
+ ...(current.executors === undefined ? {} : { executors: current.executors }),
347
347
  artifacts: current.artifacts,
348
348
  ...(current.pairPath === undefined ? {} : { pairPath: current.pairPath }),
349
349
  ...(current.persistence === undefined ? {} : { persistence: current.persistence }),
@@ -357,7 +357,7 @@ function effective(current, candidate) {
357
357
  * `pending_restart`. Returns the changed setting names rather than a flag so a client can say
358
358
  * which ones are waiting.
359
359
  */
360
- const RESTART_SETTINGS = ['listen', 'tls', 'database', 'executor', 'artifacts', 'pairPath', 'persistence', 'observability', 'mcpServers', 'lspServers'];
360
+ const RESTART_SETTINGS = ['listen', 'tls', 'database', 'executor', 'localExecutor', 'executors', 'artifacts', 'pairPath', 'persistence', 'observability', 'mcpServers', 'lspServers'];
361
361
  function restartPending(current, candidate) {
362
362
  return RESTART_SETTINGS.filter((key) => JSON.stringify(current[key]) !== JSON.stringify(candidate[key]));
363
363
  }
@@ -369,3 +369,22 @@ function appliedHot(current, candidate) {
369
369
  .filter((key) => JSON.stringify(current[key]) !== JSON.stringify(candidate[key]))
370
370
  .sort();
371
371
  }
372
+ function unsupportedSettings(config) {
373
+ const warnings = [];
374
+ const warn = (path) => { warnings.push({ path, code: 'CONFIG_NOT_IMPLEMENTED', message: 'This setting is accepted for existing files but does not control runtime behavior.' }); };
375
+ if (config.capabilityInput !== undefined)
376
+ warn('/capabilityInput');
377
+ if (config.contextProjection !== undefined)
378
+ warn('/contextProjection');
379
+ for (const [id, profile] of Object.entries(config.modelHarnessProfiles ?? {})) {
380
+ const root = `/modelHarnessProfiles/${id}`;
381
+ warn(`${root}/context/segmentOrder`);
382
+ warn(`${root}/semantics/structuredOutput`);
383
+ warn(`${root}/semantics/toolDescriptionStyle`);
384
+ warn(`${root}/semantics/recoveryConstraints/preserveReasoningState`);
385
+ warn(`${root}/cache/segmentStability`);
386
+ if (profile.ptc !== undefined)
387
+ warn(`${root}/ptc`);
388
+ }
389
+ return warnings;
390
+ }
@@ -28,10 +28,6 @@ export declare const ModelHarnessProfileSchema: import("@sinclair/typebox").TObj
28
28
  schemaVersion: import("@sinclair/typebox").TString;
29
29
  instruction: import("@sinclair/typebox").TString;
30
30
  }>>;
31
- effort: import("@sinclair/typebox").TObject<{
32
- allowed: import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>;
33
- default: import("@sinclair/typebox").TString;
34
- }>;
35
31
  cache: import("@sinclair/typebox").TObject<{
36
32
  enabledByDefault: import("@sinclair/typebox").TBoolean;
37
33
  minimumEligiblePrefixTokens: import("@sinclair/typebox").TInteger;
@@ -57,8 +53,10 @@ export declare const ConfigSchema: import("@sinclair/typebox").TObject<{
57
53
  graceMs: import("@sinclair/typebox").TInteger;
58
54
  }>>;
59
55
  executor: import("@sinclair/typebox").TObject<{
56
+ sharedFilesystem: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
60
57
  address: import("@sinclair/typebox").TString;
61
58
  identityPath: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
59
+ credentialPath: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
62
60
  tls: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TObject<{
63
61
  enabled: import("@sinclair/typebox").TBoolean;
64
62
  caFile: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
@@ -66,6 +64,25 @@ export declare const ConfigSchema: import("@sinclair/typebox").TObject<{
66
64
  keyFile: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
67
65
  }>>;
68
66
  }>;
67
+ localExecutor: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TObject<{
68
+ address: import("@sinclair/typebox").TString;
69
+ identityPath: import("@sinclair/typebox").TString;
70
+ credentialPath: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
71
+ }>>;
72
+ executors: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, import("@sinclair/typebox").TObject<{
73
+ name: import("@sinclair/typebox").TString;
74
+ address: import("@sinclair/typebox").TString;
75
+ identityPath: import("@sinclair/typebox").TString;
76
+ credentialPath: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
77
+ sharedFilesystem: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
78
+ defaultWorkspacePath: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
79
+ tls: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TObject<{
80
+ enabled: import("@sinclair/typebox").TBoolean;
81
+ caFile: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
82
+ certFile: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
83
+ keyFile: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
84
+ }>>;
85
+ }>>>;
69
86
  artifacts: import("@sinclair/typebox").TObject<{
70
87
  directory: import("@sinclair/typebox").TString;
71
88
  }>;
@@ -86,6 +103,8 @@ export declare const ConfigSchema: import("@sinclair/typebox").TObject<{
86
103
  maxFileBytes: import("@sinclair/typebox").TInteger;
87
104
  maxFiles: import("@sinclair/typebox").TInteger;
88
105
  retentionDays: import("@sinclair/typebox").TInteger;
106
+ maxQueuedRecords: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TInteger>;
107
+ maxQueuedBytes: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TInteger>;
89
108
  }>>;
90
109
  traces: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TObject<{
91
110
  capacity: import("@sinclair/typebox").TInteger;
@@ -107,8 +126,13 @@ export declare const ConfigSchema: import("@sinclair/typebox").TObject<{
107
126
  initializationOptions: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnknown>;
108
127
  maxFrameBytes: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TInteger>;
109
128
  maxBufferBytes: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TInteger>;
129
+ requestTimeoutMs: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TInteger>;
130
+ maxSessions: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TInteger>;
131
+ idleTimeoutMs: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TInteger>;
110
132
  }>>>;
111
133
  providers: import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, import("@sinclair/typebox").TObject<{
134
+ reasoningEfforts: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>>;
135
+ compactionThresholdTokens: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TInteger>;
112
136
  adapter: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"@ai-sdk/openai">, import("@sinclair/typebox").TLiteral<"@ai-sdk/anthropic">, import("@sinclair/typebox").TLiteral<"@ai-sdk/openai-compatible">]>;
113
137
  options: import("@sinclair/typebox").TObject<{
114
138
  apiKey: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TString, import("@sinclair/typebox").TObject<{
@@ -118,12 +142,30 @@ export declare const ConfigSchema: import("@sinclair/typebox").TObject<{
118
142
  baseURL: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
119
143
  headers: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, import("@sinclair/typebox").TString>>;
120
144
  }>;
145
+ promptCache: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TObject<{
146
+ mode: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"off">, import("@sinclair/typebox").TLiteral<"implicit">, import("@sinclair/typebox").TLiteral<"anthropic">, import("@sinclair/typebox").TLiteral<"openai-explicit">]>;
147
+ minimumEligiblePrefixTokens: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TInteger>;
148
+ maximumBreakpoints: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TInteger>;
149
+ positions: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"system">, import("@sinclair/typebox").TLiteral<"message">, import("@sinclair/typebox").TLiteral<"tool">]>>>;
150
+ ttl: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"5m">, import("@sinclair/typebox").TLiteral<"1h">, import("@sinclair/typebox").TLiteral<"30m">]>>;
151
+ retention: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"in_memory">, import("@sinclair/typebox").TLiteral<"24h">]>>;
152
+ }>>;
121
153
  models: import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, import("@sinclair/typebox").TObject<{
154
+ reasoningEfforts: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>>;
122
155
  contextWindowTokens: import("@sinclair/typebox").TInteger;
156
+ compactionThresholdTokens: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TInteger>;
123
157
  wireModel: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
124
158
  harnessProfile: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
125
159
  usageReliable: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
126
160
  cacheSupported: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
161
+ promptCache: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TObject<{
162
+ mode: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"off">, import("@sinclair/typebox").TLiteral<"implicit">, import("@sinclair/typebox").TLiteral<"anthropic">, import("@sinclair/typebox").TLiteral<"openai-explicit">]>;
163
+ minimumEligiblePrefixTokens: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TInteger>;
164
+ maximumBreakpoints: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TInteger>;
165
+ positions: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"system">, import("@sinclair/typebox").TLiteral<"message">, import("@sinclair/typebox").TLiteral<"tool">]>>>;
166
+ ttl: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"5m">, import("@sinclair/typebox").TLiteral<"1h">, import("@sinclair/typebox").TLiteral<"30m">]>>;
167
+ retention: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"in_memory">, import("@sinclair/typebox").TLiteral<"24h">]>>;
168
+ }>>;
127
169
  programmaticToolCallingSupported: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
128
170
  }>>;
129
171
  }>>;
@@ -156,10 +198,6 @@ export declare const ConfigSchema: import("@sinclair/typebox").TObject<{
156
198
  schemaVersion: import("@sinclair/typebox").TString;
157
199
  instruction: import("@sinclair/typebox").TString;
158
200
  }>>;
159
- effort: import("@sinclair/typebox").TObject<{
160
- allowed: import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>;
161
- default: import("@sinclair/typebox").TString;
162
- }>;
163
201
  cache: import("@sinclair/typebox").TObject<{
164
202
  enabledByDefault: import("@sinclair/typebox").TBoolean;
165
203
  minimumEligiblePrefixTokens: import("@sinclair/typebox").TInteger;
@@ -233,6 +271,14 @@ export declare const ConfigSchema: import("@sinclair/typebox").TObject<{
233
271
  outputReserveTokens: import("@sinclair/typebox").TInteger;
234
272
  safetyMarginTokens: import("@sinclair/typebox").TInteger;
235
273
  }>>;
274
+ localPtc: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TObject<{
275
+ timeoutMs: import("@sinclair/typebox").TInteger;
276
+ maxToolCalls: import("@sinclair/typebox").TInteger;
277
+ nativeCapabilities: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>>;
278
+ }>>;
279
+ slashCommands: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TObject<{
280
+ llmVisible: import("@sinclair/typebox").TArray<import("@sinclair/typebox").TLiteral<"compact">>;
281
+ }>>;
236
282
  capabilityInput: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TObject<{
237
283
  catalogMaxTokens: import("@sinclair/typebox").TInteger;
238
284
  searchResultMaxTokens: import("@sinclair/typebox").TInteger;
@@ -254,8 +300,8 @@ export declare const ConfigSchema: import("@sinclair/typebox").TObject<{
254
300
  stageVersion: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
255
301
  }>>;
256
302
  compaction: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TObject<{
257
- preservedSegmentTokens: import("@sinclair/typebox").TInteger;
258
- preservedSegmentBytes: import("@sinclair/typebox").TInteger;
303
+ preservedSegmentTokens: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TInteger>;
304
+ preservedSegmentBytes: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TInteger>;
259
305
  summaryMaxTokens: import("@sinclair/typebox").TInteger;
260
306
  }>>;
261
307
  autoReview: import("@sinclair/typebox").TObject<{
@@ -1,10 +1,19 @@
1
1
  import { Type } from '@sinclair/typebox';
2
2
  const NullableString = Type.Union([Type.String({ minLength: 1 }), Type.Null()]);
3
3
  const PositiveInteger = Type.Integer({ minimum: 1 });
4
+ const ReasoningEfforts = Type.Array(Type.String({ minLength: 1 }), { minItems: 1, uniqueItems: true });
4
5
  const Secret = Type.Union([
5
6
  Type.String({ minLength: 1 }),
6
7
  Type.Object({ env: Type.String({ minLength: 1 }), optional: Type.Optional(Type.Boolean()) }, { additionalProperties: false })
7
8
  ]);
9
+ const PromptCache = Type.Object({
10
+ mode: Type.Union([Type.Literal('off'), Type.Literal('implicit'), Type.Literal('anthropic'), Type.Literal('openai-explicit')]),
11
+ minimumEligiblePrefixTokens: Type.Optional(PositiveInteger),
12
+ maximumBreakpoints: Type.Optional(Type.Integer({ minimum: 0, maximum: 4 })),
13
+ positions: Type.Optional(Type.Array(Type.Union([Type.Literal('system'), Type.Literal('message'), Type.Literal('tool')]), { uniqueItems: true })),
14
+ ttl: Type.Optional(Type.Union([Type.Literal('5m'), Type.Literal('1h'), Type.Literal('30m')])),
15
+ retention: Type.Optional(Type.Union([Type.Literal('in_memory'), Type.Literal('24h')]))
16
+ }, { additionalProperties: false });
8
17
  const Routing = Type.Object({
9
18
  retryMaxAttempts: Type.Integer({ minimum: 1, maximum: 10 }),
10
19
  fallback: Type.Array(Type.String({ minLength: 3 }))
@@ -41,16 +50,13 @@ export const ModelHarnessProfileSchema = Type.Object({
41
50
  preserveReasoningState: Type.Boolean()
42
51
  }, { additionalProperties: false })
43
52
  }, { additionalProperties: false }),
53
+ // Legacy hosted-PTC profile fields are accepted but no longer drive execution.
44
54
  ptc: Type.Optional(Type.Object({
45
55
  enabledByDefault: Type.Boolean(),
46
56
  maxToolCalls: PositiveInteger,
47
57
  schemaVersion: Type.String({ minLength: 1 }),
48
58
  instruction: Type.String({ minLength: 1 })
49
59
  }, { additionalProperties: false })),
50
- effort: Type.Object({
51
- allowed: Type.Array(Type.String({ minLength: 1, maxLength: 64, pattern: '^[A-Za-z0-9._:-]+$' }), { minItems: 1, uniqueItems: true }),
52
- default: Type.String({ minLength: 1, maxLength: 64, pattern: '^[A-Za-z0-9._:-]+$' })
53
- }, { additionalProperties: false }),
54
60
  cache: Type.Object({
55
61
  enabledByDefault: Type.Boolean(),
56
62
  minimumEligiblePrefixTokens: PositiveInteger,
@@ -69,8 +75,10 @@ export const ConfigSchema = Type.Object({
69
75
  graceMs: Type.Integer({ minimum: 0, maximum: 600000, default: 30000 })
70
76
  }, { additionalProperties: false })),
71
77
  executor: Type.Object({
78
+ sharedFilesystem: Type.Optional(Type.Boolean({ default: false })),
72
79
  address: Type.String({ minLength: 1 }),
73
80
  identityPath: Type.Optional(Type.String({ minLength: 1 })),
81
+ credentialPath: Type.Optional(Type.String({ minLength: 1 })),
74
82
  tls: Type.Optional(Type.Object({
75
83
  enabled: Type.Boolean(),
76
84
  caFile: Type.Optional(Type.String({ minLength: 1 })),
@@ -78,6 +86,23 @@ export const ConfigSchema = Type.Object({
78
86
  keyFile: Type.Optional(Type.String({ minLength: 1 }))
79
87
  }, { additionalProperties: false }))
80
88
  }, { additionalProperties: false }),
89
+ localExecutor: Type.Optional(Type.Object({
90
+ address: Type.String({ minLength: 1 }),
91
+ identityPath: Type.String({ minLength: 1 }),
92
+ credentialPath: Type.Optional(Type.String({ minLength: 1 }))
93
+ }, { additionalProperties: false })),
94
+ executors: Type.Optional(Type.Record(Type.String({ pattern: '^(?!local$|remote$)[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$' }), Type.Object({
95
+ name: Type.String({ minLength: 1, maxLength: 128 }),
96
+ address: Type.String({ minLength: 1 }),
97
+ identityPath: Type.String({ minLength: 1 }),
98
+ credentialPath: Type.Optional(Type.String({ minLength: 1 })),
99
+ sharedFilesystem: Type.Optional(Type.Boolean()),
100
+ defaultWorkspacePath: Type.Optional(Type.String({ minLength: 1 })),
101
+ tls: Type.Optional(Type.Object({
102
+ enabled: Type.Boolean(), caFile: Type.Optional(Type.String({ minLength: 1 })),
103
+ certFile: Type.Optional(Type.String({ minLength: 1 })), keyFile: Type.Optional(Type.String({ minLength: 1 }))
104
+ }, { additionalProperties: false }))
105
+ }, { additionalProperties: false }))),
81
106
  artifacts: Type.Object({ directory: Type.String({ minLength: 1 }) }, { additionalProperties: false }),
82
107
  persistence: Type.Optional(Type.Object({
83
108
  eventLog: Type.Optional(Type.Object({
@@ -91,7 +116,9 @@ export const ConfigSchema = Type.Object({
91
116
  path: Type.String({ minLength: 1 }),
92
117
  maxFileBytes: PositiveInteger,
93
118
  maxFiles: PositiveInteger,
94
- retentionDays: PositiveInteger
119
+ retentionDays: PositiveInteger,
120
+ maxQueuedRecords: Type.Optional(PositiveInteger),
121
+ maxQueuedBytes: Type.Optional(PositiveInteger)
95
122
  }, { additionalProperties: false })),
96
123
  traces: Type.Optional(Type.Object({ capacity: PositiveInteger }, { additionalProperties: false }))
97
124
  }, { additionalProperties: false })),
@@ -107,9 +134,14 @@ export const ConfigSchema = Type.Object({
107
134
  project: Type.Optional(Type.String()),
108
135
  initializationOptions: Type.Optional(Type.Unknown()),
109
136
  maxFrameBytes: Type.Optional(PositiveInteger),
110
- maxBufferBytes: Type.Optional(PositiveInteger)
137
+ maxBufferBytes: Type.Optional(PositiveInteger),
138
+ requestTimeoutMs: Type.Optional(PositiveInteger),
139
+ maxSessions: Type.Optional(PositiveInteger),
140
+ idleTimeoutMs: Type.Optional(PositiveInteger)
111
141
  }, { additionalProperties: false }))),
112
142
  providers: Type.Record(Type.String({ minLength: 1 }), Type.Object({
143
+ reasoningEfforts: Type.Optional(ReasoningEfforts),
144
+ compactionThresholdTokens: Type.Optional(PositiveInteger),
113
145
  adapter: Type.Union([
114
146
  Type.Literal('@ai-sdk/openai'),
115
147
  Type.Literal('@ai-sdk/anthropic'),
@@ -120,12 +152,17 @@ export const ConfigSchema = Type.Object({
120
152
  baseURL: Type.Optional(Type.String({ minLength: 1 })),
121
153
  headers: Type.Optional(Type.Record(Type.String(), Type.String()))
122
154
  }, { additionalProperties: false }),
155
+ promptCache: Type.Optional(PromptCache),
123
156
  models: Type.Record(Type.String({ minLength: 1 }), Type.Object({
157
+ reasoningEfforts: Type.Optional(ReasoningEfforts),
124
158
  contextWindowTokens: PositiveInteger,
159
+ compactionThresholdTokens: Type.Optional(PositiveInteger),
125
160
  wireModel: Type.Optional(Type.String({ minLength: 1 })),
126
161
  harnessProfile: Type.Optional(Type.String({ minLength: 1 })),
127
162
  usageReliable: Type.Optional(Type.Boolean()),
128
163
  cacheSupported: Type.Optional(Type.Boolean()),
164
+ promptCache: Type.Optional(PromptCache),
165
+ // Legacy Provider flag; local PTC works without it.
129
166
  programmaticToolCallingSupported: Type.Optional(Type.Boolean())
130
167
  }, { additionalProperties: false }))
131
168
  }, { additionalProperties: false })),
@@ -141,6 +178,8 @@ export const ConfigSchema = Type.Object({
141
178
  concurrency: Type.Object({ instanceMax: PositiveInteger, perStepMax: PositiveInteger, perAdapterMax: PositiveInteger }, { additionalProperties: false }),
142
179
  worker: Type.Object({ timeoutMs: PositiveInteger, outputLimitBytes: PositiveInteger }, { additionalProperties: false }),
143
180
  contextBudget: Type.Optional(Type.Object({ outputReserveTokens: PositiveInteger, safetyMarginTokens: PositiveInteger }, { additionalProperties: false })),
181
+ localPtc: Type.Optional(Type.Object({ timeoutMs: PositiveInteger, maxToolCalls: PositiveInteger, nativeCapabilities: Type.Optional(Type.Array(Type.String({ minLength: 1 }), { uniqueItems: true })) }, { additionalProperties: false })),
182
+ slashCommands: Type.Optional(Type.Object({ llmVisible: Type.Array(Type.Literal('compact'), { uniqueItems: true }) }, { additionalProperties: false })),
144
183
  capabilityInput: Type.Optional(Type.Object({ catalogMaxTokens: PositiveInteger, searchResultMaxTokens: PositiveInteger }, { additionalProperties: false })),
145
184
  contextProjection: Type.Optional(Type.Object({
146
185
  toolResultMaxTokens: Type.Optional(PositiveInteger),
@@ -150,8 +189,9 @@ export const ConfigSchema = Type.Object({
150
189
  stageVersion: Type.Optional(Type.String({ minLength: 1 }))
151
190
  }, { additionalProperties: false })),
152
191
  compaction: Type.Optional(Type.Object({
153
- preservedSegmentTokens: PositiveInteger,
154
- preservedSegmentBytes: PositiveInteger,
192
+ // Accepted for existing configuration files; the retained tail is now fixed at 5000 tokens.
193
+ preservedSegmentTokens: Type.Optional(PositiveInteger),
194
+ preservedSegmentBytes: Type.Optional(PositiveInteger),
155
195
  summaryMaxTokens: PositiveInteger
156
196
  }, { additionalProperties: false })),
157
197
  autoReview: Type.Object({ timeoutMs: PositiveInteger, goalMaxBytes: PositiveInteger }, { additionalProperties: false }),