@hiper2d/ai-agents 0.13.1 → 0.15.0

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
  /**
@@ -1407,6 +1409,24 @@ declare class Gpt5Agent extends AbstractAgent {
1407
1409
  * inherited from a default OpenAI can change under us.
1408
1410
  */
1409
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;
1410
1430
  /**
1411
1431
  * Structured output method using Zod with OpenAI's Responses API
1412
1432
  * This provides better schema handling and runtime validation
@@ -1725,6 +1745,13 @@ declare class QwenAgent extends AbstractAgent {
1725
1745
  * this agent stays ignored — deliberately, not because effort is broken.
1726
1746
  */
1727
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
+ */
1728
1755
  private convertToOpenAIMessages;
1729
1756
  private extractThinkingAndUsage;
1730
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
  /**
@@ -1407,6 +1409,24 @@ declare class Gpt5Agent extends AbstractAgent {
1407
1409
  * inherited from a default OpenAI can change under us.
1408
1410
  */
1409
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;
1410
1430
  /**
1411
1431
  * Structured output method using Zod with OpenAI's Responses API
1412
1432
  * This provides better schema handling and runtime validation
@@ -1725,6 +1745,13 @@ declare class QwenAgent extends AbstractAgent {
1725
1745
  * this agent stays ignored — deliberately, not because effort is broken.
1726
1746
  */
1727
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
+ */
1728
1755
  private convertToOpenAIMessages;
1729
1756
  private extractThinkingAndUsage;
1730
1757
  /**
package/dist/index.js CHANGED
@@ -1240,10 +1240,15 @@ var SupportedAiModels = {
1240
1240
  // Live 2026-08-30 (one sample each): day-2 vote 11.8s, 15-character story 56.2s.
1241
1241
  tags: ["cheap"]
1242
1242
  },
1243
- // Sakana Fugu models — OpenAI-compatible. They reason internally (and bill it as
1244
- // "orchestration" tokens), but never surface reasoning to us: responses come back with
1245
- // reasoning_tokens: 0 and no reasoning_content. So hasThinking is false — there's no
1246
- // thinking content to show and no user-facing thinking toggle. Single entry per model.
1243
+ // Sakana Fugu models — OpenAI-compatible. They reason internally, but Chat Completions
1244
+ // (what FuguAgent uses) never returns the text: reasoning_tokens are reported and billed,
1245
+ // there is no reasoning_content. So hasThinking is false. Single entry per model.
1246
+ // The Messages and Responses APIs DO return reasoning (thinking blocks with an empty
1247
+ // signature / reasoning items, some with encrypted_content), but probed 2026-09-24 on
1248
+ // fugu-max it isn't worth switching for, since we keep reasoning only to replay it: the
1249
+ // returned text is often a vague status summary or empty rather than the actual
1250
+ // reasoning, and replayed reasoning reaches the model distorted (a planted "4817" came
1251
+ // back as "4829" on both APIs, presumably rewritten by the orchestrator).
1247
1252
  //
1248
1253
  // Base `fugu` was RETIRED 2026-08-04. It was carried as a cheap everyday option at an
1249
1254
  // assumed $1/$3, but reconciling token logs against the Sakana balance showed it actually
@@ -1702,6 +1707,9 @@ function getProviderSignatureFields(aiType, signature) {
1702
1707
  if (aiType.startsWith("grok")) {
1703
1708
  return { grokEncryptedReasoning: signature };
1704
1709
  }
1710
+ if (aiType.startsWith("gpt")) {
1711
+ return { openaiEncryptedReasoning: signature };
1712
+ }
1705
1713
  if (aiType.startsWith("muse-")) {
1706
1714
  return { metaEncryptedReasoning: signature };
1707
1715
  }
@@ -2526,7 +2534,54 @@ ${instruction}`);
2526
2534
  * inherited from a default OpenAI can change under us.
2527
2535
  */
