@miller-tech/uap 1.75.0 → 1.76.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.
- package/dist/.tsbuildinfo +1 -1
- package/dist/coordination/challenge-runner.d.ts.map +1 -1
- package/dist/coordination/challenge-runner.js +33 -36
- package/dist/coordination/challenge-runner.js.map +1 -1
- package/dist/coordination/database.d.ts.map +1 -1
- package/dist/coordination/database.js +3 -1
- package/dist/coordination/database.js.map +1 -1
- package/dist/coordination/service.d.ts +1 -1
- package/dist/coordination/service.d.ts.map +1 -1
- package/dist/coordination/service.js +3 -1
- package/dist/coordination/service.js.map +1 -1
- package/dist/delivery/agentic-executor.d.ts.map +1 -1
- package/dist/delivery/agentic-executor.js +19 -0
- package/dist/delivery/agentic-executor.js.map +1 -1
- package/dist/models/openai-compat-client.d.ts +8 -0
- package/dist/models/openai-compat-client.d.ts.map +1 -1
- package/dist/models/openai-compat-client.js +25 -0
- package/dist/models/openai-compat-client.js.map +1 -1
- package/dist/utils/model-slot-lease.d.ts +2 -8
- package/dist/utils/model-slot-lease.d.ts.map +1 -1
- package/dist/utils/model-slot-lease.js +42 -9
- package/dist/utils/model-slot-lease.js.map +1 -1
- package/package.json +1 -1
- package/src/policies/enforcers/__pycache__/_common.cpython-312.pyc +0 -0
- package/tools/agents/scripts/anthropic_proxy.py +16 -0
- package/tools/agents/tests/test_malformed_unclosed_think.py +71 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model-slot-lease.d.ts","sourceRoot":"","sources":["../../src/utils/model-slot-lease.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"model-slot-lease.d.ts","sourceRoot":"","sources":["../../src/utils/model-slot-lease.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAIjE,MAAM,WAAW,gBAAgB;IAC/B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,wDAAwD;IACxD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,uEAAuE;IACvE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oFAAoF;IACpF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6DAA6D;IAC7D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,6EAA6E;IAC7E,OAAO,CAAC,EAAE,mBAAmB,CAAC;CAC/B;AAID,8EAA8E;AAC9E,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAGvD;AAgCD,4EAA4E;AAC5E,wBAAsB,qBAAqB,CAAC,IAAI,GAAE,gBAAqB,GAAG,OAAO,CAAC,MAAM,CAAC,CAGxF;AAED,4DAA4D;AAC5D,wBAAsB,kBAAkB,CAAC,IAAI,GAAE,gBAAqB,GAAG,OAAO,CAAC,MAAM,CAAC,CAGrF;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EACd,IAAI,GAAE,gBAAqB,GAC1B,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,OAAO,EAAE,mBAAmB,CAAA;CAAE,CAAC,CAcnE;AAED,oEAAoE;AACpE,wBAAsB,aAAa,CAAC,CAAC,EACnC,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,IAAI,GAAE,gBAAqB,GAC1B,OAAO,CAAC,CAAC,CAAC,CAgCZ"}
|
|
@@ -6,10 +6,21 @@
|
|
|
6
6
|
* `withModelSlot(holder, fn)` acquires a slot (waiting when the budget is full),
|
|
7
7
|
* runs `fn`, and releases — the right wrapper around any model call / agent spawn.
|
|
8
8
|
*/
|
|
9
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
9
10
|
import { CoordinationService } from '../coordination/service.js';
|
|
10
11
|
import { getModelSlotBudget } from './model-slots.js';
|
|
11
12
|
import { loadUapConfigRaw } from './config-loader.js';
|
|
12
13
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
14
|
+
/** Heuristic: does this inference error indicate the backend is saturated? */
|
|
15
|
+
export function isExhaustionError(err) {
|
|
16
|
+
const m = err instanceof Error ? err.message : String(err);
|
|
17
|
+
return /\((?:429|408|502|503|504|529)\)|timed out|timeout|overloaded|too many requests|no slot|slots? (?:are )?full/i.test(m);
|
|
18
|
+
}
|
|
19
|
+
// Re-entrancy guard: nested withModelSlot calls within one async context reuse
|
|
20
|
+
// the outer lease (count as one slot) so layered wrappers can't deadlock or
|
|
21
|
+
// double-count. Cross-process callers (subprocesses) have a fresh store and
|
|
22
|
+
// lease independently, which is correct.
|
|
23
|
+
const holding = new AsyncLocalStorage();
|
|
13
24
|
function adaptiveEnabled(cwd) {
|
|
14
25
|
try {
|
|
15
26
|
const raw = loadUapConfigRaw(cwd ?? process.cwd());
|
|
@@ -22,7 +33,9 @@ function adaptiveEnabled(cwd) {
|
|
|
22
33
|
async function staticBudget(opts) {
|
|
23
34
|
if (opts.budget && opts.budget > 0)
|
|
24
35
|
return opts.budget;
|
|
25
|
-
|
|
36
|
+
// No probing on the per-call hot path — use env/config/cache/default. The
|
|
37
|
+
// cache is warmed by orchestrators (warmModelSlotBudget) and `uap coord slots`.
|
|
38
|
+
return (await getModelSlotBudget(opts.cwd, { probe: false })).budget;
|
|
26
39
|
}
|
|
27
40
|
/** Effective budget = min(static, adaptive). When backpressure is active the
|
|
28
41
|
* adaptive limit is below the static ceiling, throttling the whole fleet. */
|
|
@@ -64,20 +77,40 @@ export async function acquireModelSlot(holder, opts = {}) {
|
|
|
64
77
|
}
|
|
65
78
|
/** Run `fn` while holding a model slot (acquire → fn → release). */
|
|
66
79
|
export async function withModelSlot(holder, fn, opts = {}) {
|
|
67
|
-
|
|
80
|
+
// Already holding a slot in this async context → reuse it (re-entrant).
|
|
81
|
+
if (holding.getStore())
|
|
82
|
+
return fn();
|
|
83
|
+
// Fail-open: if the coordination layer is unavailable, run the call rather
|
|
84
|
+
// than block real work on infra problems.
|
|
85
|
+
let acquired;
|
|
86
|
+
try {
|
|
87
|
+
acquired = await acquireModelSlot(holder, opts);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return fn();
|
|
91
|
+
}
|
|
92
|
+
const { leaseId, service } = acquired;
|
|
68
93
|
// Couldn't get a slot before the deadline → the backend is saturated.
|
|
69
94
|
if (leaseId === null && adaptiveEnabled(opts.cwd)) {
|
|
70
|
-
|
|
95
|
+
try {
|
|
96
|
+
service.recordModelExhaustion(await staticBudget({ ...opts, service }));
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
/* ignore */
|
|
100
|
+
}
|
|
71
101
|
}
|
|
72
102
|
try {
|
|
73
|
-
|
|
74
|
-
if (adaptiveEnabled(opts.cwd))
|
|
75
|
-
service.recordModelSuccess(await staticBudget({ ...opts, service }));
|
|
76
|
-
return result;
|
|
103
|
+
return await holding.run(true, fn);
|
|
77
104
|
}
|
|
78
105
|
finally {
|
|
79
|
-
if (leaseId !== null)
|
|
80
|
-
|
|
106
|
+
if (leaseId !== null) {
|
|
107
|
+
try {
|
|
108
|
+
service.releaseModelSlot(leaseId);
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
/* ignore */
|
|
112
|
+
}
|
|
113
|
+
}
|
|
81
114
|
}
|
|
82
115
|
}
|
|
83
116
|
//# sourceMappingURL=model-slot-lease.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model-slot-lease.js","sourceRoot":"","sources":["../../src/utils/model-slot-lease.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAgBtD,MAAM,KAAK,GAAG,CAAC,EAAU,EAAiB,EAAE,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;AAEnF,SAAS,eAAe,CAAC,GAAY;IACnC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,gBAAgB,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAyD,CAAC;QAC3G,OAAO,GAAG,EAAE,gBAAgB,EAAE,QAAQ,KAAK,KAAK,CAAC,CAAC,yCAAyC;IAC7F,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,KAAK,UAAU,YAAY,CAAC,IAAsB;IAChD,IAAI,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC,MAAM,CAAC;IACvD,OAAO,CAAC,MAAM,kBAAkB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"model-slot-lease.js","sourceRoot":"","sources":["../../src/utils/model-slot-lease.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
Binary file
|
|
@@ -6339,6 +6339,22 @@ def _looks_malformed_tool_payload(text: str) -> bool:
|
|
|
6339
6339
|
if not text.strip():
|
|
6340
6340
|
return False
|
|
6341
6341
|
|
|
6342
|
+
# 2026-06-27: Also strip a trailing UNCLOSED <think> (opener present, no
|
|
6343
|
+
# matching </think>). Under `--reasoning auto` the model frequently runs out
|
|
6344
|
+
# of its token budget mid-reasoning, emitting
|
|
6345
|
+
# "<think> ...let me write files via multiple sandbox calls... args="
|
|
6346
|
+
# with no </think>. That is TRUNCATED reasoning, not a malformed tool call —
|
|
6347
|
+
# but the balanced-only strip above left it intact, so the meta-tool talk
|
|
6348
|
+
# inside it tripped the structural-marker / apology branches below
|
|
6349
|
+
# (false-positive malformed_payload that stalled agentic builds, e.g. the
|
|
6350
|
+
# Octopus Invaders generation: ~11 false rejections in 40 min). Drop from the
|
|
6351
|
+
# first opener to end; KEEP any text before it so a genuine malformed payload
|
|
6352
|
+
# preceding the reasoning is still detected.
|
|
6353
|
+
if "<think>" in text and "</think>" not in text:
|
|
6354
|
+
text = text[: text.index("<think>")]
|
|
6355
|
+
if not text.strip():
|
|
6356
|
+
return False
|
|
6357
|
+
|
|
6342
6358
|
# 2026-05-12: Strip orphan </parameter> and </function> closers that
|
|
6343
6359
|
# have no matching opener. Qwen3.6 leaks these training residuals
|
|
6344
6360
|
# after its visible answer when forced into tool_choice='required'
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""An UNCLOSED <think> block must NOT be classified as a malformed tool payload.
|
|
3
|
+
|
|
4
|
+
Under llama-server `--reasoning auto`, Qwen3.6 emits <think> reasoning. When it
|
|
5
|
+
runs out of its token budget mid-thought it produces an *unclosed* block, e.g.
|
|
6
|
+
|
|
7
|
+
"<think> The sandbox works. Now write the remaining files via multiple
|
|
8
|
+
sandbox calls... args="
|
|
9
|
+
|
|
10
|
+
with no </think>. That is TRUNCATED reasoning, not a malformed tool call — but
|
|
11
|
+
the prior detector only stripped *balanced* <think>...</think>, so the meta-tool
|
|
12
|
+
talk inside the unclosed block tripped the structural-marker branch and the proxy
|
|
13
|
+
rejected/retried the turn (observed: ~11 false rejections in 40 min stalling the
|
|
14
|
+
Octopus Invaders agentic build). `_looks_malformed_tool_payload` now also strips a
|
|
15
|
+
trailing unclosed <think> (keeping any text BEFORE the opener so a genuine
|
|
16
|
+
malformed payload preceding the reasoning is still caught).
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import importlib.util
|
|
20
|
+
import unittest
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _load_proxy_module():
|
|
25
|
+
proxy_path = Path(__file__).resolve().parents[1] / "scripts" / "anthropic_proxy.py"
|
|
26
|
+
spec = importlib.util.spec_from_file_location("anthropic_proxy", proxy_path)
|
|
27
|
+
module = importlib.util.module_from_spec(spec)
|
|
28
|
+
spec.loader.exec_module(module)
|
|
29
|
+
return module
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
proxy = _load_proxy_module()
|
|
33
|
+
_malformed = proxy._looks_malformed_tool_payload
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class TestUnclosedThinkNotMalformed(unittest.TestCase):
|
|
37
|
+
def test_unclosed_think_with_meta_tool_talk_is_not_malformed(self):
|
|
38
|
+
# The exact failure shape that stalled the Octopus build.
|
|
39
|
+
text = (
|
|
40
|
+
"<think> The sandbox approach works. Now I need to write all the "
|
|
41
|
+
"remaining files. Let me write them efficiently using multiple "
|
|
42
|
+
"sandbox calls in parallel. I'll write the files in dependency "
|
|
43
|
+
"order: 1. config.js (done) 2. args="
|
|
44
|
+
)
|
|
45
|
+
self.assertFalse(_malformed(text))
|
|
46
|
+
|
|
47
|
+
def test_unclosed_think_with_function_words_is_not_malformed(self):
|
|
48
|
+
# meta-tool talk that mentions <function= / <parameter inside reasoning
|
|
49
|
+
text = "<think> I should call <function=Write> with <parameter=path>..."
|
|
50
|
+
self.assertFalse(_malformed(text))
|
|
51
|
+
|
|
52
|
+
def test_balanced_think_still_not_malformed(self):
|
|
53
|
+
text = "<think> planning... </think>"
|
|
54
|
+
self.assertFalse(_malformed(text))
|
|
55
|
+
|
|
56
|
+
def test_genuine_malformed_payload_without_think_still_detected(self):
|
|
57
|
+
# No <think> at all — a real leaked tool-call fragment must still trip.
|
|
58
|
+
self.assertTrue(_malformed("<function=Write><parameter=path>x</parameter>"))
|
|
59
|
+
|
|
60
|
+
def test_malformed_payload_BEFORE_unclosed_think_still_detected(self):
|
|
61
|
+
# Keep text before the opener: a real malformed payload preceding the
|
|
62
|
+
# reasoning must still be caught.
|
|
63
|
+
text = "<function=Write><parameter=p>v</parameter> <think> now reasoning..."
|
|
64
|
+
self.assertTrue(_malformed(text))
|
|
65
|
+
|
|
66
|
+
def test_clean_prose_with_no_tool_markup_is_not_malformed(self):
|
|
67
|
+
self.assertFalse(_malformed("Here is the summary of what I did."))
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
if __name__ == "__main__":
|
|
71
|
+
unittest.main()
|