@cubicecho/agent-core 2.1.1 → 2.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.
- package/dist/side-task.js +37 -12
- package/package.json +1 -1
package/dist/side-task.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import OpenAI from "openai";
|
|
2
|
+
import { capabilitiesFor, negotiate } from "./capabilities.js";
|
|
2
3
|
import { getClient } from "./client.js";
|
|
3
4
|
import { errorMessage } from "./errors.js";
|
|
4
5
|
import { isTransient } from "./retry.js";
|
|
@@ -13,11 +14,13 @@ import { isTransient } from "./retry.js";
|
|
|
13
14
|
* the OpenAI-compatible spelling and `chat_template_kwargs` the llama.cpp/vLLM one; servers
|
|
14
15
|
* disagree about which they take, so send both. One that rejects the unknown fields gets a
|
|
15
16
|
* single retry without them, and is not offered them again.
|
|
17
|
+
*
|
|
18
|
+
* Only the second half is latched here. `reasoning_effort` is a field `negotiate` already knows
|
|
19
|
+
* how to be refused, so it is sent under `ModelCapabilities.reasoningEffort` instead — which
|
|
20
|
+
* both narrows the fallback below to the field it is really about, and shares the answer with
|
|
21
|
+
* the runs on that model rather than keeping a second opinion about it.
|
|
16
22
|
*/
|
|
17
|
-
const NO_THINKING = {
|
|
18
|
-
reasoning_effort: "none",
|
|
19
|
-
chat_template_kwargs: { enable_thinking: false },
|
|
20
|
-
};
|
|
23
|
+
const NO_THINKING = { chat_template_kwargs: { enable_thinking: false } };
|
|
21
24
|
/**
|
|
22
25
|
* The models that turned out not to take the hints, by endpoint and model.
|
|
23
26
|
*
|
|
@@ -27,9 +30,12 @@ const NO_THINKING = {
|
|
|
27
30
|
* second from ever being asked.
|
|
28
31
|
*
|
|
29
32
|
* The model belongs in the key for the same reason. One base URL is routinely many models —
|
|
30
|
-
* OpenRouter, LiteLLM, vLLM serving several at once — and whether `
|
|
31
|
-
*
|
|
32
|
-
* alone, the first model to refuse spoke for every model on it.
|
|
33
|
+
* OpenRouter, LiteLLM, vLLM serving several at once — and whether `chat_template_kwargs` reaches
|
|
34
|
+
* a chat template that reads it is a property of the model behind the route, not of the route.
|
|
35
|
+
* Keyed on the host alone, the first model to refuse spoke for every model on it.
|
|
36
|
+
*
|
|
37
|
+
* Only the `chat_template_kwargs` half is here. `reasoning_effort` is `negotiate`'s to latch, on
|
|
38
|
+
* the same (endpoint, model) pair, where a run on that model can read it too.
|
|
33
39
|
*/
|
|
34
40
|
const noHints = new Set();
|
|
35
41
|
const hintKey = (baseUrl, model) => JSON.stringify([baseUrl, model]);
|
|
@@ -81,28 +87,47 @@ const stripThinking = (text) => text
|
|
|
81
87
|
* @param options Reply ceiling, temperature, cancellation, notices.
|
|
82
88
|
*/
|
|
83
89
|
export async function ask(config, model, system, user, { maxTokens = 512, temperature = 0.3, signal, onNotice } = {}) {
|
|
84
|
-
const send = (hints) => getClient(config).chat.completions.create({
|
|
90
|
+
const send = (hints, refused) => getClient(config).chat.completions.create({
|
|
85
91
|
model,
|
|
86
|
-
|
|
87
|
-
|
|
92
|
+
// The reasoning models want the ceiling spelled the other way, and they are exactly the
|
|
93
|
+
// models a side task most wants to stop deliberating.
|
|
94
|
+
...(refused && !refused.legacyTokenLimit
|
|
95
|
+
? { max_completion_tokens: maxTokens }
|
|
96
|
+
: { max_tokens: maxTokens }),
|
|
97
|
+
// One that will only run at the temperature it was built with is sent none: a side task
|
|
98
|
+
// wants the same answer twice, and 1.0 from that model is as close as it gets.
|
|
99
|
+
...(refused && !refused.chosenTemperature ? {} : { temperature }),
|
|
88
100
|
messages: [
|
|
89
101
|
{ role: "system", content: system },
|
|
90
102
|
{ role: "user", content: user },
|
|
91
103
|
],
|
|
92
104
|
...(hints ? NO_THINKING : {}),
|
|
105
|
+
...(hints && refused?.reasoningEffort !== false ? { reasoning_effort: "none" } : {}),
|
|
93
106
|
}, { signal });
|
|
107
|
+
// The endpoint's own object, not one of this module's: what a model refuses is the same fact
|
|
108
|
+
// whether a run or a side task found it out, and the point of latching it is that only one of
|
|
109
|
+
// them has to pay for it. Nothing here sends tools or `stream_options`, so the two
|
|
110
|
+
// endpoint-level flags are not in play — the model's three are the whole of what this meets.
|
|
111
|
+
const supports = capabilitiesFor(config.baseUrl);
|
|
112
|
+
const attempt = (hints) => negotiate(supports, (_supports, _produced, refused) => send(hints, refused), {
|
|
113
|
+
model,
|
|
114
|
+
onNotice,
|
|
115
|
+
});
|
|
94
116
|
const key = hintKey(config.baseUrl, model);
|
|
95
117
|
const hints = !noHints.has(key);
|
|
96
118
|
let response;
|
|
97
119
|
try {
|
|
98
|
-
response = await
|
|
120
|
+
response = await attempt(hints);
|
|
99
121
|
}
|
|
100
122
|
catch (error) {
|
|
123
|
+
// Whatever is left after `negotiate` has answered everything it knows: on this path that is
|
|
124
|
+
// the hints it does not, which is `chat_template_kwargs` and an effort the model has but
|
|
125
|
+
// does not offer as `none`.
|
|
101
126
|
if (!hints || !rejectedTheRequest(error))
|
|
102
127
|
throw error;
|
|
103
128
|
onNotice?.("server rejected the no-thinking hints; retrying without them");
|
|
104
129
|
noHints.add(key);
|
|
105
|
-
response = await
|
|
130
|
+
response = await attempt(false);
|
|
106
131
|
}
|
|
107
132
|
const message = response.choices[0]?.message;
|
|
108
133
|
const answer = stripThinking(message?.content ?? "").trim();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cubicecho/agent-core",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.2",
|
|
4
4
|
"description": "The endpoint-agnostic half of an OpenAI-compatible agent loop: tool-schema compatibility, on-demand tool loading, one-shot side tasks, run events, and a pooled client.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"openai",
|