@cognipeer/agent-sdk 0.8.8 → 0.8.10

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.cts CHANGED
@@ -719,7 +719,8 @@ type ReflectionConfig = {
719
719
  onReflection?: (record: ReflectionRecord, ctx: ReflectionHookContext) => void;
720
720
  };
721
721
  type NativeReasoningConfig = {
722
- effort?: "minimal" | "low" | "medium" | "high";
722
+ /** `none` is OFF — a value that gets SENT. See ReasoningEffort in providers/types.ts. */
723
+ effort?: "none" | "minimal" | "low" | "medium" | "high";
723
724
  budgetTokens?: number;
724
725
  includeThoughts?: boolean;
725
726
  providerExtras?: Record<string, any>;
@@ -2481,6 +2482,8 @@ declare class StructuredOutputManager<T> {
2481
2482
  /**
2482
2483
  * Build a nudge message for when the model didn't produce structured output.
2483
2484
  * Used by the agent loop when it needs to retry.
2485
+ * User role is intentional: OpenAI-compatible chat templates such as Qwen
2486
+ * reject system messages anywhere except the beginning of the conversation.
2484
2487
  */
2485
2488
  buildNudgeMessage(isLastAttempt: boolean): {
2486
2489
  role: string;
@@ -2916,7 +2919,18 @@ type ChatCompletionRequest = {
2916
2919
  */
2917
2920
  reasoning?: ReasoningRequestConfig;
2918
2921
  };
2919
- type ReasoningEffort = "minimal" | "low" | "medium" | "high";
2922
+ /**
2923
+ * `none` is OFF, and it is a value rather than the absence of one.
2924
+ *
2925
+ * On a thinking-by-default model — every Qwen3 under vLLM, gpt-5.1 and newer on
2926
+ * OpenAI — sending no reasoning field leaves the model thinking. So "do not
2927
+ * think" is an instruction that has to travel: `reasoning_effort:"none"` on
2928
+ * OpenAI, `thinkingBudget:0` on Gemini, and no thinking block at all on
2929
+ * Anthropic (where thinking is opt-in and the absence IS off). Omitting
2930
+ * `effort` still means "say nothing about reasoning", which is the different
2931
+ * and equally necessary state.
2932
+ */
2933
+ type ReasoningEffort = "none" | "minimal" | "low" | "medium" | "high";
2920
2934
  type ReasoningRequestConfig = {
2921
2935
  /** Qualitative effort hint (mapped per-provider). */
2922
2936
  effort?: ReasoningEffort;
@@ -2979,6 +2993,28 @@ type OpenAIProviderConfig = {
2979
2993
  defaultModel?: string;
2980
2994
  defaultHeaders?: Record<string, string>;
2981
2995
  retry?: ProviderRetryConfig;
2996
+ /**
2997
+ * WHICH API a reasoning-carrying request is addressed to.
2998
+ *
2999
+ * auto (default, and today's behaviour) route to the Responses API when
3000
+ * the request carries `reasoning` AND the model name looks like an
3001
+ * o-series/gpt-5 reasoning model.
3002
+ * never always Chat Completions. `reasoning` is still mapped, as
3003
+ * `reasoning_effort` plus any `providerExtras`.
3004
+ * always always Responses, whatever the model is called.
3005
+ *
3006
+ * `auto` is a name-based guess, and it decides far more than where reasoning
3007
+ * goes: Responses is a different request shape (`input` not `messages`,
3008
+ * `text.format` not `response_format`, flat tool objects) and a different
3009
+ * response parse. A caller with its own measurements of the Chat Completions
3010
+ * body — a gateway, a proxy, an on-prem server that serves one and not the
3011
+ * other, or anything that has tuned `extra` — must be able to say so, rather
3012
+ * than discover that asking for less thinking silently changed the transport.
3013
+ *
3014
+ * Applies to any OpenAI-shaped endpoint, which is also why the guess is weak
3015
+ * where it matters most: a self-hosted model is named by whoever served it.
3016
+ */
3017
+ responsesApi?: "auto" | "never" | "always";
2982
3018
  };
2983
3019
  type AnthropicProviderConfig = {
2984
3020
  provider: "anthropic";
@@ -3083,10 +3119,17 @@ declare class OpenAIProvider extends BaseProvider {
3083
3119
  protected readonly baseURL: string;
3084
3120
  protected readonly defaultModel: string;
3085
3121
  protected readonly defaultHeaders: Record<string, string>;
3122
+ protected readonly responsesApi: "auto" | "never" | "always";
3086
3123
  constructor(config: OpenAIProviderConfig);
3087
- /** True when the request targets a reasoning model (o-series / gpt-5) so we
3088
- * should route through the Responses API, which exposes `reasoning.effort`
3089
- * and reasoning summaries. Chat Completions silently drops these. */
3124
+ /** True when this request should go to the Responses API rather than Chat
3125
+ * Completions — which exposes `reasoning.effort` and reasoning summaries that
3126
+ * Chat Completions silently drops.
3127
+ *
3128
+ * `auto` (the default) keeps the historical rule: only when the request
3129
+ * carries a reasoning config AND the model name looks like an o-series/gpt-5
3130
+ * model. `never`/`always` let a caller state the answer instead, because the
3131
+ * name check is a guess and the two APIs differ in far more than reasoning —
3132
+ * see `responsesApi` in OpenAIProviderConfig. */
3090
3133
  protected useResponsesApi(request: ChatCompletionRequest): boolean;
3091
3134
  complete(request: ChatCompletionRequest): Promise<ChatCompletionResponse>;
3092
3135
  completeStream(request: ChatCompletionRequest): AsyncGenerator<ChatCompletionChunk, void, unknown>;
package/dist/index.d.ts CHANGED
@@ -719,7 +719,8 @@ type ReflectionConfig = {
719
719
  onReflection?: (record: ReflectionRecord, ctx: ReflectionHookContext) => void;
720
720
  };
721
721
  type NativeReasoningConfig = {
722
- effort?: "minimal" | "low" | "medium" | "high";
722
+ /** `none` is OFF — a value that gets SENT. See ReasoningEffort in providers/types.ts. */
723
+ effort?: "none" | "minimal" | "low" | "medium" | "high";
723
724
  budgetTokens?: number;
724
725
  includeThoughts?: boolean;
725
726
  providerExtras?: Record<string, any>;
@@ -2481,6 +2482,8 @@ declare class StructuredOutputManager<T> {
2481
2482
  /**
2482
2483
  * Build a nudge message for when the model didn't produce structured output.
2483
2484
  * Used by the agent loop when it needs to retry.
2485
+ * User role is intentional: OpenAI-compatible chat templates such as Qwen
2486
+ * reject system messages anywhere except the beginning of the conversation.
2484
2487
  */
2485
2488
  buildNudgeMessage(isLastAttempt: boolean): {
2486
2489
  role: string;
@@ -2916,7 +2919,18 @@ type ChatCompletionRequest = {
2916
2919
  */
2917
2920
  reasoning?: ReasoningRequestConfig;
2918
2921
  };
2919
- type ReasoningEffort = "minimal" | "low" | "medium" | "high";
2922
+ /**
2923
+ * `none` is OFF, and it is a value rather than the absence of one.
2924
+ *
2925
+ * On a thinking-by-default model — every Qwen3 under vLLM, gpt-5.1 and newer on
2926
+ * OpenAI — sending no reasoning field leaves the model thinking. So "do not
2927
+ * think" is an instruction that has to travel: `reasoning_effort:"none"` on
2928
+ * OpenAI, `thinkingBudget:0` on Gemini, and no thinking block at all on
2929
+ * Anthropic (where thinking is opt-in and the absence IS off). Omitting
2930
+ * `effort` still means "say nothing about reasoning", which is the different
2931
+ * and equally necessary state.
2932
+ */
2933
+ type ReasoningEffort = "none" | "minimal" | "low" | "medium" | "high";
2920
2934
  type ReasoningRequestConfig = {
2921
2935
  /** Qualitative effort hint (mapped per-provider). */
2922
2936
  effort?: ReasoningEffort;
@@ -2979,6 +2993,28 @@ type OpenAIProviderConfig = {
2979
2993
  defaultModel?: string;
2980
2994
  defaultHeaders?: Record<string, string>;
2981
2995
  retry?: ProviderRetryConfig;
2996
+ /**
2997
+ * WHICH API a reasoning-carrying request is addressed to.
2998
+ *
2999
+ * auto (default, and today's behaviour) route to the Responses API when
3000
+ * the request carries `reasoning` AND the model name looks like an
3001
+ * o-series/gpt-5 reasoning model.
3002
+ * never always Chat Completions. `reasoning` is still mapped, as
3003
+ * `reasoning_effort` plus any `providerExtras`.
3004
+ * always always Responses, whatever the model is called.
3005
+ *
3006
+ * `auto` is a name-based guess, and it decides far more than where reasoning
3007
+ * goes: Responses is a different request shape (`input` not `messages`,
3008
+ * `text.format` not `response_format`, flat tool objects) and a different
3009
+ * response parse. A caller with its own measurements of the Chat Completions
3010
+ * body — a gateway, a proxy, an on-prem server that serves one and not the
3011
+ * other, or anything that has tuned `extra` — must be able to say so, rather
3012
+ * than discover that asking for less thinking silently changed the transport.
3013
+ *
3014
+ * Applies to any OpenAI-shaped endpoint, which is also why the guess is weak
3015
+ * where it matters most: a self-hosted model is named by whoever served it.
3016
+ */
3017
+ responsesApi?: "auto" | "never" | "always";
2982
3018
  };
2983
3019
  type AnthropicProviderConfig = {
2984
3020
  provider: "anthropic";
@@ -3083,10 +3119,17 @@ declare class OpenAIProvider extends BaseProvider {
3083
3119
  protected readonly baseURL: string;
3084
3120
  protected readonly defaultModel: string;
3085
3121
  protected readonly defaultHeaders: Record<string, string>;
3122
+ protected readonly responsesApi: "auto" | "never" | "always";
3086
3123
  constructor(config: OpenAIProviderConfig);
3087
- /** True when the request targets a reasoning model (o-series / gpt-5) so we
3088
- * should route through the Responses API, which exposes `reasoning.effort`
3089
- * and reasoning summaries. Chat Completions silently drops these. */
3124
+ /** True when this request should go to the Responses API rather than Chat
3125
+ * Completions — which exposes `reasoning.effort` and reasoning summaries that
3126
+ * Chat Completions silently drops.
3127
+ *
3128
+ * `auto` (the default) keeps the historical rule: only when the request
3129
+ * carries a reasoning config AND the model name looks like an o-series/gpt-5
3130
+ * model. `never`/`always` let a caller state the answer instead, because the
3131
+ * name check is a guess and the two APIs differ in far more than reasoning —
3132
+ * see `responsesApi` in OpenAIProviderConfig. */
3090
3133
  protected useResponsesApi(request: ChatCompletionRequest): boolean;
3091
3134
  complete(request: ChatCompletionRequest): Promise<ChatCompletionResponse>;
3092
3135
  completeStream(request: ChatCompletionRequest): AsyncGenerator<ChatCompletionChunk, void, unknown>;
package/dist/index.mjs CHANGED
@@ -4289,7 +4289,7 @@ var LEVEL_DEFAULTS = {
4289
4289
  };
4290
4290
  var VALID_LEVELS = ["minimal", "low", "medium", "high"];
4291
4291
  var VALID_CADENCES = ["off", "every_turn", "after_tool", "on_branch", "initial_then_after_tool"];
4292
- var VALID_EFFORTS = ["minimal", "low", "medium", "high"];
4292
+ var VALID_EFFORTS = ["none", "minimal", "low", "medium", "high"];
4293
4293
  function validateReasoningConfig(input) {
4294
4294
  if (!input) return;
4295
4295
  if (input.level !== void 0 && !VALID_LEVELS.includes(input.level)) {
@@ -4875,11 +4875,13 @@ var StructuredOutputManager = class {
4875
4875
  /**
4876
4876
  * Build a nudge message for when the model didn't produce structured output.
4877
4877
  * Used by the agent loop when it needs to retry.
4878
+ * User role is intentional: OpenAI-compatible chat templates such as Qwen
4879
+ * reject system messages anywhere except the beginning of the conversation.
4878
4880
  */
4879
4881
  buildNudgeMessage(isLastAttempt) {
4880
4882
  if (this.strategy.kind === "native") {
4881
4883
  return {
4882
- role: "system",
4884
+ role: "user",
4883
4885
  content: [
4884
4886
  "A structured output schema is active.",
4885
4887
  "You MUST respond with valid JSON matching the schema.",
@@ -4889,7 +4891,7 @@ var StructuredOutputManager = class {
4889
4891
  };
4890
4892
  }
4891
4893
  return {
4892
- role: "system",
4894
+ role: "user",
4893
4895
  content: [
4894
4896
  "A structured output schema is active.",
4895
4897
  "You MUST now call tool `response` with the final JSON object that matches the schema.",
@@ -4907,7 +4909,7 @@ var StructuredOutputManager = class {
4907
4909
  const fieldIssues = error.fieldErrors.map((f) => `- "${f.path}": ${f.message} (received: ${JSON.stringify(f.received)})`).join("\n");
4908
4910
  const action = this.strategy.kind === "native" ? "Please output corrected JSON matching the schema." : "Please call `response` again with corrected values matching the schema.";
4909
4911
  return {
4910
- role: "system",
4912
+ role: "user",
4911
4913
  content: [
4912
4914
  "Your previous structured output had validation errors:",
4913
4915
  fieldIssues,
@@ -8781,12 +8783,19 @@ function parseSSEBlock(block) {
8781
8783
 
8782
8784
  // src/providers/utils/reasoning.ts
8783
8785
  var ANTHROPIC_EFFORT_BUDGET = {
8786
+ // 0 = off. `applyAnthropicReasoning` bails on a falsy budget and emits no
8787
+ // thinking block, which on Anthropic IS off — thinking there is opt-in.
8788
+ none: 0,
8784
8789
  minimal: 1024,
8785
8790
  low: 2048,
8786
8791
  medium: 8192,
8787
8792
  high: 16384
8788
8793
  };
8789
8794
  var GEMINI_EFFORT_BUDGET = {
8795
+ // 0 is Gemini's own documented spelling for "do not think", and unlike
8796
+ // Anthropic it has to be SENT — hence `applyGeminiReasoning` testing for null
8797
+ // rather than falsiness.
8798
+ none: 0,
8790
8799
  minimal: 512,
8791
8800
  low: 1024,
8792
8801
  medium: 4096,
@@ -8862,14 +8871,15 @@ function clampBudget(budget, maxTokens) {
8862
8871
  }
8863
8872
  function normalizeEffort(effort) {
8864
8873
  if (!effort) return void 0;
8865
- const allowed = ["minimal", "low", "medium", "high"];
8874
+ const allowed = ["none", "minimal", "low", "medium", "high"];
8866
8875
  return allowed.includes(effort) ? effort : void 0;
8867
8876
  }
8868
8877
  function resolveBudget(reasoning, effortBudget) {
8878
+ const effort = normalizeEffort(reasoning.effort);
8879
+ if (effort === "none") return 0;
8869
8880
  if (typeof reasoning.budgetTokens === "number" && reasoning.budgetTokens > 0) {
8870
8881
  return Math.floor(reasoning.budgetTokens);
8871
8882
  }
8872
- const effort = normalizeEffort(reasoning.effort);
8873
8883
  if (effort && effortBudget[effort]) return effortBudget[effort];
8874
8884
  return void 0;
8875
8885
  }
@@ -8958,12 +8968,21 @@ var OpenAIProvider = class extends BaseProvider {
8958
8968
  ...config.defaultHeaders,
8959
8969
  ...config.organization ? { "OpenAI-Organization": config.organization } : {}
8960
8970
  };
8971
+ this.responsesApi = config.responsesApi ?? "auto";
8961
8972
  if (config.retry) this.retryPolicy = { ...this.retryPolicy, ...config.retry };
8962
8973
  }
8963
- /** True when the request targets a reasoning model (o-series / gpt-5) so we
8964
- * should route through the Responses API, which exposes `reasoning.effort`
8965
- * and reasoning summaries. Chat Completions silently drops these. */
8974
+ /** True when this request should go to the Responses API rather than Chat
8975
+ * Completions — which exposes `reasoning.effort` and reasoning summaries that
8976
+ * Chat Completions silently drops.
8977
+ *
8978
+ * `auto` (the default) keeps the historical rule: only when the request
8979
+ * carries a reasoning config AND the model name looks like an o-series/gpt-5
8980
+ * model. `never`/`always` let a caller state the answer instead, because the
8981
+ * name check is a guess and the two APIs differ in far more than reasoning —
8982
+ * see `responsesApi` in OpenAIProviderConfig. */
8966
8983
  useResponsesApi(request) {
8984
+ if (this.responsesApi === "never") return false;
8985
+ if (this.responsesApi === "always") return true;
8967
8986
  if (!request.reasoning) return false;
8968
8987
  return isReasoningModel(request.model || this.defaultModel);
8969
8988
  }
@@ -10826,7 +10845,7 @@ function fromNativeProvider(provider, options) {
10826
10845
  if (tools?.length) req.tools = tools;
10827
10846
  if (opts?.extra) req.extra = opts.extra;
10828
10847
  const reasoningOverride = invokeOptions?.reasoning;
10829
- const reasoningCfg = reasoningOverride ?? opts?.reasoning;
10848
+ const reasoningCfg = mergeReasoning(opts?.reasoning, reasoningOverride);
10830
10849
  if (reasoningCfg) req.reasoning = reasoningCfg;
10831
10850
  const tc = invokeOptions?.tool_choice ?? invokeOptions?.toolChoice;
10832
10851
  if (tc && tools?.length) {
@@ -10855,6 +10874,15 @@ function fromNativeProvider(provider, options) {
10855
10874
  return req;
10856
10875
  }
10857
10876
  }
10877
+ function mergeReasoning(base, override) {
10878
+ if (!base) return override;
10879
+ if (!override) return base;
10880
+ const merged = { ...base, ...override };
10881
+ if (base.providerExtras || override.providerExtras) {
10882
+ merged.providerExtras = { ...base.providerExtras, ...override.providerExtras };
10883
+ }
10884
+ return merged;
10885
+ }
10858
10886
  function toUnifiedMessage(msg) {
10859
10887
  const unified = {
10860
10888
  role: msg.role,