@agentionai/agents 1.4.0 → 1.6.0-beta.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/README.md CHANGED
@@ -75,7 +75,7 @@ import { ClaudeAgent, OpenAiAgent } from '@agentionai/agents';
75
75
 
76
76
  ## Features
77
77
 
78
- - **Multi-Provider, No Lock-in** - Claude, OpenAI, Gemini, Mistral, plus local models via Ollama and llama.cpp—same interface. Switch models with one line.
78
+ - **Multi-Provider, No Lock-in** - Claude, OpenAI, Gemini, Mistral, plus local models via Ollama and llama.cpp—same interface. Switch models with one line. `listModels()` asks any provider what it currently offers, in one shared shape.
79
79
  - **Composable Context Management** - Tool result masking (lossless, free) + rolling summarization (auto-firing) + sub-agent delegation (token isolation by architecture).
80
80
  - **Streaming** - `executeStream()` on Claude, OpenAI, and all OpenAI-compatible agents. Yields `{ type: "text" | "reasoning" }` chunks; tool calls handled transparently.
81
81
  - **Built-In Tools** - Use provider-defined server-side tools (e.g. Anthropic's web search, bash, text editor) alongside your own.
@@ -67,6 +67,76 @@ export type TokenUsage = {
67
67
  */
68
68
  outputTokensPerSecond?: number;
69
69
  };
70
+ /**
71
+ * What a model can do, as reported by its provider.
72
+ *
73
+ * Every flag is three-valued: `true` and `false` are the provider's answer,
74
+ * `undefined` means it does not report on that capability at all — which is the
75
+ * common case, since no provider covers all four. Filter with `!== false` when
76
+ * you want "not known to be unsupported", and with `=== true` when you need
77
+ * positive confirmation.
78
+ */
79
+ export type ModelCapabilities = {
80
+ /** Conversational generation — what an agent needs to run at all. */
81
+ chat?: boolean;
82
+ /** Function / tool calling. */
83
+ tools?: boolean;
84
+ /** Image input. */
85
+ vision?: boolean;
86
+ /** Extended thinking / reasoning. */
87
+ thinking?: boolean;
88
+ };
89
+ /**
90
+ * A model as reported by a provider's models endpoint, in a shape that is the
91
+ * same on every provider.
92
+ *
93
+ * Only `id` — the value you pass as `model` in an agent config — is guaranteed.
94
+ * Every other field is optional because no two providers report the same set:
95
+ * Anthropic gives a display name and release date but no context window,
96
+ * OpenAI gives an owner and a creation timestamp, Mistral and Gemini give
97
+ * context limits. The provider's own untouched entry is always available on
98
+ * `raw` for anything not covered here.
99
+ */
100
+ export type ModelInfo<TRaw = unknown> = {
101
+ /** Model identifier, as passed to the API in the `model` field. */
102
+ id: string;
103
+ /** Human-readable name, where the provider reports one. */
104
+ displayName?: string;
105
+ /** Release or creation date, where the provider reports one. */
106
+ created?: Date;
107
+ /** Owning organisation, where the provider reports one. */
108
+ ownedBy?: string;
109
+ /** Maximum input context in tokens, where the provider reports one. */
110
+ contextLength?: number;
111
+ /** Maximum tokens in a single response, where the provider reports one. */
112
+ maxOutputTokens?: number;
113
+ /**
114
+ * What the provider says this model supports. Absent flags mean "not
115
+ * reported", never "unsupported" — see {@link ModelCapabilities}.
116
+ */
117
+ capabilities?: ModelCapabilities;
118
+ /**
119
+ * When the provider plans to retire the model, where it publishes a date.
120
+ * Only Mistral does today; note that a model can stop working before any
121
+ * announced date, and Google in particular retires models that its listing
122
+ * still advertises.
123
+ */
124
+ deprecatedAt?: Date;
125
+ /** Model the provider recommends in its place, where it names one. */
126
+ replacedBy?: string;
127
+ /**
128
+ * Whether the model is currently held in memory, on servers that distinguish
129
+ * "offered" from "loaded" — llama.cpp's model router being the case in point,
130
+ * where an unloaded model is listed but has to be loaded before it answers.
131
+ *
132
+ * Undefined wherever the distinction does not exist or is not reported: every
133
+ * hosted provider, and a single-model `llama-server`, where the one model
134
+ * listed is by definition the loaded one.
135
+ */
136
+ loaded?: boolean;
137
+ /** The provider's unmodified entry for this model. */
138
+ raw: TRaw;
139
+ };
70
140
  /**
71
141
  * The base agent is what the other agents are inheriting from
72
142
  * Handles the BaseConfig
@@ -107,6 +177,19 @@ export declare abstract class BaseAgent<TInput = unknown, TOutput = unknown> ext
107
177
  protected abstract process(input: TInput): Promise<TOutput>;
108
178
  protected abstract handleResponse(response: unknown): Promise<unknown>;
109
179
  protected getToolDefinitions(): unknown[];
180
+ /**
181
+ * List the models the provider currently offers, straight from its models
182
+ * endpoint — the live answer, as opposed to the hand-maintained unions in
183
+ * `model-types.ts`.
184
+ *
185
+ * Overridden by every built-in agent; the base implementation throws so that
186
+ * a custom agent without a models endpoint fails with a clear message rather
187
+ * than silently returning nothing.
188
+ *
189
+ * @throws {ExecutionError} If the provider does not support listing, or the
190
+ * request fails.
191
+ */
192
+ listModels(): Promise<ModelInfo[]>;
110
193
  /**
111
194
  * Add an entry to history
112
195
  */
@@ -7,6 +7,7 @@ exports.BaseAgent = void 0;
7
7
  const events_1 = __importDefault(require("events"));
8
8
  const Tool_1 = require("../tools/Tool");
9
9
  const History_1 = require("../history/History");
10
+ const AgentError_1 = require("./errors/AgentError");
10
11
  /**
11
12
  * The base agent is what the other agents are inheriting from
12
13
  * Handles the BaseConfig
@@ -49,6 +50,21 @@ class BaseAgent extends events_1.default {
49
50
  getToolDefinitions() {
50
51
  return Array.from(this.tools.values()).map((tool) => tool.getPrompt());
51
52
  }
53
+ /**
54
+ * List the models the provider currently offers, straight from its models
55
+ * endpoint — the live answer, as opposed to the hand-maintained unions in
56
+ * `model-types.ts`.
57
+ *
58
+ * Overridden by every built-in agent; the base implementation throws so that
59
+ * a custom agent without a models endpoint fails with a clear message rather
60
+ * than silently returning nothing.
61
+ *
62
+ * @throws {ExecutionError} If the provider does not support listing, or the
63
+ * request fails.
64
+ */
65
+ async listModels() {
66
+ throw new AgentError_1.ExecutionError(`listModels() is not implemented for the '${this.vendor}' agent`);
67
+ }
52
68
  /**
53
69
  * Add an entry to history
54
70
  */
@@ -1,11 +1,57 @@
1
1
  import { Anthropic } from "@anthropic-ai/sdk";
2
- import { Message, ToolUnion, Usage } from "@anthropic-ai/sdk/resources";
2
+ import { Message, type ModelInfo as AnthropicModelInfo, ToolUnion, Usage } from "@anthropic-ai/sdk/resources";
3
3
  import { type ToolDefinition } from "../../tools/Tool";
4
4
  import { type BuiltInTool } from "../../tools/BuiltInTool";
5
- import { BaseAgent, BaseAgentConfig, TokenUsage } from "../BaseAgent";
5
+ import { BaseAgent, BaseAgentConfig, ModelInfo, TokenUsage } from "../BaseAgent";
6
6
  import { History, MessageContent } from "../../history/History";
7
7
  import { StreamChunk } from "../openai-compatible/OpenAICompatibleAgent";
8
8
  import { ClaudeModel } from "../model-types";
9
+ /** A capability node in Anthropic's model card — always at least `supported`. */
10
+ type AnthropicSupported = {
11
+ supported: boolean;
12
+ };
13
+ /**
14
+ * One entry from Anthropic's `/v1/models`.
15
+ *
16
+ * Declared here rather than taken from the SDK, whose `ModelInfo` still covers
17
+ * only `id`/`type`/`display_name`/`created_at`. The API also returns token
18
+ * limits and a capability tree — verified on the wire on 2026-08-11 — and those
19
+ * are what `contextLength`, `maxOutputTokens` and `capabilities` are read from.
20
+ * Everything past the four SDK fields is optional so that an older API version,
21
+ * or a gateway that trims the response, still typechecks.
22
+ */
23
+ export type AnthropicModelCard = AnthropicModelInfo & {
24
+ /** Context window in tokens. */
25
+ max_input_tokens?: number;
26
+ /** Largest `max_tokens` the model accepts for a response. */
27
+ max_tokens?: number;
28
+ capabilities?: {
29
+ batch?: AnthropicSupported;
30
+ citations?: AnthropicSupported;
31
+ code_execution?: AnthropicSupported;
32
+ /** Server-side context editing; the dated keys are individual strategies. */
33
+ context_management?: AnthropicSupported & {
34
+ [strategy: string]: AnthropicSupported | boolean | undefined;
35
+ };
36
+ /** Which effort levels the model accepts — the live answer to what `model-types.ts` hardcodes. */
37
+ effort?: AnthropicSupported & {
38
+ low?: AnthropicSupported;
39
+ medium?: AnthropicSupported;
40
+ high?: AnthropicSupported;
41
+ xhigh?: AnthropicSupported;
42
+ max?: AnthropicSupported;
43
+ };
44
+ image_input?: AnthropicSupported;
45
+ pdf_input?: AnthropicSupported;
46
+ structured_outputs?: AnthropicSupported;
47
+ thinking?: AnthropicSupported & {
48
+ types?: {
49
+ enabled?: AnthropicSupported;
50
+ adaptive?: AnthropicSupported;
51
+ };
52
+ };
53
+ };
54
+ };
9
55
  type AgentConfig = BaseAgentConfig & {
10
56
  apiKey: string;
11
57
  model?: ClaudeModel;
@@ -55,6 +101,20 @@ export declare class ClaudeAgent extends BaseAgent {
55
101
  private currentToolCallCount;
56
102
  constructor(config: Omit<AgentConfig, "vendor">, history?: History);
57
103
  protected getToolDefinitions(): ToolDefinition[];
104
+ /**
105
+ * List the models available to this API key, newest first.
106
+ *
107
+ * Anthropic reports token limits and a capability tree that the SDK's own
108
+ * type omits, so `raw` is typed as {@link AnthropicModelCard} — which is also
109
+ * where `contextLength` (`max_input_tokens`), `maxOutputTokens` and the
110
+ * vision/thinking flags come from. `capabilities.effort` on `raw` states which
111
+ * effort levels each model accepts. Tool support is not reported; every model
112
+ * the endpoint lists supports tools, so `capabilities.tools` stays undefined
113
+ * rather than being asserted.
114
+ *
115
+ * The result is fully paginated — the endpoint pages at 1000 models.
116
+ */
117
+ listModels(): Promise<ModelInfo<AnthropicModelCard>[]>;
58
118
  /**
59
119
  * Combine locally-executed tool definitions with provider-defined
60
120
  * (server-side) built-in tools, in the shape Anthropic's API expects.
@@ -62,6 +62,43 @@ class ClaudeAgent extends BaseAgent_1.BaseAgent {
62
62
  getToolDefinitions() {
63
63
  return Array.from(this.tools.values()).map((tool) => tool.getPrompt());
64
64
  }
65
+ /**
66
+ * List the models available to this API key, newest first.
67
+ *
68
+ * Anthropic reports token limits and a capability tree that the SDK's own
69
+ * type omits, so `raw` is typed as {@link AnthropicModelCard} — which is also
70
+ * where `contextLength` (`max_input_tokens`), `maxOutputTokens` and the
71
+ * vision/thinking flags come from. `capabilities.effort` on `raw` states which
72
+ * effort levels each model accepts. Tool support is not reported; every model
73
+ * the endpoint lists supports tools, so `capabilities.tools` stays undefined
74
+ * rather than being asserted.
75
+ *
76
+ * The result is fully paginated — the endpoint pages at 1000 models.
77
+ */
78
+ async listModels() {
79
+ try {
80
+ const models = [];
81
+ for await (const model of this.client.models.list({ limit: 1000 })) {
82
+ const card = model;
83
+ models.push({
84
+ id: card.id,
85
+ displayName: card.display_name,
86
+ created: new Date(card.created_at),
87
+ contextLength: card.max_input_tokens,
88
+ maxOutputTokens: card.max_tokens,
89
+ capabilities: {
90
+ vision: card.capabilities?.image_input?.supported,
91
+ thinking: card.capabilities?.thinking?.supported,
92
+ },
93
+ raw: card,
94
+ });
95
+ }
96
+ return models;
97
+ }
98
+ catch (error) {
99
+ throw new AgentError_1.ExecutionError(`Failed to list Anthropic models: ${error instanceof Error ? error.message : "Unknown error"}`);
100
+ }
101
+ }
65
102
  /**
66
103
  * Combine locally-executed tool definitions with provider-defined
67
104
  * (server-side) built-in tools, in the shape Anthropic's API expects.
@@ -1,7 +1,65 @@
1
1
  import { FunctionDeclarationsTool, GenerateContentResult, Schema } from "@google/generative-ai";
2
- import { BaseAgent, BaseAgentConfig, TokenUsage } from "../BaseAgent";
2
+ import { BaseAgent, BaseAgentConfig, ModelInfo, TokenUsage } from "../BaseAgent";
3
3
  import { History, MessageContent } from "../../history/History";
4
4
  import { GeminiModel } from "../model-types";
5
+ /**
6
+ * Models that `models.list` still advertises but the API no longer serves.
7
+ *
8
+ * Google leaves retired models in the listing, fully described and claiming
9
+ * `generateContent`; calling one fails with `404 — "This model is no longer
10
+ * available to new users"`. Nothing in the listing distinguishes them, and the
11
+ * stable `v1` endpoint carries them too, so the only way to keep them out of
12
+ * `listModels()` is to name them.
13
+ *
14
+ * A retirement is permanent, so this list only ever grows — an entry never
15
+ * needs revisiting, and one that disappears from the API's listing costs
16
+ * nothing to keep.
17
+ *
18
+ * Every entry is confirmed by probing `countTokens` (free, and it 404s the same
19
+ * way), most recently on 2026-08-11. Note that retirement is per-model, not per
20
+ * family: `gemini-2.5-flash-image`, `gemini-2.5-*-preview-tts` and
21
+ * `gemini-2.5-computer-use-preview-10-2025` were all still live at that date,
22
+ * which is why these are listed individually rather than matched by prefix.
23
+ *
24
+ * Pass `{ includeRetired: true }` to `listModels()` to see them anyway.
25
+ */
26
+ export declare const GEMINI_RETIRED_MODELS: readonly string[];
27
+ /** Options for {@link GeminiAgent.listModels}. */
28
+ export type GeminiListModelsOptions = {
29
+ /**
30
+ * Include models known to have been retired. Off by default: they are listed
31
+ * by the API but fail at call time.
32
+ */
33
+ includeRetired?: boolean;
34
+ };
35
+ /**
36
+ * One entry from the Generative Language API's `models.list` response.
37
+ *
38
+ * Declared here rather than imported: `@google/generative-ai` only covers
39
+ * content generation and ships no type for the models endpoint.
40
+ */
41
+ export type GeminiModelCard = {
42
+ /** Resource name, e.g. `"models/gemini-flash-latest"`. */
43
+ name: string;
44
+ baseModelId?: string;
45
+ version?: string;
46
+ displayName?: string;
47
+ description?: string;
48
+ inputTokenLimit?: number;
49
+ outputTokenLimit?: number;
50
+ /**
51
+ * e.g. `["generateContent", "countTokens"]`. Embedding models have
52
+ * `embedContent`, Imagen `predict`, Veo `predictLongRunning`, and the live
53
+ * models only `bidiGenerateContent` — none of which an agent can drive.
54
+ */
55
+ supportedGenerationMethods?: string[];
56
+ /** Whether the model reasons before answering. */
57
+ thinking?: boolean;
58
+ temperature?: number;
59
+ maxTemperature?: number;
60
+ topP?: number;
61
+ topK?: number;
62
+ };
5
63
  type AgentConfig = BaseAgentConfig & {
6
64
  apiKey: string;
7
65
  model?: GeminiModel;
@@ -34,6 +92,23 @@ export declare class GeminiAgent extends BaseAgent {
34
92
  /** Count of tool calls in current execution */
35
93
  private currentToolCallCount;
36
94
  constructor(config: Omit<AgentConfig, "vendor">, history?: History);
95
+ /**
96
+ * List the models available to this API key.
97
+ *
98
+ * Issued as a direct request to `/v1beta/models`: `@google/generative-ai`
99
+ * exposes no models endpoint, so there is no client method to call. Every
100
+ * page is followed, and the `"models/"` prefix is stripped from `id` so the
101
+ * value can be passed straight back as an agent's `model`.
102
+ *
103
+ * The list covers everything the key can reach, including embedding, image
104
+ * and live-audio models — `capabilities.chat` marks the ones an agent can
105
+ * actually drive.
106
+ *
107
+ * Models known to have been retired are left out, since the API lists them
108
+ * but no longer serves them — see {@link GEMINI_RETIRED_MODELS}. Pass
109
+ * `{ includeRetired: true }` for the listing exactly as Google returns it.
110
+ */
111
+ listModels(options?: GeminiListModelsOptions): Promise<ModelInfo<GeminiModelCard>[]>;
37
112
  protected getToolDefinitionsForGemini(): FunctionDeclarationsTool | undefined;
38
113
  /**
39
114
  * Convert JSON Schema to Gemini's FunctionDeclarationSchema format
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.GeminiAgent = void 0;
3
+ exports.GeminiAgent = exports.GEMINI_RETIRED_MODELS = void 0;
4
4
  const generative_ai_1 = require("@google/generative-ai");
5
5
  const BaseAgent_1 = require("../BaseAgent");
6
6
  const AgentEvent_1 = require("../AgentEvent");
@@ -8,6 +8,36 @@ const AgentError_1 = require("../errors/AgentError");
8
8
  const transformers_1 = require("../../history/transformers");
9
9
  const VizReporter_1 = require("../../viz/VizReporter");
10
10
  const VizConfig_1 = require("../../viz/VizConfig");
11
+ /** Base URL of the Generative Language API, matching the SDK's own default. */
12
+ const GEMINI_API_BASE = "https://generativelanguage.googleapis.com";
13
+ /**
14
+ * Models that `models.list` still advertises but the API no longer serves.
15
+ *
16
+ * Google leaves retired models in the listing, fully described and claiming
17
+ * `generateContent`; calling one fails with `404 — "This model is no longer
18
+ * available to new users"`. Nothing in the listing distinguishes them, and the
19
+ * stable `v1` endpoint carries them too, so the only way to keep them out of
20
+ * `listModels()` is to name them.
21
+ *
22
+ * A retirement is permanent, so this list only ever grows — an entry never
23
+ * needs revisiting, and one that disappears from the API's listing costs
24
+ * nothing to keep.
25
+ *
26
+ * Every entry is confirmed by probing `countTokens` (free, and it 404s the same
27
+ * way), most recently on 2026-08-11. Note that retirement is per-model, not per
28
+ * family: `gemini-2.5-flash-image`, `gemini-2.5-*-preview-tts` and
29
+ * `gemini-2.5-computer-use-preview-10-2025` were all still live at that date,
30
+ * which is why these are listed individually rather than matched by prefix.
31
+ *
32
+ * Pass `{ includeRetired: true }` to `listModels()` to see them anyway.
33
+ */
34
+ exports.GEMINI_RETIRED_MODELS = [
35
+ // Retired for new users some time before 2026-08-11
36
+ "gemini-2.5-flash",
37
+ "gemini-2.5-pro",
38
+ "gemini-2.5-flash-lite",
39
+ ];
40
+ const GEMINI_RETIRED = new Set(exports.GEMINI_RETIRED_MODELS);
11
41
  /**
12
42
  * Agent for Google Gemini models.
13
43
  *
@@ -45,12 +75,74 @@ class GeminiAgent extends BaseAgent_1.BaseAgent {
45
75
  candidateCount,
46
76
  responseMimeType,
47
77
  responseSchema,
78
+ defaultHeaders: config.defaultHeaders,
48
79
  };
49
80
  // Initialize the model
50
81
  this.generativeModel = this.client.getGenerativeModel({ model: this.config.model }, config.defaultHeaders ? { customHeaders: config.defaultHeaders } : undefined);
51
82
  // Add system message to history (skips if already exists with same content)
52
83
  this.addSystemMessage(this.getSystemMessage());
53
84
  }
85
+ /**
86
+ * List the models available to this API key.
87
+ *
88
+ * Issued as a direct request to `/v1beta/models`: `@google/generative-ai`
89
+ * exposes no models endpoint, so there is no client method to call. Every
90
+ * page is followed, and the `"models/"` prefix is stripped from `id` so the
91
+ * value can be passed straight back as an agent's `model`.
92
+ *
93
+ * The list covers everything the key can reach, including embedding, image
94
+ * and live-audio models — `capabilities.chat` marks the ones an agent can
95
+ * actually drive.
96
+ *
97
+ * Models known to have been retired are left out, since the API lists them
98
+ * but no longer serves them — see {@link GEMINI_RETIRED_MODELS}. Pass
99
+ * `{ includeRetired: true }` for the listing exactly as Google returns it.
100
+ */
101
+ async listModels(options) {
102
+ try {
103
+ const models = [];
104
+ let pageToken;
105
+ do {
106
+ const url = new URL(`${GEMINI_API_BASE}/v1beta/models`);
107
+ url.searchParams.set("pageSize", "1000");
108
+ if (pageToken) {
109
+ url.searchParams.set("pageToken", pageToken);
110
+ }
111
+ const response = await fetch(url, {
112
+ headers: {
113
+ "x-goog-api-key": this.config.apiKey,
114
+ ...this.config.defaultHeaders,
115
+ },
116
+ });
117
+ if (!response.ok) {
118
+ throw new Error(`${response.status} ${response.statusText}: ${await response.text()}`);
119
+ }
120
+ const body = (await response.json());
121
+ for (const model of body.models ?? []) {
122
+ const id = model.name.replace(/^models\//, "");
123
+ if (!options?.includeRetired && GEMINI_RETIRED.has(id)) {
124
+ continue;
125
+ }
126
+ models.push({
127
+ id,
128
+ displayName: model.displayName,
129
+ contextLength: model.inputTokenLimit,
130
+ maxOutputTokens: model.outputTokenLimit,
131
+ capabilities: {
132
+ chat: model.supportedGenerationMethods?.includes("generateContent"),
133
+ thinking: model.thinking,
134
+ },
135
+ raw: model,
136
+ });
137
+ }
138
+ pageToken = body.nextPageToken;
139
+ } while (pageToken);
140
+ return models;
141
+ }
142
+ catch (error) {
143
+ throw new AgentError_1.ExecutionError(`Failed to list Gemini models: ${error instanceof Error ? error.message : "Unknown error"}`);
144
+ }
145
+ }
54
146
  getToolDefinitionsForGemini() {
55
147
  const tools = Array.from(this.tools.values());
56
148
  if (tools.length === 0) {
@@ -1,6 +1,56 @@
1
+ import { Model } from "openai/resources/models";
1
2
  import { History } from "../../history/History";
3
+ import { ModelInfo } from "../BaseAgent";
2
4
  import { OpenAICompatibleAgent, OpenAICompatibleConfig } from "../openai-compatible/OpenAICompatibleAgent";
3
5
  import { LlamaCppModel } from "../model-types";
6
+ /**
7
+ * The GGUF details `llama-server` reports for a model it has loaded. Absent
8
+ * from the listing for a model the router knows about but has not loaded.
9
+ */
10
+ export type LlamaCppModelMeta = {
11
+ vocab_type?: number;
12
+ n_vocab?: number;
13
+ /** Context the model was actually loaded with (`--ctx-size`). */
14
+ n_ctx?: number;
15
+ /** Context the model was trained with — its ceiling, not its current size. */
16
+ n_ctx_train?: number;
17
+ n_embd?: number;
18
+ n_params?: number;
19
+ /** On-disk size in bytes. */
20
+ size?: number;
21
+ /** Quantization, e.g. `"Q6_K"`. */
22
+ ftype?: string;
23
+ };
24
+ /**
25
+ * One entry from a llama.cpp server's `/v1/models`.
26
+ *
27
+ * Everything past the OpenAI-standard fields is optional because it depends on
28
+ * how the server was started: a single-model `llama-server` reports `meta` for
29
+ * the model it is serving and nothing else, while a server in model-router mode
30
+ * lists every model it can serve, each with a `status` saying whether it is
31
+ * currently loaded. Verified against llama.cpp b10148.
32
+ */
33
+ export type LlamaCppModelCard = Model & {
34
+ /** Alternative ids that resolve to this model. */
35
+ aliases?: string[];
36
+ tags?: string[];
37
+ /** Router mode only: whether the model is in memory, and how it is launched. */
38
+ status?: {
39
+ value: "loaded" | "unloaded" | (string & {});
40
+ /** The `llama-server` argv the router uses to bring this model up. */
41
+ args?: string[];
42
+ /** The preset block backing this model, as INI text. */
43
+ preset?: string;
44
+ };
45
+ architecture?: {
46
+ input_modalities?: string[];
47
+ output_modalities?: string[];
48
+ };
49
+ /** Where the router got the model — a config preset or the local HF cache. */
50
+ source?: "preset" | "cache" | (string & {});
51
+ can_remove?: boolean;
52
+ meta?: LlamaCppModelMeta;
53
+ };
4
54
  type LlamaCppConfig = Omit<OpenAICompatibleConfig, "baseURL" | "model" | "vendor"> & {
5
55
  /** Base URL of the llama.cpp server's OpenAI-compatible API (default: `http://localhost:8080/v1`) */
6
56
  baseURL?: string;
@@ -32,6 +82,23 @@ type LlamaCppConfig = Omit<OpenAICompatibleConfig, "baseURL" | "model" | "vendor
32
82
  export declare class LlamaCppAgent extends OpenAICompatibleAgent {
33
83
  constructor(config: LlamaCppConfig, history?: History);
34
84
  protected getVendorName(): string;
85
+ /**
86
+ * List the models the server offers, adding the two things llama.cpp reports
87
+ * beyond the OpenAI-standard fields:
88
+ *
89
+ * - `loaded` — in model-router mode a listed model is not necessarily in
90
+ * memory; an unloaded one has to be loaded before it answers. Left
91
+ * undefined by a single-model server, which reports no status at all.
92
+ * - `contextLength` — `meta.n_ctx`, the context the model was actually loaded
93
+ * with, falling back to the trained ceiling `n_ctx_train`. Only loaded
94
+ * models carry `meta`.
95
+ *
96
+ * `capabilities.vision` follows from the declared input modalities. Tool
97
+ * support is not reported — it depends on the chat template, not the server —
98
+ * so it stays undefined. The rest, launch args and presets and quantization
99
+ * included, is on `raw`.
100
+ */
101
+ listModels(): Promise<ModelInfo<LlamaCppModelCard>[]>;
35
102
  }
36
103
  export {};
37
104
  //# sourceMappingURL=LlamaCppAgent.d.ts.map
@@ -39,6 +39,35 @@ class LlamaCppAgent extends OpenAICompatibleAgent_1.OpenAICompatibleAgent {
39
39
  getVendorName() {
40
40
  return "llama.cpp";
41
41
  }
42
+ /**
43
+ * List the models the server offers, adding the two things llama.cpp reports
44
+ * beyond the OpenAI-standard fields:
45
+ *
46
+ * - `loaded` — in model-router mode a listed model is not necessarily in
47
+ * memory; an unloaded one has to be loaded before it answers. Left
48
+ * undefined by a single-model server, which reports no status at all.
49
+ * - `contextLength` — `meta.n_ctx`, the context the model was actually loaded
50
+ * with, falling back to the trained ceiling `n_ctx_train`. Only loaded
51
+ * models carry `meta`.
52
+ *
53
+ * `capabilities.vision` follows from the declared input modalities. Tool
54
+ * support is not reported — it depends on the chat template, not the server —
55
+ * so it stays undefined. The rest, launch args and presets and quantization
56
+ * included, is on `raw`.
57
+ */
58
+ async listModels() {
59
+ const models = (await super.listModels());
60
+ return models.map((model) => ({
61
+ ...model,
62
+ loaded: model.raw.status
63
+ ? model.raw.status.value === "loaded"
64
+ : undefined,
65
+ contextLength: model.raw.meta?.n_ctx ?? model.raw.meta?.n_ctx_train,
66
+ capabilities: {
67
+ vision: model.raw.architecture?.input_modalities?.includes("image"),
68
+ },
69
+ }));
70
+ }
42
71
  }
43
72
  exports.LlamaCppAgent = LlamaCppAgent;
44
73
  //# sourceMappingURL=LlamaCppAgent.js.map
@@ -1,7 +1,13 @@
1
- import { BaseAgent, BaseAgentConfig, TokenUsage } from "../BaseAgent";
1
+ import { BaseAgent, BaseAgentConfig, ModelInfo, TokenUsage } from "../BaseAgent";
2
2
  import { History, MessageContent } from "../../history/History";
3
- import { ChatCompletionResponse, Tool, UsageInfo } from "@mistralai/mistralai/models/components";
3
+ import { ChatCompletionResponse, ModelList, Tool, UsageInfo } from "@mistralai/mistralai/models/components";
4
4
  import { MistralModel } from "../model-types";
5
+ /**
6
+ * One entry from Mistral's `/v1/models` response — a base model card or a
7
+ * fine-tuned one. Derived from the SDK's `ModelList` rather than imported
8
+ * directly, since the SDK exports the union under the unhelpful name `Data`.
9
+ */
10
+ export type MistralModelCard = NonNullable<ModelList["data"]>[number];
5
11
  type AgentConfig = BaseAgentConfig & {
6
12
  apiKey: string;
7
13
  model?: MistralModel;
@@ -44,6 +50,20 @@ export declare class MistralAgent extends BaseAgent {
44
50
  /** Count of tool calls in current execution */
45
51
  private currentToolCallCount;
46
52
  constructor(config: Omit<AgentConfig, "vendor">, history?: History);
53
+ /**
54
+ * List the models available to this API key, base and fine-tuned alike.
55
+ *
56
+ * Mistral is the most forthcoming of the providers: it reports a context
57
+ * window, a full capability set, and a retirement date with a replacement
58
+ * model. All of that is mapped onto the neutral fields.
59
+ *
60
+ * Note that `raw` here is the SDK's parsed view, not the wire response — the
61
+ * Mistral SDK validates against a schema that drops fields it does not know,
62
+ * so capabilities the API has added since the installed SDK version (as of
63
+ * `1.13.0`: `reasoning`, the audio flags) are gone before this code sees
64
+ * them. Every other agent's `raw` is the untouched response.
65
+ */
66
+ listModels(): Promise<ModelInfo<MistralModelCard>[]>;
47
67
  protected getToolDefinitions(): Tool[];
48
68
  protected process(_input: string): Promise<string>;
49
69
  execute(input: string | MessageContent[]): Promise<string>;
@@ -80,6 +80,42 @@ class MistralAgent extends BaseAgent_1.BaseAgent {
80
80
  // Add system message to history (skips if already exists with same content)
81
81
  this.addSystemMessage(this.getSystemMessage());
82
82
  }
83
+ /**
84
+ * List the models available to this API key, base and fine-tuned alike.
85
+ *
86
+ * Mistral is the most forthcoming of the providers: it reports a context
87
+ * window, a full capability set, and a retirement date with a replacement
88
+ * model. All of that is mapped onto the neutral fields.
89
+ *
90
+ * Note that `raw` here is the SDK's parsed view, not the wire response — the
91
+ * Mistral SDK validates against a schema that drops fields it does not know,
92
+ * so capabilities the API has added since the installed SDK version (as of
93
+ * `1.13.0`: `reasoning`, the audio flags) are gone before this code sees
94
+ * them. Every other agent's `raw` is the untouched response.
95
+ */
96
+ async listModels() {
97
+ try {
98
+ const response = await this.client.models.list();
99
+ return (response.data ?? []).map((model) => ({
100
+ id: model.id,
101
+ displayName: model.name ?? undefined,
102
+ created: model.created ? new Date(model.created * 1000) : undefined,
103
+ ownedBy: model.ownedBy,
104
+ contextLength: model.maxContextLength,
105
+ capabilities: {
106
+ chat: model.capabilities.completionChat,
107
+ tools: model.capabilities.functionCalling,
108
+ vision: model.capabilities.vision,
109
+ },
110
+ deprecatedAt: model.deprecation ?? undefined,
111
+ replacedBy: model.deprecationReplacementModel ?? undefined,
112
+ raw: model,
113
+ }));
114
+ }
115
+ catch (error) {
116
+ throw new AgentError_1.ExecutionError(`Failed to list Mistral models: ${error instanceof Error ? error.message : "Unknown error"}`);
117
+ }
118
+ }
83
119
  getToolDefinitions() {
84
120
  return Array.from(this.tools.values()).map((tool) => ({
85
121
  type: components_1.ToolTypes.Function,
@@ -1,4 +1,4 @@
1
- import { BaseAgent, BaseAgentConfig, TokenUsage } from "../BaseAgent";
1
+ import { BaseAgent, BaseAgentConfig, ModelInfo, TokenUsage } from "../BaseAgent";
2
2
  import { History, MessageContent } from "../../history/History";
3
3
  import { OllamaModel } from "../model-types";
4
4
  type AgentConfig = BaseAgentConfig & {
@@ -56,8 +56,12 @@ export declare class OllamaAgent extends BaseAgent {
56
56
  private getClient;
57
57
  /**
58
58
  * List the models currently available on the Ollama server.
59
+ *
60
+ * `id` is the tag to pass as `model` (e.g. `"llama3.2:latest"`) and `created`
61
+ * carries the local `modified_at` timestamp — Ollama reports when a model was
62
+ * last pulled or changed on this machine, not when it was released.
59
63
  */
60
- listModels(): Promise<OllamaModelInfo[]>;
64
+ listModels(): Promise<ModelInfo<OllamaModelInfo>[]>;
61
65
  protected getToolDefinitions(): OllamaToolDefinition[];
62
66
  protected process(_input: string): Promise<string>;
63
67
  execute(input: string | MessageContent[]): Promise<string>;
@@ -113,12 +113,21 @@ class OllamaAgent extends BaseAgent_1.BaseAgent {
113
113
  }
114
114
  /**
115
115
  * List the models currently available on the Ollama server.
116
+ *
117
+ * `id` is the tag to pass as `model` (e.g. `"llama3.2:latest"`) and `created`
118
+ * carries the local `modified_at` timestamp — Ollama reports when a model was
119
+ * last pulled or changed on this machine, not when it was released.
116
120
  */
117
121
  async listModels() {
118
122
  try {
119
123
  const client = await this.getClient();
120
124
  const response = await client.list();
121
- return response.models;
125
+ return response.models.map((model) => ({
126
+ id: model.model,
127
+ displayName: model.name,
128
+ created: model.modified_at ? new Date(model.modified_at) : undefined,
129
+ raw: model,
130
+ }));
122
131
  }
123
132
  catch (error) {
124
133
  throw new AgentError_1.ExecutionError(`Failed to list Ollama models: ${error instanceof Error ? error.message : "Unknown error"}`);
@@ -1,6 +1,7 @@
1
- import { BaseAgent, BaseAgentConfig, TokenUsage } from "../BaseAgent";
1
+ import { BaseAgent, BaseAgentConfig, ModelInfo, TokenUsage } from "../BaseAgent";
2
2
  import { History, MessageContent } from "../../history/History";
3
3
  import { Tool, Response, ResponseUsage } from "openai/resources/responses/responses";
4
+ import type { Model as OpenAIModelCard } from "openai/resources/models";
4
5
  import { OpenAIModel, ReasoningEffort, ReasoningEffortFor } from "../model-types";
5
6
  import { StreamChunk } from "../openai-compatible/OpenAICompatibleAgent";
6
7
  type AgentConfig<M extends OpenAIModel = OpenAIModel> = BaseAgentConfig & {
@@ -67,6 +68,14 @@ export declare class OpenAiAgent<M extends OpenAIModel = OpenAIModel> extends Ba
67
68
  /** Count of tool calls in current execution */
68
69
  private currentToolCallCount;
69
70
  constructor(config: Omit<AgentConfig<M>, "vendor">, history?: History);
71
+ /**
72
+ * List the models available to this API key.
73
+ *
74
+ * The list covers everything the key can reach — chat, embedding, audio and
75
+ * image models alike — so filter by `id` if you only want the ones this
76
+ * agent can drive.
77
+ */
78
+ listModels(): Promise<ModelInfo<OpenAIModelCard>[]>;
70
79
  protected getToolDefinitions(): Tool[];
71
80
  /**
72
81
  * Build the `reasoning` field for a Responses API request, as an object to
@@ -13,6 +13,7 @@ const transformers_1 = require("../../history/transformers");
13
13
  const VizReporter_1 = require("../../viz/VizReporter");
14
14
  const VizConfig_1 = require("../../viz/VizConfig");
15
15
  const model_types_1 = require("../model-types");
16
+ const openai_strict_1 = require("./openai-strict");
16
17
  /**
17
18
  * Lowest `reasoning.effort` the given model accepts, used to resolve
18
19
  * `disableReasoning`. Returns `undefined` when the model has no reasoning to turn
@@ -87,20 +88,44 @@ class OpenAiAgent extends BaseAgent_1.BaseAgent {
87
88
  // Add system message to history (skips if already exists with same content)
88
89
  this.addSystemMessage(this.getSystemMessage());
89
90
  }
91
+ /**
92
+ * List the models available to this API key.
93
+ *
94
+ * The list covers everything the key can reach — chat, embedding, audio and
95
+ * image models alike — so filter by `id` if you only want the ones this
96
+ * agent can drive.
97
+ */
98
+ async listModels() {
99
+ try {
100
+ const page = await this.client.models.list();
101
+ return page.data.map((model) => ({
102
+ id: model.id,
103
+ created: model.created ? new Date(model.created * 1000) : undefined,
104
+ ownedBy: model.owned_by,
105
+ raw: model,
106
+ }));
107
+ }
108
+ catch (error) {
109
+ throw new AgentError_1.ExecutionError(`Failed to list OpenAI models: ${error instanceof Error ? error.message : "Unknown error"}`);
110
+ }
111
+ }
90
112
  getToolDefinitions() {
91
113
  return Array.from(this.tools.values()).map((tool) => {
92
114
  const prompt = tool.getPrompt();
115
+ const parameters = {
116
+ type: prompt.input_schema.type,
117
+ properties: prompt.input_schema.properties,
118
+ required: prompt.input_schema.required,
119
+ additionalProperties: false,
120
+ };
93
121
  return {
94
122
  type: "function",
95
123
  name: prompt.name,
96
124
  description: prompt.description,
97
- parameters: {
98
- type: prompt.input_schema.type,
99
- properties: prompt.input_schema.properties,
100
- required: prompt.input_schema.required,
101
- additionalProperties: false,
102
- },
103
- strict: true,
125
+ parameters,
126
+ // Per tool, not unconditional: strict mode requires `required` to name
127
+ // every property, so one optional parameter would 400 the whole request
128
+ strict: (0, openai_strict_1.canUseStrictSchema)(parameters),
104
129
  };
105
130
  });
106
131
  }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * OpenAI strict function schemas.
3
+ *
4
+ * `strict: true` is a stronger contract than JSON Schema: OpenAI requires
5
+ * `required` to list *every* key in `properties`, so a tool with an optional
6
+ * parameter is rejected with a 400 before the model ever runs. Sending it
7
+ * unconditionally makes any such tool break the whole request — and since the
8
+ * tool belt is identical on every retry, every request of the session fails.
9
+ *
10
+ * The alternative fix is to declare optional parameters as nullable and require
11
+ * them anyway, per OpenAI's own guidance. In a library that is worse: it
12
+ * changes the schema every *other* provider sees (Gemini does not accept a
13
+ * `["string", "null"]` type union), and it makes each tool responsible for
14
+ * telling an explicit null from an omitted argument. Deciding `strict` per tool
15
+ * leaves the schemas untouched and keeps the guarantee for the tools that can
16
+ * already honour it.
17
+ */
18
+ /**
19
+ * Whether OpenAI will accept this parameter schema under `strict: true`.
20
+ *
21
+ * Conservative on purpose: a false negative costs the schema-adherence
22
+ * guarantee for one tool, a false positive costs the whole request. MCP tools
23
+ * come from servers the host does not control, so "unrecognised shape" has to
24
+ * mean no.
25
+ */
26
+ export declare function canUseStrictSchema(parameters: unknown): boolean;
27
+ //# sourceMappingURL=openai-strict.d.ts.map
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ /**
3
+ * OpenAI strict function schemas.
4
+ *
5
+ * `strict: true` is a stronger contract than JSON Schema: OpenAI requires
6
+ * `required` to list *every* key in `properties`, so a tool with an optional
7
+ * parameter is rejected with a 400 before the model ever runs. Sending it
8
+ * unconditionally makes any such tool break the whole request — and since the
9
+ * tool belt is identical on every retry, every request of the session fails.
10
+ *
11
+ * The alternative fix is to declare optional parameters as nullable and require
12
+ * them anyway, per OpenAI's own guidance. In a library that is worse: it
13
+ * changes the schema every *other* provider sees (Gemini does not accept a
14
+ * `["string", "null"]` type union), and it makes each tool responsible for
15
+ * telling an explicit null from an omitted argument. Deciding `strict` per tool
16
+ * leaves the schemas untouched and keeps the guarantee for the tools that can
17
+ * already honour it.
18
+ */
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.canUseStrictSchema = canUseStrictSchema;
21
+ /**
22
+ * Whether OpenAI will accept this parameter schema under `strict: true`.
23
+ *
24
+ * Conservative on purpose: a false negative costs the schema-adherence
25
+ * guarantee for one tool, a false positive costs the whole request. MCP tools
26
+ * come from servers the host does not control, so "unrecognised shape" has to
27
+ * mean no.
28
+ */
29
+ function canUseStrictSchema(parameters) {
30
+ if (!parameters || typeof parameters !== "object")
31
+ return false;
32
+ const schema = parameters;
33
+ const properties = schema.properties && typeof schema.properties === "object"
34
+ ? schema.properties
35
+ : {};
36
+ const required = Array.isArray(schema.required) ? schema.required : [];
37
+ // Strict mode also wants `additionalProperties: false` on every nested
38
+ // object, which `getToolDefinitions()` only sets at the top level. Rather
39
+ // than rewrite anyone's schema, treat a nested object as reason enough to
40
+ // drop the guarantee.
41
+ const isNested = (property) => {
42
+ if (!property || typeof property !== "object")
43
+ return false;
44
+ const value = property;
45
+ return value.type === "object" || isNested(value.items);
46
+ };
47
+ return (Object.keys(properties).every((key) => required.includes(key)) &&
48
+ !Object.values(properties).some(isNested));
49
+ }
50
+ //# sourceMappingURL=openai-strict.js.map
@@ -1,7 +1,7 @@
1
1
  import OpenAI from "openai";
2
2
  import { ChatCompletion, ChatCompletionTool } from "openai/resources/chat/completions";
3
3
  import { Model } from "openai/resources/models";
4
- import { BaseAgent, BaseAgentConfig, TokenUsage } from "../BaseAgent";
4
+ import { BaseAgent, BaseAgentConfig, ModelInfo, TokenUsage } from "../BaseAgent";
5
5
  import { AgentVendor } from "../AgentConfig";
6
6
  import { History, MessageContent } from "../../history/History";
7
7
  /**
@@ -43,8 +43,11 @@ export declare abstract class OpenAICompatibleAgent extends BaseAgent {
43
43
  protected buildExtraRequestParams(): Record<string, unknown>;
44
44
  /**
45
45
  * List the models available on the server via the `/v1/models` endpoint.
46
+ *
47
+ * Local servers vary in how much they fill in — llama.cpp reports little
48
+ * beyond the id — so most fields other than `id` are typically undefined.
46
49
  */
47
- listModels(): Promise<Model[]>;
50
+ listModels(): Promise<ModelInfo<Model>[]>;
48
51
  protected getToolDefinitions(): ChatCompletionTool[];
49
52
  protected process(_input: string): Promise<string>;
50
53
  execute(input: string | MessageContent[]): Promise<string>;
@@ -50,11 +50,19 @@ class OpenAICompatibleAgent extends BaseAgent_1.BaseAgent {
50
50
  }
51
51
  /**
52
52
  * List the models available on the server via the `/v1/models` endpoint.
53
+ *
54
+ * Local servers vary in how much they fill in — llama.cpp reports little
55
+ * beyond the id — so most fields other than `id` are typically undefined.
53
56
  */
54
57
  async listModels() {
55
58
  try {
56
59
  const page = await this.client.models.list();
57
- return page.data;
60
+ return page.data.map((model) => ({
61
+ id: model.id,
62
+ created: model.created ? new Date(model.created * 1000) : undefined,
63
+ ownedBy: model.owned_by,
64
+ raw: model,
65
+ }));
58
66
  }
59
67
  catch (error) {
60
68
  throw new AgentError_1.ExecutionError(`Failed to list ${this.getVendorName()} models: ${error instanceof Error ? error.message : "Unknown error"}`);
package/dist/gemini.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from "./core";
2
- export { GeminiAgent } from "./agents/google/GeminiAgent";
2
+ export { GeminiAgent, GEMINI_RETIRED_MODELS, } from "./agents/google/GeminiAgent";
3
+ export type { GeminiModelCard, GeminiListModelsOptions, } from "./agents/google/GeminiAgent";
3
4
  export { geminiTransformer } from "./history/transformers";
4
5
  //# sourceMappingURL=gemini.d.ts.map
package/dist/gemini.js CHANGED
@@ -14,11 +14,12 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.geminiTransformer = exports.GeminiAgent = void 0;
17
+ exports.geminiTransformer = exports.GEMINI_RETIRED_MODELS = exports.GeminiAgent = void 0;
18
18
  // Gemini Agent Entry Point
19
19
  __exportStar(require("./core"), exports);
20
20
  var GeminiAgent_1 = require("./agents/google/GeminiAgent");
21
21
  Object.defineProperty(exports, "GeminiAgent", { enumerable: true, get: function () { return GeminiAgent_1.GeminiAgent; } });
22
+ Object.defineProperty(exports, "GEMINI_RETIRED_MODELS", { enumerable: true, get: function () { return GeminiAgent_1.GEMINI_RETIRED_MODELS; } });
22
23
  var transformers_1 = require("./history/transformers");
23
24
  Object.defineProperty(exports, "geminiTransformer", { enumerable: true, get: function () { return transformers_1.geminiTransformer; } });
24
25
  //# sourceMappingURL=gemini.js.map
@@ -449,17 +449,34 @@ exports.geminiTransformer = {
449
449
  name: block.name,
450
450
  args: block.input,
451
451
  },
452
+ // Gemini 3 requires its own reasoning token back on the part it came
453
+ // from, or it rejects the follow-up request outright
454
+ ...(block.thoughtSignature
455
+ ? { thoughtSignature: block.thoughtSignature }
456
+ : {}),
452
457
  });
453
458
  }
454
459
  // Add function response parts (for user messages with tool results)
455
460
  for (const block of toolResultBlocks) {
456
- // Parse content if it's JSON, otherwise wrap in response object
461
+ // `functionResponse.response` is a protobuf Struct, so it has to be a
462
+ // JSON *object*. Parsing alone is not enough: a tool returning a plain
463
+ // string is stored as `JSON.stringify(result)`, which parses back to a
464
+ // string rather than throwing, so a bare scalar would go out and Gemini
465
+ // would answer 400 with the tool output quoted back. Anything that is
466
+ // not already an object is nested under `result` — the same shape the
467
+ // parse-failure path produces, so the model sees no difference and no
468
+ // tool has to know about any of this.
457
469
  let responseData;
458
470
  try {
459
471
  responseData = JSON.parse(block.content);
460
472
  }
461
473
  catch {
462
- responseData = { result: block.content };
474
+ responseData = block.content;
475
+ }
476
+ if (typeof responseData !== "object" ||
477
+ responseData === null ||
478
+ Array.isArray(responseData)) {
479
+ responseData = { result: responseData ?? "" };
463
480
  }
464
481
  parts.push({
465
482
  functionResponse: {
@@ -485,8 +502,14 @@ exports.geminiTransformer = {
485
502
  }
486
503
  if ("functionCall" in part && part.functionCall) {
487
504
  const fc = part.functionCall;
488
- normalizedContent.push((0, types_1.toolUse)(fc.name, // Gemini doesn't have separate IDs, use function name
489
- fc.name, (fc.args || {})));
505
+ normalizedContent.push((0, types_1.toolUse)(
506
+ // Deliberately the name, not the `id` the live response also
507
+ // carries: `toProvider` sends a tool result as
508
+ // `functionResponse.name = block.tool_use_id`, and Gemini requires
509
+ // that to be the function name. Keying the block by Gemini's id
510
+ // would desynchronise the tool_use/tool_result pair without
511
+ // buying anything — the id is never echoed back.
512
+ fc.name, fc.name, (fc.args || {}), part.thoughtSignature));
490
513
  }
491
514
  }
492
515
  return {
@@ -19,6 +19,15 @@ export type ToolUseContent = {
19
19
  id: string;
20
20
  name: string;
21
21
  input: Record<string, unknown>;
22
+ /**
23
+ * Provider-opaque reasoning token that has to be echoed back verbatim.
24
+ *
25
+ * Gemini 3 returns one beside every `functionCall` and rejects any later
26
+ * request in the conversation that omits it — "Function call is missing a
27
+ * thought_signature in functionCall parts". Nothing reads its contents; it
28
+ * only has to survive the round trip through history.
29
+ */
30
+ thoughtSignature?: string;
22
31
  };
23
32
  /**
24
33
  * Result of a tool execution
@@ -174,7 +183,7 @@ export declare function text(value: string): TextContent;
174
183
  /**
175
184
  * Create a tool use content block
176
185
  */
177
- export declare function toolUse(id: string, name: string, input: Record<string, unknown>): ToolUseContent;
186
+ export declare function toolUse(id: string, name: string, input: Record<string, unknown>, thoughtSignature?: string): ToolUseContent;
178
187
  /**
179
188
  * Create a thinking content block. Pass `redactedData` for redacted thinking.
180
189
  */
@@ -56,8 +56,16 @@ function text(value) {
56
56
  /**
57
57
  * Create a tool use content block
58
58
  */
59
- function toolUse(id, name, input) {
60
- return { type: "tool_use", id, name, input };
59
+ function toolUse(id, name, input, thoughtSignature) {
60
+ // Only set the key when there is one, so a block stored without a signature
61
+ // serializes exactly as it did before the field existed
62
+ return {
63
+ type: "tool_use",
64
+ id,
65
+ name,
66
+ input,
67
+ ...(thoughtSignature ? { thoughtSignature } : {}),
68
+ };
61
69
  }
62
70
  /**
63
71
  * Create a thinking content block. Pass `redactedData` for redacted thinking.
package/dist/index.d.ts CHANGED
@@ -2,9 +2,13 @@ export * from "./agents/BaseAgent";
2
2
  export * from "./agents/anthropic/ClaudeAgent";
3
3
  export { OpenAiAgent } from "./agents/openai/OpenAiAgent";
4
4
  export { MistralAgent } from "./agents/mistral/MistralAgent";
5
- export { GeminiAgent } from "./agents/google/GeminiAgent";
5
+ export type { MistralModelCard } from "./agents/mistral/MistralAgent";
6
+ export { GeminiAgent, GEMINI_RETIRED_MODELS, } from "./agents/google/GeminiAgent";
7
+ export type { GeminiModelCard, GeminiListModelsOptions, } from "./agents/google/GeminiAgent";
6
8
  export { OllamaAgent } from "./agents/ollama/OllamaAgent";
9
+ export type { OllamaModelInfo } from "./agents/ollama/OllamaAgent";
7
10
  export { LlamaCppAgent } from "./agents/llamacpp/LlamaCppAgent";
11
+ export type { LlamaCppModelCard, LlamaCppModelMeta, } from "./agents/llamacpp/LlamaCppAgent";
8
12
  export { OpenAICompatibleAgent } from "./agents/openai-compatible/OpenAICompatibleAgent";
9
13
  export type { OpenAICompatibleConfig, StreamChunk } from "./agents/openai-compatible/OpenAICompatibleAgent";
10
14
  export * from "./agents/model-types";
package/dist/index.js CHANGED
@@ -22,7 +22,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
22
22
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
23
23
  };
24
24
  Object.defineProperty(exports, "__esModule", { value: true });
25
- exports.chatCompletionsTransformer = exports.ollamaTransformer = exports.geminiTransformer = exports.mistralTransformer = exports.openAiTransformer = exports.anthropicTransformer = exports.OpenAICompatibleAgent = exports.LlamaCppAgent = exports.OllamaAgent = exports.GeminiAgent = exports.MistralAgent = exports.OpenAiAgent = void 0;
25
+ exports.chatCompletionsTransformer = exports.ollamaTransformer = exports.geminiTransformer = exports.mistralTransformer = exports.openAiTransformer = exports.anthropicTransformer = exports.OpenAICompatibleAgent = exports.LlamaCppAgent = exports.OllamaAgent = exports.GEMINI_RETIRED_MODELS = exports.GeminiAgent = exports.MistralAgent = exports.OpenAiAgent = void 0;
26
26
  // Agents
27
27
  __exportStar(require("./agents/BaseAgent"), exports);
28
28
  __exportStar(require("./agents/anthropic/ClaudeAgent"), exports);
@@ -32,6 +32,7 @@ var MistralAgent_1 = require("./agents/mistral/MistralAgent");
32
32
  Object.defineProperty(exports, "MistralAgent", { enumerable: true, get: function () { return MistralAgent_1.MistralAgent; } });
33
33
  var GeminiAgent_1 = require("./agents/google/GeminiAgent");
34
34
  Object.defineProperty(exports, "GeminiAgent", { enumerable: true, get: function () { return GeminiAgent_1.GeminiAgent; } });
35
+ Object.defineProperty(exports, "GEMINI_RETIRED_MODELS", { enumerable: true, get: function () { return GeminiAgent_1.GEMINI_RETIRED_MODELS; } });
35
36
  var OllamaAgent_1 = require("./agents/ollama/OllamaAgent");
36
37
  Object.defineProperty(exports, "OllamaAgent", { enumerable: true, get: function () { return OllamaAgent_1.OllamaAgent; } });
37
38
  var LlamaCppAgent_1 = require("./agents/llamacpp/LlamaCppAgent");
@@ -1,5 +1,6 @@
1
1
  export * from "./core";
2
2
  export { LlamaCppAgent } from "./agents/llamacpp/LlamaCppAgent";
3
+ export type { LlamaCppModelCard, LlamaCppModelMeta, } from "./agents/llamacpp/LlamaCppAgent";
3
4
  export { OpenAICompatibleAgent } from "./agents/openai-compatible/OpenAICompatibleAgent";
4
5
  export type { OpenAICompatibleConfig, StreamChunk } from "./agents/openai-compatible/OpenAICompatibleAgent";
5
6
  export { chatCompletionsTransformer } from "./history/transformers";
package/dist/mistral.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from "./core";
2
2
  export { MistralAgent } from "./agents/mistral/MistralAgent";
3
+ export type { MistralModelCard } from "./agents/mistral/MistralAgent";
3
4
  export { mistralTransformer } from "./history/transformers";
4
5
  //# sourceMappingURL=mistral.d.ts.map
package/dist/ollama.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from "./core";
2
2
  export { OllamaAgent } from "./agents/ollama/OllamaAgent";
3
+ export type { OllamaModelInfo } from "./agents/ollama/OllamaAgent";
3
4
  export { ollamaTransformer } from "./history/transformers";
4
5
  //# sourceMappingURL=ollama.d.ts.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@agentionai/agents",
3
3
  "author": "Laurent Zuijdwijk",
4
- "version": "1.4.0",
4
+ "version": "1.6.0-beta.0",
5
5
  "description": "Agent Library",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",