open-agents-ai 0.187.371 → 0.187.372
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/dist/index.js +227 -8
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -81,6 +81,7 @@ function loadConfigFile() {
|
|
|
81
81
|
if (typeof parsed.dryRun === "boolean") result.dryRun = parsed.dryRun;
|
|
82
82
|
if (typeof parsed.verbose === "boolean") result.verbose = parsed.verbose;
|
|
83
83
|
if (typeof parsed.dbPath === "string") result.dbPath = parsed.dbPath;
|
|
84
|
+
if (typeof parsed.thinking === "boolean") result.thinking = parsed.thinking;
|
|
84
85
|
return result;
|
|
85
86
|
} catch {
|
|
86
87
|
return {};
|
|
@@ -515672,18 +515673,38 @@ function stripThinkBlocks(s2) {
|
|
|
515672
515673
|
function computeEffectiveThink(params) {
|
|
515673
515674
|
if (process.env["OA_FORCE_NO_THINK"] === "1")
|
|
515674
515675
|
return false;
|
|
515676
|
+
if (params.suppressed)
|
|
515677
|
+
return false;
|
|
515675
515678
|
if (params.hasTools)
|
|
515676
515679
|
return false;
|
|
515677
515680
|
if (typeof params.requestThink === "boolean")
|
|
515678
515681
|
return params.requestThink;
|
|
515679
|
-
if (process.env["OA_THINK_AUTO"]
|
|
515682
|
+
if (process.env["OA_THINK_AUTO"] !== "0" && Array.isArray(params.messages)) {
|
|
515680
515683
|
const blob = params.messages.filter((m2) => m2.role === "user" || m2.role === "system").map((m2) => typeof m2.content === "string" ? m2.content : "").join("\n").toLowerCase();
|
|
515681
|
-
if (/\b(plan|decompose|analyze(?:\s+complex)?|step\s*by\s*step|reason through|think through)\b/.test(blob)) {
|
|
515684
|
+
if (/\b(plan|decompose|analyze(?:\s+complex)?|step\s*by\s*step|reason through|think through|reason step)\b/.test(blob)) {
|
|
515682
515685
|
return true;
|
|
515683
515686
|
}
|
|
515684
515687
|
}
|
|
515685
515688
|
return params.defaultThink;
|
|
515686
515689
|
}
|
|
515690
|
+
function classifyThinkOutcome(raw) {
|
|
515691
|
+
if (!raw)
|
|
515692
|
+
return "empty_after_strip";
|
|
515693
|
+
const hasOpen = /<think>/i.test(raw);
|
|
515694
|
+
const hasClose = /<\/think>/i.test(raw);
|
|
515695
|
+
if (hasOpen && !hasClose)
|
|
515696
|
+
return "unclosed_think";
|
|
515697
|
+
const stripped = stripThinkBlocks(raw);
|
|
515698
|
+
if (stripped.trim().length < 2)
|
|
515699
|
+
return "empty_after_strip";
|
|
515700
|
+
if (hasOpen && hasClose) {
|
|
515701
|
+
const thinkLen = raw.length - stripped.length;
|
|
515702
|
+
if (thinkLen > raw.length * 0.9 && stripped.trim().length < 40) {
|
|
515703
|
+
return "runaway_think";
|
|
515704
|
+
}
|
|
515705
|
+
}
|
|
515706
|
+
return null;
|
|
515707
|
+
}
|
|
515687
515708
|
var SYSTEM_PROMPT, SYSTEM_PROMPT_MEDIUM, SYSTEM_PROMPT_SMALL, VISUAL_TOOLS, AUDIO_TOOLS, SOCIAL_TOOLS, SPATIAL_TOOLS, CODE_TOOLS, AgenticRunner, OllamaAgenticBackend;
|
|
515688
515709
|
var init_agenticRunner = __esm({
|
|
515689
515710
|
"packages/orchestrator/dist/agenticRunner.js"() {
|
|
@@ -516506,6 +516527,40 @@ ${body}`;
|
|
|
516506
516527
|
}
|
|
516507
516528
|
}
|
|
516508
516529
|
}
|
|
516530
|
+
/**
|
|
516531
|
+
* Think-loop-guard runner hook. Called once per turn at the top of the
|
|
516532
|
+
* agentic loop. Responsibilities:
|
|
516533
|
+
* 1. Consume OA_THINK_GUARD_RESET env var (written by /think reset) to
|
|
516534
|
+
* clear a prior suppression — the CLI can't talk to the backend
|
|
516535
|
+
* directly, so it drops a timestamp in the env and we pick it up.
|
|
516536
|
+
* 2. Emit a one-shot user-visible warning the first turn after the
|
|
516537
|
+
* guard trips, so the user knows why answers suddenly look different.
|
|
516538
|
+
*/
|
|
516539
|
+
_lastThinkGuardResetAt = 0;
|
|
516540
|
+
_maybeApplyThinkGuard() {
|
|
516541
|
+
const resetRaw = process.env["OA_THINK_GUARD_RESET"];
|
|
516542
|
+
if (resetRaw) {
|
|
516543
|
+
const ts = Number(resetRaw);
|
|
516544
|
+
if (Number.isFinite(ts) && ts > this._lastThinkGuardResetAt) {
|
|
516545
|
+
this._lastThinkGuardResetAt = ts;
|
|
516546
|
+
if (typeof this.backend.resetThinkGuard === "function") {
|
|
516547
|
+
this.backend.resetThinkGuard();
|
|
516548
|
+
this.emit({
|
|
516549
|
+
type: "status",
|
|
516550
|
+
content: "🧠 Think-guard cleared — reasoning mode will re-enable on the next eligible request.",
|
|
516551
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
516552
|
+
});
|
|
516553
|
+
}
|
|
516554
|
+
}
|
|
516555
|
+
}
|
|
516556
|
+
if (typeof this.backend.consumeSuppressionNotice === "function" && this.backend.consumeSuppressionNotice()) {
|
|
516557
|
+
this.emit({
|
|
516558
|
+
type: "status",
|
|
516559
|
+
content: "⚠ Think-mode auto-suppressed — two consecutive empty/unclosed-<think> responses detected. Continuing with direct answers. Use `/think reset` to retry.",
|
|
516560
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
516561
|
+
});
|
|
516562
|
+
}
|
|
516563
|
+
}
|
|
516509
516564
|
/**
|
|
516510
516565
|
* Detect repetition in recent tool calls.
|
|
516511
516566
|
* Returns a score 0-1 where 1 = fully repetitive (stuck in a loop).
|
|
@@ -516784,6 +516839,7 @@ TASK: ${task}` : task;
|
|
|
516784
516839
|
}
|
|
516785
516840
|
for (let turn = 0; turn < this.options.maxTurns; turn++) {
|
|
516786
516841
|
clearTurnState(this._appState);
|
|
516842
|
+
this._maybeApplyThinkGuard();
|
|
516787
516843
|
if (this._paused) {
|
|
516788
516844
|
const shouldContinue = await this.waitIfPaused();
|
|
516789
516845
|
if (!shouldContinue) {
|
|
@@ -518264,6 +518320,7 @@ You have ${this.options.maxTurns} more turns. Continue making progress. Call tas
|
|
|
518264
518320
|
messages2.push(...compacted);
|
|
518265
518321
|
}
|
|
518266
518322
|
for (let turn = 0; turn < this.options.maxTurns; turn++) {
|
|
518323
|
+
this._maybeApplyThinkGuard();
|
|
518267
518324
|
if (this._paused) {
|
|
518268
518325
|
const shouldContinue = await this.waitIfPaused();
|
|
518269
518326
|
if (!shouldContinue) {
|
|
@@ -521120,13 +521177,23 @@ ${description}`
|
|
|
521120
521177
|
return resp;
|
|
521121
521178
|
}
|
|
521122
521179
|
};
|
|
521123
|
-
OllamaAgenticBackend = class {
|
|
521180
|
+
OllamaAgenticBackend = class _OllamaAgenticBackend {
|
|
521124
521181
|
baseUrl;
|
|
521125
521182
|
model;
|
|
521126
521183
|
apiKey;
|
|
521127
521184
|
thinking;
|
|
521128
521185
|
/** Abort signal — set by the runner so /stop can cancel in-flight requests */
|
|
521129
521186
|
_abortSignal = null;
|
|
521187
|
+
// ── Think-loop guard (0.187.372) ──────────────────────────────────────
|
|
521188
|
+
// If the model keeps producing empty / unclosed-think responses, we
|
|
521189
|
+
// assume Qwen3 dual-mode is looping and start suppressing think for
|
|
521190
|
+
// this backend instance. User can clear via /think reset.
|
|
521191
|
+
_thinkFailStreak = 0;
|
|
521192
|
+
_thinkSuccessStreak = 0;
|
|
521193
|
+
_thinkSuppressed = false;
|
|
521194
|
+
_thinkSuppressedNotified = false;
|
|
521195
|
+
static _thinkFailThreshold = 2;
|
|
521196
|
+
static _thinkRecoveryThreshold = 6;
|
|
521130
521197
|
/** Multi-key pool — round-robin rotation per request for load distribution */
|
|
521131
521198
|
_keyPool = [];
|
|
521132
521199
|
_keyIndex = 0;
|
|
@@ -521148,6 +521215,61 @@ ${description}`
|
|
|
521148
521215
|
setAbortSignal(signal) {
|
|
521149
521216
|
this._abortSignal = signal;
|
|
521150
521217
|
}
|
|
521218
|
+
/** Is think currently auto-suppressed by the loop-guard? */
|
|
521219
|
+
isThinkSuppressed() {
|
|
521220
|
+
return this._thinkSuppressed;
|
|
521221
|
+
}
|
|
521222
|
+
/** Clear the loop-guard — lets think re-enable on the next eligible request. */
|
|
521223
|
+
resetThinkGuard() {
|
|
521224
|
+
this._thinkFailStreak = 0;
|
|
521225
|
+
this._thinkSuccessStreak = 0;
|
|
521226
|
+
this._thinkSuppressed = false;
|
|
521227
|
+
this._thinkSuppressedNotified = false;
|
|
521228
|
+
}
|
|
521229
|
+
/**
|
|
521230
|
+
* Feed a completed assistant response into the loop-guard. We only
|
|
521231
|
+
* update counters on responses that WERE think=true — otherwise
|
|
521232
|
+
* think-off responses (the vast majority) would drive the counters
|
|
521233
|
+
* and mask the failure signal we're trying to detect.
|
|
521234
|
+
*
|
|
521235
|
+
* Failure classes (per classifyThinkOutcome) bump the fail streak.
|
|
521236
|
+
* Healthy think-mode responses bump the success streak and, past a
|
|
521237
|
+
* threshold, clear a prior suppression so think can come back on if
|
|
521238
|
+
* the model is behaving again.
|
|
521239
|
+
*
|
|
521240
|
+
* Returns the classification so callers can decide whether to
|
|
521241
|
+
* emit a warning / retry.
|
|
521242
|
+
*/
|
|
521243
|
+
recordThinkOutcome(raw, wasThinkRequested) {
|
|
521244
|
+
if (!wasThinkRequested)
|
|
521245
|
+
return null;
|
|
521246
|
+
const cls = classifyThinkOutcome(raw);
|
|
521247
|
+
if (cls !== null) {
|
|
521248
|
+
this._thinkFailStreak++;
|
|
521249
|
+
this._thinkSuccessStreak = 0;
|
|
521250
|
+
if (this._thinkFailStreak >= _OllamaAgenticBackend._thinkFailThreshold && !this._thinkSuppressed) {
|
|
521251
|
+
this._thinkSuppressed = true;
|
|
521252
|
+
}
|
|
521253
|
+
} else {
|
|
521254
|
+
this._thinkSuccessStreak++;
|
|
521255
|
+
this._thinkFailStreak = 0;
|
|
521256
|
+
if (this._thinkSuppressed && this._thinkSuccessStreak >= _OllamaAgenticBackend._thinkRecoveryThreshold) {
|
|
521257
|
+
this._thinkSuppressed = false;
|
|
521258
|
+
this._thinkSuppressedNotified = false;
|
|
521259
|
+
}
|
|
521260
|
+
}
|
|
521261
|
+
return cls;
|
|
521262
|
+
}
|
|
521263
|
+
/** Pick up the one-shot "notify about suppression" flag. Returns true
|
|
521264
|
+
* the first time it's called after a trip; false thereafter until
|
|
521265
|
+
* the guard resets. Used by the runner to emit a single warning. */
|
|
521266
|
+
consumeSuppressionNotice() {
|
|
521267
|
+
if (this._thinkSuppressed && !this._thinkSuppressedNotified) {
|
|
521268
|
+
this._thinkSuppressedNotified = true;
|
|
521269
|
+
return true;
|
|
521270
|
+
}
|
|
521271
|
+
return false;
|
|
521272
|
+
}
|
|
521151
521273
|
/** Build auth headers — adapts to provider (Bearer for most, x-api-key for Anthropic).
|
|
521152
521274
|
* When a key pool is set, round-robins through keys per request. */
|
|
521153
521275
|
authHeaders() {
|
|
@@ -521176,7 +521298,8 @@ ${description}`
|
|
|
521176
521298
|
requestThink: request.think,
|
|
521177
521299
|
defaultThink: this.thinking,
|
|
521178
521300
|
hasTools: Array.isArray(request.tools) && request.tools.length > 0,
|
|
521179
|
-
messages: cleanedMessages
|
|
521301
|
+
messages: cleanedMessages,
|
|
521302
|
+
suppressed: this._thinkSuppressed
|
|
521180
521303
|
});
|
|
521181
521304
|
let effectiveMaxTokens = request.maxTokens;
|
|
521182
521305
|
if (effectiveThink === true && (effectiveMaxTokens ?? 0) < 4096) {
|
|
@@ -521207,6 +521330,71 @@ ${description}`
|
|
|
521207
521330
|
const data = await resp.json();
|
|
521208
521331
|
const choices = data.choices ?? [];
|
|
521209
521332
|
const usage = data.usage;
|
|
521333
|
+
const firstChoice = choices[0];
|
|
521334
|
+
const responseText = firstChoice ? String(firstChoice.message?.content ?? "") : "";
|
|
521335
|
+
const outcome = this.recordThinkOutcome(responseText, effectiveThink === true);
|
|
521336
|
+
if (outcome !== null && effectiveThink === true) {
|
|
521337
|
+
const justSuppressed = this._thinkSuppressed && this._thinkFailStreak === _OllamaAgenticBackend._thinkFailThreshold;
|
|
521338
|
+
if (justSuppressed || outcome === "empty_after_strip" || outcome === "unclosed_think") {
|
|
521339
|
+
const retryBody = {
|
|
521340
|
+
model: this.model,
|
|
521341
|
+
messages: cleanedMessages,
|
|
521342
|
+
tools: request.tools,
|
|
521343
|
+
temperature: request.temperature,
|
|
521344
|
+
max_tokens: request.maxTokens,
|
|
521345
|
+
think: false
|
|
521346
|
+
};
|
|
521347
|
+
try {
|
|
521348
|
+
const retryOpts = {
|
|
521349
|
+
method: "POST",
|
|
521350
|
+
headers: this.authHeaders(),
|
|
521351
|
+
body: JSON.stringify(retryBody)
|
|
521352
|
+
};
|
|
521353
|
+
if (this._abortSignal)
|
|
521354
|
+
retryOpts.signal = this._abortSignal;
|
|
521355
|
+
const retryResp = await fetch(`${this.baseUrl}/v1/chat/completions`, retryOpts);
|
|
521356
|
+
if (retryResp.ok) {
|
|
521357
|
+
const retryData = await retryResp.json();
|
|
521358
|
+
const retryChoices = retryData.choices ?? [];
|
|
521359
|
+
const retryUsage = retryData.usage;
|
|
521360
|
+
if (retryChoices.length > 0) {
|
|
521361
|
+
return {
|
|
521362
|
+
choices: retryChoices.map((c8) => {
|
|
521363
|
+
const msg = c8.message;
|
|
521364
|
+
const toolCalls = msg.tool_calls ?? [];
|
|
521365
|
+
return {
|
|
521366
|
+
message: {
|
|
521367
|
+
content: msg.content || null,
|
|
521368
|
+
toolCalls: toolCalls.length > 0 ? toolCalls.map((tc) => {
|
|
521369
|
+
const fn = tc.function;
|
|
521370
|
+
let args;
|
|
521371
|
+
try {
|
|
521372
|
+
args = typeof fn.arguments === "string" ? JSON.parse(fn.arguments) : fn.arguments ?? {};
|
|
521373
|
+
} catch {
|
|
521374
|
+
const repaired = repairJson(fn.arguments);
|
|
521375
|
+
args = repaired ?? { _raw: fn.arguments };
|
|
521376
|
+
}
|
|
521377
|
+
return {
|
|
521378
|
+
id: tc.id || crypto.randomUUID(),
|
|
521379
|
+
name: fn.name,
|
|
521380
|
+
arguments: args
|
|
521381
|
+
};
|
|
521382
|
+
}) : void 0
|
|
521383
|
+
}
|
|
521384
|
+
};
|
|
521385
|
+
}),
|
|
521386
|
+
usage: retryUsage ? {
|
|
521387
|
+
totalTokens: retryUsage.total_tokens ?? 0,
|
|
521388
|
+
promptTokens: retryUsage.prompt_tokens,
|
|
521389
|
+
completionTokens: retryUsage.completion_tokens
|
|
521390
|
+
} : void 0
|
|
521391
|
+
};
|
|
521392
|
+
}
|
|
521393
|
+
}
|
|
521394
|
+
} catch {
|
|
521395
|
+
}
|
|
521396
|
+
}
|
|
521397
|
+
}
|
|
521210
521398
|
return {
|
|
521211
521399
|
choices: choices.map((c8) => {
|
|
521212
521400
|
const msg = c8.message;
|
|
@@ -521350,7 +521538,8 @@ ${description}`
|
|
|
521350
521538
|
requestThink: request.think,
|
|
521351
521539
|
defaultThink: this.thinking,
|
|
521352
521540
|
hasTools: Array.isArray(request.tools) && request.tools.length > 0,
|
|
521353
|
-
messages: cleanedMessages
|
|
521541
|
+
messages: cleanedMessages,
|
|
521542
|
+
suppressed: this._thinkSuppressed
|
|
521354
521543
|
});
|
|
521355
521544
|
let effectiveMaxTokens = request.maxTokens;
|
|
521356
521545
|
if (effectiveThink === true && (effectiveMaxTokens ?? 0) < 4096) {
|
|
@@ -521382,6 +521571,9 @@ ${description}`
|
|
|
521382
521571
|
}
|
|
521383
521572
|
let sseBuffer = "";
|
|
521384
521573
|
const decoder = new TextDecoder();
|
|
521574
|
+
let accumulatedContent = "";
|
|
521575
|
+
let accumulatedThinking = "";
|
|
521576
|
+
let sawReasoningTokens = false;
|
|
521385
521577
|
for await (const rawChunk of resp.body) {
|
|
521386
521578
|
sseBuffer += decoder.decode(rawChunk, { stream: true });
|
|
521387
521579
|
const parts = sseBuffer.split("\n\n");
|
|
@@ -521390,8 +521582,10 @@ ${description}`
|
|
|
521390
521582
|
const line = part.trim();
|
|
521391
521583
|
if (!line)
|
|
521392
521584
|
continue;
|
|
521393
|
-
if (line === "data: [DONE]")
|
|
521585
|
+
if (line === "data: [DONE]") {
|
|
521586
|
+
this._finalizeStreamGuard(effectiveThink, accumulatedContent, accumulatedThinking, sawReasoningTokens);
|
|
521394
521587
|
return;
|
|
521588
|
+
}
|
|
521395
521589
|
if (!line.startsWith("data: "))
|
|
521396
521590
|
continue;
|
|
521397
521591
|
try {
|
|
@@ -521415,9 +521609,12 @@ ${description}`
|
|
|
521415
521609
|
const finishReason = choice.finish_reason;
|
|
521416
521610
|
const reasoningToken = delta?.reasoning ?? delta?.reasoning_content;
|
|
521417
521611
|
if (reasoningToken) {
|
|
521612
|
+
sawReasoningTokens = true;
|
|
521613
|
+
accumulatedThinking += reasoningToken;
|
|
521418
521614
|
yield { type: "content", content: reasoningToken, thinking: true };
|
|
521419
521615
|
}
|
|
521420
521616
|
if (delta?.content) {
|
|
521617
|
+
accumulatedContent += delta.content;
|
|
521421
521618
|
yield { type: "content", content: delta.content };
|
|
521422
521619
|
}
|
|
521423
521620
|
const tcDeltas = delta?.tool_calls;
|
|
@@ -521451,6 +521648,23 @@ ${description}`
|
|
|
521451
521648
|
}
|
|
521452
521649
|
}
|
|
521453
521650
|
}
|
|
521651
|
+
this._finalizeStreamGuard(effectiveThink, accumulatedContent, accumulatedThinking, sawReasoningTokens);
|
|
521652
|
+
}
|
|
521653
|
+
/** Reconstruct a raw-looking assistant response from the streamed
|
|
521654
|
+
* parts, then feed it into the loop-guard. Used at stream end (both
|
|
521655
|
+
* the [DONE] case and the fell-off-the-end case). */
|
|
521656
|
+
_finalizeStreamGuard(thinkRequested, content, thinking, hadReasoningTokens) {
|
|
521657
|
+
if (!thinkRequested) {
|
|
521658
|
+
this.recordThinkOutcome(content, false);
|
|
521659
|
+
return;
|
|
521660
|
+
}
|
|
521661
|
+
let rawLike;
|
|
521662
|
+
if (hadReasoningTokens && thinking) {
|
|
521663
|
+
rawLike = `<think>${thinking}</think>${content}`;
|
|
521664
|
+
} else {
|
|
521665
|
+
rawLike = content;
|
|
521666
|
+
}
|
|
521667
|
+
this.recordThinkOutcome(rawLike, true);
|
|
521454
521668
|
}
|
|
521455
521669
|
};
|
|
521456
521670
|
}
|
|
@@ -546170,13 +546384,18 @@ Clone a new voice: /voice clone <wav-file> [name]`);
|
|
|
546170
546384
|
if (token === "status" || token === "?") {
|
|
546171
546385
|
const cur = ctx3.config.thinking ?? false;
|
|
546172
546386
|
renderInfo2(`Thinking mode: ${cur ? "on" : "off"} — ${desc(cur)}`);
|
|
546173
|
-
if (process.env["OA_THINK_AUTO"]
|
|
546387
|
+
if (process.env["OA_THINK_AUTO"] !== "0") renderInfo2("Auto-heuristic active (set OA_THINK_AUTO=0 to disable) — user messages with plan/decompose/analyze/step-by-step/reason-through auto-flip to think=on, tool calls stay off.");
|
|
546174
546388
|
if (process.env["OA_FORCE_NO_THINK"] === "1") renderWarning2("OA_FORCE_NO_THINK=1 forces off regardless of /think setting");
|
|
546175
546389
|
return "handled";
|
|
546176
546390
|
}
|
|
546177
546391
|
if (token === "auto") {
|
|
546178
546392
|
process.env["OA_THINK_AUTO"] = "1";
|
|
546179
|
-
renderInfo2("Thinking auto-heuristic enabled
|
|
546393
|
+
renderInfo2("Thinking auto-heuristic enabled (default since 0.187.372). User message containing plan/decompose/analyze/step-by-step/reason-through auto-flips think=on; tool calls still force off. Disable with OA_THINK_AUTO=0.");
|
|
546394
|
+
return "handled";
|
|
546395
|
+
}
|
|
546396
|
+
if (token === "reset" || token === "clear") {
|
|
546397
|
+
process.env["OA_THINK_GUARD_RESET"] = String(Date.now());
|
|
546398
|
+
renderInfo2("Loop-guard reset requested. If think was auto-suppressed after empty/unclosed-think responses, it will re-enable on the next eligible request.");
|
|
546180
546399
|
return "handled";
|
|
546181
546400
|
}
|
|
546182
546401
|
let isOn;
|
package/package.json
CHANGED