2528
2536
  reasoningParams() {
2529
- return this.reasoningEffort ? { reasoning: { effort: toOpenAIEffort(this.reasoningEffort) } } : {};
2537
+ return {
2538
+ reasoning: {
2539
+ ...this.reasoningEffort ? { effort: toOpenAIEffort(this.reasoningEffort) } : {},
2540
+ // Render reasoning from earlier turns into this sample. It is already the
2541
+ // default on all four models we run, but stated explicitly for the same reason
2542
+ // effort is. It only does anything because we replay the items ourselves.
2543
+ context: "all_turns"
2544
+ }
2545
+ };
2546
+ }
2547
+ /**
2548
+ * Responses API input items, with each assistant turn's stored reasoning items replayed
2549
+ * immediately before it. OpenAI attaches `encrypted_content` to reasoning items whenever
2550
+ * `store` is false, which is how we call it: the documented stateless route is to preserve
2551
+ * the output items and replay the history yourself, rather than lean on
2552
+ * `previous_response_id` and OpenAI-side session state.
2553
+ *
2554
+ * This was a single flattened string before ("User: ... / Assistant: ..."), which left
2555
+ * nowhere to put reasoning items. The system prompt still travels ONLY as `instructions`.
2556
+ *
2557
+ * Reasoning is readable only within one model family and the API silently omits items it
2558
+ * cannot read, so a bot moved between GPT-6 and GPT-5.6 Terra loses its reasoning and keeps
2559
+ * working. A bot moved to another provider never reaches this code at all: every agent
2560
+ * reads only its own field, and a missing one falls back to a plain message.
2561
+ */
2562
+ buildResponsesInput(messages) {
2563
+ const input = [];
2564
+ for (const msg of this.prepareMessages(messages)) {
2565
+ if (msg.role === "assistant" && msg.openaiEncryptedReasoning) {
2566
+ try {
2567
+ const items = JSON.parse(msg.openaiEncryptedReasoning);
2568
+ if (Array.isArray(items)) {
2569
+ input.push(...items);
2570
+ }
2571
+ } catch {
2572
+ this.logger("Failed to parse stored encrypted reasoning, replaying message without it");
2573
+ }
2574
+ }
2575
+ input.push({ role: msg.role, content: msg.content });
2576
+ }
2577
+ return input;
2578
+ }
2579
+ /** The response's reasoning items, JSON-serialized for the caller to store on the message. */
2580
+ extractReasoningItems(response) {
2581
+ const items = (response?.output ?? []).filter(
2582
+ (item) => item?.type === "reasoning" && item.encrypted_content
2583
+ );
2584
+ return items.length > 0 ? JSON.stringify(items) : void 0;
2530
2585
  }
