@agentionai/agents 1.2.0 → 1.4.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.
@@ -16,6 +16,27 @@ export interface CommonAgentConfig {
16
16
  description: string;
17
17
  /** API key for authenticating with the LLM provider */
18
18
  apiKey: string;
19
+ /**
20
+ * Extra HTTP headers sent with every request to the provider.
21
+ *
22
+ * Useful for gateway and proxy attribution, tracing, or corporate egress
23
+ * requirements. OpenRouter, for example, uses `HTTP-Referer` and `X-Title`
24
+ * to attribute traffic to your app:
25
+ *
26
+ * ```typescript
27
+ * defaultHeaders: {
28
+ * "HTTP-Referer": "https://myapp.example",
29
+ * "X-Title": "My App",
30
+ * }
31
+ * ```
32
+ *
33
+ * These override headers the agent would otherwise set, including
34
+ * `Authorization` / `x-api-key`. That follows the Anthropic and OpenAI SDKs'
35
+ * own `defaultHeaders` behaviour and is deliberate — it lets a gateway swap
36
+ * in its own auth scheme. The flip side is that setting an auth header here
37
+ * replaces `apiKey`, so do it only when that is what you mean.
38
+ */
39
+ defaultHeaders?: Record<string, string>;
19
40
  /** Enable debug logging for troubleshooting (default: false) */
20
41
  debug?: boolean;
21
42
  /** Maximum number of messages to retain in conversation history */
@@ -56,6 +56,14 @@ export type TokenUsage = {
56
56
  * Generation throughput: `output_tokens` over `generationMs`. Falls back to
57
57
  * `totalMs` when the first-token time is unknown (an unstreamed call), in
58
58
  * which case it is an end-to-end rate rather than a pure generation rate.
59
+ *
60
+ * Accurate wherever thinking is streamed, because the first thinking chunk
61
+ * starts the generation window — verified on Anthropic extended thinking and
62
+ * on DeepSeek via OpenRouter. It over-reports on OpenAI's Responses API,
63
+ * which does not stream raw reasoning: there the thinking finishes before the
64
+ * first visible token, so it lands inside `timeToFirstTokenMs` while its
65
+ * tokens still count toward `output_tokens`. For the visible-output rate on
66
+ * that path, divide `output_tokens - reasoning_tokens` by `generationMs`.
59
67
  */
60
68
  outputTokensPerSecond?: number;
61
69
  };
@@ -40,10 +40,10 @@ class ClaudeAgent extends BaseAgent_1.BaseAgent {
40
40
  const authType = config.authType ?? vendorConfig.authType ?? "apiKey";
41
41
  const thinkingBudgetTokens = config.thinkingBudgetTokens ?? vendorConfig.thinkingBudgetTokens;
42
42
  this.client = new sdk_1.Anthropic(authType === "oauth"
43
- ? { authToken: config.apiKey }
44
- : { apiKey: config.apiKey });
43
+ ? { authToken: config.apiKey, defaultHeaders: config.defaultHeaders }
44
+ : { apiKey: config.apiKey, defaultHeaders: config.defaultHeaders });
45
45
  this.config = {
46
- model: config.model || "claude-3-5-haiku-latest",
46
+ model: config.model || "claude-haiku-4-5",
47
47
  maxTokens: config.maxTokens || 1024,
48
48
  disableParallelToolUse,
49
49
  metadata,
@@ -35,7 +35,7 @@ class GeminiAgent extends BaseAgent_1.BaseAgent {
35
35
  const responseMimeType = config.responseMimeType ?? vendorConfig.responseMimeType;
36
36
  const responseSchema = config.responseSchema ?? vendorConfig.responseSchema;
37
37
  this.config = {
38
- model: config.model || "gemini-2.0-flash",
38
+ model: config.model || "gemini-flash-latest",
39
39
  maxTokens: config.maxTokens || 1024,
40
40
  apiKey: config.apiKey,
41
41
  temperature: config.temperature,
@@ -47,9 +47,7 @@ class GeminiAgent extends BaseAgent_1.BaseAgent {
47
47
  responseSchema,
48
48
  };
49
49
  // Initialize the model
50
- this.generativeModel = this.client.getGenerativeModel({
51
- model: this.config.model,
52
- });
50
+ this.generativeModel = this.client.getGenerativeModel({ model: this.config.model }, config.defaultHeaders ? { customHeaders: config.defaultHeaders } : undefined);
53
51
  // Add system message to history (skips if already exists with same content)
54
52
  this.addSystemMessage(this.getSystemMessage());
55
53
  }
@@ -26,6 +26,16 @@ type AgentConfig = BaseAgentConfig & {
26
26
  * const response = await agent.execute("Hello!");
27
27
  * ```
28
28
  */
29
+ /**
30
+ * Build a `beforeRequest` hook that adds custom headers to every request.
31
+ *
32
+ * The Mistral SDK has no `defaultHeaders` option like the Anthropic and OpenAI
33
+ * clients, so headers are injected at the HTTP layer instead. They overwrite
34
+ * headers the SDK already set, so that `defaultHeaders` means the same thing on
35
+ * every provider — see `CommonAgentConfig.defaultHeaders`. Verified against the
36
+ * OpenAI SDK on the wire: its `defaultHeaders` win over the client's own auth.
37
+ */
38
+ export declare function defaultHeadersHook(headers: Record<string, string>): (request: Request) => void;
29
39
  export declare class MistralAgent extends BaseAgent {
30
40
  private client;
31
41
  protected config: Partial<AgentConfig>;
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MistralAgent = void 0;
4
+ exports.defaultHeadersHook = defaultHeadersHook;
4
5
  const mistralai_1 = require("@mistralai/mistralai");
6
+ const http_1 = require("@mistralai/mistralai/lib/http");
5
7
  const BaseAgent_1 = require("../BaseAgent");
6
8
  const AgentEvent_1 = require("../AgentEvent");
7
9
  const AgentError_1 = require("../errors/AgentError");
@@ -25,13 +27,34 @@ const VizConfig_1 = require("../../viz/VizConfig");
25
27
  * const response = await agent.execute("Hello!");
26
28
  * ```
27
29
  */
30
+ /**
31
+ * Build a `beforeRequest` hook that adds custom headers to every request.
32
+ *
33
+ * The Mistral SDK has no `defaultHeaders` option like the Anthropic and OpenAI
34
+ * clients, so headers are injected at the HTTP layer instead. They overwrite
35
+ * headers the SDK already set, so that `defaultHeaders` means the same thing on
36
+ * every provider — see `CommonAgentConfig.defaultHeaders`. Verified against the
37
+ * OpenAI SDK on the wire: its `defaultHeaders` win over the client's own auth.
38
+ */
39
+ function defaultHeadersHook(headers) {
40
+ return (request) => {
41
+ for (const [name, value] of Object.entries(headers)) {
42
+ request.headers.set(name, value);
43
+ }
44
+ };
45
+ }
28
46
  class MistralAgent extends BaseAgent_1.BaseAgent {
29
47
  constructor(config, history) {
30
48
  super({ ...config, vendor: "mistral" }, history);
31
49
  /** Count of tool calls in current execution */
32
50
  this.currentToolCallCount = 0;
51
+ const httpClient = new http_1.HTTPClient();
52
+ if (config.defaultHeaders) {
53
+ httpClient.addHook("beforeRequest", defaultHeadersHook(config.defaultHeaders));
54
+ }
33
55
  this.client = new mistralai_1.Mistral({
34
56
  apiKey: config.apiKey,
57
+ httpClient,
35
58
  });
36
59
  // Merge flat config (deprecated) with nested vendorConfig
37
60
  // Flat config takes precedence for backward compatibility
@@ -8,19 +8,19 @@
8
8
  * You can also provide any custom string for newer models not yet listed.
9
9
  * @see https://docs.anthropic.com/en/docs/about-claude/models
10
10
  */
11
- export type ClaudeModel = "claude-opus-4-7" | "claude-opus-4-6" | "claude-sonnet-4-6" | "claude-opus-4-5" | "claude-sonnet-4-5" | "claude-haiku-4-5" | "claude-haiku-4-5-20251001" | (string & Record<never, never>);
11
+ export type ClaudeModel = "claude-opus-5" | "claude-sonnet-5" | "claude-fable-5" | "claude-opus-4-8" | "claude-opus-4-7" | "claude-opus-4-6" | "claude-sonnet-4-6" | "claude-opus-4-5" | "claude-opus-4-5-20251101" | "claude-sonnet-4-5" | "claude-sonnet-4-5-20250929" | "claude-haiku-4-5" | "claude-haiku-4-5-20251001" | (string & Record<never, never>);
12
12
  /**
13
13
  * Supported Google Gemini models.
14
14
  * You can also provide any custom string for newer models not yet listed.
15
15
  * @see https://ai.google.dev/gemini-api/docs/models/gemini
16
16
  */
17
- export type GeminiModel = "gemini-flash-latest" | "gemini-flash-lite-latest" | "gemini-3.0-pro" | "gemini-3.0-flash" | "gemini-2.5-flash-lite" | "gemini-2.0-flash-exp" | "gemini-2.0-flash" | "gemini-2.0-flash-lite" | (string & {});
17
+ export type GeminiModel = "gemini-pro-latest" | "gemini-flash-latest" | "gemini-flash-lite-latest" | "gemini-3.6-flash" | "gemini-3.5-flash" | "gemini-3.5-flash-lite" | "gemini-3.1-pro-preview" | "gemini-3.1-flash-lite" | "gemini-3.1-flash-lite-preview" | "gemini-3-pro-preview" | "gemini-3-flash-preview" | "gemini-2.5-pro" | "gemini-2.5-flash" | "gemini-2.5-flash-lite" | "gemini-2.0-flash" | "gemini-2.0-flash-001" | "gemini-2.0-flash-lite" | "gemini-2.0-flash-lite-001" | (string & {});
18
18
  /**
19
19
  * Supported Mistral models.
20
20
  * You can also provide any custom string for newer models not yet listed.
21
21
  * @see https://docs.mistral.ai/getting-started/models/
22
22
  */
23
- export type MistralModel = "mistral-large-latest" | "mistral-small-latest" | "ministral-8b-latest" | "ministral-8b-2410" | "ministral-3b-latest" | "ministral-3b-2410" | "codestral-latest" | "codestral-2405" | "mistral-embed" | "mistral-moderation-latest" | "mistral-moderation-2411" | (string & {});
23
+ export type MistralModel = "mistral-large-latest" | "mistral-large-2512" | "mistral-medium-latest" | "mistral-medium-3.5" | "mistral-small-latest" | "mistral-small-2603" | "magistral-small-latest" | "ministral-14b-latest" | "ministral-8b-latest" | "ministral-8b-2512" | "ministral-3b-latest" | "ministral-3b-2512" | "codestral-latest" | "codestral-2508" | "devstral-latest" | "devstral-medium-latest" | "mistral-embed" | "codestral-embed" | "mistral-moderation-latest" | "mistral-moderation-2603" | "mistral-ocr-latest" | (string & {});
24
24
  /**
25
25
  * Popular Ollama models (locally hosted).
26
26
  * You can also provide any custom string for models you have pulled.
@@ -80,6 +80,7 @@ class OllamaAgent extends BaseAgent_1.BaseAgent {
80
80
  this.config = {
81
81
  model: config.model || "llama3.2",
82
82
  host,
83
+ defaultHeaders: config.defaultHeaders,
83
84
  maxTokens: config.maxTokens,
84
85
  temperature: config.temperature,
85
86
  topP: config.topP,
@@ -99,7 +100,10 @@ class OllamaAgent extends BaseAgent_1.BaseAgent {
99
100
  if (!OllamaClass) {
100
101
  throw new Error("Could not find Ollama class in ollama package");
101
102
  }
102
- this._client = new OllamaClass({ host: this.config.host });
103
+ this._client = new OllamaClass({
104
+ host: this.config.host,
105
+ headers: this.config.defaultHeaders,
106
+ });
103
107
  }
104
108
  catch (err) {
105
109
  throw new AgentError_1.ExecutionError(`Failed to load 'ollama' package. Install it with: npm install ollama\n${err instanceof Error ? err.message : String(err)}`);
@@ -58,6 +58,7 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
58
58
  this.currentToolCallCount = 0;
59
59
  this.client = new openai_1.default({
60
60
  apiKey: config.apiKey,
61
+ defaultHeaders: config.defaultHeaders,
61
62
  });
62
63
  // Merge flat config (deprecated) with nested vendorConfig
63
64
  // Flat config takes precedence for backward compatibility
@@ -28,6 +28,7 @@ class OpenAICompatibleAgent extends BaseAgent_1.BaseAgent {
28
28
  this.client = new openai_1.default({
29
29
  apiKey: config.apiKey || "not-needed",
30
30
  baseURL: config.baseURL,
31
+ defaultHeaders: config.defaultHeaders,
31
32
  });
32
33
  this.config = {
33
34
  model: config.model,
@@ -320,13 +321,17 @@ class OpenAICompatibleAgent extends BaseAgent_1.BaseAgent {
320
321
  let reasoningContent = "";
321
322
  const toolCallAcc = new Map();
322
323
  let finishReason = null;
324
+ let streamUsage;
323
325
  for await (const chunk of stream) {
324
- // Final chunk carrying usage (choices is empty)
325
- if (chunk.choices.length === 0) {
326
- if (chunk.usage)
327
- this.accumulateStreamUsage(chunk.usage);
326
+ // Usage can ride on any chunk: OpenAI sends it on a final choice-less
327
+ // chunk, OpenRouter attaches it to the last content chunk (the one
328
+ // carrying finish_reason). Keep the most recent and fold it in once the
329
+ // stream ends — it is a running total for the turn, not a delta, so
330
+ // taking the last one covers both layouts without double-counting.
331
+ if (chunk.usage)
332
+ streamUsage = chunk.usage;
333
+ if (chunk.choices.length === 0)
328
334
  continue;
329
- }
330
335
  const choice = chunk.choices[0];
331
336
  finishReason = choice.finish_reason ?? finishReason;
332
337
  const delta = choice.delta;
@@ -366,6 +371,10 @@ class OpenAICompatibleAgent extends BaseAgent_1.BaseAgent {
366
371
  }
367
372
  }
368
373
  }
374
+ // Before any early return below, so a turn that hits the token limit or
375
+ // continues into a tool call still reports what it spent.
376
+ if (streamUsage)
377
+ this.accumulateStreamUsage(streamUsage);
369
378
  if (finishReason === "length") {
370
379
  const error = new AgentError_1.MaxTokensExceededError("Response exceeded maximum token limit", this.config.maxTokens || 1024);
371
380
  this.emit(AgentEvent_1.AgentEvent.MAX_TOKENS_EXCEEDED, error);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@agentionai/agents",
3
3
  "author": "Laurent Zuijdwijk",
4
- "version": "1.2.0",
4
+ "version": "1.4.0",
5
5
  "description": "Agent Library",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",