@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 CHANGED
@@ -16,6 +16,7 @@ interface AIMessage {
16
16
  googleThoughtSignature?: string;
17
17
  grokEncryptedReasoning?: string;
18
18
  metaEncryptedReasoning?: string;
19
+ openaiEncryptedReasoning?: string;
19
20
  }
20
21
  interface TokenUsage$1 {
21
22
  inputTokens: number;
@@ -510,6 +511,7 @@ declare function getProviderSignatureFields(aiType: string, signature?: string):
510
511
  googleThoughtSignature?: string;
511
512
  grokEncryptedReasoning?: string;
512
513
  metaEncryptedReasoning?: string;
514
+ openaiEncryptedReasoning?: string;
513
515
  };
514
516
 
515
517
  /**
@@ -543,8 +545,12 @@ declare function getProviderSignatureFields(aiType: string, signature?: string):
543
545
  * - Moonshot Kimi K3 (platform.kimi.ai/docs/guide/use-reasoning-effort, 2026-09-22):
544
546
  * low|high|max, default max, top-level on Chat Completions. Moonshot shipped the lower
545
547
  * levels after launch; K3 accepted only "max" when this agent was written.
546
- * Qwen accepts reasoning_effort but ignores it (thinking_budget is its knob); MiniMax exposes
547
- * no effort parameter.
548
+ * - Qwen 3.8 max/flash (docs.qwencloud.com, re-probed 2026-09-22): low|medium|xhigh, default
549
+ * xhigh, with `high` and `max` aliased onto xhigh and `minimal` onto low. It is a coarse
550
+ * alias for thinking_budget (low = 4,096, medium = 16,384, xhigh = 262,144 tokens) and the
551
+ * two are mutually exclusive — sending both is a 400. QwenAgent sends the budget instead,
552
+ * because 1,024 is tighter than the lowest level effort can express.
553
+ * MiniMax exposes no effort parameter.
548
554
  */
549
555
  type OpenAIReasoningEffort = 'minimal' | 'low' | 'medium' | 'high' | 'xhigh';
550
556
  type AnthropicReasoningEffort = 'low' | 'medium' | 'high' | 'xhigh' | 'max';
