tina4-nodejs 3.13.113 → 3.13.115

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.
@@ -24,7 +24,10 @@ export interface ChatResponse {
24
24
  /**
25
25
  * A multimodal content part. `text` carries plain UTF-8 prose; `image`
26
26
  * carries a `data:<media_type>;base64,<payload>` URI or an https:// URL
27
- * (the client translates to each provider's shape). ADR-0060.
27
+ * (the client translates to each provider's shape, ADR-0060). `tool_result`
28
+ * carries the Anthropic-style return of a locally-executed tool call
29
+ * (ADR-0061); the client translates it to OpenAI's `{role: "tool", ...}`
30
+ * turn on non-Anthropic providers.
28
31
  */
29
32
  export type ContentPart = {
30
33
  type: "text";
@@ -32,13 +35,50 @@ export type ContentPart = {
32
35
  } | {
33
36
  type: "image";
34
37
  source: string;
38
+ } | {
39
+ type: "tool_result";
40
+ tool_use_id: string;
41
+ content: string;
35
42
  };
36
43
  /** The value a caller may pass for `message.content`. ADR-0060. */
37
44
  export type AiMessageContent = string | ContentPart[];
38
- export interface AiMessage {
45
+ /**
46
+ * One conversation turn. The three "chat" roles carry a string OR a
47
+ * content-parts array (ADR-0060). The `tool` role is the OpenAI-style
48
+ * return of a tool call (ADR-0061); the client translates it to the
49
+ * Anthropic user-turn form when the current provider is Anthropic.
50
+ */
51
+ export type AiMessage = {
39
52
  role: "system" | "user" | "assistant";
40
53
  content: AiMessageContent;
54
+ } | {
55
+ role: "tool";
56
+ tool_call_id: string;
57
+ content: string;
58
+ };
59
+ /**
60
+ * A tool declaration the model may call (named `AiToolDeclaration` to
61
+ * stay out of the way of {@link ./ai.ts}'s existing `AiTool` interface
62
+ * for AI-coding-tool context installation). `parameters` is a JSON
63
+ * Schema object; it is passed to the provider unchanged (ADR-0061
64
+ * `parameters-passthrough`).
65
+ */
66
+ export interface AiToolDeclaration {
67
+ name: string;
68
+ description: string;
69
+ parameters: Record<string, unknown>;
41
70
  }
71
+ /**
72
+ * How the model picks a tool. Four Tina4 values that span the useful cases
73
+ * across providers (ADR-0061 wire-translation table):
74
+ * 'auto' — model may call any tool or answer with text
75
+ * 'none' — model must not call a tool (Anthropic omits `tools`)
76
+ * 'required' — model must call some tool
77
+ * {name: 'x'} — model must call tool 'x'
78
+ */
79
+ export type AiToolChoice = "auto" | "none" | "required" | {
80
+ name: string;
81
+ };
42
82
  /**
43
83
  * One event yielded by `Ai.chat(stream: true)`. The four variants
44
84
  * discriminated by `type`. Text deltas arrive per chunk (typewriter UX);
@@ -74,6 +114,14 @@ export interface AiChatOptions {
74
114
  stream?: boolean;
75
115
  timeout?: number;
76
116
  provider?: "local" | "openai" | "anthropic";
117
+ /** Tools the model may call. ADR-0061 — translated per provider. */
118
+ tools?: AiToolDeclaration[];
119
+ /**
120
+ * How the model picks a tool. ADR-0061 — translated per provider. If
121
+ * `'none'` on Anthropic (which has no "none" mode), `tools` is omitted
122
+ * from the outbound body entirely.
123
+ */
124
+ toolChoice?: AiToolChoice;
77
125
  }
78
126
  export interface AiEmbedOptions {
79
127
  model?: string;
@@ -91,24 +139,60 @@ export declare class Ai {
91
139
  static embed(textOrTexts: string | string[], options?: AiEmbedOptions): Promise<number[] | number[][]>;
92
140
  /**
93
141
  * Validate role + content shape. Content may be a string OR a non-empty
94
- * list of {type:'text'|'image', ...} parts (ADR-0060). Malformed parts
95
- * fail fast with AiConfigError, never reaching the wire.
142
+ * list of {type:'text'|'image'|'tool_result', ...} parts (ADR-0060 +
143
+ * ADR-0061). The `tool` role is the OpenAI-style tool-result turn
144
+ * (ADR-0061). Malformed parts fail fast with AiConfigError, never
145
+ * reaching the wire.
96
146
  */
97
147
  private static validateMessages;
98
148
  private static validateContent;
149
+ /**
150
+ * Validate the outbound tool declarations (ADR-0061). Each tool needs a
151
+ * non-empty `name`, a string `description`, and a JSON-Schema-shaped
152
+ * `parameters` object. Malformed tools fail fast with AiConfigError,
153
+ * never reaching the wire.
154
+ */
155
+ private static validateTools;
156
+ /**
157
+ * Validate the outbound tool_choice value (ADR-0061). The four accepted
158
+ * shapes are 'auto', 'none', 'required', and {name: 'x'}.
159
+ */
160
+ private static validateToolChoice;
99
161
  private static number;
100
162
  private static config;
101
163
  private static endpoint;
102
164
  private static headers;
103
165
  /**
104
166
  * Build the provider-specific request body from a Tina4-shaped message
105
- * list. Multimodal parts are translated per provider (ADR-0060):
106
- * - OpenAI/local: {type:'image_url', image_url:{url}}
107
- * - Anthropic: {type:'image', source:{type:'base64'|'url', ...}}
167
+ * list plus optional tool declarations (ADR-0060 + ADR-0061).
168
+ *
169
+ * Content parts translate per provider:
170
+ * - OpenAI/local: image → {type:'image_url', image_url:{url}}
171
+ * - Anthropic: image → {type:'image', source:{type:'base64'|'url', ...}}
108
172
  * String content is preserved verbatim in the OpenAI/local shape and
109
173
  * likewise for Anthropic (both accept a bare string).
174
+ *
175
+ * Tool-result turns are normalised to the current provider's expected
176
+ * shape (either the OpenAI `{role:"tool", tool_call_id, content}` turn or
177
+ * the Anthropic `{role:"user", content:[{type:"tool_result", ...}]}`
178
+ * turn), so an agent-loop written against Tina4 never has to fork on
179
+ * TINA4_AI_PROVIDER (ADR-0061 wire translation).
110
180
  */
111
181
  private static chatBody;
182
+ /**
183
+ * Normalise the Tina4-shaped messages into the provider's on-wire shape.
184
+ * The `tool` role and the `tool_result` content part are translated
185
+ * between the OpenAI and Anthropic forms so either input works against
186
+ * either provider (ADR-0061 return-path table).
187
+ */
188
+ private static normalizeMessagesForProvider;
189
+ /**
190
+ * Attach the outbound `tools` and `tool_choice` (ADR-0061 outbound
191
+ * translation tables) to the body in place. When toolChoice is 'none'
192
+ * on Anthropic (Anthropic has no "none" mode) the tools list is omitted
193
+ * entirely — the model cannot call what it cannot see.
194
+ */
195
+ private static applyTools;
112
196
  /**
113
197
  * Translate one message content value into the provider's on-wire shape.
114
198
  * A plain string is passed through (both providers accept a string
@@ -58,7 +58,7 @@ export type { AiTool } from "./ai.js";
58
58
  export { Sso, SSO, SsoError } from "./sso.js";
59
59
  export type { SsoOptions } from "./sso.js";
60
60
  export { Ai, AiError, AiConfigError, AiHTTPError, AiTimeoutError, AiParseError } from "./aiClient.js";
61
- export type { ChatResponse, AiMessage, AiChatOptions, AiEmbedOptions, AiEvent, ContentPart, AiMessageContent } from "./aiClient.js";
61
+ export type { ChatResponse, AiMessage, AiChatOptions, AiEmbedOptions, AiEvent, ContentPart, AiMessageContent, AiToolDeclaration, AiToolChoice } from "./aiClient.js";
62
62
  export type { ImapMessage, ImapFullMessage, ImapAttachment } from "./messenger.js";
63
63
  export { LiteBackend } from "./queueBackends/liteBackend.js";
64
64
  export { RabbitMQBackend, parseAmqpUrl } from "./queueBackends/rabbitmqBackend.js";
@@ -1,5 +1,5 @@
1
1
  import { IncomingMessage, ServerResponse } from "node:http";
2
- import type { Tina4Config } from "./types.js";
2
+ import type { Tina4Config, Tina4Request, Tina4Response } from "./types.js";
3
3
  import { Router } from "./router.js";
4
4
  import { MiddlewareChain } from "./middleware.js";
5
5
  /** How long a graceful shutdown waits for in-flight requests, in seconds. */
@@ -167,6 +167,28 @@ export declare function stop(): void;
167
167
  * Useful for testing and embedding.
168
168
  */
169
169
  export declare function handle(rawReq: IncomingMessage, rawRes: ServerResponse): Promise<void>;
170
+ /**
171
+ * Invoke a matched route's handler, binding path params BY NAME.
172
+ *
173
+ * A handler declares whatever it needs - `(id, request, response)`, `(req, res)`,
174
+ * or nothing - and each parameter is resolved by its name: a path param wins,
175
+ * then `request`/`req`, then the response.
176
+ */
177
+ /**
178
+ * Resolve a handler's argument list to `[reqOrParam, resOrParam, ...]` values.
179
+ *
180
+ * By-name path preserved (route-param names, `req`/`request`, `res`/`response`)
181
+ * so existing code keeps its DX. Any remaining unmatched name falls back to
182
+ * POSITIONAL binding: first unmatched -> request, rest -> response. That makes
183
+ * dispatch bundler-safe by construction: Bun `--compile` and terser/esbuild
184
+ * identifier-mangling rename `(req, res)` to `(req2, r$0)` — the by-name path
185
+ * misses, the positional fallback catches, and the handler still receives the
186
+ * request as its first argument. Fixes #56.
187
+ *
188
+ * Exported so the regression test can pin the behaviour directly, without a
189
+ * live HTTP server.
190
+ */
191
+ export declare function resolveHandlerArgs(handler: unknown, req: Tina4Request, res: Tina4Response, routeParams: Record<string, unknown>): unknown[];
170
192
  /**
171
193
  * Everything one request's dispatch needs beyond req/res: the resolved
172
194
  * router, middleware chain, filesystem roots and template engine a boot