@hiper2d/ai-agents 0.13.0 → 0.14.1
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.d.mts +48 -8
- package/dist/index.d.ts +48 -8
- package/dist/index.js +91 -13
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +91 -13
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1527,6 +1527,9 @@ function getProviderSignatureFields(aiType, signature) {
|
|
|
1527
1527
|
if (aiType.startsWith("grok")) {
|
|
1528
1528
|
return { grokEncryptedReasoning: signature };
|
|
1529
1529
|
}
|
|
1530
|
+
if (aiType.startsWith("gpt")) {
|
|
1531
|
+
return { openaiEncryptedReasoning: signature };
|
|
1532
|
+
}
|
|
1530
1533
|
if (aiType.startsWith("muse-")) {
|
|
1531
1534
|
return { metaEncryptedReasoning: signature };
|
|
1532
1535
|
}
|
|
@@ -2351,7 +2354,54 @@ ${instruction}`);
|
|
|
2351
2354
|
* inherited from a default OpenAI can change under us.
|
|
2352
2355
|
*/
|
|
2353
2356
|
reasoningParams() {
|
|
2354
|
-
return
|
|
2357
|
+
return {
|
|
2358
|
+
reasoning: {
|
|
2359
|
+
...this.reasoningEffort ? { effort: toOpenAIEffort(this.reasoningEffort) } : {},
|
|
2360
|
+
// Render reasoning from earlier turns into this sample. It is already the
|
|
2361
|
+
// default on all four models we run, but stated explicitly for the same reason
|
|
2362
|
+
// effort is. It only does anything because we replay the items ourselves.
|
|
2363
|
+
context: "all_turns"
|
|
2364
|
+
}
|
|
2365
|
+
};
|
|
2366
|
+
}
|
|
2367
|
+
/**
|
|
2368
|
+
* Responses API input items, with each assistant turn's stored reasoning items replayed
|
|
2369
|
+
* immediately before it. OpenAI attaches `encrypted_content` to reasoning items whenever
|
|
2370
|
+
* `store` is false, which is how we call it: the documented stateless route is to preserve
|
|
2371
|
+
* the output items and replay the history yourself, rather than lean on
|
|
2372
|
+
* `previous_response_id` and OpenAI-side session state.
|
|
2373
|
+
*
|
|
2374
|
+
* This was a single flattened string before ("User: ... / Assistant: ..."), which left
|
|
2375
|
+
* nowhere to put reasoning items. The system prompt still travels ONLY as `instructions`.
|
|
2376
|
+
*
|
|
2377
|
+
* Reasoning is readable only within one model family and the API silently omits items it
|
|
2378
|
+
* cannot read, so a bot moved between GPT-6 and GPT-5.6 Terra loses its reasoning and keeps
|
|
2379
|
+
* working. A bot moved to another provider never reaches this code at all: every agent
|
|
2380
|
+
* reads only its own field, and a missing one falls back to a plain message.
|
|
2381
|
+
*/
|
|
2382
|
+
buildResponsesInput(messages) {
|
|
2383
|
+
const input = [];
|
|
2384
|
+
for (const msg of this.prepareMessages(messages)) {
|
|
2385
|
+
if (msg.role === "assistant" && msg.openaiEncryptedReasoning) {
|
|
2386
|
+
try {
|
|
2387
|
+
const items = JSON.parse(msg.openaiEncryptedReasoning);
|
|
2388
|
+
if (Array.isArray(items)) {
|
|
2389
|
+
input.push(...items);
|
|
2390
|
+
}
|
|
2391
|
+
} catch {
|
|
2392
|
+
this.logger("Failed to parse stored encrypted reasoning, replaying message without it");
|
|
2393
|
+
}
|
|
2394
|
+
}
|
|
2395
|
+
input.push({ role: msg.role, content: msg.content });
|
|
2396
|
+
}
|
|
2397
|
+
return input;
|
|
2398
|
+
}
|
|
2399
|
+
/** The response's reasoning items, JSON-serialized for the caller to store on the message. */
|
|
2400
|
+
extractReasoningItems(response) {
|
|
2401
|
+
const items = (response?.output ?? []).filter(
|
|
2402
|
+
(item) => item?.type === "reasoning" && item.encrypted_content
|
|
2403
|
+
);
|
|
2404
|
+
return items.length > 0 ? JSON.stringify(items) : void 0;
|
|
2355
2405
|
}
|
|
2356
2406
|
/**
|
|
2357
2407
|
* Structured output method using Zod with OpenAI's Responses API
|
|
@@ -2363,7 +2413,7 @@ ${instruction}`);
|
|
|
2363
2413
|
try {
|
|
2364
2414
|
this.logAsking(messages);
|
|
2365
2415
|
this.logMessages(messages);
|
|
2366
|
-
const input = this.
|
|
2416
|
+
const input = this.buildResponsesInput(messages);
|
|
2367
2417
|
const schemaToSend = zodSchema;
|
|
2368
2418
|
let response;
|
|
2369
2419
|
try {
|
|
@@ -2373,6 +2423,8 @@ ${instruction}`);
|
|
|
2373
2423
|
input,
|
|
2374
2424
|
max_output_tokens: this.maxOutputTokens,
|
|
2375
2425
|
prompt_cache_key: this.promptCacheKey,
|
|
2426
|
+
// Stateless: encrypted reasoning is only returned for unstored responses.
|
|
2427
|
+
store: false,
|
|
2376
2428
|
...this.reasoningParams(),
|
|
2377
2429
|
text: {
|
|
2378
2430
|
format: zodTextFormat(schemaToSend, "response_schema")
|
|
@@ -2433,7 +2485,7 @@ ${instruction}`);
|
|
|
2433
2485
|
this.logReply(response.output_parsed, reasoningContent, tokenUsage);
|
|
2434
2486
|
}
|
|
2435
2487
|
this.logger(`\u2705 Response validated successfully with Zod schema`);
|
|
2436
|
-
return [response.output_parsed, reasoningContent, tokenUsage];
|
|
2488
|
+
return [response.output_parsed, reasoningContent, tokenUsage, this.extractReasoningItems(response)];
|
|
2437
2489
|
} catch (error) {
|
|
2438
2490
|
this.logger(this.logTemplates.error(this.name, error));
|
|
2439
2491
|
if (error instanceof ModelError) {
|
|
@@ -2452,13 +2504,14 @@ ${instruction}`);
|
|
|
2452
2504
|
try {
|
|
2453
2505
|
this.logAsking(messages);
|
|
2454
2506
|
this.logMessages(messages);
|
|
2455
|
-
const input = this.
|
|
2507
|
+
const input = this.buildResponsesInput(messages);
|
|
2456
2508
|
const response = await this.client.responses.create({
|
|
2457
2509
|
model: this.model,
|
|
2458
2510
|
instructions: this.instruction,
|
|
2459
2511
|
input,
|
|
2460
2512
|
max_output_tokens: this.maxOutputTokens,
|
|
2461
2513
|
prompt_cache_key: this.promptCacheKey,
|
|
2514
|
+
store: false,
|
|
2462
2515
|
...this.reasoningParams()
|
|
2463
2516
|
});
|
|
2464
2517
|
const content = response.output_text;
|
|
@@ -2500,7 +2553,7 @@ ${instruction}`);
|
|
|
2500
2553
|
}
|
|
2501
2554
|
}
|
|
2502
2555
|
this.logReply(content, "", tokenUsage);
|
|
2503
|
-
return [content, "", tokenUsage];
|
|
2556
|
+
return [content, "", tokenUsage, this.extractReasoningItems(response)];
|
|
2504
2557
|
} catch (error) {
|
|
2505
2558
|
this.logger(this.logTemplates.error(this.name, error));
|
|
2506
2559
|
if (error instanceof ModelError) {
|
|
@@ -4572,24 +4625,49 @@ var QwenAgent = class extends AbstractAgent {
|
|
|
4572
4625
|
* generation); without it the model thinks at the provider default, and qwen3.8-max's
|
|
4573
4626
|
* latency then swings 30–100s.
|
|
4574
4627
|
*
|
|
4575
|
-
* `reasoning_effort` is
|
|
4576
|
-
*
|
|
4577
|
-
*
|
|
4578
|
-
*
|
|
4579
|
-
*
|
|
4580
|
-
*
|
|
4628
|
+
* `reasoning_effort` is NOT sent, and the earlier reason recorded here was wrong. The
|
|
4629
|
+
* 2026-08-30 probe concluded "reasoning length doesn't track effort"; it had tested
|
|
4630
|
+
* low/high/max/xhigh, but Qwen only defines low|medium|xhigh and aliases both `high` and
|
|
4631
|
+
* `max` onto `xhigh`. So that probe compared low against xhigh twice and read the noise as
|
|
4632
|
+
* non-monotonicity.
|
|
4633
|
+
*
|
|
4634
|
+
* Re-probed 2026-09-22 with the documented levels, and effort tracks cleanly:
|
|
4635
|
+
* qwen3.8-max budget 1024 → 77 reasoning | low 132 | medium 160 | xhigh 422
|
|
4636
|
+
* qwen3.8-flash budget 1024 → 86 reasoning | low 87 | medium 251 | xhigh 417
|
|
4637
|
+
*
|
|
4638
|
+
* We still send the budget, for a better reason: the two are mutually exclusive (sending
|
|
4639
|
+
* both is a 400), Qwen documents effort as a coarse alias for a budget (low = 4,096,
|
|
4640
|
+
* medium = 16,384, xhigh = 262,144 tokens), and our 1,024 is tighter than the lowest level
|
|
4641
|
+
* it can express. The budget is the finer AND cheaper knob here, so `reasoningEffort` on
|
|
4642
|
+
* this agent stays ignored — deliberately, not because effort is broken.
|
|
4581
4643
|
*/
|
|
4582
4644
|
thinkingParams() {
|
|
4583
4645
|
const budget = this.thinkingBudgetTokens;
|
|
4584
4646
|
return {
|
|
4585
4647
|
enable_thinking: this.enableThinking,
|
|
4586
|
-
...this.enableThinking && budget !== void 0 ? { thinking_budget: budget } : {}
|
|
4648
|
+
...this.enableThinking && budget !== void 0 ? { thinking_budget: budget } : {},
|
|
4649
|
+
// Lets the model read the `reasoning_content` we replay on assistant messages, so a
|
|
4650
|
+
// bot keeps its own train of thought across turns. Qwen documents models as NOT
|
|
4651
|
+
// reading replayed reasoning unless this is set (qwen3.8-max and qwen3.8-flash are
|
|
4652
|
+
// both on the supported list). Measured 2026-09-22: qwen3.8-max actually used the
|
|
4653
|
+
// replayed reasoning with the flag off too, but we follow the documented contract
|
|
4654
|
+
// rather than rely on that. Stateless — the reasoning comes from our payload, not
|
|
4655
|
+
// from any server-side session.
|
|
4656
|
+
...this.enableThinking ? { preserve_thinking: true } : {}
|
|
4587
4657
|
};
|
|
4588
4658
|
}
|
|
4659
|
+
/**
|
|
4660
|
+
* Assistant turns carry their own `reasoning_content` back so the model can reference what
|
|
4661
|
+
* it was thinking on earlier turns (paired with `preserve_thinking` in thinkingParams).
|
|
4662
|
+
* Qwen's reasoning is PLAIN TEXT, not a signed or encrypted blob, so there is nothing for
|
|
4663
|
+
* another provider to reject: a bot whose model changes mid-game simply stops sending it
|
|
4664
|
+
* and keeps working. The text is the same one the caller already stores as `thinking`.
|
|
4665
|
+
*/
|
|
4589
4666
|
convertToOpenAIMessages(messages) {
|
|
4590
4667
|
return messages.map((msg) => ({
|
|
4591
4668
|
role: msg.role,
|
|
4592
|
-
content: msg.content
|
|
4669
|
+
content: msg.content,
|
|
4670
|
+
...msg.role === "assistant" && msg.thinking ? { reasoning_content: msg.thinking } : {}
|
|
4593
4671
|
}));
|
|
4594
4672
|
}
|
|
4595
4673
|
extractThinkingAndUsage(completion) {
|