loopengine 0.1.45 → 0.1.46

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.
@@ -8,6 +8,8 @@ export interface AgentEditableFields {
8
8
  model?: {
9
9
  provider: Provider;
10
10
  model?: string;
11
+ maxTokens?: number;
12
+ reasoningEffort?: string;
11
13
  };
12
14
  maxTurns?: number;
13
15
  contextBudgetTokens?: number;
@@ -18,6 +20,8 @@ export interface AgentEditResult {
18
20
  model?: {
19
21
  provider: Provider;
20
22
  model: string;
23
+ maxTokens?: number;
24
+ reasoningEffort?: string;
21
25
  };
22
26
  maxTurns?: number;
23
27
  contextBudgetTokens?: number;
@@ -162,12 +162,34 @@ export function editAgentFile(agentName, fields) {
162
162
  if (provider !== 'anthropic' && !modelName) {
163
163
  throw new AgentModelError(`A model name is required for provider '${provider}' — only anthropic has a default (claude-sonnet-5).`);
164
164
  }
165
+ const maxTokens = fields.model.maxTokens;
166
+ if (maxTokens !== undefined && (!Number.isInteger(maxTokens) || maxTokens < 1)) {
167
+ throw new AgentEditNotSupportedError('model.maxTokens must be a positive integer.');
168
+ }
169
+ const reasoningEffort = fields.model.reasoningEffort;
170
+ // Only AgentModelConfig's 'openai' variant carries this (see its own
171
+ // doc comment) — refusing rather than silently dropping it for
172
+ // another provider, same "don't guess" posture the model-name check
173
+ // right above already follows.
174
+ if (reasoningEffort !== undefined && provider !== 'openai') {
175
+ throw new AgentModelError(`model.reasoningEffort only applies to provider 'openai' — '${provider}' doesn't support it.`);
176
+ }
177
+ // Every field the form actually round-trips (not just provider/model)
178
+ // — omitting maxTokens/reasoningEffort here, the way this used to
179
+ // unconditionally build `{ provider, model }` alone, would silently
180
+ // wipe out a value that was already set the moment an operator
181
+ // changed anything else about the model through this same form.
182
+ const objectFields = [`provider: '${provider}'`, `model: ${tsStringLiteral(modelName)}`];
183
+ if (maxTokens !== undefined)
184
+ objectFields.push(`maxTokens: ${maxTokens}`);
185
+ if (reasoningEffort !== undefined)
186
+ objectFields.push(`reasoningEffort: ${tsStringLiteral(reasoningEffort)}`);
165
187
  edits.push({
166
188
  start: prop.initializer.getStart(sourceFile),
167
189
  end: prop.initializer.getEnd(),
168
- text: `{ provider: '${provider}', model: ${tsStringLiteral(modelName)} }`,
190
+ text: `{ ${objectFields.join(', ')} }`,
169
191
  });
170
- result.model = { provider, model: modelName };
192
+ result.model = { provider, model: modelName, maxTokens, reasoningEffort };
171
193
  // The property's own value is now correct, but a stale
172
194
  // "// reads ANTHROPIC_API_KEY" sitting right after it (from before a
173
195
  // provider change) would be actively misleading, not just cosmetic —
@@ -658,6 +658,7 @@ export const agentsConfigPageHtml = `<!doctype html>
658
658
  '<dt>Provider</dt><dd>' + escapeHtml(cfg.model.provider) + '</dd>' +
659
659
  '<dt>Model</dt><dd>' + escapeHtml(cfg.model.model || '(provider default)') + '</dd>' +
660
660
  '<dt>Max tokens</dt><dd>' + escapeHtml(cfg.model.maxTokens != null ? cfg.model.maxTokens : '(default)') + '</dd>' +
661
+ '<dt>Reasoning effort</dt><dd>' + escapeHtml(cfg.model.reasoningEffort || (cfg.model.provider === 'openai' ? '(provider default)' : 'n/a')) + '</dd>' +
661
662
  '</dl>' +
662
663
  '<form class="add-source" id="modelForm" style="display:none">' +
663
664
  '<label>Provider' +
@@ -670,6 +671,16 @@ export const agentsConfigPageHtml = `<!doctype html>
670
671
  '<label>Model name <span class="hint">(required for openai/deepseek; defaults to claude-sonnet-5 for anthropic)</span>' +
671
672
  '<input type="text" name="modelName" value="' + escapeHtml(cfg.model.model || '') + '">' +
672
673
  '</label>' +
674
+ '<label>Max tokens <span class="hint">(blank for provider default)</span>' +
675
+ '<input type="number" name="maxTokens" min="1" step="1" value="' + (cfg.model.maxTokens != null ? escapeHtml(cfg.model.maxTokens) : '') + '">' +
676
+ '</label>' +
677
+ '<label>Reasoning effort <span class="hint">(openai only — some reasoning models reject function tools unless this is \\\'none\\\'; leave unset otherwise)</span>' +
678
+ '<select name="reasoningEffort">' +
679
+ ['', 'none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'].map(function (v) {
680
+ return '<option value="' + v + '"' + (v === (cfg.model.reasoningEffort || '') ? ' selected' : '') + '>' + (v || '(unset)') + '</option>';
681
+ }).join('') +
682
+ '</select>' +
683
+ '</label>' +
673
684
  '<button type="submit">Save</button>' +
674
685
  '<button type="button" id="cancelModelBtn">Cancel</button>' +
675
686
  '<div class="error" id="modelError"></div>' +
@@ -793,6 +804,10 @@ export const agentsConfigPageHtml = `<!doctype html>
793
804
  var body = { model: { provider: data.get('provider') } };
794
805
  var modelName = data.get('modelName');
795
806
  if (modelName && modelName.trim()) body.model.model = modelName;
807
+ var maxTokens = data.get('maxTokens');
808
+ if (maxTokens && String(maxTokens).trim()) body.model.maxTokens = parseInt(maxTokens, 10);
809
+ var reasoningEffort = data.get('reasoningEffort');
810
+ if (reasoningEffort) body.model.reasoningEffort = reasoningEffort;
796
811
  fetch('/agents/' + encodeURIComponent(name), {
797
812
  method: 'PUT',
798
813
  headers: { 'content-type': 'application/json' },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loopengine",
3
- "version": "0.1.45",
3
+ "version": "0.1.46",
4
4
  "type": "module",
5
5
  "description": "Runtime for tool-using AI agents with a transparent ReAct loop.",
6
6
  "license": "MIT",