@@ -1403,6 +1409,24 @@ declare class Gpt5Agent extends AbstractAgent {
1403
1409
  * inherited from a default OpenAI can change under us.
1404
1410
  */
1405
1411
  private reasoningParams;
1412
+ /**
1413
+ * Responses API input items, with each assistant turn's stored reasoning items replayed
1414
+ * immediately before it. OpenAI attaches `encrypted_content` to reasoning items whenever
1415
+ * `store` is false, which is how we call it: the documented stateless route is to preserve
1416
+ * the output items and replay the history yourself, rather than lean on
1417
+ * `previous_response_id` and OpenAI-side session state.
1418
+ *
1419
+ * This was a single flattened string before ("User: ... / Assistant: ..."), which left
1420
+ * nowhere to put reasoning items. The system prompt still travels ONLY as `instructions`.
1421
+ *
1422
+ * Reasoning is readable only within one model family and the API silently omits items it
1423
+ * cannot read, so a bot moved between GPT-6 and GPT-5.6 Terra loses its reasoning and keeps
1424
+ * working. A bot moved to another provider never reaches this code at all: every agent
1425
+ * reads only its own field, and a missing one falls back to a plain message.
1426
+ */
1427
+ private buildResponsesInput;
1428
+ /** The response's reasoning items, JSON-serialized for the caller to store on the message. */
1429
+ private extractReasoningItems;
1406
1430
  /**
1407
1431
  * Structured output method using Zod with OpenAI's Responses API
1408
1432
  * This provides better schema handling and runtime validation
@@ -1704,14 +1728,30 @@ declare class QwenAgent extends AbstractAgent {
1704
1728
  * generation); without it the model thinks at the provider default, and qwen3.8-max's
1705
1729
  * latency then swings 30–100s.
1706
1730
  *
1707
- * `reasoning_effort` is deliberately NOT sent. Probed live 2026-08-30 on qwen3.8-flash and
1708
- * qwen3.8-max: every value low..max is accepted, but reasoning length doesn't track it
1709
- * (max: low → 1,686 reasoning tokens / 44s, high → 226 / 7s, xhigh → 1,102 / 30s), while
1710
- * thinking_budget bounds it reliably (≤340 at 1024). The docs also call the two mutually
1711
- * exclusive on qwen3.8-max. So on Qwen the budget IS the effort knob; `reasoningEffort`
1712
- * on this agent is ignored.
1731
+ * `reasoning_effort` is NOT sent, and the earlier reason recorded here was wrong. The
1732
+ * 2026-08-30 probe concluded "reasoning length doesn't track effort"; it had tested
1733
+ * low/high/max/xhigh, but Qwen only defines low|medium|xhigh and aliases both `high` and
1734
+ * `max` onto `xhigh`. So that probe compared low against xhigh twice and read the noise as
1735
+ * non-monotonicity.
1736
+ *
1737
+ * Re-probed 2026-09-22 with the documented levels, and effort tracks cleanly:
1738
+ * qwen3.8-max budget 1024 → 77 reasoning | low 132 | medium 160 | xhigh 422
1739
+ * qwen3.8-flash budget 1024 → 86 reasoning | low 87 | medium 251 | xhigh 417
1740
+ *
1741
+ * We still send the budget, for a better reason: the two are mutually exclusive (sending
1742
+ * both is a 400), Qwen documents effort as a coarse alias for a budget (low = 4,096,
1743
+ * medium = 16,384, xhigh = 262,144 tokens), and our 1,024 is tighter than the lowest level
1744
+ * it can express. The budget is the finer AND cheaper knob here, so `reasoningEffort` on
1745
+ * this agent stays ignored — deliberately, not because effort is broken.
1713
1746
  */
1714
1747
  private thinkingParams;
1748
+ /**
1749
+ * Assistant turns carry their own `reasoning_content` back so the model can reference what
1750
+ * it was thinking on earlier turns (paired with `preserve_thinking` in thinkingParams).
1751
+ * Qwen's reasoning is PLAIN TEXT, not a signed or encrypted blob, so there is nothing for
1752
+ * another provider to reject: a bot whose model changes mid-game simply stops sending it
1753
+ * and keeps working. The text is the same one the caller already stores as `thinking`.
1754
+ */
1715
1755
  private convertToOpenAIMessages;
1716
1756
  private extractThinkingAndUsage;
1717
1757
  /**
package/dist/index.d.ts CHANGED
@@ -16,6 +16,7 @@ interface AIMessage {
16
16
  googleThoughtSignature?: string;
17
17
  grokEncryptedReasoning?: string;
18
18
  metaEncryptedReasoning?: string;
19
+ openaiEncryptedReasoning?: string;
19
20
  }
20
21
  interface TokenUsage$1 {
21
22
  inputTokens: number;
@@ -510,6 +511,7 @@ declare function getProviderSignatureFields(aiType: string, signature?: string):
510
511
  googleThoughtSignature?: string;
511
512
  grokEncryptedReasoning?: string;
512
513
  metaEncryptedReasoning?: string;
514
+ openaiEncryptedReasoning?: string;
513
515
  };
514
516
 
515
517
  /**
@@ -543,8 +545,12 @@ declare function getProviderSignatureFields(aiType: string, signature?: string):
543
545
  * - Moonshot Kimi K3 (platform.kimi.ai/docs/guide/use-reasoning-effort, 2026-09-22):
544
546
  * low|high|max, default max, top-level on Chat Completions. Moonshot shipped the lower
545
547
  * levels after launch; K3 accepted only "max" when this agent was written.
546
- * Qwen accepts reasoning_effort but ignores it (thinking_budget is its knob); MiniMax exposes
547
- * no effort parameter.
548
+ * - Qwen 3.8 max/flash (docs.qwencloud.com, re-probed 2026-09-22): low|medium|xhigh, default
549
+ * xhigh, with `high` and `max` aliased onto xhigh and `minimal` onto low. It is a coarse
550
+ * alias for thinking_budget (low = 4,096, medium = 16,384, xhigh = 262,144 tokens) and the
551
+ * two are mutually exclusive — sending both is a 400. QwenAgent sends the budget instead,
552
+ * because 1,024 is tighter than the lowest level effort can express.
553
+ * MiniMax exposes no effort parameter.
548
554
  */
549
555
  type OpenAIReasoningEffort = 'minimal' | 'low' | 'medium' | 'high' | 'xhigh';
550
556
  type AnthropicReasoningEffort = 'low' | 'medium' | 'high' | 'xhigh' | 'max';
@@ -1403,6 +1409,24 @@ declare class Gpt5Agent extends AbstractAgent {
1403
1409
  * inherited from a default OpenAI can change under us.
1404
1410
  */
1405
1411
  private reasoningParams;
1412
+ /**
1413
+ * Responses API input items, with each assistant turn's stored reasoning items replayed
1414
+ * immediately before it. OpenAI attaches `encrypted_content` to reasoning items whenever
1415
+ * `store` is false, which is how we call it: the documented stateless route is to preserve
1416
+ * the output items and replay the history yourself, rather than lean on
1417
+ * `previous_response_id` and OpenAI-side session state.
1418
+ *
1419
+ * This was a single flattened string before ("User: ... / Assistant: ..."), which left
1420
+ * nowhere to put reasoning items. The system prompt still travels ONLY as `instructions`.
1421
+ *
1422
+ * Reasoning is readable only within one model family and the API silently omits items it
1423
+ * cannot read, so a bot moved between GPT-6 and GPT-5.6 Terra loses its reasoning and keeps
1424
+ * working. A bot moved to another provider never reaches this code at all: every agent
1425
+ * reads only its own field, and a missing one falls back to a plain message.
1426
+ */
1427
+ private buildResponsesInput;
1428
+ /** The response's reasoning items, JSON-serialized for the caller to store on the message. */
1429
+ private extractReasoningItems;
1406
1430
  /**
1407
1431
  * Structured output method using Zod with OpenAI's Responses API
1408
1432
  * This provides better schema handling and runtime validation
@@ -1704,14 +1728,30 @@ declare class QwenAgent extends AbstractAgent {
1704
1728
  * generation); without it the model thinks at the provider default, and qwen3.8-max's
1705
1729
  * latency then swings 30–100s.
1706
1730
  *
1707
- * `reasoning_effort` is deliberately NOT sent. Probed live 2026-08-30 on qwen3.8-flash and
1708
- * qwen3.8-max: every value low..max is accepted, but reasoning length doesn't track it
1709
- * (max: low → 1,686 reasoning tokens / 44s, high → 226 / 7s, xhigh → 1,102 / 30s), while
1710
- * thinking_budget bounds it reliably (≤340 at 1024). The docs also call the two mutually
1711
- * exclusive on qwen3.8-max. So on Qwen the budget IS the effort knob; `reasoningEffort`
1712
- * on this agent is ignored.
1731
+ * `reasoning_effort` is NOT sent, and the earlier reason recorded here was wrong. The
1732
+ * 2026-08-30 probe concluded "reasoning length doesn't track effort"; it had tested
1733
+ * low/high/max/xhigh, but Qwen only defines low|medium|xhigh and aliases both `high` and
1734
+ * `max` onto `xhigh`. So that probe compared low against xhigh twice and read the noise as
1735
+ * non-monotonicity.
1736
+ *
1737
+ * Re-probed 2026-09-22 with the documented levels, and effort tracks cleanly:
1738
+ * qwen3.8-max budget 1024 → 77 reasoning | low 132 | medium 160 | xhigh 422
1739
+ * qwen3.8-flash budget 1024 → 86 reasoning | low 87 | medium 251 | xhigh 417
1740
+ *
1741
+ * We still send the budget, for a better reason: the two are mutually exclusive (sending
1742
+ * both is a 400), Qwen documents effort as a coarse alias for a budget (low = 4,096,
1743
+ * medium = 16,384, xhigh = 262,144 tokens), and our 1,024 is tighter than the lowest level
1744
+ * it can express. The budget is the finer AND cheaper knob here, so `reasoningEffort` on
1745
+ * this agent stays ignored — deliberately, not because effort is broken.
1713
1746
  */
1714
1747
  private thinkingParams;
1748
+ /**
1749
+ * Assistant turns carry their own `reasoning_content` back so the model can reference what
1750
+ * it was thinking on earlier turns (paired with `preserve_thinking` in thinkingParams).
1751
+ * Qwen's reasoning is PLAIN TEXT, not a signed or encrypted blob, so there is nothing for
1752
+ * another provider to reject: a bot whose model changes mid-game simply stops sending it
1753
+ * and keeps working. The text is the same one the caller already stores as `thinking`.
1754
+ */
1715
1755
  private convertToOpenAIMessages;
1716
1756
  private extractThinkingAndUsage;
1717
1757
  /**
package/dist/index.js CHANGED
@@ -1702,6 +1702,9 @@ function getProviderSignatureFields(aiType, signature) {
1702
1702
  if (aiType.startsWith("grok")) {
1703
1703
  return { grokEncryptedReasoning: signature };
1704
1704
  }
1705
+ if (aiType.startsWith("gpt")) {
1706
+ return { openaiEncryptedReasoning: signature };
1707
+ }
1705
1708
  if (aiType.startsWith("muse-")) {
1706
1709
  return { metaEncryptedReasoning: signature };
1707
1710
  }
@@ -2526,7 +2529,54 @@ ${instruction}`);
2526
2529
  * inherited from a default OpenAI can change under us.
2527
2530
  */
2528
2531
  reasoningParams() {
2529
- return this.reasoningEffort ? { reasoning: { effort: toOpenAIEffort(this.reasoningEffort) } } : {};
2532
+ return {
2533
+ reasoning: {
2534
+ ...this.reasoningEffort ? { effort: toOpenAIEffort(this.reasoningEffort) } : {},
2535
+ // Render reasoning from earlier turns into this sample. It is already the
2536
+ // default on all four models we run, but stated explicitly for the same reason
2537
+ // effort is. It only does anything because we replay the items ourselves.
2538
+ context: "all_turns"
2539
+ }
2540
+ };
2541
+ }
2542
+ /**
2543
+ * Responses API input items, with each assistant turn's stored reasoning items replayed
2544
+ * immediately before it. OpenAI attaches `encrypted_content` to reasoning items whenever
2545
+ * `store` is false, which is how we call it: the documented stateless route is to preserve
2546
+ * the output items and replay the history yourself, rather than lean on
2547
+ * `previous_response_id` and OpenAI-side session state.
2548
+ *
2549
+ * This was a single flattened string before ("User: ... / Assistant: ..."), which left
2550
+ * nowhere to put reasoning items. The system prompt still travels ONLY as `instructions`.
2551
+ *
2552
+ * Reasoning is readable only within one model family and the API silently omits items it
2553
+ * cannot read, so a bot moved between GPT-6 and GPT-5.6 Terra loses its reasoning and keeps
2554
+ * working. A bot moved to another provider never reaches this code at all: every agent
2555
+ * reads only its own field, and a missing one falls back to a plain message.
2556
+ */
2557
+ buildResponsesInput(messages) {
2558
+ const input = [];
2559
+ for (const msg of this.prepareMessages(messages)) {
2560
+ if (msg.role === "assistant" && msg.openaiEncryptedReasoning) {
2561
+ try {
2562
+ const items = JSON.parse(msg.openaiEncryptedReasoning);
2563
+ if (Array.isArray(items)) {
2564
+ input.push(...items);
2565
+ }
2566
+ } catch {
2567
+ this.logger("Failed to parse stored encrypted reasoning, replaying message without it");
2568
+ }
2569
+ }
2570
+ input.push({ role: msg.role, content: msg.content });
2571
+ }
2572
+ return input;
2573
+ }
2574
+ /** The response's reasoning items, JSON-serialized for the caller to store on the message. */
2575
+ extractReasoningItems(response) {
2576
+ const items = (response?.output ?? []).filter(
2577
+ (item) => item?.type === "reasoning" && item.encrypted_content
2578
+ );
2579
+ return items.length > 0 ? JSON.stringify(items) : void 0;
2530
2580
  }
2531
2581
  /**
2532
2582
  * Structured output method using Zod with OpenAI's Responses API
@@ -2538,7 +2588,7 @@ ${instruction}`);
2538
2588
  try {
2539
2589
  this.logAsking(messages);
2540
2590
  this.logMessages(messages);
2541
- const input = this.prepareMessages(messages).map((msg) => `${msg.role === "user" ? "User" : "Assistant"}: ${msg.content}`).join("\n\n");
2591
+ const input = this.buildResponsesInput(messages);
2542
2592
  const schemaToSend = zodSchema;
2543
2593
  let response;
2544
2594
  try {
@@ -2548,6 +2598,8 @@ ${instruction}`);
2548
2598
  input,
2549
2599
  max_output_tokens: this.maxOutputTokens,
2550
2600
  prompt_cache_key: this.promptCacheKey,
2601
+ // Stateless: encrypted reasoning is only returned for unstored responses.
2602
+ store: false,
2551
2603
  ...this.reasoningParams(),
2552
2604
  text: {
2553
2605
  format: (0, import_zod2.zodTextFormat)(schemaToSend, "response_schema")
@@ -2608,7 +2660,7 @@ ${instruction}`);
2608
2660
  this.logReply(response.output_parsed, reasoningContent, tokenUsage);
2609
2661
  }
2610
2662
  this.logger(`\u2705 Response validated successfully with Zod schema`);
2611
- return [response.output_parsed, reasoningContent, tokenUsage];
2663
+ return [response.output_parsed, reasoningContent, tokenUsage, this.extractReasoningItems(response)];
2612
2664
  } catch (error) {
2613
2665
  this.logger(this.logTemplates.error(this.name, error));
2614
2666
  if (error instanceof ModelError) {
@@ -2627,13 +2679,14 @@ ${instruction}`);
2627
2679
  try {
2628
2680
  this.logAsking(messages);
2629
2681
  this.logMessages(messages);
2630
- const input = this.prepareMessages(messages).map((msg) => `${msg.role === "user" ? "User" : "Assistant"}: ${msg.content}`).join("\n\n");
2682
+ const input = this.buildResponsesInput(messages);
2631
2683
  const response = await this.client.responses.create({
2632
2684
  model: this.model,
2633
2685
  instructions: this.instruction,
2634
2686
  input,
2635
2687
  max_output_tokens: this.maxOutputTokens,
2636
2688
  prompt_cache_key: this.promptCacheKey,
2689
+ store: false,
2637
2690
  ...this.reasoningParams()
2638
2691
  });
2639
2692
  const content = response.output_text;
@@ -2675,7 +2728,7 @@ ${instruction}`);
2675
2728
  }
2676
2729
  }
2677
2730
  this.logReply(content, "", tokenUsage);
2678
- return [content, "", tokenUsage];
2731
+ return [content, "", tokenUsage, this.extractReasoningItems(response)];
2679
2732
  } catch (error) {
2680
2733
  this.logger(this.logTemplates.error(this.name, error));
2681
2734
  if (error instanceof ModelError) {
@@ -4747,24 +4800,49 @@ var QwenAgent = class extends AbstractAgent {
4747
4800
  * generation); without it the model thinks at the provider default, and qwen3.8-max's
4748
4801
  * latency then swings 30–100s.
4749
4802
  *
4750
- * `reasoning_effort` is deliberately NOT sent. Probed live 2026-08-30 on qwen3.8-flash and
4751
- * qwen3.8-max: every value low..max is accepted, but reasoning length doesn't track it
4752
- * (max: low → 1,686 reasoning tokens / 44s, high → 226 / 7s, xhigh → 1,102 / 30s), while
4753
- * thinking_budget bounds it reliably (≤340 at 1024). The docs also call the two mutually
4754
- * exclusive on qwen3.8-max. So on Qwen the budget IS the effort knob; `reasoningEffort`
4755
- * on this agent is ignored.
4803
+ * `reasoning_effort` is NOT sent, and the earlier reason recorded here was wrong. The
4804
+ * 2026-08-30 probe concluded "reasoning length doesn't track effort"; it had tested
4805
+ * low/high/max/xhigh, but Qwen only defines low|medium|xhigh and aliases both `high` and
4806
+ * `max` onto `xhigh`. So that probe compared low against xhigh twice and read the noise as
4807
+ * non-monotonicity.
4808
+ *
4809
+ * Re-probed 2026-09-22 with the documented levels, and effort tracks cleanly:
4810
+ * qwen3.8-max budget 1024 → 77 reasoning | low 132 | medium 160 | xhigh 422
4811
+ * qwen3.8-flash budget 1024 → 86 reasoning | low 87 | medium 251 | xhigh 417
4812
+ *
4813
+ * We still send the budget, for a better reason: the two are mutually exclusive (sending
4814
+ * both is a 400), Qwen documents effort as a coarse alias for a budget (low = 4,096,
4815
+ * medium = 16,384, xhigh = 262,144 tokens), and our 1,024 is tighter than the lowest level
4816
+ * it can express. The budget is the finer AND cheaper knob here, so `reasoningEffort` on
4817
+ * this agent stays ignored — deliberately, not because effort is broken.
4756
4818
  */
4757
4819
  thinkingParams() {
4758
4820
  const budget = this.thinkingBudgetTokens;
4759
4821
  return {
4760
4822
  enable_thinking: this.enableThinking,
4761
- ...this.enableThinking && budget !== void 0 ? { thinking_budget: budget } : {}
4823
+ ...this.enableThinking && budget !== void 0 ? { thinking_budget: budget } : {},
4824
+ // Lets the model read the `reasoning_content` we replay on assistant messages, so a
4825
+ // bot keeps its own train of thought across turns. Qwen documents models as NOT
4826
+ // reading replayed reasoning unless this is set (qwen3.8-max and qwen3.8-flash are
4827
+ // both on the supported list). Measured 2026-09-22: qwen3.8-max actually used the
4828
+ // replayed reasoning with the flag off too, but we follow the documented contract
4829
+ // rather than rely on that. Stateless — the reasoning comes from our payload, not
4830
+ // from any server-side session.
4831
+ ...this.enableThinking ? { preserve_thinking: true } : {}
4762
4832
  };
4763
4833
  }
4834
+ /**
4835
+ * Assistant turns carry their own `reasoning_content` back so the model can reference what
4836
+ * it was thinking on earlier turns (paired with `preserve_thinking` in thinkingParams).
4837
+ * Qwen's reasoning is PLAIN TEXT, not a signed or encrypted blob, so there is nothing for
4838
+ * another provider to reject: a bot whose model changes mid-game simply stops sending it
4839
+ * and keeps working. The text is the same one the caller already stores as `thinking`.
4840
+ */
4764
4841
  convertToOpenAIMessages(messages) {
4765
4842
  return messages.map((msg) => ({
4766
4843
  role: msg.role,
4767
- content: msg.content
4844
+ content: msg.content,
4845
+ ...msg.role === "assistant" && msg.thinking ? { reasoning_content: msg.thinking } : {}
4768
4846
  }));
4769
4847
  }
4770
4848
  extractThinkingAndUsage(completion) {