2531
2586
  /**
2532
2587
  * Structured output method using Zod with OpenAI's Responses API
@@ -2538,7 +2593,7 @@ ${instruction}`);
2538
2593
  try {
2539
2594
  this.logAsking(messages);
2540
2595
  this.logMessages(messages);
2541
- const input = this.prepareMessages(messages).map((msg) => `${msg.role === "user" ? "User" : "Assistant"}: ${msg.content}`).join("\n\n");
2596
+ const input = this.buildResponsesInput(messages);
2542
2597
  const schemaToSend = zodSchema;
2543
2598
  let response;
2544
2599
  try {
@@ -2548,6 +2603,8 @@ ${instruction}`);
2548
2603
  input,
2549
2604
  max_output_tokens: this.maxOutputTokens,
2550
2605
  prompt_cache_key: this.promptCacheKey,
2606
+ // Stateless: encrypted reasoning is only returned for unstored responses.
2607
+ store: false,
2551
2608
  ...this.reasoningParams(),
2552
2609
  text: {
2553
2610
  format: (0, import_zod2.zodTextFormat)(schemaToSend, "response_schema")
@@ -2608,7 +2665,7 @@ ${instruction}`);
2608
2665
  this.logReply(response.output_parsed, reasoningContent, tokenUsage);
2609
2666
  }
2610
2667
  this.logger(`\u2705 Response validated successfully with Zod schema`);
2611
- return [response.output_parsed, reasoningContent, tokenUsage];
2668
+ return [response.output_parsed, reasoningContent, tokenUsage, this.extractReasoningItems(response)];
2612
2669
  } catch (error) {
2613
2670
  this.logger(this.logTemplates.error(this.name, error));
2614
2671
  if (error instanceof ModelError) {
@@ -2627,13 +2684,14 @@ ${instruction}`);
2627
2684
  try {
2628
2685
  this.logAsking(messages);
2629
2686
  this.logMessages(messages);
2630
- const input = this.prepareMessages(messages).map((msg) => `${msg.role === "user" ? "User" : "Assistant"}: ${msg.content}`).join("\n\n");
2687
+ const input = this.buildResponsesInput(messages);
2631
2688
  const response = await this.client.responses.create({
2632
2689
  model: this.model,
2633
2690
  instructions: this.instruction,
2634
2691
  input,
2635
2692
  max_output_tokens: this.maxOutputTokens,
2636
2693
  prompt_cache_key: this.promptCacheKey,
2694
+ store: false,
2637
2695
  ...this.reasoningParams()
2638
2696
  });
2639
2697
  const content = response.output_text;
@@ -2675,7 +2733,7 @@ ${instruction}`);
2675
2733
  }
2676
2734
  }
2677
2735
  this.logReply(content, "", tokenUsage);
2678
- return [content, "", tokenUsage];
2736
+ return [content, "", tokenUsage, this.extractReasoningItems(response)];
2679
2737
  } catch (error) {
2680
2738
  this.logger(this.logTemplates.error(this.name, error));
2681
2739
  if (error instanceof ModelError) {
@@ -4767,13 +4825,29 @@ var QwenAgent = class extends AbstractAgent {
4767
4825
  const budget = this.thinkingBudgetTokens;
4768
4826
  return {
4769
4827
  enable_thinking: this.enableThinking,
4770
- ...this.enableThinking && budget !== void 0 ? { thinking_budget: budget } : {}
4828
+ ...this.enableThinking && budget !== void 0 ? { thinking_budget: budget } : {},
4829
+ // Lets the model read the `reasoning_content` we replay on assistant messages, so a
4830
+ // bot keeps its own train of thought across turns. Qwen documents models as NOT
4831
+ // reading replayed reasoning unless this is set (qwen3.8-max and qwen3.8-flash are
4832
+ // both on the supported list). Measured 2026-09-22: qwen3.8-max actually used the
4833
+ // replayed reasoning with the flag off too, but we follow the documented contract
4834
+ // rather than rely on that. Stateless — the reasoning comes from our payload, not
4835
+ // from any server-side session.
4836
+ ...this.enableThinking ? { preserve_thinking: true } : {}
4771
4837
  };
4772
4838
  }
4839
+ /**
4840
+ * Assistant turns carry their own `reasoning_content` back so the model can reference what
4841
+ * it was thinking on earlier turns (paired with `preserve_thinking` in thinkingParams).
4842
+ * Qwen's reasoning is PLAIN TEXT, not a signed or encrypted blob, so there is nothing for
4843
+ * another provider to reject: a bot whose model changes mid-game simply stops sending it
4844
+ * and keeps working. The text is the same one the caller already stores as `thinking`.
4845
+ */
4773
4846
  convertToOpenAIMessages(messages) {
4774
4847
  return messages.map((msg) => ({
4775
4848
  role: msg.role,
4776
- content: msg.content
4849
+ content: msg.content,
4850
+ ...msg.role === "assistant" && msg.thinking ? { reasoning_content: msg.thinking } : {}
4777
4851
  }));
4778
4852
  }
4779
4853
  extractThinkingAndUsage(completion) {
@@ -4840,7 +4914,11 @@ ${schemaDescription}`;
4840
4914
  const params = {
4841
4915
  ...this.defaultParams,
4842
4916
  messages: openAIMessages,
4843
- ...this.thinkingParams()
4917
+ ...this.thinkingParams(),
4918
+ response_format: {
4919
+ type: "json_schema",
4920
+ json_schema: ZodSchemaConverter.toOpenAIJsonSchema(zodSchema, "response_schema")
4921
+ }
4844
4922
  };
4845
4923
  completion = await this.client.chat.completions.create(params);
4846
4924
  } catch (apiError) {