@ouro.bot/cli 0.1.0-alpha.50 → 0.1.0-alpha.51
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/changelog.json +10 -0
- package/dist/heart/core.js +29 -4
- package/dist/heart/model-capabilities.js +40 -0
- package/dist/heart/providers/anthropic.js +56 -5
- package/dist/heart/providers/azure.js +8 -1
- package/dist/heart/providers/minimax.js +4 -0
- package/dist/heart/providers/openai-codex.js +10 -1
- package/dist/heart/streaming.js +4 -1
- package/dist/mind/prompt.js +10 -1
- package/dist/repertoire/tools-base.js +34 -0
- package/dist/repertoire/tools.js +44 -22
- package/package.json +1 -1
package/changelog.json
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
|
|
3
3
|
"versions": [
|
|
4
|
+
{
|
|
5
|
+
"version": "0.1.0-alpha.51",
|
|
6
|
+
"changes": [
|
|
7
|
+
"Agents can now adjust their own reasoning depth mid-conversation via a new set_reasoning_effort tool, with effort levels derived from a central model capabilities registry.",
|
|
8
|
+
"Anthropic extended thinking is now enabled with adaptive effort, thinking blocks are captured during streaming (including signatures and redacted blocks), persisted on conversation history, and faithfully round-tripped across turns.",
|
|
9
|
+
"Anthropic max_tokens now uses the model's actual output ceiling from the registry instead of a hardcoded 4096, removing artificial response length constraints.",
|
|
10
|
+
"Codex assistant messages are now annotated with phase labels (commentary vs final_answer) so GPT-5.4 can distinguish intermediate reasoning from completed responses in its own history.",
|
|
11
|
+
"Azure and Codex reasoning effort is now dynamic from the agent loop instead of hardcoded to medium."
|
|
12
|
+
]
|
|
13
|
+
},
|
|
4
14
|
{
|
|
5
15
|
"version": "0.1.0-alpha.50",
|
|
6
16
|
"changes": [
|
package/dist/heart/core.js
CHANGED
|
@@ -331,7 +331,12 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
331
331
|
// so turn execution remains consistent and non-fatal.
|
|
332
332
|
if (channel) {
|
|
333
333
|
try {
|
|
334
|
-
const
|
|
334
|
+
const buildSystemOptions = {
|
|
335
|
+
...options,
|
|
336
|
+
providerCapabilities: providerRuntime.capabilities,
|
|
337
|
+
supportedReasoningEfforts: providerRuntime.supportedReasoningEfforts,
|
|
338
|
+
};
|
|
339
|
+
const refreshed = await (0, prompt_1.buildSystem)(channel, buildSystemOptions, currentContext);
|
|
335
340
|
upsertSystemPrompt(messages, refreshed);
|
|
336
341
|
}
|
|
337
342
|
catch (error) {
|
|
@@ -362,13 +367,22 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
362
367
|
let completion;
|
|
363
368
|
let sawSteeringFollowUp = false;
|
|
364
369
|
let mustResolveBeforeHandoffActive = options?.mustResolveBeforeHandoff === true;
|
|
370
|
+
let currentReasoningEffort = "medium";
|
|
365
371
|
// Prevent MaxListenersExceeded warning — each iteration adds a listener
|
|
366
372
|
try {
|
|
367
373
|
require("events").setMaxListeners(50, signal);
|
|
368
374
|
}
|
|
369
375
|
catch { /* unsupported */ }
|
|
370
376
|
const toolPreferences = currentContext?.friend?.toolPreferences;
|
|
371
|
-
const baseTools = options?.tools ?? (0, tools_1.getToolsForChannel)(channel ? (0, channel_1.getChannelCapabilities)(channel) : undefined, toolPreferences && Object.keys(toolPreferences).length > 0 ? toolPreferences : undefined, currentContext);
|
|
377
|
+
const baseTools = options?.tools ?? (0, tools_1.getToolsForChannel)(channel ? (0, channel_1.getChannelCapabilities)(channel) : undefined, toolPreferences && Object.keys(toolPreferences).length > 0 ? toolPreferences : undefined, currentContext, providerRuntime.capabilities);
|
|
378
|
+
// Augment tool context with reasoning effort controls from provider
|
|
379
|
+
const augmentedToolContext = options?.toolContext
|
|
380
|
+
? {
|
|
381
|
+
...options.toolContext,
|
|
382
|
+
supportedReasoningEfforts: providerRuntime.supportedReasoningEfforts,
|
|
383
|
+
setReasoningEffort: (level) => { currentReasoningEffort = level; },
|
|
384
|
+
}
|
|
385
|
+
: undefined;
|
|
372
386
|
// Rebase provider-owned turn state from canonical messages at user-turn start.
|
|
373
387
|
// This prevents stale provider caches from replaying prior-turn context.
|
|
374
388
|
providerRuntime.resetTurnState(messages);
|
|
@@ -412,6 +426,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
412
426
|
signal,
|
|
413
427
|
traceId,
|
|
414
428
|
toolChoiceRequired,
|
|
429
|
+
reasoningEffort: currentReasoningEffort,
|
|
415
430
|
});
|
|
416
431
|
// Track usage from the latest API call
|
|
417
432
|
if (result.usage)
|
|
@@ -435,6 +450,17 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
435
450
|
if (reasoningItems.length > 0) {
|
|
436
451
|
msg._reasoning_items = reasoningItems;
|
|
437
452
|
}
|
|
453
|
+
// Store thinking blocks (Anthropic) on the assistant message for round-tripping
|
|
454
|
+
const thinkingItems = result.outputItems.filter((item) => "type" in item && (item.type === "thinking" || item.type === "redacted_thinking"));
|
|
455
|
+
if (thinkingItems.length > 0) {
|
|
456
|
+
msg._thinking_blocks = thinkingItems;
|
|
457
|
+
}
|
|
458
|
+
// Phase annotation for Codex provider
|
|
459
|
+
const hasPhaseAnnotation = providerRuntime.capabilities.has("phase-annotation");
|
|
460
|
+
const isSoleFinalAnswer = result.toolCalls.length === 1 && result.toolCalls[0].name === "final_answer";
|
|
461
|
+
if (hasPhaseAnnotation) {
|
|
462
|
+
msg.phase = isSoleFinalAnswer ? "final_answer" : "commentary";
|
|
463
|
+
}
|
|
438
464
|
if (!result.toolCalls.length) {
|
|
439
465
|
// No tool calls — accept response as-is.
|
|
440
466
|
// (Kick detection disabled; tool_choice: required + final_answer
|
|
@@ -444,7 +470,6 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
444
470
|
}
|
|
445
471
|
else {
|
|
446
472
|
// Check for final_answer sole call: intercept before tool execution
|
|
447
|
-
const isSoleFinalAnswer = result.toolCalls.length === 1 && result.toolCalls[0].name === "final_answer";
|
|
448
473
|
if (isSoleFinalAnswer) {
|
|
449
474
|
// Extract answer from the tool call arguments.
|
|
450
475
|
// Supports: {"answer":"text","intent":"..."} or "text" (JSON string).
|
|
@@ -536,7 +561,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
|
|
|
536
561
|
let success;
|
|
537
562
|
try {
|
|
538
563
|
const execToolFn = options?.execTool ?? tools_1.execTool;
|
|
539
|
-
toolResult = await execToolFn(tc.name, args, options?.toolContext);
|
|
564
|
+
toolResult = await execToolFn(tc.name, args, augmentedToolContext ?? options?.toolContext);
|
|
540
565
|
success = true;
|
|
541
566
|
}
|
|
542
567
|
catch (e) {
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MODEL_CAPABILITIES = void 0;
|
|
4
|
+
exports.getModelCapabilities = getModelCapabilities;
|
|
5
|
+
const runtime_1 = require("../nerves/runtime");
|
|
6
|
+
exports.MODEL_CAPABILITIES = {
|
|
7
|
+
"claude-opus-4-6": {
|
|
8
|
+
reasoningEffort: ["low", "medium", "high", "max"],
|
|
9
|
+
thinkingFormat: "anthropic",
|
|
10
|
+
maxOutputTokens: 128000,
|
|
11
|
+
},
|
|
12
|
+
"claude-sonnet-4-6": {
|
|
13
|
+
reasoningEffort: ["low", "medium", "high"],
|
|
14
|
+
thinkingFormat: "anthropic",
|
|
15
|
+
maxOutputTokens: 64000,
|
|
16
|
+
},
|
|
17
|
+
"gpt-5.4": {
|
|
18
|
+
reasoningEffort: ["low", "medium", "high"],
|
|
19
|
+
phase: true,
|
|
20
|
+
maxOutputTokens: 100000,
|
|
21
|
+
},
|
|
22
|
+
"gpt-5.3-codex": {
|
|
23
|
+
reasoningEffort: ["low", "medium", "high"],
|
|
24
|
+
phase: true,
|
|
25
|
+
maxOutputTokens: 100000,
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
const EMPTY_CAPABILITIES = Object.freeze({});
|
|
29
|
+
function getModelCapabilities(modelId) {
|
|
30
|
+
(0, runtime_1.emitNervesEvent)({
|
|
31
|
+
component: "engine",
|
|
32
|
+
event: "engine.model_capabilities_lookup",
|
|
33
|
+
message: `model capabilities lookup: ${modelId}`,
|
|
34
|
+
meta: { modelId, found: modelId in exports.MODEL_CAPABILITIES },
|
|
35
|
+
});
|
|
36
|
+
const entry = exports.MODEL_CAPABILITIES[modelId];
|
|
37
|
+
if (entry)
|
|
38
|
+
return entry;
|
|
39
|
+
return { ...EMPTY_CAPABILITIES };
|
|
40
|
+
}
|
|
@@ -3,12 +3,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.toAnthropicMessages = toAnthropicMessages;
|
|
6
7
|
exports.createAnthropicProviderRuntime = createAnthropicProviderRuntime;
|
|
7
8
|
const sdk_1 = __importDefault(require("@anthropic-ai/sdk"));
|
|
8
9
|
const config_1 = require("../config");
|
|
9
10
|
const identity_1 = require("../identity");
|
|
10
11
|
const runtime_1 = require("../../nerves/runtime");
|
|
11
12
|
const streaming_1 = require("../streaming");
|
|
13
|
+
const model_capabilities_1 = require("../model-capabilities");
|
|
12
14
|
const ANTHROPIC_SETUP_TOKEN_PREFIX = "sk-ant-oat01-";
|
|
13
15
|
const ANTHROPIC_SETUP_TOKEN_MIN_LENGTH = 80;
|
|
14
16
|
const ANTHROPIC_OAUTH_BETA_HEADER = "claude-code-20250219,oauth-2025-04-20,fine-grained-tool-streaming-2025-05-14,interleaved-thinking-2025-05-14";
|
|
@@ -93,6 +95,18 @@ function toAnthropicMessages(messages) {
|
|
|
93
95
|
if (msg.role === "assistant") {
|
|
94
96
|
const assistant = msg;
|
|
95
97
|
const blocks = [];
|
|
98
|
+
// Restore thinking blocks before text/tool_use blocks
|
|
99
|
+
const thinkingBlocks = assistant._thinking_blocks;
|
|
100
|
+
if (thinkingBlocks) {
|
|
101
|
+
for (const tb of thinkingBlocks) {
|
|
102
|
+
if (tb.type === "thinking") {
|
|
103
|
+
blocks.push({ type: "thinking", thinking: tb.thinking, signature: tb.signature });
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
blocks.push({ type: "redacted_thinking", data: tb.data });
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
96
110
|
const text = toAnthropicTextContent(assistant.content);
|
|
97
111
|
if (text) {
|
|
98
112
|
blocks.push({ type: "text", text });
|
|
@@ -195,11 +209,14 @@ function withAnthropicAuthGuidance(error) {
|
|
|
195
209
|
async function streamAnthropicMessages(client, model, request) {
|
|
196
210
|
const { system, messages } = toAnthropicMessages(request.messages);
|
|
197
211
|
const anthropicTools = toAnthropicTools(request.activeTools);
|
|
212
|
+
const modelCaps = (0, model_capabilities_1.getModelCapabilities)(model);
|
|
213
|
+
const maxTokens = modelCaps.maxOutputTokens ?? 16384;
|
|
198
214
|
const params = {
|
|
199
215
|
model,
|
|
200
|
-
max_tokens:
|
|
216
|
+
max_tokens: maxTokens,
|
|
201
217
|
messages,
|
|
202
218
|
stream: true,
|
|
219
|
+
thinking: { type: "adaptive", effort: request.reasoningEffort ?? "medium" },
|
|
203
220
|
};
|
|
204
221
|
if (system)
|
|
205
222
|
params.system = system;
|
|
@@ -219,6 +236,8 @@ async function streamAnthropicMessages(client, model, request) {
|
|
|
219
236
|
let streamStarted = false;
|
|
220
237
|
let usage;
|
|
221
238
|
const toolCalls = new Map();
|
|
239
|
+
const thinkingBlocks = new Map();
|
|
240
|
+
const redactedBlocks = new Map();
|
|
222
241
|
const answerStreamer = new streaming_1.FinalAnswerStreamer(request.callbacks);
|
|
223
242
|
try {
|
|
224
243
|
for await (const event of response) {
|
|
@@ -227,8 +246,14 @@ async function streamAnthropicMessages(client, model, request) {
|
|
|
227
246
|
const eventType = String(event.type ?? "");
|
|
228
247
|
if (eventType === "content_block_start") {
|
|
229
248
|
const block = event.content_block;
|
|
230
|
-
|
|
231
|
-
|
|
249
|
+
const index = Number(event.index);
|
|
250
|
+
if (block?.type === "thinking") {
|
|
251
|
+
thinkingBlocks.set(index, { type: "thinking", thinking: "", signature: "" });
|
|
252
|
+
}
|
|
253
|
+
else if (block?.type === "redacted_thinking") {
|
|
254
|
+
redactedBlocks.set(index, { type: "redacted_thinking", data: String(block.data ?? "") });
|
|
255
|
+
}
|
|
256
|
+
else if (block?.type === "tool_use") {
|
|
232
257
|
const rawInput = block.input;
|
|
233
258
|
const input = rawInput && typeof rawInput === "object"
|
|
234
259
|
? JSON.stringify(rawInput)
|
|
@@ -265,7 +290,19 @@ async function streamAnthropicMessages(client, model, request) {
|
|
|
265
290
|
request.callbacks.onModelStreamStart();
|
|
266
291
|
streamStarted = true;
|
|
267
292
|
}
|
|
268
|
-
|
|
293
|
+
const thinkingText = String(delta?.thinking ?? "");
|
|
294
|
+
request.callbacks.onReasoningChunk(thinkingText);
|
|
295
|
+
const thinkingIndex = Number(event.index);
|
|
296
|
+
const thinkingBlock = thinkingBlocks.get(thinkingIndex);
|
|
297
|
+
if (thinkingBlock)
|
|
298
|
+
thinkingBlock.thinking += thinkingText;
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
if (deltaType === "signature_delta") {
|
|
302
|
+
const sigIndex = Number(event.index);
|
|
303
|
+
const sigBlock = thinkingBlocks.get(sigIndex);
|
|
304
|
+
if (sigBlock)
|
|
305
|
+
sigBlock.signature += String(delta?.signature ?? "");
|
|
269
306
|
continue;
|
|
270
307
|
}
|
|
271
308
|
if (deltaType === "input_json_delta") {
|
|
@@ -301,10 +338,18 @@ async function streamAnthropicMessages(client, model, request) {
|
|
|
301
338
|
catch (error) {
|
|
302
339
|
throw withAnthropicAuthGuidance(error);
|
|
303
340
|
}
|
|
341
|
+
// Collect all thinking blocks (regular + redacted) sorted by index to preserve ordering
|
|
342
|
+
const allThinkingIndices = [...thinkingBlocks.keys(), ...redactedBlocks.keys()].sort((a, b) => a - b);
|
|
343
|
+
const outputItems = allThinkingIndices.map((idx) => {
|
|
344
|
+
const tb = thinkingBlocks.get(idx);
|
|
345
|
+
if (tb)
|
|
346
|
+
return tb;
|
|
347
|
+
return redactedBlocks.get(idx);
|
|
348
|
+
});
|
|
304
349
|
return {
|
|
305
350
|
content,
|
|
306
351
|
toolCalls: [...toolCalls.values()],
|
|
307
|
-
outputItems
|
|
352
|
+
outputItems,
|
|
308
353
|
usage,
|
|
309
354
|
finalAnswerStreamed: answerStreamer.streamed,
|
|
310
355
|
};
|
|
@@ -320,6 +365,10 @@ function createAnthropicProviderRuntime() {
|
|
|
320
365
|
if (!(anthropicConfig.model && anthropicConfig.setupToken)) {
|
|
321
366
|
throw new Error(getAnthropicReauthGuidance("provider 'anthropic' is selected in agent.json but providers.anthropic.model/setupToken is incomplete in secrets.json."));
|
|
322
367
|
}
|
|
368
|
+
const modelCaps = (0, model_capabilities_1.getModelCapabilities)(anthropicConfig.model);
|
|
369
|
+
const capabilities = new Set();
|
|
370
|
+
if (modelCaps.reasoningEffort)
|
|
371
|
+
capabilities.add("reasoning-effort");
|
|
323
372
|
const credential = resolveAnthropicSetupTokenCredential();
|
|
324
373
|
const client = new sdk_1.default({
|
|
325
374
|
authToken: credential.token,
|
|
@@ -333,6 +382,8 @@ function createAnthropicProviderRuntime() {
|
|
|
333
382
|
id: "anthropic",
|
|
334
383
|
model: anthropicConfig.model,
|
|
335
384
|
client,
|
|
385
|
+
capabilities,
|
|
386
|
+
supportedReasoningEfforts: modelCaps.reasoningEffort,
|
|
336
387
|
resetTurnState(_messages) {
|
|
337
388
|
// Anthropic request payload is derived from canonical messages each turn.
|
|
338
389
|
},
|
|
@@ -5,6 +5,7 @@ const openai_1 = require("openai");
|
|
|
5
5
|
const config_1 = require("../config");
|
|
6
6
|
const runtime_1 = require("../../nerves/runtime");
|
|
7
7
|
const streaming_1 = require("../streaming");
|
|
8
|
+
const model_capabilities_1 = require("../model-capabilities");
|
|
8
9
|
function createAzureProviderRuntime() {
|
|
9
10
|
(0, runtime_1.emitNervesEvent)({
|
|
10
11
|
component: "engine",
|
|
@@ -16,6 +17,10 @@ function createAzureProviderRuntime() {
|
|
|
16
17
|
if (!(azureConfig.apiKey && azureConfig.endpoint && azureConfig.deployment && azureConfig.modelName)) {
|
|
17
18
|
throw new Error("provider 'azure' is selected in agent.json but providers.azure is incomplete in secrets.json.");
|
|
18
19
|
}
|
|
20
|
+
const modelCaps = (0, model_capabilities_1.getModelCapabilities)(azureConfig.modelName);
|
|
21
|
+
const capabilities = new Set();
|
|
22
|
+
if (modelCaps.reasoningEffort)
|
|
23
|
+
capabilities.add("reasoning-effort");
|
|
19
24
|
const client = new openai_1.AzureOpenAI({
|
|
20
25
|
apiKey: azureConfig.apiKey,
|
|
21
26
|
endpoint: azureConfig.endpoint.replace(/\/openai.*$/, ""),
|
|
@@ -30,6 +35,8 @@ function createAzureProviderRuntime() {
|
|
|
30
35
|
id: "azure",
|
|
31
36
|
model: azureConfig.modelName,
|
|
32
37
|
client,
|
|
38
|
+
capabilities,
|
|
39
|
+
supportedReasoningEfforts: modelCaps.reasoningEffort,
|
|
33
40
|
resetTurnState(messages) {
|
|
34
41
|
const { instructions, input } = (0, streaming_1.toResponsesInput)(messages);
|
|
35
42
|
nativeInput = input;
|
|
@@ -48,7 +55,7 @@ function createAzureProviderRuntime() {
|
|
|
48
55
|
input: nativeInput,
|
|
49
56
|
instructions: nativeInstructions,
|
|
50
57
|
tools: (0, streaming_1.toResponsesTools)(request.activeTools),
|
|
51
|
-
reasoning: { effort: "medium", summary: "detailed" },
|
|
58
|
+
reasoning: { effort: request.reasoningEffort ?? "medium", summary: "detailed" },
|
|
52
59
|
stream: true,
|
|
53
60
|
store: false,
|
|
54
61
|
include: ["reasoning.encrypted_content"],
|
|
@@ -8,6 +8,7 @@ const openai_1 = __importDefault(require("openai"));
|
|
|
8
8
|
const config_1 = require("../config");
|
|
9
9
|
const runtime_1 = require("../../nerves/runtime");
|
|
10
10
|
const streaming_1 = require("../streaming");
|
|
11
|
+
const model_capabilities_1 = require("../model-capabilities");
|
|
11
12
|
function createMinimaxProviderRuntime() {
|
|
12
13
|
(0, runtime_1.emitNervesEvent)({
|
|
13
14
|
component: "engine",
|
|
@@ -19,6 +20,8 @@ function createMinimaxProviderRuntime() {
|
|
|
19
20
|
if (!minimaxConfig.apiKey) {
|
|
20
21
|
throw new Error("provider 'minimax' is selected in agent.json but providers.minimax.apiKey is missing in secrets.json.");
|
|
21
22
|
}
|
|
23
|
+
// Registry consulted; MiniMax models return empty defaults (no capabilities to derive)
|
|
24
|
+
(0, model_capabilities_1.getModelCapabilities)(minimaxConfig.model);
|
|
22
25
|
const client = new openai_1.default({
|
|
23
26
|
apiKey: minimaxConfig.apiKey,
|
|
24
27
|
baseURL: "https://api.minimaxi.chat/v1",
|
|
@@ -29,6 +32,7 @@ function createMinimaxProviderRuntime() {
|
|
|
29
32
|
id: "minimax",
|
|
30
33
|
model: minimaxConfig.model,
|
|
31
34
|
client,
|
|
35
|
+
capabilities: new Set(),
|
|
32
36
|
resetTurnState(_messages) {
|
|
33
37
|
// No provider-owned turn state for chat-completions providers.
|
|
34
38
|
},
|
|
@@ -9,6 +9,7 @@ const config_1 = require("../config");
|
|
|
9
9
|
const identity_1 = require("../identity");
|
|
10
10
|
const runtime_1 = require("../../nerves/runtime");
|
|
11
11
|
const streaming_1 = require("../streaming");
|
|
12
|
+
const model_capabilities_1 = require("../model-capabilities");
|
|
12
13
|
const OPENAI_CODEX_AUTH_FAILURE_MARKERS = [
|
|
13
14
|
"authentication failed",
|
|
14
15
|
"unauthorized",
|
|
@@ -106,6 +107,12 @@ function createOpenAICodexProviderRuntime() {
|
|
|
106
107
|
if (!chatgptAccountId) {
|
|
107
108
|
throw new Error(getOpenAICodexReauthGuidance("OpenAI Codex OAuth access token is missing a chatgpt_account_id claim required for chatgpt.com/backend-api/codex."));
|
|
108
109
|
}
|
|
110
|
+
const modelCaps = (0, model_capabilities_1.getModelCapabilities)(codexConfig.model);
|
|
111
|
+
const capabilities = new Set();
|
|
112
|
+
if (modelCaps.reasoningEffort)
|
|
113
|
+
capabilities.add("reasoning-effort");
|
|
114
|
+
if (modelCaps.phase)
|
|
115
|
+
capabilities.add("phase-annotation");
|
|
109
116
|
const client = new openai_1.default({
|
|
110
117
|
apiKey: token,
|
|
111
118
|
baseURL: OPENAI_CODEX_BACKEND_BASE_URL,
|
|
@@ -123,6 +130,8 @@ function createOpenAICodexProviderRuntime() {
|
|
|
123
130
|
id: "openai-codex",
|
|
124
131
|
model: codexConfig.model,
|
|
125
132
|
client,
|
|
133
|
+
capabilities,
|
|
134
|
+
supportedReasoningEfforts: modelCaps.reasoningEffort,
|
|
126
135
|
resetTurnState(messages) {
|
|
127
136
|
const { instructions, input } = (0, streaming_1.toResponsesInput)(messages);
|
|
128
137
|
nativeInput = input;
|
|
@@ -141,7 +150,7 @@ function createOpenAICodexProviderRuntime() {
|
|
|
141
150
|
input: nativeInput,
|
|
142
151
|
instructions: nativeInstructions,
|
|
143
152
|
tools: (0, streaming_1.toResponsesTools)(request.activeTools),
|
|
144
|
-
reasoning: { effort: "medium", summary: "detailed" },
|
|
153
|
+
reasoning: { effort: request.reasoningEffort ?? "medium", summary: "detailed" },
|
|
145
154
|
stream: true,
|
|
146
155
|
store: false,
|
|
147
156
|
include: ["reasoning.encrypted_content"],
|
package/dist/heart/streaming.js
CHANGED
|
@@ -185,7 +185,10 @@ function toResponsesInput(messages) {
|
|
|
185
185
|
}
|
|
186
186
|
}
|
|
187
187
|
if (a.content) {
|
|
188
|
-
|
|
188
|
+
const assistantItem = { role: "assistant", content: typeof a.content === "string" ? a.content : "" };
|
|
189
|
+
if (a.phase)
|
|
190
|
+
assistantItem.phase = a.phase;
|
|
191
|
+
input.push(assistantItem);
|
|
189
192
|
}
|
|
190
193
|
if (a.tool_calls) {
|
|
191
194
|
for (const tc of a.tool_calls) {
|
package/dist/mind/prompt.js
CHANGED
|
@@ -311,7 +311,7 @@ function dateSection() {
|
|
|
311
311
|
return `current date: ${today}`;
|
|
312
312
|
}
|
|
313
313
|
function toolsSection(channel, options, context) {
|
|
314
|
-
const channelTools = (0, tools_1.getToolsForChannel)((0, channel_1.getChannelCapabilities)(channel), undefined, context);
|
|
314
|
+
const channelTools = (0, tools_1.getToolsForChannel)((0, channel_1.getChannelCapabilities)(channel), undefined, context, options?.providerCapabilities);
|
|
315
315
|
const activeTools = (options?.toolChoiceRequired ?? true) ? [...channelTools, tools_1.finalAnswerTool] : channelTools;
|
|
316
316
|
const list = activeTools
|
|
317
317
|
.map((t) => `- ${t.function.name}: ${t.function.description}`)
|
|
@@ -383,6 +383,14 @@ function delegationHintSection(options) {
|
|
|
383
383
|
];
|
|
384
384
|
return lines.join("\n");
|
|
385
385
|
}
|
|
386
|
+
function reasoningEffortSection(options) {
|
|
387
|
+
if (!options?.providerCapabilities?.has("reasoning-effort"))
|
|
388
|
+
return "";
|
|
389
|
+
const levels = options.supportedReasoningEfforts ?? [];
|
|
390
|
+
const levelList = levels.length > 0 ? levels.join(", ") : "varies by model";
|
|
391
|
+
return `## reasoning effort
|
|
392
|
+
i can adjust my own reasoning depth using the set_reasoning_effort tool. i use higher effort for complex analysis and lower effort for simple tasks. available levels: ${levelList}.`;
|
|
393
|
+
}
|
|
386
394
|
function toolBehaviorSection(options) {
|
|
387
395
|
if (!(options?.toolChoiceRequired ?? true))
|
|
388
396
|
return "";
|
|
@@ -510,6 +518,7 @@ async function buildSystem(channel = "cli", options, context) {
|
|
|
510
518
|
providerSection(),
|
|
511
519
|
dateSection(),
|
|
512
520
|
toolsSection(channel, options, context),
|
|
521
|
+
reasoningEffortSection(options),
|
|
513
522
|
toolRestrictionSection(context),
|
|
514
523
|
mixedTrustGroupSection(context),
|
|
515
524
|
skillsSection(),
|
|
@@ -911,6 +911,40 @@ exports.baseToolDefinitions = [
|
|
|
911
911
|
return `message queued for delivery to ${friendId} on ${target}. preview: "${preview}". it will be delivered when their session is next active.`;
|
|
912
912
|
},
|
|
913
913
|
},
|
|
914
|
+
{
|
|
915
|
+
tool: {
|
|
916
|
+
type: "function",
|
|
917
|
+
function: {
|
|
918
|
+
name: "set_reasoning_effort",
|
|
919
|
+
description: "adjust your own reasoning depth for subsequent turns. use higher effort for complex analysis, lower for simple tasks.",
|
|
920
|
+
parameters: {
|
|
921
|
+
type: "object",
|
|
922
|
+
properties: {
|
|
923
|
+
level: { type: "string", description: "the reasoning effort level to set" },
|
|
924
|
+
},
|
|
925
|
+
required: ["level"],
|
|
926
|
+
},
|
|
927
|
+
},
|
|
928
|
+
},
|
|
929
|
+
handler: (args, ctx) => {
|
|
930
|
+
if (!ctx?.supportedReasoningEfforts || !ctx.setReasoningEffort) {
|
|
931
|
+
return "reasoning effort adjustment is not available in this context.";
|
|
932
|
+
}
|
|
933
|
+
const level = (args.level || "").trim();
|
|
934
|
+
if (!ctx.supportedReasoningEfforts.includes(level)) {
|
|
935
|
+
return `invalid reasoning effort level "${level}". accepted levels: ${ctx.supportedReasoningEfforts.join(", ")}`;
|
|
936
|
+
}
|
|
937
|
+
ctx.setReasoningEffort(level);
|
|
938
|
+
(0, runtime_1.emitNervesEvent)({
|
|
939
|
+
component: "repertoire",
|
|
940
|
+
event: "repertoire.reasoning_effort_changed",
|
|
941
|
+
message: `reasoning effort set to ${level}`,
|
|
942
|
+
meta: { level },
|
|
943
|
+
});
|
|
944
|
+
return `reasoning effort set to "${level}".`;
|
|
945
|
+
},
|
|
946
|
+
requiredCapability: "reasoning-effort",
|
|
947
|
+
},
|
|
914
948
|
...tools_1.codingToolDefinitions,
|
|
915
949
|
];
|
|
916
950
|
exports.tools = exports.baseToolDefinitions.map((d) => d.tool);
|
package/dist/repertoire/tools.js
CHANGED
|
@@ -35,9 +35,11 @@ function blockedLocalToolMessage() {
|
|
|
35
35
|
return "I can't do that because my trust level with you isn't high enough for local shell/file operations. Ask me for a remote-safe alternative (Graph/ADO/web), or run that operation from CLI.";
|
|
36
36
|
}
|
|
37
37
|
function baseToolsForCapabilities(capabilities, context) {
|
|
38
|
+
// Use baseToolDefinitions at call time so dynamically-added tools are included
|
|
39
|
+
const currentTools = tools_base_1.baseToolDefinitions.map((d) => d.tool);
|
|
38
40
|
if (!shouldBlockLocalTools(capabilities, context))
|
|
39
|
-
return
|
|
40
|
-
return
|
|
41
|
+
return currentTools;
|
|
42
|
+
return currentTools.filter((tool) => !exports.REMOTE_BLOCKED_LOCAL_TOOLS.has(tool.function.name));
|
|
41
43
|
}
|
|
42
44
|
// Apply a single tool preference to a tool schema, returning a new object.
|
|
43
45
|
function applyPreference(tool, pref) {
|
|
@@ -49,37 +51,55 @@ function applyPreference(tool, pref) {
|
|
|
49
51
|
},
|
|
50
52
|
};
|
|
51
53
|
}
|
|
54
|
+
// Filter out tools whose requiredCapability is not in the provider's capability set.
|
|
55
|
+
// Uses baseToolDefinitions at call time so dynamically-added tools are included.
|
|
56
|
+
// Only base tools can have requiredCapability (integration tools do not).
|
|
57
|
+
function filterByCapability(toolList, providerCapabilities) {
|
|
58
|
+
return toolList.filter((tool) => {
|
|
59
|
+
const def = tools_base_1.baseToolDefinitions.find((d) => d.tool.function.name === tool.function.name);
|
|
60
|
+
if (!def?.requiredCapability)
|
|
61
|
+
return true;
|
|
62
|
+
return providerCapabilities?.has(def.requiredCapability) === true;
|
|
63
|
+
});
|
|
64
|
+
}
|
|
52
65
|
// Return the appropriate tools list based on channel capabilities.
|
|
53
66
|
// Base tools (no integration) are always included.
|
|
54
67
|
// Teams/integration tools are included only if their integration is in availableIntegrations.
|
|
55
68
|
// When toolPreferences is provided, matching preferences are appended to tool descriptions.
|
|
56
|
-
|
|
69
|
+
// When providerCapabilities is provided, tools with requiredCapability are filtered.
|
|
70
|
+
function getToolsForChannel(capabilities, toolPreferences, context, providerCapabilities) {
|
|
57
71
|
const baseTools = baseToolsForCapabilities(capabilities, context);
|
|
58
72
|
const bluebubblesTools = capabilities?.channel === "bluebubbles"
|
|
59
73
|
? tools_bluebubbles_1.bluebubblesToolDefinitions.map((d) => d.tool)
|
|
60
74
|
: [];
|
|
75
|
+
let result;
|
|
61
76
|
if (!capabilities || capabilities.availableIntegrations.length === 0) {
|
|
62
|
-
|
|
77
|
+
result = [...baseTools, ...bluebubblesTools];
|
|
63
78
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
79
|
+
else {
|
|
80
|
+
const available = new Set(capabilities.availableIntegrations);
|
|
81
|
+
const channelDefs = [...tools_teams_1.teamsToolDefinitions, ...ado_semantic_1.adoSemanticToolDefinitions, ...tools_github_1.githubToolDefinitions];
|
|
82
|
+
// Include tools whose integration is available, plus channel tools with no integration gate (e.g. teams_send_message)
|
|
83
|
+
const integrationDefs = channelDefs.filter((d) => d.integration ? available.has(d.integration) : capabilities.channel === "teams");
|
|
84
|
+
if (!toolPreferences || Object.keys(toolPreferences).length === 0) {
|
|
85
|
+
result = [...baseTools, ...bluebubblesTools, ...integrationDefs.map((d) => d.tool)];
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
// Build a map of integration -> preference text for fast lookup
|
|
89
|
+
const prefMap = new Map();
|
|
90
|
+
for (const [key, value] of Object.entries(toolPreferences)) {
|
|
91
|
+
prefMap.set(key, value);
|
|
92
|
+
}
|
|
93
|
+
// Apply preferences to matching integration tools (new objects, no mutation)
|
|
94
|
+
// d.integration is guaranteed truthy -- integrationDefs are pre-filtered above
|
|
95
|
+
const enrichedIntegrationTools = integrationDefs.map((d) => {
|
|
96
|
+
const pref = prefMap.get(d.integration);
|
|
97
|
+
return pref ? applyPreference(d.tool, pref) : d.tool;
|
|
98
|
+
});
|
|
99
|
+
result = [...baseTools, ...bluebubblesTools, ...enrichedIntegrationTools];
|
|
100
|
+
}
|
|
70
101
|
}
|
|
71
|
-
|
|
72
|
-
const prefMap = new Map();
|
|
73
|
-
for (const [key, value] of Object.entries(toolPreferences)) {
|
|
74
|
-
prefMap.set(key, value);
|
|
75
|
-
}
|
|
76
|
-
// Apply preferences to matching integration tools (new objects, no mutation)
|
|
77
|
-
// d.integration is guaranteed truthy -- integrationDefs are pre-filtered above
|
|
78
|
-
const enrichedIntegrationTools = integrationDefs.map((d) => {
|
|
79
|
-
const pref = prefMap.get(d.integration);
|
|
80
|
-
return pref ? applyPreference(d.tool, pref) : d.tool;
|
|
81
|
-
});
|
|
82
|
-
return [...baseTools, ...bluebubblesTools, ...enrichedIntegrationTools];
|
|
102
|
+
return filterByCapability(result, providerCapabilities);
|
|
83
103
|
}
|
|
84
104
|
// Check whether a tool requires user confirmation before execution.
|
|
85
105
|
// Reads from ToolDefinition.confirmationRequired instead of a separate Set.
|
|
@@ -192,6 +212,8 @@ function summarizeArgs(name, args) {
|
|
|
192
212
|
return summarizeKeyValues(args, ["sessionId"]);
|
|
193
213
|
if (name === "bluebubbles_set_reply_target")
|
|
194
214
|
return summarizeKeyValues(args, ["target", "threadOriginatorGuid"]);
|
|
215
|
+
if (name === "set_reasoning_effort")
|
|
216
|
+
return summarizeKeyValues(args, ["level"]);
|
|
195
217
|
if (name === "claude")
|
|
196
218
|
return summarizeKeyValues(args, ["prompt"]);
|
|
197
219
|
if (name === "web_search")
|